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 num_of_whitespaces_rev = snapshot
4048 .reversed_chars_for_range(range.clone())
4049 .take_while(|c| c.is_whitespace())
4050 .count();
4051 let mut line_iter = snapshot
4052 .reversed_chars_for_range(range)
4053 .skip(num_of_whitespaces_rev);
4054 let end_tag_exists = end_tag
4055 .chars()
4056 .rev()
4057 .all(|char| line_iter.next() == Some(char));
4058 if end_tag_exists {
4059 let max_point = snapshot.line_len(start_point.row) as usize;
4060 let ordering = (num_of_whitespaces_rev
4061 + end_tag.len()
4062 + start_point.column as usize)
4063 .cmp(&max_point);
4064 let cursor_is_before_end_tag =
4065 ordering != Ordering::Greater;
4066 if cursor_is_after_start_tag {
4067 if cursor_is_before_end_tag {
4068 insert_extra_newline = true;
4069 }
4070 let cursor_is_at_start_of_end_tag =
4071 ordering == Ordering::Equal;
4072 if cursor_is_at_start_of_end_tag {
4073 indent_on_extra_newline.len = (*len).into();
4074 }
4075 }
4076 cursor_is_before_end_tag
4077 } else {
4078 true
4079 }
4080 };
4081
4082 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4083 && cursor_is_before_end_tag_if_exists
4084 {
4085 if cursor_is_after_start_tag {
4086 indent_on_newline.len = (*len).into();
4087 }
4088 Some(delimiter.clone())
4089 } else {
4090 None
4091 }
4092 });
4093
4094 (
4095 comment_delimiter,
4096 doc_delimiter,
4097 insert_extra_newline,
4098 indent_on_newline,
4099 indent_on_extra_newline,
4100 )
4101 } else {
4102 (
4103 None,
4104 None,
4105 false,
4106 IndentSize::default(),
4107 IndentSize::default(),
4108 )
4109 };
4110
4111 let prevent_auto_indent = doc_delimiter.is_some();
4112 let delimiter = comment_delimiter.or(doc_delimiter);
4113
4114 let capacity_for_delimiter =
4115 delimiter.as_deref().map(str::len).unwrap_or_default();
4116 let mut new_text = String::with_capacity(
4117 1 + capacity_for_delimiter
4118 + existing_indent.len as usize
4119 + indent_on_newline.len as usize
4120 + indent_on_extra_newline.len as usize,
4121 );
4122 new_text.push('\n');
4123 new_text.extend(existing_indent.chars());
4124 new_text.extend(indent_on_newline.chars());
4125
4126 if let Some(delimiter) = &delimiter {
4127 new_text.push_str(delimiter);
4128 }
4129
4130 if insert_extra_newline {
4131 new_text.push('\n');
4132 new_text.extend(existing_indent.chars());
4133 new_text.extend(indent_on_extra_newline.chars());
4134 }
4135
4136 let anchor = buffer.anchor_after(end);
4137 let new_selection = selection.map(|_| anchor);
4138 (
4139 ((start..end, new_text), prevent_auto_indent),
4140 (insert_extra_newline, new_selection),
4141 )
4142 })
4143 .unzip()
4144 };
4145
4146 let mut auto_indent_edits = Vec::new();
4147 let mut edits = Vec::new();
4148 for (edit, prevent_auto_indent) in edits_with_flags {
4149 if prevent_auto_indent {
4150 edits.push(edit);
4151 } else {
4152 auto_indent_edits.push(edit);
4153 }
4154 }
4155 if !edits.is_empty() {
4156 this.edit(edits, cx);
4157 }
4158 if !auto_indent_edits.is_empty() {
4159 this.edit_with_autoindent(auto_indent_edits, cx);
4160 }
4161
4162 let buffer = this.buffer.read(cx).snapshot(cx);
4163 let new_selections = selection_info
4164 .into_iter()
4165 .map(|(extra_newline_inserted, new_selection)| {
4166 let mut cursor = new_selection.end.to_point(&buffer);
4167 if extra_newline_inserted {
4168 cursor.row -= 1;
4169 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4170 }
4171 new_selection.map(|_| cursor)
4172 })
4173 .collect();
4174
4175 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4176 s.select(new_selections)
4177 });
4178 this.refresh_inline_completion(true, false, window, cx);
4179 });
4180 }
4181
4182 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4183 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4184
4185 let buffer = self.buffer.read(cx);
4186 let snapshot = buffer.snapshot(cx);
4187
4188 let mut edits = Vec::new();
4189 let mut rows = Vec::new();
4190
4191 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4192 let cursor = selection.head();
4193 let row = cursor.row;
4194
4195 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4196
4197 let newline = "\n".to_string();
4198 edits.push((start_of_line..start_of_line, newline));
4199
4200 rows.push(row + rows_inserted as u32);
4201 }
4202
4203 self.transact(window, cx, |editor, window, cx| {
4204 editor.edit(edits, cx);
4205
4206 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4207 let mut index = 0;
4208 s.move_cursors_with(|map, _, _| {
4209 let row = rows[index];
4210 index += 1;
4211
4212 let point = Point::new(row, 0);
4213 let boundary = map.next_line_boundary(point).1;
4214 let clipped = map.clip_point(boundary, Bias::Left);
4215
4216 (clipped, SelectionGoal::None)
4217 });
4218 });
4219
4220 let mut indent_edits = Vec::new();
4221 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4222 for row in rows {
4223 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4224 for (row, indent) in indents {
4225 if indent.len == 0 {
4226 continue;
4227 }
4228
4229 let text = match indent.kind {
4230 IndentKind::Space => " ".repeat(indent.len as usize),
4231 IndentKind::Tab => "\t".repeat(indent.len as usize),
4232 };
4233 let point = Point::new(row.0, 0);
4234 indent_edits.push((point..point, text));
4235 }
4236 }
4237 editor.edit(indent_edits, cx);
4238 });
4239 }
4240
4241 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4242 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4243
4244 let buffer = self.buffer.read(cx);
4245 let snapshot = buffer.snapshot(cx);
4246
4247 let mut edits = Vec::new();
4248 let mut rows = Vec::new();
4249 let mut rows_inserted = 0;
4250
4251 for selection in self.selections.all_adjusted(cx) {
4252 let cursor = selection.head();
4253 let row = cursor.row;
4254
4255 let point = Point::new(row + 1, 0);
4256 let start_of_line = snapshot.clip_point(point, Bias::Left);
4257
4258 let newline = "\n".to_string();
4259 edits.push((start_of_line..start_of_line, newline));
4260
4261 rows_inserted += 1;
4262 rows.push(row + rows_inserted);
4263 }
4264
4265 self.transact(window, cx, |editor, window, cx| {
4266 editor.edit(edits, cx);
4267
4268 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4269 let mut index = 0;
4270 s.move_cursors_with(|map, _, _| {
4271 let row = rows[index];
4272 index += 1;
4273
4274 let point = Point::new(row, 0);
4275 let boundary = map.next_line_boundary(point).1;
4276 let clipped = map.clip_point(boundary, Bias::Left);
4277
4278 (clipped, SelectionGoal::None)
4279 });
4280 });
4281
4282 let mut indent_edits = Vec::new();
4283 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4284 for row in rows {
4285 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4286 for (row, indent) in indents {
4287 if indent.len == 0 {
4288 continue;
4289 }
4290
4291 let text = match indent.kind {
4292 IndentKind::Space => " ".repeat(indent.len as usize),
4293 IndentKind::Tab => "\t".repeat(indent.len as usize),
4294 };
4295 let point = Point::new(row.0, 0);
4296 indent_edits.push((point..point, text));
4297 }
4298 }
4299 editor.edit(indent_edits, cx);
4300 });
4301 }
4302
4303 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4304 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4305 original_indent_columns: Vec::new(),
4306 });
4307 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4308 }
4309
4310 fn insert_with_autoindent_mode(
4311 &mut self,
4312 text: &str,
4313 autoindent_mode: Option<AutoindentMode>,
4314 window: &mut Window,
4315 cx: &mut Context<Self>,
4316 ) {
4317 if self.read_only(cx) {
4318 return;
4319 }
4320
4321 let text: Arc<str> = text.into();
4322 self.transact(window, cx, |this, window, cx| {
4323 let old_selections = this.selections.all_adjusted(cx);
4324 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4325 let anchors = {
4326 let snapshot = buffer.read(cx);
4327 old_selections
4328 .iter()
4329 .map(|s| {
4330 let anchor = snapshot.anchor_after(s.head());
4331 s.map(|_| anchor)
4332 })
4333 .collect::<Vec<_>>()
4334 };
4335 buffer.edit(
4336 old_selections
4337 .iter()
4338 .map(|s| (s.start..s.end, text.clone())),
4339 autoindent_mode,
4340 cx,
4341 );
4342 anchors
4343 });
4344
4345 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4346 s.select_anchors(selection_anchors);
4347 });
4348
4349 cx.notify();
4350 });
4351 }
4352
4353 fn trigger_completion_on_input(
4354 &mut self,
4355 text: &str,
4356 trigger_in_words: bool,
4357 window: &mut Window,
4358 cx: &mut Context<Self>,
4359 ) {
4360 let ignore_completion_provider = self
4361 .context_menu
4362 .borrow()
4363 .as_ref()
4364 .map(|menu| match menu {
4365 CodeContextMenu::Completions(completions_menu) => {
4366 completions_menu.ignore_completion_provider
4367 }
4368 CodeContextMenu::CodeActions(_) => false,
4369 })
4370 .unwrap_or(false);
4371
4372 if ignore_completion_provider {
4373 self.show_word_completions(&ShowWordCompletions, window, cx);
4374 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
4375 self.show_completions(
4376 &ShowCompletions {
4377 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4378 },
4379 window,
4380 cx,
4381 );
4382 } else {
4383 self.hide_context_menu(window, cx);
4384 }
4385 }
4386
4387 fn is_completion_trigger(
4388 &self,
4389 text: &str,
4390 trigger_in_words: bool,
4391 cx: &mut Context<Self>,
4392 ) -> bool {
4393 let position = self.selections.newest_anchor().head();
4394 let multibuffer = self.buffer.read(cx);
4395 let Some(buffer) = position
4396 .buffer_id
4397 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4398 else {
4399 return false;
4400 };
4401
4402 if let Some(completion_provider) = &self.completion_provider {
4403 completion_provider.is_completion_trigger(
4404 &buffer,
4405 position.text_anchor,
4406 text,
4407 trigger_in_words,
4408 cx,
4409 )
4410 } else {
4411 false
4412 }
4413 }
4414
4415 /// If any empty selections is touching the start of its innermost containing autoclose
4416 /// region, expand it to select the brackets.
4417 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4418 let selections = self.selections.all::<usize>(cx);
4419 let buffer = self.buffer.read(cx).read(cx);
4420 let new_selections = self
4421 .selections_with_autoclose_regions(selections, &buffer)
4422 .map(|(mut selection, region)| {
4423 if !selection.is_empty() {
4424 return selection;
4425 }
4426
4427 if let Some(region) = region {
4428 let mut range = region.range.to_offset(&buffer);
4429 if selection.start == range.start && range.start >= region.pair.start.len() {
4430 range.start -= region.pair.start.len();
4431 if buffer.contains_str_at(range.start, ®ion.pair.start)
4432 && buffer.contains_str_at(range.end, ®ion.pair.end)
4433 {
4434 range.end += region.pair.end.len();
4435 selection.start = range.start;
4436 selection.end = range.end;
4437
4438 return selection;
4439 }
4440 }
4441 }
4442
4443 let always_treat_brackets_as_autoclosed = buffer
4444 .language_settings_at(selection.start, cx)
4445 .always_treat_brackets_as_autoclosed;
4446
4447 if !always_treat_brackets_as_autoclosed {
4448 return selection;
4449 }
4450
4451 if let Some(scope) = buffer.language_scope_at(selection.start) {
4452 for (pair, enabled) in scope.brackets() {
4453 if !enabled || !pair.close {
4454 continue;
4455 }
4456
4457 if buffer.contains_str_at(selection.start, &pair.end) {
4458 let pair_start_len = pair.start.len();
4459 if buffer.contains_str_at(
4460 selection.start.saturating_sub(pair_start_len),
4461 &pair.start,
4462 ) {
4463 selection.start -= pair_start_len;
4464 selection.end += pair.end.len();
4465
4466 return selection;
4467 }
4468 }
4469 }
4470 }
4471
4472 selection
4473 })
4474 .collect();
4475
4476 drop(buffer);
4477 self.change_selections(None, window, cx, |selections| {
4478 selections.select(new_selections)
4479 });
4480 }
4481
4482 /// Iterate the given selections, and for each one, find the smallest surrounding
4483 /// autoclose region. This uses the ordering of the selections and the autoclose
4484 /// regions to avoid repeated comparisons.
4485 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4486 &'a self,
4487 selections: impl IntoIterator<Item = Selection<D>>,
4488 buffer: &'a MultiBufferSnapshot,
4489 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4490 let mut i = 0;
4491 let mut regions = self.autoclose_regions.as_slice();
4492 selections.into_iter().map(move |selection| {
4493 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4494
4495 let mut enclosing = None;
4496 while let Some(pair_state) = regions.get(i) {
4497 if pair_state.range.end.to_offset(buffer) < range.start {
4498 regions = ®ions[i + 1..];
4499 i = 0;
4500 } else if pair_state.range.start.to_offset(buffer) > range.end {
4501 break;
4502 } else {
4503 if pair_state.selection_id == selection.id {
4504 enclosing = Some(pair_state);
4505 }
4506 i += 1;
4507 }
4508 }
4509
4510 (selection, enclosing)
4511 })
4512 }
4513
4514 /// Remove any autoclose regions that no longer contain their selection.
4515 fn invalidate_autoclose_regions(
4516 &mut self,
4517 mut selections: &[Selection<Anchor>],
4518 buffer: &MultiBufferSnapshot,
4519 ) {
4520 self.autoclose_regions.retain(|state| {
4521 let mut i = 0;
4522 while let Some(selection) = selections.get(i) {
4523 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4524 selections = &selections[1..];
4525 continue;
4526 }
4527 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4528 break;
4529 }
4530 if selection.id == state.selection_id {
4531 return true;
4532 } else {
4533 i += 1;
4534 }
4535 }
4536 false
4537 });
4538 }
4539
4540 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4541 let offset = position.to_offset(buffer);
4542 let (word_range, kind) = buffer.surrounding_word(offset, true);
4543 if offset > word_range.start && kind == Some(CharKind::Word) {
4544 Some(
4545 buffer
4546 .text_for_range(word_range.start..offset)
4547 .collect::<String>(),
4548 )
4549 } else {
4550 None
4551 }
4552 }
4553
4554 pub fn toggle_inline_values(
4555 &mut self,
4556 _: &ToggleInlineValues,
4557 _: &mut Window,
4558 cx: &mut Context<Self>,
4559 ) {
4560 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4561
4562 self.refresh_inline_values(cx);
4563 }
4564
4565 pub fn toggle_inlay_hints(
4566 &mut self,
4567 _: &ToggleInlayHints,
4568 _: &mut Window,
4569 cx: &mut Context<Self>,
4570 ) {
4571 self.refresh_inlay_hints(
4572 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4573 cx,
4574 );
4575 }
4576
4577 pub fn inlay_hints_enabled(&self) -> bool {
4578 self.inlay_hint_cache.enabled
4579 }
4580
4581 pub fn inline_values_enabled(&self) -> bool {
4582 self.inline_value_cache.enabled
4583 }
4584
4585 #[cfg(any(test, feature = "test-support"))]
4586 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4587 self.display_map
4588 .read(cx)
4589 .current_inlays()
4590 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4591 .cloned()
4592 .collect()
4593 }
4594
4595 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4596 if self.semantics_provider.is_none() || !self.mode.is_full() {
4597 return;
4598 }
4599
4600 let reason_description = reason.description();
4601 let ignore_debounce = matches!(
4602 reason,
4603 InlayHintRefreshReason::SettingsChange(_)
4604 | InlayHintRefreshReason::Toggle(_)
4605 | InlayHintRefreshReason::ExcerptsRemoved(_)
4606 | InlayHintRefreshReason::ModifiersChanged(_)
4607 );
4608 let (invalidate_cache, required_languages) = match reason {
4609 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4610 match self.inlay_hint_cache.modifiers_override(enabled) {
4611 Some(enabled) => {
4612 if enabled {
4613 (InvalidationStrategy::RefreshRequested, None)
4614 } else {
4615 self.splice_inlays(
4616 &self
4617 .visible_inlay_hints(cx)
4618 .iter()
4619 .map(|inlay| inlay.id)
4620 .collect::<Vec<InlayId>>(),
4621 Vec::new(),
4622 cx,
4623 );
4624 return;
4625 }
4626 }
4627 None => return,
4628 }
4629 }
4630 InlayHintRefreshReason::Toggle(enabled) => {
4631 if self.inlay_hint_cache.toggle(enabled) {
4632 if enabled {
4633 (InvalidationStrategy::RefreshRequested, None)
4634 } else {
4635 self.splice_inlays(
4636 &self
4637 .visible_inlay_hints(cx)
4638 .iter()
4639 .map(|inlay| inlay.id)
4640 .collect::<Vec<InlayId>>(),
4641 Vec::new(),
4642 cx,
4643 );
4644 return;
4645 }
4646 } else {
4647 return;
4648 }
4649 }
4650 InlayHintRefreshReason::SettingsChange(new_settings) => {
4651 match self.inlay_hint_cache.update_settings(
4652 &self.buffer,
4653 new_settings,
4654 self.visible_inlay_hints(cx),
4655 cx,
4656 ) {
4657 ControlFlow::Break(Some(InlaySplice {
4658 to_remove,
4659 to_insert,
4660 })) => {
4661 self.splice_inlays(&to_remove, to_insert, cx);
4662 return;
4663 }
4664 ControlFlow::Break(None) => return,
4665 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4666 }
4667 }
4668 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4669 if let Some(InlaySplice {
4670 to_remove,
4671 to_insert,
4672 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4673 {
4674 self.splice_inlays(&to_remove, to_insert, cx);
4675 }
4676 self.display_map.update(cx, |display_map, _| {
4677 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4678 });
4679 return;
4680 }
4681 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4682 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4683 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4684 }
4685 InlayHintRefreshReason::RefreshRequested => {
4686 (InvalidationStrategy::RefreshRequested, None)
4687 }
4688 };
4689
4690 if let Some(InlaySplice {
4691 to_remove,
4692 to_insert,
4693 }) = self.inlay_hint_cache.spawn_hint_refresh(
4694 reason_description,
4695 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4696 invalidate_cache,
4697 ignore_debounce,
4698 cx,
4699 ) {
4700 self.splice_inlays(&to_remove, to_insert, cx);
4701 }
4702 }
4703
4704 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4705 self.display_map
4706 .read(cx)
4707 .current_inlays()
4708 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4709 .cloned()
4710 .collect()
4711 }
4712
4713 pub fn excerpts_for_inlay_hints_query(
4714 &self,
4715 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4716 cx: &mut Context<Editor>,
4717 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4718 let Some(project) = self.project.as_ref() else {
4719 return HashMap::default();
4720 };
4721 let project = project.read(cx);
4722 let multi_buffer = self.buffer().read(cx);
4723 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4724 let multi_buffer_visible_start = self
4725 .scroll_manager
4726 .anchor()
4727 .anchor
4728 .to_point(&multi_buffer_snapshot);
4729 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4730 multi_buffer_visible_start
4731 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4732 Bias::Left,
4733 );
4734 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4735 multi_buffer_snapshot
4736 .range_to_buffer_ranges(multi_buffer_visible_range)
4737 .into_iter()
4738 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4739 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4740 let buffer_file = project::File::from_dyn(buffer.file())?;
4741 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4742 let worktree_entry = buffer_worktree
4743 .read(cx)
4744 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4745 if worktree_entry.is_ignored {
4746 return None;
4747 }
4748
4749 let language = buffer.language()?;
4750 if let Some(restrict_to_languages) = restrict_to_languages {
4751 if !restrict_to_languages.contains(language) {
4752 return None;
4753 }
4754 }
4755 Some((
4756 excerpt_id,
4757 (
4758 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4759 buffer.version().clone(),
4760 excerpt_visible_range,
4761 ),
4762 ))
4763 })
4764 .collect()
4765 }
4766
4767 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4768 TextLayoutDetails {
4769 text_system: window.text_system().clone(),
4770 editor_style: self.style.clone().unwrap(),
4771 rem_size: window.rem_size(),
4772 scroll_anchor: self.scroll_manager.anchor(),
4773 visible_rows: self.visible_line_count(),
4774 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4775 }
4776 }
4777
4778 pub fn splice_inlays(
4779 &self,
4780 to_remove: &[InlayId],
4781 to_insert: Vec<Inlay>,
4782 cx: &mut Context<Self>,
4783 ) {
4784 self.display_map.update(cx, |display_map, cx| {
4785 display_map.splice_inlays(to_remove, to_insert, cx)
4786 });
4787 cx.notify();
4788 }
4789
4790 fn trigger_on_type_formatting(
4791 &self,
4792 input: String,
4793 window: &mut Window,
4794 cx: &mut Context<Self>,
4795 ) -> Option<Task<Result<()>>> {
4796 if input.len() != 1 {
4797 return None;
4798 }
4799
4800 let project = self.project.as_ref()?;
4801 let position = self.selections.newest_anchor().head();
4802 let (buffer, buffer_position) = self
4803 .buffer
4804 .read(cx)
4805 .text_anchor_for_position(position, cx)?;
4806
4807 let settings = language_settings::language_settings(
4808 buffer
4809 .read(cx)
4810 .language_at(buffer_position)
4811 .map(|l| l.name()),
4812 buffer.read(cx).file(),
4813 cx,
4814 );
4815 if !settings.use_on_type_format {
4816 return None;
4817 }
4818
4819 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4820 // hence we do LSP request & edit on host side only — add formats to host's history.
4821 let push_to_lsp_host_history = true;
4822 // If this is not the host, append its history with new edits.
4823 let push_to_client_history = project.read(cx).is_via_collab();
4824
4825 let on_type_formatting = project.update(cx, |project, cx| {
4826 project.on_type_format(
4827 buffer.clone(),
4828 buffer_position,
4829 input,
4830 push_to_lsp_host_history,
4831 cx,
4832 )
4833 });
4834 Some(cx.spawn_in(window, async move |editor, cx| {
4835 if let Some(transaction) = on_type_formatting.await? {
4836 if push_to_client_history {
4837 buffer
4838 .update(cx, |buffer, _| {
4839 buffer.push_transaction(transaction, Instant::now());
4840 buffer.finalize_last_transaction();
4841 })
4842 .ok();
4843 }
4844 editor.update(cx, |editor, cx| {
4845 editor.refresh_document_highlights(cx);
4846 })?;
4847 }
4848 Ok(())
4849 }))
4850 }
4851
4852 pub fn show_word_completions(
4853 &mut self,
4854 _: &ShowWordCompletions,
4855 window: &mut Window,
4856 cx: &mut Context<Self>,
4857 ) {
4858 self.open_completions_menu(true, None, window, cx);
4859 }
4860
4861 pub fn show_completions(
4862 &mut self,
4863 options: &ShowCompletions,
4864 window: &mut Window,
4865 cx: &mut Context<Self>,
4866 ) {
4867 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4868 }
4869
4870 fn open_completions_menu(
4871 &mut self,
4872 ignore_completion_provider: bool,
4873 trigger: Option<&str>,
4874 window: &mut Window,
4875 cx: &mut Context<Self>,
4876 ) {
4877 if self.pending_rename.is_some() {
4878 return;
4879 }
4880 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4881 return;
4882 }
4883
4884 let position = self.selections.newest_anchor().head();
4885 if position.diff_base_anchor.is_some() {
4886 return;
4887 }
4888 let (buffer, buffer_position) =
4889 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4890 output
4891 } else {
4892 return;
4893 };
4894 let buffer_snapshot = buffer.read(cx).snapshot();
4895 let show_completion_documentation = buffer_snapshot
4896 .settings_at(buffer_position, cx)
4897 .show_completion_documentation;
4898
4899 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4900
4901 let trigger_kind = match trigger {
4902 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4903 CompletionTriggerKind::TRIGGER_CHARACTER
4904 }
4905 _ => CompletionTriggerKind::INVOKED,
4906 };
4907 let completion_context = CompletionContext {
4908 trigger_character: trigger.and_then(|trigger| {
4909 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4910 Some(String::from(trigger))
4911 } else {
4912 None
4913 }
4914 }),
4915 trigger_kind,
4916 };
4917
4918 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4919 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4920 let word_to_exclude = buffer_snapshot
4921 .text_for_range(old_range.clone())
4922 .collect::<String>();
4923 (
4924 buffer_snapshot.anchor_before(old_range.start)
4925 ..buffer_snapshot.anchor_after(old_range.end),
4926 Some(word_to_exclude),
4927 )
4928 } else {
4929 (buffer_position..buffer_position, None)
4930 };
4931
4932 let completion_settings = language_settings(
4933 buffer_snapshot
4934 .language_at(buffer_position)
4935 .map(|language| language.name()),
4936 buffer_snapshot.file(),
4937 cx,
4938 )
4939 .completions;
4940
4941 // The document can be large, so stay in reasonable bounds when searching for words,
4942 // otherwise completion pop-up might be slow to appear.
4943 const WORD_LOOKUP_ROWS: u32 = 5_000;
4944 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4945 let min_word_search = buffer_snapshot.clip_point(
4946 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4947 Bias::Left,
4948 );
4949 let max_word_search = buffer_snapshot.clip_point(
4950 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4951 Bias::Right,
4952 );
4953 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4954 ..buffer_snapshot.point_to_offset(max_word_search);
4955
4956 let provider = self
4957 .completion_provider
4958 .as_ref()
4959 .filter(|_| !ignore_completion_provider);
4960 let skip_digits = query
4961 .as_ref()
4962 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4963
4964 let (mut words, provided_completions) = match provider {
4965 Some(provider) => {
4966 let completions = provider.completions(
4967 position.excerpt_id,
4968 &buffer,
4969 buffer_position,
4970 completion_context,
4971 window,
4972 cx,
4973 );
4974
4975 let words = match completion_settings.words {
4976 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4977 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4978 .background_spawn(async move {
4979 buffer_snapshot.words_in_range(WordsQuery {
4980 fuzzy_contents: None,
4981 range: word_search_range,
4982 skip_digits,
4983 })
4984 }),
4985 };
4986
4987 (words, completions)
4988 }
4989 None => (
4990 cx.background_spawn(async move {
4991 buffer_snapshot.words_in_range(WordsQuery {
4992 fuzzy_contents: None,
4993 range: word_search_range,
4994 skip_digits,
4995 })
4996 }),
4997 Task::ready(Ok(None)),
4998 ),
4999 };
5000
5001 let sort_completions = provider
5002 .as_ref()
5003 .map_or(false, |provider| provider.sort_completions());
5004
5005 let filter_completions = provider
5006 .as_ref()
5007 .map_or(true, |provider| provider.filter_completions());
5008
5009 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5010
5011 let id = post_inc(&mut self.next_completion_id);
5012 let task = cx.spawn_in(window, async move |editor, cx| {
5013 async move {
5014 editor.update(cx, |this, _| {
5015 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5016 })?;
5017
5018 let mut completions = Vec::new();
5019 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
5020 completions.extend(provided_completions);
5021 if completion_settings.words == WordsCompletionMode::Fallback {
5022 words = Task::ready(BTreeMap::default());
5023 }
5024 }
5025
5026 let mut words = words.await;
5027 if let Some(word_to_exclude) = &word_to_exclude {
5028 words.remove(word_to_exclude);
5029 }
5030 for lsp_completion in &completions {
5031 words.remove(&lsp_completion.new_text);
5032 }
5033 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5034 replace_range: old_range.clone(),
5035 new_text: word.clone(),
5036 label: CodeLabel::plain(word, None),
5037 icon_path: None,
5038 documentation: None,
5039 source: CompletionSource::BufferWord {
5040 word_range,
5041 resolved: false,
5042 },
5043 insert_text_mode: Some(InsertTextMode::AS_IS),
5044 confirm: None,
5045 }));
5046
5047 let menu = if completions.is_empty() {
5048 None
5049 } else {
5050 let mut menu = CompletionsMenu::new(
5051 id,
5052 sort_completions,
5053 show_completion_documentation,
5054 ignore_completion_provider,
5055 position,
5056 buffer.clone(),
5057 completions.into(),
5058 snippet_sort_order,
5059 );
5060
5061 menu.filter(
5062 if filter_completions {
5063 query.as_deref()
5064 } else {
5065 None
5066 },
5067 cx.background_executor().clone(),
5068 )
5069 .await;
5070
5071 menu.visible().then_some(menu)
5072 };
5073
5074 editor.update_in(cx, |editor, window, cx| {
5075 match editor.context_menu.borrow().as_ref() {
5076 None => {}
5077 Some(CodeContextMenu::Completions(prev_menu)) => {
5078 if prev_menu.id > id {
5079 return;
5080 }
5081 }
5082 _ => return,
5083 }
5084
5085 if editor.focus_handle.is_focused(window) && menu.is_some() {
5086 let mut menu = menu.unwrap();
5087 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
5088 crate::hover_popover::hide_hover(editor, cx);
5089 *editor.context_menu.borrow_mut() =
5090 Some(CodeContextMenu::Completions(menu));
5091
5092 if editor.show_edit_predictions_in_menu() {
5093 editor.update_visible_inline_completion(window, cx);
5094 } else {
5095 editor.discard_inline_completion(false, cx);
5096 }
5097
5098 cx.notify();
5099 } else if editor.completion_tasks.len() <= 1 {
5100 // If there are no more completion tasks and the last menu was
5101 // empty, we should hide it.
5102 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5103 // If it was already hidden and we don't show inline
5104 // completions in the menu, we should also show the
5105 // inline-completion when available.
5106 if was_hidden && editor.show_edit_predictions_in_menu() {
5107 editor.update_visible_inline_completion(window, cx);
5108 }
5109 }
5110 })?;
5111
5112 anyhow::Ok(())
5113 }
5114 .log_err()
5115 .await
5116 });
5117
5118 self.completion_tasks.push((id, task));
5119 }
5120
5121 #[cfg(feature = "test-support")]
5122 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5123 let menu = self.context_menu.borrow();
5124 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5125 let completions = menu.completions.borrow();
5126 Some(completions.to_vec())
5127 } else {
5128 None
5129 }
5130 }
5131
5132 pub fn confirm_completion(
5133 &mut self,
5134 action: &ConfirmCompletion,
5135 window: &mut Window,
5136 cx: &mut Context<Self>,
5137 ) -> Option<Task<Result<()>>> {
5138 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5139 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5140 }
5141
5142 pub fn confirm_completion_insert(
5143 &mut self,
5144 _: &ConfirmCompletionInsert,
5145 window: &mut Window,
5146 cx: &mut Context<Self>,
5147 ) -> Option<Task<Result<()>>> {
5148 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5149 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5150 }
5151
5152 pub fn confirm_completion_replace(
5153 &mut self,
5154 _: &ConfirmCompletionReplace,
5155 window: &mut Window,
5156 cx: &mut Context<Self>,
5157 ) -> Option<Task<Result<()>>> {
5158 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5159 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5160 }
5161
5162 pub fn compose_completion(
5163 &mut self,
5164 action: &ComposeCompletion,
5165 window: &mut Window,
5166 cx: &mut Context<Self>,
5167 ) -> Option<Task<Result<()>>> {
5168 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5169 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5170 }
5171
5172 fn do_completion(
5173 &mut self,
5174 item_ix: Option<usize>,
5175 intent: CompletionIntent,
5176 window: &mut Window,
5177 cx: &mut Context<Editor>,
5178 ) -> Option<Task<Result<()>>> {
5179 use language::ToOffset as _;
5180
5181 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5182 else {
5183 return None;
5184 };
5185
5186 let candidate_id = {
5187 let entries = completions_menu.entries.borrow();
5188 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5189 if self.show_edit_predictions_in_menu() {
5190 self.discard_inline_completion(true, cx);
5191 }
5192 mat.candidate_id
5193 };
5194
5195 let buffer_handle = completions_menu.buffer;
5196 let completion = completions_menu
5197 .completions
5198 .borrow()
5199 .get(candidate_id)?
5200 .clone();
5201 cx.stop_propagation();
5202
5203 let snapshot = self.buffer.read(cx).snapshot(cx);
5204 let newest_anchor = self.selections.newest_anchor();
5205
5206 let snippet;
5207 let new_text;
5208 if completion.is_snippet() {
5209 let mut snippet_source = completion.new_text.clone();
5210 if let Some(scope) = snapshot.language_scope_at(newest_anchor.head()) {
5211 if scope.prefers_label_for_snippet_in_completion() {
5212 if let Some(label) = completion.label() {
5213 if matches!(
5214 completion.kind(),
5215 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
5216 ) {
5217 snippet_source = label;
5218 }
5219 }
5220 }
5221 }
5222 snippet = Some(Snippet::parse(&snippet_source).log_err()?);
5223 new_text = snippet.as_ref().unwrap().text.clone();
5224 } else {
5225 snippet = None;
5226 new_text = completion.new_text.clone();
5227 };
5228
5229 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
5230 let buffer = buffer_handle.read(cx);
5231 let replace_range_multibuffer = {
5232 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5233 let multibuffer_anchor = snapshot
5234 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5235 .unwrap()
5236 ..snapshot
5237 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5238 .unwrap();
5239 multibuffer_anchor.start.to_offset(&snapshot)
5240 ..multibuffer_anchor.end.to_offset(&snapshot)
5241 };
5242 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5243 return None;
5244 }
5245
5246 let old_text = buffer
5247 .text_for_range(replace_range.clone())
5248 .collect::<String>();
5249 let lookbehind = newest_anchor
5250 .start
5251 .text_anchor
5252 .to_offset(buffer)
5253 .saturating_sub(replace_range.start);
5254 let lookahead = replace_range
5255 .end
5256 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5257 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5258 let suffix = &old_text[lookbehind.min(old_text.len())..];
5259
5260 let selections = self.selections.all::<usize>(cx);
5261 let mut ranges = Vec::new();
5262 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5263
5264 for selection in &selections {
5265 let range = if selection.id == newest_anchor.id {
5266 replace_range_multibuffer.clone()
5267 } else {
5268 let mut range = selection.range();
5269
5270 // if prefix is present, don't duplicate it
5271 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5272 range.start = range.start.saturating_sub(lookbehind);
5273
5274 // if suffix is also present, mimic the newest cursor and replace it
5275 if selection.id != newest_anchor.id
5276 && snapshot.contains_str_at(range.end, suffix)
5277 {
5278 range.end += lookahead;
5279 }
5280 }
5281 range
5282 };
5283
5284 ranges.push(range.clone());
5285
5286 if !self.linked_edit_ranges.is_empty() {
5287 let start_anchor = snapshot.anchor_before(range.start);
5288 let end_anchor = snapshot.anchor_after(range.end);
5289 if let Some(ranges) = self
5290 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5291 {
5292 for (buffer, edits) in ranges {
5293 linked_edits
5294 .entry(buffer.clone())
5295 .or_default()
5296 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5297 }
5298 }
5299 }
5300 }
5301
5302 cx.emit(EditorEvent::InputHandled {
5303 utf16_range_to_replace: None,
5304 text: new_text.clone().into(),
5305 });
5306
5307 self.transact(window, cx, |this, window, cx| {
5308 if let Some(mut snippet) = snippet {
5309 snippet.text = new_text.to_string();
5310 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5311 } else {
5312 this.buffer.update(cx, |buffer, cx| {
5313 let auto_indent = match completion.insert_text_mode {
5314 Some(InsertTextMode::AS_IS) => None,
5315 _ => this.autoindent_mode.clone(),
5316 };
5317 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5318 buffer.edit(edits, auto_indent, cx);
5319 });
5320 }
5321 for (buffer, edits) in linked_edits {
5322 buffer.update(cx, |buffer, cx| {
5323 let snapshot = buffer.snapshot();
5324 let edits = edits
5325 .into_iter()
5326 .map(|(range, text)| {
5327 use text::ToPoint as TP;
5328 let end_point = TP::to_point(&range.end, &snapshot);
5329 let start_point = TP::to_point(&range.start, &snapshot);
5330 (start_point..end_point, text)
5331 })
5332 .sorted_by_key(|(range, _)| range.start);
5333 buffer.edit(edits, None, cx);
5334 })
5335 }
5336
5337 this.refresh_inline_completion(true, false, window, cx);
5338 });
5339
5340 let show_new_completions_on_confirm = completion
5341 .confirm
5342 .as_ref()
5343 .map_or(false, |confirm| confirm(intent, window, cx));
5344 if show_new_completions_on_confirm {
5345 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5346 }
5347
5348 let provider = self.completion_provider.as_ref()?;
5349 drop(completion);
5350 let apply_edits = provider.apply_additional_edits_for_completion(
5351 buffer_handle,
5352 completions_menu.completions.clone(),
5353 candidate_id,
5354 true,
5355 cx,
5356 );
5357
5358 let editor_settings = EditorSettings::get_global(cx);
5359 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5360 // After the code completion is finished, users often want to know what signatures are needed.
5361 // so we should automatically call signature_help
5362 self.show_signature_help(&ShowSignatureHelp, window, cx);
5363 }
5364
5365 Some(cx.foreground_executor().spawn(async move {
5366 apply_edits.await?;
5367 Ok(())
5368 }))
5369 }
5370
5371 pub fn toggle_code_actions(
5372 &mut self,
5373 action: &ToggleCodeActions,
5374 window: &mut Window,
5375 cx: &mut Context<Self>,
5376 ) {
5377 let quick_launch = action.quick_launch;
5378 let mut context_menu = self.context_menu.borrow_mut();
5379 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5380 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
5381 // Toggle if we're selecting the same one
5382 *context_menu = None;
5383 cx.notify();
5384 return;
5385 } else {
5386 // Otherwise, clear it and start a new one
5387 *context_menu = None;
5388 cx.notify();
5389 }
5390 }
5391 drop(context_menu);
5392 let snapshot = self.snapshot(window, cx);
5393 let deployed_from_indicator = action.deployed_from_indicator;
5394 let mut task = self.code_actions_task.take();
5395 let action = action.clone();
5396 cx.spawn_in(window, async move |editor, cx| {
5397 while let Some(prev_task) = task {
5398 prev_task.await.log_err();
5399 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5400 }
5401
5402 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5403 if editor.focus_handle.is_focused(window) {
5404 let multibuffer_point = action
5405 .deployed_from_indicator
5406 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
5407 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
5408 let (buffer, buffer_row) = snapshot
5409 .buffer_snapshot
5410 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5411 .and_then(|(buffer_snapshot, range)| {
5412 editor
5413 .buffer
5414 .read(cx)
5415 .buffer(buffer_snapshot.remote_id())
5416 .map(|buffer| (buffer, range.start.row))
5417 })?;
5418 let (_, code_actions) = editor
5419 .available_code_actions
5420 .clone()
5421 .and_then(|(location, code_actions)| {
5422 let snapshot = location.buffer.read(cx).snapshot();
5423 let point_range = location.range.to_point(&snapshot);
5424 let point_range = point_range.start.row..=point_range.end.row;
5425 if point_range.contains(&buffer_row) {
5426 Some((location, code_actions))
5427 } else {
5428 None
5429 }
5430 })
5431 .unzip();
5432 let buffer_id = buffer.read(cx).remote_id();
5433 let tasks = editor
5434 .tasks
5435 .get(&(buffer_id, buffer_row))
5436 .map(|t| Arc::new(t.to_owned()));
5437 if tasks.is_none() && code_actions.is_none() {
5438 return None;
5439 }
5440
5441 editor.completion_tasks.clear();
5442 editor.discard_inline_completion(false, cx);
5443 let task_context =
5444 tasks
5445 .as_ref()
5446 .zip(editor.project.clone())
5447 .map(|(tasks, project)| {
5448 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5449 });
5450
5451 Some(cx.spawn_in(window, async move |editor, cx| {
5452 let task_context = match task_context {
5453 Some(task_context) => task_context.await,
5454 None => None,
5455 };
5456 let resolved_tasks =
5457 tasks
5458 .zip(task_context.clone())
5459 .map(|(tasks, task_context)| ResolvedTasks {
5460 templates: tasks.resolve(&task_context).collect(),
5461 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5462 multibuffer_point.row,
5463 tasks.column,
5464 )),
5465 });
5466 let debug_scenarios = editor.update(cx, |editor, cx| {
5467 if cx.has_flag::<DebuggerFeatureFlag>() {
5468 maybe!({
5469 let project = editor.project.as_ref()?;
5470 let dap_store = project.read(cx).dap_store();
5471 let mut scenarios = vec![];
5472 let resolved_tasks = resolved_tasks.as_ref()?;
5473 let buffer = buffer.read(cx);
5474 let language = buffer.language()?;
5475 let file = buffer.file();
5476 let debug_adapter =
5477 language_settings(language.name().into(), file, cx)
5478 .debuggers
5479 .first()
5480 .map(SharedString::from)
5481 .or_else(|| {
5482 language
5483 .config()
5484 .debuggers
5485 .first()
5486 .map(SharedString::from)
5487 })?;
5488
5489 dap_store.update(cx, |dap_store, cx| {
5490 for (_, task) in &resolved_tasks.templates {
5491 if let Some(scenario) = dap_store
5492 .debug_scenario_for_build_task(
5493 task.original_task().clone(),
5494 debug_adapter.clone().into(),
5495 task.display_label().to_owned().into(),
5496 cx,
5497 )
5498 {
5499 scenarios.push(scenario);
5500 }
5501 }
5502 });
5503 Some(scenarios)
5504 })
5505 .unwrap_or_default()
5506 } else {
5507 vec![]
5508 }
5509 })?;
5510 let spawn_straight_away = quick_launch
5511 && resolved_tasks
5512 .as_ref()
5513 .map_or(false, |tasks| tasks.templates.len() == 1)
5514 && code_actions
5515 .as_ref()
5516 .map_or(true, |actions| actions.is_empty())
5517 && debug_scenarios.is_empty();
5518 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5519 crate::hover_popover::hide_hover(editor, cx);
5520 *editor.context_menu.borrow_mut() =
5521 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5522 buffer,
5523 actions: CodeActionContents::new(
5524 resolved_tasks,
5525 code_actions,
5526 debug_scenarios,
5527 task_context.unwrap_or_default(),
5528 ),
5529 selected_item: Default::default(),
5530 scroll_handle: UniformListScrollHandle::default(),
5531 deployed_from_indicator,
5532 }));
5533 if spawn_straight_away {
5534 if let Some(task) = editor.confirm_code_action(
5535 &ConfirmCodeAction { item_ix: Some(0) },
5536 window,
5537 cx,
5538 ) {
5539 cx.notify();
5540 return task;
5541 }
5542 }
5543 cx.notify();
5544 Task::ready(Ok(()))
5545 }) {
5546 task.await
5547 } else {
5548 Ok(())
5549 }
5550 }))
5551 } else {
5552 Some(Task::ready(Ok(())))
5553 }
5554 })?;
5555 if let Some(task) = spawned_test_task {
5556 task.await?;
5557 }
5558
5559 anyhow::Ok(())
5560 })
5561 .detach_and_log_err(cx);
5562 }
5563
5564 pub fn confirm_code_action(
5565 &mut self,
5566 action: &ConfirmCodeAction,
5567 window: &mut Window,
5568 cx: &mut Context<Self>,
5569 ) -> Option<Task<Result<()>>> {
5570 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5571
5572 let actions_menu =
5573 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5574 menu
5575 } else {
5576 return None;
5577 };
5578
5579 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5580 let action = actions_menu.actions.get(action_ix)?;
5581 let title = action.label();
5582 let buffer = actions_menu.buffer;
5583 let workspace = self.workspace()?;
5584
5585 match action {
5586 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5587 workspace.update(cx, |workspace, cx| {
5588 workspace.schedule_resolved_task(
5589 task_source_kind,
5590 resolved_task,
5591 false,
5592 window,
5593 cx,
5594 );
5595
5596 Some(Task::ready(Ok(())))
5597 })
5598 }
5599 CodeActionsItem::CodeAction {
5600 excerpt_id,
5601 action,
5602 provider,
5603 } => {
5604 let apply_code_action =
5605 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5606 let workspace = workspace.downgrade();
5607 Some(cx.spawn_in(window, async move |editor, cx| {
5608 let project_transaction = apply_code_action.await?;
5609 Self::open_project_transaction(
5610 &editor,
5611 workspace,
5612 project_transaction,
5613 title,
5614 cx,
5615 )
5616 .await
5617 }))
5618 }
5619 CodeActionsItem::DebugScenario(scenario) => {
5620 let context = actions_menu.actions.context.clone();
5621
5622 workspace.update(cx, |workspace, cx| {
5623 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
5624 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5625 });
5626 Some(Task::ready(Ok(())))
5627 }
5628 }
5629 }
5630
5631 pub async fn open_project_transaction(
5632 this: &WeakEntity<Editor>,
5633 workspace: WeakEntity<Workspace>,
5634 transaction: ProjectTransaction,
5635 title: String,
5636 cx: &mut AsyncWindowContext,
5637 ) -> Result<()> {
5638 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5639 cx.update(|_, cx| {
5640 entries.sort_unstable_by_key(|(buffer, _)| {
5641 buffer.read(cx).file().map(|f| f.path().clone())
5642 });
5643 })?;
5644
5645 // If the project transaction's edits are all contained within this editor, then
5646 // avoid opening a new editor to display them.
5647
5648 if let Some((buffer, transaction)) = entries.first() {
5649 if entries.len() == 1 {
5650 let excerpt = this.update(cx, |editor, cx| {
5651 editor
5652 .buffer()
5653 .read(cx)
5654 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5655 })?;
5656 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5657 if excerpted_buffer == *buffer {
5658 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5659 let excerpt_range = excerpt_range.to_offset(buffer);
5660 buffer
5661 .edited_ranges_for_transaction::<usize>(transaction)
5662 .all(|range| {
5663 excerpt_range.start <= range.start
5664 && excerpt_range.end >= range.end
5665 })
5666 })?;
5667
5668 if all_edits_within_excerpt {
5669 return Ok(());
5670 }
5671 }
5672 }
5673 }
5674 } else {
5675 return Ok(());
5676 }
5677
5678 let mut ranges_to_highlight = Vec::new();
5679 let excerpt_buffer = cx.new(|cx| {
5680 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5681 for (buffer_handle, transaction) in &entries {
5682 let edited_ranges = buffer_handle
5683 .read(cx)
5684 .edited_ranges_for_transaction::<Point>(transaction)
5685 .collect::<Vec<_>>();
5686 let (ranges, _) = multibuffer.set_excerpts_for_path(
5687 PathKey::for_buffer(buffer_handle, cx),
5688 buffer_handle.clone(),
5689 edited_ranges,
5690 DEFAULT_MULTIBUFFER_CONTEXT,
5691 cx,
5692 );
5693
5694 ranges_to_highlight.extend(ranges);
5695 }
5696 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5697 multibuffer
5698 })?;
5699
5700 workspace.update_in(cx, |workspace, window, cx| {
5701 let project = workspace.project().clone();
5702 let editor =
5703 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5704 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5705 editor.update(cx, |editor, cx| {
5706 editor.highlight_background::<Self>(
5707 &ranges_to_highlight,
5708 |theme| theme.editor_highlighted_line_background,
5709 cx,
5710 );
5711 });
5712 })?;
5713
5714 Ok(())
5715 }
5716
5717 pub fn clear_code_action_providers(&mut self) {
5718 self.code_action_providers.clear();
5719 self.available_code_actions.take();
5720 }
5721
5722 pub fn add_code_action_provider(
5723 &mut self,
5724 provider: Rc<dyn CodeActionProvider>,
5725 window: &mut Window,
5726 cx: &mut Context<Self>,
5727 ) {
5728 if self
5729 .code_action_providers
5730 .iter()
5731 .any(|existing_provider| existing_provider.id() == provider.id())
5732 {
5733 return;
5734 }
5735
5736 self.code_action_providers.push(provider);
5737 self.refresh_code_actions(window, cx);
5738 }
5739
5740 pub fn remove_code_action_provider(
5741 &mut self,
5742 id: Arc<str>,
5743 window: &mut Window,
5744 cx: &mut Context<Self>,
5745 ) {
5746 self.code_action_providers
5747 .retain(|provider| provider.id() != id);
5748 self.refresh_code_actions(window, cx);
5749 }
5750
5751 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5752 let newest_selection = self.selections.newest_anchor().clone();
5753 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5754 let buffer = self.buffer.read(cx);
5755 if newest_selection.head().diff_base_anchor.is_some() {
5756 return None;
5757 }
5758 let (start_buffer, start) =
5759 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5760 let (end_buffer, end) =
5761 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5762 if start_buffer != end_buffer {
5763 return None;
5764 }
5765
5766 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5767 cx.background_executor()
5768 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5769 .await;
5770
5771 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5772 let providers = this.code_action_providers.clone();
5773 let tasks = this
5774 .code_action_providers
5775 .iter()
5776 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5777 .collect::<Vec<_>>();
5778 (providers, tasks)
5779 })?;
5780
5781 let mut actions = Vec::new();
5782 for (provider, provider_actions) in
5783 providers.into_iter().zip(future::join_all(tasks).await)
5784 {
5785 if let Some(provider_actions) = provider_actions.log_err() {
5786 actions.extend(provider_actions.into_iter().map(|action| {
5787 AvailableCodeAction {
5788 excerpt_id: newest_selection.start.excerpt_id,
5789 action,
5790 provider: provider.clone(),
5791 }
5792 }));
5793 }
5794 }
5795
5796 this.update(cx, |this, cx| {
5797 this.available_code_actions = if actions.is_empty() {
5798 None
5799 } else {
5800 Some((
5801 Location {
5802 buffer: start_buffer,
5803 range: start..end,
5804 },
5805 actions.into(),
5806 ))
5807 };
5808 cx.notify();
5809 })
5810 }));
5811 None
5812 }
5813
5814 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5815 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5816 self.show_git_blame_inline = false;
5817
5818 self.show_git_blame_inline_delay_task =
5819 Some(cx.spawn_in(window, async move |this, cx| {
5820 cx.background_executor().timer(delay).await;
5821
5822 this.update(cx, |this, cx| {
5823 this.show_git_blame_inline = true;
5824 cx.notify();
5825 })
5826 .log_err();
5827 }));
5828 }
5829 }
5830
5831 fn show_blame_popover(
5832 &mut self,
5833 blame_entry: &BlameEntry,
5834 position: gpui::Point<Pixels>,
5835 cx: &mut Context<Self>,
5836 ) {
5837 if let Some(state) = &mut self.inline_blame_popover {
5838 state.hide_task.take();
5839 cx.notify();
5840 } else {
5841 let delay = EditorSettings::get_global(cx).hover_popover_delay;
5842 let show_task = cx.spawn(async move |editor, cx| {
5843 cx.background_executor()
5844 .timer(std::time::Duration::from_millis(delay))
5845 .await;
5846 editor
5847 .update(cx, |editor, cx| {
5848 if let Some(state) = &mut editor.inline_blame_popover {
5849 state.show_task = None;
5850 cx.notify();
5851 }
5852 })
5853 .ok();
5854 });
5855 let Some(blame) = self.blame.as_ref() else {
5856 return;
5857 };
5858 let blame = blame.read(cx);
5859 let details = blame.details_for_entry(&blame_entry);
5860 let markdown = cx.new(|cx| {
5861 Markdown::new(
5862 details
5863 .as_ref()
5864 .map(|message| message.message.clone())
5865 .unwrap_or_default(),
5866 None,
5867 None,
5868 cx,
5869 )
5870 });
5871 self.inline_blame_popover = Some(InlineBlamePopover {
5872 position,
5873 show_task: Some(show_task),
5874 hide_task: None,
5875 popover_bounds: None,
5876 popover_state: InlineBlamePopoverState {
5877 scroll_handle: ScrollHandle::new(),
5878 commit_message: details,
5879 markdown,
5880 },
5881 });
5882 }
5883 }
5884
5885 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
5886 if let Some(state) = &mut self.inline_blame_popover {
5887 if state.show_task.is_some() {
5888 self.inline_blame_popover.take();
5889 cx.notify();
5890 } else {
5891 let hide_task = cx.spawn(async move |editor, cx| {
5892 cx.background_executor()
5893 .timer(std::time::Duration::from_millis(100))
5894 .await;
5895 editor
5896 .update(cx, |editor, cx| {
5897 editor.inline_blame_popover.take();
5898 cx.notify();
5899 })
5900 .ok();
5901 });
5902 state.hide_task = Some(hide_task);
5903 }
5904 }
5905 }
5906
5907 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5908 if self.pending_rename.is_some() {
5909 return None;
5910 }
5911
5912 let provider = self.semantics_provider.clone()?;
5913 let buffer = self.buffer.read(cx);
5914 let newest_selection = self.selections.newest_anchor().clone();
5915 let cursor_position = newest_selection.head();
5916 let (cursor_buffer, cursor_buffer_position) =
5917 buffer.text_anchor_for_position(cursor_position, cx)?;
5918 let (tail_buffer, tail_buffer_position) =
5919 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5920 if cursor_buffer != tail_buffer {
5921 return None;
5922 }
5923
5924 let snapshot = cursor_buffer.read(cx).snapshot();
5925 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
5926 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
5927 if start_word_range != end_word_range {
5928 self.document_highlights_task.take();
5929 self.clear_background_highlights::<DocumentHighlightRead>(cx);
5930 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
5931 return None;
5932 }
5933
5934 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5935 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5936 cx.background_executor()
5937 .timer(Duration::from_millis(debounce))
5938 .await;
5939
5940 let highlights = if let Some(highlights) = cx
5941 .update(|cx| {
5942 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5943 })
5944 .ok()
5945 .flatten()
5946 {
5947 highlights.await.log_err()
5948 } else {
5949 None
5950 };
5951
5952 if let Some(highlights) = highlights {
5953 this.update(cx, |this, cx| {
5954 if this.pending_rename.is_some() {
5955 return;
5956 }
5957
5958 let buffer_id = cursor_position.buffer_id;
5959 let buffer = this.buffer.read(cx);
5960 if !buffer
5961 .text_anchor_for_position(cursor_position, cx)
5962 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5963 {
5964 return;
5965 }
5966
5967 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5968 let mut write_ranges = Vec::new();
5969 let mut read_ranges = Vec::new();
5970 for highlight in highlights {
5971 for (excerpt_id, excerpt_range) in
5972 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5973 {
5974 let start = highlight
5975 .range
5976 .start
5977 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5978 let end = highlight
5979 .range
5980 .end
5981 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5982 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5983 continue;
5984 }
5985
5986 let range = Anchor {
5987 buffer_id,
5988 excerpt_id,
5989 text_anchor: start,
5990 diff_base_anchor: None,
5991 }..Anchor {
5992 buffer_id,
5993 excerpt_id,
5994 text_anchor: end,
5995 diff_base_anchor: None,
5996 };
5997 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5998 write_ranges.push(range);
5999 } else {
6000 read_ranges.push(range);
6001 }
6002 }
6003 }
6004
6005 this.highlight_background::<DocumentHighlightRead>(
6006 &read_ranges,
6007 |theme| theme.editor_document_highlight_read_background,
6008 cx,
6009 );
6010 this.highlight_background::<DocumentHighlightWrite>(
6011 &write_ranges,
6012 |theme| theme.editor_document_highlight_write_background,
6013 cx,
6014 );
6015 cx.notify();
6016 })
6017 .log_err();
6018 }
6019 }));
6020 None
6021 }
6022
6023 fn prepare_highlight_query_from_selection(
6024 &mut self,
6025 cx: &mut Context<Editor>,
6026 ) -> Option<(String, Range<Anchor>)> {
6027 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6028 return None;
6029 }
6030 if !EditorSettings::get_global(cx).selection_highlight {
6031 return None;
6032 }
6033 if self.selections.count() != 1 || self.selections.line_mode {
6034 return None;
6035 }
6036 let selection = self.selections.newest::<Point>(cx);
6037 if selection.is_empty() || selection.start.row != selection.end.row {
6038 return None;
6039 }
6040 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6041 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6042 let query = multi_buffer_snapshot
6043 .text_for_range(selection_anchor_range.clone())
6044 .collect::<String>();
6045 if query.trim().is_empty() {
6046 return None;
6047 }
6048 Some((query, selection_anchor_range))
6049 }
6050
6051 fn update_selection_occurrence_highlights(
6052 &mut self,
6053 query_text: String,
6054 query_range: Range<Anchor>,
6055 multi_buffer_range_to_query: Range<Point>,
6056 use_debounce: bool,
6057 window: &mut Window,
6058 cx: &mut Context<Editor>,
6059 ) -> Task<()> {
6060 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6061 cx.spawn_in(window, async move |editor, cx| {
6062 if use_debounce {
6063 cx.background_executor()
6064 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6065 .await;
6066 }
6067 let match_task = cx.background_spawn(async move {
6068 let buffer_ranges = multi_buffer_snapshot
6069 .range_to_buffer_ranges(multi_buffer_range_to_query)
6070 .into_iter()
6071 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6072 let mut match_ranges = Vec::new();
6073 let Ok(regex) = project::search::SearchQuery::text(
6074 query_text.clone(),
6075 false,
6076 false,
6077 false,
6078 Default::default(),
6079 Default::default(),
6080 false,
6081 None,
6082 ) else {
6083 return Vec::default();
6084 };
6085 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6086 match_ranges.extend(
6087 regex
6088 .search(&buffer_snapshot, Some(search_range.clone()))
6089 .await
6090 .into_iter()
6091 .filter_map(|match_range| {
6092 let match_start = buffer_snapshot
6093 .anchor_after(search_range.start + match_range.start);
6094 let match_end = buffer_snapshot
6095 .anchor_before(search_range.start + match_range.end);
6096 let match_anchor_range = Anchor::range_in_buffer(
6097 excerpt_id,
6098 buffer_snapshot.remote_id(),
6099 match_start..match_end,
6100 );
6101 (match_anchor_range != query_range).then_some(match_anchor_range)
6102 }),
6103 );
6104 }
6105 match_ranges
6106 });
6107 let match_ranges = match_task.await;
6108 editor
6109 .update_in(cx, |editor, _, cx| {
6110 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6111 if !match_ranges.is_empty() {
6112 editor.highlight_background::<SelectedTextHighlight>(
6113 &match_ranges,
6114 |theme| theme.editor_document_highlight_bracket_background,
6115 cx,
6116 )
6117 }
6118 })
6119 .log_err();
6120 })
6121 }
6122
6123 fn refresh_selected_text_highlights(
6124 &mut self,
6125 on_buffer_edit: bool,
6126 window: &mut Window,
6127 cx: &mut Context<Editor>,
6128 ) {
6129 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6130 else {
6131 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6132 self.quick_selection_highlight_task.take();
6133 self.debounced_selection_highlight_task.take();
6134 return;
6135 };
6136 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6137 if on_buffer_edit
6138 || self
6139 .quick_selection_highlight_task
6140 .as_ref()
6141 .map_or(true, |(prev_anchor_range, _)| {
6142 prev_anchor_range != &query_range
6143 })
6144 {
6145 let multi_buffer_visible_start = self
6146 .scroll_manager
6147 .anchor()
6148 .anchor
6149 .to_point(&multi_buffer_snapshot);
6150 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6151 multi_buffer_visible_start
6152 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6153 Bias::Left,
6154 );
6155 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6156 self.quick_selection_highlight_task = Some((
6157 query_range.clone(),
6158 self.update_selection_occurrence_highlights(
6159 query_text.clone(),
6160 query_range.clone(),
6161 multi_buffer_visible_range,
6162 false,
6163 window,
6164 cx,
6165 ),
6166 ));
6167 }
6168 if on_buffer_edit
6169 || self
6170 .debounced_selection_highlight_task
6171 .as_ref()
6172 .map_or(true, |(prev_anchor_range, _)| {
6173 prev_anchor_range != &query_range
6174 })
6175 {
6176 let multi_buffer_start = multi_buffer_snapshot
6177 .anchor_before(0)
6178 .to_point(&multi_buffer_snapshot);
6179 let multi_buffer_end = multi_buffer_snapshot
6180 .anchor_after(multi_buffer_snapshot.len())
6181 .to_point(&multi_buffer_snapshot);
6182 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6183 self.debounced_selection_highlight_task = Some((
6184 query_range.clone(),
6185 self.update_selection_occurrence_highlights(
6186 query_text,
6187 query_range,
6188 multi_buffer_full_range,
6189 true,
6190 window,
6191 cx,
6192 ),
6193 ));
6194 }
6195 }
6196
6197 pub fn refresh_inline_completion(
6198 &mut self,
6199 debounce: bool,
6200 user_requested: bool,
6201 window: &mut Window,
6202 cx: &mut Context<Self>,
6203 ) -> Option<()> {
6204 let provider = self.edit_prediction_provider()?;
6205 let cursor = self.selections.newest_anchor().head();
6206 let (buffer, cursor_buffer_position) =
6207 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6208
6209 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6210 self.discard_inline_completion(false, cx);
6211 return None;
6212 }
6213
6214 if !user_requested
6215 && (!self.should_show_edit_predictions()
6216 || !self.is_focused(window)
6217 || buffer.read(cx).is_empty())
6218 {
6219 self.discard_inline_completion(false, cx);
6220 return None;
6221 }
6222
6223 self.update_visible_inline_completion(window, cx);
6224 provider.refresh(
6225 self.project.clone(),
6226 buffer,
6227 cursor_buffer_position,
6228 debounce,
6229 cx,
6230 );
6231 Some(())
6232 }
6233
6234 fn show_edit_predictions_in_menu(&self) -> bool {
6235 match self.edit_prediction_settings {
6236 EditPredictionSettings::Disabled => false,
6237 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6238 }
6239 }
6240
6241 pub fn edit_predictions_enabled(&self) -> bool {
6242 match self.edit_prediction_settings {
6243 EditPredictionSettings::Disabled => false,
6244 EditPredictionSettings::Enabled { .. } => true,
6245 }
6246 }
6247
6248 fn edit_prediction_requires_modifier(&self) -> bool {
6249 match self.edit_prediction_settings {
6250 EditPredictionSettings::Disabled => false,
6251 EditPredictionSettings::Enabled {
6252 preview_requires_modifier,
6253 ..
6254 } => preview_requires_modifier,
6255 }
6256 }
6257
6258 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6259 if self.edit_prediction_provider.is_none() {
6260 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6261 } else {
6262 let selection = self.selections.newest_anchor();
6263 let cursor = selection.head();
6264
6265 if let Some((buffer, cursor_buffer_position)) =
6266 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6267 {
6268 self.edit_prediction_settings =
6269 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6270 }
6271 }
6272 }
6273
6274 fn edit_prediction_settings_at_position(
6275 &self,
6276 buffer: &Entity<Buffer>,
6277 buffer_position: language::Anchor,
6278 cx: &App,
6279 ) -> EditPredictionSettings {
6280 if !self.mode.is_full()
6281 || !self.show_inline_completions_override.unwrap_or(true)
6282 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6283 {
6284 return EditPredictionSettings::Disabled;
6285 }
6286
6287 let buffer = buffer.read(cx);
6288
6289 let file = buffer.file();
6290
6291 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6292 return EditPredictionSettings::Disabled;
6293 };
6294
6295 let by_provider = matches!(
6296 self.menu_inline_completions_policy,
6297 MenuInlineCompletionsPolicy::ByProvider
6298 );
6299
6300 let show_in_menu = by_provider
6301 && self
6302 .edit_prediction_provider
6303 .as_ref()
6304 .map_or(false, |provider| {
6305 provider.provider.show_completions_in_menu()
6306 });
6307
6308 let preview_requires_modifier =
6309 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6310
6311 EditPredictionSettings::Enabled {
6312 show_in_menu,
6313 preview_requires_modifier,
6314 }
6315 }
6316
6317 fn should_show_edit_predictions(&self) -> bool {
6318 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6319 }
6320
6321 pub fn edit_prediction_preview_is_active(&self) -> bool {
6322 matches!(
6323 self.edit_prediction_preview,
6324 EditPredictionPreview::Active { .. }
6325 )
6326 }
6327
6328 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6329 let cursor = self.selections.newest_anchor().head();
6330 if let Some((buffer, cursor_position)) =
6331 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6332 {
6333 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6334 } else {
6335 false
6336 }
6337 }
6338
6339 pub fn supports_minimap(&self, cx: &App) -> bool {
6340 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6341 }
6342
6343 fn edit_predictions_enabled_in_buffer(
6344 &self,
6345 buffer: &Entity<Buffer>,
6346 buffer_position: language::Anchor,
6347 cx: &App,
6348 ) -> bool {
6349 maybe!({
6350 if self.read_only(cx) {
6351 return Some(false);
6352 }
6353 let provider = self.edit_prediction_provider()?;
6354 if !provider.is_enabled(&buffer, buffer_position, cx) {
6355 return Some(false);
6356 }
6357 let buffer = buffer.read(cx);
6358 let Some(file) = buffer.file() else {
6359 return Some(true);
6360 };
6361 let settings = all_language_settings(Some(file), cx);
6362 Some(settings.edit_predictions_enabled_for_file(file, cx))
6363 })
6364 .unwrap_or(false)
6365 }
6366
6367 fn cycle_inline_completion(
6368 &mut self,
6369 direction: Direction,
6370 window: &mut Window,
6371 cx: &mut Context<Self>,
6372 ) -> Option<()> {
6373 let provider = self.edit_prediction_provider()?;
6374 let cursor = self.selections.newest_anchor().head();
6375 let (buffer, cursor_buffer_position) =
6376 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6377 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6378 return None;
6379 }
6380
6381 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6382 self.update_visible_inline_completion(window, cx);
6383
6384 Some(())
6385 }
6386
6387 pub fn show_inline_completion(
6388 &mut self,
6389 _: &ShowEditPrediction,
6390 window: &mut Window,
6391 cx: &mut Context<Self>,
6392 ) {
6393 if !self.has_active_inline_completion() {
6394 self.refresh_inline_completion(false, true, window, cx);
6395 return;
6396 }
6397
6398 self.update_visible_inline_completion(window, cx);
6399 }
6400
6401 pub fn display_cursor_names(
6402 &mut self,
6403 _: &DisplayCursorNames,
6404 window: &mut Window,
6405 cx: &mut Context<Self>,
6406 ) {
6407 self.show_cursor_names(window, cx);
6408 }
6409
6410 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6411 self.show_cursor_names = true;
6412 cx.notify();
6413 cx.spawn_in(window, async move |this, cx| {
6414 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6415 this.update(cx, |this, cx| {
6416 this.show_cursor_names = false;
6417 cx.notify()
6418 })
6419 .ok()
6420 })
6421 .detach();
6422 }
6423
6424 pub fn next_edit_prediction(
6425 &mut self,
6426 _: &NextEditPrediction,
6427 window: &mut Window,
6428 cx: &mut Context<Self>,
6429 ) {
6430 if self.has_active_inline_completion() {
6431 self.cycle_inline_completion(Direction::Next, window, cx);
6432 } else {
6433 let is_copilot_disabled = self
6434 .refresh_inline_completion(false, true, window, cx)
6435 .is_none();
6436 if is_copilot_disabled {
6437 cx.propagate();
6438 }
6439 }
6440 }
6441
6442 pub fn previous_edit_prediction(
6443 &mut self,
6444 _: &PreviousEditPrediction,
6445 window: &mut Window,
6446 cx: &mut Context<Self>,
6447 ) {
6448 if self.has_active_inline_completion() {
6449 self.cycle_inline_completion(Direction::Prev, window, cx);
6450 } else {
6451 let is_copilot_disabled = self
6452 .refresh_inline_completion(false, true, window, cx)
6453 .is_none();
6454 if is_copilot_disabled {
6455 cx.propagate();
6456 }
6457 }
6458 }
6459
6460 pub fn accept_edit_prediction(
6461 &mut self,
6462 _: &AcceptEditPrediction,
6463 window: &mut Window,
6464 cx: &mut Context<Self>,
6465 ) {
6466 if self.show_edit_predictions_in_menu() {
6467 self.hide_context_menu(window, cx);
6468 }
6469
6470 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6471 return;
6472 };
6473
6474 self.report_inline_completion_event(
6475 active_inline_completion.completion_id.clone(),
6476 true,
6477 cx,
6478 );
6479
6480 match &active_inline_completion.completion {
6481 InlineCompletion::Move { target, .. } => {
6482 let target = *target;
6483
6484 if let Some(position_map) = &self.last_position_map {
6485 if position_map
6486 .visible_row_range
6487 .contains(&target.to_display_point(&position_map.snapshot).row())
6488 || !self.edit_prediction_requires_modifier()
6489 {
6490 self.unfold_ranges(&[target..target], true, false, cx);
6491 // Note that this is also done in vim's handler of the Tab action.
6492 self.change_selections(
6493 Some(Autoscroll::newest()),
6494 window,
6495 cx,
6496 |selections| {
6497 selections.select_anchor_ranges([target..target]);
6498 },
6499 );
6500 self.clear_row_highlights::<EditPredictionPreview>();
6501
6502 self.edit_prediction_preview
6503 .set_previous_scroll_position(None);
6504 } else {
6505 self.edit_prediction_preview
6506 .set_previous_scroll_position(Some(
6507 position_map.snapshot.scroll_anchor,
6508 ));
6509
6510 self.highlight_rows::<EditPredictionPreview>(
6511 target..target,
6512 cx.theme().colors().editor_highlighted_line_background,
6513 RowHighlightOptions {
6514 autoscroll: true,
6515 ..Default::default()
6516 },
6517 cx,
6518 );
6519 self.request_autoscroll(Autoscroll::fit(), cx);
6520 }
6521 }
6522 }
6523 InlineCompletion::Edit { edits, .. } => {
6524 if let Some(provider) = self.edit_prediction_provider() {
6525 provider.accept(cx);
6526 }
6527
6528 // Store the transaction ID and selections before applying the edit
6529 let transaction_id_prev =
6530 self.buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
6531
6532 let snapshot = self.buffer.read(cx).snapshot(cx);
6533 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6534
6535 self.buffer.update(cx, |buffer, cx| {
6536 buffer.edit(edits.iter().cloned(), None, cx)
6537 });
6538
6539 self.change_selections(None, window, cx, |s| {
6540 s.select_anchor_ranges([last_edit_end..last_edit_end]);
6541 });
6542
6543 let selections = self.selections.disjoint_anchors();
6544 if let Some(transaction_id_now) =
6545 self.buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
6546 {
6547 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
6548 if has_new_transaction {
6549 self.selection_history
6550 .insert_transaction(transaction_id_now, selections);
6551 }
6552 }
6553
6554 self.update_visible_inline_completion(window, cx);
6555 if self.active_inline_completion.is_none() {
6556 self.refresh_inline_completion(true, true, window, cx);
6557 }
6558
6559 cx.notify();
6560 }
6561 }
6562
6563 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6564 }
6565
6566 pub fn accept_partial_inline_completion(
6567 &mut self,
6568 _: &AcceptPartialEditPrediction,
6569 window: &mut Window,
6570 cx: &mut Context<Self>,
6571 ) {
6572 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6573 return;
6574 };
6575 if self.selections.count() != 1 {
6576 return;
6577 }
6578
6579 self.report_inline_completion_event(
6580 active_inline_completion.completion_id.clone(),
6581 true,
6582 cx,
6583 );
6584
6585 match &active_inline_completion.completion {
6586 InlineCompletion::Move { target, .. } => {
6587 let target = *target;
6588 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6589 selections.select_anchor_ranges([target..target]);
6590 });
6591 }
6592 InlineCompletion::Edit { edits, .. } => {
6593 // Find an insertion that starts at the cursor position.
6594 let snapshot = self.buffer.read(cx).snapshot(cx);
6595 let cursor_offset = self.selections.newest::<usize>(cx).head();
6596 let insertion = edits.iter().find_map(|(range, text)| {
6597 let range = range.to_offset(&snapshot);
6598 if range.is_empty() && range.start == cursor_offset {
6599 Some(text)
6600 } else {
6601 None
6602 }
6603 });
6604
6605 if let Some(text) = insertion {
6606 let mut partial_completion = text
6607 .chars()
6608 .by_ref()
6609 .take_while(|c| c.is_alphabetic())
6610 .collect::<String>();
6611 if partial_completion.is_empty() {
6612 partial_completion = text
6613 .chars()
6614 .by_ref()
6615 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6616 .collect::<String>();
6617 }
6618
6619 cx.emit(EditorEvent::InputHandled {
6620 utf16_range_to_replace: None,
6621 text: partial_completion.clone().into(),
6622 });
6623
6624 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6625
6626 self.refresh_inline_completion(true, true, window, cx);
6627 cx.notify();
6628 } else {
6629 self.accept_edit_prediction(&Default::default(), window, cx);
6630 }
6631 }
6632 }
6633 }
6634
6635 fn discard_inline_completion(
6636 &mut self,
6637 should_report_inline_completion_event: bool,
6638 cx: &mut Context<Self>,
6639 ) -> bool {
6640 if should_report_inline_completion_event {
6641 let completion_id = self
6642 .active_inline_completion
6643 .as_ref()
6644 .and_then(|active_completion| active_completion.completion_id.clone());
6645
6646 self.report_inline_completion_event(completion_id, false, cx);
6647 }
6648
6649 if let Some(provider) = self.edit_prediction_provider() {
6650 provider.discard(cx);
6651 }
6652
6653 self.take_active_inline_completion(cx)
6654 }
6655
6656 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6657 let Some(provider) = self.edit_prediction_provider() else {
6658 return;
6659 };
6660
6661 let Some((_, buffer, _)) = self
6662 .buffer
6663 .read(cx)
6664 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6665 else {
6666 return;
6667 };
6668
6669 let extension = buffer
6670 .read(cx)
6671 .file()
6672 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6673
6674 let event_type = match accepted {
6675 true => "Edit Prediction Accepted",
6676 false => "Edit Prediction Discarded",
6677 };
6678 telemetry::event!(
6679 event_type,
6680 provider = provider.name(),
6681 prediction_id = id,
6682 suggestion_accepted = accepted,
6683 file_extension = extension,
6684 );
6685 }
6686
6687 pub fn has_active_inline_completion(&self) -> bool {
6688 self.active_inline_completion.is_some()
6689 }
6690
6691 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6692 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6693 return false;
6694 };
6695
6696 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6697 self.clear_highlights::<InlineCompletionHighlight>(cx);
6698 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6699 true
6700 }
6701
6702 /// Returns true when we're displaying the edit prediction popover below the cursor
6703 /// like we are not previewing and the LSP autocomplete menu is visible
6704 /// or we are in `when_holding_modifier` mode.
6705 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6706 if self.edit_prediction_preview_is_active()
6707 || !self.show_edit_predictions_in_menu()
6708 || !self.edit_predictions_enabled()
6709 {
6710 return false;
6711 }
6712
6713 if self.has_visible_completions_menu() {
6714 return true;
6715 }
6716
6717 has_completion && self.edit_prediction_requires_modifier()
6718 }
6719
6720 fn handle_modifiers_changed(
6721 &mut self,
6722 modifiers: Modifiers,
6723 position_map: &PositionMap,
6724 window: &mut Window,
6725 cx: &mut Context<Self>,
6726 ) {
6727 if self.show_edit_predictions_in_menu() {
6728 self.update_edit_prediction_preview(&modifiers, window, cx);
6729 }
6730
6731 self.update_selection_mode(&modifiers, position_map, window, cx);
6732
6733 let mouse_position = window.mouse_position();
6734 if !position_map.text_hitbox.is_hovered(window) {
6735 return;
6736 }
6737
6738 self.update_hovered_link(
6739 position_map.point_for_position(mouse_position),
6740 &position_map.snapshot,
6741 modifiers,
6742 window,
6743 cx,
6744 )
6745 }
6746
6747 fn update_selection_mode(
6748 &mut self,
6749 modifiers: &Modifiers,
6750 position_map: &PositionMap,
6751 window: &mut Window,
6752 cx: &mut Context<Self>,
6753 ) {
6754 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6755 return;
6756 }
6757
6758 let mouse_position = window.mouse_position();
6759 let point_for_position = position_map.point_for_position(mouse_position);
6760 let position = point_for_position.previous_valid;
6761
6762 self.select(
6763 SelectPhase::BeginColumnar {
6764 position,
6765 reset: false,
6766 goal_column: point_for_position.exact_unclipped.column(),
6767 },
6768 window,
6769 cx,
6770 );
6771 }
6772
6773 fn update_edit_prediction_preview(
6774 &mut self,
6775 modifiers: &Modifiers,
6776 window: &mut Window,
6777 cx: &mut Context<Self>,
6778 ) {
6779 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6780 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6781 return;
6782 };
6783
6784 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6785 if matches!(
6786 self.edit_prediction_preview,
6787 EditPredictionPreview::Inactive { .. }
6788 ) {
6789 self.edit_prediction_preview = EditPredictionPreview::Active {
6790 previous_scroll_position: None,
6791 since: Instant::now(),
6792 };
6793
6794 self.update_visible_inline_completion(window, cx);
6795 cx.notify();
6796 }
6797 } else if let EditPredictionPreview::Active {
6798 previous_scroll_position,
6799 since,
6800 } = self.edit_prediction_preview
6801 {
6802 if let (Some(previous_scroll_position), Some(position_map)) =
6803 (previous_scroll_position, self.last_position_map.as_ref())
6804 {
6805 self.set_scroll_position(
6806 previous_scroll_position
6807 .scroll_position(&position_map.snapshot.display_snapshot),
6808 window,
6809 cx,
6810 );
6811 }
6812
6813 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6814 released_too_fast: since.elapsed() < Duration::from_millis(200),
6815 };
6816 self.clear_row_highlights::<EditPredictionPreview>();
6817 self.update_visible_inline_completion(window, cx);
6818 cx.notify();
6819 }
6820 }
6821
6822 fn update_visible_inline_completion(
6823 &mut self,
6824 _window: &mut Window,
6825 cx: &mut Context<Self>,
6826 ) -> Option<()> {
6827 let selection = self.selections.newest_anchor();
6828 let cursor = selection.head();
6829 let multibuffer = self.buffer.read(cx).snapshot(cx);
6830 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6831 let excerpt_id = cursor.excerpt_id;
6832
6833 let show_in_menu = self.show_edit_predictions_in_menu();
6834 let completions_menu_has_precedence = !show_in_menu
6835 && (self.context_menu.borrow().is_some()
6836 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6837
6838 if completions_menu_has_precedence
6839 || !offset_selection.is_empty()
6840 || self
6841 .active_inline_completion
6842 .as_ref()
6843 .map_or(false, |completion| {
6844 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6845 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6846 !invalidation_range.contains(&offset_selection.head())
6847 })
6848 {
6849 self.discard_inline_completion(false, cx);
6850 return None;
6851 }
6852
6853 self.take_active_inline_completion(cx);
6854 let Some(provider) = self.edit_prediction_provider() else {
6855 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6856 return None;
6857 };
6858
6859 let (buffer, cursor_buffer_position) =
6860 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6861
6862 self.edit_prediction_settings =
6863 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6864
6865 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6866
6867 if self.edit_prediction_indent_conflict {
6868 let cursor_point = cursor.to_point(&multibuffer);
6869
6870 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6871
6872 if let Some((_, indent)) = indents.iter().next() {
6873 if indent.len == cursor_point.column {
6874 self.edit_prediction_indent_conflict = false;
6875 }
6876 }
6877 }
6878
6879 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6880 let edits = inline_completion
6881 .edits
6882 .into_iter()
6883 .flat_map(|(range, new_text)| {
6884 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6885 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6886 Some((start..end, new_text))
6887 })
6888 .collect::<Vec<_>>();
6889 if edits.is_empty() {
6890 return None;
6891 }
6892
6893 let first_edit_start = edits.first().unwrap().0.start;
6894 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6895 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6896
6897 let last_edit_end = edits.last().unwrap().0.end;
6898 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6899 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6900
6901 let cursor_row = cursor.to_point(&multibuffer).row;
6902
6903 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6904
6905 let mut inlay_ids = Vec::new();
6906 let invalidation_row_range;
6907 let move_invalidation_row_range = if cursor_row < edit_start_row {
6908 Some(cursor_row..edit_end_row)
6909 } else if cursor_row > edit_end_row {
6910 Some(edit_start_row..cursor_row)
6911 } else {
6912 None
6913 };
6914 let is_move =
6915 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6916 let completion = if is_move {
6917 invalidation_row_range =
6918 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6919 let target = first_edit_start;
6920 InlineCompletion::Move { target, snapshot }
6921 } else {
6922 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6923 && !self.inline_completions_hidden_for_vim_mode;
6924
6925 if show_completions_in_buffer {
6926 if edits
6927 .iter()
6928 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6929 {
6930 let mut inlays = Vec::new();
6931 for (range, new_text) in &edits {
6932 let inlay = Inlay::inline_completion(
6933 post_inc(&mut self.next_inlay_id),
6934 range.start,
6935 new_text.as_str(),
6936 );
6937 inlay_ids.push(inlay.id);
6938 inlays.push(inlay);
6939 }
6940
6941 self.splice_inlays(&[], inlays, cx);
6942 } else {
6943 let background_color = cx.theme().status().deleted_background;
6944 self.highlight_text::<InlineCompletionHighlight>(
6945 edits.iter().map(|(range, _)| range.clone()).collect(),
6946 HighlightStyle {
6947 background_color: Some(background_color),
6948 ..Default::default()
6949 },
6950 cx,
6951 );
6952 }
6953 }
6954
6955 invalidation_row_range = edit_start_row..edit_end_row;
6956
6957 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6958 if provider.show_tab_accept_marker() {
6959 EditDisplayMode::TabAccept
6960 } else {
6961 EditDisplayMode::Inline
6962 }
6963 } else {
6964 EditDisplayMode::DiffPopover
6965 };
6966
6967 InlineCompletion::Edit {
6968 edits,
6969 edit_preview: inline_completion.edit_preview,
6970 display_mode,
6971 snapshot,
6972 }
6973 };
6974
6975 let invalidation_range = multibuffer
6976 .anchor_before(Point::new(invalidation_row_range.start, 0))
6977 ..multibuffer.anchor_after(Point::new(
6978 invalidation_row_range.end,
6979 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6980 ));
6981
6982 self.stale_inline_completion_in_menu = None;
6983 self.active_inline_completion = Some(InlineCompletionState {
6984 inlay_ids,
6985 completion,
6986 completion_id: inline_completion.id,
6987 invalidation_range,
6988 });
6989
6990 cx.notify();
6991
6992 Some(())
6993 }
6994
6995 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6996 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6997 }
6998
6999 fn clear_tasks(&mut self) {
7000 self.tasks.clear()
7001 }
7002
7003 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7004 if self.tasks.insert(key, value).is_some() {
7005 // This case should hopefully be rare, but just in case...
7006 log::error!(
7007 "multiple different run targets found on a single line, only the last target will be rendered"
7008 )
7009 }
7010 }
7011
7012 /// Get all display points of breakpoints that will be rendered within editor
7013 ///
7014 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7015 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7016 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7017 fn active_breakpoints(
7018 &self,
7019 range: Range<DisplayRow>,
7020 window: &mut Window,
7021 cx: &mut Context<Self>,
7022 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7023 let mut breakpoint_display_points = HashMap::default();
7024
7025 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7026 return breakpoint_display_points;
7027 };
7028
7029 let snapshot = self.snapshot(window, cx);
7030
7031 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7032 let Some(project) = self.project.as_ref() else {
7033 return breakpoint_display_points;
7034 };
7035
7036 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7037 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7038
7039 for (buffer_snapshot, range, excerpt_id) in
7040 multi_buffer_snapshot.range_to_buffer_ranges(range)
7041 {
7042 let Some(buffer) = project.read_with(cx, |this, cx| {
7043 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
7044 }) else {
7045 continue;
7046 };
7047 let breakpoints = breakpoint_store.read(cx).breakpoints(
7048 &buffer,
7049 Some(
7050 buffer_snapshot.anchor_before(range.start)
7051 ..buffer_snapshot.anchor_after(range.end),
7052 ),
7053 buffer_snapshot,
7054 cx,
7055 );
7056 for (breakpoint, state) in breakpoints {
7057 let multi_buffer_anchor =
7058 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7059 let position = multi_buffer_anchor
7060 .to_point(&multi_buffer_snapshot)
7061 .to_display_point(&snapshot);
7062
7063 breakpoint_display_points.insert(
7064 position.row(),
7065 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7066 );
7067 }
7068 }
7069
7070 breakpoint_display_points
7071 }
7072
7073 fn breakpoint_context_menu(
7074 &self,
7075 anchor: Anchor,
7076 window: &mut Window,
7077 cx: &mut Context<Self>,
7078 ) -> Entity<ui::ContextMenu> {
7079 let weak_editor = cx.weak_entity();
7080 let focus_handle = self.focus_handle(cx);
7081
7082 let row = self
7083 .buffer
7084 .read(cx)
7085 .snapshot(cx)
7086 .summary_for_anchor::<Point>(&anchor)
7087 .row;
7088
7089 let breakpoint = self
7090 .breakpoint_at_row(row, window, cx)
7091 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7092
7093 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7094 "Edit Log Breakpoint"
7095 } else {
7096 "Set Log Breakpoint"
7097 };
7098
7099 let condition_breakpoint_msg = if breakpoint
7100 .as_ref()
7101 .is_some_and(|bp| bp.1.condition.is_some())
7102 {
7103 "Edit Condition Breakpoint"
7104 } else {
7105 "Set Condition Breakpoint"
7106 };
7107
7108 let hit_condition_breakpoint_msg = if breakpoint
7109 .as_ref()
7110 .is_some_and(|bp| bp.1.hit_condition.is_some())
7111 {
7112 "Edit Hit Condition Breakpoint"
7113 } else {
7114 "Set Hit Condition Breakpoint"
7115 };
7116
7117 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7118 "Unset Breakpoint"
7119 } else {
7120 "Set Breakpoint"
7121 };
7122
7123 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
7124 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
7125
7126 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7127 BreakpointState::Enabled => Some("Disable"),
7128 BreakpointState::Disabled => Some("Enable"),
7129 });
7130
7131 let (anchor, breakpoint) =
7132 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7133
7134 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7135 menu.on_blur_subscription(Subscription::new(|| {}))
7136 .context(focus_handle)
7137 .when(run_to_cursor, |this| {
7138 let weak_editor = weak_editor.clone();
7139 this.entry("Run to cursor", None, move |window, cx| {
7140 weak_editor
7141 .update(cx, |editor, cx| {
7142 editor.change_selections(None, window, cx, |s| {
7143 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7144 });
7145 })
7146 .ok();
7147
7148 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
7149 })
7150 .separator()
7151 })
7152 .when_some(toggle_state_msg, |this, msg| {
7153 this.entry(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::InvertState,
7163 cx,
7164 );
7165 })
7166 .log_err();
7167 }
7168 })
7169 })
7170 .entry(set_breakpoint_msg, None, {
7171 let weak_editor = weak_editor.clone();
7172 let breakpoint = breakpoint.clone();
7173 move |_window, cx| {
7174 weak_editor
7175 .update(cx, |this, cx| {
7176 this.edit_breakpoint_at_anchor(
7177 anchor,
7178 breakpoint.as_ref().clone(),
7179 BreakpointEditAction::Toggle,
7180 cx,
7181 );
7182 })
7183 .log_err();
7184 }
7185 })
7186 .entry(log_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::Log,
7196 window,
7197 cx,
7198 );
7199 })
7200 .log_err();
7201 }
7202 })
7203 .entry(condition_breakpoint_msg, None, {
7204 let breakpoint = breakpoint.clone();
7205 let weak_editor = weak_editor.clone();
7206 move |window, cx| {
7207 weak_editor
7208 .update(cx, |this, cx| {
7209 this.add_edit_breakpoint_block(
7210 anchor,
7211 breakpoint.as_ref(),
7212 BreakpointPromptEditAction::Condition,
7213 window,
7214 cx,
7215 );
7216 })
7217 .log_err();
7218 }
7219 })
7220 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7221 weak_editor
7222 .update(cx, |this, cx| {
7223 this.add_edit_breakpoint_block(
7224 anchor,
7225 breakpoint.as_ref(),
7226 BreakpointPromptEditAction::HitCondition,
7227 window,
7228 cx,
7229 );
7230 })
7231 .log_err();
7232 })
7233 })
7234 }
7235
7236 fn render_breakpoint(
7237 &self,
7238 position: Anchor,
7239 row: DisplayRow,
7240 breakpoint: &Breakpoint,
7241 state: Option<BreakpointSessionState>,
7242 cx: &mut Context<Self>,
7243 ) -> IconButton {
7244 let is_rejected = state.is_some_and(|s| !s.verified);
7245 // Is it a breakpoint that shows up when hovering over gutter?
7246 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7247 (false, false),
7248 |PhantomBreakpointIndicator {
7249 is_active,
7250 display_row,
7251 collides_with_existing_breakpoint,
7252 }| {
7253 (
7254 is_active && display_row == row,
7255 collides_with_existing_breakpoint,
7256 )
7257 },
7258 );
7259
7260 let (color, icon) = {
7261 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7262 (false, false) => ui::IconName::DebugBreakpoint,
7263 (true, false) => ui::IconName::DebugLogBreakpoint,
7264 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7265 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7266 };
7267
7268 let color = if is_phantom {
7269 Color::Hint
7270 } else if is_rejected {
7271 Color::Disabled
7272 } else {
7273 Color::Debugger
7274 };
7275
7276 (color, icon)
7277 };
7278
7279 let breakpoint = Arc::from(breakpoint.clone());
7280
7281 let alt_as_text = gpui::Keystroke {
7282 modifiers: Modifiers::secondary_key(),
7283 ..Default::default()
7284 };
7285 let primary_action_text = if breakpoint.is_disabled() {
7286 "Enable breakpoint"
7287 } else if is_phantom && !collides_with_existing {
7288 "Set breakpoint"
7289 } else {
7290 "Unset breakpoint"
7291 };
7292 let focus_handle = self.focus_handle.clone();
7293
7294 let meta = if is_rejected {
7295 SharedString::from("No executable code is associated with this line.")
7296 } else if collides_with_existing && !breakpoint.is_disabled() {
7297 SharedString::from(format!(
7298 "{alt_as_text}-click to disable,\nright-click for more options."
7299 ))
7300 } else {
7301 SharedString::from("Right-click for more options.")
7302 };
7303 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7304 .icon_size(IconSize::XSmall)
7305 .size(ui::ButtonSize::None)
7306 .when(is_rejected, |this| {
7307 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7308 })
7309 .icon_color(color)
7310 .style(ButtonStyle::Transparent)
7311 .on_click(cx.listener({
7312 let breakpoint = breakpoint.clone();
7313
7314 move |editor, event: &ClickEvent, window, cx| {
7315 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7316 BreakpointEditAction::InvertState
7317 } else {
7318 BreakpointEditAction::Toggle
7319 };
7320
7321 window.focus(&editor.focus_handle(cx));
7322 editor.edit_breakpoint_at_anchor(
7323 position,
7324 breakpoint.as_ref().clone(),
7325 edit_action,
7326 cx,
7327 );
7328 }
7329 }))
7330 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7331 editor.set_breakpoint_context_menu(
7332 row,
7333 Some(position),
7334 event.down.position,
7335 window,
7336 cx,
7337 );
7338 }))
7339 .tooltip(move |window, cx| {
7340 Tooltip::with_meta_in(
7341 primary_action_text,
7342 Some(&ToggleBreakpoint),
7343 meta.clone(),
7344 &focus_handle,
7345 window,
7346 cx,
7347 )
7348 })
7349 }
7350
7351 fn build_tasks_context(
7352 project: &Entity<Project>,
7353 buffer: &Entity<Buffer>,
7354 buffer_row: u32,
7355 tasks: &Arc<RunnableTasks>,
7356 cx: &mut Context<Self>,
7357 ) -> Task<Option<task::TaskContext>> {
7358 let position = Point::new(buffer_row, tasks.column);
7359 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7360 let location = Location {
7361 buffer: buffer.clone(),
7362 range: range_start..range_start,
7363 };
7364 // Fill in the environmental variables from the tree-sitter captures
7365 let mut captured_task_variables = TaskVariables::default();
7366 for (capture_name, value) in tasks.extra_variables.clone() {
7367 captured_task_variables.insert(
7368 task::VariableName::Custom(capture_name.into()),
7369 value.clone(),
7370 );
7371 }
7372 project.update(cx, |project, cx| {
7373 project.task_store().update(cx, |task_store, cx| {
7374 task_store.task_context_for_location(captured_task_variables, location, cx)
7375 })
7376 })
7377 }
7378
7379 pub fn spawn_nearest_task(
7380 &mut self,
7381 action: &SpawnNearestTask,
7382 window: &mut Window,
7383 cx: &mut Context<Self>,
7384 ) {
7385 let Some((workspace, _)) = self.workspace.clone() else {
7386 return;
7387 };
7388 let Some(project) = self.project.clone() else {
7389 return;
7390 };
7391
7392 // Try to find a closest, enclosing node using tree-sitter that has a
7393 // task
7394 let Some((buffer, buffer_row, tasks)) = self
7395 .find_enclosing_node_task(cx)
7396 // Or find the task that's closest in row-distance.
7397 .or_else(|| self.find_closest_task(cx))
7398 else {
7399 return;
7400 };
7401
7402 let reveal_strategy = action.reveal;
7403 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7404 cx.spawn_in(window, async move |_, cx| {
7405 let context = task_context.await?;
7406 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7407
7408 let resolved = &mut resolved_task.resolved;
7409 resolved.reveal = reveal_strategy;
7410
7411 workspace
7412 .update_in(cx, |workspace, window, cx| {
7413 workspace.schedule_resolved_task(
7414 task_source_kind,
7415 resolved_task,
7416 false,
7417 window,
7418 cx,
7419 );
7420 })
7421 .ok()
7422 })
7423 .detach();
7424 }
7425
7426 fn find_closest_task(
7427 &mut self,
7428 cx: &mut Context<Self>,
7429 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7430 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7431
7432 let ((buffer_id, row), tasks) = self
7433 .tasks
7434 .iter()
7435 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7436
7437 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7438 let tasks = Arc::new(tasks.to_owned());
7439 Some((buffer, *row, tasks))
7440 }
7441
7442 fn find_enclosing_node_task(
7443 &mut self,
7444 cx: &mut Context<Self>,
7445 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7446 let snapshot = self.buffer.read(cx).snapshot(cx);
7447 let offset = self.selections.newest::<usize>(cx).head();
7448 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7449 let buffer_id = excerpt.buffer().remote_id();
7450
7451 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7452 let mut cursor = layer.node().walk();
7453
7454 while cursor.goto_first_child_for_byte(offset).is_some() {
7455 if cursor.node().end_byte() == offset {
7456 cursor.goto_next_sibling();
7457 }
7458 }
7459
7460 // Ascend to the smallest ancestor that contains the range and has a task.
7461 loop {
7462 let node = cursor.node();
7463 let node_range = node.byte_range();
7464 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7465
7466 // Check if this node contains our offset
7467 if node_range.start <= offset && node_range.end >= offset {
7468 // If it contains offset, check for task
7469 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7470 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7471 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7472 }
7473 }
7474
7475 if !cursor.goto_parent() {
7476 break;
7477 }
7478 }
7479 None
7480 }
7481
7482 fn render_run_indicator(
7483 &self,
7484 _style: &EditorStyle,
7485 is_active: bool,
7486 row: DisplayRow,
7487 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7488 cx: &mut Context<Self>,
7489 ) -> IconButton {
7490 let color = Color::Muted;
7491 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7492
7493 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7494 .shape(ui::IconButtonShape::Square)
7495 .icon_size(IconSize::XSmall)
7496 .icon_color(color)
7497 .toggle_state(is_active)
7498 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7499 let quick_launch = e.down.button == MouseButton::Left;
7500 window.focus(&editor.focus_handle(cx));
7501 editor.toggle_code_actions(
7502 &ToggleCodeActions {
7503 deployed_from_indicator: Some(row),
7504 quick_launch,
7505 },
7506 window,
7507 cx,
7508 );
7509 }))
7510 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7511 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7512 }))
7513 }
7514
7515 pub fn context_menu_visible(&self) -> bool {
7516 !self.edit_prediction_preview_is_active()
7517 && self
7518 .context_menu
7519 .borrow()
7520 .as_ref()
7521 .map_or(false, |menu| menu.visible())
7522 }
7523
7524 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7525 self.context_menu
7526 .borrow()
7527 .as_ref()
7528 .map(|menu| menu.origin())
7529 }
7530
7531 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7532 self.context_menu_options = Some(options);
7533 }
7534
7535 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7536 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7537
7538 fn render_edit_prediction_popover(
7539 &mut self,
7540 text_bounds: &Bounds<Pixels>,
7541 content_origin: gpui::Point<Pixels>,
7542 right_margin: Pixels,
7543 editor_snapshot: &EditorSnapshot,
7544 visible_row_range: Range<DisplayRow>,
7545 scroll_top: f32,
7546 scroll_bottom: f32,
7547 line_layouts: &[LineWithInvisibles],
7548 line_height: Pixels,
7549 scroll_pixel_position: gpui::Point<Pixels>,
7550 newest_selection_head: Option<DisplayPoint>,
7551 editor_width: Pixels,
7552 style: &EditorStyle,
7553 window: &mut Window,
7554 cx: &mut App,
7555 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7556 if self.mode().is_minimap() {
7557 return None;
7558 }
7559 let active_inline_completion = self.active_inline_completion.as_ref()?;
7560
7561 if self.edit_prediction_visible_in_cursor_popover(true) {
7562 return None;
7563 }
7564
7565 match &active_inline_completion.completion {
7566 InlineCompletion::Move { target, .. } => {
7567 let target_display_point = target.to_display_point(editor_snapshot);
7568
7569 if self.edit_prediction_requires_modifier() {
7570 if !self.edit_prediction_preview_is_active() {
7571 return None;
7572 }
7573
7574 self.render_edit_prediction_modifier_jump_popover(
7575 text_bounds,
7576 content_origin,
7577 visible_row_range,
7578 line_layouts,
7579 line_height,
7580 scroll_pixel_position,
7581 newest_selection_head,
7582 target_display_point,
7583 window,
7584 cx,
7585 )
7586 } else {
7587 self.render_edit_prediction_eager_jump_popover(
7588 text_bounds,
7589 content_origin,
7590 editor_snapshot,
7591 visible_row_range,
7592 scroll_top,
7593 scroll_bottom,
7594 line_height,
7595 scroll_pixel_position,
7596 target_display_point,
7597 editor_width,
7598 window,
7599 cx,
7600 )
7601 }
7602 }
7603 InlineCompletion::Edit {
7604 display_mode: EditDisplayMode::Inline,
7605 ..
7606 } => None,
7607 InlineCompletion::Edit {
7608 display_mode: EditDisplayMode::TabAccept,
7609 edits,
7610 ..
7611 } => {
7612 let range = &edits.first()?.0;
7613 let target_display_point = range.end.to_display_point(editor_snapshot);
7614
7615 self.render_edit_prediction_end_of_line_popover(
7616 "Accept",
7617 editor_snapshot,
7618 visible_row_range,
7619 target_display_point,
7620 line_height,
7621 scroll_pixel_position,
7622 content_origin,
7623 editor_width,
7624 window,
7625 cx,
7626 )
7627 }
7628 InlineCompletion::Edit {
7629 edits,
7630 edit_preview,
7631 display_mode: EditDisplayMode::DiffPopover,
7632 snapshot,
7633 } => self.render_edit_prediction_diff_popover(
7634 text_bounds,
7635 content_origin,
7636 right_margin,
7637 editor_snapshot,
7638 visible_row_range,
7639 line_layouts,
7640 line_height,
7641 scroll_pixel_position,
7642 newest_selection_head,
7643 editor_width,
7644 style,
7645 edits,
7646 edit_preview,
7647 snapshot,
7648 window,
7649 cx,
7650 ),
7651 }
7652 }
7653
7654 fn render_edit_prediction_modifier_jump_popover(
7655 &mut self,
7656 text_bounds: &Bounds<Pixels>,
7657 content_origin: gpui::Point<Pixels>,
7658 visible_row_range: Range<DisplayRow>,
7659 line_layouts: &[LineWithInvisibles],
7660 line_height: Pixels,
7661 scroll_pixel_position: gpui::Point<Pixels>,
7662 newest_selection_head: Option<DisplayPoint>,
7663 target_display_point: DisplayPoint,
7664 window: &mut Window,
7665 cx: &mut App,
7666 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7667 let scrolled_content_origin =
7668 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7669
7670 const SCROLL_PADDING_Y: Pixels = px(12.);
7671
7672 if target_display_point.row() < visible_row_range.start {
7673 return self.render_edit_prediction_scroll_popover(
7674 |_| SCROLL_PADDING_Y,
7675 IconName::ArrowUp,
7676 visible_row_range,
7677 line_layouts,
7678 newest_selection_head,
7679 scrolled_content_origin,
7680 window,
7681 cx,
7682 );
7683 } else if target_display_point.row() >= visible_row_range.end {
7684 return self.render_edit_prediction_scroll_popover(
7685 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7686 IconName::ArrowDown,
7687 visible_row_range,
7688 line_layouts,
7689 newest_selection_head,
7690 scrolled_content_origin,
7691 window,
7692 cx,
7693 );
7694 }
7695
7696 const POLE_WIDTH: Pixels = px(2.);
7697
7698 let line_layout =
7699 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7700 let target_column = target_display_point.column() as usize;
7701
7702 let target_x = line_layout.x_for_index(target_column);
7703 let target_y =
7704 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7705
7706 let flag_on_right = target_x < text_bounds.size.width / 2.;
7707
7708 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7709 border_color.l += 0.001;
7710
7711 let mut element = v_flex()
7712 .items_end()
7713 .when(flag_on_right, |el| el.items_start())
7714 .child(if flag_on_right {
7715 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7716 .rounded_bl(px(0.))
7717 .rounded_tl(px(0.))
7718 .border_l_2()
7719 .border_color(border_color)
7720 } else {
7721 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7722 .rounded_br(px(0.))
7723 .rounded_tr(px(0.))
7724 .border_r_2()
7725 .border_color(border_color)
7726 })
7727 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7728 .into_any();
7729
7730 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7731
7732 let mut origin = scrolled_content_origin + point(target_x, target_y)
7733 - point(
7734 if flag_on_right {
7735 POLE_WIDTH
7736 } else {
7737 size.width - POLE_WIDTH
7738 },
7739 size.height - line_height,
7740 );
7741
7742 origin.x = origin.x.max(content_origin.x);
7743
7744 element.prepaint_at(origin, window, cx);
7745
7746 Some((element, origin))
7747 }
7748
7749 fn render_edit_prediction_scroll_popover(
7750 &mut self,
7751 to_y: impl Fn(Size<Pixels>) -> Pixels,
7752 scroll_icon: IconName,
7753 visible_row_range: Range<DisplayRow>,
7754 line_layouts: &[LineWithInvisibles],
7755 newest_selection_head: Option<DisplayPoint>,
7756 scrolled_content_origin: gpui::Point<Pixels>,
7757 window: &mut Window,
7758 cx: &mut App,
7759 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7760 let mut element = self
7761 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7762 .into_any();
7763
7764 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7765
7766 let cursor = newest_selection_head?;
7767 let cursor_row_layout =
7768 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7769 let cursor_column = cursor.column() as usize;
7770
7771 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7772
7773 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7774
7775 element.prepaint_at(origin, window, cx);
7776 Some((element, origin))
7777 }
7778
7779 fn render_edit_prediction_eager_jump_popover(
7780 &mut self,
7781 text_bounds: &Bounds<Pixels>,
7782 content_origin: gpui::Point<Pixels>,
7783 editor_snapshot: &EditorSnapshot,
7784 visible_row_range: Range<DisplayRow>,
7785 scroll_top: f32,
7786 scroll_bottom: f32,
7787 line_height: Pixels,
7788 scroll_pixel_position: gpui::Point<Pixels>,
7789 target_display_point: DisplayPoint,
7790 editor_width: Pixels,
7791 window: &mut Window,
7792 cx: &mut App,
7793 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7794 if target_display_point.row().as_f32() < scroll_top {
7795 let mut element = self
7796 .render_edit_prediction_line_popover(
7797 "Jump to Edit",
7798 Some(IconName::ArrowUp),
7799 window,
7800 cx,
7801 )?
7802 .into_any();
7803
7804 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7805 let offset = point(
7806 (text_bounds.size.width - size.width) / 2.,
7807 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7808 );
7809
7810 let origin = text_bounds.origin + offset;
7811 element.prepaint_at(origin, window, cx);
7812 Some((element, origin))
7813 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7814 let mut element = self
7815 .render_edit_prediction_line_popover(
7816 "Jump to Edit",
7817 Some(IconName::ArrowDown),
7818 window,
7819 cx,
7820 )?
7821 .into_any();
7822
7823 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7824 let offset = point(
7825 (text_bounds.size.width - size.width) / 2.,
7826 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7827 );
7828
7829 let origin = text_bounds.origin + offset;
7830 element.prepaint_at(origin, window, cx);
7831 Some((element, origin))
7832 } else {
7833 self.render_edit_prediction_end_of_line_popover(
7834 "Jump to Edit",
7835 editor_snapshot,
7836 visible_row_range,
7837 target_display_point,
7838 line_height,
7839 scroll_pixel_position,
7840 content_origin,
7841 editor_width,
7842 window,
7843 cx,
7844 )
7845 }
7846 }
7847
7848 fn render_edit_prediction_end_of_line_popover(
7849 self: &mut Editor,
7850 label: &'static str,
7851 editor_snapshot: &EditorSnapshot,
7852 visible_row_range: Range<DisplayRow>,
7853 target_display_point: DisplayPoint,
7854 line_height: Pixels,
7855 scroll_pixel_position: gpui::Point<Pixels>,
7856 content_origin: gpui::Point<Pixels>,
7857 editor_width: Pixels,
7858 window: &mut Window,
7859 cx: &mut App,
7860 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7861 let target_line_end = DisplayPoint::new(
7862 target_display_point.row(),
7863 editor_snapshot.line_len(target_display_point.row()),
7864 );
7865
7866 let mut element = self
7867 .render_edit_prediction_line_popover(label, None, window, cx)?
7868 .into_any();
7869
7870 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7871
7872 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7873
7874 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7875 let mut origin = start_point
7876 + line_origin
7877 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7878 origin.x = origin.x.max(content_origin.x);
7879
7880 let max_x = content_origin.x + editor_width - size.width;
7881
7882 if origin.x > max_x {
7883 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7884
7885 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7886 origin.y += offset;
7887 IconName::ArrowUp
7888 } else {
7889 origin.y -= offset;
7890 IconName::ArrowDown
7891 };
7892
7893 element = self
7894 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7895 .into_any();
7896
7897 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7898
7899 origin.x = content_origin.x + editor_width - size.width - px(2.);
7900 }
7901
7902 element.prepaint_at(origin, window, cx);
7903 Some((element, origin))
7904 }
7905
7906 fn render_edit_prediction_diff_popover(
7907 self: &Editor,
7908 text_bounds: &Bounds<Pixels>,
7909 content_origin: gpui::Point<Pixels>,
7910 right_margin: Pixels,
7911 editor_snapshot: &EditorSnapshot,
7912 visible_row_range: Range<DisplayRow>,
7913 line_layouts: &[LineWithInvisibles],
7914 line_height: Pixels,
7915 scroll_pixel_position: gpui::Point<Pixels>,
7916 newest_selection_head: Option<DisplayPoint>,
7917 editor_width: Pixels,
7918 style: &EditorStyle,
7919 edits: &Vec<(Range<Anchor>, String)>,
7920 edit_preview: &Option<language::EditPreview>,
7921 snapshot: &language::BufferSnapshot,
7922 window: &mut Window,
7923 cx: &mut App,
7924 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7925 let edit_start = edits
7926 .first()
7927 .unwrap()
7928 .0
7929 .start
7930 .to_display_point(editor_snapshot);
7931 let edit_end = edits
7932 .last()
7933 .unwrap()
7934 .0
7935 .end
7936 .to_display_point(editor_snapshot);
7937
7938 let is_visible = visible_row_range.contains(&edit_start.row())
7939 || visible_row_range.contains(&edit_end.row());
7940 if !is_visible {
7941 return None;
7942 }
7943
7944 let highlighted_edits =
7945 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7946
7947 let styled_text = highlighted_edits.to_styled_text(&style.text);
7948 let line_count = highlighted_edits.text.lines().count();
7949
7950 const BORDER_WIDTH: Pixels = px(1.);
7951
7952 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7953 let has_keybind = keybind.is_some();
7954
7955 let mut element = h_flex()
7956 .items_start()
7957 .child(
7958 h_flex()
7959 .bg(cx.theme().colors().editor_background)
7960 .border(BORDER_WIDTH)
7961 .shadow_sm()
7962 .border_color(cx.theme().colors().border)
7963 .rounded_l_lg()
7964 .when(line_count > 1, |el| el.rounded_br_lg())
7965 .pr_1()
7966 .child(styled_text),
7967 )
7968 .child(
7969 h_flex()
7970 .h(line_height + BORDER_WIDTH * 2.)
7971 .px_1p5()
7972 .gap_1()
7973 // Workaround: For some reason, there's a gap if we don't do this
7974 .ml(-BORDER_WIDTH)
7975 .shadow(smallvec![gpui::BoxShadow {
7976 color: gpui::black().opacity(0.05),
7977 offset: point(px(1.), px(1.)),
7978 blur_radius: px(2.),
7979 spread_radius: px(0.),
7980 }])
7981 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7982 .border(BORDER_WIDTH)
7983 .border_color(cx.theme().colors().border)
7984 .rounded_r_lg()
7985 .id("edit_prediction_diff_popover_keybind")
7986 .when(!has_keybind, |el| {
7987 let status_colors = cx.theme().status();
7988
7989 el.bg(status_colors.error_background)
7990 .border_color(status_colors.error.opacity(0.6))
7991 .child(Icon::new(IconName::Info).color(Color::Error))
7992 .cursor_default()
7993 .hoverable_tooltip(move |_window, cx| {
7994 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7995 })
7996 })
7997 .children(keybind),
7998 )
7999 .into_any();
8000
8001 let longest_row =
8002 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8003 let longest_line_width = if visible_row_range.contains(&longest_row) {
8004 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8005 } else {
8006 layout_line(
8007 longest_row,
8008 editor_snapshot,
8009 style,
8010 editor_width,
8011 |_| false,
8012 window,
8013 cx,
8014 )
8015 .width
8016 };
8017
8018 let viewport_bounds =
8019 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8020 right: -right_margin,
8021 ..Default::default()
8022 });
8023
8024 let x_after_longest =
8025 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8026 - scroll_pixel_position.x;
8027
8028 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8029
8030 // Fully visible if it can be displayed within the window (allow overlapping other
8031 // panes). However, this is only allowed if the popover starts within text_bounds.
8032 let can_position_to_the_right = x_after_longest < text_bounds.right()
8033 && x_after_longest + element_bounds.width < viewport_bounds.right();
8034
8035 let mut origin = if can_position_to_the_right {
8036 point(
8037 x_after_longest,
8038 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8039 - scroll_pixel_position.y,
8040 )
8041 } else {
8042 let cursor_row = newest_selection_head.map(|head| head.row());
8043 let above_edit = edit_start
8044 .row()
8045 .0
8046 .checked_sub(line_count as u32)
8047 .map(DisplayRow);
8048 let below_edit = Some(edit_end.row() + 1);
8049 let above_cursor =
8050 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8051 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8052
8053 // Place the edit popover adjacent to the edit if there is a location
8054 // available that is onscreen and does not obscure the cursor. Otherwise,
8055 // place it adjacent to the cursor.
8056 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8057 .into_iter()
8058 .flatten()
8059 .find(|&start_row| {
8060 let end_row = start_row + line_count as u32;
8061 visible_row_range.contains(&start_row)
8062 && visible_row_range.contains(&end_row)
8063 && cursor_row.map_or(true, |cursor_row| {
8064 !((start_row..end_row).contains(&cursor_row))
8065 })
8066 })?;
8067
8068 content_origin
8069 + point(
8070 -scroll_pixel_position.x,
8071 row_target.as_f32() * line_height - scroll_pixel_position.y,
8072 )
8073 };
8074
8075 origin.x -= BORDER_WIDTH;
8076
8077 window.defer_draw(element, origin, 1);
8078
8079 // Do not return an element, since it will already be drawn due to defer_draw.
8080 None
8081 }
8082
8083 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8084 px(30.)
8085 }
8086
8087 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8088 if self.read_only(cx) {
8089 cx.theme().players().read_only()
8090 } else {
8091 self.style.as_ref().unwrap().local_player
8092 }
8093 }
8094
8095 fn render_edit_prediction_accept_keybind(
8096 &self,
8097 window: &mut Window,
8098 cx: &App,
8099 ) -> Option<AnyElement> {
8100 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
8101 let accept_keystroke = accept_binding.keystroke()?;
8102
8103 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8104
8105 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8106 Color::Accent
8107 } else {
8108 Color::Muted
8109 };
8110
8111 h_flex()
8112 .px_0p5()
8113 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8114 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8115 .text_size(TextSize::XSmall.rems(cx))
8116 .child(h_flex().children(ui::render_modifiers(
8117 &accept_keystroke.modifiers,
8118 PlatformStyle::platform(),
8119 Some(modifiers_color),
8120 Some(IconSize::XSmall.rems().into()),
8121 true,
8122 )))
8123 .when(is_platform_style_mac, |parent| {
8124 parent.child(accept_keystroke.key.clone())
8125 })
8126 .when(!is_platform_style_mac, |parent| {
8127 parent.child(
8128 Key::new(
8129 util::capitalize(&accept_keystroke.key),
8130 Some(Color::Default),
8131 )
8132 .size(Some(IconSize::XSmall.rems().into())),
8133 )
8134 })
8135 .into_any()
8136 .into()
8137 }
8138
8139 fn render_edit_prediction_line_popover(
8140 &self,
8141 label: impl Into<SharedString>,
8142 icon: Option<IconName>,
8143 window: &mut Window,
8144 cx: &App,
8145 ) -> Option<Stateful<Div>> {
8146 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8147
8148 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8149 let has_keybind = keybind.is_some();
8150
8151 let result = h_flex()
8152 .id("ep-line-popover")
8153 .py_0p5()
8154 .pl_1()
8155 .pr(padding_right)
8156 .gap_1()
8157 .rounded_md()
8158 .border_1()
8159 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8160 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8161 .shadow_sm()
8162 .when(!has_keybind, |el| {
8163 let status_colors = cx.theme().status();
8164
8165 el.bg(status_colors.error_background)
8166 .border_color(status_colors.error.opacity(0.6))
8167 .pl_2()
8168 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8169 .cursor_default()
8170 .hoverable_tooltip(move |_window, cx| {
8171 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8172 })
8173 })
8174 .children(keybind)
8175 .child(
8176 Label::new(label)
8177 .size(LabelSize::Small)
8178 .when(!has_keybind, |el| {
8179 el.color(cx.theme().status().error.into()).strikethrough()
8180 }),
8181 )
8182 .when(!has_keybind, |el| {
8183 el.child(
8184 h_flex().ml_1().child(
8185 Icon::new(IconName::Info)
8186 .size(IconSize::Small)
8187 .color(cx.theme().status().error.into()),
8188 ),
8189 )
8190 })
8191 .when_some(icon, |element, icon| {
8192 element.child(
8193 div()
8194 .mt(px(1.5))
8195 .child(Icon::new(icon).size(IconSize::Small)),
8196 )
8197 });
8198
8199 Some(result)
8200 }
8201
8202 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8203 let accent_color = cx.theme().colors().text_accent;
8204 let editor_bg_color = cx.theme().colors().editor_background;
8205 editor_bg_color.blend(accent_color.opacity(0.1))
8206 }
8207
8208 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8209 let accent_color = cx.theme().colors().text_accent;
8210 let editor_bg_color = cx.theme().colors().editor_background;
8211 editor_bg_color.blend(accent_color.opacity(0.6))
8212 }
8213
8214 fn render_edit_prediction_cursor_popover(
8215 &self,
8216 min_width: Pixels,
8217 max_width: Pixels,
8218 cursor_point: Point,
8219 style: &EditorStyle,
8220 accept_keystroke: Option<&gpui::Keystroke>,
8221 _window: &Window,
8222 cx: &mut Context<Editor>,
8223 ) -> Option<AnyElement> {
8224 let provider = self.edit_prediction_provider.as_ref()?;
8225
8226 if provider.provider.needs_terms_acceptance(cx) {
8227 return Some(
8228 h_flex()
8229 .min_w(min_width)
8230 .flex_1()
8231 .px_2()
8232 .py_1()
8233 .gap_3()
8234 .elevation_2(cx)
8235 .hover(|style| style.bg(cx.theme().colors().element_hover))
8236 .id("accept-terms")
8237 .cursor_pointer()
8238 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8239 .on_click(cx.listener(|this, _event, window, cx| {
8240 cx.stop_propagation();
8241 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8242 window.dispatch_action(
8243 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8244 cx,
8245 );
8246 }))
8247 .child(
8248 h_flex()
8249 .flex_1()
8250 .gap_2()
8251 .child(Icon::new(IconName::ZedPredict))
8252 .child(Label::new("Accept Terms of Service"))
8253 .child(div().w_full())
8254 .child(
8255 Icon::new(IconName::ArrowUpRight)
8256 .color(Color::Muted)
8257 .size(IconSize::Small),
8258 )
8259 .into_any_element(),
8260 )
8261 .into_any(),
8262 );
8263 }
8264
8265 let is_refreshing = provider.provider.is_refreshing(cx);
8266
8267 fn pending_completion_container() -> Div {
8268 h_flex()
8269 .h_full()
8270 .flex_1()
8271 .gap_2()
8272 .child(Icon::new(IconName::ZedPredict))
8273 }
8274
8275 let completion = match &self.active_inline_completion {
8276 Some(prediction) => {
8277 if !self.has_visible_completions_menu() {
8278 const RADIUS: Pixels = px(6.);
8279 const BORDER_WIDTH: Pixels = px(1.);
8280
8281 return Some(
8282 h_flex()
8283 .elevation_2(cx)
8284 .border(BORDER_WIDTH)
8285 .border_color(cx.theme().colors().border)
8286 .when(accept_keystroke.is_none(), |el| {
8287 el.border_color(cx.theme().status().error)
8288 })
8289 .rounded(RADIUS)
8290 .rounded_tl(px(0.))
8291 .overflow_hidden()
8292 .child(div().px_1p5().child(match &prediction.completion {
8293 InlineCompletion::Move { target, snapshot } => {
8294 use text::ToPoint as _;
8295 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8296 {
8297 Icon::new(IconName::ZedPredictDown)
8298 } else {
8299 Icon::new(IconName::ZedPredictUp)
8300 }
8301 }
8302 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8303 }))
8304 .child(
8305 h_flex()
8306 .gap_1()
8307 .py_1()
8308 .px_2()
8309 .rounded_r(RADIUS - BORDER_WIDTH)
8310 .border_l_1()
8311 .border_color(cx.theme().colors().border)
8312 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8313 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8314 el.child(
8315 Label::new("Hold")
8316 .size(LabelSize::Small)
8317 .when(accept_keystroke.is_none(), |el| {
8318 el.strikethrough()
8319 })
8320 .line_height_style(LineHeightStyle::UiLabel),
8321 )
8322 })
8323 .id("edit_prediction_cursor_popover_keybind")
8324 .when(accept_keystroke.is_none(), |el| {
8325 let status_colors = cx.theme().status();
8326
8327 el.bg(status_colors.error_background)
8328 .border_color(status_colors.error.opacity(0.6))
8329 .child(Icon::new(IconName::Info).color(Color::Error))
8330 .cursor_default()
8331 .hoverable_tooltip(move |_window, cx| {
8332 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8333 .into()
8334 })
8335 })
8336 .when_some(
8337 accept_keystroke.as_ref(),
8338 |el, accept_keystroke| {
8339 el.child(h_flex().children(ui::render_modifiers(
8340 &accept_keystroke.modifiers,
8341 PlatformStyle::platform(),
8342 Some(Color::Default),
8343 Some(IconSize::XSmall.rems().into()),
8344 false,
8345 )))
8346 },
8347 ),
8348 )
8349 .into_any(),
8350 );
8351 }
8352
8353 self.render_edit_prediction_cursor_popover_preview(
8354 prediction,
8355 cursor_point,
8356 style,
8357 cx,
8358 )?
8359 }
8360
8361 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8362 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8363 stale_completion,
8364 cursor_point,
8365 style,
8366 cx,
8367 )?,
8368
8369 None => {
8370 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8371 }
8372 },
8373
8374 None => pending_completion_container().child(Label::new("No Prediction")),
8375 };
8376
8377 let completion = if is_refreshing {
8378 completion
8379 .with_animation(
8380 "loading-completion",
8381 Animation::new(Duration::from_secs(2))
8382 .repeat()
8383 .with_easing(pulsating_between(0.4, 0.8)),
8384 |label, delta| label.opacity(delta),
8385 )
8386 .into_any_element()
8387 } else {
8388 completion.into_any_element()
8389 };
8390
8391 let has_completion = self.active_inline_completion.is_some();
8392
8393 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8394 Some(
8395 h_flex()
8396 .min_w(min_width)
8397 .max_w(max_width)
8398 .flex_1()
8399 .elevation_2(cx)
8400 .border_color(cx.theme().colors().border)
8401 .child(
8402 div()
8403 .flex_1()
8404 .py_1()
8405 .px_2()
8406 .overflow_hidden()
8407 .child(completion),
8408 )
8409 .when_some(accept_keystroke, |el, accept_keystroke| {
8410 if !accept_keystroke.modifiers.modified() {
8411 return el;
8412 }
8413
8414 el.child(
8415 h_flex()
8416 .h_full()
8417 .border_l_1()
8418 .rounded_r_lg()
8419 .border_color(cx.theme().colors().border)
8420 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8421 .gap_1()
8422 .py_1()
8423 .px_2()
8424 .child(
8425 h_flex()
8426 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8427 .when(is_platform_style_mac, |parent| parent.gap_1())
8428 .child(h_flex().children(ui::render_modifiers(
8429 &accept_keystroke.modifiers,
8430 PlatformStyle::platform(),
8431 Some(if !has_completion {
8432 Color::Muted
8433 } else {
8434 Color::Default
8435 }),
8436 None,
8437 false,
8438 ))),
8439 )
8440 .child(Label::new("Preview").into_any_element())
8441 .opacity(if has_completion { 1.0 } else { 0.4 }),
8442 )
8443 })
8444 .into_any(),
8445 )
8446 }
8447
8448 fn render_edit_prediction_cursor_popover_preview(
8449 &self,
8450 completion: &InlineCompletionState,
8451 cursor_point: Point,
8452 style: &EditorStyle,
8453 cx: &mut Context<Editor>,
8454 ) -> Option<Div> {
8455 use text::ToPoint as _;
8456
8457 fn render_relative_row_jump(
8458 prefix: impl Into<String>,
8459 current_row: u32,
8460 target_row: u32,
8461 ) -> Div {
8462 let (row_diff, arrow) = if target_row < current_row {
8463 (current_row - target_row, IconName::ArrowUp)
8464 } else {
8465 (target_row - current_row, IconName::ArrowDown)
8466 };
8467
8468 h_flex()
8469 .child(
8470 Label::new(format!("{}{}", prefix.into(), row_diff))
8471 .color(Color::Muted)
8472 .size(LabelSize::Small),
8473 )
8474 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8475 }
8476
8477 match &completion.completion {
8478 InlineCompletion::Move {
8479 target, snapshot, ..
8480 } => Some(
8481 h_flex()
8482 .px_2()
8483 .gap_2()
8484 .flex_1()
8485 .child(
8486 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8487 Icon::new(IconName::ZedPredictDown)
8488 } else {
8489 Icon::new(IconName::ZedPredictUp)
8490 },
8491 )
8492 .child(Label::new("Jump to Edit")),
8493 ),
8494
8495 InlineCompletion::Edit {
8496 edits,
8497 edit_preview,
8498 snapshot,
8499 display_mode: _,
8500 } => {
8501 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8502
8503 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8504 &snapshot,
8505 &edits,
8506 edit_preview.as_ref()?,
8507 true,
8508 cx,
8509 )
8510 .first_line_preview();
8511
8512 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8513 .with_default_highlights(&style.text, highlighted_edits.highlights);
8514
8515 let preview = h_flex()
8516 .gap_1()
8517 .min_w_16()
8518 .child(styled_text)
8519 .when(has_more_lines, |parent| parent.child("…"));
8520
8521 let left = if first_edit_row != cursor_point.row {
8522 render_relative_row_jump("", cursor_point.row, first_edit_row)
8523 .into_any_element()
8524 } else {
8525 Icon::new(IconName::ZedPredict).into_any_element()
8526 };
8527
8528 Some(
8529 h_flex()
8530 .h_full()
8531 .flex_1()
8532 .gap_2()
8533 .pr_1()
8534 .overflow_x_hidden()
8535 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8536 .child(left)
8537 .child(preview),
8538 )
8539 }
8540 }
8541 }
8542
8543 fn render_context_menu(
8544 &self,
8545 style: &EditorStyle,
8546 max_height_in_lines: u32,
8547 window: &mut Window,
8548 cx: &mut Context<Editor>,
8549 ) -> Option<AnyElement> {
8550 let menu = self.context_menu.borrow();
8551 let menu = menu.as_ref()?;
8552 if !menu.visible() {
8553 return None;
8554 };
8555 Some(menu.render(style, max_height_in_lines, window, cx))
8556 }
8557
8558 fn render_context_menu_aside(
8559 &mut self,
8560 max_size: Size<Pixels>,
8561 window: &mut Window,
8562 cx: &mut Context<Editor>,
8563 ) -> Option<AnyElement> {
8564 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8565 if menu.visible() {
8566 menu.render_aside(self, max_size, window, cx)
8567 } else {
8568 None
8569 }
8570 })
8571 }
8572
8573 fn hide_context_menu(
8574 &mut self,
8575 window: &mut Window,
8576 cx: &mut Context<Self>,
8577 ) -> Option<CodeContextMenu> {
8578 cx.notify();
8579 self.completion_tasks.clear();
8580 let context_menu = self.context_menu.borrow_mut().take();
8581 self.stale_inline_completion_in_menu.take();
8582 self.update_visible_inline_completion(window, cx);
8583 context_menu
8584 }
8585
8586 fn show_snippet_choices(
8587 &mut self,
8588 choices: &Vec<String>,
8589 selection: Range<Anchor>,
8590 cx: &mut Context<Self>,
8591 ) {
8592 if selection.start.buffer_id.is_none() {
8593 return;
8594 }
8595 let buffer_id = selection.start.buffer_id.unwrap();
8596 let buffer = self.buffer().read(cx).buffer(buffer_id);
8597 let id = post_inc(&mut self.next_completion_id);
8598 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8599
8600 if let Some(buffer) = buffer {
8601 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8602 CompletionsMenu::new_snippet_choices(
8603 id,
8604 true,
8605 choices,
8606 selection,
8607 buffer,
8608 snippet_sort_order,
8609 ),
8610 ));
8611 }
8612 }
8613
8614 pub fn insert_snippet(
8615 &mut self,
8616 insertion_ranges: &[Range<usize>],
8617 snippet: Snippet,
8618 window: &mut Window,
8619 cx: &mut Context<Self>,
8620 ) -> Result<()> {
8621 struct Tabstop<T> {
8622 is_end_tabstop: bool,
8623 ranges: Vec<Range<T>>,
8624 choices: Option<Vec<String>>,
8625 }
8626
8627 let tabstops = self.buffer.update(cx, |buffer, cx| {
8628 let snippet_text: Arc<str> = snippet.text.clone().into();
8629 let edits = insertion_ranges
8630 .iter()
8631 .cloned()
8632 .map(|range| (range, snippet_text.clone()));
8633 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8634
8635 let snapshot = &*buffer.read(cx);
8636 let snippet = &snippet;
8637 snippet
8638 .tabstops
8639 .iter()
8640 .map(|tabstop| {
8641 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8642 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8643 });
8644 let mut tabstop_ranges = tabstop
8645 .ranges
8646 .iter()
8647 .flat_map(|tabstop_range| {
8648 let mut delta = 0_isize;
8649 insertion_ranges.iter().map(move |insertion_range| {
8650 let insertion_start = insertion_range.start as isize + delta;
8651 delta +=
8652 snippet.text.len() as isize - insertion_range.len() as isize;
8653
8654 let start = ((insertion_start + tabstop_range.start) as usize)
8655 .min(snapshot.len());
8656 let end = ((insertion_start + tabstop_range.end) as usize)
8657 .min(snapshot.len());
8658 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8659 })
8660 })
8661 .collect::<Vec<_>>();
8662 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8663
8664 Tabstop {
8665 is_end_tabstop,
8666 ranges: tabstop_ranges,
8667 choices: tabstop.choices.clone(),
8668 }
8669 })
8670 .collect::<Vec<_>>()
8671 });
8672 if let Some(tabstop) = tabstops.first() {
8673 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8674 s.select_ranges(tabstop.ranges.iter().cloned());
8675 });
8676
8677 if let Some(choices) = &tabstop.choices {
8678 if let Some(selection) = tabstop.ranges.first() {
8679 self.show_snippet_choices(choices, selection.clone(), cx)
8680 }
8681 }
8682
8683 // If we're already at the last tabstop and it's at the end of the snippet,
8684 // we're done, we don't need to keep the state around.
8685 if !tabstop.is_end_tabstop {
8686 let choices = tabstops
8687 .iter()
8688 .map(|tabstop| tabstop.choices.clone())
8689 .collect();
8690
8691 let ranges = tabstops
8692 .into_iter()
8693 .map(|tabstop| tabstop.ranges)
8694 .collect::<Vec<_>>();
8695
8696 self.snippet_stack.push(SnippetState {
8697 active_index: 0,
8698 ranges,
8699 choices,
8700 });
8701 }
8702
8703 // Check whether the just-entered snippet ends with an auto-closable bracket.
8704 if self.autoclose_regions.is_empty() {
8705 let snapshot = self.buffer.read(cx).snapshot(cx);
8706 for selection in &mut self.selections.all::<Point>(cx) {
8707 let selection_head = selection.head();
8708 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8709 continue;
8710 };
8711
8712 let mut bracket_pair = None;
8713 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8714 let prev_chars = snapshot
8715 .reversed_chars_at(selection_head)
8716 .collect::<String>();
8717 for (pair, enabled) in scope.brackets() {
8718 if enabled
8719 && pair.close
8720 && prev_chars.starts_with(pair.start.as_str())
8721 && next_chars.starts_with(pair.end.as_str())
8722 {
8723 bracket_pair = Some(pair.clone());
8724 break;
8725 }
8726 }
8727 if let Some(pair) = bracket_pair {
8728 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8729 let autoclose_enabled =
8730 self.use_autoclose && snapshot_settings.use_autoclose;
8731 if autoclose_enabled {
8732 let start = snapshot.anchor_after(selection_head);
8733 let end = snapshot.anchor_after(selection_head);
8734 self.autoclose_regions.push(AutocloseRegion {
8735 selection_id: selection.id,
8736 range: start..end,
8737 pair,
8738 });
8739 }
8740 }
8741 }
8742 }
8743 }
8744 Ok(())
8745 }
8746
8747 pub fn move_to_next_snippet_tabstop(
8748 &mut self,
8749 window: &mut Window,
8750 cx: &mut Context<Self>,
8751 ) -> bool {
8752 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8753 }
8754
8755 pub fn move_to_prev_snippet_tabstop(
8756 &mut self,
8757 window: &mut Window,
8758 cx: &mut Context<Self>,
8759 ) -> bool {
8760 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8761 }
8762
8763 pub fn move_to_snippet_tabstop(
8764 &mut self,
8765 bias: Bias,
8766 window: &mut Window,
8767 cx: &mut Context<Self>,
8768 ) -> bool {
8769 if let Some(mut snippet) = self.snippet_stack.pop() {
8770 match bias {
8771 Bias::Left => {
8772 if snippet.active_index > 0 {
8773 snippet.active_index -= 1;
8774 } else {
8775 self.snippet_stack.push(snippet);
8776 return false;
8777 }
8778 }
8779 Bias::Right => {
8780 if snippet.active_index + 1 < snippet.ranges.len() {
8781 snippet.active_index += 1;
8782 } else {
8783 self.snippet_stack.push(snippet);
8784 return false;
8785 }
8786 }
8787 }
8788 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8789 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8790 s.select_anchor_ranges(current_ranges.iter().cloned())
8791 });
8792
8793 if let Some(choices) = &snippet.choices[snippet.active_index] {
8794 if let Some(selection) = current_ranges.first() {
8795 self.show_snippet_choices(&choices, selection.clone(), cx);
8796 }
8797 }
8798
8799 // If snippet state is not at the last tabstop, push it back on the stack
8800 if snippet.active_index + 1 < snippet.ranges.len() {
8801 self.snippet_stack.push(snippet);
8802 }
8803 return true;
8804 }
8805 }
8806
8807 false
8808 }
8809
8810 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8811 self.transact(window, cx, |this, window, cx| {
8812 this.select_all(&SelectAll, window, cx);
8813 this.insert("", window, cx);
8814 });
8815 }
8816
8817 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8818 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8819 self.transact(window, cx, |this, window, cx| {
8820 this.select_autoclose_pair(window, cx);
8821 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8822 if !this.linked_edit_ranges.is_empty() {
8823 let selections = this.selections.all::<MultiBufferPoint>(cx);
8824 let snapshot = this.buffer.read(cx).snapshot(cx);
8825
8826 for selection in selections.iter() {
8827 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8828 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8829 if selection_start.buffer_id != selection_end.buffer_id {
8830 continue;
8831 }
8832 if let Some(ranges) =
8833 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8834 {
8835 for (buffer, entries) in ranges {
8836 linked_ranges.entry(buffer).or_default().extend(entries);
8837 }
8838 }
8839 }
8840 }
8841
8842 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8843 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8844 for selection in &mut selections {
8845 if selection.is_empty() {
8846 let old_head = selection.head();
8847 let mut new_head =
8848 movement::left(&display_map, old_head.to_display_point(&display_map))
8849 .to_point(&display_map);
8850 if let Some((buffer, line_buffer_range)) = display_map
8851 .buffer_snapshot
8852 .buffer_line_for_row(MultiBufferRow(old_head.row))
8853 {
8854 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8855 let indent_len = match indent_size.kind {
8856 IndentKind::Space => {
8857 buffer.settings_at(line_buffer_range.start, cx).tab_size
8858 }
8859 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8860 };
8861 if old_head.column <= indent_size.len && old_head.column > 0 {
8862 let indent_len = indent_len.get();
8863 new_head = cmp::min(
8864 new_head,
8865 MultiBufferPoint::new(
8866 old_head.row,
8867 ((old_head.column - 1) / indent_len) * indent_len,
8868 ),
8869 );
8870 }
8871 }
8872
8873 selection.set_head(new_head, SelectionGoal::None);
8874 }
8875 }
8876
8877 this.signature_help_state.set_backspace_pressed(true);
8878 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8879 s.select(selections)
8880 });
8881 this.insert("", window, cx);
8882 let empty_str: Arc<str> = Arc::from("");
8883 for (buffer, edits) in linked_ranges {
8884 let snapshot = buffer.read(cx).snapshot();
8885 use text::ToPoint as TP;
8886
8887 let edits = edits
8888 .into_iter()
8889 .map(|range| {
8890 let end_point = TP::to_point(&range.end, &snapshot);
8891 let mut start_point = TP::to_point(&range.start, &snapshot);
8892
8893 if end_point == start_point {
8894 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8895 .saturating_sub(1);
8896 start_point =
8897 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8898 };
8899
8900 (start_point..end_point, empty_str.clone())
8901 })
8902 .sorted_by_key(|(range, _)| range.start)
8903 .collect::<Vec<_>>();
8904 buffer.update(cx, |this, cx| {
8905 this.edit(edits, None, cx);
8906 })
8907 }
8908 this.refresh_inline_completion(true, false, window, cx);
8909 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8910 });
8911 }
8912
8913 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8914 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8915 self.transact(window, cx, |this, window, cx| {
8916 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8917 s.move_with(|map, selection| {
8918 if selection.is_empty() {
8919 let cursor = movement::right(map, selection.head());
8920 selection.end = cursor;
8921 selection.reversed = true;
8922 selection.goal = SelectionGoal::None;
8923 }
8924 })
8925 });
8926 this.insert("", window, cx);
8927 this.refresh_inline_completion(true, false, window, cx);
8928 });
8929 }
8930
8931 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8932 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8933 if self.move_to_prev_snippet_tabstop(window, cx) {
8934 return;
8935 }
8936 self.outdent(&Outdent, window, cx);
8937 }
8938
8939 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8940 if self.move_to_next_snippet_tabstop(window, cx) {
8941 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8942 return;
8943 }
8944 if self.read_only(cx) {
8945 return;
8946 }
8947 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8948 let mut selections = self.selections.all_adjusted(cx);
8949 let buffer = self.buffer.read(cx);
8950 let snapshot = buffer.snapshot(cx);
8951 let rows_iter = selections.iter().map(|s| s.head().row);
8952 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8953
8954 let has_some_cursor_in_whitespace = selections
8955 .iter()
8956 .filter(|selection| selection.is_empty())
8957 .any(|selection| {
8958 let cursor = selection.head();
8959 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8960 cursor.column < current_indent.len
8961 });
8962
8963 let mut edits = Vec::new();
8964 let mut prev_edited_row = 0;
8965 let mut row_delta = 0;
8966 for selection in &mut selections {
8967 if selection.start.row != prev_edited_row {
8968 row_delta = 0;
8969 }
8970 prev_edited_row = selection.end.row;
8971
8972 // If the selection is non-empty, then increase the indentation of the selected lines.
8973 if !selection.is_empty() {
8974 row_delta =
8975 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8976 continue;
8977 }
8978
8979 let cursor = selection.head();
8980 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8981 if let Some(suggested_indent) =
8982 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8983 {
8984 // Don't do anything if already at suggested indent
8985 // and there is any other cursor which is not
8986 if has_some_cursor_in_whitespace
8987 && cursor.column == current_indent.len
8988 && current_indent.len == suggested_indent.len
8989 {
8990 continue;
8991 }
8992
8993 // Adjust line and move cursor to suggested indent
8994 // if cursor is not at suggested indent
8995 if cursor.column < suggested_indent.len
8996 && cursor.column <= current_indent.len
8997 && current_indent.len <= suggested_indent.len
8998 {
8999 selection.start = Point::new(cursor.row, suggested_indent.len);
9000 selection.end = selection.start;
9001 if row_delta == 0 {
9002 edits.extend(Buffer::edit_for_indent_size_adjustment(
9003 cursor.row,
9004 current_indent,
9005 suggested_indent,
9006 ));
9007 row_delta = suggested_indent.len - current_indent.len;
9008 }
9009 continue;
9010 }
9011
9012 // If current indent is more than suggested indent
9013 // only move cursor to current indent and skip indent
9014 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9015 selection.start = Point::new(cursor.row, current_indent.len);
9016 selection.end = selection.start;
9017 continue;
9018 }
9019 }
9020
9021 // Otherwise, insert a hard or soft tab.
9022 let settings = buffer.language_settings_at(cursor, cx);
9023 let tab_size = if settings.hard_tabs {
9024 IndentSize::tab()
9025 } else {
9026 let tab_size = settings.tab_size.get();
9027 let indent_remainder = snapshot
9028 .text_for_range(Point::new(cursor.row, 0)..cursor)
9029 .flat_map(str::chars)
9030 .fold(row_delta % tab_size, |counter: u32, c| {
9031 if c == '\t' {
9032 0
9033 } else {
9034 (counter + 1) % tab_size
9035 }
9036 });
9037
9038 let chars_to_next_tab_stop = tab_size - indent_remainder;
9039 IndentSize::spaces(chars_to_next_tab_stop)
9040 };
9041 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9042 selection.end = selection.start;
9043 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9044 row_delta += tab_size.len;
9045 }
9046
9047 self.transact(window, cx, |this, window, cx| {
9048 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9049 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9050 s.select(selections)
9051 });
9052 this.refresh_inline_completion(true, false, window, cx);
9053 });
9054 }
9055
9056 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9057 if self.read_only(cx) {
9058 return;
9059 }
9060 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9061 let mut selections = self.selections.all::<Point>(cx);
9062 let mut prev_edited_row = 0;
9063 let mut row_delta = 0;
9064 let mut edits = Vec::new();
9065 let buffer = self.buffer.read(cx);
9066 let snapshot = buffer.snapshot(cx);
9067 for selection in &mut selections {
9068 if selection.start.row != prev_edited_row {
9069 row_delta = 0;
9070 }
9071 prev_edited_row = selection.end.row;
9072
9073 row_delta =
9074 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9075 }
9076
9077 self.transact(window, cx, |this, window, cx| {
9078 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9079 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9080 s.select(selections)
9081 });
9082 });
9083 }
9084
9085 fn indent_selection(
9086 buffer: &MultiBuffer,
9087 snapshot: &MultiBufferSnapshot,
9088 selection: &mut Selection<Point>,
9089 edits: &mut Vec<(Range<Point>, String)>,
9090 delta_for_start_row: u32,
9091 cx: &App,
9092 ) -> u32 {
9093 let settings = buffer.language_settings_at(selection.start, cx);
9094 let tab_size = settings.tab_size.get();
9095 let indent_kind = if settings.hard_tabs {
9096 IndentKind::Tab
9097 } else {
9098 IndentKind::Space
9099 };
9100 let mut start_row = selection.start.row;
9101 let mut end_row = selection.end.row + 1;
9102
9103 // If a selection ends at the beginning of a line, don't indent
9104 // that last line.
9105 if selection.end.column == 0 && selection.end.row > selection.start.row {
9106 end_row -= 1;
9107 }
9108
9109 // Avoid re-indenting a row that has already been indented by a
9110 // previous selection, but still update this selection's column
9111 // to reflect that indentation.
9112 if delta_for_start_row > 0 {
9113 start_row += 1;
9114 selection.start.column += delta_for_start_row;
9115 if selection.end.row == selection.start.row {
9116 selection.end.column += delta_for_start_row;
9117 }
9118 }
9119
9120 let mut delta_for_end_row = 0;
9121 let has_multiple_rows = start_row + 1 != end_row;
9122 for row in start_row..end_row {
9123 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9124 let indent_delta = match (current_indent.kind, indent_kind) {
9125 (IndentKind::Space, IndentKind::Space) => {
9126 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9127 IndentSize::spaces(columns_to_next_tab_stop)
9128 }
9129 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9130 (_, IndentKind::Tab) => IndentSize::tab(),
9131 };
9132
9133 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9134 0
9135 } else {
9136 selection.start.column
9137 };
9138 let row_start = Point::new(row, start);
9139 edits.push((
9140 row_start..row_start,
9141 indent_delta.chars().collect::<String>(),
9142 ));
9143
9144 // Update this selection's endpoints to reflect the indentation.
9145 if row == selection.start.row {
9146 selection.start.column += indent_delta.len;
9147 }
9148 if row == selection.end.row {
9149 selection.end.column += indent_delta.len;
9150 delta_for_end_row = indent_delta.len;
9151 }
9152 }
9153
9154 if selection.start.row == selection.end.row {
9155 delta_for_start_row + delta_for_end_row
9156 } else {
9157 delta_for_end_row
9158 }
9159 }
9160
9161 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9162 if self.read_only(cx) {
9163 return;
9164 }
9165 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9166 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9167 let selections = self.selections.all::<Point>(cx);
9168 let mut deletion_ranges = Vec::new();
9169 let mut last_outdent = None;
9170 {
9171 let buffer = self.buffer.read(cx);
9172 let snapshot = buffer.snapshot(cx);
9173 for selection in &selections {
9174 let settings = buffer.language_settings_at(selection.start, cx);
9175 let tab_size = settings.tab_size.get();
9176 let mut rows = selection.spanned_rows(false, &display_map);
9177
9178 // Avoid re-outdenting a row that has already been outdented by a
9179 // previous selection.
9180 if let Some(last_row) = last_outdent {
9181 if last_row == rows.start {
9182 rows.start = rows.start.next_row();
9183 }
9184 }
9185 let has_multiple_rows = rows.len() > 1;
9186 for row in rows.iter_rows() {
9187 let indent_size = snapshot.indent_size_for_line(row);
9188 if indent_size.len > 0 {
9189 let deletion_len = match indent_size.kind {
9190 IndentKind::Space => {
9191 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9192 if columns_to_prev_tab_stop == 0 {
9193 tab_size
9194 } else {
9195 columns_to_prev_tab_stop
9196 }
9197 }
9198 IndentKind::Tab => 1,
9199 };
9200 let start = if has_multiple_rows
9201 || deletion_len > selection.start.column
9202 || indent_size.len < selection.start.column
9203 {
9204 0
9205 } else {
9206 selection.start.column - deletion_len
9207 };
9208 deletion_ranges.push(
9209 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9210 );
9211 last_outdent = Some(row);
9212 }
9213 }
9214 }
9215 }
9216
9217 self.transact(window, cx, |this, window, cx| {
9218 this.buffer.update(cx, |buffer, cx| {
9219 let empty_str: Arc<str> = Arc::default();
9220 buffer.edit(
9221 deletion_ranges
9222 .into_iter()
9223 .map(|range| (range, empty_str.clone())),
9224 None,
9225 cx,
9226 );
9227 });
9228 let selections = this.selections.all::<usize>(cx);
9229 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9230 s.select(selections)
9231 });
9232 });
9233 }
9234
9235 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9236 if self.read_only(cx) {
9237 return;
9238 }
9239 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9240 let selections = self
9241 .selections
9242 .all::<usize>(cx)
9243 .into_iter()
9244 .map(|s| s.range());
9245
9246 self.transact(window, cx, |this, window, cx| {
9247 this.buffer.update(cx, |buffer, cx| {
9248 buffer.autoindent_ranges(selections, cx);
9249 });
9250 let selections = this.selections.all::<usize>(cx);
9251 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9252 s.select(selections)
9253 });
9254 });
9255 }
9256
9257 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9258 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9259 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9260 let selections = self.selections.all::<Point>(cx);
9261
9262 let mut new_cursors = Vec::new();
9263 let mut edit_ranges = Vec::new();
9264 let mut selections = selections.iter().peekable();
9265 while let Some(selection) = selections.next() {
9266 let mut rows = selection.spanned_rows(false, &display_map);
9267 let goal_display_column = selection.head().to_display_point(&display_map).column();
9268
9269 // Accumulate contiguous regions of rows that we want to delete.
9270 while let Some(next_selection) = selections.peek() {
9271 let next_rows = next_selection.spanned_rows(false, &display_map);
9272 if next_rows.start <= rows.end {
9273 rows.end = next_rows.end;
9274 selections.next().unwrap();
9275 } else {
9276 break;
9277 }
9278 }
9279
9280 let buffer = &display_map.buffer_snapshot;
9281 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9282 let edit_end;
9283 let cursor_buffer_row;
9284 if buffer.max_point().row >= rows.end.0 {
9285 // If there's a line after the range, delete the \n from the end of the row range
9286 // and position the cursor on the next line.
9287 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9288 cursor_buffer_row = rows.end;
9289 } else {
9290 // If there isn't a line after the range, delete the \n from the line before the
9291 // start of the row range and position the cursor there.
9292 edit_start = edit_start.saturating_sub(1);
9293 edit_end = buffer.len();
9294 cursor_buffer_row = rows.start.previous_row();
9295 }
9296
9297 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9298 *cursor.column_mut() =
9299 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9300
9301 new_cursors.push((
9302 selection.id,
9303 buffer.anchor_after(cursor.to_point(&display_map)),
9304 ));
9305 edit_ranges.push(edit_start..edit_end);
9306 }
9307
9308 self.transact(window, cx, |this, window, cx| {
9309 let buffer = this.buffer.update(cx, |buffer, cx| {
9310 let empty_str: Arc<str> = Arc::default();
9311 buffer.edit(
9312 edit_ranges
9313 .into_iter()
9314 .map(|range| (range, empty_str.clone())),
9315 None,
9316 cx,
9317 );
9318 buffer.snapshot(cx)
9319 });
9320 let new_selections = new_cursors
9321 .into_iter()
9322 .map(|(id, cursor)| {
9323 let cursor = cursor.to_point(&buffer);
9324 Selection {
9325 id,
9326 start: cursor,
9327 end: cursor,
9328 reversed: false,
9329 goal: SelectionGoal::None,
9330 }
9331 })
9332 .collect();
9333
9334 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9335 s.select(new_selections);
9336 });
9337 });
9338 }
9339
9340 pub fn join_lines_impl(
9341 &mut self,
9342 insert_whitespace: bool,
9343 window: &mut Window,
9344 cx: &mut Context<Self>,
9345 ) {
9346 if self.read_only(cx) {
9347 return;
9348 }
9349 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9350 for selection in self.selections.all::<Point>(cx) {
9351 let start = MultiBufferRow(selection.start.row);
9352 // Treat single line selections as if they include the next line. Otherwise this action
9353 // would do nothing for single line selections individual cursors.
9354 let end = if selection.start.row == selection.end.row {
9355 MultiBufferRow(selection.start.row + 1)
9356 } else {
9357 MultiBufferRow(selection.end.row)
9358 };
9359
9360 if let Some(last_row_range) = row_ranges.last_mut() {
9361 if start <= last_row_range.end {
9362 last_row_range.end = end;
9363 continue;
9364 }
9365 }
9366 row_ranges.push(start..end);
9367 }
9368
9369 let snapshot = self.buffer.read(cx).snapshot(cx);
9370 let mut cursor_positions = Vec::new();
9371 for row_range in &row_ranges {
9372 let anchor = snapshot.anchor_before(Point::new(
9373 row_range.end.previous_row().0,
9374 snapshot.line_len(row_range.end.previous_row()),
9375 ));
9376 cursor_positions.push(anchor..anchor);
9377 }
9378
9379 self.transact(window, cx, |this, window, cx| {
9380 for row_range in row_ranges.into_iter().rev() {
9381 for row in row_range.iter_rows().rev() {
9382 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9383 let next_line_row = row.next_row();
9384 let indent = snapshot.indent_size_for_line(next_line_row);
9385 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9386
9387 let replace =
9388 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9389 " "
9390 } else {
9391 ""
9392 };
9393
9394 this.buffer.update(cx, |buffer, cx| {
9395 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9396 });
9397 }
9398 }
9399
9400 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9401 s.select_anchor_ranges(cursor_positions)
9402 });
9403 });
9404 }
9405
9406 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9407 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9408 self.join_lines_impl(true, window, cx);
9409 }
9410
9411 pub fn sort_lines_case_sensitive(
9412 &mut self,
9413 _: &SortLinesCaseSensitive,
9414 window: &mut Window,
9415 cx: &mut Context<Self>,
9416 ) {
9417 self.manipulate_lines(window, cx, |lines| lines.sort())
9418 }
9419
9420 pub fn sort_lines_case_insensitive(
9421 &mut self,
9422 _: &SortLinesCaseInsensitive,
9423 window: &mut Window,
9424 cx: &mut Context<Self>,
9425 ) {
9426 self.manipulate_lines(window, cx, |lines| {
9427 lines.sort_by_key(|line| line.to_lowercase())
9428 })
9429 }
9430
9431 pub fn unique_lines_case_insensitive(
9432 &mut self,
9433 _: &UniqueLinesCaseInsensitive,
9434 window: &mut Window,
9435 cx: &mut Context<Self>,
9436 ) {
9437 self.manipulate_lines(window, cx, |lines| {
9438 let mut seen = HashSet::default();
9439 lines.retain(|line| seen.insert(line.to_lowercase()));
9440 })
9441 }
9442
9443 pub fn unique_lines_case_sensitive(
9444 &mut self,
9445 _: &UniqueLinesCaseSensitive,
9446 window: &mut Window,
9447 cx: &mut Context<Self>,
9448 ) {
9449 self.manipulate_lines(window, cx, |lines| {
9450 let mut seen = HashSet::default();
9451 lines.retain(|line| seen.insert(*line));
9452 })
9453 }
9454
9455 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9456 let Some(project) = self.project.clone() else {
9457 return;
9458 };
9459 self.reload(project, window, cx)
9460 .detach_and_notify_err(window, cx);
9461 }
9462
9463 pub fn restore_file(
9464 &mut self,
9465 _: &::git::RestoreFile,
9466 window: &mut Window,
9467 cx: &mut Context<Self>,
9468 ) {
9469 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9470 let mut buffer_ids = HashSet::default();
9471 let snapshot = self.buffer().read(cx).snapshot(cx);
9472 for selection in self.selections.all::<usize>(cx) {
9473 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9474 }
9475
9476 let buffer = self.buffer().read(cx);
9477 let ranges = buffer_ids
9478 .into_iter()
9479 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9480 .collect::<Vec<_>>();
9481
9482 self.restore_hunks_in_ranges(ranges, window, cx);
9483 }
9484
9485 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9486 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9487 let selections = self
9488 .selections
9489 .all(cx)
9490 .into_iter()
9491 .map(|s| s.range())
9492 .collect();
9493 self.restore_hunks_in_ranges(selections, window, cx);
9494 }
9495
9496 pub fn restore_hunks_in_ranges(
9497 &mut self,
9498 ranges: Vec<Range<Point>>,
9499 window: &mut Window,
9500 cx: &mut Context<Editor>,
9501 ) {
9502 let mut revert_changes = HashMap::default();
9503 let chunk_by = self
9504 .snapshot(window, cx)
9505 .hunks_for_ranges(ranges)
9506 .into_iter()
9507 .chunk_by(|hunk| hunk.buffer_id);
9508 for (buffer_id, hunks) in &chunk_by {
9509 let hunks = hunks.collect::<Vec<_>>();
9510 for hunk in &hunks {
9511 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9512 }
9513 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9514 }
9515 drop(chunk_by);
9516 if !revert_changes.is_empty() {
9517 self.transact(window, cx, |editor, window, cx| {
9518 editor.restore(revert_changes, window, cx);
9519 });
9520 }
9521 }
9522
9523 pub fn open_active_item_in_terminal(
9524 &mut self,
9525 _: &OpenInTerminal,
9526 window: &mut Window,
9527 cx: &mut Context<Self>,
9528 ) {
9529 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9530 let project_path = buffer.read(cx).project_path(cx)?;
9531 let project = self.project.as_ref()?.read(cx);
9532 let entry = project.entry_for_path(&project_path, cx)?;
9533 let parent = match &entry.canonical_path {
9534 Some(canonical_path) => canonical_path.to_path_buf(),
9535 None => project.absolute_path(&project_path, cx)?,
9536 }
9537 .parent()?
9538 .to_path_buf();
9539 Some(parent)
9540 }) {
9541 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9542 }
9543 }
9544
9545 fn set_breakpoint_context_menu(
9546 &mut self,
9547 display_row: DisplayRow,
9548 position: Option<Anchor>,
9549 clicked_point: gpui::Point<Pixels>,
9550 window: &mut Window,
9551 cx: &mut Context<Self>,
9552 ) {
9553 if !cx.has_flag::<DebuggerFeatureFlag>() {
9554 return;
9555 }
9556 let source = self
9557 .buffer
9558 .read(cx)
9559 .snapshot(cx)
9560 .anchor_before(Point::new(display_row.0, 0u32));
9561
9562 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9563
9564 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9565 self,
9566 source,
9567 clicked_point,
9568 context_menu,
9569 window,
9570 cx,
9571 );
9572 }
9573
9574 fn add_edit_breakpoint_block(
9575 &mut self,
9576 anchor: Anchor,
9577 breakpoint: &Breakpoint,
9578 edit_action: BreakpointPromptEditAction,
9579 window: &mut Window,
9580 cx: &mut Context<Self>,
9581 ) {
9582 let weak_editor = cx.weak_entity();
9583 let bp_prompt = cx.new(|cx| {
9584 BreakpointPromptEditor::new(
9585 weak_editor,
9586 anchor,
9587 breakpoint.clone(),
9588 edit_action,
9589 window,
9590 cx,
9591 )
9592 });
9593
9594 let height = bp_prompt.update(cx, |this, cx| {
9595 this.prompt
9596 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9597 });
9598 let cloned_prompt = bp_prompt.clone();
9599 let blocks = vec![BlockProperties {
9600 style: BlockStyle::Sticky,
9601 placement: BlockPlacement::Above(anchor),
9602 height: Some(height),
9603 render: Arc::new(move |cx| {
9604 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9605 cloned_prompt.clone().into_any_element()
9606 }),
9607 priority: 0,
9608 render_in_minimap: true,
9609 }];
9610
9611 let focus_handle = bp_prompt.focus_handle(cx);
9612 window.focus(&focus_handle);
9613
9614 let block_ids = self.insert_blocks(blocks, None, cx);
9615 bp_prompt.update(cx, |prompt, _| {
9616 prompt.add_block_ids(block_ids);
9617 });
9618 }
9619
9620 pub(crate) fn breakpoint_at_row(
9621 &self,
9622 row: u32,
9623 window: &mut Window,
9624 cx: &mut Context<Self>,
9625 ) -> Option<(Anchor, Breakpoint)> {
9626 let snapshot = self.snapshot(window, cx);
9627 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9628
9629 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9630 }
9631
9632 pub(crate) fn breakpoint_at_anchor(
9633 &self,
9634 breakpoint_position: Anchor,
9635 snapshot: &EditorSnapshot,
9636 cx: &mut Context<Self>,
9637 ) -> Option<(Anchor, Breakpoint)> {
9638 let project = self.project.clone()?;
9639
9640 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9641 snapshot
9642 .buffer_snapshot
9643 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9644 })?;
9645
9646 let enclosing_excerpt = breakpoint_position.excerpt_id;
9647 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
9648 let buffer_snapshot = buffer.read(cx).snapshot();
9649
9650 let row = buffer_snapshot
9651 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9652 .row;
9653
9654 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9655 let anchor_end = snapshot
9656 .buffer_snapshot
9657 .anchor_after(Point::new(row, line_len));
9658
9659 let bp = self
9660 .breakpoint_store
9661 .as_ref()?
9662 .read_with(cx, |breakpoint_store, cx| {
9663 breakpoint_store
9664 .breakpoints(
9665 &buffer,
9666 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9667 &buffer_snapshot,
9668 cx,
9669 )
9670 .next()
9671 .and_then(|(bp, _)| {
9672 let breakpoint_row = buffer_snapshot
9673 .summary_for_anchor::<text::PointUtf16>(&bp.position)
9674 .row;
9675
9676 if breakpoint_row == row {
9677 snapshot
9678 .buffer_snapshot
9679 .anchor_in_excerpt(enclosing_excerpt, bp.position)
9680 .map(|position| (position, bp.bp.clone()))
9681 } else {
9682 None
9683 }
9684 })
9685 });
9686 bp
9687 }
9688
9689 pub fn edit_log_breakpoint(
9690 &mut self,
9691 _: &EditLogBreakpoint,
9692 window: &mut Window,
9693 cx: &mut Context<Self>,
9694 ) {
9695 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9696 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9697 message: None,
9698 state: BreakpointState::Enabled,
9699 condition: None,
9700 hit_condition: None,
9701 });
9702
9703 self.add_edit_breakpoint_block(
9704 anchor,
9705 &breakpoint,
9706 BreakpointPromptEditAction::Log,
9707 window,
9708 cx,
9709 );
9710 }
9711 }
9712
9713 fn breakpoints_at_cursors(
9714 &self,
9715 window: &mut Window,
9716 cx: &mut Context<Self>,
9717 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9718 let snapshot = self.snapshot(window, cx);
9719 let cursors = self
9720 .selections
9721 .disjoint_anchors()
9722 .into_iter()
9723 .map(|selection| {
9724 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9725
9726 let breakpoint_position = self
9727 .breakpoint_at_row(cursor_position.row, window, cx)
9728 .map(|bp| bp.0)
9729 .unwrap_or_else(|| {
9730 snapshot
9731 .display_snapshot
9732 .buffer_snapshot
9733 .anchor_after(Point::new(cursor_position.row, 0))
9734 });
9735
9736 let breakpoint = self
9737 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9738 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9739
9740 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9741 })
9742 // 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.
9743 .collect::<HashMap<Anchor, _>>();
9744
9745 cursors.into_iter().collect()
9746 }
9747
9748 pub fn enable_breakpoint(
9749 &mut self,
9750 _: &crate::actions::EnableBreakpoint,
9751 window: &mut Window,
9752 cx: &mut Context<Self>,
9753 ) {
9754 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9755 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9756 continue;
9757 };
9758 self.edit_breakpoint_at_anchor(
9759 anchor,
9760 breakpoint,
9761 BreakpointEditAction::InvertState,
9762 cx,
9763 );
9764 }
9765 }
9766
9767 pub fn disable_breakpoint(
9768 &mut self,
9769 _: &crate::actions::DisableBreakpoint,
9770 window: &mut Window,
9771 cx: &mut Context<Self>,
9772 ) {
9773 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9774 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9775 continue;
9776 };
9777 self.edit_breakpoint_at_anchor(
9778 anchor,
9779 breakpoint,
9780 BreakpointEditAction::InvertState,
9781 cx,
9782 );
9783 }
9784 }
9785
9786 pub fn toggle_breakpoint(
9787 &mut self,
9788 _: &crate::actions::ToggleBreakpoint,
9789 window: &mut Window,
9790 cx: &mut Context<Self>,
9791 ) {
9792 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9793 if let Some(breakpoint) = breakpoint {
9794 self.edit_breakpoint_at_anchor(
9795 anchor,
9796 breakpoint,
9797 BreakpointEditAction::Toggle,
9798 cx,
9799 );
9800 } else {
9801 self.edit_breakpoint_at_anchor(
9802 anchor,
9803 Breakpoint::new_standard(),
9804 BreakpointEditAction::Toggle,
9805 cx,
9806 );
9807 }
9808 }
9809 }
9810
9811 pub fn edit_breakpoint_at_anchor(
9812 &mut self,
9813 breakpoint_position: Anchor,
9814 breakpoint: Breakpoint,
9815 edit_action: BreakpointEditAction,
9816 cx: &mut Context<Self>,
9817 ) {
9818 let Some(breakpoint_store) = &self.breakpoint_store else {
9819 return;
9820 };
9821
9822 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9823 if breakpoint_position == Anchor::min() {
9824 self.buffer()
9825 .read(cx)
9826 .excerpt_buffer_ids()
9827 .into_iter()
9828 .next()
9829 } else {
9830 None
9831 }
9832 }) else {
9833 return;
9834 };
9835
9836 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9837 return;
9838 };
9839
9840 breakpoint_store.update(cx, |breakpoint_store, cx| {
9841 breakpoint_store.toggle_breakpoint(
9842 buffer,
9843 BreakpointWithPosition {
9844 position: breakpoint_position.text_anchor,
9845 bp: breakpoint,
9846 },
9847 edit_action,
9848 cx,
9849 );
9850 });
9851
9852 cx.notify();
9853 }
9854
9855 #[cfg(any(test, feature = "test-support"))]
9856 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9857 self.breakpoint_store.clone()
9858 }
9859
9860 pub fn prepare_restore_change(
9861 &self,
9862 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9863 hunk: &MultiBufferDiffHunk,
9864 cx: &mut App,
9865 ) -> Option<()> {
9866 if hunk.is_created_file() {
9867 return None;
9868 }
9869 let buffer = self.buffer.read(cx);
9870 let diff = buffer.diff_for(hunk.buffer_id)?;
9871 let buffer = buffer.buffer(hunk.buffer_id)?;
9872 let buffer = buffer.read(cx);
9873 let original_text = diff
9874 .read(cx)
9875 .base_text()
9876 .as_rope()
9877 .slice(hunk.diff_base_byte_range.clone());
9878 let buffer_snapshot = buffer.snapshot();
9879 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9880 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9881 probe
9882 .0
9883 .start
9884 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9885 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9886 }) {
9887 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9888 Some(())
9889 } else {
9890 None
9891 }
9892 }
9893
9894 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9895 self.manipulate_lines(window, cx, |lines| lines.reverse())
9896 }
9897
9898 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9899 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9900 }
9901
9902 fn manipulate_lines<Fn>(
9903 &mut self,
9904 window: &mut Window,
9905 cx: &mut Context<Self>,
9906 mut callback: Fn,
9907 ) where
9908 Fn: FnMut(&mut Vec<&str>),
9909 {
9910 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9911
9912 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9913 let buffer = self.buffer.read(cx).snapshot(cx);
9914
9915 let mut edits = Vec::new();
9916
9917 let selections = self.selections.all::<Point>(cx);
9918 let mut selections = selections.iter().peekable();
9919 let mut contiguous_row_selections = Vec::new();
9920 let mut new_selections = Vec::new();
9921 let mut added_lines = 0;
9922 let mut removed_lines = 0;
9923
9924 while let Some(selection) = selections.next() {
9925 let (start_row, end_row) = consume_contiguous_rows(
9926 &mut contiguous_row_selections,
9927 selection,
9928 &display_map,
9929 &mut selections,
9930 );
9931
9932 let start_point = Point::new(start_row.0, 0);
9933 let end_point = Point::new(
9934 end_row.previous_row().0,
9935 buffer.line_len(end_row.previous_row()),
9936 );
9937 let text = buffer
9938 .text_for_range(start_point..end_point)
9939 .collect::<String>();
9940
9941 let mut lines = text.split('\n').collect_vec();
9942
9943 let lines_before = lines.len();
9944 callback(&mut lines);
9945 let lines_after = lines.len();
9946
9947 edits.push((start_point..end_point, lines.join("\n")));
9948
9949 // Selections must change based on added and removed line count
9950 let start_row =
9951 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9952 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9953 new_selections.push(Selection {
9954 id: selection.id,
9955 start: start_row,
9956 end: end_row,
9957 goal: SelectionGoal::None,
9958 reversed: selection.reversed,
9959 });
9960
9961 if lines_after > lines_before {
9962 added_lines += lines_after - lines_before;
9963 } else if lines_before > lines_after {
9964 removed_lines += lines_before - lines_after;
9965 }
9966 }
9967
9968 self.transact(window, cx, |this, window, cx| {
9969 let buffer = this.buffer.update(cx, |buffer, cx| {
9970 buffer.edit(edits, None, cx);
9971 buffer.snapshot(cx)
9972 });
9973
9974 // Recalculate offsets on newly edited buffer
9975 let new_selections = new_selections
9976 .iter()
9977 .map(|s| {
9978 let start_point = Point::new(s.start.0, 0);
9979 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9980 Selection {
9981 id: s.id,
9982 start: buffer.point_to_offset(start_point),
9983 end: buffer.point_to_offset(end_point),
9984 goal: s.goal,
9985 reversed: s.reversed,
9986 }
9987 })
9988 .collect();
9989
9990 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9991 s.select(new_selections);
9992 });
9993
9994 this.request_autoscroll(Autoscroll::fit(), cx);
9995 });
9996 }
9997
9998 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
9999 self.manipulate_text(window, cx, |text| {
10000 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10001 if has_upper_case_characters {
10002 text.to_lowercase()
10003 } else {
10004 text.to_uppercase()
10005 }
10006 })
10007 }
10008
10009 pub fn convert_to_upper_case(
10010 &mut self,
10011 _: &ConvertToUpperCase,
10012 window: &mut Window,
10013 cx: &mut Context<Self>,
10014 ) {
10015 self.manipulate_text(window, cx, |text| text.to_uppercase())
10016 }
10017
10018 pub fn convert_to_lower_case(
10019 &mut self,
10020 _: &ConvertToLowerCase,
10021 window: &mut Window,
10022 cx: &mut Context<Self>,
10023 ) {
10024 self.manipulate_text(window, cx, |text| text.to_lowercase())
10025 }
10026
10027 pub fn convert_to_title_case(
10028 &mut self,
10029 _: &ConvertToTitleCase,
10030 window: &mut Window,
10031 cx: &mut Context<Self>,
10032 ) {
10033 self.manipulate_text(window, cx, |text| {
10034 text.split('\n')
10035 .map(|line| line.to_case(Case::Title))
10036 .join("\n")
10037 })
10038 }
10039
10040 pub fn convert_to_snake_case(
10041 &mut self,
10042 _: &ConvertToSnakeCase,
10043 window: &mut Window,
10044 cx: &mut Context<Self>,
10045 ) {
10046 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10047 }
10048
10049 pub fn convert_to_kebab_case(
10050 &mut self,
10051 _: &ConvertToKebabCase,
10052 window: &mut Window,
10053 cx: &mut Context<Self>,
10054 ) {
10055 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10056 }
10057
10058 pub fn convert_to_upper_camel_case(
10059 &mut self,
10060 _: &ConvertToUpperCamelCase,
10061 window: &mut Window,
10062 cx: &mut Context<Self>,
10063 ) {
10064 self.manipulate_text(window, cx, |text| {
10065 text.split('\n')
10066 .map(|line| line.to_case(Case::UpperCamel))
10067 .join("\n")
10068 })
10069 }
10070
10071 pub fn convert_to_lower_camel_case(
10072 &mut self,
10073 _: &ConvertToLowerCamelCase,
10074 window: &mut Window,
10075 cx: &mut Context<Self>,
10076 ) {
10077 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10078 }
10079
10080 pub fn convert_to_opposite_case(
10081 &mut self,
10082 _: &ConvertToOppositeCase,
10083 window: &mut Window,
10084 cx: &mut Context<Self>,
10085 ) {
10086 self.manipulate_text(window, cx, |text| {
10087 text.chars()
10088 .fold(String::with_capacity(text.len()), |mut t, c| {
10089 if c.is_uppercase() {
10090 t.extend(c.to_lowercase());
10091 } else {
10092 t.extend(c.to_uppercase());
10093 }
10094 t
10095 })
10096 })
10097 }
10098
10099 pub fn convert_to_rot13(
10100 &mut self,
10101 _: &ConvertToRot13,
10102 window: &mut Window,
10103 cx: &mut Context<Self>,
10104 ) {
10105 self.manipulate_text(window, cx, |text| {
10106 text.chars()
10107 .map(|c| match c {
10108 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10109 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10110 _ => c,
10111 })
10112 .collect()
10113 })
10114 }
10115
10116 pub fn convert_to_rot47(
10117 &mut self,
10118 _: &ConvertToRot47,
10119 window: &mut Window,
10120 cx: &mut Context<Self>,
10121 ) {
10122 self.manipulate_text(window, cx, |text| {
10123 text.chars()
10124 .map(|c| {
10125 let code_point = c as u32;
10126 if code_point >= 33 && code_point <= 126 {
10127 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10128 }
10129 c
10130 })
10131 .collect()
10132 })
10133 }
10134
10135 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10136 where
10137 Fn: FnMut(&str) -> String,
10138 {
10139 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10140 let buffer = self.buffer.read(cx).snapshot(cx);
10141
10142 let mut new_selections = Vec::new();
10143 let mut edits = Vec::new();
10144 let mut selection_adjustment = 0i32;
10145
10146 for selection in self.selections.all::<usize>(cx) {
10147 let selection_is_empty = selection.is_empty();
10148
10149 let (start, end) = if selection_is_empty {
10150 let word_range = movement::surrounding_word(
10151 &display_map,
10152 selection.start.to_display_point(&display_map),
10153 );
10154 let start = word_range.start.to_offset(&display_map, Bias::Left);
10155 let end = word_range.end.to_offset(&display_map, Bias::Left);
10156 (start, end)
10157 } else {
10158 (selection.start, selection.end)
10159 };
10160
10161 let text = buffer.text_for_range(start..end).collect::<String>();
10162 let old_length = text.len() as i32;
10163 let text = callback(&text);
10164
10165 new_selections.push(Selection {
10166 start: (start as i32 - selection_adjustment) as usize,
10167 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10168 goal: SelectionGoal::None,
10169 ..selection
10170 });
10171
10172 selection_adjustment += old_length - text.len() as i32;
10173
10174 edits.push((start..end, text));
10175 }
10176
10177 self.transact(window, cx, |this, window, cx| {
10178 this.buffer.update(cx, |buffer, cx| {
10179 buffer.edit(edits, None, cx);
10180 });
10181
10182 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10183 s.select(new_selections);
10184 });
10185
10186 this.request_autoscroll(Autoscroll::fit(), cx);
10187 });
10188 }
10189
10190 pub fn duplicate(
10191 &mut self,
10192 upwards: bool,
10193 whole_lines: bool,
10194 window: &mut Window,
10195 cx: &mut Context<Self>,
10196 ) {
10197 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10198
10199 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10200 let buffer = &display_map.buffer_snapshot;
10201 let selections = self.selections.all::<Point>(cx);
10202
10203 let mut edits = Vec::new();
10204 let mut selections_iter = selections.iter().peekable();
10205 while let Some(selection) = selections_iter.next() {
10206 let mut rows = selection.spanned_rows(false, &display_map);
10207 // duplicate line-wise
10208 if whole_lines || selection.start == selection.end {
10209 // Avoid duplicating the same lines twice.
10210 while let Some(next_selection) = selections_iter.peek() {
10211 let next_rows = next_selection.spanned_rows(false, &display_map);
10212 if next_rows.start < rows.end {
10213 rows.end = next_rows.end;
10214 selections_iter.next().unwrap();
10215 } else {
10216 break;
10217 }
10218 }
10219
10220 // Copy the text from the selected row region and splice it either at the start
10221 // or end of the region.
10222 let start = Point::new(rows.start.0, 0);
10223 let end = Point::new(
10224 rows.end.previous_row().0,
10225 buffer.line_len(rows.end.previous_row()),
10226 );
10227 let text = buffer
10228 .text_for_range(start..end)
10229 .chain(Some("\n"))
10230 .collect::<String>();
10231 let insert_location = if upwards {
10232 Point::new(rows.end.0, 0)
10233 } else {
10234 start
10235 };
10236 edits.push((insert_location..insert_location, text));
10237 } else {
10238 // duplicate character-wise
10239 let start = selection.start;
10240 let end = selection.end;
10241 let text = buffer.text_for_range(start..end).collect::<String>();
10242 edits.push((selection.end..selection.end, text));
10243 }
10244 }
10245
10246 self.transact(window, cx, |this, _, cx| {
10247 this.buffer.update(cx, |buffer, cx| {
10248 buffer.edit(edits, None, cx);
10249 });
10250
10251 this.request_autoscroll(Autoscroll::fit(), cx);
10252 });
10253 }
10254
10255 pub fn duplicate_line_up(
10256 &mut self,
10257 _: &DuplicateLineUp,
10258 window: &mut Window,
10259 cx: &mut Context<Self>,
10260 ) {
10261 self.duplicate(true, true, window, cx);
10262 }
10263
10264 pub fn duplicate_line_down(
10265 &mut self,
10266 _: &DuplicateLineDown,
10267 window: &mut Window,
10268 cx: &mut Context<Self>,
10269 ) {
10270 self.duplicate(false, true, window, cx);
10271 }
10272
10273 pub fn duplicate_selection(
10274 &mut self,
10275 _: &DuplicateSelection,
10276 window: &mut Window,
10277 cx: &mut Context<Self>,
10278 ) {
10279 self.duplicate(false, false, window, cx);
10280 }
10281
10282 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10283 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10284
10285 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10286 let buffer = self.buffer.read(cx).snapshot(cx);
10287
10288 let mut edits = Vec::new();
10289 let mut unfold_ranges = Vec::new();
10290 let mut refold_creases = Vec::new();
10291
10292 let selections = self.selections.all::<Point>(cx);
10293 let mut selections = selections.iter().peekable();
10294 let mut contiguous_row_selections = Vec::new();
10295 let mut new_selections = Vec::new();
10296
10297 while let Some(selection) = selections.next() {
10298 // Find all the selections that span a contiguous row range
10299 let (start_row, end_row) = consume_contiguous_rows(
10300 &mut contiguous_row_selections,
10301 selection,
10302 &display_map,
10303 &mut selections,
10304 );
10305
10306 // Move the text spanned by the row range to be before the line preceding the row range
10307 if start_row.0 > 0 {
10308 let range_to_move = Point::new(
10309 start_row.previous_row().0,
10310 buffer.line_len(start_row.previous_row()),
10311 )
10312 ..Point::new(
10313 end_row.previous_row().0,
10314 buffer.line_len(end_row.previous_row()),
10315 );
10316 let insertion_point = display_map
10317 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10318 .0;
10319
10320 // Don't move lines across excerpts
10321 if buffer
10322 .excerpt_containing(insertion_point..range_to_move.end)
10323 .is_some()
10324 {
10325 let text = buffer
10326 .text_for_range(range_to_move.clone())
10327 .flat_map(|s| s.chars())
10328 .skip(1)
10329 .chain(['\n'])
10330 .collect::<String>();
10331
10332 edits.push((
10333 buffer.anchor_after(range_to_move.start)
10334 ..buffer.anchor_before(range_to_move.end),
10335 String::new(),
10336 ));
10337 let insertion_anchor = buffer.anchor_after(insertion_point);
10338 edits.push((insertion_anchor..insertion_anchor, text));
10339
10340 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10341
10342 // Move selections up
10343 new_selections.extend(contiguous_row_selections.drain(..).map(
10344 |mut selection| {
10345 selection.start.row -= row_delta;
10346 selection.end.row -= row_delta;
10347 selection
10348 },
10349 ));
10350
10351 // Move folds up
10352 unfold_ranges.push(range_to_move.clone());
10353 for fold in display_map.folds_in_range(
10354 buffer.anchor_before(range_to_move.start)
10355 ..buffer.anchor_after(range_to_move.end),
10356 ) {
10357 let mut start = fold.range.start.to_point(&buffer);
10358 let mut end = fold.range.end.to_point(&buffer);
10359 start.row -= row_delta;
10360 end.row -= row_delta;
10361 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10362 }
10363 }
10364 }
10365
10366 // If we didn't move line(s), preserve the existing selections
10367 new_selections.append(&mut contiguous_row_selections);
10368 }
10369
10370 self.transact(window, cx, |this, window, cx| {
10371 this.unfold_ranges(&unfold_ranges, true, true, cx);
10372 this.buffer.update(cx, |buffer, cx| {
10373 for (range, text) in edits {
10374 buffer.edit([(range, text)], None, cx);
10375 }
10376 });
10377 this.fold_creases(refold_creases, true, window, cx);
10378 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10379 s.select(new_selections);
10380 })
10381 });
10382 }
10383
10384 pub fn move_line_down(
10385 &mut self,
10386 _: &MoveLineDown,
10387 window: &mut Window,
10388 cx: &mut Context<Self>,
10389 ) {
10390 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10391
10392 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10393 let buffer = self.buffer.read(cx).snapshot(cx);
10394
10395 let mut edits = Vec::new();
10396 let mut unfold_ranges = Vec::new();
10397 let mut refold_creases = Vec::new();
10398
10399 let selections = self.selections.all::<Point>(cx);
10400 let mut selections = selections.iter().peekable();
10401 let mut contiguous_row_selections = Vec::new();
10402 let mut new_selections = Vec::new();
10403
10404 while let Some(selection) = selections.next() {
10405 // Find all the selections that span a contiguous row range
10406 let (start_row, end_row) = consume_contiguous_rows(
10407 &mut contiguous_row_selections,
10408 selection,
10409 &display_map,
10410 &mut selections,
10411 );
10412
10413 // Move the text spanned by the row range to be after the last line of the row range
10414 if end_row.0 <= buffer.max_point().row {
10415 let range_to_move =
10416 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10417 let insertion_point = display_map
10418 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10419 .0;
10420
10421 // Don't move lines across excerpt boundaries
10422 if buffer
10423 .excerpt_containing(range_to_move.start..insertion_point)
10424 .is_some()
10425 {
10426 let mut text = String::from("\n");
10427 text.extend(buffer.text_for_range(range_to_move.clone()));
10428 text.pop(); // Drop trailing newline
10429 edits.push((
10430 buffer.anchor_after(range_to_move.start)
10431 ..buffer.anchor_before(range_to_move.end),
10432 String::new(),
10433 ));
10434 let insertion_anchor = buffer.anchor_after(insertion_point);
10435 edits.push((insertion_anchor..insertion_anchor, text));
10436
10437 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10438
10439 // Move selections down
10440 new_selections.extend(contiguous_row_selections.drain(..).map(
10441 |mut selection| {
10442 selection.start.row += row_delta;
10443 selection.end.row += row_delta;
10444 selection
10445 },
10446 ));
10447
10448 // Move folds down
10449 unfold_ranges.push(range_to_move.clone());
10450 for fold in display_map.folds_in_range(
10451 buffer.anchor_before(range_to_move.start)
10452 ..buffer.anchor_after(range_to_move.end),
10453 ) {
10454 let mut start = fold.range.start.to_point(&buffer);
10455 let mut end = fold.range.end.to_point(&buffer);
10456 start.row += row_delta;
10457 end.row += row_delta;
10458 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10459 }
10460 }
10461 }
10462
10463 // If we didn't move line(s), preserve the existing selections
10464 new_selections.append(&mut contiguous_row_selections);
10465 }
10466
10467 self.transact(window, cx, |this, window, cx| {
10468 this.unfold_ranges(&unfold_ranges, true, true, cx);
10469 this.buffer.update(cx, |buffer, cx| {
10470 for (range, text) in edits {
10471 buffer.edit([(range, text)], None, cx);
10472 }
10473 });
10474 this.fold_creases(refold_creases, true, window, cx);
10475 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10476 s.select(new_selections)
10477 });
10478 });
10479 }
10480
10481 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10482 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10483 let text_layout_details = &self.text_layout_details(window);
10484 self.transact(window, cx, |this, window, cx| {
10485 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10486 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10487 s.move_with(|display_map, selection| {
10488 if !selection.is_empty() {
10489 return;
10490 }
10491
10492 let mut head = selection.head();
10493 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10494 if head.column() == display_map.line_len(head.row()) {
10495 transpose_offset = display_map
10496 .buffer_snapshot
10497 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10498 }
10499
10500 if transpose_offset == 0 {
10501 return;
10502 }
10503
10504 *head.column_mut() += 1;
10505 head = display_map.clip_point(head, Bias::Right);
10506 let goal = SelectionGoal::HorizontalPosition(
10507 display_map
10508 .x_for_display_point(head, text_layout_details)
10509 .into(),
10510 );
10511 selection.collapse_to(head, goal);
10512
10513 let transpose_start = display_map
10514 .buffer_snapshot
10515 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10516 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10517 let transpose_end = display_map
10518 .buffer_snapshot
10519 .clip_offset(transpose_offset + 1, Bias::Right);
10520 if let Some(ch) =
10521 display_map.buffer_snapshot.chars_at(transpose_start).next()
10522 {
10523 edits.push((transpose_start..transpose_offset, String::new()));
10524 edits.push((transpose_end..transpose_end, ch.to_string()));
10525 }
10526 }
10527 });
10528 edits
10529 });
10530 this.buffer
10531 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10532 let selections = this.selections.all::<usize>(cx);
10533 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10534 s.select(selections);
10535 });
10536 });
10537 }
10538
10539 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10540 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10541 self.rewrap_impl(RewrapOptions::default(), cx)
10542 }
10543
10544 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10545 let buffer = self.buffer.read(cx).snapshot(cx);
10546 let selections = self.selections.all::<Point>(cx);
10547 let mut selections = selections.iter().peekable();
10548
10549 let mut edits = Vec::new();
10550 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10551
10552 while let Some(selection) = selections.next() {
10553 let mut start_row = selection.start.row;
10554 let mut end_row = selection.end.row;
10555
10556 // Skip selections that overlap with a range that has already been rewrapped.
10557 let selection_range = start_row..end_row;
10558 if rewrapped_row_ranges
10559 .iter()
10560 .any(|range| range.overlaps(&selection_range))
10561 {
10562 continue;
10563 }
10564
10565 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10566
10567 // Since not all lines in the selection may be at the same indent
10568 // level, choose the indent size that is the most common between all
10569 // of the lines.
10570 //
10571 // If there is a tie, we use the deepest indent.
10572 let (indent_size, indent_end) = {
10573 let mut indent_size_occurrences = HashMap::default();
10574 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10575
10576 for row in start_row..=end_row {
10577 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10578 rows_by_indent_size.entry(indent).or_default().push(row);
10579 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10580 }
10581
10582 let indent_size = indent_size_occurrences
10583 .into_iter()
10584 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10585 .map(|(indent, _)| indent)
10586 .unwrap_or_default();
10587 let row = rows_by_indent_size[&indent_size][0];
10588 let indent_end = Point::new(row, indent_size.len);
10589
10590 (indent_size, indent_end)
10591 };
10592
10593 let mut line_prefix = indent_size.chars().collect::<String>();
10594
10595 let mut inside_comment = false;
10596 if let Some(comment_prefix) =
10597 buffer
10598 .language_scope_at(selection.head())
10599 .and_then(|language| {
10600 language
10601 .line_comment_prefixes()
10602 .iter()
10603 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10604 .cloned()
10605 })
10606 {
10607 line_prefix.push_str(&comment_prefix);
10608 inside_comment = true;
10609 }
10610
10611 let language_settings = buffer.language_settings_at(selection.head(), cx);
10612 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10613 RewrapBehavior::InComments => inside_comment,
10614 RewrapBehavior::InSelections => !selection.is_empty(),
10615 RewrapBehavior::Anywhere => true,
10616 };
10617
10618 let should_rewrap = options.override_language_settings
10619 || allow_rewrap_based_on_language
10620 || self.hard_wrap.is_some();
10621 if !should_rewrap {
10622 continue;
10623 }
10624
10625 if selection.is_empty() {
10626 'expand_upwards: while start_row > 0 {
10627 let prev_row = start_row - 1;
10628 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10629 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10630 {
10631 start_row = prev_row;
10632 } else {
10633 break 'expand_upwards;
10634 }
10635 }
10636
10637 'expand_downwards: while end_row < buffer.max_point().row {
10638 let next_row = end_row + 1;
10639 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10640 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10641 {
10642 end_row = next_row;
10643 } else {
10644 break 'expand_downwards;
10645 }
10646 }
10647 }
10648
10649 let start = Point::new(start_row, 0);
10650 let start_offset = start.to_offset(&buffer);
10651 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10652 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10653 let Some(lines_without_prefixes) = selection_text
10654 .lines()
10655 .map(|line| {
10656 line.strip_prefix(&line_prefix)
10657 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10658 .with_context(|| {
10659 format!("line did not start with prefix {line_prefix:?}: {line:?}")
10660 })
10661 })
10662 .collect::<Result<Vec<_>, _>>()
10663 .log_err()
10664 else {
10665 continue;
10666 };
10667
10668 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10669 buffer
10670 .language_settings_at(Point::new(start_row, 0), cx)
10671 .preferred_line_length as usize
10672 });
10673 let wrapped_text = wrap_with_prefix(
10674 line_prefix,
10675 lines_without_prefixes.join("\n"),
10676 wrap_column,
10677 tab_size,
10678 options.preserve_existing_whitespace,
10679 );
10680
10681 // TODO: should always use char-based diff while still supporting cursor behavior that
10682 // matches vim.
10683 let mut diff_options = DiffOptions::default();
10684 if options.override_language_settings {
10685 diff_options.max_word_diff_len = 0;
10686 diff_options.max_word_diff_line_count = 0;
10687 } else {
10688 diff_options.max_word_diff_len = usize::MAX;
10689 diff_options.max_word_diff_line_count = usize::MAX;
10690 }
10691
10692 for (old_range, new_text) in
10693 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10694 {
10695 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10696 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10697 edits.push((edit_start..edit_end, new_text));
10698 }
10699
10700 rewrapped_row_ranges.push(start_row..=end_row);
10701 }
10702
10703 self.buffer
10704 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10705 }
10706
10707 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10708 let mut text = String::new();
10709 let buffer = self.buffer.read(cx).snapshot(cx);
10710 let mut selections = self.selections.all::<Point>(cx);
10711 let mut clipboard_selections = Vec::with_capacity(selections.len());
10712 {
10713 let max_point = buffer.max_point();
10714 let mut is_first = true;
10715 for selection in &mut selections {
10716 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10717 if is_entire_line {
10718 selection.start = Point::new(selection.start.row, 0);
10719 if !selection.is_empty() && selection.end.column == 0 {
10720 selection.end = cmp::min(max_point, selection.end);
10721 } else {
10722 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10723 }
10724 selection.goal = SelectionGoal::None;
10725 }
10726 if is_first {
10727 is_first = false;
10728 } else {
10729 text += "\n";
10730 }
10731 let mut len = 0;
10732 for chunk in buffer.text_for_range(selection.start..selection.end) {
10733 text.push_str(chunk);
10734 len += chunk.len();
10735 }
10736 clipboard_selections.push(ClipboardSelection {
10737 len,
10738 is_entire_line,
10739 first_line_indent: buffer
10740 .indent_size_for_line(MultiBufferRow(selection.start.row))
10741 .len,
10742 });
10743 }
10744 }
10745
10746 self.transact(window, cx, |this, window, cx| {
10747 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10748 s.select(selections);
10749 });
10750 this.insert("", window, cx);
10751 });
10752 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10753 }
10754
10755 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10756 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10757 let item = self.cut_common(window, cx);
10758 cx.write_to_clipboard(item);
10759 }
10760
10761 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10762 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10763 self.change_selections(None, window, cx, |s| {
10764 s.move_with(|snapshot, sel| {
10765 if sel.is_empty() {
10766 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10767 }
10768 });
10769 });
10770 let item = self.cut_common(window, cx);
10771 cx.set_global(KillRing(item))
10772 }
10773
10774 pub fn kill_ring_yank(
10775 &mut self,
10776 _: &KillRingYank,
10777 window: &mut Window,
10778 cx: &mut Context<Self>,
10779 ) {
10780 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10781 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10782 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10783 (kill_ring.text().to_string(), kill_ring.metadata_json())
10784 } else {
10785 return;
10786 }
10787 } else {
10788 return;
10789 };
10790 self.do_paste(&text, metadata, false, window, cx);
10791 }
10792
10793 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10794 self.do_copy(true, cx);
10795 }
10796
10797 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10798 self.do_copy(false, cx);
10799 }
10800
10801 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10802 let selections = self.selections.all::<Point>(cx);
10803 let buffer = self.buffer.read(cx).read(cx);
10804 let mut text = String::new();
10805
10806 let mut clipboard_selections = Vec::with_capacity(selections.len());
10807 {
10808 let max_point = buffer.max_point();
10809 let mut is_first = true;
10810 for selection in &selections {
10811 let mut start = selection.start;
10812 let mut end = selection.end;
10813 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10814 if is_entire_line {
10815 start = Point::new(start.row, 0);
10816 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10817 }
10818
10819 let mut trimmed_selections = Vec::new();
10820 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10821 let row = MultiBufferRow(start.row);
10822 let first_indent = buffer.indent_size_for_line(row);
10823 if first_indent.len == 0 || start.column > first_indent.len {
10824 trimmed_selections.push(start..end);
10825 } else {
10826 trimmed_selections.push(
10827 Point::new(row.0, first_indent.len)
10828 ..Point::new(row.0, buffer.line_len(row)),
10829 );
10830 for row in start.row + 1..=end.row {
10831 let mut line_len = buffer.line_len(MultiBufferRow(row));
10832 if row == end.row {
10833 line_len = end.column;
10834 }
10835 if line_len == 0 {
10836 trimmed_selections
10837 .push(Point::new(row, 0)..Point::new(row, line_len));
10838 continue;
10839 }
10840 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10841 if row_indent_size.len >= first_indent.len {
10842 trimmed_selections.push(
10843 Point::new(row, first_indent.len)..Point::new(row, line_len),
10844 );
10845 } else {
10846 trimmed_selections.clear();
10847 trimmed_selections.push(start..end);
10848 break;
10849 }
10850 }
10851 }
10852 } else {
10853 trimmed_selections.push(start..end);
10854 }
10855
10856 for trimmed_range in trimmed_selections {
10857 if is_first {
10858 is_first = false;
10859 } else {
10860 text += "\n";
10861 }
10862 let mut len = 0;
10863 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10864 text.push_str(chunk);
10865 len += chunk.len();
10866 }
10867 clipboard_selections.push(ClipboardSelection {
10868 len,
10869 is_entire_line,
10870 first_line_indent: buffer
10871 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10872 .len,
10873 });
10874 }
10875 }
10876 }
10877
10878 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10879 text,
10880 clipboard_selections,
10881 ));
10882 }
10883
10884 pub fn do_paste(
10885 &mut self,
10886 text: &String,
10887 clipboard_selections: Option<Vec<ClipboardSelection>>,
10888 handle_entire_lines: bool,
10889 window: &mut Window,
10890 cx: &mut Context<Self>,
10891 ) {
10892 if self.read_only(cx) {
10893 return;
10894 }
10895
10896 let clipboard_text = Cow::Borrowed(text);
10897
10898 self.transact(window, cx, |this, window, cx| {
10899 if let Some(mut clipboard_selections) = clipboard_selections {
10900 let old_selections = this.selections.all::<usize>(cx);
10901 let all_selections_were_entire_line =
10902 clipboard_selections.iter().all(|s| s.is_entire_line);
10903 let first_selection_indent_column =
10904 clipboard_selections.first().map(|s| s.first_line_indent);
10905 if clipboard_selections.len() != old_selections.len() {
10906 clipboard_selections.drain(..);
10907 }
10908 let cursor_offset = this.selections.last::<usize>(cx).head();
10909 let mut auto_indent_on_paste = true;
10910
10911 this.buffer.update(cx, |buffer, cx| {
10912 let snapshot = buffer.read(cx);
10913 auto_indent_on_paste = snapshot
10914 .language_settings_at(cursor_offset, cx)
10915 .auto_indent_on_paste;
10916
10917 let mut start_offset = 0;
10918 let mut edits = Vec::new();
10919 let mut original_indent_columns = Vec::new();
10920 for (ix, selection) in old_selections.iter().enumerate() {
10921 let to_insert;
10922 let entire_line;
10923 let original_indent_column;
10924 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10925 let end_offset = start_offset + clipboard_selection.len;
10926 to_insert = &clipboard_text[start_offset..end_offset];
10927 entire_line = clipboard_selection.is_entire_line;
10928 start_offset = end_offset + 1;
10929 original_indent_column = Some(clipboard_selection.first_line_indent);
10930 } else {
10931 to_insert = clipboard_text.as_str();
10932 entire_line = all_selections_were_entire_line;
10933 original_indent_column = first_selection_indent_column
10934 }
10935
10936 // If the corresponding selection was empty when this slice of the
10937 // clipboard text was written, then the entire line containing the
10938 // selection was copied. If this selection is also currently empty,
10939 // then paste the line before the current line of the buffer.
10940 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10941 let column = selection.start.to_point(&snapshot).column as usize;
10942 let line_start = selection.start - column;
10943 line_start..line_start
10944 } else {
10945 selection.range()
10946 };
10947
10948 edits.push((range, to_insert));
10949 original_indent_columns.push(original_indent_column);
10950 }
10951 drop(snapshot);
10952
10953 buffer.edit(
10954 edits,
10955 if auto_indent_on_paste {
10956 Some(AutoindentMode::Block {
10957 original_indent_columns,
10958 })
10959 } else {
10960 None
10961 },
10962 cx,
10963 );
10964 });
10965
10966 let selections = this.selections.all::<usize>(cx);
10967 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10968 s.select(selections)
10969 });
10970 } else {
10971 this.insert(&clipboard_text, window, cx);
10972 }
10973 });
10974 }
10975
10976 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10977 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10978 if let Some(item) = cx.read_from_clipboard() {
10979 let entries = item.entries();
10980
10981 match entries.first() {
10982 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10983 // of all the pasted entries.
10984 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10985 .do_paste(
10986 clipboard_string.text(),
10987 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10988 true,
10989 window,
10990 cx,
10991 ),
10992 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10993 }
10994 }
10995 }
10996
10997 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10998 if self.read_only(cx) {
10999 return;
11000 }
11001
11002 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11003
11004 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11005 if let Some((selections, _)) =
11006 self.selection_history.transaction(transaction_id).cloned()
11007 {
11008 self.change_selections(None, window, cx, |s| {
11009 s.select_anchors(selections.to_vec());
11010 });
11011 } else {
11012 log::error!(
11013 "No entry in selection_history found for undo. \
11014 This may correspond to a bug where undo does not update the selection. \
11015 If this is occurring, please add details to \
11016 https://github.com/zed-industries/zed/issues/22692"
11017 );
11018 }
11019 self.request_autoscroll(Autoscroll::fit(), cx);
11020 self.unmark_text(window, cx);
11021 self.refresh_inline_completion(true, false, window, cx);
11022 cx.emit(EditorEvent::Edited { transaction_id });
11023 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11024 }
11025 }
11026
11027 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11028 if self.read_only(cx) {
11029 return;
11030 }
11031
11032 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11033
11034 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11035 if let Some((_, Some(selections))) =
11036 self.selection_history.transaction(transaction_id).cloned()
11037 {
11038 self.change_selections(None, window, cx, |s| {
11039 s.select_anchors(selections.to_vec());
11040 });
11041 } else {
11042 log::error!(
11043 "No entry in selection_history found for redo. \
11044 This may correspond to a bug where undo does not update the selection. \
11045 If this is occurring, please add details to \
11046 https://github.com/zed-industries/zed/issues/22692"
11047 );
11048 }
11049 self.request_autoscroll(Autoscroll::fit(), cx);
11050 self.unmark_text(window, cx);
11051 self.refresh_inline_completion(true, false, window, cx);
11052 cx.emit(EditorEvent::Edited { transaction_id });
11053 }
11054 }
11055
11056 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11057 self.buffer
11058 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11059 }
11060
11061 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11062 self.buffer
11063 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11064 }
11065
11066 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11067 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11068 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11069 s.move_with(|map, selection| {
11070 let cursor = if selection.is_empty() {
11071 movement::left(map, selection.start)
11072 } else {
11073 selection.start
11074 };
11075 selection.collapse_to(cursor, SelectionGoal::None);
11076 });
11077 })
11078 }
11079
11080 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11081 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11082 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11083 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11084 })
11085 }
11086
11087 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11088 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11089 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11090 s.move_with(|map, selection| {
11091 let cursor = if selection.is_empty() {
11092 movement::right(map, selection.end)
11093 } else {
11094 selection.end
11095 };
11096 selection.collapse_to(cursor, SelectionGoal::None)
11097 });
11098 })
11099 }
11100
11101 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11102 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11103 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11104 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11105 })
11106 }
11107
11108 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11109 if self.take_rename(true, window, cx).is_some() {
11110 return;
11111 }
11112
11113 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11114 cx.propagate();
11115 return;
11116 }
11117
11118 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11119
11120 let text_layout_details = &self.text_layout_details(window);
11121 let selection_count = self.selections.count();
11122 let first_selection = self.selections.first_anchor();
11123
11124 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11125 s.move_with(|map, selection| {
11126 if !selection.is_empty() {
11127 selection.goal = SelectionGoal::None;
11128 }
11129 let (cursor, goal) = movement::up(
11130 map,
11131 selection.start,
11132 selection.goal,
11133 false,
11134 text_layout_details,
11135 );
11136 selection.collapse_to(cursor, goal);
11137 });
11138 });
11139
11140 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11141 {
11142 cx.propagate();
11143 }
11144 }
11145
11146 pub fn move_up_by_lines(
11147 &mut self,
11148 action: &MoveUpByLines,
11149 window: &mut Window,
11150 cx: &mut Context<Self>,
11151 ) {
11152 if self.take_rename(true, window, cx).is_some() {
11153 return;
11154 }
11155
11156 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11157 cx.propagate();
11158 return;
11159 }
11160
11161 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11162
11163 let text_layout_details = &self.text_layout_details(window);
11164
11165 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11166 s.move_with(|map, selection| {
11167 if !selection.is_empty() {
11168 selection.goal = SelectionGoal::None;
11169 }
11170 let (cursor, goal) = movement::up_by_rows(
11171 map,
11172 selection.start,
11173 action.lines,
11174 selection.goal,
11175 false,
11176 text_layout_details,
11177 );
11178 selection.collapse_to(cursor, goal);
11179 });
11180 })
11181 }
11182
11183 pub fn move_down_by_lines(
11184 &mut self,
11185 action: &MoveDownByLines,
11186 window: &mut Window,
11187 cx: &mut Context<Self>,
11188 ) {
11189 if self.take_rename(true, window, cx).is_some() {
11190 return;
11191 }
11192
11193 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11194 cx.propagate();
11195 return;
11196 }
11197
11198 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11199
11200 let text_layout_details = &self.text_layout_details(window);
11201
11202 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11203 s.move_with(|map, selection| {
11204 if !selection.is_empty() {
11205 selection.goal = SelectionGoal::None;
11206 }
11207 let (cursor, goal) = movement::down_by_rows(
11208 map,
11209 selection.start,
11210 action.lines,
11211 selection.goal,
11212 false,
11213 text_layout_details,
11214 );
11215 selection.collapse_to(cursor, goal);
11216 });
11217 })
11218 }
11219
11220 pub fn select_down_by_lines(
11221 &mut self,
11222 action: &SelectDownByLines,
11223 window: &mut Window,
11224 cx: &mut Context<Self>,
11225 ) {
11226 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11227 let text_layout_details = &self.text_layout_details(window);
11228 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11229 s.move_heads_with(|map, head, goal| {
11230 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11231 })
11232 })
11233 }
11234
11235 pub fn select_up_by_lines(
11236 &mut self,
11237 action: &SelectUpByLines,
11238 window: &mut Window,
11239 cx: &mut Context<Self>,
11240 ) {
11241 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11242 let text_layout_details = &self.text_layout_details(window);
11243 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11244 s.move_heads_with(|map, head, goal| {
11245 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11246 })
11247 })
11248 }
11249
11250 pub fn select_page_up(
11251 &mut self,
11252 _: &SelectPageUp,
11253 window: &mut Window,
11254 cx: &mut Context<Self>,
11255 ) {
11256 let Some(row_count) = self.visible_row_count() else {
11257 return;
11258 };
11259
11260 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11261
11262 let text_layout_details = &self.text_layout_details(window);
11263
11264 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11265 s.move_heads_with(|map, head, goal| {
11266 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11267 })
11268 })
11269 }
11270
11271 pub fn move_page_up(
11272 &mut self,
11273 action: &MovePageUp,
11274 window: &mut Window,
11275 cx: &mut Context<Self>,
11276 ) {
11277 if self.take_rename(true, window, cx).is_some() {
11278 return;
11279 }
11280
11281 if self
11282 .context_menu
11283 .borrow_mut()
11284 .as_mut()
11285 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
11286 .unwrap_or(false)
11287 {
11288 return;
11289 }
11290
11291 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11292 cx.propagate();
11293 return;
11294 }
11295
11296 let Some(row_count) = self.visible_row_count() else {
11297 return;
11298 };
11299
11300 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11301
11302 let autoscroll = if action.center_cursor {
11303 Autoscroll::center()
11304 } else {
11305 Autoscroll::fit()
11306 };
11307
11308 let text_layout_details = &self.text_layout_details(window);
11309
11310 self.change_selections(Some(autoscroll), window, cx, |s| {
11311 s.move_with(|map, selection| {
11312 if !selection.is_empty() {
11313 selection.goal = SelectionGoal::None;
11314 }
11315 let (cursor, goal) = movement::up_by_rows(
11316 map,
11317 selection.end,
11318 row_count,
11319 selection.goal,
11320 false,
11321 text_layout_details,
11322 );
11323 selection.collapse_to(cursor, goal);
11324 });
11325 });
11326 }
11327
11328 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11329 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11330 let text_layout_details = &self.text_layout_details(window);
11331 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11332 s.move_heads_with(|map, head, goal| {
11333 movement::up(map, head, goal, false, text_layout_details)
11334 })
11335 })
11336 }
11337
11338 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11339 self.take_rename(true, window, cx);
11340
11341 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11342 cx.propagate();
11343 return;
11344 }
11345
11346 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11347
11348 let text_layout_details = &self.text_layout_details(window);
11349 let selection_count = self.selections.count();
11350 let first_selection = self.selections.first_anchor();
11351
11352 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11353 s.move_with(|map, selection| {
11354 if !selection.is_empty() {
11355 selection.goal = SelectionGoal::None;
11356 }
11357 let (cursor, goal) = movement::down(
11358 map,
11359 selection.end,
11360 selection.goal,
11361 false,
11362 text_layout_details,
11363 );
11364 selection.collapse_to(cursor, goal);
11365 });
11366 });
11367
11368 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11369 {
11370 cx.propagate();
11371 }
11372 }
11373
11374 pub fn select_page_down(
11375 &mut self,
11376 _: &SelectPageDown,
11377 window: &mut Window,
11378 cx: &mut Context<Self>,
11379 ) {
11380 let Some(row_count) = self.visible_row_count() else {
11381 return;
11382 };
11383
11384 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11385
11386 let text_layout_details = &self.text_layout_details(window);
11387
11388 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11389 s.move_heads_with(|map, head, goal| {
11390 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11391 })
11392 })
11393 }
11394
11395 pub fn move_page_down(
11396 &mut self,
11397 action: &MovePageDown,
11398 window: &mut Window,
11399 cx: &mut Context<Self>,
11400 ) {
11401 if self.take_rename(true, window, cx).is_some() {
11402 return;
11403 }
11404
11405 if self
11406 .context_menu
11407 .borrow_mut()
11408 .as_mut()
11409 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
11410 .unwrap_or(false)
11411 {
11412 return;
11413 }
11414
11415 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11416 cx.propagate();
11417 return;
11418 }
11419
11420 let Some(row_count) = self.visible_row_count() else {
11421 return;
11422 };
11423
11424 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11425
11426 let autoscroll = if action.center_cursor {
11427 Autoscroll::center()
11428 } else {
11429 Autoscroll::fit()
11430 };
11431
11432 let text_layout_details = &self.text_layout_details(window);
11433 self.change_selections(Some(autoscroll), window, cx, |s| {
11434 s.move_with(|map, selection| {
11435 if !selection.is_empty() {
11436 selection.goal = SelectionGoal::None;
11437 }
11438 let (cursor, goal) = movement::down_by_rows(
11439 map,
11440 selection.end,
11441 row_count,
11442 selection.goal,
11443 false,
11444 text_layout_details,
11445 );
11446 selection.collapse_to(cursor, goal);
11447 });
11448 });
11449 }
11450
11451 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11452 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11453 let text_layout_details = &self.text_layout_details(window);
11454 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11455 s.move_heads_with(|map, head, goal| {
11456 movement::down(map, head, goal, false, text_layout_details)
11457 })
11458 });
11459 }
11460
11461 pub fn context_menu_first(
11462 &mut self,
11463 _: &ContextMenuFirst,
11464 _window: &mut Window,
11465 cx: &mut Context<Self>,
11466 ) {
11467 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11468 context_menu.select_first(self.completion_provider.as_deref(), cx);
11469 }
11470 }
11471
11472 pub fn context_menu_prev(
11473 &mut self,
11474 _: &ContextMenuPrevious,
11475 _window: &mut Window,
11476 cx: &mut Context<Self>,
11477 ) {
11478 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11479 context_menu.select_prev(self.completion_provider.as_deref(), cx);
11480 }
11481 }
11482
11483 pub fn context_menu_next(
11484 &mut self,
11485 _: &ContextMenuNext,
11486 _window: &mut Window,
11487 cx: &mut Context<Self>,
11488 ) {
11489 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11490 context_menu.select_next(self.completion_provider.as_deref(), cx);
11491 }
11492 }
11493
11494 pub fn context_menu_last(
11495 &mut self,
11496 _: &ContextMenuLast,
11497 _window: &mut Window,
11498 cx: &mut Context<Self>,
11499 ) {
11500 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11501 context_menu.select_last(self.completion_provider.as_deref(), cx);
11502 }
11503 }
11504
11505 pub fn move_to_previous_word_start(
11506 &mut self,
11507 _: &MoveToPreviousWordStart,
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_word_start(map, head),
11516 SelectionGoal::None,
11517 )
11518 });
11519 })
11520 }
11521
11522 pub fn move_to_previous_subword_start(
11523 &mut self,
11524 _: &MoveToPreviousSubwordStart,
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_cursors_with(|map, head, _| {
11531 (
11532 movement::previous_subword_start(map, head),
11533 SelectionGoal::None,
11534 )
11535 });
11536 })
11537 }
11538
11539 pub fn select_to_previous_word_start(
11540 &mut self,
11541 _: &SelectToPreviousWordStart,
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_word_start(map, head),
11550 SelectionGoal::None,
11551 )
11552 });
11553 })
11554 }
11555
11556 pub fn select_to_previous_subword_start(
11557 &mut self,
11558 _: &SelectToPreviousSubwordStart,
11559 window: &mut Window,
11560 cx: &mut Context<Self>,
11561 ) {
11562 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11563 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11564 s.move_heads_with(|map, head, _| {
11565 (
11566 movement::previous_subword_start(map, head),
11567 SelectionGoal::None,
11568 )
11569 });
11570 })
11571 }
11572
11573 pub fn delete_to_previous_word_start(
11574 &mut self,
11575 action: &DeleteToPreviousWordStart,
11576 window: &mut Window,
11577 cx: &mut Context<Self>,
11578 ) {
11579 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11580 self.transact(window, cx, |this, window, cx| {
11581 this.select_autoclose_pair(window, cx);
11582 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11583 s.move_with(|map, selection| {
11584 if selection.is_empty() {
11585 let cursor = if action.ignore_newlines {
11586 movement::previous_word_start(map, selection.head())
11587 } else {
11588 movement::previous_word_start_or_newline(map, selection.head())
11589 };
11590 selection.set_head(cursor, SelectionGoal::None);
11591 }
11592 });
11593 });
11594 this.insert("", window, cx);
11595 });
11596 }
11597
11598 pub fn delete_to_previous_subword_start(
11599 &mut self,
11600 _: &DeleteToPreviousSubwordStart,
11601 window: &mut Window,
11602 cx: &mut Context<Self>,
11603 ) {
11604 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11605 self.transact(window, cx, |this, window, cx| {
11606 this.select_autoclose_pair(window, cx);
11607 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11608 s.move_with(|map, selection| {
11609 if selection.is_empty() {
11610 let cursor = movement::previous_subword_start(map, selection.head());
11611 selection.set_head(cursor, SelectionGoal::None);
11612 }
11613 });
11614 });
11615 this.insert("", window, cx);
11616 });
11617 }
11618
11619 pub fn move_to_next_word_end(
11620 &mut self,
11621 _: &MoveToNextWordEnd,
11622 window: &mut Window,
11623 cx: &mut Context<Self>,
11624 ) {
11625 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11626 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11627 s.move_cursors_with(|map, head, _| {
11628 (movement::next_word_end(map, head), SelectionGoal::None)
11629 });
11630 })
11631 }
11632
11633 pub fn move_to_next_subword_end(
11634 &mut self,
11635 _: &MoveToNextSubwordEnd,
11636 window: &mut Window,
11637 cx: &mut Context<Self>,
11638 ) {
11639 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11640 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11641 s.move_cursors_with(|map, head, _| {
11642 (movement::next_subword_end(map, head), SelectionGoal::None)
11643 });
11644 })
11645 }
11646
11647 pub fn select_to_next_word_end(
11648 &mut self,
11649 _: &SelectToNextWordEnd,
11650 window: &mut Window,
11651 cx: &mut Context<Self>,
11652 ) {
11653 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11654 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11655 s.move_heads_with(|map, head, _| {
11656 (movement::next_word_end(map, head), SelectionGoal::None)
11657 });
11658 })
11659 }
11660
11661 pub fn select_to_next_subword_end(
11662 &mut self,
11663 _: &SelectToNextSubwordEnd,
11664 window: &mut Window,
11665 cx: &mut Context<Self>,
11666 ) {
11667 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11668 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11669 s.move_heads_with(|map, head, _| {
11670 (movement::next_subword_end(map, head), SelectionGoal::None)
11671 });
11672 })
11673 }
11674
11675 pub fn delete_to_next_word_end(
11676 &mut self,
11677 action: &DeleteToNextWordEnd,
11678 window: &mut Window,
11679 cx: &mut Context<Self>,
11680 ) {
11681 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11682 self.transact(window, cx, |this, window, cx| {
11683 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11684 s.move_with(|map, selection| {
11685 if selection.is_empty() {
11686 let cursor = if action.ignore_newlines {
11687 movement::next_word_end(map, selection.head())
11688 } else {
11689 movement::next_word_end_or_newline(map, selection.head())
11690 };
11691 selection.set_head(cursor, SelectionGoal::None);
11692 }
11693 });
11694 });
11695 this.insert("", window, cx);
11696 });
11697 }
11698
11699 pub fn delete_to_next_subword_end(
11700 &mut self,
11701 _: &DeleteToNextSubwordEnd,
11702 window: &mut Window,
11703 cx: &mut Context<Self>,
11704 ) {
11705 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11706 self.transact(window, cx, |this, window, cx| {
11707 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11708 s.move_with(|map, selection| {
11709 if selection.is_empty() {
11710 let cursor = movement::next_subword_end(map, selection.head());
11711 selection.set_head(cursor, SelectionGoal::None);
11712 }
11713 });
11714 });
11715 this.insert("", window, cx);
11716 });
11717 }
11718
11719 pub fn move_to_beginning_of_line(
11720 &mut self,
11721 action: &MoveToBeginningOfLine,
11722 window: &mut Window,
11723 cx: &mut Context<Self>,
11724 ) {
11725 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11726 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11727 s.move_cursors_with(|map, head, _| {
11728 (
11729 movement::indented_line_beginning(
11730 map,
11731 head,
11732 action.stop_at_soft_wraps,
11733 action.stop_at_indent,
11734 ),
11735 SelectionGoal::None,
11736 )
11737 });
11738 })
11739 }
11740
11741 pub fn select_to_beginning_of_line(
11742 &mut self,
11743 action: &SelectToBeginningOfLine,
11744 window: &mut Window,
11745 cx: &mut Context<Self>,
11746 ) {
11747 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11748 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11749 s.move_heads_with(|map, head, _| {
11750 (
11751 movement::indented_line_beginning(
11752 map,
11753 head,
11754 action.stop_at_soft_wraps,
11755 action.stop_at_indent,
11756 ),
11757 SelectionGoal::None,
11758 )
11759 });
11760 });
11761 }
11762
11763 pub fn delete_to_beginning_of_line(
11764 &mut self,
11765 action: &DeleteToBeginningOfLine,
11766 window: &mut Window,
11767 cx: &mut Context<Self>,
11768 ) {
11769 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11770 self.transact(window, cx, |this, window, cx| {
11771 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11772 s.move_with(|_, selection| {
11773 selection.reversed = true;
11774 });
11775 });
11776
11777 this.select_to_beginning_of_line(
11778 &SelectToBeginningOfLine {
11779 stop_at_soft_wraps: false,
11780 stop_at_indent: action.stop_at_indent,
11781 },
11782 window,
11783 cx,
11784 );
11785 this.backspace(&Backspace, window, cx);
11786 });
11787 }
11788
11789 pub fn move_to_end_of_line(
11790 &mut self,
11791 action: &MoveToEndOfLine,
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_cursors_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 select_to_end_of_line(
11807 &mut self,
11808 action: &SelectToEndOfLine,
11809 window: &mut Window,
11810 cx: &mut Context<Self>,
11811 ) {
11812 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11813 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11814 s.move_heads_with(|map, head, _| {
11815 (
11816 movement::line_end(map, head, action.stop_at_soft_wraps),
11817 SelectionGoal::None,
11818 )
11819 });
11820 })
11821 }
11822
11823 pub fn delete_to_end_of_line(
11824 &mut self,
11825 _: &DeleteToEndOfLine,
11826 window: &mut Window,
11827 cx: &mut Context<Self>,
11828 ) {
11829 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11830 self.transact(window, cx, |this, window, cx| {
11831 this.select_to_end_of_line(
11832 &SelectToEndOfLine {
11833 stop_at_soft_wraps: false,
11834 },
11835 window,
11836 cx,
11837 );
11838 this.delete(&Delete, window, cx);
11839 });
11840 }
11841
11842 pub fn cut_to_end_of_line(
11843 &mut self,
11844 _: &CutToEndOfLine,
11845 window: &mut Window,
11846 cx: &mut Context<Self>,
11847 ) {
11848 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11849 self.transact(window, cx, |this, window, cx| {
11850 this.select_to_end_of_line(
11851 &SelectToEndOfLine {
11852 stop_at_soft_wraps: false,
11853 },
11854 window,
11855 cx,
11856 );
11857 this.cut(&Cut, window, cx);
11858 });
11859 }
11860
11861 pub fn move_to_start_of_paragraph(
11862 &mut self,
11863 _: &MoveToStartOfParagraph,
11864 window: &mut Window,
11865 cx: &mut Context<Self>,
11866 ) {
11867 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11868 cx.propagate();
11869 return;
11870 }
11871 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11872 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11873 s.move_with(|map, selection| {
11874 selection.collapse_to(
11875 movement::start_of_paragraph(map, selection.head(), 1),
11876 SelectionGoal::None,
11877 )
11878 });
11879 })
11880 }
11881
11882 pub fn move_to_end_of_paragraph(
11883 &mut self,
11884 _: &MoveToEndOfParagraph,
11885 window: &mut Window,
11886 cx: &mut Context<Self>,
11887 ) {
11888 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11889 cx.propagate();
11890 return;
11891 }
11892 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11893 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11894 s.move_with(|map, selection| {
11895 selection.collapse_to(
11896 movement::end_of_paragraph(map, selection.head(), 1),
11897 SelectionGoal::None,
11898 )
11899 });
11900 })
11901 }
11902
11903 pub fn select_to_start_of_paragraph(
11904 &mut self,
11905 _: &SelectToStartOfParagraph,
11906 window: &mut Window,
11907 cx: &mut Context<Self>,
11908 ) {
11909 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11910 cx.propagate();
11911 return;
11912 }
11913 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11914 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11915 s.move_heads_with(|map, head, _| {
11916 (
11917 movement::start_of_paragraph(map, head, 1),
11918 SelectionGoal::None,
11919 )
11920 });
11921 })
11922 }
11923
11924 pub fn select_to_end_of_paragraph(
11925 &mut self,
11926 _: &SelectToEndOfParagraph,
11927 window: &mut Window,
11928 cx: &mut Context<Self>,
11929 ) {
11930 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11931 cx.propagate();
11932 return;
11933 }
11934 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11935 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11936 s.move_heads_with(|map, head, _| {
11937 (
11938 movement::end_of_paragraph(map, head, 1),
11939 SelectionGoal::None,
11940 )
11941 });
11942 })
11943 }
11944
11945 pub fn move_to_start_of_excerpt(
11946 &mut self,
11947 _: &MoveToStartOfExcerpt,
11948 window: &mut Window,
11949 cx: &mut Context<Self>,
11950 ) {
11951 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11952 cx.propagate();
11953 return;
11954 }
11955 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11956 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11957 s.move_with(|map, selection| {
11958 selection.collapse_to(
11959 movement::start_of_excerpt(
11960 map,
11961 selection.head(),
11962 workspace::searchable::Direction::Prev,
11963 ),
11964 SelectionGoal::None,
11965 )
11966 });
11967 })
11968 }
11969
11970 pub fn move_to_start_of_next_excerpt(
11971 &mut self,
11972 _: &MoveToStartOfNextExcerpt,
11973 window: &mut Window,
11974 cx: &mut Context<Self>,
11975 ) {
11976 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11977 cx.propagate();
11978 return;
11979 }
11980
11981 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11982 s.move_with(|map, selection| {
11983 selection.collapse_to(
11984 movement::start_of_excerpt(
11985 map,
11986 selection.head(),
11987 workspace::searchable::Direction::Next,
11988 ),
11989 SelectionGoal::None,
11990 )
11991 });
11992 })
11993 }
11994
11995 pub fn move_to_end_of_excerpt(
11996 &mut self,
11997 _: &MoveToEndOfExcerpt,
11998 window: &mut Window,
11999 cx: &mut Context<Self>,
12000 ) {
12001 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12002 cx.propagate();
12003 return;
12004 }
12005 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12006 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12007 s.move_with(|map, selection| {
12008 selection.collapse_to(
12009 movement::end_of_excerpt(
12010 map,
12011 selection.head(),
12012 workspace::searchable::Direction::Next,
12013 ),
12014 SelectionGoal::None,
12015 )
12016 });
12017 })
12018 }
12019
12020 pub fn move_to_end_of_previous_excerpt(
12021 &mut self,
12022 _: &MoveToEndOfPreviousExcerpt,
12023 window: &mut Window,
12024 cx: &mut Context<Self>,
12025 ) {
12026 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12027 cx.propagate();
12028 return;
12029 }
12030 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12031 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12032 s.move_with(|map, selection| {
12033 selection.collapse_to(
12034 movement::end_of_excerpt(
12035 map,
12036 selection.head(),
12037 workspace::searchable::Direction::Prev,
12038 ),
12039 SelectionGoal::None,
12040 )
12041 });
12042 })
12043 }
12044
12045 pub fn select_to_start_of_excerpt(
12046 &mut self,
12047 _: &SelectToStartOfExcerpt,
12048 window: &mut Window,
12049 cx: &mut Context<Self>,
12050 ) {
12051 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12052 cx.propagate();
12053 return;
12054 }
12055 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12056 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12057 s.move_heads_with(|map, head, _| {
12058 (
12059 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12060 SelectionGoal::None,
12061 )
12062 });
12063 })
12064 }
12065
12066 pub fn select_to_start_of_next_excerpt(
12067 &mut self,
12068 _: &SelectToStartOfNextExcerpt,
12069 window: &mut Window,
12070 cx: &mut Context<Self>,
12071 ) {
12072 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12073 cx.propagate();
12074 return;
12075 }
12076 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12077 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12078 s.move_heads_with(|map, head, _| {
12079 (
12080 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12081 SelectionGoal::None,
12082 )
12083 });
12084 })
12085 }
12086
12087 pub fn select_to_end_of_excerpt(
12088 &mut self,
12089 _: &SelectToEndOfExcerpt,
12090 window: &mut Window,
12091 cx: &mut Context<Self>,
12092 ) {
12093 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12094 cx.propagate();
12095 return;
12096 }
12097 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12098 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12099 s.move_heads_with(|map, head, _| {
12100 (
12101 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12102 SelectionGoal::None,
12103 )
12104 });
12105 })
12106 }
12107
12108 pub fn select_to_end_of_previous_excerpt(
12109 &mut self,
12110 _: &SelectToEndOfPreviousExcerpt,
12111 window: &mut Window,
12112 cx: &mut Context<Self>,
12113 ) {
12114 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12115 cx.propagate();
12116 return;
12117 }
12118 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12119 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12120 s.move_heads_with(|map, head, _| {
12121 (
12122 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12123 SelectionGoal::None,
12124 )
12125 });
12126 })
12127 }
12128
12129 pub fn move_to_beginning(
12130 &mut self,
12131 _: &MoveToBeginning,
12132 window: &mut Window,
12133 cx: &mut Context<Self>,
12134 ) {
12135 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12136 cx.propagate();
12137 return;
12138 }
12139 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12140 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12141 s.select_ranges(vec![0..0]);
12142 });
12143 }
12144
12145 pub fn select_to_beginning(
12146 &mut self,
12147 _: &SelectToBeginning,
12148 window: &mut Window,
12149 cx: &mut Context<Self>,
12150 ) {
12151 let mut selection = self.selections.last::<Point>(cx);
12152 selection.set_head(Point::zero(), SelectionGoal::None);
12153 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12154 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12155 s.select(vec![selection]);
12156 });
12157 }
12158
12159 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12160 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12161 cx.propagate();
12162 return;
12163 }
12164 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12165 let cursor = self.buffer.read(cx).read(cx).len();
12166 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12167 s.select_ranges(vec![cursor..cursor])
12168 });
12169 }
12170
12171 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12172 self.nav_history = nav_history;
12173 }
12174
12175 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12176 self.nav_history.as_ref()
12177 }
12178
12179 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12180 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12181 }
12182
12183 fn push_to_nav_history(
12184 &mut self,
12185 cursor_anchor: Anchor,
12186 new_position: Option<Point>,
12187 is_deactivate: bool,
12188 cx: &mut Context<Self>,
12189 ) {
12190 if let Some(nav_history) = self.nav_history.as_mut() {
12191 let buffer = self.buffer.read(cx).read(cx);
12192 let cursor_position = cursor_anchor.to_point(&buffer);
12193 let scroll_state = self.scroll_manager.anchor();
12194 let scroll_top_row = scroll_state.top_row(&buffer);
12195 drop(buffer);
12196
12197 if let Some(new_position) = new_position {
12198 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12199 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12200 return;
12201 }
12202 }
12203
12204 nav_history.push(
12205 Some(NavigationData {
12206 cursor_anchor,
12207 cursor_position,
12208 scroll_anchor: scroll_state,
12209 scroll_top_row,
12210 }),
12211 cx,
12212 );
12213 cx.emit(EditorEvent::PushedToNavHistory {
12214 anchor: cursor_anchor,
12215 is_deactivate,
12216 })
12217 }
12218 }
12219
12220 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12221 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12222 let buffer = self.buffer.read(cx).snapshot(cx);
12223 let mut selection = self.selections.first::<usize>(cx);
12224 selection.set_head(buffer.len(), SelectionGoal::None);
12225 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12226 s.select(vec![selection]);
12227 });
12228 }
12229
12230 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12231 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12232 let end = self.buffer.read(cx).read(cx).len();
12233 self.change_selections(None, window, cx, |s| {
12234 s.select_ranges(vec![0..end]);
12235 });
12236 }
12237
12238 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12239 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12240 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12241 let mut selections = self.selections.all::<Point>(cx);
12242 let max_point = display_map.buffer_snapshot.max_point();
12243 for selection in &mut selections {
12244 let rows = selection.spanned_rows(true, &display_map);
12245 selection.start = Point::new(rows.start.0, 0);
12246 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12247 selection.reversed = false;
12248 }
12249 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12250 s.select(selections);
12251 });
12252 }
12253
12254 pub fn split_selection_into_lines(
12255 &mut self,
12256 _: &SplitSelectionIntoLines,
12257 window: &mut Window,
12258 cx: &mut Context<Self>,
12259 ) {
12260 let selections = self
12261 .selections
12262 .all::<Point>(cx)
12263 .into_iter()
12264 .map(|selection| selection.start..selection.end)
12265 .collect::<Vec<_>>();
12266 self.unfold_ranges(&selections, true, true, cx);
12267
12268 let mut new_selection_ranges = Vec::new();
12269 {
12270 let buffer = self.buffer.read(cx).read(cx);
12271 for selection in selections {
12272 for row in selection.start.row..selection.end.row {
12273 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12274 new_selection_ranges.push(cursor..cursor);
12275 }
12276
12277 let is_multiline_selection = selection.start.row != selection.end.row;
12278 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12279 // so this action feels more ergonomic when paired with other selection operations
12280 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12281 if !should_skip_last {
12282 new_selection_ranges.push(selection.end..selection.end);
12283 }
12284 }
12285 }
12286 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12287 s.select_ranges(new_selection_ranges);
12288 });
12289 }
12290
12291 pub fn add_selection_above(
12292 &mut self,
12293 _: &AddSelectionAbove,
12294 window: &mut Window,
12295 cx: &mut Context<Self>,
12296 ) {
12297 self.add_selection(true, window, cx);
12298 }
12299
12300 pub fn add_selection_below(
12301 &mut self,
12302 _: &AddSelectionBelow,
12303 window: &mut Window,
12304 cx: &mut Context<Self>,
12305 ) {
12306 self.add_selection(false, window, cx);
12307 }
12308
12309 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12310 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12311
12312 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12313 let mut selections = self.selections.all::<Point>(cx);
12314 let text_layout_details = self.text_layout_details(window);
12315 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12316 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12317 let range = oldest_selection.display_range(&display_map).sorted();
12318
12319 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12320 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12321 let positions = start_x.min(end_x)..start_x.max(end_x);
12322
12323 selections.clear();
12324 let mut stack = Vec::new();
12325 for row in range.start.row().0..=range.end.row().0 {
12326 if let Some(selection) = self.selections.build_columnar_selection(
12327 &display_map,
12328 DisplayRow(row),
12329 &positions,
12330 oldest_selection.reversed,
12331 &text_layout_details,
12332 ) {
12333 stack.push(selection.id);
12334 selections.push(selection);
12335 }
12336 }
12337
12338 if above {
12339 stack.reverse();
12340 }
12341
12342 AddSelectionsState { above, stack }
12343 });
12344
12345 let last_added_selection = *state.stack.last().unwrap();
12346 let mut new_selections = Vec::new();
12347 if above == state.above {
12348 let end_row = if above {
12349 DisplayRow(0)
12350 } else {
12351 display_map.max_point().row()
12352 };
12353
12354 'outer: for selection in selections {
12355 if selection.id == last_added_selection {
12356 let range = selection.display_range(&display_map).sorted();
12357 debug_assert_eq!(range.start.row(), range.end.row());
12358 let mut row = range.start.row();
12359 let positions =
12360 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12361 px(start)..px(end)
12362 } else {
12363 let start_x =
12364 display_map.x_for_display_point(range.start, &text_layout_details);
12365 let end_x =
12366 display_map.x_for_display_point(range.end, &text_layout_details);
12367 start_x.min(end_x)..start_x.max(end_x)
12368 };
12369
12370 while row != end_row {
12371 if above {
12372 row.0 -= 1;
12373 } else {
12374 row.0 += 1;
12375 }
12376
12377 if let Some(new_selection) = self.selections.build_columnar_selection(
12378 &display_map,
12379 row,
12380 &positions,
12381 selection.reversed,
12382 &text_layout_details,
12383 ) {
12384 state.stack.push(new_selection.id);
12385 if above {
12386 new_selections.push(new_selection);
12387 new_selections.push(selection);
12388 } else {
12389 new_selections.push(selection);
12390 new_selections.push(new_selection);
12391 }
12392
12393 continue 'outer;
12394 }
12395 }
12396 }
12397
12398 new_selections.push(selection);
12399 }
12400 } else {
12401 new_selections = selections;
12402 new_selections.retain(|s| s.id != last_added_selection);
12403 state.stack.pop();
12404 }
12405
12406 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12407 s.select(new_selections);
12408 });
12409 if state.stack.len() > 1 {
12410 self.add_selections_state = Some(state);
12411 }
12412 }
12413
12414 fn select_match_ranges(
12415 &mut self,
12416 range: Range<usize>,
12417 reversed: bool,
12418 replace_newest: bool,
12419 auto_scroll: Option<Autoscroll>,
12420 window: &mut Window,
12421 cx: &mut Context<Editor>,
12422 ) {
12423 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12424 self.change_selections(auto_scroll, window, cx, |s| {
12425 if replace_newest {
12426 s.delete(s.newest_anchor().id);
12427 }
12428 if reversed {
12429 s.insert_range(range.end..range.start);
12430 } else {
12431 s.insert_range(range);
12432 }
12433 });
12434 }
12435
12436 pub fn select_next_match_internal(
12437 &mut self,
12438 display_map: &DisplaySnapshot,
12439 replace_newest: bool,
12440 autoscroll: Option<Autoscroll>,
12441 window: &mut Window,
12442 cx: &mut Context<Self>,
12443 ) -> Result<()> {
12444 let buffer = &display_map.buffer_snapshot;
12445 let mut selections = self.selections.all::<usize>(cx);
12446 if let Some(mut select_next_state) = self.select_next_state.take() {
12447 let query = &select_next_state.query;
12448 if !select_next_state.done {
12449 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12450 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12451 let mut next_selected_range = None;
12452
12453 let bytes_after_last_selection =
12454 buffer.bytes_in_range(last_selection.end..buffer.len());
12455 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12456 let query_matches = query
12457 .stream_find_iter(bytes_after_last_selection)
12458 .map(|result| (last_selection.end, result))
12459 .chain(
12460 query
12461 .stream_find_iter(bytes_before_first_selection)
12462 .map(|result| (0, result)),
12463 );
12464
12465 for (start_offset, query_match) in query_matches {
12466 let query_match = query_match.unwrap(); // can only fail due to I/O
12467 let offset_range =
12468 start_offset + query_match.start()..start_offset + query_match.end();
12469 let display_range = offset_range.start.to_display_point(display_map)
12470 ..offset_range.end.to_display_point(display_map);
12471
12472 if !select_next_state.wordwise
12473 || (!movement::is_inside_word(display_map, display_range.start)
12474 && !movement::is_inside_word(display_map, display_range.end))
12475 {
12476 // TODO: This is n^2, because we might check all the selections
12477 if !selections
12478 .iter()
12479 .any(|selection| selection.range().overlaps(&offset_range))
12480 {
12481 next_selected_range = Some(offset_range);
12482 break;
12483 }
12484 }
12485 }
12486
12487 if let Some(next_selected_range) = next_selected_range {
12488 self.select_match_ranges(
12489 next_selected_range,
12490 last_selection.reversed,
12491 replace_newest,
12492 autoscroll,
12493 window,
12494 cx,
12495 );
12496 } else {
12497 select_next_state.done = true;
12498 }
12499 }
12500
12501 self.select_next_state = Some(select_next_state);
12502 } else {
12503 let mut only_carets = true;
12504 let mut same_text_selected = true;
12505 let mut selected_text = None;
12506
12507 let mut selections_iter = selections.iter().peekable();
12508 while let Some(selection) = selections_iter.next() {
12509 if selection.start != selection.end {
12510 only_carets = false;
12511 }
12512
12513 if same_text_selected {
12514 if selected_text.is_none() {
12515 selected_text =
12516 Some(buffer.text_for_range(selection.range()).collect::<String>());
12517 }
12518
12519 if let Some(next_selection) = selections_iter.peek() {
12520 if next_selection.range().len() == selection.range().len() {
12521 let next_selected_text = buffer
12522 .text_for_range(next_selection.range())
12523 .collect::<String>();
12524 if Some(next_selected_text) != selected_text {
12525 same_text_selected = false;
12526 selected_text = None;
12527 }
12528 } else {
12529 same_text_selected = false;
12530 selected_text = None;
12531 }
12532 }
12533 }
12534 }
12535
12536 if only_carets {
12537 for selection in &mut selections {
12538 let word_range = movement::surrounding_word(
12539 display_map,
12540 selection.start.to_display_point(display_map),
12541 );
12542 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12543 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12544 selection.goal = SelectionGoal::None;
12545 selection.reversed = false;
12546 self.select_match_ranges(
12547 selection.start..selection.end,
12548 selection.reversed,
12549 replace_newest,
12550 autoscroll,
12551 window,
12552 cx,
12553 );
12554 }
12555
12556 if selections.len() == 1 {
12557 let selection = selections
12558 .last()
12559 .expect("ensured that there's only one selection");
12560 let query = buffer
12561 .text_for_range(selection.start..selection.end)
12562 .collect::<String>();
12563 let is_empty = query.is_empty();
12564 let select_state = SelectNextState {
12565 query: AhoCorasick::new(&[query])?,
12566 wordwise: true,
12567 done: is_empty,
12568 };
12569 self.select_next_state = Some(select_state);
12570 } else {
12571 self.select_next_state = None;
12572 }
12573 } else if let Some(selected_text) = selected_text {
12574 self.select_next_state = Some(SelectNextState {
12575 query: AhoCorasick::new(&[selected_text])?,
12576 wordwise: false,
12577 done: false,
12578 });
12579 self.select_next_match_internal(
12580 display_map,
12581 replace_newest,
12582 autoscroll,
12583 window,
12584 cx,
12585 )?;
12586 }
12587 }
12588 Ok(())
12589 }
12590
12591 pub fn select_all_matches(
12592 &mut self,
12593 _action: &SelectAllMatches,
12594 window: &mut Window,
12595 cx: &mut Context<Self>,
12596 ) -> Result<()> {
12597 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12598
12599 self.push_to_selection_history();
12600 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12601
12602 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12603 let Some(select_next_state) = self.select_next_state.as_mut() else {
12604 return Ok(());
12605 };
12606 if select_next_state.done {
12607 return Ok(());
12608 }
12609
12610 let mut new_selections = Vec::new();
12611
12612 let reversed = self.selections.oldest::<usize>(cx).reversed;
12613 let buffer = &display_map.buffer_snapshot;
12614 let query_matches = select_next_state
12615 .query
12616 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12617
12618 for query_match in query_matches.into_iter() {
12619 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12620 let offset_range = if reversed {
12621 query_match.end()..query_match.start()
12622 } else {
12623 query_match.start()..query_match.end()
12624 };
12625 let display_range = offset_range.start.to_display_point(&display_map)
12626 ..offset_range.end.to_display_point(&display_map);
12627
12628 if !select_next_state.wordwise
12629 || (!movement::is_inside_word(&display_map, display_range.start)
12630 && !movement::is_inside_word(&display_map, display_range.end))
12631 {
12632 new_selections.push(offset_range.start..offset_range.end);
12633 }
12634 }
12635
12636 select_next_state.done = true;
12637 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12638 self.change_selections(None, window, cx, |selections| {
12639 selections.select_ranges(new_selections)
12640 });
12641
12642 Ok(())
12643 }
12644
12645 pub fn select_next(
12646 &mut self,
12647 action: &SelectNext,
12648 window: &mut Window,
12649 cx: &mut Context<Self>,
12650 ) -> Result<()> {
12651 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12652 self.push_to_selection_history();
12653 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12654 self.select_next_match_internal(
12655 &display_map,
12656 action.replace_newest,
12657 Some(Autoscroll::newest()),
12658 window,
12659 cx,
12660 )?;
12661 Ok(())
12662 }
12663
12664 pub fn select_previous(
12665 &mut self,
12666 action: &SelectPrevious,
12667 window: &mut Window,
12668 cx: &mut Context<Self>,
12669 ) -> Result<()> {
12670 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12671 self.push_to_selection_history();
12672 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12673 let buffer = &display_map.buffer_snapshot;
12674 let mut selections = self.selections.all::<usize>(cx);
12675 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12676 let query = &select_prev_state.query;
12677 if !select_prev_state.done {
12678 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12679 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12680 let mut next_selected_range = None;
12681 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12682 let bytes_before_last_selection =
12683 buffer.reversed_bytes_in_range(0..last_selection.start);
12684 let bytes_after_first_selection =
12685 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12686 let query_matches = query
12687 .stream_find_iter(bytes_before_last_selection)
12688 .map(|result| (last_selection.start, result))
12689 .chain(
12690 query
12691 .stream_find_iter(bytes_after_first_selection)
12692 .map(|result| (buffer.len(), result)),
12693 );
12694 for (end_offset, query_match) in query_matches {
12695 let query_match = query_match.unwrap(); // can only fail due to I/O
12696 let offset_range =
12697 end_offset - query_match.end()..end_offset - query_match.start();
12698 let display_range = offset_range.start.to_display_point(&display_map)
12699 ..offset_range.end.to_display_point(&display_map);
12700
12701 if !select_prev_state.wordwise
12702 || (!movement::is_inside_word(&display_map, display_range.start)
12703 && !movement::is_inside_word(&display_map, display_range.end))
12704 {
12705 next_selected_range = Some(offset_range);
12706 break;
12707 }
12708 }
12709
12710 if let Some(next_selected_range) = next_selected_range {
12711 self.select_match_ranges(
12712 next_selected_range,
12713 last_selection.reversed,
12714 action.replace_newest,
12715 Some(Autoscroll::newest()),
12716 window,
12717 cx,
12718 );
12719 } else {
12720 select_prev_state.done = true;
12721 }
12722 }
12723
12724 self.select_prev_state = Some(select_prev_state);
12725 } else {
12726 let mut only_carets = true;
12727 let mut same_text_selected = true;
12728 let mut selected_text = None;
12729
12730 let mut selections_iter = selections.iter().peekable();
12731 while let Some(selection) = selections_iter.next() {
12732 if selection.start != selection.end {
12733 only_carets = false;
12734 }
12735
12736 if same_text_selected {
12737 if selected_text.is_none() {
12738 selected_text =
12739 Some(buffer.text_for_range(selection.range()).collect::<String>());
12740 }
12741
12742 if let Some(next_selection) = selections_iter.peek() {
12743 if next_selection.range().len() == selection.range().len() {
12744 let next_selected_text = buffer
12745 .text_for_range(next_selection.range())
12746 .collect::<String>();
12747 if Some(next_selected_text) != selected_text {
12748 same_text_selected = false;
12749 selected_text = None;
12750 }
12751 } else {
12752 same_text_selected = false;
12753 selected_text = None;
12754 }
12755 }
12756 }
12757 }
12758
12759 if only_carets {
12760 for selection in &mut selections {
12761 let word_range = movement::surrounding_word(
12762 &display_map,
12763 selection.start.to_display_point(&display_map),
12764 );
12765 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12766 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12767 selection.goal = SelectionGoal::None;
12768 selection.reversed = false;
12769 self.select_match_ranges(
12770 selection.start..selection.end,
12771 selection.reversed,
12772 action.replace_newest,
12773 Some(Autoscroll::newest()),
12774 window,
12775 cx,
12776 );
12777 }
12778 if selections.len() == 1 {
12779 let selection = selections
12780 .last()
12781 .expect("ensured that there's only one selection");
12782 let query = buffer
12783 .text_for_range(selection.start..selection.end)
12784 .collect::<String>();
12785 let is_empty = query.is_empty();
12786 let select_state = SelectNextState {
12787 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12788 wordwise: true,
12789 done: is_empty,
12790 };
12791 self.select_prev_state = Some(select_state);
12792 } else {
12793 self.select_prev_state = None;
12794 }
12795 } else if let Some(selected_text) = selected_text {
12796 self.select_prev_state = Some(SelectNextState {
12797 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12798 wordwise: false,
12799 done: false,
12800 });
12801 self.select_previous(action, window, cx)?;
12802 }
12803 }
12804 Ok(())
12805 }
12806
12807 pub fn find_next_match(
12808 &mut self,
12809 _: &FindNextMatch,
12810 window: &mut Window,
12811 cx: &mut Context<Self>,
12812 ) -> Result<()> {
12813 let selections = self.selections.disjoint_anchors();
12814 match selections.first() {
12815 Some(first) if selections.len() >= 2 => {
12816 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12817 s.select_ranges([first.range()]);
12818 });
12819 }
12820 _ => self.select_next(
12821 &SelectNext {
12822 replace_newest: true,
12823 },
12824 window,
12825 cx,
12826 )?,
12827 }
12828 Ok(())
12829 }
12830
12831 pub fn find_previous_match(
12832 &mut self,
12833 _: &FindPreviousMatch,
12834 window: &mut Window,
12835 cx: &mut Context<Self>,
12836 ) -> Result<()> {
12837 let selections = self.selections.disjoint_anchors();
12838 match selections.last() {
12839 Some(last) if selections.len() >= 2 => {
12840 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12841 s.select_ranges([last.range()]);
12842 });
12843 }
12844 _ => self.select_previous(
12845 &SelectPrevious {
12846 replace_newest: true,
12847 },
12848 window,
12849 cx,
12850 )?,
12851 }
12852 Ok(())
12853 }
12854
12855 pub fn toggle_comments(
12856 &mut self,
12857 action: &ToggleComments,
12858 window: &mut Window,
12859 cx: &mut Context<Self>,
12860 ) {
12861 if self.read_only(cx) {
12862 return;
12863 }
12864 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12865 let text_layout_details = &self.text_layout_details(window);
12866 self.transact(window, cx, |this, window, cx| {
12867 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12868 let mut edits = Vec::new();
12869 let mut selection_edit_ranges = Vec::new();
12870 let mut last_toggled_row = None;
12871 let snapshot = this.buffer.read(cx).read(cx);
12872 let empty_str: Arc<str> = Arc::default();
12873 let mut suffixes_inserted = Vec::new();
12874 let ignore_indent = action.ignore_indent;
12875
12876 fn comment_prefix_range(
12877 snapshot: &MultiBufferSnapshot,
12878 row: MultiBufferRow,
12879 comment_prefix: &str,
12880 comment_prefix_whitespace: &str,
12881 ignore_indent: bool,
12882 ) -> Range<Point> {
12883 let indent_size = if ignore_indent {
12884 0
12885 } else {
12886 snapshot.indent_size_for_line(row).len
12887 };
12888
12889 let start = Point::new(row.0, indent_size);
12890
12891 let mut line_bytes = snapshot
12892 .bytes_in_range(start..snapshot.max_point())
12893 .flatten()
12894 .copied();
12895
12896 // If this line currently begins with the line comment prefix, then record
12897 // the range containing the prefix.
12898 if line_bytes
12899 .by_ref()
12900 .take(comment_prefix.len())
12901 .eq(comment_prefix.bytes())
12902 {
12903 // Include any whitespace that matches the comment prefix.
12904 let matching_whitespace_len = line_bytes
12905 .zip(comment_prefix_whitespace.bytes())
12906 .take_while(|(a, b)| a == b)
12907 .count() as u32;
12908 let end = Point::new(
12909 start.row,
12910 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12911 );
12912 start..end
12913 } else {
12914 start..start
12915 }
12916 }
12917
12918 fn comment_suffix_range(
12919 snapshot: &MultiBufferSnapshot,
12920 row: MultiBufferRow,
12921 comment_suffix: &str,
12922 comment_suffix_has_leading_space: bool,
12923 ) -> Range<Point> {
12924 let end = Point::new(row.0, snapshot.line_len(row));
12925 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12926
12927 let mut line_end_bytes = snapshot
12928 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12929 .flatten()
12930 .copied();
12931
12932 let leading_space_len = if suffix_start_column > 0
12933 && line_end_bytes.next() == Some(b' ')
12934 && comment_suffix_has_leading_space
12935 {
12936 1
12937 } else {
12938 0
12939 };
12940
12941 // If this line currently begins with the line comment prefix, then record
12942 // the range containing the prefix.
12943 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12944 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12945 start..end
12946 } else {
12947 end..end
12948 }
12949 }
12950
12951 // TODO: Handle selections that cross excerpts
12952 for selection in &mut selections {
12953 let start_column = snapshot
12954 .indent_size_for_line(MultiBufferRow(selection.start.row))
12955 .len;
12956 let language = if let Some(language) =
12957 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12958 {
12959 language
12960 } else {
12961 continue;
12962 };
12963
12964 selection_edit_ranges.clear();
12965
12966 // If multiple selections contain a given row, avoid processing that
12967 // row more than once.
12968 let mut start_row = MultiBufferRow(selection.start.row);
12969 if last_toggled_row == Some(start_row) {
12970 start_row = start_row.next_row();
12971 }
12972 let end_row =
12973 if selection.end.row > selection.start.row && selection.end.column == 0 {
12974 MultiBufferRow(selection.end.row - 1)
12975 } else {
12976 MultiBufferRow(selection.end.row)
12977 };
12978 last_toggled_row = Some(end_row);
12979
12980 if start_row > end_row {
12981 continue;
12982 }
12983
12984 // If the language has line comments, toggle those.
12985 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12986
12987 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12988 if ignore_indent {
12989 full_comment_prefixes = full_comment_prefixes
12990 .into_iter()
12991 .map(|s| Arc::from(s.trim_end()))
12992 .collect();
12993 }
12994
12995 if !full_comment_prefixes.is_empty() {
12996 let first_prefix = full_comment_prefixes
12997 .first()
12998 .expect("prefixes is non-empty");
12999 let prefix_trimmed_lengths = full_comment_prefixes
13000 .iter()
13001 .map(|p| p.trim_end_matches(' ').len())
13002 .collect::<SmallVec<[usize; 4]>>();
13003
13004 let mut all_selection_lines_are_comments = true;
13005
13006 for row in start_row.0..=end_row.0 {
13007 let row = MultiBufferRow(row);
13008 if start_row < end_row && snapshot.is_line_blank(row) {
13009 continue;
13010 }
13011
13012 let prefix_range = full_comment_prefixes
13013 .iter()
13014 .zip(prefix_trimmed_lengths.iter().copied())
13015 .map(|(prefix, trimmed_prefix_len)| {
13016 comment_prefix_range(
13017 snapshot.deref(),
13018 row,
13019 &prefix[..trimmed_prefix_len],
13020 &prefix[trimmed_prefix_len..],
13021 ignore_indent,
13022 )
13023 })
13024 .max_by_key(|range| range.end.column - range.start.column)
13025 .expect("prefixes is non-empty");
13026
13027 if prefix_range.is_empty() {
13028 all_selection_lines_are_comments = false;
13029 }
13030
13031 selection_edit_ranges.push(prefix_range);
13032 }
13033
13034 if all_selection_lines_are_comments {
13035 edits.extend(
13036 selection_edit_ranges
13037 .iter()
13038 .cloned()
13039 .map(|range| (range, empty_str.clone())),
13040 );
13041 } else {
13042 let min_column = selection_edit_ranges
13043 .iter()
13044 .map(|range| range.start.column)
13045 .min()
13046 .unwrap_or(0);
13047 edits.extend(selection_edit_ranges.iter().map(|range| {
13048 let position = Point::new(range.start.row, min_column);
13049 (position..position, first_prefix.clone())
13050 }));
13051 }
13052 } else if let Some((full_comment_prefix, comment_suffix)) =
13053 language.block_comment_delimiters()
13054 {
13055 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13056 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13057 let prefix_range = comment_prefix_range(
13058 snapshot.deref(),
13059 start_row,
13060 comment_prefix,
13061 comment_prefix_whitespace,
13062 ignore_indent,
13063 );
13064 let suffix_range = comment_suffix_range(
13065 snapshot.deref(),
13066 end_row,
13067 comment_suffix.trim_start_matches(' '),
13068 comment_suffix.starts_with(' '),
13069 );
13070
13071 if prefix_range.is_empty() || suffix_range.is_empty() {
13072 edits.push((
13073 prefix_range.start..prefix_range.start,
13074 full_comment_prefix.clone(),
13075 ));
13076 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13077 suffixes_inserted.push((end_row, comment_suffix.len()));
13078 } else {
13079 edits.push((prefix_range, empty_str.clone()));
13080 edits.push((suffix_range, empty_str.clone()));
13081 }
13082 } else {
13083 continue;
13084 }
13085 }
13086
13087 drop(snapshot);
13088 this.buffer.update(cx, |buffer, cx| {
13089 buffer.edit(edits, None, cx);
13090 });
13091
13092 // Adjust selections so that they end before any comment suffixes that
13093 // were inserted.
13094 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13095 let mut selections = this.selections.all::<Point>(cx);
13096 let snapshot = this.buffer.read(cx).read(cx);
13097 for selection in &mut selections {
13098 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13099 match row.cmp(&MultiBufferRow(selection.end.row)) {
13100 Ordering::Less => {
13101 suffixes_inserted.next();
13102 continue;
13103 }
13104 Ordering::Greater => break,
13105 Ordering::Equal => {
13106 if selection.end.column == snapshot.line_len(row) {
13107 if selection.is_empty() {
13108 selection.start.column -= suffix_len as u32;
13109 }
13110 selection.end.column -= suffix_len as u32;
13111 }
13112 break;
13113 }
13114 }
13115 }
13116 }
13117
13118 drop(snapshot);
13119 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13120 s.select(selections)
13121 });
13122
13123 let selections = this.selections.all::<Point>(cx);
13124 let selections_on_single_row = selections.windows(2).all(|selections| {
13125 selections[0].start.row == selections[1].start.row
13126 && selections[0].end.row == selections[1].end.row
13127 && selections[0].start.row == selections[0].end.row
13128 });
13129 let selections_selecting = selections
13130 .iter()
13131 .any(|selection| selection.start != selection.end);
13132 let advance_downwards = action.advance_downwards
13133 && selections_on_single_row
13134 && !selections_selecting
13135 && !matches!(this.mode, EditorMode::SingleLine { .. });
13136
13137 if advance_downwards {
13138 let snapshot = this.buffer.read(cx).snapshot(cx);
13139
13140 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13141 s.move_cursors_with(|display_snapshot, display_point, _| {
13142 let mut point = display_point.to_point(display_snapshot);
13143 point.row += 1;
13144 point = snapshot.clip_point(point, Bias::Left);
13145 let display_point = point.to_display_point(display_snapshot);
13146 let goal = SelectionGoal::HorizontalPosition(
13147 display_snapshot
13148 .x_for_display_point(display_point, text_layout_details)
13149 .into(),
13150 );
13151 (display_point, goal)
13152 })
13153 });
13154 }
13155 });
13156 }
13157
13158 pub fn select_enclosing_symbol(
13159 &mut self,
13160 _: &SelectEnclosingSymbol,
13161 window: &mut Window,
13162 cx: &mut Context<Self>,
13163 ) {
13164 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13165
13166 let buffer = self.buffer.read(cx).snapshot(cx);
13167 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13168
13169 fn update_selection(
13170 selection: &Selection<usize>,
13171 buffer_snap: &MultiBufferSnapshot,
13172 ) -> Option<Selection<usize>> {
13173 let cursor = selection.head();
13174 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13175 for symbol in symbols.iter().rev() {
13176 let start = symbol.range.start.to_offset(buffer_snap);
13177 let end = symbol.range.end.to_offset(buffer_snap);
13178 let new_range = start..end;
13179 if start < selection.start || end > selection.end {
13180 return Some(Selection {
13181 id: selection.id,
13182 start: new_range.start,
13183 end: new_range.end,
13184 goal: SelectionGoal::None,
13185 reversed: selection.reversed,
13186 });
13187 }
13188 }
13189 None
13190 }
13191
13192 let mut selected_larger_symbol = false;
13193 let new_selections = old_selections
13194 .iter()
13195 .map(|selection| match update_selection(selection, &buffer) {
13196 Some(new_selection) => {
13197 if new_selection.range() != selection.range() {
13198 selected_larger_symbol = true;
13199 }
13200 new_selection
13201 }
13202 None => selection.clone(),
13203 })
13204 .collect::<Vec<_>>();
13205
13206 if selected_larger_symbol {
13207 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13208 s.select(new_selections);
13209 });
13210 }
13211 }
13212
13213 pub fn select_larger_syntax_node(
13214 &mut self,
13215 _: &SelectLargerSyntaxNode,
13216 window: &mut Window,
13217 cx: &mut Context<Self>,
13218 ) {
13219 let Some(visible_row_count) = self.visible_row_count() else {
13220 return;
13221 };
13222 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13223 if old_selections.is_empty() {
13224 return;
13225 }
13226
13227 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13228
13229 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13230 let buffer = self.buffer.read(cx).snapshot(cx);
13231
13232 let mut selected_larger_node = false;
13233 let mut new_selections = old_selections
13234 .iter()
13235 .map(|selection| {
13236 let old_range = selection.start..selection.end;
13237
13238 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13239 // manually select word at selection
13240 if ["string_content", "inline"].contains(&node.kind()) {
13241 let word_range = {
13242 let display_point = buffer
13243 .offset_to_point(old_range.start)
13244 .to_display_point(&display_map);
13245 let Range { start, end } =
13246 movement::surrounding_word(&display_map, display_point);
13247 start.to_point(&display_map).to_offset(&buffer)
13248 ..end.to_point(&display_map).to_offset(&buffer)
13249 };
13250 // ignore if word is already selected
13251 if !word_range.is_empty() && old_range != word_range {
13252 let last_word_range = {
13253 let display_point = buffer
13254 .offset_to_point(old_range.end)
13255 .to_display_point(&display_map);
13256 let Range { start, end } =
13257 movement::surrounding_word(&display_map, display_point);
13258 start.to_point(&display_map).to_offset(&buffer)
13259 ..end.to_point(&display_map).to_offset(&buffer)
13260 };
13261 // only select word if start and end point belongs to same word
13262 if word_range == last_word_range {
13263 selected_larger_node = true;
13264 return Selection {
13265 id: selection.id,
13266 start: word_range.start,
13267 end: word_range.end,
13268 goal: SelectionGoal::None,
13269 reversed: selection.reversed,
13270 };
13271 }
13272 }
13273 }
13274 }
13275
13276 let mut new_range = old_range.clone();
13277 while let Some((_node, containing_range)) =
13278 buffer.syntax_ancestor(new_range.clone())
13279 {
13280 new_range = match containing_range {
13281 MultiOrSingleBufferOffsetRange::Single(_) => break,
13282 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13283 };
13284 if !display_map.intersects_fold(new_range.start)
13285 && !display_map.intersects_fold(new_range.end)
13286 {
13287 break;
13288 }
13289 }
13290
13291 selected_larger_node |= new_range != old_range;
13292 Selection {
13293 id: selection.id,
13294 start: new_range.start,
13295 end: new_range.end,
13296 goal: SelectionGoal::None,
13297 reversed: selection.reversed,
13298 }
13299 })
13300 .collect::<Vec<_>>();
13301
13302 if !selected_larger_node {
13303 return; // don't put this call in the history
13304 }
13305
13306 // scroll based on transformation done to the last selection created by the user
13307 let (last_old, last_new) = old_selections
13308 .last()
13309 .zip(new_selections.last().cloned())
13310 .expect("old_selections isn't empty");
13311
13312 // revert selection
13313 let is_selection_reversed = {
13314 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13315 new_selections.last_mut().expect("checked above").reversed =
13316 should_newest_selection_be_reversed;
13317 should_newest_selection_be_reversed
13318 };
13319
13320 if selected_larger_node {
13321 self.select_syntax_node_history.disable_clearing = true;
13322 self.change_selections(None, window, cx, |s| {
13323 s.select(new_selections.clone());
13324 });
13325 self.select_syntax_node_history.disable_clearing = false;
13326 }
13327
13328 let start_row = last_new.start.to_display_point(&display_map).row().0;
13329 let end_row = last_new.end.to_display_point(&display_map).row().0;
13330 let selection_height = end_row - start_row + 1;
13331 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13332
13333 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13334 let scroll_behavior = if fits_on_the_screen {
13335 self.request_autoscroll(Autoscroll::fit(), cx);
13336 SelectSyntaxNodeScrollBehavior::FitSelection
13337 } else if is_selection_reversed {
13338 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13339 SelectSyntaxNodeScrollBehavior::CursorTop
13340 } else {
13341 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13342 SelectSyntaxNodeScrollBehavior::CursorBottom
13343 };
13344
13345 self.select_syntax_node_history.push((
13346 old_selections,
13347 scroll_behavior,
13348 is_selection_reversed,
13349 ));
13350 }
13351
13352 pub fn select_smaller_syntax_node(
13353 &mut self,
13354 _: &SelectSmallerSyntaxNode,
13355 window: &mut Window,
13356 cx: &mut Context<Self>,
13357 ) {
13358 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13359
13360 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13361 self.select_syntax_node_history.pop()
13362 {
13363 if let Some(selection) = selections.last_mut() {
13364 selection.reversed = is_selection_reversed;
13365 }
13366
13367 self.select_syntax_node_history.disable_clearing = true;
13368 self.change_selections(None, window, cx, |s| {
13369 s.select(selections.to_vec());
13370 });
13371 self.select_syntax_node_history.disable_clearing = false;
13372
13373 match scroll_behavior {
13374 SelectSyntaxNodeScrollBehavior::CursorTop => {
13375 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13376 }
13377 SelectSyntaxNodeScrollBehavior::FitSelection => {
13378 self.request_autoscroll(Autoscroll::fit(), cx);
13379 }
13380 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13381 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13382 }
13383 }
13384 }
13385 }
13386
13387 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13388 if !EditorSettings::get_global(cx).gutter.runnables {
13389 self.clear_tasks();
13390 return Task::ready(());
13391 }
13392 let project = self.project.as_ref().map(Entity::downgrade);
13393 let task_sources = self.lsp_task_sources(cx);
13394 cx.spawn_in(window, async move |editor, cx| {
13395 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13396 let Some(project) = project.and_then(|p| p.upgrade()) else {
13397 return;
13398 };
13399 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13400 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13401 }) else {
13402 return;
13403 };
13404
13405 let hide_runnables = project
13406 .update(cx, |project, cx| {
13407 // Do not display any test indicators in non-dev server remote projects.
13408 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13409 })
13410 .unwrap_or(true);
13411 if hide_runnables {
13412 return;
13413 }
13414 let new_rows =
13415 cx.background_spawn({
13416 let snapshot = display_snapshot.clone();
13417 async move {
13418 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13419 }
13420 })
13421 .await;
13422 let Ok(lsp_tasks) =
13423 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13424 else {
13425 return;
13426 };
13427 let lsp_tasks = lsp_tasks.await;
13428
13429 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13430 lsp_tasks
13431 .into_iter()
13432 .flat_map(|(kind, tasks)| {
13433 tasks.into_iter().filter_map(move |(location, task)| {
13434 Some((kind.clone(), location?, task))
13435 })
13436 })
13437 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13438 let buffer = location.target.buffer;
13439 let buffer_snapshot = buffer.read(cx).snapshot();
13440 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13441 |(excerpt_id, snapshot, _)| {
13442 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13443 display_snapshot
13444 .buffer_snapshot
13445 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13446 } else {
13447 None
13448 }
13449 },
13450 );
13451 if let Some(offset) = offset {
13452 let task_buffer_range =
13453 location.target.range.to_point(&buffer_snapshot);
13454 let context_buffer_range =
13455 task_buffer_range.to_offset(&buffer_snapshot);
13456 let context_range = BufferOffset(context_buffer_range.start)
13457 ..BufferOffset(context_buffer_range.end);
13458
13459 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13460 .or_insert_with(|| RunnableTasks {
13461 templates: Vec::new(),
13462 offset,
13463 column: task_buffer_range.start.column,
13464 extra_variables: HashMap::default(),
13465 context_range,
13466 })
13467 .templates
13468 .push((kind, task.original_task().clone()));
13469 }
13470
13471 acc
13472 })
13473 }) else {
13474 return;
13475 };
13476
13477 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
13478 editor
13479 .update(cx, |editor, _| {
13480 editor.clear_tasks();
13481 for (key, mut value) in rows {
13482 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13483 value.templates.extend(lsp_tasks.templates);
13484 }
13485
13486 editor.insert_tasks(key, value);
13487 }
13488 for (key, value) in lsp_tasks_by_rows {
13489 editor.insert_tasks(key, value);
13490 }
13491 })
13492 .ok();
13493 })
13494 }
13495 fn fetch_runnable_ranges(
13496 snapshot: &DisplaySnapshot,
13497 range: Range<Anchor>,
13498 ) -> Vec<language::RunnableRange> {
13499 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13500 }
13501
13502 fn runnable_rows(
13503 project: Entity<Project>,
13504 snapshot: DisplaySnapshot,
13505 runnable_ranges: Vec<RunnableRange>,
13506 mut cx: AsyncWindowContext,
13507 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13508 runnable_ranges
13509 .into_iter()
13510 .filter_map(|mut runnable| {
13511 let tasks = cx
13512 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13513 .ok()?;
13514 if tasks.is_empty() {
13515 return None;
13516 }
13517
13518 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13519
13520 let row = snapshot
13521 .buffer_snapshot
13522 .buffer_line_for_row(MultiBufferRow(point.row))?
13523 .1
13524 .start
13525 .row;
13526
13527 let context_range =
13528 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13529 Some((
13530 (runnable.buffer_id, row),
13531 RunnableTasks {
13532 templates: tasks,
13533 offset: snapshot
13534 .buffer_snapshot
13535 .anchor_before(runnable.run_range.start),
13536 context_range,
13537 column: point.column,
13538 extra_variables: runnable.extra_captures,
13539 },
13540 ))
13541 })
13542 .collect()
13543 }
13544
13545 fn templates_with_tags(
13546 project: &Entity<Project>,
13547 runnable: &mut Runnable,
13548 cx: &mut App,
13549 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13550 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13551 let (worktree_id, file) = project
13552 .buffer_for_id(runnable.buffer, cx)
13553 .and_then(|buffer| buffer.read(cx).file())
13554 .map(|file| (file.worktree_id(cx), file.clone()))
13555 .unzip();
13556
13557 (
13558 project.task_store().read(cx).task_inventory().cloned(),
13559 worktree_id,
13560 file,
13561 )
13562 });
13563
13564 let mut templates_with_tags = mem::take(&mut runnable.tags)
13565 .into_iter()
13566 .flat_map(|RunnableTag(tag)| {
13567 inventory
13568 .as_ref()
13569 .into_iter()
13570 .flat_map(|inventory| {
13571 inventory.read(cx).list_tasks(
13572 file.clone(),
13573 Some(runnable.language.clone()),
13574 worktree_id,
13575 cx,
13576 )
13577 })
13578 .filter(move |(_, template)| {
13579 template.tags.iter().any(|source_tag| source_tag == &tag)
13580 })
13581 })
13582 .sorted_by_key(|(kind, _)| kind.to_owned())
13583 .collect::<Vec<_>>();
13584 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13585 // Strongest source wins; if we have worktree tag binding, prefer that to
13586 // global and language bindings;
13587 // if we have a global binding, prefer that to language binding.
13588 let first_mismatch = templates_with_tags
13589 .iter()
13590 .position(|(tag_source, _)| tag_source != leading_tag_source);
13591 if let Some(index) = first_mismatch {
13592 templates_with_tags.truncate(index);
13593 }
13594 }
13595
13596 templates_with_tags
13597 }
13598
13599 pub fn move_to_enclosing_bracket(
13600 &mut self,
13601 _: &MoveToEnclosingBracket,
13602 window: &mut Window,
13603 cx: &mut Context<Self>,
13604 ) {
13605 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13606 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13607 s.move_offsets_with(|snapshot, selection| {
13608 let Some(enclosing_bracket_ranges) =
13609 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13610 else {
13611 return;
13612 };
13613
13614 let mut best_length = usize::MAX;
13615 let mut best_inside = false;
13616 let mut best_in_bracket_range = false;
13617 let mut best_destination = None;
13618 for (open, close) in enclosing_bracket_ranges {
13619 let close = close.to_inclusive();
13620 let length = close.end() - open.start;
13621 let inside = selection.start >= open.end && selection.end <= *close.start();
13622 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13623 || close.contains(&selection.head());
13624
13625 // If best is next to a bracket and current isn't, skip
13626 if !in_bracket_range && best_in_bracket_range {
13627 continue;
13628 }
13629
13630 // Prefer smaller lengths unless best is inside and current isn't
13631 if length > best_length && (best_inside || !inside) {
13632 continue;
13633 }
13634
13635 best_length = length;
13636 best_inside = inside;
13637 best_in_bracket_range = in_bracket_range;
13638 best_destination = Some(
13639 if close.contains(&selection.start) && close.contains(&selection.end) {
13640 if inside { open.end } else { open.start }
13641 } else if inside {
13642 *close.start()
13643 } else {
13644 *close.end()
13645 },
13646 );
13647 }
13648
13649 if let Some(destination) = best_destination {
13650 selection.collapse_to(destination, SelectionGoal::None);
13651 }
13652 })
13653 });
13654 }
13655
13656 pub fn undo_selection(
13657 &mut self,
13658 _: &UndoSelection,
13659 window: &mut Window,
13660 cx: &mut Context<Self>,
13661 ) {
13662 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13663 self.end_selection(window, cx);
13664 self.selection_history.mode = SelectionHistoryMode::Undoing;
13665 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13666 self.change_selections(None, window, cx, |s| {
13667 s.select_anchors(entry.selections.to_vec())
13668 });
13669 self.select_next_state = entry.select_next_state;
13670 self.select_prev_state = entry.select_prev_state;
13671 self.add_selections_state = entry.add_selections_state;
13672 self.request_autoscroll(Autoscroll::newest(), cx);
13673 }
13674 self.selection_history.mode = SelectionHistoryMode::Normal;
13675 }
13676
13677 pub fn redo_selection(
13678 &mut self,
13679 _: &RedoSelection,
13680 window: &mut Window,
13681 cx: &mut Context<Self>,
13682 ) {
13683 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13684 self.end_selection(window, cx);
13685 self.selection_history.mode = SelectionHistoryMode::Redoing;
13686 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13687 self.change_selections(None, window, cx, |s| {
13688 s.select_anchors(entry.selections.to_vec())
13689 });
13690 self.select_next_state = entry.select_next_state;
13691 self.select_prev_state = entry.select_prev_state;
13692 self.add_selections_state = entry.add_selections_state;
13693 self.request_autoscroll(Autoscroll::newest(), cx);
13694 }
13695 self.selection_history.mode = SelectionHistoryMode::Normal;
13696 }
13697
13698 pub fn expand_excerpts(
13699 &mut self,
13700 action: &ExpandExcerpts,
13701 _: &mut Window,
13702 cx: &mut Context<Self>,
13703 ) {
13704 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13705 }
13706
13707 pub fn expand_excerpts_down(
13708 &mut self,
13709 action: &ExpandExcerptsDown,
13710 _: &mut Window,
13711 cx: &mut Context<Self>,
13712 ) {
13713 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13714 }
13715
13716 pub fn expand_excerpts_up(
13717 &mut self,
13718 action: &ExpandExcerptsUp,
13719 _: &mut Window,
13720 cx: &mut Context<Self>,
13721 ) {
13722 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13723 }
13724
13725 pub fn expand_excerpts_for_direction(
13726 &mut self,
13727 lines: u32,
13728 direction: ExpandExcerptDirection,
13729
13730 cx: &mut Context<Self>,
13731 ) {
13732 let selections = self.selections.disjoint_anchors();
13733
13734 let lines = if lines == 0 {
13735 EditorSettings::get_global(cx).expand_excerpt_lines
13736 } else {
13737 lines
13738 };
13739
13740 self.buffer.update(cx, |buffer, cx| {
13741 let snapshot = buffer.snapshot(cx);
13742 let mut excerpt_ids = selections
13743 .iter()
13744 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13745 .collect::<Vec<_>>();
13746 excerpt_ids.sort();
13747 excerpt_ids.dedup();
13748 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13749 })
13750 }
13751
13752 pub fn expand_excerpt(
13753 &mut self,
13754 excerpt: ExcerptId,
13755 direction: ExpandExcerptDirection,
13756 window: &mut Window,
13757 cx: &mut Context<Self>,
13758 ) {
13759 let current_scroll_position = self.scroll_position(cx);
13760 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13761 let mut should_scroll_up = false;
13762
13763 if direction == ExpandExcerptDirection::Down {
13764 let multi_buffer = self.buffer.read(cx);
13765 let snapshot = multi_buffer.snapshot(cx);
13766 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13767 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13768 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13769 let buffer_snapshot = buffer.read(cx).snapshot();
13770 let excerpt_end_row =
13771 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13772 let last_row = buffer_snapshot.max_point().row;
13773 let lines_below = last_row.saturating_sub(excerpt_end_row);
13774 should_scroll_up = lines_below >= lines_to_expand;
13775 }
13776 }
13777 }
13778 }
13779
13780 self.buffer.update(cx, |buffer, cx| {
13781 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13782 });
13783
13784 if should_scroll_up {
13785 let new_scroll_position =
13786 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13787 self.set_scroll_position(new_scroll_position, window, cx);
13788 }
13789 }
13790
13791 pub fn go_to_singleton_buffer_point(
13792 &mut self,
13793 point: Point,
13794 window: &mut Window,
13795 cx: &mut Context<Self>,
13796 ) {
13797 self.go_to_singleton_buffer_range(point..point, window, cx);
13798 }
13799
13800 pub fn go_to_singleton_buffer_range(
13801 &mut self,
13802 range: Range<Point>,
13803 window: &mut Window,
13804 cx: &mut Context<Self>,
13805 ) {
13806 let multibuffer = self.buffer().read(cx);
13807 let Some(buffer) = multibuffer.as_singleton() else {
13808 return;
13809 };
13810 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13811 return;
13812 };
13813 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13814 return;
13815 };
13816 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13817 s.select_anchor_ranges([start..end])
13818 });
13819 }
13820
13821 pub fn go_to_diagnostic(
13822 &mut self,
13823 _: &GoToDiagnostic,
13824 window: &mut Window,
13825 cx: &mut Context<Self>,
13826 ) {
13827 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13828 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13829 }
13830
13831 pub fn go_to_prev_diagnostic(
13832 &mut self,
13833 _: &GoToPreviousDiagnostic,
13834 window: &mut Window,
13835 cx: &mut Context<Self>,
13836 ) {
13837 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13838 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13839 }
13840
13841 pub fn go_to_diagnostic_impl(
13842 &mut self,
13843 direction: Direction,
13844 window: &mut Window,
13845 cx: &mut Context<Self>,
13846 ) {
13847 let buffer = self.buffer.read(cx).snapshot(cx);
13848 let selection = self.selections.newest::<usize>(cx);
13849
13850 let mut active_group_id = None;
13851 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13852 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13853 active_group_id = Some(active_group.group_id);
13854 }
13855 }
13856
13857 fn filtered(
13858 snapshot: EditorSnapshot,
13859 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13860 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13861 diagnostics
13862 .filter(|entry| entry.range.start != entry.range.end)
13863 .filter(|entry| !entry.diagnostic.is_unnecessary)
13864 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13865 }
13866
13867 let snapshot = self.snapshot(window, cx);
13868 let before = filtered(
13869 snapshot.clone(),
13870 buffer
13871 .diagnostics_in_range(0..selection.start)
13872 .filter(|entry| entry.range.start <= selection.start),
13873 );
13874 let after = filtered(
13875 snapshot,
13876 buffer
13877 .diagnostics_in_range(selection.start..buffer.len())
13878 .filter(|entry| entry.range.start >= selection.start),
13879 );
13880
13881 let mut found: Option<DiagnosticEntry<usize>> = None;
13882 if direction == Direction::Prev {
13883 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13884 {
13885 for diagnostic in prev_diagnostics.into_iter().rev() {
13886 if diagnostic.range.start != selection.start
13887 || active_group_id
13888 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13889 {
13890 found = Some(diagnostic);
13891 break 'outer;
13892 }
13893 }
13894 }
13895 } else {
13896 for diagnostic in after.chain(before) {
13897 if diagnostic.range.start != selection.start
13898 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13899 {
13900 found = Some(diagnostic);
13901 break;
13902 }
13903 }
13904 }
13905 let Some(next_diagnostic) = found else {
13906 return;
13907 };
13908
13909 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13910 return;
13911 };
13912 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13913 s.select_ranges(vec![
13914 next_diagnostic.range.start..next_diagnostic.range.start,
13915 ])
13916 });
13917 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13918 self.refresh_inline_completion(false, true, window, cx);
13919 }
13920
13921 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13922 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13923 let snapshot = self.snapshot(window, cx);
13924 let selection = self.selections.newest::<Point>(cx);
13925 self.go_to_hunk_before_or_after_position(
13926 &snapshot,
13927 selection.head(),
13928 Direction::Next,
13929 window,
13930 cx,
13931 );
13932 }
13933
13934 pub fn go_to_hunk_before_or_after_position(
13935 &mut self,
13936 snapshot: &EditorSnapshot,
13937 position: Point,
13938 direction: Direction,
13939 window: &mut Window,
13940 cx: &mut Context<Editor>,
13941 ) {
13942 let row = if direction == Direction::Next {
13943 self.hunk_after_position(snapshot, position)
13944 .map(|hunk| hunk.row_range.start)
13945 } else {
13946 self.hunk_before_position(snapshot, position)
13947 };
13948
13949 if let Some(row) = row {
13950 let destination = Point::new(row.0, 0);
13951 let autoscroll = Autoscroll::center();
13952
13953 self.unfold_ranges(&[destination..destination], false, false, cx);
13954 self.change_selections(Some(autoscroll), window, cx, |s| {
13955 s.select_ranges([destination..destination]);
13956 });
13957 }
13958 }
13959
13960 fn hunk_after_position(
13961 &mut self,
13962 snapshot: &EditorSnapshot,
13963 position: Point,
13964 ) -> Option<MultiBufferDiffHunk> {
13965 snapshot
13966 .buffer_snapshot
13967 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13968 .find(|hunk| hunk.row_range.start.0 > position.row)
13969 .or_else(|| {
13970 snapshot
13971 .buffer_snapshot
13972 .diff_hunks_in_range(Point::zero()..position)
13973 .find(|hunk| hunk.row_range.end.0 < position.row)
13974 })
13975 }
13976
13977 fn go_to_prev_hunk(
13978 &mut self,
13979 _: &GoToPreviousHunk,
13980 window: &mut Window,
13981 cx: &mut Context<Self>,
13982 ) {
13983 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13984 let snapshot = self.snapshot(window, cx);
13985 let selection = self.selections.newest::<Point>(cx);
13986 self.go_to_hunk_before_or_after_position(
13987 &snapshot,
13988 selection.head(),
13989 Direction::Prev,
13990 window,
13991 cx,
13992 );
13993 }
13994
13995 fn hunk_before_position(
13996 &mut self,
13997 snapshot: &EditorSnapshot,
13998 position: Point,
13999 ) -> Option<MultiBufferRow> {
14000 snapshot
14001 .buffer_snapshot
14002 .diff_hunk_before(position)
14003 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14004 }
14005
14006 fn go_to_next_change(
14007 &mut self,
14008 _: &GoToNextChange,
14009 window: &mut Window,
14010 cx: &mut Context<Self>,
14011 ) {
14012 if let Some(selections) = self
14013 .change_list
14014 .next_change(1, Direction::Next)
14015 .map(|s| s.to_vec())
14016 {
14017 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14018 let map = s.display_map();
14019 s.select_display_ranges(selections.iter().map(|a| {
14020 let point = a.to_display_point(&map);
14021 point..point
14022 }))
14023 })
14024 }
14025 }
14026
14027 fn go_to_previous_change(
14028 &mut self,
14029 _: &GoToPreviousChange,
14030 window: &mut Window,
14031 cx: &mut Context<Self>,
14032 ) {
14033 if let Some(selections) = self
14034 .change_list
14035 .next_change(1, Direction::Prev)
14036 .map(|s| s.to_vec())
14037 {
14038 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14039 let map = s.display_map();
14040 s.select_display_ranges(selections.iter().map(|a| {
14041 let point = a.to_display_point(&map);
14042 point..point
14043 }))
14044 })
14045 }
14046 }
14047
14048 fn go_to_line<T: 'static>(
14049 &mut self,
14050 position: Anchor,
14051 highlight_color: Option<Hsla>,
14052 window: &mut Window,
14053 cx: &mut Context<Self>,
14054 ) {
14055 let snapshot = self.snapshot(window, cx).display_snapshot;
14056 let position = position.to_point(&snapshot.buffer_snapshot);
14057 let start = snapshot
14058 .buffer_snapshot
14059 .clip_point(Point::new(position.row, 0), Bias::Left);
14060 let end = start + Point::new(1, 0);
14061 let start = snapshot.buffer_snapshot.anchor_before(start);
14062 let end = snapshot.buffer_snapshot.anchor_before(end);
14063
14064 self.highlight_rows::<T>(
14065 start..end,
14066 highlight_color
14067 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14068 Default::default(),
14069 cx,
14070 );
14071
14072 if self.buffer.read(cx).is_singleton() {
14073 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14074 }
14075 }
14076
14077 pub fn go_to_definition(
14078 &mut self,
14079 _: &GoToDefinition,
14080 window: &mut Window,
14081 cx: &mut Context<Self>,
14082 ) -> Task<Result<Navigated>> {
14083 let definition =
14084 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14085 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14086 cx.spawn_in(window, async move |editor, cx| {
14087 if definition.await? == Navigated::Yes {
14088 return Ok(Navigated::Yes);
14089 }
14090 match fallback_strategy {
14091 GoToDefinitionFallback::None => Ok(Navigated::No),
14092 GoToDefinitionFallback::FindAllReferences => {
14093 match editor.update_in(cx, |editor, window, cx| {
14094 editor.find_all_references(&FindAllReferences, window, cx)
14095 })? {
14096 Some(references) => references.await,
14097 None => Ok(Navigated::No),
14098 }
14099 }
14100 }
14101 })
14102 }
14103
14104 pub fn go_to_declaration(
14105 &mut self,
14106 _: &GoToDeclaration,
14107 window: &mut Window,
14108 cx: &mut Context<Self>,
14109 ) -> Task<Result<Navigated>> {
14110 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14111 }
14112
14113 pub fn go_to_declaration_split(
14114 &mut self,
14115 _: &GoToDeclaration,
14116 window: &mut Window,
14117 cx: &mut Context<Self>,
14118 ) -> Task<Result<Navigated>> {
14119 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14120 }
14121
14122 pub fn go_to_implementation(
14123 &mut self,
14124 _: &GoToImplementation,
14125 window: &mut Window,
14126 cx: &mut Context<Self>,
14127 ) -> Task<Result<Navigated>> {
14128 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14129 }
14130
14131 pub fn go_to_implementation_split(
14132 &mut self,
14133 _: &GoToImplementationSplit,
14134 window: &mut Window,
14135 cx: &mut Context<Self>,
14136 ) -> Task<Result<Navigated>> {
14137 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14138 }
14139
14140 pub fn go_to_type_definition(
14141 &mut self,
14142 _: &GoToTypeDefinition,
14143 window: &mut Window,
14144 cx: &mut Context<Self>,
14145 ) -> Task<Result<Navigated>> {
14146 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14147 }
14148
14149 pub fn go_to_definition_split(
14150 &mut self,
14151 _: &GoToDefinitionSplit,
14152 window: &mut Window,
14153 cx: &mut Context<Self>,
14154 ) -> Task<Result<Navigated>> {
14155 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14156 }
14157
14158 pub fn go_to_type_definition_split(
14159 &mut self,
14160 _: &GoToTypeDefinitionSplit,
14161 window: &mut Window,
14162 cx: &mut Context<Self>,
14163 ) -> Task<Result<Navigated>> {
14164 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14165 }
14166
14167 fn go_to_definition_of_kind(
14168 &mut self,
14169 kind: GotoDefinitionKind,
14170 split: bool,
14171 window: &mut Window,
14172 cx: &mut Context<Self>,
14173 ) -> Task<Result<Navigated>> {
14174 let Some(provider) = self.semantics_provider.clone() else {
14175 return Task::ready(Ok(Navigated::No));
14176 };
14177 let head = self.selections.newest::<usize>(cx).head();
14178 let buffer = self.buffer.read(cx);
14179 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14180 text_anchor
14181 } else {
14182 return Task::ready(Ok(Navigated::No));
14183 };
14184
14185 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14186 return Task::ready(Ok(Navigated::No));
14187 };
14188
14189 cx.spawn_in(window, async move |editor, cx| {
14190 let definitions = definitions.await?;
14191 let navigated = editor
14192 .update_in(cx, |editor, window, cx| {
14193 editor.navigate_to_hover_links(
14194 Some(kind),
14195 definitions
14196 .into_iter()
14197 .filter(|location| {
14198 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14199 })
14200 .map(HoverLink::Text)
14201 .collect::<Vec<_>>(),
14202 split,
14203 window,
14204 cx,
14205 )
14206 })?
14207 .await?;
14208 anyhow::Ok(navigated)
14209 })
14210 }
14211
14212 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14213 let selection = self.selections.newest_anchor();
14214 let head = selection.head();
14215 let tail = selection.tail();
14216
14217 let Some((buffer, start_position)) =
14218 self.buffer.read(cx).text_anchor_for_position(head, cx)
14219 else {
14220 return;
14221 };
14222
14223 let end_position = if head != tail {
14224 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14225 return;
14226 };
14227 Some(pos)
14228 } else {
14229 None
14230 };
14231
14232 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14233 let url = if let Some(end_pos) = end_position {
14234 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14235 } else {
14236 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14237 };
14238
14239 if let Some(url) = url {
14240 editor.update(cx, |_, cx| {
14241 cx.open_url(&url);
14242 })
14243 } else {
14244 Ok(())
14245 }
14246 });
14247
14248 url_finder.detach();
14249 }
14250
14251 pub fn open_selected_filename(
14252 &mut self,
14253 _: &OpenSelectedFilename,
14254 window: &mut Window,
14255 cx: &mut Context<Self>,
14256 ) {
14257 let Some(workspace) = self.workspace() else {
14258 return;
14259 };
14260
14261 let position = self.selections.newest_anchor().head();
14262
14263 let Some((buffer, buffer_position)) =
14264 self.buffer.read(cx).text_anchor_for_position(position, cx)
14265 else {
14266 return;
14267 };
14268
14269 let project = self.project.clone();
14270
14271 cx.spawn_in(window, async move |_, cx| {
14272 let result = find_file(&buffer, project, buffer_position, cx).await;
14273
14274 if let Some((_, path)) = result {
14275 workspace
14276 .update_in(cx, |workspace, window, cx| {
14277 workspace.open_resolved_path(path, window, cx)
14278 })?
14279 .await?;
14280 }
14281 anyhow::Ok(())
14282 })
14283 .detach();
14284 }
14285
14286 pub(crate) fn navigate_to_hover_links(
14287 &mut self,
14288 kind: Option<GotoDefinitionKind>,
14289 mut definitions: Vec<HoverLink>,
14290 split: bool,
14291 window: &mut Window,
14292 cx: &mut Context<Editor>,
14293 ) -> Task<Result<Navigated>> {
14294 // If there is one definition, just open it directly
14295 if definitions.len() == 1 {
14296 let definition = definitions.pop().unwrap();
14297
14298 enum TargetTaskResult {
14299 Location(Option<Location>),
14300 AlreadyNavigated,
14301 }
14302
14303 let target_task = match definition {
14304 HoverLink::Text(link) => {
14305 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14306 }
14307 HoverLink::InlayHint(lsp_location, server_id) => {
14308 let computation =
14309 self.compute_target_location(lsp_location, server_id, window, cx);
14310 cx.background_spawn(async move {
14311 let location = computation.await?;
14312 Ok(TargetTaskResult::Location(location))
14313 })
14314 }
14315 HoverLink::Url(url) => {
14316 cx.open_url(&url);
14317 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14318 }
14319 HoverLink::File(path) => {
14320 if let Some(workspace) = self.workspace() {
14321 cx.spawn_in(window, async move |_, cx| {
14322 workspace
14323 .update_in(cx, |workspace, window, cx| {
14324 workspace.open_resolved_path(path, window, cx)
14325 })?
14326 .await
14327 .map(|_| TargetTaskResult::AlreadyNavigated)
14328 })
14329 } else {
14330 Task::ready(Ok(TargetTaskResult::Location(None)))
14331 }
14332 }
14333 };
14334 cx.spawn_in(window, async move |editor, cx| {
14335 let target = match target_task.await.context("target resolution task")? {
14336 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14337 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14338 TargetTaskResult::Location(Some(target)) => target,
14339 };
14340
14341 editor.update_in(cx, |editor, window, cx| {
14342 let Some(workspace) = editor.workspace() else {
14343 return Navigated::No;
14344 };
14345 let pane = workspace.read(cx).active_pane().clone();
14346
14347 let range = target.range.to_point(target.buffer.read(cx));
14348 let range = editor.range_for_match(&range);
14349 let range = collapse_multiline_range(range);
14350
14351 if !split
14352 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14353 {
14354 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14355 } else {
14356 window.defer(cx, move |window, cx| {
14357 let target_editor: Entity<Self> =
14358 workspace.update(cx, |workspace, cx| {
14359 let pane = if split {
14360 workspace.adjacent_pane(window, cx)
14361 } else {
14362 workspace.active_pane().clone()
14363 };
14364
14365 workspace.open_project_item(
14366 pane,
14367 target.buffer.clone(),
14368 true,
14369 true,
14370 window,
14371 cx,
14372 )
14373 });
14374 target_editor.update(cx, |target_editor, cx| {
14375 // When selecting a definition in a different buffer, disable the nav history
14376 // to avoid creating a history entry at the previous cursor location.
14377 pane.update(cx, |pane, _| pane.disable_history());
14378 target_editor.go_to_singleton_buffer_range(range, window, cx);
14379 pane.update(cx, |pane, _| pane.enable_history());
14380 });
14381 });
14382 }
14383 Navigated::Yes
14384 })
14385 })
14386 } else if !definitions.is_empty() {
14387 cx.spawn_in(window, async move |editor, cx| {
14388 let (title, location_tasks, workspace) = editor
14389 .update_in(cx, |editor, window, cx| {
14390 let tab_kind = match kind {
14391 Some(GotoDefinitionKind::Implementation) => "Implementations",
14392 _ => "Definitions",
14393 };
14394 let title = definitions
14395 .iter()
14396 .find_map(|definition| match definition {
14397 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14398 let buffer = origin.buffer.read(cx);
14399 format!(
14400 "{} for {}",
14401 tab_kind,
14402 buffer
14403 .text_for_range(origin.range.clone())
14404 .collect::<String>()
14405 )
14406 }),
14407 HoverLink::InlayHint(_, _) => None,
14408 HoverLink::Url(_) => None,
14409 HoverLink::File(_) => None,
14410 })
14411 .unwrap_or(tab_kind.to_string());
14412 let location_tasks = definitions
14413 .into_iter()
14414 .map(|definition| match definition {
14415 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14416 HoverLink::InlayHint(lsp_location, server_id) => editor
14417 .compute_target_location(lsp_location, server_id, window, cx),
14418 HoverLink::Url(_) => Task::ready(Ok(None)),
14419 HoverLink::File(_) => Task::ready(Ok(None)),
14420 })
14421 .collect::<Vec<_>>();
14422 (title, location_tasks, editor.workspace().clone())
14423 })
14424 .context("location tasks preparation")?;
14425
14426 let locations = future::join_all(location_tasks)
14427 .await
14428 .into_iter()
14429 .filter_map(|location| location.transpose())
14430 .collect::<Result<_>>()
14431 .context("location tasks")?;
14432
14433 let Some(workspace) = workspace else {
14434 return Ok(Navigated::No);
14435 };
14436 let opened = workspace
14437 .update_in(cx, |workspace, window, cx| {
14438 Self::open_locations_in_multibuffer(
14439 workspace,
14440 locations,
14441 title,
14442 split,
14443 MultibufferSelectionMode::First,
14444 window,
14445 cx,
14446 )
14447 })
14448 .ok();
14449
14450 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14451 })
14452 } else {
14453 Task::ready(Ok(Navigated::No))
14454 }
14455 }
14456
14457 fn compute_target_location(
14458 &self,
14459 lsp_location: lsp::Location,
14460 server_id: LanguageServerId,
14461 window: &mut Window,
14462 cx: &mut Context<Self>,
14463 ) -> Task<anyhow::Result<Option<Location>>> {
14464 let Some(project) = self.project.clone() else {
14465 return Task::ready(Ok(None));
14466 };
14467
14468 cx.spawn_in(window, async move |editor, cx| {
14469 let location_task = editor.update(cx, |_, cx| {
14470 project.update(cx, |project, cx| {
14471 let language_server_name = project
14472 .language_server_statuses(cx)
14473 .find(|(id, _)| server_id == *id)
14474 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14475 language_server_name.map(|language_server_name| {
14476 project.open_local_buffer_via_lsp(
14477 lsp_location.uri.clone(),
14478 server_id,
14479 language_server_name,
14480 cx,
14481 )
14482 })
14483 })
14484 })?;
14485 let location = match location_task {
14486 Some(task) => Some({
14487 let target_buffer_handle = task.await.context("open local buffer")?;
14488 let range = target_buffer_handle.update(cx, |target_buffer, _| {
14489 let target_start = target_buffer
14490 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14491 let target_end = target_buffer
14492 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14493 target_buffer.anchor_after(target_start)
14494 ..target_buffer.anchor_before(target_end)
14495 })?;
14496 Location {
14497 buffer: target_buffer_handle,
14498 range,
14499 }
14500 }),
14501 None => None,
14502 };
14503 Ok(location)
14504 })
14505 }
14506
14507 pub fn find_all_references(
14508 &mut self,
14509 _: &FindAllReferences,
14510 window: &mut Window,
14511 cx: &mut Context<Self>,
14512 ) -> Option<Task<Result<Navigated>>> {
14513 let selection = self.selections.newest::<usize>(cx);
14514 let multi_buffer = self.buffer.read(cx);
14515 let head = selection.head();
14516
14517 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14518 let head_anchor = multi_buffer_snapshot.anchor_at(
14519 head,
14520 if head < selection.tail() {
14521 Bias::Right
14522 } else {
14523 Bias::Left
14524 },
14525 );
14526
14527 match self
14528 .find_all_references_task_sources
14529 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14530 {
14531 Ok(_) => {
14532 log::info!(
14533 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14534 );
14535 return None;
14536 }
14537 Err(i) => {
14538 self.find_all_references_task_sources.insert(i, head_anchor);
14539 }
14540 }
14541
14542 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14543 let workspace = self.workspace()?;
14544 let project = workspace.read(cx).project().clone();
14545 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14546 Some(cx.spawn_in(window, async move |editor, cx| {
14547 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14548 if let Ok(i) = editor
14549 .find_all_references_task_sources
14550 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14551 {
14552 editor.find_all_references_task_sources.remove(i);
14553 }
14554 });
14555
14556 let locations = references.await?;
14557 if locations.is_empty() {
14558 return anyhow::Ok(Navigated::No);
14559 }
14560
14561 workspace.update_in(cx, |workspace, window, cx| {
14562 let title = locations
14563 .first()
14564 .as_ref()
14565 .map(|location| {
14566 let buffer = location.buffer.read(cx);
14567 format!(
14568 "References to `{}`",
14569 buffer
14570 .text_for_range(location.range.clone())
14571 .collect::<String>()
14572 )
14573 })
14574 .unwrap();
14575 Self::open_locations_in_multibuffer(
14576 workspace,
14577 locations,
14578 title,
14579 false,
14580 MultibufferSelectionMode::First,
14581 window,
14582 cx,
14583 );
14584 Navigated::Yes
14585 })
14586 }))
14587 }
14588
14589 /// Opens a multibuffer with the given project locations in it
14590 pub fn open_locations_in_multibuffer(
14591 workspace: &mut Workspace,
14592 mut locations: Vec<Location>,
14593 title: String,
14594 split: bool,
14595 multibuffer_selection_mode: MultibufferSelectionMode,
14596 window: &mut Window,
14597 cx: &mut Context<Workspace>,
14598 ) {
14599 // If there are multiple definitions, open them in a multibuffer
14600 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14601 let mut locations = locations.into_iter().peekable();
14602 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14603 let capability = workspace.project().read(cx).capability();
14604
14605 let excerpt_buffer = cx.new(|cx| {
14606 let mut multibuffer = MultiBuffer::new(capability);
14607 while let Some(location) = locations.next() {
14608 let buffer = location.buffer.read(cx);
14609 let mut ranges_for_buffer = Vec::new();
14610 let range = location.range.to_point(buffer);
14611 ranges_for_buffer.push(range.clone());
14612
14613 while let Some(next_location) = locations.peek() {
14614 if next_location.buffer == location.buffer {
14615 ranges_for_buffer.push(next_location.range.to_point(buffer));
14616 locations.next();
14617 } else {
14618 break;
14619 }
14620 }
14621
14622 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14623 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14624 PathKey::for_buffer(&location.buffer, cx),
14625 location.buffer.clone(),
14626 ranges_for_buffer,
14627 DEFAULT_MULTIBUFFER_CONTEXT,
14628 cx,
14629 );
14630 ranges.extend(new_ranges)
14631 }
14632
14633 multibuffer.with_title(title)
14634 });
14635
14636 let editor = cx.new(|cx| {
14637 Editor::for_multibuffer(
14638 excerpt_buffer,
14639 Some(workspace.project().clone()),
14640 window,
14641 cx,
14642 )
14643 });
14644 editor.update(cx, |editor, cx| {
14645 match multibuffer_selection_mode {
14646 MultibufferSelectionMode::First => {
14647 if let Some(first_range) = ranges.first() {
14648 editor.change_selections(None, window, cx, |selections| {
14649 selections.clear_disjoint();
14650 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14651 });
14652 }
14653 editor.highlight_background::<Self>(
14654 &ranges,
14655 |theme| theme.editor_highlighted_line_background,
14656 cx,
14657 );
14658 }
14659 MultibufferSelectionMode::All => {
14660 editor.change_selections(None, window, cx, |selections| {
14661 selections.clear_disjoint();
14662 selections.select_anchor_ranges(ranges);
14663 });
14664 }
14665 }
14666 editor.register_buffers_with_language_servers(cx);
14667 });
14668
14669 let item = Box::new(editor);
14670 let item_id = item.item_id();
14671
14672 if split {
14673 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14674 } else {
14675 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14676 let (preview_item_id, preview_item_idx) =
14677 workspace.active_pane().update(cx, |pane, _| {
14678 (pane.preview_item_id(), pane.preview_item_idx())
14679 });
14680
14681 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14682
14683 if let Some(preview_item_id) = preview_item_id {
14684 workspace.active_pane().update(cx, |pane, cx| {
14685 pane.remove_item(preview_item_id, false, false, window, cx);
14686 });
14687 }
14688 } else {
14689 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14690 }
14691 }
14692 workspace.active_pane().update(cx, |pane, cx| {
14693 pane.set_preview_item_id(Some(item_id), cx);
14694 });
14695 }
14696
14697 pub fn rename(
14698 &mut self,
14699 _: &Rename,
14700 window: &mut Window,
14701 cx: &mut Context<Self>,
14702 ) -> Option<Task<Result<()>>> {
14703 use language::ToOffset as _;
14704
14705 let provider = self.semantics_provider.clone()?;
14706 let selection = self.selections.newest_anchor().clone();
14707 let (cursor_buffer, cursor_buffer_position) = self
14708 .buffer
14709 .read(cx)
14710 .text_anchor_for_position(selection.head(), cx)?;
14711 let (tail_buffer, cursor_buffer_position_end) = self
14712 .buffer
14713 .read(cx)
14714 .text_anchor_for_position(selection.tail(), cx)?;
14715 if tail_buffer != cursor_buffer {
14716 return None;
14717 }
14718
14719 let snapshot = cursor_buffer.read(cx).snapshot();
14720 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14721 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14722 let prepare_rename = provider
14723 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14724 .unwrap_or_else(|| Task::ready(Ok(None)));
14725 drop(snapshot);
14726
14727 Some(cx.spawn_in(window, async move |this, cx| {
14728 let rename_range = if let Some(range) = prepare_rename.await? {
14729 Some(range)
14730 } else {
14731 this.update(cx, |this, cx| {
14732 let buffer = this.buffer.read(cx).snapshot(cx);
14733 let mut buffer_highlights = this
14734 .document_highlights_for_position(selection.head(), &buffer)
14735 .filter(|highlight| {
14736 highlight.start.excerpt_id == selection.head().excerpt_id
14737 && highlight.end.excerpt_id == selection.head().excerpt_id
14738 });
14739 buffer_highlights
14740 .next()
14741 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14742 })?
14743 };
14744 if let Some(rename_range) = rename_range {
14745 this.update_in(cx, |this, window, cx| {
14746 let snapshot = cursor_buffer.read(cx).snapshot();
14747 let rename_buffer_range = rename_range.to_offset(&snapshot);
14748 let cursor_offset_in_rename_range =
14749 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14750 let cursor_offset_in_rename_range_end =
14751 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14752
14753 this.take_rename(false, window, cx);
14754 let buffer = this.buffer.read(cx).read(cx);
14755 let cursor_offset = selection.head().to_offset(&buffer);
14756 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14757 let rename_end = rename_start + rename_buffer_range.len();
14758 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14759 let mut old_highlight_id = None;
14760 let old_name: Arc<str> = buffer
14761 .chunks(rename_start..rename_end, true)
14762 .map(|chunk| {
14763 if old_highlight_id.is_none() {
14764 old_highlight_id = chunk.syntax_highlight_id;
14765 }
14766 chunk.text
14767 })
14768 .collect::<String>()
14769 .into();
14770
14771 drop(buffer);
14772
14773 // Position the selection in the rename editor so that it matches the current selection.
14774 this.show_local_selections = false;
14775 let rename_editor = cx.new(|cx| {
14776 let mut editor = Editor::single_line(window, cx);
14777 editor.buffer.update(cx, |buffer, cx| {
14778 buffer.edit([(0..0, old_name.clone())], None, cx)
14779 });
14780 let rename_selection_range = match cursor_offset_in_rename_range
14781 .cmp(&cursor_offset_in_rename_range_end)
14782 {
14783 Ordering::Equal => {
14784 editor.select_all(&SelectAll, window, cx);
14785 return editor;
14786 }
14787 Ordering::Less => {
14788 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14789 }
14790 Ordering::Greater => {
14791 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14792 }
14793 };
14794 if rename_selection_range.end > old_name.len() {
14795 editor.select_all(&SelectAll, window, cx);
14796 } else {
14797 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14798 s.select_ranges([rename_selection_range]);
14799 });
14800 }
14801 editor
14802 });
14803 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14804 if e == &EditorEvent::Focused {
14805 cx.emit(EditorEvent::FocusedIn)
14806 }
14807 })
14808 .detach();
14809
14810 let write_highlights =
14811 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14812 let read_highlights =
14813 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14814 let ranges = write_highlights
14815 .iter()
14816 .flat_map(|(_, ranges)| ranges.iter())
14817 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14818 .cloned()
14819 .collect();
14820
14821 this.highlight_text::<Rename>(
14822 ranges,
14823 HighlightStyle {
14824 fade_out: Some(0.6),
14825 ..Default::default()
14826 },
14827 cx,
14828 );
14829 let rename_focus_handle = rename_editor.focus_handle(cx);
14830 window.focus(&rename_focus_handle);
14831 let block_id = this.insert_blocks(
14832 [BlockProperties {
14833 style: BlockStyle::Flex,
14834 placement: BlockPlacement::Below(range.start),
14835 height: Some(1),
14836 render: Arc::new({
14837 let rename_editor = rename_editor.clone();
14838 move |cx: &mut BlockContext| {
14839 let mut text_style = cx.editor_style.text.clone();
14840 if let Some(highlight_style) = old_highlight_id
14841 .and_then(|h| h.style(&cx.editor_style.syntax))
14842 {
14843 text_style = text_style.highlight(highlight_style);
14844 }
14845 div()
14846 .block_mouse_down()
14847 .pl(cx.anchor_x)
14848 .child(EditorElement::new(
14849 &rename_editor,
14850 EditorStyle {
14851 background: cx.theme().system().transparent,
14852 local_player: cx.editor_style.local_player,
14853 text: text_style,
14854 scrollbar_width: cx.editor_style.scrollbar_width,
14855 syntax: cx.editor_style.syntax.clone(),
14856 status: cx.editor_style.status.clone(),
14857 inlay_hints_style: HighlightStyle {
14858 font_weight: Some(FontWeight::BOLD),
14859 ..make_inlay_hints_style(cx.app)
14860 },
14861 inline_completion_styles: make_suggestion_styles(
14862 cx.app,
14863 ),
14864 ..EditorStyle::default()
14865 },
14866 ))
14867 .into_any_element()
14868 }
14869 }),
14870 priority: 0,
14871 render_in_minimap: true,
14872 }],
14873 Some(Autoscroll::fit()),
14874 cx,
14875 )[0];
14876 this.pending_rename = Some(RenameState {
14877 range,
14878 old_name,
14879 editor: rename_editor,
14880 block_id,
14881 });
14882 })?;
14883 }
14884
14885 Ok(())
14886 }))
14887 }
14888
14889 pub fn confirm_rename(
14890 &mut self,
14891 _: &ConfirmRename,
14892 window: &mut Window,
14893 cx: &mut Context<Self>,
14894 ) -> Option<Task<Result<()>>> {
14895 let rename = self.take_rename(false, window, cx)?;
14896 let workspace = self.workspace()?.downgrade();
14897 let (buffer, start) = self
14898 .buffer
14899 .read(cx)
14900 .text_anchor_for_position(rename.range.start, cx)?;
14901 let (end_buffer, _) = self
14902 .buffer
14903 .read(cx)
14904 .text_anchor_for_position(rename.range.end, cx)?;
14905 if buffer != end_buffer {
14906 return None;
14907 }
14908
14909 let old_name = rename.old_name;
14910 let new_name = rename.editor.read(cx).text(cx);
14911
14912 let rename = self.semantics_provider.as_ref()?.perform_rename(
14913 &buffer,
14914 start,
14915 new_name.clone(),
14916 cx,
14917 )?;
14918
14919 Some(cx.spawn_in(window, async move |editor, cx| {
14920 let project_transaction = rename.await?;
14921 Self::open_project_transaction(
14922 &editor,
14923 workspace,
14924 project_transaction,
14925 format!("Rename: {} → {}", old_name, new_name),
14926 cx,
14927 )
14928 .await?;
14929
14930 editor.update(cx, |editor, cx| {
14931 editor.refresh_document_highlights(cx);
14932 })?;
14933 Ok(())
14934 }))
14935 }
14936
14937 fn take_rename(
14938 &mut self,
14939 moving_cursor: bool,
14940 window: &mut Window,
14941 cx: &mut Context<Self>,
14942 ) -> Option<RenameState> {
14943 let rename = self.pending_rename.take()?;
14944 if rename.editor.focus_handle(cx).is_focused(window) {
14945 window.focus(&self.focus_handle);
14946 }
14947
14948 self.remove_blocks(
14949 [rename.block_id].into_iter().collect(),
14950 Some(Autoscroll::fit()),
14951 cx,
14952 );
14953 self.clear_highlights::<Rename>(cx);
14954 self.show_local_selections = true;
14955
14956 if moving_cursor {
14957 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14958 editor.selections.newest::<usize>(cx).head()
14959 });
14960
14961 // Update the selection to match the position of the selection inside
14962 // the rename editor.
14963 let snapshot = self.buffer.read(cx).read(cx);
14964 let rename_range = rename.range.to_offset(&snapshot);
14965 let cursor_in_editor = snapshot
14966 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14967 .min(rename_range.end);
14968 drop(snapshot);
14969
14970 self.change_selections(None, window, cx, |s| {
14971 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14972 });
14973 } else {
14974 self.refresh_document_highlights(cx);
14975 }
14976
14977 Some(rename)
14978 }
14979
14980 pub fn pending_rename(&self) -> Option<&RenameState> {
14981 self.pending_rename.as_ref()
14982 }
14983
14984 fn format(
14985 &mut self,
14986 _: &Format,
14987 window: &mut Window,
14988 cx: &mut Context<Self>,
14989 ) -> Option<Task<Result<()>>> {
14990 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14991
14992 let project = match &self.project {
14993 Some(project) => project.clone(),
14994 None => return None,
14995 };
14996
14997 Some(self.perform_format(
14998 project,
14999 FormatTrigger::Manual,
15000 FormatTarget::Buffers,
15001 window,
15002 cx,
15003 ))
15004 }
15005
15006 fn format_selections(
15007 &mut self,
15008 _: &FormatSelections,
15009 window: &mut Window,
15010 cx: &mut Context<Self>,
15011 ) -> Option<Task<Result<()>>> {
15012 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15013
15014 let project = match &self.project {
15015 Some(project) => project.clone(),
15016 None => return None,
15017 };
15018
15019 let ranges = self
15020 .selections
15021 .all_adjusted(cx)
15022 .into_iter()
15023 .map(|selection| selection.range())
15024 .collect_vec();
15025
15026 Some(self.perform_format(
15027 project,
15028 FormatTrigger::Manual,
15029 FormatTarget::Ranges(ranges),
15030 window,
15031 cx,
15032 ))
15033 }
15034
15035 fn perform_format(
15036 &mut self,
15037 project: Entity<Project>,
15038 trigger: FormatTrigger,
15039 target: FormatTarget,
15040 window: &mut Window,
15041 cx: &mut Context<Self>,
15042 ) -> Task<Result<()>> {
15043 let buffer = self.buffer.clone();
15044 let (buffers, target) = match target {
15045 FormatTarget::Buffers => {
15046 let mut buffers = buffer.read(cx).all_buffers();
15047 if trigger == FormatTrigger::Save {
15048 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15049 }
15050 (buffers, LspFormatTarget::Buffers)
15051 }
15052 FormatTarget::Ranges(selection_ranges) => {
15053 let multi_buffer = buffer.read(cx);
15054 let snapshot = multi_buffer.read(cx);
15055 let mut buffers = HashSet::default();
15056 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15057 BTreeMap::new();
15058 for selection_range in selection_ranges {
15059 for (buffer, buffer_range, _) in
15060 snapshot.range_to_buffer_ranges(selection_range)
15061 {
15062 let buffer_id = buffer.remote_id();
15063 let start = buffer.anchor_before(buffer_range.start);
15064 let end = buffer.anchor_after(buffer_range.end);
15065 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15066 buffer_id_to_ranges
15067 .entry(buffer_id)
15068 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15069 .or_insert_with(|| vec![start..end]);
15070 }
15071 }
15072 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15073 }
15074 };
15075
15076 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
15077 let selections_prev = transaction_id_prev
15078 .and_then(|transaction_id_prev| {
15079 // default to selections as they were after the last edit, if we have them,
15080 // instead of how they are now.
15081 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15082 // will take you back to where you made the last edit, instead of staying where you scrolled
15083 self.selection_history
15084 .transaction(transaction_id_prev)
15085 .map(|t| t.0.clone())
15086 })
15087 .unwrap_or_else(|| {
15088 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15089 self.selections.disjoint_anchors()
15090 });
15091
15092 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15093 let format = project.update(cx, |project, cx| {
15094 project.format(buffers, target, true, trigger, cx)
15095 });
15096
15097 cx.spawn_in(window, async move |editor, cx| {
15098 let transaction = futures::select_biased! {
15099 transaction = format.log_err().fuse() => transaction,
15100 () = timeout => {
15101 log::warn!("timed out waiting for formatting");
15102 None
15103 }
15104 };
15105
15106 buffer
15107 .update(cx, |buffer, cx| {
15108 if let Some(transaction) = transaction {
15109 if !buffer.is_singleton() {
15110 buffer.push_transaction(&transaction.0, cx);
15111 }
15112 }
15113 cx.notify();
15114 })
15115 .ok();
15116
15117 if let Some(transaction_id_now) =
15118 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15119 {
15120 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15121 if has_new_transaction {
15122 _ = editor.update(cx, |editor, _| {
15123 editor
15124 .selection_history
15125 .insert_transaction(transaction_id_now, selections_prev);
15126 });
15127 }
15128 }
15129
15130 Ok(())
15131 })
15132 }
15133
15134 fn organize_imports(
15135 &mut self,
15136 _: &OrganizeImports,
15137 window: &mut Window,
15138 cx: &mut Context<Self>,
15139 ) -> Option<Task<Result<()>>> {
15140 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15141 let project = match &self.project {
15142 Some(project) => project.clone(),
15143 None => return None,
15144 };
15145 Some(self.perform_code_action_kind(
15146 project,
15147 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15148 window,
15149 cx,
15150 ))
15151 }
15152
15153 fn perform_code_action_kind(
15154 &mut self,
15155 project: Entity<Project>,
15156 kind: CodeActionKind,
15157 window: &mut Window,
15158 cx: &mut Context<Self>,
15159 ) -> Task<Result<()>> {
15160 let buffer = self.buffer.clone();
15161 let buffers = buffer.read(cx).all_buffers();
15162 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15163 let apply_action = project.update(cx, |project, cx| {
15164 project.apply_code_action_kind(buffers, kind, true, cx)
15165 });
15166 cx.spawn_in(window, async move |_, cx| {
15167 let transaction = futures::select_biased! {
15168 () = timeout => {
15169 log::warn!("timed out waiting for executing code action");
15170 None
15171 }
15172 transaction = apply_action.log_err().fuse() => transaction,
15173 };
15174 buffer
15175 .update(cx, |buffer, cx| {
15176 // check if we need this
15177 if let Some(transaction) = transaction {
15178 if !buffer.is_singleton() {
15179 buffer.push_transaction(&transaction.0, cx);
15180 }
15181 }
15182 cx.notify();
15183 })
15184 .ok();
15185 Ok(())
15186 })
15187 }
15188
15189 fn restart_language_server(
15190 &mut self,
15191 _: &RestartLanguageServer,
15192 _: &mut Window,
15193 cx: &mut Context<Self>,
15194 ) {
15195 if let Some(project) = self.project.clone() {
15196 self.buffer.update(cx, |multi_buffer, cx| {
15197 project.update(cx, |project, cx| {
15198 project.restart_language_servers_for_buffers(
15199 multi_buffer.all_buffers().into_iter().collect(),
15200 cx,
15201 );
15202 });
15203 })
15204 }
15205 }
15206
15207 fn stop_language_server(
15208 &mut self,
15209 _: &StopLanguageServer,
15210 _: &mut Window,
15211 cx: &mut Context<Self>,
15212 ) {
15213 if let Some(project) = self.project.clone() {
15214 self.buffer.update(cx, |multi_buffer, cx| {
15215 project.update(cx, |project, cx| {
15216 project.stop_language_servers_for_buffers(
15217 multi_buffer.all_buffers().into_iter().collect(),
15218 cx,
15219 );
15220 cx.emit(project::Event::RefreshInlayHints);
15221 });
15222 });
15223 }
15224 }
15225
15226 fn cancel_language_server_work(
15227 workspace: &mut Workspace,
15228 _: &actions::CancelLanguageServerWork,
15229 _: &mut Window,
15230 cx: &mut Context<Workspace>,
15231 ) {
15232 let project = workspace.project();
15233 let buffers = workspace
15234 .active_item(cx)
15235 .and_then(|item| item.act_as::<Editor>(cx))
15236 .map_or(HashSet::default(), |editor| {
15237 editor.read(cx).buffer.read(cx).all_buffers()
15238 });
15239 project.update(cx, |project, cx| {
15240 project.cancel_language_server_work_for_buffers(buffers, cx);
15241 });
15242 }
15243
15244 fn show_character_palette(
15245 &mut self,
15246 _: &ShowCharacterPalette,
15247 window: &mut Window,
15248 _: &mut Context<Self>,
15249 ) {
15250 window.show_character_palette();
15251 }
15252
15253 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15254 if self.mode.is_minimap() {
15255 return;
15256 }
15257
15258 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15259 let buffer = self.buffer.read(cx).snapshot(cx);
15260 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15261 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15262 let is_valid = buffer
15263 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15264 .any(|entry| {
15265 entry.diagnostic.is_primary
15266 && !entry.range.is_empty()
15267 && entry.range.start == primary_range_start
15268 && entry.diagnostic.message == active_diagnostics.active_message
15269 });
15270
15271 if !is_valid {
15272 self.dismiss_diagnostics(cx);
15273 }
15274 }
15275 }
15276
15277 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15278 match &self.active_diagnostics {
15279 ActiveDiagnostic::Group(group) => Some(group),
15280 _ => None,
15281 }
15282 }
15283
15284 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15285 self.dismiss_diagnostics(cx);
15286 self.active_diagnostics = ActiveDiagnostic::All;
15287 }
15288
15289 fn activate_diagnostics(
15290 &mut self,
15291 buffer_id: BufferId,
15292 diagnostic: DiagnosticEntry<usize>,
15293 window: &mut Window,
15294 cx: &mut Context<Self>,
15295 ) {
15296 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15297 return;
15298 }
15299 self.dismiss_diagnostics(cx);
15300 let snapshot = self.snapshot(window, cx);
15301 let buffer = self.buffer.read(cx).snapshot(cx);
15302 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15303 return;
15304 };
15305
15306 let diagnostic_group = buffer
15307 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15308 .collect::<Vec<_>>();
15309
15310 let blocks =
15311 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15312
15313 let blocks = self.display_map.update(cx, |display_map, cx| {
15314 display_map.insert_blocks(blocks, cx).into_iter().collect()
15315 });
15316 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15317 active_range: buffer.anchor_before(diagnostic.range.start)
15318 ..buffer.anchor_after(diagnostic.range.end),
15319 active_message: diagnostic.diagnostic.message.clone(),
15320 group_id: diagnostic.diagnostic.group_id,
15321 blocks,
15322 });
15323 cx.notify();
15324 }
15325
15326 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15327 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15328 return;
15329 };
15330
15331 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15332 if let ActiveDiagnostic::Group(group) = prev {
15333 self.display_map.update(cx, |display_map, cx| {
15334 display_map.remove_blocks(group.blocks, cx);
15335 });
15336 cx.notify();
15337 }
15338 }
15339
15340 /// Disable inline diagnostics rendering for this editor.
15341 pub fn disable_inline_diagnostics(&mut self) {
15342 self.inline_diagnostics_enabled = false;
15343 self.inline_diagnostics_update = Task::ready(());
15344 self.inline_diagnostics.clear();
15345 }
15346
15347 pub fn diagnostics_enabled(&self) -> bool {
15348 self.mode.is_full()
15349 }
15350
15351 pub fn inline_diagnostics_enabled(&self) -> bool {
15352 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15353 }
15354
15355 pub fn show_inline_diagnostics(&self) -> bool {
15356 self.show_inline_diagnostics
15357 }
15358
15359 pub fn toggle_inline_diagnostics(
15360 &mut self,
15361 _: &ToggleInlineDiagnostics,
15362 window: &mut Window,
15363 cx: &mut Context<Editor>,
15364 ) {
15365 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15366 self.refresh_inline_diagnostics(false, window, cx);
15367 }
15368
15369 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15370 self.diagnostics_max_severity = severity;
15371 self.display_map.update(cx, |display_map, _| {
15372 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15373 });
15374 }
15375
15376 pub fn toggle_diagnostics(
15377 &mut self,
15378 _: &ToggleDiagnostics,
15379 window: &mut Window,
15380 cx: &mut Context<Editor>,
15381 ) {
15382 if !self.diagnostics_enabled() {
15383 return;
15384 }
15385
15386 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15387 EditorSettings::get_global(cx)
15388 .diagnostics_max_severity
15389 .filter(|severity| severity != &DiagnosticSeverity::Off)
15390 .unwrap_or(DiagnosticSeverity::Hint)
15391 } else {
15392 DiagnosticSeverity::Off
15393 };
15394 self.set_max_diagnostics_severity(new_severity, cx);
15395 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15396 self.active_diagnostics = ActiveDiagnostic::None;
15397 self.inline_diagnostics_update = Task::ready(());
15398 self.inline_diagnostics.clear();
15399 } else {
15400 self.refresh_inline_diagnostics(false, window, cx);
15401 }
15402
15403 cx.notify();
15404 }
15405
15406 pub fn toggle_minimap(
15407 &mut self,
15408 _: &ToggleMinimap,
15409 window: &mut Window,
15410 cx: &mut Context<Editor>,
15411 ) {
15412 if self.supports_minimap(cx) {
15413 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15414 }
15415 }
15416
15417 fn refresh_inline_diagnostics(
15418 &mut self,
15419 debounce: bool,
15420 window: &mut Window,
15421 cx: &mut Context<Self>,
15422 ) {
15423 let max_severity = ProjectSettings::get_global(cx)
15424 .diagnostics
15425 .inline
15426 .max_severity
15427 .unwrap_or(self.diagnostics_max_severity);
15428
15429 if self.mode.is_minimap()
15430 || !self.inline_diagnostics_enabled()
15431 || !self.show_inline_diagnostics
15432 || max_severity == DiagnosticSeverity::Off
15433 {
15434 self.inline_diagnostics_update = Task::ready(());
15435 self.inline_diagnostics.clear();
15436 return;
15437 }
15438
15439 let debounce_ms = ProjectSettings::get_global(cx)
15440 .diagnostics
15441 .inline
15442 .update_debounce_ms;
15443 let debounce = if debounce && debounce_ms > 0 {
15444 Some(Duration::from_millis(debounce_ms))
15445 } else {
15446 None
15447 };
15448 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15449 let editor = editor.upgrade().unwrap();
15450
15451 if let Some(debounce) = debounce {
15452 cx.background_executor().timer(debounce).await;
15453 }
15454 let Some(snapshot) = editor
15455 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15456 .ok()
15457 else {
15458 return;
15459 };
15460
15461 let new_inline_diagnostics = cx
15462 .background_spawn(async move {
15463 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15464 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15465 let message = diagnostic_entry
15466 .diagnostic
15467 .message
15468 .split_once('\n')
15469 .map(|(line, _)| line)
15470 .map(SharedString::new)
15471 .unwrap_or_else(|| {
15472 SharedString::from(diagnostic_entry.diagnostic.message)
15473 });
15474 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15475 let (Ok(i) | Err(i)) = inline_diagnostics
15476 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15477 inline_diagnostics.insert(
15478 i,
15479 (
15480 start_anchor,
15481 InlineDiagnostic {
15482 message,
15483 group_id: diagnostic_entry.diagnostic.group_id,
15484 start: diagnostic_entry.range.start.to_point(&snapshot),
15485 is_primary: diagnostic_entry.diagnostic.is_primary,
15486 severity: diagnostic_entry.diagnostic.severity,
15487 },
15488 ),
15489 );
15490 }
15491 inline_diagnostics
15492 })
15493 .await;
15494
15495 editor
15496 .update(cx, |editor, cx| {
15497 editor.inline_diagnostics = new_inline_diagnostics;
15498 cx.notify();
15499 })
15500 .ok();
15501 });
15502 }
15503
15504 pub fn set_selections_from_remote(
15505 &mut self,
15506 selections: Vec<Selection<Anchor>>,
15507 pending_selection: Option<Selection<Anchor>>,
15508 window: &mut Window,
15509 cx: &mut Context<Self>,
15510 ) {
15511 let old_cursor_position = self.selections.newest_anchor().head();
15512 self.selections.change_with(cx, |s| {
15513 s.select_anchors(selections);
15514 if let Some(pending_selection) = pending_selection {
15515 s.set_pending(pending_selection, SelectMode::Character);
15516 } else {
15517 s.clear_pending();
15518 }
15519 });
15520 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15521 }
15522
15523 fn push_to_selection_history(&mut self) {
15524 self.selection_history.push(SelectionHistoryEntry {
15525 selections: self.selections.disjoint_anchors(),
15526 select_next_state: self.select_next_state.clone(),
15527 select_prev_state: self.select_prev_state.clone(),
15528 add_selections_state: self.add_selections_state.clone(),
15529 });
15530 }
15531
15532 pub fn transact(
15533 &mut self,
15534 window: &mut Window,
15535 cx: &mut Context<Self>,
15536 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15537 ) -> Option<TransactionId> {
15538 self.start_transaction_at(Instant::now(), window, cx);
15539 update(self, window, cx);
15540 self.end_transaction_at(Instant::now(), cx)
15541 }
15542
15543 pub fn start_transaction_at(
15544 &mut self,
15545 now: Instant,
15546 window: &mut Window,
15547 cx: &mut Context<Self>,
15548 ) {
15549 self.end_selection(window, cx);
15550 if let Some(tx_id) = self
15551 .buffer
15552 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15553 {
15554 self.selection_history
15555 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15556 cx.emit(EditorEvent::TransactionBegun {
15557 transaction_id: tx_id,
15558 })
15559 }
15560 }
15561
15562 pub fn end_transaction_at(
15563 &mut self,
15564 now: Instant,
15565 cx: &mut Context<Self>,
15566 ) -> Option<TransactionId> {
15567 if let Some(transaction_id) = self
15568 .buffer
15569 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15570 {
15571 if let Some((_, end_selections)) =
15572 self.selection_history.transaction_mut(transaction_id)
15573 {
15574 *end_selections = Some(self.selections.disjoint_anchors());
15575 } else {
15576 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15577 }
15578
15579 cx.emit(EditorEvent::Edited { transaction_id });
15580 Some(transaction_id)
15581 } else {
15582 None
15583 }
15584 }
15585
15586 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15587 if self.selection_mark_mode {
15588 self.change_selections(None, window, cx, |s| {
15589 s.move_with(|_, sel| {
15590 sel.collapse_to(sel.head(), SelectionGoal::None);
15591 });
15592 })
15593 }
15594 self.selection_mark_mode = true;
15595 cx.notify();
15596 }
15597
15598 pub fn swap_selection_ends(
15599 &mut self,
15600 _: &actions::SwapSelectionEnds,
15601 window: &mut Window,
15602 cx: &mut Context<Self>,
15603 ) {
15604 self.change_selections(None, window, cx, |s| {
15605 s.move_with(|_, sel| {
15606 if sel.start != sel.end {
15607 sel.reversed = !sel.reversed
15608 }
15609 });
15610 });
15611 self.request_autoscroll(Autoscroll::newest(), cx);
15612 cx.notify();
15613 }
15614
15615 pub fn toggle_fold(
15616 &mut self,
15617 _: &actions::ToggleFold,
15618 window: &mut Window,
15619 cx: &mut Context<Self>,
15620 ) {
15621 if self.is_singleton(cx) {
15622 let selection = self.selections.newest::<Point>(cx);
15623
15624 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15625 let range = if selection.is_empty() {
15626 let point = selection.head().to_display_point(&display_map);
15627 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15628 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15629 .to_point(&display_map);
15630 start..end
15631 } else {
15632 selection.range()
15633 };
15634 if display_map.folds_in_range(range).next().is_some() {
15635 self.unfold_lines(&Default::default(), window, cx)
15636 } else {
15637 self.fold(&Default::default(), window, cx)
15638 }
15639 } else {
15640 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15641 let buffer_ids: HashSet<_> = self
15642 .selections
15643 .disjoint_anchor_ranges()
15644 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15645 .collect();
15646
15647 let should_unfold = buffer_ids
15648 .iter()
15649 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15650
15651 for buffer_id in buffer_ids {
15652 if should_unfold {
15653 self.unfold_buffer(buffer_id, cx);
15654 } else {
15655 self.fold_buffer(buffer_id, cx);
15656 }
15657 }
15658 }
15659 }
15660
15661 pub fn toggle_fold_recursive(
15662 &mut self,
15663 _: &actions::ToggleFoldRecursive,
15664 window: &mut Window,
15665 cx: &mut Context<Self>,
15666 ) {
15667 let selection = self.selections.newest::<Point>(cx);
15668
15669 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15670 let range = if selection.is_empty() {
15671 let point = selection.head().to_display_point(&display_map);
15672 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15673 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15674 .to_point(&display_map);
15675 start..end
15676 } else {
15677 selection.range()
15678 };
15679 if display_map.folds_in_range(range).next().is_some() {
15680 self.unfold_recursive(&Default::default(), window, cx)
15681 } else {
15682 self.fold_recursive(&Default::default(), window, cx)
15683 }
15684 }
15685
15686 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15687 if self.is_singleton(cx) {
15688 let mut to_fold = Vec::new();
15689 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15690 let selections = self.selections.all_adjusted(cx);
15691
15692 for selection in selections {
15693 let range = selection.range().sorted();
15694 let buffer_start_row = range.start.row;
15695
15696 if range.start.row != range.end.row {
15697 let mut found = false;
15698 let mut row = range.start.row;
15699 while row <= range.end.row {
15700 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15701 {
15702 found = true;
15703 row = crease.range().end.row + 1;
15704 to_fold.push(crease);
15705 } else {
15706 row += 1
15707 }
15708 }
15709 if found {
15710 continue;
15711 }
15712 }
15713
15714 for row in (0..=range.start.row).rev() {
15715 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15716 if crease.range().end.row >= buffer_start_row {
15717 to_fold.push(crease);
15718 if row <= range.start.row {
15719 break;
15720 }
15721 }
15722 }
15723 }
15724 }
15725
15726 self.fold_creases(to_fold, true, window, cx);
15727 } else {
15728 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15729 let buffer_ids = self
15730 .selections
15731 .disjoint_anchor_ranges()
15732 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15733 .collect::<HashSet<_>>();
15734 for buffer_id in buffer_ids {
15735 self.fold_buffer(buffer_id, cx);
15736 }
15737 }
15738 }
15739
15740 fn fold_at_level(
15741 &mut self,
15742 fold_at: &FoldAtLevel,
15743 window: &mut Window,
15744 cx: &mut Context<Self>,
15745 ) {
15746 if !self.buffer.read(cx).is_singleton() {
15747 return;
15748 }
15749
15750 let fold_at_level = fold_at.0;
15751 let snapshot = self.buffer.read(cx).snapshot(cx);
15752 let mut to_fold = Vec::new();
15753 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15754
15755 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15756 while start_row < end_row {
15757 match self
15758 .snapshot(window, cx)
15759 .crease_for_buffer_row(MultiBufferRow(start_row))
15760 {
15761 Some(crease) => {
15762 let nested_start_row = crease.range().start.row + 1;
15763 let nested_end_row = crease.range().end.row;
15764
15765 if current_level < fold_at_level {
15766 stack.push((nested_start_row, nested_end_row, current_level + 1));
15767 } else if current_level == fold_at_level {
15768 to_fold.push(crease);
15769 }
15770
15771 start_row = nested_end_row + 1;
15772 }
15773 None => start_row += 1,
15774 }
15775 }
15776 }
15777
15778 self.fold_creases(to_fold, true, window, cx);
15779 }
15780
15781 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15782 if self.buffer.read(cx).is_singleton() {
15783 let mut fold_ranges = Vec::new();
15784 let snapshot = self.buffer.read(cx).snapshot(cx);
15785
15786 for row in 0..snapshot.max_row().0 {
15787 if let Some(foldable_range) = self
15788 .snapshot(window, cx)
15789 .crease_for_buffer_row(MultiBufferRow(row))
15790 {
15791 fold_ranges.push(foldable_range);
15792 }
15793 }
15794
15795 self.fold_creases(fold_ranges, true, window, cx);
15796 } else {
15797 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15798 editor
15799 .update_in(cx, |editor, _, cx| {
15800 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15801 editor.fold_buffer(buffer_id, cx);
15802 }
15803 })
15804 .ok();
15805 });
15806 }
15807 }
15808
15809 pub fn fold_function_bodies(
15810 &mut self,
15811 _: &actions::FoldFunctionBodies,
15812 window: &mut Window,
15813 cx: &mut Context<Self>,
15814 ) {
15815 let snapshot = self.buffer.read(cx).snapshot(cx);
15816
15817 let ranges = snapshot
15818 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15819 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15820 .collect::<Vec<_>>();
15821
15822 let creases = ranges
15823 .into_iter()
15824 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15825 .collect();
15826
15827 self.fold_creases(creases, true, window, cx);
15828 }
15829
15830 pub fn fold_recursive(
15831 &mut self,
15832 _: &actions::FoldRecursive,
15833 window: &mut Window,
15834 cx: &mut Context<Self>,
15835 ) {
15836 let mut to_fold = Vec::new();
15837 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15838 let selections = self.selections.all_adjusted(cx);
15839
15840 for selection in selections {
15841 let range = selection.range().sorted();
15842 let buffer_start_row = range.start.row;
15843
15844 if range.start.row != range.end.row {
15845 let mut found = false;
15846 for row in range.start.row..=range.end.row {
15847 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15848 found = true;
15849 to_fold.push(crease);
15850 }
15851 }
15852 if found {
15853 continue;
15854 }
15855 }
15856
15857 for row in (0..=range.start.row).rev() {
15858 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15859 if crease.range().end.row >= buffer_start_row {
15860 to_fold.push(crease);
15861 } else {
15862 break;
15863 }
15864 }
15865 }
15866 }
15867
15868 self.fold_creases(to_fold, true, window, cx);
15869 }
15870
15871 pub fn fold_at(
15872 &mut self,
15873 buffer_row: MultiBufferRow,
15874 window: &mut Window,
15875 cx: &mut Context<Self>,
15876 ) {
15877 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15878
15879 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15880 let autoscroll = self
15881 .selections
15882 .all::<Point>(cx)
15883 .iter()
15884 .any(|selection| crease.range().overlaps(&selection.range()));
15885
15886 self.fold_creases(vec![crease], autoscroll, window, cx);
15887 }
15888 }
15889
15890 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15891 if self.is_singleton(cx) {
15892 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15893 let buffer = &display_map.buffer_snapshot;
15894 let selections = self.selections.all::<Point>(cx);
15895 let ranges = selections
15896 .iter()
15897 .map(|s| {
15898 let range = s.display_range(&display_map).sorted();
15899 let mut start = range.start.to_point(&display_map);
15900 let mut end = range.end.to_point(&display_map);
15901 start.column = 0;
15902 end.column = buffer.line_len(MultiBufferRow(end.row));
15903 start..end
15904 })
15905 .collect::<Vec<_>>();
15906
15907 self.unfold_ranges(&ranges, true, true, cx);
15908 } else {
15909 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15910 let buffer_ids = self
15911 .selections
15912 .disjoint_anchor_ranges()
15913 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15914 .collect::<HashSet<_>>();
15915 for buffer_id in buffer_ids {
15916 self.unfold_buffer(buffer_id, cx);
15917 }
15918 }
15919 }
15920
15921 pub fn unfold_recursive(
15922 &mut self,
15923 _: &UnfoldRecursive,
15924 _window: &mut Window,
15925 cx: &mut Context<Self>,
15926 ) {
15927 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15928 let selections = self.selections.all::<Point>(cx);
15929 let ranges = selections
15930 .iter()
15931 .map(|s| {
15932 let mut range = s.display_range(&display_map).sorted();
15933 *range.start.column_mut() = 0;
15934 *range.end.column_mut() = display_map.line_len(range.end.row());
15935 let start = range.start.to_point(&display_map);
15936 let end = range.end.to_point(&display_map);
15937 start..end
15938 })
15939 .collect::<Vec<_>>();
15940
15941 self.unfold_ranges(&ranges, true, true, cx);
15942 }
15943
15944 pub fn unfold_at(
15945 &mut self,
15946 buffer_row: MultiBufferRow,
15947 _window: &mut Window,
15948 cx: &mut Context<Self>,
15949 ) {
15950 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15951
15952 let intersection_range = Point::new(buffer_row.0, 0)
15953 ..Point::new(
15954 buffer_row.0,
15955 display_map.buffer_snapshot.line_len(buffer_row),
15956 );
15957
15958 let autoscroll = self
15959 .selections
15960 .all::<Point>(cx)
15961 .iter()
15962 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15963
15964 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15965 }
15966
15967 pub fn unfold_all(
15968 &mut self,
15969 _: &actions::UnfoldAll,
15970 _window: &mut Window,
15971 cx: &mut Context<Self>,
15972 ) {
15973 if self.buffer.read(cx).is_singleton() {
15974 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15975 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15976 } else {
15977 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15978 editor
15979 .update(cx, |editor, cx| {
15980 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15981 editor.unfold_buffer(buffer_id, cx);
15982 }
15983 })
15984 .ok();
15985 });
15986 }
15987 }
15988
15989 pub fn fold_selected_ranges(
15990 &mut self,
15991 _: &FoldSelectedRanges,
15992 window: &mut Window,
15993 cx: &mut Context<Self>,
15994 ) {
15995 let selections = self.selections.all_adjusted(cx);
15996 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15997 let ranges = selections
15998 .into_iter()
15999 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16000 .collect::<Vec<_>>();
16001 self.fold_creases(ranges, true, window, cx);
16002 }
16003
16004 pub fn fold_ranges<T: ToOffset + Clone>(
16005 &mut self,
16006 ranges: Vec<Range<T>>,
16007 auto_scroll: bool,
16008 window: &mut Window,
16009 cx: &mut Context<Self>,
16010 ) {
16011 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16012 let ranges = ranges
16013 .into_iter()
16014 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16015 .collect::<Vec<_>>();
16016 self.fold_creases(ranges, auto_scroll, window, cx);
16017 }
16018
16019 pub fn fold_creases<T: ToOffset + Clone>(
16020 &mut self,
16021 creases: Vec<Crease<T>>,
16022 auto_scroll: bool,
16023 _window: &mut Window,
16024 cx: &mut Context<Self>,
16025 ) {
16026 if creases.is_empty() {
16027 return;
16028 }
16029
16030 let mut buffers_affected = HashSet::default();
16031 let multi_buffer = self.buffer().read(cx);
16032 for crease in &creases {
16033 if let Some((_, buffer, _)) =
16034 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16035 {
16036 buffers_affected.insert(buffer.read(cx).remote_id());
16037 };
16038 }
16039
16040 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16041
16042 if auto_scroll {
16043 self.request_autoscroll(Autoscroll::fit(), cx);
16044 }
16045
16046 cx.notify();
16047
16048 self.scrollbar_marker_state.dirty = true;
16049 self.folds_did_change(cx);
16050 }
16051
16052 /// Removes any folds whose ranges intersect any of the given ranges.
16053 pub fn unfold_ranges<T: ToOffset + Clone>(
16054 &mut self,
16055 ranges: &[Range<T>],
16056 inclusive: bool,
16057 auto_scroll: bool,
16058 cx: &mut Context<Self>,
16059 ) {
16060 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16061 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16062 });
16063 self.folds_did_change(cx);
16064 }
16065
16066 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16067 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16068 return;
16069 }
16070 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16071 self.display_map.update(cx, |display_map, cx| {
16072 display_map.fold_buffers([buffer_id], cx)
16073 });
16074 cx.emit(EditorEvent::BufferFoldToggled {
16075 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16076 folded: true,
16077 });
16078 cx.notify();
16079 }
16080
16081 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16082 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16083 return;
16084 }
16085 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16086 self.display_map.update(cx, |display_map, cx| {
16087 display_map.unfold_buffers([buffer_id], cx);
16088 });
16089 cx.emit(EditorEvent::BufferFoldToggled {
16090 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16091 folded: false,
16092 });
16093 cx.notify();
16094 }
16095
16096 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16097 self.display_map.read(cx).is_buffer_folded(buffer)
16098 }
16099
16100 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16101 self.display_map.read(cx).folded_buffers()
16102 }
16103
16104 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16105 self.display_map.update(cx, |display_map, cx| {
16106 display_map.disable_header_for_buffer(buffer_id, cx);
16107 });
16108 cx.notify();
16109 }
16110
16111 /// Removes any folds with the given ranges.
16112 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16113 &mut self,
16114 ranges: &[Range<T>],
16115 type_id: TypeId,
16116 auto_scroll: bool,
16117 cx: &mut Context<Self>,
16118 ) {
16119 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16120 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16121 });
16122 self.folds_did_change(cx);
16123 }
16124
16125 fn remove_folds_with<T: ToOffset + Clone>(
16126 &mut self,
16127 ranges: &[Range<T>],
16128 auto_scroll: bool,
16129 cx: &mut Context<Self>,
16130 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16131 ) {
16132 if ranges.is_empty() {
16133 return;
16134 }
16135
16136 let mut buffers_affected = HashSet::default();
16137 let multi_buffer = self.buffer().read(cx);
16138 for range in ranges {
16139 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16140 buffers_affected.insert(buffer.read(cx).remote_id());
16141 };
16142 }
16143
16144 self.display_map.update(cx, update);
16145
16146 if auto_scroll {
16147 self.request_autoscroll(Autoscroll::fit(), cx);
16148 }
16149
16150 cx.notify();
16151 self.scrollbar_marker_state.dirty = true;
16152 self.active_indent_guides_state.dirty = true;
16153 }
16154
16155 pub fn update_fold_widths(
16156 &mut self,
16157 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16158 cx: &mut Context<Self>,
16159 ) -> bool {
16160 self.display_map
16161 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16162 }
16163
16164 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16165 self.display_map.read(cx).fold_placeholder.clone()
16166 }
16167
16168 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16169 self.buffer.update(cx, |buffer, cx| {
16170 buffer.set_all_diff_hunks_expanded(cx);
16171 });
16172 }
16173
16174 pub fn expand_all_diff_hunks(
16175 &mut self,
16176 _: &ExpandAllDiffHunks,
16177 _window: &mut Window,
16178 cx: &mut Context<Self>,
16179 ) {
16180 self.buffer.update(cx, |buffer, cx| {
16181 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16182 });
16183 }
16184
16185 pub fn toggle_selected_diff_hunks(
16186 &mut self,
16187 _: &ToggleSelectedDiffHunks,
16188 _window: &mut Window,
16189 cx: &mut Context<Self>,
16190 ) {
16191 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16192 self.toggle_diff_hunks_in_ranges(ranges, cx);
16193 }
16194
16195 pub fn diff_hunks_in_ranges<'a>(
16196 &'a self,
16197 ranges: &'a [Range<Anchor>],
16198 buffer: &'a MultiBufferSnapshot,
16199 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16200 ranges.iter().flat_map(move |range| {
16201 let end_excerpt_id = range.end.excerpt_id;
16202 let range = range.to_point(buffer);
16203 let mut peek_end = range.end;
16204 if range.end.row < buffer.max_row().0 {
16205 peek_end = Point::new(range.end.row + 1, 0);
16206 }
16207 buffer
16208 .diff_hunks_in_range(range.start..peek_end)
16209 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16210 })
16211 }
16212
16213 pub fn has_stageable_diff_hunks_in_ranges(
16214 &self,
16215 ranges: &[Range<Anchor>],
16216 snapshot: &MultiBufferSnapshot,
16217 ) -> bool {
16218 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16219 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16220 }
16221
16222 pub fn toggle_staged_selected_diff_hunks(
16223 &mut self,
16224 _: &::git::ToggleStaged,
16225 _: &mut Window,
16226 cx: &mut Context<Self>,
16227 ) {
16228 let snapshot = self.buffer.read(cx).snapshot(cx);
16229 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16230 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16231 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16232 }
16233
16234 pub fn set_render_diff_hunk_controls(
16235 &mut self,
16236 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16237 cx: &mut Context<Self>,
16238 ) {
16239 self.render_diff_hunk_controls = render_diff_hunk_controls;
16240 cx.notify();
16241 }
16242
16243 pub fn stage_and_next(
16244 &mut self,
16245 _: &::git::StageAndNext,
16246 window: &mut Window,
16247 cx: &mut Context<Self>,
16248 ) {
16249 self.do_stage_or_unstage_and_next(true, window, cx);
16250 }
16251
16252 pub fn unstage_and_next(
16253 &mut self,
16254 _: &::git::UnstageAndNext,
16255 window: &mut Window,
16256 cx: &mut Context<Self>,
16257 ) {
16258 self.do_stage_or_unstage_and_next(false, window, cx);
16259 }
16260
16261 pub fn stage_or_unstage_diff_hunks(
16262 &mut self,
16263 stage: bool,
16264 ranges: Vec<Range<Anchor>>,
16265 cx: &mut Context<Self>,
16266 ) {
16267 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16268 cx.spawn(async move |this, cx| {
16269 task.await?;
16270 this.update(cx, |this, cx| {
16271 let snapshot = this.buffer.read(cx).snapshot(cx);
16272 let chunk_by = this
16273 .diff_hunks_in_ranges(&ranges, &snapshot)
16274 .chunk_by(|hunk| hunk.buffer_id);
16275 for (buffer_id, hunks) in &chunk_by {
16276 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16277 }
16278 })
16279 })
16280 .detach_and_log_err(cx);
16281 }
16282
16283 fn save_buffers_for_ranges_if_needed(
16284 &mut self,
16285 ranges: &[Range<Anchor>],
16286 cx: &mut Context<Editor>,
16287 ) -> Task<Result<()>> {
16288 let multibuffer = self.buffer.read(cx);
16289 let snapshot = multibuffer.read(cx);
16290 let buffer_ids: HashSet<_> = ranges
16291 .iter()
16292 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16293 .collect();
16294 drop(snapshot);
16295
16296 let mut buffers = HashSet::default();
16297 for buffer_id in buffer_ids {
16298 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16299 let buffer = buffer_entity.read(cx);
16300 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16301 {
16302 buffers.insert(buffer_entity);
16303 }
16304 }
16305 }
16306
16307 if let Some(project) = &self.project {
16308 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16309 } else {
16310 Task::ready(Ok(()))
16311 }
16312 }
16313
16314 fn do_stage_or_unstage_and_next(
16315 &mut self,
16316 stage: bool,
16317 window: &mut Window,
16318 cx: &mut Context<Self>,
16319 ) {
16320 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16321
16322 if ranges.iter().any(|range| range.start != range.end) {
16323 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16324 return;
16325 }
16326
16327 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16328 let snapshot = self.snapshot(window, cx);
16329 let position = self.selections.newest::<Point>(cx).head();
16330 let mut row = snapshot
16331 .buffer_snapshot
16332 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16333 .find(|hunk| hunk.row_range.start.0 > position.row)
16334 .map(|hunk| hunk.row_range.start);
16335
16336 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16337 // Outside of the project diff editor, wrap around to the beginning.
16338 if !all_diff_hunks_expanded {
16339 row = row.or_else(|| {
16340 snapshot
16341 .buffer_snapshot
16342 .diff_hunks_in_range(Point::zero()..position)
16343 .find(|hunk| hunk.row_range.end.0 < position.row)
16344 .map(|hunk| hunk.row_range.start)
16345 });
16346 }
16347
16348 if let Some(row) = row {
16349 let destination = Point::new(row.0, 0);
16350 let autoscroll = Autoscroll::center();
16351
16352 self.unfold_ranges(&[destination..destination], false, false, cx);
16353 self.change_selections(Some(autoscroll), window, cx, |s| {
16354 s.select_ranges([destination..destination]);
16355 });
16356 }
16357 }
16358
16359 fn do_stage_or_unstage(
16360 &self,
16361 stage: bool,
16362 buffer_id: BufferId,
16363 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16364 cx: &mut App,
16365 ) -> Option<()> {
16366 let project = self.project.as_ref()?;
16367 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16368 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16369 let buffer_snapshot = buffer.read(cx).snapshot();
16370 let file_exists = buffer_snapshot
16371 .file()
16372 .is_some_and(|file| file.disk_state().exists());
16373 diff.update(cx, |diff, cx| {
16374 diff.stage_or_unstage_hunks(
16375 stage,
16376 &hunks
16377 .map(|hunk| buffer_diff::DiffHunk {
16378 buffer_range: hunk.buffer_range,
16379 diff_base_byte_range: hunk.diff_base_byte_range,
16380 secondary_status: hunk.secondary_status,
16381 range: Point::zero()..Point::zero(), // unused
16382 })
16383 .collect::<Vec<_>>(),
16384 &buffer_snapshot,
16385 file_exists,
16386 cx,
16387 )
16388 });
16389 None
16390 }
16391
16392 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16393 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16394 self.buffer
16395 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16396 }
16397
16398 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16399 self.buffer.update(cx, |buffer, cx| {
16400 let ranges = vec![Anchor::min()..Anchor::max()];
16401 if !buffer.all_diff_hunks_expanded()
16402 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16403 {
16404 buffer.collapse_diff_hunks(ranges, cx);
16405 true
16406 } else {
16407 false
16408 }
16409 })
16410 }
16411
16412 fn toggle_diff_hunks_in_ranges(
16413 &mut self,
16414 ranges: Vec<Range<Anchor>>,
16415 cx: &mut Context<Editor>,
16416 ) {
16417 self.buffer.update(cx, |buffer, cx| {
16418 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16419 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16420 })
16421 }
16422
16423 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16424 self.buffer.update(cx, |buffer, cx| {
16425 let snapshot = buffer.snapshot(cx);
16426 let excerpt_id = range.end.excerpt_id;
16427 let point_range = range.to_point(&snapshot);
16428 let expand = !buffer.single_hunk_is_expanded(range, cx);
16429 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16430 })
16431 }
16432
16433 pub(crate) fn apply_all_diff_hunks(
16434 &mut self,
16435 _: &ApplyAllDiffHunks,
16436 window: &mut Window,
16437 cx: &mut Context<Self>,
16438 ) {
16439 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16440
16441 let buffers = self.buffer.read(cx).all_buffers();
16442 for branch_buffer in buffers {
16443 branch_buffer.update(cx, |branch_buffer, cx| {
16444 branch_buffer.merge_into_base(Vec::new(), cx);
16445 });
16446 }
16447
16448 if let Some(project) = self.project.clone() {
16449 self.save(true, project, window, cx).detach_and_log_err(cx);
16450 }
16451 }
16452
16453 pub(crate) fn apply_selected_diff_hunks(
16454 &mut self,
16455 _: &ApplyDiffHunk,
16456 window: &mut Window,
16457 cx: &mut Context<Self>,
16458 ) {
16459 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16460 let snapshot = self.snapshot(window, cx);
16461 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16462 let mut ranges_by_buffer = HashMap::default();
16463 self.transact(window, cx, |editor, _window, cx| {
16464 for hunk in hunks {
16465 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16466 ranges_by_buffer
16467 .entry(buffer.clone())
16468 .or_insert_with(Vec::new)
16469 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16470 }
16471 }
16472
16473 for (buffer, ranges) in ranges_by_buffer {
16474 buffer.update(cx, |buffer, cx| {
16475 buffer.merge_into_base(ranges, cx);
16476 });
16477 }
16478 });
16479
16480 if let Some(project) = self.project.clone() {
16481 self.save(true, project, window, cx).detach_and_log_err(cx);
16482 }
16483 }
16484
16485 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16486 if hovered != self.gutter_hovered {
16487 self.gutter_hovered = hovered;
16488 cx.notify();
16489 }
16490 }
16491
16492 pub fn insert_blocks(
16493 &mut self,
16494 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16495 autoscroll: Option<Autoscroll>,
16496 cx: &mut Context<Self>,
16497 ) -> Vec<CustomBlockId> {
16498 let blocks = self
16499 .display_map
16500 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16501 if let Some(autoscroll) = autoscroll {
16502 self.request_autoscroll(autoscroll, cx);
16503 }
16504 cx.notify();
16505 blocks
16506 }
16507
16508 pub fn resize_blocks(
16509 &mut self,
16510 heights: HashMap<CustomBlockId, u32>,
16511 autoscroll: Option<Autoscroll>,
16512 cx: &mut Context<Self>,
16513 ) {
16514 self.display_map
16515 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16516 if let Some(autoscroll) = autoscroll {
16517 self.request_autoscroll(autoscroll, cx);
16518 }
16519 cx.notify();
16520 }
16521
16522 pub fn replace_blocks(
16523 &mut self,
16524 renderers: HashMap<CustomBlockId, RenderBlock>,
16525 autoscroll: Option<Autoscroll>,
16526 cx: &mut Context<Self>,
16527 ) {
16528 self.display_map
16529 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16530 if let Some(autoscroll) = autoscroll {
16531 self.request_autoscroll(autoscroll, cx);
16532 }
16533 cx.notify();
16534 }
16535
16536 pub fn remove_blocks(
16537 &mut self,
16538 block_ids: HashSet<CustomBlockId>,
16539 autoscroll: Option<Autoscroll>,
16540 cx: &mut Context<Self>,
16541 ) {
16542 self.display_map.update(cx, |display_map, cx| {
16543 display_map.remove_blocks(block_ids, cx)
16544 });
16545 if let Some(autoscroll) = autoscroll {
16546 self.request_autoscroll(autoscroll, cx);
16547 }
16548 cx.notify();
16549 }
16550
16551 pub fn row_for_block(
16552 &self,
16553 block_id: CustomBlockId,
16554 cx: &mut Context<Self>,
16555 ) -> Option<DisplayRow> {
16556 self.display_map
16557 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16558 }
16559
16560 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16561 self.focused_block = Some(focused_block);
16562 }
16563
16564 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16565 self.focused_block.take()
16566 }
16567
16568 pub fn insert_creases(
16569 &mut self,
16570 creases: impl IntoIterator<Item = Crease<Anchor>>,
16571 cx: &mut Context<Self>,
16572 ) -> Vec<CreaseId> {
16573 self.display_map
16574 .update(cx, |map, cx| map.insert_creases(creases, cx))
16575 }
16576
16577 pub fn remove_creases(
16578 &mut self,
16579 ids: impl IntoIterator<Item = CreaseId>,
16580 cx: &mut Context<Self>,
16581 ) -> Vec<(CreaseId, Range<Anchor>)> {
16582 self.display_map
16583 .update(cx, |map, cx| map.remove_creases(ids, cx))
16584 }
16585
16586 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16587 self.display_map
16588 .update(cx, |map, cx| map.snapshot(cx))
16589 .longest_row()
16590 }
16591
16592 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16593 self.display_map
16594 .update(cx, |map, cx| map.snapshot(cx))
16595 .max_point()
16596 }
16597
16598 pub fn text(&self, cx: &App) -> String {
16599 self.buffer.read(cx).read(cx).text()
16600 }
16601
16602 pub fn is_empty(&self, cx: &App) -> bool {
16603 self.buffer.read(cx).read(cx).is_empty()
16604 }
16605
16606 pub fn text_option(&self, cx: &App) -> Option<String> {
16607 let text = self.text(cx);
16608 let text = text.trim();
16609
16610 if text.is_empty() {
16611 return None;
16612 }
16613
16614 Some(text.to_string())
16615 }
16616
16617 pub fn set_text(
16618 &mut self,
16619 text: impl Into<Arc<str>>,
16620 window: &mut Window,
16621 cx: &mut Context<Self>,
16622 ) {
16623 self.transact(window, cx, |this, _, cx| {
16624 this.buffer
16625 .read(cx)
16626 .as_singleton()
16627 .expect("you can only call set_text on editors for singleton buffers")
16628 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16629 });
16630 }
16631
16632 pub fn display_text(&self, cx: &mut App) -> String {
16633 self.display_map
16634 .update(cx, |map, cx| map.snapshot(cx))
16635 .text()
16636 }
16637
16638 fn create_minimap(
16639 &self,
16640 minimap_settings: MinimapSettings,
16641 window: &mut Window,
16642 cx: &mut Context<Self>,
16643 ) -> Option<Entity<Self>> {
16644 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
16645 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
16646 }
16647
16648 fn initialize_new_minimap(
16649 &self,
16650 minimap_settings: MinimapSettings,
16651 window: &mut Window,
16652 cx: &mut Context<Self>,
16653 ) -> Entity<Self> {
16654 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
16655
16656 let mut minimap = Editor::new_internal(
16657 EditorMode::Minimap {
16658 parent: cx.weak_entity(),
16659 },
16660 self.buffer.clone(),
16661 self.project.clone(),
16662 Some(self.display_map.clone()),
16663 window,
16664 cx,
16665 );
16666 minimap.scroll_manager.clone_state(&self.scroll_manager);
16667 minimap.set_text_style_refinement(TextStyleRefinement {
16668 font_size: Some(MINIMAP_FONT_SIZE),
16669 font_weight: Some(MINIMAP_FONT_WEIGHT),
16670 ..Default::default()
16671 });
16672 minimap.update_minimap_configuration(minimap_settings, cx);
16673 cx.new(|_| minimap)
16674 }
16675
16676 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
16677 let current_line_highlight = minimap_settings
16678 .current_line_highlight
16679 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
16680 self.set_current_line_highlight(Some(current_line_highlight));
16681 }
16682
16683 pub fn minimap(&self) -> Option<&Entity<Self>> {
16684 self.minimap
16685 .as_ref()
16686 .filter(|_| self.minimap_visibility.visible())
16687 }
16688
16689 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16690 let mut wrap_guides = smallvec::smallvec![];
16691
16692 if self.show_wrap_guides == Some(false) {
16693 return wrap_guides;
16694 }
16695
16696 let settings = self.buffer.read(cx).language_settings(cx);
16697 if settings.show_wrap_guides {
16698 match self.soft_wrap_mode(cx) {
16699 SoftWrap::Column(soft_wrap) => {
16700 wrap_guides.push((soft_wrap as usize, true));
16701 }
16702 SoftWrap::Bounded(soft_wrap) => {
16703 wrap_guides.push((soft_wrap as usize, true));
16704 }
16705 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16706 }
16707 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16708 }
16709
16710 wrap_guides
16711 }
16712
16713 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16714 let settings = self.buffer.read(cx).language_settings(cx);
16715 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16716 match mode {
16717 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16718 SoftWrap::None
16719 }
16720 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16721 language_settings::SoftWrap::PreferredLineLength => {
16722 SoftWrap::Column(settings.preferred_line_length)
16723 }
16724 language_settings::SoftWrap::Bounded => {
16725 SoftWrap::Bounded(settings.preferred_line_length)
16726 }
16727 }
16728 }
16729
16730 pub fn set_soft_wrap_mode(
16731 &mut self,
16732 mode: language_settings::SoftWrap,
16733
16734 cx: &mut Context<Self>,
16735 ) {
16736 self.soft_wrap_mode_override = Some(mode);
16737 cx.notify();
16738 }
16739
16740 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16741 self.hard_wrap = hard_wrap;
16742 cx.notify();
16743 }
16744
16745 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16746 self.text_style_refinement = Some(style);
16747 }
16748
16749 /// called by the Element so we know what style we were most recently rendered with.
16750 pub(crate) fn set_style(
16751 &mut self,
16752 style: EditorStyle,
16753 window: &mut Window,
16754 cx: &mut Context<Self>,
16755 ) {
16756 // We intentionally do not inform the display map about the minimap style
16757 // so that wrapping is not recalculated and stays consistent for the editor
16758 // and its linked minimap.
16759 if !self.mode.is_minimap() {
16760 let rem_size = window.rem_size();
16761 self.display_map.update(cx, |map, cx| {
16762 map.set_font(
16763 style.text.font(),
16764 style.text.font_size.to_pixels(rem_size),
16765 cx,
16766 )
16767 });
16768 }
16769 self.style = Some(style);
16770 }
16771
16772 pub fn style(&self) -> Option<&EditorStyle> {
16773 self.style.as_ref()
16774 }
16775
16776 // Called by the element. This method is not designed to be called outside of the editor
16777 // element's layout code because it does not notify when rewrapping is computed synchronously.
16778 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16779 self.display_map
16780 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16781 }
16782
16783 pub fn set_soft_wrap(&mut self) {
16784 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16785 }
16786
16787 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16788 if self.soft_wrap_mode_override.is_some() {
16789 self.soft_wrap_mode_override.take();
16790 } else {
16791 let soft_wrap = match self.soft_wrap_mode(cx) {
16792 SoftWrap::GitDiff => return,
16793 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16794 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16795 language_settings::SoftWrap::None
16796 }
16797 };
16798 self.soft_wrap_mode_override = Some(soft_wrap);
16799 }
16800 cx.notify();
16801 }
16802
16803 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16804 let Some(workspace) = self.workspace() else {
16805 return;
16806 };
16807 let fs = workspace.read(cx).app_state().fs.clone();
16808 let current_show = TabBarSettings::get_global(cx).show;
16809 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16810 setting.show = Some(!current_show);
16811 });
16812 }
16813
16814 pub fn toggle_indent_guides(
16815 &mut self,
16816 _: &ToggleIndentGuides,
16817 _: &mut Window,
16818 cx: &mut Context<Self>,
16819 ) {
16820 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16821 self.buffer
16822 .read(cx)
16823 .language_settings(cx)
16824 .indent_guides
16825 .enabled
16826 });
16827 self.show_indent_guides = Some(!currently_enabled);
16828 cx.notify();
16829 }
16830
16831 fn should_show_indent_guides(&self) -> Option<bool> {
16832 self.show_indent_guides
16833 }
16834
16835 pub fn toggle_line_numbers(
16836 &mut self,
16837 _: &ToggleLineNumbers,
16838 _: &mut Window,
16839 cx: &mut Context<Self>,
16840 ) {
16841 let mut editor_settings = EditorSettings::get_global(cx).clone();
16842 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16843 EditorSettings::override_global(editor_settings, cx);
16844 }
16845
16846 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16847 if let Some(show_line_numbers) = self.show_line_numbers {
16848 return show_line_numbers;
16849 }
16850 EditorSettings::get_global(cx).gutter.line_numbers
16851 }
16852
16853 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16854 self.use_relative_line_numbers
16855 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16856 }
16857
16858 pub fn toggle_relative_line_numbers(
16859 &mut self,
16860 _: &ToggleRelativeLineNumbers,
16861 _: &mut Window,
16862 cx: &mut Context<Self>,
16863 ) {
16864 let is_relative = self.should_use_relative_line_numbers(cx);
16865 self.set_relative_line_number(Some(!is_relative), cx)
16866 }
16867
16868 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16869 self.use_relative_line_numbers = is_relative;
16870 cx.notify();
16871 }
16872
16873 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16874 self.show_gutter = show_gutter;
16875 cx.notify();
16876 }
16877
16878 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
16879 self.show_scrollbars = show_scrollbars;
16880 cx.notify();
16881 }
16882
16883 pub fn set_minimap_visibility(
16884 &mut self,
16885 minimap_visibility: MinimapVisibility,
16886 window: &mut Window,
16887 cx: &mut Context<Self>,
16888 ) {
16889 if self.minimap_visibility != minimap_visibility {
16890 if minimap_visibility.visible() && self.minimap.is_none() {
16891 let minimap_settings = EditorSettings::get_global(cx).minimap;
16892 self.minimap =
16893 self.create_minimap(minimap_settings.with_show_override(), window, cx);
16894 }
16895 self.minimap_visibility = minimap_visibility;
16896 cx.notify();
16897 }
16898 }
16899
16900 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16901 self.set_show_scrollbars(false, cx);
16902 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
16903 }
16904
16905 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
16906 self.show_line_numbers = Some(show_line_numbers);
16907 cx.notify();
16908 }
16909
16910 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
16911 self.disable_expand_excerpt_buttons = true;
16912 cx.notify();
16913 }
16914
16915 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
16916 self.show_git_diff_gutter = Some(show_git_diff_gutter);
16917 cx.notify();
16918 }
16919
16920 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
16921 self.show_code_actions = Some(show_code_actions);
16922 cx.notify();
16923 }
16924
16925 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
16926 self.show_runnables = Some(show_runnables);
16927 cx.notify();
16928 }
16929
16930 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
16931 self.show_breakpoints = Some(show_breakpoints);
16932 cx.notify();
16933 }
16934
16935 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
16936 if self.display_map.read(cx).masked != masked {
16937 self.display_map.update(cx, |map, _| map.masked = masked);
16938 }
16939 cx.notify()
16940 }
16941
16942 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
16943 self.show_wrap_guides = Some(show_wrap_guides);
16944 cx.notify();
16945 }
16946
16947 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
16948 self.show_indent_guides = Some(show_indent_guides);
16949 cx.notify();
16950 }
16951
16952 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
16953 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
16954 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
16955 if let Some(dir) = file.abs_path(cx).parent() {
16956 return Some(dir.to_owned());
16957 }
16958 }
16959
16960 if let Some(project_path) = buffer.read(cx).project_path(cx) {
16961 return Some(project_path.path.to_path_buf());
16962 }
16963 }
16964
16965 None
16966 }
16967
16968 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
16969 self.active_excerpt(cx)?
16970 .1
16971 .read(cx)
16972 .file()
16973 .and_then(|f| f.as_local())
16974 }
16975
16976 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16977 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16978 let buffer = buffer.read(cx);
16979 if let Some(project_path) = buffer.project_path(cx) {
16980 let project = self.project.as_ref()?.read(cx);
16981 project.absolute_path(&project_path, cx)
16982 } else {
16983 buffer
16984 .file()
16985 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
16986 }
16987 })
16988 }
16989
16990 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16991 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16992 let project_path = buffer.read(cx).project_path(cx)?;
16993 let project = self.project.as_ref()?.read(cx);
16994 let entry = project.entry_for_path(&project_path, cx)?;
16995 let path = entry.path.to_path_buf();
16996 Some(path)
16997 })
16998 }
16999
17000 pub fn reveal_in_finder(
17001 &mut self,
17002 _: &RevealInFileManager,
17003 _window: &mut Window,
17004 cx: &mut Context<Self>,
17005 ) {
17006 if let Some(target) = self.target_file(cx) {
17007 cx.reveal_path(&target.abs_path(cx));
17008 }
17009 }
17010
17011 pub fn copy_path(
17012 &mut self,
17013 _: &zed_actions::workspace::CopyPath,
17014 _window: &mut Window,
17015 cx: &mut Context<Self>,
17016 ) {
17017 if let Some(path) = self.target_file_abs_path(cx) {
17018 if let Some(path) = path.to_str() {
17019 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17020 }
17021 }
17022 }
17023
17024 pub fn copy_relative_path(
17025 &mut self,
17026 _: &zed_actions::workspace::CopyRelativePath,
17027 _window: &mut Window,
17028 cx: &mut Context<Self>,
17029 ) {
17030 if let Some(path) = self.target_file_path(cx) {
17031 if let Some(path) = path.to_str() {
17032 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17033 }
17034 }
17035 }
17036
17037 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17038 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17039 buffer.read(cx).project_path(cx)
17040 } else {
17041 None
17042 }
17043 }
17044
17045 // Returns true if the editor handled a go-to-line request
17046 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17047 maybe!({
17048 let breakpoint_store = self.breakpoint_store.as_ref()?;
17049
17050 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17051 else {
17052 self.clear_row_highlights::<ActiveDebugLine>();
17053 return None;
17054 };
17055
17056 let position = active_stack_frame.position;
17057 let buffer_id = position.buffer_id?;
17058 let snapshot = self
17059 .project
17060 .as_ref()?
17061 .read(cx)
17062 .buffer_for_id(buffer_id, cx)?
17063 .read(cx)
17064 .snapshot();
17065
17066 let mut handled = false;
17067 for (id, ExcerptRange { context, .. }) in
17068 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17069 {
17070 if context.start.cmp(&position, &snapshot).is_ge()
17071 || context.end.cmp(&position, &snapshot).is_lt()
17072 {
17073 continue;
17074 }
17075 let snapshot = self.buffer.read(cx).snapshot(cx);
17076 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17077
17078 handled = true;
17079 self.clear_row_highlights::<ActiveDebugLine>();
17080
17081 self.go_to_line::<ActiveDebugLine>(
17082 multibuffer_anchor,
17083 Some(cx.theme().colors().editor_debugger_active_line_background),
17084 window,
17085 cx,
17086 );
17087
17088 cx.notify();
17089 }
17090
17091 handled.then_some(())
17092 })
17093 .is_some()
17094 }
17095
17096 pub fn copy_file_name_without_extension(
17097 &mut self,
17098 _: &CopyFileNameWithoutExtension,
17099 _: &mut Window,
17100 cx: &mut Context<Self>,
17101 ) {
17102 if let Some(file) = self.target_file(cx) {
17103 if let Some(file_stem) = file.path().file_stem() {
17104 if let Some(name) = file_stem.to_str() {
17105 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17106 }
17107 }
17108 }
17109 }
17110
17111 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17112 if let Some(file) = self.target_file(cx) {
17113 if let Some(file_name) = file.path().file_name() {
17114 if let Some(name) = file_name.to_str() {
17115 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17116 }
17117 }
17118 }
17119 }
17120
17121 pub fn toggle_git_blame(
17122 &mut self,
17123 _: &::git::Blame,
17124 window: &mut Window,
17125 cx: &mut Context<Self>,
17126 ) {
17127 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17128
17129 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17130 self.start_git_blame(true, window, cx);
17131 }
17132
17133 cx.notify();
17134 }
17135
17136 pub fn toggle_git_blame_inline(
17137 &mut self,
17138 _: &ToggleGitBlameInline,
17139 window: &mut Window,
17140 cx: &mut Context<Self>,
17141 ) {
17142 self.toggle_git_blame_inline_internal(true, window, cx);
17143 cx.notify();
17144 }
17145
17146 pub fn open_git_blame_commit(
17147 &mut self,
17148 _: &OpenGitBlameCommit,
17149 window: &mut Window,
17150 cx: &mut Context<Self>,
17151 ) {
17152 self.open_git_blame_commit_internal(window, cx);
17153 }
17154
17155 fn open_git_blame_commit_internal(
17156 &mut self,
17157 window: &mut Window,
17158 cx: &mut Context<Self>,
17159 ) -> Option<()> {
17160 let blame = self.blame.as_ref()?;
17161 let snapshot = self.snapshot(window, cx);
17162 let cursor = self.selections.newest::<Point>(cx).head();
17163 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17164 let blame_entry = blame
17165 .update(cx, |blame, cx| {
17166 blame
17167 .blame_for_rows(
17168 &[RowInfo {
17169 buffer_id: Some(buffer.remote_id()),
17170 buffer_row: Some(point.row),
17171 ..Default::default()
17172 }],
17173 cx,
17174 )
17175 .next()
17176 })
17177 .flatten()?;
17178 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17179 let repo = blame.read(cx).repository(cx)?;
17180 let workspace = self.workspace()?.downgrade();
17181 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17182 None
17183 }
17184
17185 pub fn git_blame_inline_enabled(&self) -> bool {
17186 self.git_blame_inline_enabled
17187 }
17188
17189 pub fn toggle_selection_menu(
17190 &mut self,
17191 _: &ToggleSelectionMenu,
17192 _: &mut Window,
17193 cx: &mut Context<Self>,
17194 ) {
17195 self.show_selection_menu = self
17196 .show_selection_menu
17197 .map(|show_selections_menu| !show_selections_menu)
17198 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17199
17200 cx.notify();
17201 }
17202
17203 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17204 self.show_selection_menu
17205 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17206 }
17207
17208 fn start_git_blame(
17209 &mut self,
17210 user_triggered: bool,
17211 window: &mut Window,
17212 cx: &mut Context<Self>,
17213 ) {
17214 if let Some(project) = self.project.as_ref() {
17215 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17216 return;
17217 };
17218
17219 if buffer.read(cx).file().is_none() {
17220 return;
17221 }
17222
17223 let focused = self.focus_handle(cx).contains_focused(window, cx);
17224
17225 let project = project.clone();
17226 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17227 self.blame_subscription =
17228 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17229 self.blame = Some(blame);
17230 }
17231 }
17232
17233 fn toggle_git_blame_inline_internal(
17234 &mut self,
17235 user_triggered: bool,
17236 window: &mut Window,
17237 cx: &mut Context<Self>,
17238 ) {
17239 if self.git_blame_inline_enabled {
17240 self.git_blame_inline_enabled = false;
17241 self.show_git_blame_inline = false;
17242 self.show_git_blame_inline_delay_task.take();
17243 } else {
17244 self.git_blame_inline_enabled = true;
17245 self.start_git_blame_inline(user_triggered, window, cx);
17246 }
17247
17248 cx.notify();
17249 }
17250
17251 fn start_git_blame_inline(
17252 &mut self,
17253 user_triggered: bool,
17254 window: &mut Window,
17255 cx: &mut Context<Self>,
17256 ) {
17257 self.start_git_blame(user_triggered, window, cx);
17258
17259 if ProjectSettings::get_global(cx)
17260 .git
17261 .inline_blame_delay()
17262 .is_some()
17263 {
17264 self.start_inline_blame_timer(window, cx);
17265 } else {
17266 self.show_git_blame_inline = true
17267 }
17268 }
17269
17270 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17271 self.blame.as_ref()
17272 }
17273
17274 pub fn show_git_blame_gutter(&self) -> bool {
17275 self.show_git_blame_gutter
17276 }
17277
17278 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17279 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17280 }
17281
17282 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17283 self.show_git_blame_inline
17284 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17285 && !self.newest_selection_head_on_empty_line(cx)
17286 && self.has_blame_entries(cx)
17287 }
17288
17289 fn has_blame_entries(&self, cx: &App) -> bool {
17290 self.blame()
17291 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17292 }
17293
17294 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17295 let cursor_anchor = self.selections.newest_anchor().head();
17296
17297 let snapshot = self.buffer.read(cx).snapshot(cx);
17298 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17299
17300 snapshot.line_len(buffer_row) == 0
17301 }
17302
17303 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17304 let buffer_and_selection = maybe!({
17305 let selection = self.selections.newest::<Point>(cx);
17306 let selection_range = selection.range();
17307
17308 let multi_buffer = self.buffer().read(cx);
17309 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17310 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17311
17312 let (buffer, range, _) = if selection.reversed {
17313 buffer_ranges.first()
17314 } else {
17315 buffer_ranges.last()
17316 }?;
17317
17318 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17319 ..text::ToPoint::to_point(&range.end, &buffer).row;
17320 Some((
17321 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17322 selection,
17323 ))
17324 });
17325
17326 let Some((buffer, selection)) = buffer_and_selection else {
17327 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17328 };
17329
17330 let Some(project) = self.project.as_ref() else {
17331 return Task::ready(Err(anyhow!("editor does not have project")));
17332 };
17333
17334 project.update(cx, |project, cx| {
17335 project.get_permalink_to_line(&buffer, selection, cx)
17336 })
17337 }
17338
17339 pub fn copy_permalink_to_line(
17340 &mut self,
17341 _: &CopyPermalinkToLine,
17342 window: &mut Window,
17343 cx: &mut Context<Self>,
17344 ) {
17345 let permalink_task = self.get_permalink_to_line(cx);
17346 let workspace = self.workspace();
17347
17348 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17349 Ok(permalink) => {
17350 cx.update(|_, cx| {
17351 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17352 })
17353 .ok();
17354 }
17355 Err(err) => {
17356 let message = format!("Failed to copy permalink: {err}");
17357
17358 anyhow::Result::<()>::Err(err).log_err();
17359
17360 if let Some(workspace) = workspace {
17361 workspace
17362 .update_in(cx, |workspace, _, cx| {
17363 struct CopyPermalinkToLine;
17364
17365 workspace.show_toast(
17366 Toast::new(
17367 NotificationId::unique::<CopyPermalinkToLine>(),
17368 message,
17369 ),
17370 cx,
17371 )
17372 })
17373 .ok();
17374 }
17375 }
17376 })
17377 .detach();
17378 }
17379
17380 pub fn copy_file_location(
17381 &mut self,
17382 _: &CopyFileLocation,
17383 _: &mut Window,
17384 cx: &mut Context<Self>,
17385 ) {
17386 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17387 if let Some(file) = self.target_file(cx) {
17388 if let Some(path) = file.path().to_str() {
17389 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17390 }
17391 }
17392 }
17393
17394 pub fn open_permalink_to_line(
17395 &mut self,
17396 _: &OpenPermalinkToLine,
17397 window: &mut Window,
17398 cx: &mut Context<Self>,
17399 ) {
17400 let permalink_task = self.get_permalink_to_line(cx);
17401 let workspace = self.workspace();
17402
17403 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17404 Ok(permalink) => {
17405 cx.update(|_, cx| {
17406 cx.open_url(permalink.as_ref());
17407 })
17408 .ok();
17409 }
17410 Err(err) => {
17411 let message = format!("Failed to open permalink: {err}");
17412
17413 anyhow::Result::<()>::Err(err).log_err();
17414
17415 if let Some(workspace) = workspace {
17416 workspace
17417 .update(cx, |workspace, cx| {
17418 struct OpenPermalinkToLine;
17419
17420 workspace.show_toast(
17421 Toast::new(
17422 NotificationId::unique::<OpenPermalinkToLine>(),
17423 message,
17424 ),
17425 cx,
17426 )
17427 })
17428 .ok();
17429 }
17430 }
17431 })
17432 .detach();
17433 }
17434
17435 pub fn insert_uuid_v4(
17436 &mut self,
17437 _: &InsertUuidV4,
17438 window: &mut Window,
17439 cx: &mut Context<Self>,
17440 ) {
17441 self.insert_uuid(UuidVersion::V4, window, cx);
17442 }
17443
17444 pub fn insert_uuid_v7(
17445 &mut self,
17446 _: &InsertUuidV7,
17447 window: &mut Window,
17448 cx: &mut Context<Self>,
17449 ) {
17450 self.insert_uuid(UuidVersion::V7, window, cx);
17451 }
17452
17453 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17454 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17455 self.transact(window, cx, |this, window, cx| {
17456 let edits = this
17457 .selections
17458 .all::<Point>(cx)
17459 .into_iter()
17460 .map(|selection| {
17461 let uuid = match version {
17462 UuidVersion::V4 => uuid::Uuid::new_v4(),
17463 UuidVersion::V7 => uuid::Uuid::now_v7(),
17464 };
17465
17466 (selection.range(), uuid.to_string())
17467 });
17468 this.edit(edits, cx);
17469 this.refresh_inline_completion(true, false, window, cx);
17470 });
17471 }
17472
17473 pub fn open_selections_in_multibuffer(
17474 &mut self,
17475 _: &OpenSelectionsInMultibuffer,
17476 window: &mut Window,
17477 cx: &mut Context<Self>,
17478 ) {
17479 let multibuffer = self.buffer.read(cx);
17480
17481 let Some(buffer) = multibuffer.as_singleton() else {
17482 return;
17483 };
17484
17485 let Some(workspace) = self.workspace() else {
17486 return;
17487 };
17488
17489 let locations = self
17490 .selections
17491 .disjoint_anchors()
17492 .iter()
17493 .map(|range| Location {
17494 buffer: buffer.clone(),
17495 range: range.start.text_anchor..range.end.text_anchor,
17496 })
17497 .collect::<Vec<_>>();
17498
17499 let title = multibuffer.title(cx).to_string();
17500
17501 cx.spawn_in(window, async move |_, cx| {
17502 workspace.update_in(cx, |workspace, window, cx| {
17503 Self::open_locations_in_multibuffer(
17504 workspace,
17505 locations,
17506 format!("Selections for '{title}'"),
17507 false,
17508 MultibufferSelectionMode::All,
17509 window,
17510 cx,
17511 );
17512 })
17513 })
17514 .detach();
17515 }
17516
17517 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17518 /// last highlight added will be used.
17519 ///
17520 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17521 pub fn highlight_rows<T: 'static>(
17522 &mut self,
17523 range: Range<Anchor>,
17524 color: Hsla,
17525 options: RowHighlightOptions,
17526 cx: &mut Context<Self>,
17527 ) {
17528 let snapshot = self.buffer().read(cx).snapshot(cx);
17529 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17530 let ix = row_highlights.binary_search_by(|highlight| {
17531 Ordering::Equal
17532 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17533 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17534 });
17535
17536 if let Err(mut ix) = ix {
17537 let index = post_inc(&mut self.highlight_order);
17538
17539 // If this range intersects with the preceding highlight, then merge it with
17540 // the preceding highlight. Otherwise insert a new highlight.
17541 let mut merged = false;
17542 if ix > 0 {
17543 let prev_highlight = &mut row_highlights[ix - 1];
17544 if prev_highlight
17545 .range
17546 .end
17547 .cmp(&range.start, &snapshot)
17548 .is_ge()
17549 {
17550 ix -= 1;
17551 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17552 prev_highlight.range.end = range.end;
17553 }
17554 merged = true;
17555 prev_highlight.index = index;
17556 prev_highlight.color = color;
17557 prev_highlight.options = options;
17558 }
17559 }
17560
17561 if !merged {
17562 row_highlights.insert(
17563 ix,
17564 RowHighlight {
17565 range: range.clone(),
17566 index,
17567 color,
17568 options,
17569 type_id: TypeId::of::<T>(),
17570 },
17571 );
17572 }
17573
17574 // If any of the following highlights intersect with this one, merge them.
17575 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17576 let highlight = &row_highlights[ix];
17577 if next_highlight
17578 .range
17579 .start
17580 .cmp(&highlight.range.end, &snapshot)
17581 .is_le()
17582 {
17583 if next_highlight
17584 .range
17585 .end
17586 .cmp(&highlight.range.end, &snapshot)
17587 .is_gt()
17588 {
17589 row_highlights[ix].range.end = next_highlight.range.end;
17590 }
17591 row_highlights.remove(ix + 1);
17592 } else {
17593 break;
17594 }
17595 }
17596 }
17597 }
17598
17599 /// Remove any highlighted row ranges of the given type that intersect the
17600 /// given ranges.
17601 pub fn remove_highlighted_rows<T: 'static>(
17602 &mut self,
17603 ranges_to_remove: Vec<Range<Anchor>>,
17604 cx: &mut Context<Self>,
17605 ) {
17606 let snapshot = self.buffer().read(cx).snapshot(cx);
17607 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17608 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17609 row_highlights.retain(|highlight| {
17610 while let Some(range_to_remove) = ranges_to_remove.peek() {
17611 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17612 Ordering::Less | Ordering::Equal => {
17613 ranges_to_remove.next();
17614 }
17615 Ordering::Greater => {
17616 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17617 Ordering::Less | Ordering::Equal => {
17618 return false;
17619 }
17620 Ordering::Greater => break,
17621 }
17622 }
17623 }
17624 }
17625
17626 true
17627 })
17628 }
17629
17630 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17631 pub fn clear_row_highlights<T: 'static>(&mut self) {
17632 self.highlighted_rows.remove(&TypeId::of::<T>());
17633 }
17634
17635 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17636 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17637 self.highlighted_rows
17638 .get(&TypeId::of::<T>())
17639 .map_or(&[] as &[_], |vec| vec.as_slice())
17640 .iter()
17641 .map(|highlight| (highlight.range.clone(), highlight.color))
17642 }
17643
17644 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17645 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17646 /// Allows to ignore certain kinds of highlights.
17647 pub fn highlighted_display_rows(
17648 &self,
17649 window: &mut Window,
17650 cx: &mut App,
17651 ) -> BTreeMap<DisplayRow, LineHighlight> {
17652 let snapshot = self.snapshot(window, cx);
17653 let mut used_highlight_orders = HashMap::default();
17654 self.highlighted_rows
17655 .iter()
17656 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17657 .fold(
17658 BTreeMap::<DisplayRow, LineHighlight>::new(),
17659 |mut unique_rows, highlight| {
17660 let start = highlight.range.start.to_display_point(&snapshot);
17661 let end = highlight.range.end.to_display_point(&snapshot);
17662 let start_row = start.row().0;
17663 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17664 && end.column() == 0
17665 {
17666 end.row().0.saturating_sub(1)
17667 } else {
17668 end.row().0
17669 };
17670 for row in start_row..=end_row {
17671 let used_index =
17672 used_highlight_orders.entry(row).or_insert(highlight.index);
17673 if highlight.index >= *used_index {
17674 *used_index = highlight.index;
17675 unique_rows.insert(
17676 DisplayRow(row),
17677 LineHighlight {
17678 include_gutter: highlight.options.include_gutter,
17679 border: None,
17680 background: highlight.color.into(),
17681 type_id: Some(highlight.type_id),
17682 },
17683 );
17684 }
17685 }
17686 unique_rows
17687 },
17688 )
17689 }
17690
17691 pub fn highlighted_display_row_for_autoscroll(
17692 &self,
17693 snapshot: &DisplaySnapshot,
17694 ) -> Option<DisplayRow> {
17695 self.highlighted_rows
17696 .values()
17697 .flat_map(|highlighted_rows| highlighted_rows.iter())
17698 .filter_map(|highlight| {
17699 if highlight.options.autoscroll {
17700 Some(highlight.range.start.to_display_point(snapshot).row())
17701 } else {
17702 None
17703 }
17704 })
17705 .min()
17706 }
17707
17708 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17709 self.highlight_background::<SearchWithinRange>(
17710 ranges,
17711 |colors| colors.editor_document_highlight_read_background,
17712 cx,
17713 )
17714 }
17715
17716 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17717 self.breadcrumb_header = Some(new_header);
17718 }
17719
17720 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17721 self.clear_background_highlights::<SearchWithinRange>(cx);
17722 }
17723
17724 pub fn highlight_background<T: 'static>(
17725 &mut self,
17726 ranges: &[Range<Anchor>],
17727 color_fetcher: fn(&ThemeColors) -> Hsla,
17728 cx: &mut Context<Self>,
17729 ) {
17730 self.background_highlights
17731 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17732 self.scrollbar_marker_state.dirty = true;
17733 cx.notify();
17734 }
17735
17736 pub fn clear_background_highlights<T: 'static>(
17737 &mut self,
17738 cx: &mut Context<Self>,
17739 ) -> Option<BackgroundHighlight> {
17740 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17741 if !text_highlights.1.is_empty() {
17742 self.scrollbar_marker_state.dirty = true;
17743 cx.notify();
17744 }
17745 Some(text_highlights)
17746 }
17747
17748 pub fn highlight_gutter<T: 'static>(
17749 &mut self,
17750 ranges: &[Range<Anchor>],
17751 color_fetcher: fn(&App) -> Hsla,
17752 cx: &mut Context<Self>,
17753 ) {
17754 self.gutter_highlights
17755 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17756 cx.notify();
17757 }
17758
17759 pub fn clear_gutter_highlights<T: 'static>(
17760 &mut self,
17761 cx: &mut Context<Self>,
17762 ) -> Option<GutterHighlight> {
17763 cx.notify();
17764 self.gutter_highlights.remove(&TypeId::of::<T>())
17765 }
17766
17767 #[cfg(feature = "test-support")]
17768 pub fn all_text_background_highlights(
17769 &self,
17770 window: &mut Window,
17771 cx: &mut Context<Self>,
17772 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17773 let snapshot = self.snapshot(window, cx);
17774 let buffer = &snapshot.buffer_snapshot;
17775 let start = buffer.anchor_before(0);
17776 let end = buffer.anchor_after(buffer.len());
17777 let theme = cx.theme().colors();
17778 self.background_highlights_in_range(start..end, &snapshot, theme)
17779 }
17780
17781 #[cfg(feature = "test-support")]
17782 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17783 let snapshot = self.buffer().read(cx).snapshot(cx);
17784
17785 let highlights = self
17786 .background_highlights
17787 .get(&TypeId::of::<items::BufferSearchHighlights>());
17788
17789 if let Some((_color, ranges)) = highlights {
17790 ranges
17791 .iter()
17792 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17793 .collect_vec()
17794 } else {
17795 vec![]
17796 }
17797 }
17798
17799 fn document_highlights_for_position<'a>(
17800 &'a self,
17801 position: Anchor,
17802 buffer: &'a MultiBufferSnapshot,
17803 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17804 let read_highlights = self
17805 .background_highlights
17806 .get(&TypeId::of::<DocumentHighlightRead>())
17807 .map(|h| &h.1);
17808 let write_highlights = self
17809 .background_highlights
17810 .get(&TypeId::of::<DocumentHighlightWrite>())
17811 .map(|h| &h.1);
17812 let left_position = position.bias_left(buffer);
17813 let right_position = position.bias_right(buffer);
17814 read_highlights
17815 .into_iter()
17816 .chain(write_highlights)
17817 .flat_map(move |ranges| {
17818 let start_ix = match ranges.binary_search_by(|probe| {
17819 let cmp = probe.end.cmp(&left_position, buffer);
17820 if cmp.is_ge() {
17821 Ordering::Greater
17822 } else {
17823 Ordering::Less
17824 }
17825 }) {
17826 Ok(i) | Err(i) => i,
17827 };
17828
17829 ranges[start_ix..]
17830 .iter()
17831 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17832 })
17833 }
17834
17835 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17836 self.background_highlights
17837 .get(&TypeId::of::<T>())
17838 .map_or(false, |(_, highlights)| !highlights.is_empty())
17839 }
17840
17841 pub fn background_highlights_in_range(
17842 &self,
17843 search_range: Range<Anchor>,
17844 display_snapshot: &DisplaySnapshot,
17845 theme: &ThemeColors,
17846 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17847 let mut results = Vec::new();
17848 for (color_fetcher, ranges) in self.background_highlights.values() {
17849 let color = color_fetcher(theme);
17850 let start_ix = match ranges.binary_search_by(|probe| {
17851 let cmp = probe
17852 .end
17853 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17854 if cmp.is_gt() {
17855 Ordering::Greater
17856 } else {
17857 Ordering::Less
17858 }
17859 }) {
17860 Ok(i) | Err(i) => i,
17861 };
17862 for range in &ranges[start_ix..] {
17863 if range
17864 .start
17865 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17866 .is_ge()
17867 {
17868 break;
17869 }
17870
17871 let start = range.start.to_display_point(display_snapshot);
17872 let end = range.end.to_display_point(display_snapshot);
17873 results.push((start..end, color))
17874 }
17875 }
17876 results
17877 }
17878
17879 pub fn background_highlight_row_ranges<T: 'static>(
17880 &self,
17881 search_range: Range<Anchor>,
17882 display_snapshot: &DisplaySnapshot,
17883 count: usize,
17884 ) -> Vec<RangeInclusive<DisplayPoint>> {
17885 let mut results = Vec::new();
17886 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
17887 return vec![];
17888 };
17889
17890 let start_ix = match ranges.binary_search_by(|probe| {
17891 let cmp = probe
17892 .end
17893 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17894 if cmp.is_gt() {
17895 Ordering::Greater
17896 } else {
17897 Ordering::Less
17898 }
17899 }) {
17900 Ok(i) | Err(i) => i,
17901 };
17902 let mut push_region = |start: Option<Point>, end: Option<Point>| {
17903 if let (Some(start_display), Some(end_display)) = (start, end) {
17904 results.push(
17905 start_display.to_display_point(display_snapshot)
17906 ..=end_display.to_display_point(display_snapshot),
17907 );
17908 }
17909 };
17910 let mut start_row: Option<Point> = None;
17911 let mut end_row: Option<Point> = None;
17912 if ranges.len() > count {
17913 return Vec::new();
17914 }
17915 for range in &ranges[start_ix..] {
17916 if range
17917 .start
17918 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17919 .is_ge()
17920 {
17921 break;
17922 }
17923 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
17924 if let Some(current_row) = &end_row {
17925 if end.row == current_row.row {
17926 continue;
17927 }
17928 }
17929 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
17930 if start_row.is_none() {
17931 assert_eq!(end_row, None);
17932 start_row = Some(start);
17933 end_row = Some(end);
17934 continue;
17935 }
17936 if let Some(current_end) = end_row.as_mut() {
17937 if start.row > current_end.row + 1 {
17938 push_region(start_row, end_row);
17939 start_row = Some(start);
17940 end_row = Some(end);
17941 } else {
17942 // Merge two hunks.
17943 *current_end = end;
17944 }
17945 } else {
17946 unreachable!();
17947 }
17948 }
17949 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
17950 push_region(start_row, end_row);
17951 results
17952 }
17953
17954 pub fn gutter_highlights_in_range(
17955 &self,
17956 search_range: Range<Anchor>,
17957 display_snapshot: &DisplaySnapshot,
17958 cx: &App,
17959 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17960 let mut results = Vec::new();
17961 for (color_fetcher, ranges) in self.gutter_highlights.values() {
17962 let color = color_fetcher(cx);
17963 let start_ix = match ranges.binary_search_by(|probe| {
17964 let cmp = probe
17965 .end
17966 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17967 if cmp.is_gt() {
17968 Ordering::Greater
17969 } else {
17970 Ordering::Less
17971 }
17972 }) {
17973 Ok(i) | Err(i) => i,
17974 };
17975 for range in &ranges[start_ix..] {
17976 if range
17977 .start
17978 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17979 .is_ge()
17980 {
17981 break;
17982 }
17983
17984 let start = range.start.to_display_point(display_snapshot);
17985 let end = range.end.to_display_point(display_snapshot);
17986 results.push((start..end, color))
17987 }
17988 }
17989 results
17990 }
17991
17992 /// Get the text ranges corresponding to the redaction query
17993 pub fn redacted_ranges(
17994 &self,
17995 search_range: Range<Anchor>,
17996 display_snapshot: &DisplaySnapshot,
17997 cx: &App,
17998 ) -> Vec<Range<DisplayPoint>> {
17999 display_snapshot
18000 .buffer_snapshot
18001 .redacted_ranges(search_range, |file| {
18002 if let Some(file) = file {
18003 file.is_private()
18004 && EditorSettings::get(
18005 Some(SettingsLocation {
18006 worktree_id: file.worktree_id(cx),
18007 path: file.path().as_ref(),
18008 }),
18009 cx,
18010 )
18011 .redact_private_values
18012 } else {
18013 false
18014 }
18015 })
18016 .map(|range| {
18017 range.start.to_display_point(display_snapshot)
18018 ..range.end.to_display_point(display_snapshot)
18019 })
18020 .collect()
18021 }
18022
18023 pub fn highlight_text<T: 'static>(
18024 &mut self,
18025 ranges: Vec<Range<Anchor>>,
18026 style: HighlightStyle,
18027 cx: &mut Context<Self>,
18028 ) {
18029 self.display_map.update(cx, |map, _| {
18030 map.highlight_text(TypeId::of::<T>(), ranges, style)
18031 });
18032 cx.notify();
18033 }
18034
18035 pub(crate) fn highlight_inlays<T: 'static>(
18036 &mut self,
18037 highlights: Vec<InlayHighlight>,
18038 style: HighlightStyle,
18039 cx: &mut Context<Self>,
18040 ) {
18041 self.display_map.update(cx, |map, _| {
18042 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18043 });
18044 cx.notify();
18045 }
18046
18047 pub fn text_highlights<'a, T: 'static>(
18048 &'a self,
18049 cx: &'a App,
18050 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18051 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18052 }
18053
18054 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18055 let cleared = self
18056 .display_map
18057 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18058 if cleared {
18059 cx.notify();
18060 }
18061 }
18062
18063 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18064 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18065 && self.focus_handle.is_focused(window)
18066 }
18067
18068 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18069 self.show_cursor_when_unfocused = is_enabled;
18070 cx.notify();
18071 }
18072
18073 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18074 cx.notify();
18075 }
18076
18077 fn on_debug_session_event(
18078 &mut self,
18079 _session: Entity<Session>,
18080 event: &SessionEvent,
18081 cx: &mut Context<Self>,
18082 ) {
18083 match event {
18084 SessionEvent::InvalidateInlineValue => {
18085 self.refresh_inline_values(cx);
18086 }
18087 _ => {}
18088 }
18089 }
18090
18091 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18092 let Some(project) = self.project.clone() else {
18093 return;
18094 };
18095
18096 if !self.inline_value_cache.enabled {
18097 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18098 self.splice_inlays(&inlays, Vec::new(), cx);
18099 return;
18100 }
18101
18102 let current_execution_position = self
18103 .highlighted_rows
18104 .get(&TypeId::of::<ActiveDebugLine>())
18105 .and_then(|lines| lines.last().map(|line| line.range.start));
18106
18107 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18108 let inline_values = editor
18109 .update(cx, |editor, cx| {
18110 let Some(current_execution_position) = current_execution_position else {
18111 return Some(Task::ready(Ok(Vec::new())));
18112 };
18113
18114 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18115 let snapshot = buffer.snapshot(cx);
18116
18117 let excerpt = snapshot.excerpt_containing(
18118 current_execution_position..current_execution_position,
18119 )?;
18120
18121 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18122 })?;
18123
18124 let range =
18125 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18126
18127 project.inline_values(buffer, range, cx)
18128 })
18129 .ok()
18130 .flatten()?
18131 .await
18132 .context("refreshing debugger inlays")
18133 .log_err()?;
18134
18135 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18136
18137 for (buffer_id, inline_value) in inline_values
18138 .into_iter()
18139 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18140 {
18141 buffer_inline_values
18142 .entry(buffer_id)
18143 .or_default()
18144 .push(inline_value);
18145 }
18146
18147 editor
18148 .update(cx, |editor, cx| {
18149 let snapshot = editor.buffer.read(cx).snapshot(cx);
18150 let mut new_inlays = Vec::default();
18151
18152 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18153 let buffer_id = buffer_snapshot.remote_id();
18154 buffer_inline_values
18155 .get(&buffer_id)
18156 .into_iter()
18157 .flatten()
18158 .for_each(|hint| {
18159 let inlay = Inlay::debugger_hint(
18160 post_inc(&mut editor.next_inlay_id),
18161 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18162 hint.text(),
18163 );
18164
18165 new_inlays.push(inlay);
18166 });
18167 }
18168
18169 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18170 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18171
18172 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18173 })
18174 .ok()?;
18175 Some(())
18176 });
18177 }
18178
18179 fn on_buffer_event(
18180 &mut self,
18181 multibuffer: &Entity<MultiBuffer>,
18182 event: &multi_buffer::Event,
18183 window: &mut Window,
18184 cx: &mut Context<Self>,
18185 ) {
18186 match event {
18187 multi_buffer::Event::Edited {
18188 singleton_buffer_edited,
18189 edited_buffer: buffer_edited,
18190 } => {
18191 self.scrollbar_marker_state.dirty = true;
18192 self.active_indent_guides_state.dirty = true;
18193 self.refresh_active_diagnostics(cx);
18194 self.refresh_code_actions(window, cx);
18195 self.refresh_selected_text_highlights(true, window, cx);
18196 refresh_matching_bracket_highlights(self, window, cx);
18197 if self.has_active_inline_completion() {
18198 self.update_visible_inline_completion(window, cx);
18199 }
18200 if let Some(buffer) = buffer_edited {
18201 let buffer_id = buffer.read(cx).remote_id();
18202 if !self.registered_buffers.contains_key(&buffer_id) {
18203 if let Some(project) = self.project.as_ref() {
18204 project.update(cx, |project, cx| {
18205 self.registered_buffers.insert(
18206 buffer_id,
18207 project.register_buffer_with_language_servers(&buffer, cx),
18208 );
18209 })
18210 }
18211 }
18212 }
18213 cx.emit(EditorEvent::BufferEdited);
18214 cx.emit(SearchEvent::MatchesInvalidated);
18215 if *singleton_buffer_edited {
18216 if let Some(project) = &self.project {
18217 #[allow(clippy::mutable_key_type)]
18218 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18219 multibuffer
18220 .all_buffers()
18221 .into_iter()
18222 .filter_map(|buffer| {
18223 buffer.update(cx, |buffer, cx| {
18224 let language = buffer.language()?;
18225 let should_discard = project.update(cx, |project, cx| {
18226 project.is_local()
18227 && !project.has_language_servers_for(buffer, cx)
18228 });
18229 should_discard.not().then_some(language.clone())
18230 })
18231 })
18232 .collect::<HashSet<_>>()
18233 });
18234 if !languages_affected.is_empty() {
18235 self.refresh_inlay_hints(
18236 InlayHintRefreshReason::BufferEdited(languages_affected),
18237 cx,
18238 );
18239 }
18240 }
18241 }
18242
18243 let Some(project) = &self.project else { return };
18244 let (telemetry, is_via_ssh) = {
18245 let project = project.read(cx);
18246 let telemetry = project.client().telemetry().clone();
18247 let is_via_ssh = project.is_via_ssh();
18248 (telemetry, is_via_ssh)
18249 };
18250 refresh_linked_ranges(self, window, cx);
18251 telemetry.log_edit_event("editor", is_via_ssh);
18252 }
18253 multi_buffer::Event::ExcerptsAdded {
18254 buffer,
18255 predecessor,
18256 excerpts,
18257 } => {
18258 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18259 let buffer_id = buffer.read(cx).remote_id();
18260 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18261 if let Some(project) = &self.project {
18262 update_uncommitted_diff_for_buffer(
18263 cx.entity(),
18264 project,
18265 [buffer.clone()],
18266 self.buffer.clone(),
18267 cx,
18268 )
18269 .detach();
18270 }
18271 }
18272 cx.emit(EditorEvent::ExcerptsAdded {
18273 buffer: buffer.clone(),
18274 predecessor: *predecessor,
18275 excerpts: excerpts.clone(),
18276 });
18277 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18278 }
18279 multi_buffer::Event::ExcerptsRemoved {
18280 ids,
18281 removed_buffer_ids,
18282 } => {
18283 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18284 let buffer = self.buffer.read(cx);
18285 self.registered_buffers
18286 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18287 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18288 cx.emit(EditorEvent::ExcerptsRemoved {
18289 ids: ids.clone(),
18290 removed_buffer_ids: removed_buffer_ids.clone(),
18291 })
18292 }
18293 multi_buffer::Event::ExcerptsEdited {
18294 excerpt_ids,
18295 buffer_ids,
18296 } => {
18297 self.display_map.update(cx, |map, cx| {
18298 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18299 });
18300 cx.emit(EditorEvent::ExcerptsEdited {
18301 ids: excerpt_ids.clone(),
18302 })
18303 }
18304 multi_buffer::Event::ExcerptsExpanded { ids } => {
18305 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18306 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18307 }
18308 multi_buffer::Event::Reparsed(buffer_id) => {
18309 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18310 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18311
18312 cx.emit(EditorEvent::Reparsed(*buffer_id));
18313 }
18314 multi_buffer::Event::DiffHunksToggled => {
18315 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18316 }
18317 multi_buffer::Event::LanguageChanged(buffer_id) => {
18318 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18319 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18320 cx.emit(EditorEvent::Reparsed(*buffer_id));
18321 cx.notify();
18322 }
18323 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18324 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18325 multi_buffer::Event::FileHandleChanged
18326 | multi_buffer::Event::Reloaded
18327 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18328 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18329 multi_buffer::Event::DiagnosticsUpdated => {
18330 self.refresh_active_diagnostics(cx);
18331 self.refresh_inline_diagnostics(true, window, cx);
18332 self.scrollbar_marker_state.dirty = true;
18333 cx.notify();
18334 }
18335 _ => {}
18336 };
18337 }
18338
18339 pub fn start_temporary_diff_override(&mut self) {
18340 self.load_diff_task.take();
18341 self.temporary_diff_override = true;
18342 }
18343
18344 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18345 self.temporary_diff_override = false;
18346 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18347 self.buffer.update(cx, |buffer, cx| {
18348 buffer.set_all_diff_hunks_collapsed(cx);
18349 });
18350
18351 if let Some(project) = self.project.clone() {
18352 self.load_diff_task = Some(
18353 update_uncommitted_diff_for_buffer(
18354 cx.entity(),
18355 &project,
18356 self.buffer.read(cx).all_buffers(),
18357 self.buffer.clone(),
18358 cx,
18359 )
18360 .shared(),
18361 );
18362 }
18363 }
18364
18365 fn on_display_map_changed(
18366 &mut self,
18367 _: Entity<DisplayMap>,
18368 _: &mut Window,
18369 cx: &mut Context<Self>,
18370 ) {
18371 cx.notify();
18372 }
18373
18374 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18375 let new_severity = if self.diagnostics_enabled() {
18376 EditorSettings::get_global(cx)
18377 .diagnostics_max_severity
18378 .unwrap_or(DiagnosticSeverity::Hint)
18379 } else {
18380 DiagnosticSeverity::Off
18381 };
18382 self.set_max_diagnostics_severity(new_severity, cx);
18383 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18384 self.update_edit_prediction_settings(cx);
18385 self.refresh_inline_completion(true, false, window, cx);
18386 self.refresh_inlay_hints(
18387 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18388 self.selections.newest_anchor().head(),
18389 &self.buffer.read(cx).snapshot(cx),
18390 cx,
18391 )),
18392 cx,
18393 );
18394
18395 let old_cursor_shape = self.cursor_shape;
18396
18397 {
18398 let editor_settings = EditorSettings::get_global(cx);
18399 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18400 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18401 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18402 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18403 }
18404
18405 if old_cursor_shape != self.cursor_shape {
18406 cx.emit(EditorEvent::CursorShapeChanged);
18407 }
18408
18409 let project_settings = ProjectSettings::get_global(cx);
18410 self.serialize_dirty_buffers =
18411 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18412
18413 if self.mode.is_full() {
18414 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18415 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18416 if self.show_inline_diagnostics != show_inline_diagnostics {
18417 self.show_inline_diagnostics = show_inline_diagnostics;
18418 self.refresh_inline_diagnostics(false, window, cx);
18419 }
18420
18421 if self.git_blame_inline_enabled != inline_blame_enabled {
18422 self.toggle_git_blame_inline_internal(false, window, cx);
18423 }
18424
18425 let minimap_settings = EditorSettings::get_global(cx).minimap;
18426 if self.minimap_visibility.visible() != minimap_settings.minimap_enabled() {
18427 self.set_minimap_visibility(
18428 self.minimap_visibility.toggle_visibility(),
18429 window,
18430 cx,
18431 );
18432 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18433 minimap_entity.update(cx, |minimap_editor, cx| {
18434 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18435 })
18436 }
18437 }
18438
18439 cx.notify();
18440 }
18441
18442 pub fn set_searchable(&mut self, searchable: bool) {
18443 self.searchable = searchable;
18444 }
18445
18446 pub fn searchable(&self) -> bool {
18447 self.searchable
18448 }
18449
18450 fn open_proposed_changes_editor(
18451 &mut self,
18452 _: &OpenProposedChangesEditor,
18453 window: &mut Window,
18454 cx: &mut Context<Self>,
18455 ) {
18456 let Some(workspace) = self.workspace() else {
18457 cx.propagate();
18458 return;
18459 };
18460
18461 let selections = self.selections.all::<usize>(cx);
18462 let multi_buffer = self.buffer.read(cx);
18463 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18464 let mut new_selections_by_buffer = HashMap::default();
18465 for selection in selections {
18466 for (buffer, range, _) in
18467 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18468 {
18469 let mut range = range.to_point(buffer);
18470 range.start.column = 0;
18471 range.end.column = buffer.line_len(range.end.row);
18472 new_selections_by_buffer
18473 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18474 .or_insert(Vec::new())
18475 .push(range)
18476 }
18477 }
18478
18479 let proposed_changes_buffers = new_selections_by_buffer
18480 .into_iter()
18481 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18482 .collect::<Vec<_>>();
18483 let proposed_changes_editor = cx.new(|cx| {
18484 ProposedChangesEditor::new(
18485 "Proposed changes",
18486 proposed_changes_buffers,
18487 self.project.clone(),
18488 window,
18489 cx,
18490 )
18491 });
18492
18493 window.defer(cx, move |window, cx| {
18494 workspace.update(cx, |workspace, cx| {
18495 workspace.active_pane().update(cx, |pane, cx| {
18496 pane.add_item(
18497 Box::new(proposed_changes_editor),
18498 true,
18499 true,
18500 None,
18501 window,
18502 cx,
18503 );
18504 });
18505 });
18506 });
18507 }
18508
18509 pub fn open_excerpts_in_split(
18510 &mut self,
18511 _: &OpenExcerptsSplit,
18512 window: &mut Window,
18513 cx: &mut Context<Self>,
18514 ) {
18515 self.open_excerpts_common(None, true, window, cx)
18516 }
18517
18518 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18519 self.open_excerpts_common(None, false, window, cx)
18520 }
18521
18522 fn open_excerpts_common(
18523 &mut self,
18524 jump_data: Option<JumpData>,
18525 split: bool,
18526 window: &mut Window,
18527 cx: &mut Context<Self>,
18528 ) {
18529 let Some(workspace) = self.workspace() else {
18530 cx.propagate();
18531 return;
18532 };
18533
18534 if self.buffer.read(cx).is_singleton() {
18535 cx.propagate();
18536 return;
18537 }
18538
18539 let mut new_selections_by_buffer = HashMap::default();
18540 match &jump_data {
18541 Some(JumpData::MultiBufferPoint {
18542 excerpt_id,
18543 position,
18544 anchor,
18545 line_offset_from_top,
18546 }) => {
18547 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18548 if let Some(buffer) = multi_buffer_snapshot
18549 .buffer_id_for_excerpt(*excerpt_id)
18550 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18551 {
18552 let buffer_snapshot = buffer.read(cx).snapshot();
18553 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18554 language::ToPoint::to_point(anchor, &buffer_snapshot)
18555 } else {
18556 buffer_snapshot.clip_point(*position, Bias::Left)
18557 };
18558 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18559 new_selections_by_buffer.insert(
18560 buffer,
18561 (
18562 vec![jump_to_offset..jump_to_offset],
18563 Some(*line_offset_from_top),
18564 ),
18565 );
18566 }
18567 }
18568 Some(JumpData::MultiBufferRow {
18569 row,
18570 line_offset_from_top,
18571 }) => {
18572 let point = MultiBufferPoint::new(row.0, 0);
18573 if let Some((buffer, buffer_point, _)) =
18574 self.buffer.read(cx).point_to_buffer_point(point, cx)
18575 {
18576 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18577 new_selections_by_buffer
18578 .entry(buffer)
18579 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18580 .0
18581 .push(buffer_offset..buffer_offset)
18582 }
18583 }
18584 None => {
18585 let selections = self.selections.all::<usize>(cx);
18586 let multi_buffer = self.buffer.read(cx);
18587 for selection in selections {
18588 for (snapshot, range, _, anchor) in multi_buffer
18589 .snapshot(cx)
18590 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18591 {
18592 if let Some(anchor) = anchor {
18593 // selection is in a deleted hunk
18594 let Some(buffer_id) = anchor.buffer_id else {
18595 continue;
18596 };
18597 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18598 continue;
18599 };
18600 let offset = text::ToOffset::to_offset(
18601 &anchor.text_anchor,
18602 &buffer_handle.read(cx).snapshot(),
18603 );
18604 let range = offset..offset;
18605 new_selections_by_buffer
18606 .entry(buffer_handle)
18607 .or_insert((Vec::new(), None))
18608 .0
18609 .push(range)
18610 } else {
18611 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18612 else {
18613 continue;
18614 };
18615 new_selections_by_buffer
18616 .entry(buffer_handle)
18617 .or_insert((Vec::new(), None))
18618 .0
18619 .push(range)
18620 }
18621 }
18622 }
18623 }
18624 }
18625
18626 new_selections_by_buffer
18627 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18628
18629 if new_selections_by_buffer.is_empty() {
18630 return;
18631 }
18632
18633 // We defer the pane interaction because we ourselves are a workspace item
18634 // and activating a new item causes the pane to call a method on us reentrantly,
18635 // which panics if we're on the stack.
18636 window.defer(cx, move |window, cx| {
18637 workspace.update(cx, |workspace, cx| {
18638 let pane = if split {
18639 workspace.adjacent_pane(window, cx)
18640 } else {
18641 workspace.active_pane().clone()
18642 };
18643
18644 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18645 let editor = buffer
18646 .read(cx)
18647 .file()
18648 .is_none()
18649 .then(|| {
18650 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18651 // so `workspace.open_project_item` will never find them, always opening a new editor.
18652 // Instead, we try to activate the existing editor in the pane first.
18653 let (editor, pane_item_index) =
18654 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18655 let editor = item.downcast::<Editor>()?;
18656 let singleton_buffer =
18657 editor.read(cx).buffer().read(cx).as_singleton()?;
18658 if singleton_buffer == buffer {
18659 Some((editor, i))
18660 } else {
18661 None
18662 }
18663 })?;
18664 pane.update(cx, |pane, cx| {
18665 pane.activate_item(pane_item_index, true, true, window, cx)
18666 });
18667 Some(editor)
18668 })
18669 .flatten()
18670 .unwrap_or_else(|| {
18671 workspace.open_project_item::<Self>(
18672 pane.clone(),
18673 buffer,
18674 true,
18675 true,
18676 window,
18677 cx,
18678 )
18679 });
18680
18681 editor.update(cx, |editor, cx| {
18682 let autoscroll = match scroll_offset {
18683 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18684 None => Autoscroll::newest(),
18685 };
18686 let nav_history = editor.nav_history.take();
18687 editor.change_selections(Some(autoscroll), window, cx, |s| {
18688 s.select_ranges(ranges);
18689 });
18690 editor.nav_history = nav_history;
18691 });
18692 }
18693 })
18694 });
18695 }
18696
18697 // For now, don't allow opening excerpts in buffers that aren't backed by
18698 // regular project files.
18699 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18700 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18701 }
18702
18703 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18704 let snapshot = self.buffer.read(cx).read(cx);
18705 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18706 Some(
18707 ranges
18708 .iter()
18709 .map(move |range| {
18710 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18711 })
18712 .collect(),
18713 )
18714 }
18715
18716 fn selection_replacement_ranges(
18717 &self,
18718 range: Range<OffsetUtf16>,
18719 cx: &mut App,
18720 ) -> Vec<Range<OffsetUtf16>> {
18721 let selections = self.selections.all::<OffsetUtf16>(cx);
18722 let newest_selection = selections
18723 .iter()
18724 .max_by_key(|selection| selection.id)
18725 .unwrap();
18726 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
18727 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
18728 let snapshot = self.buffer.read(cx).read(cx);
18729 selections
18730 .into_iter()
18731 .map(|mut selection| {
18732 selection.start.0 =
18733 (selection.start.0 as isize).saturating_add(start_delta) as usize;
18734 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
18735 snapshot.clip_offset_utf16(selection.start, Bias::Left)
18736 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
18737 })
18738 .collect()
18739 }
18740
18741 fn report_editor_event(
18742 &self,
18743 event_type: &'static str,
18744 file_extension: Option<String>,
18745 cx: &App,
18746 ) {
18747 if cfg!(any(test, feature = "test-support")) {
18748 return;
18749 }
18750
18751 let Some(project) = &self.project else { return };
18752
18753 // If None, we are in a file without an extension
18754 let file = self
18755 .buffer
18756 .read(cx)
18757 .as_singleton()
18758 .and_then(|b| b.read(cx).file());
18759 let file_extension = file_extension.or(file
18760 .as_ref()
18761 .and_then(|file| Path::new(file.file_name(cx)).extension())
18762 .and_then(|e| e.to_str())
18763 .map(|a| a.to_string()));
18764
18765 let vim_mode = vim_enabled(cx);
18766
18767 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
18768 let copilot_enabled = edit_predictions_provider
18769 == language::language_settings::EditPredictionProvider::Copilot;
18770 let copilot_enabled_for_language = self
18771 .buffer
18772 .read(cx)
18773 .language_settings(cx)
18774 .show_edit_predictions;
18775
18776 let project = project.read(cx);
18777 telemetry::event!(
18778 event_type,
18779 file_extension,
18780 vim_mode,
18781 copilot_enabled,
18782 copilot_enabled_for_language,
18783 edit_predictions_provider,
18784 is_via_ssh = project.is_via_ssh(),
18785 );
18786 }
18787
18788 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
18789 /// with each line being an array of {text, highlight} objects.
18790 fn copy_highlight_json(
18791 &mut self,
18792 _: &CopyHighlightJson,
18793 window: &mut Window,
18794 cx: &mut Context<Self>,
18795 ) {
18796 #[derive(Serialize)]
18797 struct Chunk<'a> {
18798 text: String,
18799 highlight: Option<&'a str>,
18800 }
18801
18802 let snapshot = self.buffer.read(cx).snapshot(cx);
18803 let range = self
18804 .selected_text_range(false, window, cx)
18805 .and_then(|selection| {
18806 if selection.range.is_empty() {
18807 None
18808 } else {
18809 Some(selection.range)
18810 }
18811 })
18812 .unwrap_or_else(|| 0..snapshot.len());
18813
18814 let chunks = snapshot.chunks(range, true);
18815 let mut lines = Vec::new();
18816 let mut line: VecDeque<Chunk> = VecDeque::new();
18817
18818 let Some(style) = self.style.as_ref() else {
18819 return;
18820 };
18821
18822 for chunk in chunks {
18823 let highlight = chunk
18824 .syntax_highlight_id
18825 .and_then(|id| id.name(&style.syntax));
18826 let mut chunk_lines = chunk.text.split('\n').peekable();
18827 while let Some(text) = chunk_lines.next() {
18828 let mut merged_with_last_token = false;
18829 if let Some(last_token) = line.back_mut() {
18830 if last_token.highlight == highlight {
18831 last_token.text.push_str(text);
18832 merged_with_last_token = true;
18833 }
18834 }
18835
18836 if !merged_with_last_token {
18837 line.push_back(Chunk {
18838 text: text.into(),
18839 highlight,
18840 });
18841 }
18842
18843 if chunk_lines.peek().is_some() {
18844 if line.len() > 1 && line.front().unwrap().text.is_empty() {
18845 line.pop_front();
18846 }
18847 if line.len() > 1 && line.back().unwrap().text.is_empty() {
18848 line.pop_back();
18849 }
18850
18851 lines.push(mem::take(&mut line));
18852 }
18853 }
18854 }
18855
18856 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
18857 return;
18858 };
18859 cx.write_to_clipboard(ClipboardItem::new_string(lines));
18860 }
18861
18862 pub fn open_context_menu(
18863 &mut self,
18864 _: &OpenContextMenu,
18865 window: &mut Window,
18866 cx: &mut Context<Self>,
18867 ) {
18868 self.request_autoscroll(Autoscroll::newest(), cx);
18869 let position = self.selections.newest_display(cx).start;
18870 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
18871 }
18872
18873 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
18874 &self.inlay_hint_cache
18875 }
18876
18877 pub fn replay_insert_event(
18878 &mut self,
18879 text: &str,
18880 relative_utf16_range: Option<Range<isize>>,
18881 window: &mut Window,
18882 cx: &mut Context<Self>,
18883 ) {
18884 if !self.input_enabled {
18885 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18886 return;
18887 }
18888 if let Some(relative_utf16_range) = relative_utf16_range {
18889 let selections = self.selections.all::<OffsetUtf16>(cx);
18890 self.change_selections(None, window, cx, |s| {
18891 let new_ranges = selections.into_iter().map(|range| {
18892 let start = OffsetUtf16(
18893 range
18894 .head()
18895 .0
18896 .saturating_add_signed(relative_utf16_range.start),
18897 );
18898 let end = OffsetUtf16(
18899 range
18900 .head()
18901 .0
18902 .saturating_add_signed(relative_utf16_range.end),
18903 );
18904 start..end
18905 });
18906 s.select_ranges(new_ranges);
18907 });
18908 }
18909
18910 self.handle_input(text, window, cx);
18911 }
18912
18913 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
18914 let Some(provider) = self.semantics_provider.as_ref() else {
18915 return false;
18916 };
18917
18918 let mut supports = false;
18919 self.buffer().update(cx, |this, cx| {
18920 this.for_each_buffer(|buffer| {
18921 supports |= provider.supports_inlay_hints(buffer, cx);
18922 });
18923 });
18924
18925 supports
18926 }
18927
18928 pub fn is_focused(&self, window: &Window) -> bool {
18929 self.focus_handle.is_focused(window)
18930 }
18931
18932 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18933 cx.emit(EditorEvent::Focused);
18934
18935 if let Some(descendant) = self
18936 .last_focused_descendant
18937 .take()
18938 .and_then(|descendant| descendant.upgrade())
18939 {
18940 window.focus(&descendant);
18941 } else {
18942 if let Some(blame) = self.blame.as_ref() {
18943 blame.update(cx, GitBlame::focus)
18944 }
18945
18946 self.blink_manager.update(cx, BlinkManager::enable);
18947 self.show_cursor_names(window, cx);
18948 self.buffer.update(cx, |buffer, cx| {
18949 buffer.finalize_last_transaction(cx);
18950 if self.leader_id.is_none() {
18951 buffer.set_active_selections(
18952 &self.selections.disjoint_anchors(),
18953 self.selections.line_mode,
18954 self.cursor_shape,
18955 cx,
18956 );
18957 }
18958 });
18959 }
18960 }
18961
18962 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18963 cx.emit(EditorEvent::FocusedIn)
18964 }
18965
18966 fn handle_focus_out(
18967 &mut self,
18968 event: FocusOutEvent,
18969 _window: &mut Window,
18970 cx: &mut Context<Self>,
18971 ) {
18972 if event.blurred != self.focus_handle {
18973 self.last_focused_descendant = Some(event.blurred);
18974 }
18975 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
18976 }
18977
18978 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18979 self.blink_manager.update(cx, BlinkManager::disable);
18980 self.buffer
18981 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
18982
18983 if let Some(blame) = self.blame.as_ref() {
18984 blame.update(cx, GitBlame::blur)
18985 }
18986 if !self.hover_state.focused(window, cx) {
18987 hide_hover(self, cx);
18988 }
18989 if !self
18990 .context_menu
18991 .borrow()
18992 .as_ref()
18993 .is_some_and(|context_menu| context_menu.focused(window, cx))
18994 {
18995 self.hide_context_menu(window, cx);
18996 }
18997 self.discard_inline_completion(false, cx);
18998 cx.emit(EditorEvent::Blurred);
18999 cx.notify();
19000 }
19001
19002 pub fn register_action<A: Action>(
19003 &mut self,
19004 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19005 ) -> Subscription {
19006 let id = self.next_editor_action_id.post_inc();
19007 let listener = Arc::new(listener);
19008 self.editor_actions.borrow_mut().insert(
19009 id,
19010 Box::new(move |window, _| {
19011 let listener = listener.clone();
19012 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19013 let action = action.downcast_ref().unwrap();
19014 if phase == DispatchPhase::Bubble {
19015 listener(action, window, cx)
19016 }
19017 })
19018 }),
19019 );
19020
19021 let editor_actions = self.editor_actions.clone();
19022 Subscription::new(move || {
19023 editor_actions.borrow_mut().remove(&id);
19024 })
19025 }
19026
19027 pub fn file_header_size(&self) -> u32 {
19028 FILE_HEADER_HEIGHT
19029 }
19030
19031 pub fn restore(
19032 &mut self,
19033 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19034 window: &mut Window,
19035 cx: &mut Context<Self>,
19036 ) {
19037 let workspace = self.workspace();
19038 let project = self.project.as_ref();
19039 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19040 let mut tasks = Vec::new();
19041 for (buffer_id, changes) in revert_changes {
19042 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19043 buffer.update(cx, |buffer, cx| {
19044 buffer.edit(
19045 changes
19046 .into_iter()
19047 .map(|(range, text)| (range, text.to_string())),
19048 None,
19049 cx,
19050 );
19051 });
19052
19053 if let Some(project) =
19054 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19055 {
19056 project.update(cx, |project, cx| {
19057 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19058 })
19059 }
19060 }
19061 }
19062 tasks
19063 });
19064 cx.spawn_in(window, async move |_, cx| {
19065 for (buffer, task) in save_tasks {
19066 let result = task.await;
19067 if result.is_err() {
19068 let Some(path) = buffer
19069 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19070 .ok()
19071 else {
19072 continue;
19073 };
19074 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19075 let Some(task) = cx
19076 .update_window_entity(&workspace, |workspace, window, cx| {
19077 workspace
19078 .open_path_preview(path, None, false, false, false, window, cx)
19079 })
19080 .ok()
19081 else {
19082 continue;
19083 };
19084 task.await.log_err();
19085 }
19086 }
19087 }
19088 })
19089 .detach();
19090 self.change_selections(None, window, cx, |selections| selections.refresh());
19091 }
19092
19093 pub fn to_pixel_point(
19094 &self,
19095 source: multi_buffer::Anchor,
19096 editor_snapshot: &EditorSnapshot,
19097 window: &mut Window,
19098 ) -> Option<gpui::Point<Pixels>> {
19099 let source_point = source.to_display_point(editor_snapshot);
19100 self.display_to_pixel_point(source_point, editor_snapshot, window)
19101 }
19102
19103 pub fn display_to_pixel_point(
19104 &self,
19105 source: DisplayPoint,
19106 editor_snapshot: &EditorSnapshot,
19107 window: &mut Window,
19108 ) -> Option<gpui::Point<Pixels>> {
19109 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19110 let text_layout_details = self.text_layout_details(window);
19111 let scroll_top = text_layout_details
19112 .scroll_anchor
19113 .scroll_position(editor_snapshot)
19114 .y;
19115
19116 if source.row().as_f32() < scroll_top.floor() {
19117 return None;
19118 }
19119 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19120 let source_y = line_height * (source.row().as_f32() - scroll_top);
19121 Some(gpui::Point::new(source_x, source_y))
19122 }
19123
19124 pub fn has_visible_completions_menu(&self) -> bool {
19125 !self.edit_prediction_preview_is_active()
19126 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19127 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19128 })
19129 }
19130
19131 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19132 if self.mode.is_minimap() {
19133 return;
19134 }
19135 self.addons
19136 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19137 }
19138
19139 pub fn unregister_addon<T: Addon>(&mut self) {
19140 self.addons.remove(&std::any::TypeId::of::<T>());
19141 }
19142
19143 pub fn addon<T: Addon>(&self) -> Option<&T> {
19144 let type_id = std::any::TypeId::of::<T>();
19145 self.addons
19146 .get(&type_id)
19147 .and_then(|item| item.to_any().downcast_ref::<T>())
19148 }
19149
19150 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19151 let type_id = std::any::TypeId::of::<T>();
19152 self.addons
19153 .get_mut(&type_id)
19154 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19155 }
19156
19157 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19158 let text_layout_details = self.text_layout_details(window);
19159 let style = &text_layout_details.editor_style;
19160 let font_id = window.text_system().resolve_font(&style.text.font());
19161 let font_size = style.text.font_size.to_pixels(window.rem_size());
19162 let line_height = style.text.line_height_in_pixels(window.rem_size());
19163 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19164
19165 gpui::Size::new(em_width, line_height)
19166 }
19167
19168 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
19169 self.load_diff_task.clone()
19170 }
19171
19172 fn read_metadata_from_db(
19173 &mut self,
19174 item_id: u64,
19175 workspace_id: WorkspaceId,
19176 window: &mut Window,
19177 cx: &mut Context<Editor>,
19178 ) {
19179 if self.is_singleton(cx)
19180 && !self.mode.is_minimap()
19181 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
19182 {
19183 let buffer_snapshot = OnceCell::new();
19184
19185 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
19186 if !folds.is_empty() {
19187 let snapshot =
19188 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19189 self.fold_ranges(
19190 folds
19191 .into_iter()
19192 .map(|(start, end)| {
19193 snapshot.clip_offset(start, Bias::Left)
19194 ..snapshot.clip_offset(end, Bias::Right)
19195 })
19196 .collect(),
19197 false,
19198 window,
19199 cx,
19200 );
19201 }
19202 }
19203
19204 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
19205 if !selections.is_empty() {
19206 let snapshot =
19207 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19208 self.change_selections(None, window, cx, |s| {
19209 s.select_ranges(selections.into_iter().map(|(start, end)| {
19210 snapshot.clip_offset(start, Bias::Left)
19211 ..snapshot.clip_offset(end, Bias::Right)
19212 }));
19213 });
19214 }
19215 };
19216 }
19217
19218 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
19219 }
19220}
19221
19222fn vim_enabled(cx: &App) -> bool {
19223 cx.global::<SettingsStore>()
19224 .raw_user_settings()
19225 .get("vim_mode")
19226 == Some(&serde_json::Value::Bool(true))
19227}
19228
19229// Consider user intent and default settings
19230fn choose_completion_range(
19231 completion: &Completion,
19232 intent: CompletionIntent,
19233 buffer: &Entity<Buffer>,
19234 cx: &mut Context<Editor>,
19235) -> Range<usize> {
19236 fn should_replace(
19237 completion: &Completion,
19238 insert_range: &Range<text::Anchor>,
19239 intent: CompletionIntent,
19240 completion_mode_setting: LspInsertMode,
19241 buffer: &Buffer,
19242 ) -> bool {
19243 // specific actions take precedence over settings
19244 match intent {
19245 CompletionIntent::CompleteWithInsert => return false,
19246 CompletionIntent::CompleteWithReplace => return true,
19247 CompletionIntent::Complete | CompletionIntent::Compose => {}
19248 }
19249
19250 match completion_mode_setting {
19251 LspInsertMode::Insert => false,
19252 LspInsertMode::Replace => true,
19253 LspInsertMode::ReplaceSubsequence => {
19254 let mut text_to_replace = buffer.chars_for_range(
19255 buffer.anchor_before(completion.replace_range.start)
19256 ..buffer.anchor_after(completion.replace_range.end),
19257 );
19258 let mut completion_text = completion.new_text.chars();
19259
19260 // is `text_to_replace` a subsequence of `completion_text`
19261 text_to_replace
19262 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
19263 }
19264 LspInsertMode::ReplaceSuffix => {
19265 let range_after_cursor = insert_range.end..completion.replace_range.end;
19266
19267 let text_after_cursor = buffer
19268 .text_for_range(
19269 buffer.anchor_before(range_after_cursor.start)
19270 ..buffer.anchor_after(range_after_cursor.end),
19271 )
19272 .collect::<String>();
19273 completion.new_text.ends_with(&text_after_cursor)
19274 }
19275 }
19276 }
19277
19278 let buffer = buffer.read(cx);
19279
19280 if let CompletionSource::Lsp {
19281 insert_range: Some(insert_range),
19282 ..
19283 } = &completion.source
19284 {
19285 let completion_mode_setting =
19286 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
19287 .completions
19288 .lsp_insert_mode;
19289
19290 if !should_replace(
19291 completion,
19292 &insert_range,
19293 intent,
19294 completion_mode_setting,
19295 buffer,
19296 ) {
19297 return insert_range.to_offset(buffer);
19298 }
19299 }
19300
19301 completion.replace_range.to_offset(buffer)
19302}
19303
19304fn insert_extra_newline_brackets(
19305 buffer: &MultiBufferSnapshot,
19306 range: Range<usize>,
19307 language: &language::LanguageScope,
19308) -> bool {
19309 let leading_whitespace_len = buffer
19310 .reversed_chars_at(range.start)
19311 .take_while(|c| c.is_whitespace() && *c != '\n')
19312 .map(|c| c.len_utf8())
19313 .sum::<usize>();
19314 let trailing_whitespace_len = buffer
19315 .chars_at(range.end)
19316 .take_while(|c| c.is_whitespace() && *c != '\n')
19317 .map(|c| c.len_utf8())
19318 .sum::<usize>();
19319 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
19320
19321 language.brackets().any(|(pair, enabled)| {
19322 let pair_start = pair.start.trim_end();
19323 let pair_end = pair.end.trim_start();
19324
19325 enabled
19326 && pair.newline
19327 && buffer.contains_str_at(range.end, pair_end)
19328 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
19329 })
19330}
19331
19332fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
19333 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
19334 [(buffer, range, _)] => (*buffer, range.clone()),
19335 _ => return false,
19336 };
19337 let pair = {
19338 let mut result: Option<BracketMatch> = None;
19339
19340 for pair in buffer
19341 .all_bracket_ranges(range.clone())
19342 .filter(move |pair| {
19343 pair.open_range.start <= range.start && pair.close_range.end >= range.end
19344 })
19345 {
19346 let len = pair.close_range.end - pair.open_range.start;
19347
19348 if let Some(existing) = &result {
19349 let existing_len = existing.close_range.end - existing.open_range.start;
19350 if len > existing_len {
19351 continue;
19352 }
19353 }
19354
19355 result = Some(pair);
19356 }
19357
19358 result
19359 };
19360 let Some(pair) = pair else {
19361 return false;
19362 };
19363 pair.newline_only
19364 && buffer
19365 .chars_for_range(pair.open_range.end..range.start)
19366 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
19367 .all(|c| c.is_whitespace() && c != '\n')
19368}
19369
19370fn update_uncommitted_diff_for_buffer(
19371 editor: Entity<Editor>,
19372 project: &Entity<Project>,
19373 buffers: impl IntoIterator<Item = Entity<Buffer>>,
19374 buffer: Entity<MultiBuffer>,
19375 cx: &mut App,
19376) -> Task<()> {
19377 let mut tasks = Vec::new();
19378 project.update(cx, |project, cx| {
19379 for buffer in buffers {
19380 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
19381 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
19382 }
19383 }
19384 });
19385 cx.spawn(async move |cx| {
19386 let diffs = future::join_all(tasks).await;
19387 if editor
19388 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19389 .unwrap_or(false)
19390 {
19391 return;
19392 }
19393
19394 buffer
19395 .update(cx, |buffer, cx| {
19396 for diff in diffs.into_iter().flatten() {
19397 buffer.add_diff(diff, cx);
19398 }
19399 })
19400 .ok();
19401 })
19402}
19403
19404fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
19405 let tab_size = tab_size.get() as usize;
19406 let mut width = offset;
19407
19408 for ch in text.chars() {
19409 width += if ch == '\t' {
19410 tab_size - (width % tab_size)
19411 } else {
19412 1
19413 };
19414 }
19415
19416 width - offset
19417}
19418
19419#[cfg(test)]
19420mod tests {
19421 use super::*;
19422
19423 #[test]
19424 fn test_string_size_with_expanded_tabs() {
19425 let nz = |val| NonZeroU32::new(val).unwrap();
19426 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
19427 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
19428 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
19429 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
19430 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
19431 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
19432 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
19433 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
19434 }
19435}
19436
19437/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
19438struct WordBreakingTokenizer<'a> {
19439 input: &'a str,
19440}
19441
19442impl<'a> WordBreakingTokenizer<'a> {
19443 fn new(input: &'a str) -> Self {
19444 Self { input }
19445 }
19446}
19447
19448fn is_char_ideographic(ch: char) -> bool {
19449 use unicode_script::Script::*;
19450 use unicode_script::UnicodeScript;
19451 matches!(ch.script(), Han | Tangut | Yi)
19452}
19453
19454fn is_grapheme_ideographic(text: &str) -> bool {
19455 text.chars().any(is_char_ideographic)
19456}
19457
19458fn is_grapheme_whitespace(text: &str) -> bool {
19459 text.chars().any(|x| x.is_whitespace())
19460}
19461
19462fn should_stay_with_preceding_ideograph(text: &str) -> bool {
19463 text.chars().next().map_or(false, |ch| {
19464 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
19465 })
19466}
19467
19468#[derive(PartialEq, Eq, Debug, Clone, Copy)]
19469enum WordBreakToken<'a> {
19470 Word { token: &'a str, grapheme_len: usize },
19471 InlineWhitespace { token: &'a str, grapheme_len: usize },
19472 Newline,
19473}
19474
19475impl<'a> Iterator for WordBreakingTokenizer<'a> {
19476 /// Yields a span, the count of graphemes in the token, and whether it was
19477 /// whitespace. Note that it also breaks at word boundaries.
19478 type Item = WordBreakToken<'a>;
19479
19480 fn next(&mut self) -> Option<Self::Item> {
19481 use unicode_segmentation::UnicodeSegmentation;
19482 if self.input.is_empty() {
19483 return None;
19484 }
19485
19486 let mut iter = self.input.graphemes(true).peekable();
19487 let mut offset = 0;
19488 let mut grapheme_len = 0;
19489 if let Some(first_grapheme) = iter.next() {
19490 let is_newline = first_grapheme == "\n";
19491 let is_whitespace = is_grapheme_whitespace(first_grapheme);
19492 offset += first_grapheme.len();
19493 grapheme_len += 1;
19494 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
19495 if let Some(grapheme) = iter.peek().copied() {
19496 if should_stay_with_preceding_ideograph(grapheme) {
19497 offset += grapheme.len();
19498 grapheme_len += 1;
19499 }
19500 }
19501 } else {
19502 let mut words = self.input[offset..].split_word_bound_indices().peekable();
19503 let mut next_word_bound = words.peek().copied();
19504 if next_word_bound.map_or(false, |(i, _)| i == 0) {
19505 next_word_bound = words.next();
19506 }
19507 while let Some(grapheme) = iter.peek().copied() {
19508 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19509 break;
19510 };
19511 if is_grapheme_whitespace(grapheme) != is_whitespace
19512 || (grapheme == "\n") != is_newline
19513 {
19514 break;
19515 };
19516 offset += grapheme.len();
19517 grapheme_len += 1;
19518 iter.next();
19519 }
19520 }
19521 let token = &self.input[..offset];
19522 self.input = &self.input[offset..];
19523 if token == "\n" {
19524 Some(WordBreakToken::Newline)
19525 } else if is_whitespace {
19526 Some(WordBreakToken::InlineWhitespace {
19527 token,
19528 grapheme_len,
19529 })
19530 } else {
19531 Some(WordBreakToken::Word {
19532 token,
19533 grapheme_len,
19534 })
19535 }
19536 } else {
19537 None
19538 }
19539 }
19540}
19541
19542#[test]
19543fn test_word_breaking_tokenizer() {
19544 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
19545 ("", &[]),
19546 (" ", &[whitespace(" ", 2)]),
19547 ("Ʒ", &[word("Ʒ", 1)]),
19548 ("Ǽ", &[word("Ǽ", 1)]),
19549 ("⋑", &[word("⋑", 1)]),
19550 ("⋑⋑", &[word("⋑⋑", 2)]),
19551 (
19552 "原理,进而",
19553 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
19554 ),
19555 (
19556 "hello world",
19557 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
19558 ),
19559 (
19560 "hello, world",
19561 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
19562 ),
19563 (
19564 " hello world",
19565 &[
19566 whitespace(" ", 2),
19567 word("hello", 5),
19568 whitespace(" ", 1),
19569 word("world", 5),
19570 ],
19571 ),
19572 (
19573 "这是什么 \n 钢笔",
19574 &[
19575 word("这", 1),
19576 word("是", 1),
19577 word("什", 1),
19578 word("么", 1),
19579 whitespace(" ", 1),
19580 newline(),
19581 whitespace(" ", 1),
19582 word("钢", 1),
19583 word("笔", 1),
19584 ],
19585 ),
19586 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
19587 ];
19588
19589 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19590 WordBreakToken::Word {
19591 token,
19592 grapheme_len,
19593 }
19594 }
19595
19596 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19597 WordBreakToken::InlineWhitespace {
19598 token,
19599 grapheme_len,
19600 }
19601 }
19602
19603 fn newline() -> WordBreakToken<'static> {
19604 WordBreakToken::Newline
19605 }
19606
19607 for (input, result) in tests {
19608 assert_eq!(
19609 WordBreakingTokenizer::new(input)
19610 .collect::<Vec<_>>()
19611 .as_slice(),
19612 *result,
19613 );
19614 }
19615}
19616
19617fn wrap_with_prefix(
19618 line_prefix: String,
19619 unwrapped_text: String,
19620 wrap_column: usize,
19621 tab_size: NonZeroU32,
19622 preserve_existing_whitespace: bool,
19623) -> String {
19624 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
19625 let mut wrapped_text = String::new();
19626 let mut current_line = line_prefix.clone();
19627
19628 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
19629 let mut current_line_len = line_prefix_len;
19630 let mut in_whitespace = false;
19631 for token in tokenizer {
19632 let have_preceding_whitespace = in_whitespace;
19633 match token {
19634 WordBreakToken::Word {
19635 token,
19636 grapheme_len,
19637 } => {
19638 in_whitespace = false;
19639 if current_line_len + grapheme_len > wrap_column
19640 && current_line_len != line_prefix_len
19641 {
19642 wrapped_text.push_str(current_line.trim_end());
19643 wrapped_text.push('\n');
19644 current_line.truncate(line_prefix.len());
19645 current_line_len = line_prefix_len;
19646 }
19647 current_line.push_str(token);
19648 current_line_len += grapheme_len;
19649 }
19650 WordBreakToken::InlineWhitespace {
19651 mut token,
19652 mut grapheme_len,
19653 } => {
19654 in_whitespace = true;
19655 if have_preceding_whitespace && !preserve_existing_whitespace {
19656 continue;
19657 }
19658 if !preserve_existing_whitespace {
19659 token = " ";
19660 grapheme_len = 1;
19661 }
19662 if current_line_len + grapheme_len > wrap_column {
19663 wrapped_text.push_str(current_line.trim_end());
19664 wrapped_text.push('\n');
19665 current_line.truncate(line_prefix.len());
19666 current_line_len = line_prefix_len;
19667 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
19668 current_line.push_str(token);
19669 current_line_len += grapheme_len;
19670 }
19671 }
19672 WordBreakToken::Newline => {
19673 in_whitespace = true;
19674 if preserve_existing_whitespace {
19675 wrapped_text.push_str(current_line.trim_end());
19676 wrapped_text.push('\n');
19677 current_line.truncate(line_prefix.len());
19678 current_line_len = line_prefix_len;
19679 } else if have_preceding_whitespace {
19680 continue;
19681 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
19682 {
19683 wrapped_text.push_str(current_line.trim_end());
19684 wrapped_text.push('\n');
19685 current_line.truncate(line_prefix.len());
19686 current_line_len = line_prefix_len;
19687 } else if current_line_len != line_prefix_len {
19688 current_line.push(' ');
19689 current_line_len += 1;
19690 }
19691 }
19692 }
19693 }
19694
19695 if !current_line.is_empty() {
19696 wrapped_text.push_str(¤t_line);
19697 }
19698 wrapped_text
19699}
19700
19701#[test]
19702fn test_wrap_with_prefix() {
19703 assert_eq!(
19704 wrap_with_prefix(
19705 "# ".to_string(),
19706 "abcdefg".to_string(),
19707 4,
19708 NonZeroU32::new(4).unwrap(),
19709 false,
19710 ),
19711 "# abcdefg"
19712 );
19713 assert_eq!(
19714 wrap_with_prefix(
19715 "".to_string(),
19716 "\thello world".to_string(),
19717 8,
19718 NonZeroU32::new(4).unwrap(),
19719 false,
19720 ),
19721 "hello\nworld"
19722 );
19723 assert_eq!(
19724 wrap_with_prefix(
19725 "// ".to_string(),
19726 "xx \nyy zz aa bb cc".to_string(),
19727 12,
19728 NonZeroU32::new(4).unwrap(),
19729 false,
19730 ),
19731 "// xx yy zz\n// aa bb cc"
19732 );
19733 assert_eq!(
19734 wrap_with_prefix(
19735 String::new(),
19736 "这是什么 \n 钢笔".to_string(),
19737 3,
19738 NonZeroU32::new(4).unwrap(),
19739 false,
19740 ),
19741 "这是什\n么 钢\n笔"
19742 );
19743}
19744
19745pub trait CollaborationHub {
19746 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
19747 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
19748 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
19749}
19750
19751impl CollaborationHub for Entity<Project> {
19752 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
19753 self.read(cx).collaborators()
19754 }
19755
19756 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
19757 self.read(cx).user_store().read(cx).participant_indices()
19758 }
19759
19760 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
19761 let this = self.read(cx);
19762 let user_ids = this.collaborators().values().map(|c| c.user_id);
19763 this.user_store().read_with(cx, |user_store, cx| {
19764 user_store.participant_names(user_ids, cx)
19765 })
19766 }
19767}
19768
19769pub trait SemanticsProvider {
19770 fn hover(
19771 &self,
19772 buffer: &Entity<Buffer>,
19773 position: text::Anchor,
19774 cx: &mut App,
19775 ) -> Option<Task<Vec<project::Hover>>>;
19776
19777 fn inline_values(
19778 &self,
19779 buffer_handle: Entity<Buffer>,
19780 range: Range<text::Anchor>,
19781 cx: &mut App,
19782 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19783
19784 fn inlay_hints(
19785 &self,
19786 buffer_handle: Entity<Buffer>,
19787 range: Range<text::Anchor>,
19788 cx: &mut App,
19789 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19790
19791 fn resolve_inlay_hint(
19792 &self,
19793 hint: InlayHint,
19794 buffer_handle: Entity<Buffer>,
19795 server_id: LanguageServerId,
19796 cx: &mut App,
19797 ) -> Option<Task<anyhow::Result<InlayHint>>>;
19798
19799 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
19800
19801 fn document_highlights(
19802 &self,
19803 buffer: &Entity<Buffer>,
19804 position: text::Anchor,
19805 cx: &mut App,
19806 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
19807
19808 fn definitions(
19809 &self,
19810 buffer: &Entity<Buffer>,
19811 position: text::Anchor,
19812 kind: GotoDefinitionKind,
19813 cx: &mut App,
19814 ) -> Option<Task<Result<Vec<LocationLink>>>>;
19815
19816 fn range_for_rename(
19817 &self,
19818 buffer: &Entity<Buffer>,
19819 position: text::Anchor,
19820 cx: &mut App,
19821 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
19822
19823 fn perform_rename(
19824 &self,
19825 buffer: &Entity<Buffer>,
19826 position: text::Anchor,
19827 new_name: String,
19828 cx: &mut App,
19829 ) -> Option<Task<Result<ProjectTransaction>>>;
19830}
19831
19832pub trait CompletionProvider {
19833 fn completions(
19834 &self,
19835 excerpt_id: ExcerptId,
19836 buffer: &Entity<Buffer>,
19837 buffer_position: text::Anchor,
19838 trigger: CompletionContext,
19839 window: &mut Window,
19840 cx: &mut Context<Editor>,
19841 ) -> Task<Result<Option<Vec<Completion>>>>;
19842
19843 fn resolve_completions(
19844 &self,
19845 buffer: Entity<Buffer>,
19846 completion_indices: Vec<usize>,
19847 completions: Rc<RefCell<Box<[Completion]>>>,
19848 cx: &mut Context<Editor>,
19849 ) -> Task<Result<bool>>;
19850
19851 fn apply_additional_edits_for_completion(
19852 &self,
19853 _buffer: Entity<Buffer>,
19854 _completions: Rc<RefCell<Box<[Completion]>>>,
19855 _completion_index: usize,
19856 _push_to_history: bool,
19857 _cx: &mut Context<Editor>,
19858 ) -> Task<Result<Option<language::Transaction>>> {
19859 Task::ready(Ok(None))
19860 }
19861
19862 fn is_completion_trigger(
19863 &self,
19864 buffer: &Entity<Buffer>,
19865 position: language::Anchor,
19866 text: &str,
19867 trigger_in_words: bool,
19868 cx: &mut Context<Editor>,
19869 ) -> bool;
19870
19871 fn sort_completions(&self) -> bool {
19872 true
19873 }
19874
19875 fn filter_completions(&self) -> bool {
19876 true
19877 }
19878}
19879
19880pub trait CodeActionProvider {
19881 fn id(&self) -> Arc<str>;
19882
19883 fn code_actions(
19884 &self,
19885 buffer: &Entity<Buffer>,
19886 range: Range<text::Anchor>,
19887 window: &mut Window,
19888 cx: &mut App,
19889 ) -> Task<Result<Vec<CodeAction>>>;
19890
19891 fn apply_code_action(
19892 &self,
19893 buffer_handle: Entity<Buffer>,
19894 action: CodeAction,
19895 excerpt_id: ExcerptId,
19896 push_to_history: bool,
19897 window: &mut Window,
19898 cx: &mut App,
19899 ) -> Task<Result<ProjectTransaction>>;
19900}
19901
19902impl CodeActionProvider for Entity<Project> {
19903 fn id(&self) -> Arc<str> {
19904 "project".into()
19905 }
19906
19907 fn code_actions(
19908 &self,
19909 buffer: &Entity<Buffer>,
19910 range: Range<text::Anchor>,
19911 _window: &mut Window,
19912 cx: &mut App,
19913 ) -> Task<Result<Vec<CodeAction>>> {
19914 self.update(cx, |project, cx| {
19915 let code_lens = project.code_lens(buffer, range.clone(), cx);
19916 let code_actions = project.code_actions(buffer, range, None, cx);
19917 cx.background_spawn(async move {
19918 let (code_lens, code_actions) = join(code_lens, code_actions).await;
19919 Ok(code_lens
19920 .context("code lens fetch")?
19921 .into_iter()
19922 .chain(code_actions.context("code action fetch")?)
19923 .collect())
19924 })
19925 })
19926 }
19927
19928 fn apply_code_action(
19929 &self,
19930 buffer_handle: Entity<Buffer>,
19931 action: CodeAction,
19932 _excerpt_id: ExcerptId,
19933 push_to_history: bool,
19934 _window: &mut Window,
19935 cx: &mut App,
19936 ) -> Task<Result<ProjectTransaction>> {
19937 self.update(cx, |project, cx| {
19938 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19939 })
19940 }
19941}
19942
19943fn snippet_completions(
19944 project: &Project,
19945 buffer: &Entity<Buffer>,
19946 buffer_position: text::Anchor,
19947 cx: &mut App,
19948) -> Task<Result<Vec<Completion>>> {
19949 let languages = buffer.read(cx).languages_at(buffer_position);
19950 let snippet_store = project.snippets().read(cx);
19951
19952 let scopes: Vec<_> = languages
19953 .iter()
19954 .filter_map(|language| {
19955 let language_name = language.lsp_id();
19956 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19957
19958 if snippets.is_empty() {
19959 None
19960 } else {
19961 Some((language.default_scope(), snippets))
19962 }
19963 })
19964 .collect();
19965
19966 if scopes.is_empty() {
19967 return Task::ready(Ok(vec![]));
19968 }
19969
19970 let snapshot = buffer.read(cx).text_snapshot();
19971 let chars: String = snapshot
19972 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19973 .collect();
19974 let executor = cx.background_executor().clone();
19975
19976 cx.background_spawn(async move {
19977 let mut all_results: Vec<Completion> = Vec::new();
19978 for (scope, snippets) in scopes.into_iter() {
19979 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19980 let mut last_word = chars
19981 .chars()
19982 .take_while(|c| classifier.is_word(*c))
19983 .collect::<String>();
19984 last_word = last_word.chars().rev().collect();
19985
19986 if last_word.is_empty() {
19987 return Ok(vec![]);
19988 }
19989
19990 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
19991 let to_lsp = |point: &text::Anchor| {
19992 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
19993 point_to_lsp(end)
19994 };
19995 let lsp_end = to_lsp(&buffer_position);
19996
19997 let candidates = snippets
19998 .iter()
19999 .enumerate()
20000 .flat_map(|(ix, snippet)| {
20001 snippet
20002 .prefix
20003 .iter()
20004 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
20005 })
20006 .collect::<Vec<StringMatchCandidate>>();
20007
20008 let mut matches = fuzzy::match_strings(
20009 &candidates,
20010 &last_word,
20011 last_word.chars().any(|c| c.is_uppercase()),
20012 100,
20013 &Default::default(),
20014 executor.clone(),
20015 )
20016 .await;
20017
20018 // Remove all candidates where the query's start does not match the start of any word in the candidate
20019 if let Some(query_start) = last_word.chars().next() {
20020 matches.retain(|string_match| {
20021 split_words(&string_match.string).any(|word| {
20022 // Check that the first codepoint of the word as lowercase matches the first
20023 // codepoint of the query as lowercase
20024 word.chars()
20025 .flat_map(|codepoint| codepoint.to_lowercase())
20026 .zip(query_start.to_lowercase())
20027 .all(|(word_cp, query_cp)| word_cp == query_cp)
20028 })
20029 });
20030 }
20031
20032 let matched_strings = matches
20033 .into_iter()
20034 .map(|m| m.string)
20035 .collect::<HashSet<_>>();
20036
20037 let mut result: Vec<Completion> = snippets
20038 .iter()
20039 .filter_map(|snippet| {
20040 let matching_prefix = snippet
20041 .prefix
20042 .iter()
20043 .find(|prefix| matched_strings.contains(*prefix))?;
20044 let start = as_offset - last_word.len();
20045 let start = snapshot.anchor_before(start);
20046 let range = start..buffer_position;
20047 let lsp_start = to_lsp(&start);
20048 let lsp_range = lsp::Range {
20049 start: lsp_start,
20050 end: lsp_end,
20051 };
20052 Some(Completion {
20053 replace_range: range,
20054 new_text: snippet.body.clone(),
20055 source: CompletionSource::Lsp {
20056 insert_range: None,
20057 server_id: LanguageServerId(usize::MAX),
20058 resolved: true,
20059 lsp_completion: Box::new(lsp::CompletionItem {
20060 label: snippet.prefix.first().unwrap().clone(),
20061 kind: Some(CompletionItemKind::SNIPPET),
20062 label_details: snippet.description.as_ref().map(|description| {
20063 lsp::CompletionItemLabelDetails {
20064 detail: Some(description.clone()),
20065 description: None,
20066 }
20067 }),
20068 insert_text_format: Some(InsertTextFormat::SNIPPET),
20069 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20070 lsp::InsertReplaceEdit {
20071 new_text: snippet.body.clone(),
20072 insert: lsp_range,
20073 replace: lsp_range,
20074 },
20075 )),
20076 filter_text: Some(snippet.body.clone()),
20077 sort_text: Some(char::MAX.to_string()),
20078 ..lsp::CompletionItem::default()
20079 }),
20080 lsp_defaults: None,
20081 },
20082 label: CodeLabel {
20083 text: matching_prefix.clone(),
20084 runs: Vec::new(),
20085 filter_range: 0..matching_prefix.len(),
20086 },
20087 icon_path: None,
20088 documentation: Some(
20089 CompletionDocumentation::SingleLineAndMultiLinePlainText {
20090 single_line: snippet.name.clone().into(),
20091 plain_text: snippet
20092 .description
20093 .clone()
20094 .map(|description| description.into()),
20095 },
20096 ),
20097 insert_text_mode: None,
20098 confirm: None,
20099 })
20100 })
20101 .collect();
20102
20103 all_results.append(&mut result);
20104 }
20105
20106 Ok(all_results)
20107 })
20108}
20109
20110impl CompletionProvider for Entity<Project> {
20111 fn completions(
20112 &self,
20113 _excerpt_id: ExcerptId,
20114 buffer: &Entity<Buffer>,
20115 buffer_position: text::Anchor,
20116 options: CompletionContext,
20117 _window: &mut Window,
20118 cx: &mut Context<Editor>,
20119 ) -> Task<Result<Option<Vec<Completion>>>> {
20120 self.update(cx, |project, cx| {
20121 let snippets = snippet_completions(project, buffer, buffer_position, cx);
20122 let project_completions = project.completions(buffer, buffer_position, options, cx);
20123 cx.background_spawn(async move {
20124 let snippets_completions = snippets.await?;
20125 match project_completions.await? {
20126 Some(mut completions) => {
20127 completions.extend(snippets_completions);
20128 Ok(Some(completions))
20129 }
20130 None => {
20131 if snippets_completions.is_empty() {
20132 Ok(None)
20133 } else {
20134 Ok(Some(snippets_completions))
20135 }
20136 }
20137 }
20138 })
20139 })
20140 }
20141
20142 fn resolve_completions(
20143 &self,
20144 buffer: Entity<Buffer>,
20145 completion_indices: Vec<usize>,
20146 completions: Rc<RefCell<Box<[Completion]>>>,
20147 cx: &mut Context<Editor>,
20148 ) -> Task<Result<bool>> {
20149 self.update(cx, |project, cx| {
20150 project.lsp_store().update(cx, |lsp_store, cx| {
20151 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
20152 })
20153 })
20154 }
20155
20156 fn apply_additional_edits_for_completion(
20157 &self,
20158 buffer: Entity<Buffer>,
20159 completions: Rc<RefCell<Box<[Completion]>>>,
20160 completion_index: usize,
20161 push_to_history: bool,
20162 cx: &mut Context<Editor>,
20163 ) -> Task<Result<Option<language::Transaction>>> {
20164 self.update(cx, |project, cx| {
20165 project.lsp_store().update(cx, |lsp_store, cx| {
20166 lsp_store.apply_additional_edits_for_completion(
20167 buffer,
20168 completions,
20169 completion_index,
20170 push_to_history,
20171 cx,
20172 )
20173 })
20174 })
20175 }
20176
20177 fn is_completion_trigger(
20178 &self,
20179 buffer: &Entity<Buffer>,
20180 position: language::Anchor,
20181 text: &str,
20182 trigger_in_words: bool,
20183 cx: &mut Context<Editor>,
20184 ) -> bool {
20185 let mut chars = text.chars();
20186 let char = if let Some(char) = chars.next() {
20187 char
20188 } else {
20189 return false;
20190 };
20191 if chars.next().is_some() {
20192 return false;
20193 }
20194
20195 let buffer = buffer.read(cx);
20196 let snapshot = buffer.snapshot();
20197 if !snapshot.settings_at(position, cx).show_completions_on_input {
20198 return false;
20199 }
20200 let classifier = snapshot.char_classifier_at(position).for_completion(true);
20201 if trigger_in_words && classifier.is_word(char) {
20202 return true;
20203 }
20204
20205 buffer.completion_triggers().contains(text)
20206 }
20207}
20208
20209impl SemanticsProvider for Entity<Project> {
20210 fn hover(
20211 &self,
20212 buffer: &Entity<Buffer>,
20213 position: text::Anchor,
20214 cx: &mut App,
20215 ) -> Option<Task<Vec<project::Hover>>> {
20216 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
20217 }
20218
20219 fn document_highlights(
20220 &self,
20221 buffer: &Entity<Buffer>,
20222 position: text::Anchor,
20223 cx: &mut App,
20224 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
20225 Some(self.update(cx, |project, cx| {
20226 project.document_highlights(buffer, position, cx)
20227 }))
20228 }
20229
20230 fn definitions(
20231 &self,
20232 buffer: &Entity<Buffer>,
20233 position: text::Anchor,
20234 kind: GotoDefinitionKind,
20235 cx: &mut App,
20236 ) -> Option<Task<Result<Vec<LocationLink>>>> {
20237 Some(self.update(cx, |project, cx| match kind {
20238 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
20239 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
20240 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
20241 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
20242 }))
20243 }
20244
20245 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
20246 // TODO: make this work for remote projects
20247 self.update(cx, |project, cx| {
20248 if project
20249 .active_debug_session(cx)
20250 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
20251 {
20252 return true;
20253 }
20254
20255 buffer.update(cx, |buffer, cx| {
20256 project.any_language_server_supports_inlay_hints(buffer, cx)
20257 })
20258 })
20259 }
20260
20261 fn inline_values(
20262 &self,
20263 buffer_handle: Entity<Buffer>,
20264
20265 range: Range<text::Anchor>,
20266 cx: &mut App,
20267 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20268 self.update(cx, |project, cx| {
20269 let (session, active_stack_frame) = project.active_debug_session(cx)?;
20270
20271 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
20272 })
20273 }
20274
20275 fn inlay_hints(
20276 &self,
20277 buffer_handle: Entity<Buffer>,
20278 range: Range<text::Anchor>,
20279 cx: &mut App,
20280 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20281 Some(self.update(cx, |project, cx| {
20282 project.inlay_hints(buffer_handle, range, cx)
20283 }))
20284 }
20285
20286 fn resolve_inlay_hint(
20287 &self,
20288 hint: InlayHint,
20289 buffer_handle: Entity<Buffer>,
20290 server_id: LanguageServerId,
20291 cx: &mut App,
20292 ) -> Option<Task<anyhow::Result<InlayHint>>> {
20293 Some(self.update(cx, |project, cx| {
20294 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
20295 }))
20296 }
20297
20298 fn range_for_rename(
20299 &self,
20300 buffer: &Entity<Buffer>,
20301 position: text::Anchor,
20302 cx: &mut App,
20303 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
20304 Some(self.update(cx, |project, cx| {
20305 let buffer = buffer.clone();
20306 let task = project.prepare_rename(buffer.clone(), position, cx);
20307 cx.spawn(async move |_, cx| {
20308 Ok(match task.await? {
20309 PrepareRenameResponse::Success(range) => Some(range),
20310 PrepareRenameResponse::InvalidPosition => None,
20311 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
20312 // Fallback on using TreeSitter info to determine identifier range
20313 buffer.update(cx, |buffer, _| {
20314 let snapshot = buffer.snapshot();
20315 let (range, kind) = snapshot.surrounding_word(position);
20316 if kind != Some(CharKind::Word) {
20317 return None;
20318 }
20319 Some(
20320 snapshot.anchor_before(range.start)
20321 ..snapshot.anchor_after(range.end),
20322 )
20323 })?
20324 }
20325 })
20326 })
20327 }))
20328 }
20329
20330 fn perform_rename(
20331 &self,
20332 buffer: &Entity<Buffer>,
20333 position: text::Anchor,
20334 new_name: String,
20335 cx: &mut App,
20336 ) -> Option<Task<Result<ProjectTransaction>>> {
20337 Some(self.update(cx, |project, cx| {
20338 project.perform_rename(buffer.clone(), position, new_name, cx)
20339 }))
20340 }
20341}
20342
20343fn inlay_hint_settings(
20344 location: Anchor,
20345 snapshot: &MultiBufferSnapshot,
20346 cx: &mut Context<Editor>,
20347) -> InlayHintSettings {
20348 let file = snapshot.file_at(location);
20349 let language = snapshot.language_at(location).map(|l| l.name());
20350 language_settings(language, file, cx).inlay_hints
20351}
20352
20353fn consume_contiguous_rows(
20354 contiguous_row_selections: &mut Vec<Selection<Point>>,
20355 selection: &Selection<Point>,
20356 display_map: &DisplaySnapshot,
20357 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
20358) -> (MultiBufferRow, MultiBufferRow) {
20359 contiguous_row_selections.push(selection.clone());
20360 let start_row = MultiBufferRow(selection.start.row);
20361 let mut end_row = ending_row(selection, display_map);
20362
20363 while let Some(next_selection) = selections.peek() {
20364 if next_selection.start.row <= end_row.0 {
20365 end_row = ending_row(next_selection, display_map);
20366 contiguous_row_selections.push(selections.next().unwrap().clone());
20367 } else {
20368 break;
20369 }
20370 }
20371 (start_row, end_row)
20372}
20373
20374fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
20375 if next_selection.end.column > 0 || next_selection.is_empty() {
20376 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
20377 } else {
20378 MultiBufferRow(next_selection.end.row)
20379 }
20380}
20381
20382impl EditorSnapshot {
20383 pub fn remote_selections_in_range<'a>(
20384 &'a self,
20385 range: &'a Range<Anchor>,
20386 collaboration_hub: &dyn CollaborationHub,
20387 cx: &'a App,
20388 ) -> impl 'a + Iterator<Item = RemoteSelection> {
20389 let participant_names = collaboration_hub.user_names(cx);
20390 let participant_indices = collaboration_hub.user_participant_indices(cx);
20391 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20392 let collaborators_by_replica_id = collaborators_by_peer_id
20393 .values()
20394 .map(|collaborator| (collaborator.replica_id, collaborator))
20395 .collect::<HashMap<_, _>>();
20396 self.buffer_snapshot
20397 .selections_in_range(range, false)
20398 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20399 if replica_id == AGENT_REPLICA_ID {
20400 Some(RemoteSelection {
20401 replica_id,
20402 selection,
20403 cursor_shape,
20404 line_mode,
20405 collaborator_id: CollaboratorId::Agent,
20406 user_name: Some("Agent".into()),
20407 color: cx.theme().players().agent(),
20408 })
20409 } else {
20410 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20411 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20412 let user_name = participant_names.get(&collaborator.user_id).cloned();
20413 Some(RemoteSelection {
20414 replica_id,
20415 selection,
20416 cursor_shape,
20417 line_mode,
20418 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20419 user_name,
20420 color: if let Some(index) = participant_index {
20421 cx.theme().players().color_for_participant(index.0)
20422 } else {
20423 cx.theme().players().absent()
20424 },
20425 })
20426 }
20427 })
20428 }
20429
20430 pub fn hunks_for_ranges(
20431 &self,
20432 ranges: impl IntoIterator<Item = Range<Point>>,
20433 ) -> Vec<MultiBufferDiffHunk> {
20434 let mut hunks = Vec::new();
20435 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20436 HashMap::default();
20437 for query_range in ranges {
20438 let query_rows =
20439 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20440 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20441 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20442 ) {
20443 // Include deleted hunks that are adjacent to the query range, because
20444 // otherwise they would be missed.
20445 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20446 if hunk.status().is_deleted() {
20447 intersects_range |= hunk.row_range.start == query_rows.end;
20448 intersects_range |= hunk.row_range.end == query_rows.start;
20449 }
20450 if intersects_range {
20451 if !processed_buffer_rows
20452 .entry(hunk.buffer_id)
20453 .or_default()
20454 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20455 {
20456 continue;
20457 }
20458 hunks.push(hunk);
20459 }
20460 }
20461 }
20462
20463 hunks
20464 }
20465
20466 fn display_diff_hunks_for_rows<'a>(
20467 &'a self,
20468 display_rows: Range<DisplayRow>,
20469 folded_buffers: &'a HashSet<BufferId>,
20470 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20471 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20472 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20473
20474 self.buffer_snapshot
20475 .diff_hunks_in_range(buffer_start..buffer_end)
20476 .filter_map(|hunk| {
20477 if folded_buffers.contains(&hunk.buffer_id) {
20478 return None;
20479 }
20480
20481 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20482 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20483
20484 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20485 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20486
20487 let display_hunk = if hunk_display_start.column() != 0 {
20488 DisplayDiffHunk::Folded {
20489 display_row: hunk_display_start.row(),
20490 }
20491 } else {
20492 let mut end_row = hunk_display_end.row();
20493 if hunk_display_end.column() > 0 {
20494 end_row.0 += 1;
20495 }
20496 let is_created_file = hunk.is_created_file();
20497 DisplayDiffHunk::Unfolded {
20498 status: hunk.status(),
20499 diff_base_byte_range: hunk.diff_base_byte_range,
20500 display_row_range: hunk_display_start.row()..end_row,
20501 multi_buffer_range: Anchor::range_in_buffer(
20502 hunk.excerpt_id,
20503 hunk.buffer_id,
20504 hunk.buffer_range,
20505 ),
20506 is_created_file,
20507 }
20508 };
20509
20510 Some(display_hunk)
20511 })
20512 }
20513
20514 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20515 self.display_snapshot.buffer_snapshot.language_at(position)
20516 }
20517
20518 pub fn is_focused(&self) -> bool {
20519 self.is_focused
20520 }
20521
20522 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20523 self.placeholder_text.as_ref()
20524 }
20525
20526 pub fn scroll_position(&self) -> gpui::Point<f32> {
20527 self.scroll_anchor.scroll_position(&self.display_snapshot)
20528 }
20529
20530 fn gutter_dimensions(
20531 &self,
20532 font_id: FontId,
20533 font_size: Pixels,
20534 max_line_number_width: Pixels,
20535 cx: &App,
20536 ) -> Option<GutterDimensions> {
20537 if !self.show_gutter {
20538 return None;
20539 }
20540
20541 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20542 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20543
20544 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20545 matches!(
20546 ProjectSettings::get_global(cx).git.git_gutter,
20547 Some(GitGutterSetting::TrackedFiles)
20548 )
20549 });
20550 let gutter_settings = EditorSettings::get_global(cx).gutter;
20551 let show_line_numbers = self
20552 .show_line_numbers
20553 .unwrap_or(gutter_settings.line_numbers);
20554 let line_gutter_width = if show_line_numbers {
20555 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20556 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20557 max_line_number_width.max(min_width_for_number_on_gutter)
20558 } else {
20559 0.0.into()
20560 };
20561
20562 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20563 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20564
20565 let git_blame_entries_width =
20566 self.git_blame_gutter_max_author_length
20567 .map(|max_author_length| {
20568 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20569 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20570
20571 /// The number of characters to dedicate to gaps and margins.
20572 const SPACING_WIDTH: usize = 4;
20573
20574 let max_char_count = max_author_length.min(renderer.max_author_length())
20575 + ::git::SHORT_SHA_LENGTH
20576 + MAX_RELATIVE_TIMESTAMP.len()
20577 + SPACING_WIDTH;
20578
20579 em_advance * max_char_count
20580 });
20581
20582 let is_singleton = self.buffer_snapshot.is_singleton();
20583
20584 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
20585 left_padding += if !is_singleton {
20586 em_width * 4.0
20587 } else if show_runnables || show_breakpoints {
20588 em_width * 3.0
20589 } else if show_git_gutter && show_line_numbers {
20590 em_width * 2.0
20591 } else if show_git_gutter || show_line_numbers {
20592 em_width
20593 } else {
20594 px(0.)
20595 };
20596
20597 let shows_folds = is_singleton && gutter_settings.folds;
20598
20599 let right_padding = if shows_folds && show_line_numbers {
20600 em_width * 4.0
20601 } else if shows_folds || (!is_singleton && show_line_numbers) {
20602 em_width * 3.0
20603 } else if show_line_numbers {
20604 em_width
20605 } else {
20606 px(0.)
20607 };
20608
20609 Some(GutterDimensions {
20610 left_padding,
20611 right_padding,
20612 width: line_gutter_width + left_padding + right_padding,
20613 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
20614 git_blame_entries_width,
20615 })
20616 }
20617
20618 pub fn render_crease_toggle(
20619 &self,
20620 buffer_row: MultiBufferRow,
20621 row_contains_cursor: bool,
20622 editor: Entity<Editor>,
20623 window: &mut Window,
20624 cx: &mut App,
20625 ) -> Option<AnyElement> {
20626 let folded = self.is_line_folded(buffer_row);
20627 let mut is_foldable = false;
20628
20629 if let Some(crease) = self
20630 .crease_snapshot
20631 .query_row(buffer_row, &self.buffer_snapshot)
20632 {
20633 is_foldable = true;
20634 match crease {
20635 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20636 if let Some(render_toggle) = render_toggle {
20637 let toggle_callback =
20638 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20639 if folded {
20640 editor.update(cx, |editor, cx| {
20641 editor.fold_at(buffer_row, window, cx)
20642 });
20643 } else {
20644 editor.update(cx, |editor, cx| {
20645 editor.unfold_at(buffer_row, window, cx)
20646 });
20647 }
20648 });
20649 return Some((render_toggle)(
20650 buffer_row,
20651 folded,
20652 toggle_callback,
20653 window,
20654 cx,
20655 ));
20656 }
20657 }
20658 }
20659 }
20660
20661 is_foldable |= self.starts_indent(buffer_row);
20662
20663 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20664 Some(
20665 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20666 .toggle_state(folded)
20667 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20668 if folded {
20669 this.unfold_at(buffer_row, window, cx);
20670 } else {
20671 this.fold_at(buffer_row, window, cx);
20672 }
20673 }))
20674 .into_any_element(),
20675 )
20676 } else {
20677 None
20678 }
20679 }
20680
20681 pub fn render_crease_trailer(
20682 &self,
20683 buffer_row: MultiBufferRow,
20684 window: &mut Window,
20685 cx: &mut App,
20686 ) -> Option<AnyElement> {
20687 let folded = self.is_line_folded(buffer_row);
20688 if let Crease::Inline { render_trailer, .. } = self
20689 .crease_snapshot
20690 .query_row(buffer_row, &self.buffer_snapshot)?
20691 {
20692 let render_trailer = render_trailer.as_ref()?;
20693 Some(render_trailer(buffer_row, folded, window, cx))
20694 } else {
20695 None
20696 }
20697 }
20698}
20699
20700impl Deref for EditorSnapshot {
20701 type Target = DisplaySnapshot;
20702
20703 fn deref(&self) -> &Self::Target {
20704 &self.display_snapshot
20705 }
20706}
20707
20708#[derive(Clone, Debug, PartialEq, Eq)]
20709pub enum EditorEvent {
20710 InputIgnored {
20711 text: Arc<str>,
20712 },
20713 InputHandled {
20714 utf16_range_to_replace: Option<Range<isize>>,
20715 text: Arc<str>,
20716 },
20717 ExcerptsAdded {
20718 buffer: Entity<Buffer>,
20719 predecessor: ExcerptId,
20720 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
20721 },
20722 ExcerptsRemoved {
20723 ids: Vec<ExcerptId>,
20724 removed_buffer_ids: Vec<BufferId>,
20725 },
20726 BufferFoldToggled {
20727 ids: Vec<ExcerptId>,
20728 folded: bool,
20729 },
20730 ExcerptsEdited {
20731 ids: Vec<ExcerptId>,
20732 },
20733 ExcerptsExpanded {
20734 ids: Vec<ExcerptId>,
20735 },
20736 BufferEdited,
20737 Edited {
20738 transaction_id: clock::Lamport,
20739 },
20740 Reparsed(BufferId),
20741 Focused,
20742 FocusedIn,
20743 Blurred,
20744 DirtyChanged,
20745 Saved,
20746 TitleChanged,
20747 DiffBaseChanged,
20748 SelectionsChanged {
20749 local: bool,
20750 },
20751 ScrollPositionChanged {
20752 local: bool,
20753 autoscroll: bool,
20754 },
20755 Closed,
20756 TransactionUndone {
20757 transaction_id: clock::Lamport,
20758 },
20759 TransactionBegun {
20760 transaction_id: clock::Lamport,
20761 },
20762 Reloaded,
20763 CursorShapeChanged,
20764 PushedToNavHistory {
20765 anchor: Anchor,
20766 is_deactivate: bool,
20767 },
20768}
20769
20770impl EventEmitter<EditorEvent> for Editor {}
20771
20772impl Focusable for Editor {
20773 fn focus_handle(&self, _cx: &App) -> FocusHandle {
20774 self.focus_handle.clone()
20775 }
20776}
20777
20778impl Render for Editor {
20779 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20780 let settings = ThemeSettings::get_global(cx);
20781
20782 let mut text_style = match self.mode {
20783 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
20784 color: cx.theme().colors().editor_foreground,
20785 font_family: settings.ui_font.family.clone(),
20786 font_features: settings.ui_font.features.clone(),
20787 font_fallbacks: settings.ui_font.fallbacks.clone(),
20788 font_size: rems(0.875).into(),
20789 font_weight: settings.ui_font.weight,
20790 line_height: relative(settings.buffer_line_height.value()),
20791 ..Default::default()
20792 },
20793 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
20794 color: cx.theme().colors().editor_foreground,
20795 font_family: settings.buffer_font.family.clone(),
20796 font_features: settings.buffer_font.features.clone(),
20797 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20798 font_size: settings.buffer_font_size(cx).into(),
20799 font_weight: settings.buffer_font.weight,
20800 line_height: relative(settings.buffer_line_height.value()),
20801 ..Default::default()
20802 },
20803 };
20804 if let Some(text_style_refinement) = &self.text_style_refinement {
20805 text_style.refine(text_style_refinement)
20806 }
20807
20808 let background = match self.mode {
20809 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
20810 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
20811 EditorMode::Full { .. } => cx.theme().colors().editor_background,
20812 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
20813 };
20814
20815 EditorElement::new(
20816 &cx.entity(),
20817 EditorStyle {
20818 background,
20819 local_player: cx.theme().players().local(),
20820 text: text_style,
20821 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
20822 syntax: cx.theme().syntax().clone(),
20823 status: cx.theme().status().clone(),
20824 inlay_hints_style: make_inlay_hints_style(cx),
20825 inline_completion_styles: make_suggestion_styles(cx),
20826 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
20827 show_underlines: !self.mode.is_minimap(),
20828 },
20829 )
20830 }
20831}
20832
20833impl EntityInputHandler for Editor {
20834 fn text_for_range(
20835 &mut self,
20836 range_utf16: Range<usize>,
20837 adjusted_range: &mut Option<Range<usize>>,
20838 _: &mut Window,
20839 cx: &mut Context<Self>,
20840 ) -> Option<String> {
20841 let snapshot = self.buffer.read(cx).read(cx);
20842 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
20843 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
20844 if (start.0..end.0) != range_utf16 {
20845 adjusted_range.replace(start.0..end.0);
20846 }
20847 Some(snapshot.text_for_range(start..end).collect())
20848 }
20849
20850 fn selected_text_range(
20851 &mut self,
20852 ignore_disabled_input: bool,
20853 _: &mut Window,
20854 cx: &mut Context<Self>,
20855 ) -> Option<UTF16Selection> {
20856 // Prevent the IME menu from appearing when holding down an alphabetic key
20857 // while input is disabled.
20858 if !ignore_disabled_input && !self.input_enabled {
20859 return None;
20860 }
20861
20862 let selection = self.selections.newest::<OffsetUtf16>(cx);
20863 let range = selection.range();
20864
20865 Some(UTF16Selection {
20866 range: range.start.0..range.end.0,
20867 reversed: selection.reversed,
20868 })
20869 }
20870
20871 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
20872 let snapshot = self.buffer.read(cx).read(cx);
20873 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
20874 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
20875 }
20876
20877 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20878 self.clear_highlights::<InputComposition>(cx);
20879 self.ime_transaction.take();
20880 }
20881
20882 fn replace_text_in_range(
20883 &mut self,
20884 range_utf16: Option<Range<usize>>,
20885 text: &str,
20886 window: &mut Window,
20887 cx: &mut Context<Self>,
20888 ) {
20889 if !self.input_enabled {
20890 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20891 return;
20892 }
20893
20894 self.transact(window, cx, |this, window, cx| {
20895 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
20896 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20897 Some(this.selection_replacement_ranges(range_utf16, cx))
20898 } else {
20899 this.marked_text_ranges(cx)
20900 };
20901
20902 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
20903 let newest_selection_id = this.selections.newest_anchor().id;
20904 this.selections
20905 .all::<OffsetUtf16>(cx)
20906 .iter()
20907 .zip(ranges_to_replace.iter())
20908 .find_map(|(selection, range)| {
20909 if selection.id == newest_selection_id {
20910 Some(
20911 (range.start.0 as isize - selection.head().0 as isize)
20912 ..(range.end.0 as isize - selection.head().0 as isize),
20913 )
20914 } else {
20915 None
20916 }
20917 })
20918 });
20919
20920 cx.emit(EditorEvent::InputHandled {
20921 utf16_range_to_replace: range_to_replace,
20922 text: text.into(),
20923 });
20924
20925 if let Some(new_selected_ranges) = new_selected_ranges {
20926 this.change_selections(None, window, cx, |selections| {
20927 selections.select_ranges(new_selected_ranges)
20928 });
20929 this.backspace(&Default::default(), window, cx);
20930 }
20931
20932 this.handle_input(text, window, cx);
20933 });
20934
20935 if let Some(transaction) = self.ime_transaction {
20936 self.buffer.update(cx, |buffer, cx| {
20937 buffer.group_until_transaction(transaction, cx);
20938 });
20939 }
20940
20941 self.unmark_text(window, cx);
20942 }
20943
20944 fn replace_and_mark_text_in_range(
20945 &mut self,
20946 range_utf16: Option<Range<usize>>,
20947 text: &str,
20948 new_selected_range_utf16: Option<Range<usize>>,
20949 window: &mut Window,
20950 cx: &mut Context<Self>,
20951 ) {
20952 if !self.input_enabled {
20953 return;
20954 }
20955
20956 let transaction = self.transact(window, cx, |this, window, cx| {
20957 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
20958 let snapshot = this.buffer.read(cx).read(cx);
20959 if let Some(relative_range_utf16) = range_utf16.as_ref() {
20960 for marked_range in &mut marked_ranges {
20961 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
20962 marked_range.start.0 += relative_range_utf16.start;
20963 marked_range.start =
20964 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
20965 marked_range.end =
20966 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
20967 }
20968 }
20969 Some(marked_ranges)
20970 } else if let Some(range_utf16) = range_utf16 {
20971 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20972 Some(this.selection_replacement_ranges(range_utf16, cx))
20973 } else {
20974 None
20975 };
20976
20977 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
20978 let newest_selection_id = this.selections.newest_anchor().id;
20979 this.selections
20980 .all::<OffsetUtf16>(cx)
20981 .iter()
20982 .zip(ranges_to_replace.iter())
20983 .find_map(|(selection, range)| {
20984 if selection.id == newest_selection_id {
20985 Some(
20986 (range.start.0 as isize - selection.head().0 as isize)
20987 ..(range.end.0 as isize - selection.head().0 as isize),
20988 )
20989 } else {
20990 None
20991 }
20992 })
20993 });
20994
20995 cx.emit(EditorEvent::InputHandled {
20996 utf16_range_to_replace: range_to_replace,
20997 text: text.into(),
20998 });
20999
21000 if let Some(ranges) = ranges_to_replace {
21001 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
21002 }
21003
21004 let marked_ranges = {
21005 let snapshot = this.buffer.read(cx).read(cx);
21006 this.selections
21007 .disjoint_anchors()
21008 .iter()
21009 .map(|selection| {
21010 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
21011 })
21012 .collect::<Vec<_>>()
21013 };
21014
21015 if text.is_empty() {
21016 this.unmark_text(window, cx);
21017 } else {
21018 this.highlight_text::<InputComposition>(
21019 marked_ranges.clone(),
21020 HighlightStyle {
21021 underline: Some(UnderlineStyle {
21022 thickness: px(1.),
21023 color: None,
21024 wavy: false,
21025 }),
21026 ..Default::default()
21027 },
21028 cx,
21029 );
21030 }
21031
21032 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
21033 let use_autoclose = this.use_autoclose;
21034 let use_auto_surround = this.use_auto_surround;
21035 this.set_use_autoclose(false);
21036 this.set_use_auto_surround(false);
21037 this.handle_input(text, window, cx);
21038 this.set_use_autoclose(use_autoclose);
21039 this.set_use_auto_surround(use_auto_surround);
21040
21041 if let Some(new_selected_range) = new_selected_range_utf16 {
21042 let snapshot = this.buffer.read(cx).read(cx);
21043 let new_selected_ranges = marked_ranges
21044 .into_iter()
21045 .map(|marked_range| {
21046 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
21047 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
21048 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
21049 snapshot.clip_offset_utf16(new_start, Bias::Left)
21050 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
21051 })
21052 .collect::<Vec<_>>();
21053
21054 drop(snapshot);
21055 this.change_selections(None, window, cx, |selections| {
21056 selections.select_ranges(new_selected_ranges)
21057 });
21058 }
21059 });
21060
21061 self.ime_transaction = self.ime_transaction.or(transaction);
21062 if let Some(transaction) = self.ime_transaction {
21063 self.buffer.update(cx, |buffer, cx| {
21064 buffer.group_until_transaction(transaction, cx);
21065 });
21066 }
21067
21068 if self.text_highlights::<InputComposition>(cx).is_none() {
21069 self.ime_transaction.take();
21070 }
21071 }
21072
21073 fn bounds_for_range(
21074 &mut self,
21075 range_utf16: Range<usize>,
21076 element_bounds: gpui::Bounds<Pixels>,
21077 window: &mut Window,
21078 cx: &mut Context<Self>,
21079 ) -> Option<gpui::Bounds<Pixels>> {
21080 let text_layout_details = self.text_layout_details(window);
21081 let gpui::Size {
21082 width: em_width,
21083 height: line_height,
21084 } = self.character_size(window);
21085
21086 let snapshot = self.snapshot(window, cx);
21087 let scroll_position = snapshot.scroll_position();
21088 let scroll_left = scroll_position.x * em_width;
21089
21090 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
21091 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
21092 + self.gutter_dimensions.width
21093 + self.gutter_dimensions.margin;
21094 let y = line_height * (start.row().as_f32() - scroll_position.y);
21095
21096 Some(Bounds {
21097 origin: element_bounds.origin + point(x, y),
21098 size: size(em_width, line_height),
21099 })
21100 }
21101
21102 fn character_index_for_point(
21103 &mut self,
21104 point: gpui::Point<Pixels>,
21105 _window: &mut Window,
21106 _cx: &mut Context<Self>,
21107 ) -> Option<usize> {
21108 let position_map = self.last_position_map.as_ref()?;
21109 if !position_map.text_hitbox.contains(&point) {
21110 return None;
21111 }
21112 let display_point = position_map.point_for_position(point).previous_valid;
21113 let anchor = position_map
21114 .snapshot
21115 .display_point_to_anchor(display_point, Bias::Left);
21116 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
21117 Some(utf16_offset.0)
21118 }
21119}
21120
21121trait SelectionExt {
21122 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
21123 fn spanned_rows(
21124 &self,
21125 include_end_if_at_line_start: bool,
21126 map: &DisplaySnapshot,
21127 ) -> Range<MultiBufferRow>;
21128}
21129
21130impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
21131 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
21132 let start = self
21133 .start
21134 .to_point(&map.buffer_snapshot)
21135 .to_display_point(map);
21136 let end = self
21137 .end
21138 .to_point(&map.buffer_snapshot)
21139 .to_display_point(map);
21140 if self.reversed {
21141 end..start
21142 } else {
21143 start..end
21144 }
21145 }
21146
21147 fn spanned_rows(
21148 &self,
21149 include_end_if_at_line_start: bool,
21150 map: &DisplaySnapshot,
21151 ) -> Range<MultiBufferRow> {
21152 let start = self.start.to_point(&map.buffer_snapshot);
21153 let mut end = self.end.to_point(&map.buffer_snapshot);
21154 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
21155 end.row -= 1;
21156 }
21157
21158 let buffer_start = map.prev_line_boundary(start).0;
21159 let buffer_end = map.next_line_boundary(end).0;
21160 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
21161 }
21162}
21163
21164impl<T: InvalidationRegion> InvalidationStack<T> {
21165 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
21166 where
21167 S: Clone + ToOffset,
21168 {
21169 while let Some(region) = self.last() {
21170 let all_selections_inside_invalidation_ranges =
21171 if selections.len() == region.ranges().len() {
21172 selections
21173 .iter()
21174 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
21175 .all(|(selection, invalidation_range)| {
21176 let head = selection.head().to_offset(buffer);
21177 invalidation_range.start <= head && invalidation_range.end >= head
21178 })
21179 } else {
21180 false
21181 };
21182
21183 if all_selections_inside_invalidation_ranges {
21184 break;
21185 } else {
21186 self.pop();
21187 }
21188 }
21189 }
21190}
21191
21192impl<T> Default for InvalidationStack<T> {
21193 fn default() -> Self {
21194 Self(Default::default())
21195 }
21196}
21197
21198impl<T> Deref for InvalidationStack<T> {
21199 type Target = Vec<T>;
21200
21201 fn deref(&self) -> &Self::Target {
21202 &self.0
21203 }
21204}
21205
21206impl<T> DerefMut for InvalidationStack<T> {
21207 fn deref_mut(&mut self) -> &mut Self::Target {
21208 &mut self.0
21209 }
21210}
21211
21212impl InvalidationRegion for SnippetState {
21213 fn ranges(&self) -> &[Range<Anchor>] {
21214 &self.ranges[self.active_index]
21215 }
21216}
21217
21218fn inline_completion_edit_text(
21219 current_snapshot: &BufferSnapshot,
21220 edits: &[(Range<Anchor>, String)],
21221 edit_preview: &EditPreview,
21222 include_deletions: bool,
21223 cx: &App,
21224) -> HighlightedText {
21225 let edits = edits
21226 .iter()
21227 .map(|(anchor, text)| {
21228 (
21229 anchor.start.text_anchor..anchor.end.text_anchor,
21230 text.clone(),
21231 )
21232 })
21233 .collect::<Vec<_>>();
21234
21235 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
21236}
21237
21238pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
21239 match severity {
21240 lsp::DiagnosticSeverity::ERROR => colors.error,
21241 lsp::DiagnosticSeverity::WARNING => colors.warning,
21242 lsp::DiagnosticSeverity::INFORMATION => colors.info,
21243 lsp::DiagnosticSeverity::HINT => colors.info,
21244 _ => colors.ignored,
21245 }
21246}
21247
21248pub fn styled_runs_for_code_label<'a>(
21249 label: &'a CodeLabel,
21250 syntax_theme: &'a theme::SyntaxTheme,
21251) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
21252 let fade_out = HighlightStyle {
21253 fade_out: Some(0.35),
21254 ..Default::default()
21255 };
21256
21257 let mut prev_end = label.filter_range.end;
21258 label
21259 .runs
21260 .iter()
21261 .enumerate()
21262 .flat_map(move |(ix, (range, highlight_id))| {
21263 let style = if let Some(style) = highlight_id.style(syntax_theme) {
21264 style
21265 } else {
21266 return Default::default();
21267 };
21268 let mut muted_style = style;
21269 muted_style.highlight(fade_out);
21270
21271 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
21272 if range.start >= label.filter_range.end {
21273 if range.start > prev_end {
21274 runs.push((prev_end..range.start, fade_out));
21275 }
21276 runs.push((range.clone(), muted_style));
21277 } else if range.end <= label.filter_range.end {
21278 runs.push((range.clone(), style));
21279 } else {
21280 runs.push((range.start..label.filter_range.end, style));
21281 runs.push((label.filter_range.end..range.end, muted_style));
21282 }
21283 prev_end = cmp::max(prev_end, range.end);
21284
21285 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
21286 runs.push((prev_end..label.text.len(), fade_out));
21287 }
21288
21289 runs
21290 })
21291}
21292
21293pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
21294 let mut prev_index = 0;
21295 let mut prev_codepoint: Option<char> = None;
21296 text.char_indices()
21297 .chain([(text.len(), '\0')])
21298 .filter_map(move |(index, codepoint)| {
21299 let prev_codepoint = prev_codepoint.replace(codepoint)?;
21300 let is_boundary = index == text.len()
21301 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
21302 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
21303 if is_boundary {
21304 let chunk = &text[prev_index..index];
21305 prev_index = index;
21306 Some(chunk)
21307 } else {
21308 None
21309 }
21310 })
21311}
21312
21313pub trait RangeToAnchorExt: Sized {
21314 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
21315
21316 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
21317 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
21318 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
21319 }
21320}
21321
21322impl<T: ToOffset> RangeToAnchorExt for Range<T> {
21323 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
21324 let start_offset = self.start.to_offset(snapshot);
21325 let end_offset = self.end.to_offset(snapshot);
21326 if start_offset == end_offset {
21327 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
21328 } else {
21329 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
21330 }
21331 }
21332}
21333
21334pub trait RowExt {
21335 fn as_f32(&self) -> f32;
21336
21337 fn next_row(&self) -> Self;
21338
21339 fn previous_row(&self) -> Self;
21340
21341 fn minus(&self, other: Self) -> u32;
21342}
21343
21344impl RowExt for DisplayRow {
21345 fn as_f32(&self) -> f32 {
21346 self.0 as f32
21347 }
21348
21349 fn next_row(&self) -> Self {
21350 Self(self.0 + 1)
21351 }
21352
21353 fn previous_row(&self) -> Self {
21354 Self(self.0.saturating_sub(1))
21355 }
21356
21357 fn minus(&self, other: Self) -> u32 {
21358 self.0 - other.0
21359 }
21360}
21361
21362impl RowExt for MultiBufferRow {
21363 fn as_f32(&self) -> f32 {
21364 self.0 as f32
21365 }
21366
21367 fn next_row(&self) -> Self {
21368 Self(self.0 + 1)
21369 }
21370
21371 fn previous_row(&self) -> Self {
21372 Self(self.0.saturating_sub(1))
21373 }
21374
21375 fn minus(&self, other: Self) -> u32 {
21376 self.0 - other.0
21377 }
21378}
21379
21380trait RowRangeExt {
21381 type Row;
21382
21383 fn len(&self) -> usize;
21384
21385 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
21386}
21387
21388impl RowRangeExt for Range<MultiBufferRow> {
21389 type Row = MultiBufferRow;
21390
21391 fn len(&self) -> usize {
21392 (self.end.0 - self.start.0) as usize
21393 }
21394
21395 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21396 (self.start.0..self.end.0).map(MultiBufferRow)
21397 }
21398}
21399
21400impl RowRangeExt for Range<DisplayRow> {
21401 type Row = DisplayRow;
21402
21403 fn len(&self) -> usize {
21404 (self.end.0 - self.start.0) as usize
21405 }
21406
21407 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21408 (self.start.0..self.end.0).map(DisplayRow)
21409 }
21410}
21411
21412/// If select range has more than one line, we
21413/// just point the cursor to range.start.
21414fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21415 if range.start.row == range.end.row {
21416 range
21417 } else {
21418 range.start..range.start
21419 }
21420}
21421pub struct KillRing(ClipboardItem);
21422impl Global for KillRing {}
21423
21424const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21425
21426enum BreakpointPromptEditAction {
21427 Log,
21428 Condition,
21429 HitCondition,
21430}
21431
21432struct BreakpointPromptEditor {
21433 pub(crate) prompt: Entity<Editor>,
21434 editor: WeakEntity<Editor>,
21435 breakpoint_anchor: Anchor,
21436 breakpoint: Breakpoint,
21437 edit_action: BreakpointPromptEditAction,
21438 block_ids: HashSet<CustomBlockId>,
21439 editor_margins: Arc<Mutex<EditorMargins>>,
21440 _subscriptions: Vec<Subscription>,
21441}
21442
21443impl BreakpointPromptEditor {
21444 const MAX_LINES: u8 = 4;
21445
21446 fn new(
21447 editor: WeakEntity<Editor>,
21448 breakpoint_anchor: Anchor,
21449 breakpoint: Breakpoint,
21450 edit_action: BreakpointPromptEditAction,
21451 window: &mut Window,
21452 cx: &mut Context<Self>,
21453 ) -> Self {
21454 let base_text = match edit_action {
21455 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21456 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21457 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21458 }
21459 .map(|msg| msg.to_string())
21460 .unwrap_or_default();
21461
21462 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21463 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21464
21465 let prompt = cx.new(|cx| {
21466 let mut prompt = Editor::new(
21467 EditorMode::AutoHeight {
21468 max_lines: Self::MAX_LINES as usize,
21469 },
21470 buffer,
21471 None,
21472 window,
21473 cx,
21474 );
21475 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21476 prompt.set_show_cursor_when_unfocused(false, cx);
21477 prompt.set_placeholder_text(
21478 match edit_action {
21479 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21480 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21481 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21482 },
21483 cx,
21484 );
21485
21486 prompt
21487 });
21488
21489 Self {
21490 prompt,
21491 editor,
21492 breakpoint_anchor,
21493 breakpoint,
21494 edit_action,
21495 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21496 block_ids: Default::default(),
21497 _subscriptions: vec![],
21498 }
21499 }
21500
21501 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21502 self.block_ids.extend(block_ids)
21503 }
21504
21505 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21506 if let Some(editor) = self.editor.upgrade() {
21507 let message = self
21508 .prompt
21509 .read(cx)
21510 .buffer
21511 .read(cx)
21512 .as_singleton()
21513 .expect("A multi buffer in breakpoint prompt isn't possible")
21514 .read(cx)
21515 .as_rope()
21516 .to_string();
21517
21518 editor.update(cx, |editor, cx| {
21519 editor.edit_breakpoint_at_anchor(
21520 self.breakpoint_anchor,
21521 self.breakpoint.clone(),
21522 match self.edit_action {
21523 BreakpointPromptEditAction::Log => {
21524 BreakpointEditAction::EditLogMessage(message.into())
21525 }
21526 BreakpointPromptEditAction::Condition => {
21527 BreakpointEditAction::EditCondition(message.into())
21528 }
21529 BreakpointPromptEditAction::HitCondition => {
21530 BreakpointEditAction::EditHitCondition(message.into())
21531 }
21532 },
21533 cx,
21534 );
21535
21536 editor.remove_blocks(self.block_ids.clone(), None, cx);
21537 cx.focus_self(window);
21538 });
21539 }
21540 }
21541
21542 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21543 self.editor
21544 .update(cx, |editor, cx| {
21545 editor.remove_blocks(self.block_ids.clone(), None, cx);
21546 window.focus(&editor.focus_handle);
21547 })
21548 .log_err();
21549 }
21550
21551 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21552 let settings = ThemeSettings::get_global(cx);
21553 let text_style = TextStyle {
21554 color: if self.prompt.read(cx).read_only(cx) {
21555 cx.theme().colors().text_disabled
21556 } else {
21557 cx.theme().colors().text
21558 },
21559 font_family: settings.buffer_font.family.clone(),
21560 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21561 font_size: settings.buffer_font_size(cx).into(),
21562 font_weight: settings.buffer_font.weight,
21563 line_height: relative(settings.buffer_line_height.value()),
21564 ..Default::default()
21565 };
21566 EditorElement::new(
21567 &self.prompt,
21568 EditorStyle {
21569 background: cx.theme().colors().editor_background,
21570 local_player: cx.theme().players().local(),
21571 text: text_style,
21572 ..Default::default()
21573 },
21574 )
21575 }
21576}
21577
21578impl Render for BreakpointPromptEditor {
21579 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21580 let editor_margins = *self.editor_margins.lock();
21581 let gutter_dimensions = editor_margins.gutter;
21582 h_flex()
21583 .key_context("Editor")
21584 .bg(cx.theme().colors().editor_background)
21585 .border_y_1()
21586 .border_color(cx.theme().status().info_border)
21587 .size_full()
21588 .py(window.line_height() / 2.5)
21589 .on_action(cx.listener(Self::confirm))
21590 .on_action(cx.listener(Self::cancel))
21591 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21592 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21593 }
21594}
21595
21596impl Focusable for BreakpointPromptEditor {
21597 fn focus_handle(&self, cx: &App) -> FocusHandle {
21598 self.prompt.focus_handle(cx)
21599 }
21600}
21601
21602fn all_edits_insertions_or_deletions(
21603 edits: &Vec<(Range<Anchor>, String)>,
21604 snapshot: &MultiBufferSnapshot,
21605) -> bool {
21606 let mut all_insertions = true;
21607 let mut all_deletions = true;
21608
21609 for (range, new_text) in edits.iter() {
21610 let range_is_empty = range.to_offset(&snapshot).is_empty();
21611 let text_is_empty = new_text.is_empty();
21612
21613 if range_is_empty != text_is_empty {
21614 if range_is_empty {
21615 all_deletions = false;
21616 } else {
21617 all_insertions = false;
21618 }
21619 } else {
21620 return false;
21621 }
21622
21623 if !all_insertions && !all_deletions {
21624 return false;
21625 }
21626 }
21627 all_insertions || all_deletions
21628}
21629
21630struct MissingEditPredictionKeybindingTooltip;
21631
21632impl Render for MissingEditPredictionKeybindingTooltip {
21633 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21634 ui::tooltip_container(window, cx, |container, _, cx| {
21635 container
21636 .flex_shrink_0()
21637 .max_w_80()
21638 .min_h(rems_from_px(124.))
21639 .justify_between()
21640 .child(
21641 v_flex()
21642 .flex_1()
21643 .text_ui_sm(cx)
21644 .child(Label::new("Conflict with Accept Keybinding"))
21645 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21646 )
21647 .child(
21648 h_flex()
21649 .pb_1()
21650 .gap_1()
21651 .items_end()
21652 .w_full()
21653 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21654 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21655 }))
21656 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21657 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21658 })),
21659 )
21660 })
21661 }
21662}
21663
21664#[derive(Debug, Clone, Copy, PartialEq)]
21665pub struct LineHighlight {
21666 pub background: Background,
21667 pub border: Option<gpui::Hsla>,
21668 pub include_gutter: bool,
21669 pub type_id: Option<TypeId>,
21670}
21671
21672fn render_diff_hunk_controls(
21673 row: u32,
21674 status: &DiffHunkStatus,
21675 hunk_range: Range<Anchor>,
21676 is_created_file: bool,
21677 line_height: Pixels,
21678 editor: &Entity<Editor>,
21679 _window: &mut Window,
21680 cx: &mut App,
21681) -> AnyElement {
21682 h_flex()
21683 .h(line_height)
21684 .mr_1()
21685 .gap_1()
21686 .px_0p5()
21687 .pb_1()
21688 .border_x_1()
21689 .border_b_1()
21690 .border_color(cx.theme().colors().border_variant)
21691 .rounded_b_lg()
21692 .bg(cx.theme().colors().editor_background)
21693 .gap_1()
21694 .occlude()
21695 .shadow_md()
21696 .child(if status.has_secondary_hunk() {
21697 Button::new(("stage", row as u64), "Stage")
21698 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21699 .tooltip({
21700 let focus_handle = editor.focus_handle(cx);
21701 move |window, cx| {
21702 Tooltip::for_action_in(
21703 "Stage Hunk",
21704 &::git::ToggleStaged,
21705 &focus_handle,
21706 window,
21707 cx,
21708 )
21709 }
21710 })
21711 .on_click({
21712 let editor = editor.clone();
21713 move |_event, _window, cx| {
21714 editor.update(cx, |editor, cx| {
21715 editor.stage_or_unstage_diff_hunks(
21716 true,
21717 vec![hunk_range.start..hunk_range.start],
21718 cx,
21719 );
21720 });
21721 }
21722 })
21723 } else {
21724 Button::new(("unstage", row as u64), "Unstage")
21725 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21726 .tooltip({
21727 let focus_handle = editor.focus_handle(cx);
21728 move |window, cx| {
21729 Tooltip::for_action_in(
21730 "Unstage Hunk",
21731 &::git::ToggleStaged,
21732 &focus_handle,
21733 window,
21734 cx,
21735 )
21736 }
21737 })
21738 .on_click({
21739 let editor = editor.clone();
21740 move |_event, _window, cx| {
21741 editor.update(cx, |editor, cx| {
21742 editor.stage_or_unstage_diff_hunks(
21743 false,
21744 vec![hunk_range.start..hunk_range.start],
21745 cx,
21746 );
21747 });
21748 }
21749 })
21750 })
21751 .child(
21752 Button::new(("restore", row as u64), "Restore")
21753 .tooltip({
21754 let focus_handle = editor.focus_handle(cx);
21755 move |window, cx| {
21756 Tooltip::for_action_in(
21757 "Restore Hunk",
21758 &::git::Restore,
21759 &focus_handle,
21760 window,
21761 cx,
21762 )
21763 }
21764 })
21765 .on_click({
21766 let editor = editor.clone();
21767 move |_event, window, cx| {
21768 editor.update(cx, |editor, cx| {
21769 let snapshot = editor.snapshot(window, cx);
21770 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
21771 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
21772 });
21773 }
21774 })
21775 .disabled(is_created_file),
21776 )
21777 .when(
21778 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
21779 |el| {
21780 el.child(
21781 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
21782 .shape(IconButtonShape::Square)
21783 .icon_size(IconSize::Small)
21784 // .disabled(!has_multiple_hunks)
21785 .tooltip({
21786 let focus_handle = editor.focus_handle(cx);
21787 move |window, cx| {
21788 Tooltip::for_action_in(
21789 "Next Hunk",
21790 &GoToHunk,
21791 &focus_handle,
21792 window,
21793 cx,
21794 )
21795 }
21796 })
21797 .on_click({
21798 let editor = editor.clone();
21799 move |_event, window, cx| {
21800 editor.update(cx, |editor, cx| {
21801 let snapshot = editor.snapshot(window, cx);
21802 let position =
21803 hunk_range.end.to_point(&snapshot.buffer_snapshot);
21804 editor.go_to_hunk_before_or_after_position(
21805 &snapshot,
21806 position,
21807 Direction::Next,
21808 window,
21809 cx,
21810 );
21811 editor.expand_selected_diff_hunks(cx);
21812 });
21813 }
21814 }),
21815 )
21816 .child(
21817 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
21818 .shape(IconButtonShape::Square)
21819 .icon_size(IconSize::Small)
21820 // .disabled(!has_multiple_hunks)
21821 .tooltip({
21822 let focus_handle = editor.focus_handle(cx);
21823 move |window, cx| {
21824 Tooltip::for_action_in(
21825 "Previous Hunk",
21826 &GoToPreviousHunk,
21827 &focus_handle,
21828 window,
21829 cx,
21830 )
21831 }
21832 })
21833 .on_click({
21834 let editor = editor.clone();
21835 move |_event, window, cx| {
21836 editor.update(cx, |editor, cx| {
21837 let snapshot = editor.snapshot(window, cx);
21838 let point =
21839 hunk_range.start.to_point(&snapshot.buffer_snapshot);
21840 editor.go_to_hunk_before_or_after_position(
21841 &snapshot,
21842 point,
21843 Direction::Prev,
21844 window,
21845 cx,
21846 );
21847 editor.expand_selected_diff_hunks(cx);
21848 });
21849 }
21850 }),
21851 )
21852 },
21853 )
21854 .into_any_element()
21855}