1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod editor_tests;
46#[cfg(test)]
47mod inline_completion_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
54use aho_corasick::AhoCorasick;
55use anyhow::{Context as _, Result, anyhow};
56use blink_manager::BlinkManager;
57use buffer_diff::DiffHunkStatus;
58use client::{Collaborator, ParticipantIndex};
59use clock::{AGENT_REPLICA_ID, ReplicaId};
60use collections::{BTreeMap, HashMap, HashSet, VecDeque};
61use convert_case::{Case, Casing};
62use dap::TelemetrySpawnLocation;
63use display_map::*;
64pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
65pub use editor_settings::{
66 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes,
67 SearchSettings, 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, StreamExt as _,
78 future::{self, Shared, join},
79 stream::FuturesUnordered,
80};
81use fuzzy::{StringMatch, StringMatchCandidate};
82
83use ::git::blame::BlameEntry;
84use ::git::{Restore, blame::ParsedCommitMessage};
85use code_context_menus::{
86 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
87 CompletionsMenu, ContextMenuOrigin,
88};
89use git::blame::{GitBlame, GlobalBlameRenderer};
90use gpui::{
91 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
92 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
93 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
94 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
95 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
96 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
97 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
98 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
99};
100use highlight_matching_bracket::refresh_matching_bracket_highlights;
101use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
102pub use hover_popover::hover_markdown_style;
103use hover_popover::{HoverState, hide_hover};
104use indent_guides::ActiveIndentGuidesState;
105use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
106pub use inline_completion::Direction;
107use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
108pub use items::MAX_TAB_TITLE_LEN;
109use itertools::Itertools;
110use language::{
111 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
112 CursorShape, DiagnosticEntry, DiagnosticSourceKind, DiffOptions, DocumentationConfig,
113 EditPredictionsMode, EditPreview, HighlightedText, IndentKind, IndentSize, Language,
114 OffsetRangeExt, Point, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions,
115 WordsQuery,
116 language_settings::{
117 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
118 all_language_settings, language_settings,
119 },
120 point_from_lsp, text_diff_with_options,
121};
122use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
123use linked_editing_ranges::refresh_linked_ranges;
124use markdown::Markdown;
125use mouse_context_menu::MouseContextMenu;
126use persistence::DB;
127use project::{
128 BreakpointWithPosition, CompletionResponse, LspPullDiagnostics, ProjectPath, PulledDiagnostics,
129 debugger::{
130 breakpoint_store::{
131 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
132 BreakpointStoreEvent,
133 },
134 session::{Session, SessionEvent},
135 },
136 project_settings::DiagnosticSeverity,
137};
138
139pub use git::blame::BlameRenderer;
140pub use proposed_changes_editor::{
141 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
142};
143use std::{cell::OnceCell, iter::Peekable, ops::Not};
144use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
145
146pub use lsp::CompletionContext;
147use lsp::{
148 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
149 LanguageServerId, LanguageServerName,
150};
151
152use language::BufferSnapshot;
153pub use lsp_ext::lsp_tasks;
154use movement::TextLayoutDetails;
155pub use multi_buffer::{
156 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
157 RowInfo, ToOffset, ToPoint,
158};
159use multi_buffer::{
160 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
161 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
162};
163use parking_lot::Mutex;
164use project::{
165 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
166 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
167 TaskSourceKind,
168 debugger::breakpoint_store::Breakpoint,
169 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
170 project_settings::{GitGutterSetting, ProjectSettings},
171};
172use rand::prelude::*;
173use rpc::{ErrorExt, proto::*};
174use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
175use selections_collection::{
176 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
177};
178use serde::{Deserialize, Serialize};
179use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
180use smallvec::{SmallVec, smallvec};
181use snippet::Snippet;
182use std::sync::Arc;
183use std::{
184 any::TypeId,
185 borrow::Cow,
186 cell::RefCell,
187 cmp::{self, Ordering, Reverse},
188 mem,
189 num::NonZeroU32,
190 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
191 path::{Path, PathBuf},
192 rc::Rc,
193 time::{Duration, Instant},
194};
195pub use sum_tree::Bias;
196use sum_tree::TreeMap;
197use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
198use theme::{
199 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
200 observe_buffer_font_size_adjustment,
201};
202use ui::{
203 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
204 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
205};
206use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
207use workspace::{
208 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
209 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
210 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
211 item::{ItemHandle, PreviewTabsSettings},
212 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
213 searchable::SearchEvent,
214};
215
216use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
217use crate::{
218 code_context_menus::CompletionsMenuSource,
219 hover_links::{find_url, find_url_from_range},
220};
221
222pub const FILE_HEADER_HEIGHT: u32 = 2;
223pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
224pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
225const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
226const MAX_LINE_LEN: usize = 1024;
227const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
228const MAX_SELECTION_HISTORY_LEN: usize = 1024;
229pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
230#[doc(hidden)]
231pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
232const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
233
234pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
235pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
236pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
237
238pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
239pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
240pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
241pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
242
243pub type RenderDiffHunkControlsFn = Arc<
244 dyn Fn(
245 u32,
246 &DiffHunkStatus,
247 Range<Anchor>,
248 bool,
249 Pixels,
250 &Entity<Editor>,
251 &mut Window,
252 &mut App,
253 ) -> AnyElement,
254>;
255
256const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
257 alt: true,
258 shift: true,
259 control: false,
260 platform: false,
261 function: false,
262};
263
264struct InlineValueCache {
265 enabled: bool,
266 inlays: Vec<InlayId>,
267 refresh_task: Task<Option<()>>,
268}
269
270impl InlineValueCache {
271 fn new(enabled: bool) -> Self {
272 Self {
273 enabled,
274 inlays: Vec::new(),
275 refresh_task: Task::ready(None),
276 }
277 }
278}
279
280#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
281pub enum InlayId {
282 InlineCompletion(usize),
283 Hint(usize),
284 DebuggerValue(usize),
285}
286
287impl InlayId {
288 fn id(&self) -> usize {
289 match self {
290 Self::InlineCompletion(id) => *id,
291 Self::Hint(id) => *id,
292 Self::DebuggerValue(id) => *id,
293 }
294 }
295}
296
297pub enum ActiveDebugLine {}
298pub enum DebugStackFrameLine {}
299enum DocumentHighlightRead {}
300enum DocumentHighlightWrite {}
301enum InputComposition {}
302enum SelectedTextHighlight {}
303
304pub enum ConflictsOuter {}
305pub enum ConflictsOurs {}
306pub enum ConflictsTheirs {}
307pub enum ConflictsOursMarker {}
308pub enum ConflictsTheirsMarker {}
309
310#[derive(Debug, Copy, Clone, PartialEq, Eq)]
311pub enum Navigated {
312 Yes,
313 No,
314}
315
316impl Navigated {
317 pub fn from_bool(yes: bool) -> Navigated {
318 if yes { Navigated::Yes } else { Navigated::No }
319 }
320}
321
322#[derive(Debug, Clone, PartialEq, Eq)]
323enum DisplayDiffHunk {
324 Folded {
325 display_row: DisplayRow,
326 },
327 Unfolded {
328 is_created_file: bool,
329 diff_base_byte_range: Range<usize>,
330 display_row_range: Range<DisplayRow>,
331 multi_buffer_range: Range<Anchor>,
332 status: DiffHunkStatus,
333 },
334}
335
336pub enum HideMouseCursorOrigin {
337 TypingAction,
338 MovementAction,
339}
340
341pub fn init_settings(cx: &mut App) {
342 EditorSettings::register(cx);
343}
344
345pub fn init(cx: &mut App) {
346 init_settings(cx);
347
348 cx.set_global(GlobalBlameRenderer(Arc::new(())));
349
350 workspace::register_project_item::<Editor>(cx);
351 workspace::FollowableViewRegistry::register::<Editor>(cx);
352 workspace::register_serializable_item::<Editor>(cx);
353
354 cx.observe_new(
355 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
356 workspace.register_action(Editor::new_file);
357 workspace.register_action(Editor::new_file_vertical);
358 workspace.register_action(Editor::new_file_horizontal);
359 workspace.register_action(Editor::cancel_language_server_work);
360 },
361 )
362 .detach();
363
364 cx.on_action(move |_: &workspace::NewFile, cx| {
365 let app_state = workspace::AppState::global(cx);
366 if let Some(app_state) = app_state.upgrade() {
367 workspace::open_new(
368 Default::default(),
369 app_state,
370 cx,
371 |workspace, window, cx| {
372 Editor::new_file(workspace, &Default::default(), window, cx)
373 },
374 )
375 .detach();
376 }
377 });
378 cx.on_action(move |_: &workspace::NewWindow, cx| {
379 let app_state = workspace::AppState::global(cx);
380 if let Some(app_state) = app_state.upgrade() {
381 workspace::open_new(
382 Default::default(),
383 app_state,
384 cx,
385 |workspace, window, cx| {
386 cx.activate(true);
387 Editor::new_file(workspace, &Default::default(), window, cx)
388 },
389 )
390 .detach();
391 }
392 });
393}
394
395pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
396 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
397}
398
399pub trait DiagnosticRenderer {
400 fn render_group(
401 &self,
402 diagnostic_group: Vec<DiagnosticEntry<Point>>,
403 buffer_id: BufferId,
404 snapshot: EditorSnapshot,
405 editor: WeakEntity<Editor>,
406 cx: &mut App,
407 ) -> Vec<BlockProperties<Anchor>>;
408
409 fn render_hover(
410 &self,
411 diagnostic_group: Vec<DiagnosticEntry<Point>>,
412 range: Range<Point>,
413 buffer_id: BufferId,
414 cx: &mut App,
415 ) -> Option<Entity<markdown::Markdown>>;
416
417 fn open_link(
418 &self,
419 editor: &mut Editor,
420 link: SharedString,
421 window: &mut Window,
422 cx: &mut Context<Editor>,
423 );
424}
425
426pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
427
428impl GlobalDiagnosticRenderer {
429 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
430 cx.try_global::<Self>().map(|g| g.0.clone())
431 }
432}
433
434impl gpui::Global for GlobalDiagnosticRenderer {}
435pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
436 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
437}
438
439pub struct SearchWithinRange;
440
441trait InvalidationRegion {
442 fn ranges(&self) -> &[Range<Anchor>];
443}
444
445#[derive(Clone, Debug, PartialEq)]
446pub enum SelectPhase {
447 Begin {
448 position: DisplayPoint,
449 add: bool,
450 click_count: usize,
451 },
452 BeginColumnar {
453 position: DisplayPoint,
454 reset: bool,
455 goal_column: u32,
456 },
457 Extend {
458 position: DisplayPoint,
459 click_count: usize,
460 },
461 Update {
462 position: DisplayPoint,
463 goal_column: u32,
464 scroll_delta: gpui::Point<f32>,
465 },
466 End,
467}
468
469#[derive(Clone, Debug)]
470pub enum SelectMode {
471 Character,
472 Word(Range<Anchor>),
473 Line(Range<Anchor>),
474 All,
475}
476
477#[derive(Clone, PartialEq, Eq, Debug)]
478pub enum EditorMode {
479 SingleLine {
480 auto_width: bool,
481 },
482 AutoHeight {
483 max_lines: usize,
484 },
485 Full {
486 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
487 scale_ui_elements_with_buffer_font_size: bool,
488 /// When set to `true`, the editor will render a background for the active line.
489 show_active_line_background: bool,
490 /// When set to `true`, the editor's height will be determined by its content.
491 sized_by_content: bool,
492 },
493 Minimap {
494 parent: WeakEntity<Editor>,
495 },
496}
497
498impl EditorMode {
499 pub fn full() -> Self {
500 Self::Full {
501 scale_ui_elements_with_buffer_font_size: true,
502 show_active_line_background: true,
503 sized_by_content: false,
504 }
505 }
506
507 pub fn is_full(&self) -> bool {
508 matches!(self, Self::Full { .. })
509 }
510
511 fn is_minimap(&self) -> bool {
512 matches!(self, Self::Minimap { .. })
513 }
514}
515
516#[derive(Copy, Clone, Debug)]
517pub enum SoftWrap {
518 /// Prefer not to wrap at all.
519 ///
520 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
521 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
522 GitDiff,
523 /// Prefer a single line generally, unless an overly long line is encountered.
524 None,
525 /// Soft wrap lines that exceed the editor width.
526 EditorWidth,
527 /// Soft wrap lines at the preferred line length.
528 Column(u32),
529 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
530 Bounded(u32),
531}
532
533#[derive(Clone)]
534pub struct EditorStyle {
535 pub background: Hsla,
536 pub local_player: PlayerColor,
537 pub text: TextStyle,
538 pub scrollbar_width: Pixels,
539 pub syntax: Arc<SyntaxTheme>,
540 pub status: StatusColors,
541 pub inlay_hints_style: HighlightStyle,
542 pub inline_completion_styles: InlineCompletionStyles,
543 pub unnecessary_code_fade: f32,
544 pub show_underlines: bool,
545}
546
547impl Default for EditorStyle {
548 fn default() -> Self {
549 Self {
550 background: Hsla::default(),
551 local_player: PlayerColor::default(),
552 text: TextStyle::default(),
553 scrollbar_width: Pixels::default(),
554 syntax: Default::default(),
555 // HACK: Status colors don't have a real default.
556 // We should look into removing the status colors from the editor
557 // style and retrieve them directly from the theme.
558 status: StatusColors::dark(),
559 inlay_hints_style: HighlightStyle::default(),
560 inline_completion_styles: InlineCompletionStyles {
561 insertion: HighlightStyle::default(),
562 whitespace: HighlightStyle::default(),
563 },
564 unnecessary_code_fade: Default::default(),
565 show_underlines: true,
566 }
567 }
568}
569
570pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
571 let show_background = language_settings::language_settings(None, None, cx)
572 .inlay_hints
573 .show_background;
574
575 HighlightStyle {
576 color: Some(cx.theme().status().hint),
577 background_color: show_background.then(|| cx.theme().status().hint_background),
578 ..HighlightStyle::default()
579 }
580}
581
582pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
583 InlineCompletionStyles {
584 insertion: HighlightStyle {
585 color: Some(cx.theme().status().predictive),
586 ..HighlightStyle::default()
587 },
588 whitespace: HighlightStyle {
589 background_color: Some(cx.theme().status().created_background),
590 ..HighlightStyle::default()
591 },
592 }
593}
594
595type CompletionId = usize;
596
597pub(crate) enum EditDisplayMode {
598 TabAccept,
599 DiffPopover,
600 Inline,
601}
602
603enum InlineCompletion {
604 Edit {
605 edits: Vec<(Range<Anchor>, String)>,
606 edit_preview: Option<EditPreview>,
607 display_mode: EditDisplayMode,
608 snapshot: BufferSnapshot,
609 },
610 Move {
611 target: Anchor,
612 snapshot: BufferSnapshot,
613 },
614}
615
616struct InlineCompletionState {
617 inlay_ids: Vec<InlayId>,
618 completion: InlineCompletion,
619 completion_id: Option<SharedString>,
620 invalidation_range: Range<Anchor>,
621}
622
623enum EditPredictionSettings {
624 Disabled,
625 Enabled {
626 show_in_menu: bool,
627 preview_requires_modifier: bool,
628 },
629}
630
631enum InlineCompletionHighlight {}
632
633#[derive(Debug, Clone)]
634struct InlineDiagnostic {
635 message: SharedString,
636 group_id: usize,
637 is_primary: bool,
638 start: Point,
639 severity: lsp::DiagnosticSeverity,
640}
641
642pub enum MenuInlineCompletionsPolicy {
643 Never,
644 ByProvider,
645}
646
647pub enum EditPredictionPreview {
648 /// Modifier is not pressed
649 Inactive { released_too_fast: bool },
650 /// Modifier pressed
651 Active {
652 since: Instant,
653 previous_scroll_position: Option<ScrollAnchor>,
654 },
655}
656
657impl EditPredictionPreview {
658 pub fn released_too_fast(&self) -> bool {
659 match self {
660 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
661 EditPredictionPreview::Active { .. } => false,
662 }
663 }
664
665 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
666 if let EditPredictionPreview::Active {
667 previous_scroll_position,
668 ..
669 } = self
670 {
671 *previous_scroll_position = scroll_position;
672 }
673 }
674}
675
676pub struct ContextMenuOptions {
677 pub min_entries_visible: usize,
678 pub max_entries_visible: usize,
679 pub placement: Option<ContextMenuPlacement>,
680}
681
682#[derive(Debug, Clone, PartialEq, Eq)]
683pub enum ContextMenuPlacement {
684 Above,
685 Below,
686}
687
688#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
689struct EditorActionId(usize);
690
691impl EditorActionId {
692 pub fn post_inc(&mut self) -> Self {
693 let answer = self.0;
694
695 *self = Self(answer + 1);
696
697 Self(answer)
698 }
699}
700
701// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
702// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
703
704type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
705type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
706
707#[derive(Default)]
708struct ScrollbarMarkerState {
709 scrollbar_size: Size<Pixels>,
710 dirty: bool,
711 markers: Arc<[PaintQuad]>,
712 pending_refresh: Option<Task<Result<()>>>,
713}
714
715impl ScrollbarMarkerState {
716 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
717 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
718 }
719}
720
721#[derive(Clone, Copy, PartialEq, Eq)]
722pub enum MinimapVisibility {
723 Disabled,
724 Enabled {
725 /// The configuration currently present in the users settings.
726 setting_configuration: bool,
727 /// Whether to override the currently set visibility from the users setting.
728 toggle_override: bool,
729 },
730}
731
732impl MinimapVisibility {
733 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
734 if mode.is_full() {
735 Self::Enabled {
736 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
737 toggle_override: false,
738 }
739 } else {
740 Self::Disabled
741 }
742 }
743
744 fn hidden(&self) -> Self {
745 match *self {
746 Self::Enabled {
747 setting_configuration,
748 ..
749 } => Self::Enabled {
750 setting_configuration,
751 toggle_override: setting_configuration,
752 },
753 Self::Disabled => Self::Disabled,
754 }
755 }
756
757 fn disabled(&self) -> bool {
758 match *self {
759 Self::Disabled => true,
760 _ => false,
761 }
762 }
763
764 fn settings_visibility(&self) -> bool {
765 match *self {
766 Self::Enabled {
767 setting_configuration,
768 ..
769 } => setting_configuration,
770 _ => false,
771 }
772 }
773
774 fn visible(&self) -> bool {
775 match *self {
776 Self::Enabled {
777 setting_configuration,
778 toggle_override,
779 } => setting_configuration ^ toggle_override,
780 _ => false,
781 }
782 }
783
784 fn toggle_visibility(&self) -> Self {
785 match *self {
786 Self::Enabled {
787 toggle_override,
788 setting_configuration,
789 } => Self::Enabled {
790 setting_configuration,
791 toggle_override: !toggle_override,
792 },
793 Self::Disabled => Self::Disabled,
794 }
795 }
796}
797
798#[derive(Clone, Debug)]
799struct RunnableTasks {
800 templates: Vec<(TaskSourceKind, TaskTemplate)>,
801 offset: multi_buffer::Anchor,
802 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
803 column: u32,
804 // Values of all named captures, including those starting with '_'
805 extra_variables: HashMap<String, String>,
806 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
807 context_range: Range<BufferOffset>,
808}
809
810impl RunnableTasks {
811 fn resolve<'a>(
812 &'a self,
813 cx: &'a task::TaskContext,
814 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
815 self.templates.iter().filter_map(|(kind, template)| {
816 template
817 .resolve_task(&kind.to_id_base(), cx)
818 .map(|task| (kind.clone(), task))
819 })
820 }
821}
822
823#[derive(Clone)]
824pub struct ResolvedTasks {
825 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
826 position: Anchor,
827}
828
829#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
830struct BufferOffset(usize);
831
832// Addons allow storing per-editor state in other crates (e.g. Vim)
833pub trait Addon: 'static {
834 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
835
836 fn render_buffer_header_controls(
837 &self,
838 _: &ExcerptInfo,
839 _: &Window,
840 _: &App,
841 ) -> Option<AnyElement> {
842 None
843 }
844
845 fn to_any(&self) -> &dyn std::any::Any;
846
847 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
848 None
849 }
850}
851
852/// A set of caret positions, registered when the editor was edited.
853pub struct ChangeList {
854 changes: Vec<Vec<Anchor>>,
855 /// Currently "selected" change.
856 position: Option<usize>,
857}
858
859impl ChangeList {
860 pub fn new() -> Self {
861 Self {
862 changes: Vec::new(),
863 position: None,
864 }
865 }
866
867 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
868 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
869 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
870 if self.changes.is_empty() {
871 return None;
872 }
873
874 let prev = self.position.unwrap_or(self.changes.len());
875 let next = if direction == Direction::Prev {
876 prev.saturating_sub(count)
877 } else {
878 (prev + count).min(self.changes.len() - 1)
879 };
880 self.position = Some(next);
881 self.changes.get(next).map(|anchors| anchors.as_slice())
882 }
883
884 /// Adds a new change to the list, resetting the change list position.
885 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
886 self.position.take();
887 if pop_state {
888 self.changes.pop();
889 }
890 self.changes.push(new_positions.clone());
891 }
892
893 pub fn last(&self) -> Option<&[Anchor]> {
894 self.changes.last().map(|anchors| anchors.as_slice())
895 }
896}
897
898#[derive(Clone)]
899struct InlineBlamePopoverState {
900 scroll_handle: ScrollHandle,
901 commit_message: Option<ParsedCommitMessage>,
902 markdown: Entity<Markdown>,
903}
904
905struct InlineBlamePopover {
906 position: gpui::Point<Pixels>,
907 show_task: Option<Task<()>>,
908 hide_task: Option<Task<()>>,
909 popover_bounds: Option<Bounds<Pixels>>,
910 popover_state: InlineBlamePopoverState,
911}
912
913/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
914/// a breakpoint on them.
915#[derive(Clone, Copy, Debug)]
916struct PhantomBreakpointIndicator {
917 display_row: DisplayRow,
918 /// There's a small debounce between hovering over the line and showing the indicator.
919 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
920 is_active: bool,
921 collides_with_existing_breakpoint: bool,
922}
923/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
924///
925/// See the [module level documentation](self) for more information.
926pub struct Editor {
927 focus_handle: FocusHandle,
928 last_focused_descendant: Option<WeakFocusHandle>,
929 /// The text buffer being edited
930 buffer: Entity<MultiBuffer>,
931 /// Map of how text in the buffer should be displayed.
932 /// Handles soft wraps, folds, fake inlay text insertions, etc.
933 pub display_map: Entity<DisplayMap>,
934 pub selections: SelectionsCollection,
935 pub scroll_manager: ScrollManager,
936 /// When inline assist editors are linked, they all render cursors because
937 /// typing enters text into each of them, even the ones that aren't focused.
938 pub(crate) show_cursor_when_unfocused: bool,
939 columnar_selection_tail: Option<Anchor>,
940 columnar_display_point: Option<DisplayPoint>,
941 add_selections_state: Option<AddSelectionsState>,
942 select_next_state: Option<SelectNextState>,
943 select_prev_state: Option<SelectNextState>,
944 selection_history: SelectionHistory,
945 defer_selection_effects: bool,
946 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
947 autoclose_regions: Vec<AutocloseRegion>,
948 snippet_stack: InvalidationStack<SnippetState>,
949 select_syntax_node_history: SelectSyntaxNodeHistory,
950 ime_transaction: Option<TransactionId>,
951 pub diagnostics_max_severity: DiagnosticSeverity,
952 active_diagnostics: ActiveDiagnostic,
953 show_inline_diagnostics: bool,
954 inline_diagnostics_update: Task<()>,
955 inline_diagnostics_enabled: bool,
956 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
957 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
958 hard_wrap: Option<usize>,
959
960 // TODO: make this a access method
961 pub project: Option<Entity<Project>>,
962 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
963 completion_provider: Option<Rc<dyn CompletionProvider>>,
964 collaboration_hub: Option<Box<dyn CollaborationHub>>,
965 blink_manager: Entity<BlinkManager>,
966 show_cursor_names: bool,
967 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
968 pub show_local_selections: bool,
969 mode: EditorMode,
970 show_breadcrumbs: bool,
971 show_gutter: bool,
972 show_scrollbars: ScrollbarAxes,
973 minimap_visibility: MinimapVisibility,
974 offset_content: bool,
975 disable_expand_excerpt_buttons: bool,
976 show_line_numbers: Option<bool>,
977 use_relative_line_numbers: Option<bool>,
978 show_git_diff_gutter: Option<bool>,
979 show_code_actions: Option<bool>,
980 show_runnables: Option<bool>,
981 show_breakpoints: Option<bool>,
982 show_wrap_guides: Option<bool>,
983 show_indent_guides: Option<bool>,
984 placeholder_text: Option<Arc<str>>,
985 highlight_order: usize,
986 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
987 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
988 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
989 scrollbar_marker_state: ScrollbarMarkerState,
990 active_indent_guides_state: ActiveIndentGuidesState,
991 nav_history: Option<ItemNavHistory>,
992 context_menu: RefCell<Option<CodeContextMenu>>,
993 context_menu_options: Option<ContextMenuOptions>,
994 mouse_context_menu: Option<MouseContextMenu>,
995 completion_tasks: Vec<(CompletionId, Task<()>)>,
996 inline_blame_popover: Option<InlineBlamePopover>,
997 signature_help_state: SignatureHelpState,
998 auto_signature_help: Option<bool>,
999 find_all_references_task_sources: Vec<Anchor>,
1000 next_completion_id: CompletionId,
1001 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1002 code_actions_task: Option<Task<Result<()>>>,
1003 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1004 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1005 document_highlights_task: Option<Task<()>>,
1006 linked_editing_range_task: Option<Task<Option<()>>>,
1007 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1008 pending_rename: Option<RenameState>,
1009 searchable: bool,
1010 cursor_shape: CursorShape,
1011 current_line_highlight: Option<CurrentLineHighlight>,
1012 collapse_matches: bool,
1013 autoindent_mode: Option<AutoindentMode>,
1014 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1015 input_enabled: bool,
1016 use_modal_editing: bool,
1017 read_only: bool,
1018 leader_id: Option<CollaboratorId>,
1019 remote_id: Option<ViewId>,
1020 pub hover_state: HoverState,
1021 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1022 gutter_hovered: bool,
1023 hovered_link_state: Option<HoveredLinkState>,
1024 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1025 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1026 active_inline_completion: Option<InlineCompletionState>,
1027 /// Used to prevent flickering as the user types while the menu is open
1028 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1029 edit_prediction_settings: EditPredictionSettings,
1030 inline_completions_hidden_for_vim_mode: bool,
1031 show_inline_completions_override: Option<bool>,
1032 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1033 edit_prediction_preview: EditPredictionPreview,
1034 edit_prediction_indent_conflict: bool,
1035 edit_prediction_requires_modifier_in_indent_conflict: bool,
1036 inlay_hint_cache: InlayHintCache,
1037 next_inlay_id: usize,
1038 _subscriptions: Vec<Subscription>,
1039 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1040 gutter_dimensions: GutterDimensions,
1041 style: Option<EditorStyle>,
1042 text_style_refinement: Option<TextStyleRefinement>,
1043 next_editor_action_id: EditorActionId,
1044 editor_actions:
1045 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
1046 use_autoclose: bool,
1047 use_auto_surround: bool,
1048 auto_replace_emoji_shortcode: bool,
1049 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1050 show_git_blame_gutter: bool,
1051 show_git_blame_inline: bool,
1052 show_git_blame_inline_delay_task: Option<Task<()>>,
1053 git_blame_inline_enabled: bool,
1054 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1055 serialize_dirty_buffers: bool,
1056 show_selection_menu: Option<bool>,
1057 blame: Option<Entity<GitBlame>>,
1058 blame_subscription: Option<Subscription>,
1059 custom_context_menu: Option<
1060 Box<
1061 dyn 'static
1062 + Fn(
1063 &mut Self,
1064 DisplayPoint,
1065 &mut Window,
1066 &mut Context<Self>,
1067 ) -> Option<Entity<ui::ContextMenu>>,
1068 >,
1069 >,
1070 last_bounds: Option<Bounds<Pixels>>,
1071 last_position_map: Option<Rc<PositionMap>>,
1072 expect_bounds_change: Option<Bounds<Pixels>>,
1073 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1074 tasks_update_task: Option<Task<()>>,
1075 breakpoint_store: Option<Entity<BreakpointStore>>,
1076 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1077 pull_diagnostics_task: Task<()>,
1078 in_project_search: bool,
1079 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1080 breadcrumb_header: Option<String>,
1081 focused_block: Option<FocusedBlock>,
1082 next_scroll_position: NextScrollCursorCenterTopBottom,
1083 addons: HashMap<TypeId, Box<dyn Addon>>,
1084 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1085 load_diff_task: Option<Shared<Task<()>>>,
1086 /// Whether we are temporarily displaying a diff other than git's
1087 temporary_diff_override: bool,
1088 selection_mark_mode: bool,
1089 toggle_fold_multiple_buffers: Task<()>,
1090 _scroll_cursor_center_top_bottom_task: Task<()>,
1091 serialize_selections: Task<()>,
1092 serialize_folds: Task<()>,
1093 mouse_cursor_hidden: bool,
1094 minimap: Option<Entity<Self>>,
1095 hide_mouse_mode: HideMouseMode,
1096 pub change_list: ChangeList,
1097 inline_value_cache: InlineValueCache,
1098}
1099
1100#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1101enum NextScrollCursorCenterTopBottom {
1102 #[default]
1103 Center,
1104 Top,
1105 Bottom,
1106}
1107
1108impl NextScrollCursorCenterTopBottom {
1109 fn next(&self) -> Self {
1110 match self {
1111 Self::Center => Self::Top,
1112 Self::Top => Self::Bottom,
1113 Self::Bottom => Self::Center,
1114 }
1115 }
1116}
1117
1118#[derive(Clone)]
1119pub struct EditorSnapshot {
1120 pub mode: EditorMode,
1121 show_gutter: bool,
1122 show_line_numbers: Option<bool>,
1123 show_git_diff_gutter: Option<bool>,
1124 show_code_actions: Option<bool>,
1125 show_runnables: Option<bool>,
1126 show_breakpoints: Option<bool>,
1127 git_blame_gutter_max_author_length: Option<usize>,
1128 pub display_snapshot: DisplaySnapshot,
1129 pub placeholder_text: Option<Arc<str>>,
1130 is_focused: bool,
1131 scroll_anchor: ScrollAnchor,
1132 ongoing_scroll: OngoingScroll,
1133 current_line_highlight: CurrentLineHighlight,
1134 gutter_hovered: bool,
1135}
1136
1137#[derive(Default, Debug, Clone, Copy)]
1138pub struct GutterDimensions {
1139 pub left_padding: Pixels,
1140 pub right_padding: Pixels,
1141 pub width: Pixels,
1142 pub margin: Pixels,
1143 pub git_blame_entries_width: Option<Pixels>,
1144}
1145
1146impl GutterDimensions {
1147 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1148 Self {
1149 margin: Self::default_gutter_margin(font_id, font_size, cx),
1150 ..Default::default()
1151 }
1152 }
1153
1154 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1155 -cx.text_system().descent(font_id, font_size)
1156 }
1157 /// The full width of the space taken up by the gutter.
1158 pub fn full_width(&self) -> Pixels {
1159 self.margin + self.width
1160 }
1161
1162 /// The width of the space reserved for the fold indicators,
1163 /// use alongside 'justify_end' and `gutter_width` to
1164 /// right align content with the line numbers
1165 pub fn fold_area_width(&self) -> Pixels {
1166 self.margin + self.right_padding
1167 }
1168}
1169
1170#[derive(Debug)]
1171pub struct RemoteSelection {
1172 pub replica_id: ReplicaId,
1173 pub selection: Selection<Anchor>,
1174 pub cursor_shape: CursorShape,
1175 pub collaborator_id: CollaboratorId,
1176 pub line_mode: bool,
1177 pub user_name: Option<SharedString>,
1178 pub color: PlayerColor,
1179}
1180
1181#[derive(Clone, Debug)]
1182struct SelectionHistoryEntry {
1183 selections: Arc<[Selection<Anchor>]>,
1184 select_next_state: Option<SelectNextState>,
1185 select_prev_state: Option<SelectNextState>,
1186 add_selections_state: Option<AddSelectionsState>,
1187}
1188
1189enum SelectionHistoryMode {
1190 Normal,
1191 Undoing,
1192 Redoing,
1193}
1194
1195#[derive(Clone, PartialEq, Eq, Hash)]
1196struct HoveredCursor {
1197 replica_id: u16,
1198 selection_id: usize,
1199}
1200
1201impl Default for SelectionHistoryMode {
1202 fn default() -> Self {
1203 Self::Normal
1204 }
1205}
1206
1207struct DeferredSelectionEffectsState {
1208 changed: bool,
1209 should_update_completions: bool,
1210 autoscroll: Option<Autoscroll>,
1211 old_cursor_position: Anchor,
1212 history_entry: SelectionHistoryEntry,
1213}
1214
1215#[derive(Default)]
1216struct SelectionHistory {
1217 #[allow(clippy::type_complexity)]
1218 selections_by_transaction:
1219 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1220 mode: SelectionHistoryMode,
1221 undo_stack: VecDeque<SelectionHistoryEntry>,
1222 redo_stack: VecDeque<SelectionHistoryEntry>,
1223}
1224
1225impl SelectionHistory {
1226 fn insert_transaction(
1227 &mut self,
1228 transaction_id: TransactionId,
1229 selections: Arc<[Selection<Anchor>]>,
1230 ) {
1231 self.selections_by_transaction
1232 .insert(transaction_id, (selections, None));
1233 }
1234
1235 #[allow(clippy::type_complexity)]
1236 fn transaction(
1237 &self,
1238 transaction_id: TransactionId,
1239 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1240 self.selections_by_transaction.get(&transaction_id)
1241 }
1242
1243 #[allow(clippy::type_complexity)]
1244 fn transaction_mut(
1245 &mut self,
1246 transaction_id: TransactionId,
1247 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1248 self.selections_by_transaction.get_mut(&transaction_id)
1249 }
1250
1251 fn push(&mut self, entry: SelectionHistoryEntry) {
1252 if !entry.selections.is_empty() {
1253 match self.mode {
1254 SelectionHistoryMode::Normal => {
1255 self.push_undo(entry);
1256 self.redo_stack.clear();
1257 }
1258 SelectionHistoryMode::Undoing => self.push_redo(entry),
1259 SelectionHistoryMode::Redoing => self.push_undo(entry),
1260 }
1261 }
1262 }
1263
1264 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1265 if self
1266 .undo_stack
1267 .back()
1268 .map_or(true, |e| e.selections != entry.selections)
1269 {
1270 self.undo_stack.push_back(entry);
1271 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1272 self.undo_stack.pop_front();
1273 }
1274 }
1275 }
1276
1277 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1278 if self
1279 .redo_stack
1280 .back()
1281 .map_or(true, |e| e.selections != entry.selections)
1282 {
1283 self.redo_stack.push_back(entry);
1284 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1285 self.redo_stack.pop_front();
1286 }
1287 }
1288 }
1289}
1290
1291#[derive(Clone, Copy)]
1292pub struct RowHighlightOptions {
1293 pub autoscroll: bool,
1294 pub include_gutter: bool,
1295}
1296
1297impl Default for RowHighlightOptions {
1298 fn default() -> Self {
1299 Self {
1300 autoscroll: Default::default(),
1301 include_gutter: true,
1302 }
1303 }
1304}
1305
1306struct RowHighlight {
1307 index: usize,
1308 range: Range<Anchor>,
1309 color: Hsla,
1310 options: RowHighlightOptions,
1311 type_id: TypeId,
1312}
1313
1314#[derive(Clone, Debug)]
1315struct AddSelectionsState {
1316 groups: Vec<AddSelectionsGroup>,
1317}
1318
1319#[derive(Clone, Debug)]
1320struct AddSelectionsGroup {
1321 above: bool,
1322 stack: Vec<usize>,
1323}
1324
1325#[derive(Clone)]
1326struct SelectNextState {
1327 query: AhoCorasick,
1328 wordwise: bool,
1329 done: bool,
1330}
1331
1332impl std::fmt::Debug for SelectNextState {
1333 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1334 f.debug_struct(std::any::type_name::<Self>())
1335 .field("wordwise", &self.wordwise)
1336 .field("done", &self.done)
1337 .finish()
1338 }
1339}
1340
1341#[derive(Debug)]
1342struct AutocloseRegion {
1343 selection_id: usize,
1344 range: Range<Anchor>,
1345 pair: BracketPair,
1346}
1347
1348#[derive(Debug)]
1349struct SnippetState {
1350 ranges: Vec<Vec<Range<Anchor>>>,
1351 active_index: usize,
1352 choices: Vec<Option<Vec<String>>>,
1353}
1354
1355#[doc(hidden)]
1356pub struct RenameState {
1357 pub range: Range<Anchor>,
1358 pub old_name: Arc<str>,
1359 pub editor: Entity<Editor>,
1360 block_id: CustomBlockId,
1361}
1362
1363struct InvalidationStack<T>(Vec<T>);
1364
1365struct RegisteredInlineCompletionProvider {
1366 provider: Arc<dyn InlineCompletionProviderHandle>,
1367 _subscription: Subscription,
1368}
1369
1370#[derive(Debug, PartialEq, Eq)]
1371pub struct ActiveDiagnosticGroup {
1372 pub active_range: Range<Anchor>,
1373 pub active_message: String,
1374 pub group_id: usize,
1375 pub blocks: HashSet<CustomBlockId>,
1376}
1377
1378#[derive(Debug, PartialEq, Eq)]
1379
1380pub(crate) enum ActiveDiagnostic {
1381 None,
1382 All,
1383 Group(ActiveDiagnosticGroup),
1384}
1385
1386#[derive(Serialize, Deserialize, Clone, Debug)]
1387pub struct ClipboardSelection {
1388 /// The number of bytes in this selection.
1389 pub len: usize,
1390 /// Whether this was a full-line selection.
1391 pub is_entire_line: bool,
1392 /// The indentation of the first line when this content was originally copied.
1393 pub first_line_indent: u32,
1394}
1395
1396// selections, scroll behavior, was newest selection reversed
1397type SelectSyntaxNodeHistoryState = (
1398 Box<[Selection<usize>]>,
1399 SelectSyntaxNodeScrollBehavior,
1400 bool,
1401);
1402
1403#[derive(Default)]
1404struct SelectSyntaxNodeHistory {
1405 stack: Vec<SelectSyntaxNodeHistoryState>,
1406 // disable temporarily to allow changing selections without losing the stack
1407 pub disable_clearing: bool,
1408}
1409
1410impl SelectSyntaxNodeHistory {
1411 pub fn try_clear(&mut self) {
1412 if !self.disable_clearing {
1413 self.stack.clear();
1414 }
1415 }
1416
1417 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1418 self.stack.push(selection);
1419 }
1420
1421 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1422 self.stack.pop()
1423 }
1424}
1425
1426enum SelectSyntaxNodeScrollBehavior {
1427 CursorTop,
1428 FitSelection,
1429 CursorBottom,
1430}
1431
1432#[derive(Debug)]
1433pub(crate) struct NavigationData {
1434 cursor_anchor: Anchor,
1435 cursor_position: Point,
1436 scroll_anchor: ScrollAnchor,
1437 scroll_top_row: u32,
1438}
1439
1440#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1441pub enum GotoDefinitionKind {
1442 Symbol,
1443 Declaration,
1444 Type,
1445 Implementation,
1446}
1447
1448#[derive(Debug, Clone)]
1449enum InlayHintRefreshReason {
1450 ModifiersChanged(bool),
1451 Toggle(bool),
1452 SettingsChange(InlayHintSettings),
1453 NewLinesShown,
1454 BufferEdited(HashSet<Arc<Language>>),
1455 RefreshRequested,
1456 ExcerptsRemoved(Vec<ExcerptId>),
1457}
1458
1459impl InlayHintRefreshReason {
1460 fn description(&self) -> &'static str {
1461 match self {
1462 Self::ModifiersChanged(_) => "modifiers changed",
1463 Self::Toggle(_) => "toggle",
1464 Self::SettingsChange(_) => "settings change",
1465 Self::NewLinesShown => "new lines shown",
1466 Self::BufferEdited(_) => "buffer edited",
1467 Self::RefreshRequested => "refresh requested",
1468 Self::ExcerptsRemoved(_) => "excerpts removed",
1469 }
1470 }
1471}
1472
1473pub enum FormatTarget {
1474 Buffers,
1475 Ranges(Vec<Range<MultiBufferPoint>>),
1476}
1477
1478pub(crate) struct FocusedBlock {
1479 id: BlockId,
1480 focus_handle: WeakFocusHandle,
1481}
1482
1483#[derive(Clone)]
1484enum JumpData {
1485 MultiBufferRow {
1486 row: MultiBufferRow,
1487 line_offset_from_top: u32,
1488 },
1489 MultiBufferPoint {
1490 excerpt_id: ExcerptId,
1491 position: Point,
1492 anchor: text::Anchor,
1493 line_offset_from_top: u32,
1494 },
1495}
1496
1497pub enum MultibufferSelectionMode {
1498 First,
1499 All,
1500}
1501
1502#[derive(Clone, Copy, Debug, Default)]
1503pub struct RewrapOptions {
1504 pub override_language_settings: bool,
1505 pub preserve_existing_whitespace: bool,
1506}
1507
1508impl Editor {
1509 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1510 let buffer = cx.new(|cx| Buffer::local("", cx));
1511 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1512 Self::new(
1513 EditorMode::SingleLine { auto_width: false },
1514 buffer,
1515 None,
1516 window,
1517 cx,
1518 )
1519 }
1520
1521 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1522 let buffer = cx.new(|cx| Buffer::local("", cx));
1523 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1524 Self::new(EditorMode::full(), buffer, None, window, cx)
1525 }
1526
1527 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1528 let buffer = cx.new(|cx| Buffer::local("", cx));
1529 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1530 Self::new(
1531 EditorMode::SingleLine { auto_width: true },
1532 buffer,
1533 None,
1534 window,
1535 cx,
1536 )
1537 }
1538
1539 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1540 let buffer = cx.new(|cx| Buffer::local("", cx));
1541 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1542 Self::new(
1543 EditorMode::AutoHeight { max_lines },
1544 buffer,
1545 None,
1546 window,
1547 cx,
1548 )
1549 }
1550
1551 pub fn for_buffer(
1552 buffer: Entity<Buffer>,
1553 project: Option<Entity<Project>>,
1554 window: &mut Window,
1555 cx: &mut Context<Self>,
1556 ) -> Self {
1557 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1558 Self::new(EditorMode::full(), buffer, project, window, cx)
1559 }
1560
1561 pub fn for_multibuffer(
1562 buffer: Entity<MultiBuffer>,
1563 project: Option<Entity<Project>>,
1564 window: &mut Window,
1565 cx: &mut Context<Self>,
1566 ) -> Self {
1567 Self::new(EditorMode::full(), buffer, project, window, cx)
1568 }
1569
1570 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1571 let mut clone = Self::new(
1572 self.mode.clone(),
1573 self.buffer.clone(),
1574 self.project.clone(),
1575 window,
1576 cx,
1577 );
1578 self.display_map.update(cx, |display_map, cx| {
1579 let snapshot = display_map.snapshot(cx);
1580 clone.display_map.update(cx, |display_map, cx| {
1581 display_map.set_state(&snapshot, cx);
1582 });
1583 });
1584 clone.folds_did_change(cx);
1585 clone.selections.clone_state(&self.selections);
1586 clone.scroll_manager.clone_state(&self.scroll_manager);
1587 clone.searchable = self.searchable;
1588 clone.read_only = self.read_only;
1589 clone
1590 }
1591
1592 pub fn new(
1593 mode: EditorMode,
1594 buffer: Entity<MultiBuffer>,
1595 project: Option<Entity<Project>>,
1596 window: &mut Window,
1597 cx: &mut Context<Self>,
1598 ) -> Self {
1599 Editor::new_internal(mode, buffer, project, None, window, cx)
1600 }
1601
1602 fn new_internal(
1603 mode: EditorMode,
1604 buffer: Entity<MultiBuffer>,
1605 project: Option<Entity<Project>>,
1606 display_map: Option<Entity<DisplayMap>>,
1607 window: &mut Window,
1608 cx: &mut Context<Self>,
1609 ) -> Self {
1610 debug_assert!(
1611 display_map.is_none() || mode.is_minimap(),
1612 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1613 );
1614
1615 let full_mode = mode.is_full();
1616 let diagnostics_max_severity = if full_mode {
1617 EditorSettings::get_global(cx)
1618 .diagnostics_max_severity
1619 .unwrap_or(DiagnosticSeverity::Hint)
1620 } else {
1621 DiagnosticSeverity::Off
1622 };
1623 let style = window.text_style();
1624 let font_size = style.font_size.to_pixels(window.rem_size());
1625 let editor = cx.entity().downgrade();
1626 let fold_placeholder = FoldPlaceholder {
1627 constrain_width: true,
1628 render: Arc::new(move |fold_id, fold_range, cx| {
1629 let editor = editor.clone();
1630 div()
1631 .id(fold_id)
1632 .bg(cx.theme().colors().ghost_element_background)
1633 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1634 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1635 .rounded_xs()
1636 .size_full()
1637 .cursor_pointer()
1638 .child("⋯")
1639 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1640 .on_click(move |_, _window, cx| {
1641 editor
1642 .update(cx, |editor, cx| {
1643 editor.unfold_ranges(
1644 &[fold_range.start..fold_range.end],
1645 true,
1646 false,
1647 cx,
1648 );
1649 cx.stop_propagation();
1650 })
1651 .ok();
1652 })
1653 .into_any()
1654 }),
1655 merge_adjacent: true,
1656 ..FoldPlaceholder::default()
1657 };
1658 let display_map = display_map.unwrap_or_else(|| {
1659 cx.new(|cx| {
1660 DisplayMap::new(
1661 buffer.clone(),
1662 style.font(),
1663 font_size,
1664 None,
1665 FILE_HEADER_HEIGHT,
1666 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1667 fold_placeholder,
1668 diagnostics_max_severity,
1669 cx,
1670 )
1671 })
1672 });
1673
1674 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1675
1676 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1677
1678 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1679 .then(|| language_settings::SoftWrap::None);
1680
1681 let mut project_subscriptions = Vec::new();
1682 if mode.is_full() {
1683 if let Some(project) = project.as_ref() {
1684 project_subscriptions.push(cx.subscribe_in(
1685 project,
1686 window,
1687 |editor, _, event, window, cx| match event {
1688 project::Event::RefreshCodeLens => {
1689 // we always query lens with actions, without storing them, always refreshing them
1690 }
1691 project::Event::RefreshInlayHints => {
1692 editor
1693 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1694 }
1695 project::Event::LanguageServerAdded(..)
1696 | project::Event::LanguageServerRemoved(..) => {
1697 if editor.tasks_update_task.is_none() {
1698 editor.tasks_update_task =
1699 Some(editor.refresh_runnables(window, cx));
1700 }
1701 editor.pull_diagnostics(window, cx);
1702 }
1703 project::Event::PullWorkspaceDiagnostics => {
1704 editor.pull_diagnostics(window, cx);
1705 }
1706 project::Event::SnippetEdit(id, snippet_edits) => {
1707 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1708 let focus_handle = editor.focus_handle(cx);
1709 if focus_handle.is_focused(window) {
1710 let snapshot = buffer.read(cx).snapshot();
1711 for (range, snippet) in snippet_edits {
1712 let editor_range =
1713 language::range_from_lsp(*range).to_offset(&snapshot);
1714 editor
1715 .insert_snippet(
1716 &[editor_range],
1717 snippet.clone(),
1718 window,
1719 cx,
1720 )
1721 .ok();
1722 }
1723 }
1724 }
1725 }
1726 _ => {}
1727 },
1728 ));
1729 if let Some(task_inventory) = project
1730 .read(cx)
1731 .task_store()
1732 .read(cx)
1733 .task_inventory()
1734 .cloned()
1735 {
1736 project_subscriptions.push(cx.observe_in(
1737 &task_inventory,
1738 window,
1739 |editor, _, window, cx| {
1740 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1741 },
1742 ));
1743 };
1744
1745 project_subscriptions.push(cx.subscribe_in(
1746 &project.read(cx).breakpoint_store(),
1747 window,
1748 |editor, _, event, window, cx| match event {
1749 BreakpointStoreEvent::ClearDebugLines => {
1750 editor.clear_row_highlights::<ActiveDebugLine>();
1751 editor.refresh_inline_values(cx);
1752 }
1753 BreakpointStoreEvent::SetDebugLine => {
1754 if editor.go_to_active_debug_line(window, cx) {
1755 cx.stop_propagation();
1756 }
1757
1758 editor.refresh_inline_values(cx);
1759 }
1760 _ => {}
1761 },
1762 ));
1763 }
1764 }
1765
1766 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1767
1768 let inlay_hint_settings =
1769 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1770 let focus_handle = cx.focus_handle();
1771 cx.on_focus(&focus_handle, window, Self::handle_focus)
1772 .detach();
1773 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1774 .detach();
1775 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1776 .detach();
1777 cx.on_blur(&focus_handle, window, Self::handle_blur)
1778 .detach();
1779
1780 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1781 Some(false)
1782 } else {
1783 None
1784 };
1785
1786 let breakpoint_store = match (&mode, project.as_ref()) {
1787 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1788 _ => None,
1789 };
1790
1791 let mut code_action_providers = Vec::new();
1792 let mut load_uncommitted_diff = None;
1793 if let Some(project) = project.clone() {
1794 load_uncommitted_diff = Some(
1795 update_uncommitted_diff_for_buffer(
1796 cx.entity(),
1797 &project,
1798 buffer.read(cx).all_buffers(),
1799 buffer.clone(),
1800 cx,
1801 )
1802 .shared(),
1803 );
1804 code_action_providers.push(Rc::new(project) as Rc<_>);
1805 }
1806
1807 let mut editor = Self {
1808 focus_handle,
1809 show_cursor_when_unfocused: false,
1810 last_focused_descendant: None,
1811 buffer: buffer.clone(),
1812 display_map: display_map.clone(),
1813 selections,
1814 scroll_manager: ScrollManager::new(cx),
1815 columnar_selection_tail: None,
1816 columnar_display_point: None,
1817 add_selections_state: None,
1818 select_next_state: None,
1819 select_prev_state: None,
1820 selection_history: SelectionHistory::default(),
1821 defer_selection_effects: false,
1822 deferred_selection_effects_state: None,
1823 autoclose_regions: Vec::new(),
1824 snippet_stack: InvalidationStack::default(),
1825 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1826 ime_transaction: None,
1827 active_diagnostics: ActiveDiagnostic::None,
1828 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1829 inline_diagnostics_update: Task::ready(()),
1830 inline_diagnostics: Vec::new(),
1831 soft_wrap_mode_override,
1832 diagnostics_max_severity,
1833 hard_wrap: None,
1834 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1835 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1836 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1837 project,
1838 blink_manager: blink_manager.clone(),
1839 show_local_selections: true,
1840 show_scrollbars: ScrollbarAxes {
1841 horizontal: full_mode,
1842 vertical: full_mode,
1843 },
1844 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1845 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1846 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1847 show_gutter: mode.is_full(),
1848 show_line_numbers: None,
1849 use_relative_line_numbers: None,
1850 disable_expand_excerpt_buttons: false,
1851 show_git_diff_gutter: None,
1852 show_code_actions: None,
1853 show_runnables: None,
1854 show_breakpoints: None,
1855 show_wrap_guides: None,
1856 show_indent_guides,
1857 placeholder_text: None,
1858 highlight_order: 0,
1859 highlighted_rows: HashMap::default(),
1860 background_highlights: TreeMap::default(),
1861 gutter_highlights: TreeMap::default(),
1862 scrollbar_marker_state: ScrollbarMarkerState::default(),
1863 active_indent_guides_state: ActiveIndentGuidesState::default(),
1864 nav_history: None,
1865 context_menu: RefCell::new(None),
1866 context_menu_options: None,
1867 mouse_context_menu: None,
1868 completion_tasks: Vec::new(),
1869 inline_blame_popover: None,
1870 signature_help_state: SignatureHelpState::default(),
1871 auto_signature_help: None,
1872 find_all_references_task_sources: Vec::new(),
1873 next_completion_id: 0,
1874 next_inlay_id: 0,
1875 code_action_providers,
1876 available_code_actions: None,
1877 code_actions_task: None,
1878 quick_selection_highlight_task: None,
1879 debounced_selection_highlight_task: None,
1880 document_highlights_task: None,
1881 linked_editing_range_task: None,
1882 pending_rename: None,
1883 searchable: true,
1884 cursor_shape: EditorSettings::get_global(cx)
1885 .cursor_shape
1886 .unwrap_or_default(),
1887 current_line_highlight: None,
1888 autoindent_mode: Some(AutoindentMode::EachLine),
1889 collapse_matches: false,
1890 workspace: None,
1891 input_enabled: true,
1892 use_modal_editing: mode.is_full(),
1893 read_only: mode.is_minimap(),
1894 use_autoclose: true,
1895 use_auto_surround: true,
1896 auto_replace_emoji_shortcode: false,
1897 jsx_tag_auto_close_enabled_in_any_buffer: false,
1898 leader_id: None,
1899 remote_id: None,
1900 hover_state: HoverState::default(),
1901 pending_mouse_down: None,
1902 hovered_link_state: None,
1903 edit_prediction_provider: None,
1904 active_inline_completion: None,
1905 stale_inline_completion_in_menu: None,
1906 edit_prediction_preview: EditPredictionPreview::Inactive {
1907 released_too_fast: false,
1908 },
1909 inline_diagnostics_enabled: mode.is_full(),
1910 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1911 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1912
1913 gutter_hovered: false,
1914 pixel_position_of_newest_cursor: None,
1915 last_bounds: None,
1916 last_position_map: None,
1917 expect_bounds_change: None,
1918 gutter_dimensions: GutterDimensions::default(),
1919 style: None,
1920 show_cursor_names: false,
1921 hovered_cursors: HashMap::default(),
1922 next_editor_action_id: EditorActionId::default(),
1923 editor_actions: Rc::default(),
1924 inline_completions_hidden_for_vim_mode: false,
1925 show_inline_completions_override: None,
1926 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1927 edit_prediction_settings: EditPredictionSettings::Disabled,
1928 edit_prediction_indent_conflict: false,
1929 edit_prediction_requires_modifier_in_indent_conflict: true,
1930 custom_context_menu: None,
1931 show_git_blame_gutter: false,
1932 show_git_blame_inline: false,
1933 show_selection_menu: None,
1934 show_git_blame_inline_delay_task: None,
1935 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1936 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1937 serialize_dirty_buffers: !mode.is_minimap()
1938 && ProjectSettings::get_global(cx)
1939 .session
1940 .restore_unsaved_buffers,
1941 blame: None,
1942 blame_subscription: None,
1943 tasks: BTreeMap::default(),
1944
1945 breakpoint_store,
1946 gutter_breakpoint_indicator: (None, None),
1947 _subscriptions: vec![
1948 cx.observe(&buffer, Self::on_buffer_changed),
1949 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1950 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1951 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1952 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1953 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1954 cx.observe_window_activation(window, |editor, window, cx| {
1955 let active = window.is_window_active();
1956 editor.blink_manager.update(cx, |blink_manager, cx| {
1957 if active {
1958 blink_manager.enable(cx);
1959 } else {
1960 blink_manager.disable(cx);
1961 }
1962 });
1963 if active {
1964 editor.show_mouse_cursor();
1965 }
1966 }),
1967 ],
1968 tasks_update_task: None,
1969 pull_diagnostics_task: Task::ready(()),
1970 linked_edit_ranges: Default::default(),
1971 in_project_search: false,
1972 previous_search_ranges: None,
1973 breadcrumb_header: None,
1974 focused_block: None,
1975 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1976 addons: HashMap::default(),
1977 registered_buffers: HashMap::default(),
1978 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1979 selection_mark_mode: false,
1980 toggle_fold_multiple_buffers: Task::ready(()),
1981 serialize_selections: Task::ready(()),
1982 serialize_folds: Task::ready(()),
1983 text_style_refinement: None,
1984 load_diff_task: load_uncommitted_diff,
1985 temporary_diff_override: false,
1986 mouse_cursor_hidden: false,
1987 minimap: None,
1988 hide_mouse_mode: EditorSettings::get_global(cx)
1989 .hide_mouse
1990 .unwrap_or_default(),
1991 change_list: ChangeList::new(),
1992 mode,
1993 };
1994 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
1995 editor
1996 ._subscriptions
1997 .push(cx.observe(breakpoints, |_, _, cx| {
1998 cx.notify();
1999 }));
2000 }
2001 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2002 editor._subscriptions.extend(project_subscriptions);
2003
2004 editor._subscriptions.push(cx.subscribe_in(
2005 &cx.entity(),
2006 window,
2007 |editor, _, e: &EditorEvent, window, cx| match e {
2008 EditorEvent::ScrollPositionChanged { local, .. } => {
2009 if *local {
2010 let new_anchor = editor.scroll_manager.anchor();
2011 let snapshot = editor.snapshot(window, cx);
2012 editor.update_restoration_data(cx, move |data| {
2013 data.scroll_position = (
2014 new_anchor.top_row(&snapshot.buffer_snapshot),
2015 new_anchor.offset,
2016 );
2017 });
2018 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2019 editor.inline_blame_popover.take();
2020 }
2021 }
2022 EditorEvent::Edited { .. } => {
2023 if !vim_enabled(cx) {
2024 let (map, selections) = editor.selections.all_adjusted_display(cx);
2025 let pop_state = editor
2026 .change_list
2027 .last()
2028 .map(|previous| {
2029 previous.len() == selections.len()
2030 && previous.iter().enumerate().all(|(ix, p)| {
2031 p.to_display_point(&map).row()
2032 == selections[ix].head().row()
2033 })
2034 })
2035 .unwrap_or(false);
2036 let new_positions = selections
2037 .into_iter()
2038 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2039 .collect();
2040 editor
2041 .change_list
2042 .push_to_change_list(pop_state, new_positions);
2043 }
2044 }
2045 _ => (),
2046 },
2047 ));
2048
2049 if let Some(dap_store) = editor
2050 .project
2051 .as_ref()
2052 .map(|project| project.read(cx).dap_store())
2053 {
2054 let weak_editor = cx.weak_entity();
2055
2056 editor
2057 ._subscriptions
2058 .push(
2059 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2060 let session_entity = cx.entity();
2061 weak_editor
2062 .update(cx, |editor, cx| {
2063 editor._subscriptions.push(
2064 cx.subscribe(&session_entity, Self::on_debug_session_event),
2065 );
2066 })
2067 .ok();
2068 }),
2069 );
2070
2071 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2072 editor
2073 ._subscriptions
2074 .push(cx.subscribe(&session, Self::on_debug_session_event));
2075 }
2076 }
2077
2078 editor.end_selection(window, cx);
2079 editor.scroll_manager.show_scrollbars(window, cx);
2080 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2081
2082 if full_mode {
2083 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2084 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2085
2086 if editor.git_blame_inline_enabled {
2087 editor.start_git_blame_inline(false, window, cx);
2088 }
2089
2090 editor.go_to_active_debug_line(window, cx);
2091
2092 if let Some(buffer) = buffer.read(cx).as_singleton() {
2093 if let Some(project) = editor.project.as_ref() {
2094 let handle = project.update(cx, |project, cx| {
2095 project.register_buffer_with_language_servers(&buffer, cx)
2096 });
2097 editor
2098 .registered_buffers
2099 .insert(buffer.read(cx).remote_id(), handle);
2100 }
2101 }
2102
2103 editor.minimap =
2104 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2105 editor.pull_diagnostics(window, cx);
2106 }
2107
2108 editor.report_editor_event("Editor Opened", None, cx);
2109 editor
2110 }
2111
2112 pub fn deploy_mouse_context_menu(
2113 &mut self,
2114 position: gpui::Point<Pixels>,
2115 context_menu: Entity<ContextMenu>,
2116 window: &mut Window,
2117 cx: &mut Context<Self>,
2118 ) {
2119 self.mouse_context_menu = Some(MouseContextMenu::new(
2120 self,
2121 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2122 context_menu,
2123 window,
2124 cx,
2125 ));
2126 }
2127
2128 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2129 self.mouse_context_menu
2130 .as_ref()
2131 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2132 }
2133
2134 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2135 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2136 }
2137
2138 fn key_context_internal(
2139 &self,
2140 has_active_edit_prediction: bool,
2141 window: &Window,
2142 cx: &App,
2143 ) -> KeyContext {
2144 let mut key_context = KeyContext::new_with_defaults();
2145 key_context.add("Editor");
2146 let mode = match self.mode {
2147 EditorMode::SingleLine { .. } => "single_line",
2148 EditorMode::AutoHeight { .. } => "auto_height",
2149 EditorMode::Minimap { .. } => "minimap",
2150 EditorMode::Full { .. } => "full",
2151 };
2152
2153 if EditorSettings::jupyter_enabled(cx) {
2154 key_context.add("jupyter");
2155 }
2156
2157 key_context.set("mode", mode);
2158 if self.pending_rename.is_some() {
2159 key_context.add("renaming");
2160 }
2161
2162 match self.context_menu.borrow().as_ref() {
2163 Some(CodeContextMenu::Completions(_)) => {
2164 key_context.add("menu");
2165 key_context.add("showing_completions");
2166 }
2167 Some(CodeContextMenu::CodeActions(_)) => {
2168 key_context.add("menu");
2169 key_context.add("showing_code_actions")
2170 }
2171 None => {}
2172 }
2173
2174 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2175 if !self.focus_handle(cx).contains_focused(window, cx)
2176 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2177 {
2178 for addon in self.addons.values() {
2179 addon.extend_key_context(&mut key_context, cx)
2180 }
2181 }
2182
2183 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2184 if let Some(extension) = singleton_buffer
2185 .read(cx)
2186 .file()
2187 .and_then(|file| file.path().extension()?.to_str())
2188 {
2189 key_context.set("extension", extension.to_string());
2190 }
2191 } else {
2192 key_context.add("multibuffer");
2193 }
2194
2195 if has_active_edit_prediction {
2196 if self.edit_prediction_in_conflict() {
2197 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2198 } else {
2199 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2200 key_context.add("copilot_suggestion");
2201 }
2202 }
2203
2204 if self.selection_mark_mode {
2205 key_context.add("selection_mode");
2206 }
2207
2208 key_context
2209 }
2210
2211 fn show_mouse_cursor(&mut self) {
2212 self.mouse_cursor_hidden = false;
2213 }
2214
2215 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2216 self.mouse_cursor_hidden = match origin {
2217 HideMouseCursorOrigin::TypingAction => {
2218 matches!(
2219 self.hide_mouse_mode,
2220 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2221 )
2222 }
2223 HideMouseCursorOrigin::MovementAction => {
2224 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2225 }
2226 };
2227 }
2228
2229 pub fn edit_prediction_in_conflict(&self) -> bool {
2230 if !self.show_edit_predictions_in_menu() {
2231 return false;
2232 }
2233
2234 let showing_completions = self
2235 .context_menu
2236 .borrow()
2237 .as_ref()
2238 .map_or(false, |context| {
2239 matches!(context, CodeContextMenu::Completions(_))
2240 });
2241
2242 showing_completions
2243 || self.edit_prediction_requires_modifier()
2244 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2245 // bindings to insert tab characters.
2246 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2247 }
2248
2249 pub fn accept_edit_prediction_keybind(
2250 &self,
2251 accept_partial: bool,
2252 window: &Window,
2253 cx: &App,
2254 ) -> AcceptEditPredictionBinding {
2255 let key_context = self.key_context_internal(true, window, cx);
2256 let in_conflict = self.edit_prediction_in_conflict();
2257
2258 let bindings = if accept_partial {
2259 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2260 } else {
2261 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2262 };
2263
2264 // TODO: if the binding contains multiple keystrokes, display all of them, not
2265 // just the first one.
2266 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2267 !in_conflict
2268 || binding
2269 .keystrokes()
2270 .first()
2271 .map_or(false, |keystroke| keystroke.modifiers.modified())
2272 }))
2273 }
2274
2275 pub fn new_file(
2276 workspace: &mut Workspace,
2277 _: &workspace::NewFile,
2278 window: &mut Window,
2279 cx: &mut Context<Workspace>,
2280 ) {
2281 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2282 "Failed to create buffer",
2283 window,
2284 cx,
2285 |e, _, _| match e.error_code() {
2286 ErrorCode::RemoteUpgradeRequired => Some(format!(
2287 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2288 e.error_tag("required").unwrap_or("the latest version")
2289 )),
2290 _ => None,
2291 },
2292 );
2293 }
2294
2295 pub fn new_in_workspace(
2296 workspace: &mut Workspace,
2297 window: &mut Window,
2298 cx: &mut Context<Workspace>,
2299 ) -> Task<Result<Entity<Editor>>> {
2300 let project = workspace.project().clone();
2301 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2302
2303 cx.spawn_in(window, async move |workspace, cx| {
2304 let buffer = create.await?;
2305 workspace.update_in(cx, |workspace, window, cx| {
2306 let editor =
2307 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2308 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2309 editor
2310 })
2311 })
2312 }
2313
2314 fn new_file_vertical(
2315 workspace: &mut Workspace,
2316 _: &workspace::NewFileSplitVertical,
2317 window: &mut Window,
2318 cx: &mut Context<Workspace>,
2319 ) {
2320 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2321 }
2322
2323 fn new_file_horizontal(
2324 workspace: &mut Workspace,
2325 _: &workspace::NewFileSplitHorizontal,
2326 window: &mut Window,
2327 cx: &mut Context<Workspace>,
2328 ) {
2329 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2330 }
2331
2332 fn new_file_in_direction(
2333 workspace: &mut Workspace,
2334 direction: SplitDirection,
2335 window: &mut Window,
2336 cx: &mut Context<Workspace>,
2337 ) {
2338 let project = workspace.project().clone();
2339 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2340
2341 cx.spawn_in(window, async move |workspace, cx| {
2342 let buffer = create.await?;
2343 workspace.update_in(cx, move |workspace, window, cx| {
2344 workspace.split_item(
2345 direction,
2346 Box::new(
2347 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2348 ),
2349 window,
2350 cx,
2351 )
2352 })?;
2353 anyhow::Ok(())
2354 })
2355 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2356 match e.error_code() {
2357 ErrorCode::RemoteUpgradeRequired => Some(format!(
2358 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2359 e.error_tag("required").unwrap_or("the latest version")
2360 )),
2361 _ => None,
2362 }
2363 });
2364 }
2365
2366 pub fn leader_id(&self) -> Option<CollaboratorId> {
2367 self.leader_id
2368 }
2369
2370 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2371 &self.buffer
2372 }
2373
2374 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2375 self.workspace.as_ref()?.0.upgrade()
2376 }
2377
2378 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2379 self.buffer().read(cx).title(cx)
2380 }
2381
2382 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2383 let git_blame_gutter_max_author_length = self
2384 .render_git_blame_gutter(cx)
2385 .then(|| {
2386 if let Some(blame) = self.blame.as_ref() {
2387 let max_author_length =
2388 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2389 Some(max_author_length)
2390 } else {
2391 None
2392 }
2393 })
2394 .flatten();
2395
2396 EditorSnapshot {
2397 mode: self.mode.clone(),
2398 show_gutter: self.show_gutter,
2399 show_line_numbers: self.show_line_numbers,
2400 show_git_diff_gutter: self.show_git_diff_gutter,
2401 show_code_actions: self.show_code_actions,
2402 show_runnables: self.show_runnables,
2403 show_breakpoints: self.show_breakpoints,
2404 git_blame_gutter_max_author_length,
2405 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2406 scroll_anchor: self.scroll_manager.anchor(),
2407 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2408 placeholder_text: self.placeholder_text.clone(),
2409 is_focused: self.focus_handle.is_focused(window),
2410 current_line_highlight: self
2411 .current_line_highlight
2412 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2413 gutter_hovered: self.gutter_hovered,
2414 }
2415 }
2416
2417 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2418 self.buffer.read(cx).language_at(point, cx)
2419 }
2420
2421 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2422 self.buffer.read(cx).read(cx).file_at(point).cloned()
2423 }
2424
2425 pub fn active_excerpt(
2426 &self,
2427 cx: &App,
2428 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2429 self.buffer
2430 .read(cx)
2431 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2432 }
2433
2434 pub fn mode(&self) -> &EditorMode {
2435 &self.mode
2436 }
2437
2438 pub fn set_mode(&mut self, mode: EditorMode) {
2439 self.mode = mode;
2440 }
2441
2442 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2443 self.collaboration_hub.as_deref()
2444 }
2445
2446 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2447 self.collaboration_hub = Some(hub);
2448 }
2449
2450 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2451 self.in_project_search = in_project_search;
2452 }
2453
2454 pub fn set_custom_context_menu(
2455 &mut self,
2456 f: impl 'static
2457 + Fn(
2458 &mut Self,
2459 DisplayPoint,
2460 &mut Window,
2461 &mut Context<Self>,
2462 ) -> Option<Entity<ui::ContextMenu>>,
2463 ) {
2464 self.custom_context_menu = Some(Box::new(f))
2465 }
2466
2467 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2468 self.completion_provider = provider;
2469 }
2470
2471 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2472 self.semantics_provider.clone()
2473 }
2474
2475 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2476 self.semantics_provider = provider;
2477 }
2478
2479 pub fn set_edit_prediction_provider<T>(
2480 &mut self,
2481 provider: Option<Entity<T>>,
2482 window: &mut Window,
2483 cx: &mut Context<Self>,
2484 ) where
2485 T: EditPredictionProvider,
2486 {
2487 self.edit_prediction_provider =
2488 provider.map(|provider| RegisteredInlineCompletionProvider {
2489 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2490 if this.focus_handle.is_focused(window) {
2491 this.update_visible_inline_completion(window, cx);
2492 }
2493 }),
2494 provider: Arc::new(provider),
2495 });
2496 self.update_edit_prediction_settings(cx);
2497 self.refresh_inline_completion(false, false, window, cx);
2498 }
2499
2500 pub fn placeholder_text(&self) -> Option<&str> {
2501 self.placeholder_text.as_deref()
2502 }
2503
2504 pub fn set_placeholder_text(
2505 &mut self,
2506 placeholder_text: impl Into<Arc<str>>,
2507 cx: &mut Context<Self>,
2508 ) {
2509 let placeholder_text = Some(placeholder_text.into());
2510 if self.placeholder_text != placeholder_text {
2511 self.placeholder_text = placeholder_text;
2512 cx.notify();
2513 }
2514 }
2515
2516 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2517 self.cursor_shape = cursor_shape;
2518
2519 // Disrupt blink for immediate user feedback that the cursor shape has changed
2520 self.blink_manager.update(cx, BlinkManager::show_cursor);
2521
2522 cx.notify();
2523 }
2524
2525 pub fn set_current_line_highlight(
2526 &mut self,
2527 current_line_highlight: Option<CurrentLineHighlight>,
2528 ) {
2529 self.current_line_highlight = current_line_highlight;
2530 }
2531
2532 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2533 self.collapse_matches = collapse_matches;
2534 }
2535
2536 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2537 let buffers = self.buffer.read(cx).all_buffers();
2538 let Some(project) = self.project.as_ref() else {
2539 return;
2540 };
2541 project.update(cx, |project, cx| {
2542 for buffer in buffers {
2543 self.registered_buffers
2544 .entry(buffer.read(cx).remote_id())
2545 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2546 }
2547 })
2548 }
2549
2550 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2551 if self.collapse_matches {
2552 return range.start..range.start;
2553 }
2554 range.clone()
2555 }
2556
2557 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2558 if self.display_map.read(cx).clip_at_line_ends != clip {
2559 self.display_map
2560 .update(cx, |map, _| map.clip_at_line_ends = clip);
2561 }
2562 }
2563
2564 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2565 self.input_enabled = input_enabled;
2566 }
2567
2568 pub fn set_inline_completions_hidden_for_vim_mode(
2569 &mut self,
2570 hidden: bool,
2571 window: &mut Window,
2572 cx: &mut Context<Self>,
2573 ) {
2574 if hidden != self.inline_completions_hidden_for_vim_mode {
2575 self.inline_completions_hidden_for_vim_mode = hidden;
2576 if hidden {
2577 self.update_visible_inline_completion(window, cx);
2578 } else {
2579 self.refresh_inline_completion(true, false, window, cx);
2580 }
2581 }
2582 }
2583
2584 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2585 self.menu_inline_completions_policy = value;
2586 }
2587
2588 pub fn set_autoindent(&mut self, autoindent: bool) {
2589 if autoindent {
2590 self.autoindent_mode = Some(AutoindentMode::EachLine);
2591 } else {
2592 self.autoindent_mode = None;
2593 }
2594 }
2595
2596 pub fn read_only(&self, cx: &App) -> bool {
2597 self.read_only || self.buffer.read(cx).read_only()
2598 }
2599
2600 pub fn set_read_only(&mut self, read_only: bool) {
2601 self.read_only = read_only;
2602 }
2603
2604 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2605 self.use_autoclose = autoclose;
2606 }
2607
2608 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2609 self.use_auto_surround = auto_surround;
2610 }
2611
2612 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2613 self.auto_replace_emoji_shortcode = auto_replace;
2614 }
2615
2616 pub fn toggle_edit_predictions(
2617 &mut self,
2618 _: &ToggleEditPrediction,
2619 window: &mut Window,
2620 cx: &mut Context<Self>,
2621 ) {
2622 if self.show_inline_completions_override.is_some() {
2623 self.set_show_edit_predictions(None, window, cx);
2624 } else {
2625 let show_edit_predictions = !self.edit_predictions_enabled();
2626 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2627 }
2628 }
2629
2630 pub fn set_show_edit_predictions(
2631 &mut self,
2632 show_edit_predictions: Option<bool>,
2633 window: &mut Window,
2634 cx: &mut Context<Self>,
2635 ) {
2636 self.show_inline_completions_override = show_edit_predictions;
2637 self.update_edit_prediction_settings(cx);
2638
2639 if let Some(false) = show_edit_predictions {
2640 self.discard_inline_completion(false, cx);
2641 } else {
2642 self.refresh_inline_completion(false, true, window, cx);
2643 }
2644 }
2645
2646 fn inline_completions_disabled_in_scope(
2647 &self,
2648 buffer: &Entity<Buffer>,
2649 buffer_position: language::Anchor,
2650 cx: &App,
2651 ) -> bool {
2652 let snapshot = buffer.read(cx).snapshot();
2653 let settings = snapshot.settings_at(buffer_position, cx);
2654
2655 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2656 return false;
2657 };
2658
2659 scope.override_name().map_or(false, |scope_name| {
2660 settings
2661 .edit_predictions_disabled_in
2662 .iter()
2663 .any(|s| s == scope_name)
2664 })
2665 }
2666
2667 pub fn set_use_modal_editing(&mut self, to: bool) {
2668 self.use_modal_editing = to;
2669 }
2670
2671 pub fn use_modal_editing(&self) -> bool {
2672 self.use_modal_editing
2673 }
2674
2675 fn selections_did_change(
2676 &mut self,
2677 local: bool,
2678 old_cursor_position: &Anchor,
2679 should_update_completions: bool,
2680 window: &mut Window,
2681 cx: &mut Context<Self>,
2682 ) {
2683 window.invalidate_character_coordinates();
2684
2685 // Copy selections to primary selection buffer
2686 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2687 if local {
2688 let selections = self.selections.all::<usize>(cx);
2689 let buffer_handle = self.buffer.read(cx).read(cx);
2690
2691 let mut text = String::new();
2692 for (index, selection) in selections.iter().enumerate() {
2693 let text_for_selection = buffer_handle
2694 .text_for_range(selection.start..selection.end)
2695 .collect::<String>();
2696
2697 text.push_str(&text_for_selection);
2698 if index != selections.len() - 1 {
2699 text.push('\n');
2700 }
2701 }
2702
2703 if !text.is_empty() {
2704 cx.write_to_primary(ClipboardItem::new_string(text));
2705 }
2706 }
2707
2708 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2709 self.buffer.update(cx, |buffer, cx| {
2710 buffer.set_active_selections(
2711 &self.selections.disjoint_anchors(),
2712 self.selections.line_mode,
2713 self.cursor_shape,
2714 cx,
2715 )
2716 });
2717 }
2718 let display_map = self
2719 .display_map
2720 .update(cx, |display_map, cx| display_map.snapshot(cx));
2721 let buffer = &display_map.buffer_snapshot;
2722 if self.selections.count() == 1 {
2723 self.add_selections_state = None;
2724 }
2725 self.select_next_state = None;
2726 self.select_prev_state = None;
2727 self.select_syntax_node_history.try_clear();
2728 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2729 self.snippet_stack
2730 .invalidate(&self.selections.disjoint_anchors(), buffer);
2731 self.take_rename(false, window, cx);
2732
2733 let newest_selection = self.selections.newest_anchor();
2734 let new_cursor_position = newest_selection.head();
2735 let selection_start = newest_selection.start;
2736
2737 self.push_to_nav_history(
2738 *old_cursor_position,
2739 Some(new_cursor_position.to_point(buffer)),
2740 false,
2741 cx,
2742 );
2743
2744 if local {
2745 if let Some(buffer_id) = new_cursor_position.buffer_id {
2746 if !self.registered_buffers.contains_key(&buffer_id) {
2747 if let Some(project) = self.project.as_ref() {
2748 project.update(cx, |project, cx| {
2749 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2750 return;
2751 };
2752 self.registered_buffers.insert(
2753 buffer_id,
2754 project.register_buffer_with_language_servers(&buffer, cx),
2755 );
2756 })
2757 }
2758 }
2759 }
2760
2761 let mut context_menu = self.context_menu.borrow_mut();
2762 let completion_menu = match context_menu.as_ref() {
2763 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2764 Some(CodeContextMenu::CodeActions(_)) => {
2765 *context_menu = None;
2766 None
2767 }
2768 None => None,
2769 };
2770 let completion_position = completion_menu.map(|menu| menu.initial_position);
2771 drop(context_menu);
2772
2773 if should_update_completions {
2774 if let Some(completion_position) = completion_position {
2775 let start_offset = selection_start.to_offset(buffer);
2776 let position_matches = start_offset == completion_position.to_offset(buffer);
2777 let continue_showing = if position_matches {
2778 if self.snippet_stack.is_empty() {
2779 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
2780 } else {
2781 // Snippet choices can be shown even when the cursor is in whitespace.
2782 // Dismissing the menu when actions like backspace
2783 true
2784 }
2785 } else {
2786 false
2787 };
2788
2789 if continue_showing {
2790 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2791 } else {
2792 self.hide_context_menu(window, cx);
2793 }
2794 }
2795 }
2796
2797 hide_hover(self, cx);
2798
2799 if old_cursor_position.to_display_point(&display_map).row()
2800 != new_cursor_position.to_display_point(&display_map).row()
2801 {
2802 self.available_code_actions.take();
2803 }
2804 self.refresh_code_actions(window, cx);
2805 self.refresh_document_highlights(cx);
2806 self.refresh_selected_text_highlights(false, window, cx);
2807 refresh_matching_bracket_highlights(self, window, cx);
2808 self.update_visible_inline_completion(window, cx);
2809 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2810 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2811 self.inline_blame_popover.take();
2812 if self.git_blame_inline_enabled {
2813 self.start_inline_blame_timer(window, cx);
2814 }
2815 }
2816
2817 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2818 cx.emit(EditorEvent::SelectionsChanged { local });
2819
2820 let selections = &self.selections.disjoint;
2821 if selections.len() == 1 {
2822 cx.emit(SearchEvent::ActiveMatchChanged)
2823 }
2824 if local {
2825 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2826 let inmemory_selections = selections
2827 .iter()
2828 .map(|s| {
2829 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2830 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2831 })
2832 .collect();
2833 self.update_restoration_data(cx, |data| {
2834 data.selections = inmemory_selections;
2835 });
2836
2837 if WorkspaceSettings::get(None, cx).restore_on_startup
2838 != RestoreOnStartupBehavior::None
2839 {
2840 if let Some(workspace_id) =
2841 self.workspace.as_ref().and_then(|workspace| workspace.1)
2842 {
2843 let snapshot = self.buffer().read(cx).snapshot(cx);
2844 let selections = selections.clone();
2845 let background_executor = cx.background_executor().clone();
2846 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2847 self.serialize_selections = cx.background_spawn(async move {
2848 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2849 let db_selections = selections
2850 .iter()
2851 .map(|selection| {
2852 (
2853 selection.start.to_offset(&snapshot),
2854 selection.end.to_offset(&snapshot),
2855 )
2856 })
2857 .collect();
2858
2859 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2860 .await
2861 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2862 .log_err();
2863 });
2864 }
2865 }
2866 }
2867 }
2868
2869 cx.notify();
2870 }
2871
2872 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2873 use text::ToOffset as _;
2874 use text::ToPoint as _;
2875
2876 if self.mode.is_minimap()
2877 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2878 {
2879 return;
2880 }
2881
2882 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2883 return;
2884 };
2885
2886 let snapshot = singleton.read(cx).snapshot();
2887 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2888 let display_snapshot = display_map.snapshot(cx);
2889
2890 display_snapshot
2891 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2892 .map(|fold| {
2893 fold.range.start.text_anchor.to_point(&snapshot)
2894 ..fold.range.end.text_anchor.to_point(&snapshot)
2895 })
2896 .collect()
2897 });
2898 self.update_restoration_data(cx, |data| {
2899 data.folds = inmemory_folds;
2900 });
2901
2902 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2903 return;
2904 };
2905 let background_executor = cx.background_executor().clone();
2906 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2907 let db_folds = self.display_map.update(cx, |display_map, cx| {
2908 display_map
2909 .snapshot(cx)
2910 .folds_in_range(0..snapshot.len())
2911 .map(|fold| {
2912 (
2913 fold.range.start.text_anchor.to_offset(&snapshot),
2914 fold.range.end.text_anchor.to_offset(&snapshot),
2915 )
2916 })
2917 .collect()
2918 });
2919 self.serialize_folds = cx.background_spawn(async move {
2920 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2921 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2922 .await
2923 .with_context(|| {
2924 format!(
2925 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2926 )
2927 })
2928 .log_err();
2929 });
2930 }
2931
2932 pub fn sync_selections(
2933 &mut self,
2934 other: Entity<Editor>,
2935 cx: &mut Context<Self>,
2936 ) -> gpui::Subscription {
2937 let other_selections = other.read(cx).selections.disjoint.to_vec();
2938 self.selections.change_with(cx, |selections| {
2939 selections.select_anchors(other_selections);
2940 });
2941
2942 let other_subscription =
2943 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2944 EditorEvent::SelectionsChanged { local: true } => {
2945 let other_selections = other.read(cx).selections.disjoint.to_vec();
2946 if other_selections.is_empty() {
2947 return;
2948 }
2949 this.selections.change_with(cx, |selections| {
2950 selections.select_anchors(other_selections);
2951 });
2952 }
2953 _ => {}
2954 });
2955
2956 let this_subscription =
2957 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2958 EditorEvent::SelectionsChanged { local: true } => {
2959 let these_selections = this.selections.disjoint.to_vec();
2960 if these_selections.is_empty() {
2961 return;
2962 }
2963 other.update(cx, |other_editor, cx| {
2964 other_editor.selections.change_with(cx, |selections| {
2965 selections.select_anchors(these_selections);
2966 })
2967 });
2968 }
2969 _ => {}
2970 });
2971
2972 Subscription::join(other_subscription, this_subscription)
2973 }
2974
2975 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
2976 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
2977 /// effects of selection change occur at the end of the transaction.
2978 pub fn change_selections<R>(
2979 &mut self,
2980 autoscroll: Option<Autoscroll>,
2981 window: &mut Window,
2982 cx: &mut Context<Self>,
2983 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2984 ) -> R {
2985 self.change_selections_inner(true, autoscroll, window, cx, change)
2986 }
2987
2988 pub(crate) fn change_selections_without_updating_completions<R>(
2989 &mut self,
2990 autoscroll: Option<Autoscroll>,
2991 window: &mut Window,
2992 cx: &mut Context<Self>,
2993 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2994 ) -> R {
2995 self.change_selections_inner(false, autoscroll, window, cx, change)
2996 }
2997
2998 fn change_selections_inner<R>(
2999 &mut self,
3000 should_update_completions: bool,
3001 autoscroll: Option<Autoscroll>,
3002 window: &mut Window,
3003 cx: &mut Context<Self>,
3004 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3005 ) -> R {
3006 if let Some(state) = &mut self.deferred_selection_effects_state {
3007 state.autoscroll = autoscroll.or(state.autoscroll);
3008 state.should_update_completions = should_update_completions;
3009 let (changed, result) = self.selections.change_with(cx, change);
3010 state.changed |= changed;
3011 return result;
3012 }
3013 let mut state = DeferredSelectionEffectsState {
3014 changed: false,
3015 should_update_completions,
3016 autoscroll,
3017 old_cursor_position: self.selections.newest_anchor().head(),
3018 history_entry: SelectionHistoryEntry {
3019 selections: self.selections.disjoint_anchors(),
3020 select_next_state: self.select_next_state.clone(),
3021 select_prev_state: self.select_prev_state.clone(),
3022 add_selections_state: self.add_selections_state.clone(),
3023 },
3024 };
3025 let (changed, result) = self.selections.change_with(cx, change);
3026 state.changed = state.changed || changed;
3027 if self.defer_selection_effects {
3028 self.deferred_selection_effects_state = Some(state);
3029 } else {
3030 self.apply_selection_effects(state, window, cx);
3031 }
3032 result
3033 }
3034
3035 /// Defers the effects of selection change, so that the effects of multiple calls to
3036 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3037 /// to selection history and the state of popovers based on selection position aren't
3038 /// erroneously updated.
3039 pub fn with_selection_effects_deferred<R>(
3040 &mut self,
3041 window: &mut Window,
3042 cx: &mut Context<Self>,
3043 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3044 ) -> R {
3045 let already_deferred = self.defer_selection_effects;
3046 self.defer_selection_effects = true;
3047 let result = update(self, window, cx);
3048 if !already_deferred {
3049 self.defer_selection_effects = false;
3050 if let Some(state) = self.deferred_selection_effects_state.take() {
3051 self.apply_selection_effects(state, window, cx);
3052 }
3053 }
3054 result
3055 }
3056
3057 fn apply_selection_effects(
3058 &mut self,
3059 state: DeferredSelectionEffectsState,
3060 window: &mut Window,
3061 cx: &mut Context<Self>,
3062 ) {
3063 if state.changed {
3064 self.selection_history.push(state.history_entry);
3065
3066 if let Some(autoscroll) = state.autoscroll {
3067 self.request_autoscroll(autoscroll, cx);
3068 }
3069
3070 let old_cursor_position = &state.old_cursor_position;
3071
3072 self.selections_did_change(
3073 true,
3074 &old_cursor_position,
3075 state.should_update_completions,
3076 window,
3077 cx,
3078 );
3079
3080 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3081 self.show_signature_help(&ShowSignatureHelp, window, cx);
3082 }
3083 }
3084 }
3085
3086 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3087 where
3088 I: IntoIterator<Item = (Range<S>, T)>,
3089 S: ToOffset,
3090 T: Into<Arc<str>>,
3091 {
3092 if self.read_only(cx) {
3093 return;
3094 }
3095
3096 self.buffer
3097 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3098 }
3099
3100 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3101 where
3102 I: IntoIterator<Item = (Range<S>, T)>,
3103 S: ToOffset,
3104 T: Into<Arc<str>>,
3105 {
3106 if self.read_only(cx) {
3107 return;
3108 }
3109
3110 self.buffer.update(cx, |buffer, cx| {
3111 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3112 });
3113 }
3114
3115 pub fn edit_with_block_indent<I, S, T>(
3116 &mut self,
3117 edits: I,
3118 original_indent_columns: Vec<Option<u32>>,
3119 cx: &mut Context<Self>,
3120 ) where
3121 I: IntoIterator<Item = (Range<S>, T)>,
3122 S: ToOffset,
3123 T: Into<Arc<str>>,
3124 {
3125 if self.read_only(cx) {
3126 return;
3127 }
3128
3129 self.buffer.update(cx, |buffer, cx| {
3130 buffer.edit(
3131 edits,
3132 Some(AutoindentMode::Block {
3133 original_indent_columns,
3134 }),
3135 cx,
3136 )
3137 });
3138 }
3139
3140 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3141 self.hide_context_menu(window, cx);
3142
3143 match phase {
3144 SelectPhase::Begin {
3145 position,
3146 add,
3147 click_count,
3148 } => self.begin_selection(position, add, click_count, window, cx),
3149 SelectPhase::BeginColumnar {
3150 position,
3151 goal_column,
3152 reset,
3153 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3154 SelectPhase::Extend {
3155 position,
3156 click_count,
3157 } => self.extend_selection(position, click_count, window, cx),
3158 SelectPhase::Update {
3159 position,
3160 goal_column,
3161 scroll_delta,
3162 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3163 SelectPhase::End => self.end_selection(window, cx),
3164 }
3165 }
3166
3167 fn extend_selection(
3168 &mut self,
3169 position: DisplayPoint,
3170 click_count: usize,
3171 window: &mut Window,
3172 cx: &mut Context<Self>,
3173 ) {
3174 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3175 let tail = self.selections.newest::<usize>(cx).tail();
3176 self.begin_selection(position, false, click_count, window, cx);
3177
3178 let position = position.to_offset(&display_map, Bias::Left);
3179 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3180
3181 let mut pending_selection = self
3182 .selections
3183 .pending_anchor()
3184 .expect("extend_selection not called with pending selection");
3185 if position >= tail {
3186 pending_selection.start = tail_anchor;
3187 } else {
3188 pending_selection.end = tail_anchor;
3189 pending_selection.reversed = true;
3190 }
3191
3192 let mut pending_mode = self.selections.pending_mode().unwrap();
3193 match &mut pending_mode {
3194 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3195 _ => {}
3196 }
3197
3198 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3199
3200 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3201 s.set_pending(pending_selection, pending_mode)
3202 });
3203 }
3204
3205 fn begin_selection(
3206 &mut self,
3207 position: DisplayPoint,
3208 add: bool,
3209 click_count: usize,
3210 window: &mut Window,
3211 cx: &mut Context<Self>,
3212 ) {
3213 if !self.focus_handle.is_focused(window) {
3214 self.last_focused_descendant = None;
3215 window.focus(&self.focus_handle);
3216 }
3217
3218 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3219 let buffer = &display_map.buffer_snapshot;
3220 let position = display_map.clip_point(position, Bias::Left);
3221
3222 let start;
3223 let end;
3224 let mode;
3225 let mut auto_scroll;
3226 match click_count {
3227 1 => {
3228 start = buffer.anchor_before(position.to_point(&display_map));
3229 end = start;
3230 mode = SelectMode::Character;
3231 auto_scroll = true;
3232 }
3233 2 => {
3234 let range = movement::surrounding_word(&display_map, position);
3235 start = buffer.anchor_before(range.start.to_point(&display_map));
3236 end = buffer.anchor_before(range.end.to_point(&display_map));
3237 mode = SelectMode::Word(start..end);
3238 auto_scroll = true;
3239 }
3240 3 => {
3241 let position = display_map
3242 .clip_point(position, Bias::Left)
3243 .to_point(&display_map);
3244 let line_start = display_map.prev_line_boundary(position).0;
3245 let next_line_start = buffer.clip_point(
3246 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3247 Bias::Left,
3248 );
3249 start = buffer.anchor_before(line_start);
3250 end = buffer.anchor_before(next_line_start);
3251 mode = SelectMode::Line(start..end);
3252 auto_scroll = true;
3253 }
3254 _ => {
3255 start = buffer.anchor_before(0);
3256 end = buffer.anchor_before(buffer.len());
3257 mode = SelectMode::All;
3258 auto_scroll = false;
3259 }
3260 }
3261 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3262
3263 let point_to_delete: Option<usize> = {
3264 let selected_points: Vec<Selection<Point>> =
3265 self.selections.disjoint_in_range(start..end, cx);
3266
3267 if !add || click_count > 1 {
3268 None
3269 } else if !selected_points.is_empty() {
3270 Some(selected_points[0].id)
3271 } else {
3272 let clicked_point_already_selected =
3273 self.selections.disjoint.iter().find(|selection| {
3274 selection.start.to_point(buffer) == start.to_point(buffer)
3275 || selection.end.to_point(buffer) == end.to_point(buffer)
3276 });
3277
3278 clicked_point_already_selected.map(|selection| selection.id)
3279 }
3280 };
3281
3282 let selections_count = self.selections.count();
3283
3284 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3285 if let Some(point_to_delete) = point_to_delete {
3286 s.delete(point_to_delete);
3287
3288 if selections_count == 1 {
3289 s.set_pending_anchor_range(start..end, mode);
3290 }
3291 } else {
3292 if !add {
3293 s.clear_disjoint();
3294 }
3295
3296 s.set_pending_anchor_range(start..end, mode);
3297 }
3298 });
3299 }
3300
3301 fn begin_columnar_selection(
3302 &mut self,
3303 position: DisplayPoint,
3304 goal_column: u32,
3305 reset: bool,
3306 window: &mut Window,
3307 cx: &mut Context<Self>,
3308 ) {
3309 if !self.focus_handle.is_focused(window) {
3310 self.last_focused_descendant = None;
3311 window.focus(&self.focus_handle);
3312 }
3313
3314 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3315
3316 if reset {
3317 let pointer_position = display_map
3318 .buffer_snapshot
3319 .anchor_before(position.to_point(&display_map));
3320
3321 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3322 s.clear_disjoint();
3323 s.set_pending_anchor_range(
3324 pointer_position..pointer_position,
3325 SelectMode::Character,
3326 );
3327 });
3328 if position.column() != goal_column {
3329 self.columnar_display_point = Some(DisplayPoint::new(position.row(), goal_column));
3330 } else {
3331 self.columnar_display_point = None;
3332 }
3333 }
3334
3335 let tail = self.selections.newest::<Point>(cx).tail();
3336 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3337
3338 if !reset {
3339 self.columnar_display_point = None;
3340 self.select_columns(
3341 tail.to_display_point(&display_map),
3342 position,
3343 goal_column,
3344 &display_map,
3345 window,
3346 cx,
3347 );
3348 }
3349 }
3350
3351 fn update_selection(
3352 &mut self,
3353 position: DisplayPoint,
3354 goal_column: u32,
3355 scroll_delta: gpui::Point<f32>,
3356 window: &mut Window,
3357 cx: &mut Context<Self>,
3358 ) {
3359 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3360
3361 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3362 let tail = self
3363 .columnar_display_point
3364 .unwrap_or_else(|| tail.to_display_point(&display_map));
3365 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3366 } else if let Some(mut pending) = self.selections.pending_anchor() {
3367 let buffer = self.buffer.read(cx).snapshot(cx);
3368 let head;
3369 let tail;
3370 let mode = self.selections.pending_mode().unwrap();
3371 match &mode {
3372 SelectMode::Character => {
3373 head = position.to_point(&display_map);
3374 tail = pending.tail().to_point(&buffer);
3375 }
3376 SelectMode::Word(original_range) => {
3377 let original_display_range = original_range.start.to_display_point(&display_map)
3378 ..original_range.end.to_display_point(&display_map);
3379 let original_buffer_range = original_display_range.start.to_point(&display_map)
3380 ..original_display_range.end.to_point(&display_map);
3381 if movement::is_inside_word(&display_map, position)
3382 || original_display_range.contains(&position)
3383 {
3384 let word_range = movement::surrounding_word(&display_map, position);
3385 if word_range.start < original_display_range.start {
3386 head = word_range.start.to_point(&display_map);
3387 } else {
3388 head = word_range.end.to_point(&display_map);
3389 }
3390 } else {
3391 head = position.to_point(&display_map);
3392 }
3393
3394 if head <= original_buffer_range.start {
3395 tail = original_buffer_range.end;
3396 } else {
3397 tail = original_buffer_range.start;
3398 }
3399 }
3400 SelectMode::Line(original_range) => {
3401 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3402
3403 let position = display_map
3404 .clip_point(position, Bias::Left)
3405 .to_point(&display_map);
3406 let line_start = display_map.prev_line_boundary(position).0;
3407 let next_line_start = buffer.clip_point(
3408 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3409 Bias::Left,
3410 );
3411
3412 if line_start < original_range.start {
3413 head = line_start
3414 } else {
3415 head = next_line_start
3416 }
3417
3418 if head <= original_range.start {
3419 tail = original_range.end;
3420 } else {
3421 tail = original_range.start;
3422 }
3423 }
3424 SelectMode::All => {
3425 return;
3426 }
3427 };
3428
3429 if head < tail {
3430 pending.start = buffer.anchor_before(head);
3431 pending.end = buffer.anchor_before(tail);
3432 pending.reversed = true;
3433 } else {
3434 pending.start = buffer.anchor_before(tail);
3435 pending.end = buffer.anchor_before(head);
3436 pending.reversed = false;
3437 }
3438
3439 self.change_selections(None, window, cx, |s| {
3440 s.set_pending(pending, mode);
3441 });
3442 } else {
3443 log::error!("update_selection dispatched with no pending selection");
3444 return;
3445 }
3446
3447 self.apply_scroll_delta(scroll_delta, window, cx);
3448 cx.notify();
3449 }
3450
3451 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3452 self.columnar_selection_tail.take();
3453 if self.selections.pending_anchor().is_some() {
3454 let selections = self.selections.all::<usize>(cx);
3455 self.change_selections(None, window, cx, |s| {
3456 s.select(selections);
3457 s.clear_pending();
3458 });
3459 }
3460 }
3461
3462 fn select_columns(
3463 &mut self,
3464 tail: DisplayPoint,
3465 head: DisplayPoint,
3466 goal_column: u32,
3467 display_map: &DisplaySnapshot,
3468 window: &mut Window,
3469 cx: &mut Context<Self>,
3470 ) {
3471 let start_row = cmp::min(tail.row(), head.row());
3472 let end_row = cmp::max(tail.row(), head.row());
3473 let start_column = cmp::min(tail.column(), goal_column);
3474 let end_column = cmp::max(tail.column(), goal_column);
3475 let reversed = start_column < tail.column();
3476
3477 let selection_ranges = (start_row.0..=end_row.0)
3478 .map(DisplayRow)
3479 .filter_map(|row| {
3480 if !display_map.is_block_line(row) {
3481 let start = display_map
3482 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3483 .to_point(display_map);
3484 let end = display_map
3485 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3486 .to_point(display_map);
3487 if reversed {
3488 Some(end..start)
3489 } else {
3490 Some(start..end)
3491 }
3492 } else {
3493 None
3494 }
3495 })
3496 .collect::<Vec<_>>();
3497
3498 let mut non_empty_ranges = selection_ranges
3499 .iter()
3500 .filter(|selection_range| selection_range.start != selection_range.end)
3501 .peekable();
3502
3503 let ranges = if non_empty_ranges.peek().is_some() {
3504 non_empty_ranges.cloned().collect()
3505 } else {
3506 selection_ranges
3507 };
3508
3509 self.change_selections(None, window, cx, |s| {
3510 s.select_ranges(ranges);
3511 });
3512 cx.notify();
3513 }
3514
3515 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3516 self.selections
3517 .all_adjusted(cx)
3518 .iter()
3519 .any(|selection| !selection.is_empty())
3520 }
3521
3522 pub fn has_pending_nonempty_selection(&self) -> bool {
3523 let pending_nonempty_selection = match self.selections.pending_anchor() {
3524 Some(Selection { start, end, .. }) => start != end,
3525 None => false,
3526 };
3527
3528 pending_nonempty_selection
3529 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3530 }
3531
3532 pub fn has_pending_selection(&self) -> bool {
3533 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3534 }
3535
3536 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3537 self.selection_mark_mode = false;
3538
3539 if self.clear_expanded_diff_hunks(cx) {
3540 cx.notify();
3541 return;
3542 }
3543 if self.dismiss_menus_and_popups(true, window, cx) {
3544 return;
3545 }
3546
3547 if self.mode.is_full()
3548 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3549 {
3550 return;
3551 }
3552
3553 cx.propagate();
3554 }
3555
3556 pub fn dismiss_menus_and_popups(
3557 &mut self,
3558 is_user_requested: bool,
3559 window: &mut Window,
3560 cx: &mut Context<Self>,
3561 ) -> bool {
3562 if self.take_rename(false, window, cx).is_some() {
3563 return true;
3564 }
3565
3566 if hide_hover(self, cx) {
3567 return true;
3568 }
3569
3570 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3571 return true;
3572 }
3573
3574 if self.hide_context_menu(window, cx).is_some() {
3575 return true;
3576 }
3577
3578 if self.mouse_context_menu.take().is_some() {
3579 return true;
3580 }
3581
3582 if is_user_requested && self.discard_inline_completion(true, cx) {
3583 return true;
3584 }
3585
3586 if self.snippet_stack.pop().is_some() {
3587 return true;
3588 }
3589
3590 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3591 self.dismiss_diagnostics(cx);
3592 return true;
3593 }
3594
3595 false
3596 }
3597
3598 fn linked_editing_ranges_for(
3599 &self,
3600 selection: Range<text::Anchor>,
3601 cx: &App,
3602 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3603 if self.linked_edit_ranges.is_empty() {
3604 return None;
3605 }
3606 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3607 selection.end.buffer_id.and_then(|end_buffer_id| {
3608 if selection.start.buffer_id != Some(end_buffer_id) {
3609 return None;
3610 }
3611 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3612 let snapshot = buffer.read(cx).snapshot();
3613 self.linked_edit_ranges
3614 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3615 .map(|ranges| (ranges, snapshot, buffer))
3616 })?;
3617 use text::ToOffset as TO;
3618 // find offset from the start of current range to current cursor position
3619 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3620
3621 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3622 let start_difference = start_offset - start_byte_offset;
3623 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3624 let end_difference = end_offset - start_byte_offset;
3625 // Current range has associated linked ranges.
3626 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3627 for range in linked_ranges.iter() {
3628 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3629 let end_offset = start_offset + end_difference;
3630 let start_offset = start_offset + start_difference;
3631 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3632 continue;
3633 }
3634 if self.selections.disjoint_anchor_ranges().any(|s| {
3635 if s.start.buffer_id != selection.start.buffer_id
3636 || s.end.buffer_id != selection.end.buffer_id
3637 {
3638 return false;
3639 }
3640 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3641 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3642 }) {
3643 continue;
3644 }
3645 let start = buffer_snapshot.anchor_after(start_offset);
3646 let end = buffer_snapshot.anchor_after(end_offset);
3647 linked_edits
3648 .entry(buffer.clone())
3649 .or_default()
3650 .push(start..end);
3651 }
3652 Some(linked_edits)
3653 }
3654
3655 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3656 let text: Arc<str> = text.into();
3657
3658 if self.read_only(cx) {
3659 return;
3660 }
3661
3662 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3663
3664 let selections = self.selections.all_adjusted(cx);
3665 let mut bracket_inserted = false;
3666 let mut edits = Vec::new();
3667 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3668 let mut new_selections = Vec::with_capacity(selections.len());
3669 let mut new_autoclose_regions = Vec::new();
3670 let snapshot = self.buffer.read(cx).read(cx);
3671 let mut clear_linked_edit_ranges = false;
3672
3673 for (selection, autoclose_region) in
3674 self.selections_with_autoclose_regions(selections, &snapshot)
3675 {
3676 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3677 // Determine if the inserted text matches the opening or closing
3678 // bracket of any of this language's bracket pairs.
3679 let mut bracket_pair = None;
3680 let mut is_bracket_pair_start = false;
3681 let mut is_bracket_pair_end = false;
3682 if !text.is_empty() {
3683 let mut bracket_pair_matching_end = None;
3684 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3685 // and they are removing the character that triggered IME popup.
3686 for (pair, enabled) in scope.brackets() {
3687 if !pair.close && !pair.surround {
3688 continue;
3689 }
3690
3691 if enabled && pair.start.ends_with(text.as_ref()) {
3692 let prefix_len = pair.start.len() - text.len();
3693 let preceding_text_matches_prefix = prefix_len == 0
3694 || (selection.start.column >= (prefix_len as u32)
3695 && snapshot.contains_str_at(
3696 Point::new(
3697 selection.start.row,
3698 selection.start.column - (prefix_len as u32),
3699 ),
3700 &pair.start[..prefix_len],
3701 ));
3702 if preceding_text_matches_prefix {
3703 bracket_pair = Some(pair.clone());
3704 is_bracket_pair_start = true;
3705 break;
3706 }
3707 }
3708 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3709 {
3710 // take first bracket pair matching end, but don't break in case a later bracket
3711 // pair matches start
3712 bracket_pair_matching_end = Some(pair.clone());
3713 }
3714 }
3715 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3716 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3717 is_bracket_pair_end = true;
3718 }
3719 }
3720
3721 if let Some(bracket_pair) = bracket_pair {
3722 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3723 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3724 let auto_surround =
3725 self.use_auto_surround && snapshot_settings.use_auto_surround;
3726 if selection.is_empty() {
3727 if is_bracket_pair_start {
3728 // If the inserted text is a suffix of an opening bracket and the
3729 // selection is preceded by the rest of the opening bracket, then
3730 // insert the closing bracket.
3731 let following_text_allows_autoclose = snapshot
3732 .chars_at(selection.start)
3733 .next()
3734 .map_or(true, |c| scope.should_autoclose_before(c));
3735
3736 let preceding_text_allows_autoclose = selection.start.column == 0
3737 || snapshot.reversed_chars_at(selection.start).next().map_or(
3738 true,
3739 |c| {
3740 bracket_pair.start != bracket_pair.end
3741 || !snapshot
3742 .char_classifier_at(selection.start)
3743 .is_word(c)
3744 },
3745 );
3746
3747 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3748 && bracket_pair.start.len() == 1
3749 {
3750 let target = bracket_pair.start.chars().next().unwrap();
3751 let current_line_count = snapshot
3752 .reversed_chars_at(selection.start)
3753 .take_while(|&c| c != '\n')
3754 .filter(|&c| c == target)
3755 .count();
3756 current_line_count % 2 == 1
3757 } else {
3758 false
3759 };
3760
3761 if autoclose
3762 && bracket_pair.close
3763 && following_text_allows_autoclose
3764 && preceding_text_allows_autoclose
3765 && !is_closing_quote
3766 {
3767 let anchor = snapshot.anchor_before(selection.end);
3768 new_selections.push((selection.map(|_| anchor), text.len()));
3769 new_autoclose_regions.push((
3770 anchor,
3771 text.len(),
3772 selection.id,
3773 bracket_pair.clone(),
3774 ));
3775 edits.push((
3776 selection.range(),
3777 format!("{}{}", text, bracket_pair.end).into(),
3778 ));
3779 bracket_inserted = true;
3780 continue;
3781 }
3782 }
3783
3784 if let Some(region) = autoclose_region {
3785 // If the selection is followed by an auto-inserted closing bracket,
3786 // then don't insert that closing bracket again; just move the selection
3787 // past the closing bracket.
3788 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3789 && text.as_ref() == region.pair.end.as_str();
3790 if should_skip {
3791 let anchor = snapshot.anchor_after(selection.end);
3792 new_selections
3793 .push((selection.map(|_| anchor), region.pair.end.len()));
3794 continue;
3795 }
3796 }
3797
3798 let always_treat_brackets_as_autoclosed = snapshot
3799 .language_settings_at(selection.start, cx)
3800 .always_treat_brackets_as_autoclosed;
3801 if always_treat_brackets_as_autoclosed
3802 && is_bracket_pair_end
3803 && snapshot.contains_str_at(selection.end, text.as_ref())
3804 {
3805 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3806 // and the inserted text is a closing bracket and the selection is followed
3807 // by the closing bracket then move the selection past the closing bracket.
3808 let anchor = snapshot.anchor_after(selection.end);
3809 new_selections.push((selection.map(|_| anchor), text.len()));
3810 continue;
3811 }
3812 }
3813 // If an opening bracket is 1 character long and is typed while
3814 // text is selected, then surround that text with the bracket pair.
3815 else if auto_surround
3816 && bracket_pair.surround
3817 && is_bracket_pair_start
3818 && bracket_pair.start.chars().count() == 1
3819 {
3820 edits.push((selection.start..selection.start, text.clone()));
3821 edits.push((
3822 selection.end..selection.end,
3823 bracket_pair.end.as_str().into(),
3824 ));
3825 bracket_inserted = true;
3826 new_selections.push((
3827 Selection {
3828 id: selection.id,
3829 start: snapshot.anchor_after(selection.start),
3830 end: snapshot.anchor_before(selection.end),
3831 reversed: selection.reversed,
3832 goal: selection.goal,
3833 },
3834 0,
3835 ));
3836 continue;
3837 }
3838 }
3839 }
3840
3841 if self.auto_replace_emoji_shortcode
3842 && selection.is_empty()
3843 && text.as_ref().ends_with(':')
3844 {
3845 if let Some(possible_emoji_short_code) =
3846 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3847 {
3848 if !possible_emoji_short_code.is_empty() {
3849 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3850 let emoji_shortcode_start = Point::new(
3851 selection.start.row,
3852 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3853 );
3854
3855 // Remove shortcode from buffer
3856 edits.push((
3857 emoji_shortcode_start..selection.start,
3858 "".to_string().into(),
3859 ));
3860 new_selections.push((
3861 Selection {
3862 id: selection.id,
3863 start: snapshot.anchor_after(emoji_shortcode_start),
3864 end: snapshot.anchor_before(selection.start),
3865 reversed: selection.reversed,
3866 goal: selection.goal,
3867 },
3868 0,
3869 ));
3870
3871 // Insert emoji
3872 let selection_start_anchor = snapshot.anchor_after(selection.start);
3873 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3874 edits.push((selection.start..selection.end, emoji.to_string().into()));
3875
3876 continue;
3877 }
3878 }
3879 }
3880 }
3881
3882 // If not handling any auto-close operation, then just replace the selected
3883 // text with the given input and move the selection to the end of the
3884 // newly inserted text.
3885 let anchor = snapshot.anchor_after(selection.end);
3886 if !self.linked_edit_ranges.is_empty() {
3887 let start_anchor = snapshot.anchor_before(selection.start);
3888
3889 let is_word_char = text.chars().next().map_or(true, |char| {
3890 let classifier = snapshot
3891 .char_classifier_at(start_anchor.to_offset(&snapshot))
3892 .ignore_punctuation(true);
3893 classifier.is_word(char)
3894 });
3895
3896 if is_word_char {
3897 if let Some(ranges) = self
3898 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3899 {
3900 for (buffer, edits) in ranges {
3901 linked_edits
3902 .entry(buffer.clone())
3903 .or_default()
3904 .extend(edits.into_iter().map(|range| (range, text.clone())));
3905 }
3906 }
3907 } else {
3908 clear_linked_edit_ranges = true;
3909 }
3910 }
3911
3912 new_selections.push((selection.map(|_| anchor), 0));
3913 edits.push((selection.start..selection.end, text.clone()));
3914 }
3915
3916 drop(snapshot);
3917
3918 self.transact(window, cx, |this, window, cx| {
3919 if clear_linked_edit_ranges {
3920 this.linked_edit_ranges.clear();
3921 }
3922 let initial_buffer_versions =
3923 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3924
3925 this.buffer.update(cx, |buffer, cx| {
3926 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3927 });
3928 for (buffer, edits) in linked_edits {
3929 buffer.update(cx, |buffer, cx| {
3930 let snapshot = buffer.snapshot();
3931 let edits = edits
3932 .into_iter()
3933 .map(|(range, text)| {
3934 use text::ToPoint as TP;
3935 let end_point = TP::to_point(&range.end, &snapshot);
3936 let start_point = TP::to_point(&range.start, &snapshot);
3937 (start_point..end_point, text)
3938 })
3939 .sorted_by_key(|(range, _)| range.start);
3940 buffer.edit(edits, None, cx);
3941 })
3942 }
3943 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3944 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3945 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3946 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3947 .zip(new_selection_deltas)
3948 .map(|(selection, delta)| Selection {
3949 id: selection.id,
3950 start: selection.start + delta,
3951 end: selection.end + delta,
3952 reversed: selection.reversed,
3953 goal: SelectionGoal::None,
3954 })
3955 .collect::<Vec<_>>();
3956
3957 let mut i = 0;
3958 for (position, delta, selection_id, pair) in new_autoclose_regions {
3959 let position = position.to_offset(&map.buffer_snapshot) + delta;
3960 let start = map.buffer_snapshot.anchor_before(position);
3961 let end = map.buffer_snapshot.anchor_after(position);
3962 while let Some(existing_state) = this.autoclose_regions.get(i) {
3963 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3964 Ordering::Less => i += 1,
3965 Ordering::Greater => break,
3966 Ordering::Equal => {
3967 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3968 Ordering::Less => i += 1,
3969 Ordering::Equal => break,
3970 Ordering::Greater => break,
3971 }
3972 }
3973 }
3974 }
3975 this.autoclose_regions.insert(
3976 i,
3977 AutocloseRegion {
3978 selection_id,
3979 range: start..end,
3980 pair,
3981 },
3982 );
3983 }
3984
3985 let had_active_inline_completion = this.has_active_inline_completion();
3986 this.change_selections_without_updating_completions(
3987 Some(Autoscroll::fit()),
3988 window,
3989 cx,
3990 |s| s.select(new_selections),
3991 );
3992
3993 if !bracket_inserted {
3994 if let Some(on_type_format_task) =
3995 this.trigger_on_type_formatting(text.to_string(), window, cx)
3996 {
3997 on_type_format_task.detach_and_log_err(cx);
3998 }
3999 }
4000
4001 let editor_settings = EditorSettings::get_global(cx);
4002 if bracket_inserted
4003 && (editor_settings.auto_signature_help
4004 || editor_settings.show_signature_help_after_edits)
4005 {
4006 this.show_signature_help(&ShowSignatureHelp, window, cx);
4007 }
4008
4009 let trigger_in_words =
4010 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4011 if this.hard_wrap.is_some() {
4012 let latest: Range<Point> = this.selections.newest(cx).range();
4013 if latest.is_empty()
4014 && this
4015 .buffer()
4016 .read(cx)
4017 .snapshot(cx)
4018 .line_len(MultiBufferRow(latest.start.row))
4019 == latest.start.column
4020 {
4021 this.rewrap_impl(
4022 RewrapOptions {
4023 override_language_settings: true,
4024 preserve_existing_whitespace: true,
4025 },
4026 cx,
4027 )
4028 }
4029 }
4030 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4031 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4032 this.refresh_inline_completion(true, false, window, cx);
4033 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4034 });
4035 }
4036
4037 fn find_possible_emoji_shortcode_at_position(
4038 snapshot: &MultiBufferSnapshot,
4039 position: Point,
4040 ) -> Option<String> {
4041 let mut chars = Vec::new();
4042 let mut found_colon = false;
4043 for char in snapshot.reversed_chars_at(position).take(100) {
4044 // Found a possible emoji shortcode in the middle of the buffer
4045 if found_colon {
4046 if char.is_whitespace() {
4047 chars.reverse();
4048 return Some(chars.iter().collect());
4049 }
4050 // If the previous character is not a whitespace, we are in the middle of a word
4051 // and we only want to complete the shortcode if the word is made up of other emojis
4052 let mut containing_word = String::new();
4053 for ch in snapshot
4054 .reversed_chars_at(position)
4055 .skip(chars.len() + 1)
4056 .take(100)
4057 {
4058 if ch.is_whitespace() {
4059 break;
4060 }
4061 containing_word.push(ch);
4062 }
4063 let containing_word = containing_word.chars().rev().collect::<String>();
4064 if util::word_consists_of_emojis(containing_word.as_str()) {
4065 chars.reverse();
4066 return Some(chars.iter().collect());
4067 }
4068 }
4069
4070 if char.is_whitespace() || !char.is_ascii() {
4071 return None;
4072 }
4073 if char == ':' {
4074 found_colon = true;
4075 } else {
4076 chars.push(char);
4077 }
4078 }
4079 // Found a possible emoji shortcode at the beginning of the buffer
4080 chars.reverse();
4081 Some(chars.iter().collect())
4082 }
4083
4084 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4085 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4086 self.transact(window, cx, |this, window, cx| {
4087 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4088 let selections = this.selections.all::<usize>(cx);
4089 let multi_buffer = this.buffer.read(cx);
4090 let buffer = multi_buffer.snapshot(cx);
4091 selections
4092 .iter()
4093 .map(|selection| {
4094 let start_point = selection.start.to_point(&buffer);
4095 let mut existing_indent =
4096 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4097 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4098 let start = selection.start;
4099 let end = selection.end;
4100 let selection_is_empty = start == end;
4101 let language_scope = buffer.language_scope_at(start);
4102 let (
4103 comment_delimiter,
4104 doc_delimiter,
4105 insert_extra_newline,
4106 indent_on_newline,
4107 indent_on_extra_newline,
4108 ) = if let Some(language) = &language_scope {
4109 let mut insert_extra_newline =
4110 insert_extra_newline_brackets(&buffer, start..end, language)
4111 || insert_extra_newline_tree_sitter(&buffer, start..end);
4112
4113 // Comment extension on newline is allowed only for cursor selections
4114 let comment_delimiter = maybe!({
4115 if !selection_is_empty {
4116 return None;
4117 }
4118
4119 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4120 return None;
4121 }
4122
4123 let delimiters = language.line_comment_prefixes();
4124 let max_len_of_delimiter =
4125 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4126 let (snapshot, range) =
4127 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4128
4129 let num_of_whitespaces = snapshot
4130 .chars_for_range(range.clone())
4131 .take_while(|c| c.is_whitespace())
4132 .count();
4133 let comment_candidate = snapshot
4134 .chars_for_range(range)
4135 .skip(num_of_whitespaces)
4136 .take(max_len_of_delimiter)
4137 .collect::<String>();
4138 let (delimiter, trimmed_len) = delimiters
4139 .iter()
4140 .filter_map(|delimiter| {
4141 let prefix = delimiter.trim_end();
4142 if comment_candidate.starts_with(prefix) {
4143 Some((delimiter, prefix.len()))
4144 } else {
4145 None
4146 }
4147 })
4148 .max_by_key(|(_, len)| *len)?;
4149
4150 let cursor_is_placed_after_comment_marker =
4151 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4152 if cursor_is_placed_after_comment_marker {
4153 Some(delimiter.clone())
4154 } else {
4155 None
4156 }
4157 });
4158
4159 let mut indent_on_newline = IndentSize::spaces(0);
4160 let mut indent_on_extra_newline = IndentSize::spaces(0);
4161
4162 let doc_delimiter = maybe!({
4163 if !selection_is_empty {
4164 return None;
4165 }
4166
4167 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4168 return None;
4169 }
4170
4171 let DocumentationConfig {
4172 start: start_tag,
4173 end: end_tag,
4174 prefix: delimiter,
4175 tab_size: len,
4176 } = language.documentation()?;
4177
4178 let is_within_block_comment = buffer
4179 .language_scope_at(start_point)
4180 .is_some_and(|scope| scope.override_name() == Some("comment"));
4181 if !is_within_block_comment {
4182 return None;
4183 }
4184
4185 let (snapshot, range) =
4186 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4187
4188 let num_of_whitespaces = snapshot
4189 .chars_for_range(range.clone())
4190 .take_while(|c| c.is_whitespace())
4191 .count();
4192
4193 // It is safe to use a column from MultiBufferPoint in context of a single buffer ranges, because we're only ever looking at a single line at a time.
4194 let column = start_point.column;
4195 let cursor_is_after_start_tag = {
4196 let start_tag_len = start_tag.len();
4197 let start_tag_line = snapshot
4198 .chars_for_range(range.clone())
4199 .skip(num_of_whitespaces)
4200 .take(start_tag_len)
4201 .collect::<String>();
4202 if start_tag_line.starts_with(start_tag.as_ref()) {
4203 num_of_whitespaces + start_tag_len <= column as usize
4204 } else {
4205 false
4206 }
4207 };
4208
4209 let cursor_is_after_delimiter = {
4210 let delimiter_trim = delimiter.trim_end();
4211 let delimiter_line = snapshot
4212 .chars_for_range(range.clone())
4213 .skip(num_of_whitespaces)
4214 .take(delimiter_trim.len())
4215 .collect::<String>();
4216 if delimiter_line.starts_with(delimiter_trim) {
4217 num_of_whitespaces + delimiter_trim.len() <= column as usize
4218 } else {
4219 false
4220 }
4221 };
4222
4223 let cursor_is_before_end_tag_if_exists = {
4224 let mut char_position = 0u32;
4225 let mut end_tag_offset = None;
4226
4227 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4228 if let Some(byte_pos) = chunk.find(&**end_tag) {
4229 let chars_before_match =
4230 chunk[..byte_pos].chars().count() as u32;
4231 end_tag_offset =
4232 Some(char_position + chars_before_match);
4233 break 'outer;
4234 }
4235 char_position += chunk.chars().count() as u32;
4236 }
4237
4238 if let Some(end_tag_offset) = end_tag_offset {
4239 let cursor_is_before_end_tag = column <= end_tag_offset;
4240 if cursor_is_after_start_tag {
4241 if cursor_is_before_end_tag {
4242 insert_extra_newline = true;
4243 }
4244 let cursor_is_at_start_of_end_tag =
4245 column == end_tag_offset;
4246 if cursor_is_at_start_of_end_tag {
4247 indent_on_extra_newline.len = (*len).into();
4248 }
4249 }
4250 cursor_is_before_end_tag
4251 } else {
4252 true
4253 }
4254 };
4255
4256 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4257 && cursor_is_before_end_tag_if_exists
4258 {
4259 if cursor_is_after_start_tag {
4260 indent_on_newline.len = (*len).into();
4261 }
4262 Some(delimiter.clone())
4263 } else {
4264 None
4265 }
4266 });
4267
4268 (
4269 comment_delimiter,
4270 doc_delimiter,
4271 insert_extra_newline,
4272 indent_on_newline,
4273 indent_on_extra_newline,
4274 )
4275 } else {
4276 (
4277 None,
4278 None,
4279 false,
4280 IndentSize::default(),
4281 IndentSize::default(),
4282 )
4283 };
4284
4285 let prevent_auto_indent = doc_delimiter.is_some();
4286 let delimiter = comment_delimiter.or(doc_delimiter);
4287
4288 let capacity_for_delimiter =
4289 delimiter.as_deref().map(str::len).unwrap_or_default();
4290 let mut new_text = String::with_capacity(
4291 1 + capacity_for_delimiter
4292 + existing_indent.len as usize
4293 + indent_on_newline.len as usize
4294 + indent_on_extra_newline.len as usize,
4295 );
4296 new_text.push('\n');
4297 new_text.extend(existing_indent.chars());
4298 new_text.extend(indent_on_newline.chars());
4299
4300 if let Some(delimiter) = &delimiter {
4301 new_text.push_str(delimiter);
4302 }
4303
4304 if insert_extra_newline {
4305 new_text.push('\n');
4306 new_text.extend(existing_indent.chars());
4307 new_text.extend(indent_on_extra_newline.chars());
4308 }
4309
4310 let anchor = buffer.anchor_after(end);
4311 let new_selection = selection.map(|_| anchor);
4312 (
4313 ((start..end, new_text), prevent_auto_indent),
4314 (insert_extra_newline, new_selection),
4315 )
4316 })
4317 .unzip()
4318 };
4319
4320 let mut auto_indent_edits = Vec::new();
4321 let mut edits = Vec::new();
4322 for (edit, prevent_auto_indent) in edits_with_flags {
4323 if prevent_auto_indent {
4324 edits.push(edit);
4325 } else {
4326 auto_indent_edits.push(edit);
4327 }
4328 }
4329 if !edits.is_empty() {
4330 this.edit(edits, cx);
4331 }
4332 if !auto_indent_edits.is_empty() {
4333 this.edit_with_autoindent(auto_indent_edits, cx);
4334 }
4335
4336 let buffer = this.buffer.read(cx).snapshot(cx);
4337 let new_selections = selection_info
4338 .into_iter()
4339 .map(|(extra_newline_inserted, new_selection)| {
4340 let mut cursor = new_selection.end.to_point(&buffer);
4341 if extra_newline_inserted {
4342 cursor.row -= 1;
4343 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4344 }
4345 new_selection.map(|_| cursor)
4346 })
4347 .collect();
4348
4349 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4350 s.select(new_selections)
4351 });
4352 this.refresh_inline_completion(true, false, window, cx);
4353 });
4354 }
4355
4356 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4357 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4358
4359 let buffer = self.buffer.read(cx);
4360 let snapshot = buffer.snapshot(cx);
4361
4362 let mut edits = Vec::new();
4363 let mut rows = Vec::new();
4364
4365 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4366 let cursor = selection.head();
4367 let row = cursor.row;
4368
4369 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4370
4371 let newline = "\n".to_string();
4372 edits.push((start_of_line..start_of_line, newline));
4373
4374 rows.push(row + rows_inserted as u32);
4375 }
4376
4377 self.transact(window, cx, |editor, window, cx| {
4378 editor.edit(edits, cx);
4379
4380 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4381 let mut index = 0;
4382 s.move_cursors_with(|map, _, _| {
4383 let row = rows[index];
4384 index += 1;
4385
4386 let point = Point::new(row, 0);
4387 let boundary = map.next_line_boundary(point).1;
4388 let clipped = map.clip_point(boundary, Bias::Left);
4389
4390 (clipped, SelectionGoal::None)
4391 });
4392 });
4393
4394 let mut indent_edits = Vec::new();
4395 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4396 for row in rows {
4397 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4398 for (row, indent) in indents {
4399 if indent.len == 0 {
4400 continue;
4401 }
4402
4403 let text = match indent.kind {
4404 IndentKind::Space => " ".repeat(indent.len as usize),
4405 IndentKind::Tab => "\t".repeat(indent.len as usize),
4406 };
4407 let point = Point::new(row.0, 0);
4408 indent_edits.push((point..point, text));
4409 }
4410 }
4411 editor.edit(indent_edits, cx);
4412 });
4413 }
4414
4415 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4416 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4417
4418 let buffer = self.buffer.read(cx);
4419 let snapshot = buffer.snapshot(cx);
4420
4421 let mut edits = Vec::new();
4422 let mut rows = Vec::new();
4423 let mut rows_inserted = 0;
4424
4425 for selection in self.selections.all_adjusted(cx) {
4426 let cursor = selection.head();
4427 let row = cursor.row;
4428
4429 let point = Point::new(row + 1, 0);
4430 let start_of_line = snapshot.clip_point(point, Bias::Left);
4431
4432 let newline = "\n".to_string();
4433 edits.push((start_of_line..start_of_line, newline));
4434
4435 rows_inserted += 1;
4436 rows.push(row + rows_inserted);
4437 }
4438
4439 self.transact(window, cx, |editor, window, cx| {
4440 editor.edit(edits, cx);
4441
4442 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4443 let mut index = 0;
4444 s.move_cursors_with(|map, _, _| {
4445 let row = rows[index];
4446 index += 1;
4447
4448 let point = Point::new(row, 0);
4449 let boundary = map.next_line_boundary(point).1;
4450 let clipped = map.clip_point(boundary, Bias::Left);
4451
4452 (clipped, SelectionGoal::None)
4453 });
4454 });
4455
4456 let mut indent_edits = Vec::new();
4457 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4458 for row in rows {
4459 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4460 for (row, indent) in indents {
4461 if indent.len == 0 {
4462 continue;
4463 }
4464
4465 let text = match indent.kind {
4466 IndentKind::Space => " ".repeat(indent.len as usize),
4467 IndentKind::Tab => "\t".repeat(indent.len as usize),
4468 };
4469 let point = Point::new(row.0, 0);
4470 indent_edits.push((point..point, text));
4471 }
4472 }
4473 editor.edit(indent_edits, cx);
4474 });
4475 }
4476
4477 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4478 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4479 original_indent_columns: Vec::new(),
4480 });
4481 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4482 }
4483
4484 fn insert_with_autoindent_mode(
4485 &mut self,
4486 text: &str,
4487 autoindent_mode: Option<AutoindentMode>,
4488 window: &mut Window,
4489 cx: &mut Context<Self>,
4490 ) {
4491 if self.read_only(cx) {
4492 return;
4493 }
4494
4495 let text: Arc<str> = text.into();
4496 self.transact(window, cx, |this, window, cx| {
4497 let old_selections = this.selections.all_adjusted(cx);
4498 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4499 let anchors = {
4500 let snapshot = buffer.read(cx);
4501 old_selections
4502 .iter()
4503 .map(|s| {
4504 let anchor = snapshot.anchor_after(s.head());
4505 s.map(|_| anchor)
4506 })
4507 .collect::<Vec<_>>()
4508 };
4509 buffer.edit(
4510 old_selections
4511 .iter()
4512 .map(|s| (s.start..s.end, text.clone())),
4513 autoindent_mode,
4514 cx,
4515 );
4516 anchors
4517 });
4518
4519 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4520 s.select_anchors(selection_anchors);
4521 });
4522
4523 cx.notify();
4524 });
4525 }
4526
4527 fn trigger_completion_on_input(
4528 &mut self,
4529 text: &str,
4530 trigger_in_words: bool,
4531 window: &mut Window,
4532 cx: &mut Context<Self>,
4533 ) {
4534 let completions_source = self
4535 .context_menu
4536 .borrow()
4537 .as_ref()
4538 .and_then(|menu| match menu {
4539 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4540 CodeContextMenu::CodeActions(_) => None,
4541 });
4542
4543 match completions_source {
4544 Some(CompletionsMenuSource::Words) => {
4545 self.show_word_completions(&ShowWordCompletions, window, cx)
4546 }
4547 Some(CompletionsMenuSource::Normal)
4548 | Some(CompletionsMenuSource::SnippetChoices)
4549 | None
4550 if self.is_completion_trigger(
4551 text,
4552 trigger_in_words,
4553 completions_source.is_some(),
4554 cx,
4555 ) =>
4556 {
4557 self.show_completions(
4558 &ShowCompletions {
4559 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4560 },
4561 window,
4562 cx,
4563 )
4564 }
4565 _ => {
4566 self.hide_context_menu(window, cx);
4567 }
4568 }
4569 }
4570
4571 fn is_completion_trigger(
4572 &self,
4573 text: &str,
4574 trigger_in_words: bool,
4575 menu_is_open: bool,
4576 cx: &mut Context<Self>,
4577 ) -> bool {
4578 let position = self.selections.newest_anchor().head();
4579 let multibuffer = self.buffer.read(cx);
4580 let Some(buffer) = position
4581 .buffer_id
4582 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4583 else {
4584 return false;
4585 };
4586
4587 if let Some(completion_provider) = &self.completion_provider {
4588 completion_provider.is_completion_trigger(
4589 &buffer,
4590 position.text_anchor,
4591 text,
4592 trigger_in_words,
4593 menu_is_open,
4594 cx,
4595 )
4596 } else {
4597 false
4598 }
4599 }
4600
4601 /// If any empty selections is touching the start of its innermost containing autoclose
4602 /// region, expand it to select the brackets.
4603 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4604 let selections = self.selections.all::<usize>(cx);
4605 let buffer = self.buffer.read(cx).read(cx);
4606 let new_selections = self
4607 .selections_with_autoclose_regions(selections, &buffer)
4608 .map(|(mut selection, region)| {
4609 if !selection.is_empty() {
4610 return selection;
4611 }
4612
4613 if let Some(region) = region {
4614 let mut range = region.range.to_offset(&buffer);
4615 if selection.start == range.start && range.start >= region.pair.start.len() {
4616 range.start -= region.pair.start.len();
4617 if buffer.contains_str_at(range.start, ®ion.pair.start)
4618 && buffer.contains_str_at(range.end, ®ion.pair.end)
4619 {
4620 range.end += region.pair.end.len();
4621 selection.start = range.start;
4622 selection.end = range.end;
4623
4624 return selection;
4625 }
4626 }
4627 }
4628
4629 let always_treat_brackets_as_autoclosed = buffer
4630 .language_settings_at(selection.start, cx)
4631 .always_treat_brackets_as_autoclosed;
4632
4633 if !always_treat_brackets_as_autoclosed {
4634 return selection;
4635 }
4636
4637 if let Some(scope) = buffer.language_scope_at(selection.start) {
4638 for (pair, enabled) in scope.brackets() {
4639 if !enabled || !pair.close {
4640 continue;
4641 }
4642
4643 if buffer.contains_str_at(selection.start, &pair.end) {
4644 let pair_start_len = pair.start.len();
4645 if buffer.contains_str_at(
4646 selection.start.saturating_sub(pair_start_len),
4647 &pair.start,
4648 ) {
4649 selection.start -= pair_start_len;
4650 selection.end += pair.end.len();
4651
4652 return selection;
4653 }
4654 }
4655 }
4656 }
4657
4658 selection
4659 })
4660 .collect();
4661
4662 drop(buffer);
4663 self.change_selections(None, window, cx, |selections| {
4664 selections.select(new_selections)
4665 });
4666 }
4667
4668 /// Iterate the given selections, and for each one, find the smallest surrounding
4669 /// autoclose region. This uses the ordering of the selections and the autoclose
4670 /// regions to avoid repeated comparisons.
4671 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4672 &'a self,
4673 selections: impl IntoIterator<Item = Selection<D>>,
4674 buffer: &'a MultiBufferSnapshot,
4675 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4676 let mut i = 0;
4677 let mut regions = self.autoclose_regions.as_slice();
4678 selections.into_iter().map(move |selection| {
4679 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4680
4681 let mut enclosing = None;
4682 while let Some(pair_state) = regions.get(i) {
4683 if pair_state.range.end.to_offset(buffer) < range.start {
4684 regions = ®ions[i + 1..];
4685 i = 0;
4686 } else if pair_state.range.start.to_offset(buffer) > range.end {
4687 break;
4688 } else {
4689 if pair_state.selection_id == selection.id {
4690 enclosing = Some(pair_state);
4691 }
4692 i += 1;
4693 }
4694 }
4695
4696 (selection, enclosing)
4697 })
4698 }
4699
4700 /// Remove any autoclose regions that no longer contain their selection.
4701 fn invalidate_autoclose_regions(
4702 &mut self,
4703 mut selections: &[Selection<Anchor>],
4704 buffer: &MultiBufferSnapshot,
4705 ) {
4706 self.autoclose_regions.retain(|state| {
4707 let mut i = 0;
4708 while let Some(selection) = selections.get(i) {
4709 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4710 selections = &selections[1..];
4711 continue;
4712 }
4713 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4714 break;
4715 }
4716 if selection.id == state.selection_id {
4717 return true;
4718 } else {
4719 i += 1;
4720 }
4721 }
4722 false
4723 });
4724 }
4725
4726 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4727 let offset = position.to_offset(buffer);
4728 let (word_range, kind) = buffer.surrounding_word(offset, true);
4729 if offset > word_range.start && kind == Some(CharKind::Word) {
4730 Some(
4731 buffer
4732 .text_for_range(word_range.start..offset)
4733 .collect::<String>(),
4734 )
4735 } else {
4736 None
4737 }
4738 }
4739
4740 pub fn toggle_inline_values(
4741 &mut self,
4742 _: &ToggleInlineValues,
4743 _: &mut Window,
4744 cx: &mut Context<Self>,
4745 ) {
4746 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4747
4748 self.refresh_inline_values(cx);
4749 }
4750
4751 pub fn toggle_inlay_hints(
4752 &mut self,
4753 _: &ToggleInlayHints,
4754 _: &mut Window,
4755 cx: &mut Context<Self>,
4756 ) {
4757 self.refresh_inlay_hints(
4758 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4759 cx,
4760 );
4761 }
4762
4763 pub fn inlay_hints_enabled(&self) -> bool {
4764 self.inlay_hint_cache.enabled
4765 }
4766
4767 pub fn inline_values_enabled(&self) -> bool {
4768 self.inline_value_cache.enabled
4769 }
4770
4771 #[cfg(any(test, feature = "test-support"))]
4772 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4773 self.display_map
4774 .read(cx)
4775 .current_inlays()
4776 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4777 .cloned()
4778 .collect()
4779 }
4780
4781 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4782 if self.semantics_provider.is_none() || !self.mode.is_full() {
4783 return;
4784 }
4785
4786 let reason_description = reason.description();
4787 let ignore_debounce = matches!(
4788 reason,
4789 InlayHintRefreshReason::SettingsChange(_)
4790 | InlayHintRefreshReason::Toggle(_)
4791 | InlayHintRefreshReason::ExcerptsRemoved(_)
4792 | InlayHintRefreshReason::ModifiersChanged(_)
4793 );
4794 let (invalidate_cache, required_languages) = match reason {
4795 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4796 match self.inlay_hint_cache.modifiers_override(enabled) {
4797 Some(enabled) => {
4798 if enabled {
4799 (InvalidationStrategy::RefreshRequested, None)
4800 } else {
4801 self.splice_inlays(
4802 &self
4803 .visible_inlay_hints(cx)
4804 .iter()
4805 .map(|inlay| inlay.id)
4806 .collect::<Vec<InlayId>>(),
4807 Vec::new(),
4808 cx,
4809 );
4810 return;
4811 }
4812 }
4813 None => return,
4814 }
4815 }
4816 InlayHintRefreshReason::Toggle(enabled) => {
4817 if self.inlay_hint_cache.toggle(enabled) {
4818 if enabled {
4819 (InvalidationStrategy::RefreshRequested, None)
4820 } else {
4821 self.splice_inlays(
4822 &self
4823 .visible_inlay_hints(cx)
4824 .iter()
4825 .map(|inlay| inlay.id)
4826 .collect::<Vec<InlayId>>(),
4827 Vec::new(),
4828 cx,
4829 );
4830 return;
4831 }
4832 } else {
4833 return;
4834 }
4835 }
4836 InlayHintRefreshReason::SettingsChange(new_settings) => {
4837 match self.inlay_hint_cache.update_settings(
4838 &self.buffer,
4839 new_settings,
4840 self.visible_inlay_hints(cx),
4841 cx,
4842 ) {
4843 ControlFlow::Break(Some(InlaySplice {
4844 to_remove,
4845 to_insert,
4846 })) => {
4847 self.splice_inlays(&to_remove, to_insert, cx);
4848 return;
4849 }
4850 ControlFlow::Break(None) => return,
4851 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4852 }
4853 }
4854 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4855 if let Some(InlaySplice {
4856 to_remove,
4857 to_insert,
4858 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4859 {
4860 self.splice_inlays(&to_remove, to_insert, cx);
4861 }
4862 self.display_map.update(cx, |display_map, _| {
4863 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4864 });
4865 return;
4866 }
4867 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4868 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4869 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4870 }
4871 InlayHintRefreshReason::RefreshRequested => {
4872 (InvalidationStrategy::RefreshRequested, None)
4873 }
4874 };
4875
4876 if let Some(InlaySplice {
4877 to_remove,
4878 to_insert,
4879 }) = self.inlay_hint_cache.spawn_hint_refresh(
4880 reason_description,
4881 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4882 invalidate_cache,
4883 ignore_debounce,
4884 cx,
4885 ) {
4886 self.splice_inlays(&to_remove, to_insert, cx);
4887 }
4888 }
4889
4890 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4891 self.display_map
4892 .read(cx)
4893 .current_inlays()
4894 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4895 .cloned()
4896 .collect()
4897 }
4898
4899 pub fn excerpts_for_inlay_hints_query(
4900 &self,
4901 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4902 cx: &mut Context<Editor>,
4903 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4904 let Some(project) = self.project.as_ref() else {
4905 return HashMap::default();
4906 };
4907 let project = project.read(cx);
4908 let multi_buffer = self.buffer().read(cx);
4909 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4910 let multi_buffer_visible_start = self
4911 .scroll_manager
4912 .anchor()
4913 .anchor
4914 .to_point(&multi_buffer_snapshot);
4915 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4916 multi_buffer_visible_start
4917 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4918 Bias::Left,
4919 );
4920 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4921 multi_buffer_snapshot
4922 .range_to_buffer_ranges(multi_buffer_visible_range)
4923 .into_iter()
4924 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4925 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4926 let buffer_file = project::File::from_dyn(buffer.file())?;
4927 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4928 let worktree_entry = buffer_worktree
4929 .read(cx)
4930 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4931 if worktree_entry.is_ignored {
4932 return None;
4933 }
4934
4935 let language = buffer.language()?;
4936 if let Some(restrict_to_languages) = restrict_to_languages {
4937 if !restrict_to_languages.contains(language) {
4938 return None;
4939 }
4940 }
4941 Some((
4942 excerpt_id,
4943 (
4944 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4945 buffer.version().clone(),
4946 excerpt_visible_range,
4947 ),
4948 ))
4949 })
4950 .collect()
4951 }
4952
4953 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4954 TextLayoutDetails {
4955 text_system: window.text_system().clone(),
4956 editor_style: self.style.clone().unwrap(),
4957 rem_size: window.rem_size(),
4958 scroll_anchor: self.scroll_manager.anchor(),
4959 visible_rows: self.visible_line_count(),
4960 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4961 }
4962 }
4963
4964 pub fn splice_inlays(
4965 &self,
4966 to_remove: &[InlayId],
4967 to_insert: Vec<Inlay>,
4968 cx: &mut Context<Self>,
4969 ) {
4970 self.display_map.update(cx, |display_map, cx| {
4971 display_map.splice_inlays(to_remove, to_insert, cx)
4972 });
4973 cx.notify();
4974 }
4975
4976 fn trigger_on_type_formatting(
4977 &self,
4978 input: String,
4979 window: &mut Window,
4980 cx: &mut Context<Self>,
4981 ) -> Option<Task<Result<()>>> {
4982 if input.len() != 1 {
4983 return None;
4984 }
4985
4986 let project = self.project.as_ref()?;
4987 let position = self.selections.newest_anchor().head();
4988 let (buffer, buffer_position) = self
4989 .buffer
4990 .read(cx)
4991 .text_anchor_for_position(position, cx)?;
4992
4993 let settings = language_settings::language_settings(
4994 buffer
4995 .read(cx)
4996 .language_at(buffer_position)
4997 .map(|l| l.name()),
4998 buffer.read(cx).file(),
4999 cx,
5000 );
5001 if !settings.use_on_type_format {
5002 return None;
5003 }
5004
5005 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5006 // hence we do LSP request & edit on host side only — add formats to host's history.
5007 let push_to_lsp_host_history = true;
5008 // If this is not the host, append its history with new edits.
5009 let push_to_client_history = project.read(cx).is_via_collab();
5010
5011 let on_type_formatting = project.update(cx, |project, cx| {
5012 project.on_type_format(
5013 buffer.clone(),
5014 buffer_position,
5015 input,
5016 push_to_lsp_host_history,
5017 cx,
5018 )
5019 });
5020 Some(cx.spawn_in(window, async move |editor, cx| {
5021 if let Some(transaction) = on_type_formatting.await? {
5022 if push_to_client_history {
5023 buffer
5024 .update(cx, |buffer, _| {
5025 buffer.push_transaction(transaction, Instant::now());
5026 buffer.finalize_last_transaction();
5027 })
5028 .ok();
5029 }
5030 editor.update(cx, |editor, cx| {
5031 editor.refresh_document_highlights(cx);
5032 })?;
5033 }
5034 Ok(())
5035 }))
5036 }
5037
5038 pub fn show_word_completions(
5039 &mut self,
5040 _: &ShowWordCompletions,
5041 window: &mut Window,
5042 cx: &mut Context<Self>,
5043 ) {
5044 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5045 }
5046
5047 pub fn show_completions(
5048 &mut self,
5049 options: &ShowCompletions,
5050 window: &mut Window,
5051 cx: &mut Context<Self>,
5052 ) {
5053 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5054 }
5055
5056 fn open_or_update_completions_menu(
5057 &mut self,
5058 requested_source: Option<CompletionsMenuSource>,
5059 trigger: Option<&str>,
5060 window: &mut Window,
5061 cx: &mut Context<Self>,
5062 ) {
5063 if self.pending_rename.is_some() {
5064 return;
5065 }
5066
5067 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5068 // inserted and selected. To handle that case, the start of the selection is used so that
5069 // the menu starts with all choices.
5070 let position = self.selections.newest_anchor().start;
5071 if position.diff_base_anchor.is_some() {
5072 return;
5073 }
5074 let (buffer, buffer_position) =
5075 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5076 output
5077 } else {
5078 return;
5079 };
5080 let buffer_snapshot = buffer.read(cx).snapshot();
5081
5082 let query: Option<Arc<String>> =
5083 Self::completion_query(&self.buffer.read(cx).read(cx), position)
5084 .map(|query| query.into());
5085
5086 let provider = match requested_source {
5087 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5088 Some(CompletionsMenuSource::Words) => None,
5089 Some(CompletionsMenuSource::SnippetChoices) => {
5090 log::error!("bug: SnippetChoices requested_source is not handled");
5091 None
5092 }
5093 };
5094
5095 let sort_completions = provider
5096 .as_ref()
5097 .map_or(false, |provider| provider.sort_completions());
5098
5099 let filter_completions = provider
5100 .as_ref()
5101 .map_or(true, |provider| provider.filter_completions());
5102
5103 // When `is_incomplete` is false, can filter completions instead of re-querying when the
5104 // current query is a suffix of the initial query.
5105 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5106 if !menu.is_incomplete && filter_completions {
5107 // If the new query is a suffix of the old query (typing more characters) and
5108 // the previous result was complete, the existing completions can be filtered.
5109 //
5110 // Note that this is always true for snippet completions.
5111 let query_matches = match (&menu.initial_query, &query) {
5112 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5113 (None, _) => true,
5114 _ => false,
5115 };
5116 if query_matches {
5117 let position_matches = if menu.initial_position == position {
5118 true
5119 } else {
5120 let snapshot = self.buffer.read(cx).read(cx);
5121 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5122 };
5123 if position_matches {
5124 menu.filter(query.clone(), provider.clone(), window, cx);
5125 return;
5126 }
5127 }
5128 }
5129 };
5130
5131 let trigger_kind = match trigger {
5132 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5133 CompletionTriggerKind::TRIGGER_CHARACTER
5134 }
5135 _ => CompletionTriggerKind::INVOKED,
5136 };
5137 let completion_context = CompletionContext {
5138 trigger_character: trigger.and_then(|trigger| {
5139 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5140 Some(String::from(trigger))
5141 } else {
5142 None
5143 }
5144 }),
5145 trigger_kind,
5146 };
5147
5148 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5149 buffer_snapshot.surrounding_word(buffer_position)
5150 {
5151 let word_to_exclude = buffer_snapshot
5152 .text_for_range(word_range.clone())
5153 .collect::<String>();
5154 (
5155 buffer_snapshot.anchor_before(word_range.start)
5156 ..buffer_snapshot.anchor_after(buffer_position),
5157 Some(word_to_exclude),
5158 )
5159 } else {
5160 (buffer_position..buffer_position, None)
5161 };
5162
5163 let language = buffer_snapshot
5164 .language_at(buffer_position)
5165 .map(|language| language.name());
5166
5167 let completion_settings =
5168 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5169
5170 let show_completion_documentation = buffer_snapshot
5171 .settings_at(buffer_position, cx)
5172 .show_completion_documentation;
5173
5174 // The document can be large, so stay in reasonable bounds when searching for words,
5175 // otherwise completion pop-up might be slow to appear.
5176 const WORD_LOOKUP_ROWS: u32 = 5_000;
5177 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5178 let min_word_search = buffer_snapshot.clip_point(
5179 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5180 Bias::Left,
5181 );
5182 let max_word_search = buffer_snapshot.clip_point(
5183 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5184 Bias::Right,
5185 );
5186 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5187 ..buffer_snapshot.point_to_offset(max_word_search);
5188
5189 let skip_digits = query
5190 .as_ref()
5191 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5192
5193 let (mut words, provider_responses) = match &provider {
5194 Some(provider) => {
5195 let provider_responses = provider.completions(
5196 position.excerpt_id,
5197 &buffer,
5198 buffer_position,
5199 completion_context,
5200 window,
5201 cx,
5202 );
5203
5204 let words = match completion_settings.words {
5205 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5206 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5207 .background_spawn(async move {
5208 buffer_snapshot.words_in_range(WordsQuery {
5209 fuzzy_contents: None,
5210 range: word_search_range,
5211 skip_digits,
5212 })
5213 }),
5214 };
5215
5216 (words, provider_responses)
5217 }
5218 None => (
5219 cx.background_spawn(async move {
5220 buffer_snapshot.words_in_range(WordsQuery {
5221 fuzzy_contents: None,
5222 range: word_search_range,
5223 skip_digits,
5224 })
5225 }),
5226 Task::ready(Ok(Vec::new())),
5227 ),
5228 };
5229
5230 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5231
5232 let id = post_inc(&mut self.next_completion_id);
5233 let task = cx.spawn_in(window, async move |editor, cx| {
5234 let Ok(()) = editor.update(cx, |this, _| {
5235 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5236 }) else {
5237 return;
5238 };
5239
5240 // TODO: Ideally completions from different sources would be selectively re-queried, so
5241 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5242 let mut completions = Vec::new();
5243 let mut is_incomplete = false;
5244 if let Some(provider_responses) = provider_responses.await.log_err() {
5245 if !provider_responses.is_empty() {
5246 for response in provider_responses {
5247 completions.extend(response.completions);
5248 is_incomplete = is_incomplete || response.is_incomplete;
5249 }
5250 if completion_settings.words == WordsCompletionMode::Fallback {
5251 words = Task::ready(BTreeMap::default());
5252 }
5253 }
5254 }
5255
5256 let mut words = words.await;
5257 if let Some(word_to_exclude) = &word_to_exclude {
5258 words.remove(word_to_exclude);
5259 }
5260 for lsp_completion in &completions {
5261 words.remove(&lsp_completion.new_text);
5262 }
5263 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5264 replace_range: word_replace_range.clone(),
5265 new_text: word.clone(),
5266 label: CodeLabel::plain(word, None),
5267 icon_path: None,
5268 documentation: None,
5269 source: CompletionSource::BufferWord {
5270 word_range,
5271 resolved: false,
5272 },
5273 insert_text_mode: Some(InsertTextMode::AS_IS),
5274 confirm: None,
5275 }));
5276
5277 let menu = if completions.is_empty() {
5278 None
5279 } else {
5280 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5281 let languages = editor
5282 .workspace
5283 .as_ref()
5284 .and_then(|(workspace, _)| workspace.upgrade())
5285 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5286 let menu = CompletionsMenu::new(
5287 id,
5288 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5289 sort_completions,
5290 show_completion_documentation,
5291 position,
5292 query.clone(),
5293 is_incomplete,
5294 buffer.clone(),
5295 completions.into(),
5296 snippet_sort_order,
5297 languages,
5298 language,
5299 cx,
5300 );
5301
5302 let query = if filter_completions { query } else { None };
5303 let matches_task = if let Some(query) = query {
5304 menu.do_async_filtering(query, cx)
5305 } else {
5306 Task::ready(menu.unfiltered_matches())
5307 };
5308 (menu, matches_task)
5309 }) else {
5310 return;
5311 };
5312
5313 let matches = matches_task.await;
5314
5315 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5316 // Newer menu already set, so exit.
5317 match editor.context_menu.borrow().as_ref() {
5318 Some(CodeContextMenu::Completions(prev_menu)) => {
5319 if prev_menu.id > id {
5320 return;
5321 }
5322 }
5323 _ => {}
5324 };
5325
5326 // Only valid to take prev_menu because it the new menu is immediately set
5327 // below, or the menu is hidden.
5328 match editor.context_menu.borrow_mut().take() {
5329 Some(CodeContextMenu::Completions(prev_menu)) => {
5330 let position_matches =
5331 if prev_menu.initial_position == menu.initial_position {
5332 true
5333 } else {
5334 let snapshot = editor.buffer.read(cx).read(cx);
5335 prev_menu.initial_position.to_offset(&snapshot)
5336 == menu.initial_position.to_offset(&snapshot)
5337 };
5338 if position_matches {
5339 // Preserve markdown cache before `set_filter_results` because it will
5340 // try to populate the documentation cache.
5341 menu.preserve_markdown_cache(prev_menu);
5342 }
5343 }
5344 _ => {}
5345 };
5346
5347 menu.set_filter_results(matches, provider, window, cx);
5348 }) else {
5349 return;
5350 };
5351
5352 menu.visible().then_some(menu)
5353 };
5354
5355 editor
5356 .update_in(cx, |editor, window, cx| {
5357 if editor.focus_handle.is_focused(window) {
5358 if let Some(menu) = menu {
5359 *editor.context_menu.borrow_mut() =
5360 Some(CodeContextMenu::Completions(menu));
5361
5362 crate::hover_popover::hide_hover(editor, cx);
5363 if editor.show_edit_predictions_in_menu() {
5364 editor.update_visible_inline_completion(window, cx);
5365 } else {
5366 editor.discard_inline_completion(false, cx);
5367 }
5368
5369 cx.notify();
5370 return;
5371 }
5372 }
5373
5374 if editor.completion_tasks.len() <= 1 {
5375 // If there are no more completion tasks and the last menu was empty, we should hide it.
5376 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5377 // If it was already hidden and we don't show inline completions in the menu, we should
5378 // also show the inline-completion when available.
5379 if was_hidden && editor.show_edit_predictions_in_menu() {
5380 editor.update_visible_inline_completion(window, cx);
5381 }
5382 }
5383 })
5384 .ok();
5385 });
5386
5387 self.completion_tasks.push((id, task));
5388 }
5389
5390 #[cfg(feature = "test-support")]
5391 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5392 let menu = self.context_menu.borrow();
5393 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5394 let completions = menu.completions.borrow();
5395 Some(completions.to_vec())
5396 } else {
5397 None
5398 }
5399 }
5400
5401 pub fn with_completions_menu_matching_id<R>(
5402 &self,
5403 id: CompletionId,
5404 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5405 ) -> R {
5406 let mut context_menu = self.context_menu.borrow_mut();
5407 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5408 return f(None);
5409 };
5410 if completions_menu.id != id {
5411 return f(None);
5412 }
5413 f(Some(completions_menu))
5414 }
5415
5416 pub fn confirm_completion(
5417 &mut self,
5418 action: &ConfirmCompletion,
5419 window: &mut Window,
5420 cx: &mut Context<Self>,
5421 ) -> Option<Task<Result<()>>> {
5422 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5423 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5424 }
5425
5426 pub fn confirm_completion_insert(
5427 &mut self,
5428 _: &ConfirmCompletionInsert,
5429 window: &mut Window,
5430 cx: &mut Context<Self>,
5431 ) -> Option<Task<Result<()>>> {
5432 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5433 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5434 }
5435
5436 pub fn confirm_completion_replace(
5437 &mut self,
5438 _: &ConfirmCompletionReplace,
5439 window: &mut Window,
5440 cx: &mut Context<Self>,
5441 ) -> Option<Task<Result<()>>> {
5442 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5443 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5444 }
5445
5446 pub fn compose_completion(
5447 &mut self,
5448 action: &ComposeCompletion,
5449 window: &mut Window,
5450 cx: &mut Context<Self>,
5451 ) -> Option<Task<Result<()>>> {
5452 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5453 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5454 }
5455
5456 fn do_completion(
5457 &mut self,
5458 item_ix: Option<usize>,
5459 intent: CompletionIntent,
5460 window: &mut Window,
5461 cx: &mut Context<Editor>,
5462 ) -> Option<Task<Result<()>>> {
5463 use language::ToOffset as _;
5464
5465 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5466 else {
5467 return None;
5468 };
5469
5470 let candidate_id = {
5471 let entries = completions_menu.entries.borrow();
5472 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5473 if self.show_edit_predictions_in_menu() {
5474 self.discard_inline_completion(true, cx);
5475 }
5476 mat.candidate_id
5477 };
5478
5479 let completion = completions_menu
5480 .completions
5481 .borrow()
5482 .get(candidate_id)?
5483 .clone();
5484 cx.stop_propagation();
5485
5486 let buffer_handle = completions_menu.buffer.clone();
5487
5488 let CompletionEdit {
5489 new_text,
5490 snippet,
5491 replace_range,
5492 } = process_completion_for_edit(
5493 &completion,
5494 intent,
5495 &buffer_handle,
5496 &completions_menu.initial_position.text_anchor,
5497 cx,
5498 );
5499
5500 let buffer = buffer_handle.read(cx);
5501 let snapshot = self.buffer.read(cx).snapshot(cx);
5502 let newest_anchor = self.selections.newest_anchor();
5503 let replace_range_multibuffer = {
5504 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5505 let multibuffer_anchor = snapshot
5506 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5507 .unwrap()
5508 ..snapshot
5509 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5510 .unwrap();
5511 multibuffer_anchor.start.to_offset(&snapshot)
5512 ..multibuffer_anchor.end.to_offset(&snapshot)
5513 };
5514 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5515 return None;
5516 }
5517
5518 let old_text = buffer
5519 .text_for_range(replace_range.clone())
5520 .collect::<String>();
5521 let lookbehind = newest_anchor
5522 .start
5523 .text_anchor
5524 .to_offset(buffer)
5525 .saturating_sub(replace_range.start);
5526 let lookahead = replace_range
5527 .end
5528 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5529 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5530 let suffix = &old_text[lookbehind.min(old_text.len())..];
5531
5532 let selections = self.selections.all::<usize>(cx);
5533 let mut ranges = Vec::new();
5534 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5535
5536 for selection in &selections {
5537 let range = if selection.id == newest_anchor.id {
5538 replace_range_multibuffer.clone()
5539 } else {
5540 let mut range = selection.range();
5541
5542 // if prefix is present, don't duplicate it
5543 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5544 range.start = range.start.saturating_sub(lookbehind);
5545
5546 // if suffix is also present, mimic the newest cursor and replace it
5547 if selection.id != newest_anchor.id
5548 && snapshot.contains_str_at(range.end, suffix)
5549 {
5550 range.end += lookahead;
5551 }
5552 }
5553 range
5554 };
5555
5556 ranges.push(range.clone());
5557
5558 if !self.linked_edit_ranges.is_empty() {
5559 let start_anchor = snapshot.anchor_before(range.start);
5560 let end_anchor = snapshot.anchor_after(range.end);
5561 if let Some(ranges) = self
5562 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5563 {
5564 for (buffer, edits) in ranges {
5565 linked_edits
5566 .entry(buffer.clone())
5567 .or_default()
5568 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5569 }
5570 }
5571 }
5572 }
5573
5574 let common_prefix_len = old_text
5575 .chars()
5576 .zip(new_text.chars())
5577 .take_while(|(a, b)| a == b)
5578 .map(|(a, _)| a.len_utf8())
5579 .sum::<usize>();
5580
5581 cx.emit(EditorEvent::InputHandled {
5582 utf16_range_to_replace: None,
5583 text: new_text[common_prefix_len..].into(),
5584 });
5585
5586 self.transact(window, cx, |this, window, cx| {
5587 if let Some(mut snippet) = snippet {
5588 snippet.text = new_text.to_string();
5589 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5590 } else {
5591 this.buffer.update(cx, |buffer, cx| {
5592 let auto_indent = match completion.insert_text_mode {
5593 Some(InsertTextMode::AS_IS) => None,
5594 _ => this.autoindent_mode.clone(),
5595 };
5596 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5597 buffer.edit(edits, auto_indent, cx);
5598 });
5599 }
5600 for (buffer, edits) in linked_edits {
5601 buffer.update(cx, |buffer, cx| {
5602 let snapshot = buffer.snapshot();
5603 let edits = edits
5604 .into_iter()
5605 .map(|(range, text)| {
5606 use text::ToPoint as TP;
5607 let end_point = TP::to_point(&range.end, &snapshot);
5608 let start_point = TP::to_point(&range.start, &snapshot);
5609 (start_point..end_point, text)
5610 })
5611 .sorted_by_key(|(range, _)| range.start);
5612 buffer.edit(edits, None, cx);
5613 })
5614 }
5615
5616 this.refresh_inline_completion(true, false, window, cx);
5617 });
5618
5619 let show_new_completions_on_confirm = completion
5620 .confirm
5621 .as_ref()
5622 .map_or(false, |confirm| confirm(intent, window, cx));
5623 if show_new_completions_on_confirm {
5624 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5625 }
5626
5627 let provider = self.completion_provider.as_ref()?;
5628 drop(completion);
5629 let apply_edits = provider.apply_additional_edits_for_completion(
5630 buffer_handle,
5631 completions_menu.completions.clone(),
5632 candidate_id,
5633 true,
5634 cx,
5635 );
5636
5637 let editor_settings = EditorSettings::get_global(cx);
5638 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5639 // After the code completion is finished, users often want to know what signatures are needed.
5640 // so we should automatically call signature_help
5641 self.show_signature_help(&ShowSignatureHelp, window, cx);
5642 }
5643
5644 Some(cx.foreground_executor().spawn(async move {
5645 apply_edits.await?;
5646 Ok(())
5647 }))
5648 }
5649
5650 pub fn toggle_code_actions(
5651 &mut self,
5652 action: &ToggleCodeActions,
5653 window: &mut Window,
5654 cx: &mut Context<Self>,
5655 ) {
5656 let quick_launch = action.quick_launch;
5657 let mut context_menu = self.context_menu.borrow_mut();
5658 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5659 if code_actions.deployed_from == action.deployed_from {
5660 // Toggle if we're selecting the same one
5661 *context_menu = None;
5662 cx.notify();
5663 return;
5664 } else {
5665 // Otherwise, clear it and start a new one
5666 *context_menu = None;
5667 cx.notify();
5668 }
5669 }
5670 drop(context_menu);
5671 let snapshot = self.snapshot(window, cx);
5672 let deployed_from = action.deployed_from.clone();
5673 let mut task = self.code_actions_task.take();
5674 let action = action.clone();
5675 cx.spawn_in(window, async move |editor, cx| {
5676 while let Some(prev_task) = task {
5677 prev_task.await.log_err();
5678 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5679 }
5680
5681 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5682 if editor.focus_handle.is_focused(window) {
5683 let multibuffer_point = match &action.deployed_from {
5684 Some(CodeActionSource::Indicator(row)) => {
5685 DisplayPoint::new(*row, 0).to_point(&snapshot)
5686 }
5687 _ => editor.selections.newest::<Point>(cx).head(),
5688 };
5689 let (buffer, buffer_row) = snapshot
5690 .buffer_snapshot
5691 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5692 .and_then(|(buffer_snapshot, range)| {
5693 editor
5694 .buffer
5695 .read(cx)
5696 .buffer(buffer_snapshot.remote_id())
5697 .map(|buffer| (buffer, range.start.row))
5698 })?;
5699 let (_, code_actions) = editor
5700 .available_code_actions
5701 .clone()
5702 .and_then(|(location, code_actions)| {
5703 let snapshot = location.buffer.read(cx).snapshot();
5704 let point_range = location.range.to_point(&snapshot);
5705 let point_range = point_range.start.row..=point_range.end.row;
5706 if point_range.contains(&buffer_row) {
5707 Some((location, code_actions))
5708 } else {
5709 None
5710 }
5711 })
5712 .unzip();
5713 let buffer_id = buffer.read(cx).remote_id();
5714 let tasks = editor
5715 .tasks
5716 .get(&(buffer_id, buffer_row))
5717 .map(|t| Arc::new(t.to_owned()));
5718 if tasks.is_none() && code_actions.is_none() {
5719 return None;
5720 }
5721
5722 editor.completion_tasks.clear();
5723 editor.discard_inline_completion(false, cx);
5724 let task_context =
5725 tasks
5726 .as_ref()
5727 .zip(editor.project.clone())
5728 .map(|(tasks, project)| {
5729 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5730 });
5731
5732 Some(cx.spawn_in(window, async move |editor, cx| {
5733 let task_context = match task_context {
5734 Some(task_context) => task_context.await,
5735 None => None,
5736 };
5737 let resolved_tasks =
5738 tasks
5739 .zip(task_context.clone())
5740 .map(|(tasks, task_context)| ResolvedTasks {
5741 templates: tasks.resolve(&task_context).collect(),
5742 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5743 multibuffer_point.row,
5744 tasks.column,
5745 )),
5746 });
5747 let debug_scenarios = editor.update(cx, |editor, cx| {
5748 if cx.has_flag::<DebuggerFeatureFlag>() {
5749 maybe!({
5750 let project = editor.project.as_ref()?;
5751 let dap_store = project.read(cx).dap_store();
5752 let mut scenarios = vec![];
5753 let resolved_tasks = resolved_tasks.as_ref()?;
5754 let buffer = buffer.read(cx);
5755 let language = buffer.language()?;
5756 let file = buffer.file();
5757 let debug_adapter =
5758 language_settings(language.name().into(), file, cx)
5759 .debuggers
5760 .first()
5761 .map(SharedString::from)
5762 .or_else(|| {
5763 language
5764 .config()
5765 .debuggers
5766 .first()
5767 .map(SharedString::from)
5768 })?;
5769
5770 dap_store.update(cx, |dap_store, cx| {
5771 for (_, task) in &resolved_tasks.templates {
5772 if let Some(scenario) = dap_store
5773 .debug_scenario_for_build_task(
5774 task.original_task().clone(),
5775 debug_adapter.clone().into(),
5776 task.display_label().to_owned().into(),
5777 cx,
5778 )
5779 {
5780 scenarios.push(scenario);
5781 }
5782 }
5783 });
5784 Some(scenarios)
5785 })
5786 .unwrap_or_default()
5787 } else {
5788 vec![]
5789 }
5790 })?;
5791 let spawn_straight_away = quick_launch
5792 && resolved_tasks
5793 .as_ref()
5794 .map_or(false, |tasks| tasks.templates.len() == 1)
5795 && code_actions
5796 .as_ref()
5797 .map_or(true, |actions| actions.is_empty())
5798 && debug_scenarios.is_empty();
5799 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5800 crate::hover_popover::hide_hover(editor, cx);
5801 *editor.context_menu.borrow_mut() =
5802 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5803 buffer,
5804 actions: CodeActionContents::new(
5805 resolved_tasks,
5806 code_actions,
5807 debug_scenarios,
5808 task_context.unwrap_or_default(),
5809 ),
5810 selected_item: Default::default(),
5811 scroll_handle: UniformListScrollHandle::default(),
5812 deployed_from,
5813 }));
5814 if spawn_straight_away {
5815 if let Some(task) = editor.confirm_code_action(
5816 &ConfirmCodeAction { item_ix: Some(0) },
5817 window,
5818 cx,
5819 ) {
5820 cx.notify();
5821 return task;
5822 }
5823 }
5824 cx.notify();
5825 Task::ready(Ok(()))
5826 }) {
5827 task.await
5828 } else {
5829 Ok(())
5830 }
5831 }))
5832 } else {
5833 Some(Task::ready(Ok(())))
5834 }
5835 })?;
5836 if let Some(task) = spawned_test_task {
5837 task.await?;
5838 }
5839
5840 anyhow::Ok(())
5841 })
5842 .detach_and_log_err(cx);
5843 }
5844
5845 pub fn confirm_code_action(
5846 &mut self,
5847 action: &ConfirmCodeAction,
5848 window: &mut Window,
5849 cx: &mut Context<Self>,
5850 ) -> Option<Task<Result<()>>> {
5851 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5852
5853 let actions_menu =
5854 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5855 menu
5856 } else {
5857 return None;
5858 };
5859
5860 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5861 let action = actions_menu.actions.get(action_ix)?;
5862 let title = action.label();
5863 let buffer = actions_menu.buffer;
5864 let workspace = self.workspace()?;
5865
5866 match action {
5867 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5868 workspace.update(cx, |workspace, cx| {
5869 workspace.schedule_resolved_task(
5870 task_source_kind,
5871 resolved_task,
5872 false,
5873 window,
5874 cx,
5875 );
5876
5877 Some(Task::ready(Ok(())))
5878 })
5879 }
5880 CodeActionsItem::CodeAction {
5881 excerpt_id,
5882 action,
5883 provider,
5884 } => {
5885 let apply_code_action =
5886 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5887 let workspace = workspace.downgrade();
5888 Some(cx.spawn_in(window, async move |editor, cx| {
5889 let project_transaction = apply_code_action.await?;
5890 Self::open_project_transaction(
5891 &editor,
5892 workspace,
5893 project_transaction,
5894 title,
5895 cx,
5896 )
5897 .await
5898 }))
5899 }
5900 CodeActionsItem::DebugScenario(scenario) => {
5901 let context = actions_menu.actions.context.clone();
5902
5903 workspace.update(cx, |workspace, cx| {
5904 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
5905 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5906 });
5907 Some(Task::ready(Ok(())))
5908 }
5909 }
5910 }
5911
5912 pub async fn open_project_transaction(
5913 this: &WeakEntity<Editor>,
5914 workspace: WeakEntity<Workspace>,
5915 transaction: ProjectTransaction,
5916 title: String,
5917 cx: &mut AsyncWindowContext,
5918 ) -> Result<()> {
5919 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5920 cx.update(|_, cx| {
5921 entries.sort_unstable_by_key(|(buffer, _)| {
5922 buffer.read(cx).file().map(|f| f.path().clone())
5923 });
5924 })?;
5925
5926 // If the project transaction's edits are all contained within this editor, then
5927 // avoid opening a new editor to display them.
5928
5929 if let Some((buffer, transaction)) = entries.first() {
5930 if entries.len() == 1 {
5931 let excerpt = this.update(cx, |editor, cx| {
5932 editor
5933 .buffer()
5934 .read(cx)
5935 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5936 })?;
5937 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5938 if excerpted_buffer == *buffer {
5939 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5940 let excerpt_range = excerpt_range.to_offset(buffer);
5941 buffer
5942 .edited_ranges_for_transaction::<usize>(transaction)
5943 .all(|range| {
5944 excerpt_range.start <= range.start
5945 && excerpt_range.end >= range.end
5946 })
5947 })?;
5948
5949 if all_edits_within_excerpt {
5950 return Ok(());
5951 }
5952 }
5953 }
5954 }
5955 } else {
5956 return Ok(());
5957 }
5958
5959 let mut ranges_to_highlight = Vec::new();
5960 let excerpt_buffer = cx.new(|cx| {
5961 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5962 for (buffer_handle, transaction) in &entries {
5963 let edited_ranges = buffer_handle
5964 .read(cx)
5965 .edited_ranges_for_transaction::<Point>(transaction)
5966 .collect::<Vec<_>>();
5967 let (ranges, _) = multibuffer.set_excerpts_for_path(
5968 PathKey::for_buffer(buffer_handle, cx),
5969 buffer_handle.clone(),
5970 edited_ranges,
5971 DEFAULT_MULTIBUFFER_CONTEXT,
5972 cx,
5973 );
5974
5975 ranges_to_highlight.extend(ranges);
5976 }
5977 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5978 multibuffer
5979 })?;
5980
5981 workspace.update_in(cx, |workspace, window, cx| {
5982 let project = workspace.project().clone();
5983 let editor =
5984 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5985 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5986 editor.update(cx, |editor, cx| {
5987 editor.highlight_background::<Self>(
5988 &ranges_to_highlight,
5989 |theme| theme.editor_highlighted_line_background,
5990 cx,
5991 );
5992 });
5993 })?;
5994
5995 Ok(())
5996 }
5997
5998 pub fn clear_code_action_providers(&mut self) {
5999 self.code_action_providers.clear();
6000 self.available_code_actions.take();
6001 }
6002
6003 pub fn add_code_action_provider(
6004 &mut self,
6005 provider: Rc<dyn CodeActionProvider>,
6006 window: &mut Window,
6007 cx: &mut Context<Self>,
6008 ) {
6009 if self
6010 .code_action_providers
6011 .iter()
6012 .any(|existing_provider| existing_provider.id() == provider.id())
6013 {
6014 return;
6015 }
6016
6017 self.code_action_providers.push(provider);
6018 self.refresh_code_actions(window, cx);
6019 }
6020
6021 pub fn remove_code_action_provider(
6022 &mut self,
6023 id: Arc<str>,
6024 window: &mut Window,
6025 cx: &mut Context<Self>,
6026 ) {
6027 self.code_action_providers
6028 .retain(|provider| provider.id() != id);
6029 self.refresh_code_actions(window, cx);
6030 }
6031
6032 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6033 !self.code_action_providers.is_empty()
6034 && EditorSettings::get_global(cx).toolbar.code_actions
6035 }
6036
6037 pub fn has_available_code_actions(&self) -> bool {
6038 self.available_code_actions
6039 .as_ref()
6040 .is_some_and(|(_, actions)| !actions.is_empty())
6041 }
6042
6043 fn render_inline_code_actions(
6044 &self,
6045 icon_size: ui::IconSize,
6046 display_row: DisplayRow,
6047 is_active: bool,
6048 cx: &mut Context<Self>,
6049 ) -> AnyElement {
6050 let show_tooltip = !self.context_menu_visible();
6051 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6052 .icon_size(icon_size)
6053 .shape(ui::IconButtonShape::Square)
6054 .style(ButtonStyle::Transparent)
6055 .icon_color(ui::Color::Hidden)
6056 .toggle_state(is_active)
6057 .when(show_tooltip, |this| {
6058 this.tooltip({
6059 let focus_handle = self.focus_handle.clone();
6060 move |window, cx| {
6061 Tooltip::for_action_in(
6062 "Toggle Code Actions",
6063 &ToggleCodeActions {
6064 deployed_from: None,
6065 quick_launch: false,
6066 },
6067 &focus_handle,
6068 window,
6069 cx,
6070 )
6071 }
6072 })
6073 })
6074 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6075 window.focus(&editor.focus_handle(cx));
6076 editor.toggle_code_actions(
6077 &crate::actions::ToggleCodeActions {
6078 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6079 display_row,
6080 )),
6081 quick_launch: false,
6082 },
6083 window,
6084 cx,
6085 );
6086 }))
6087 .into_any_element()
6088 }
6089
6090 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6091 &self.context_menu
6092 }
6093
6094 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6095 let newest_selection = self.selections.newest_anchor().clone();
6096 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6097 let buffer = self.buffer.read(cx);
6098 if newest_selection.head().diff_base_anchor.is_some() {
6099 return None;
6100 }
6101 let (start_buffer, start) =
6102 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6103 let (end_buffer, end) =
6104 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6105 if start_buffer != end_buffer {
6106 return None;
6107 }
6108
6109 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6110 cx.background_executor()
6111 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6112 .await;
6113
6114 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6115 let providers = this.code_action_providers.clone();
6116 let tasks = this
6117 .code_action_providers
6118 .iter()
6119 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6120 .collect::<Vec<_>>();
6121 (providers, tasks)
6122 })?;
6123
6124 let mut actions = Vec::new();
6125 for (provider, provider_actions) in
6126 providers.into_iter().zip(future::join_all(tasks).await)
6127 {
6128 if let Some(provider_actions) = provider_actions.log_err() {
6129 actions.extend(provider_actions.into_iter().map(|action| {
6130 AvailableCodeAction {
6131 excerpt_id: newest_selection.start.excerpt_id,
6132 action,
6133 provider: provider.clone(),
6134 }
6135 }));
6136 }
6137 }
6138
6139 this.update(cx, |this, cx| {
6140 this.available_code_actions = if actions.is_empty() {
6141 None
6142 } else {
6143 Some((
6144 Location {
6145 buffer: start_buffer,
6146 range: start..end,
6147 },
6148 actions.into(),
6149 ))
6150 };
6151 cx.notify();
6152 })
6153 }));
6154 None
6155 }
6156
6157 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6158 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6159 self.show_git_blame_inline = false;
6160
6161 self.show_git_blame_inline_delay_task =
6162 Some(cx.spawn_in(window, async move |this, cx| {
6163 cx.background_executor().timer(delay).await;
6164
6165 this.update(cx, |this, cx| {
6166 this.show_git_blame_inline = true;
6167 cx.notify();
6168 })
6169 .log_err();
6170 }));
6171 }
6172 }
6173
6174 fn show_blame_popover(
6175 &mut self,
6176 blame_entry: &BlameEntry,
6177 position: gpui::Point<Pixels>,
6178 cx: &mut Context<Self>,
6179 ) {
6180 if let Some(state) = &mut self.inline_blame_popover {
6181 state.hide_task.take();
6182 cx.notify();
6183 } else {
6184 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6185 let show_task = cx.spawn(async move |editor, cx| {
6186 cx.background_executor()
6187 .timer(std::time::Duration::from_millis(delay))
6188 .await;
6189 editor
6190 .update(cx, |editor, cx| {
6191 if let Some(state) = &mut editor.inline_blame_popover {
6192 state.show_task = None;
6193 cx.notify();
6194 }
6195 })
6196 .ok();
6197 });
6198 let Some(blame) = self.blame.as_ref() else {
6199 return;
6200 };
6201 let blame = blame.read(cx);
6202 let details = blame.details_for_entry(&blame_entry);
6203 let markdown = cx.new(|cx| {
6204 Markdown::new(
6205 details
6206 .as_ref()
6207 .map(|message| message.message.clone())
6208 .unwrap_or_default(),
6209 None,
6210 None,
6211 cx,
6212 )
6213 });
6214 self.inline_blame_popover = Some(InlineBlamePopover {
6215 position,
6216 show_task: Some(show_task),
6217 hide_task: None,
6218 popover_bounds: None,
6219 popover_state: InlineBlamePopoverState {
6220 scroll_handle: ScrollHandle::new(),
6221 commit_message: details,
6222 markdown,
6223 },
6224 });
6225 }
6226 }
6227
6228 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6229 if let Some(state) = &mut self.inline_blame_popover {
6230 if state.show_task.is_some() {
6231 self.inline_blame_popover.take();
6232 cx.notify();
6233 } else {
6234 let hide_task = cx.spawn(async move |editor, cx| {
6235 cx.background_executor()
6236 .timer(std::time::Duration::from_millis(100))
6237 .await;
6238 editor
6239 .update(cx, |editor, cx| {
6240 editor.inline_blame_popover.take();
6241 cx.notify();
6242 })
6243 .ok();
6244 });
6245 state.hide_task = Some(hide_task);
6246 }
6247 }
6248 }
6249
6250 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6251 if self.pending_rename.is_some() {
6252 return None;
6253 }
6254
6255 let provider = self.semantics_provider.clone()?;
6256 let buffer = self.buffer.read(cx);
6257 let newest_selection = self.selections.newest_anchor().clone();
6258 let cursor_position = newest_selection.head();
6259 let (cursor_buffer, cursor_buffer_position) =
6260 buffer.text_anchor_for_position(cursor_position, cx)?;
6261 let (tail_buffer, tail_buffer_position) =
6262 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6263 if cursor_buffer != tail_buffer {
6264 return None;
6265 }
6266
6267 let snapshot = cursor_buffer.read(cx).snapshot();
6268 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6269 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6270 if start_word_range != end_word_range {
6271 self.document_highlights_task.take();
6272 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6273 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6274 return None;
6275 }
6276
6277 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6278 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6279 cx.background_executor()
6280 .timer(Duration::from_millis(debounce))
6281 .await;
6282
6283 let highlights = if let Some(highlights) = cx
6284 .update(|cx| {
6285 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6286 })
6287 .ok()
6288 .flatten()
6289 {
6290 highlights.await.log_err()
6291 } else {
6292 None
6293 };
6294
6295 if let Some(highlights) = highlights {
6296 this.update(cx, |this, cx| {
6297 if this.pending_rename.is_some() {
6298 return;
6299 }
6300
6301 let buffer_id = cursor_position.buffer_id;
6302 let buffer = this.buffer.read(cx);
6303 if !buffer
6304 .text_anchor_for_position(cursor_position, cx)
6305 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6306 {
6307 return;
6308 }
6309
6310 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6311 let mut write_ranges = Vec::new();
6312 let mut read_ranges = Vec::new();
6313 for highlight in highlights {
6314 for (excerpt_id, excerpt_range) in
6315 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6316 {
6317 let start = highlight
6318 .range
6319 .start
6320 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6321 let end = highlight
6322 .range
6323 .end
6324 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6325 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6326 continue;
6327 }
6328
6329 let range = Anchor {
6330 buffer_id,
6331 excerpt_id,
6332 text_anchor: start,
6333 diff_base_anchor: None,
6334 }..Anchor {
6335 buffer_id,
6336 excerpt_id,
6337 text_anchor: end,
6338 diff_base_anchor: None,
6339 };
6340 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6341 write_ranges.push(range);
6342 } else {
6343 read_ranges.push(range);
6344 }
6345 }
6346 }
6347
6348 this.highlight_background::<DocumentHighlightRead>(
6349 &read_ranges,
6350 |theme| theme.editor_document_highlight_read_background,
6351 cx,
6352 );
6353 this.highlight_background::<DocumentHighlightWrite>(
6354 &write_ranges,
6355 |theme| theme.editor_document_highlight_write_background,
6356 cx,
6357 );
6358 cx.notify();
6359 })
6360 .log_err();
6361 }
6362 }));
6363 None
6364 }
6365
6366 fn prepare_highlight_query_from_selection(
6367 &mut self,
6368 cx: &mut Context<Editor>,
6369 ) -> Option<(String, Range<Anchor>)> {
6370 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6371 return None;
6372 }
6373 if !EditorSettings::get_global(cx).selection_highlight {
6374 return None;
6375 }
6376 if self.selections.count() != 1 || self.selections.line_mode {
6377 return None;
6378 }
6379 let selection = self.selections.newest::<Point>(cx);
6380 if selection.is_empty() || selection.start.row != selection.end.row {
6381 return None;
6382 }
6383 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6384 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6385 let query = multi_buffer_snapshot
6386 .text_for_range(selection_anchor_range.clone())
6387 .collect::<String>();
6388 if query.trim().is_empty() {
6389 return None;
6390 }
6391 Some((query, selection_anchor_range))
6392 }
6393
6394 fn update_selection_occurrence_highlights(
6395 &mut self,
6396 query_text: String,
6397 query_range: Range<Anchor>,
6398 multi_buffer_range_to_query: Range<Point>,
6399 use_debounce: bool,
6400 window: &mut Window,
6401 cx: &mut Context<Editor>,
6402 ) -> Task<()> {
6403 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6404 cx.spawn_in(window, async move |editor, cx| {
6405 if use_debounce {
6406 cx.background_executor()
6407 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6408 .await;
6409 }
6410 let match_task = cx.background_spawn(async move {
6411 let buffer_ranges = multi_buffer_snapshot
6412 .range_to_buffer_ranges(multi_buffer_range_to_query)
6413 .into_iter()
6414 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6415 let mut match_ranges = Vec::new();
6416 let Ok(regex) = project::search::SearchQuery::text(
6417 query_text.clone(),
6418 false,
6419 false,
6420 false,
6421 Default::default(),
6422 Default::default(),
6423 false,
6424 None,
6425 ) else {
6426 return Vec::default();
6427 };
6428 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6429 match_ranges.extend(
6430 regex
6431 .search(&buffer_snapshot, Some(search_range.clone()))
6432 .await
6433 .into_iter()
6434 .filter_map(|match_range| {
6435 let match_start = buffer_snapshot
6436 .anchor_after(search_range.start + match_range.start);
6437 let match_end = buffer_snapshot
6438 .anchor_before(search_range.start + match_range.end);
6439 let match_anchor_range = Anchor::range_in_buffer(
6440 excerpt_id,
6441 buffer_snapshot.remote_id(),
6442 match_start..match_end,
6443 );
6444 (match_anchor_range != query_range).then_some(match_anchor_range)
6445 }),
6446 );
6447 }
6448 match_ranges
6449 });
6450 let match_ranges = match_task.await;
6451 editor
6452 .update_in(cx, |editor, _, cx| {
6453 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6454 if !match_ranges.is_empty() {
6455 editor.highlight_background::<SelectedTextHighlight>(
6456 &match_ranges,
6457 |theme| theme.editor_document_highlight_bracket_background,
6458 cx,
6459 )
6460 }
6461 })
6462 .log_err();
6463 })
6464 }
6465
6466 fn refresh_selected_text_highlights(
6467 &mut self,
6468 on_buffer_edit: bool,
6469 window: &mut Window,
6470 cx: &mut Context<Editor>,
6471 ) {
6472 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6473 else {
6474 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6475 self.quick_selection_highlight_task.take();
6476 self.debounced_selection_highlight_task.take();
6477 return;
6478 };
6479 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6480 if on_buffer_edit
6481 || self
6482 .quick_selection_highlight_task
6483 .as_ref()
6484 .map_or(true, |(prev_anchor_range, _)| {
6485 prev_anchor_range != &query_range
6486 })
6487 {
6488 let multi_buffer_visible_start = self
6489 .scroll_manager
6490 .anchor()
6491 .anchor
6492 .to_point(&multi_buffer_snapshot);
6493 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6494 multi_buffer_visible_start
6495 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6496 Bias::Left,
6497 );
6498 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6499 self.quick_selection_highlight_task = Some((
6500 query_range.clone(),
6501 self.update_selection_occurrence_highlights(
6502 query_text.clone(),
6503 query_range.clone(),
6504 multi_buffer_visible_range,
6505 false,
6506 window,
6507 cx,
6508 ),
6509 ));
6510 }
6511 if on_buffer_edit
6512 || self
6513 .debounced_selection_highlight_task
6514 .as_ref()
6515 .map_or(true, |(prev_anchor_range, _)| {
6516 prev_anchor_range != &query_range
6517 })
6518 {
6519 let multi_buffer_start = multi_buffer_snapshot
6520 .anchor_before(0)
6521 .to_point(&multi_buffer_snapshot);
6522 let multi_buffer_end = multi_buffer_snapshot
6523 .anchor_after(multi_buffer_snapshot.len())
6524 .to_point(&multi_buffer_snapshot);
6525 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6526 self.debounced_selection_highlight_task = Some((
6527 query_range.clone(),
6528 self.update_selection_occurrence_highlights(
6529 query_text,
6530 query_range,
6531 multi_buffer_full_range,
6532 true,
6533 window,
6534 cx,
6535 ),
6536 ));
6537 }
6538 }
6539
6540 pub fn refresh_inline_completion(
6541 &mut self,
6542 debounce: bool,
6543 user_requested: bool,
6544 window: &mut Window,
6545 cx: &mut Context<Self>,
6546 ) -> Option<()> {
6547 let provider = self.edit_prediction_provider()?;
6548 let cursor = self.selections.newest_anchor().head();
6549 let (buffer, cursor_buffer_position) =
6550 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6551
6552 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6553 self.discard_inline_completion(false, cx);
6554 return None;
6555 }
6556
6557 if !user_requested
6558 && (!self.should_show_edit_predictions()
6559 || !self.is_focused(window)
6560 || buffer.read(cx).is_empty())
6561 {
6562 self.discard_inline_completion(false, cx);
6563 return None;
6564 }
6565
6566 self.update_visible_inline_completion(window, cx);
6567 provider.refresh(
6568 self.project.clone(),
6569 buffer,
6570 cursor_buffer_position,
6571 debounce,
6572 cx,
6573 );
6574 Some(())
6575 }
6576
6577 fn show_edit_predictions_in_menu(&self) -> bool {
6578 match self.edit_prediction_settings {
6579 EditPredictionSettings::Disabled => false,
6580 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6581 }
6582 }
6583
6584 pub fn edit_predictions_enabled(&self) -> bool {
6585 match self.edit_prediction_settings {
6586 EditPredictionSettings::Disabled => false,
6587 EditPredictionSettings::Enabled { .. } => true,
6588 }
6589 }
6590
6591 fn edit_prediction_requires_modifier(&self) -> bool {
6592 match self.edit_prediction_settings {
6593 EditPredictionSettings::Disabled => false,
6594 EditPredictionSettings::Enabled {
6595 preview_requires_modifier,
6596 ..
6597 } => preview_requires_modifier,
6598 }
6599 }
6600
6601 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6602 if self.edit_prediction_provider.is_none() {
6603 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6604 } else {
6605 let selection = self.selections.newest_anchor();
6606 let cursor = selection.head();
6607
6608 if let Some((buffer, cursor_buffer_position)) =
6609 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6610 {
6611 self.edit_prediction_settings =
6612 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6613 }
6614 }
6615 }
6616
6617 fn edit_prediction_settings_at_position(
6618 &self,
6619 buffer: &Entity<Buffer>,
6620 buffer_position: language::Anchor,
6621 cx: &App,
6622 ) -> EditPredictionSettings {
6623 if !self.mode.is_full()
6624 || !self.show_inline_completions_override.unwrap_or(true)
6625 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6626 {
6627 return EditPredictionSettings::Disabled;
6628 }
6629
6630 let buffer = buffer.read(cx);
6631
6632 let file = buffer.file();
6633
6634 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6635 return EditPredictionSettings::Disabled;
6636 };
6637
6638 let by_provider = matches!(
6639 self.menu_inline_completions_policy,
6640 MenuInlineCompletionsPolicy::ByProvider
6641 );
6642
6643 let show_in_menu = by_provider
6644 && self
6645 .edit_prediction_provider
6646 .as_ref()
6647 .map_or(false, |provider| {
6648 provider.provider.show_completions_in_menu()
6649 });
6650
6651 let preview_requires_modifier =
6652 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6653
6654 EditPredictionSettings::Enabled {
6655 show_in_menu,
6656 preview_requires_modifier,
6657 }
6658 }
6659
6660 fn should_show_edit_predictions(&self) -> bool {
6661 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6662 }
6663
6664 pub fn edit_prediction_preview_is_active(&self) -> bool {
6665 matches!(
6666 self.edit_prediction_preview,
6667 EditPredictionPreview::Active { .. }
6668 )
6669 }
6670
6671 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6672 let cursor = self.selections.newest_anchor().head();
6673 if let Some((buffer, cursor_position)) =
6674 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6675 {
6676 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6677 } else {
6678 false
6679 }
6680 }
6681
6682 pub fn supports_minimap(&self, cx: &App) -> bool {
6683 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6684 }
6685
6686 fn edit_predictions_enabled_in_buffer(
6687 &self,
6688 buffer: &Entity<Buffer>,
6689 buffer_position: language::Anchor,
6690 cx: &App,
6691 ) -> bool {
6692 maybe!({
6693 if self.read_only(cx) {
6694 return Some(false);
6695 }
6696 let provider = self.edit_prediction_provider()?;
6697 if !provider.is_enabled(&buffer, buffer_position, cx) {
6698 return Some(false);
6699 }
6700 let buffer = buffer.read(cx);
6701 let Some(file) = buffer.file() else {
6702 return Some(true);
6703 };
6704 let settings = all_language_settings(Some(file), cx);
6705 Some(settings.edit_predictions_enabled_for_file(file, cx))
6706 })
6707 .unwrap_or(false)
6708 }
6709
6710 fn cycle_inline_completion(
6711 &mut self,
6712 direction: Direction,
6713 window: &mut Window,
6714 cx: &mut Context<Self>,
6715 ) -> Option<()> {
6716 let provider = self.edit_prediction_provider()?;
6717 let cursor = self.selections.newest_anchor().head();
6718 let (buffer, cursor_buffer_position) =
6719 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6720 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6721 return None;
6722 }
6723
6724 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6725 self.update_visible_inline_completion(window, cx);
6726
6727 Some(())
6728 }
6729
6730 pub fn show_inline_completion(
6731 &mut self,
6732 _: &ShowEditPrediction,
6733 window: &mut Window,
6734 cx: &mut Context<Self>,
6735 ) {
6736 if !self.has_active_inline_completion() {
6737 self.refresh_inline_completion(false, true, window, cx);
6738 return;
6739 }
6740
6741 self.update_visible_inline_completion(window, cx);
6742 }
6743
6744 pub fn display_cursor_names(
6745 &mut self,
6746 _: &DisplayCursorNames,
6747 window: &mut Window,
6748 cx: &mut Context<Self>,
6749 ) {
6750 self.show_cursor_names(window, cx);
6751 }
6752
6753 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6754 self.show_cursor_names = true;
6755 cx.notify();
6756 cx.spawn_in(window, async move |this, cx| {
6757 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6758 this.update(cx, |this, cx| {
6759 this.show_cursor_names = false;
6760 cx.notify()
6761 })
6762 .ok()
6763 })
6764 .detach();
6765 }
6766
6767 pub fn next_edit_prediction(
6768 &mut self,
6769 _: &NextEditPrediction,
6770 window: &mut Window,
6771 cx: &mut Context<Self>,
6772 ) {
6773 if self.has_active_inline_completion() {
6774 self.cycle_inline_completion(Direction::Next, window, cx);
6775 } else {
6776 let is_copilot_disabled = self
6777 .refresh_inline_completion(false, true, window, cx)
6778 .is_none();
6779 if is_copilot_disabled {
6780 cx.propagate();
6781 }
6782 }
6783 }
6784
6785 pub fn previous_edit_prediction(
6786 &mut self,
6787 _: &PreviousEditPrediction,
6788 window: &mut Window,
6789 cx: &mut Context<Self>,
6790 ) {
6791 if self.has_active_inline_completion() {
6792 self.cycle_inline_completion(Direction::Prev, window, cx);
6793 } else {
6794 let is_copilot_disabled = self
6795 .refresh_inline_completion(false, true, window, cx)
6796 .is_none();
6797 if is_copilot_disabled {
6798 cx.propagate();
6799 }
6800 }
6801 }
6802
6803 pub fn accept_edit_prediction(
6804 &mut self,
6805 _: &AcceptEditPrediction,
6806 window: &mut Window,
6807 cx: &mut Context<Self>,
6808 ) {
6809 if self.show_edit_predictions_in_menu() {
6810 self.hide_context_menu(window, cx);
6811 }
6812
6813 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6814 return;
6815 };
6816
6817 self.report_inline_completion_event(
6818 active_inline_completion.completion_id.clone(),
6819 true,
6820 cx,
6821 );
6822
6823 match &active_inline_completion.completion {
6824 InlineCompletion::Move { target, .. } => {
6825 let target = *target;
6826
6827 if let Some(position_map) = &self.last_position_map {
6828 if position_map
6829 .visible_row_range
6830 .contains(&target.to_display_point(&position_map.snapshot).row())
6831 || !self.edit_prediction_requires_modifier()
6832 {
6833 self.unfold_ranges(&[target..target], true, false, cx);
6834 // Note that this is also done in vim's handler of the Tab action.
6835 self.change_selections(
6836 Some(Autoscroll::newest()),
6837 window,
6838 cx,
6839 |selections| {
6840 selections.select_anchor_ranges([target..target]);
6841 },
6842 );
6843 self.clear_row_highlights::<EditPredictionPreview>();
6844
6845 self.edit_prediction_preview
6846 .set_previous_scroll_position(None);
6847 } else {
6848 self.edit_prediction_preview
6849 .set_previous_scroll_position(Some(
6850 position_map.snapshot.scroll_anchor,
6851 ));
6852
6853 self.highlight_rows::<EditPredictionPreview>(
6854 target..target,
6855 cx.theme().colors().editor_highlighted_line_background,
6856 RowHighlightOptions {
6857 autoscroll: true,
6858 ..Default::default()
6859 },
6860 cx,
6861 );
6862 self.request_autoscroll(Autoscroll::fit(), cx);
6863 }
6864 }
6865 }
6866 InlineCompletion::Edit { edits, .. } => {
6867 if let Some(provider) = self.edit_prediction_provider() {
6868 provider.accept(cx);
6869 }
6870
6871 // Store the transaction ID and selections before applying the edit
6872 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
6873
6874 let snapshot = self.buffer.read(cx).snapshot(cx);
6875 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6876
6877 self.buffer.update(cx, |buffer, cx| {
6878 buffer.edit(edits.iter().cloned(), None, cx)
6879 });
6880
6881 self.change_selections(None, window, cx, |s| {
6882 s.select_anchor_ranges([last_edit_end..last_edit_end]);
6883 });
6884
6885 let selections = self.selections.disjoint_anchors();
6886 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
6887 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
6888 if has_new_transaction {
6889 self.selection_history
6890 .insert_transaction(transaction_id_now, selections);
6891 }
6892 }
6893
6894 self.update_visible_inline_completion(window, cx);
6895 if self.active_inline_completion.is_none() {
6896 self.refresh_inline_completion(true, true, window, cx);
6897 }
6898
6899 cx.notify();
6900 }
6901 }
6902
6903 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6904 }
6905
6906 pub fn accept_partial_inline_completion(
6907 &mut self,
6908 _: &AcceptPartialEditPrediction,
6909 window: &mut Window,
6910 cx: &mut Context<Self>,
6911 ) {
6912 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6913 return;
6914 };
6915 if self.selections.count() != 1 {
6916 return;
6917 }
6918
6919 self.report_inline_completion_event(
6920 active_inline_completion.completion_id.clone(),
6921 true,
6922 cx,
6923 );
6924
6925 match &active_inline_completion.completion {
6926 InlineCompletion::Move { target, .. } => {
6927 let target = *target;
6928 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6929 selections.select_anchor_ranges([target..target]);
6930 });
6931 }
6932 InlineCompletion::Edit { edits, .. } => {
6933 // Find an insertion that starts at the cursor position.
6934 let snapshot = self.buffer.read(cx).snapshot(cx);
6935 let cursor_offset = self.selections.newest::<usize>(cx).head();
6936 let insertion = edits.iter().find_map(|(range, text)| {
6937 let range = range.to_offset(&snapshot);
6938 if range.is_empty() && range.start == cursor_offset {
6939 Some(text)
6940 } else {
6941 None
6942 }
6943 });
6944
6945 if let Some(text) = insertion {
6946 let mut partial_completion = text
6947 .chars()
6948 .by_ref()
6949 .take_while(|c| c.is_alphabetic())
6950 .collect::<String>();
6951 if partial_completion.is_empty() {
6952 partial_completion = text
6953 .chars()
6954 .by_ref()
6955 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6956 .collect::<String>();
6957 }
6958
6959 cx.emit(EditorEvent::InputHandled {
6960 utf16_range_to_replace: None,
6961 text: partial_completion.clone().into(),
6962 });
6963
6964 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6965
6966 self.refresh_inline_completion(true, true, window, cx);
6967 cx.notify();
6968 } else {
6969 self.accept_edit_prediction(&Default::default(), window, cx);
6970 }
6971 }
6972 }
6973 }
6974
6975 fn discard_inline_completion(
6976 &mut self,
6977 should_report_inline_completion_event: bool,
6978 cx: &mut Context<Self>,
6979 ) -> bool {
6980 if should_report_inline_completion_event {
6981 let completion_id = self
6982 .active_inline_completion
6983 .as_ref()
6984 .and_then(|active_completion| active_completion.completion_id.clone());
6985
6986 self.report_inline_completion_event(completion_id, false, cx);
6987 }
6988
6989 if let Some(provider) = self.edit_prediction_provider() {
6990 provider.discard(cx);
6991 }
6992
6993 self.take_active_inline_completion(cx)
6994 }
6995
6996 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6997 let Some(provider) = self.edit_prediction_provider() else {
6998 return;
6999 };
7000
7001 let Some((_, buffer, _)) = self
7002 .buffer
7003 .read(cx)
7004 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7005 else {
7006 return;
7007 };
7008
7009 let extension = buffer
7010 .read(cx)
7011 .file()
7012 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7013
7014 let event_type = match accepted {
7015 true => "Edit Prediction Accepted",
7016 false => "Edit Prediction Discarded",
7017 };
7018 telemetry::event!(
7019 event_type,
7020 provider = provider.name(),
7021 prediction_id = id,
7022 suggestion_accepted = accepted,
7023 file_extension = extension,
7024 );
7025 }
7026
7027 pub fn has_active_inline_completion(&self) -> bool {
7028 self.active_inline_completion.is_some()
7029 }
7030
7031 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7032 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7033 return false;
7034 };
7035
7036 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7037 self.clear_highlights::<InlineCompletionHighlight>(cx);
7038 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7039 true
7040 }
7041
7042 /// Returns true when we're displaying the edit prediction popover below the cursor
7043 /// like we are not previewing and the LSP autocomplete menu is visible
7044 /// or we are in `when_holding_modifier` mode.
7045 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7046 if self.edit_prediction_preview_is_active()
7047 || !self.show_edit_predictions_in_menu()
7048 || !self.edit_predictions_enabled()
7049 {
7050 return false;
7051 }
7052
7053 if self.has_visible_completions_menu() {
7054 return true;
7055 }
7056
7057 has_completion && self.edit_prediction_requires_modifier()
7058 }
7059
7060 fn handle_modifiers_changed(
7061 &mut self,
7062 modifiers: Modifiers,
7063 position_map: &PositionMap,
7064 window: &mut Window,
7065 cx: &mut Context<Self>,
7066 ) {
7067 if self.show_edit_predictions_in_menu() {
7068 self.update_edit_prediction_preview(&modifiers, window, cx);
7069 }
7070
7071 self.update_selection_mode(&modifiers, position_map, window, cx);
7072
7073 let mouse_position = window.mouse_position();
7074 if !position_map.text_hitbox.is_hovered(window) {
7075 return;
7076 }
7077
7078 self.update_hovered_link(
7079 position_map.point_for_position(mouse_position),
7080 &position_map.snapshot,
7081 modifiers,
7082 window,
7083 cx,
7084 )
7085 }
7086
7087 fn update_selection_mode(
7088 &mut self,
7089 modifiers: &Modifiers,
7090 position_map: &PositionMap,
7091 window: &mut Window,
7092 cx: &mut Context<Self>,
7093 ) {
7094 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
7095 return;
7096 }
7097
7098 let mouse_position = window.mouse_position();
7099 let point_for_position = position_map.point_for_position(mouse_position);
7100 let position = point_for_position.previous_valid;
7101
7102 self.select(
7103 SelectPhase::BeginColumnar {
7104 position,
7105 reset: false,
7106 goal_column: point_for_position.exact_unclipped.column(),
7107 },
7108 window,
7109 cx,
7110 );
7111 }
7112
7113 fn update_edit_prediction_preview(
7114 &mut self,
7115 modifiers: &Modifiers,
7116 window: &mut Window,
7117 cx: &mut Context<Self>,
7118 ) {
7119 let mut modifiers_held = false;
7120 if let Some(accept_keystroke) = self
7121 .accept_edit_prediction_keybind(false, window, cx)
7122 .keystroke()
7123 {
7124 modifiers_held = modifiers_held
7125 || (&accept_keystroke.modifiers == modifiers
7126 && accept_keystroke.modifiers.modified());
7127 };
7128 if let Some(accept_partial_keystroke) = self
7129 .accept_edit_prediction_keybind(true, window, cx)
7130 .keystroke()
7131 {
7132 modifiers_held = modifiers_held
7133 || (&accept_partial_keystroke.modifiers == modifiers
7134 && accept_partial_keystroke.modifiers.modified());
7135 }
7136
7137 if modifiers_held {
7138 if matches!(
7139 self.edit_prediction_preview,
7140 EditPredictionPreview::Inactive { .. }
7141 ) {
7142 self.edit_prediction_preview = EditPredictionPreview::Active {
7143 previous_scroll_position: None,
7144 since: Instant::now(),
7145 };
7146
7147 self.update_visible_inline_completion(window, cx);
7148 cx.notify();
7149 }
7150 } else if let EditPredictionPreview::Active {
7151 previous_scroll_position,
7152 since,
7153 } = self.edit_prediction_preview
7154 {
7155 if let (Some(previous_scroll_position), Some(position_map)) =
7156 (previous_scroll_position, self.last_position_map.as_ref())
7157 {
7158 self.set_scroll_position(
7159 previous_scroll_position
7160 .scroll_position(&position_map.snapshot.display_snapshot),
7161 window,
7162 cx,
7163 );
7164 }
7165
7166 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7167 released_too_fast: since.elapsed() < Duration::from_millis(200),
7168 };
7169 self.clear_row_highlights::<EditPredictionPreview>();
7170 self.update_visible_inline_completion(window, cx);
7171 cx.notify();
7172 }
7173 }
7174
7175 fn update_visible_inline_completion(
7176 &mut self,
7177 _window: &mut Window,
7178 cx: &mut Context<Self>,
7179 ) -> Option<()> {
7180 let selection = self.selections.newest_anchor();
7181 let cursor = selection.head();
7182 let multibuffer = self.buffer.read(cx).snapshot(cx);
7183 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7184 let excerpt_id = cursor.excerpt_id;
7185
7186 let show_in_menu = self.show_edit_predictions_in_menu();
7187 let completions_menu_has_precedence = !show_in_menu
7188 && (self.context_menu.borrow().is_some()
7189 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7190
7191 if completions_menu_has_precedence
7192 || !offset_selection.is_empty()
7193 || self
7194 .active_inline_completion
7195 .as_ref()
7196 .map_or(false, |completion| {
7197 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7198 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7199 !invalidation_range.contains(&offset_selection.head())
7200 })
7201 {
7202 self.discard_inline_completion(false, cx);
7203 return None;
7204 }
7205
7206 self.take_active_inline_completion(cx);
7207 let Some(provider) = self.edit_prediction_provider() else {
7208 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7209 return None;
7210 };
7211
7212 let (buffer, cursor_buffer_position) =
7213 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7214
7215 self.edit_prediction_settings =
7216 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7217
7218 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7219
7220 if self.edit_prediction_indent_conflict {
7221 let cursor_point = cursor.to_point(&multibuffer);
7222
7223 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7224
7225 if let Some((_, indent)) = indents.iter().next() {
7226 if indent.len == cursor_point.column {
7227 self.edit_prediction_indent_conflict = false;
7228 }
7229 }
7230 }
7231
7232 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7233 let edits = inline_completion
7234 .edits
7235 .into_iter()
7236 .flat_map(|(range, new_text)| {
7237 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7238 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7239 Some((start..end, new_text))
7240 })
7241 .collect::<Vec<_>>();
7242 if edits.is_empty() {
7243 return None;
7244 }
7245
7246 let first_edit_start = edits.first().unwrap().0.start;
7247 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7248 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7249
7250 let last_edit_end = edits.last().unwrap().0.end;
7251 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7252 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7253
7254 let cursor_row = cursor.to_point(&multibuffer).row;
7255
7256 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7257
7258 let mut inlay_ids = Vec::new();
7259 let invalidation_row_range;
7260 let move_invalidation_row_range = if cursor_row < edit_start_row {
7261 Some(cursor_row..edit_end_row)
7262 } else if cursor_row > edit_end_row {
7263 Some(edit_start_row..cursor_row)
7264 } else {
7265 None
7266 };
7267 let is_move =
7268 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7269 let completion = if is_move {
7270 invalidation_row_range =
7271 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7272 let target = first_edit_start;
7273 InlineCompletion::Move { target, snapshot }
7274 } else {
7275 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7276 && !self.inline_completions_hidden_for_vim_mode;
7277
7278 if show_completions_in_buffer {
7279 if edits
7280 .iter()
7281 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7282 {
7283 let mut inlays = Vec::new();
7284 for (range, new_text) in &edits {
7285 let inlay = Inlay::inline_completion(
7286 post_inc(&mut self.next_inlay_id),
7287 range.start,
7288 new_text.as_str(),
7289 );
7290 inlay_ids.push(inlay.id);
7291 inlays.push(inlay);
7292 }
7293
7294 self.splice_inlays(&[], inlays, cx);
7295 } else {
7296 let background_color = cx.theme().status().deleted_background;
7297 self.highlight_text::<InlineCompletionHighlight>(
7298 edits.iter().map(|(range, _)| range.clone()).collect(),
7299 HighlightStyle {
7300 background_color: Some(background_color),
7301 ..Default::default()
7302 },
7303 cx,
7304 );
7305 }
7306 }
7307
7308 invalidation_row_range = edit_start_row..edit_end_row;
7309
7310 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7311 if provider.show_tab_accept_marker() {
7312 EditDisplayMode::TabAccept
7313 } else {
7314 EditDisplayMode::Inline
7315 }
7316 } else {
7317 EditDisplayMode::DiffPopover
7318 };
7319
7320 InlineCompletion::Edit {
7321 edits,
7322 edit_preview: inline_completion.edit_preview,
7323 display_mode,
7324 snapshot,
7325 }
7326 };
7327
7328 let invalidation_range = multibuffer
7329 .anchor_before(Point::new(invalidation_row_range.start, 0))
7330 ..multibuffer.anchor_after(Point::new(
7331 invalidation_row_range.end,
7332 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7333 ));
7334
7335 self.stale_inline_completion_in_menu = None;
7336 self.active_inline_completion = Some(InlineCompletionState {
7337 inlay_ids,
7338 completion,
7339 completion_id: inline_completion.id,
7340 invalidation_range,
7341 });
7342
7343 cx.notify();
7344
7345 Some(())
7346 }
7347
7348 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7349 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7350 }
7351
7352 fn clear_tasks(&mut self) {
7353 self.tasks.clear()
7354 }
7355
7356 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7357 if self.tasks.insert(key, value).is_some() {
7358 // This case should hopefully be rare, but just in case...
7359 log::error!(
7360 "multiple different run targets found on a single line, only the last target will be rendered"
7361 )
7362 }
7363 }
7364
7365 /// Get all display points of breakpoints that will be rendered within editor
7366 ///
7367 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7368 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7369 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7370 fn active_breakpoints(
7371 &self,
7372 range: Range<DisplayRow>,
7373 window: &mut Window,
7374 cx: &mut Context<Self>,
7375 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7376 let mut breakpoint_display_points = HashMap::default();
7377
7378 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7379 return breakpoint_display_points;
7380 };
7381
7382 let snapshot = self.snapshot(window, cx);
7383
7384 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7385 let Some(project) = self.project.as_ref() else {
7386 return breakpoint_display_points;
7387 };
7388
7389 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7390 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7391
7392 for (buffer_snapshot, range, excerpt_id) in
7393 multi_buffer_snapshot.range_to_buffer_ranges(range)
7394 {
7395 let Some(buffer) = project
7396 .read(cx)
7397 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7398 else {
7399 continue;
7400 };
7401 let breakpoints = breakpoint_store.read(cx).breakpoints(
7402 &buffer,
7403 Some(
7404 buffer_snapshot.anchor_before(range.start)
7405 ..buffer_snapshot.anchor_after(range.end),
7406 ),
7407 buffer_snapshot,
7408 cx,
7409 );
7410 for (breakpoint, state) in breakpoints {
7411 let multi_buffer_anchor =
7412 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7413 let position = multi_buffer_anchor
7414 .to_point(&multi_buffer_snapshot)
7415 .to_display_point(&snapshot);
7416
7417 breakpoint_display_points.insert(
7418 position.row(),
7419 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7420 );
7421 }
7422 }
7423
7424 breakpoint_display_points
7425 }
7426
7427 fn breakpoint_context_menu(
7428 &self,
7429 anchor: Anchor,
7430 window: &mut Window,
7431 cx: &mut Context<Self>,
7432 ) -> Entity<ui::ContextMenu> {
7433 let weak_editor = cx.weak_entity();
7434 let focus_handle = self.focus_handle(cx);
7435
7436 let row = self
7437 .buffer
7438 .read(cx)
7439 .snapshot(cx)
7440 .summary_for_anchor::<Point>(&anchor)
7441 .row;
7442
7443 let breakpoint = self
7444 .breakpoint_at_row(row, window, cx)
7445 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7446
7447 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7448 "Edit Log Breakpoint"
7449 } else {
7450 "Set Log Breakpoint"
7451 };
7452
7453 let condition_breakpoint_msg = if breakpoint
7454 .as_ref()
7455 .is_some_and(|bp| bp.1.condition.is_some())
7456 {
7457 "Edit Condition Breakpoint"
7458 } else {
7459 "Set Condition Breakpoint"
7460 };
7461
7462 let hit_condition_breakpoint_msg = if breakpoint
7463 .as_ref()
7464 .is_some_and(|bp| bp.1.hit_condition.is_some())
7465 {
7466 "Edit Hit Condition Breakpoint"
7467 } else {
7468 "Set Hit Condition Breakpoint"
7469 };
7470
7471 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7472 "Unset Breakpoint"
7473 } else {
7474 "Set Breakpoint"
7475 };
7476
7477 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
7478 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
7479
7480 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7481 BreakpointState::Enabled => Some("Disable"),
7482 BreakpointState::Disabled => Some("Enable"),
7483 });
7484
7485 let (anchor, breakpoint) =
7486 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7487
7488 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7489 menu.on_blur_subscription(Subscription::new(|| {}))
7490 .context(focus_handle)
7491 .when(run_to_cursor, |this| {
7492 let weak_editor = weak_editor.clone();
7493 this.entry("Run to cursor", None, move |window, cx| {
7494 weak_editor
7495 .update(cx, |editor, cx| {
7496 editor.change_selections(None, window, cx, |s| {
7497 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7498 });
7499 })
7500 .ok();
7501
7502 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
7503 })
7504 .separator()
7505 })
7506 .when_some(toggle_state_msg, |this, msg| {
7507 this.entry(msg, None, {
7508 let weak_editor = weak_editor.clone();
7509 let breakpoint = breakpoint.clone();
7510 move |_window, cx| {
7511 weak_editor
7512 .update(cx, |this, cx| {
7513 this.edit_breakpoint_at_anchor(
7514 anchor,
7515 breakpoint.as_ref().clone(),
7516 BreakpointEditAction::InvertState,
7517 cx,
7518 );
7519 })
7520 .log_err();
7521 }
7522 })
7523 })
7524 .entry(set_breakpoint_msg, None, {
7525 let weak_editor = weak_editor.clone();
7526 let breakpoint = breakpoint.clone();
7527 move |_window, cx| {
7528 weak_editor
7529 .update(cx, |this, cx| {
7530 this.edit_breakpoint_at_anchor(
7531 anchor,
7532 breakpoint.as_ref().clone(),
7533 BreakpointEditAction::Toggle,
7534 cx,
7535 );
7536 })
7537 .log_err();
7538 }
7539 })
7540 .entry(log_breakpoint_msg, None, {
7541 let breakpoint = breakpoint.clone();
7542 let weak_editor = weak_editor.clone();
7543 move |window, cx| {
7544 weak_editor
7545 .update(cx, |this, cx| {
7546 this.add_edit_breakpoint_block(
7547 anchor,
7548 breakpoint.as_ref(),
7549 BreakpointPromptEditAction::Log,
7550 window,
7551 cx,
7552 );
7553 })
7554 .log_err();
7555 }
7556 })
7557 .entry(condition_breakpoint_msg, None, {
7558 let breakpoint = breakpoint.clone();
7559 let weak_editor = weak_editor.clone();
7560 move |window, cx| {
7561 weak_editor
7562 .update(cx, |this, cx| {
7563 this.add_edit_breakpoint_block(
7564 anchor,
7565 breakpoint.as_ref(),
7566 BreakpointPromptEditAction::Condition,
7567 window,
7568 cx,
7569 );
7570 })
7571 .log_err();
7572 }
7573 })
7574 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7575 weak_editor
7576 .update(cx, |this, cx| {
7577 this.add_edit_breakpoint_block(
7578 anchor,
7579 breakpoint.as_ref(),
7580 BreakpointPromptEditAction::HitCondition,
7581 window,
7582 cx,
7583 );
7584 })
7585 .log_err();
7586 })
7587 })
7588 }
7589
7590 fn render_breakpoint(
7591 &self,
7592 position: Anchor,
7593 row: DisplayRow,
7594 breakpoint: &Breakpoint,
7595 state: Option<BreakpointSessionState>,
7596 cx: &mut Context<Self>,
7597 ) -> IconButton {
7598 let is_rejected = state.is_some_and(|s| !s.verified);
7599 // Is it a breakpoint that shows up when hovering over gutter?
7600 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7601 (false, false),
7602 |PhantomBreakpointIndicator {
7603 is_active,
7604 display_row,
7605 collides_with_existing_breakpoint,
7606 }| {
7607 (
7608 is_active && display_row == row,
7609 collides_with_existing_breakpoint,
7610 )
7611 },
7612 );
7613
7614 let (color, icon) = {
7615 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7616 (false, false) => ui::IconName::DebugBreakpoint,
7617 (true, false) => ui::IconName::DebugLogBreakpoint,
7618 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7619 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7620 };
7621
7622 let color = if is_phantom {
7623 Color::Hint
7624 } else if is_rejected {
7625 Color::Disabled
7626 } else {
7627 Color::Debugger
7628 };
7629
7630 (color, icon)
7631 };
7632
7633 let breakpoint = Arc::from(breakpoint.clone());
7634
7635 let alt_as_text = gpui::Keystroke {
7636 modifiers: Modifiers::secondary_key(),
7637 ..Default::default()
7638 };
7639 let primary_action_text = if breakpoint.is_disabled() {
7640 "Enable breakpoint"
7641 } else if is_phantom && !collides_with_existing {
7642 "Set breakpoint"
7643 } else {
7644 "Unset breakpoint"
7645 };
7646 let focus_handle = self.focus_handle.clone();
7647
7648 let meta = if is_rejected {
7649 SharedString::from("No executable code is associated with this line.")
7650 } else if collides_with_existing && !breakpoint.is_disabled() {
7651 SharedString::from(format!(
7652 "{alt_as_text}-click to disable,\nright-click for more options."
7653 ))
7654 } else {
7655 SharedString::from("Right-click for more options.")
7656 };
7657 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7658 .icon_size(IconSize::XSmall)
7659 .size(ui::ButtonSize::None)
7660 .when(is_rejected, |this| {
7661 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7662 })
7663 .icon_color(color)
7664 .style(ButtonStyle::Transparent)
7665 .on_click(cx.listener({
7666 let breakpoint = breakpoint.clone();
7667
7668 move |editor, event: &ClickEvent, window, cx| {
7669 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7670 BreakpointEditAction::InvertState
7671 } else {
7672 BreakpointEditAction::Toggle
7673 };
7674
7675 window.focus(&editor.focus_handle(cx));
7676 editor.edit_breakpoint_at_anchor(
7677 position,
7678 breakpoint.as_ref().clone(),
7679 edit_action,
7680 cx,
7681 );
7682 }
7683 }))
7684 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7685 editor.set_breakpoint_context_menu(
7686 row,
7687 Some(position),
7688 event.down.position,
7689 window,
7690 cx,
7691 );
7692 }))
7693 .tooltip(move |window, cx| {
7694 Tooltip::with_meta_in(
7695 primary_action_text,
7696 Some(&ToggleBreakpoint),
7697 meta.clone(),
7698 &focus_handle,
7699 window,
7700 cx,
7701 )
7702 })
7703 }
7704
7705 fn build_tasks_context(
7706 project: &Entity<Project>,
7707 buffer: &Entity<Buffer>,
7708 buffer_row: u32,
7709 tasks: &Arc<RunnableTasks>,
7710 cx: &mut Context<Self>,
7711 ) -> Task<Option<task::TaskContext>> {
7712 let position = Point::new(buffer_row, tasks.column);
7713 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7714 let location = Location {
7715 buffer: buffer.clone(),
7716 range: range_start..range_start,
7717 };
7718 // Fill in the environmental variables from the tree-sitter captures
7719 let mut captured_task_variables = TaskVariables::default();
7720 for (capture_name, value) in tasks.extra_variables.clone() {
7721 captured_task_variables.insert(
7722 task::VariableName::Custom(capture_name.into()),
7723 value.clone(),
7724 );
7725 }
7726 project.update(cx, |project, cx| {
7727 project.task_store().update(cx, |task_store, cx| {
7728 task_store.task_context_for_location(captured_task_variables, location, cx)
7729 })
7730 })
7731 }
7732
7733 pub fn spawn_nearest_task(
7734 &mut self,
7735 action: &SpawnNearestTask,
7736 window: &mut Window,
7737 cx: &mut Context<Self>,
7738 ) {
7739 let Some((workspace, _)) = self.workspace.clone() else {
7740 return;
7741 };
7742 let Some(project) = self.project.clone() else {
7743 return;
7744 };
7745
7746 // Try to find a closest, enclosing node using tree-sitter that has a
7747 // task
7748 let Some((buffer, buffer_row, tasks)) = self
7749 .find_enclosing_node_task(cx)
7750 // Or find the task that's closest in row-distance.
7751 .or_else(|| self.find_closest_task(cx))
7752 else {
7753 return;
7754 };
7755
7756 let reveal_strategy = action.reveal;
7757 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7758 cx.spawn_in(window, async move |_, cx| {
7759 let context = task_context.await?;
7760 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7761
7762 let resolved = &mut resolved_task.resolved;
7763 resolved.reveal = reveal_strategy;
7764
7765 workspace
7766 .update_in(cx, |workspace, window, cx| {
7767 workspace.schedule_resolved_task(
7768 task_source_kind,
7769 resolved_task,
7770 false,
7771 window,
7772 cx,
7773 );
7774 })
7775 .ok()
7776 })
7777 .detach();
7778 }
7779
7780 fn find_closest_task(
7781 &mut self,
7782 cx: &mut Context<Self>,
7783 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7784 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7785
7786 let ((buffer_id, row), tasks) = self
7787 .tasks
7788 .iter()
7789 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7790
7791 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7792 let tasks = Arc::new(tasks.to_owned());
7793 Some((buffer, *row, tasks))
7794 }
7795
7796 fn find_enclosing_node_task(
7797 &mut self,
7798 cx: &mut Context<Self>,
7799 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7800 let snapshot = self.buffer.read(cx).snapshot(cx);
7801 let offset = self.selections.newest::<usize>(cx).head();
7802 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7803 let buffer_id = excerpt.buffer().remote_id();
7804
7805 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7806 let mut cursor = layer.node().walk();
7807
7808 while cursor.goto_first_child_for_byte(offset).is_some() {
7809 if cursor.node().end_byte() == offset {
7810 cursor.goto_next_sibling();
7811 }
7812 }
7813
7814 // Ascend to the smallest ancestor that contains the range and has a task.
7815 loop {
7816 let node = cursor.node();
7817 let node_range = node.byte_range();
7818 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7819
7820 // Check if this node contains our offset
7821 if node_range.start <= offset && node_range.end >= offset {
7822 // If it contains offset, check for task
7823 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7824 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7825 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7826 }
7827 }
7828
7829 if !cursor.goto_parent() {
7830 break;
7831 }
7832 }
7833 None
7834 }
7835
7836 fn render_run_indicator(
7837 &self,
7838 _style: &EditorStyle,
7839 is_active: bool,
7840 row: DisplayRow,
7841 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7842 cx: &mut Context<Self>,
7843 ) -> IconButton {
7844 let color = Color::Muted;
7845 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7846
7847 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7848 .shape(ui::IconButtonShape::Square)
7849 .icon_size(IconSize::XSmall)
7850 .icon_color(color)
7851 .toggle_state(is_active)
7852 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7853 let quick_launch = e.down.button == MouseButton::Left;
7854 window.focus(&editor.focus_handle(cx));
7855 editor.toggle_code_actions(
7856 &ToggleCodeActions {
7857 deployed_from: Some(CodeActionSource::Indicator(row)),
7858 quick_launch,
7859 },
7860 window,
7861 cx,
7862 );
7863 }))
7864 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7865 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7866 }))
7867 }
7868
7869 pub fn context_menu_visible(&self) -> bool {
7870 !self.edit_prediction_preview_is_active()
7871 && self
7872 .context_menu
7873 .borrow()
7874 .as_ref()
7875 .map_or(false, |menu| menu.visible())
7876 }
7877
7878 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7879 self.context_menu
7880 .borrow()
7881 .as_ref()
7882 .map(|menu| menu.origin())
7883 }
7884
7885 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7886 self.context_menu_options = Some(options);
7887 }
7888
7889 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7890 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7891
7892 fn render_edit_prediction_popover(
7893 &mut self,
7894 text_bounds: &Bounds<Pixels>,
7895 content_origin: gpui::Point<Pixels>,
7896 right_margin: Pixels,
7897 editor_snapshot: &EditorSnapshot,
7898 visible_row_range: Range<DisplayRow>,
7899 scroll_top: f32,
7900 scroll_bottom: f32,
7901 line_layouts: &[LineWithInvisibles],
7902 line_height: Pixels,
7903 scroll_pixel_position: gpui::Point<Pixels>,
7904 newest_selection_head: Option<DisplayPoint>,
7905 editor_width: Pixels,
7906 style: &EditorStyle,
7907 window: &mut Window,
7908 cx: &mut App,
7909 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7910 if self.mode().is_minimap() {
7911 return None;
7912 }
7913 let active_inline_completion = self.active_inline_completion.as_ref()?;
7914
7915 if self.edit_prediction_visible_in_cursor_popover(true) {
7916 return None;
7917 }
7918
7919 match &active_inline_completion.completion {
7920 InlineCompletion::Move { target, .. } => {
7921 let target_display_point = target.to_display_point(editor_snapshot);
7922
7923 if self.edit_prediction_requires_modifier() {
7924 if !self.edit_prediction_preview_is_active() {
7925 return None;
7926 }
7927
7928 self.render_edit_prediction_modifier_jump_popover(
7929 text_bounds,
7930 content_origin,
7931 visible_row_range,
7932 line_layouts,
7933 line_height,
7934 scroll_pixel_position,
7935 newest_selection_head,
7936 target_display_point,
7937 window,
7938 cx,
7939 )
7940 } else {
7941 self.render_edit_prediction_eager_jump_popover(
7942 text_bounds,
7943 content_origin,
7944 editor_snapshot,
7945 visible_row_range,
7946 scroll_top,
7947 scroll_bottom,
7948 line_height,
7949 scroll_pixel_position,
7950 target_display_point,
7951 editor_width,
7952 window,
7953 cx,
7954 )
7955 }
7956 }
7957 InlineCompletion::Edit {
7958 display_mode: EditDisplayMode::Inline,
7959 ..
7960 } => None,
7961 InlineCompletion::Edit {
7962 display_mode: EditDisplayMode::TabAccept,
7963 edits,
7964 ..
7965 } => {
7966 let range = &edits.first()?.0;
7967 let target_display_point = range.end.to_display_point(editor_snapshot);
7968
7969 self.render_edit_prediction_end_of_line_popover(
7970 "Accept",
7971 editor_snapshot,
7972 visible_row_range,
7973 target_display_point,
7974 line_height,
7975 scroll_pixel_position,
7976 content_origin,
7977 editor_width,
7978 window,
7979 cx,
7980 )
7981 }
7982 InlineCompletion::Edit {
7983 edits,
7984 edit_preview,
7985 display_mode: EditDisplayMode::DiffPopover,
7986 snapshot,
7987 } => self.render_edit_prediction_diff_popover(
7988 text_bounds,
7989 content_origin,
7990 right_margin,
7991 editor_snapshot,
7992 visible_row_range,
7993 line_layouts,
7994 line_height,
7995 scroll_pixel_position,
7996 newest_selection_head,
7997 editor_width,
7998 style,
7999 edits,
8000 edit_preview,
8001 snapshot,
8002 window,
8003 cx,
8004 ),
8005 }
8006 }
8007
8008 fn render_edit_prediction_modifier_jump_popover(
8009 &mut self,
8010 text_bounds: &Bounds<Pixels>,
8011 content_origin: gpui::Point<Pixels>,
8012 visible_row_range: Range<DisplayRow>,
8013 line_layouts: &[LineWithInvisibles],
8014 line_height: Pixels,
8015 scroll_pixel_position: gpui::Point<Pixels>,
8016 newest_selection_head: Option<DisplayPoint>,
8017 target_display_point: DisplayPoint,
8018 window: &mut Window,
8019 cx: &mut App,
8020 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8021 let scrolled_content_origin =
8022 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8023
8024 const SCROLL_PADDING_Y: Pixels = px(12.);
8025
8026 if target_display_point.row() < visible_row_range.start {
8027 return self.render_edit_prediction_scroll_popover(
8028 |_| SCROLL_PADDING_Y,
8029 IconName::ArrowUp,
8030 visible_row_range,
8031 line_layouts,
8032 newest_selection_head,
8033 scrolled_content_origin,
8034 window,
8035 cx,
8036 );
8037 } else if target_display_point.row() >= visible_row_range.end {
8038 return self.render_edit_prediction_scroll_popover(
8039 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8040 IconName::ArrowDown,
8041 visible_row_range,
8042 line_layouts,
8043 newest_selection_head,
8044 scrolled_content_origin,
8045 window,
8046 cx,
8047 );
8048 }
8049
8050 const POLE_WIDTH: Pixels = px(2.);
8051
8052 let line_layout =
8053 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8054 let target_column = target_display_point.column() as usize;
8055
8056 let target_x = line_layout.x_for_index(target_column);
8057 let target_y =
8058 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8059
8060 let flag_on_right = target_x < text_bounds.size.width / 2.;
8061
8062 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8063 border_color.l += 0.001;
8064
8065 let mut element = v_flex()
8066 .items_end()
8067 .when(flag_on_right, |el| el.items_start())
8068 .child(if flag_on_right {
8069 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8070 .rounded_bl(px(0.))
8071 .rounded_tl(px(0.))
8072 .border_l_2()
8073 .border_color(border_color)
8074 } else {
8075 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8076 .rounded_br(px(0.))
8077 .rounded_tr(px(0.))
8078 .border_r_2()
8079 .border_color(border_color)
8080 })
8081 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8082 .into_any();
8083
8084 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8085
8086 let mut origin = scrolled_content_origin + point(target_x, target_y)
8087 - point(
8088 if flag_on_right {
8089 POLE_WIDTH
8090 } else {
8091 size.width - POLE_WIDTH
8092 },
8093 size.height - line_height,
8094 );
8095
8096 origin.x = origin.x.max(content_origin.x);
8097
8098 element.prepaint_at(origin, window, cx);
8099
8100 Some((element, origin))
8101 }
8102
8103 fn render_edit_prediction_scroll_popover(
8104 &mut self,
8105 to_y: impl Fn(Size<Pixels>) -> Pixels,
8106 scroll_icon: IconName,
8107 visible_row_range: Range<DisplayRow>,
8108 line_layouts: &[LineWithInvisibles],
8109 newest_selection_head: Option<DisplayPoint>,
8110 scrolled_content_origin: gpui::Point<Pixels>,
8111 window: &mut Window,
8112 cx: &mut App,
8113 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8114 let mut element = self
8115 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8116 .into_any();
8117
8118 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8119
8120 let cursor = newest_selection_head?;
8121 let cursor_row_layout =
8122 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8123 let cursor_column = cursor.column() as usize;
8124
8125 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8126
8127 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8128
8129 element.prepaint_at(origin, window, cx);
8130 Some((element, origin))
8131 }
8132
8133 fn render_edit_prediction_eager_jump_popover(
8134 &mut self,
8135 text_bounds: &Bounds<Pixels>,
8136 content_origin: gpui::Point<Pixels>,
8137 editor_snapshot: &EditorSnapshot,
8138 visible_row_range: Range<DisplayRow>,
8139 scroll_top: f32,
8140 scroll_bottom: f32,
8141 line_height: Pixels,
8142 scroll_pixel_position: gpui::Point<Pixels>,
8143 target_display_point: DisplayPoint,
8144 editor_width: Pixels,
8145 window: &mut Window,
8146 cx: &mut App,
8147 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8148 if target_display_point.row().as_f32() < scroll_top {
8149 let mut element = self
8150 .render_edit_prediction_line_popover(
8151 "Jump to Edit",
8152 Some(IconName::ArrowUp),
8153 window,
8154 cx,
8155 )?
8156 .into_any();
8157
8158 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8159 let offset = point(
8160 (text_bounds.size.width - size.width) / 2.,
8161 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8162 );
8163
8164 let origin = text_bounds.origin + offset;
8165 element.prepaint_at(origin, window, cx);
8166 Some((element, origin))
8167 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8168 let mut element = self
8169 .render_edit_prediction_line_popover(
8170 "Jump to Edit",
8171 Some(IconName::ArrowDown),
8172 window,
8173 cx,
8174 )?
8175 .into_any();
8176
8177 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8178 let offset = point(
8179 (text_bounds.size.width - size.width) / 2.,
8180 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8181 );
8182
8183 let origin = text_bounds.origin + offset;
8184 element.prepaint_at(origin, window, cx);
8185 Some((element, origin))
8186 } else {
8187 self.render_edit_prediction_end_of_line_popover(
8188 "Jump to Edit",
8189 editor_snapshot,
8190 visible_row_range,
8191 target_display_point,
8192 line_height,
8193 scroll_pixel_position,
8194 content_origin,
8195 editor_width,
8196 window,
8197 cx,
8198 )
8199 }
8200 }
8201
8202 fn render_edit_prediction_end_of_line_popover(
8203 self: &mut Editor,
8204 label: &'static str,
8205 editor_snapshot: &EditorSnapshot,
8206 visible_row_range: Range<DisplayRow>,
8207 target_display_point: DisplayPoint,
8208 line_height: Pixels,
8209 scroll_pixel_position: gpui::Point<Pixels>,
8210 content_origin: gpui::Point<Pixels>,
8211 editor_width: Pixels,
8212 window: &mut Window,
8213 cx: &mut App,
8214 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8215 let target_line_end = DisplayPoint::new(
8216 target_display_point.row(),
8217 editor_snapshot.line_len(target_display_point.row()),
8218 );
8219
8220 let mut element = self
8221 .render_edit_prediction_line_popover(label, None, window, cx)?
8222 .into_any();
8223
8224 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8225
8226 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8227
8228 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8229 let mut origin = start_point
8230 + line_origin
8231 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8232 origin.x = origin.x.max(content_origin.x);
8233
8234 let max_x = content_origin.x + editor_width - size.width;
8235
8236 if origin.x > max_x {
8237 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8238
8239 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8240 origin.y += offset;
8241 IconName::ArrowUp
8242 } else {
8243 origin.y -= offset;
8244 IconName::ArrowDown
8245 };
8246
8247 element = self
8248 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8249 .into_any();
8250
8251 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8252
8253 origin.x = content_origin.x + editor_width - size.width - px(2.);
8254 }
8255
8256 element.prepaint_at(origin, window, cx);
8257 Some((element, origin))
8258 }
8259
8260 fn render_edit_prediction_diff_popover(
8261 self: &Editor,
8262 text_bounds: &Bounds<Pixels>,
8263 content_origin: gpui::Point<Pixels>,
8264 right_margin: Pixels,
8265 editor_snapshot: &EditorSnapshot,
8266 visible_row_range: Range<DisplayRow>,
8267 line_layouts: &[LineWithInvisibles],
8268 line_height: Pixels,
8269 scroll_pixel_position: gpui::Point<Pixels>,
8270 newest_selection_head: Option<DisplayPoint>,
8271 editor_width: Pixels,
8272 style: &EditorStyle,
8273 edits: &Vec<(Range<Anchor>, String)>,
8274 edit_preview: &Option<language::EditPreview>,
8275 snapshot: &language::BufferSnapshot,
8276 window: &mut Window,
8277 cx: &mut App,
8278 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8279 let edit_start = edits
8280 .first()
8281 .unwrap()
8282 .0
8283 .start
8284 .to_display_point(editor_snapshot);
8285 let edit_end = edits
8286 .last()
8287 .unwrap()
8288 .0
8289 .end
8290 .to_display_point(editor_snapshot);
8291
8292 let is_visible = visible_row_range.contains(&edit_start.row())
8293 || visible_row_range.contains(&edit_end.row());
8294 if !is_visible {
8295 return None;
8296 }
8297
8298 let highlighted_edits =
8299 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8300
8301 let styled_text = highlighted_edits.to_styled_text(&style.text);
8302 let line_count = highlighted_edits.text.lines().count();
8303
8304 const BORDER_WIDTH: Pixels = px(1.);
8305
8306 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8307 let has_keybind = keybind.is_some();
8308
8309 let mut element = h_flex()
8310 .items_start()
8311 .child(
8312 h_flex()
8313 .bg(cx.theme().colors().editor_background)
8314 .border(BORDER_WIDTH)
8315 .shadow_sm()
8316 .border_color(cx.theme().colors().border)
8317 .rounded_l_lg()
8318 .when(line_count > 1, |el| el.rounded_br_lg())
8319 .pr_1()
8320 .child(styled_text),
8321 )
8322 .child(
8323 h_flex()
8324 .h(line_height + BORDER_WIDTH * 2.)
8325 .px_1p5()
8326 .gap_1()
8327 // Workaround: For some reason, there's a gap if we don't do this
8328 .ml(-BORDER_WIDTH)
8329 .shadow(vec![gpui::BoxShadow {
8330 color: gpui::black().opacity(0.05),
8331 offset: point(px(1.), px(1.)),
8332 blur_radius: px(2.),
8333 spread_radius: px(0.),
8334 }])
8335 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8336 .border(BORDER_WIDTH)
8337 .border_color(cx.theme().colors().border)
8338 .rounded_r_lg()
8339 .id("edit_prediction_diff_popover_keybind")
8340 .when(!has_keybind, |el| {
8341 let status_colors = cx.theme().status();
8342
8343 el.bg(status_colors.error_background)
8344 .border_color(status_colors.error.opacity(0.6))
8345 .child(Icon::new(IconName::Info).color(Color::Error))
8346 .cursor_default()
8347 .hoverable_tooltip(move |_window, cx| {
8348 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8349 })
8350 })
8351 .children(keybind),
8352 )
8353 .into_any();
8354
8355 let longest_row =
8356 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8357 let longest_line_width = if visible_row_range.contains(&longest_row) {
8358 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8359 } else {
8360 layout_line(
8361 longest_row,
8362 editor_snapshot,
8363 style,
8364 editor_width,
8365 |_| false,
8366 window,
8367 cx,
8368 )
8369 .width
8370 };
8371
8372 let viewport_bounds =
8373 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8374 right: -right_margin,
8375 ..Default::default()
8376 });
8377
8378 let x_after_longest =
8379 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8380 - scroll_pixel_position.x;
8381
8382 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8383
8384 // Fully visible if it can be displayed within the window (allow overlapping other
8385 // panes). However, this is only allowed if the popover starts within text_bounds.
8386 let can_position_to_the_right = x_after_longest < text_bounds.right()
8387 && x_after_longest + element_bounds.width < viewport_bounds.right();
8388
8389 let mut origin = if can_position_to_the_right {
8390 point(
8391 x_after_longest,
8392 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8393 - scroll_pixel_position.y,
8394 )
8395 } else {
8396 let cursor_row = newest_selection_head.map(|head| head.row());
8397 let above_edit = edit_start
8398 .row()
8399 .0
8400 .checked_sub(line_count as u32)
8401 .map(DisplayRow);
8402 let below_edit = Some(edit_end.row() + 1);
8403 let above_cursor =
8404 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8405 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8406
8407 // Place the edit popover adjacent to the edit if there is a location
8408 // available that is onscreen and does not obscure the cursor. Otherwise,
8409 // place it adjacent to the cursor.
8410 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8411 .into_iter()
8412 .flatten()
8413 .find(|&start_row| {
8414 let end_row = start_row + line_count as u32;
8415 visible_row_range.contains(&start_row)
8416 && visible_row_range.contains(&end_row)
8417 && cursor_row.map_or(true, |cursor_row| {
8418 !((start_row..end_row).contains(&cursor_row))
8419 })
8420 })?;
8421
8422 content_origin
8423 + point(
8424 -scroll_pixel_position.x,
8425 row_target.as_f32() * line_height - scroll_pixel_position.y,
8426 )
8427 };
8428
8429 origin.x -= BORDER_WIDTH;
8430
8431 window.defer_draw(element, origin, 1);
8432
8433 // Do not return an element, since it will already be drawn due to defer_draw.
8434 None
8435 }
8436
8437 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8438 px(30.)
8439 }
8440
8441 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8442 if self.read_only(cx) {
8443 cx.theme().players().read_only()
8444 } else {
8445 self.style.as_ref().unwrap().local_player
8446 }
8447 }
8448
8449 fn render_edit_prediction_accept_keybind(
8450 &self,
8451 window: &mut Window,
8452 cx: &App,
8453 ) -> Option<AnyElement> {
8454 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8455 let accept_keystroke = accept_binding.keystroke()?;
8456
8457 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8458
8459 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8460 Color::Accent
8461 } else {
8462 Color::Muted
8463 };
8464
8465 h_flex()
8466 .px_0p5()
8467 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8468 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8469 .text_size(TextSize::XSmall.rems(cx))
8470 .child(h_flex().children(ui::render_modifiers(
8471 &accept_keystroke.modifiers,
8472 PlatformStyle::platform(),
8473 Some(modifiers_color),
8474 Some(IconSize::XSmall.rems().into()),
8475 true,
8476 )))
8477 .when(is_platform_style_mac, |parent| {
8478 parent.child(accept_keystroke.key.clone())
8479 })
8480 .when(!is_platform_style_mac, |parent| {
8481 parent.child(
8482 Key::new(
8483 util::capitalize(&accept_keystroke.key),
8484 Some(Color::Default),
8485 )
8486 .size(Some(IconSize::XSmall.rems().into())),
8487 )
8488 })
8489 .into_any()
8490 .into()
8491 }
8492
8493 fn render_edit_prediction_line_popover(
8494 &self,
8495 label: impl Into<SharedString>,
8496 icon: Option<IconName>,
8497 window: &mut Window,
8498 cx: &App,
8499 ) -> Option<Stateful<Div>> {
8500 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8501
8502 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8503 let has_keybind = keybind.is_some();
8504
8505 let result = h_flex()
8506 .id("ep-line-popover")
8507 .py_0p5()
8508 .pl_1()
8509 .pr(padding_right)
8510 .gap_1()
8511 .rounded_md()
8512 .border_1()
8513 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8514 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8515 .shadow_sm()
8516 .when(!has_keybind, |el| {
8517 let status_colors = cx.theme().status();
8518
8519 el.bg(status_colors.error_background)
8520 .border_color(status_colors.error.opacity(0.6))
8521 .pl_2()
8522 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8523 .cursor_default()
8524 .hoverable_tooltip(move |_window, cx| {
8525 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8526 })
8527 })
8528 .children(keybind)
8529 .child(
8530 Label::new(label)
8531 .size(LabelSize::Small)
8532 .when(!has_keybind, |el| {
8533 el.color(cx.theme().status().error.into()).strikethrough()
8534 }),
8535 )
8536 .when(!has_keybind, |el| {
8537 el.child(
8538 h_flex().ml_1().child(
8539 Icon::new(IconName::Info)
8540 .size(IconSize::Small)
8541 .color(cx.theme().status().error.into()),
8542 ),
8543 )
8544 })
8545 .when_some(icon, |element, icon| {
8546 element.child(
8547 div()
8548 .mt(px(1.5))
8549 .child(Icon::new(icon).size(IconSize::Small)),
8550 )
8551 });
8552
8553 Some(result)
8554 }
8555
8556 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8557 let accent_color = cx.theme().colors().text_accent;
8558 let editor_bg_color = cx.theme().colors().editor_background;
8559 editor_bg_color.blend(accent_color.opacity(0.1))
8560 }
8561
8562 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8563 let accent_color = cx.theme().colors().text_accent;
8564 let editor_bg_color = cx.theme().colors().editor_background;
8565 editor_bg_color.blend(accent_color.opacity(0.6))
8566 }
8567
8568 fn render_edit_prediction_cursor_popover(
8569 &self,
8570 min_width: Pixels,
8571 max_width: Pixels,
8572 cursor_point: Point,
8573 style: &EditorStyle,
8574 accept_keystroke: Option<&gpui::Keystroke>,
8575 _window: &Window,
8576 cx: &mut Context<Editor>,
8577 ) -> Option<AnyElement> {
8578 let provider = self.edit_prediction_provider.as_ref()?;
8579
8580 if provider.provider.needs_terms_acceptance(cx) {
8581 return Some(
8582 h_flex()
8583 .min_w(min_width)
8584 .flex_1()
8585 .px_2()
8586 .py_1()
8587 .gap_3()
8588 .elevation_2(cx)
8589 .hover(|style| style.bg(cx.theme().colors().element_hover))
8590 .id("accept-terms")
8591 .cursor_pointer()
8592 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8593 .on_click(cx.listener(|this, _event, window, cx| {
8594 cx.stop_propagation();
8595 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8596 window.dispatch_action(
8597 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8598 cx,
8599 );
8600 }))
8601 .child(
8602 h_flex()
8603 .flex_1()
8604 .gap_2()
8605 .child(Icon::new(IconName::ZedPredict))
8606 .child(Label::new("Accept Terms of Service"))
8607 .child(div().w_full())
8608 .child(
8609 Icon::new(IconName::ArrowUpRight)
8610 .color(Color::Muted)
8611 .size(IconSize::Small),
8612 )
8613 .into_any_element(),
8614 )
8615 .into_any(),
8616 );
8617 }
8618
8619 let is_refreshing = provider.provider.is_refreshing(cx);
8620
8621 fn pending_completion_container() -> Div {
8622 h_flex()
8623 .h_full()
8624 .flex_1()
8625 .gap_2()
8626 .child(Icon::new(IconName::ZedPredict))
8627 }
8628
8629 let completion = match &self.active_inline_completion {
8630 Some(prediction) => {
8631 if !self.has_visible_completions_menu() {
8632 const RADIUS: Pixels = px(6.);
8633 const BORDER_WIDTH: Pixels = px(1.);
8634
8635 return Some(
8636 h_flex()
8637 .elevation_2(cx)
8638 .border(BORDER_WIDTH)
8639 .border_color(cx.theme().colors().border)
8640 .when(accept_keystroke.is_none(), |el| {
8641 el.border_color(cx.theme().status().error)
8642 })
8643 .rounded(RADIUS)
8644 .rounded_tl(px(0.))
8645 .overflow_hidden()
8646 .child(div().px_1p5().child(match &prediction.completion {
8647 InlineCompletion::Move { target, snapshot } => {
8648 use text::ToPoint as _;
8649 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8650 {
8651 Icon::new(IconName::ZedPredictDown)
8652 } else {
8653 Icon::new(IconName::ZedPredictUp)
8654 }
8655 }
8656 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8657 }))
8658 .child(
8659 h_flex()
8660 .gap_1()
8661 .py_1()
8662 .px_2()
8663 .rounded_r(RADIUS - BORDER_WIDTH)
8664 .border_l_1()
8665 .border_color(cx.theme().colors().border)
8666 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8667 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8668 el.child(
8669 Label::new("Hold")
8670 .size(LabelSize::Small)
8671 .when(accept_keystroke.is_none(), |el| {
8672 el.strikethrough()
8673 })
8674 .line_height_style(LineHeightStyle::UiLabel),
8675 )
8676 })
8677 .id("edit_prediction_cursor_popover_keybind")
8678 .when(accept_keystroke.is_none(), |el| {
8679 let status_colors = cx.theme().status();
8680
8681 el.bg(status_colors.error_background)
8682 .border_color(status_colors.error.opacity(0.6))
8683 .child(Icon::new(IconName::Info).color(Color::Error))
8684 .cursor_default()
8685 .hoverable_tooltip(move |_window, cx| {
8686 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8687 .into()
8688 })
8689 })
8690 .when_some(
8691 accept_keystroke.as_ref(),
8692 |el, accept_keystroke| {
8693 el.child(h_flex().children(ui::render_modifiers(
8694 &accept_keystroke.modifiers,
8695 PlatformStyle::platform(),
8696 Some(Color::Default),
8697 Some(IconSize::XSmall.rems().into()),
8698 false,
8699 )))
8700 },
8701 ),
8702 )
8703 .into_any(),
8704 );
8705 }
8706
8707 self.render_edit_prediction_cursor_popover_preview(
8708 prediction,
8709 cursor_point,
8710 style,
8711 cx,
8712 )?
8713 }
8714
8715 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8716 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8717 stale_completion,
8718 cursor_point,
8719 style,
8720 cx,
8721 )?,
8722
8723 None => {
8724 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8725 }
8726 },
8727
8728 None => pending_completion_container().child(Label::new("No Prediction")),
8729 };
8730
8731 let completion = if is_refreshing {
8732 completion
8733 .with_animation(
8734 "loading-completion",
8735 Animation::new(Duration::from_secs(2))
8736 .repeat()
8737 .with_easing(pulsating_between(0.4, 0.8)),
8738 |label, delta| label.opacity(delta),
8739 )
8740 .into_any_element()
8741 } else {
8742 completion.into_any_element()
8743 };
8744
8745 let has_completion = self.active_inline_completion.is_some();
8746
8747 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8748 Some(
8749 h_flex()
8750 .min_w(min_width)
8751 .max_w(max_width)
8752 .flex_1()
8753 .elevation_2(cx)
8754 .border_color(cx.theme().colors().border)
8755 .child(
8756 div()
8757 .flex_1()
8758 .py_1()
8759 .px_2()
8760 .overflow_hidden()
8761 .child(completion),
8762 )
8763 .when_some(accept_keystroke, |el, accept_keystroke| {
8764 if !accept_keystroke.modifiers.modified() {
8765 return el;
8766 }
8767
8768 el.child(
8769 h_flex()
8770 .h_full()
8771 .border_l_1()
8772 .rounded_r_lg()
8773 .border_color(cx.theme().colors().border)
8774 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8775 .gap_1()
8776 .py_1()
8777 .px_2()
8778 .child(
8779 h_flex()
8780 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8781 .when(is_platform_style_mac, |parent| parent.gap_1())
8782 .child(h_flex().children(ui::render_modifiers(
8783 &accept_keystroke.modifiers,
8784 PlatformStyle::platform(),
8785 Some(if !has_completion {
8786 Color::Muted
8787 } else {
8788 Color::Default
8789 }),
8790 None,
8791 false,
8792 ))),
8793 )
8794 .child(Label::new("Preview").into_any_element())
8795 .opacity(if has_completion { 1.0 } else { 0.4 }),
8796 )
8797 })
8798 .into_any(),
8799 )
8800 }
8801
8802 fn render_edit_prediction_cursor_popover_preview(
8803 &self,
8804 completion: &InlineCompletionState,
8805 cursor_point: Point,
8806 style: &EditorStyle,
8807 cx: &mut Context<Editor>,
8808 ) -> Option<Div> {
8809 use text::ToPoint as _;
8810
8811 fn render_relative_row_jump(
8812 prefix: impl Into<String>,
8813 current_row: u32,
8814 target_row: u32,
8815 ) -> Div {
8816 let (row_diff, arrow) = if target_row < current_row {
8817 (current_row - target_row, IconName::ArrowUp)
8818 } else {
8819 (target_row - current_row, IconName::ArrowDown)
8820 };
8821
8822 h_flex()
8823 .child(
8824 Label::new(format!("{}{}", prefix.into(), row_diff))
8825 .color(Color::Muted)
8826 .size(LabelSize::Small),
8827 )
8828 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8829 }
8830
8831 match &completion.completion {
8832 InlineCompletion::Move {
8833 target, snapshot, ..
8834 } => Some(
8835 h_flex()
8836 .px_2()
8837 .gap_2()
8838 .flex_1()
8839 .child(
8840 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8841 Icon::new(IconName::ZedPredictDown)
8842 } else {
8843 Icon::new(IconName::ZedPredictUp)
8844 },
8845 )
8846 .child(Label::new("Jump to Edit")),
8847 ),
8848
8849 InlineCompletion::Edit {
8850 edits,
8851 edit_preview,
8852 snapshot,
8853 display_mode: _,
8854 } => {
8855 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8856
8857 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8858 &snapshot,
8859 &edits,
8860 edit_preview.as_ref()?,
8861 true,
8862 cx,
8863 )
8864 .first_line_preview();
8865
8866 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8867 .with_default_highlights(&style.text, highlighted_edits.highlights);
8868
8869 let preview = h_flex()
8870 .gap_1()
8871 .min_w_16()
8872 .child(styled_text)
8873 .when(has_more_lines, |parent| parent.child("…"));
8874
8875 let left = if first_edit_row != cursor_point.row {
8876 render_relative_row_jump("", cursor_point.row, first_edit_row)
8877 .into_any_element()
8878 } else {
8879 Icon::new(IconName::ZedPredict).into_any_element()
8880 };
8881
8882 Some(
8883 h_flex()
8884 .h_full()
8885 .flex_1()
8886 .gap_2()
8887 .pr_1()
8888 .overflow_x_hidden()
8889 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8890 .child(left)
8891 .child(preview),
8892 )
8893 }
8894 }
8895 }
8896
8897 pub fn render_context_menu(
8898 &self,
8899 style: &EditorStyle,
8900 max_height_in_lines: u32,
8901 window: &mut Window,
8902 cx: &mut Context<Editor>,
8903 ) -> Option<AnyElement> {
8904 let menu = self.context_menu.borrow();
8905 let menu = menu.as_ref()?;
8906 if !menu.visible() {
8907 return None;
8908 };
8909 Some(menu.render(style, max_height_in_lines, window, cx))
8910 }
8911
8912 fn render_context_menu_aside(
8913 &mut self,
8914 max_size: Size<Pixels>,
8915 window: &mut Window,
8916 cx: &mut Context<Editor>,
8917 ) -> Option<AnyElement> {
8918 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8919 if menu.visible() {
8920 menu.render_aside(max_size, window, cx)
8921 } else {
8922 None
8923 }
8924 })
8925 }
8926
8927 fn hide_context_menu(
8928 &mut self,
8929 window: &mut Window,
8930 cx: &mut Context<Self>,
8931 ) -> Option<CodeContextMenu> {
8932 cx.notify();
8933 self.completion_tasks.clear();
8934 let context_menu = self.context_menu.borrow_mut().take();
8935 self.stale_inline_completion_in_menu.take();
8936 self.update_visible_inline_completion(window, cx);
8937 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
8938 if let Some(completion_provider) = &self.completion_provider {
8939 completion_provider.selection_changed(None, window, cx);
8940 }
8941 }
8942 context_menu
8943 }
8944
8945 fn show_snippet_choices(
8946 &mut self,
8947 choices: &Vec<String>,
8948 selection: Range<Anchor>,
8949 cx: &mut Context<Self>,
8950 ) {
8951 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
8952 (Some(a), Some(b)) if a == b => a,
8953 _ => {
8954 log::error!("expected anchor range to have matching buffer IDs");
8955 return;
8956 }
8957 };
8958 let multi_buffer = self.buffer().read(cx);
8959 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
8960 return;
8961 };
8962
8963 let id = post_inc(&mut self.next_completion_id);
8964 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8965 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8966 CompletionsMenu::new_snippet_choices(
8967 id,
8968 true,
8969 choices,
8970 selection,
8971 buffer,
8972 snippet_sort_order,
8973 ),
8974 ));
8975 }
8976
8977 pub fn insert_snippet(
8978 &mut self,
8979 insertion_ranges: &[Range<usize>],
8980 snippet: Snippet,
8981 window: &mut Window,
8982 cx: &mut Context<Self>,
8983 ) -> Result<()> {
8984 struct Tabstop<T> {
8985 is_end_tabstop: bool,
8986 ranges: Vec<Range<T>>,
8987 choices: Option<Vec<String>>,
8988 }
8989
8990 let tabstops = self.buffer.update(cx, |buffer, cx| {
8991 let snippet_text: Arc<str> = snippet.text.clone().into();
8992 let edits = insertion_ranges
8993 .iter()
8994 .cloned()
8995 .map(|range| (range, snippet_text.clone()));
8996 let autoindent_mode = AutoindentMode::Block {
8997 original_indent_columns: Vec::new(),
8998 };
8999 buffer.edit(edits, Some(autoindent_mode), cx);
9000
9001 let snapshot = &*buffer.read(cx);
9002 let snippet = &snippet;
9003 snippet
9004 .tabstops
9005 .iter()
9006 .map(|tabstop| {
9007 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9008 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9009 });
9010 let mut tabstop_ranges = tabstop
9011 .ranges
9012 .iter()
9013 .flat_map(|tabstop_range| {
9014 let mut delta = 0_isize;
9015 insertion_ranges.iter().map(move |insertion_range| {
9016 let insertion_start = insertion_range.start as isize + delta;
9017 delta +=
9018 snippet.text.len() as isize - insertion_range.len() as isize;
9019
9020 let start = ((insertion_start + tabstop_range.start) as usize)
9021 .min(snapshot.len());
9022 let end = ((insertion_start + tabstop_range.end) as usize)
9023 .min(snapshot.len());
9024 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9025 })
9026 })
9027 .collect::<Vec<_>>();
9028 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9029
9030 Tabstop {
9031 is_end_tabstop,
9032 ranges: tabstop_ranges,
9033 choices: tabstop.choices.clone(),
9034 }
9035 })
9036 .collect::<Vec<_>>()
9037 });
9038 if let Some(tabstop) = tabstops.first() {
9039 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9040 // Reverse order so that the first range is the newest created selection.
9041 // Completions will use it and autoscroll will prioritize it.
9042 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9043 });
9044
9045 if let Some(choices) = &tabstop.choices {
9046 if let Some(selection) = tabstop.ranges.first() {
9047 self.show_snippet_choices(choices, selection.clone(), cx)
9048 }
9049 }
9050
9051 // If we're already at the last tabstop and it's at the end of the snippet,
9052 // we're done, we don't need to keep the state around.
9053 if !tabstop.is_end_tabstop {
9054 let choices = tabstops
9055 .iter()
9056 .map(|tabstop| tabstop.choices.clone())
9057 .collect();
9058
9059 let ranges = tabstops
9060 .into_iter()
9061 .map(|tabstop| tabstop.ranges)
9062 .collect::<Vec<_>>();
9063
9064 self.snippet_stack.push(SnippetState {
9065 active_index: 0,
9066 ranges,
9067 choices,
9068 });
9069 }
9070
9071 // Check whether the just-entered snippet ends with an auto-closable bracket.
9072 if self.autoclose_regions.is_empty() {
9073 let snapshot = self.buffer.read(cx).snapshot(cx);
9074 for selection in &mut self.selections.all::<Point>(cx) {
9075 let selection_head = selection.head();
9076 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9077 continue;
9078 };
9079
9080 let mut bracket_pair = None;
9081 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9082 let prev_chars = snapshot
9083 .reversed_chars_at(selection_head)
9084 .collect::<String>();
9085 for (pair, enabled) in scope.brackets() {
9086 if enabled
9087 && pair.close
9088 && prev_chars.starts_with(pair.start.as_str())
9089 && next_chars.starts_with(pair.end.as_str())
9090 {
9091 bracket_pair = Some(pair.clone());
9092 break;
9093 }
9094 }
9095 if let Some(pair) = bracket_pair {
9096 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9097 let autoclose_enabled =
9098 self.use_autoclose && snapshot_settings.use_autoclose;
9099 if autoclose_enabled {
9100 let start = snapshot.anchor_after(selection_head);
9101 let end = snapshot.anchor_after(selection_head);
9102 self.autoclose_regions.push(AutocloseRegion {
9103 selection_id: selection.id,
9104 range: start..end,
9105 pair,
9106 });
9107 }
9108 }
9109 }
9110 }
9111 }
9112 Ok(())
9113 }
9114
9115 pub fn move_to_next_snippet_tabstop(
9116 &mut self,
9117 window: &mut Window,
9118 cx: &mut Context<Self>,
9119 ) -> bool {
9120 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9121 }
9122
9123 pub fn move_to_prev_snippet_tabstop(
9124 &mut self,
9125 window: &mut Window,
9126 cx: &mut Context<Self>,
9127 ) -> bool {
9128 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9129 }
9130
9131 pub fn move_to_snippet_tabstop(
9132 &mut self,
9133 bias: Bias,
9134 window: &mut Window,
9135 cx: &mut Context<Self>,
9136 ) -> bool {
9137 if let Some(mut snippet) = self.snippet_stack.pop() {
9138 match bias {
9139 Bias::Left => {
9140 if snippet.active_index > 0 {
9141 snippet.active_index -= 1;
9142 } else {
9143 self.snippet_stack.push(snippet);
9144 return false;
9145 }
9146 }
9147 Bias::Right => {
9148 if snippet.active_index + 1 < snippet.ranges.len() {
9149 snippet.active_index += 1;
9150 } else {
9151 self.snippet_stack.push(snippet);
9152 return false;
9153 }
9154 }
9155 }
9156 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9157 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9158 // Reverse order so that the first range is the newest created selection.
9159 // Completions will use it and autoscroll will prioritize it.
9160 s.select_ranges(current_ranges.iter().rev().cloned())
9161 });
9162
9163 if let Some(choices) = &snippet.choices[snippet.active_index] {
9164 if let Some(selection) = current_ranges.first() {
9165 self.show_snippet_choices(&choices, selection.clone(), cx);
9166 }
9167 }
9168
9169 // If snippet state is not at the last tabstop, push it back on the stack
9170 if snippet.active_index + 1 < snippet.ranges.len() {
9171 self.snippet_stack.push(snippet);
9172 }
9173 return true;
9174 }
9175 }
9176
9177 false
9178 }
9179
9180 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9181 self.transact(window, cx, |this, window, cx| {
9182 this.select_all(&SelectAll, window, cx);
9183 this.insert("", window, cx);
9184 });
9185 }
9186
9187 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9188 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9189 self.transact(window, cx, |this, window, cx| {
9190 this.select_autoclose_pair(window, cx);
9191 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9192 if !this.linked_edit_ranges.is_empty() {
9193 let selections = this.selections.all::<MultiBufferPoint>(cx);
9194 let snapshot = this.buffer.read(cx).snapshot(cx);
9195
9196 for selection in selections.iter() {
9197 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9198 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9199 if selection_start.buffer_id != selection_end.buffer_id {
9200 continue;
9201 }
9202 if let Some(ranges) =
9203 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9204 {
9205 for (buffer, entries) in ranges {
9206 linked_ranges.entry(buffer).or_default().extend(entries);
9207 }
9208 }
9209 }
9210 }
9211
9212 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9213 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9214 for selection in &mut selections {
9215 if selection.is_empty() {
9216 let old_head = selection.head();
9217 let mut new_head =
9218 movement::left(&display_map, old_head.to_display_point(&display_map))
9219 .to_point(&display_map);
9220 if let Some((buffer, line_buffer_range)) = display_map
9221 .buffer_snapshot
9222 .buffer_line_for_row(MultiBufferRow(old_head.row))
9223 {
9224 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9225 let indent_len = match indent_size.kind {
9226 IndentKind::Space => {
9227 buffer.settings_at(line_buffer_range.start, cx).tab_size
9228 }
9229 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9230 };
9231 if old_head.column <= indent_size.len && old_head.column > 0 {
9232 let indent_len = indent_len.get();
9233 new_head = cmp::min(
9234 new_head,
9235 MultiBufferPoint::new(
9236 old_head.row,
9237 ((old_head.column - 1) / indent_len) * indent_len,
9238 ),
9239 );
9240 }
9241 }
9242
9243 selection.set_head(new_head, SelectionGoal::None);
9244 }
9245 }
9246
9247 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9248 s.select(selections)
9249 });
9250 this.insert("", window, cx);
9251 let empty_str: Arc<str> = Arc::from("");
9252 for (buffer, edits) in linked_ranges {
9253 let snapshot = buffer.read(cx).snapshot();
9254 use text::ToPoint as TP;
9255
9256 let edits = edits
9257 .into_iter()
9258 .map(|range| {
9259 let end_point = TP::to_point(&range.end, &snapshot);
9260 let mut start_point = TP::to_point(&range.start, &snapshot);
9261
9262 if end_point == start_point {
9263 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9264 .saturating_sub(1);
9265 start_point =
9266 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9267 };
9268
9269 (start_point..end_point, empty_str.clone())
9270 })
9271 .sorted_by_key(|(range, _)| range.start)
9272 .collect::<Vec<_>>();
9273 buffer.update(cx, |this, cx| {
9274 this.edit(edits, None, cx);
9275 })
9276 }
9277 this.refresh_inline_completion(true, false, window, cx);
9278 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9279 });
9280 }
9281
9282 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9283 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9284 self.transact(window, cx, |this, window, cx| {
9285 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9286 s.move_with(|map, selection| {
9287 if selection.is_empty() {
9288 let cursor = movement::right(map, selection.head());
9289 selection.end = cursor;
9290 selection.reversed = true;
9291 selection.goal = SelectionGoal::None;
9292 }
9293 })
9294 });
9295 this.insert("", window, cx);
9296 this.refresh_inline_completion(true, false, window, cx);
9297 });
9298 }
9299
9300 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9301 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9302 if self.move_to_prev_snippet_tabstop(window, cx) {
9303 return;
9304 }
9305 self.outdent(&Outdent, window, cx);
9306 }
9307
9308 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9309 if self.move_to_next_snippet_tabstop(window, cx) {
9310 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9311 return;
9312 }
9313 if self.read_only(cx) {
9314 return;
9315 }
9316 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9317 let mut selections = self.selections.all_adjusted(cx);
9318 let buffer = self.buffer.read(cx);
9319 let snapshot = buffer.snapshot(cx);
9320 let rows_iter = selections.iter().map(|s| s.head().row);
9321 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9322
9323 let has_some_cursor_in_whitespace = selections
9324 .iter()
9325 .filter(|selection| selection.is_empty())
9326 .any(|selection| {
9327 let cursor = selection.head();
9328 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9329 cursor.column < current_indent.len
9330 });
9331
9332 let mut edits = Vec::new();
9333 let mut prev_edited_row = 0;
9334 let mut row_delta = 0;
9335 for selection in &mut selections {
9336 if selection.start.row != prev_edited_row {
9337 row_delta = 0;
9338 }
9339 prev_edited_row = selection.end.row;
9340
9341 // If the selection is non-empty, then increase the indentation of the selected lines.
9342 if !selection.is_empty() {
9343 row_delta =
9344 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9345 continue;
9346 }
9347
9348 let cursor = selection.head();
9349 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9350 if let Some(suggested_indent) =
9351 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9352 {
9353 // Don't do anything if already at suggested indent
9354 // and there is any other cursor which is not
9355 if has_some_cursor_in_whitespace
9356 && cursor.column == current_indent.len
9357 && current_indent.len == suggested_indent.len
9358 {
9359 continue;
9360 }
9361
9362 // Adjust line and move cursor to suggested indent
9363 // if cursor is not at suggested indent
9364 if cursor.column < suggested_indent.len
9365 && cursor.column <= current_indent.len
9366 && current_indent.len <= suggested_indent.len
9367 {
9368 selection.start = Point::new(cursor.row, suggested_indent.len);
9369 selection.end = selection.start;
9370 if row_delta == 0 {
9371 edits.extend(Buffer::edit_for_indent_size_adjustment(
9372 cursor.row,
9373 current_indent,
9374 suggested_indent,
9375 ));
9376 row_delta = suggested_indent.len - current_indent.len;
9377 }
9378 continue;
9379 }
9380
9381 // If current indent is more than suggested indent
9382 // only move cursor to current indent and skip indent
9383 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9384 selection.start = Point::new(cursor.row, current_indent.len);
9385 selection.end = selection.start;
9386 continue;
9387 }
9388 }
9389
9390 // Otherwise, insert a hard or soft tab.
9391 let settings = buffer.language_settings_at(cursor, cx);
9392 let tab_size = if settings.hard_tabs {
9393 IndentSize::tab()
9394 } else {
9395 let tab_size = settings.tab_size.get();
9396 let indent_remainder = snapshot
9397 .text_for_range(Point::new(cursor.row, 0)..cursor)
9398 .flat_map(str::chars)
9399 .fold(row_delta % tab_size, |counter: u32, c| {
9400 if c == '\t' {
9401 0
9402 } else {
9403 (counter + 1) % tab_size
9404 }
9405 });
9406
9407 let chars_to_next_tab_stop = tab_size - indent_remainder;
9408 IndentSize::spaces(chars_to_next_tab_stop)
9409 };
9410 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9411 selection.end = selection.start;
9412 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9413 row_delta += tab_size.len;
9414 }
9415
9416 self.transact(window, cx, |this, window, cx| {
9417 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9418 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9419 s.select(selections)
9420 });
9421 this.refresh_inline_completion(true, false, window, cx);
9422 });
9423 }
9424
9425 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9426 if self.read_only(cx) {
9427 return;
9428 }
9429 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9430 let mut selections = self.selections.all::<Point>(cx);
9431 let mut prev_edited_row = 0;
9432 let mut row_delta = 0;
9433 let mut edits = Vec::new();
9434 let buffer = self.buffer.read(cx);
9435 let snapshot = buffer.snapshot(cx);
9436 for selection in &mut selections {
9437 if selection.start.row != prev_edited_row {
9438 row_delta = 0;
9439 }
9440 prev_edited_row = selection.end.row;
9441
9442 row_delta =
9443 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9444 }
9445
9446 self.transact(window, cx, |this, window, cx| {
9447 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9448 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9449 s.select(selections)
9450 });
9451 });
9452 }
9453
9454 fn indent_selection(
9455 buffer: &MultiBuffer,
9456 snapshot: &MultiBufferSnapshot,
9457 selection: &mut Selection<Point>,
9458 edits: &mut Vec<(Range<Point>, String)>,
9459 delta_for_start_row: u32,
9460 cx: &App,
9461 ) -> u32 {
9462 let settings = buffer.language_settings_at(selection.start, cx);
9463 let tab_size = settings.tab_size.get();
9464 let indent_kind = if settings.hard_tabs {
9465 IndentKind::Tab
9466 } else {
9467 IndentKind::Space
9468 };
9469 let mut start_row = selection.start.row;
9470 let mut end_row = selection.end.row + 1;
9471
9472 // If a selection ends at the beginning of a line, don't indent
9473 // that last line.
9474 if selection.end.column == 0 && selection.end.row > selection.start.row {
9475 end_row -= 1;
9476 }
9477
9478 // Avoid re-indenting a row that has already been indented by a
9479 // previous selection, but still update this selection's column
9480 // to reflect that indentation.
9481 if delta_for_start_row > 0 {
9482 start_row += 1;
9483 selection.start.column += delta_for_start_row;
9484 if selection.end.row == selection.start.row {
9485 selection.end.column += delta_for_start_row;
9486 }
9487 }
9488
9489 let mut delta_for_end_row = 0;
9490 let has_multiple_rows = start_row + 1 != end_row;
9491 for row in start_row..end_row {
9492 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9493 let indent_delta = match (current_indent.kind, indent_kind) {
9494 (IndentKind::Space, IndentKind::Space) => {
9495 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9496 IndentSize::spaces(columns_to_next_tab_stop)
9497 }
9498 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9499 (_, IndentKind::Tab) => IndentSize::tab(),
9500 };
9501
9502 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9503 0
9504 } else {
9505 selection.start.column
9506 };
9507 let row_start = Point::new(row, start);
9508 edits.push((
9509 row_start..row_start,
9510 indent_delta.chars().collect::<String>(),
9511 ));
9512
9513 // Update this selection's endpoints to reflect the indentation.
9514 if row == selection.start.row {
9515 selection.start.column += indent_delta.len;
9516 }
9517 if row == selection.end.row {
9518 selection.end.column += indent_delta.len;
9519 delta_for_end_row = indent_delta.len;
9520 }
9521 }
9522
9523 if selection.start.row == selection.end.row {
9524 delta_for_start_row + delta_for_end_row
9525 } else {
9526 delta_for_end_row
9527 }
9528 }
9529
9530 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9531 if self.read_only(cx) {
9532 return;
9533 }
9534 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9535 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9536 let selections = self.selections.all::<Point>(cx);
9537 let mut deletion_ranges = Vec::new();
9538 let mut last_outdent = None;
9539 {
9540 let buffer = self.buffer.read(cx);
9541 let snapshot = buffer.snapshot(cx);
9542 for selection in &selections {
9543 let settings = buffer.language_settings_at(selection.start, cx);
9544 let tab_size = settings.tab_size.get();
9545 let mut rows = selection.spanned_rows(false, &display_map);
9546
9547 // Avoid re-outdenting a row that has already been outdented by a
9548 // previous selection.
9549 if let Some(last_row) = last_outdent {
9550 if last_row == rows.start {
9551 rows.start = rows.start.next_row();
9552 }
9553 }
9554 let has_multiple_rows = rows.len() > 1;
9555 for row in rows.iter_rows() {
9556 let indent_size = snapshot.indent_size_for_line(row);
9557 if indent_size.len > 0 {
9558 let deletion_len = match indent_size.kind {
9559 IndentKind::Space => {
9560 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9561 if columns_to_prev_tab_stop == 0 {
9562 tab_size
9563 } else {
9564 columns_to_prev_tab_stop
9565 }
9566 }
9567 IndentKind::Tab => 1,
9568 };
9569 let start = if has_multiple_rows
9570 || deletion_len > selection.start.column
9571 || indent_size.len < selection.start.column
9572 {
9573 0
9574 } else {
9575 selection.start.column - deletion_len
9576 };
9577 deletion_ranges.push(
9578 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9579 );
9580 last_outdent = Some(row);
9581 }
9582 }
9583 }
9584 }
9585
9586 self.transact(window, cx, |this, window, cx| {
9587 this.buffer.update(cx, |buffer, cx| {
9588 let empty_str: Arc<str> = Arc::default();
9589 buffer.edit(
9590 deletion_ranges
9591 .into_iter()
9592 .map(|range| (range, empty_str.clone())),
9593 None,
9594 cx,
9595 );
9596 });
9597 let selections = this.selections.all::<usize>(cx);
9598 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9599 s.select(selections)
9600 });
9601 });
9602 }
9603
9604 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9605 if self.read_only(cx) {
9606 return;
9607 }
9608 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9609 let selections = self
9610 .selections
9611 .all::<usize>(cx)
9612 .into_iter()
9613 .map(|s| s.range());
9614
9615 self.transact(window, cx, |this, window, cx| {
9616 this.buffer.update(cx, |buffer, cx| {
9617 buffer.autoindent_ranges(selections, cx);
9618 });
9619 let selections = this.selections.all::<usize>(cx);
9620 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9621 s.select(selections)
9622 });
9623 });
9624 }
9625
9626 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9627 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9628 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9629 let selections = self.selections.all::<Point>(cx);
9630
9631 let mut new_cursors = Vec::new();
9632 let mut edit_ranges = Vec::new();
9633 let mut selections = selections.iter().peekable();
9634 while let Some(selection) = selections.next() {
9635 let mut rows = selection.spanned_rows(false, &display_map);
9636 let goal_display_column = selection.head().to_display_point(&display_map).column();
9637
9638 // Accumulate contiguous regions of rows that we want to delete.
9639 while let Some(next_selection) = selections.peek() {
9640 let next_rows = next_selection.spanned_rows(false, &display_map);
9641 if next_rows.start <= rows.end {
9642 rows.end = next_rows.end;
9643 selections.next().unwrap();
9644 } else {
9645 break;
9646 }
9647 }
9648
9649 let buffer = &display_map.buffer_snapshot;
9650 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9651 let edit_end;
9652 let cursor_buffer_row;
9653 if buffer.max_point().row >= rows.end.0 {
9654 // If there's a line after the range, delete the \n from the end of the row range
9655 // and position the cursor on the next line.
9656 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9657 cursor_buffer_row = rows.end;
9658 } else {
9659 // If there isn't a line after the range, delete the \n from the line before the
9660 // start of the row range and position the cursor there.
9661 edit_start = edit_start.saturating_sub(1);
9662 edit_end = buffer.len();
9663 cursor_buffer_row = rows.start.previous_row();
9664 }
9665
9666 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9667 *cursor.column_mut() =
9668 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9669
9670 new_cursors.push((
9671 selection.id,
9672 buffer.anchor_after(cursor.to_point(&display_map)),
9673 ));
9674 edit_ranges.push(edit_start..edit_end);
9675 }
9676
9677 self.transact(window, cx, |this, window, cx| {
9678 let buffer = this.buffer.update(cx, |buffer, cx| {
9679 let empty_str: Arc<str> = Arc::default();
9680 buffer.edit(
9681 edit_ranges
9682 .into_iter()
9683 .map(|range| (range, empty_str.clone())),
9684 None,
9685 cx,
9686 );
9687 buffer.snapshot(cx)
9688 });
9689 let new_selections = new_cursors
9690 .into_iter()
9691 .map(|(id, cursor)| {
9692 let cursor = cursor.to_point(&buffer);
9693 Selection {
9694 id,
9695 start: cursor,
9696 end: cursor,
9697 reversed: false,
9698 goal: SelectionGoal::None,
9699 }
9700 })
9701 .collect();
9702
9703 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9704 s.select(new_selections);
9705 });
9706 });
9707 }
9708
9709 pub fn join_lines_impl(
9710 &mut self,
9711 insert_whitespace: bool,
9712 window: &mut Window,
9713 cx: &mut Context<Self>,
9714 ) {
9715 if self.read_only(cx) {
9716 return;
9717 }
9718 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9719 for selection in self.selections.all::<Point>(cx) {
9720 let start = MultiBufferRow(selection.start.row);
9721 // Treat single line selections as if they include the next line. Otherwise this action
9722 // would do nothing for single line selections individual cursors.
9723 let end = if selection.start.row == selection.end.row {
9724 MultiBufferRow(selection.start.row + 1)
9725 } else {
9726 MultiBufferRow(selection.end.row)
9727 };
9728
9729 if let Some(last_row_range) = row_ranges.last_mut() {
9730 if start <= last_row_range.end {
9731 last_row_range.end = end;
9732 continue;
9733 }
9734 }
9735 row_ranges.push(start..end);
9736 }
9737
9738 let snapshot = self.buffer.read(cx).snapshot(cx);
9739 let mut cursor_positions = Vec::new();
9740 for row_range in &row_ranges {
9741 let anchor = snapshot.anchor_before(Point::new(
9742 row_range.end.previous_row().0,
9743 snapshot.line_len(row_range.end.previous_row()),
9744 ));
9745 cursor_positions.push(anchor..anchor);
9746 }
9747
9748 self.transact(window, cx, |this, window, cx| {
9749 for row_range in row_ranges.into_iter().rev() {
9750 for row in row_range.iter_rows().rev() {
9751 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9752 let next_line_row = row.next_row();
9753 let indent = snapshot.indent_size_for_line(next_line_row);
9754 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9755
9756 let replace =
9757 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9758 " "
9759 } else {
9760 ""
9761 };
9762
9763 this.buffer.update(cx, |buffer, cx| {
9764 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9765 });
9766 }
9767 }
9768
9769 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9770 s.select_anchor_ranges(cursor_positions)
9771 });
9772 });
9773 }
9774
9775 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9776 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9777 self.join_lines_impl(true, window, cx);
9778 }
9779
9780 pub fn sort_lines_case_sensitive(
9781 &mut self,
9782 _: &SortLinesCaseSensitive,
9783 window: &mut Window,
9784 cx: &mut Context<Self>,
9785 ) {
9786 self.manipulate_lines(window, cx, |lines| lines.sort())
9787 }
9788
9789 pub fn sort_lines_case_insensitive(
9790 &mut self,
9791 _: &SortLinesCaseInsensitive,
9792 window: &mut Window,
9793 cx: &mut Context<Self>,
9794 ) {
9795 self.manipulate_lines(window, cx, |lines| {
9796 lines.sort_by_key(|line| line.to_lowercase())
9797 })
9798 }
9799
9800 pub fn unique_lines_case_insensitive(
9801 &mut self,
9802 _: &UniqueLinesCaseInsensitive,
9803 window: &mut Window,
9804 cx: &mut Context<Self>,
9805 ) {
9806 self.manipulate_lines(window, cx, |lines| {
9807 let mut seen = HashSet::default();
9808 lines.retain(|line| seen.insert(line.to_lowercase()));
9809 })
9810 }
9811
9812 pub fn unique_lines_case_sensitive(
9813 &mut self,
9814 _: &UniqueLinesCaseSensitive,
9815 window: &mut Window,
9816 cx: &mut Context<Self>,
9817 ) {
9818 self.manipulate_lines(window, cx, |lines| {
9819 let mut seen = HashSet::default();
9820 lines.retain(|line| seen.insert(*line));
9821 })
9822 }
9823
9824 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9825 let Some(project) = self.project.clone() else {
9826 return;
9827 };
9828 self.reload(project, window, cx)
9829 .detach_and_notify_err(window, cx);
9830 }
9831
9832 pub fn restore_file(
9833 &mut self,
9834 _: &::git::RestoreFile,
9835 window: &mut Window,
9836 cx: &mut Context<Self>,
9837 ) {
9838 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9839 let mut buffer_ids = HashSet::default();
9840 let snapshot = self.buffer().read(cx).snapshot(cx);
9841 for selection in self.selections.all::<usize>(cx) {
9842 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9843 }
9844
9845 let buffer = self.buffer().read(cx);
9846 let ranges = buffer_ids
9847 .into_iter()
9848 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9849 .collect::<Vec<_>>();
9850
9851 self.restore_hunks_in_ranges(ranges, window, cx);
9852 }
9853
9854 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9855 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9856 let selections = self
9857 .selections
9858 .all(cx)
9859 .into_iter()
9860 .map(|s| s.range())
9861 .collect();
9862 self.restore_hunks_in_ranges(selections, window, cx);
9863 }
9864
9865 pub fn restore_hunks_in_ranges(
9866 &mut self,
9867 ranges: Vec<Range<Point>>,
9868 window: &mut Window,
9869 cx: &mut Context<Editor>,
9870 ) {
9871 let mut revert_changes = HashMap::default();
9872 let chunk_by = self
9873 .snapshot(window, cx)
9874 .hunks_for_ranges(ranges)
9875 .into_iter()
9876 .chunk_by(|hunk| hunk.buffer_id);
9877 for (buffer_id, hunks) in &chunk_by {
9878 let hunks = hunks.collect::<Vec<_>>();
9879 for hunk in &hunks {
9880 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9881 }
9882 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9883 }
9884 drop(chunk_by);
9885 if !revert_changes.is_empty() {
9886 self.transact(window, cx, |editor, window, cx| {
9887 editor.restore(revert_changes, window, cx);
9888 });
9889 }
9890 }
9891
9892 pub fn open_active_item_in_terminal(
9893 &mut self,
9894 _: &OpenInTerminal,
9895 window: &mut Window,
9896 cx: &mut Context<Self>,
9897 ) {
9898 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9899 let project_path = buffer.read(cx).project_path(cx)?;
9900 let project = self.project.as_ref()?.read(cx);
9901 let entry = project.entry_for_path(&project_path, cx)?;
9902 let parent = match &entry.canonical_path {
9903 Some(canonical_path) => canonical_path.to_path_buf(),
9904 None => project.absolute_path(&project_path, cx)?,
9905 }
9906 .parent()?
9907 .to_path_buf();
9908 Some(parent)
9909 }) {
9910 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9911 }
9912 }
9913
9914 fn set_breakpoint_context_menu(
9915 &mut self,
9916 display_row: DisplayRow,
9917 position: Option<Anchor>,
9918 clicked_point: gpui::Point<Pixels>,
9919 window: &mut Window,
9920 cx: &mut Context<Self>,
9921 ) {
9922 if !cx.has_flag::<DebuggerFeatureFlag>() {
9923 return;
9924 }
9925 let source = self
9926 .buffer
9927 .read(cx)
9928 .snapshot(cx)
9929 .anchor_before(Point::new(display_row.0, 0u32));
9930
9931 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9932
9933 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9934 self,
9935 source,
9936 clicked_point,
9937 context_menu,
9938 window,
9939 cx,
9940 );
9941 }
9942
9943 fn add_edit_breakpoint_block(
9944 &mut self,
9945 anchor: Anchor,
9946 breakpoint: &Breakpoint,
9947 edit_action: BreakpointPromptEditAction,
9948 window: &mut Window,
9949 cx: &mut Context<Self>,
9950 ) {
9951 let weak_editor = cx.weak_entity();
9952 let bp_prompt = cx.new(|cx| {
9953 BreakpointPromptEditor::new(
9954 weak_editor,
9955 anchor,
9956 breakpoint.clone(),
9957 edit_action,
9958 window,
9959 cx,
9960 )
9961 });
9962
9963 let height = bp_prompt.update(cx, |this, cx| {
9964 this.prompt
9965 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9966 });
9967 let cloned_prompt = bp_prompt.clone();
9968 let blocks = vec![BlockProperties {
9969 style: BlockStyle::Sticky,
9970 placement: BlockPlacement::Above(anchor),
9971 height: Some(height),
9972 render: Arc::new(move |cx| {
9973 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9974 cloned_prompt.clone().into_any_element()
9975 }),
9976 priority: 0,
9977 render_in_minimap: true,
9978 }];
9979
9980 let focus_handle = bp_prompt.focus_handle(cx);
9981 window.focus(&focus_handle);
9982
9983 let block_ids = self.insert_blocks(blocks, None, cx);
9984 bp_prompt.update(cx, |prompt, _| {
9985 prompt.add_block_ids(block_ids);
9986 });
9987 }
9988
9989 pub(crate) fn breakpoint_at_row(
9990 &self,
9991 row: u32,
9992 window: &mut Window,
9993 cx: &mut Context<Self>,
9994 ) -> Option<(Anchor, Breakpoint)> {
9995 let snapshot = self.snapshot(window, cx);
9996 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9997
9998 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9999 }
10000
10001 pub(crate) fn breakpoint_at_anchor(
10002 &self,
10003 breakpoint_position: Anchor,
10004 snapshot: &EditorSnapshot,
10005 cx: &mut Context<Self>,
10006 ) -> Option<(Anchor, Breakpoint)> {
10007 let project = self.project.clone()?;
10008
10009 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10010 snapshot
10011 .buffer_snapshot
10012 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10013 })?;
10014
10015 let enclosing_excerpt = breakpoint_position.excerpt_id;
10016 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10017 let buffer_snapshot = buffer.read(cx).snapshot();
10018
10019 let row = buffer_snapshot
10020 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10021 .row;
10022
10023 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10024 let anchor_end = snapshot
10025 .buffer_snapshot
10026 .anchor_after(Point::new(row, line_len));
10027
10028 let bp = self
10029 .breakpoint_store
10030 .as_ref()?
10031 .read_with(cx, |breakpoint_store, cx| {
10032 breakpoint_store
10033 .breakpoints(
10034 &buffer,
10035 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10036 &buffer_snapshot,
10037 cx,
10038 )
10039 .next()
10040 .and_then(|(bp, _)| {
10041 let breakpoint_row = buffer_snapshot
10042 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10043 .row;
10044
10045 if breakpoint_row == row {
10046 snapshot
10047 .buffer_snapshot
10048 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10049 .map(|position| (position, bp.bp.clone()))
10050 } else {
10051 None
10052 }
10053 })
10054 });
10055 bp
10056 }
10057
10058 pub fn edit_log_breakpoint(
10059 &mut self,
10060 _: &EditLogBreakpoint,
10061 window: &mut Window,
10062 cx: &mut Context<Self>,
10063 ) {
10064 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10065 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10066 message: None,
10067 state: BreakpointState::Enabled,
10068 condition: None,
10069 hit_condition: None,
10070 });
10071
10072 self.add_edit_breakpoint_block(
10073 anchor,
10074 &breakpoint,
10075 BreakpointPromptEditAction::Log,
10076 window,
10077 cx,
10078 );
10079 }
10080 }
10081
10082 fn breakpoints_at_cursors(
10083 &self,
10084 window: &mut Window,
10085 cx: &mut Context<Self>,
10086 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10087 let snapshot = self.snapshot(window, cx);
10088 let cursors = self
10089 .selections
10090 .disjoint_anchors()
10091 .into_iter()
10092 .map(|selection| {
10093 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10094
10095 let breakpoint_position = self
10096 .breakpoint_at_row(cursor_position.row, window, cx)
10097 .map(|bp| bp.0)
10098 .unwrap_or_else(|| {
10099 snapshot
10100 .display_snapshot
10101 .buffer_snapshot
10102 .anchor_after(Point::new(cursor_position.row, 0))
10103 });
10104
10105 let breakpoint = self
10106 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10107 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10108
10109 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10110 })
10111 // 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.
10112 .collect::<HashMap<Anchor, _>>();
10113
10114 cursors.into_iter().collect()
10115 }
10116
10117 pub fn enable_breakpoint(
10118 &mut self,
10119 _: &crate::actions::EnableBreakpoint,
10120 window: &mut Window,
10121 cx: &mut Context<Self>,
10122 ) {
10123 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10124 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10125 continue;
10126 };
10127 self.edit_breakpoint_at_anchor(
10128 anchor,
10129 breakpoint,
10130 BreakpointEditAction::InvertState,
10131 cx,
10132 );
10133 }
10134 }
10135
10136 pub fn disable_breakpoint(
10137 &mut self,
10138 _: &crate::actions::DisableBreakpoint,
10139 window: &mut Window,
10140 cx: &mut Context<Self>,
10141 ) {
10142 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10143 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10144 continue;
10145 };
10146 self.edit_breakpoint_at_anchor(
10147 anchor,
10148 breakpoint,
10149 BreakpointEditAction::InvertState,
10150 cx,
10151 );
10152 }
10153 }
10154
10155 pub fn toggle_breakpoint(
10156 &mut self,
10157 _: &crate::actions::ToggleBreakpoint,
10158 window: &mut Window,
10159 cx: &mut Context<Self>,
10160 ) {
10161 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10162 if let Some(breakpoint) = breakpoint {
10163 self.edit_breakpoint_at_anchor(
10164 anchor,
10165 breakpoint,
10166 BreakpointEditAction::Toggle,
10167 cx,
10168 );
10169 } else {
10170 self.edit_breakpoint_at_anchor(
10171 anchor,
10172 Breakpoint::new_standard(),
10173 BreakpointEditAction::Toggle,
10174 cx,
10175 );
10176 }
10177 }
10178 }
10179
10180 pub fn edit_breakpoint_at_anchor(
10181 &mut self,
10182 breakpoint_position: Anchor,
10183 breakpoint: Breakpoint,
10184 edit_action: BreakpointEditAction,
10185 cx: &mut Context<Self>,
10186 ) {
10187 let Some(breakpoint_store) = &self.breakpoint_store else {
10188 return;
10189 };
10190
10191 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10192 if breakpoint_position == Anchor::min() {
10193 self.buffer()
10194 .read(cx)
10195 .excerpt_buffer_ids()
10196 .into_iter()
10197 .next()
10198 } else {
10199 None
10200 }
10201 }) else {
10202 return;
10203 };
10204
10205 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10206 return;
10207 };
10208
10209 breakpoint_store.update(cx, |breakpoint_store, cx| {
10210 breakpoint_store.toggle_breakpoint(
10211 buffer,
10212 BreakpointWithPosition {
10213 position: breakpoint_position.text_anchor,
10214 bp: breakpoint,
10215 },
10216 edit_action,
10217 cx,
10218 );
10219 });
10220
10221 cx.notify();
10222 }
10223
10224 #[cfg(any(test, feature = "test-support"))]
10225 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10226 self.breakpoint_store.clone()
10227 }
10228
10229 pub fn prepare_restore_change(
10230 &self,
10231 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10232 hunk: &MultiBufferDiffHunk,
10233 cx: &mut App,
10234 ) -> Option<()> {
10235 if hunk.is_created_file() {
10236 return None;
10237 }
10238 let buffer = self.buffer.read(cx);
10239 let diff = buffer.diff_for(hunk.buffer_id)?;
10240 let buffer = buffer.buffer(hunk.buffer_id)?;
10241 let buffer = buffer.read(cx);
10242 let original_text = diff
10243 .read(cx)
10244 .base_text()
10245 .as_rope()
10246 .slice(hunk.diff_base_byte_range.clone());
10247 let buffer_snapshot = buffer.snapshot();
10248 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10249 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10250 probe
10251 .0
10252 .start
10253 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10254 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10255 }) {
10256 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10257 Some(())
10258 } else {
10259 None
10260 }
10261 }
10262
10263 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10264 self.manipulate_lines(window, cx, |lines| lines.reverse())
10265 }
10266
10267 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10268 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10269 }
10270
10271 fn manipulate_lines<Fn>(
10272 &mut self,
10273 window: &mut Window,
10274 cx: &mut Context<Self>,
10275 mut callback: Fn,
10276 ) where
10277 Fn: FnMut(&mut Vec<&str>),
10278 {
10279 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10280
10281 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10282 let buffer = self.buffer.read(cx).snapshot(cx);
10283
10284 let mut edits = Vec::new();
10285
10286 let selections = self.selections.all::<Point>(cx);
10287 let mut selections = selections.iter().peekable();
10288 let mut contiguous_row_selections = Vec::new();
10289 let mut new_selections = Vec::new();
10290 let mut added_lines = 0;
10291 let mut removed_lines = 0;
10292
10293 while let Some(selection) = selections.next() {
10294 let (start_row, end_row) = consume_contiguous_rows(
10295 &mut contiguous_row_selections,
10296 selection,
10297 &display_map,
10298 &mut selections,
10299 );
10300
10301 let start_point = Point::new(start_row.0, 0);
10302 let end_point = Point::new(
10303 end_row.previous_row().0,
10304 buffer.line_len(end_row.previous_row()),
10305 );
10306 let text = buffer
10307 .text_for_range(start_point..end_point)
10308 .collect::<String>();
10309
10310 let mut lines = text.split('\n').collect_vec();
10311
10312 let lines_before = lines.len();
10313 callback(&mut lines);
10314 let lines_after = lines.len();
10315
10316 edits.push((start_point..end_point, lines.join("\n")));
10317
10318 // Selections must change based on added and removed line count
10319 let start_row =
10320 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10321 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10322 new_selections.push(Selection {
10323 id: selection.id,
10324 start: start_row,
10325 end: end_row,
10326 goal: SelectionGoal::None,
10327 reversed: selection.reversed,
10328 });
10329
10330 if lines_after > lines_before {
10331 added_lines += lines_after - lines_before;
10332 } else if lines_before > lines_after {
10333 removed_lines += lines_before - lines_after;
10334 }
10335 }
10336
10337 self.transact(window, cx, |this, window, cx| {
10338 let buffer = this.buffer.update(cx, |buffer, cx| {
10339 buffer.edit(edits, None, cx);
10340 buffer.snapshot(cx)
10341 });
10342
10343 // Recalculate offsets on newly edited buffer
10344 let new_selections = new_selections
10345 .iter()
10346 .map(|s| {
10347 let start_point = Point::new(s.start.0, 0);
10348 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10349 Selection {
10350 id: s.id,
10351 start: buffer.point_to_offset(start_point),
10352 end: buffer.point_to_offset(end_point),
10353 goal: s.goal,
10354 reversed: s.reversed,
10355 }
10356 })
10357 .collect();
10358
10359 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10360 s.select(new_selections);
10361 });
10362
10363 this.request_autoscroll(Autoscroll::fit(), cx);
10364 });
10365 }
10366
10367 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10368 self.manipulate_text(window, cx, |text| {
10369 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10370 if has_upper_case_characters {
10371 text.to_lowercase()
10372 } else {
10373 text.to_uppercase()
10374 }
10375 })
10376 }
10377
10378 pub fn convert_to_upper_case(
10379 &mut self,
10380 _: &ConvertToUpperCase,
10381 window: &mut Window,
10382 cx: &mut Context<Self>,
10383 ) {
10384 self.manipulate_text(window, cx, |text| text.to_uppercase())
10385 }
10386
10387 pub fn convert_to_lower_case(
10388 &mut self,
10389 _: &ConvertToLowerCase,
10390 window: &mut Window,
10391 cx: &mut Context<Self>,
10392 ) {
10393 self.manipulate_text(window, cx, |text| text.to_lowercase())
10394 }
10395
10396 pub fn convert_to_title_case(
10397 &mut self,
10398 _: &ConvertToTitleCase,
10399 window: &mut Window,
10400 cx: &mut Context<Self>,
10401 ) {
10402 self.manipulate_text(window, cx, |text| {
10403 text.split('\n')
10404 .map(|line| line.to_case(Case::Title))
10405 .join("\n")
10406 })
10407 }
10408
10409 pub fn convert_to_snake_case(
10410 &mut self,
10411 _: &ConvertToSnakeCase,
10412 window: &mut Window,
10413 cx: &mut Context<Self>,
10414 ) {
10415 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10416 }
10417
10418 pub fn convert_to_kebab_case(
10419 &mut self,
10420 _: &ConvertToKebabCase,
10421 window: &mut Window,
10422 cx: &mut Context<Self>,
10423 ) {
10424 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10425 }
10426
10427 pub fn convert_to_upper_camel_case(
10428 &mut self,
10429 _: &ConvertToUpperCamelCase,
10430 window: &mut Window,
10431 cx: &mut Context<Self>,
10432 ) {
10433 self.manipulate_text(window, cx, |text| {
10434 text.split('\n')
10435 .map(|line| line.to_case(Case::UpperCamel))
10436 .join("\n")
10437 })
10438 }
10439
10440 pub fn convert_to_lower_camel_case(
10441 &mut self,
10442 _: &ConvertToLowerCamelCase,
10443 window: &mut Window,
10444 cx: &mut Context<Self>,
10445 ) {
10446 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10447 }
10448
10449 pub fn convert_to_opposite_case(
10450 &mut self,
10451 _: &ConvertToOppositeCase,
10452 window: &mut Window,
10453 cx: &mut Context<Self>,
10454 ) {
10455 self.manipulate_text(window, cx, |text| {
10456 text.chars()
10457 .fold(String::with_capacity(text.len()), |mut t, c| {
10458 if c.is_uppercase() {
10459 t.extend(c.to_lowercase());
10460 } else {
10461 t.extend(c.to_uppercase());
10462 }
10463 t
10464 })
10465 })
10466 }
10467
10468 pub fn convert_to_rot13(
10469 &mut self,
10470 _: &ConvertToRot13,
10471 window: &mut Window,
10472 cx: &mut Context<Self>,
10473 ) {
10474 self.manipulate_text(window, cx, |text| {
10475 text.chars()
10476 .map(|c| match c {
10477 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10478 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10479 _ => c,
10480 })
10481 .collect()
10482 })
10483 }
10484
10485 pub fn convert_to_rot47(
10486 &mut self,
10487 _: &ConvertToRot47,
10488 window: &mut Window,
10489 cx: &mut Context<Self>,
10490 ) {
10491 self.manipulate_text(window, cx, |text| {
10492 text.chars()
10493 .map(|c| {
10494 let code_point = c as u32;
10495 if code_point >= 33 && code_point <= 126 {
10496 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10497 }
10498 c
10499 })
10500 .collect()
10501 })
10502 }
10503
10504 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10505 where
10506 Fn: FnMut(&str) -> String,
10507 {
10508 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10509 let buffer = self.buffer.read(cx).snapshot(cx);
10510
10511 let mut new_selections = Vec::new();
10512 let mut edits = Vec::new();
10513 let mut selection_adjustment = 0i32;
10514
10515 for selection in self.selections.all::<usize>(cx) {
10516 let selection_is_empty = selection.is_empty();
10517
10518 let (start, end) = if selection_is_empty {
10519 let word_range = movement::surrounding_word(
10520 &display_map,
10521 selection.start.to_display_point(&display_map),
10522 );
10523 let start = word_range.start.to_offset(&display_map, Bias::Left);
10524 let end = word_range.end.to_offset(&display_map, Bias::Left);
10525 (start, end)
10526 } else {
10527 (selection.start, selection.end)
10528 };
10529
10530 let text = buffer.text_for_range(start..end).collect::<String>();
10531 let old_length = text.len() as i32;
10532 let text = callback(&text);
10533
10534 new_selections.push(Selection {
10535 start: (start as i32 - selection_adjustment) as usize,
10536 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10537 goal: SelectionGoal::None,
10538 ..selection
10539 });
10540
10541 selection_adjustment += old_length - text.len() as i32;
10542
10543 edits.push((start..end, text));
10544 }
10545
10546 self.transact(window, cx, |this, window, cx| {
10547 this.buffer.update(cx, |buffer, cx| {
10548 buffer.edit(edits, None, cx);
10549 });
10550
10551 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10552 s.select(new_selections);
10553 });
10554
10555 this.request_autoscroll(Autoscroll::fit(), cx);
10556 });
10557 }
10558
10559 pub fn duplicate(
10560 &mut self,
10561 upwards: bool,
10562 whole_lines: bool,
10563 window: &mut Window,
10564 cx: &mut Context<Self>,
10565 ) {
10566 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10567
10568 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10569 let buffer = &display_map.buffer_snapshot;
10570 let selections = self.selections.all::<Point>(cx);
10571
10572 let mut edits = Vec::new();
10573 let mut selections_iter = selections.iter().peekable();
10574 while let Some(selection) = selections_iter.next() {
10575 let mut rows = selection.spanned_rows(false, &display_map);
10576 // duplicate line-wise
10577 if whole_lines || selection.start == selection.end {
10578 // Avoid duplicating the same lines twice.
10579 while let Some(next_selection) = selections_iter.peek() {
10580 let next_rows = next_selection.spanned_rows(false, &display_map);
10581 if next_rows.start < rows.end {
10582 rows.end = next_rows.end;
10583 selections_iter.next().unwrap();
10584 } else {
10585 break;
10586 }
10587 }
10588
10589 // Copy the text from the selected row region and splice it either at the start
10590 // or end of the region.
10591 let start = Point::new(rows.start.0, 0);
10592 let end = Point::new(
10593 rows.end.previous_row().0,
10594 buffer.line_len(rows.end.previous_row()),
10595 );
10596 let text = buffer
10597 .text_for_range(start..end)
10598 .chain(Some("\n"))
10599 .collect::<String>();
10600 let insert_location = if upwards {
10601 Point::new(rows.end.0, 0)
10602 } else {
10603 start
10604 };
10605 edits.push((insert_location..insert_location, text));
10606 } else {
10607 // duplicate character-wise
10608 let start = selection.start;
10609 let end = selection.end;
10610 let text = buffer.text_for_range(start..end).collect::<String>();
10611 edits.push((selection.end..selection.end, text));
10612 }
10613 }
10614
10615 self.transact(window, cx, |this, _, cx| {
10616 this.buffer.update(cx, |buffer, cx| {
10617 buffer.edit(edits, None, cx);
10618 });
10619
10620 this.request_autoscroll(Autoscroll::fit(), cx);
10621 });
10622 }
10623
10624 pub fn duplicate_line_up(
10625 &mut self,
10626 _: &DuplicateLineUp,
10627 window: &mut Window,
10628 cx: &mut Context<Self>,
10629 ) {
10630 self.duplicate(true, true, window, cx);
10631 }
10632
10633 pub fn duplicate_line_down(
10634 &mut self,
10635 _: &DuplicateLineDown,
10636 window: &mut Window,
10637 cx: &mut Context<Self>,
10638 ) {
10639 self.duplicate(false, true, window, cx);
10640 }
10641
10642 pub fn duplicate_selection(
10643 &mut self,
10644 _: &DuplicateSelection,
10645 window: &mut Window,
10646 cx: &mut Context<Self>,
10647 ) {
10648 self.duplicate(false, false, window, cx);
10649 }
10650
10651 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10652 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10653
10654 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10655 let buffer = self.buffer.read(cx).snapshot(cx);
10656
10657 let mut edits = Vec::new();
10658 let mut unfold_ranges = Vec::new();
10659 let mut refold_creases = Vec::new();
10660
10661 let selections = self.selections.all::<Point>(cx);
10662 let mut selections = selections.iter().peekable();
10663 let mut contiguous_row_selections = Vec::new();
10664 let mut new_selections = Vec::new();
10665
10666 while let Some(selection) = selections.next() {
10667 // Find all the selections that span a contiguous row range
10668 let (start_row, end_row) = consume_contiguous_rows(
10669 &mut contiguous_row_selections,
10670 selection,
10671 &display_map,
10672 &mut selections,
10673 );
10674
10675 // Move the text spanned by the row range to be before the line preceding the row range
10676 if start_row.0 > 0 {
10677 let range_to_move = Point::new(
10678 start_row.previous_row().0,
10679 buffer.line_len(start_row.previous_row()),
10680 )
10681 ..Point::new(
10682 end_row.previous_row().0,
10683 buffer.line_len(end_row.previous_row()),
10684 );
10685 let insertion_point = display_map
10686 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10687 .0;
10688
10689 // Don't move lines across excerpts
10690 if buffer
10691 .excerpt_containing(insertion_point..range_to_move.end)
10692 .is_some()
10693 {
10694 let text = buffer
10695 .text_for_range(range_to_move.clone())
10696 .flat_map(|s| s.chars())
10697 .skip(1)
10698 .chain(['\n'])
10699 .collect::<String>();
10700
10701 edits.push((
10702 buffer.anchor_after(range_to_move.start)
10703 ..buffer.anchor_before(range_to_move.end),
10704 String::new(),
10705 ));
10706 let insertion_anchor = buffer.anchor_after(insertion_point);
10707 edits.push((insertion_anchor..insertion_anchor, text));
10708
10709 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10710
10711 // Move selections up
10712 new_selections.extend(contiguous_row_selections.drain(..).map(
10713 |mut selection| {
10714 selection.start.row -= row_delta;
10715 selection.end.row -= row_delta;
10716 selection
10717 },
10718 ));
10719
10720 // Move folds up
10721 unfold_ranges.push(range_to_move.clone());
10722 for fold in display_map.folds_in_range(
10723 buffer.anchor_before(range_to_move.start)
10724 ..buffer.anchor_after(range_to_move.end),
10725 ) {
10726 let mut start = fold.range.start.to_point(&buffer);
10727 let mut end = fold.range.end.to_point(&buffer);
10728 start.row -= row_delta;
10729 end.row -= row_delta;
10730 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10731 }
10732 }
10733 }
10734
10735 // If we didn't move line(s), preserve the existing selections
10736 new_selections.append(&mut contiguous_row_selections);
10737 }
10738
10739 self.transact(window, cx, |this, window, cx| {
10740 this.unfold_ranges(&unfold_ranges, true, true, cx);
10741 this.buffer.update(cx, |buffer, cx| {
10742 for (range, text) in edits {
10743 buffer.edit([(range, text)], None, cx);
10744 }
10745 });
10746 this.fold_creases(refold_creases, true, window, cx);
10747 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10748 s.select(new_selections);
10749 })
10750 });
10751 }
10752
10753 pub fn move_line_down(
10754 &mut self,
10755 _: &MoveLineDown,
10756 window: &mut Window,
10757 cx: &mut Context<Self>,
10758 ) {
10759 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10760
10761 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10762 let buffer = self.buffer.read(cx).snapshot(cx);
10763
10764 let mut edits = Vec::new();
10765 let mut unfold_ranges = Vec::new();
10766 let mut refold_creases = Vec::new();
10767
10768 let selections = self.selections.all::<Point>(cx);
10769 let mut selections = selections.iter().peekable();
10770 let mut contiguous_row_selections = Vec::new();
10771 let mut new_selections = Vec::new();
10772
10773 while let Some(selection) = selections.next() {
10774 // Find all the selections that span a contiguous row range
10775 let (start_row, end_row) = consume_contiguous_rows(
10776 &mut contiguous_row_selections,
10777 selection,
10778 &display_map,
10779 &mut selections,
10780 );
10781
10782 // Move the text spanned by the row range to be after the last line of the row range
10783 if end_row.0 <= buffer.max_point().row {
10784 let range_to_move =
10785 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10786 let insertion_point = display_map
10787 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10788 .0;
10789
10790 // Don't move lines across excerpt boundaries
10791 if buffer
10792 .excerpt_containing(range_to_move.start..insertion_point)
10793 .is_some()
10794 {
10795 let mut text = String::from("\n");
10796 text.extend(buffer.text_for_range(range_to_move.clone()));
10797 text.pop(); // Drop trailing newline
10798 edits.push((
10799 buffer.anchor_after(range_to_move.start)
10800 ..buffer.anchor_before(range_to_move.end),
10801 String::new(),
10802 ));
10803 let insertion_anchor = buffer.anchor_after(insertion_point);
10804 edits.push((insertion_anchor..insertion_anchor, text));
10805
10806 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10807
10808 // Move selections down
10809 new_selections.extend(contiguous_row_selections.drain(..).map(
10810 |mut selection| {
10811 selection.start.row += row_delta;
10812 selection.end.row += row_delta;
10813 selection
10814 },
10815 ));
10816
10817 // Move folds down
10818 unfold_ranges.push(range_to_move.clone());
10819 for fold in display_map.folds_in_range(
10820 buffer.anchor_before(range_to_move.start)
10821 ..buffer.anchor_after(range_to_move.end),
10822 ) {
10823 let mut start = fold.range.start.to_point(&buffer);
10824 let mut end = fold.range.end.to_point(&buffer);
10825 start.row += row_delta;
10826 end.row += row_delta;
10827 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10828 }
10829 }
10830 }
10831
10832 // If we didn't move line(s), preserve the existing selections
10833 new_selections.append(&mut contiguous_row_selections);
10834 }
10835
10836 self.transact(window, cx, |this, window, cx| {
10837 this.unfold_ranges(&unfold_ranges, true, true, cx);
10838 this.buffer.update(cx, |buffer, cx| {
10839 for (range, text) in edits {
10840 buffer.edit([(range, text)], None, cx);
10841 }
10842 });
10843 this.fold_creases(refold_creases, true, window, cx);
10844 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10845 s.select(new_selections)
10846 });
10847 });
10848 }
10849
10850 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10851 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10852 let text_layout_details = &self.text_layout_details(window);
10853 self.transact(window, cx, |this, window, cx| {
10854 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10855 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10856 s.move_with(|display_map, selection| {
10857 if !selection.is_empty() {
10858 return;
10859 }
10860
10861 let mut head = selection.head();
10862 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10863 if head.column() == display_map.line_len(head.row()) {
10864 transpose_offset = display_map
10865 .buffer_snapshot
10866 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10867 }
10868
10869 if transpose_offset == 0 {
10870 return;
10871 }
10872
10873 *head.column_mut() += 1;
10874 head = display_map.clip_point(head, Bias::Right);
10875 let goal = SelectionGoal::HorizontalPosition(
10876 display_map
10877 .x_for_display_point(head, text_layout_details)
10878 .into(),
10879 );
10880 selection.collapse_to(head, goal);
10881
10882 let transpose_start = display_map
10883 .buffer_snapshot
10884 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10885 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10886 let transpose_end = display_map
10887 .buffer_snapshot
10888 .clip_offset(transpose_offset + 1, Bias::Right);
10889 if let Some(ch) =
10890 display_map.buffer_snapshot.chars_at(transpose_start).next()
10891 {
10892 edits.push((transpose_start..transpose_offset, String::new()));
10893 edits.push((transpose_end..transpose_end, ch.to_string()));
10894 }
10895 }
10896 });
10897 edits
10898 });
10899 this.buffer
10900 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10901 let selections = this.selections.all::<usize>(cx);
10902 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10903 s.select(selections);
10904 });
10905 });
10906 }
10907
10908 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10909 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10910 self.rewrap_impl(RewrapOptions::default(), cx)
10911 }
10912
10913 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10914 let buffer = self.buffer.read(cx).snapshot(cx);
10915 let selections = self.selections.all::<Point>(cx);
10916
10917 // Shrink and split selections to respect paragraph boundaries.
10918 let ranges = selections.into_iter().flat_map(|selection| {
10919 let language_settings = buffer.language_settings_at(selection.head(), cx);
10920 let language_scope = buffer.language_scope_at(selection.head());
10921
10922 let Some(start_row) = (selection.start.row..=selection.end.row)
10923 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
10924 else {
10925 return vec![];
10926 };
10927 let Some(end_row) = (selection.start.row..=selection.end.row)
10928 .rev()
10929 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
10930 else {
10931 return vec![];
10932 };
10933
10934 let mut row = start_row;
10935 let mut ranges = Vec::new();
10936 while let Some(blank_row) =
10937 (row..end_row).find(|row| buffer.is_line_blank(MultiBufferRow(*row)))
10938 {
10939 let next_paragraph_start = (blank_row + 1..=end_row)
10940 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
10941 .unwrap();
10942 ranges.push((
10943 language_settings.clone(),
10944 language_scope.clone(),
10945 Point::new(row, 0)..Point::new(blank_row - 1, 0),
10946 ));
10947 row = next_paragraph_start;
10948 }
10949 ranges.push((
10950 language_settings.clone(),
10951 language_scope.clone(),
10952 Point::new(row, 0)..Point::new(end_row, 0),
10953 ));
10954
10955 ranges
10956 });
10957
10958 let mut edits = Vec::new();
10959 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10960
10961 for (language_settings, language_scope, range) in ranges {
10962 let mut start_row = range.start.row;
10963 let mut end_row = range.end.row;
10964
10965 // Skip selections that overlap with a range that has already been rewrapped.
10966 let selection_range = start_row..end_row;
10967 if rewrapped_row_ranges
10968 .iter()
10969 .any(|range| range.overlaps(&selection_range))
10970 {
10971 continue;
10972 }
10973
10974 let tab_size = language_settings.tab_size;
10975
10976 // Since not all lines in the selection may be at the same indent
10977 // level, choose the indent size that is the most common between all
10978 // of the lines.
10979 //
10980 // If there is a tie, we use the deepest indent.
10981 let (indent_size, indent_end) = {
10982 let mut indent_size_occurrences = HashMap::default();
10983 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10984
10985 for row in start_row..=end_row {
10986 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10987 rows_by_indent_size.entry(indent).or_default().push(row);
10988 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10989 }
10990
10991 let indent_size = indent_size_occurrences
10992 .into_iter()
10993 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10994 .map(|(indent, _)| indent)
10995 .unwrap_or_default();
10996 let row = rows_by_indent_size[&indent_size][0];
10997 let indent_end = Point::new(row, indent_size.len);
10998
10999 (indent_size, indent_end)
11000 };
11001
11002 let mut line_prefix = indent_size.chars().collect::<String>();
11003
11004 let mut inside_comment = false;
11005 if let Some(comment_prefix) = language_scope.and_then(|language| {
11006 language
11007 .line_comment_prefixes()
11008 .iter()
11009 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11010 .cloned()
11011 }) {
11012 line_prefix.push_str(&comment_prefix);
11013 inside_comment = true;
11014 }
11015
11016 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11017 RewrapBehavior::InComments => inside_comment,
11018 RewrapBehavior::InSelections => !range.is_empty(),
11019 RewrapBehavior::Anywhere => true,
11020 };
11021
11022 let should_rewrap = options.override_language_settings
11023 || allow_rewrap_based_on_language
11024 || self.hard_wrap.is_some();
11025 if !should_rewrap {
11026 continue;
11027 }
11028
11029 if range.is_empty() {
11030 'expand_upwards: while start_row > 0 {
11031 let prev_row = start_row - 1;
11032 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11033 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11034 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11035 {
11036 start_row = prev_row;
11037 } else {
11038 break 'expand_upwards;
11039 }
11040 }
11041
11042 'expand_downwards: while end_row < buffer.max_point().row {
11043 let next_row = end_row + 1;
11044 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11045 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11046 && !buffer.is_line_blank(MultiBufferRow(next_row))
11047 {
11048 end_row = next_row;
11049 } else {
11050 break 'expand_downwards;
11051 }
11052 }
11053 }
11054
11055 let start = Point::new(start_row, 0);
11056 let start_offset = start.to_offset(&buffer);
11057 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11058 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11059 let Some(lines_without_prefixes) = selection_text
11060 .lines()
11061 .map(|line| {
11062 line.strip_prefix(&line_prefix)
11063 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
11064 .with_context(|| {
11065 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11066 })
11067 })
11068 .collect::<Result<Vec<_>, _>>()
11069 .log_err()
11070 else {
11071 continue;
11072 };
11073
11074 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11075 buffer
11076 .language_settings_at(Point::new(start_row, 0), cx)
11077 .preferred_line_length as usize
11078 });
11079 let wrapped_text = wrap_with_prefix(
11080 line_prefix,
11081 lines_without_prefixes.join("\n"),
11082 wrap_column,
11083 tab_size,
11084 options.preserve_existing_whitespace,
11085 );
11086
11087 // TODO: should always use char-based diff while still supporting cursor behavior that
11088 // matches vim.
11089 let mut diff_options = DiffOptions::default();
11090 if options.override_language_settings {
11091 diff_options.max_word_diff_len = 0;
11092 diff_options.max_word_diff_line_count = 0;
11093 } else {
11094 diff_options.max_word_diff_len = usize::MAX;
11095 diff_options.max_word_diff_line_count = usize::MAX;
11096 }
11097
11098 for (old_range, new_text) in
11099 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11100 {
11101 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11102 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11103 edits.push((edit_start..edit_end, new_text));
11104 }
11105
11106 rewrapped_row_ranges.push(start_row..=end_row);
11107 }
11108
11109 self.buffer
11110 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11111 }
11112
11113 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11114 let mut text = String::new();
11115 let buffer = self.buffer.read(cx).snapshot(cx);
11116 let mut selections = self.selections.all::<Point>(cx);
11117 let mut clipboard_selections = Vec::with_capacity(selections.len());
11118 {
11119 let max_point = buffer.max_point();
11120 let mut is_first = true;
11121 for selection in &mut selections {
11122 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11123 if is_entire_line {
11124 selection.start = Point::new(selection.start.row, 0);
11125 if !selection.is_empty() && selection.end.column == 0 {
11126 selection.end = cmp::min(max_point, selection.end);
11127 } else {
11128 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11129 }
11130 selection.goal = SelectionGoal::None;
11131 }
11132 if is_first {
11133 is_first = false;
11134 } else {
11135 text += "\n";
11136 }
11137 let mut len = 0;
11138 for chunk in buffer.text_for_range(selection.start..selection.end) {
11139 text.push_str(chunk);
11140 len += chunk.len();
11141 }
11142 clipboard_selections.push(ClipboardSelection {
11143 len,
11144 is_entire_line,
11145 first_line_indent: buffer
11146 .indent_size_for_line(MultiBufferRow(selection.start.row))
11147 .len,
11148 });
11149 }
11150 }
11151
11152 self.transact(window, cx, |this, window, cx| {
11153 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11154 s.select(selections);
11155 });
11156 this.insert("", window, cx);
11157 });
11158 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11159 }
11160
11161 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11162 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11163 let item = self.cut_common(window, cx);
11164 cx.write_to_clipboard(item);
11165 }
11166
11167 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11168 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11169 self.change_selections(None, window, cx, |s| {
11170 s.move_with(|snapshot, sel| {
11171 if sel.is_empty() {
11172 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11173 }
11174 });
11175 });
11176 let item = self.cut_common(window, cx);
11177 cx.set_global(KillRing(item))
11178 }
11179
11180 pub fn kill_ring_yank(
11181 &mut self,
11182 _: &KillRingYank,
11183 window: &mut Window,
11184 cx: &mut Context<Self>,
11185 ) {
11186 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11187 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11188 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11189 (kill_ring.text().to_string(), kill_ring.metadata_json())
11190 } else {
11191 return;
11192 }
11193 } else {
11194 return;
11195 };
11196 self.do_paste(&text, metadata, false, window, cx);
11197 }
11198
11199 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11200 self.do_copy(true, cx);
11201 }
11202
11203 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11204 self.do_copy(false, cx);
11205 }
11206
11207 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11208 let selections = self.selections.all::<Point>(cx);
11209 let buffer = self.buffer.read(cx).read(cx);
11210 let mut text = String::new();
11211
11212 let mut clipboard_selections = Vec::with_capacity(selections.len());
11213 {
11214 let max_point = buffer.max_point();
11215 let mut is_first = true;
11216 for selection in &selections {
11217 let mut start = selection.start;
11218 let mut end = selection.end;
11219 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11220 if is_entire_line {
11221 start = Point::new(start.row, 0);
11222 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11223 }
11224
11225 let mut trimmed_selections = Vec::new();
11226 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11227 let row = MultiBufferRow(start.row);
11228 let first_indent = buffer.indent_size_for_line(row);
11229 if first_indent.len == 0 || start.column > first_indent.len {
11230 trimmed_selections.push(start..end);
11231 } else {
11232 trimmed_selections.push(
11233 Point::new(row.0, first_indent.len)
11234 ..Point::new(row.0, buffer.line_len(row)),
11235 );
11236 for row in start.row + 1..=end.row {
11237 let mut line_len = buffer.line_len(MultiBufferRow(row));
11238 if row == end.row {
11239 line_len = end.column;
11240 }
11241 if line_len == 0 {
11242 trimmed_selections
11243 .push(Point::new(row, 0)..Point::new(row, line_len));
11244 continue;
11245 }
11246 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11247 if row_indent_size.len >= first_indent.len {
11248 trimmed_selections.push(
11249 Point::new(row, first_indent.len)..Point::new(row, line_len),
11250 );
11251 } else {
11252 trimmed_selections.clear();
11253 trimmed_selections.push(start..end);
11254 break;
11255 }
11256 }
11257 }
11258 } else {
11259 trimmed_selections.push(start..end);
11260 }
11261
11262 for trimmed_range in trimmed_selections {
11263 if is_first {
11264 is_first = false;
11265 } else {
11266 text += "\n";
11267 }
11268 let mut len = 0;
11269 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11270 text.push_str(chunk);
11271 len += chunk.len();
11272 }
11273 clipboard_selections.push(ClipboardSelection {
11274 len,
11275 is_entire_line,
11276 first_line_indent: buffer
11277 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11278 .len,
11279 });
11280 }
11281 }
11282 }
11283
11284 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11285 text,
11286 clipboard_selections,
11287 ));
11288 }
11289
11290 pub fn do_paste(
11291 &mut self,
11292 text: &String,
11293 clipboard_selections: Option<Vec<ClipboardSelection>>,
11294 handle_entire_lines: bool,
11295 window: &mut Window,
11296 cx: &mut Context<Self>,
11297 ) {
11298 if self.read_only(cx) {
11299 return;
11300 }
11301
11302 let clipboard_text = Cow::Borrowed(text);
11303
11304 self.transact(window, cx, |this, window, cx| {
11305 if let Some(mut clipboard_selections) = clipboard_selections {
11306 let old_selections = this.selections.all::<usize>(cx);
11307 let all_selections_were_entire_line =
11308 clipboard_selections.iter().all(|s| s.is_entire_line);
11309 let first_selection_indent_column =
11310 clipboard_selections.first().map(|s| s.first_line_indent);
11311 if clipboard_selections.len() != old_selections.len() {
11312 clipboard_selections.drain(..);
11313 }
11314 let cursor_offset = this.selections.last::<usize>(cx).head();
11315 let mut auto_indent_on_paste = true;
11316
11317 this.buffer.update(cx, |buffer, cx| {
11318 let snapshot = buffer.read(cx);
11319 auto_indent_on_paste = snapshot
11320 .language_settings_at(cursor_offset, cx)
11321 .auto_indent_on_paste;
11322
11323 let mut start_offset = 0;
11324 let mut edits = Vec::new();
11325 let mut original_indent_columns = Vec::new();
11326 for (ix, selection) in old_selections.iter().enumerate() {
11327 let to_insert;
11328 let entire_line;
11329 let original_indent_column;
11330 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11331 let end_offset = start_offset + clipboard_selection.len;
11332 to_insert = &clipboard_text[start_offset..end_offset];
11333 entire_line = clipboard_selection.is_entire_line;
11334 start_offset = end_offset + 1;
11335 original_indent_column = Some(clipboard_selection.first_line_indent);
11336 } else {
11337 to_insert = clipboard_text.as_str();
11338 entire_line = all_selections_were_entire_line;
11339 original_indent_column = first_selection_indent_column
11340 }
11341
11342 // If the corresponding selection was empty when this slice of the
11343 // clipboard text was written, then the entire line containing the
11344 // selection was copied. If this selection is also currently empty,
11345 // then paste the line before the current line of the buffer.
11346 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11347 let column = selection.start.to_point(&snapshot).column as usize;
11348 let line_start = selection.start - column;
11349 line_start..line_start
11350 } else {
11351 selection.range()
11352 };
11353
11354 edits.push((range, to_insert));
11355 original_indent_columns.push(original_indent_column);
11356 }
11357 drop(snapshot);
11358
11359 buffer.edit(
11360 edits,
11361 if auto_indent_on_paste {
11362 Some(AutoindentMode::Block {
11363 original_indent_columns,
11364 })
11365 } else {
11366 None
11367 },
11368 cx,
11369 );
11370 });
11371
11372 let selections = this.selections.all::<usize>(cx);
11373 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11374 s.select(selections)
11375 });
11376 } else {
11377 this.insert(&clipboard_text, window, cx);
11378 }
11379 });
11380 }
11381
11382 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11383 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11384 if let Some(item) = cx.read_from_clipboard() {
11385 let entries = item.entries();
11386
11387 match entries.first() {
11388 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11389 // of all the pasted entries.
11390 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11391 .do_paste(
11392 clipboard_string.text(),
11393 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11394 true,
11395 window,
11396 cx,
11397 ),
11398 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11399 }
11400 }
11401 }
11402
11403 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11404 if self.read_only(cx) {
11405 return;
11406 }
11407
11408 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11409
11410 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11411 if let Some((selections, _)) =
11412 self.selection_history.transaction(transaction_id).cloned()
11413 {
11414 self.change_selections(None, window, cx, |s| {
11415 s.select_anchors(selections.to_vec());
11416 });
11417 } else {
11418 log::error!(
11419 "No entry in selection_history found for undo. \
11420 This may correspond to a bug where undo does not update the selection. \
11421 If this is occurring, please add details to \
11422 https://github.com/zed-industries/zed/issues/22692"
11423 );
11424 }
11425 self.request_autoscroll(Autoscroll::fit(), cx);
11426 self.unmark_text(window, cx);
11427 self.refresh_inline_completion(true, false, window, cx);
11428 cx.emit(EditorEvent::Edited { transaction_id });
11429 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11430 }
11431 }
11432
11433 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11434 if self.read_only(cx) {
11435 return;
11436 }
11437
11438 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11439
11440 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11441 if let Some((_, Some(selections))) =
11442 self.selection_history.transaction(transaction_id).cloned()
11443 {
11444 self.change_selections(None, window, cx, |s| {
11445 s.select_anchors(selections.to_vec());
11446 });
11447 } else {
11448 log::error!(
11449 "No entry in selection_history found for redo. \
11450 This may correspond to a bug where undo does not update the selection. \
11451 If this is occurring, please add details to \
11452 https://github.com/zed-industries/zed/issues/22692"
11453 );
11454 }
11455 self.request_autoscroll(Autoscroll::fit(), cx);
11456 self.unmark_text(window, cx);
11457 self.refresh_inline_completion(true, false, window, cx);
11458 cx.emit(EditorEvent::Edited { transaction_id });
11459 }
11460 }
11461
11462 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11463 self.buffer
11464 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11465 }
11466
11467 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11468 self.buffer
11469 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11470 }
11471
11472 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11473 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11474 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11475 s.move_with(|map, selection| {
11476 let cursor = if selection.is_empty() {
11477 movement::left(map, selection.start)
11478 } else {
11479 selection.start
11480 };
11481 selection.collapse_to(cursor, SelectionGoal::None);
11482 });
11483 })
11484 }
11485
11486 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11487 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11488 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11489 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11490 })
11491 }
11492
11493 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11494 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11495 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11496 s.move_with(|map, selection| {
11497 let cursor = if selection.is_empty() {
11498 movement::right(map, selection.end)
11499 } else {
11500 selection.end
11501 };
11502 selection.collapse_to(cursor, SelectionGoal::None)
11503 });
11504 })
11505 }
11506
11507 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11508 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11509 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11510 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11511 })
11512 }
11513
11514 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11515 if self.take_rename(true, window, cx).is_some() {
11516 return;
11517 }
11518
11519 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11520 cx.propagate();
11521 return;
11522 }
11523
11524 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11525
11526 let text_layout_details = &self.text_layout_details(window);
11527 let selection_count = self.selections.count();
11528 let first_selection = self.selections.first_anchor();
11529
11530 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11531 s.move_with(|map, selection| {
11532 if !selection.is_empty() {
11533 selection.goal = SelectionGoal::None;
11534 }
11535 let (cursor, goal) = movement::up(
11536 map,
11537 selection.start,
11538 selection.goal,
11539 false,
11540 text_layout_details,
11541 );
11542 selection.collapse_to(cursor, goal);
11543 });
11544 });
11545
11546 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11547 {
11548 cx.propagate();
11549 }
11550 }
11551
11552 pub fn move_up_by_lines(
11553 &mut self,
11554 action: &MoveUpByLines,
11555 window: &mut Window,
11556 cx: &mut Context<Self>,
11557 ) {
11558 if self.take_rename(true, window, cx).is_some() {
11559 return;
11560 }
11561
11562 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11563 cx.propagate();
11564 return;
11565 }
11566
11567 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11568
11569 let text_layout_details = &self.text_layout_details(window);
11570
11571 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11572 s.move_with(|map, selection| {
11573 if !selection.is_empty() {
11574 selection.goal = SelectionGoal::None;
11575 }
11576 let (cursor, goal) = movement::up_by_rows(
11577 map,
11578 selection.start,
11579 action.lines,
11580 selection.goal,
11581 false,
11582 text_layout_details,
11583 );
11584 selection.collapse_to(cursor, goal);
11585 });
11586 })
11587 }
11588
11589 pub fn move_down_by_lines(
11590 &mut self,
11591 action: &MoveDownByLines,
11592 window: &mut Window,
11593 cx: &mut Context<Self>,
11594 ) {
11595 if self.take_rename(true, window, cx).is_some() {
11596 return;
11597 }
11598
11599 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11600 cx.propagate();
11601 return;
11602 }
11603
11604 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11605
11606 let text_layout_details = &self.text_layout_details(window);
11607
11608 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11609 s.move_with(|map, selection| {
11610 if !selection.is_empty() {
11611 selection.goal = SelectionGoal::None;
11612 }
11613 let (cursor, goal) = movement::down_by_rows(
11614 map,
11615 selection.start,
11616 action.lines,
11617 selection.goal,
11618 false,
11619 text_layout_details,
11620 );
11621 selection.collapse_to(cursor, goal);
11622 });
11623 })
11624 }
11625
11626 pub fn select_down_by_lines(
11627 &mut self,
11628 action: &SelectDownByLines,
11629 window: &mut Window,
11630 cx: &mut Context<Self>,
11631 ) {
11632 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11633 let text_layout_details = &self.text_layout_details(window);
11634 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11635 s.move_heads_with(|map, head, goal| {
11636 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11637 })
11638 })
11639 }
11640
11641 pub fn select_up_by_lines(
11642 &mut self,
11643 action: &SelectUpByLines,
11644 window: &mut Window,
11645 cx: &mut Context<Self>,
11646 ) {
11647 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11648 let text_layout_details = &self.text_layout_details(window);
11649 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11650 s.move_heads_with(|map, head, goal| {
11651 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11652 })
11653 })
11654 }
11655
11656 pub fn select_page_up(
11657 &mut self,
11658 _: &SelectPageUp,
11659 window: &mut Window,
11660 cx: &mut Context<Self>,
11661 ) {
11662 let Some(row_count) = self.visible_row_count() else {
11663 return;
11664 };
11665
11666 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11667
11668 let text_layout_details = &self.text_layout_details(window);
11669
11670 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11671 s.move_heads_with(|map, head, goal| {
11672 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11673 })
11674 })
11675 }
11676
11677 pub fn move_page_up(
11678 &mut self,
11679 action: &MovePageUp,
11680 window: &mut Window,
11681 cx: &mut Context<Self>,
11682 ) {
11683 if self.take_rename(true, window, cx).is_some() {
11684 return;
11685 }
11686
11687 if self
11688 .context_menu
11689 .borrow_mut()
11690 .as_mut()
11691 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11692 .unwrap_or(false)
11693 {
11694 return;
11695 }
11696
11697 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11698 cx.propagate();
11699 return;
11700 }
11701
11702 let Some(row_count) = self.visible_row_count() else {
11703 return;
11704 };
11705
11706 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11707
11708 let autoscroll = if action.center_cursor {
11709 Autoscroll::center()
11710 } else {
11711 Autoscroll::fit()
11712 };
11713
11714 let text_layout_details = &self.text_layout_details(window);
11715
11716 self.change_selections(Some(autoscroll), window, cx, |s| {
11717 s.move_with(|map, selection| {
11718 if !selection.is_empty() {
11719 selection.goal = SelectionGoal::None;
11720 }
11721 let (cursor, goal) = movement::up_by_rows(
11722 map,
11723 selection.end,
11724 row_count,
11725 selection.goal,
11726 false,
11727 text_layout_details,
11728 );
11729 selection.collapse_to(cursor, goal);
11730 });
11731 });
11732 }
11733
11734 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11735 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11736 let text_layout_details = &self.text_layout_details(window);
11737 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11738 s.move_heads_with(|map, head, goal| {
11739 movement::up(map, head, goal, false, text_layout_details)
11740 })
11741 })
11742 }
11743
11744 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11745 self.take_rename(true, window, cx);
11746
11747 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11748 cx.propagate();
11749 return;
11750 }
11751
11752 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11753
11754 let text_layout_details = &self.text_layout_details(window);
11755 let selection_count = self.selections.count();
11756 let first_selection = self.selections.first_anchor();
11757
11758 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11759 s.move_with(|map, selection| {
11760 if !selection.is_empty() {
11761 selection.goal = SelectionGoal::None;
11762 }
11763 let (cursor, goal) = movement::down(
11764 map,
11765 selection.end,
11766 selection.goal,
11767 false,
11768 text_layout_details,
11769 );
11770 selection.collapse_to(cursor, goal);
11771 });
11772 });
11773
11774 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11775 {
11776 cx.propagate();
11777 }
11778 }
11779
11780 pub fn select_page_down(
11781 &mut self,
11782 _: &SelectPageDown,
11783 window: &mut Window,
11784 cx: &mut Context<Self>,
11785 ) {
11786 let Some(row_count) = self.visible_row_count() else {
11787 return;
11788 };
11789
11790 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11791
11792 let text_layout_details = &self.text_layout_details(window);
11793
11794 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11795 s.move_heads_with(|map, head, goal| {
11796 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11797 })
11798 })
11799 }
11800
11801 pub fn move_page_down(
11802 &mut self,
11803 action: &MovePageDown,
11804 window: &mut Window,
11805 cx: &mut Context<Self>,
11806 ) {
11807 if self.take_rename(true, window, cx).is_some() {
11808 return;
11809 }
11810
11811 if self
11812 .context_menu
11813 .borrow_mut()
11814 .as_mut()
11815 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
11816 .unwrap_or(false)
11817 {
11818 return;
11819 }
11820
11821 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11822 cx.propagate();
11823 return;
11824 }
11825
11826 let Some(row_count) = self.visible_row_count() else {
11827 return;
11828 };
11829
11830 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11831
11832 let autoscroll = if action.center_cursor {
11833 Autoscroll::center()
11834 } else {
11835 Autoscroll::fit()
11836 };
11837
11838 let text_layout_details = &self.text_layout_details(window);
11839 self.change_selections(Some(autoscroll), window, cx, |s| {
11840 s.move_with(|map, selection| {
11841 if !selection.is_empty() {
11842 selection.goal = SelectionGoal::None;
11843 }
11844 let (cursor, goal) = movement::down_by_rows(
11845 map,
11846 selection.end,
11847 row_count,
11848 selection.goal,
11849 false,
11850 text_layout_details,
11851 );
11852 selection.collapse_to(cursor, goal);
11853 });
11854 });
11855 }
11856
11857 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11858 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11859 let text_layout_details = &self.text_layout_details(window);
11860 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11861 s.move_heads_with(|map, head, goal| {
11862 movement::down(map, head, goal, false, text_layout_details)
11863 })
11864 });
11865 }
11866
11867 pub fn context_menu_first(
11868 &mut self,
11869 _: &ContextMenuFirst,
11870 window: &mut Window,
11871 cx: &mut Context<Self>,
11872 ) {
11873 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11874 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
11875 }
11876 }
11877
11878 pub fn context_menu_prev(
11879 &mut self,
11880 _: &ContextMenuPrevious,
11881 window: &mut Window,
11882 cx: &mut Context<Self>,
11883 ) {
11884 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11885 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
11886 }
11887 }
11888
11889 pub fn context_menu_next(
11890 &mut self,
11891 _: &ContextMenuNext,
11892 window: &mut Window,
11893 cx: &mut Context<Self>,
11894 ) {
11895 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11896 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
11897 }
11898 }
11899
11900 pub fn context_menu_last(
11901 &mut self,
11902 _: &ContextMenuLast,
11903 window: &mut Window,
11904 cx: &mut Context<Self>,
11905 ) {
11906 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11907 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
11908 }
11909 }
11910
11911 pub fn move_to_previous_word_start(
11912 &mut self,
11913 _: &MoveToPreviousWordStart,
11914 window: &mut Window,
11915 cx: &mut Context<Self>,
11916 ) {
11917 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11918 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11919 s.move_cursors_with(|map, head, _| {
11920 (
11921 movement::previous_word_start(map, head),
11922 SelectionGoal::None,
11923 )
11924 });
11925 })
11926 }
11927
11928 pub fn move_to_previous_subword_start(
11929 &mut self,
11930 _: &MoveToPreviousSubwordStart,
11931 window: &mut Window,
11932 cx: &mut Context<Self>,
11933 ) {
11934 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11935 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11936 s.move_cursors_with(|map, head, _| {
11937 (
11938 movement::previous_subword_start(map, head),
11939 SelectionGoal::None,
11940 )
11941 });
11942 })
11943 }
11944
11945 pub fn select_to_previous_word_start(
11946 &mut self,
11947 _: &SelectToPreviousWordStart,
11948 window: &mut Window,
11949 cx: &mut Context<Self>,
11950 ) {
11951 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11952 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11953 s.move_heads_with(|map, head, _| {
11954 (
11955 movement::previous_word_start(map, head),
11956 SelectionGoal::None,
11957 )
11958 });
11959 })
11960 }
11961
11962 pub fn select_to_previous_subword_start(
11963 &mut self,
11964 _: &SelectToPreviousSubwordStart,
11965 window: &mut Window,
11966 cx: &mut Context<Self>,
11967 ) {
11968 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11969 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11970 s.move_heads_with(|map, head, _| {
11971 (
11972 movement::previous_subword_start(map, head),
11973 SelectionGoal::None,
11974 )
11975 });
11976 })
11977 }
11978
11979 pub fn delete_to_previous_word_start(
11980 &mut self,
11981 action: &DeleteToPreviousWordStart,
11982 window: &mut Window,
11983 cx: &mut Context<Self>,
11984 ) {
11985 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11986 self.transact(window, cx, |this, window, cx| {
11987 this.select_autoclose_pair(window, cx);
11988 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11989 s.move_with(|map, selection| {
11990 if selection.is_empty() {
11991 let cursor = if action.ignore_newlines {
11992 movement::previous_word_start(map, selection.head())
11993 } else {
11994 movement::previous_word_start_or_newline(map, selection.head())
11995 };
11996 selection.set_head(cursor, SelectionGoal::None);
11997 }
11998 });
11999 });
12000 this.insert("", window, cx);
12001 });
12002 }
12003
12004 pub fn delete_to_previous_subword_start(
12005 &mut self,
12006 _: &DeleteToPreviousSubwordStart,
12007 window: &mut Window,
12008 cx: &mut Context<Self>,
12009 ) {
12010 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12011 self.transact(window, cx, |this, window, cx| {
12012 this.select_autoclose_pair(window, cx);
12013 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12014 s.move_with(|map, selection| {
12015 if selection.is_empty() {
12016 let cursor = movement::previous_subword_start(map, selection.head());
12017 selection.set_head(cursor, SelectionGoal::None);
12018 }
12019 });
12020 });
12021 this.insert("", window, cx);
12022 });
12023 }
12024
12025 pub fn move_to_next_word_end(
12026 &mut self,
12027 _: &MoveToNextWordEnd,
12028 window: &mut Window,
12029 cx: &mut Context<Self>,
12030 ) {
12031 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12032 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12033 s.move_cursors_with(|map, head, _| {
12034 (movement::next_word_end(map, head), SelectionGoal::None)
12035 });
12036 })
12037 }
12038
12039 pub fn move_to_next_subword_end(
12040 &mut self,
12041 _: &MoveToNextSubwordEnd,
12042 window: &mut Window,
12043 cx: &mut Context<Self>,
12044 ) {
12045 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12046 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12047 s.move_cursors_with(|map, head, _| {
12048 (movement::next_subword_end(map, head), SelectionGoal::None)
12049 });
12050 })
12051 }
12052
12053 pub fn select_to_next_word_end(
12054 &mut self,
12055 _: &SelectToNextWordEnd,
12056 window: &mut Window,
12057 cx: &mut Context<Self>,
12058 ) {
12059 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12060 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12061 s.move_heads_with(|map, head, _| {
12062 (movement::next_word_end(map, head), SelectionGoal::None)
12063 });
12064 })
12065 }
12066
12067 pub fn select_to_next_subword_end(
12068 &mut self,
12069 _: &SelectToNextSubwordEnd,
12070 window: &mut Window,
12071 cx: &mut Context<Self>,
12072 ) {
12073 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12074 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12075 s.move_heads_with(|map, head, _| {
12076 (movement::next_subword_end(map, head), SelectionGoal::None)
12077 });
12078 })
12079 }
12080
12081 pub fn delete_to_next_word_end(
12082 &mut self,
12083 action: &DeleteToNextWordEnd,
12084 window: &mut Window,
12085 cx: &mut Context<Self>,
12086 ) {
12087 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12088 self.transact(window, cx, |this, window, cx| {
12089 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12090 s.move_with(|map, selection| {
12091 if selection.is_empty() {
12092 let cursor = if action.ignore_newlines {
12093 movement::next_word_end(map, selection.head())
12094 } else {
12095 movement::next_word_end_or_newline(map, selection.head())
12096 };
12097 selection.set_head(cursor, SelectionGoal::None);
12098 }
12099 });
12100 });
12101 this.insert("", window, cx);
12102 });
12103 }
12104
12105 pub fn delete_to_next_subword_end(
12106 &mut self,
12107 _: &DeleteToNextSubwordEnd,
12108 window: &mut Window,
12109 cx: &mut Context<Self>,
12110 ) {
12111 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12112 self.transact(window, cx, |this, window, cx| {
12113 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12114 s.move_with(|map, selection| {
12115 if selection.is_empty() {
12116 let cursor = movement::next_subword_end(map, selection.head());
12117 selection.set_head(cursor, SelectionGoal::None);
12118 }
12119 });
12120 });
12121 this.insert("", window, cx);
12122 });
12123 }
12124
12125 pub fn move_to_beginning_of_line(
12126 &mut self,
12127 action: &MoveToBeginningOfLine,
12128 window: &mut Window,
12129 cx: &mut Context<Self>,
12130 ) {
12131 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12132 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12133 s.move_cursors_with(|map, head, _| {
12134 (
12135 movement::indented_line_beginning(
12136 map,
12137 head,
12138 action.stop_at_soft_wraps,
12139 action.stop_at_indent,
12140 ),
12141 SelectionGoal::None,
12142 )
12143 });
12144 })
12145 }
12146
12147 pub fn select_to_beginning_of_line(
12148 &mut self,
12149 action: &SelectToBeginningOfLine,
12150 window: &mut Window,
12151 cx: &mut Context<Self>,
12152 ) {
12153 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12154 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12155 s.move_heads_with(|map, head, _| {
12156 (
12157 movement::indented_line_beginning(
12158 map,
12159 head,
12160 action.stop_at_soft_wraps,
12161 action.stop_at_indent,
12162 ),
12163 SelectionGoal::None,
12164 )
12165 });
12166 });
12167 }
12168
12169 pub fn delete_to_beginning_of_line(
12170 &mut self,
12171 action: &DeleteToBeginningOfLine,
12172 window: &mut Window,
12173 cx: &mut Context<Self>,
12174 ) {
12175 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12176 self.transact(window, cx, |this, window, cx| {
12177 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12178 s.move_with(|_, selection| {
12179 selection.reversed = true;
12180 });
12181 });
12182
12183 this.select_to_beginning_of_line(
12184 &SelectToBeginningOfLine {
12185 stop_at_soft_wraps: false,
12186 stop_at_indent: action.stop_at_indent,
12187 },
12188 window,
12189 cx,
12190 );
12191 this.backspace(&Backspace, window, cx);
12192 });
12193 }
12194
12195 pub fn move_to_end_of_line(
12196 &mut self,
12197 action: &MoveToEndOfLine,
12198 window: &mut Window,
12199 cx: &mut Context<Self>,
12200 ) {
12201 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12202 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12203 s.move_cursors_with(|map, head, _| {
12204 (
12205 movement::line_end(map, head, action.stop_at_soft_wraps),
12206 SelectionGoal::None,
12207 )
12208 });
12209 })
12210 }
12211
12212 pub fn select_to_end_of_line(
12213 &mut self,
12214 action: &SelectToEndOfLine,
12215 window: &mut Window,
12216 cx: &mut Context<Self>,
12217 ) {
12218 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12219 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12220 s.move_heads_with(|map, head, _| {
12221 (
12222 movement::line_end(map, head, action.stop_at_soft_wraps),
12223 SelectionGoal::None,
12224 )
12225 });
12226 })
12227 }
12228
12229 pub fn delete_to_end_of_line(
12230 &mut self,
12231 _: &DeleteToEndOfLine,
12232 window: &mut Window,
12233 cx: &mut Context<Self>,
12234 ) {
12235 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12236 self.transact(window, cx, |this, window, cx| {
12237 this.select_to_end_of_line(
12238 &SelectToEndOfLine {
12239 stop_at_soft_wraps: false,
12240 },
12241 window,
12242 cx,
12243 );
12244 this.delete(&Delete, window, cx);
12245 });
12246 }
12247
12248 pub fn cut_to_end_of_line(
12249 &mut self,
12250 _: &CutToEndOfLine,
12251 window: &mut Window,
12252 cx: &mut Context<Self>,
12253 ) {
12254 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12255 self.transact(window, cx, |this, window, cx| {
12256 this.select_to_end_of_line(
12257 &SelectToEndOfLine {
12258 stop_at_soft_wraps: false,
12259 },
12260 window,
12261 cx,
12262 );
12263 this.cut(&Cut, window, cx);
12264 });
12265 }
12266
12267 pub fn move_to_start_of_paragraph(
12268 &mut self,
12269 _: &MoveToStartOfParagraph,
12270 window: &mut Window,
12271 cx: &mut Context<Self>,
12272 ) {
12273 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12274 cx.propagate();
12275 return;
12276 }
12277 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12278 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12279 s.move_with(|map, selection| {
12280 selection.collapse_to(
12281 movement::start_of_paragraph(map, selection.head(), 1),
12282 SelectionGoal::None,
12283 )
12284 });
12285 })
12286 }
12287
12288 pub fn move_to_end_of_paragraph(
12289 &mut self,
12290 _: &MoveToEndOfParagraph,
12291 window: &mut Window,
12292 cx: &mut Context<Self>,
12293 ) {
12294 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12295 cx.propagate();
12296 return;
12297 }
12298 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12299 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12300 s.move_with(|map, selection| {
12301 selection.collapse_to(
12302 movement::end_of_paragraph(map, selection.head(), 1),
12303 SelectionGoal::None,
12304 )
12305 });
12306 })
12307 }
12308
12309 pub fn select_to_start_of_paragraph(
12310 &mut self,
12311 _: &SelectToStartOfParagraph,
12312 window: &mut Window,
12313 cx: &mut Context<Self>,
12314 ) {
12315 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12316 cx.propagate();
12317 return;
12318 }
12319 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12320 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12321 s.move_heads_with(|map, head, _| {
12322 (
12323 movement::start_of_paragraph(map, head, 1),
12324 SelectionGoal::None,
12325 )
12326 });
12327 })
12328 }
12329
12330 pub fn select_to_end_of_paragraph(
12331 &mut self,
12332 _: &SelectToEndOfParagraph,
12333 window: &mut Window,
12334 cx: &mut Context<Self>,
12335 ) {
12336 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12337 cx.propagate();
12338 return;
12339 }
12340 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12341 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12342 s.move_heads_with(|map, head, _| {
12343 (
12344 movement::end_of_paragraph(map, head, 1),
12345 SelectionGoal::None,
12346 )
12347 });
12348 })
12349 }
12350
12351 pub fn move_to_start_of_excerpt(
12352 &mut self,
12353 _: &MoveToStartOfExcerpt,
12354 window: &mut Window,
12355 cx: &mut Context<Self>,
12356 ) {
12357 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12358 cx.propagate();
12359 return;
12360 }
12361 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12362 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12363 s.move_with(|map, selection| {
12364 selection.collapse_to(
12365 movement::start_of_excerpt(
12366 map,
12367 selection.head(),
12368 workspace::searchable::Direction::Prev,
12369 ),
12370 SelectionGoal::None,
12371 )
12372 });
12373 })
12374 }
12375
12376 pub fn move_to_start_of_next_excerpt(
12377 &mut self,
12378 _: &MoveToStartOfNextExcerpt,
12379 window: &mut Window,
12380 cx: &mut Context<Self>,
12381 ) {
12382 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12383 cx.propagate();
12384 return;
12385 }
12386
12387 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12388 s.move_with(|map, selection| {
12389 selection.collapse_to(
12390 movement::start_of_excerpt(
12391 map,
12392 selection.head(),
12393 workspace::searchable::Direction::Next,
12394 ),
12395 SelectionGoal::None,
12396 )
12397 });
12398 })
12399 }
12400
12401 pub fn move_to_end_of_excerpt(
12402 &mut self,
12403 _: &MoveToEndOfExcerpt,
12404 window: &mut Window,
12405 cx: &mut Context<Self>,
12406 ) {
12407 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12408 cx.propagate();
12409 return;
12410 }
12411 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12412 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12413 s.move_with(|map, selection| {
12414 selection.collapse_to(
12415 movement::end_of_excerpt(
12416 map,
12417 selection.head(),
12418 workspace::searchable::Direction::Next,
12419 ),
12420 SelectionGoal::None,
12421 )
12422 });
12423 })
12424 }
12425
12426 pub fn move_to_end_of_previous_excerpt(
12427 &mut self,
12428 _: &MoveToEndOfPreviousExcerpt,
12429 window: &mut Window,
12430 cx: &mut Context<Self>,
12431 ) {
12432 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12433 cx.propagate();
12434 return;
12435 }
12436 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12437 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12438 s.move_with(|map, selection| {
12439 selection.collapse_to(
12440 movement::end_of_excerpt(
12441 map,
12442 selection.head(),
12443 workspace::searchable::Direction::Prev,
12444 ),
12445 SelectionGoal::None,
12446 )
12447 });
12448 })
12449 }
12450
12451 pub fn select_to_start_of_excerpt(
12452 &mut self,
12453 _: &SelectToStartOfExcerpt,
12454 window: &mut Window,
12455 cx: &mut Context<Self>,
12456 ) {
12457 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12458 cx.propagate();
12459 return;
12460 }
12461 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12462 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12463 s.move_heads_with(|map, head, _| {
12464 (
12465 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12466 SelectionGoal::None,
12467 )
12468 });
12469 })
12470 }
12471
12472 pub fn select_to_start_of_next_excerpt(
12473 &mut self,
12474 _: &SelectToStartOfNextExcerpt,
12475 window: &mut Window,
12476 cx: &mut Context<Self>,
12477 ) {
12478 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12479 cx.propagate();
12480 return;
12481 }
12482 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12483 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12484 s.move_heads_with(|map, head, _| {
12485 (
12486 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12487 SelectionGoal::None,
12488 )
12489 });
12490 })
12491 }
12492
12493 pub fn select_to_end_of_excerpt(
12494 &mut self,
12495 _: &SelectToEndOfExcerpt,
12496 window: &mut Window,
12497 cx: &mut Context<Self>,
12498 ) {
12499 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12500 cx.propagate();
12501 return;
12502 }
12503 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12504 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12505 s.move_heads_with(|map, head, _| {
12506 (
12507 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12508 SelectionGoal::None,
12509 )
12510 });
12511 })
12512 }
12513
12514 pub fn select_to_end_of_previous_excerpt(
12515 &mut self,
12516 _: &SelectToEndOfPreviousExcerpt,
12517 window: &mut Window,
12518 cx: &mut Context<Self>,
12519 ) {
12520 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12521 cx.propagate();
12522 return;
12523 }
12524 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12525 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12526 s.move_heads_with(|map, head, _| {
12527 (
12528 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12529 SelectionGoal::None,
12530 )
12531 });
12532 })
12533 }
12534
12535 pub fn move_to_beginning(
12536 &mut self,
12537 _: &MoveToBeginning,
12538 window: &mut Window,
12539 cx: &mut Context<Self>,
12540 ) {
12541 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12542 cx.propagate();
12543 return;
12544 }
12545 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12546 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12547 s.select_ranges(vec![0..0]);
12548 });
12549 }
12550
12551 pub fn select_to_beginning(
12552 &mut self,
12553 _: &SelectToBeginning,
12554 window: &mut Window,
12555 cx: &mut Context<Self>,
12556 ) {
12557 let mut selection = self.selections.last::<Point>(cx);
12558 selection.set_head(Point::zero(), SelectionGoal::None);
12559 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12560 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12561 s.select(vec![selection]);
12562 });
12563 }
12564
12565 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12566 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12567 cx.propagate();
12568 return;
12569 }
12570 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12571 let cursor = self.buffer.read(cx).read(cx).len();
12572 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12573 s.select_ranges(vec![cursor..cursor])
12574 });
12575 }
12576
12577 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12578 self.nav_history = nav_history;
12579 }
12580
12581 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12582 self.nav_history.as_ref()
12583 }
12584
12585 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12586 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12587 }
12588
12589 fn push_to_nav_history(
12590 &mut self,
12591 cursor_anchor: Anchor,
12592 new_position: Option<Point>,
12593 is_deactivate: bool,
12594 cx: &mut Context<Self>,
12595 ) {
12596 if let Some(nav_history) = self.nav_history.as_mut() {
12597 let buffer = self.buffer.read(cx).read(cx);
12598 let cursor_position = cursor_anchor.to_point(&buffer);
12599 let scroll_state = self.scroll_manager.anchor();
12600 let scroll_top_row = scroll_state.top_row(&buffer);
12601 drop(buffer);
12602
12603 if let Some(new_position) = new_position {
12604 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12605 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12606 return;
12607 }
12608 }
12609
12610 nav_history.push(
12611 Some(NavigationData {
12612 cursor_anchor,
12613 cursor_position,
12614 scroll_anchor: scroll_state,
12615 scroll_top_row,
12616 }),
12617 cx,
12618 );
12619 cx.emit(EditorEvent::PushedToNavHistory {
12620 anchor: cursor_anchor,
12621 is_deactivate,
12622 })
12623 }
12624 }
12625
12626 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12627 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12628 let buffer = self.buffer.read(cx).snapshot(cx);
12629 let mut selection = self.selections.first::<usize>(cx);
12630 selection.set_head(buffer.len(), SelectionGoal::None);
12631 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12632 s.select(vec![selection]);
12633 });
12634 }
12635
12636 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12637 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12638 let end = self.buffer.read(cx).read(cx).len();
12639 self.change_selections(None, window, cx, |s| {
12640 s.select_ranges(vec![0..end]);
12641 });
12642 }
12643
12644 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12645 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12646 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12647 let mut selections = self.selections.all::<Point>(cx);
12648 let max_point = display_map.buffer_snapshot.max_point();
12649 for selection in &mut selections {
12650 let rows = selection.spanned_rows(true, &display_map);
12651 selection.start = Point::new(rows.start.0, 0);
12652 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12653 selection.reversed = false;
12654 }
12655 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12656 s.select(selections);
12657 });
12658 }
12659
12660 pub fn split_selection_into_lines(
12661 &mut self,
12662 _: &SplitSelectionIntoLines,
12663 window: &mut Window,
12664 cx: &mut Context<Self>,
12665 ) {
12666 let selections = self
12667 .selections
12668 .all::<Point>(cx)
12669 .into_iter()
12670 .map(|selection| selection.start..selection.end)
12671 .collect::<Vec<_>>();
12672 self.unfold_ranges(&selections, true, true, cx);
12673
12674 let mut new_selection_ranges = Vec::new();
12675 {
12676 let buffer = self.buffer.read(cx).read(cx);
12677 for selection in selections {
12678 for row in selection.start.row..selection.end.row {
12679 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12680 new_selection_ranges.push(cursor..cursor);
12681 }
12682
12683 let is_multiline_selection = selection.start.row != selection.end.row;
12684 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12685 // so this action feels more ergonomic when paired with other selection operations
12686 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12687 if !should_skip_last {
12688 new_selection_ranges.push(selection.end..selection.end);
12689 }
12690 }
12691 }
12692 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12693 s.select_ranges(new_selection_ranges);
12694 });
12695 }
12696
12697 pub fn add_selection_above(
12698 &mut self,
12699 _: &AddSelectionAbove,
12700 window: &mut Window,
12701 cx: &mut Context<Self>,
12702 ) {
12703 self.add_selection(true, window, cx);
12704 }
12705
12706 pub fn add_selection_below(
12707 &mut self,
12708 _: &AddSelectionBelow,
12709 window: &mut Window,
12710 cx: &mut Context<Self>,
12711 ) {
12712 self.add_selection(false, window, cx);
12713 }
12714
12715 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12716 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12717
12718 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12719 let all_selections = self.selections.all::<Point>(cx);
12720 let text_layout_details = self.text_layout_details(window);
12721
12722 let (mut columnar_selections, new_selections_to_columnarize) = {
12723 if let Some(state) = self.add_selections_state.as_ref() {
12724 let columnar_selection_ids: HashSet<_> = state
12725 .groups
12726 .iter()
12727 .flat_map(|group| group.stack.iter())
12728 .copied()
12729 .collect();
12730
12731 all_selections
12732 .into_iter()
12733 .partition(|s| columnar_selection_ids.contains(&s.id))
12734 } else {
12735 (Vec::new(), all_selections)
12736 }
12737 };
12738
12739 let mut state = self
12740 .add_selections_state
12741 .take()
12742 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
12743
12744 for selection in new_selections_to_columnarize {
12745 let range = selection.display_range(&display_map).sorted();
12746 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12747 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12748 let positions = start_x.min(end_x)..start_x.max(end_x);
12749 let mut stack = Vec::new();
12750 for row in range.start.row().0..=range.end.row().0 {
12751 if let Some(selection) = self.selections.build_columnar_selection(
12752 &display_map,
12753 DisplayRow(row),
12754 &positions,
12755 selection.reversed,
12756 &text_layout_details,
12757 ) {
12758 stack.push(selection.id);
12759 columnar_selections.push(selection);
12760 }
12761 }
12762 if !stack.is_empty() {
12763 if above {
12764 stack.reverse();
12765 }
12766 state.groups.push(AddSelectionsGroup { above, stack });
12767 }
12768 }
12769
12770 let mut final_selections = Vec::new();
12771 let end_row = if above {
12772 DisplayRow(0)
12773 } else {
12774 display_map.max_point().row()
12775 };
12776
12777 let mut last_added_item_per_group = HashMap::default();
12778 for group in state.groups.iter_mut() {
12779 if let Some(last_id) = group.stack.last() {
12780 last_added_item_per_group.insert(*last_id, group);
12781 }
12782 }
12783
12784 for selection in columnar_selections {
12785 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
12786 if above == group.above {
12787 let range = selection.display_range(&display_map).sorted();
12788 debug_assert_eq!(range.start.row(), range.end.row());
12789 let mut row = range.start.row();
12790 let positions =
12791 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12792 px(start)..px(end)
12793 } else {
12794 let start_x =
12795 display_map.x_for_display_point(range.start, &text_layout_details);
12796 let end_x =
12797 display_map.x_for_display_point(range.end, &text_layout_details);
12798 start_x.min(end_x)..start_x.max(end_x)
12799 };
12800
12801 let mut maybe_new_selection = None;
12802 while row != end_row {
12803 if above {
12804 row.0 -= 1;
12805 } else {
12806 row.0 += 1;
12807 }
12808 if let Some(new_selection) = self.selections.build_columnar_selection(
12809 &display_map,
12810 row,
12811 &positions,
12812 selection.reversed,
12813 &text_layout_details,
12814 ) {
12815 maybe_new_selection = Some(new_selection);
12816 break;
12817 }
12818 }
12819
12820 if let Some(new_selection) = maybe_new_selection {
12821 group.stack.push(new_selection.id);
12822 if above {
12823 final_selections.push(new_selection);
12824 final_selections.push(selection);
12825 } else {
12826 final_selections.push(selection);
12827 final_selections.push(new_selection);
12828 }
12829 } else {
12830 final_selections.push(selection);
12831 }
12832 } else {
12833 group.stack.pop();
12834 }
12835 } else {
12836 final_selections.push(selection);
12837 }
12838 }
12839
12840 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12841 s.select(final_selections);
12842 });
12843
12844 let final_selection_ids: HashSet<_> = self
12845 .selections
12846 .all::<Point>(cx)
12847 .iter()
12848 .map(|s| s.id)
12849 .collect();
12850 state.groups.retain_mut(|group| {
12851 // selections might get merged above so we remove invalid items from stacks
12852 group.stack.retain(|id| final_selection_ids.contains(id));
12853
12854 // single selection in stack can be treated as initial state
12855 group.stack.len() > 1
12856 });
12857
12858 if !state.groups.is_empty() {
12859 self.add_selections_state = Some(state);
12860 }
12861 }
12862
12863 fn select_match_ranges(
12864 &mut self,
12865 range: Range<usize>,
12866 reversed: bool,
12867 replace_newest: bool,
12868 auto_scroll: Option<Autoscroll>,
12869 window: &mut Window,
12870 cx: &mut Context<Editor>,
12871 ) {
12872 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12873 self.change_selections(auto_scroll, window, cx, |s| {
12874 if replace_newest {
12875 s.delete(s.newest_anchor().id);
12876 }
12877 if reversed {
12878 s.insert_range(range.end..range.start);
12879 } else {
12880 s.insert_range(range);
12881 }
12882 });
12883 }
12884
12885 pub fn select_next_match_internal(
12886 &mut self,
12887 display_map: &DisplaySnapshot,
12888 replace_newest: bool,
12889 autoscroll: Option<Autoscroll>,
12890 window: &mut Window,
12891 cx: &mut Context<Self>,
12892 ) -> Result<()> {
12893 let buffer = &display_map.buffer_snapshot;
12894 let mut selections = self.selections.all::<usize>(cx);
12895 if let Some(mut select_next_state) = self.select_next_state.take() {
12896 let query = &select_next_state.query;
12897 if !select_next_state.done {
12898 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12899 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12900 let mut next_selected_range = None;
12901
12902 let bytes_after_last_selection =
12903 buffer.bytes_in_range(last_selection.end..buffer.len());
12904 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12905 let query_matches = query
12906 .stream_find_iter(bytes_after_last_selection)
12907 .map(|result| (last_selection.end, result))
12908 .chain(
12909 query
12910 .stream_find_iter(bytes_before_first_selection)
12911 .map(|result| (0, result)),
12912 );
12913
12914 for (start_offset, query_match) in query_matches {
12915 let query_match = query_match.unwrap(); // can only fail due to I/O
12916 let offset_range =
12917 start_offset + query_match.start()..start_offset + query_match.end();
12918 let display_range = offset_range.start.to_display_point(display_map)
12919 ..offset_range.end.to_display_point(display_map);
12920
12921 if !select_next_state.wordwise
12922 || (!movement::is_inside_word(display_map, display_range.start)
12923 && !movement::is_inside_word(display_map, display_range.end))
12924 {
12925 // TODO: This is n^2, because we might check all the selections
12926 if !selections
12927 .iter()
12928 .any(|selection| selection.range().overlaps(&offset_range))
12929 {
12930 next_selected_range = Some(offset_range);
12931 break;
12932 }
12933 }
12934 }
12935
12936 if let Some(next_selected_range) = next_selected_range {
12937 self.select_match_ranges(
12938 next_selected_range,
12939 last_selection.reversed,
12940 replace_newest,
12941 autoscroll,
12942 window,
12943 cx,
12944 );
12945 } else {
12946 select_next_state.done = true;
12947 }
12948 }
12949
12950 self.select_next_state = Some(select_next_state);
12951 } else {
12952 let mut only_carets = true;
12953 let mut same_text_selected = true;
12954 let mut selected_text = None;
12955
12956 let mut selections_iter = selections.iter().peekable();
12957 while let Some(selection) = selections_iter.next() {
12958 if selection.start != selection.end {
12959 only_carets = false;
12960 }
12961
12962 if same_text_selected {
12963 if selected_text.is_none() {
12964 selected_text =
12965 Some(buffer.text_for_range(selection.range()).collect::<String>());
12966 }
12967
12968 if let Some(next_selection) = selections_iter.peek() {
12969 if next_selection.range().len() == selection.range().len() {
12970 let next_selected_text = buffer
12971 .text_for_range(next_selection.range())
12972 .collect::<String>();
12973 if Some(next_selected_text) != selected_text {
12974 same_text_selected = false;
12975 selected_text = None;
12976 }
12977 } else {
12978 same_text_selected = false;
12979 selected_text = None;
12980 }
12981 }
12982 }
12983 }
12984
12985 if only_carets {
12986 for selection in &mut selections {
12987 let word_range = movement::surrounding_word(
12988 display_map,
12989 selection.start.to_display_point(display_map),
12990 );
12991 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12992 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12993 selection.goal = SelectionGoal::None;
12994 selection.reversed = false;
12995 self.select_match_ranges(
12996 selection.start..selection.end,
12997 selection.reversed,
12998 replace_newest,
12999 autoscroll,
13000 window,
13001 cx,
13002 );
13003 }
13004
13005 if selections.len() == 1 {
13006 let selection = selections
13007 .last()
13008 .expect("ensured that there's only one selection");
13009 let query = buffer
13010 .text_for_range(selection.start..selection.end)
13011 .collect::<String>();
13012 let is_empty = query.is_empty();
13013 let select_state = SelectNextState {
13014 query: AhoCorasick::new(&[query])?,
13015 wordwise: true,
13016 done: is_empty,
13017 };
13018 self.select_next_state = Some(select_state);
13019 } else {
13020 self.select_next_state = None;
13021 }
13022 } else if let Some(selected_text) = selected_text {
13023 self.select_next_state = Some(SelectNextState {
13024 query: AhoCorasick::new(&[selected_text])?,
13025 wordwise: false,
13026 done: false,
13027 });
13028 self.select_next_match_internal(
13029 display_map,
13030 replace_newest,
13031 autoscroll,
13032 window,
13033 cx,
13034 )?;
13035 }
13036 }
13037 Ok(())
13038 }
13039
13040 pub fn select_all_matches(
13041 &mut self,
13042 _action: &SelectAllMatches,
13043 window: &mut Window,
13044 cx: &mut Context<Self>,
13045 ) -> Result<()> {
13046 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13047
13048 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13049
13050 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13051 let Some(select_next_state) = self.select_next_state.as_mut() else {
13052 return Ok(());
13053 };
13054 if select_next_state.done {
13055 return Ok(());
13056 }
13057
13058 let mut new_selections = Vec::new();
13059
13060 let reversed = self.selections.oldest::<usize>(cx).reversed;
13061 let buffer = &display_map.buffer_snapshot;
13062 let query_matches = select_next_state
13063 .query
13064 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13065
13066 for query_match in query_matches.into_iter() {
13067 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13068 let offset_range = if reversed {
13069 query_match.end()..query_match.start()
13070 } else {
13071 query_match.start()..query_match.end()
13072 };
13073 let display_range = offset_range.start.to_display_point(&display_map)
13074 ..offset_range.end.to_display_point(&display_map);
13075
13076 if !select_next_state.wordwise
13077 || (!movement::is_inside_word(&display_map, display_range.start)
13078 && !movement::is_inside_word(&display_map, display_range.end))
13079 {
13080 new_selections.push(offset_range.start..offset_range.end);
13081 }
13082 }
13083
13084 select_next_state.done = true;
13085 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13086 self.change_selections(None, window, cx, |selections| {
13087 selections.select_ranges(new_selections)
13088 });
13089
13090 Ok(())
13091 }
13092
13093 pub fn select_next(
13094 &mut self,
13095 action: &SelectNext,
13096 window: &mut Window,
13097 cx: &mut Context<Self>,
13098 ) -> Result<()> {
13099 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13100 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13101 self.select_next_match_internal(
13102 &display_map,
13103 action.replace_newest,
13104 Some(Autoscroll::newest()),
13105 window,
13106 cx,
13107 )?;
13108 Ok(())
13109 }
13110
13111 pub fn select_previous(
13112 &mut self,
13113 action: &SelectPrevious,
13114 window: &mut Window,
13115 cx: &mut Context<Self>,
13116 ) -> Result<()> {
13117 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13118 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13119 let buffer = &display_map.buffer_snapshot;
13120 let mut selections = self.selections.all::<usize>(cx);
13121 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13122 let query = &select_prev_state.query;
13123 if !select_prev_state.done {
13124 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13125 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13126 let mut next_selected_range = None;
13127 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13128 let bytes_before_last_selection =
13129 buffer.reversed_bytes_in_range(0..last_selection.start);
13130 let bytes_after_first_selection =
13131 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13132 let query_matches = query
13133 .stream_find_iter(bytes_before_last_selection)
13134 .map(|result| (last_selection.start, result))
13135 .chain(
13136 query
13137 .stream_find_iter(bytes_after_first_selection)
13138 .map(|result| (buffer.len(), result)),
13139 );
13140 for (end_offset, query_match) in query_matches {
13141 let query_match = query_match.unwrap(); // can only fail due to I/O
13142 let offset_range =
13143 end_offset - query_match.end()..end_offset - query_match.start();
13144 let display_range = offset_range.start.to_display_point(&display_map)
13145 ..offset_range.end.to_display_point(&display_map);
13146
13147 if !select_prev_state.wordwise
13148 || (!movement::is_inside_word(&display_map, display_range.start)
13149 && !movement::is_inside_word(&display_map, display_range.end))
13150 {
13151 next_selected_range = Some(offset_range);
13152 break;
13153 }
13154 }
13155
13156 if let Some(next_selected_range) = next_selected_range {
13157 self.select_match_ranges(
13158 next_selected_range,
13159 last_selection.reversed,
13160 action.replace_newest,
13161 Some(Autoscroll::newest()),
13162 window,
13163 cx,
13164 );
13165 } else {
13166 select_prev_state.done = true;
13167 }
13168 }
13169
13170 self.select_prev_state = Some(select_prev_state);
13171 } else {
13172 let mut only_carets = true;
13173 let mut same_text_selected = true;
13174 let mut selected_text = None;
13175
13176 let mut selections_iter = selections.iter().peekable();
13177 while let Some(selection) = selections_iter.next() {
13178 if selection.start != selection.end {
13179 only_carets = false;
13180 }
13181
13182 if same_text_selected {
13183 if selected_text.is_none() {
13184 selected_text =
13185 Some(buffer.text_for_range(selection.range()).collect::<String>());
13186 }
13187
13188 if let Some(next_selection) = selections_iter.peek() {
13189 if next_selection.range().len() == selection.range().len() {
13190 let next_selected_text = buffer
13191 .text_for_range(next_selection.range())
13192 .collect::<String>();
13193 if Some(next_selected_text) != selected_text {
13194 same_text_selected = false;
13195 selected_text = None;
13196 }
13197 } else {
13198 same_text_selected = false;
13199 selected_text = None;
13200 }
13201 }
13202 }
13203 }
13204
13205 if only_carets {
13206 for selection in &mut selections {
13207 let word_range = movement::surrounding_word(
13208 &display_map,
13209 selection.start.to_display_point(&display_map),
13210 );
13211 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
13212 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
13213 selection.goal = SelectionGoal::None;
13214 selection.reversed = false;
13215 self.select_match_ranges(
13216 selection.start..selection.end,
13217 selection.reversed,
13218 action.replace_newest,
13219 Some(Autoscroll::newest()),
13220 window,
13221 cx,
13222 );
13223 }
13224 if selections.len() == 1 {
13225 let selection = selections
13226 .last()
13227 .expect("ensured that there's only one selection");
13228 let query = buffer
13229 .text_for_range(selection.start..selection.end)
13230 .collect::<String>();
13231 let is_empty = query.is_empty();
13232 let select_state = SelectNextState {
13233 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13234 wordwise: true,
13235 done: is_empty,
13236 };
13237 self.select_prev_state = Some(select_state);
13238 } else {
13239 self.select_prev_state = None;
13240 }
13241 } else if let Some(selected_text) = selected_text {
13242 self.select_prev_state = Some(SelectNextState {
13243 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13244 wordwise: false,
13245 done: false,
13246 });
13247 self.select_previous(action, window, cx)?;
13248 }
13249 }
13250 Ok(())
13251 }
13252
13253 pub fn find_next_match(
13254 &mut self,
13255 _: &FindNextMatch,
13256 window: &mut Window,
13257 cx: &mut Context<Self>,
13258 ) -> Result<()> {
13259 let selections = self.selections.disjoint_anchors();
13260 match selections.first() {
13261 Some(first) if selections.len() >= 2 => {
13262 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13263 s.select_ranges([first.range()]);
13264 });
13265 }
13266 _ => self.select_next(
13267 &SelectNext {
13268 replace_newest: true,
13269 },
13270 window,
13271 cx,
13272 )?,
13273 }
13274 Ok(())
13275 }
13276
13277 pub fn find_previous_match(
13278 &mut self,
13279 _: &FindPreviousMatch,
13280 window: &mut Window,
13281 cx: &mut Context<Self>,
13282 ) -> Result<()> {
13283 let selections = self.selections.disjoint_anchors();
13284 match selections.last() {
13285 Some(last) if selections.len() >= 2 => {
13286 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13287 s.select_ranges([last.range()]);
13288 });
13289 }
13290 _ => self.select_previous(
13291 &SelectPrevious {
13292 replace_newest: true,
13293 },
13294 window,
13295 cx,
13296 )?,
13297 }
13298 Ok(())
13299 }
13300
13301 pub fn toggle_comments(
13302 &mut self,
13303 action: &ToggleComments,
13304 window: &mut Window,
13305 cx: &mut Context<Self>,
13306 ) {
13307 if self.read_only(cx) {
13308 return;
13309 }
13310 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13311 let text_layout_details = &self.text_layout_details(window);
13312 self.transact(window, cx, |this, window, cx| {
13313 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13314 let mut edits = Vec::new();
13315 let mut selection_edit_ranges = Vec::new();
13316 let mut last_toggled_row = None;
13317 let snapshot = this.buffer.read(cx).read(cx);
13318 let empty_str: Arc<str> = Arc::default();
13319 let mut suffixes_inserted = Vec::new();
13320 let ignore_indent = action.ignore_indent;
13321
13322 fn comment_prefix_range(
13323 snapshot: &MultiBufferSnapshot,
13324 row: MultiBufferRow,
13325 comment_prefix: &str,
13326 comment_prefix_whitespace: &str,
13327 ignore_indent: bool,
13328 ) -> Range<Point> {
13329 let indent_size = if ignore_indent {
13330 0
13331 } else {
13332 snapshot.indent_size_for_line(row).len
13333 };
13334
13335 let start = Point::new(row.0, indent_size);
13336
13337 let mut line_bytes = snapshot
13338 .bytes_in_range(start..snapshot.max_point())
13339 .flatten()
13340 .copied();
13341
13342 // If this line currently begins with the line comment prefix, then record
13343 // the range containing the prefix.
13344 if line_bytes
13345 .by_ref()
13346 .take(comment_prefix.len())
13347 .eq(comment_prefix.bytes())
13348 {
13349 // Include any whitespace that matches the comment prefix.
13350 let matching_whitespace_len = line_bytes
13351 .zip(comment_prefix_whitespace.bytes())
13352 .take_while(|(a, b)| a == b)
13353 .count() as u32;
13354 let end = Point::new(
13355 start.row,
13356 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13357 );
13358 start..end
13359 } else {
13360 start..start
13361 }
13362 }
13363
13364 fn comment_suffix_range(
13365 snapshot: &MultiBufferSnapshot,
13366 row: MultiBufferRow,
13367 comment_suffix: &str,
13368 comment_suffix_has_leading_space: bool,
13369 ) -> Range<Point> {
13370 let end = Point::new(row.0, snapshot.line_len(row));
13371 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13372
13373 let mut line_end_bytes = snapshot
13374 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13375 .flatten()
13376 .copied();
13377
13378 let leading_space_len = if suffix_start_column > 0
13379 && line_end_bytes.next() == Some(b' ')
13380 && comment_suffix_has_leading_space
13381 {
13382 1
13383 } else {
13384 0
13385 };
13386
13387 // If this line currently begins with the line comment prefix, then record
13388 // the range containing the prefix.
13389 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13390 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13391 start..end
13392 } else {
13393 end..end
13394 }
13395 }
13396
13397 // TODO: Handle selections that cross excerpts
13398 for selection in &mut selections {
13399 let start_column = snapshot
13400 .indent_size_for_line(MultiBufferRow(selection.start.row))
13401 .len;
13402 let language = if let Some(language) =
13403 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13404 {
13405 language
13406 } else {
13407 continue;
13408 };
13409
13410 selection_edit_ranges.clear();
13411
13412 // If multiple selections contain a given row, avoid processing that
13413 // row more than once.
13414 let mut start_row = MultiBufferRow(selection.start.row);
13415 if last_toggled_row == Some(start_row) {
13416 start_row = start_row.next_row();
13417 }
13418 let end_row =
13419 if selection.end.row > selection.start.row && selection.end.column == 0 {
13420 MultiBufferRow(selection.end.row - 1)
13421 } else {
13422 MultiBufferRow(selection.end.row)
13423 };
13424 last_toggled_row = Some(end_row);
13425
13426 if start_row > end_row {
13427 continue;
13428 }
13429
13430 // If the language has line comments, toggle those.
13431 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13432
13433 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13434 if ignore_indent {
13435 full_comment_prefixes = full_comment_prefixes
13436 .into_iter()
13437 .map(|s| Arc::from(s.trim_end()))
13438 .collect();
13439 }
13440
13441 if !full_comment_prefixes.is_empty() {
13442 let first_prefix = full_comment_prefixes
13443 .first()
13444 .expect("prefixes is non-empty");
13445 let prefix_trimmed_lengths = full_comment_prefixes
13446 .iter()
13447 .map(|p| p.trim_end_matches(' ').len())
13448 .collect::<SmallVec<[usize; 4]>>();
13449
13450 let mut all_selection_lines_are_comments = true;
13451
13452 for row in start_row.0..=end_row.0 {
13453 let row = MultiBufferRow(row);
13454 if start_row < end_row && snapshot.is_line_blank(row) {
13455 continue;
13456 }
13457
13458 let prefix_range = full_comment_prefixes
13459 .iter()
13460 .zip(prefix_trimmed_lengths.iter().copied())
13461 .map(|(prefix, trimmed_prefix_len)| {
13462 comment_prefix_range(
13463 snapshot.deref(),
13464 row,
13465 &prefix[..trimmed_prefix_len],
13466 &prefix[trimmed_prefix_len..],
13467 ignore_indent,
13468 )
13469 })
13470 .max_by_key(|range| range.end.column - range.start.column)
13471 .expect("prefixes is non-empty");
13472
13473 if prefix_range.is_empty() {
13474 all_selection_lines_are_comments = false;
13475 }
13476
13477 selection_edit_ranges.push(prefix_range);
13478 }
13479
13480 if all_selection_lines_are_comments {
13481 edits.extend(
13482 selection_edit_ranges
13483 .iter()
13484 .cloned()
13485 .map(|range| (range, empty_str.clone())),
13486 );
13487 } else {
13488 let min_column = selection_edit_ranges
13489 .iter()
13490 .map(|range| range.start.column)
13491 .min()
13492 .unwrap_or(0);
13493 edits.extend(selection_edit_ranges.iter().map(|range| {
13494 let position = Point::new(range.start.row, min_column);
13495 (position..position, first_prefix.clone())
13496 }));
13497 }
13498 } else if let Some((full_comment_prefix, comment_suffix)) =
13499 language.block_comment_delimiters()
13500 {
13501 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13502 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13503 let prefix_range = comment_prefix_range(
13504 snapshot.deref(),
13505 start_row,
13506 comment_prefix,
13507 comment_prefix_whitespace,
13508 ignore_indent,
13509 );
13510 let suffix_range = comment_suffix_range(
13511 snapshot.deref(),
13512 end_row,
13513 comment_suffix.trim_start_matches(' '),
13514 comment_suffix.starts_with(' '),
13515 );
13516
13517 if prefix_range.is_empty() || suffix_range.is_empty() {
13518 edits.push((
13519 prefix_range.start..prefix_range.start,
13520 full_comment_prefix.clone(),
13521 ));
13522 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13523 suffixes_inserted.push((end_row, comment_suffix.len()));
13524 } else {
13525 edits.push((prefix_range, empty_str.clone()));
13526 edits.push((suffix_range, empty_str.clone()));
13527 }
13528 } else {
13529 continue;
13530 }
13531 }
13532
13533 drop(snapshot);
13534 this.buffer.update(cx, |buffer, cx| {
13535 buffer.edit(edits, None, cx);
13536 });
13537
13538 // Adjust selections so that they end before any comment suffixes that
13539 // were inserted.
13540 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13541 let mut selections = this.selections.all::<Point>(cx);
13542 let snapshot = this.buffer.read(cx).read(cx);
13543 for selection in &mut selections {
13544 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13545 match row.cmp(&MultiBufferRow(selection.end.row)) {
13546 Ordering::Less => {
13547 suffixes_inserted.next();
13548 continue;
13549 }
13550 Ordering::Greater => break,
13551 Ordering::Equal => {
13552 if selection.end.column == snapshot.line_len(row) {
13553 if selection.is_empty() {
13554 selection.start.column -= suffix_len as u32;
13555 }
13556 selection.end.column -= suffix_len as u32;
13557 }
13558 break;
13559 }
13560 }
13561 }
13562 }
13563
13564 drop(snapshot);
13565 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13566 s.select(selections)
13567 });
13568
13569 let selections = this.selections.all::<Point>(cx);
13570 let selections_on_single_row = selections.windows(2).all(|selections| {
13571 selections[0].start.row == selections[1].start.row
13572 && selections[0].end.row == selections[1].end.row
13573 && selections[0].start.row == selections[0].end.row
13574 });
13575 let selections_selecting = selections
13576 .iter()
13577 .any(|selection| selection.start != selection.end);
13578 let advance_downwards = action.advance_downwards
13579 && selections_on_single_row
13580 && !selections_selecting
13581 && !matches!(this.mode, EditorMode::SingleLine { .. });
13582
13583 if advance_downwards {
13584 let snapshot = this.buffer.read(cx).snapshot(cx);
13585
13586 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13587 s.move_cursors_with(|display_snapshot, display_point, _| {
13588 let mut point = display_point.to_point(display_snapshot);
13589 point.row += 1;
13590 point = snapshot.clip_point(point, Bias::Left);
13591 let display_point = point.to_display_point(display_snapshot);
13592 let goal = SelectionGoal::HorizontalPosition(
13593 display_snapshot
13594 .x_for_display_point(display_point, text_layout_details)
13595 .into(),
13596 );
13597 (display_point, goal)
13598 })
13599 });
13600 }
13601 });
13602 }
13603
13604 pub fn select_enclosing_symbol(
13605 &mut self,
13606 _: &SelectEnclosingSymbol,
13607 window: &mut Window,
13608 cx: &mut Context<Self>,
13609 ) {
13610 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13611
13612 let buffer = self.buffer.read(cx).snapshot(cx);
13613 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13614
13615 fn update_selection(
13616 selection: &Selection<usize>,
13617 buffer_snap: &MultiBufferSnapshot,
13618 ) -> Option<Selection<usize>> {
13619 let cursor = selection.head();
13620 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13621 for symbol in symbols.iter().rev() {
13622 let start = symbol.range.start.to_offset(buffer_snap);
13623 let end = symbol.range.end.to_offset(buffer_snap);
13624 let new_range = start..end;
13625 if start < selection.start || end > selection.end {
13626 return Some(Selection {
13627 id: selection.id,
13628 start: new_range.start,
13629 end: new_range.end,
13630 goal: SelectionGoal::None,
13631 reversed: selection.reversed,
13632 });
13633 }
13634 }
13635 None
13636 }
13637
13638 let mut selected_larger_symbol = false;
13639 let new_selections = old_selections
13640 .iter()
13641 .map(|selection| match update_selection(selection, &buffer) {
13642 Some(new_selection) => {
13643 if new_selection.range() != selection.range() {
13644 selected_larger_symbol = true;
13645 }
13646 new_selection
13647 }
13648 None => selection.clone(),
13649 })
13650 .collect::<Vec<_>>();
13651
13652 if selected_larger_symbol {
13653 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13654 s.select(new_selections);
13655 });
13656 }
13657 }
13658
13659 pub fn select_larger_syntax_node(
13660 &mut self,
13661 _: &SelectLargerSyntaxNode,
13662 window: &mut Window,
13663 cx: &mut Context<Self>,
13664 ) {
13665 let Some(visible_row_count) = self.visible_row_count() else {
13666 return;
13667 };
13668 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13669 if old_selections.is_empty() {
13670 return;
13671 }
13672
13673 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13674
13675 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13676 let buffer = self.buffer.read(cx).snapshot(cx);
13677
13678 let mut selected_larger_node = false;
13679 let mut new_selections = old_selections
13680 .iter()
13681 .map(|selection| {
13682 let old_range = selection.start..selection.end;
13683
13684 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13685 // manually select word at selection
13686 if ["string_content", "inline"].contains(&node.kind()) {
13687 let word_range = {
13688 let display_point = buffer
13689 .offset_to_point(old_range.start)
13690 .to_display_point(&display_map);
13691 let Range { start, end } =
13692 movement::surrounding_word(&display_map, display_point);
13693 start.to_point(&display_map).to_offset(&buffer)
13694 ..end.to_point(&display_map).to_offset(&buffer)
13695 };
13696 // ignore if word is already selected
13697 if !word_range.is_empty() && old_range != word_range {
13698 let last_word_range = {
13699 let display_point = buffer
13700 .offset_to_point(old_range.end)
13701 .to_display_point(&display_map);
13702 let Range { start, end } =
13703 movement::surrounding_word(&display_map, display_point);
13704 start.to_point(&display_map).to_offset(&buffer)
13705 ..end.to_point(&display_map).to_offset(&buffer)
13706 };
13707 // only select word if start and end point belongs to same word
13708 if word_range == last_word_range {
13709 selected_larger_node = true;
13710 return Selection {
13711 id: selection.id,
13712 start: word_range.start,
13713 end: word_range.end,
13714 goal: SelectionGoal::None,
13715 reversed: selection.reversed,
13716 };
13717 }
13718 }
13719 }
13720 }
13721
13722 let mut new_range = old_range.clone();
13723 while let Some((_node, containing_range)) =
13724 buffer.syntax_ancestor(new_range.clone())
13725 {
13726 new_range = match containing_range {
13727 MultiOrSingleBufferOffsetRange::Single(_) => break,
13728 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13729 };
13730 if !display_map.intersects_fold(new_range.start)
13731 && !display_map.intersects_fold(new_range.end)
13732 {
13733 break;
13734 }
13735 }
13736
13737 selected_larger_node |= new_range != old_range;
13738 Selection {
13739 id: selection.id,
13740 start: new_range.start,
13741 end: new_range.end,
13742 goal: SelectionGoal::None,
13743 reversed: selection.reversed,
13744 }
13745 })
13746 .collect::<Vec<_>>();
13747
13748 if !selected_larger_node {
13749 return; // don't put this call in the history
13750 }
13751
13752 // scroll based on transformation done to the last selection created by the user
13753 let (last_old, last_new) = old_selections
13754 .last()
13755 .zip(new_selections.last().cloned())
13756 .expect("old_selections isn't empty");
13757
13758 // revert selection
13759 let is_selection_reversed = {
13760 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13761 new_selections.last_mut().expect("checked above").reversed =
13762 should_newest_selection_be_reversed;
13763 should_newest_selection_be_reversed
13764 };
13765
13766 if selected_larger_node {
13767 self.select_syntax_node_history.disable_clearing = true;
13768 self.change_selections(None, window, cx, |s| {
13769 s.select(new_selections.clone());
13770 });
13771 self.select_syntax_node_history.disable_clearing = false;
13772 }
13773
13774 let start_row = last_new.start.to_display_point(&display_map).row().0;
13775 let end_row = last_new.end.to_display_point(&display_map).row().0;
13776 let selection_height = end_row - start_row + 1;
13777 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13778
13779 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13780 let scroll_behavior = if fits_on_the_screen {
13781 self.request_autoscroll(Autoscroll::fit(), cx);
13782 SelectSyntaxNodeScrollBehavior::FitSelection
13783 } else if is_selection_reversed {
13784 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13785 SelectSyntaxNodeScrollBehavior::CursorTop
13786 } else {
13787 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13788 SelectSyntaxNodeScrollBehavior::CursorBottom
13789 };
13790
13791 self.select_syntax_node_history.push((
13792 old_selections,
13793 scroll_behavior,
13794 is_selection_reversed,
13795 ));
13796 }
13797
13798 pub fn select_smaller_syntax_node(
13799 &mut self,
13800 _: &SelectSmallerSyntaxNode,
13801 window: &mut Window,
13802 cx: &mut Context<Self>,
13803 ) {
13804 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13805
13806 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13807 self.select_syntax_node_history.pop()
13808 {
13809 if let Some(selection) = selections.last_mut() {
13810 selection.reversed = is_selection_reversed;
13811 }
13812
13813 self.select_syntax_node_history.disable_clearing = true;
13814 self.change_selections(None, window, cx, |s| {
13815 s.select(selections.to_vec());
13816 });
13817 self.select_syntax_node_history.disable_clearing = false;
13818
13819 match scroll_behavior {
13820 SelectSyntaxNodeScrollBehavior::CursorTop => {
13821 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13822 }
13823 SelectSyntaxNodeScrollBehavior::FitSelection => {
13824 self.request_autoscroll(Autoscroll::fit(), cx);
13825 }
13826 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13827 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13828 }
13829 }
13830 }
13831 }
13832
13833 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13834 if !EditorSettings::get_global(cx).gutter.runnables {
13835 self.clear_tasks();
13836 return Task::ready(());
13837 }
13838 let project = self.project.as_ref().map(Entity::downgrade);
13839 let task_sources = self.lsp_task_sources(cx);
13840 let multi_buffer = self.buffer.downgrade();
13841 cx.spawn_in(window, async move |editor, cx| {
13842 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13843 let Some(project) = project.and_then(|p| p.upgrade()) else {
13844 return;
13845 };
13846 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13847 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13848 }) else {
13849 return;
13850 };
13851
13852 let hide_runnables = project
13853 .update(cx, |project, cx| {
13854 // Do not display any test indicators in non-dev server remote projects.
13855 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13856 })
13857 .unwrap_or(true);
13858 if hide_runnables {
13859 return;
13860 }
13861 let new_rows =
13862 cx.background_spawn({
13863 let snapshot = display_snapshot.clone();
13864 async move {
13865 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13866 }
13867 })
13868 .await;
13869 let Ok(lsp_tasks) =
13870 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13871 else {
13872 return;
13873 };
13874 let lsp_tasks = lsp_tasks.await;
13875
13876 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13877 lsp_tasks
13878 .into_iter()
13879 .flat_map(|(kind, tasks)| {
13880 tasks.into_iter().filter_map(move |(location, task)| {
13881 Some((kind.clone(), location?, task))
13882 })
13883 })
13884 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13885 let buffer = location.target.buffer;
13886 let buffer_snapshot = buffer.read(cx).snapshot();
13887 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13888 |(excerpt_id, snapshot, _)| {
13889 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13890 display_snapshot
13891 .buffer_snapshot
13892 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13893 } else {
13894 None
13895 }
13896 },
13897 );
13898 if let Some(offset) = offset {
13899 let task_buffer_range =
13900 location.target.range.to_point(&buffer_snapshot);
13901 let context_buffer_range =
13902 task_buffer_range.to_offset(&buffer_snapshot);
13903 let context_range = BufferOffset(context_buffer_range.start)
13904 ..BufferOffset(context_buffer_range.end);
13905
13906 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13907 .or_insert_with(|| RunnableTasks {
13908 templates: Vec::new(),
13909 offset,
13910 column: task_buffer_range.start.column,
13911 extra_variables: HashMap::default(),
13912 context_range,
13913 })
13914 .templates
13915 .push((kind, task.original_task().clone()));
13916 }
13917
13918 acc
13919 })
13920 }) else {
13921 return;
13922 };
13923
13924 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
13925 buffer.language_settings(cx).tasks.prefer_lsp
13926 }) else {
13927 return;
13928 };
13929
13930 let rows = Self::runnable_rows(
13931 project,
13932 display_snapshot,
13933 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
13934 new_rows,
13935 cx.clone(),
13936 );
13937 editor
13938 .update(cx, |editor, _| {
13939 editor.clear_tasks();
13940 for (key, mut value) in rows {
13941 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13942 value.templates.extend(lsp_tasks.templates);
13943 }
13944
13945 editor.insert_tasks(key, value);
13946 }
13947 for (key, value) in lsp_tasks_by_rows {
13948 editor.insert_tasks(key, value);
13949 }
13950 })
13951 .ok();
13952 })
13953 }
13954 fn fetch_runnable_ranges(
13955 snapshot: &DisplaySnapshot,
13956 range: Range<Anchor>,
13957 ) -> Vec<language::RunnableRange> {
13958 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13959 }
13960
13961 fn runnable_rows(
13962 project: Entity<Project>,
13963 snapshot: DisplaySnapshot,
13964 prefer_lsp: bool,
13965 runnable_ranges: Vec<RunnableRange>,
13966 mut cx: AsyncWindowContext,
13967 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13968 runnable_ranges
13969 .into_iter()
13970 .filter_map(|mut runnable| {
13971 let mut tasks = cx
13972 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13973 .ok()?;
13974 if prefer_lsp {
13975 tasks.retain(|(task_kind, _)| {
13976 !matches!(task_kind, TaskSourceKind::Language { .. })
13977 });
13978 }
13979 if tasks.is_empty() {
13980 return None;
13981 }
13982
13983 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13984
13985 let row = snapshot
13986 .buffer_snapshot
13987 .buffer_line_for_row(MultiBufferRow(point.row))?
13988 .1
13989 .start
13990 .row;
13991
13992 let context_range =
13993 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13994 Some((
13995 (runnable.buffer_id, row),
13996 RunnableTasks {
13997 templates: tasks,
13998 offset: snapshot
13999 .buffer_snapshot
14000 .anchor_before(runnable.run_range.start),
14001 context_range,
14002 column: point.column,
14003 extra_variables: runnable.extra_captures,
14004 },
14005 ))
14006 })
14007 .collect()
14008 }
14009
14010 fn templates_with_tags(
14011 project: &Entity<Project>,
14012 runnable: &mut Runnable,
14013 cx: &mut App,
14014 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
14015 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14016 let (worktree_id, file) = project
14017 .buffer_for_id(runnable.buffer, cx)
14018 .and_then(|buffer| buffer.read(cx).file())
14019 .map(|file| (file.worktree_id(cx), file.clone()))
14020 .unzip();
14021
14022 (
14023 project.task_store().read(cx).task_inventory().cloned(),
14024 worktree_id,
14025 file,
14026 )
14027 });
14028
14029 let mut templates_with_tags = mem::take(&mut runnable.tags)
14030 .into_iter()
14031 .flat_map(|RunnableTag(tag)| {
14032 inventory
14033 .as_ref()
14034 .into_iter()
14035 .flat_map(|inventory| {
14036 inventory.read(cx).list_tasks(
14037 file.clone(),
14038 Some(runnable.language.clone()),
14039 worktree_id,
14040 cx,
14041 )
14042 })
14043 .filter(move |(_, template)| {
14044 template.tags.iter().any(|source_tag| source_tag == &tag)
14045 })
14046 })
14047 .sorted_by_key(|(kind, _)| kind.to_owned())
14048 .collect::<Vec<_>>();
14049 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14050 // Strongest source wins; if we have worktree tag binding, prefer that to
14051 // global and language bindings;
14052 // if we have a global binding, prefer that to language binding.
14053 let first_mismatch = templates_with_tags
14054 .iter()
14055 .position(|(tag_source, _)| tag_source != leading_tag_source);
14056 if let Some(index) = first_mismatch {
14057 templates_with_tags.truncate(index);
14058 }
14059 }
14060
14061 templates_with_tags
14062 }
14063
14064 pub fn move_to_enclosing_bracket(
14065 &mut self,
14066 _: &MoveToEnclosingBracket,
14067 window: &mut Window,
14068 cx: &mut Context<Self>,
14069 ) {
14070 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14071 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14072 s.move_offsets_with(|snapshot, selection| {
14073 let Some(enclosing_bracket_ranges) =
14074 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14075 else {
14076 return;
14077 };
14078
14079 let mut best_length = usize::MAX;
14080 let mut best_inside = false;
14081 let mut best_in_bracket_range = false;
14082 let mut best_destination = None;
14083 for (open, close) in enclosing_bracket_ranges {
14084 let close = close.to_inclusive();
14085 let length = close.end() - open.start;
14086 let inside = selection.start >= open.end && selection.end <= *close.start();
14087 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14088 || close.contains(&selection.head());
14089
14090 // If best is next to a bracket and current isn't, skip
14091 if !in_bracket_range && best_in_bracket_range {
14092 continue;
14093 }
14094
14095 // Prefer smaller lengths unless best is inside and current isn't
14096 if length > best_length && (best_inside || !inside) {
14097 continue;
14098 }
14099
14100 best_length = length;
14101 best_inside = inside;
14102 best_in_bracket_range = in_bracket_range;
14103 best_destination = Some(
14104 if close.contains(&selection.start) && close.contains(&selection.end) {
14105 if inside { open.end } else { open.start }
14106 } else if inside {
14107 *close.start()
14108 } else {
14109 *close.end()
14110 },
14111 );
14112 }
14113
14114 if let Some(destination) = best_destination {
14115 selection.collapse_to(destination, SelectionGoal::None);
14116 }
14117 })
14118 });
14119 }
14120
14121 pub fn undo_selection(
14122 &mut self,
14123 _: &UndoSelection,
14124 window: &mut Window,
14125 cx: &mut Context<Self>,
14126 ) {
14127 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14128 self.end_selection(window, cx);
14129 self.selection_history.mode = SelectionHistoryMode::Undoing;
14130 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14131 self.change_selections(None, window, cx, |s| {
14132 s.select_anchors(entry.selections.to_vec())
14133 });
14134 self.select_next_state = entry.select_next_state;
14135 self.select_prev_state = entry.select_prev_state;
14136 self.add_selections_state = entry.add_selections_state;
14137 self.request_autoscroll(Autoscroll::newest(), cx);
14138 }
14139 self.selection_history.mode = SelectionHistoryMode::Normal;
14140 }
14141
14142 pub fn redo_selection(
14143 &mut self,
14144 _: &RedoSelection,
14145 window: &mut Window,
14146 cx: &mut Context<Self>,
14147 ) {
14148 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14149 self.end_selection(window, cx);
14150 self.selection_history.mode = SelectionHistoryMode::Redoing;
14151 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14152 self.change_selections(None, window, cx, |s| {
14153 s.select_anchors(entry.selections.to_vec())
14154 });
14155 self.select_next_state = entry.select_next_state;
14156 self.select_prev_state = entry.select_prev_state;
14157 self.add_selections_state = entry.add_selections_state;
14158 self.request_autoscroll(Autoscroll::newest(), cx);
14159 }
14160 self.selection_history.mode = SelectionHistoryMode::Normal;
14161 }
14162
14163 pub fn expand_excerpts(
14164 &mut self,
14165 action: &ExpandExcerpts,
14166 _: &mut Window,
14167 cx: &mut Context<Self>,
14168 ) {
14169 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14170 }
14171
14172 pub fn expand_excerpts_down(
14173 &mut self,
14174 action: &ExpandExcerptsDown,
14175 _: &mut Window,
14176 cx: &mut Context<Self>,
14177 ) {
14178 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14179 }
14180
14181 pub fn expand_excerpts_up(
14182 &mut self,
14183 action: &ExpandExcerptsUp,
14184 _: &mut Window,
14185 cx: &mut Context<Self>,
14186 ) {
14187 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14188 }
14189
14190 pub fn expand_excerpts_for_direction(
14191 &mut self,
14192 lines: u32,
14193 direction: ExpandExcerptDirection,
14194
14195 cx: &mut Context<Self>,
14196 ) {
14197 let selections = self.selections.disjoint_anchors();
14198
14199 let lines = if lines == 0 {
14200 EditorSettings::get_global(cx).expand_excerpt_lines
14201 } else {
14202 lines
14203 };
14204
14205 self.buffer.update(cx, |buffer, cx| {
14206 let snapshot = buffer.snapshot(cx);
14207 let mut excerpt_ids = selections
14208 .iter()
14209 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14210 .collect::<Vec<_>>();
14211 excerpt_ids.sort();
14212 excerpt_ids.dedup();
14213 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14214 })
14215 }
14216
14217 pub fn expand_excerpt(
14218 &mut self,
14219 excerpt: ExcerptId,
14220 direction: ExpandExcerptDirection,
14221 window: &mut Window,
14222 cx: &mut Context<Self>,
14223 ) {
14224 let current_scroll_position = self.scroll_position(cx);
14225 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14226 let mut should_scroll_up = false;
14227
14228 if direction == ExpandExcerptDirection::Down {
14229 let multi_buffer = self.buffer.read(cx);
14230 let snapshot = multi_buffer.snapshot(cx);
14231 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14232 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14233 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14234 let buffer_snapshot = buffer.read(cx).snapshot();
14235 let excerpt_end_row =
14236 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14237 let last_row = buffer_snapshot.max_point().row;
14238 let lines_below = last_row.saturating_sub(excerpt_end_row);
14239 should_scroll_up = lines_below >= lines_to_expand;
14240 }
14241 }
14242 }
14243 }
14244
14245 self.buffer.update(cx, |buffer, cx| {
14246 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14247 });
14248
14249 if should_scroll_up {
14250 let new_scroll_position =
14251 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14252 self.set_scroll_position(new_scroll_position, window, cx);
14253 }
14254 }
14255
14256 pub fn go_to_singleton_buffer_point(
14257 &mut self,
14258 point: Point,
14259 window: &mut Window,
14260 cx: &mut Context<Self>,
14261 ) {
14262 self.go_to_singleton_buffer_range(point..point, window, cx);
14263 }
14264
14265 pub fn go_to_singleton_buffer_range(
14266 &mut self,
14267 range: Range<Point>,
14268 window: &mut Window,
14269 cx: &mut Context<Self>,
14270 ) {
14271 let multibuffer = self.buffer().read(cx);
14272 let Some(buffer) = multibuffer.as_singleton() else {
14273 return;
14274 };
14275 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14276 return;
14277 };
14278 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14279 return;
14280 };
14281 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14282 s.select_anchor_ranges([start..end])
14283 });
14284 }
14285
14286 pub fn go_to_diagnostic(
14287 &mut self,
14288 _: &GoToDiagnostic,
14289 window: &mut Window,
14290 cx: &mut Context<Self>,
14291 ) {
14292 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14293 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14294 }
14295
14296 pub fn go_to_prev_diagnostic(
14297 &mut self,
14298 _: &GoToPreviousDiagnostic,
14299 window: &mut Window,
14300 cx: &mut Context<Self>,
14301 ) {
14302 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14303 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14304 }
14305
14306 pub fn go_to_diagnostic_impl(
14307 &mut self,
14308 direction: Direction,
14309 window: &mut Window,
14310 cx: &mut Context<Self>,
14311 ) {
14312 let buffer = self.buffer.read(cx).snapshot(cx);
14313 let selection = self.selections.newest::<usize>(cx);
14314
14315 let mut active_group_id = None;
14316 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14317 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14318 active_group_id = Some(active_group.group_id);
14319 }
14320 }
14321
14322 fn filtered(
14323 snapshot: EditorSnapshot,
14324 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14325 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14326 diagnostics
14327 .filter(|entry| entry.range.start != entry.range.end)
14328 .filter(|entry| !entry.diagnostic.is_unnecessary)
14329 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14330 }
14331
14332 let snapshot = self.snapshot(window, cx);
14333 let before = filtered(
14334 snapshot.clone(),
14335 buffer
14336 .diagnostics_in_range(0..selection.start)
14337 .filter(|entry| entry.range.start <= selection.start),
14338 );
14339 let after = filtered(
14340 snapshot,
14341 buffer
14342 .diagnostics_in_range(selection.start..buffer.len())
14343 .filter(|entry| entry.range.start >= selection.start),
14344 );
14345
14346 let mut found: Option<DiagnosticEntry<usize>> = None;
14347 if direction == Direction::Prev {
14348 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14349 {
14350 for diagnostic in prev_diagnostics.into_iter().rev() {
14351 if diagnostic.range.start != selection.start
14352 || active_group_id
14353 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14354 {
14355 found = Some(diagnostic);
14356 break 'outer;
14357 }
14358 }
14359 }
14360 } else {
14361 for diagnostic in after.chain(before) {
14362 if diagnostic.range.start != selection.start
14363 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14364 {
14365 found = Some(diagnostic);
14366 break;
14367 }
14368 }
14369 }
14370 let Some(next_diagnostic) = found else {
14371 return;
14372 };
14373
14374 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14375 return;
14376 };
14377 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14378 s.select_ranges(vec![
14379 next_diagnostic.range.start..next_diagnostic.range.start,
14380 ])
14381 });
14382 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14383 self.refresh_inline_completion(false, true, window, cx);
14384 }
14385
14386 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14387 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14388 let snapshot = self.snapshot(window, cx);
14389 let selection = self.selections.newest::<Point>(cx);
14390 self.go_to_hunk_before_or_after_position(
14391 &snapshot,
14392 selection.head(),
14393 Direction::Next,
14394 window,
14395 cx,
14396 );
14397 }
14398
14399 pub fn go_to_hunk_before_or_after_position(
14400 &mut self,
14401 snapshot: &EditorSnapshot,
14402 position: Point,
14403 direction: Direction,
14404 window: &mut Window,
14405 cx: &mut Context<Editor>,
14406 ) {
14407 let row = if direction == Direction::Next {
14408 self.hunk_after_position(snapshot, position)
14409 .map(|hunk| hunk.row_range.start)
14410 } else {
14411 self.hunk_before_position(snapshot, position)
14412 };
14413
14414 if let Some(row) = row {
14415 let destination = Point::new(row.0, 0);
14416 let autoscroll = Autoscroll::center();
14417
14418 self.unfold_ranges(&[destination..destination], false, false, cx);
14419 self.change_selections(Some(autoscroll), window, cx, |s| {
14420 s.select_ranges([destination..destination]);
14421 });
14422 }
14423 }
14424
14425 fn hunk_after_position(
14426 &mut self,
14427 snapshot: &EditorSnapshot,
14428 position: Point,
14429 ) -> Option<MultiBufferDiffHunk> {
14430 snapshot
14431 .buffer_snapshot
14432 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14433 .find(|hunk| hunk.row_range.start.0 > position.row)
14434 .or_else(|| {
14435 snapshot
14436 .buffer_snapshot
14437 .diff_hunks_in_range(Point::zero()..position)
14438 .find(|hunk| hunk.row_range.end.0 < position.row)
14439 })
14440 }
14441
14442 fn go_to_prev_hunk(
14443 &mut self,
14444 _: &GoToPreviousHunk,
14445 window: &mut Window,
14446 cx: &mut Context<Self>,
14447 ) {
14448 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14449 let snapshot = self.snapshot(window, cx);
14450 let selection = self.selections.newest::<Point>(cx);
14451 self.go_to_hunk_before_or_after_position(
14452 &snapshot,
14453 selection.head(),
14454 Direction::Prev,
14455 window,
14456 cx,
14457 );
14458 }
14459
14460 fn hunk_before_position(
14461 &mut self,
14462 snapshot: &EditorSnapshot,
14463 position: Point,
14464 ) -> Option<MultiBufferRow> {
14465 snapshot
14466 .buffer_snapshot
14467 .diff_hunk_before(position)
14468 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14469 }
14470
14471 fn go_to_next_change(
14472 &mut self,
14473 _: &GoToNextChange,
14474 window: &mut Window,
14475 cx: &mut Context<Self>,
14476 ) {
14477 if let Some(selections) = self
14478 .change_list
14479 .next_change(1, Direction::Next)
14480 .map(|s| s.to_vec())
14481 {
14482 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14483 let map = s.display_map();
14484 s.select_display_ranges(selections.iter().map(|a| {
14485 let point = a.to_display_point(&map);
14486 point..point
14487 }))
14488 })
14489 }
14490 }
14491
14492 fn go_to_previous_change(
14493 &mut self,
14494 _: &GoToPreviousChange,
14495 window: &mut Window,
14496 cx: &mut Context<Self>,
14497 ) {
14498 if let Some(selections) = self
14499 .change_list
14500 .next_change(1, Direction::Prev)
14501 .map(|s| s.to_vec())
14502 {
14503 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14504 let map = s.display_map();
14505 s.select_display_ranges(selections.iter().map(|a| {
14506 let point = a.to_display_point(&map);
14507 point..point
14508 }))
14509 })
14510 }
14511 }
14512
14513 fn go_to_line<T: 'static>(
14514 &mut self,
14515 position: Anchor,
14516 highlight_color: Option<Hsla>,
14517 window: &mut Window,
14518 cx: &mut Context<Self>,
14519 ) {
14520 let snapshot = self.snapshot(window, cx).display_snapshot;
14521 let position = position.to_point(&snapshot.buffer_snapshot);
14522 let start = snapshot
14523 .buffer_snapshot
14524 .clip_point(Point::new(position.row, 0), Bias::Left);
14525 let end = start + Point::new(1, 0);
14526 let start = snapshot.buffer_snapshot.anchor_before(start);
14527 let end = snapshot.buffer_snapshot.anchor_before(end);
14528
14529 self.highlight_rows::<T>(
14530 start..end,
14531 highlight_color
14532 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14533 Default::default(),
14534 cx,
14535 );
14536
14537 if self.buffer.read(cx).is_singleton() {
14538 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14539 }
14540 }
14541
14542 pub fn go_to_definition(
14543 &mut self,
14544 _: &GoToDefinition,
14545 window: &mut Window,
14546 cx: &mut Context<Self>,
14547 ) -> Task<Result<Navigated>> {
14548 let definition =
14549 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14550 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14551 cx.spawn_in(window, async move |editor, cx| {
14552 if definition.await? == Navigated::Yes {
14553 return Ok(Navigated::Yes);
14554 }
14555 match fallback_strategy {
14556 GoToDefinitionFallback::None => Ok(Navigated::No),
14557 GoToDefinitionFallback::FindAllReferences => {
14558 match editor.update_in(cx, |editor, window, cx| {
14559 editor.find_all_references(&FindAllReferences, window, cx)
14560 })? {
14561 Some(references) => references.await,
14562 None => Ok(Navigated::No),
14563 }
14564 }
14565 }
14566 })
14567 }
14568
14569 pub fn go_to_declaration(
14570 &mut self,
14571 _: &GoToDeclaration,
14572 window: &mut Window,
14573 cx: &mut Context<Self>,
14574 ) -> Task<Result<Navigated>> {
14575 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14576 }
14577
14578 pub fn go_to_declaration_split(
14579 &mut self,
14580 _: &GoToDeclaration,
14581 window: &mut Window,
14582 cx: &mut Context<Self>,
14583 ) -> Task<Result<Navigated>> {
14584 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14585 }
14586
14587 pub fn go_to_implementation(
14588 &mut self,
14589 _: &GoToImplementation,
14590 window: &mut Window,
14591 cx: &mut Context<Self>,
14592 ) -> Task<Result<Navigated>> {
14593 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14594 }
14595
14596 pub fn go_to_implementation_split(
14597 &mut self,
14598 _: &GoToImplementationSplit,
14599 window: &mut Window,
14600 cx: &mut Context<Self>,
14601 ) -> Task<Result<Navigated>> {
14602 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14603 }
14604
14605 pub fn go_to_type_definition(
14606 &mut self,
14607 _: &GoToTypeDefinition,
14608 window: &mut Window,
14609 cx: &mut Context<Self>,
14610 ) -> Task<Result<Navigated>> {
14611 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14612 }
14613
14614 pub fn go_to_definition_split(
14615 &mut self,
14616 _: &GoToDefinitionSplit,
14617 window: &mut Window,
14618 cx: &mut Context<Self>,
14619 ) -> Task<Result<Navigated>> {
14620 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14621 }
14622
14623 pub fn go_to_type_definition_split(
14624 &mut self,
14625 _: &GoToTypeDefinitionSplit,
14626 window: &mut Window,
14627 cx: &mut Context<Self>,
14628 ) -> Task<Result<Navigated>> {
14629 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14630 }
14631
14632 fn go_to_definition_of_kind(
14633 &mut self,
14634 kind: GotoDefinitionKind,
14635 split: bool,
14636 window: &mut Window,
14637 cx: &mut Context<Self>,
14638 ) -> Task<Result<Navigated>> {
14639 let Some(provider) = self.semantics_provider.clone() else {
14640 return Task::ready(Ok(Navigated::No));
14641 };
14642 let head = self.selections.newest::<usize>(cx).head();
14643 let buffer = self.buffer.read(cx);
14644 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14645 text_anchor
14646 } else {
14647 return Task::ready(Ok(Navigated::No));
14648 };
14649
14650 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14651 return Task::ready(Ok(Navigated::No));
14652 };
14653
14654 cx.spawn_in(window, async move |editor, cx| {
14655 let definitions = definitions.await?;
14656 let navigated = editor
14657 .update_in(cx, |editor, window, cx| {
14658 editor.navigate_to_hover_links(
14659 Some(kind),
14660 definitions
14661 .into_iter()
14662 .filter(|location| {
14663 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14664 })
14665 .map(HoverLink::Text)
14666 .collect::<Vec<_>>(),
14667 split,
14668 window,
14669 cx,
14670 )
14671 })?
14672 .await?;
14673 anyhow::Ok(navigated)
14674 })
14675 }
14676
14677 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14678 let selection = self.selections.newest_anchor();
14679 let head = selection.head();
14680 let tail = selection.tail();
14681
14682 let Some((buffer, start_position)) =
14683 self.buffer.read(cx).text_anchor_for_position(head, cx)
14684 else {
14685 return;
14686 };
14687
14688 let end_position = if head != tail {
14689 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14690 return;
14691 };
14692 Some(pos)
14693 } else {
14694 None
14695 };
14696
14697 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14698 let url = if let Some(end_pos) = end_position {
14699 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14700 } else {
14701 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14702 };
14703
14704 if let Some(url) = url {
14705 editor.update(cx, |_, cx| {
14706 cx.open_url(&url);
14707 })
14708 } else {
14709 Ok(())
14710 }
14711 });
14712
14713 url_finder.detach();
14714 }
14715
14716 pub fn open_selected_filename(
14717 &mut self,
14718 _: &OpenSelectedFilename,
14719 window: &mut Window,
14720 cx: &mut Context<Self>,
14721 ) {
14722 let Some(workspace) = self.workspace() else {
14723 return;
14724 };
14725
14726 let position = self.selections.newest_anchor().head();
14727
14728 let Some((buffer, buffer_position)) =
14729 self.buffer.read(cx).text_anchor_for_position(position, cx)
14730 else {
14731 return;
14732 };
14733
14734 let project = self.project.clone();
14735
14736 cx.spawn_in(window, async move |_, cx| {
14737 let result = find_file(&buffer, project, buffer_position, cx).await;
14738
14739 if let Some((_, path)) = result {
14740 workspace
14741 .update_in(cx, |workspace, window, cx| {
14742 workspace.open_resolved_path(path, window, cx)
14743 })?
14744 .await?;
14745 }
14746 anyhow::Ok(())
14747 })
14748 .detach();
14749 }
14750
14751 pub(crate) fn navigate_to_hover_links(
14752 &mut self,
14753 kind: Option<GotoDefinitionKind>,
14754 mut definitions: Vec<HoverLink>,
14755 split: bool,
14756 window: &mut Window,
14757 cx: &mut Context<Editor>,
14758 ) -> Task<Result<Navigated>> {
14759 // If there is one definition, just open it directly
14760 if definitions.len() == 1 {
14761 let definition = definitions.pop().unwrap();
14762
14763 enum TargetTaskResult {
14764 Location(Option<Location>),
14765 AlreadyNavigated,
14766 }
14767
14768 let target_task = match definition {
14769 HoverLink::Text(link) => {
14770 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14771 }
14772 HoverLink::InlayHint(lsp_location, server_id) => {
14773 let computation =
14774 self.compute_target_location(lsp_location, server_id, window, cx);
14775 cx.background_spawn(async move {
14776 let location = computation.await?;
14777 Ok(TargetTaskResult::Location(location))
14778 })
14779 }
14780 HoverLink::Url(url) => {
14781 cx.open_url(&url);
14782 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14783 }
14784 HoverLink::File(path) => {
14785 if let Some(workspace) = self.workspace() {
14786 cx.spawn_in(window, async move |_, cx| {
14787 workspace
14788 .update_in(cx, |workspace, window, cx| {
14789 workspace.open_resolved_path(path, window, cx)
14790 })?
14791 .await
14792 .map(|_| TargetTaskResult::AlreadyNavigated)
14793 })
14794 } else {
14795 Task::ready(Ok(TargetTaskResult::Location(None)))
14796 }
14797 }
14798 };
14799 cx.spawn_in(window, async move |editor, cx| {
14800 let target = match target_task.await.context("target resolution task")? {
14801 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14802 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14803 TargetTaskResult::Location(Some(target)) => target,
14804 };
14805
14806 editor.update_in(cx, |editor, window, cx| {
14807 let Some(workspace) = editor.workspace() else {
14808 return Navigated::No;
14809 };
14810 let pane = workspace.read(cx).active_pane().clone();
14811
14812 let range = target.range.to_point(target.buffer.read(cx));
14813 let range = editor.range_for_match(&range);
14814 let range = collapse_multiline_range(range);
14815
14816 if !split
14817 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14818 {
14819 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14820 } else {
14821 window.defer(cx, move |window, cx| {
14822 let target_editor: Entity<Self> =
14823 workspace.update(cx, |workspace, cx| {
14824 let pane = if split {
14825 workspace.adjacent_pane(window, cx)
14826 } else {
14827 workspace.active_pane().clone()
14828 };
14829
14830 workspace.open_project_item(
14831 pane,
14832 target.buffer.clone(),
14833 true,
14834 true,
14835 window,
14836 cx,
14837 )
14838 });
14839 target_editor.update(cx, |target_editor, cx| {
14840 // When selecting a definition in a different buffer, disable the nav history
14841 // to avoid creating a history entry at the previous cursor location.
14842 pane.update(cx, |pane, _| pane.disable_history());
14843 target_editor.go_to_singleton_buffer_range(range, window, cx);
14844 pane.update(cx, |pane, _| pane.enable_history());
14845 });
14846 });
14847 }
14848 Navigated::Yes
14849 })
14850 })
14851 } else if !definitions.is_empty() {
14852 cx.spawn_in(window, async move |editor, cx| {
14853 let (title, location_tasks, workspace) = editor
14854 .update_in(cx, |editor, window, cx| {
14855 let tab_kind = match kind {
14856 Some(GotoDefinitionKind::Implementation) => "Implementations",
14857 _ => "Definitions",
14858 };
14859 let title = definitions
14860 .iter()
14861 .find_map(|definition| match definition {
14862 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14863 let buffer = origin.buffer.read(cx);
14864 format!(
14865 "{} for {}",
14866 tab_kind,
14867 buffer
14868 .text_for_range(origin.range.clone())
14869 .collect::<String>()
14870 )
14871 }),
14872 HoverLink::InlayHint(_, _) => None,
14873 HoverLink::Url(_) => None,
14874 HoverLink::File(_) => None,
14875 })
14876 .unwrap_or(tab_kind.to_string());
14877 let location_tasks = definitions
14878 .into_iter()
14879 .map(|definition| match definition {
14880 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14881 HoverLink::InlayHint(lsp_location, server_id) => editor
14882 .compute_target_location(lsp_location, server_id, window, cx),
14883 HoverLink::Url(_) => Task::ready(Ok(None)),
14884 HoverLink::File(_) => Task::ready(Ok(None)),
14885 })
14886 .collect::<Vec<_>>();
14887 (title, location_tasks, editor.workspace().clone())
14888 })
14889 .context("location tasks preparation")?;
14890
14891 let locations = future::join_all(location_tasks)
14892 .await
14893 .into_iter()
14894 .filter_map(|location| location.transpose())
14895 .collect::<Result<_>>()
14896 .context("location tasks")?;
14897
14898 let Some(workspace) = workspace else {
14899 return Ok(Navigated::No);
14900 };
14901 let opened = workspace
14902 .update_in(cx, |workspace, window, cx| {
14903 Self::open_locations_in_multibuffer(
14904 workspace,
14905 locations,
14906 title,
14907 split,
14908 MultibufferSelectionMode::First,
14909 window,
14910 cx,
14911 )
14912 })
14913 .ok();
14914
14915 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14916 })
14917 } else {
14918 Task::ready(Ok(Navigated::No))
14919 }
14920 }
14921
14922 fn compute_target_location(
14923 &self,
14924 lsp_location: lsp::Location,
14925 server_id: LanguageServerId,
14926 window: &mut Window,
14927 cx: &mut Context<Self>,
14928 ) -> Task<anyhow::Result<Option<Location>>> {
14929 let Some(project) = self.project.clone() else {
14930 return Task::ready(Ok(None));
14931 };
14932
14933 cx.spawn_in(window, async move |editor, cx| {
14934 let location_task = editor.update(cx, |_, cx| {
14935 project.update(cx, |project, cx| {
14936 let language_server_name = project
14937 .language_server_statuses(cx)
14938 .find(|(id, _)| server_id == *id)
14939 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14940 language_server_name.map(|language_server_name| {
14941 project.open_local_buffer_via_lsp(
14942 lsp_location.uri.clone(),
14943 server_id,
14944 language_server_name,
14945 cx,
14946 )
14947 })
14948 })
14949 })?;
14950 let location = match location_task {
14951 Some(task) => Some({
14952 let target_buffer_handle = task.await.context("open local buffer")?;
14953 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
14954 let target_start = target_buffer
14955 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14956 let target_end = target_buffer
14957 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14958 target_buffer.anchor_after(target_start)
14959 ..target_buffer.anchor_before(target_end)
14960 })?;
14961 Location {
14962 buffer: target_buffer_handle,
14963 range,
14964 }
14965 }),
14966 None => None,
14967 };
14968 Ok(location)
14969 })
14970 }
14971
14972 pub fn find_all_references(
14973 &mut self,
14974 _: &FindAllReferences,
14975 window: &mut Window,
14976 cx: &mut Context<Self>,
14977 ) -> Option<Task<Result<Navigated>>> {
14978 let selection = self.selections.newest::<usize>(cx);
14979 let multi_buffer = self.buffer.read(cx);
14980 let head = selection.head();
14981
14982 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14983 let head_anchor = multi_buffer_snapshot.anchor_at(
14984 head,
14985 if head < selection.tail() {
14986 Bias::Right
14987 } else {
14988 Bias::Left
14989 },
14990 );
14991
14992 match self
14993 .find_all_references_task_sources
14994 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14995 {
14996 Ok(_) => {
14997 log::info!(
14998 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14999 );
15000 return None;
15001 }
15002 Err(i) => {
15003 self.find_all_references_task_sources.insert(i, head_anchor);
15004 }
15005 }
15006
15007 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15008 let workspace = self.workspace()?;
15009 let project = workspace.read(cx).project().clone();
15010 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15011 Some(cx.spawn_in(window, async move |editor, cx| {
15012 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15013 if let Ok(i) = editor
15014 .find_all_references_task_sources
15015 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15016 {
15017 editor.find_all_references_task_sources.remove(i);
15018 }
15019 });
15020
15021 let locations = references.await?;
15022 if locations.is_empty() {
15023 return anyhow::Ok(Navigated::No);
15024 }
15025
15026 workspace.update_in(cx, |workspace, window, cx| {
15027 let title = locations
15028 .first()
15029 .as_ref()
15030 .map(|location| {
15031 let buffer = location.buffer.read(cx);
15032 format!(
15033 "References to `{}`",
15034 buffer
15035 .text_for_range(location.range.clone())
15036 .collect::<String>()
15037 )
15038 })
15039 .unwrap();
15040 Self::open_locations_in_multibuffer(
15041 workspace,
15042 locations,
15043 title,
15044 false,
15045 MultibufferSelectionMode::First,
15046 window,
15047 cx,
15048 );
15049 Navigated::Yes
15050 })
15051 }))
15052 }
15053
15054 /// Opens a multibuffer with the given project locations in it
15055 pub fn open_locations_in_multibuffer(
15056 workspace: &mut Workspace,
15057 mut locations: Vec<Location>,
15058 title: String,
15059 split: bool,
15060 multibuffer_selection_mode: MultibufferSelectionMode,
15061 window: &mut Window,
15062 cx: &mut Context<Workspace>,
15063 ) {
15064 // If there are multiple definitions, open them in a multibuffer
15065 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15066 let mut locations = locations.into_iter().peekable();
15067 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15068 let capability = workspace.project().read(cx).capability();
15069
15070 let excerpt_buffer = cx.new(|cx| {
15071 let mut multibuffer = MultiBuffer::new(capability);
15072 while let Some(location) = locations.next() {
15073 let buffer = location.buffer.read(cx);
15074 let mut ranges_for_buffer = Vec::new();
15075 let range = location.range.to_point(buffer);
15076 ranges_for_buffer.push(range.clone());
15077
15078 while let Some(next_location) = locations.peek() {
15079 if next_location.buffer == location.buffer {
15080 ranges_for_buffer.push(next_location.range.to_point(buffer));
15081 locations.next();
15082 } else {
15083 break;
15084 }
15085 }
15086
15087 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15088 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15089 PathKey::for_buffer(&location.buffer, cx),
15090 location.buffer.clone(),
15091 ranges_for_buffer,
15092 DEFAULT_MULTIBUFFER_CONTEXT,
15093 cx,
15094 );
15095 ranges.extend(new_ranges)
15096 }
15097
15098 multibuffer.with_title(title)
15099 });
15100
15101 let editor = cx.new(|cx| {
15102 Editor::for_multibuffer(
15103 excerpt_buffer,
15104 Some(workspace.project().clone()),
15105 window,
15106 cx,
15107 )
15108 });
15109 editor.update(cx, |editor, cx| {
15110 match multibuffer_selection_mode {
15111 MultibufferSelectionMode::First => {
15112 if let Some(first_range) = ranges.first() {
15113 editor.change_selections(None, window, cx, |selections| {
15114 selections.clear_disjoint();
15115 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
15116 });
15117 }
15118 editor.highlight_background::<Self>(
15119 &ranges,
15120 |theme| theme.editor_highlighted_line_background,
15121 cx,
15122 );
15123 }
15124 MultibufferSelectionMode::All => {
15125 editor.change_selections(None, window, cx, |selections| {
15126 selections.clear_disjoint();
15127 selections.select_anchor_ranges(ranges);
15128 });
15129 }
15130 }
15131 editor.register_buffers_with_language_servers(cx);
15132 });
15133
15134 let item = Box::new(editor);
15135 let item_id = item.item_id();
15136
15137 if split {
15138 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15139 } else {
15140 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15141 let (preview_item_id, preview_item_idx) =
15142 workspace.active_pane().read_with(cx, |pane, _| {
15143 (pane.preview_item_id(), pane.preview_item_idx())
15144 });
15145
15146 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15147
15148 if let Some(preview_item_id) = preview_item_id {
15149 workspace.active_pane().update(cx, |pane, cx| {
15150 pane.remove_item(preview_item_id, false, false, window, cx);
15151 });
15152 }
15153 } else {
15154 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15155 }
15156 }
15157 workspace.active_pane().update(cx, |pane, cx| {
15158 pane.set_preview_item_id(Some(item_id), cx);
15159 });
15160 }
15161
15162 pub fn rename(
15163 &mut self,
15164 _: &Rename,
15165 window: &mut Window,
15166 cx: &mut Context<Self>,
15167 ) -> Option<Task<Result<()>>> {
15168 use language::ToOffset as _;
15169
15170 let provider = self.semantics_provider.clone()?;
15171 let selection = self.selections.newest_anchor().clone();
15172 let (cursor_buffer, cursor_buffer_position) = self
15173 .buffer
15174 .read(cx)
15175 .text_anchor_for_position(selection.head(), cx)?;
15176 let (tail_buffer, cursor_buffer_position_end) = self
15177 .buffer
15178 .read(cx)
15179 .text_anchor_for_position(selection.tail(), cx)?;
15180 if tail_buffer != cursor_buffer {
15181 return None;
15182 }
15183
15184 let snapshot = cursor_buffer.read(cx).snapshot();
15185 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15186 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15187 let prepare_rename = provider
15188 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15189 .unwrap_or_else(|| Task::ready(Ok(None)));
15190 drop(snapshot);
15191
15192 Some(cx.spawn_in(window, async move |this, cx| {
15193 let rename_range = if let Some(range) = prepare_rename.await? {
15194 Some(range)
15195 } else {
15196 this.update(cx, |this, cx| {
15197 let buffer = this.buffer.read(cx).snapshot(cx);
15198 let mut buffer_highlights = this
15199 .document_highlights_for_position(selection.head(), &buffer)
15200 .filter(|highlight| {
15201 highlight.start.excerpt_id == selection.head().excerpt_id
15202 && highlight.end.excerpt_id == selection.head().excerpt_id
15203 });
15204 buffer_highlights
15205 .next()
15206 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15207 })?
15208 };
15209 if let Some(rename_range) = rename_range {
15210 this.update_in(cx, |this, window, cx| {
15211 let snapshot = cursor_buffer.read(cx).snapshot();
15212 let rename_buffer_range = rename_range.to_offset(&snapshot);
15213 let cursor_offset_in_rename_range =
15214 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15215 let cursor_offset_in_rename_range_end =
15216 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15217
15218 this.take_rename(false, window, cx);
15219 let buffer = this.buffer.read(cx).read(cx);
15220 let cursor_offset = selection.head().to_offset(&buffer);
15221 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15222 let rename_end = rename_start + rename_buffer_range.len();
15223 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15224 let mut old_highlight_id = None;
15225 let old_name: Arc<str> = buffer
15226 .chunks(rename_start..rename_end, true)
15227 .map(|chunk| {
15228 if old_highlight_id.is_none() {
15229 old_highlight_id = chunk.syntax_highlight_id;
15230 }
15231 chunk.text
15232 })
15233 .collect::<String>()
15234 .into();
15235
15236 drop(buffer);
15237
15238 // Position the selection in the rename editor so that it matches the current selection.
15239 this.show_local_selections = false;
15240 let rename_editor = cx.new(|cx| {
15241 let mut editor = Editor::single_line(window, cx);
15242 editor.buffer.update(cx, |buffer, cx| {
15243 buffer.edit([(0..0, old_name.clone())], None, cx)
15244 });
15245 let rename_selection_range = match cursor_offset_in_rename_range
15246 .cmp(&cursor_offset_in_rename_range_end)
15247 {
15248 Ordering::Equal => {
15249 editor.select_all(&SelectAll, window, cx);
15250 return editor;
15251 }
15252 Ordering::Less => {
15253 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15254 }
15255 Ordering::Greater => {
15256 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15257 }
15258 };
15259 if rename_selection_range.end > old_name.len() {
15260 editor.select_all(&SelectAll, window, cx);
15261 } else {
15262 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15263 s.select_ranges([rename_selection_range]);
15264 });
15265 }
15266 editor
15267 });
15268 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15269 if e == &EditorEvent::Focused {
15270 cx.emit(EditorEvent::FocusedIn)
15271 }
15272 })
15273 .detach();
15274
15275 let write_highlights =
15276 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15277 let read_highlights =
15278 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15279 let ranges = write_highlights
15280 .iter()
15281 .flat_map(|(_, ranges)| ranges.iter())
15282 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15283 .cloned()
15284 .collect();
15285
15286 this.highlight_text::<Rename>(
15287 ranges,
15288 HighlightStyle {
15289 fade_out: Some(0.6),
15290 ..Default::default()
15291 },
15292 cx,
15293 );
15294 let rename_focus_handle = rename_editor.focus_handle(cx);
15295 window.focus(&rename_focus_handle);
15296 let block_id = this.insert_blocks(
15297 [BlockProperties {
15298 style: BlockStyle::Flex,
15299 placement: BlockPlacement::Below(range.start),
15300 height: Some(1),
15301 render: Arc::new({
15302 let rename_editor = rename_editor.clone();
15303 move |cx: &mut BlockContext| {
15304 let mut text_style = cx.editor_style.text.clone();
15305 if let Some(highlight_style) = old_highlight_id
15306 .and_then(|h| h.style(&cx.editor_style.syntax))
15307 {
15308 text_style = text_style.highlight(highlight_style);
15309 }
15310 div()
15311 .block_mouse_except_scroll()
15312 .pl(cx.anchor_x)
15313 .child(EditorElement::new(
15314 &rename_editor,
15315 EditorStyle {
15316 background: cx.theme().system().transparent,
15317 local_player: cx.editor_style.local_player,
15318 text: text_style,
15319 scrollbar_width: cx.editor_style.scrollbar_width,
15320 syntax: cx.editor_style.syntax.clone(),
15321 status: cx.editor_style.status.clone(),
15322 inlay_hints_style: HighlightStyle {
15323 font_weight: Some(FontWeight::BOLD),
15324 ..make_inlay_hints_style(cx.app)
15325 },
15326 inline_completion_styles: make_suggestion_styles(
15327 cx.app,
15328 ),
15329 ..EditorStyle::default()
15330 },
15331 ))
15332 .into_any_element()
15333 }
15334 }),
15335 priority: 0,
15336 render_in_minimap: true,
15337 }],
15338 Some(Autoscroll::fit()),
15339 cx,
15340 )[0];
15341 this.pending_rename = Some(RenameState {
15342 range,
15343 old_name,
15344 editor: rename_editor,
15345 block_id,
15346 });
15347 })?;
15348 }
15349
15350 Ok(())
15351 }))
15352 }
15353
15354 pub fn confirm_rename(
15355 &mut self,
15356 _: &ConfirmRename,
15357 window: &mut Window,
15358 cx: &mut Context<Self>,
15359 ) -> Option<Task<Result<()>>> {
15360 let rename = self.take_rename(false, window, cx)?;
15361 let workspace = self.workspace()?.downgrade();
15362 let (buffer, start) = self
15363 .buffer
15364 .read(cx)
15365 .text_anchor_for_position(rename.range.start, cx)?;
15366 let (end_buffer, _) = self
15367 .buffer
15368 .read(cx)
15369 .text_anchor_for_position(rename.range.end, cx)?;
15370 if buffer != end_buffer {
15371 return None;
15372 }
15373
15374 let old_name = rename.old_name;
15375 let new_name = rename.editor.read(cx).text(cx);
15376
15377 let rename = self.semantics_provider.as_ref()?.perform_rename(
15378 &buffer,
15379 start,
15380 new_name.clone(),
15381 cx,
15382 )?;
15383
15384 Some(cx.spawn_in(window, async move |editor, cx| {
15385 let project_transaction = rename.await?;
15386 Self::open_project_transaction(
15387 &editor,
15388 workspace,
15389 project_transaction,
15390 format!("Rename: {} → {}", old_name, new_name),
15391 cx,
15392 )
15393 .await?;
15394
15395 editor.update(cx, |editor, cx| {
15396 editor.refresh_document_highlights(cx);
15397 })?;
15398 Ok(())
15399 }))
15400 }
15401
15402 fn take_rename(
15403 &mut self,
15404 moving_cursor: bool,
15405 window: &mut Window,
15406 cx: &mut Context<Self>,
15407 ) -> Option<RenameState> {
15408 let rename = self.pending_rename.take()?;
15409 if rename.editor.focus_handle(cx).is_focused(window) {
15410 window.focus(&self.focus_handle);
15411 }
15412
15413 self.remove_blocks(
15414 [rename.block_id].into_iter().collect(),
15415 Some(Autoscroll::fit()),
15416 cx,
15417 );
15418 self.clear_highlights::<Rename>(cx);
15419 self.show_local_selections = true;
15420
15421 if moving_cursor {
15422 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15423 editor.selections.newest::<usize>(cx).head()
15424 });
15425
15426 // Update the selection to match the position of the selection inside
15427 // the rename editor.
15428 let snapshot = self.buffer.read(cx).read(cx);
15429 let rename_range = rename.range.to_offset(&snapshot);
15430 let cursor_in_editor = snapshot
15431 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15432 .min(rename_range.end);
15433 drop(snapshot);
15434
15435 self.change_selections(None, window, cx, |s| {
15436 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15437 });
15438 } else {
15439 self.refresh_document_highlights(cx);
15440 }
15441
15442 Some(rename)
15443 }
15444
15445 pub fn pending_rename(&self) -> Option<&RenameState> {
15446 self.pending_rename.as_ref()
15447 }
15448
15449 fn format(
15450 &mut self,
15451 _: &Format,
15452 window: &mut Window,
15453 cx: &mut Context<Self>,
15454 ) -> Option<Task<Result<()>>> {
15455 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15456
15457 let project = match &self.project {
15458 Some(project) => project.clone(),
15459 None => return None,
15460 };
15461
15462 Some(self.perform_format(
15463 project,
15464 FormatTrigger::Manual,
15465 FormatTarget::Buffers,
15466 window,
15467 cx,
15468 ))
15469 }
15470
15471 fn format_selections(
15472 &mut self,
15473 _: &FormatSelections,
15474 window: &mut Window,
15475 cx: &mut Context<Self>,
15476 ) -> Option<Task<Result<()>>> {
15477 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15478
15479 let project = match &self.project {
15480 Some(project) => project.clone(),
15481 None => return None,
15482 };
15483
15484 let ranges = self
15485 .selections
15486 .all_adjusted(cx)
15487 .into_iter()
15488 .map(|selection| selection.range())
15489 .collect_vec();
15490
15491 Some(self.perform_format(
15492 project,
15493 FormatTrigger::Manual,
15494 FormatTarget::Ranges(ranges),
15495 window,
15496 cx,
15497 ))
15498 }
15499
15500 fn perform_format(
15501 &mut self,
15502 project: Entity<Project>,
15503 trigger: FormatTrigger,
15504 target: FormatTarget,
15505 window: &mut Window,
15506 cx: &mut Context<Self>,
15507 ) -> Task<Result<()>> {
15508 let buffer = self.buffer.clone();
15509 let (buffers, target) = match target {
15510 FormatTarget::Buffers => {
15511 let mut buffers = buffer.read(cx).all_buffers();
15512 if trigger == FormatTrigger::Save {
15513 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15514 }
15515 (buffers, LspFormatTarget::Buffers)
15516 }
15517 FormatTarget::Ranges(selection_ranges) => {
15518 let multi_buffer = buffer.read(cx);
15519 let snapshot = multi_buffer.read(cx);
15520 let mut buffers = HashSet::default();
15521 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15522 BTreeMap::new();
15523 for selection_range in selection_ranges {
15524 for (buffer, buffer_range, _) in
15525 snapshot.range_to_buffer_ranges(selection_range)
15526 {
15527 let buffer_id = buffer.remote_id();
15528 let start = buffer.anchor_before(buffer_range.start);
15529 let end = buffer.anchor_after(buffer_range.end);
15530 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15531 buffer_id_to_ranges
15532 .entry(buffer_id)
15533 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15534 .or_insert_with(|| vec![start..end]);
15535 }
15536 }
15537 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15538 }
15539 };
15540
15541 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15542 let selections_prev = transaction_id_prev
15543 .and_then(|transaction_id_prev| {
15544 // default to selections as they were after the last edit, if we have them,
15545 // instead of how they are now.
15546 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15547 // will take you back to where you made the last edit, instead of staying where you scrolled
15548 self.selection_history
15549 .transaction(transaction_id_prev)
15550 .map(|t| t.0.clone())
15551 })
15552 .unwrap_or_else(|| {
15553 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15554 self.selections.disjoint_anchors()
15555 });
15556
15557 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15558 let format = project.update(cx, |project, cx| {
15559 project.format(buffers, target, true, trigger, cx)
15560 });
15561
15562 cx.spawn_in(window, async move |editor, cx| {
15563 let transaction = futures::select_biased! {
15564 transaction = format.log_err().fuse() => transaction,
15565 () = timeout => {
15566 log::warn!("timed out waiting for formatting");
15567 None
15568 }
15569 };
15570
15571 buffer
15572 .update(cx, |buffer, cx| {
15573 if let Some(transaction) = transaction {
15574 if !buffer.is_singleton() {
15575 buffer.push_transaction(&transaction.0, cx);
15576 }
15577 }
15578 cx.notify();
15579 })
15580 .ok();
15581
15582 if let Some(transaction_id_now) =
15583 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15584 {
15585 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15586 if has_new_transaction {
15587 _ = editor.update(cx, |editor, _| {
15588 editor
15589 .selection_history
15590 .insert_transaction(transaction_id_now, selections_prev);
15591 });
15592 }
15593 }
15594
15595 Ok(())
15596 })
15597 }
15598
15599 fn organize_imports(
15600 &mut self,
15601 _: &OrganizeImports,
15602 window: &mut Window,
15603 cx: &mut Context<Self>,
15604 ) -> Option<Task<Result<()>>> {
15605 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15606 let project = match &self.project {
15607 Some(project) => project.clone(),
15608 None => return None,
15609 };
15610 Some(self.perform_code_action_kind(
15611 project,
15612 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15613 window,
15614 cx,
15615 ))
15616 }
15617
15618 fn perform_code_action_kind(
15619 &mut self,
15620 project: Entity<Project>,
15621 kind: CodeActionKind,
15622 window: &mut Window,
15623 cx: &mut Context<Self>,
15624 ) -> Task<Result<()>> {
15625 let buffer = self.buffer.clone();
15626 let buffers = buffer.read(cx).all_buffers();
15627 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15628 let apply_action = project.update(cx, |project, cx| {
15629 project.apply_code_action_kind(buffers, kind, true, cx)
15630 });
15631 cx.spawn_in(window, async move |_, cx| {
15632 let transaction = futures::select_biased! {
15633 () = timeout => {
15634 log::warn!("timed out waiting for executing code action");
15635 None
15636 }
15637 transaction = apply_action.log_err().fuse() => transaction,
15638 };
15639 buffer
15640 .update(cx, |buffer, cx| {
15641 // check if we need this
15642 if let Some(transaction) = transaction {
15643 if !buffer.is_singleton() {
15644 buffer.push_transaction(&transaction.0, cx);
15645 }
15646 }
15647 cx.notify();
15648 })
15649 .ok();
15650 Ok(())
15651 })
15652 }
15653
15654 fn restart_language_server(
15655 &mut self,
15656 _: &RestartLanguageServer,
15657 _: &mut Window,
15658 cx: &mut Context<Self>,
15659 ) {
15660 if let Some(project) = self.project.clone() {
15661 self.buffer.update(cx, |multi_buffer, cx| {
15662 project.update(cx, |project, cx| {
15663 project.restart_language_servers_for_buffers(
15664 multi_buffer.all_buffers().into_iter().collect(),
15665 cx,
15666 );
15667 });
15668 })
15669 }
15670 }
15671
15672 fn stop_language_server(
15673 &mut self,
15674 _: &StopLanguageServer,
15675 _: &mut Window,
15676 cx: &mut Context<Self>,
15677 ) {
15678 if let Some(project) = self.project.clone() {
15679 self.buffer.update(cx, |multi_buffer, cx| {
15680 project.update(cx, |project, cx| {
15681 project.stop_language_servers_for_buffers(
15682 multi_buffer.all_buffers().into_iter().collect(),
15683 cx,
15684 );
15685 cx.emit(project::Event::RefreshInlayHints);
15686 });
15687 });
15688 }
15689 }
15690
15691 fn cancel_language_server_work(
15692 workspace: &mut Workspace,
15693 _: &actions::CancelLanguageServerWork,
15694 _: &mut Window,
15695 cx: &mut Context<Workspace>,
15696 ) {
15697 let project = workspace.project();
15698 let buffers = workspace
15699 .active_item(cx)
15700 .and_then(|item| item.act_as::<Editor>(cx))
15701 .map_or(HashSet::default(), |editor| {
15702 editor.read(cx).buffer.read(cx).all_buffers()
15703 });
15704 project.update(cx, |project, cx| {
15705 project.cancel_language_server_work_for_buffers(buffers, cx);
15706 });
15707 }
15708
15709 fn show_character_palette(
15710 &mut self,
15711 _: &ShowCharacterPalette,
15712 window: &mut Window,
15713 _: &mut Context<Self>,
15714 ) {
15715 window.show_character_palette();
15716 }
15717
15718 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15719 if self.mode.is_minimap() {
15720 return;
15721 }
15722
15723 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15724 let buffer = self.buffer.read(cx).snapshot(cx);
15725 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15726 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15727 let is_valid = buffer
15728 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15729 .any(|entry| {
15730 entry.diagnostic.is_primary
15731 && !entry.range.is_empty()
15732 && entry.range.start == primary_range_start
15733 && entry.diagnostic.message == active_diagnostics.active_message
15734 });
15735
15736 if !is_valid {
15737 self.dismiss_diagnostics(cx);
15738 }
15739 }
15740 }
15741
15742 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15743 match &self.active_diagnostics {
15744 ActiveDiagnostic::Group(group) => Some(group),
15745 _ => None,
15746 }
15747 }
15748
15749 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15750 self.dismiss_diagnostics(cx);
15751 self.active_diagnostics = ActiveDiagnostic::All;
15752 }
15753
15754 fn activate_diagnostics(
15755 &mut self,
15756 buffer_id: BufferId,
15757 diagnostic: DiagnosticEntry<usize>,
15758 window: &mut Window,
15759 cx: &mut Context<Self>,
15760 ) {
15761 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15762 return;
15763 }
15764 self.dismiss_diagnostics(cx);
15765 let snapshot = self.snapshot(window, cx);
15766 let buffer = self.buffer.read(cx).snapshot(cx);
15767 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15768 return;
15769 };
15770
15771 let diagnostic_group = buffer
15772 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15773 .collect::<Vec<_>>();
15774
15775 let blocks =
15776 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15777
15778 let blocks = self.display_map.update(cx, |display_map, cx| {
15779 display_map.insert_blocks(blocks, cx).into_iter().collect()
15780 });
15781 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15782 active_range: buffer.anchor_before(diagnostic.range.start)
15783 ..buffer.anchor_after(diagnostic.range.end),
15784 active_message: diagnostic.diagnostic.message.clone(),
15785 group_id: diagnostic.diagnostic.group_id,
15786 blocks,
15787 });
15788 cx.notify();
15789 }
15790
15791 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15792 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15793 return;
15794 };
15795
15796 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15797 if let ActiveDiagnostic::Group(group) = prev {
15798 self.display_map.update(cx, |display_map, cx| {
15799 display_map.remove_blocks(group.blocks, cx);
15800 });
15801 cx.notify();
15802 }
15803 }
15804
15805 /// Disable inline diagnostics rendering for this editor.
15806 pub fn disable_inline_diagnostics(&mut self) {
15807 self.inline_diagnostics_enabled = false;
15808 self.inline_diagnostics_update = Task::ready(());
15809 self.inline_diagnostics.clear();
15810 }
15811
15812 pub fn diagnostics_enabled(&self) -> bool {
15813 self.mode.is_full()
15814 }
15815
15816 pub fn inline_diagnostics_enabled(&self) -> bool {
15817 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15818 }
15819
15820 pub fn show_inline_diagnostics(&self) -> bool {
15821 self.show_inline_diagnostics
15822 }
15823
15824 pub fn toggle_inline_diagnostics(
15825 &mut self,
15826 _: &ToggleInlineDiagnostics,
15827 window: &mut Window,
15828 cx: &mut Context<Editor>,
15829 ) {
15830 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15831 self.refresh_inline_diagnostics(false, window, cx);
15832 }
15833
15834 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15835 self.diagnostics_max_severity = severity;
15836 self.display_map.update(cx, |display_map, _| {
15837 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15838 });
15839 }
15840
15841 pub fn toggle_diagnostics(
15842 &mut self,
15843 _: &ToggleDiagnostics,
15844 window: &mut Window,
15845 cx: &mut Context<Editor>,
15846 ) {
15847 if !self.diagnostics_enabled() {
15848 return;
15849 }
15850
15851 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15852 EditorSettings::get_global(cx)
15853 .diagnostics_max_severity
15854 .filter(|severity| severity != &DiagnosticSeverity::Off)
15855 .unwrap_or(DiagnosticSeverity::Hint)
15856 } else {
15857 DiagnosticSeverity::Off
15858 };
15859 self.set_max_diagnostics_severity(new_severity, cx);
15860 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15861 self.active_diagnostics = ActiveDiagnostic::None;
15862 self.inline_diagnostics_update = Task::ready(());
15863 self.inline_diagnostics.clear();
15864 } else {
15865 self.refresh_inline_diagnostics(false, window, cx);
15866 }
15867
15868 cx.notify();
15869 }
15870
15871 pub fn toggle_minimap(
15872 &mut self,
15873 _: &ToggleMinimap,
15874 window: &mut Window,
15875 cx: &mut Context<Editor>,
15876 ) {
15877 if self.supports_minimap(cx) {
15878 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15879 }
15880 }
15881
15882 fn refresh_inline_diagnostics(
15883 &mut self,
15884 debounce: bool,
15885 window: &mut Window,
15886 cx: &mut Context<Self>,
15887 ) {
15888 let max_severity = ProjectSettings::get_global(cx)
15889 .diagnostics
15890 .inline
15891 .max_severity
15892 .unwrap_or(self.diagnostics_max_severity);
15893
15894 if !self.inline_diagnostics_enabled()
15895 || !self.show_inline_diagnostics
15896 || max_severity == DiagnosticSeverity::Off
15897 {
15898 self.inline_diagnostics_update = Task::ready(());
15899 self.inline_diagnostics.clear();
15900 return;
15901 }
15902
15903 let debounce_ms = ProjectSettings::get_global(cx)
15904 .diagnostics
15905 .inline
15906 .update_debounce_ms;
15907 let debounce = if debounce && debounce_ms > 0 {
15908 Some(Duration::from_millis(debounce_ms))
15909 } else {
15910 None
15911 };
15912 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15913 if let Some(debounce) = debounce {
15914 cx.background_executor().timer(debounce).await;
15915 }
15916 let Some(snapshot) = editor.upgrade().and_then(|editor| {
15917 editor
15918 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15919 .ok()
15920 }) else {
15921 return;
15922 };
15923
15924 let new_inline_diagnostics = cx
15925 .background_spawn(async move {
15926 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15927 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15928 let message = diagnostic_entry
15929 .diagnostic
15930 .message
15931 .split_once('\n')
15932 .map(|(line, _)| line)
15933 .map(SharedString::new)
15934 .unwrap_or_else(|| {
15935 SharedString::from(diagnostic_entry.diagnostic.message)
15936 });
15937 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15938 let (Ok(i) | Err(i)) = inline_diagnostics
15939 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15940 inline_diagnostics.insert(
15941 i,
15942 (
15943 start_anchor,
15944 InlineDiagnostic {
15945 message,
15946 group_id: diagnostic_entry.diagnostic.group_id,
15947 start: diagnostic_entry.range.start.to_point(&snapshot),
15948 is_primary: diagnostic_entry.diagnostic.is_primary,
15949 severity: diagnostic_entry.diagnostic.severity,
15950 },
15951 ),
15952 );
15953 }
15954 inline_diagnostics
15955 })
15956 .await;
15957
15958 editor
15959 .update(cx, |editor, cx| {
15960 editor.inline_diagnostics = new_inline_diagnostics;
15961 cx.notify();
15962 })
15963 .ok();
15964 });
15965 }
15966
15967 fn pull_diagnostics(&mut self, window: &Window, cx: &mut Context<Self>) -> Option<()> {
15968 let project = self.project.as_ref()?.downgrade();
15969 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
15970 .diagnostics
15971 .lsp_pull_diagnostics;
15972 if !pull_diagnostics_settings.enabled {
15973 return None;
15974 }
15975 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
15976 let buffers = self.buffer.read(cx).all_buffers();
15977
15978 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
15979 cx.background_executor().timer(debounce).await;
15980
15981 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
15982 buffers
15983 .into_iter()
15984 .flat_map(|buffer| {
15985 Some(project.upgrade()?.pull_diagnostics_for_buffer(buffer, cx))
15986 })
15987 .collect::<FuturesUnordered<_>>()
15988 }) else {
15989 return;
15990 };
15991
15992 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
15993 match pull_task {
15994 Ok(()) => {
15995 if editor
15996 .update_in(cx, |editor, window, cx| {
15997 editor.update_diagnostics_state(window, cx);
15998 })
15999 .is_err()
16000 {
16001 return;
16002 }
16003 }
16004 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16005 }
16006 }
16007 });
16008
16009 Some(())
16010 }
16011
16012 pub fn set_selections_from_remote(
16013 &mut self,
16014 selections: Vec<Selection<Anchor>>,
16015 pending_selection: Option<Selection<Anchor>>,
16016 window: &mut Window,
16017 cx: &mut Context<Self>,
16018 ) {
16019 let old_cursor_position = self.selections.newest_anchor().head();
16020 self.selections.change_with(cx, |s| {
16021 s.select_anchors(selections);
16022 if let Some(pending_selection) = pending_selection {
16023 s.set_pending(pending_selection, SelectMode::Character);
16024 } else {
16025 s.clear_pending();
16026 }
16027 });
16028 self.selections_did_change(false, &old_cursor_position, true, window, cx);
16029 }
16030
16031 pub fn transact(
16032 &mut self,
16033 window: &mut Window,
16034 cx: &mut Context<Self>,
16035 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16036 ) -> Option<TransactionId> {
16037 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16038 this.start_transaction_at(Instant::now(), window, cx);
16039 update(this, window, cx);
16040 this.end_transaction_at(Instant::now(), cx)
16041 })
16042 }
16043
16044 pub fn start_transaction_at(
16045 &mut self,
16046 now: Instant,
16047 window: &mut Window,
16048 cx: &mut Context<Self>,
16049 ) {
16050 self.end_selection(window, cx);
16051 if let Some(tx_id) = self
16052 .buffer
16053 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16054 {
16055 self.selection_history
16056 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16057 cx.emit(EditorEvent::TransactionBegun {
16058 transaction_id: tx_id,
16059 })
16060 }
16061 }
16062
16063 pub fn end_transaction_at(
16064 &mut self,
16065 now: Instant,
16066 cx: &mut Context<Self>,
16067 ) -> Option<TransactionId> {
16068 if let Some(transaction_id) = self
16069 .buffer
16070 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16071 {
16072 if let Some((_, end_selections)) =
16073 self.selection_history.transaction_mut(transaction_id)
16074 {
16075 *end_selections = Some(self.selections.disjoint_anchors());
16076 } else {
16077 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16078 }
16079
16080 cx.emit(EditorEvent::Edited { transaction_id });
16081 Some(transaction_id)
16082 } else {
16083 None
16084 }
16085 }
16086
16087 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16088 if self.selection_mark_mode {
16089 self.change_selections(None, window, cx, |s| {
16090 s.move_with(|_, sel| {
16091 sel.collapse_to(sel.head(), SelectionGoal::None);
16092 });
16093 })
16094 }
16095 self.selection_mark_mode = true;
16096 cx.notify();
16097 }
16098
16099 pub fn swap_selection_ends(
16100 &mut self,
16101 _: &actions::SwapSelectionEnds,
16102 window: &mut Window,
16103 cx: &mut Context<Self>,
16104 ) {
16105 self.change_selections(None, window, cx, |s| {
16106 s.move_with(|_, sel| {
16107 if sel.start != sel.end {
16108 sel.reversed = !sel.reversed
16109 }
16110 });
16111 });
16112 self.request_autoscroll(Autoscroll::newest(), cx);
16113 cx.notify();
16114 }
16115
16116 pub fn toggle_fold(
16117 &mut self,
16118 _: &actions::ToggleFold,
16119 window: &mut Window,
16120 cx: &mut Context<Self>,
16121 ) {
16122 if self.is_singleton(cx) {
16123 let selection = self.selections.newest::<Point>(cx);
16124
16125 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16126 let range = if selection.is_empty() {
16127 let point = selection.head().to_display_point(&display_map);
16128 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16129 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16130 .to_point(&display_map);
16131 start..end
16132 } else {
16133 selection.range()
16134 };
16135 if display_map.folds_in_range(range).next().is_some() {
16136 self.unfold_lines(&Default::default(), window, cx)
16137 } else {
16138 self.fold(&Default::default(), window, cx)
16139 }
16140 } else {
16141 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16142 let buffer_ids: HashSet<_> = self
16143 .selections
16144 .disjoint_anchor_ranges()
16145 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16146 .collect();
16147
16148 let should_unfold = buffer_ids
16149 .iter()
16150 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16151
16152 for buffer_id in buffer_ids {
16153 if should_unfold {
16154 self.unfold_buffer(buffer_id, cx);
16155 } else {
16156 self.fold_buffer(buffer_id, cx);
16157 }
16158 }
16159 }
16160 }
16161
16162 pub fn toggle_fold_recursive(
16163 &mut self,
16164 _: &actions::ToggleFoldRecursive,
16165 window: &mut Window,
16166 cx: &mut Context<Self>,
16167 ) {
16168 let selection = self.selections.newest::<Point>(cx);
16169
16170 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16171 let range = if selection.is_empty() {
16172 let point = selection.head().to_display_point(&display_map);
16173 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16174 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16175 .to_point(&display_map);
16176 start..end
16177 } else {
16178 selection.range()
16179 };
16180 if display_map.folds_in_range(range).next().is_some() {
16181 self.unfold_recursive(&Default::default(), window, cx)
16182 } else {
16183 self.fold_recursive(&Default::default(), window, cx)
16184 }
16185 }
16186
16187 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16188 if self.is_singleton(cx) {
16189 let mut to_fold = Vec::new();
16190 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16191 let selections = self.selections.all_adjusted(cx);
16192
16193 for selection in selections {
16194 let range = selection.range().sorted();
16195 let buffer_start_row = range.start.row;
16196
16197 if range.start.row != range.end.row {
16198 let mut found = false;
16199 let mut row = range.start.row;
16200 while row <= range.end.row {
16201 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16202 {
16203 found = true;
16204 row = crease.range().end.row + 1;
16205 to_fold.push(crease);
16206 } else {
16207 row += 1
16208 }
16209 }
16210 if found {
16211 continue;
16212 }
16213 }
16214
16215 for row in (0..=range.start.row).rev() {
16216 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16217 if crease.range().end.row >= buffer_start_row {
16218 to_fold.push(crease);
16219 if row <= range.start.row {
16220 break;
16221 }
16222 }
16223 }
16224 }
16225 }
16226
16227 self.fold_creases(to_fold, true, window, cx);
16228 } else {
16229 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16230 let buffer_ids = self
16231 .selections
16232 .disjoint_anchor_ranges()
16233 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16234 .collect::<HashSet<_>>();
16235 for buffer_id in buffer_ids {
16236 self.fold_buffer(buffer_id, cx);
16237 }
16238 }
16239 }
16240
16241 fn fold_at_level(
16242 &mut self,
16243 fold_at: &FoldAtLevel,
16244 window: &mut Window,
16245 cx: &mut Context<Self>,
16246 ) {
16247 if !self.buffer.read(cx).is_singleton() {
16248 return;
16249 }
16250
16251 let fold_at_level = fold_at.0;
16252 let snapshot = self.buffer.read(cx).snapshot(cx);
16253 let mut to_fold = Vec::new();
16254 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16255
16256 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16257 while start_row < end_row {
16258 match self
16259 .snapshot(window, cx)
16260 .crease_for_buffer_row(MultiBufferRow(start_row))
16261 {
16262 Some(crease) => {
16263 let nested_start_row = crease.range().start.row + 1;
16264 let nested_end_row = crease.range().end.row;
16265
16266 if current_level < fold_at_level {
16267 stack.push((nested_start_row, nested_end_row, current_level + 1));
16268 } else if current_level == fold_at_level {
16269 to_fold.push(crease);
16270 }
16271
16272 start_row = nested_end_row + 1;
16273 }
16274 None => start_row += 1,
16275 }
16276 }
16277 }
16278
16279 self.fold_creases(to_fold, true, window, cx);
16280 }
16281
16282 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16283 if self.buffer.read(cx).is_singleton() {
16284 let mut fold_ranges = Vec::new();
16285 let snapshot = self.buffer.read(cx).snapshot(cx);
16286
16287 for row in 0..snapshot.max_row().0 {
16288 if let Some(foldable_range) = self
16289 .snapshot(window, cx)
16290 .crease_for_buffer_row(MultiBufferRow(row))
16291 {
16292 fold_ranges.push(foldable_range);
16293 }
16294 }
16295
16296 self.fold_creases(fold_ranges, true, window, cx);
16297 } else {
16298 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16299 editor
16300 .update_in(cx, |editor, _, cx| {
16301 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16302 editor.fold_buffer(buffer_id, cx);
16303 }
16304 })
16305 .ok();
16306 });
16307 }
16308 }
16309
16310 pub fn fold_function_bodies(
16311 &mut self,
16312 _: &actions::FoldFunctionBodies,
16313 window: &mut Window,
16314 cx: &mut Context<Self>,
16315 ) {
16316 let snapshot = self.buffer.read(cx).snapshot(cx);
16317
16318 let ranges = snapshot
16319 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16320 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16321 .collect::<Vec<_>>();
16322
16323 let creases = ranges
16324 .into_iter()
16325 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16326 .collect();
16327
16328 self.fold_creases(creases, true, window, cx);
16329 }
16330
16331 pub fn fold_recursive(
16332 &mut self,
16333 _: &actions::FoldRecursive,
16334 window: &mut Window,
16335 cx: &mut Context<Self>,
16336 ) {
16337 let mut to_fold = Vec::new();
16338 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16339 let selections = self.selections.all_adjusted(cx);
16340
16341 for selection in selections {
16342 let range = selection.range().sorted();
16343 let buffer_start_row = range.start.row;
16344
16345 if range.start.row != range.end.row {
16346 let mut found = false;
16347 for row in range.start.row..=range.end.row {
16348 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16349 found = true;
16350 to_fold.push(crease);
16351 }
16352 }
16353 if found {
16354 continue;
16355 }
16356 }
16357
16358 for row in (0..=range.start.row).rev() {
16359 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16360 if crease.range().end.row >= buffer_start_row {
16361 to_fold.push(crease);
16362 } else {
16363 break;
16364 }
16365 }
16366 }
16367 }
16368
16369 self.fold_creases(to_fold, true, window, cx);
16370 }
16371
16372 pub fn fold_at(
16373 &mut self,
16374 buffer_row: MultiBufferRow,
16375 window: &mut Window,
16376 cx: &mut Context<Self>,
16377 ) {
16378 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16379
16380 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16381 let autoscroll = self
16382 .selections
16383 .all::<Point>(cx)
16384 .iter()
16385 .any(|selection| crease.range().overlaps(&selection.range()));
16386
16387 self.fold_creases(vec![crease], autoscroll, window, cx);
16388 }
16389 }
16390
16391 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16392 if self.is_singleton(cx) {
16393 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16394 let buffer = &display_map.buffer_snapshot;
16395 let selections = self.selections.all::<Point>(cx);
16396 let ranges = selections
16397 .iter()
16398 .map(|s| {
16399 let range = s.display_range(&display_map).sorted();
16400 let mut start = range.start.to_point(&display_map);
16401 let mut end = range.end.to_point(&display_map);
16402 start.column = 0;
16403 end.column = buffer.line_len(MultiBufferRow(end.row));
16404 start..end
16405 })
16406 .collect::<Vec<_>>();
16407
16408 self.unfold_ranges(&ranges, true, true, cx);
16409 } else {
16410 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16411 let buffer_ids = self
16412 .selections
16413 .disjoint_anchor_ranges()
16414 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16415 .collect::<HashSet<_>>();
16416 for buffer_id in buffer_ids {
16417 self.unfold_buffer(buffer_id, cx);
16418 }
16419 }
16420 }
16421
16422 pub fn unfold_recursive(
16423 &mut self,
16424 _: &UnfoldRecursive,
16425 _window: &mut Window,
16426 cx: &mut Context<Self>,
16427 ) {
16428 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16429 let selections = self.selections.all::<Point>(cx);
16430 let ranges = selections
16431 .iter()
16432 .map(|s| {
16433 let mut range = s.display_range(&display_map).sorted();
16434 *range.start.column_mut() = 0;
16435 *range.end.column_mut() = display_map.line_len(range.end.row());
16436 let start = range.start.to_point(&display_map);
16437 let end = range.end.to_point(&display_map);
16438 start..end
16439 })
16440 .collect::<Vec<_>>();
16441
16442 self.unfold_ranges(&ranges, true, true, cx);
16443 }
16444
16445 pub fn unfold_at(
16446 &mut self,
16447 buffer_row: MultiBufferRow,
16448 _window: &mut Window,
16449 cx: &mut Context<Self>,
16450 ) {
16451 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16452
16453 let intersection_range = Point::new(buffer_row.0, 0)
16454 ..Point::new(
16455 buffer_row.0,
16456 display_map.buffer_snapshot.line_len(buffer_row),
16457 );
16458
16459 let autoscroll = self
16460 .selections
16461 .all::<Point>(cx)
16462 .iter()
16463 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16464
16465 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16466 }
16467
16468 pub fn unfold_all(
16469 &mut self,
16470 _: &actions::UnfoldAll,
16471 _window: &mut Window,
16472 cx: &mut Context<Self>,
16473 ) {
16474 if self.buffer.read(cx).is_singleton() {
16475 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16476 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16477 } else {
16478 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16479 editor
16480 .update(cx, |editor, cx| {
16481 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16482 editor.unfold_buffer(buffer_id, cx);
16483 }
16484 })
16485 .ok();
16486 });
16487 }
16488 }
16489
16490 pub fn fold_selected_ranges(
16491 &mut self,
16492 _: &FoldSelectedRanges,
16493 window: &mut Window,
16494 cx: &mut Context<Self>,
16495 ) {
16496 let selections = self.selections.all_adjusted(cx);
16497 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16498 let ranges = selections
16499 .into_iter()
16500 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16501 .collect::<Vec<_>>();
16502 self.fold_creases(ranges, true, window, cx);
16503 }
16504
16505 pub fn fold_ranges<T: ToOffset + Clone>(
16506 &mut self,
16507 ranges: Vec<Range<T>>,
16508 auto_scroll: bool,
16509 window: &mut Window,
16510 cx: &mut Context<Self>,
16511 ) {
16512 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16513 let ranges = ranges
16514 .into_iter()
16515 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16516 .collect::<Vec<_>>();
16517 self.fold_creases(ranges, auto_scroll, window, cx);
16518 }
16519
16520 pub fn fold_creases<T: ToOffset + Clone>(
16521 &mut self,
16522 creases: Vec<Crease<T>>,
16523 auto_scroll: bool,
16524 _window: &mut Window,
16525 cx: &mut Context<Self>,
16526 ) {
16527 if creases.is_empty() {
16528 return;
16529 }
16530
16531 let mut buffers_affected = HashSet::default();
16532 let multi_buffer = self.buffer().read(cx);
16533 for crease in &creases {
16534 if let Some((_, buffer, _)) =
16535 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16536 {
16537 buffers_affected.insert(buffer.read(cx).remote_id());
16538 };
16539 }
16540
16541 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16542
16543 if auto_scroll {
16544 self.request_autoscroll(Autoscroll::fit(), cx);
16545 }
16546
16547 cx.notify();
16548
16549 self.scrollbar_marker_state.dirty = true;
16550 self.folds_did_change(cx);
16551 }
16552
16553 /// Removes any folds whose ranges intersect any of the given ranges.
16554 pub fn unfold_ranges<T: ToOffset + Clone>(
16555 &mut self,
16556 ranges: &[Range<T>],
16557 inclusive: bool,
16558 auto_scroll: bool,
16559 cx: &mut Context<Self>,
16560 ) {
16561 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16562 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16563 });
16564 self.folds_did_change(cx);
16565 }
16566
16567 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16568 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16569 return;
16570 }
16571 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16572 self.display_map.update(cx, |display_map, cx| {
16573 display_map.fold_buffers([buffer_id], cx)
16574 });
16575 cx.emit(EditorEvent::BufferFoldToggled {
16576 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16577 folded: true,
16578 });
16579 cx.notify();
16580 }
16581
16582 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16583 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16584 return;
16585 }
16586 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16587 self.display_map.update(cx, |display_map, cx| {
16588 display_map.unfold_buffers([buffer_id], cx);
16589 });
16590 cx.emit(EditorEvent::BufferFoldToggled {
16591 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16592 folded: false,
16593 });
16594 cx.notify();
16595 }
16596
16597 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16598 self.display_map.read(cx).is_buffer_folded(buffer)
16599 }
16600
16601 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16602 self.display_map.read(cx).folded_buffers()
16603 }
16604
16605 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16606 self.display_map.update(cx, |display_map, cx| {
16607 display_map.disable_header_for_buffer(buffer_id, cx);
16608 });
16609 cx.notify();
16610 }
16611
16612 /// Removes any folds with the given ranges.
16613 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16614 &mut self,
16615 ranges: &[Range<T>],
16616 type_id: TypeId,
16617 auto_scroll: bool,
16618 cx: &mut Context<Self>,
16619 ) {
16620 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16621 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16622 });
16623 self.folds_did_change(cx);
16624 }
16625
16626 fn remove_folds_with<T: ToOffset + Clone>(
16627 &mut self,
16628 ranges: &[Range<T>],
16629 auto_scroll: bool,
16630 cx: &mut Context<Self>,
16631 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16632 ) {
16633 if ranges.is_empty() {
16634 return;
16635 }
16636
16637 let mut buffers_affected = HashSet::default();
16638 let multi_buffer = self.buffer().read(cx);
16639 for range in ranges {
16640 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16641 buffers_affected.insert(buffer.read(cx).remote_id());
16642 };
16643 }
16644
16645 self.display_map.update(cx, update);
16646
16647 if auto_scroll {
16648 self.request_autoscroll(Autoscroll::fit(), cx);
16649 }
16650
16651 cx.notify();
16652 self.scrollbar_marker_state.dirty = true;
16653 self.active_indent_guides_state.dirty = true;
16654 }
16655
16656 pub fn update_fold_widths(
16657 &mut self,
16658 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16659 cx: &mut Context<Self>,
16660 ) -> bool {
16661 self.display_map
16662 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16663 }
16664
16665 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16666 self.display_map.read(cx).fold_placeholder.clone()
16667 }
16668
16669 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16670 self.buffer.update(cx, |buffer, cx| {
16671 buffer.set_all_diff_hunks_expanded(cx);
16672 });
16673 }
16674
16675 pub fn expand_all_diff_hunks(
16676 &mut self,
16677 _: &ExpandAllDiffHunks,
16678 _window: &mut Window,
16679 cx: &mut Context<Self>,
16680 ) {
16681 self.buffer.update(cx, |buffer, cx| {
16682 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16683 });
16684 }
16685
16686 pub fn toggle_selected_diff_hunks(
16687 &mut self,
16688 _: &ToggleSelectedDiffHunks,
16689 _window: &mut Window,
16690 cx: &mut Context<Self>,
16691 ) {
16692 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16693 self.toggle_diff_hunks_in_ranges(ranges, cx);
16694 }
16695
16696 pub fn diff_hunks_in_ranges<'a>(
16697 &'a self,
16698 ranges: &'a [Range<Anchor>],
16699 buffer: &'a MultiBufferSnapshot,
16700 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16701 ranges.iter().flat_map(move |range| {
16702 let end_excerpt_id = range.end.excerpt_id;
16703 let range = range.to_point(buffer);
16704 let mut peek_end = range.end;
16705 if range.end.row < buffer.max_row().0 {
16706 peek_end = Point::new(range.end.row + 1, 0);
16707 }
16708 buffer
16709 .diff_hunks_in_range(range.start..peek_end)
16710 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16711 })
16712 }
16713
16714 pub fn has_stageable_diff_hunks_in_ranges(
16715 &self,
16716 ranges: &[Range<Anchor>],
16717 snapshot: &MultiBufferSnapshot,
16718 ) -> bool {
16719 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16720 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16721 }
16722
16723 pub fn toggle_staged_selected_diff_hunks(
16724 &mut self,
16725 _: &::git::ToggleStaged,
16726 _: &mut Window,
16727 cx: &mut Context<Self>,
16728 ) {
16729 let snapshot = self.buffer.read(cx).snapshot(cx);
16730 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16731 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16732 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16733 }
16734
16735 pub fn set_render_diff_hunk_controls(
16736 &mut self,
16737 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16738 cx: &mut Context<Self>,
16739 ) {
16740 self.render_diff_hunk_controls = render_diff_hunk_controls;
16741 cx.notify();
16742 }
16743
16744 pub fn stage_and_next(
16745 &mut self,
16746 _: &::git::StageAndNext,
16747 window: &mut Window,
16748 cx: &mut Context<Self>,
16749 ) {
16750 self.do_stage_or_unstage_and_next(true, window, cx);
16751 }
16752
16753 pub fn unstage_and_next(
16754 &mut self,
16755 _: &::git::UnstageAndNext,
16756 window: &mut Window,
16757 cx: &mut Context<Self>,
16758 ) {
16759 self.do_stage_or_unstage_and_next(false, window, cx);
16760 }
16761
16762 pub fn stage_or_unstage_diff_hunks(
16763 &mut self,
16764 stage: bool,
16765 ranges: Vec<Range<Anchor>>,
16766 cx: &mut Context<Self>,
16767 ) {
16768 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16769 cx.spawn(async move |this, cx| {
16770 task.await?;
16771 this.update(cx, |this, cx| {
16772 let snapshot = this.buffer.read(cx).snapshot(cx);
16773 let chunk_by = this
16774 .diff_hunks_in_ranges(&ranges, &snapshot)
16775 .chunk_by(|hunk| hunk.buffer_id);
16776 for (buffer_id, hunks) in &chunk_by {
16777 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16778 }
16779 })
16780 })
16781 .detach_and_log_err(cx);
16782 }
16783
16784 fn save_buffers_for_ranges_if_needed(
16785 &mut self,
16786 ranges: &[Range<Anchor>],
16787 cx: &mut Context<Editor>,
16788 ) -> Task<Result<()>> {
16789 let multibuffer = self.buffer.read(cx);
16790 let snapshot = multibuffer.read(cx);
16791 let buffer_ids: HashSet<_> = ranges
16792 .iter()
16793 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16794 .collect();
16795 drop(snapshot);
16796
16797 let mut buffers = HashSet::default();
16798 for buffer_id in buffer_ids {
16799 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16800 let buffer = buffer_entity.read(cx);
16801 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16802 {
16803 buffers.insert(buffer_entity);
16804 }
16805 }
16806 }
16807
16808 if let Some(project) = &self.project {
16809 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16810 } else {
16811 Task::ready(Ok(()))
16812 }
16813 }
16814
16815 fn do_stage_or_unstage_and_next(
16816 &mut self,
16817 stage: bool,
16818 window: &mut Window,
16819 cx: &mut Context<Self>,
16820 ) {
16821 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16822
16823 if ranges.iter().any(|range| range.start != range.end) {
16824 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16825 return;
16826 }
16827
16828 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16829 let snapshot = self.snapshot(window, cx);
16830 let position = self.selections.newest::<Point>(cx).head();
16831 let mut row = snapshot
16832 .buffer_snapshot
16833 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16834 .find(|hunk| hunk.row_range.start.0 > position.row)
16835 .map(|hunk| hunk.row_range.start);
16836
16837 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16838 // Outside of the project diff editor, wrap around to the beginning.
16839 if !all_diff_hunks_expanded {
16840 row = row.or_else(|| {
16841 snapshot
16842 .buffer_snapshot
16843 .diff_hunks_in_range(Point::zero()..position)
16844 .find(|hunk| hunk.row_range.end.0 < position.row)
16845 .map(|hunk| hunk.row_range.start)
16846 });
16847 }
16848
16849 if let Some(row) = row {
16850 let destination = Point::new(row.0, 0);
16851 let autoscroll = Autoscroll::center();
16852
16853 self.unfold_ranges(&[destination..destination], false, false, cx);
16854 self.change_selections(Some(autoscroll), window, cx, |s| {
16855 s.select_ranges([destination..destination]);
16856 });
16857 }
16858 }
16859
16860 fn do_stage_or_unstage(
16861 &self,
16862 stage: bool,
16863 buffer_id: BufferId,
16864 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16865 cx: &mut App,
16866 ) -> Option<()> {
16867 let project = self.project.as_ref()?;
16868 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16869 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16870 let buffer_snapshot = buffer.read(cx).snapshot();
16871 let file_exists = buffer_snapshot
16872 .file()
16873 .is_some_and(|file| file.disk_state().exists());
16874 diff.update(cx, |diff, cx| {
16875 diff.stage_or_unstage_hunks(
16876 stage,
16877 &hunks
16878 .map(|hunk| buffer_diff::DiffHunk {
16879 buffer_range: hunk.buffer_range,
16880 diff_base_byte_range: hunk.diff_base_byte_range,
16881 secondary_status: hunk.secondary_status,
16882 range: Point::zero()..Point::zero(), // unused
16883 })
16884 .collect::<Vec<_>>(),
16885 &buffer_snapshot,
16886 file_exists,
16887 cx,
16888 )
16889 });
16890 None
16891 }
16892
16893 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16894 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16895 self.buffer
16896 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16897 }
16898
16899 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16900 self.buffer.update(cx, |buffer, cx| {
16901 let ranges = vec![Anchor::min()..Anchor::max()];
16902 if !buffer.all_diff_hunks_expanded()
16903 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16904 {
16905 buffer.collapse_diff_hunks(ranges, cx);
16906 true
16907 } else {
16908 false
16909 }
16910 })
16911 }
16912
16913 fn toggle_diff_hunks_in_ranges(
16914 &mut self,
16915 ranges: Vec<Range<Anchor>>,
16916 cx: &mut Context<Editor>,
16917 ) {
16918 self.buffer.update(cx, |buffer, cx| {
16919 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16920 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16921 })
16922 }
16923
16924 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16925 self.buffer.update(cx, |buffer, cx| {
16926 let snapshot = buffer.snapshot(cx);
16927 let excerpt_id = range.end.excerpt_id;
16928 let point_range = range.to_point(&snapshot);
16929 let expand = !buffer.single_hunk_is_expanded(range, cx);
16930 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16931 })
16932 }
16933
16934 pub(crate) fn apply_all_diff_hunks(
16935 &mut self,
16936 _: &ApplyAllDiffHunks,
16937 window: &mut Window,
16938 cx: &mut Context<Self>,
16939 ) {
16940 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16941
16942 let buffers = self.buffer.read(cx).all_buffers();
16943 for branch_buffer in buffers {
16944 branch_buffer.update(cx, |branch_buffer, cx| {
16945 branch_buffer.merge_into_base(Vec::new(), cx);
16946 });
16947 }
16948
16949 if let Some(project) = self.project.clone() {
16950 self.save(true, project, window, cx).detach_and_log_err(cx);
16951 }
16952 }
16953
16954 pub(crate) fn apply_selected_diff_hunks(
16955 &mut self,
16956 _: &ApplyDiffHunk,
16957 window: &mut Window,
16958 cx: &mut Context<Self>,
16959 ) {
16960 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16961 let snapshot = self.snapshot(window, cx);
16962 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16963 let mut ranges_by_buffer = HashMap::default();
16964 self.transact(window, cx, |editor, _window, cx| {
16965 for hunk in hunks {
16966 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16967 ranges_by_buffer
16968 .entry(buffer.clone())
16969 .or_insert_with(Vec::new)
16970 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16971 }
16972 }
16973
16974 for (buffer, ranges) in ranges_by_buffer {
16975 buffer.update(cx, |buffer, cx| {
16976 buffer.merge_into_base(ranges, cx);
16977 });
16978 }
16979 });
16980
16981 if let Some(project) = self.project.clone() {
16982 self.save(true, project, window, cx).detach_and_log_err(cx);
16983 }
16984 }
16985
16986 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16987 if hovered != self.gutter_hovered {
16988 self.gutter_hovered = hovered;
16989 cx.notify();
16990 }
16991 }
16992
16993 pub fn insert_blocks(
16994 &mut self,
16995 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16996 autoscroll: Option<Autoscroll>,
16997 cx: &mut Context<Self>,
16998 ) -> Vec<CustomBlockId> {
16999 let blocks = self
17000 .display_map
17001 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17002 if let Some(autoscroll) = autoscroll {
17003 self.request_autoscroll(autoscroll, cx);
17004 }
17005 cx.notify();
17006 blocks
17007 }
17008
17009 pub fn resize_blocks(
17010 &mut self,
17011 heights: HashMap<CustomBlockId, u32>,
17012 autoscroll: Option<Autoscroll>,
17013 cx: &mut Context<Self>,
17014 ) {
17015 self.display_map
17016 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17017 if let Some(autoscroll) = autoscroll {
17018 self.request_autoscroll(autoscroll, cx);
17019 }
17020 cx.notify();
17021 }
17022
17023 pub fn replace_blocks(
17024 &mut self,
17025 renderers: HashMap<CustomBlockId, RenderBlock>,
17026 autoscroll: Option<Autoscroll>,
17027 cx: &mut Context<Self>,
17028 ) {
17029 self.display_map
17030 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17031 if let Some(autoscroll) = autoscroll {
17032 self.request_autoscroll(autoscroll, cx);
17033 }
17034 cx.notify();
17035 }
17036
17037 pub fn remove_blocks(
17038 &mut self,
17039 block_ids: HashSet<CustomBlockId>,
17040 autoscroll: Option<Autoscroll>,
17041 cx: &mut Context<Self>,
17042 ) {
17043 self.display_map.update(cx, |display_map, cx| {
17044 display_map.remove_blocks(block_ids, cx)
17045 });
17046 if let Some(autoscroll) = autoscroll {
17047 self.request_autoscroll(autoscroll, cx);
17048 }
17049 cx.notify();
17050 }
17051
17052 pub fn row_for_block(
17053 &self,
17054 block_id: CustomBlockId,
17055 cx: &mut Context<Self>,
17056 ) -> Option<DisplayRow> {
17057 self.display_map
17058 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17059 }
17060
17061 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17062 self.focused_block = Some(focused_block);
17063 }
17064
17065 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17066 self.focused_block.take()
17067 }
17068
17069 pub fn insert_creases(
17070 &mut self,
17071 creases: impl IntoIterator<Item = Crease<Anchor>>,
17072 cx: &mut Context<Self>,
17073 ) -> Vec<CreaseId> {
17074 self.display_map
17075 .update(cx, |map, cx| map.insert_creases(creases, cx))
17076 }
17077
17078 pub fn remove_creases(
17079 &mut self,
17080 ids: impl IntoIterator<Item = CreaseId>,
17081 cx: &mut Context<Self>,
17082 ) -> Vec<(CreaseId, Range<Anchor>)> {
17083 self.display_map
17084 .update(cx, |map, cx| map.remove_creases(ids, cx))
17085 }
17086
17087 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17088 self.display_map
17089 .update(cx, |map, cx| map.snapshot(cx))
17090 .longest_row()
17091 }
17092
17093 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17094 self.display_map
17095 .update(cx, |map, cx| map.snapshot(cx))
17096 .max_point()
17097 }
17098
17099 pub fn text(&self, cx: &App) -> String {
17100 self.buffer.read(cx).read(cx).text()
17101 }
17102
17103 pub fn is_empty(&self, cx: &App) -> bool {
17104 self.buffer.read(cx).read(cx).is_empty()
17105 }
17106
17107 pub fn text_option(&self, cx: &App) -> Option<String> {
17108 let text = self.text(cx);
17109 let text = text.trim();
17110
17111 if text.is_empty() {
17112 return None;
17113 }
17114
17115 Some(text.to_string())
17116 }
17117
17118 pub fn set_text(
17119 &mut self,
17120 text: impl Into<Arc<str>>,
17121 window: &mut Window,
17122 cx: &mut Context<Self>,
17123 ) {
17124 self.transact(window, cx, |this, _, cx| {
17125 this.buffer
17126 .read(cx)
17127 .as_singleton()
17128 .expect("you can only call set_text on editors for singleton buffers")
17129 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17130 });
17131 }
17132
17133 pub fn display_text(&self, cx: &mut App) -> String {
17134 self.display_map
17135 .update(cx, |map, cx| map.snapshot(cx))
17136 .text()
17137 }
17138
17139 fn create_minimap(
17140 &self,
17141 minimap_settings: MinimapSettings,
17142 window: &mut Window,
17143 cx: &mut Context<Self>,
17144 ) -> Option<Entity<Self>> {
17145 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17146 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17147 }
17148
17149 fn initialize_new_minimap(
17150 &self,
17151 minimap_settings: MinimapSettings,
17152 window: &mut Window,
17153 cx: &mut Context<Self>,
17154 ) -> Entity<Self> {
17155 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17156
17157 let mut minimap = Editor::new_internal(
17158 EditorMode::Minimap {
17159 parent: cx.weak_entity(),
17160 },
17161 self.buffer.clone(),
17162 self.project.clone(),
17163 Some(self.display_map.clone()),
17164 window,
17165 cx,
17166 );
17167 minimap.scroll_manager.clone_state(&self.scroll_manager);
17168 minimap.set_text_style_refinement(TextStyleRefinement {
17169 font_size: Some(MINIMAP_FONT_SIZE),
17170 font_weight: Some(MINIMAP_FONT_WEIGHT),
17171 ..Default::default()
17172 });
17173 minimap.update_minimap_configuration(minimap_settings, cx);
17174 cx.new(|_| minimap)
17175 }
17176
17177 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17178 let current_line_highlight = minimap_settings
17179 .current_line_highlight
17180 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17181 self.set_current_line_highlight(Some(current_line_highlight));
17182 }
17183
17184 pub fn minimap(&self) -> Option<&Entity<Self>> {
17185 self.minimap
17186 .as_ref()
17187 .filter(|_| self.minimap_visibility.visible())
17188 }
17189
17190 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17191 let mut wrap_guides = smallvec![];
17192
17193 if self.show_wrap_guides == Some(false) {
17194 return wrap_guides;
17195 }
17196
17197 let settings = self.buffer.read(cx).language_settings(cx);
17198 if settings.show_wrap_guides {
17199 match self.soft_wrap_mode(cx) {
17200 SoftWrap::Column(soft_wrap) => {
17201 wrap_guides.push((soft_wrap as usize, true));
17202 }
17203 SoftWrap::Bounded(soft_wrap) => {
17204 wrap_guides.push((soft_wrap as usize, true));
17205 }
17206 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17207 }
17208 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17209 }
17210
17211 wrap_guides
17212 }
17213
17214 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17215 let settings = self.buffer.read(cx).language_settings(cx);
17216 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17217 match mode {
17218 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17219 SoftWrap::None
17220 }
17221 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17222 language_settings::SoftWrap::PreferredLineLength => {
17223 SoftWrap::Column(settings.preferred_line_length)
17224 }
17225 language_settings::SoftWrap::Bounded => {
17226 SoftWrap::Bounded(settings.preferred_line_length)
17227 }
17228 }
17229 }
17230
17231 pub fn set_soft_wrap_mode(
17232 &mut self,
17233 mode: language_settings::SoftWrap,
17234
17235 cx: &mut Context<Self>,
17236 ) {
17237 self.soft_wrap_mode_override = Some(mode);
17238 cx.notify();
17239 }
17240
17241 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17242 self.hard_wrap = hard_wrap;
17243 cx.notify();
17244 }
17245
17246 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17247 self.text_style_refinement = Some(style);
17248 }
17249
17250 /// called by the Element so we know what style we were most recently rendered with.
17251 pub(crate) fn set_style(
17252 &mut self,
17253 style: EditorStyle,
17254 window: &mut Window,
17255 cx: &mut Context<Self>,
17256 ) {
17257 // We intentionally do not inform the display map about the minimap style
17258 // so that wrapping is not recalculated and stays consistent for the editor
17259 // and its linked minimap.
17260 if !self.mode.is_minimap() {
17261 let rem_size = window.rem_size();
17262 self.display_map.update(cx, |map, cx| {
17263 map.set_font(
17264 style.text.font(),
17265 style.text.font_size.to_pixels(rem_size),
17266 cx,
17267 )
17268 });
17269 }
17270 self.style = Some(style);
17271 }
17272
17273 pub fn style(&self) -> Option<&EditorStyle> {
17274 self.style.as_ref()
17275 }
17276
17277 // Called by the element. This method is not designed to be called outside of the editor
17278 // element's layout code because it does not notify when rewrapping is computed synchronously.
17279 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17280 self.display_map
17281 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17282 }
17283
17284 pub fn set_soft_wrap(&mut self) {
17285 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17286 }
17287
17288 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17289 if self.soft_wrap_mode_override.is_some() {
17290 self.soft_wrap_mode_override.take();
17291 } else {
17292 let soft_wrap = match self.soft_wrap_mode(cx) {
17293 SoftWrap::GitDiff => return,
17294 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17295 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17296 language_settings::SoftWrap::None
17297 }
17298 };
17299 self.soft_wrap_mode_override = Some(soft_wrap);
17300 }
17301 cx.notify();
17302 }
17303
17304 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17305 let Some(workspace) = self.workspace() else {
17306 return;
17307 };
17308 let fs = workspace.read(cx).app_state().fs.clone();
17309 let current_show = TabBarSettings::get_global(cx).show;
17310 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17311 setting.show = Some(!current_show);
17312 });
17313 }
17314
17315 pub fn toggle_indent_guides(
17316 &mut self,
17317 _: &ToggleIndentGuides,
17318 _: &mut Window,
17319 cx: &mut Context<Self>,
17320 ) {
17321 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17322 self.buffer
17323 .read(cx)
17324 .language_settings(cx)
17325 .indent_guides
17326 .enabled
17327 });
17328 self.show_indent_guides = Some(!currently_enabled);
17329 cx.notify();
17330 }
17331
17332 fn should_show_indent_guides(&self) -> Option<bool> {
17333 self.show_indent_guides
17334 }
17335
17336 pub fn toggle_line_numbers(
17337 &mut self,
17338 _: &ToggleLineNumbers,
17339 _: &mut Window,
17340 cx: &mut Context<Self>,
17341 ) {
17342 let mut editor_settings = EditorSettings::get_global(cx).clone();
17343 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17344 EditorSettings::override_global(editor_settings, cx);
17345 }
17346
17347 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17348 if let Some(show_line_numbers) = self.show_line_numbers {
17349 return show_line_numbers;
17350 }
17351 EditorSettings::get_global(cx).gutter.line_numbers
17352 }
17353
17354 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17355 self.use_relative_line_numbers
17356 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17357 }
17358
17359 pub fn toggle_relative_line_numbers(
17360 &mut self,
17361 _: &ToggleRelativeLineNumbers,
17362 _: &mut Window,
17363 cx: &mut Context<Self>,
17364 ) {
17365 let is_relative = self.should_use_relative_line_numbers(cx);
17366 self.set_relative_line_number(Some(!is_relative), cx)
17367 }
17368
17369 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17370 self.use_relative_line_numbers = is_relative;
17371 cx.notify();
17372 }
17373
17374 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17375 self.show_gutter = show_gutter;
17376 cx.notify();
17377 }
17378
17379 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17380 self.show_scrollbars = ScrollbarAxes {
17381 horizontal: show,
17382 vertical: show,
17383 };
17384 cx.notify();
17385 }
17386
17387 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17388 self.show_scrollbars.vertical = show;
17389 cx.notify();
17390 }
17391
17392 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17393 self.show_scrollbars.horizontal = show;
17394 cx.notify();
17395 }
17396
17397 pub fn set_minimap_visibility(
17398 &mut self,
17399 minimap_visibility: MinimapVisibility,
17400 window: &mut Window,
17401 cx: &mut Context<Self>,
17402 ) {
17403 if self.minimap_visibility != minimap_visibility {
17404 if minimap_visibility.visible() && self.minimap.is_none() {
17405 let minimap_settings = EditorSettings::get_global(cx).minimap;
17406 self.minimap =
17407 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17408 }
17409 self.minimap_visibility = minimap_visibility;
17410 cx.notify();
17411 }
17412 }
17413
17414 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17415 self.set_show_scrollbars(false, cx);
17416 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17417 }
17418
17419 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17420 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17421 }
17422
17423 /// Normally the text in full mode and auto height editors is padded on the
17424 /// left side by roughly half a character width for improved hit testing.
17425 ///
17426 /// Use this method to disable this for cases where this is not wanted (e.g.
17427 /// if you want to align the editor text with some other text above or below)
17428 /// or if you want to add this padding to single-line editors.
17429 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17430 self.offset_content = offset_content;
17431 cx.notify();
17432 }
17433
17434 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17435 self.show_line_numbers = Some(show_line_numbers);
17436 cx.notify();
17437 }
17438
17439 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17440 self.disable_expand_excerpt_buttons = true;
17441 cx.notify();
17442 }
17443
17444 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17445 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17446 cx.notify();
17447 }
17448
17449 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17450 self.show_code_actions = Some(show_code_actions);
17451 cx.notify();
17452 }
17453
17454 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17455 self.show_runnables = Some(show_runnables);
17456 cx.notify();
17457 }
17458
17459 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17460 self.show_breakpoints = Some(show_breakpoints);
17461 cx.notify();
17462 }
17463
17464 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17465 if self.display_map.read(cx).masked != masked {
17466 self.display_map.update(cx, |map, _| map.masked = masked);
17467 }
17468 cx.notify()
17469 }
17470
17471 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17472 self.show_wrap_guides = Some(show_wrap_guides);
17473 cx.notify();
17474 }
17475
17476 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17477 self.show_indent_guides = Some(show_indent_guides);
17478 cx.notify();
17479 }
17480
17481 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17482 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17483 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17484 if let Some(dir) = file.abs_path(cx).parent() {
17485 return Some(dir.to_owned());
17486 }
17487 }
17488
17489 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17490 return Some(project_path.path.to_path_buf());
17491 }
17492 }
17493
17494 None
17495 }
17496
17497 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17498 self.active_excerpt(cx)?
17499 .1
17500 .read(cx)
17501 .file()
17502 .and_then(|f| f.as_local())
17503 }
17504
17505 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17506 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17507 let buffer = buffer.read(cx);
17508 if let Some(project_path) = buffer.project_path(cx) {
17509 let project = self.project.as_ref()?.read(cx);
17510 project.absolute_path(&project_path, cx)
17511 } else {
17512 buffer
17513 .file()
17514 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17515 }
17516 })
17517 }
17518
17519 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17520 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17521 let project_path = buffer.read(cx).project_path(cx)?;
17522 let project = self.project.as_ref()?.read(cx);
17523 let entry = project.entry_for_path(&project_path, cx)?;
17524 let path = entry.path.to_path_buf();
17525 Some(path)
17526 })
17527 }
17528
17529 pub fn reveal_in_finder(
17530 &mut self,
17531 _: &RevealInFileManager,
17532 _window: &mut Window,
17533 cx: &mut Context<Self>,
17534 ) {
17535 if let Some(target) = self.target_file(cx) {
17536 cx.reveal_path(&target.abs_path(cx));
17537 }
17538 }
17539
17540 pub fn copy_path(
17541 &mut self,
17542 _: &zed_actions::workspace::CopyPath,
17543 _window: &mut Window,
17544 cx: &mut Context<Self>,
17545 ) {
17546 if let Some(path) = self.target_file_abs_path(cx) {
17547 if let Some(path) = path.to_str() {
17548 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17549 }
17550 }
17551 }
17552
17553 pub fn copy_relative_path(
17554 &mut self,
17555 _: &zed_actions::workspace::CopyRelativePath,
17556 _window: &mut Window,
17557 cx: &mut Context<Self>,
17558 ) {
17559 if let Some(path) = self.target_file_path(cx) {
17560 if let Some(path) = path.to_str() {
17561 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17562 }
17563 }
17564 }
17565
17566 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17567 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17568 buffer.read(cx).project_path(cx)
17569 } else {
17570 None
17571 }
17572 }
17573
17574 // Returns true if the editor handled a go-to-line request
17575 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17576 maybe!({
17577 let breakpoint_store = self.breakpoint_store.as_ref()?;
17578
17579 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17580 else {
17581 self.clear_row_highlights::<ActiveDebugLine>();
17582 return None;
17583 };
17584
17585 let position = active_stack_frame.position;
17586 let buffer_id = position.buffer_id?;
17587 let snapshot = self
17588 .project
17589 .as_ref()?
17590 .read(cx)
17591 .buffer_for_id(buffer_id, cx)?
17592 .read(cx)
17593 .snapshot();
17594
17595 let mut handled = false;
17596 for (id, ExcerptRange { context, .. }) in
17597 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17598 {
17599 if context.start.cmp(&position, &snapshot).is_ge()
17600 || context.end.cmp(&position, &snapshot).is_lt()
17601 {
17602 continue;
17603 }
17604 let snapshot = self.buffer.read(cx).snapshot(cx);
17605 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17606
17607 handled = true;
17608 self.clear_row_highlights::<ActiveDebugLine>();
17609
17610 self.go_to_line::<ActiveDebugLine>(
17611 multibuffer_anchor,
17612 Some(cx.theme().colors().editor_debugger_active_line_background),
17613 window,
17614 cx,
17615 );
17616
17617 cx.notify();
17618 }
17619
17620 handled.then_some(())
17621 })
17622 .is_some()
17623 }
17624
17625 pub fn copy_file_name_without_extension(
17626 &mut self,
17627 _: &CopyFileNameWithoutExtension,
17628 _: &mut Window,
17629 cx: &mut Context<Self>,
17630 ) {
17631 if let Some(file) = self.target_file(cx) {
17632 if let Some(file_stem) = file.path().file_stem() {
17633 if let Some(name) = file_stem.to_str() {
17634 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17635 }
17636 }
17637 }
17638 }
17639
17640 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17641 if let Some(file) = self.target_file(cx) {
17642 if let Some(file_name) = file.path().file_name() {
17643 if let Some(name) = file_name.to_str() {
17644 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17645 }
17646 }
17647 }
17648 }
17649
17650 pub fn toggle_git_blame(
17651 &mut self,
17652 _: &::git::Blame,
17653 window: &mut Window,
17654 cx: &mut Context<Self>,
17655 ) {
17656 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17657
17658 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17659 self.start_git_blame(true, window, cx);
17660 }
17661
17662 cx.notify();
17663 }
17664
17665 pub fn toggle_git_blame_inline(
17666 &mut self,
17667 _: &ToggleGitBlameInline,
17668 window: &mut Window,
17669 cx: &mut Context<Self>,
17670 ) {
17671 self.toggle_git_blame_inline_internal(true, window, cx);
17672 cx.notify();
17673 }
17674
17675 pub fn open_git_blame_commit(
17676 &mut self,
17677 _: &OpenGitBlameCommit,
17678 window: &mut Window,
17679 cx: &mut Context<Self>,
17680 ) {
17681 self.open_git_blame_commit_internal(window, cx);
17682 }
17683
17684 fn open_git_blame_commit_internal(
17685 &mut self,
17686 window: &mut Window,
17687 cx: &mut Context<Self>,
17688 ) -> Option<()> {
17689 let blame = self.blame.as_ref()?;
17690 let snapshot = self.snapshot(window, cx);
17691 let cursor = self.selections.newest::<Point>(cx).head();
17692 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17693 let blame_entry = blame
17694 .update(cx, |blame, cx| {
17695 blame
17696 .blame_for_rows(
17697 &[RowInfo {
17698 buffer_id: Some(buffer.remote_id()),
17699 buffer_row: Some(point.row),
17700 ..Default::default()
17701 }],
17702 cx,
17703 )
17704 .next()
17705 })
17706 .flatten()?;
17707 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17708 let repo = blame.read(cx).repository(cx)?;
17709 let workspace = self.workspace()?.downgrade();
17710 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17711 None
17712 }
17713
17714 pub fn git_blame_inline_enabled(&self) -> bool {
17715 self.git_blame_inline_enabled
17716 }
17717
17718 pub fn toggle_selection_menu(
17719 &mut self,
17720 _: &ToggleSelectionMenu,
17721 _: &mut Window,
17722 cx: &mut Context<Self>,
17723 ) {
17724 self.show_selection_menu = self
17725 .show_selection_menu
17726 .map(|show_selections_menu| !show_selections_menu)
17727 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17728
17729 cx.notify();
17730 }
17731
17732 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17733 self.show_selection_menu
17734 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17735 }
17736
17737 fn start_git_blame(
17738 &mut self,
17739 user_triggered: bool,
17740 window: &mut Window,
17741 cx: &mut Context<Self>,
17742 ) {
17743 if let Some(project) = self.project.as_ref() {
17744 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17745 return;
17746 };
17747
17748 if buffer.read(cx).file().is_none() {
17749 return;
17750 }
17751
17752 let focused = self.focus_handle(cx).contains_focused(window, cx);
17753
17754 let project = project.clone();
17755 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17756 self.blame_subscription =
17757 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17758 self.blame = Some(blame);
17759 }
17760 }
17761
17762 fn toggle_git_blame_inline_internal(
17763 &mut self,
17764 user_triggered: bool,
17765 window: &mut Window,
17766 cx: &mut Context<Self>,
17767 ) {
17768 if self.git_blame_inline_enabled {
17769 self.git_blame_inline_enabled = false;
17770 self.show_git_blame_inline = false;
17771 self.show_git_blame_inline_delay_task.take();
17772 } else {
17773 self.git_blame_inline_enabled = true;
17774 self.start_git_blame_inline(user_triggered, window, cx);
17775 }
17776
17777 cx.notify();
17778 }
17779
17780 fn start_git_blame_inline(
17781 &mut self,
17782 user_triggered: bool,
17783 window: &mut Window,
17784 cx: &mut Context<Self>,
17785 ) {
17786 self.start_git_blame(user_triggered, window, cx);
17787
17788 if ProjectSettings::get_global(cx)
17789 .git
17790 .inline_blame_delay()
17791 .is_some()
17792 {
17793 self.start_inline_blame_timer(window, cx);
17794 } else {
17795 self.show_git_blame_inline = true
17796 }
17797 }
17798
17799 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17800 self.blame.as_ref()
17801 }
17802
17803 pub fn show_git_blame_gutter(&self) -> bool {
17804 self.show_git_blame_gutter
17805 }
17806
17807 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17808 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17809 }
17810
17811 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17812 self.show_git_blame_inline
17813 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17814 && !self.newest_selection_head_on_empty_line(cx)
17815 && self.has_blame_entries(cx)
17816 }
17817
17818 fn has_blame_entries(&self, cx: &App) -> bool {
17819 self.blame()
17820 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17821 }
17822
17823 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17824 let cursor_anchor = self.selections.newest_anchor().head();
17825
17826 let snapshot = self.buffer.read(cx).snapshot(cx);
17827 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17828
17829 snapshot.line_len(buffer_row) == 0
17830 }
17831
17832 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17833 let buffer_and_selection = maybe!({
17834 let selection = self.selections.newest::<Point>(cx);
17835 let selection_range = selection.range();
17836
17837 let multi_buffer = self.buffer().read(cx);
17838 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17839 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17840
17841 let (buffer, range, _) = if selection.reversed {
17842 buffer_ranges.first()
17843 } else {
17844 buffer_ranges.last()
17845 }?;
17846
17847 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17848 ..text::ToPoint::to_point(&range.end, &buffer).row;
17849 Some((
17850 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17851 selection,
17852 ))
17853 });
17854
17855 let Some((buffer, selection)) = buffer_and_selection else {
17856 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17857 };
17858
17859 let Some(project) = self.project.as_ref() else {
17860 return Task::ready(Err(anyhow!("editor does not have project")));
17861 };
17862
17863 project.update(cx, |project, cx| {
17864 project.get_permalink_to_line(&buffer, selection, cx)
17865 })
17866 }
17867
17868 pub fn copy_permalink_to_line(
17869 &mut self,
17870 _: &CopyPermalinkToLine,
17871 window: &mut Window,
17872 cx: &mut Context<Self>,
17873 ) {
17874 let permalink_task = self.get_permalink_to_line(cx);
17875 let workspace = self.workspace();
17876
17877 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17878 Ok(permalink) => {
17879 cx.update(|_, cx| {
17880 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17881 })
17882 .ok();
17883 }
17884 Err(err) => {
17885 let message = format!("Failed to copy permalink: {err}");
17886
17887 anyhow::Result::<()>::Err(err).log_err();
17888
17889 if let Some(workspace) = workspace {
17890 workspace
17891 .update_in(cx, |workspace, _, cx| {
17892 struct CopyPermalinkToLine;
17893
17894 workspace.show_toast(
17895 Toast::new(
17896 NotificationId::unique::<CopyPermalinkToLine>(),
17897 message,
17898 ),
17899 cx,
17900 )
17901 })
17902 .ok();
17903 }
17904 }
17905 })
17906 .detach();
17907 }
17908
17909 pub fn copy_file_location(
17910 &mut self,
17911 _: &CopyFileLocation,
17912 _: &mut Window,
17913 cx: &mut Context<Self>,
17914 ) {
17915 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17916 if let Some(file) = self.target_file(cx) {
17917 if let Some(path) = file.path().to_str() {
17918 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17919 }
17920 }
17921 }
17922
17923 pub fn open_permalink_to_line(
17924 &mut self,
17925 _: &OpenPermalinkToLine,
17926 window: &mut Window,
17927 cx: &mut Context<Self>,
17928 ) {
17929 let permalink_task = self.get_permalink_to_line(cx);
17930 let workspace = self.workspace();
17931
17932 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17933 Ok(permalink) => {
17934 cx.update(|_, cx| {
17935 cx.open_url(permalink.as_ref());
17936 })
17937 .ok();
17938 }
17939 Err(err) => {
17940 let message = format!("Failed to open permalink: {err}");
17941
17942 anyhow::Result::<()>::Err(err).log_err();
17943
17944 if let Some(workspace) = workspace {
17945 workspace
17946 .update(cx, |workspace, cx| {
17947 struct OpenPermalinkToLine;
17948
17949 workspace.show_toast(
17950 Toast::new(
17951 NotificationId::unique::<OpenPermalinkToLine>(),
17952 message,
17953 ),
17954 cx,
17955 )
17956 })
17957 .ok();
17958 }
17959 }
17960 })
17961 .detach();
17962 }
17963
17964 pub fn insert_uuid_v4(
17965 &mut self,
17966 _: &InsertUuidV4,
17967 window: &mut Window,
17968 cx: &mut Context<Self>,
17969 ) {
17970 self.insert_uuid(UuidVersion::V4, window, cx);
17971 }
17972
17973 pub fn insert_uuid_v7(
17974 &mut self,
17975 _: &InsertUuidV7,
17976 window: &mut Window,
17977 cx: &mut Context<Self>,
17978 ) {
17979 self.insert_uuid(UuidVersion::V7, window, cx);
17980 }
17981
17982 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17983 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17984 self.transact(window, cx, |this, window, cx| {
17985 let edits = this
17986 .selections
17987 .all::<Point>(cx)
17988 .into_iter()
17989 .map(|selection| {
17990 let uuid = match version {
17991 UuidVersion::V4 => uuid::Uuid::new_v4(),
17992 UuidVersion::V7 => uuid::Uuid::now_v7(),
17993 };
17994
17995 (selection.range(), uuid.to_string())
17996 });
17997 this.edit(edits, cx);
17998 this.refresh_inline_completion(true, false, window, cx);
17999 });
18000 }
18001
18002 pub fn open_selections_in_multibuffer(
18003 &mut self,
18004 _: &OpenSelectionsInMultibuffer,
18005 window: &mut Window,
18006 cx: &mut Context<Self>,
18007 ) {
18008 let multibuffer = self.buffer.read(cx);
18009
18010 let Some(buffer) = multibuffer.as_singleton() else {
18011 return;
18012 };
18013
18014 let Some(workspace) = self.workspace() else {
18015 return;
18016 };
18017
18018 let locations = self
18019 .selections
18020 .disjoint_anchors()
18021 .iter()
18022 .map(|selection| {
18023 let range = if selection.reversed {
18024 selection.end.text_anchor..selection.start.text_anchor
18025 } else {
18026 selection.start.text_anchor..selection.end.text_anchor
18027 };
18028 Location {
18029 buffer: buffer.clone(),
18030 range,
18031 }
18032 })
18033 .collect::<Vec<_>>();
18034
18035 let title = multibuffer.title(cx).to_string();
18036
18037 cx.spawn_in(window, async move |_, cx| {
18038 workspace.update_in(cx, |workspace, window, cx| {
18039 Self::open_locations_in_multibuffer(
18040 workspace,
18041 locations,
18042 format!("Selections for '{title}'"),
18043 false,
18044 MultibufferSelectionMode::All,
18045 window,
18046 cx,
18047 );
18048 })
18049 })
18050 .detach();
18051 }
18052
18053 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18054 /// last highlight added will be used.
18055 ///
18056 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18057 pub fn highlight_rows<T: 'static>(
18058 &mut self,
18059 range: Range<Anchor>,
18060 color: Hsla,
18061 options: RowHighlightOptions,
18062 cx: &mut Context<Self>,
18063 ) {
18064 let snapshot = self.buffer().read(cx).snapshot(cx);
18065 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18066 let ix = row_highlights.binary_search_by(|highlight| {
18067 Ordering::Equal
18068 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18069 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18070 });
18071
18072 if let Err(mut ix) = ix {
18073 let index = post_inc(&mut self.highlight_order);
18074
18075 // If this range intersects with the preceding highlight, then merge it with
18076 // the preceding highlight. Otherwise insert a new highlight.
18077 let mut merged = false;
18078 if ix > 0 {
18079 let prev_highlight = &mut row_highlights[ix - 1];
18080 if prev_highlight
18081 .range
18082 .end
18083 .cmp(&range.start, &snapshot)
18084 .is_ge()
18085 {
18086 ix -= 1;
18087 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18088 prev_highlight.range.end = range.end;
18089 }
18090 merged = true;
18091 prev_highlight.index = index;
18092 prev_highlight.color = color;
18093 prev_highlight.options = options;
18094 }
18095 }
18096
18097 if !merged {
18098 row_highlights.insert(
18099 ix,
18100 RowHighlight {
18101 range: range.clone(),
18102 index,
18103 color,
18104 options,
18105 type_id: TypeId::of::<T>(),
18106 },
18107 );
18108 }
18109
18110 // If any of the following highlights intersect with this one, merge them.
18111 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18112 let highlight = &row_highlights[ix];
18113 if next_highlight
18114 .range
18115 .start
18116 .cmp(&highlight.range.end, &snapshot)
18117 .is_le()
18118 {
18119 if next_highlight
18120 .range
18121 .end
18122 .cmp(&highlight.range.end, &snapshot)
18123 .is_gt()
18124 {
18125 row_highlights[ix].range.end = next_highlight.range.end;
18126 }
18127 row_highlights.remove(ix + 1);
18128 } else {
18129 break;
18130 }
18131 }
18132 }
18133 }
18134
18135 /// Remove any highlighted row ranges of the given type that intersect the
18136 /// given ranges.
18137 pub fn remove_highlighted_rows<T: 'static>(
18138 &mut self,
18139 ranges_to_remove: Vec<Range<Anchor>>,
18140 cx: &mut Context<Self>,
18141 ) {
18142 let snapshot = self.buffer().read(cx).snapshot(cx);
18143 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18144 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18145 row_highlights.retain(|highlight| {
18146 while let Some(range_to_remove) = ranges_to_remove.peek() {
18147 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18148 Ordering::Less | Ordering::Equal => {
18149 ranges_to_remove.next();
18150 }
18151 Ordering::Greater => {
18152 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18153 Ordering::Less | Ordering::Equal => {
18154 return false;
18155 }
18156 Ordering::Greater => break,
18157 }
18158 }
18159 }
18160 }
18161
18162 true
18163 })
18164 }
18165
18166 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18167 pub fn clear_row_highlights<T: 'static>(&mut self) {
18168 self.highlighted_rows.remove(&TypeId::of::<T>());
18169 }
18170
18171 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18172 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18173 self.highlighted_rows
18174 .get(&TypeId::of::<T>())
18175 .map_or(&[] as &[_], |vec| vec.as_slice())
18176 .iter()
18177 .map(|highlight| (highlight.range.clone(), highlight.color))
18178 }
18179
18180 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18181 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18182 /// Allows to ignore certain kinds of highlights.
18183 pub fn highlighted_display_rows(
18184 &self,
18185 window: &mut Window,
18186 cx: &mut App,
18187 ) -> BTreeMap<DisplayRow, LineHighlight> {
18188 let snapshot = self.snapshot(window, cx);
18189 let mut used_highlight_orders = HashMap::default();
18190 self.highlighted_rows
18191 .iter()
18192 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18193 .fold(
18194 BTreeMap::<DisplayRow, LineHighlight>::new(),
18195 |mut unique_rows, highlight| {
18196 let start = highlight.range.start.to_display_point(&snapshot);
18197 let end = highlight.range.end.to_display_point(&snapshot);
18198 let start_row = start.row().0;
18199 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18200 && end.column() == 0
18201 {
18202 end.row().0.saturating_sub(1)
18203 } else {
18204 end.row().0
18205 };
18206 for row in start_row..=end_row {
18207 let used_index =
18208 used_highlight_orders.entry(row).or_insert(highlight.index);
18209 if highlight.index >= *used_index {
18210 *used_index = highlight.index;
18211 unique_rows.insert(
18212 DisplayRow(row),
18213 LineHighlight {
18214 include_gutter: highlight.options.include_gutter,
18215 border: None,
18216 background: highlight.color.into(),
18217 type_id: Some(highlight.type_id),
18218 },
18219 );
18220 }
18221 }
18222 unique_rows
18223 },
18224 )
18225 }
18226
18227 pub fn highlighted_display_row_for_autoscroll(
18228 &self,
18229 snapshot: &DisplaySnapshot,
18230 ) -> Option<DisplayRow> {
18231 self.highlighted_rows
18232 .values()
18233 .flat_map(|highlighted_rows| highlighted_rows.iter())
18234 .filter_map(|highlight| {
18235 if highlight.options.autoscroll {
18236 Some(highlight.range.start.to_display_point(snapshot).row())
18237 } else {
18238 None
18239 }
18240 })
18241 .min()
18242 }
18243
18244 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18245 self.highlight_background::<SearchWithinRange>(
18246 ranges,
18247 |colors| colors.editor_document_highlight_read_background,
18248 cx,
18249 )
18250 }
18251
18252 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18253 self.breadcrumb_header = Some(new_header);
18254 }
18255
18256 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18257 self.clear_background_highlights::<SearchWithinRange>(cx);
18258 }
18259
18260 pub fn highlight_background<T: 'static>(
18261 &mut self,
18262 ranges: &[Range<Anchor>],
18263 color_fetcher: fn(&ThemeColors) -> Hsla,
18264 cx: &mut Context<Self>,
18265 ) {
18266 self.background_highlights
18267 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18268 self.scrollbar_marker_state.dirty = true;
18269 cx.notify();
18270 }
18271
18272 pub fn clear_background_highlights<T: 'static>(
18273 &mut self,
18274 cx: &mut Context<Self>,
18275 ) -> Option<BackgroundHighlight> {
18276 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
18277 if !text_highlights.1.is_empty() {
18278 self.scrollbar_marker_state.dirty = true;
18279 cx.notify();
18280 }
18281 Some(text_highlights)
18282 }
18283
18284 pub fn highlight_gutter<T: 'static>(
18285 &mut self,
18286 ranges: &[Range<Anchor>],
18287 color_fetcher: fn(&App) -> Hsla,
18288 cx: &mut Context<Self>,
18289 ) {
18290 self.gutter_highlights
18291 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18292 cx.notify();
18293 }
18294
18295 pub fn clear_gutter_highlights<T: 'static>(
18296 &mut self,
18297 cx: &mut Context<Self>,
18298 ) -> Option<GutterHighlight> {
18299 cx.notify();
18300 self.gutter_highlights.remove(&TypeId::of::<T>())
18301 }
18302
18303 #[cfg(feature = "test-support")]
18304 pub fn all_text_background_highlights(
18305 &self,
18306 window: &mut Window,
18307 cx: &mut Context<Self>,
18308 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18309 let snapshot = self.snapshot(window, cx);
18310 let buffer = &snapshot.buffer_snapshot;
18311 let start = buffer.anchor_before(0);
18312 let end = buffer.anchor_after(buffer.len());
18313 let theme = cx.theme().colors();
18314 self.background_highlights_in_range(start..end, &snapshot, theme)
18315 }
18316
18317 #[cfg(feature = "test-support")]
18318 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18319 let snapshot = self.buffer().read(cx).snapshot(cx);
18320
18321 let highlights = self
18322 .background_highlights
18323 .get(&TypeId::of::<items::BufferSearchHighlights>());
18324
18325 if let Some((_color, ranges)) = highlights {
18326 ranges
18327 .iter()
18328 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18329 .collect_vec()
18330 } else {
18331 vec![]
18332 }
18333 }
18334
18335 fn document_highlights_for_position<'a>(
18336 &'a self,
18337 position: Anchor,
18338 buffer: &'a MultiBufferSnapshot,
18339 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18340 let read_highlights = self
18341 .background_highlights
18342 .get(&TypeId::of::<DocumentHighlightRead>())
18343 .map(|h| &h.1);
18344 let write_highlights = self
18345 .background_highlights
18346 .get(&TypeId::of::<DocumentHighlightWrite>())
18347 .map(|h| &h.1);
18348 let left_position = position.bias_left(buffer);
18349 let right_position = position.bias_right(buffer);
18350 read_highlights
18351 .into_iter()
18352 .chain(write_highlights)
18353 .flat_map(move |ranges| {
18354 let start_ix = match ranges.binary_search_by(|probe| {
18355 let cmp = probe.end.cmp(&left_position, buffer);
18356 if cmp.is_ge() {
18357 Ordering::Greater
18358 } else {
18359 Ordering::Less
18360 }
18361 }) {
18362 Ok(i) | Err(i) => i,
18363 };
18364
18365 ranges[start_ix..]
18366 .iter()
18367 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
18368 })
18369 }
18370
18371 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18372 self.background_highlights
18373 .get(&TypeId::of::<T>())
18374 .map_or(false, |(_, highlights)| !highlights.is_empty())
18375 }
18376
18377 pub fn background_highlights_in_range(
18378 &self,
18379 search_range: Range<Anchor>,
18380 display_snapshot: &DisplaySnapshot,
18381 theme: &ThemeColors,
18382 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18383 let mut results = Vec::new();
18384 for (color_fetcher, ranges) in self.background_highlights.values() {
18385 let color = color_fetcher(theme);
18386 let start_ix = match ranges.binary_search_by(|probe| {
18387 let cmp = probe
18388 .end
18389 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18390 if cmp.is_gt() {
18391 Ordering::Greater
18392 } else {
18393 Ordering::Less
18394 }
18395 }) {
18396 Ok(i) | Err(i) => i,
18397 };
18398 for range in &ranges[start_ix..] {
18399 if range
18400 .start
18401 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18402 .is_ge()
18403 {
18404 break;
18405 }
18406
18407 let start = range.start.to_display_point(display_snapshot);
18408 let end = range.end.to_display_point(display_snapshot);
18409 results.push((start..end, color))
18410 }
18411 }
18412 results
18413 }
18414
18415 pub fn background_highlight_row_ranges<T: 'static>(
18416 &self,
18417 search_range: Range<Anchor>,
18418 display_snapshot: &DisplaySnapshot,
18419 count: usize,
18420 ) -> Vec<RangeInclusive<DisplayPoint>> {
18421 let mut results = Vec::new();
18422 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
18423 return vec![];
18424 };
18425
18426 let start_ix = match ranges.binary_search_by(|probe| {
18427 let cmp = probe
18428 .end
18429 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18430 if cmp.is_gt() {
18431 Ordering::Greater
18432 } else {
18433 Ordering::Less
18434 }
18435 }) {
18436 Ok(i) | Err(i) => i,
18437 };
18438 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18439 if let (Some(start_display), Some(end_display)) = (start, end) {
18440 results.push(
18441 start_display.to_display_point(display_snapshot)
18442 ..=end_display.to_display_point(display_snapshot),
18443 );
18444 }
18445 };
18446 let mut start_row: Option<Point> = None;
18447 let mut end_row: Option<Point> = None;
18448 if ranges.len() > count {
18449 return Vec::new();
18450 }
18451 for range in &ranges[start_ix..] {
18452 if range
18453 .start
18454 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18455 .is_ge()
18456 {
18457 break;
18458 }
18459 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18460 if let Some(current_row) = &end_row {
18461 if end.row == current_row.row {
18462 continue;
18463 }
18464 }
18465 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18466 if start_row.is_none() {
18467 assert_eq!(end_row, None);
18468 start_row = Some(start);
18469 end_row = Some(end);
18470 continue;
18471 }
18472 if let Some(current_end) = end_row.as_mut() {
18473 if start.row > current_end.row + 1 {
18474 push_region(start_row, end_row);
18475 start_row = Some(start);
18476 end_row = Some(end);
18477 } else {
18478 // Merge two hunks.
18479 *current_end = end;
18480 }
18481 } else {
18482 unreachable!();
18483 }
18484 }
18485 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18486 push_region(start_row, end_row);
18487 results
18488 }
18489
18490 pub fn gutter_highlights_in_range(
18491 &self,
18492 search_range: Range<Anchor>,
18493 display_snapshot: &DisplaySnapshot,
18494 cx: &App,
18495 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18496 let mut results = Vec::new();
18497 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18498 let color = color_fetcher(cx);
18499 let start_ix = match ranges.binary_search_by(|probe| {
18500 let cmp = probe
18501 .end
18502 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18503 if cmp.is_gt() {
18504 Ordering::Greater
18505 } else {
18506 Ordering::Less
18507 }
18508 }) {
18509 Ok(i) | Err(i) => i,
18510 };
18511 for range in &ranges[start_ix..] {
18512 if range
18513 .start
18514 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18515 .is_ge()
18516 {
18517 break;
18518 }
18519
18520 let start = range.start.to_display_point(display_snapshot);
18521 let end = range.end.to_display_point(display_snapshot);
18522 results.push((start..end, color))
18523 }
18524 }
18525 results
18526 }
18527
18528 /// Get the text ranges corresponding to the redaction query
18529 pub fn redacted_ranges(
18530 &self,
18531 search_range: Range<Anchor>,
18532 display_snapshot: &DisplaySnapshot,
18533 cx: &App,
18534 ) -> Vec<Range<DisplayPoint>> {
18535 display_snapshot
18536 .buffer_snapshot
18537 .redacted_ranges(search_range, |file| {
18538 if let Some(file) = file {
18539 file.is_private()
18540 && EditorSettings::get(
18541 Some(SettingsLocation {
18542 worktree_id: file.worktree_id(cx),
18543 path: file.path().as_ref(),
18544 }),
18545 cx,
18546 )
18547 .redact_private_values
18548 } else {
18549 false
18550 }
18551 })
18552 .map(|range| {
18553 range.start.to_display_point(display_snapshot)
18554 ..range.end.to_display_point(display_snapshot)
18555 })
18556 .collect()
18557 }
18558
18559 pub fn highlight_text<T: 'static>(
18560 &mut self,
18561 ranges: Vec<Range<Anchor>>,
18562 style: HighlightStyle,
18563 cx: &mut Context<Self>,
18564 ) {
18565 self.display_map.update(cx, |map, _| {
18566 map.highlight_text(TypeId::of::<T>(), ranges, style)
18567 });
18568 cx.notify();
18569 }
18570
18571 pub(crate) fn highlight_inlays<T: 'static>(
18572 &mut self,
18573 highlights: Vec<InlayHighlight>,
18574 style: HighlightStyle,
18575 cx: &mut Context<Self>,
18576 ) {
18577 self.display_map.update(cx, |map, _| {
18578 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18579 });
18580 cx.notify();
18581 }
18582
18583 pub fn text_highlights<'a, T: 'static>(
18584 &'a self,
18585 cx: &'a App,
18586 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18587 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18588 }
18589
18590 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18591 let cleared = self
18592 .display_map
18593 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18594 if cleared {
18595 cx.notify();
18596 }
18597 }
18598
18599 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18600 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18601 && self.focus_handle.is_focused(window)
18602 }
18603
18604 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18605 self.show_cursor_when_unfocused = is_enabled;
18606 cx.notify();
18607 }
18608
18609 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18610 cx.notify();
18611 }
18612
18613 fn on_debug_session_event(
18614 &mut self,
18615 _session: Entity<Session>,
18616 event: &SessionEvent,
18617 cx: &mut Context<Self>,
18618 ) {
18619 match event {
18620 SessionEvent::InvalidateInlineValue => {
18621 self.refresh_inline_values(cx);
18622 }
18623 _ => {}
18624 }
18625 }
18626
18627 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18628 let Some(project) = self.project.clone() else {
18629 return;
18630 };
18631
18632 if !self.inline_value_cache.enabled {
18633 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18634 self.splice_inlays(&inlays, Vec::new(), cx);
18635 return;
18636 }
18637
18638 let current_execution_position = self
18639 .highlighted_rows
18640 .get(&TypeId::of::<ActiveDebugLine>())
18641 .and_then(|lines| lines.last().map(|line| line.range.start));
18642
18643 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18644 let inline_values = editor
18645 .update(cx, |editor, cx| {
18646 let Some(current_execution_position) = current_execution_position else {
18647 return Some(Task::ready(Ok(Vec::new())));
18648 };
18649
18650 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18651 let snapshot = buffer.snapshot(cx);
18652
18653 let excerpt = snapshot.excerpt_containing(
18654 current_execution_position..current_execution_position,
18655 )?;
18656
18657 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18658 })?;
18659
18660 let range =
18661 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18662
18663 project.inline_values(buffer, range, cx)
18664 })
18665 .ok()
18666 .flatten()?
18667 .await
18668 .context("refreshing debugger inlays")
18669 .log_err()?;
18670
18671 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18672
18673 for (buffer_id, inline_value) in inline_values
18674 .into_iter()
18675 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18676 {
18677 buffer_inline_values
18678 .entry(buffer_id)
18679 .or_default()
18680 .push(inline_value);
18681 }
18682
18683 editor
18684 .update(cx, |editor, cx| {
18685 let snapshot = editor.buffer.read(cx).snapshot(cx);
18686 let mut new_inlays = Vec::default();
18687
18688 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18689 let buffer_id = buffer_snapshot.remote_id();
18690 buffer_inline_values
18691 .get(&buffer_id)
18692 .into_iter()
18693 .flatten()
18694 .for_each(|hint| {
18695 let inlay = Inlay::debugger_hint(
18696 post_inc(&mut editor.next_inlay_id),
18697 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18698 hint.text(),
18699 );
18700
18701 new_inlays.push(inlay);
18702 });
18703 }
18704
18705 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18706 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18707
18708 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18709 })
18710 .ok()?;
18711 Some(())
18712 });
18713 }
18714
18715 fn on_buffer_event(
18716 &mut self,
18717 multibuffer: &Entity<MultiBuffer>,
18718 event: &multi_buffer::Event,
18719 window: &mut Window,
18720 cx: &mut Context<Self>,
18721 ) {
18722 match event {
18723 multi_buffer::Event::Edited {
18724 singleton_buffer_edited,
18725 edited_buffer,
18726 } => {
18727 self.scrollbar_marker_state.dirty = true;
18728 self.active_indent_guides_state.dirty = true;
18729 self.refresh_active_diagnostics(cx);
18730 self.refresh_code_actions(window, cx);
18731 self.refresh_selected_text_highlights(true, window, cx);
18732 refresh_matching_bracket_highlights(self, window, cx);
18733 if self.has_active_inline_completion() {
18734 self.update_visible_inline_completion(window, cx);
18735 }
18736 if let Some(project) = self.project.as_ref() {
18737 project.update(cx, |project, cx| {
18738 if edited_buffer
18739 .as_ref()
18740 .is_some_and(|buffer| buffer.read(cx).file().is_some())
18741 {
18742 // Diagnostics are not local: an edit within one file (`pub mod foo()` -> `pub mod bar()`), may cause errors in another files with `foo()`.
18743 // Hence, emit a project-wide event to pull for every buffer's diagnostics that has an open editor.
18744 // TODO: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnostic_refresh explains the flow how
18745 // diagnostics should be pulled: instead of pulling every open editor's buffer's diagnostics (which happens effectively due to emitting this event),
18746 // we should only pull for the current buffer's diagnostics and get the rest via the workspace diagnostics LSP request — this is not implemented yet.
18747 cx.emit(project::Event::PullWorkspaceDiagnostics);
18748 }
18749
18750 if let Some(buffer) = edited_buffer {
18751 self.registered_buffers
18752 .entry(buffer.read(cx).remote_id())
18753 .or_insert_with(|| {
18754 project.register_buffer_with_language_servers(&buffer, cx)
18755 });
18756 }
18757 });
18758 }
18759 cx.emit(EditorEvent::BufferEdited);
18760 cx.emit(SearchEvent::MatchesInvalidated);
18761 if *singleton_buffer_edited {
18762 if let Some(project) = &self.project {
18763 #[allow(clippy::mutable_key_type)]
18764 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18765 multibuffer
18766 .all_buffers()
18767 .into_iter()
18768 .filter_map(|buffer| {
18769 buffer.update(cx, |buffer, cx| {
18770 let language = buffer.language()?;
18771 let should_discard = project.update(cx, |project, cx| {
18772 project.is_local()
18773 && !project.has_language_servers_for(buffer, cx)
18774 });
18775 should_discard.not().then_some(language.clone())
18776 })
18777 })
18778 .collect::<HashSet<_>>()
18779 });
18780 if !languages_affected.is_empty() {
18781 self.refresh_inlay_hints(
18782 InlayHintRefreshReason::BufferEdited(languages_affected),
18783 cx,
18784 );
18785 }
18786 }
18787 }
18788
18789 let Some(project) = &self.project else { return };
18790 let (telemetry, is_via_ssh) = {
18791 let project = project.read(cx);
18792 let telemetry = project.client().telemetry().clone();
18793 let is_via_ssh = project.is_via_ssh();
18794 (telemetry, is_via_ssh)
18795 };
18796 refresh_linked_ranges(self, window, cx);
18797 telemetry.log_edit_event("editor", is_via_ssh);
18798 }
18799 multi_buffer::Event::ExcerptsAdded {
18800 buffer,
18801 predecessor,
18802 excerpts,
18803 } => {
18804 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18805 let buffer_id = buffer.read(cx).remote_id();
18806 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18807 if let Some(project) = &self.project {
18808 update_uncommitted_diff_for_buffer(
18809 cx.entity(),
18810 project,
18811 [buffer.clone()],
18812 self.buffer.clone(),
18813 cx,
18814 )
18815 .detach();
18816 }
18817 }
18818 cx.emit(EditorEvent::ExcerptsAdded {
18819 buffer: buffer.clone(),
18820 predecessor: *predecessor,
18821 excerpts: excerpts.clone(),
18822 });
18823 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18824 }
18825 multi_buffer::Event::ExcerptsRemoved {
18826 ids,
18827 removed_buffer_ids,
18828 } => {
18829 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18830 let buffer = self.buffer.read(cx);
18831 self.registered_buffers
18832 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18833 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18834 cx.emit(EditorEvent::ExcerptsRemoved {
18835 ids: ids.clone(),
18836 removed_buffer_ids: removed_buffer_ids.clone(),
18837 })
18838 }
18839 multi_buffer::Event::ExcerptsEdited {
18840 excerpt_ids,
18841 buffer_ids,
18842 } => {
18843 self.display_map.update(cx, |map, cx| {
18844 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18845 });
18846 cx.emit(EditorEvent::ExcerptsEdited {
18847 ids: excerpt_ids.clone(),
18848 })
18849 }
18850 multi_buffer::Event::ExcerptsExpanded { ids } => {
18851 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18852 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18853 }
18854 multi_buffer::Event::Reparsed(buffer_id) => {
18855 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18856 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18857
18858 cx.emit(EditorEvent::Reparsed(*buffer_id));
18859 }
18860 multi_buffer::Event::DiffHunksToggled => {
18861 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18862 }
18863 multi_buffer::Event::LanguageChanged(buffer_id) => {
18864 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18865 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18866 cx.emit(EditorEvent::Reparsed(*buffer_id));
18867 cx.notify();
18868 }
18869 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18870 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18871 multi_buffer::Event::FileHandleChanged
18872 | multi_buffer::Event::Reloaded
18873 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18874 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18875 multi_buffer::Event::DiagnosticsUpdated => {
18876 self.update_diagnostics_state(window, cx);
18877 }
18878 _ => {}
18879 };
18880 }
18881
18882 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
18883 self.refresh_active_diagnostics(cx);
18884 self.refresh_inline_diagnostics(true, window, cx);
18885 self.scrollbar_marker_state.dirty = true;
18886 cx.notify();
18887 }
18888
18889 pub fn start_temporary_diff_override(&mut self) {
18890 self.load_diff_task.take();
18891 self.temporary_diff_override = true;
18892 }
18893
18894 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18895 self.temporary_diff_override = false;
18896 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18897 self.buffer.update(cx, |buffer, cx| {
18898 buffer.set_all_diff_hunks_collapsed(cx);
18899 });
18900
18901 if let Some(project) = self.project.clone() {
18902 self.load_diff_task = Some(
18903 update_uncommitted_diff_for_buffer(
18904 cx.entity(),
18905 &project,
18906 self.buffer.read(cx).all_buffers(),
18907 self.buffer.clone(),
18908 cx,
18909 )
18910 .shared(),
18911 );
18912 }
18913 }
18914
18915 fn on_display_map_changed(
18916 &mut self,
18917 _: Entity<DisplayMap>,
18918 _: &mut Window,
18919 cx: &mut Context<Self>,
18920 ) {
18921 cx.notify();
18922 }
18923
18924 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18925 let new_severity = if self.diagnostics_enabled() {
18926 EditorSettings::get_global(cx)
18927 .diagnostics_max_severity
18928 .unwrap_or(DiagnosticSeverity::Hint)
18929 } else {
18930 DiagnosticSeverity::Off
18931 };
18932 self.set_max_diagnostics_severity(new_severity, cx);
18933 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18934 self.update_edit_prediction_settings(cx);
18935 self.refresh_inline_completion(true, false, window, cx);
18936 self.refresh_inlay_hints(
18937 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18938 self.selections.newest_anchor().head(),
18939 &self.buffer.read(cx).snapshot(cx),
18940 cx,
18941 )),
18942 cx,
18943 );
18944
18945 let old_cursor_shape = self.cursor_shape;
18946
18947 {
18948 let editor_settings = EditorSettings::get_global(cx);
18949 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18950 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18951 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18952 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18953 }
18954
18955 if old_cursor_shape != self.cursor_shape {
18956 cx.emit(EditorEvent::CursorShapeChanged);
18957 }
18958
18959 let project_settings = ProjectSettings::get_global(cx);
18960 self.serialize_dirty_buffers =
18961 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18962
18963 if self.mode.is_full() {
18964 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18965 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18966 if self.show_inline_diagnostics != show_inline_diagnostics {
18967 self.show_inline_diagnostics = show_inline_diagnostics;
18968 self.refresh_inline_diagnostics(false, window, cx);
18969 }
18970
18971 if self.git_blame_inline_enabled != inline_blame_enabled {
18972 self.toggle_git_blame_inline_internal(false, window, cx);
18973 }
18974
18975 let minimap_settings = EditorSettings::get_global(cx).minimap;
18976 if self.minimap_visibility != MinimapVisibility::Disabled {
18977 if self.minimap_visibility.settings_visibility()
18978 != minimap_settings.minimap_enabled()
18979 {
18980 self.set_minimap_visibility(
18981 MinimapVisibility::for_mode(self.mode(), cx),
18982 window,
18983 cx,
18984 );
18985 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18986 minimap_entity.update(cx, |minimap_editor, cx| {
18987 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18988 })
18989 }
18990 }
18991 }
18992
18993 cx.notify();
18994 }
18995
18996 pub fn set_searchable(&mut self, searchable: bool) {
18997 self.searchable = searchable;
18998 }
18999
19000 pub fn searchable(&self) -> bool {
19001 self.searchable
19002 }
19003
19004 fn open_proposed_changes_editor(
19005 &mut self,
19006 _: &OpenProposedChangesEditor,
19007 window: &mut Window,
19008 cx: &mut Context<Self>,
19009 ) {
19010 let Some(workspace) = self.workspace() else {
19011 cx.propagate();
19012 return;
19013 };
19014
19015 let selections = self.selections.all::<usize>(cx);
19016 let multi_buffer = self.buffer.read(cx);
19017 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19018 let mut new_selections_by_buffer = HashMap::default();
19019 for selection in selections {
19020 for (buffer, range, _) in
19021 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19022 {
19023 let mut range = range.to_point(buffer);
19024 range.start.column = 0;
19025 range.end.column = buffer.line_len(range.end.row);
19026 new_selections_by_buffer
19027 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19028 .or_insert(Vec::new())
19029 .push(range)
19030 }
19031 }
19032
19033 let proposed_changes_buffers = new_selections_by_buffer
19034 .into_iter()
19035 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19036 .collect::<Vec<_>>();
19037 let proposed_changes_editor = cx.new(|cx| {
19038 ProposedChangesEditor::new(
19039 "Proposed changes",
19040 proposed_changes_buffers,
19041 self.project.clone(),
19042 window,
19043 cx,
19044 )
19045 });
19046
19047 window.defer(cx, move |window, cx| {
19048 workspace.update(cx, |workspace, cx| {
19049 workspace.active_pane().update(cx, |pane, cx| {
19050 pane.add_item(
19051 Box::new(proposed_changes_editor),
19052 true,
19053 true,
19054 None,
19055 window,
19056 cx,
19057 );
19058 });
19059 });
19060 });
19061 }
19062
19063 pub fn open_excerpts_in_split(
19064 &mut self,
19065 _: &OpenExcerptsSplit,
19066 window: &mut Window,
19067 cx: &mut Context<Self>,
19068 ) {
19069 self.open_excerpts_common(None, true, window, cx)
19070 }
19071
19072 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19073 self.open_excerpts_common(None, false, window, cx)
19074 }
19075
19076 fn open_excerpts_common(
19077 &mut self,
19078 jump_data: Option<JumpData>,
19079 split: bool,
19080 window: &mut Window,
19081 cx: &mut Context<Self>,
19082 ) {
19083 let Some(workspace) = self.workspace() else {
19084 cx.propagate();
19085 return;
19086 };
19087
19088 if self.buffer.read(cx).is_singleton() {
19089 cx.propagate();
19090 return;
19091 }
19092
19093 let mut new_selections_by_buffer = HashMap::default();
19094 match &jump_data {
19095 Some(JumpData::MultiBufferPoint {
19096 excerpt_id,
19097 position,
19098 anchor,
19099 line_offset_from_top,
19100 }) => {
19101 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19102 if let Some(buffer) = multi_buffer_snapshot
19103 .buffer_id_for_excerpt(*excerpt_id)
19104 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19105 {
19106 let buffer_snapshot = buffer.read(cx).snapshot();
19107 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19108 language::ToPoint::to_point(anchor, &buffer_snapshot)
19109 } else {
19110 buffer_snapshot.clip_point(*position, Bias::Left)
19111 };
19112 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19113 new_selections_by_buffer.insert(
19114 buffer,
19115 (
19116 vec![jump_to_offset..jump_to_offset],
19117 Some(*line_offset_from_top),
19118 ),
19119 );
19120 }
19121 }
19122 Some(JumpData::MultiBufferRow {
19123 row,
19124 line_offset_from_top,
19125 }) => {
19126 let point = MultiBufferPoint::new(row.0, 0);
19127 if let Some((buffer, buffer_point, _)) =
19128 self.buffer.read(cx).point_to_buffer_point(point, cx)
19129 {
19130 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
19131 new_selections_by_buffer
19132 .entry(buffer)
19133 .or_insert((Vec::new(), Some(*line_offset_from_top)))
19134 .0
19135 .push(buffer_offset..buffer_offset)
19136 }
19137 }
19138 None => {
19139 let selections = self.selections.all::<usize>(cx);
19140 let multi_buffer = self.buffer.read(cx);
19141 for selection in selections {
19142 for (snapshot, range, _, anchor) in multi_buffer
19143 .snapshot(cx)
19144 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
19145 {
19146 if let Some(anchor) = anchor {
19147 // selection is in a deleted hunk
19148 let Some(buffer_id) = anchor.buffer_id else {
19149 continue;
19150 };
19151 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
19152 continue;
19153 };
19154 let offset = text::ToOffset::to_offset(
19155 &anchor.text_anchor,
19156 &buffer_handle.read(cx).snapshot(),
19157 );
19158 let range = offset..offset;
19159 new_selections_by_buffer
19160 .entry(buffer_handle)
19161 .or_insert((Vec::new(), None))
19162 .0
19163 .push(range)
19164 } else {
19165 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
19166 else {
19167 continue;
19168 };
19169 new_selections_by_buffer
19170 .entry(buffer_handle)
19171 .or_insert((Vec::new(), None))
19172 .0
19173 .push(range)
19174 }
19175 }
19176 }
19177 }
19178 }
19179
19180 new_selections_by_buffer
19181 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19182
19183 if new_selections_by_buffer.is_empty() {
19184 return;
19185 }
19186
19187 // We defer the pane interaction because we ourselves are a workspace item
19188 // and activating a new item causes the pane to call a method on us reentrantly,
19189 // which panics if we're on the stack.
19190 window.defer(cx, move |window, cx| {
19191 workspace.update(cx, |workspace, cx| {
19192 let pane = if split {
19193 workspace.adjacent_pane(window, cx)
19194 } else {
19195 workspace.active_pane().clone()
19196 };
19197
19198 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19199 let editor = buffer
19200 .read(cx)
19201 .file()
19202 .is_none()
19203 .then(|| {
19204 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19205 // so `workspace.open_project_item` will never find them, always opening a new editor.
19206 // Instead, we try to activate the existing editor in the pane first.
19207 let (editor, pane_item_index) =
19208 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19209 let editor = item.downcast::<Editor>()?;
19210 let singleton_buffer =
19211 editor.read(cx).buffer().read(cx).as_singleton()?;
19212 if singleton_buffer == buffer {
19213 Some((editor, i))
19214 } else {
19215 None
19216 }
19217 })?;
19218 pane.update(cx, |pane, cx| {
19219 pane.activate_item(pane_item_index, true, true, window, cx)
19220 });
19221 Some(editor)
19222 })
19223 .flatten()
19224 .unwrap_or_else(|| {
19225 workspace.open_project_item::<Self>(
19226 pane.clone(),
19227 buffer,
19228 true,
19229 true,
19230 window,
19231 cx,
19232 )
19233 });
19234
19235 editor.update(cx, |editor, cx| {
19236 let autoscroll = match scroll_offset {
19237 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19238 None => Autoscroll::newest(),
19239 };
19240 let nav_history = editor.nav_history.take();
19241 editor.change_selections(Some(autoscroll), window, cx, |s| {
19242 s.select_ranges(ranges);
19243 });
19244 editor.nav_history = nav_history;
19245 });
19246 }
19247 })
19248 });
19249 }
19250
19251 // For now, don't allow opening excerpts in buffers that aren't backed by
19252 // regular project files.
19253 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19254 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19255 }
19256
19257 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19258 let snapshot = self.buffer.read(cx).read(cx);
19259 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
19260 Some(
19261 ranges
19262 .iter()
19263 .map(move |range| {
19264 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19265 })
19266 .collect(),
19267 )
19268 }
19269
19270 fn selection_replacement_ranges(
19271 &self,
19272 range: Range<OffsetUtf16>,
19273 cx: &mut App,
19274 ) -> Vec<Range<OffsetUtf16>> {
19275 let selections = self.selections.all::<OffsetUtf16>(cx);
19276 let newest_selection = selections
19277 .iter()
19278 .max_by_key(|selection| selection.id)
19279 .unwrap();
19280 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19281 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19282 let snapshot = self.buffer.read(cx).read(cx);
19283 selections
19284 .into_iter()
19285 .map(|mut selection| {
19286 selection.start.0 =
19287 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19288 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19289 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19290 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19291 })
19292 .collect()
19293 }
19294
19295 fn report_editor_event(
19296 &self,
19297 event_type: &'static str,
19298 file_extension: Option<String>,
19299 cx: &App,
19300 ) {
19301 if cfg!(any(test, feature = "test-support")) {
19302 return;
19303 }
19304
19305 let Some(project) = &self.project else { return };
19306
19307 // If None, we are in a file without an extension
19308 let file = self
19309 .buffer
19310 .read(cx)
19311 .as_singleton()
19312 .and_then(|b| b.read(cx).file());
19313 let file_extension = file_extension.or(file
19314 .as_ref()
19315 .and_then(|file| Path::new(file.file_name(cx)).extension())
19316 .and_then(|e| e.to_str())
19317 .map(|a| a.to_string()));
19318
19319 let vim_mode = vim_enabled(cx);
19320
19321 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19322 let copilot_enabled = edit_predictions_provider
19323 == language::language_settings::EditPredictionProvider::Copilot;
19324 let copilot_enabled_for_language = self
19325 .buffer
19326 .read(cx)
19327 .language_settings(cx)
19328 .show_edit_predictions;
19329
19330 let project = project.read(cx);
19331 telemetry::event!(
19332 event_type,
19333 file_extension,
19334 vim_mode,
19335 copilot_enabled,
19336 copilot_enabled_for_language,
19337 edit_predictions_provider,
19338 is_via_ssh = project.is_via_ssh(),
19339 );
19340 }
19341
19342 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19343 /// with each line being an array of {text, highlight} objects.
19344 fn copy_highlight_json(
19345 &mut self,
19346 _: &CopyHighlightJson,
19347 window: &mut Window,
19348 cx: &mut Context<Self>,
19349 ) {
19350 #[derive(Serialize)]
19351 struct Chunk<'a> {
19352 text: String,
19353 highlight: Option<&'a str>,
19354 }
19355
19356 let snapshot = self.buffer.read(cx).snapshot(cx);
19357 let range = self
19358 .selected_text_range(false, window, cx)
19359 .and_then(|selection| {
19360 if selection.range.is_empty() {
19361 None
19362 } else {
19363 Some(selection.range)
19364 }
19365 })
19366 .unwrap_or_else(|| 0..snapshot.len());
19367
19368 let chunks = snapshot.chunks(range, true);
19369 let mut lines = Vec::new();
19370 let mut line: VecDeque<Chunk> = VecDeque::new();
19371
19372 let Some(style) = self.style.as_ref() else {
19373 return;
19374 };
19375
19376 for chunk in chunks {
19377 let highlight = chunk
19378 .syntax_highlight_id
19379 .and_then(|id| id.name(&style.syntax));
19380 let mut chunk_lines = chunk.text.split('\n').peekable();
19381 while let Some(text) = chunk_lines.next() {
19382 let mut merged_with_last_token = false;
19383 if let Some(last_token) = line.back_mut() {
19384 if last_token.highlight == highlight {
19385 last_token.text.push_str(text);
19386 merged_with_last_token = true;
19387 }
19388 }
19389
19390 if !merged_with_last_token {
19391 line.push_back(Chunk {
19392 text: text.into(),
19393 highlight,
19394 });
19395 }
19396
19397 if chunk_lines.peek().is_some() {
19398 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19399 line.pop_front();
19400 }
19401 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19402 line.pop_back();
19403 }
19404
19405 lines.push(mem::take(&mut line));
19406 }
19407 }
19408 }
19409
19410 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19411 return;
19412 };
19413 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19414 }
19415
19416 pub fn open_context_menu(
19417 &mut self,
19418 _: &OpenContextMenu,
19419 window: &mut Window,
19420 cx: &mut Context<Self>,
19421 ) {
19422 self.request_autoscroll(Autoscroll::newest(), cx);
19423 let position = self.selections.newest_display(cx).start;
19424 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19425 }
19426
19427 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19428 &self.inlay_hint_cache
19429 }
19430
19431 pub fn replay_insert_event(
19432 &mut self,
19433 text: &str,
19434 relative_utf16_range: Option<Range<isize>>,
19435 window: &mut Window,
19436 cx: &mut Context<Self>,
19437 ) {
19438 if !self.input_enabled {
19439 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19440 return;
19441 }
19442 if let Some(relative_utf16_range) = relative_utf16_range {
19443 let selections = self.selections.all::<OffsetUtf16>(cx);
19444 self.change_selections(None, window, cx, |s| {
19445 let new_ranges = selections.into_iter().map(|range| {
19446 let start = OffsetUtf16(
19447 range
19448 .head()
19449 .0
19450 .saturating_add_signed(relative_utf16_range.start),
19451 );
19452 let end = OffsetUtf16(
19453 range
19454 .head()
19455 .0
19456 .saturating_add_signed(relative_utf16_range.end),
19457 );
19458 start..end
19459 });
19460 s.select_ranges(new_ranges);
19461 });
19462 }
19463
19464 self.handle_input(text, window, cx);
19465 }
19466
19467 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19468 let Some(provider) = self.semantics_provider.as_ref() else {
19469 return false;
19470 };
19471
19472 let mut supports = false;
19473 self.buffer().update(cx, |this, cx| {
19474 this.for_each_buffer(|buffer| {
19475 supports |= provider.supports_inlay_hints(buffer, cx);
19476 });
19477 });
19478
19479 supports
19480 }
19481
19482 pub fn is_focused(&self, window: &Window) -> bool {
19483 self.focus_handle.is_focused(window)
19484 }
19485
19486 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19487 cx.emit(EditorEvent::Focused);
19488
19489 if let Some(descendant) = self
19490 .last_focused_descendant
19491 .take()
19492 .and_then(|descendant| descendant.upgrade())
19493 {
19494 window.focus(&descendant);
19495 } else {
19496 if let Some(blame) = self.blame.as_ref() {
19497 blame.update(cx, GitBlame::focus)
19498 }
19499
19500 self.blink_manager.update(cx, BlinkManager::enable);
19501 self.show_cursor_names(window, cx);
19502 self.buffer.update(cx, |buffer, cx| {
19503 buffer.finalize_last_transaction(cx);
19504 if self.leader_id.is_none() {
19505 buffer.set_active_selections(
19506 &self.selections.disjoint_anchors(),
19507 self.selections.line_mode,
19508 self.cursor_shape,
19509 cx,
19510 );
19511 }
19512 });
19513 }
19514 }
19515
19516 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19517 cx.emit(EditorEvent::FocusedIn)
19518 }
19519
19520 fn handle_focus_out(
19521 &mut self,
19522 event: FocusOutEvent,
19523 _window: &mut Window,
19524 cx: &mut Context<Self>,
19525 ) {
19526 if event.blurred != self.focus_handle {
19527 self.last_focused_descendant = Some(event.blurred);
19528 }
19529 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19530 }
19531
19532 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19533 self.blink_manager.update(cx, BlinkManager::disable);
19534 self.buffer
19535 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19536
19537 if let Some(blame) = self.blame.as_ref() {
19538 blame.update(cx, GitBlame::blur)
19539 }
19540 if !self.hover_state.focused(window, cx) {
19541 hide_hover(self, cx);
19542 }
19543 if !self
19544 .context_menu
19545 .borrow()
19546 .as_ref()
19547 .is_some_and(|context_menu| context_menu.focused(window, cx))
19548 {
19549 self.hide_context_menu(window, cx);
19550 }
19551 self.discard_inline_completion(false, cx);
19552 cx.emit(EditorEvent::Blurred);
19553 cx.notify();
19554 }
19555
19556 pub fn register_action<A: Action>(
19557 &mut self,
19558 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19559 ) -> Subscription {
19560 let id = self.next_editor_action_id.post_inc();
19561 let listener = Arc::new(listener);
19562 self.editor_actions.borrow_mut().insert(
19563 id,
19564 Box::new(move |window, _| {
19565 let listener = listener.clone();
19566 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19567 let action = action.downcast_ref().unwrap();
19568 if phase == DispatchPhase::Bubble {
19569 listener(action, window, cx)
19570 }
19571 })
19572 }),
19573 );
19574
19575 let editor_actions = self.editor_actions.clone();
19576 Subscription::new(move || {
19577 editor_actions.borrow_mut().remove(&id);
19578 })
19579 }
19580
19581 pub fn file_header_size(&self) -> u32 {
19582 FILE_HEADER_HEIGHT
19583 }
19584
19585 pub fn restore(
19586 &mut self,
19587 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19588 window: &mut Window,
19589 cx: &mut Context<Self>,
19590 ) {
19591 let workspace = self.workspace();
19592 let project = self.project.as_ref();
19593 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19594 let mut tasks = Vec::new();
19595 for (buffer_id, changes) in revert_changes {
19596 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19597 buffer.update(cx, |buffer, cx| {
19598 buffer.edit(
19599 changes
19600 .into_iter()
19601 .map(|(range, text)| (range, text.to_string())),
19602 None,
19603 cx,
19604 );
19605 });
19606
19607 if let Some(project) =
19608 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19609 {
19610 project.update(cx, |project, cx| {
19611 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19612 })
19613 }
19614 }
19615 }
19616 tasks
19617 });
19618 cx.spawn_in(window, async move |_, cx| {
19619 for (buffer, task) in save_tasks {
19620 let result = task.await;
19621 if result.is_err() {
19622 let Some(path) = buffer
19623 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19624 .ok()
19625 else {
19626 continue;
19627 };
19628 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19629 let Some(task) = cx
19630 .update_window_entity(&workspace, |workspace, window, cx| {
19631 workspace
19632 .open_path_preview(path, None, false, false, false, window, cx)
19633 })
19634 .ok()
19635 else {
19636 continue;
19637 };
19638 task.await.log_err();
19639 }
19640 }
19641 }
19642 })
19643 .detach();
19644 self.change_selections(None, window, cx, |selections| selections.refresh());
19645 }
19646
19647 pub fn to_pixel_point(
19648 &self,
19649 source: multi_buffer::Anchor,
19650 editor_snapshot: &EditorSnapshot,
19651 window: &mut Window,
19652 ) -> Option<gpui::Point<Pixels>> {
19653 let source_point = source.to_display_point(editor_snapshot);
19654 self.display_to_pixel_point(source_point, editor_snapshot, window)
19655 }
19656
19657 pub fn display_to_pixel_point(
19658 &self,
19659 source: DisplayPoint,
19660 editor_snapshot: &EditorSnapshot,
19661 window: &mut Window,
19662 ) -> Option<gpui::Point<Pixels>> {
19663 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19664 let text_layout_details = self.text_layout_details(window);
19665 let scroll_top = text_layout_details
19666 .scroll_anchor
19667 .scroll_position(editor_snapshot)
19668 .y;
19669
19670 if source.row().as_f32() < scroll_top.floor() {
19671 return None;
19672 }
19673 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19674 let source_y = line_height * (source.row().as_f32() - scroll_top);
19675 Some(gpui::Point::new(source_x, source_y))
19676 }
19677
19678 pub fn has_visible_completions_menu(&self) -> bool {
19679 !self.edit_prediction_preview_is_active()
19680 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19681 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19682 })
19683 }
19684
19685 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19686 if self.mode.is_minimap() {
19687 return;
19688 }
19689 self.addons
19690 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19691 }
19692
19693 pub fn unregister_addon<T: Addon>(&mut self) {
19694 self.addons.remove(&std::any::TypeId::of::<T>());
19695 }
19696
19697 pub fn addon<T: Addon>(&self) -> Option<&T> {
19698 let type_id = std::any::TypeId::of::<T>();
19699 self.addons
19700 .get(&type_id)
19701 .and_then(|item| item.to_any().downcast_ref::<T>())
19702 }
19703
19704 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19705 let type_id = std::any::TypeId::of::<T>();
19706 self.addons
19707 .get_mut(&type_id)
19708 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19709 }
19710
19711 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19712 let text_layout_details = self.text_layout_details(window);
19713 let style = &text_layout_details.editor_style;
19714 let font_id = window.text_system().resolve_font(&style.text.font());
19715 let font_size = style.text.font_size.to_pixels(window.rem_size());
19716 let line_height = style.text.line_height_in_pixels(window.rem_size());
19717 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19718
19719 gpui::Size::new(em_width, line_height)
19720 }
19721
19722 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
19723 self.load_diff_task.clone()
19724 }
19725
19726 fn read_metadata_from_db(
19727 &mut self,
19728 item_id: u64,
19729 workspace_id: WorkspaceId,
19730 window: &mut Window,
19731 cx: &mut Context<Editor>,
19732 ) {
19733 if self.is_singleton(cx)
19734 && !self.mode.is_minimap()
19735 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
19736 {
19737 let buffer_snapshot = OnceCell::new();
19738
19739 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
19740 if !folds.is_empty() {
19741 let snapshot =
19742 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19743 self.fold_ranges(
19744 folds
19745 .into_iter()
19746 .map(|(start, end)| {
19747 snapshot.clip_offset(start, Bias::Left)
19748 ..snapshot.clip_offset(end, Bias::Right)
19749 })
19750 .collect(),
19751 false,
19752 window,
19753 cx,
19754 );
19755 }
19756 }
19757
19758 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
19759 if !selections.is_empty() {
19760 let snapshot =
19761 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19762 self.change_selections(None, window, cx, |s| {
19763 s.select_ranges(selections.into_iter().map(|(start, end)| {
19764 snapshot.clip_offset(start, Bias::Left)
19765 ..snapshot.clip_offset(end, Bias::Right)
19766 }));
19767 });
19768 }
19769 };
19770 }
19771
19772 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
19773 }
19774}
19775
19776fn vim_enabled(cx: &App) -> bool {
19777 cx.global::<SettingsStore>()
19778 .raw_user_settings()
19779 .get("vim_mode")
19780 == Some(&serde_json::Value::Bool(true))
19781}
19782
19783fn process_completion_for_edit(
19784 completion: &Completion,
19785 intent: CompletionIntent,
19786 buffer: &Entity<Buffer>,
19787 cursor_position: &text::Anchor,
19788 cx: &mut Context<Editor>,
19789) -> CompletionEdit {
19790 let buffer = buffer.read(cx);
19791 let buffer_snapshot = buffer.snapshot();
19792 let (snippet, new_text) = if completion.is_snippet() {
19793 let mut snippet_source = completion.new_text.clone();
19794 if let Some(scope) = buffer_snapshot.language_scope_at(cursor_position) {
19795 if scope.prefers_label_for_snippet_in_completion() {
19796 if let Some(label) = completion.label() {
19797 if matches!(
19798 completion.kind(),
19799 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
19800 ) {
19801 snippet_source = label;
19802 }
19803 }
19804 }
19805 }
19806 match Snippet::parse(&snippet_source).log_err() {
19807 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
19808 None => (None, completion.new_text.clone()),
19809 }
19810 } else {
19811 (None, completion.new_text.clone())
19812 };
19813
19814 let mut range_to_replace = {
19815 let replace_range = &completion.replace_range;
19816 if let CompletionSource::Lsp {
19817 insert_range: Some(insert_range),
19818 ..
19819 } = &completion.source
19820 {
19821 debug_assert_eq!(
19822 insert_range.start, replace_range.start,
19823 "insert_range and replace_range should start at the same position"
19824 );
19825 debug_assert!(
19826 insert_range
19827 .start
19828 .cmp(&cursor_position, &buffer_snapshot)
19829 .is_le(),
19830 "insert_range should start before or at cursor position"
19831 );
19832 debug_assert!(
19833 replace_range
19834 .start
19835 .cmp(&cursor_position, &buffer_snapshot)
19836 .is_le(),
19837 "replace_range should start before or at cursor position"
19838 );
19839 debug_assert!(
19840 insert_range
19841 .end
19842 .cmp(&cursor_position, &buffer_snapshot)
19843 .is_le(),
19844 "insert_range should end before or at cursor position"
19845 );
19846
19847 let should_replace = match intent {
19848 CompletionIntent::CompleteWithInsert => false,
19849 CompletionIntent::CompleteWithReplace => true,
19850 CompletionIntent::Complete | CompletionIntent::Compose => {
19851 let insert_mode =
19852 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
19853 .completions
19854 .lsp_insert_mode;
19855 match insert_mode {
19856 LspInsertMode::Insert => false,
19857 LspInsertMode::Replace => true,
19858 LspInsertMode::ReplaceSubsequence => {
19859 let mut text_to_replace = buffer.chars_for_range(
19860 buffer.anchor_before(replace_range.start)
19861 ..buffer.anchor_after(replace_range.end),
19862 );
19863 let mut current_needle = text_to_replace.next();
19864 for haystack_ch in completion.label.text.chars() {
19865 if let Some(needle_ch) = current_needle {
19866 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
19867 current_needle = text_to_replace.next();
19868 }
19869 }
19870 }
19871 current_needle.is_none()
19872 }
19873 LspInsertMode::ReplaceSuffix => {
19874 if replace_range
19875 .end
19876 .cmp(&cursor_position, &buffer_snapshot)
19877 .is_gt()
19878 {
19879 let range_after_cursor = *cursor_position..replace_range.end;
19880 let text_after_cursor = buffer
19881 .text_for_range(
19882 buffer.anchor_before(range_after_cursor.start)
19883 ..buffer.anchor_after(range_after_cursor.end),
19884 )
19885 .collect::<String>()
19886 .to_ascii_lowercase();
19887 completion
19888 .label
19889 .text
19890 .to_ascii_lowercase()
19891 .ends_with(&text_after_cursor)
19892 } else {
19893 true
19894 }
19895 }
19896 }
19897 }
19898 };
19899
19900 if should_replace {
19901 replace_range.clone()
19902 } else {
19903 insert_range.clone()
19904 }
19905 } else {
19906 replace_range.clone()
19907 }
19908 };
19909
19910 if range_to_replace
19911 .end
19912 .cmp(&cursor_position, &buffer_snapshot)
19913 .is_lt()
19914 {
19915 range_to_replace.end = *cursor_position;
19916 }
19917
19918 CompletionEdit {
19919 new_text,
19920 replace_range: range_to_replace.to_offset(&buffer),
19921 snippet,
19922 }
19923}
19924
19925struct CompletionEdit {
19926 new_text: String,
19927 replace_range: Range<usize>,
19928 snippet: Option<Snippet>,
19929}
19930
19931fn insert_extra_newline_brackets(
19932 buffer: &MultiBufferSnapshot,
19933 range: Range<usize>,
19934 language: &language::LanguageScope,
19935) -> bool {
19936 let leading_whitespace_len = buffer
19937 .reversed_chars_at(range.start)
19938 .take_while(|c| c.is_whitespace() && *c != '\n')
19939 .map(|c| c.len_utf8())
19940 .sum::<usize>();
19941 let trailing_whitespace_len = buffer
19942 .chars_at(range.end)
19943 .take_while(|c| c.is_whitespace() && *c != '\n')
19944 .map(|c| c.len_utf8())
19945 .sum::<usize>();
19946 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
19947
19948 language.brackets().any(|(pair, enabled)| {
19949 let pair_start = pair.start.trim_end();
19950 let pair_end = pair.end.trim_start();
19951
19952 enabled
19953 && pair.newline
19954 && buffer.contains_str_at(range.end, pair_end)
19955 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
19956 })
19957}
19958
19959fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
19960 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
19961 [(buffer, range, _)] => (*buffer, range.clone()),
19962 _ => return false,
19963 };
19964 let pair = {
19965 let mut result: Option<BracketMatch> = None;
19966
19967 for pair in buffer
19968 .all_bracket_ranges(range.clone())
19969 .filter(move |pair| {
19970 pair.open_range.start <= range.start && pair.close_range.end >= range.end
19971 })
19972 {
19973 let len = pair.close_range.end - pair.open_range.start;
19974
19975 if let Some(existing) = &result {
19976 let existing_len = existing.close_range.end - existing.open_range.start;
19977 if len > existing_len {
19978 continue;
19979 }
19980 }
19981
19982 result = Some(pair);
19983 }
19984
19985 result
19986 };
19987 let Some(pair) = pair else {
19988 return false;
19989 };
19990 pair.newline_only
19991 && buffer
19992 .chars_for_range(pair.open_range.end..range.start)
19993 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
19994 .all(|c| c.is_whitespace() && c != '\n')
19995}
19996
19997fn update_uncommitted_diff_for_buffer(
19998 editor: Entity<Editor>,
19999 project: &Entity<Project>,
20000 buffers: impl IntoIterator<Item = Entity<Buffer>>,
20001 buffer: Entity<MultiBuffer>,
20002 cx: &mut App,
20003) -> Task<()> {
20004 let mut tasks = Vec::new();
20005 project.update(cx, |project, cx| {
20006 for buffer in buffers {
20007 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
20008 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
20009 }
20010 }
20011 });
20012 cx.spawn(async move |cx| {
20013 let diffs = future::join_all(tasks).await;
20014 if editor
20015 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
20016 .unwrap_or(false)
20017 {
20018 return;
20019 }
20020
20021 buffer
20022 .update(cx, |buffer, cx| {
20023 for diff in diffs.into_iter().flatten() {
20024 buffer.add_diff(diff, cx);
20025 }
20026 })
20027 .ok();
20028 })
20029}
20030
20031fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
20032 let tab_size = tab_size.get() as usize;
20033 let mut width = offset;
20034
20035 for ch in text.chars() {
20036 width += if ch == '\t' {
20037 tab_size - (width % tab_size)
20038 } else {
20039 1
20040 };
20041 }
20042
20043 width - offset
20044}
20045
20046#[cfg(test)]
20047mod tests {
20048 use super::*;
20049
20050 #[test]
20051 fn test_string_size_with_expanded_tabs() {
20052 let nz = |val| NonZeroU32::new(val).unwrap();
20053 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
20054 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
20055 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
20056 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
20057 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
20058 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
20059 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
20060 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
20061 }
20062}
20063
20064/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
20065struct WordBreakingTokenizer<'a> {
20066 input: &'a str,
20067}
20068
20069impl<'a> WordBreakingTokenizer<'a> {
20070 fn new(input: &'a str) -> Self {
20071 Self { input }
20072 }
20073}
20074
20075fn is_char_ideographic(ch: char) -> bool {
20076 use unicode_script::Script::*;
20077 use unicode_script::UnicodeScript;
20078 matches!(ch.script(), Han | Tangut | Yi)
20079}
20080
20081fn is_grapheme_ideographic(text: &str) -> bool {
20082 text.chars().any(is_char_ideographic)
20083}
20084
20085fn is_grapheme_whitespace(text: &str) -> bool {
20086 text.chars().any(|x| x.is_whitespace())
20087}
20088
20089fn should_stay_with_preceding_ideograph(text: &str) -> bool {
20090 text.chars().next().map_or(false, |ch| {
20091 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
20092 })
20093}
20094
20095#[derive(PartialEq, Eq, Debug, Clone, Copy)]
20096enum WordBreakToken<'a> {
20097 Word { token: &'a str, grapheme_len: usize },
20098 InlineWhitespace { token: &'a str, grapheme_len: usize },
20099 Newline,
20100}
20101
20102impl<'a> Iterator for WordBreakingTokenizer<'a> {
20103 /// Yields a span, the count of graphemes in the token, and whether it was
20104 /// whitespace. Note that it also breaks at word boundaries.
20105 type Item = WordBreakToken<'a>;
20106
20107 fn next(&mut self) -> Option<Self::Item> {
20108 use unicode_segmentation::UnicodeSegmentation;
20109 if self.input.is_empty() {
20110 return None;
20111 }
20112
20113 let mut iter = self.input.graphemes(true).peekable();
20114 let mut offset = 0;
20115 let mut grapheme_len = 0;
20116 if let Some(first_grapheme) = iter.next() {
20117 let is_newline = first_grapheme == "\n";
20118 let is_whitespace = is_grapheme_whitespace(first_grapheme);
20119 offset += first_grapheme.len();
20120 grapheme_len += 1;
20121 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
20122 if let Some(grapheme) = iter.peek().copied() {
20123 if should_stay_with_preceding_ideograph(grapheme) {
20124 offset += grapheme.len();
20125 grapheme_len += 1;
20126 }
20127 }
20128 } else {
20129 let mut words = self.input[offset..].split_word_bound_indices().peekable();
20130 let mut next_word_bound = words.peek().copied();
20131 if next_word_bound.map_or(false, |(i, _)| i == 0) {
20132 next_word_bound = words.next();
20133 }
20134 while let Some(grapheme) = iter.peek().copied() {
20135 if next_word_bound.map_or(false, |(i, _)| i == offset) {
20136 break;
20137 };
20138 if is_grapheme_whitespace(grapheme) != is_whitespace
20139 || (grapheme == "\n") != is_newline
20140 {
20141 break;
20142 };
20143 offset += grapheme.len();
20144 grapheme_len += 1;
20145 iter.next();
20146 }
20147 }
20148 let token = &self.input[..offset];
20149 self.input = &self.input[offset..];
20150 if token == "\n" {
20151 Some(WordBreakToken::Newline)
20152 } else if is_whitespace {
20153 Some(WordBreakToken::InlineWhitespace {
20154 token,
20155 grapheme_len,
20156 })
20157 } else {
20158 Some(WordBreakToken::Word {
20159 token,
20160 grapheme_len,
20161 })
20162 }
20163 } else {
20164 None
20165 }
20166 }
20167}
20168
20169#[test]
20170fn test_word_breaking_tokenizer() {
20171 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
20172 ("", &[]),
20173 (" ", &[whitespace(" ", 2)]),
20174 ("Ʒ", &[word("Ʒ", 1)]),
20175 ("Ǽ", &[word("Ǽ", 1)]),
20176 ("⋑", &[word("⋑", 1)]),
20177 ("⋑⋑", &[word("⋑⋑", 2)]),
20178 (
20179 "原理,进而",
20180 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20181 ),
20182 (
20183 "hello world",
20184 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20185 ),
20186 (
20187 "hello, world",
20188 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
20189 ),
20190 (
20191 " hello world",
20192 &[
20193 whitespace(" ", 2),
20194 word("hello", 5),
20195 whitespace(" ", 1),
20196 word("world", 5),
20197 ],
20198 ),
20199 (
20200 "这是什么 \n 钢笔",
20201 &[
20202 word("这", 1),
20203 word("是", 1),
20204 word("什", 1),
20205 word("么", 1),
20206 whitespace(" ", 1),
20207 newline(),
20208 whitespace(" ", 1),
20209 word("钢", 1),
20210 word("笔", 1),
20211 ],
20212 ),
20213 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
20214 ];
20215
20216 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20217 WordBreakToken::Word {
20218 token,
20219 grapheme_len,
20220 }
20221 }
20222
20223 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20224 WordBreakToken::InlineWhitespace {
20225 token,
20226 grapheme_len,
20227 }
20228 }
20229
20230 fn newline() -> WordBreakToken<'static> {
20231 WordBreakToken::Newline
20232 }
20233
20234 for (input, result) in tests {
20235 assert_eq!(
20236 WordBreakingTokenizer::new(input)
20237 .collect::<Vec<_>>()
20238 .as_slice(),
20239 *result,
20240 );
20241 }
20242}
20243
20244fn wrap_with_prefix(
20245 line_prefix: String,
20246 unwrapped_text: String,
20247 wrap_column: usize,
20248 tab_size: NonZeroU32,
20249 preserve_existing_whitespace: bool,
20250) -> String {
20251 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
20252 let mut wrapped_text = String::new();
20253 let mut current_line = line_prefix.clone();
20254
20255 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
20256 let mut current_line_len = line_prefix_len;
20257 let mut in_whitespace = false;
20258 for token in tokenizer {
20259 let have_preceding_whitespace = in_whitespace;
20260 match token {
20261 WordBreakToken::Word {
20262 token,
20263 grapheme_len,
20264 } => {
20265 in_whitespace = false;
20266 if current_line_len + grapheme_len > wrap_column
20267 && current_line_len != line_prefix_len
20268 {
20269 wrapped_text.push_str(current_line.trim_end());
20270 wrapped_text.push('\n');
20271 current_line.truncate(line_prefix.len());
20272 current_line_len = line_prefix_len;
20273 }
20274 current_line.push_str(token);
20275 current_line_len += grapheme_len;
20276 }
20277 WordBreakToken::InlineWhitespace {
20278 mut token,
20279 mut grapheme_len,
20280 } => {
20281 in_whitespace = true;
20282 if have_preceding_whitespace && !preserve_existing_whitespace {
20283 continue;
20284 }
20285 if !preserve_existing_whitespace {
20286 token = " ";
20287 grapheme_len = 1;
20288 }
20289 if current_line_len + grapheme_len > wrap_column {
20290 wrapped_text.push_str(current_line.trim_end());
20291 wrapped_text.push('\n');
20292 current_line.truncate(line_prefix.len());
20293 current_line_len = line_prefix_len;
20294 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
20295 current_line.push_str(token);
20296 current_line_len += grapheme_len;
20297 }
20298 }
20299 WordBreakToken::Newline => {
20300 in_whitespace = true;
20301 if preserve_existing_whitespace {
20302 wrapped_text.push_str(current_line.trim_end());
20303 wrapped_text.push('\n');
20304 current_line.truncate(line_prefix.len());
20305 current_line_len = line_prefix_len;
20306 } else if have_preceding_whitespace {
20307 continue;
20308 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
20309 {
20310 wrapped_text.push_str(current_line.trim_end());
20311 wrapped_text.push('\n');
20312 current_line.truncate(line_prefix.len());
20313 current_line_len = line_prefix_len;
20314 } else if current_line_len != line_prefix_len {
20315 current_line.push(' ');
20316 current_line_len += 1;
20317 }
20318 }
20319 }
20320 }
20321
20322 if !current_line.is_empty() {
20323 wrapped_text.push_str(¤t_line);
20324 }
20325 wrapped_text
20326}
20327
20328#[test]
20329fn test_wrap_with_prefix() {
20330 assert_eq!(
20331 wrap_with_prefix(
20332 "# ".to_string(),
20333 "abcdefg".to_string(),
20334 4,
20335 NonZeroU32::new(4).unwrap(),
20336 false,
20337 ),
20338 "# abcdefg"
20339 );
20340 assert_eq!(
20341 wrap_with_prefix(
20342 "".to_string(),
20343 "\thello world".to_string(),
20344 8,
20345 NonZeroU32::new(4).unwrap(),
20346 false,
20347 ),
20348 "hello\nworld"
20349 );
20350 assert_eq!(
20351 wrap_with_prefix(
20352 "// ".to_string(),
20353 "xx \nyy zz aa bb cc".to_string(),
20354 12,
20355 NonZeroU32::new(4).unwrap(),
20356 false,
20357 ),
20358 "// xx yy zz\n// aa bb cc"
20359 );
20360 assert_eq!(
20361 wrap_with_prefix(
20362 String::new(),
20363 "这是什么 \n 钢笔".to_string(),
20364 3,
20365 NonZeroU32::new(4).unwrap(),
20366 false,
20367 ),
20368 "这是什\n么 钢\n笔"
20369 );
20370}
20371
20372pub trait CollaborationHub {
20373 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20374 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20375 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20376}
20377
20378impl CollaborationHub for Entity<Project> {
20379 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20380 self.read(cx).collaborators()
20381 }
20382
20383 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
20384 self.read(cx).user_store().read(cx).participant_indices()
20385 }
20386
20387 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
20388 let this = self.read(cx);
20389 let user_ids = this.collaborators().values().map(|c| c.user_id);
20390 this.user_store().read(cx).participant_names(user_ids, cx)
20391 }
20392}
20393
20394pub trait SemanticsProvider {
20395 fn hover(
20396 &self,
20397 buffer: &Entity<Buffer>,
20398 position: text::Anchor,
20399 cx: &mut App,
20400 ) -> Option<Task<Vec<project::Hover>>>;
20401
20402 fn inline_values(
20403 &self,
20404 buffer_handle: Entity<Buffer>,
20405 range: Range<text::Anchor>,
20406 cx: &mut App,
20407 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20408
20409 fn inlay_hints(
20410 &self,
20411 buffer_handle: Entity<Buffer>,
20412 range: Range<text::Anchor>,
20413 cx: &mut App,
20414 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20415
20416 fn resolve_inlay_hint(
20417 &self,
20418 hint: InlayHint,
20419 buffer_handle: Entity<Buffer>,
20420 server_id: LanguageServerId,
20421 cx: &mut App,
20422 ) -> Option<Task<anyhow::Result<InlayHint>>>;
20423
20424 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
20425
20426 fn document_highlights(
20427 &self,
20428 buffer: &Entity<Buffer>,
20429 position: text::Anchor,
20430 cx: &mut App,
20431 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
20432
20433 fn definitions(
20434 &self,
20435 buffer: &Entity<Buffer>,
20436 position: text::Anchor,
20437 kind: GotoDefinitionKind,
20438 cx: &mut App,
20439 ) -> Option<Task<Result<Vec<LocationLink>>>>;
20440
20441 fn range_for_rename(
20442 &self,
20443 buffer: &Entity<Buffer>,
20444 position: text::Anchor,
20445 cx: &mut App,
20446 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
20447
20448 fn perform_rename(
20449 &self,
20450 buffer: &Entity<Buffer>,
20451 position: text::Anchor,
20452 new_name: String,
20453 cx: &mut App,
20454 ) -> Option<Task<Result<ProjectTransaction>>>;
20455
20456 fn pull_diagnostics_for_buffer(
20457 &self,
20458 buffer: Entity<Buffer>,
20459 cx: &mut App,
20460 ) -> Task<anyhow::Result<()>>;
20461}
20462
20463pub trait CompletionProvider {
20464 fn completions(
20465 &self,
20466 excerpt_id: ExcerptId,
20467 buffer: &Entity<Buffer>,
20468 buffer_position: text::Anchor,
20469 trigger: CompletionContext,
20470 window: &mut Window,
20471 cx: &mut Context<Editor>,
20472 ) -> Task<Result<Vec<CompletionResponse>>>;
20473
20474 fn resolve_completions(
20475 &self,
20476 _buffer: Entity<Buffer>,
20477 _completion_indices: Vec<usize>,
20478 _completions: Rc<RefCell<Box<[Completion]>>>,
20479 _cx: &mut Context<Editor>,
20480 ) -> Task<Result<bool>> {
20481 Task::ready(Ok(false))
20482 }
20483
20484 fn apply_additional_edits_for_completion(
20485 &self,
20486 _buffer: Entity<Buffer>,
20487 _completions: Rc<RefCell<Box<[Completion]>>>,
20488 _completion_index: usize,
20489 _push_to_history: bool,
20490 _cx: &mut Context<Editor>,
20491 ) -> Task<Result<Option<language::Transaction>>> {
20492 Task::ready(Ok(None))
20493 }
20494
20495 fn is_completion_trigger(
20496 &self,
20497 buffer: &Entity<Buffer>,
20498 position: language::Anchor,
20499 text: &str,
20500 trigger_in_words: bool,
20501 menu_is_open: bool,
20502 cx: &mut Context<Editor>,
20503 ) -> bool;
20504
20505 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
20506
20507 fn sort_completions(&self) -> bool {
20508 true
20509 }
20510
20511 fn filter_completions(&self) -> bool {
20512 true
20513 }
20514}
20515
20516pub trait CodeActionProvider {
20517 fn id(&self) -> Arc<str>;
20518
20519 fn code_actions(
20520 &self,
20521 buffer: &Entity<Buffer>,
20522 range: Range<text::Anchor>,
20523 window: &mut Window,
20524 cx: &mut App,
20525 ) -> Task<Result<Vec<CodeAction>>>;
20526
20527 fn apply_code_action(
20528 &self,
20529 buffer_handle: Entity<Buffer>,
20530 action: CodeAction,
20531 excerpt_id: ExcerptId,
20532 push_to_history: bool,
20533 window: &mut Window,
20534 cx: &mut App,
20535 ) -> Task<Result<ProjectTransaction>>;
20536}
20537
20538impl CodeActionProvider for Entity<Project> {
20539 fn id(&self) -> Arc<str> {
20540 "project".into()
20541 }
20542
20543 fn code_actions(
20544 &self,
20545 buffer: &Entity<Buffer>,
20546 range: Range<text::Anchor>,
20547 _window: &mut Window,
20548 cx: &mut App,
20549 ) -> Task<Result<Vec<CodeAction>>> {
20550 self.update(cx, |project, cx| {
20551 let code_lens = project.code_lens(buffer, range.clone(), cx);
20552 let code_actions = project.code_actions(buffer, range, None, cx);
20553 cx.background_spawn(async move {
20554 let (code_lens, code_actions) = join(code_lens, code_actions).await;
20555 Ok(code_lens
20556 .context("code lens fetch")?
20557 .into_iter()
20558 .chain(code_actions.context("code action fetch")?)
20559 .collect())
20560 })
20561 })
20562 }
20563
20564 fn apply_code_action(
20565 &self,
20566 buffer_handle: Entity<Buffer>,
20567 action: CodeAction,
20568 _excerpt_id: ExcerptId,
20569 push_to_history: bool,
20570 _window: &mut Window,
20571 cx: &mut App,
20572 ) -> Task<Result<ProjectTransaction>> {
20573 self.update(cx, |project, cx| {
20574 project.apply_code_action(buffer_handle, action, push_to_history, cx)
20575 })
20576 }
20577}
20578
20579fn snippet_completions(
20580 project: &Project,
20581 buffer: &Entity<Buffer>,
20582 buffer_position: text::Anchor,
20583 cx: &mut App,
20584) -> Task<Result<CompletionResponse>> {
20585 let languages = buffer.read(cx).languages_at(buffer_position);
20586 let snippet_store = project.snippets().read(cx);
20587
20588 let scopes: Vec<_> = languages
20589 .iter()
20590 .filter_map(|language| {
20591 let language_name = language.lsp_id();
20592 let snippets = snippet_store.snippets_for(Some(language_name), cx);
20593
20594 if snippets.is_empty() {
20595 None
20596 } else {
20597 Some((language.default_scope(), snippets))
20598 }
20599 })
20600 .collect();
20601
20602 if scopes.is_empty() {
20603 return Task::ready(Ok(CompletionResponse {
20604 completions: vec![],
20605 is_incomplete: false,
20606 }));
20607 }
20608
20609 let snapshot = buffer.read(cx).text_snapshot();
20610 let chars: String = snapshot
20611 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
20612 .collect();
20613 let executor = cx.background_executor().clone();
20614
20615 cx.background_spawn(async move {
20616 let mut is_incomplete = false;
20617 let mut completions: Vec<Completion> = Vec::new();
20618 for (scope, snippets) in scopes.into_iter() {
20619 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
20620 let mut last_word = chars
20621 .chars()
20622 .take_while(|c| classifier.is_word(*c))
20623 .collect::<String>();
20624 last_word = last_word.chars().rev().collect();
20625
20626 if last_word.is_empty() {
20627 return Ok(CompletionResponse {
20628 completions: vec![],
20629 is_incomplete: true,
20630 });
20631 }
20632
20633 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
20634 let to_lsp = |point: &text::Anchor| {
20635 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
20636 point_to_lsp(end)
20637 };
20638 let lsp_end = to_lsp(&buffer_position);
20639
20640 let candidates = snippets
20641 .iter()
20642 .enumerate()
20643 .flat_map(|(ix, snippet)| {
20644 snippet
20645 .prefix
20646 .iter()
20647 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
20648 })
20649 .collect::<Vec<StringMatchCandidate>>();
20650
20651 const MAX_RESULTS: usize = 100;
20652 let mut matches = fuzzy::match_strings(
20653 &candidates,
20654 &last_word,
20655 last_word.chars().any(|c| c.is_uppercase()),
20656 MAX_RESULTS,
20657 &Default::default(),
20658 executor.clone(),
20659 )
20660 .await;
20661
20662 if matches.len() >= MAX_RESULTS {
20663 is_incomplete = true;
20664 }
20665
20666 // Remove all candidates where the query's start does not match the start of any word in the candidate
20667 if let Some(query_start) = last_word.chars().next() {
20668 matches.retain(|string_match| {
20669 split_words(&string_match.string).any(|word| {
20670 // Check that the first codepoint of the word as lowercase matches the first
20671 // codepoint of the query as lowercase
20672 word.chars()
20673 .flat_map(|codepoint| codepoint.to_lowercase())
20674 .zip(query_start.to_lowercase())
20675 .all(|(word_cp, query_cp)| word_cp == query_cp)
20676 })
20677 });
20678 }
20679
20680 let matched_strings = matches
20681 .into_iter()
20682 .map(|m| m.string)
20683 .collect::<HashSet<_>>();
20684
20685 completions.extend(snippets.iter().filter_map(|snippet| {
20686 let matching_prefix = snippet
20687 .prefix
20688 .iter()
20689 .find(|prefix| matched_strings.contains(*prefix))?;
20690 let start = as_offset - last_word.len();
20691 let start = snapshot.anchor_before(start);
20692 let range = start..buffer_position;
20693 let lsp_start = to_lsp(&start);
20694 let lsp_range = lsp::Range {
20695 start: lsp_start,
20696 end: lsp_end,
20697 };
20698 Some(Completion {
20699 replace_range: range,
20700 new_text: snippet.body.clone(),
20701 source: CompletionSource::Lsp {
20702 insert_range: None,
20703 server_id: LanguageServerId(usize::MAX),
20704 resolved: true,
20705 lsp_completion: Box::new(lsp::CompletionItem {
20706 label: snippet.prefix.first().unwrap().clone(),
20707 kind: Some(CompletionItemKind::SNIPPET),
20708 label_details: snippet.description.as_ref().map(|description| {
20709 lsp::CompletionItemLabelDetails {
20710 detail: Some(description.clone()),
20711 description: None,
20712 }
20713 }),
20714 insert_text_format: Some(InsertTextFormat::SNIPPET),
20715 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20716 lsp::InsertReplaceEdit {
20717 new_text: snippet.body.clone(),
20718 insert: lsp_range,
20719 replace: lsp_range,
20720 },
20721 )),
20722 filter_text: Some(snippet.body.clone()),
20723 sort_text: Some(char::MAX.to_string()),
20724 ..lsp::CompletionItem::default()
20725 }),
20726 lsp_defaults: None,
20727 },
20728 label: CodeLabel {
20729 text: matching_prefix.clone(),
20730 runs: Vec::new(),
20731 filter_range: 0..matching_prefix.len(),
20732 },
20733 icon_path: None,
20734 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
20735 single_line: snippet.name.clone().into(),
20736 plain_text: snippet
20737 .description
20738 .clone()
20739 .map(|description| description.into()),
20740 }),
20741 insert_text_mode: None,
20742 confirm: None,
20743 })
20744 }))
20745 }
20746
20747 Ok(CompletionResponse {
20748 completions,
20749 is_incomplete,
20750 })
20751 })
20752}
20753
20754impl CompletionProvider for Entity<Project> {
20755 fn completions(
20756 &self,
20757 _excerpt_id: ExcerptId,
20758 buffer: &Entity<Buffer>,
20759 buffer_position: text::Anchor,
20760 options: CompletionContext,
20761 _window: &mut Window,
20762 cx: &mut Context<Editor>,
20763 ) -> Task<Result<Vec<CompletionResponse>>> {
20764 self.update(cx, |project, cx| {
20765 let snippets = snippet_completions(project, buffer, buffer_position, cx);
20766 let project_completions = project.completions(buffer, buffer_position, options, cx);
20767 cx.background_spawn(async move {
20768 let mut responses = project_completions.await?;
20769 let snippets = snippets.await?;
20770 if !snippets.completions.is_empty() {
20771 responses.push(snippets);
20772 }
20773 Ok(responses)
20774 })
20775 })
20776 }
20777
20778 fn resolve_completions(
20779 &self,
20780 buffer: Entity<Buffer>,
20781 completion_indices: Vec<usize>,
20782 completions: Rc<RefCell<Box<[Completion]>>>,
20783 cx: &mut Context<Editor>,
20784 ) -> Task<Result<bool>> {
20785 self.update(cx, |project, cx| {
20786 project.lsp_store().update(cx, |lsp_store, cx| {
20787 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
20788 })
20789 })
20790 }
20791
20792 fn apply_additional_edits_for_completion(
20793 &self,
20794 buffer: Entity<Buffer>,
20795 completions: Rc<RefCell<Box<[Completion]>>>,
20796 completion_index: usize,
20797 push_to_history: bool,
20798 cx: &mut Context<Editor>,
20799 ) -> Task<Result<Option<language::Transaction>>> {
20800 self.update(cx, |project, cx| {
20801 project.lsp_store().update(cx, |lsp_store, cx| {
20802 lsp_store.apply_additional_edits_for_completion(
20803 buffer,
20804 completions,
20805 completion_index,
20806 push_to_history,
20807 cx,
20808 )
20809 })
20810 })
20811 }
20812
20813 fn is_completion_trigger(
20814 &self,
20815 buffer: &Entity<Buffer>,
20816 position: language::Anchor,
20817 text: &str,
20818 trigger_in_words: bool,
20819 menu_is_open: bool,
20820 cx: &mut Context<Editor>,
20821 ) -> bool {
20822 let mut chars = text.chars();
20823 let char = if let Some(char) = chars.next() {
20824 char
20825 } else {
20826 return false;
20827 };
20828 if chars.next().is_some() {
20829 return false;
20830 }
20831
20832 let buffer = buffer.read(cx);
20833 let snapshot = buffer.snapshot();
20834 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
20835 return false;
20836 }
20837 let classifier = snapshot.char_classifier_at(position).for_completion(true);
20838 if trigger_in_words && classifier.is_word(char) {
20839 return true;
20840 }
20841
20842 buffer.completion_triggers().contains(text)
20843 }
20844}
20845
20846impl SemanticsProvider for Entity<Project> {
20847 fn hover(
20848 &self,
20849 buffer: &Entity<Buffer>,
20850 position: text::Anchor,
20851 cx: &mut App,
20852 ) -> Option<Task<Vec<project::Hover>>> {
20853 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
20854 }
20855
20856 fn document_highlights(
20857 &self,
20858 buffer: &Entity<Buffer>,
20859 position: text::Anchor,
20860 cx: &mut App,
20861 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
20862 Some(self.update(cx, |project, cx| {
20863 project.document_highlights(buffer, position, cx)
20864 }))
20865 }
20866
20867 fn definitions(
20868 &self,
20869 buffer: &Entity<Buffer>,
20870 position: text::Anchor,
20871 kind: GotoDefinitionKind,
20872 cx: &mut App,
20873 ) -> Option<Task<Result<Vec<LocationLink>>>> {
20874 Some(self.update(cx, |project, cx| match kind {
20875 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
20876 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
20877 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
20878 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
20879 }))
20880 }
20881
20882 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
20883 // TODO: make this work for remote projects
20884 self.update(cx, |project, cx| {
20885 if project
20886 .active_debug_session(cx)
20887 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
20888 {
20889 return true;
20890 }
20891
20892 buffer.update(cx, |buffer, cx| {
20893 project.any_language_server_supports_inlay_hints(buffer, cx)
20894 })
20895 })
20896 }
20897
20898 fn inline_values(
20899 &self,
20900 buffer_handle: Entity<Buffer>,
20901
20902 range: Range<text::Anchor>,
20903 cx: &mut App,
20904 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20905 self.update(cx, |project, cx| {
20906 let (session, active_stack_frame) = project.active_debug_session(cx)?;
20907
20908 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
20909 })
20910 }
20911
20912 fn inlay_hints(
20913 &self,
20914 buffer_handle: Entity<Buffer>,
20915 range: Range<text::Anchor>,
20916 cx: &mut App,
20917 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20918 Some(self.update(cx, |project, cx| {
20919 project.inlay_hints(buffer_handle, range, cx)
20920 }))
20921 }
20922
20923 fn resolve_inlay_hint(
20924 &self,
20925 hint: InlayHint,
20926 buffer_handle: Entity<Buffer>,
20927 server_id: LanguageServerId,
20928 cx: &mut App,
20929 ) -> Option<Task<anyhow::Result<InlayHint>>> {
20930 Some(self.update(cx, |project, cx| {
20931 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
20932 }))
20933 }
20934
20935 fn range_for_rename(
20936 &self,
20937 buffer: &Entity<Buffer>,
20938 position: text::Anchor,
20939 cx: &mut App,
20940 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
20941 Some(self.update(cx, |project, cx| {
20942 let buffer = buffer.clone();
20943 let task = project.prepare_rename(buffer.clone(), position, cx);
20944 cx.spawn(async move |_, cx| {
20945 Ok(match task.await? {
20946 PrepareRenameResponse::Success(range) => Some(range),
20947 PrepareRenameResponse::InvalidPosition => None,
20948 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
20949 // Fallback on using TreeSitter info to determine identifier range
20950 buffer.read_with(cx, |buffer, _| {
20951 let snapshot = buffer.snapshot();
20952 let (range, kind) = snapshot.surrounding_word(position);
20953 if kind != Some(CharKind::Word) {
20954 return None;
20955 }
20956 Some(
20957 snapshot.anchor_before(range.start)
20958 ..snapshot.anchor_after(range.end),
20959 )
20960 })?
20961 }
20962 })
20963 })
20964 }))
20965 }
20966
20967 fn perform_rename(
20968 &self,
20969 buffer: &Entity<Buffer>,
20970 position: text::Anchor,
20971 new_name: String,
20972 cx: &mut App,
20973 ) -> Option<Task<Result<ProjectTransaction>>> {
20974 Some(self.update(cx, |project, cx| {
20975 project.perform_rename(buffer.clone(), position, new_name, cx)
20976 }))
20977 }
20978
20979 fn pull_diagnostics_for_buffer(
20980 &self,
20981 buffer: Entity<Buffer>,
20982 cx: &mut App,
20983 ) -> Task<anyhow::Result<()>> {
20984 let diagnostics = self.update(cx, |project, cx| {
20985 project
20986 .lsp_store()
20987 .update(cx, |lsp_store, cx| lsp_store.pull_diagnostics(buffer, cx))
20988 });
20989 let project = self.clone();
20990 cx.spawn(async move |cx| {
20991 let diagnostics = diagnostics.await.context("pulling diagnostics")?;
20992 project.update(cx, |project, cx| {
20993 project.lsp_store().update(cx, |lsp_store, cx| {
20994 for diagnostics_set in diagnostics {
20995 let LspPullDiagnostics::Response {
20996 server_id,
20997 uri,
20998 diagnostics,
20999 } = diagnostics_set
21000 else {
21001 continue;
21002 };
21003
21004 let adapter = lsp_store.language_server_adapter_for_id(server_id);
21005 let disk_based_sources = adapter
21006 .as_ref()
21007 .map(|adapter| adapter.disk_based_diagnostic_sources.as_slice())
21008 .unwrap_or(&[]);
21009 match diagnostics {
21010 PulledDiagnostics::Unchanged { result_id } => {
21011 lsp_store
21012 .merge_diagnostics(
21013 server_id,
21014 lsp::PublishDiagnosticsParams {
21015 uri: uri.clone(),
21016 diagnostics: Vec::new(),
21017 version: None,
21018 },
21019 Some(result_id),
21020 DiagnosticSourceKind::Pulled,
21021 disk_based_sources,
21022 |_, _| true,
21023 cx,
21024 )
21025 .log_err();
21026 }
21027 PulledDiagnostics::Changed {
21028 diagnostics,
21029 result_id,
21030 } => {
21031 lsp_store
21032 .merge_diagnostics(
21033 server_id,
21034 lsp::PublishDiagnosticsParams {
21035 uri: uri.clone(),
21036 diagnostics,
21037 version: None,
21038 },
21039 result_id,
21040 DiagnosticSourceKind::Pulled,
21041 disk_based_sources,
21042 |old_diagnostic, _| match old_diagnostic.source_kind {
21043 DiagnosticSourceKind::Pulled => false,
21044 DiagnosticSourceKind::Other
21045 | DiagnosticSourceKind::Pushed => true,
21046 },
21047 cx,
21048 )
21049 .log_err();
21050 }
21051 }
21052 }
21053 })
21054 })
21055 })
21056 }
21057}
21058
21059fn inlay_hint_settings(
21060 location: Anchor,
21061 snapshot: &MultiBufferSnapshot,
21062 cx: &mut Context<Editor>,
21063) -> InlayHintSettings {
21064 let file = snapshot.file_at(location);
21065 let language = snapshot.language_at(location).map(|l| l.name());
21066 language_settings(language, file, cx).inlay_hints
21067}
21068
21069fn consume_contiguous_rows(
21070 contiguous_row_selections: &mut Vec<Selection<Point>>,
21071 selection: &Selection<Point>,
21072 display_map: &DisplaySnapshot,
21073 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
21074) -> (MultiBufferRow, MultiBufferRow) {
21075 contiguous_row_selections.push(selection.clone());
21076 let start_row = MultiBufferRow(selection.start.row);
21077 let mut end_row = ending_row(selection, display_map);
21078
21079 while let Some(next_selection) = selections.peek() {
21080 if next_selection.start.row <= end_row.0 {
21081 end_row = ending_row(next_selection, display_map);
21082 contiguous_row_selections.push(selections.next().unwrap().clone());
21083 } else {
21084 break;
21085 }
21086 }
21087 (start_row, end_row)
21088}
21089
21090fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
21091 if next_selection.end.column > 0 || next_selection.is_empty() {
21092 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
21093 } else {
21094 MultiBufferRow(next_selection.end.row)
21095 }
21096}
21097
21098impl EditorSnapshot {
21099 pub fn remote_selections_in_range<'a>(
21100 &'a self,
21101 range: &'a Range<Anchor>,
21102 collaboration_hub: &dyn CollaborationHub,
21103 cx: &'a App,
21104 ) -> impl 'a + Iterator<Item = RemoteSelection> {
21105 let participant_names = collaboration_hub.user_names(cx);
21106 let participant_indices = collaboration_hub.user_participant_indices(cx);
21107 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
21108 let collaborators_by_replica_id = collaborators_by_peer_id
21109 .values()
21110 .map(|collaborator| (collaborator.replica_id, collaborator))
21111 .collect::<HashMap<_, _>>();
21112 self.buffer_snapshot
21113 .selections_in_range(range, false)
21114 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
21115 if replica_id == AGENT_REPLICA_ID {
21116 Some(RemoteSelection {
21117 replica_id,
21118 selection,
21119 cursor_shape,
21120 line_mode,
21121 collaborator_id: CollaboratorId::Agent,
21122 user_name: Some("Agent".into()),
21123 color: cx.theme().players().agent(),
21124 })
21125 } else {
21126 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
21127 let participant_index = participant_indices.get(&collaborator.user_id).copied();
21128 let user_name = participant_names.get(&collaborator.user_id).cloned();
21129 Some(RemoteSelection {
21130 replica_id,
21131 selection,
21132 cursor_shape,
21133 line_mode,
21134 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
21135 user_name,
21136 color: if let Some(index) = participant_index {
21137 cx.theme().players().color_for_participant(index.0)
21138 } else {
21139 cx.theme().players().absent()
21140 },
21141 })
21142 }
21143 })
21144 }
21145
21146 pub fn hunks_for_ranges(
21147 &self,
21148 ranges: impl IntoIterator<Item = Range<Point>>,
21149 ) -> Vec<MultiBufferDiffHunk> {
21150 let mut hunks = Vec::new();
21151 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
21152 HashMap::default();
21153 for query_range in ranges {
21154 let query_rows =
21155 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
21156 for hunk in self.buffer_snapshot.diff_hunks_in_range(
21157 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
21158 ) {
21159 // Include deleted hunks that are adjacent to the query range, because
21160 // otherwise they would be missed.
21161 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
21162 if hunk.status().is_deleted() {
21163 intersects_range |= hunk.row_range.start == query_rows.end;
21164 intersects_range |= hunk.row_range.end == query_rows.start;
21165 }
21166 if intersects_range {
21167 if !processed_buffer_rows
21168 .entry(hunk.buffer_id)
21169 .or_default()
21170 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
21171 {
21172 continue;
21173 }
21174 hunks.push(hunk);
21175 }
21176 }
21177 }
21178
21179 hunks
21180 }
21181
21182 fn display_diff_hunks_for_rows<'a>(
21183 &'a self,
21184 display_rows: Range<DisplayRow>,
21185 folded_buffers: &'a HashSet<BufferId>,
21186 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
21187 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
21188 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
21189
21190 self.buffer_snapshot
21191 .diff_hunks_in_range(buffer_start..buffer_end)
21192 .filter_map(|hunk| {
21193 if folded_buffers.contains(&hunk.buffer_id) {
21194 return None;
21195 }
21196
21197 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
21198 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
21199
21200 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
21201 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
21202
21203 let display_hunk = if hunk_display_start.column() != 0 {
21204 DisplayDiffHunk::Folded {
21205 display_row: hunk_display_start.row(),
21206 }
21207 } else {
21208 let mut end_row = hunk_display_end.row();
21209 if hunk_display_end.column() > 0 {
21210 end_row.0 += 1;
21211 }
21212 let is_created_file = hunk.is_created_file();
21213 DisplayDiffHunk::Unfolded {
21214 status: hunk.status(),
21215 diff_base_byte_range: hunk.diff_base_byte_range,
21216 display_row_range: hunk_display_start.row()..end_row,
21217 multi_buffer_range: Anchor::range_in_buffer(
21218 hunk.excerpt_id,
21219 hunk.buffer_id,
21220 hunk.buffer_range,
21221 ),
21222 is_created_file,
21223 }
21224 };
21225
21226 Some(display_hunk)
21227 })
21228 }
21229
21230 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
21231 self.display_snapshot.buffer_snapshot.language_at(position)
21232 }
21233
21234 pub fn is_focused(&self) -> bool {
21235 self.is_focused
21236 }
21237
21238 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
21239 self.placeholder_text.as_ref()
21240 }
21241
21242 pub fn scroll_position(&self) -> gpui::Point<f32> {
21243 self.scroll_anchor.scroll_position(&self.display_snapshot)
21244 }
21245
21246 fn gutter_dimensions(
21247 &self,
21248 font_id: FontId,
21249 font_size: Pixels,
21250 max_line_number_width: Pixels,
21251 cx: &App,
21252 ) -> Option<GutterDimensions> {
21253 if !self.show_gutter {
21254 return None;
21255 }
21256
21257 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
21258 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
21259
21260 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
21261 matches!(
21262 ProjectSettings::get_global(cx).git.git_gutter,
21263 Some(GitGutterSetting::TrackedFiles)
21264 )
21265 });
21266 let gutter_settings = EditorSettings::get_global(cx).gutter;
21267 let show_line_numbers = self
21268 .show_line_numbers
21269 .unwrap_or(gutter_settings.line_numbers);
21270 let line_gutter_width = if show_line_numbers {
21271 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
21272 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
21273 max_line_number_width.max(min_width_for_number_on_gutter)
21274 } else {
21275 0.0.into()
21276 };
21277
21278 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
21279 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
21280
21281 let git_blame_entries_width =
21282 self.git_blame_gutter_max_author_length
21283 .map(|max_author_length| {
21284 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21285 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
21286
21287 /// The number of characters to dedicate to gaps and margins.
21288 const SPACING_WIDTH: usize = 4;
21289
21290 let max_char_count = max_author_length.min(renderer.max_author_length())
21291 + ::git::SHORT_SHA_LENGTH
21292 + MAX_RELATIVE_TIMESTAMP.len()
21293 + SPACING_WIDTH;
21294
21295 em_advance * max_char_count
21296 });
21297
21298 let is_singleton = self.buffer_snapshot.is_singleton();
21299
21300 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
21301 left_padding += if !is_singleton {
21302 em_width * 4.0
21303 } else if show_runnables || show_breakpoints {
21304 em_width * 3.0
21305 } else if show_git_gutter && show_line_numbers {
21306 em_width * 2.0
21307 } else if show_git_gutter || show_line_numbers {
21308 em_width
21309 } else {
21310 px(0.)
21311 };
21312
21313 let shows_folds = is_singleton && gutter_settings.folds;
21314
21315 let right_padding = if shows_folds && show_line_numbers {
21316 em_width * 4.0
21317 } else if shows_folds || (!is_singleton && show_line_numbers) {
21318 em_width * 3.0
21319 } else if show_line_numbers {
21320 em_width
21321 } else {
21322 px(0.)
21323 };
21324
21325 Some(GutterDimensions {
21326 left_padding,
21327 right_padding,
21328 width: line_gutter_width + left_padding + right_padding,
21329 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
21330 git_blame_entries_width,
21331 })
21332 }
21333
21334 pub fn render_crease_toggle(
21335 &self,
21336 buffer_row: MultiBufferRow,
21337 row_contains_cursor: bool,
21338 editor: Entity<Editor>,
21339 window: &mut Window,
21340 cx: &mut App,
21341 ) -> Option<AnyElement> {
21342 let folded = self.is_line_folded(buffer_row);
21343 let mut is_foldable = false;
21344
21345 if let Some(crease) = self
21346 .crease_snapshot
21347 .query_row(buffer_row, &self.buffer_snapshot)
21348 {
21349 is_foldable = true;
21350 match crease {
21351 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
21352 if let Some(render_toggle) = render_toggle {
21353 let toggle_callback =
21354 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
21355 if folded {
21356 editor.update(cx, |editor, cx| {
21357 editor.fold_at(buffer_row, window, cx)
21358 });
21359 } else {
21360 editor.update(cx, |editor, cx| {
21361 editor.unfold_at(buffer_row, window, cx)
21362 });
21363 }
21364 });
21365 return Some((render_toggle)(
21366 buffer_row,
21367 folded,
21368 toggle_callback,
21369 window,
21370 cx,
21371 ));
21372 }
21373 }
21374 }
21375 }
21376
21377 is_foldable |= self.starts_indent(buffer_row);
21378
21379 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
21380 Some(
21381 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
21382 .toggle_state(folded)
21383 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
21384 if folded {
21385 this.unfold_at(buffer_row, window, cx);
21386 } else {
21387 this.fold_at(buffer_row, window, cx);
21388 }
21389 }))
21390 .into_any_element(),
21391 )
21392 } else {
21393 None
21394 }
21395 }
21396
21397 pub fn render_crease_trailer(
21398 &self,
21399 buffer_row: MultiBufferRow,
21400 window: &mut Window,
21401 cx: &mut App,
21402 ) -> Option<AnyElement> {
21403 let folded = self.is_line_folded(buffer_row);
21404 if let Crease::Inline { render_trailer, .. } = self
21405 .crease_snapshot
21406 .query_row(buffer_row, &self.buffer_snapshot)?
21407 {
21408 let render_trailer = render_trailer.as_ref()?;
21409 Some(render_trailer(buffer_row, folded, window, cx))
21410 } else {
21411 None
21412 }
21413 }
21414}
21415
21416impl Deref for EditorSnapshot {
21417 type Target = DisplaySnapshot;
21418
21419 fn deref(&self) -> &Self::Target {
21420 &self.display_snapshot
21421 }
21422}
21423
21424#[derive(Clone, Debug, PartialEq, Eq)]
21425pub enum EditorEvent {
21426 InputIgnored {
21427 text: Arc<str>,
21428 },
21429 InputHandled {
21430 utf16_range_to_replace: Option<Range<isize>>,
21431 text: Arc<str>,
21432 },
21433 ExcerptsAdded {
21434 buffer: Entity<Buffer>,
21435 predecessor: ExcerptId,
21436 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21437 },
21438 ExcerptsRemoved {
21439 ids: Vec<ExcerptId>,
21440 removed_buffer_ids: Vec<BufferId>,
21441 },
21442 BufferFoldToggled {
21443 ids: Vec<ExcerptId>,
21444 folded: bool,
21445 },
21446 ExcerptsEdited {
21447 ids: Vec<ExcerptId>,
21448 },
21449 ExcerptsExpanded {
21450 ids: Vec<ExcerptId>,
21451 },
21452 BufferEdited,
21453 Edited {
21454 transaction_id: clock::Lamport,
21455 },
21456 Reparsed(BufferId),
21457 Focused,
21458 FocusedIn,
21459 Blurred,
21460 DirtyChanged,
21461 Saved,
21462 TitleChanged,
21463 DiffBaseChanged,
21464 SelectionsChanged {
21465 local: bool,
21466 },
21467 ScrollPositionChanged {
21468 local: bool,
21469 autoscroll: bool,
21470 },
21471 Closed,
21472 TransactionUndone {
21473 transaction_id: clock::Lamport,
21474 },
21475 TransactionBegun {
21476 transaction_id: clock::Lamport,
21477 },
21478 Reloaded,
21479 CursorShapeChanged,
21480 PushedToNavHistory {
21481 anchor: Anchor,
21482 is_deactivate: bool,
21483 },
21484}
21485
21486impl EventEmitter<EditorEvent> for Editor {}
21487
21488impl Focusable for Editor {
21489 fn focus_handle(&self, _cx: &App) -> FocusHandle {
21490 self.focus_handle.clone()
21491 }
21492}
21493
21494impl Render for Editor {
21495 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21496 let settings = ThemeSettings::get_global(cx);
21497
21498 let mut text_style = match self.mode {
21499 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21500 color: cx.theme().colors().editor_foreground,
21501 font_family: settings.ui_font.family.clone(),
21502 font_features: settings.ui_font.features.clone(),
21503 font_fallbacks: settings.ui_font.fallbacks.clone(),
21504 font_size: rems(0.875).into(),
21505 font_weight: settings.ui_font.weight,
21506 line_height: relative(settings.buffer_line_height.value()),
21507 ..Default::default()
21508 },
21509 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
21510 color: cx.theme().colors().editor_foreground,
21511 font_family: settings.buffer_font.family.clone(),
21512 font_features: settings.buffer_font.features.clone(),
21513 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21514 font_size: settings.buffer_font_size(cx).into(),
21515 font_weight: settings.buffer_font.weight,
21516 line_height: relative(settings.buffer_line_height.value()),
21517 ..Default::default()
21518 },
21519 };
21520 if let Some(text_style_refinement) = &self.text_style_refinement {
21521 text_style.refine(text_style_refinement)
21522 }
21523
21524 let background = match self.mode {
21525 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
21526 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
21527 EditorMode::Full { .. } => cx.theme().colors().editor_background,
21528 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
21529 };
21530
21531 EditorElement::new(
21532 &cx.entity(),
21533 EditorStyle {
21534 background,
21535 local_player: cx.theme().players().local(),
21536 text: text_style,
21537 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
21538 syntax: cx.theme().syntax().clone(),
21539 status: cx.theme().status().clone(),
21540 inlay_hints_style: make_inlay_hints_style(cx),
21541 inline_completion_styles: make_suggestion_styles(cx),
21542 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
21543 show_underlines: !self.mode.is_minimap(),
21544 },
21545 )
21546 }
21547}
21548
21549impl EntityInputHandler for Editor {
21550 fn text_for_range(
21551 &mut self,
21552 range_utf16: Range<usize>,
21553 adjusted_range: &mut Option<Range<usize>>,
21554 _: &mut Window,
21555 cx: &mut Context<Self>,
21556 ) -> Option<String> {
21557 let snapshot = self.buffer.read(cx).read(cx);
21558 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
21559 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
21560 if (start.0..end.0) != range_utf16 {
21561 adjusted_range.replace(start.0..end.0);
21562 }
21563 Some(snapshot.text_for_range(start..end).collect())
21564 }
21565
21566 fn selected_text_range(
21567 &mut self,
21568 ignore_disabled_input: bool,
21569 _: &mut Window,
21570 cx: &mut Context<Self>,
21571 ) -> Option<UTF16Selection> {
21572 // Prevent the IME menu from appearing when holding down an alphabetic key
21573 // while input is disabled.
21574 if !ignore_disabled_input && !self.input_enabled {
21575 return None;
21576 }
21577
21578 let selection = self.selections.newest::<OffsetUtf16>(cx);
21579 let range = selection.range();
21580
21581 Some(UTF16Selection {
21582 range: range.start.0..range.end.0,
21583 reversed: selection.reversed,
21584 })
21585 }
21586
21587 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
21588 let snapshot = self.buffer.read(cx).read(cx);
21589 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
21590 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
21591 }
21592
21593 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21594 self.clear_highlights::<InputComposition>(cx);
21595 self.ime_transaction.take();
21596 }
21597
21598 fn replace_text_in_range(
21599 &mut self,
21600 range_utf16: Option<Range<usize>>,
21601 text: &str,
21602 window: &mut Window,
21603 cx: &mut Context<Self>,
21604 ) {
21605 if !self.input_enabled {
21606 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21607 return;
21608 }
21609
21610 self.transact(window, cx, |this, window, cx| {
21611 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
21612 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21613 Some(this.selection_replacement_ranges(range_utf16, cx))
21614 } else {
21615 this.marked_text_ranges(cx)
21616 };
21617
21618 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
21619 let newest_selection_id = this.selections.newest_anchor().id;
21620 this.selections
21621 .all::<OffsetUtf16>(cx)
21622 .iter()
21623 .zip(ranges_to_replace.iter())
21624 .find_map(|(selection, range)| {
21625 if selection.id == newest_selection_id {
21626 Some(
21627 (range.start.0 as isize - selection.head().0 as isize)
21628 ..(range.end.0 as isize - selection.head().0 as isize),
21629 )
21630 } else {
21631 None
21632 }
21633 })
21634 });
21635
21636 cx.emit(EditorEvent::InputHandled {
21637 utf16_range_to_replace: range_to_replace,
21638 text: text.into(),
21639 });
21640
21641 if let Some(new_selected_ranges) = new_selected_ranges {
21642 this.change_selections(None, window, cx, |selections| {
21643 selections.select_ranges(new_selected_ranges)
21644 });
21645 this.backspace(&Default::default(), window, cx);
21646 }
21647
21648 this.handle_input(text, window, cx);
21649 });
21650
21651 if let Some(transaction) = self.ime_transaction {
21652 self.buffer.update(cx, |buffer, cx| {
21653 buffer.group_until_transaction(transaction, cx);
21654 });
21655 }
21656
21657 self.unmark_text(window, cx);
21658 }
21659
21660 fn replace_and_mark_text_in_range(
21661 &mut self,
21662 range_utf16: Option<Range<usize>>,
21663 text: &str,
21664 new_selected_range_utf16: Option<Range<usize>>,
21665 window: &mut Window,
21666 cx: &mut Context<Self>,
21667 ) {
21668 if !self.input_enabled {
21669 return;
21670 }
21671
21672 let transaction = self.transact(window, cx, |this, window, cx| {
21673 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
21674 let snapshot = this.buffer.read(cx).read(cx);
21675 if let Some(relative_range_utf16) = range_utf16.as_ref() {
21676 for marked_range in &mut marked_ranges {
21677 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
21678 marked_range.start.0 += relative_range_utf16.start;
21679 marked_range.start =
21680 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
21681 marked_range.end =
21682 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
21683 }
21684 }
21685 Some(marked_ranges)
21686 } else if let Some(range_utf16) = range_utf16 {
21687 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21688 Some(this.selection_replacement_ranges(range_utf16, cx))
21689 } else {
21690 None
21691 };
21692
21693 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
21694 let newest_selection_id = this.selections.newest_anchor().id;
21695 this.selections
21696 .all::<OffsetUtf16>(cx)
21697 .iter()
21698 .zip(ranges_to_replace.iter())
21699 .find_map(|(selection, range)| {
21700 if selection.id == newest_selection_id {
21701 Some(
21702 (range.start.0 as isize - selection.head().0 as isize)
21703 ..(range.end.0 as isize - selection.head().0 as isize),
21704 )
21705 } else {
21706 None
21707 }
21708 })
21709 });
21710
21711 cx.emit(EditorEvent::InputHandled {
21712 utf16_range_to_replace: range_to_replace,
21713 text: text.into(),
21714 });
21715
21716 if let Some(ranges) = ranges_to_replace {
21717 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
21718 }
21719
21720 let marked_ranges = {
21721 let snapshot = this.buffer.read(cx).read(cx);
21722 this.selections
21723 .disjoint_anchors()
21724 .iter()
21725 .map(|selection| {
21726 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
21727 })
21728 .collect::<Vec<_>>()
21729 };
21730
21731 if text.is_empty() {
21732 this.unmark_text(window, cx);
21733 } else {
21734 this.highlight_text::<InputComposition>(
21735 marked_ranges.clone(),
21736 HighlightStyle {
21737 underline: Some(UnderlineStyle {
21738 thickness: px(1.),
21739 color: None,
21740 wavy: false,
21741 }),
21742 ..Default::default()
21743 },
21744 cx,
21745 );
21746 }
21747
21748 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
21749 let use_autoclose = this.use_autoclose;
21750 let use_auto_surround = this.use_auto_surround;
21751 this.set_use_autoclose(false);
21752 this.set_use_auto_surround(false);
21753 this.handle_input(text, window, cx);
21754 this.set_use_autoclose(use_autoclose);
21755 this.set_use_auto_surround(use_auto_surround);
21756
21757 if let Some(new_selected_range) = new_selected_range_utf16 {
21758 let snapshot = this.buffer.read(cx).read(cx);
21759 let new_selected_ranges = marked_ranges
21760 .into_iter()
21761 .map(|marked_range| {
21762 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
21763 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
21764 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
21765 snapshot.clip_offset_utf16(new_start, Bias::Left)
21766 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
21767 })
21768 .collect::<Vec<_>>();
21769
21770 drop(snapshot);
21771 this.change_selections(None, window, cx, |selections| {
21772 selections.select_ranges(new_selected_ranges)
21773 });
21774 }
21775 });
21776
21777 self.ime_transaction = self.ime_transaction.or(transaction);
21778 if let Some(transaction) = self.ime_transaction {
21779 self.buffer.update(cx, |buffer, cx| {
21780 buffer.group_until_transaction(transaction, cx);
21781 });
21782 }
21783
21784 if self.text_highlights::<InputComposition>(cx).is_none() {
21785 self.ime_transaction.take();
21786 }
21787 }
21788
21789 fn bounds_for_range(
21790 &mut self,
21791 range_utf16: Range<usize>,
21792 element_bounds: gpui::Bounds<Pixels>,
21793 window: &mut Window,
21794 cx: &mut Context<Self>,
21795 ) -> Option<gpui::Bounds<Pixels>> {
21796 let text_layout_details = self.text_layout_details(window);
21797 let gpui::Size {
21798 width: em_width,
21799 height: line_height,
21800 } = self.character_size(window);
21801
21802 let snapshot = self.snapshot(window, cx);
21803 let scroll_position = snapshot.scroll_position();
21804 let scroll_left = scroll_position.x * em_width;
21805
21806 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
21807 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
21808 + self.gutter_dimensions.width
21809 + self.gutter_dimensions.margin;
21810 let y = line_height * (start.row().as_f32() - scroll_position.y);
21811
21812 Some(Bounds {
21813 origin: element_bounds.origin + point(x, y),
21814 size: size(em_width, line_height),
21815 })
21816 }
21817
21818 fn character_index_for_point(
21819 &mut self,
21820 point: gpui::Point<Pixels>,
21821 _window: &mut Window,
21822 _cx: &mut Context<Self>,
21823 ) -> Option<usize> {
21824 let position_map = self.last_position_map.as_ref()?;
21825 if !position_map.text_hitbox.contains(&point) {
21826 return None;
21827 }
21828 let display_point = position_map.point_for_position(point).previous_valid;
21829 let anchor = position_map
21830 .snapshot
21831 .display_point_to_anchor(display_point, Bias::Left);
21832 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
21833 Some(utf16_offset.0)
21834 }
21835}
21836
21837trait SelectionExt {
21838 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
21839 fn spanned_rows(
21840 &self,
21841 include_end_if_at_line_start: bool,
21842 map: &DisplaySnapshot,
21843 ) -> Range<MultiBufferRow>;
21844}
21845
21846impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
21847 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
21848 let start = self
21849 .start
21850 .to_point(&map.buffer_snapshot)
21851 .to_display_point(map);
21852 let end = self
21853 .end
21854 .to_point(&map.buffer_snapshot)
21855 .to_display_point(map);
21856 if self.reversed {
21857 end..start
21858 } else {
21859 start..end
21860 }
21861 }
21862
21863 fn spanned_rows(
21864 &self,
21865 include_end_if_at_line_start: bool,
21866 map: &DisplaySnapshot,
21867 ) -> Range<MultiBufferRow> {
21868 let start = self.start.to_point(&map.buffer_snapshot);
21869 let mut end = self.end.to_point(&map.buffer_snapshot);
21870 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
21871 end.row -= 1;
21872 }
21873
21874 let buffer_start = map.prev_line_boundary(start).0;
21875 let buffer_end = map.next_line_boundary(end).0;
21876 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
21877 }
21878}
21879
21880impl<T: InvalidationRegion> InvalidationStack<T> {
21881 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
21882 where
21883 S: Clone + ToOffset,
21884 {
21885 while let Some(region) = self.last() {
21886 let all_selections_inside_invalidation_ranges =
21887 if selections.len() == region.ranges().len() {
21888 selections
21889 .iter()
21890 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
21891 .all(|(selection, invalidation_range)| {
21892 let head = selection.head().to_offset(buffer);
21893 invalidation_range.start <= head && invalidation_range.end >= head
21894 })
21895 } else {
21896 false
21897 };
21898
21899 if all_selections_inside_invalidation_ranges {
21900 break;
21901 } else {
21902 self.pop();
21903 }
21904 }
21905 }
21906}
21907
21908impl<T> Default for InvalidationStack<T> {
21909 fn default() -> Self {
21910 Self(Default::default())
21911 }
21912}
21913
21914impl<T> Deref for InvalidationStack<T> {
21915 type Target = Vec<T>;
21916
21917 fn deref(&self) -> &Self::Target {
21918 &self.0
21919 }
21920}
21921
21922impl<T> DerefMut for InvalidationStack<T> {
21923 fn deref_mut(&mut self) -> &mut Self::Target {
21924 &mut self.0
21925 }
21926}
21927
21928impl InvalidationRegion for SnippetState {
21929 fn ranges(&self) -> &[Range<Anchor>] {
21930 &self.ranges[self.active_index]
21931 }
21932}
21933
21934fn inline_completion_edit_text(
21935 current_snapshot: &BufferSnapshot,
21936 edits: &[(Range<Anchor>, String)],
21937 edit_preview: &EditPreview,
21938 include_deletions: bool,
21939 cx: &App,
21940) -> HighlightedText {
21941 let edits = edits
21942 .iter()
21943 .map(|(anchor, text)| {
21944 (
21945 anchor.start.text_anchor..anchor.end.text_anchor,
21946 text.clone(),
21947 )
21948 })
21949 .collect::<Vec<_>>();
21950
21951 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
21952}
21953
21954pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
21955 match severity {
21956 lsp::DiagnosticSeverity::ERROR => colors.error,
21957 lsp::DiagnosticSeverity::WARNING => colors.warning,
21958 lsp::DiagnosticSeverity::INFORMATION => colors.info,
21959 lsp::DiagnosticSeverity::HINT => colors.info,
21960 _ => colors.ignored,
21961 }
21962}
21963
21964pub fn styled_runs_for_code_label<'a>(
21965 label: &'a CodeLabel,
21966 syntax_theme: &'a theme::SyntaxTheme,
21967) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
21968 let fade_out = HighlightStyle {
21969 fade_out: Some(0.35),
21970 ..Default::default()
21971 };
21972
21973 let mut prev_end = label.filter_range.end;
21974 label
21975 .runs
21976 .iter()
21977 .enumerate()
21978 .flat_map(move |(ix, (range, highlight_id))| {
21979 let style = if let Some(style) = highlight_id.style(syntax_theme) {
21980 style
21981 } else {
21982 return Default::default();
21983 };
21984 let mut muted_style = style;
21985 muted_style.highlight(fade_out);
21986
21987 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
21988 if range.start >= label.filter_range.end {
21989 if range.start > prev_end {
21990 runs.push((prev_end..range.start, fade_out));
21991 }
21992 runs.push((range.clone(), muted_style));
21993 } else if range.end <= label.filter_range.end {
21994 runs.push((range.clone(), style));
21995 } else {
21996 runs.push((range.start..label.filter_range.end, style));
21997 runs.push((label.filter_range.end..range.end, muted_style));
21998 }
21999 prev_end = cmp::max(prev_end, range.end);
22000
22001 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22002 runs.push((prev_end..label.text.len(), fade_out));
22003 }
22004
22005 runs
22006 })
22007}
22008
22009pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22010 let mut prev_index = 0;
22011 let mut prev_codepoint: Option<char> = None;
22012 text.char_indices()
22013 .chain([(text.len(), '\0')])
22014 .filter_map(move |(index, codepoint)| {
22015 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22016 let is_boundary = index == text.len()
22017 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22018 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22019 if is_boundary {
22020 let chunk = &text[prev_index..index];
22021 prev_index = index;
22022 Some(chunk)
22023 } else {
22024 None
22025 }
22026 })
22027}
22028
22029pub trait RangeToAnchorExt: Sized {
22030 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22031
22032 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22033 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22034 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22035 }
22036}
22037
22038impl<T: ToOffset> RangeToAnchorExt for Range<T> {
22039 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
22040 let start_offset = self.start.to_offset(snapshot);
22041 let end_offset = self.end.to_offset(snapshot);
22042 if start_offset == end_offset {
22043 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
22044 } else {
22045 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
22046 }
22047 }
22048}
22049
22050pub trait RowExt {
22051 fn as_f32(&self) -> f32;
22052
22053 fn next_row(&self) -> Self;
22054
22055 fn previous_row(&self) -> Self;
22056
22057 fn minus(&self, other: Self) -> u32;
22058}
22059
22060impl RowExt for DisplayRow {
22061 fn as_f32(&self) -> f32 {
22062 self.0 as f32
22063 }
22064
22065 fn next_row(&self) -> Self {
22066 Self(self.0 + 1)
22067 }
22068
22069 fn previous_row(&self) -> Self {
22070 Self(self.0.saturating_sub(1))
22071 }
22072
22073 fn minus(&self, other: Self) -> u32 {
22074 self.0 - other.0
22075 }
22076}
22077
22078impl RowExt for MultiBufferRow {
22079 fn as_f32(&self) -> f32 {
22080 self.0 as f32
22081 }
22082
22083 fn next_row(&self) -> Self {
22084 Self(self.0 + 1)
22085 }
22086
22087 fn previous_row(&self) -> Self {
22088 Self(self.0.saturating_sub(1))
22089 }
22090
22091 fn minus(&self, other: Self) -> u32 {
22092 self.0 - other.0
22093 }
22094}
22095
22096trait RowRangeExt {
22097 type Row;
22098
22099 fn len(&self) -> usize;
22100
22101 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
22102}
22103
22104impl RowRangeExt for Range<MultiBufferRow> {
22105 type Row = MultiBufferRow;
22106
22107 fn len(&self) -> usize {
22108 (self.end.0 - self.start.0) as usize
22109 }
22110
22111 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
22112 (self.start.0..self.end.0).map(MultiBufferRow)
22113 }
22114}
22115
22116impl RowRangeExt for Range<DisplayRow> {
22117 type Row = DisplayRow;
22118
22119 fn len(&self) -> usize {
22120 (self.end.0 - self.start.0) as usize
22121 }
22122
22123 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
22124 (self.start.0..self.end.0).map(DisplayRow)
22125 }
22126}
22127
22128/// If select range has more than one line, we
22129/// just point the cursor to range.start.
22130fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
22131 if range.start.row == range.end.row {
22132 range
22133 } else {
22134 range.start..range.start
22135 }
22136}
22137pub struct KillRing(ClipboardItem);
22138impl Global for KillRing {}
22139
22140const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
22141
22142enum BreakpointPromptEditAction {
22143 Log,
22144 Condition,
22145 HitCondition,
22146}
22147
22148struct BreakpointPromptEditor {
22149 pub(crate) prompt: Entity<Editor>,
22150 editor: WeakEntity<Editor>,
22151 breakpoint_anchor: Anchor,
22152 breakpoint: Breakpoint,
22153 edit_action: BreakpointPromptEditAction,
22154 block_ids: HashSet<CustomBlockId>,
22155 editor_margins: Arc<Mutex<EditorMargins>>,
22156 _subscriptions: Vec<Subscription>,
22157}
22158
22159impl BreakpointPromptEditor {
22160 const MAX_LINES: u8 = 4;
22161
22162 fn new(
22163 editor: WeakEntity<Editor>,
22164 breakpoint_anchor: Anchor,
22165 breakpoint: Breakpoint,
22166 edit_action: BreakpointPromptEditAction,
22167 window: &mut Window,
22168 cx: &mut Context<Self>,
22169 ) -> Self {
22170 let base_text = match edit_action {
22171 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
22172 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
22173 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
22174 }
22175 .map(|msg| msg.to_string())
22176 .unwrap_or_default();
22177
22178 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
22179 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
22180
22181 let prompt = cx.new(|cx| {
22182 let mut prompt = Editor::new(
22183 EditorMode::AutoHeight {
22184 max_lines: Self::MAX_LINES as usize,
22185 },
22186 buffer,
22187 None,
22188 window,
22189 cx,
22190 );
22191 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
22192 prompt.set_show_cursor_when_unfocused(false, cx);
22193 prompt.set_placeholder_text(
22194 match edit_action {
22195 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
22196 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
22197 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
22198 },
22199 cx,
22200 );
22201
22202 prompt
22203 });
22204
22205 Self {
22206 prompt,
22207 editor,
22208 breakpoint_anchor,
22209 breakpoint,
22210 edit_action,
22211 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
22212 block_ids: Default::default(),
22213 _subscriptions: vec![],
22214 }
22215 }
22216
22217 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
22218 self.block_ids.extend(block_ids)
22219 }
22220
22221 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
22222 if let Some(editor) = self.editor.upgrade() {
22223 let message = self
22224 .prompt
22225 .read(cx)
22226 .buffer
22227 .read(cx)
22228 .as_singleton()
22229 .expect("A multi buffer in breakpoint prompt isn't possible")
22230 .read(cx)
22231 .as_rope()
22232 .to_string();
22233
22234 editor.update(cx, |editor, cx| {
22235 editor.edit_breakpoint_at_anchor(
22236 self.breakpoint_anchor,
22237 self.breakpoint.clone(),
22238 match self.edit_action {
22239 BreakpointPromptEditAction::Log => {
22240 BreakpointEditAction::EditLogMessage(message.into())
22241 }
22242 BreakpointPromptEditAction::Condition => {
22243 BreakpointEditAction::EditCondition(message.into())
22244 }
22245 BreakpointPromptEditAction::HitCondition => {
22246 BreakpointEditAction::EditHitCondition(message.into())
22247 }
22248 },
22249 cx,
22250 );
22251
22252 editor.remove_blocks(self.block_ids.clone(), None, cx);
22253 cx.focus_self(window);
22254 });
22255 }
22256 }
22257
22258 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
22259 self.editor
22260 .update(cx, |editor, cx| {
22261 editor.remove_blocks(self.block_ids.clone(), None, cx);
22262 window.focus(&editor.focus_handle);
22263 })
22264 .log_err();
22265 }
22266
22267 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22268 let settings = ThemeSettings::get_global(cx);
22269 let text_style = TextStyle {
22270 color: if self.prompt.read(cx).read_only(cx) {
22271 cx.theme().colors().text_disabled
22272 } else {
22273 cx.theme().colors().text
22274 },
22275 font_family: settings.buffer_font.family.clone(),
22276 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22277 font_size: settings.buffer_font_size(cx).into(),
22278 font_weight: settings.buffer_font.weight,
22279 line_height: relative(settings.buffer_line_height.value()),
22280 ..Default::default()
22281 };
22282 EditorElement::new(
22283 &self.prompt,
22284 EditorStyle {
22285 background: cx.theme().colors().editor_background,
22286 local_player: cx.theme().players().local(),
22287 text: text_style,
22288 ..Default::default()
22289 },
22290 )
22291 }
22292}
22293
22294impl Render for BreakpointPromptEditor {
22295 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22296 let editor_margins = *self.editor_margins.lock();
22297 let gutter_dimensions = editor_margins.gutter;
22298 h_flex()
22299 .key_context("Editor")
22300 .bg(cx.theme().colors().editor_background)
22301 .border_y_1()
22302 .border_color(cx.theme().status().info_border)
22303 .size_full()
22304 .py(window.line_height() / 2.5)
22305 .on_action(cx.listener(Self::confirm))
22306 .on_action(cx.listener(Self::cancel))
22307 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
22308 .child(div().flex_1().child(self.render_prompt_editor(cx)))
22309 }
22310}
22311
22312impl Focusable for BreakpointPromptEditor {
22313 fn focus_handle(&self, cx: &App) -> FocusHandle {
22314 self.prompt.focus_handle(cx)
22315 }
22316}
22317
22318fn all_edits_insertions_or_deletions(
22319 edits: &Vec<(Range<Anchor>, String)>,
22320 snapshot: &MultiBufferSnapshot,
22321) -> bool {
22322 let mut all_insertions = true;
22323 let mut all_deletions = true;
22324
22325 for (range, new_text) in edits.iter() {
22326 let range_is_empty = range.to_offset(&snapshot).is_empty();
22327 let text_is_empty = new_text.is_empty();
22328
22329 if range_is_empty != text_is_empty {
22330 if range_is_empty {
22331 all_deletions = false;
22332 } else {
22333 all_insertions = false;
22334 }
22335 } else {
22336 return false;
22337 }
22338
22339 if !all_insertions && !all_deletions {
22340 return false;
22341 }
22342 }
22343 all_insertions || all_deletions
22344}
22345
22346struct MissingEditPredictionKeybindingTooltip;
22347
22348impl Render for MissingEditPredictionKeybindingTooltip {
22349 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22350 ui::tooltip_container(window, cx, |container, _, cx| {
22351 container
22352 .flex_shrink_0()
22353 .max_w_80()
22354 .min_h(rems_from_px(124.))
22355 .justify_between()
22356 .child(
22357 v_flex()
22358 .flex_1()
22359 .text_ui_sm(cx)
22360 .child(Label::new("Conflict with Accept Keybinding"))
22361 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
22362 )
22363 .child(
22364 h_flex()
22365 .pb_1()
22366 .gap_1()
22367 .items_end()
22368 .w_full()
22369 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
22370 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
22371 }))
22372 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
22373 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
22374 })),
22375 )
22376 })
22377 }
22378}
22379
22380#[derive(Debug, Clone, Copy, PartialEq)]
22381pub struct LineHighlight {
22382 pub background: Background,
22383 pub border: Option<gpui::Hsla>,
22384 pub include_gutter: bool,
22385 pub type_id: Option<TypeId>,
22386}
22387
22388fn render_diff_hunk_controls(
22389 row: u32,
22390 status: &DiffHunkStatus,
22391 hunk_range: Range<Anchor>,
22392 is_created_file: bool,
22393 line_height: Pixels,
22394 editor: &Entity<Editor>,
22395 _window: &mut Window,
22396 cx: &mut App,
22397) -> AnyElement {
22398 h_flex()
22399 .h(line_height)
22400 .mr_1()
22401 .gap_1()
22402 .px_0p5()
22403 .pb_1()
22404 .border_x_1()
22405 .border_b_1()
22406 .border_color(cx.theme().colors().border_variant)
22407 .rounded_b_lg()
22408 .bg(cx.theme().colors().editor_background)
22409 .gap_1()
22410 .block_mouse_except_scroll()
22411 .shadow_md()
22412 .child(if status.has_secondary_hunk() {
22413 Button::new(("stage", row as u64), "Stage")
22414 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22415 .tooltip({
22416 let focus_handle = editor.focus_handle(cx);
22417 move |window, cx| {
22418 Tooltip::for_action_in(
22419 "Stage Hunk",
22420 &::git::ToggleStaged,
22421 &focus_handle,
22422 window,
22423 cx,
22424 )
22425 }
22426 })
22427 .on_click({
22428 let editor = editor.clone();
22429 move |_event, _window, cx| {
22430 editor.update(cx, |editor, cx| {
22431 editor.stage_or_unstage_diff_hunks(
22432 true,
22433 vec![hunk_range.start..hunk_range.start],
22434 cx,
22435 );
22436 });
22437 }
22438 })
22439 } else {
22440 Button::new(("unstage", row as u64), "Unstage")
22441 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22442 .tooltip({
22443 let focus_handle = editor.focus_handle(cx);
22444 move |window, cx| {
22445 Tooltip::for_action_in(
22446 "Unstage Hunk",
22447 &::git::ToggleStaged,
22448 &focus_handle,
22449 window,
22450 cx,
22451 )
22452 }
22453 })
22454 .on_click({
22455 let editor = editor.clone();
22456 move |_event, _window, cx| {
22457 editor.update(cx, |editor, cx| {
22458 editor.stage_or_unstage_diff_hunks(
22459 false,
22460 vec![hunk_range.start..hunk_range.start],
22461 cx,
22462 );
22463 });
22464 }
22465 })
22466 })
22467 .child(
22468 Button::new(("restore", row as u64), "Restore")
22469 .tooltip({
22470 let focus_handle = editor.focus_handle(cx);
22471 move |window, cx| {
22472 Tooltip::for_action_in(
22473 "Restore Hunk",
22474 &::git::Restore,
22475 &focus_handle,
22476 window,
22477 cx,
22478 )
22479 }
22480 })
22481 .on_click({
22482 let editor = editor.clone();
22483 move |_event, window, cx| {
22484 editor.update(cx, |editor, cx| {
22485 let snapshot = editor.snapshot(window, cx);
22486 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
22487 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
22488 });
22489 }
22490 })
22491 .disabled(is_created_file),
22492 )
22493 .when(
22494 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
22495 |el| {
22496 el.child(
22497 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
22498 .shape(IconButtonShape::Square)
22499 .icon_size(IconSize::Small)
22500 // .disabled(!has_multiple_hunks)
22501 .tooltip({
22502 let focus_handle = editor.focus_handle(cx);
22503 move |window, cx| {
22504 Tooltip::for_action_in(
22505 "Next Hunk",
22506 &GoToHunk,
22507 &focus_handle,
22508 window,
22509 cx,
22510 )
22511 }
22512 })
22513 .on_click({
22514 let editor = editor.clone();
22515 move |_event, window, cx| {
22516 editor.update(cx, |editor, cx| {
22517 let snapshot = editor.snapshot(window, cx);
22518 let position =
22519 hunk_range.end.to_point(&snapshot.buffer_snapshot);
22520 editor.go_to_hunk_before_or_after_position(
22521 &snapshot,
22522 position,
22523 Direction::Next,
22524 window,
22525 cx,
22526 );
22527 editor.expand_selected_diff_hunks(cx);
22528 });
22529 }
22530 }),
22531 )
22532 .child(
22533 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
22534 .shape(IconButtonShape::Square)
22535 .icon_size(IconSize::Small)
22536 // .disabled(!has_multiple_hunks)
22537 .tooltip({
22538 let focus_handle = editor.focus_handle(cx);
22539 move |window, cx| {
22540 Tooltip::for_action_in(
22541 "Previous Hunk",
22542 &GoToPreviousHunk,
22543 &focus_handle,
22544 window,
22545 cx,
22546 )
22547 }
22548 })
22549 .on_click({
22550 let editor = editor.clone();
22551 move |_event, window, cx| {
22552 editor.update(cx, |editor, cx| {
22553 let snapshot = editor.snapshot(window, cx);
22554 let point =
22555 hunk_range.start.to_point(&snapshot.buffer_snapshot);
22556 editor.go_to_hunk_before_or_after_position(
22557 &snapshot,
22558 point,
22559 Direction::Prev,
22560 window,
22561 cx,
22562 );
22563 editor.expand_selected_diff_hunks(cx);
22564 });
22565 }
22566 }),
22567 )
22568 },
22569 )
22570 .into_any_element()
22571}