1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18mod code_context_menus;
19pub mod commit_tooltip;
20pub mod display_map;
21mod editor_settings;
22mod editor_settings_controls;
23mod element;
24mod git;
25mod highlight_matching_bracket;
26mod hover_links;
27mod hover_popover;
28mod indent_guides;
29mod inlay_hint_cache;
30pub mod items;
31mod jsx_tag_auto_close;
32mod linked_editing_ranges;
33mod lsp_ext;
34mod mouse_context_menu;
35pub mod movement;
36mod persistence;
37mod proposed_changes_editor;
38mod rust_analyzer_ext;
39pub mod scroll;
40mod selections_collection;
41pub mod tasks;
42
43#[cfg(test)]
44mod editor_tests;
45#[cfg(test)]
46mod inline_completion_tests;
47mod signature_help;
48#[cfg(any(test, feature = "test-support"))]
49pub mod test;
50
51pub(crate) use actions::*;
52pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
53use aho_corasick::AhoCorasick;
54use anyhow::{anyhow, Context as _, Result};
55use blink_manager::BlinkManager;
56use buffer_diff::DiffHunkStatus;
57use client::{Collaborator, ParticipantIndex};
58use clock::ReplicaId;
59use collections::{BTreeMap, HashMap, HashSet, VecDeque};
60use convert_case::{Case, Casing};
61use display_map::*;
62pub use display_map::{DisplayPoint, FoldPlaceholder};
63pub use editor_settings::{
64 CurrentLineHighlight, EditorSettings, ScrollBeyondLastLine, SearchSettings, ShowScrollbar,
65};
66pub use editor_settings_controls::*;
67use element::{layout_line, AcceptEditPredictionBinding, LineWithInvisibles, PositionMap};
68pub use element::{
69 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
70};
71use feature_flags::{Debugger, FeatureFlagAppExt};
72use futures::{
73 future::{self, join, Shared},
74 FutureExt,
75};
76use fuzzy::StringMatchCandidate;
77
78use ::git::Restore;
79use code_context_menus::{
80 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
81 CompletionsMenu, ContextMenuOrigin,
82};
83use git::blame::GitBlame;
84use gpui::{
85 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size, Action, Animation,
86 AnimationExt, AnyElement, App, AppContext, AsyncWindowContext, AvailableSpace, Background,
87 Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context, DispatchPhase, Edges, Entity,
88 EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent, Focusable, FontId, FontWeight,
89 Global, HighlightStyle, Hsla, KeyContext, Modifiers, MouseButton, MouseDownEvent, PaintQuad,
90 ParentElement, Pixels, Render, SharedString, Size, Stateful, Styled, StyledText, Subscription,
91 Task, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle,
92 WeakEntity, WeakFocusHandle, Window,
93};
94use highlight_matching_bracket::refresh_matching_bracket_highlights;
95use hover_links::{find_file, HoverLink, HoveredLinkState, InlayHighlight};
96use hover_popover::{hide_hover, HoverState};
97use indent_guides::ActiveIndentGuidesState;
98use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
99pub use inline_completion::Direction;
100use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
101pub use items::MAX_TAB_TITLE_LEN;
102use itertools::Itertools;
103use language::{
104 language_settings::{
105 self, all_language_settings, language_settings, InlayHintSettings, RewrapBehavior,
106 WordsCompletionMode,
107 },
108 point_from_lsp, text_diff_with_options, AutoindentMode, BracketMatch, BracketPair, Buffer,
109 Capability, CharKind, CodeLabel, CursorShape, Diagnostic, DiffOptions, EditPredictionsMode,
110 EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point,
111 Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
112};
113use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange};
114use linked_editing_ranges::refresh_linked_ranges;
115use mouse_context_menu::MouseContextMenu;
116use persistence::DB;
117use project::{
118 debugger::breakpoint_store::{BreakpointEditAction, BreakpointStore, BreakpointStoreEvent},
119 ProjectPath,
120};
121
122pub use proposed_changes_editor::{
123 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
124};
125use smallvec::smallvec;
126use std::{cell::OnceCell, iter::Peekable};
127use task::{ResolvedTask, TaskTemplate, TaskVariables};
128
129pub use lsp::CompletionContext;
130use lsp::{
131 CodeActionKind, CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity,
132 InsertTextFormat, LanguageServerId, LanguageServerName,
133};
134
135use language::BufferSnapshot;
136use movement::TextLayoutDetails;
137pub use multi_buffer::{
138 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, RowInfo,
139 ToOffset, ToPoint,
140};
141use multi_buffer::{
142 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
143 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
144};
145use parking_lot::Mutex;
146use project::{
147 debugger::breakpoint_store::{Breakpoint, BreakpointKind},
148 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
149 project_settings::{GitGutterSetting, ProjectSettings},
150 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
151 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
152 TaskSourceKind,
153};
154use rand::prelude::*;
155use rpc::{proto::*, ErrorExt};
156use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
157use selections_collection::{
158 resolve_selections, MutableSelectionsCollection, SelectionsCollection,
159};
160use serde::{Deserialize, Serialize};
161use settings::{update_settings_file, Settings, SettingsLocation, SettingsStore};
162use smallvec::SmallVec;
163use snippet::Snippet;
164use std::sync::Arc;
165use std::{
166 any::TypeId,
167 borrow::Cow,
168 cell::RefCell,
169 cmp::{self, Ordering, Reverse},
170 mem,
171 num::NonZeroU32,
172 ops::{ControlFlow, Deref, DerefMut, Not as _, Range, RangeInclusive},
173 path::{Path, PathBuf},
174 rc::Rc,
175 time::{Duration, Instant},
176};
177pub use sum_tree::Bias;
178use sum_tree::TreeMap;
179use text::{BufferId, OffsetUtf16, Rope};
180use theme::{
181 observe_buffer_font_size_adjustment, ActiveTheme, PlayerColor, StatusColors, SyntaxTheme,
182 ThemeColors, ThemeSettings,
183};
184use ui::{
185 h_flex, prelude::*, ButtonSize, ButtonStyle, Disclosure, IconButton, IconName, IconSize, Key,
186 Tooltip,
187};
188use util::{maybe, post_inc, RangeExt, ResultExt, TryFutureExt};
189use workspace::{
190 item::{ItemHandle, PreviewTabsSettings},
191 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
192 searchable::SearchEvent,
193 Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
194 RestoreOnStartupBehavior, SplitDirection, TabBarSettings, Toast, ViewId, Workspace,
195 WorkspaceId, WorkspaceSettings, SERIALIZATION_THROTTLE_TIME,
196};
197
198use crate::hover_links::{find_url, find_url_from_range};
199use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
200
201pub const FILE_HEADER_HEIGHT: u32 = 2;
202pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
203pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
204const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
205const MAX_LINE_LEN: usize = 1024;
206const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
207const MAX_SELECTION_HISTORY_LEN: usize = 1024;
208pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
209#[doc(hidden)]
210pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
211
212pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
213pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
214pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
215
216pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
217pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
218pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
219
220const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
221 alt: true,
222 shift: true,
223 control: false,
224 platform: false,
225 function: false,
226};
227
228#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
229pub enum InlayId {
230 InlineCompletion(usize),
231 Hint(usize),
232}
233
234impl InlayId {
235 fn id(&self) -> usize {
236 match self {
237 Self::InlineCompletion(id) => *id,
238 Self::Hint(id) => *id,
239 }
240 }
241}
242
243pub enum DebugCurrentRowHighlight {}
244enum DocumentHighlightRead {}
245enum DocumentHighlightWrite {}
246enum InputComposition {}
247enum SelectedTextHighlight {}
248
249#[derive(Debug, Copy, Clone, PartialEq, Eq)]
250pub enum Navigated {
251 Yes,
252 No,
253}
254
255impl Navigated {
256 pub fn from_bool(yes: bool) -> Navigated {
257 if yes {
258 Navigated::Yes
259 } else {
260 Navigated::No
261 }
262 }
263}
264
265#[derive(Debug, Clone, PartialEq, Eq)]
266enum DisplayDiffHunk {
267 Folded {
268 display_row: DisplayRow,
269 },
270 Unfolded {
271 is_created_file: bool,
272 diff_base_byte_range: Range<usize>,
273 display_row_range: Range<DisplayRow>,
274 multi_buffer_range: Range<Anchor>,
275 status: DiffHunkStatus,
276 },
277}
278
279pub fn init_settings(cx: &mut App) {
280 EditorSettings::register(cx);
281}
282
283pub fn init(cx: &mut App) {
284 init_settings(cx);
285
286 workspace::register_project_item::<Editor>(cx);
287 workspace::FollowableViewRegistry::register::<Editor>(cx);
288 workspace::register_serializable_item::<Editor>(cx);
289
290 cx.observe_new(
291 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
292 workspace.register_action(Editor::new_file);
293 workspace.register_action(Editor::new_file_vertical);
294 workspace.register_action(Editor::new_file_horizontal);
295 workspace.register_action(Editor::cancel_language_server_work);
296 },
297 )
298 .detach();
299
300 cx.on_action(move |_: &workspace::NewFile, cx| {
301 let app_state = workspace::AppState::global(cx);
302 if let Some(app_state) = app_state.upgrade() {
303 workspace::open_new(
304 Default::default(),
305 app_state,
306 cx,
307 |workspace, window, cx| {
308 Editor::new_file(workspace, &Default::default(), window, cx)
309 },
310 )
311 .detach();
312 }
313 });
314 cx.on_action(move |_: &workspace::NewWindow, cx| {
315 let app_state = workspace::AppState::global(cx);
316 if let Some(app_state) = app_state.upgrade() {
317 workspace::open_new(
318 Default::default(),
319 app_state,
320 cx,
321 |workspace, window, cx| {
322 cx.activate(true);
323 Editor::new_file(workspace, &Default::default(), window, cx)
324 },
325 )
326 .detach();
327 }
328 });
329}
330
331pub struct SearchWithinRange;
332
333trait InvalidationRegion {
334 fn ranges(&self) -> &[Range<Anchor>];
335}
336
337#[derive(Clone, Debug, PartialEq)]
338pub enum SelectPhase {
339 Begin {
340 position: DisplayPoint,
341 add: bool,
342 click_count: usize,
343 },
344 BeginColumnar {
345 position: DisplayPoint,
346 reset: bool,
347 goal_column: u32,
348 },
349 Extend {
350 position: DisplayPoint,
351 click_count: usize,
352 },
353 Update {
354 position: DisplayPoint,
355 goal_column: u32,
356 scroll_delta: gpui::Point<f32>,
357 },
358 End,
359}
360
361#[derive(Clone, Debug)]
362pub enum SelectMode {
363 Character,
364 Word(Range<Anchor>),
365 Line(Range<Anchor>),
366 All,
367}
368
369#[derive(Copy, Clone, PartialEq, Eq, Debug)]
370pub enum EditorMode {
371 SingleLine { auto_width: bool },
372 AutoHeight { max_lines: usize },
373 Full,
374}
375
376#[derive(Copy, Clone, Debug)]
377pub enum SoftWrap {
378 /// Prefer not to wrap at all.
379 ///
380 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
381 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
382 GitDiff,
383 /// Prefer a single line generally, unless an overly long line is encountered.
384 None,
385 /// Soft wrap lines that exceed the editor width.
386 EditorWidth,
387 /// Soft wrap lines at the preferred line length.
388 Column(u32),
389 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
390 Bounded(u32),
391}
392
393#[derive(Clone)]
394pub struct EditorStyle {
395 pub background: Hsla,
396 pub local_player: PlayerColor,
397 pub text: TextStyle,
398 pub scrollbar_width: Pixels,
399 pub syntax: Arc<SyntaxTheme>,
400 pub status: StatusColors,
401 pub inlay_hints_style: HighlightStyle,
402 pub inline_completion_styles: InlineCompletionStyles,
403 pub unnecessary_code_fade: f32,
404}
405
406impl Default for EditorStyle {
407 fn default() -> Self {
408 Self {
409 background: Hsla::default(),
410 local_player: PlayerColor::default(),
411 text: TextStyle::default(),
412 scrollbar_width: Pixels::default(),
413 syntax: Default::default(),
414 // HACK: Status colors don't have a real default.
415 // We should look into removing the status colors from the editor
416 // style and retrieve them directly from the theme.
417 status: StatusColors::dark(),
418 inlay_hints_style: HighlightStyle::default(),
419 inline_completion_styles: InlineCompletionStyles {
420 insertion: HighlightStyle::default(),
421 whitespace: HighlightStyle::default(),
422 },
423 unnecessary_code_fade: Default::default(),
424 }
425 }
426}
427
428pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
429 let show_background = language_settings::language_settings(None, None, cx)
430 .inlay_hints
431 .show_background;
432
433 HighlightStyle {
434 color: Some(cx.theme().status().hint),
435 background_color: show_background.then(|| cx.theme().status().hint_background),
436 ..HighlightStyle::default()
437 }
438}
439
440pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
441 InlineCompletionStyles {
442 insertion: HighlightStyle {
443 color: Some(cx.theme().status().predictive),
444 ..HighlightStyle::default()
445 },
446 whitespace: HighlightStyle {
447 background_color: Some(cx.theme().status().created_background),
448 ..HighlightStyle::default()
449 },
450 }
451}
452
453type CompletionId = usize;
454
455pub(crate) enum EditDisplayMode {
456 TabAccept,
457 DiffPopover,
458 Inline,
459}
460
461enum InlineCompletion {
462 Edit {
463 edits: Vec<(Range<Anchor>, String)>,
464 edit_preview: Option<EditPreview>,
465 display_mode: EditDisplayMode,
466 snapshot: BufferSnapshot,
467 },
468 Move {
469 target: Anchor,
470 snapshot: BufferSnapshot,
471 },
472}
473
474struct InlineCompletionState {
475 inlay_ids: Vec<InlayId>,
476 completion: InlineCompletion,
477 completion_id: Option<SharedString>,
478 invalidation_range: Range<Anchor>,
479}
480
481enum EditPredictionSettings {
482 Disabled,
483 Enabled {
484 show_in_menu: bool,
485 preview_requires_modifier: bool,
486 },
487}
488
489enum InlineCompletionHighlight {}
490
491#[derive(Debug, Clone)]
492struct InlineDiagnostic {
493 message: SharedString,
494 group_id: usize,
495 is_primary: bool,
496 start: Point,
497 severity: DiagnosticSeverity,
498}
499
500pub enum MenuInlineCompletionsPolicy {
501 Never,
502 ByProvider,
503}
504
505pub enum EditPredictionPreview {
506 /// Modifier is not pressed
507 Inactive { released_too_fast: bool },
508 /// Modifier pressed
509 Active {
510 since: Instant,
511 previous_scroll_position: Option<ScrollAnchor>,
512 },
513}
514
515impl EditPredictionPreview {
516 pub fn released_too_fast(&self) -> bool {
517 match self {
518 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
519 EditPredictionPreview::Active { .. } => false,
520 }
521 }
522
523 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
524 if let EditPredictionPreview::Active {
525 previous_scroll_position,
526 ..
527 } = self
528 {
529 *previous_scroll_position = scroll_position;
530 }
531 }
532}
533
534pub struct ContextMenuOptions {
535 pub min_entries_visible: usize,
536 pub max_entries_visible: usize,
537 pub placement: Option<ContextMenuPlacement>,
538}
539
540#[derive(Debug, Clone, PartialEq, Eq)]
541pub enum ContextMenuPlacement {
542 Above,
543 Below,
544}
545
546#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
547struct EditorActionId(usize);
548
549impl EditorActionId {
550 pub fn post_inc(&mut self) -> Self {
551 let answer = self.0;
552
553 *self = Self(answer + 1);
554
555 Self(answer)
556 }
557}
558
559// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
560// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
561
562type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
563type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
564
565#[derive(Default)]
566struct ScrollbarMarkerState {
567 scrollbar_size: Size<Pixels>,
568 dirty: bool,
569 markers: Arc<[PaintQuad]>,
570 pending_refresh: Option<Task<Result<()>>>,
571}
572
573impl ScrollbarMarkerState {
574 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
575 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
576 }
577}
578
579#[derive(Clone, Debug)]
580struct RunnableTasks {
581 templates: Vec<(TaskSourceKind, TaskTemplate)>,
582 offset: multi_buffer::Anchor,
583 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
584 column: u32,
585 // Values of all named captures, including those starting with '_'
586 extra_variables: HashMap<String, String>,
587 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
588 context_range: Range<BufferOffset>,
589}
590
591impl RunnableTasks {
592 fn resolve<'a>(
593 &'a self,
594 cx: &'a task::TaskContext,
595 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
596 self.templates.iter().filter_map(|(kind, template)| {
597 template
598 .resolve_task(&kind.to_id_base(), cx)
599 .map(|task| (kind.clone(), task))
600 })
601 }
602}
603
604#[derive(Clone)]
605struct ResolvedTasks {
606 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
607 position: Anchor,
608}
609
610#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
611struct BufferOffset(usize);
612
613// Addons allow storing per-editor state in other crates (e.g. Vim)
614pub trait Addon: 'static {
615 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
616
617 fn render_buffer_header_controls(
618 &self,
619 _: &ExcerptInfo,
620 _: &Window,
621 _: &App,
622 ) -> Option<AnyElement> {
623 None
624 }
625
626 fn to_any(&self) -> &dyn std::any::Any;
627}
628
629/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
630///
631/// See the [module level documentation](self) for more information.
632pub struct Editor {
633 focus_handle: FocusHandle,
634 last_focused_descendant: Option<WeakFocusHandle>,
635 /// The text buffer being edited
636 buffer: Entity<MultiBuffer>,
637 /// Map of how text in the buffer should be displayed.
638 /// Handles soft wraps, folds, fake inlay text insertions, etc.
639 pub display_map: Entity<DisplayMap>,
640 pub selections: SelectionsCollection,
641 pub scroll_manager: ScrollManager,
642 /// When inline assist editors are linked, they all render cursors because
643 /// typing enters text into each of them, even the ones that aren't focused.
644 pub(crate) show_cursor_when_unfocused: bool,
645 columnar_selection_tail: Option<Anchor>,
646 add_selections_state: Option<AddSelectionsState>,
647 select_next_state: Option<SelectNextState>,
648 select_prev_state: Option<SelectNextState>,
649 selection_history: SelectionHistory,
650 autoclose_regions: Vec<AutocloseRegion>,
651 snippet_stack: InvalidationStack<SnippetState>,
652 select_syntax_node_history: SelectSyntaxNodeHistory,
653 ime_transaction: Option<TransactionId>,
654 active_diagnostics: Option<ActiveDiagnosticGroup>,
655 show_inline_diagnostics: bool,
656 inline_diagnostics_update: Task<()>,
657 inline_diagnostics_enabled: bool,
658 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
659 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
660 hard_wrap: Option<usize>,
661
662 // TODO: make this a access method
663 pub project: Option<Entity<Project>>,
664 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
665 completion_provider: Option<Box<dyn CompletionProvider>>,
666 collaboration_hub: Option<Box<dyn CollaborationHub>>,
667 blink_manager: Entity<BlinkManager>,
668 show_cursor_names: bool,
669 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
670 pub show_local_selections: bool,
671 mode: EditorMode,
672 show_breadcrumbs: bool,
673 show_gutter: bool,
674 show_scrollbars: bool,
675 show_line_numbers: Option<bool>,
676 use_relative_line_numbers: Option<bool>,
677 show_git_diff_gutter: Option<bool>,
678 show_code_actions: Option<bool>,
679 show_runnables: Option<bool>,
680 show_breakpoints: Option<bool>,
681 show_wrap_guides: Option<bool>,
682 show_indent_guides: Option<bool>,
683 placeholder_text: Option<Arc<str>>,
684 highlight_order: usize,
685 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
686 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
687 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
688 scrollbar_marker_state: ScrollbarMarkerState,
689 active_indent_guides_state: ActiveIndentGuidesState,
690 nav_history: Option<ItemNavHistory>,
691 context_menu: RefCell<Option<CodeContextMenu>>,
692 context_menu_options: Option<ContextMenuOptions>,
693 mouse_context_menu: Option<MouseContextMenu>,
694 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
695 signature_help_state: SignatureHelpState,
696 auto_signature_help: Option<bool>,
697 find_all_references_task_sources: Vec<Anchor>,
698 next_completion_id: CompletionId,
699 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
700 code_actions_task: Option<Task<Result<()>>>,
701 selection_highlight_task: Option<Task<()>>,
702 document_highlights_task: Option<Task<()>>,
703 linked_editing_range_task: Option<Task<Option<()>>>,
704 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
705 pending_rename: Option<RenameState>,
706 searchable: bool,
707 cursor_shape: CursorShape,
708 current_line_highlight: Option<CurrentLineHighlight>,
709 collapse_matches: bool,
710 autoindent_mode: Option<AutoindentMode>,
711 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
712 input_enabled: bool,
713 use_modal_editing: bool,
714 read_only: bool,
715 leader_peer_id: Option<PeerId>,
716 remote_id: Option<ViewId>,
717 hover_state: HoverState,
718 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
719 gutter_hovered: bool,
720 hovered_link_state: Option<HoveredLinkState>,
721 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
722 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
723 active_inline_completion: Option<InlineCompletionState>,
724 /// Used to prevent flickering as the user types while the menu is open
725 stale_inline_completion_in_menu: Option<InlineCompletionState>,
726 edit_prediction_settings: EditPredictionSettings,
727 inline_completions_hidden_for_vim_mode: bool,
728 show_inline_completions_override: Option<bool>,
729 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
730 edit_prediction_preview: EditPredictionPreview,
731 edit_prediction_indent_conflict: bool,
732 edit_prediction_requires_modifier_in_indent_conflict: bool,
733 inlay_hint_cache: InlayHintCache,
734 next_inlay_id: usize,
735 _subscriptions: Vec<Subscription>,
736 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
737 gutter_dimensions: GutterDimensions,
738 style: Option<EditorStyle>,
739 text_style_refinement: Option<TextStyleRefinement>,
740 next_editor_action_id: EditorActionId,
741 editor_actions:
742 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
743 use_autoclose: bool,
744 use_auto_surround: bool,
745 auto_replace_emoji_shortcode: bool,
746 jsx_tag_auto_close_enabled_in_any_buffer: bool,
747 show_git_blame_gutter: bool,
748 show_git_blame_inline: bool,
749 show_git_blame_inline_delay_task: Option<Task<()>>,
750 git_blame_inline_tooltip: Option<WeakEntity<crate::commit_tooltip::CommitTooltip>>,
751 git_blame_inline_enabled: bool,
752 serialize_dirty_buffers: bool,
753 show_selection_menu: Option<bool>,
754 blame: Option<Entity<GitBlame>>,
755 blame_subscription: Option<Subscription>,
756 custom_context_menu: Option<
757 Box<
758 dyn 'static
759 + Fn(
760 &mut Self,
761 DisplayPoint,
762 &mut Window,
763 &mut Context<Self>,
764 ) -> Option<Entity<ui::ContextMenu>>,
765 >,
766 >,
767 last_bounds: Option<Bounds<Pixels>>,
768 last_position_map: Option<Rc<PositionMap>>,
769 expect_bounds_change: Option<Bounds<Pixels>>,
770 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
771 tasks_update_task: Option<Task<()>>,
772 pub breakpoint_store: Option<Entity<BreakpointStore>>,
773 /// Allow's a user to create a breakpoint by selecting this indicator
774 /// It should be None while a user is not hovering over the gutter
775 /// Otherwise it represents the point that the breakpoint will be shown
776 pub gutter_breakpoint_indicator: Option<DisplayPoint>,
777 in_project_search: bool,
778 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
779 breadcrumb_header: Option<String>,
780 focused_block: Option<FocusedBlock>,
781 next_scroll_position: NextScrollCursorCenterTopBottom,
782 addons: HashMap<TypeId, Box<dyn Addon>>,
783 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
784 load_diff_task: Option<Shared<Task<()>>>,
785 selection_mark_mode: bool,
786 toggle_fold_multiple_buffers: Task<()>,
787 _scroll_cursor_center_top_bottom_task: Task<()>,
788 serialize_selections: Task<()>,
789 serialize_folds: Task<()>,
790}
791
792#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
793enum NextScrollCursorCenterTopBottom {
794 #[default]
795 Center,
796 Top,
797 Bottom,
798}
799
800impl NextScrollCursorCenterTopBottom {
801 fn next(&self) -> Self {
802 match self {
803 Self::Center => Self::Top,
804 Self::Top => Self::Bottom,
805 Self::Bottom => Self::Center,
806 }
807 }
808}
809
810#[derive(Clone)]
811pub struct EditorSnapshot {
812 pub mode: EditorMode,
813 show_gutter: bool,
814 show_line_numbers: Option<bool>,
815 show_git_diff_gutter: Option<bool>,
816 show_code_actions: Option<bool>,
817 show_runnables: Option<bool>,
818 show_breakpoints: Option<bool>,
819 git_blame_gutter_max_author_length: Option<usize>,
820 pub display_snapshot: DisplaySnapshot,
821 pub placeholder_text: Option<Arc<str>>,
822 is_focused: bool,
823 scroll_anchor: ScrollAnchor,
824 ongoing_scroll: OngoingScroll,
825 current_line_highlight: CurrentLineHighlight,
826 gutter_hovered: bool,
827}
828
829const GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED: usize = 20;
830
831#[derive(Default, Debug, Clone, Copy)]
832pub struct GutterDimensions {
833 pub left_padding: Pixels,
834 pub right_padding: Pixels,
835 pub width: Pixels,
836 pub margin: Pixels,
837 pub git_blame_entries_width: Option<Pixels>,
838}
839
840impl GutterDimensions {
841 /// The full width of the space taken up by the gutter.
842 pub fn full_width(&self) -> Pixels {
843 self.margin + self.width
844 }
845
846 /// The width of the space reserved for the fold indicators,
847 /// use alongside 'justify_end' and `gutter_width` to
848 /// right align content with the line numbers
849 pub fn fold_area_width(&self) -> Pixels {
850 self.margin + self.right_padding
851 }
852}
853
854#[derive(Debug)]
855pub struct RemoteSelection {
856 pub replica_id: ReplicaId,
857 pub selection: Selection<Anchor>,
858 pub cursor_shape: CursorShape,
859 pub peer_id: PeerId,
860 pub line_mode: bool,
861 pub participant_index: Option<ParticipantIndex>,
862 pub user_name: Option<SharedString>,
863}
864
865#[derive(Clone, Debug)]
866struct SelectionHistoryEntry {
867 selections: Arc<[Selection<Anchor>]>,
868 select_next_state: Option<SelectNextState>,
869 select_prev_state: Option<SelectNextState>,
870 add_selections_state: Option<AddSelectionsState>,
871}
872
873enum SelectionHistoryMode {
874 Normal,
875 Undoing,
876 Redoing,
877}
878
879#[derive(Clone, PartialEq, Eq, Hash)]
880struct HoveredCursor {
881 replica_id: u16,
882 selection_id: usize,
883}
884
885impl Default for SelectionHistoryMode {
886 fn default() -> Self {
887 Self::Normal
888 }
889}
890
891#[derive(Default)]
892struct SelectionHistory {
893 #[allow(clippy::type_complexity)]
894 selections_by_transaction:
895 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
896 mode: SelectionHistoryMode,
897 undo_stack: VecDeque<SelectionHistoryEntry>,
898 redo_stack: VecDeque<SelectionHistoryEntry>,
899}
900
901impl SelectionHistory {
902 fn insert_transaction(
903 &mut self,
904 transaction_id: TransactionId,
905 selections: Arc<[Selection<Anchor>]>,
906 ) {
907 self.selections_by_transaction
908 .insert(transaction_id, (selections, None));
909 }
910
911 #[allow(clippy::type_complexity)]
912 fn transaction(
913 &self,
914 transaction_id: TransactionId,
915 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
916 self.selections_by_transaction.get(&transaction_id)
917 }
918
919 #[allow(clippy::type_complexity)]
920 fn transaction_mut(
921 &mut self,
922 transaction_id: TransactionId,
923 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
924 self.selections_by_transaction.get_mut(&transaction_id)
925 }
926
927 fn push(&mut self, entry: SelectionHistoryEntry) {
928 if !entry.selections.is_empty() {
929 match self.mode {
930 SelectionHistoryMode::Normal => {
931 self.push_undo(entry);
932 self.redo_stack.clear();
933 }
934 SelectionHistoryMode::Undoing => self.push_redo(entry),
935 SelectionHistoryMode::Redoing => self.push_undo(entry),
936 }
937 }
938 }
939
940 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
941 if self
942 .undo_stack
943 .back()
944 .map_or(true, |e| e.selections != entry.selections)
945 {
946 self.undo_stack.push_back(entry);
947 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
948 self.undo_stack.pop_front();
949 }
950 }
951 }
952
953 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
954 if self
955 .redo_stack
956 .back()
957 .map_or(true, |e| e.selections != entry.selections)
958 {
959 self.redo_stack.push_back(entry);
960 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
961 self.redo_stack.pop_front();
962 }
963 }
964 }
965}
966
967struct RowHighlight {
968 index: usize,
969 range: Range<Anchor>,
970 color: Hsla,
971 should_autoscroll: bool,
972}
973
974#[derive(Clone, Debug)]
975struct AddSelectionsState {
976 above: bool,
977 stack: Vec<usize>,
978}
979
980#[derive(Clone)]
981struct SelectNextState {
982 query: AhoCorasick,
983 wordwise: bool,
984 done: bool,
985}
986
987impl std::fmt::Debug for SelectNextState {
988 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
989 f.debug_struct(std::any::type_name::<Self>())
990 .field("wordwise", &self.wordwise)
991 .field("done", &self.done)
992 .finish()
993 }
994}
995
996#[derive(Debug)]
997struct AutocloseRegion {
998 selection_id: usize,
999 range: Range<Anchor>,
1000 pair: BracketPair,
1001}
1002
1003#[derive(Debug)]
1004struct SnippetState {
1005 ranges: Vec<Vec<Range<Anchor>>>,
1006 active_index: usize,
1007 choices: Vec<Option<Vec<String>>>,
1008}
1009
1010#[doc(hidden)]
1011pub struct RenameState {
1012 pub range: Range<Anchor>,
1013 pub old_name: Arc<str>,
1014 pub editor: Entity<Editor>,
1015 block_id: CustomBlockId,
1016}
1017
1018struct InvalidationStack<T>(Vec<T>);
1019
1020struct RegisteredInlineCompletionProvider {
1021 provider: Arc<dyn InlineCompletionProviderHandle>,
1022 _subscription: Subscription,
1023}
1024
1025#[derive(Debug, PartialEq, Eq)]
1026struct ActiveDiagnosticGroup {
1027 primary_range: Range<Anchor>,
1028 primary_message: String,
1029 group_id: usize,
1030 blocks: HashMap<CustomBlockId, Diagnostic>,
1031 is_valid: bool,
1032}
1033
1034#[derive(Serialize, Deserialize, Clone, Debug)]
1035pub struct ClipboardSelection {
1036 /// The number of bytes in this selection.
1037 pub len: usize,
1038 /// Whether this was a full-line selection.
1039 pub is_entire_line: bool,
1040 /// The indentation of the first line when this content was originally copied.
1041 pub first_line_indent: u32,
1042}
1043
1044// selections, scroll behavior, was newest selection reversed
1045type SelectSyntaxNodeHistoryState = (
1046 Box<[Selection<usize>]>,
1047 SelectSyntaxNodeScrollBehavior,
1048 bool,
1049);
1050
1051#[derive(Default)]
1052struct SelectSyntaxNodeHistory {
1053 stack: Vec<SelectSyntaxNodeHistoryState>,
1054 // disable temporarily to allow changing selections without losing the stack
1055 pub disable_clearing: bool,
1056}
1057
1058impl SelectSyntaxNodeHistory {
1059 pub fn try_clear(&mut self) {
1060 if !self.disable_clearing {
1061 self.stack.clear();
1062 }
1063 }
1064
1065 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1066 self.stack.push(selection);
1067 }
1068
1069 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1070 self.stack.pop()
1071 }
1072}
1073
1074enum SelectSyntaxNodeScrollBehavior {
1075 CursorTop,
1076 CenterSelection,
1077 CursorBottom,
1078}
1079
1080#[derive(Debug)]
1081pub(crate) struct NavigationData {
1082 cursor_anchor: Anchor,
1083 cursor_position: Point,
1084 scroll_anchor: ScrollAnchor,
1085 scroll_top_row: u32,
1086}
1087
1088#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1089pub enum GotoDefinitionKind {
1090 Symbol,
1091 Declaration,
1092 Type,
1093 Implementation,
1094}
1095
1096#[derive(Debug, Clone)]
1097enum InlayHintRefreshReason {
1098 ModifiersChanged(bool),
1099 Toggle(bool),
1100 SettingsChange(InlayHintSettings),
1101 NewLinesShown,
1102 BufferEdited(HashSet<Arc<Language>>),
1103 RefreshRequested,
1104 ExcerptsRemoved(Vec<ExcerptId>),
1105}
1106
1107impl InlayHintRefreshReason {
1108 fn description(&self) -> &'static str {
1109 match self {
1110 Self::ModifiersChanged(_) => "modifiers changed",
1111 Self::Toggle(_) => "toggle",
1112 Self::SettingsChange(_) => "settings change",
1113 Self::NewLinesShown => "new lines shown",
1114 Self::BufferEdited(_) => "buffer edited",
1115 Self::RefreshRequested => "refresh requested",
1116 Self::ExcerptsRemoved(_) => "excerpts removed",
1117 }
1118 }
1119}
1120
1121pub enum FormatTarget {
1122 Buffers,
1123 Ranges(Vec<Range<MultiBufferPoint>>),
1124}
1125
1126pub(crate) struct FocusedBlock {
1127 id: BlockId,
1128 focus_handle: WeakFocusHandle,
1129}
1130
1131#[derive(Clone)]
1132enum JumpData {
1133 MultiBufferRow {
1134 row: MultiBufferRow,
1135 line_offset_from_top: u32,
1136 },
1137 MultiBufferPoint {
1138 excerpt_id: ExcerptId,
1139 position: Point,
1140 anchor: text::Anchor,
1141 line_offset_from_top: u32,
1142 },
1143}
1144
1145pub enum MultibufferSelectionMode {
1146 First,
1147 All,
1148}
1149
1150#[derive(Clone, Copy, Debug, Default)]
1151pub struct RewrapOptions {
1152 pub override_language_settings: bool,
1153 pub preserve_existing_whitespace: bool,
1154}
1155
1156impl Editor {
1157 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1158 let buffer = cx.new(|cx| Buffer::local("", cx));
1159 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1160 Self::new(
1161 EditorMode::SingleLine { auto_width: false },
1162 buffer,
1163 None,
1164 window,
1165 cx,
1166 )
1167 }
1168
1169 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1170 let buffer = cx.new(|cx| Buffer::local("", cx));
1171 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1172 Self::new(EditorMode::Full, buffer, None, window, cx)
1173 }
1174
1175 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1176 let buffer = cx.new(|cx| Buffer::local("", cx));
1177 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1178 Self::new(
1179 EditorMode::SingleLine { auto_width: true },
1180 buffer,
1181 None,
1182 window,
1183 cx,
1184 )
1185 }
1186
1187 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1188 let buffer = cx.new(|cx| Buffer::local("", cx));
1189 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1190 Self::new(
1191 EditorMode::AutoHeight { max_lines },
1192 buffer,
1193 None,
1194 window,
1195 cx,
1196 )
1197 }
1198
1199 pub fn for_buffer(
1200 buffer: Entity<Buffer>,
1201 project: Option<Entity<Project>>,
1202 window: &mut Window,
1203 cx: &mut Context<Self>,
1204 ) -> Self {
1205 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1206 Self::new(EditorMode::Full, buffer, project, window, cx)
1207 }
1208
1209 pub fn for_multibuffer(
1210 buffer: Entity<MultiBuffer>,
1211 project: Option<Entity<Project>>,
1212 window: &mut Window,
1213 cx: &mut Context<Self>,
1214 ) -> Self {
1215 Self::new(EditorMode::Full, buffer, project, window, cx)
1216 }
1217
1218 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1219 let mut clone = Self::new(
1220 self.mode,
1221 self.buffer.clone(),
1222 self.project.clone(),
1223 window,
1224 cx,
1225 );
1226 self.display_map.update(cx, |display_map, cx| {
1227 let snapshot = display_map.snapshot(cx);
1228 clone.display_map.update(cx, |display_map, cx| {
1229 display_map.set_state(&snapshot, cx);
1230 });
1231 });
1232 clone.folds_did_change(cx);
1233 clone.selections.clone_state(&self.selections);
1234 clone.scroll_manager.clone_state(&self.scroll_manager);
1235 clone.searchable = self.searchable;
1236 clone
1237 }
1238
1239 pub fn new(
1240 mode: EditorMode,
1241 buffer: Entity<MultiBuffer>,
1242 project: Option<Entity<Project>>,
1243 window: &mut Window,
1244 cx: &mut Context<Self>,
1245 ) -> Self {
1246 let style = window.text_style();
1247 let font_size = style.font_size.to_pixels(window.rem_size());
1248 let editor = cx.entity().downgrade();
1249 let fold_placeholder = FoldPlaceholder {
1250 constrain_width: true,
1251 render: Arc::new(move |fold_id, fold_range, cx| {
1252 let editor = editor.clone();
1253 div()
1254 .id(fold_id)
1255 .bg(cx.theme().colors().ghost_element_background)
1256 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1257 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1258 .rounded_xs()
1259 .size_full()
1260 .cursor_pointer()
1261 .child("⋯")
1262 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1263 .on_click(move |_, _window, cx| {
1264 editor
1265 .update(cx, |editor, cx| {
1266 editor.unfold_ranges(
1267 &[fold_range.start..fold_range.end],
1268 true,
1269 false,
1270 cx,
1271 );
1272 cx.stop_propagation();
1273 })
1274 .ok();
1275 })
1276 .into_any()
1277 }),
1278 merge_adjacent: true,
1279 ..Default::default()
1280 };
1281 let display_map = cx.new(|cx| {
1282 DisplayMap::new(
1283 buffer.clone(),
1284 style.font(),
1285 font_size,
1286 None,
1287 FILE_HEADER_HEIGHT,
1288 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1289 fold_placeholder,
1290 cx,
1291 )
1292 });
1293
1294 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1295
1296 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1297
1298 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1299 .then(|| language_settings::SoftWrap::None);
1300
1301 let mut project_subscriptions = Vec::new();
1302 if mode == EditorMode::Full {
1303 if let Some(project) = project.as_ref() {
1304 project_subscriptions.push(cx.subscribe_in(
1305 project,
1306 window,
1307 |editor, _, event, window, cx| match event {
1308 project::Event::RefreshCodeLens => {
1309 // we always query lens with actions, without storing them, always refreshing them
1310 }
1311 project::Event::RefreshInlayHints => {
1312 editor
1313 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1314 }
1315 project::Event::SnippetEdit(id, snippet_edits) => {
1316 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1317 let focus_handle = editor.focus_handle(cx);
1318 if focus_handle.is_focused(window) {
1319 let snapshot = buffer.read(cx).snapshot();
1320 for (range, snippet) in snippet_edits {
1321 let editor_range =
1322 language::range_from_lsp(*range).to_offset(&snapshot);
1323 editor
1324 .insert_snippet(
1325 &[editor_range],
1326 snippet.clone(),
1327 window,
1328 cx,
1329 )
1330 .ok();
1331 }
1332 }
1333 }
1334 }
1335 _ => {}
1336 },
1337 ));
1338 if let Some(task_inventory) = project
1339 .read(cx)
1340 .task_store()
1341 .read(cx)
1342 .task_inventory()
1343 .cloned()
1344 {
1345 project_subscriptions.push(cx.observe_in(
1346 &task_inventory,
1347 window,
1348 |editor, _, window, cx| {
1349 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1350 },
1351 ));
1352 };
1353
1354 project_subscriptions.push(cx.subscribe_in(
1355 &project.read(cx).breakpoint_store(),
1356 window,
1357 |editor, _, event, window, cx| match event {
1358 BreakpointStoreEvent::ActiveDebugLineChanged => {
1359 editor.go_to_active_debug_line(window, cx);
1360 }
1361 _ => {}
1362 },
1363 ));
1364 }
1365 }
1366
1367 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1368
1369 let inlay_hint_settings =
1370 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1371 let focus_handle = cx.focus_handle();
1372 cx.on_focus(&focus_handle, window, Self::handle_focus)
1373 .detach();
1374 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1375 .detach();
1376 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1377 .detach();
1378 cx.on_blur(&focus_handle, window, Self::handle_blur)
1379 .detach();
1380
1381 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1382 Some(false)
1383 } else {
1384 None
1385 };
1386
1387 let breakpoint_store = match (mode, project.as_ref()) {
1388 (EditorMode::Full, Some(project)) => Some(project.read(cx).breakpoint_store()),
1389 _ => None,
1390 };
1391
1392 let mut code_action_providers = Vec::new();
1393 let mut load_uncommitted_diff = None;
1394 if let Some(project) = project.clone() {
1395 load_uncommitted_diff = Some(
1396 get_uncommitted_diff_for_buffer(
1397 &project,
1398 buffer.read(cx).all_buffers(),
1399 buffer.clone(),
1400 cx,
1401 )
1402 .shared(),
1403 );
1404 code_action_providers.push(Rc::new(project) as Rc<_>);
1405 }
1406
1407 let mut this = Self {
1408 focus_handle,
1409 show_cursor_when_unfocused: false,
1410 last_focused_descendant: None,
1411 buffer: buffer.clone(),
1412 display_map: display_map.clone(),
1413 selections,
1414 scroll_manager: ScrollManager::new(cx),
1415 columnar_selection_tail: None,
1416 add_selections_state: None,
1417 select_next_state: None,
1418 select_prev_state: None,
1419 selection_history: Default::default(),
1420 autoclose_regions: Default::default(),
1421 snippet_stack: Default::default(),
1422 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1423 ime_transaction: Default::default(),
1424 active_diagnostics: None,
1425 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1426 inline_diagnostics_update: Task::ready(()),
1427 inline_diagnostics: Vec::new(),
1428 soft_wrap_mode_override,
1429 hard_wrap: None,
1430 completion_provider: project.clone().map(|project| Box::new(project) as _),
1431 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1432 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1433 project,
1434 blink_manager: blink_manager.clone(),
1435 show_local_selections: true,
1436 show_scrollbars: true,
1437 mode,
1438 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1439 show_gutter: mode == EditorMode::Full,
1440 show_line_numbers: None,
1441 use_relative_line_numbers: None,
1442 show_git_diff_gutter: None,
1443 show_code_actions: None,
1444 show_runnables: None,
1445 show_breakpoints: None,
1446 show_wrap_guides: None,
1447 show_indent_guides,
1448 placeholder_text: None,
1449 highlight_order: 0,
1450 highlighted_rows: HashMap::default(),
1451 background_highlights: Default::default(),
1452 gutter_highlights: TreeMap::default(),
1453 scrollbar_marker_state: ScrollbarMarkerState::default(),
1454 active_indent_guides_state: ActiveIndentGuidesState::default(),
1455 nav_history: None,
1456 context_menu: RefCell::new(None),
1457 context_menu_options: None,
1458 mouse_context_menu: None,
1459 completion_tasks: Default::default(),
1460 signature_help_state: SignatureHelpState::default(),
1461 auto_signature_help: None,
1462 find_all_references_task_sources: Vec::new(),
1463 next_completion_id: 0,
1464 next_inlay_id: 0,
1465 code_action_providers,
1466 available_code_actions: Default::default(),
1467 code_actions_task: Default::default(),
1468 selection_highlight_task: Default::default(),
1469 document_highlights_task: Default::default(),
1470 linked_editing_range_task: Default::default(),
1471 pending_rename: Default::default(),
1472 searchable: true,
1473 cursor_shape: EditorSettings::get_global(cx)
1474 .cursor_shape
1475 .unwrap_or_default(),
1476 current_line_highlight: None,
1477 autoindent_mode: Some(AutoindentMode::EachLine),
1478 collapse_matches: false,
1479 workspace: None,
1480 input_enabled: true,
1481 use_modal_editing: mode == EditorMode::Full,
1482 read_only: false,
1483 use_autoclose: true,
1484 use_auto_surround: true,
1485 auto_replace_emoji_shortcode: false,
1486 jsx_tag_auto_close_enabled_in_any_buffer: false,
1487 leader_peer_id: None,
1488 remote_id: None,
1489 hover_state: Default::default(),
1490 pending_mouse_down: None,
1491 hovered_link_state: Default::default(),
1492 edit_prediction_provider: None,
1493 active_inline_completion: None,
1494 stale_inline_completion_in_menu: None,
1495 edit_prediction_preview: EditPredictionPreview::Inactive {
1496 released_too_fast: false,
1497 },
1498 inline_diagnostics_enabled: mode == EditorMode::Full,
1499 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1500
1501 gutter_hovered: false,
1502 pixel_position_of_newest_cursor: None,
1503 last_bounds: None,
1504 last_position_map: None,
1505 expect_bounds_change: None,
1506 gutter_dimensions: GutterDimensions::default(),
1507 style: None,
1508 show_cursor_names: false,
1509 hovered_cursors: Default::default(),
1510 next_editor_action_id: EditorActionId::default(),
1511 editor_actions: Rc::default(),
1512 inline_completions_hidden_for_vim_mode: false,
1513 show_inline_completions_override: None,
1514 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1515 edit_prediction_settings: EditPredictionSettings::Disabled,
1516 edit_prediction_indent_conflict: false,
1517 edit_prediction_requires_modifier_in_indent_conflict: true,
1518 custom_context_menu: None,
1519 show_git_blame_gutter: false,
1520 show_git_blame_inline: false,
1521 show_selection_menu: None,
1522 show_git_blame_inline_delay_task: None,
1523 git_blame_inline_tooltip: None,
1524 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1525 serialize_dirty_buffers: ProjectSettings::get_global(cx)
1526 .session
1527 .restore_unsaved_buffers,
1528 blame: None,
1529 blame_subscription: None,
1530 tasks: Default::default(),
1531
1532 breakpoint_store,
1533 gutter_breakpoint_indicator: None,
1534 _subscriptions: vec![
1535 cx.observe(&buffer, Self::on_buffer_changed),
1536 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1537 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1538 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1539 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1540 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1541 cx.observe_window_activation(window, |editor, window, cx| {
1542 let active = window.is_window_active();
1543 editor.blink_manager.update(cx, |blink_manager, cx| {
1544 if active {
1545 blink_manager.enable(cx);
1546 } else {
1547 blink_manager.disable(cx);
1548 }
1549 });
1550 }),
1551 ],
1552 tasks_update_task: None,
1553 linked_edit_ranges: Default::default(),
1554 in_project_search: false,
1555 previous_search_ranges: None,
1556 breadcrumb_header: None,
1557 focused_block: None,
1558 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1559 addons: HashMap::default(),
1560 registered_buffers: HashMap::default(),
1561 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1562 selection_mark_mode: false,
1563 toggle_fold_multiple_buffers: Task::ready(()),
1564 serialize_selections: Task::ready(()),
1565 serialize_folds: Task::ready(()),
1566 text_style_refinement: None,
1567 load_diff_task: load_uncommitted_diff,
1568 };
1569 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1570 this._subscriptions
1571 .push(cx.observe(breakpoints, |_, _, cx| {
1572 cx.notify();
1573 }));
1574 }
1575 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1576 this._subscriptions.extend(project_subscriptions);
1577
1578 this.end_selection(window, cx);
1579 this.scroll_manager.show_scrollbar(window, cx);
1580 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1581
1582 if mode == EditorMode::Full {
1583 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1584 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1585
1586 if this.git_blame_inline_enabled {
1587 this.git_blame_inline_enabled = true;
1588 this.start_git_blame_inline(false, window, cx);
1589 }
1590
1591 this.go_to_active_debug_line(window, cx);
1592
1593 if let Some(buffer) = buffer.read(cx).as_singleton() {
1594 if let Some(project) = this.project.as_ref() {
1595 let handle = project.update(cx, |project, cx| {
1596 project.register_buffer_with_language_servers(&buffer, cx)
1597 });
1598 this.registered_buffers
1599 .insert(buffer.read(cx).remote_id(), handle);
1600 }
1601 }
1602 }
1603
1604 this.report_editor_event("Editor Opened", None, cx);
1605 this
1606 }
1607
1608 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
1609 self.mouse_context_menu
1610 .as_ref()
1611 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
1612 }
1613
1614 fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
1615 self.key_context_internal(self.has_active_inline_completion(), window, cx)
1616 }
1617
1618 fn key_context_internal(
1619 &self,
1620 has_active_edit_prediction: bool,
1621 window: &Window,
1622 cx: &App,
1623 ) -> KeyContext {
1624 let mut key_context = KeyContext::new_with_defaults();
1625 key_context.add("Editor");
1626 let mode = match self.mode {
1627 EditorMode::SingleLine { .. } => "single_line",
1628 EditorMode::AutoHeight { .. } => "auto_height",
1629 EditorMode::Full => "full",
1630 };
1631
1632 if EditorSettings::jupyter_enabled(cx) {
1633 key_context.add("jupyter");
1634 }
1635
1636 key_context.set("mode", mode);
1637 if self.pending_rename.is_some() {
1638 key_context.add("renaming");
1639 }
1640
1641 match self.context_menu.borrow().as_ref() {
1642 Some(CodeContextMenu::Completions(_)) => {
1643 key_context.add("menu");
1644 key_context.add("showing_completions");
1645 }
1646 Some(CodeContextMenu::CodeActions(_)) => {
1647 key_context.add("menu");
1648 key_context.add("showing_code_actions")
1649 }
1650 None => {}
1651 }
1652
1653 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
1654 if !self.focus_handle(cx).contains_focused(window, cx)
1655 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
1656 {
1657 for addon in self.addons.values() {
1658 addon.extend_key_context(&mut key_context, cx)
1659 }
1660 }
1661
1662 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
1663 if let Some(extension) = singleton_buffer
1664 .read(cx)
1665 .file()
1666 .and_then(|file| file.path().extension()?.to_str())
1667 {
1668 key_context.set("extension", extension.to_string());
1669 }
1670 } else {
1671 key_context.add("multibuffer");
1672 }
1673
1674 if has_active_edit_prediction {
1675 if self.edit_prediction_in_conflict() {
1676 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
1677 } else {
1678 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
1679 key_context.add("copilot_suggestion");
1680 }
1681 }
1682
1683 if self.selection_mark_mode {
1684 key_context.add("selection_mode");
1685 }
1686
1687 key_context
1688 }
1689
1690 pub fn edit_prediction_in_conflict(&self) -> bool {
1691 if !self.show_edit_predictions_in_menu() {
1692 return false;
1693 }
1694
1695 let showing_completions = self
1696 .context_menu
1697 .borrow()
1698 .as_ref()
1699 .map_or(false, |context| {
1700 matches!(context, CodeContextMenu::Completions(_))
1701 });
1702
1703 showing_completions
1704 || self.edit_prediction_requires_modifier()
1705 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
1706 // bindings to insert tab characters.
1707 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
1708 }
1709
1710 pub fn accept_edit_prediction_keybind(
1711 &self,
1712 window: &Window,
1713 cx: &App,
1714 ) -> AcceptEditPredictionBinding {
1715 let key_context = self.key_context_internal(true, window, cx);
1716 let in_conflict = self.edit_prediction_in_conflict();
1717
1718 AcceptEditPredictionBinding(
1719 window
1720 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
1721 .into_iter()
1722 .filter(|binding| {
1723 !in_conflict
1724 || binding
1725 .keystrokes()
1726 .first()
1727 .map_or(false, |keystroke| keystroke.modifiers.modified())
1728 })
1729 .rev()
1730 .min_by_key(|binding| {
1731 binding
1732 .keystrokes()
1733 .first()
1734 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
1735 }),
1736 )
1737 }
1738
1739 pub fn new_file(
1740 workspace: &mut Workspace,
1741 _: &workspace::NewFile,
1742 window: &mut Window,
1743 cx: &mut Context<Workspace>,
1744 ) {
1745 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
1746 "Failed to create buffer",
1747 window,
1748 cx,
1749 |e, _, _| match e.error_code() {
1750 ErrorCode::RemoteUpgradeRequired => Some(format!(
1751 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1752 e.error_tag("required").unwrap_or("the latest version")
1753 )),
1754 _ => None,
1755 },
1756 );
1757 }
1758
1759 pub fn new_in_workspace(
1760 workspace: &mut Workspace,
1761 window: &mut Window,
1762 cx: &mut Context<Workspace>,
1763 ) -> Task<Result<Entity<Editor>>> {
1764 let project = workspace.project().clone();
1765 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1766
1767 cx.spawn_in(window, async move |workspace, cx| {
1768 let buffer = create.await?;
1769 workspace.update_in(cx, |workspace, window, cx| {
1770 let editor =
1771 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
1772 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
1773 editor
1774 })
1775 })
1776 }
1777
1778 fn new_file_vertical(
1779 workspace: &mut Workspace,
1780 _: &workspace::NewFileSplitVertical,
1781 window: &mut Window,
1782 cx: &mut Context<Workspace>,
1783 ) {
1784 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
1785 }
1786
1787 fn new_file_horizontal(
1788 workspace: &mut Workspace,
1789 _: &workspace::NewFileSplitHorizontal,
1790 window: &mut Window,
1791 cx: &mut Context<Workspace>,
1792 ) {
1793 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
1794 }
1795
1796 fn new_file_in_direction(
1797 workspace: &mut Workspace,
1798 direction: SplitDirection,
1799 window: &mut Window,
1800 cx: &mut Context<Workspace>,
1801 ) {
1802 let project = workspace.project().clone();
1803 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1804
1805 cx.spawn_in(window, async move |workspace, cx| {
1806 let buffer = create.await?;
1807 workspace.update_in(cx, move |workspace, window, cx| {
1808 workspace.split_item(
1809 direction,
1810 Box::new(
1811 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
1812 ),
1813 window,
1814 cx,
1815 )
1816 })?;
1817 anyhow::Ok(())
1818 })
1819 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
1820 match e.error_code() {
1821 ErrorCode::RemoteUpgradeRequired => Some(format!(
1822 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1823 e.error_tag("required").unwrap_or("the latest version")
1824 )),
1825 _ => None,
1826 }
1827 });
1828 }
1829
1830 pub fn leader_peer_id(&self) -> Option<PeerId> {
1831 self.leader_peer_id
1832 }
1833
1834 pub fn buffer(&self) -> &Entity<MultiBuffer> {
1835 &self.buffer
1836 }
1837
1838 pub fn workspace(&self) -> Option<Entity<Workspace>> {
1839 self.workspace.as_ref()?.0.upgrade()
1840 }
1841
1842 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
1843 self.buffer().read(cx).title(cx)
1844 }
1845
1846 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
1847 let git_blame_gutter_max_author_length = self
1848 .render_git_blame_gutter(cx)
1849 .then(|| {
1850 if let Some(blame) = self.blame.as_ref() {
1851 let max_author_length =
1852 blame.update(cx, |blame, cx| blame.max_author_length(cx));
1853 Some(max_author_length)
1854 } else {
1855 None
1856 }
1857 })
1858 .flatten();
1859
1860 EditorSnapshot {
1861 mode: self.mode,
1862 show_gutter: self.show_gutter,
1863 show_line_numbers: self.show_line_numbers,
1864 show_git_diff_gutter: self.show_git_diff_gutter,
1865 show_code_actions: self.show_code_actions,
1866 show_runnables: self.show_runnables,
1867 show_breakpoints: self.show_breakpoints,
1868 git_blame_gutter_max_author_length,
1869 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
1870 scroll_anchor: self.scroll_manager.anchor(),
1871 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
1872 placeholder_text: self.placeholder_text.clone(),
1873 is_focused: self.focus_handle.is_focused(window),
1874 current_line_highlight: self
1875 .current_line_highlight
1876 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
1877 gutter_hovered: self.gutter_hovered,
1878 }
1879 }
1880
1881 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
1882 self.buffer.read(cx).language_at(point, cx)
1883 }
1884
1885 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
1886 self.buffer.read(cx).read(cx).file_at(point).cloned()
1887 }
1888
1889 pub fn active_excerpt(
1890 &self,
1891 cx: &App,
1892 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
1893 self.buffer
1894 .read(cx)
1895 .excerpt_containing(self.selections.newest_anchor().head(), cx)
1896 }
1897
1898 pub fn mode(&self) -> EditorMode {
1899 self.mode
1900 }
1901
1902 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
1903 self.collaboration_hub.as_deref()
1904 }
1905
1906 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
1907 self.collaboration_hub = Some(hub);
1908 }
1909
1910 pub fn set_in_project_search(&mut self, in_project_search: bool) {
1911 self.in_project_search = in_project_search;
1912 }
1913
1914 pub fn set_custom_context_menu(
1915 &mut self,
1916 f: impl 'static
1917 + Fn(
1918 &mut Self,
1919 DisplayPoint,
1920 &mut Window,
1921 &mut Context<Self>,
1922 ) -> Option<Entity<ui::ContextMenu>>,
1923 ) {
1924 self.custom_context_menu = Some(Box::new(f))
1925 }
1926
1927 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
1928 self.completion_provider = provider;
1929 }
1930
1931 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
1932 self.semantics_provider.clone()
1933 }
1934
1935 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
1936 self.semantics_provider = provider;
1937 }
1938
1939 pub fn set_edit_prediction_provider<T>(
1940 &mut self,
1941 provider: Option<Entity<T>>,
1942 window: &mut Window,
1943 cx: &mut Context<Self>,
1944 ) where
1945 T: EditPredictionProvider,
1946 {
1947 self.edit_prediction_provider =
1948 provider.map(|provider| RegisteredInlineCompletionProvider {
1949 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
1950 if this.focus_handle.is_focused(window) {
1951 this.update_visible_inline_completion(window, cx);
1952 }
1953 }),
1954 provider: Arc::new(provider),
1955 });
1956 self.update_edit_prediction_settings(cx);
1957 self.refresh_inline_completion(false, false, window, cx);
1958 }
1959
1960 pub fn placeholder_text(&self) -> Option<&str> {
1961 self.placeholder_text.as_deref()
1962 }
1963
1964 pub fn set_placeholder_text(
1965 &mut self,
1966 placeholder_text: impl Into<Arc<str>>,
1967 cx: &mut Context<Self>,
1968 ) {
1969 let placeholder_text = Some(placeholder_text.into());
1970 if self.placeholder_text != placeholder_text {
1971 self.placeholder_text = placeholder_text;
1972 cx.notify();
1973 }
1974 }
1975
1976 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
1977 self.cursor_shape = cursor_shape;
1978
1979 // Disrupt blink for immediate user feedback that the cursor shape has changed
1980 self.blink_manager.update(cx, BlinkManager::show_cursor);
1981
1982 cx.notify();
1983 }
1984
1985 pub fn set_current_line_highlight(
1986 &mut self,
1987 current_line_highlight: Option<CurrentLineHighlight>,
1988 ) {
1989 self.current_line_highlight = current_line_highlight;
1990 }
1991
1992 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
1993 self.collapse_matches = collapse_matches;
1994 }
1995
1996 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
1997 let buffers = self.buffer.read(cx).all_buffers();
1998 let Some(project) = self.project.as_ref() else {
1999 return;
2000 };
2001 project.update(cx, |project, cx| {
2002 for buffer in buffers {
2003 self.registered_buffers
2004 .entry(buffer.read(cx).remote_id())
2005 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2006 }
2007 })
2008 }
2009
2010 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2011 if self.collapse_matches {
2012 return range.start..range.start;
2013 }
2014 range.clone()
2015 }
2016
2017 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2018 if self.display_map.read(cx).clip_at_line_ends != clip {
2019 self.display_map
2020 .update(cx, |map, _| map.clip_at_line_ends = clip);
2021 }
2022 }
2023
2024 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2025 self.input_enabled = input_enabled;
2026 }
2027
2028 pub fn set_inline_completions_hidden_for_vim_mode(
2029 &mut self,
2030 hidden: bool,
2031 window: &mut Window,
2032 cx: &mut Context<Self>,
2033 ) {
2034 if hidden != self.inline_completions_hidden_for_vim_mode {
2035 self.inline_completions_hidden_for_vim_mode = hidden;
2036 if hidden {
2037 self.update_visible_inline_completion(window, cx);
2038 } else {
2039 self.refresh_inline_completion(true, false, window, cx);
2040 }
2041 }
2042 }
2043
2044 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2045 self.menu_inline_completions_policy = value;
2046 }
2047
2048 pub fn set_autoindent(&mut self, autoindent: bool) {
2049 if autoindent {
2050 self.autoindent_mode = Some(AutoindentMode::EachLine);
2051 } else {
2052 self.autoindent_mode = None;
2053 }
2054 }
2055
2056 pub fn read_only(&self, cx: &App) -> bool {
2057 self.read_only || self.buffer.read(cx).read_only()
2058 }
2059
2060 pub fn set_read_only(&mut self, read_only: bool) {
2061 self.read_only = read_only;
2062 }
2063
2064 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2065 self.use_autoclose = autoclose;
2066 }
2067
2068 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2069 self.use_auto_surround = auto_surround;
2070 }
2071
2072 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2073 self.auto_replace_emoji_shortcode = auto_replace;
2074 }
2075
2076 pub fn toggle_edit_predictions(
2077 &mut self,
2078 _: &ToggleEditPrediction,
2079 window: &mut Window,
2080 cx: &mut Context<Self>,
2081 ) {
2082 if self.show_inline_completions_override.is_some() {
2083 self.set_show_edit_predictions(None, window, cx);
2084 } else {
2085 let show_edit_predictions = !self.edit_predictions_enabled();
2086 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2087 }
2088 }
2089
2090 pub fn set_show_edit_predictions(
2091 &mut self,
2092 show_edit_predictions: Option<bool>,
2093 window: &mut Window,
2094 cx: &mut Context<Self>,
2095 ) {
2096 self.show_inline_completions_override = show_edit_predictions;
2097 self.update_edit_prediction_settings(cx);
2098
2099 if let Some(false) = show_edit_predictions {
2100 self.discard_inline_completion(false, cx);
2101 } else {
2102 self.refresh_inline_completion(false, true, window, cx);
2103 }
2104 }
2105
2106 fn inline_completions_disabled_in_scope(
2107 &self,
2108 buffer: &Entity<Buffer>,
2109 buffer_position: language::Anchor,
2110 cx: &App,
2111 ) -> bool {
2112 let snapshot = buffer.read(cx).snapshot();
2113 let settings = snapshot.settings_at(buffer_position, cx);
2114
2115 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2116 return false;
2117 };
2118
2119 scope.override_name().map_or(false, |scope_name| {
2120 settings
2121 .edit_predictions_disabled_in
2122 .iter()
2123 .any(|s| s == scope_name)
2124 })
2125 }
2126
2127 pub fn set_use_modal_editing(&mut self, to: bool) {
2128 self.use_modal_editing = to;
2129 }
2130
2131 pub fn use_modal_editing(&self) -> bool {
2132 self.use_modal_editing
2133 }
2134
2135 fn selections_did_change(
2136 &mut self,
2137 local: bool,
2138 old_cursor_position: &Anchor,
2139 show_completions: bool,
2140 window: &mut Window,
2141 cx: &mut Context<Self>,
2142 ) {
2143 window.invalidate_character_coordinates();
2144
2145 // Copy selections to primary selection buffer
2146 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2147 if local {
2148 let selections = self.selections.all::<usize>(cx);
2149 let buffer_handle = self.buffer.read(cx).read(cx);
2150
2151 let mut text = String::new();
2152 for (index, selection) in selections.iter().enumerate() {
2153 let text_for_selection = buffer_handle
2154 .text_for_range(selection.start..selection.end)
2155 .collect::<String>();
2156
2157 text.push_str(&text_for_selection);
2158 if index != selections.len() - 1 {
2159 text.push('\n');
2160 }
2161 }
2162
2163 if !text.is_empty() {
2164 cx.write_to_primary(ClipboardItem::new_string(text));
2165 }
2166 }
2167
2168 if self.focus_handle.is_focused(window) && self.leader_peer_id.is_none() {
2169 self.buffer.update(cx, |buffer, cx| {
2170 buffer.set_active_selections(
2171 &self.selections.disjoint_anchors(),
2172 self.selections.line_mode,
2173 self.cursor_shape,
2174 cx,
2175 )
2176 });
2177 }
2178 let display_map = self
2179 .display_map
2180 .update(cx, |display_map, cx| display_map.snapshot(cx));
2181 let buffer = &display_map.buffer_snapshot;
2182 self.add_selections_state = None;
2183 self.select_next_state = None;
2184 self.select_prev_state = None;
2185 self.select_syntax_node_history.try_clear();
2186 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2187 self.snippet_stack
2188 .invalidate(&self.selections.disjoint_anchors(), buffer);
2189 self.take_rename(false, window, cx);
2190
2191 let new_cursor_position = self.selections.newest_anchor().head();
2192
2193 self.push_to_nav_history(
2194 *old_cursor_position,
2195 Some(new_cursor_position.to_point(buffer)),
2196 false,
2197 cx,
2198 );
2199
2200 if local {
2201 let new_cursor_position = self.selections.newest_anchor().head();
2202 let mut context_menu = self.context_menu.borrow_mut();
2203 let completion_menu = match context_menu.as_ref() {
2204 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2205 _ => {
2206 *context_menu = None;
2207 None
2208 }
2209 };
2210 if let Some(buffer_id) = new_cursor_position.buffer_id {
2211 if !self.registered_buffers.contains_key(&buffer_id) {
2212 if let Some(project) = self.project.as_ref() {
2213 project.update(cx, |project, cx| {
2214 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2215 return;
2216 };
2217 self.registered_buffers.insert(
2218 buffer_id,
2219 project.register_buffer_with_language_servers(&buffer, cx),
2220 );
2221 })
2222 }
2223 }
2224 }
2225
2226 if let Some(completion_menu) = completion_menu {
2227 let cursor_position = new_cursor_position.to_offset(buffer);
2228 let (word_range, kind) =
2229 buffer.surrounding_word(completion_menu.initial_position, true);
2230 if kind == Some(CharKind::Word)
2231 && word_range.to_inclusive().contains(&cursor_position)
2232 {
2233 let mut completion_menu = completion_menu.clone();
2234 drop(context_menu);
2235
2236 let query = Self::completion_query(buffer, cursor_position);
2237 cx.spawn(async move |this, cx| {
2238 completion_menu
2239 .filter(query.as_deref(), cx.background_executor().clone())
2240 .await;
2241
2242 this.update(cx, |this, cx| {
2243 let mut context_menu = this.context_menu.borrow_mut();
2244 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2245 else {
2246 return;
2247 };
2248
2249 if menu.id > completion_menu.id {
2250 return;
2251 }
2252
2253 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2254 drop(context_menu);
2255 cx.notify();
2256 })
2257 })
2258 .detach();
2259
2260 if show_completions {
2261 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2262 }
2263 } else {
2264 drop(context_menu);
2265 self.hide_context_menu(window, cx);
2266 }
2267 } else {
2268 drop(context_menu);
2269 }
2270
2271 hide_hover(self, cx);
2272
2273 if old_cursor_position.to_display_point(&display_map).row()
2274 != new_cursor_position.to_display_point(&display_map).row()
2275 {
2276 self.available_code_actions.take();
2277 }
2278 self.refresh_code_actions(window, cx);
2279 self.refresh_document_highlights(cx);
2280 self.refresh_selected_text_highlights(window, cx);
2281 refresh_matching_bracket_highlights(self, window, cx);
2282 self.update_visible_inline_completion(window, cx);
2283 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2284 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2285 if self.git_blame_inline_enabled {
2286 self.start_inline_blame_timer(window, cx);
2287 }
2288 }
2289
2290 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2291 cx.emit(EditorEvent::SelectionsChanged { local });
2292
2293 let selections = &self.selections.disjoint;
2294 if selections.len() == 1 {
2295 cx.emit(SearchEvent::ActiveMatchChanged)
2296 }
2297 if local
2298 && self.is_singleton(cx)
2299 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
2300 {
2301 if let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) {
2302 let background_executor = cx.background_executor().clone();
2303 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2304 let snapshot = self.buffer().read(cx).snapshot(cx);
2305 let selections = selections.clone();
2306 self.serialize_selections = cx.background_spawn(async move {
2307 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2308 let selections = selections
2309 .iter()
2310 .map(|selection| {
2311 (
2312 selection.start.to_offset(&snapshot),
2313 selection.end.to_offset(&snapshot),
2314 )
2315 })
2316 .collect();
2317
2318 DB.save_editor_selections(editor_id, workspace_id, selections)
2319 .await
2320 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2321 .log_err();
2322 });
2323 }
2324 }
2325
2326 cx.notify();
2327 }
2328
2329 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2330 if !self.is_singleton(cx)
2331 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2332 {
2333 return;
2334 }
2335
2336 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2337 return;
2338 };
2339 let background_executor = cx.background_executor().clone();
2340 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2341 let snapshot = self.buffer().read(cx).snapshot(cx);
2342 let folds = self.display_map.update(cx, |display_map, cx| {
2343 display_map
2344 .snapshot(cx)
2345 .folds_in_range(0..snapshot.len())
2346 .map(|fold| {
2347 (
2348 fold.range.start.to_offset(&snapshot),
2349 fold.range.end.to_offset(&snapshot),
2350 )
2351 })
2352 .collect()
2353 });
2354 self.serialize_folds = cx.background_spawn(async move {
2355 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2356 DB.save_editor_folds(editor_id, workspace_id, folds)
2357 .await
2358 .with_context(|| format!("persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"))
2359 .log_err();
2360 });
2361 }
2362
2363 pub fn sync_selections(
2364 &mut self,
2365 other: Entity<Editor>,
2366 cx: &mut Context<Self>,
2367 ) -> gpui::Subscription {
2368 let other_selections = other.read(cx).selections.disjoint.to_vec();
2369 self.selections.change_with(cx, |selections| {
2370 selections.select_anchors(other_selections);
2371 });
2372
2373 let other_subscription =
2374 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2375 EditorEvent::SelectionsChanged { local: true } => {
2376 let other_selections = other.read(cx).selections.disjoint.to_vec();
2377 if other_selections.is_empty() {
2378 return;
2379 }
2380 this.selections.change_with(cx, |selections| {
2381 selections.select_anchors(other_selections);
2382 });
2383 }
2384 _ => {}
2385 });
2386
2387 let this_subscription =
2388 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2389 EditorEvent::SelectionsChanged { local: true } => {
2390 let these_selections = this.selections.disjoint.to_vec();
2391 if these_selections.is_empty() {
2392 return;
2393 }
2394 other.update(cx, |other_editor, cx| {
2395 other_editor.selections.change_with(cx, |selections| {
2396 selections.select_anchors(these_selections);
2397 })
2398 });
2399 }
2400 _ => {}
2401 });
2402
2403 Subscription::join(other_subscription, this_subscription)
2404 }
2405
2406 pub fn change_selections<R>(
2407 &mut self,
2408 autoscroll: Option<Autoscroll>,
2409 window: &mut Window,
2410 cx: &mut Context<Self>,
2411 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2412 ) -> R {
2413 self.change_selections_inner(autoscroll, true, window, cx, change)
2414 }
2415
2416 fn change_selections_inner<R>(
2417 &mut self,
2418 autoscroll: Option<Autoscroll>,
2419 request_completions: bool,
2420 window: &mut Window,
2421 cx: &mut Context<Self>,
2422 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2423 ) -> R {
2424 let old_cursor_position = self.selections.newest_anchor().head();
2425 self.push_to_selection_history();
2426
2427 let (changed, result) = self.selections.change_with(cx, change);
2428
2429 if changed {
2430 if let Some(autoscroll) = autoscroll {
2431 self.request_autoscroll(autoscroll, cx);
2432 }
2433 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2434
2435 if self.should_open_signature_help_automatically(
2436 &old_cursor_position,
2437 self.signature_help_state.backspace_pressed(),
2438 cx,
2439 ) {
2440 self.show_signature_help(&ShowSignatureHelp, window, cx);
2441 }
2442 self.signature_help_state.set_backspace_pressed(false);
2443 }
2444
2445 result
2446 }
2447
2448 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2449 where
2450 I: IntoIterator<Item = (Range<S>, T)>,
2451 S: ToOffset,
2452 T: Into<Arc<str>>,
2453 {
2454 if self.read_only(cx) {
2455 return;
2456 }
2457
2458 self.buffer
2459 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2460 }
2461
2462 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2463 where
2464 I: IntoIterator<Item = (Range<S>, T)>,
2465 S: ToOffset,
2466 T: Into<Arc<str>>,
2467 {
2468 if self.read_only(cx) {
2469 return;
2470 }
2471
2472 self.buffer.update(cx, |buffer, cx| {
2473 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2474 });
2475 }
2476
2477 pub fn edit_with_block_indent<I, S, T>(
2478 &mut self,
2479 edits: I,
2480 original_indent_columns: Vec<Option<u32>>,
2481 cx: &mut Context<Self>,
2482 ) where
2483 I: IntoIterator<Item = (Range<S>, T)>,
2484 S: ToOffset,
2485 T: Into<Arc<str>>,
2486 {
2487 if self.read_only(cx) {
2488 return;
2489 }
2490
2491 self.buffer.update(cx, |buffer, cx| {
2492 buffer.edit(
2493 edits,
2494 Some(AutoindentMode::Block {
2495 original_indent_columns,
2496 }),
2497 cx,
2498 )
2499 });
2500 }
2501
2502 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2503 self.hide_context_menu(window, cx);
2504
2505 match phase {
2506 SelectPhase::Begin {
2507 position,
2508 add,
2509 click_count,
2510 } => self.begin_selection(position, add, click_count, window, cx),
2511 SelectPhase::BeginColumnar {
2512 position,
2513 goal_column,
2514 reset,
2515 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
2516 SelectPhase::Extend {
2517 position,
2518 click_count,
2519 } => self.extend_selection(position, click_count, window, cx),
2520 SelectPhase::Update {
2521 position,
2522 goal_column,
2523 scroll_delta,
2524 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
2525 SelectPhase::End => self.end_selection(window, cx),
2526 }
2527 }
2528
2529 fn extend_selection(
2530 &mut self,
2531 position: DisplayPoint,
2532 click_count: usize,
2533 window: &mut Window,
2534 cx: &mut Context<Self>,
2535 ) {
2536 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2537 let tail = self.selections.newest::<usize>(cx).tail();
2538 self.begin_selection(position, false, click_count, window, cx);
2539
2540 let position = position.to_offset(&display_map, Bias::Left);
2541 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
2542
2543 let mut pending_selection = self
2544 .selections
2545 .pending_anchor()
2546 .expect("extend_selection not called with pending selection");
2547 if position >= tail {
2548 pending_selection.start = tail_anchor;
2549 } else {
2550 pending_selection.end = tail_anchor;
2551 pending_selection.reversed = true;
2552 }
2553
2554 let mut pending_mode = self.selections.pending_mode().unwrap();
2555 match &mut pending_mode {
2556 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
2557 _ => {}
2558 }
2559
2560 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
2561 s.set_pending(pending_selection, pending_mode)
2562 });
2563 }
2564
2565 fn begin_selection(
2566 &mut self,
2567 position: DisplayPoint,
2568 add: bool,
2569 click_count: usize,
2570 window: &mut Window,
2571 cx: &mut Context<Self>,
2572 ) {
2573 if !self.focus_handle.is_focused(window) {
2574 self.last_focused_descendant = None;
2575 window.focus(&self.focus_handle);
2576 }
2577
2578 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2579 let buffer = &display_map.buffer_snapshot;
2580 let newest_selection = self.selections.newest_anchor().clone();
2581 let position = display_map.clip_point(position, Bias::Left);
2582
2583 let start;
2584 let end;
2585 let mode;
2586 let mut auto_scroll;
2587 match click_count {
2588 1 => {
2589 start = buffer.anchor_before(position.to_point(&display_map));
2590 end = start;
2591 mode = SelectMode::Character;
2592 auto_scroll = true;
2593 }
2594 2 => {
2595 let range = movement::surrounding_word(&display_map, position);
2596 start = buffer.anchor_before(range.start.to_point(&display_map));
2597 end = buffer.anchor_before(range.end.to_point(&display_map));
2598 mode = SelectMode::Word(start..end);
2599 auto_scroll = true;
2600 }
2601 3 => {
2602 let position = display_map
2603 .clip_point(position, Bias::Left)
2604 .to_point(&display_map);
2605 let line_start = display_map.prev_line_boundary(position).0;
2606 let next_line_start = buffer.clip_point(
2607 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2608 Bias::Left,
2609 );
2610 start = buffer.anchor_before(line_start);
2611 end = buffer.anchor_before(next_line_start);
2612 mode = SelectMode::Line(start..end);
2613 auto_scroll = true;
2614 }
2615 _ => {
2616 start = buffer.anchor_before(0);
2617 end = buffer.anchor_before(buffer.len());
2618 mode = SelectMode::All;
2619 auto_scroll = false;
2620 }
2621 }
2622 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
2623
2624 let point_to_delete: Option<usize> = {
2625 let selected_points: Vec<Selection<Point>> =
2626 self.selections.disjoint_in_range(start..end, cx);
2627
2628 if !add || click_count > 1 {
2629 None
2630 } else if !selected_points.is_empty() {
2631 Some(selected_points[0].id)
2632 } else {
2633 let clicked_point_already_selected =
2634 self.selections.disjoint.iter().find(|selection| {
2635 selection.start.to_point(buffer) == start.to_point(buffer)
2636 || selection.end.to_point(buffer) == end.to_point(buffer)
2637 });
2638
2639 clicked_point_already_selected.map(|selection| selection.id)
2640 }
2641 };
2642
2643 let selections_count = self.selections.count();
2644
2645 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
2646 if let Some(point_to_delete) = point_to_delete {
2647 s.delete(point_to_delete);
2648
2649 if selections_count == 1 {
2650 s.set_pending_anchor_range(start..end, mode);
2651 }
2652 } else {
2653 if !add {
2654 s.clear_disjoint();
2655 } else if click_count > 1 {
2656 s.delete(newest_selection.id)
2657 }
2658
2659 s.set_pending_anchor_range(start..end, mode);
2660 }
2661 });
2662 }
2663
2664 fn begin_columnar_selection(
2665 &mut self,
2666 position: DisplayPoint,
2667 goal_column: u32,
2668 reset: bool,
2669 window: &mut Window,
2670 cx: &mut Context<Self>,
2671 ) {
2672 if !self.focus_handle.is_focused(window) {
2673 self.last_focused_descendant = None;
2674 window.focus(&self.focus_handle);
2675 }
2676
2677 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2678
2679 if reset {
2680 let pointer_position = display_map
2681 .buffer_snapshot
2682 .anchor_before(position.to_point(&display_map));
2683
2684 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
2685 s.clear_disjoint();
2686 s.set_pending_anchor_range(
2687 pointer_position..pointer_position,
2688 SelectMode::Character,
2689 );
2690 });
2691 }
2692
2693 let tail = self.selections.newest::<Point>(cx).tail();
2694 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
2695
2696 if !reset {
2697 self.select_columns(
2698 tail.to_display_point(&display_map),
2699 position,
2700 goal_column,
2701 &display_map,
2702 window,
2703 cx,
2704 );
2705 }
2706 }
2707
2708 fn update_selection(
2709 &mut self,
2710 position: DisplayPoint,
2711 goal_column: u32,
2712 scroll_delta: gpui::Point<f32>,
2713 window: &mut Window,
2714 cx: &mut Context<Self>,
2715 ) {
2716 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2717
2718 if let Some(tail) = self.columnar_selection_tail.as_ref() {
2719 let tail = tail.to_display_point(&display_map);
2720 self.select_columns(tail, position, goal_column, &display_map, window, cx);
2721 } else if let Some(mut pending) = self.selections.pending_anchor() {
2722 let buffer = self.buffer.read(cx).snapshot(cx);
2723 let head;
2724 let tail;
2725 let mode = self.selections.pending_mode().unwrap();
2726 match &mode {
2727 SelectMode::Character => {
2728 head = position.to_point(&display_map);
2729 tail = pending.tail().to_point(&buffer);
2730 }
2731 SelectMode::Word(original_range) => {
2732 let original_display_range = original_range.start.to_display_point(&display_map)
2733 ..original_range.end.to_display_point(&display_map);
2734 let original_buffer_range = original_display_range.start.to_point(&display_map)
2735 ..original_display_range.end.to_point(&display_map);
2736 if movement::is_inside_word(&display_map, position)
2737 || original_display_range.contains(&position)
2738 {
2739 let word_range = movement::surrounding_word(&display_map, position);
2740 if word_range.start < original_display_range.start {
2741 head = word_range.start.to_point(&display_map);
2742 } else {
2743 head = word_range.end.to_point(&display_map);
2744 }
2745 } else {
2746 head = position.to_point(&display_map);
2747 }
2748
2749 if head <= original_buffer_range.start {
2750 tail = original_buffer_range.end;
2751 } else {
2752 tail = original_buffer_range.start;
2753 }
2754 }
2755 SelectMode::Line(original_range) => {
2756 let original_range = original_range.to_point(&display_map.buffer_snapshot);
2757
2758 let position = display_map
2759 .clip_point(position, Bias::Left)
2760 .to_point(&display_map);
2761 let line_start = display_map.prev_line_boundary(position).0;
2762 let next_line_start = buffer.clip_point(
2763 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2764 Bias::Left,
2765 );
2766
2767 if line_start < original_range.start {
2768 head = line_start
2769 } else {
2770 head = next_line_start
2771 }
2772
2773 if head <= original_range.start {
2774 tail = original_range.end;
2775 } else {
2776 tail = original_range.start;
2777 }
2778 }
2779 SelectMode::All => {
2780 return;
2781 }
2782 };
2783
2784 if head < tail {
2785 pending.start = buffer.anchor_before(head);
2786 pending.end = buffer.anchor_before(tail);
2787 pending.reversed = true;
2788 } else {
2789 pending.start = buffer.anchor_before(tail);
2790 pending.end = buffer.anchor_before(head);
2791 pending.reversed = false;
2792 }
2793
2794 self.change_selections(None, window, cx, |s| {
2795 s.set_pending(pending, mode);
2796 });
2797 } else {
2798 log::error!("update_selection dispatched with no pending selection");
2799 return;
2800 }
2801
2802 self.apply_scroll_delta(scroll_delta, window, cx);
2803 cx.notify();
2804 }
2805
2806 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2807 self.columnar_selection_tail.take();
2808 if self.selections.pending_anchor().is_some() {
2809 let selections = self.selections.all::<usize>(cx);
2810 self.change_selections(None, window, cx, |s| {
2811 s.select(selections);
2812 s.clear_pending();
2813 });
2814 }
2815 }
2816
2817 fn select_columns(
2818 &mut self,
2819 tail: DisplayPoint,
2820 head: DisplayPoint,
2821 goal_column: u32,
2822 display_map: &DisplaySnapshot,
2823 window: &mut Window,
2824 cx: &mut Context<Self>,
2825 ) {
2826 let start_row = cmp::min(tail.row(), head.row());
2827 let end_row = cmp::max(tail.row(), head.row());
2828 let start_column = cmp::min(tail.column(), goal_column);
2829 let end_column = cmp::max(tail.column(), goal_column);
2830 let reversed = start_column < tail.column();
2831
2832 let selection_ranges = (start_row.0..=end_row.0)
2833 .map(DisplayRow)
2834 .filter_map(|row| {
2835 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
2836 let start = display_map
2837 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
2838 .to_point(display_map);
2839 let end = display_map
2840 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
2841 .to_point(display_map);
2842 if reversed {
2843 Some(end..start)
2844 } else {
2845 Some(start..end)
2846 }
2847 } else {
2848 None
2849 }
2850 })
2851 .collect::<Vec<_>>();
2852
2853 self.change_selections(None, window, cx, |s| {
2854 s.select_ranges(selection_ranges);
2855 });
2856 cx.notify();
2857 }
2858
2859 pub fn has_pending_nonempty_selection(&self) -> bool {
2860 let pending_nonempty_selection = match self.selections.pending_anchor() {
2861 Some(Selection { start, end, .. }) => start != end,
2862 None => false,
2863 };
2864
2865 pending_nonempty_selection
2866 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
2867 }
2868
2869 pub fn has_pending_selection(&self) -> bool {
2870 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
2871 }
2872
2873 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
2874 self.selection_mark_mode = false;
2875
2876 if self.clear_expanded_diff_hunks(cx) {
2877 cx.notify();
2878 return;
2879 }
2880 if self.dismiss_menus_and_popups(true, window, cx) {
2881 return;
2882 }
2883
2884 if self.mode == EditorMode::Full
2885 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
2886 {
2887 return;
2888 }
2889
2890 cx.propagate();
2891 }
2892
2893 pub fn dismiss_menus_and_popups(
2894 &mut self,
2895 is_user_requested: bool,
2896 window: &mut Window,
2897 cx: &mut Context<Self>,
2898 ) -> bool {
2899 if self.take_rename(false, window, cx).is_some() {
2900 return true;
2901 }
2902
2903 if hide_hover(self, cx) {
2904 return true;
2905 }
2906
2907 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
2908 return true;
2909 }
2910
2911 if self.hide_context_menu(window, cx).is_some() {
2912 return true;
2913 }
2914
2915 if self.mouse_context_menu.take().is_some() {
2916 return true;
2917 }
2918
2919 if is_user_requested && self.discard_inline_completion(true, cx) {
2920 return true;
2921 }
2922
2923 if self.snippet_stack.pop().is_some() {
2924 return true;
2925 }
2926
2927 if self.mode == EditorMode::Full && self.active_diagnostics.is_some() {
2928 self.dismiss_diagnostics(cx);
2929 return true;
2930 }
2931
2932 false
2933 }
2934
2935 fn linked_editing_ranges_for(
2936 &self,
2937 selection: Range<text::Anchor>,
2938 cx: &App,
2939 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
2940 if self.linked_edit_ranges.is_empty() {
2941 return None;
2942 }
2943 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
2944 selection.end.buffer_id.and_then(|end_buffer_id| {
2945 if selection.start.buffer_id != Some(end_buffer_id) {
2946 return None;
2947 }
2948 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
2949 let snapshot = buffer.read(cx).snapshot();
2950 self.linked_edit_ranges
2951 .get(end_buffer_id, selection.start..selection.end, &snapshot)
2952 .map(|ranges| (ranges, snapshot, buffer))
2953 })?;
2954 use text::ToOffset as TO;
2955 // find offset from the start of current range to current cursor position
2956 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
2957
2958 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
2959 let start_difference = start_offset - start_byte_offset;
2960 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
2961 let end_difference = end_offset - start_byte_offset;
2962 // Current range has associated linked ranges.
2963 let mut linked_edits = HashMap::<_, Vec<_>>::default();
2964 for range in linked_ranges.iter() {
2965 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
2966 let end_offset = start_offset + end_difference;
2967 let start_offset = start_offset + start_difference;
2968 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
2969 continue;
2970 }
2971 if self.selections.disjoint_anchor_ranges().any(|s| {
2972 if s.start.buffer_id != selection.start.buffer_id
2973 || s.end.buffer_id != selection.end.buffer_id
2974 {
2975 return false;
2976 }
2977 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
2978 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
2979 }) {
2980 continue;
2981 }
2982 let start = buffer_snapshot.anchor_after(start_offset);
2983 let end = buffer_snapshot.anchor_after(end_offset);
2984 linked_edits
2985 .entry(buffer.clone())
2986 .or_default()
2987 .push(start..end);
2988 }
2989 Some(linked_edits)
2990 }
2991
2992 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
2993 let text: Arc<str> = text.into();
2994
2995 if self.read_only(cx) {
2996 return;
2997 }
2998
2999 let selections = self.selections.all_adjusted(cx);
3000 let mut bracket_inserted = false;
3001 let mut edits = Vec::new();
3002 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3003 let mut new_selections = Vec::with_capacity(selections.len());
3004 let mut new_autoclose_regions = Vec::new();
3005 let snapshot = self.buffer.read(cx).read(cx);
3006
3007 for (selection, autoclose_region) in
3008 self.selections_with_autoclose_regions(selections, &snapshot)
3009 {
3010 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3011 // Determine if the inserted text matches the opening or closing
3012 // bracket of any of this language's bracket pairs.
3013 let mut bracket_pair = None;
3014 let mut is_bracket_pair_start = false;
3015 let mut is_bracket_pair_end = false;
3016 if !text.is_empty() {
3017 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3018 // and they are removing the character that triggered IME popup.
3019 for (pair, enabled) in scope.brackets() {
3020 if !pair.close && !pair.surround {
3021 continue;
3022 }
3023
3024 if enabled && pair.start.ends_with(text.as_ref()) {
3025 let prefix_len = pair.start.len() - text.len();
3026 let preceding_text_matches_prefix = prefix_len == 0
3027 || (selection.start.column >= (prefix_len as u32)
3028 && snapshot.contains_str_at(
3029 Point::new(
3030 selection.start.row,
3031 selection.start.column - (prefix_len as u32),
3032 ),
3033 &pair.start[..prefix_len],
3034 ));
3035 if preceding_text_matches_prefix {
3036 bracket_pair = Some(pair.clone());
3037 is_bracket_pair_start = true;
3038 break;
3039 }
3040 }
3041 if pair.end.as_str() == text.as_ref() {
3042 bracket_pair = Some(pair.clone());
3043 is_bracket_pair_end = true;
3044 break;
3045 }
3046 }
3047 }
3048
3049 if let Some(bracket_pair) = bracket_pair {
3050 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3051 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3052 let auto_surround =
3053 self.use_auto_surround && snapshot_settings.use_auto_surround;
3054 if selection.is_empty() {
3055 if is_bracket_pair_start {
3056 // If the inserted text is a suffix of an opening bracket and the
3057 // selection is preceded by the rest of the opening bracket, then
3058 // insert the closing bracket.
3059 let following_text_allows_autoclose = snapshot
3060 .chars_at(selection.start)
3061 .next()
3062 .map_or(true, |c| scope.should_autoclose_before(c));
3063
3064 let preceding_text_allows_autoclose = selection.start.column == 0
3065 || snapshot.reversed_chars_at(selection.start).next().map_or(
3066 true,
3067 |c| {
3068 bracket_pair.start != bracket_pair.end
3069 || !snapshot
3070 .char_classifier_at(selection.start)
3071 .is_word(c)
3072 },
3073 );
3074
3075 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3076 && bracket_pair.start.len() == 1
3077 {
3078 let target = bracket_pair.start.chars().next().unwrap();
3079 let current_line_count = snapshot
3080 .reversed_chars_at(selection.start)
3081 .take_while(|&c| c != '\n')
3082 .filter(|&c| c == target)
3083 .count();
3084 current_line_count % 2 == 1
3085 } else {
3086 false
3087 };
3088
3089 if autoclose
3090 && bracket_pair.close
3091 && following_text_allows_autoclose
3092 && preceding_text_allows_autoclose
3093 && !is_closing_quote
3094 {
3095 let anchor = snapshot.anchor_before(selection.end);
3096 new_selections.push((selection.map(|_| anchor), text.len()));
3097 new_autoclose_regions.push((
3098 anchor,
3099 text.len(),
3100 selection.id,
3101 bracket_pair.clone(),
3102 ));
3103 edits.push((
3104 selection.range(),
3105 format!("{}{}", text, bracket_pair.end).into(),
3106 ));
3107 bracket_inserted = true;
3108 continue;
3109 }
3110 }
3111
3112 if let Some(region) = autoclose_region {
3113 // If the selection is followed by an auto-inserted closing bracket,
3114 // then don't insert that closing bracket again; just move the selection
3115 // past the closing bracket.
3116 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3117 && text.as_ref() == region.pair.end.as_str();
3118 if should_skip {
3119 let anchor = snapshot.anchor_after(selection.end);
3120 new_selections
3121 .push((selection.map(|_| anchor), region.pair.end.len()));
3122 continue;
3123 }
3124 }
3125
3126 let always_treat_brackets_as_autoclosed = snapshot
3127 .language_settings_at(selection.start, cx)
3128 .always_treat_brackets_as_autoclosed;
3129 if always_treat_brackets_as_autoclosed
3130 && is_bracket_pair_end
3131 && snapshot.contains_str_at(selection.end, text.as_ref())
3132 {
3133 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3134 // and the inserted text is a closing bracket and the selection is followed
3135 // by the closing bracket then move the selection past the closing bracket.
3136 let anchor = snapshot.anchor_after(selection.end);
3137 new_selections.push((selection.map(|_| anchor), text.len()));
3138 continue;
3139 }
3140 }
3141 // If an opening bracket is 1 character long and is typed while
3142 // text is selected, then surround that text with the bracket pair.
3143 else if auto_surround
3144 && bracket_pair.surround
3145 && is_bracket_pair_start
3146 && bracket_pair.start.chars().count() == 1
3147 {
3148 edits.push((selection.start..selection.start, text.clone()));
3149 edits.push((
3150 selection.end..selection.end,
3151 bracket_pair.end.as_str().into(),
3152 ));
3153 bracket_inserted = true;
3154 new_selections.push((
3155 Selection {
3156 id: selection.id,
3157 start: snapshot.anchor_after(selection.start),
3158 end: snapshot.anchor_before(selection.end),
3159 reversed: selection.reversed,
3160 goal: selection.goal,
3161 },
3162 0,
3163 ));
3164 continue;
3165 }
3166 }
3167 }
3168
3169 if self.auto_replace_emoji_shortcode
3170 && selection.is_empty()
3171 && text.as_ref().ends_with(':')
3172 {
3173 if let Some(possible_emoji_short_code) =
3174 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3175 {
3176 if !possible_emoji_short_code.is_empty() {
3177 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3178 let emoji_shortcode_start = Point::new(
3179 selection.start.row,
3180 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3181 );
3182
3183 // Remove shortcode from buffer
3184 edits.push((
3185 emoji_shortcode_start..selection.start,
3186 "".to_string().into(),
3187 ));
3188 new_selections.push((
3189 Selection {
3190 id: selection.id,
3191 start: snapshot.anchor_after(emoji_shortcode_start),
3192 end: snapshot.anchor_before(selection.start),
3193 reversed: selection.reversed,
3194 goal: selection.goal,
3195 },
3196 0,
3197 ));
3198
3199 // Insert emoji
3200 let selection_start_anchor = snapshot.anchor_after(selection.start);
3201 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3202 edits.push((selection.start..selection.end, emoji.to_string().into()));
3203
3204 continue;
3205 }
3206 }
3207 }
3208 }
3209
3210 // If not handling any auto-close operation, then just replace the selected
3211 // text with the given input and move the selection to the end of the
3212 // newly inserted text.
3213 let anchor = snapshot.anchor_after(selection.end);
3214 if !self.linked_edit_ranges.is_empty() {
3215 let start_anchor = snapshot.anchor_before(selection.start);
3216
3217 let is_word_char = text.chars().next().map_or(true, |char| {
3218 let classifier = snapshot.char_classifier_at(start_anchor.to_offset(&snapshot));
3219 classifier.is_word(char)
3220 });
3221
3222 if is_word_char {
3223 if let Some(ranges) = self
3224 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3225 {
3226 for (buffer, edits) in ranges {
3227 linked_edits
3228 .entry(buffer.clone())
3229 .or_default()
3230 .extend(edits.into_iter().map(|range| (range, text.clone())));
3231 }
3232 }
3233 }
3234 }
3235
3236 new_selections.push((selection.map(|_| anchor), 0));
3237 edits.push((selection.start..selection.end, text.clone()));
3238 }
3239
3240 drop(snapshot);
3241
3242 self.transact(window, cx, |this, window, cx| {
3243 let initial_buffer_versions =
3244 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3245
3246 this.buffer.update(cx, |buffer, cx| {
3247 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3248 });
3249 for (buffer, edits) in linked_edits {
3250 buffer.update(cx, |buffer, cx| {
3251 let snapshot = buffer.snapshot();
3252 let edits = edits
3253 .into_iter()
3254 .map(|(range, text)| {
3255 use text::ToPoint as TP;
3256 let end_point = TP::to_point(&range.end, &snapshot);
3257 let start_point = TP::to_point(&range.start, &snapshot);
3258 (start_point..end_point, text)
3259 })
3260 .sorted_by_key(|(range, _)| range.start);
3261 buffer.edit(edits, None, cx);
3262 })
3263 }
3264 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3265 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3266 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3267 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3268 .zip(new_selection_deltas)
3269 .map(|(selection, delta)| Selection {
3270 id: selection.id,
3271 start: selection.start + delta,
3272 end: selection.end + delta,
3273 reversed: selection.reversed,
3274 goal: SelectionGoal::None,
3275 })
3276 .collect::<Vec<_>>();
3277
3278 let mut i = 0;
3279 for (position, delta, selection_id, pair) in new_autoclose_regions {
3280 let position = position.to_offset(&map.buffer_snapshot) + delta;
3281 let start = map.buffer_snapshot.anchor_before(position);
3282 let end = map.buffer_snapshot.anchor_after(position);
3283 while let Some(existing_state) = this.autoclose_regions.get(i) {
3284 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3285 Ordering::Less => i += 1,
3286 Ordering::Greater => break,
3287 Ordering::Equal => {
3288 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3289 Ordering::Less => i += 1,
3290 Ordering::Equal => break,
3291 Ordering::Greater => break,
3292 }
3293 }
3294 }
3295 }
3296 this.autoclose_regions.insert(
3297 i,
3298 AutocloseRegion {
3299 selection_id,
3300 range: start..end,
3301 pair,
3302 },
3303 );
3304 }
3305
3306 let had_active_inline_completion = this.has_active_inline_completion();
3307 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3308 s.select(new_selections)
3309 });
3310
3311 if !bracket_inserted {
3312 if let Some(on_type_format_task) =
3313 this.trigger_on_type_formatting(text.to_string(), window, cx)
3314 {
3315 on_type_format_task.detach_and_log_err(cx);
3316 }
3317 }
3318
3319 let editor_settings = EditorSettings::get_global(cx);
3320 if bracket_inserted
3321 && (editor_settings.auto_signature_help
3322 || editor_settings.show_signature_help_after_edits)
3323 {
3324 this.show_signature_help(&ShowSignatureHelp, window, cx);
3325 }
3326
3327 let trigger_in_words =
3328 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3329 if this.hard_wrap.is_some() {
3330 let latest: Range<Point> = this.selections.newest(cx).range();
3331 if latest.is_empty()
3332 && this
3333 .buffer()
3334 .read(cx)
3335 .snapshot(cx)
3336 .line_len(MultiBufferRow(latest.start.row))
3337 == latest.start.column
3338 {
3339 this.rewrap_impl(
3340 RewrapOptions {
3341 override_language_settings: true,
3342 preserve_existing_whitespace: true,
3343 },
3344 cx,
3345 )
3346 }
3347 }
3348 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3349 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3350 this.refresh_inline_completion(true, false, window, cx);
3351 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3352 });
3353 }
3354
3355 fn find_possible_emoji_shortcode_at_position(
3356 snapshot: &MultiBufferSnapshot,
3357 position: Point,
3358 ) -> Option<String> {
3359 let mut chars = Vec::new();
3360 let mut found_colon = false;
3361 for char in snapshot.reversed_chars_at(position).take(100) {
3362 // Found a possible emoji shortcode in the middle of the buffer
3363 if found_colon {
3364 if char.is_whitespace() {
3365 chars.reverse();
3366 return Some(chars.iter().collect());
3367 }
3368 // If the previous character is not a whitespace, we are in the middle of a word
3369 // and we only want to complete the shortcode if the word is made up of other emojis
3370 let mut containing_word = String::new();
3371 for ch in snapshot
3372 .reversed_chars_at(position)
3373 .skip(chars.len() + 1)
3374 .take(100)
3375 {
3376 if ch.is_whitespace() {
3377 break;
3378 }
3379 containing_word.push(ch);
3380 }
3381 let containing_word = containing_word.chars().rev().collect::<String>();
3382 if util::word_consists_of_emojis(containing_word.as_str()) {
3383 chars.reverse();
3384 return Some(chars.iter().collect());
3385 }
3386 }
3387
3388 if char.is_whitespace() || !char.is_ascii() {
3389 return None;
3390 }
3391 if char == ':' {
3392 found_colon = true;
3393 } else {
3394 chars.push(char);
3395 }
3396 }
3397 // Found a possible emoji shortcode at the beginning of the buffer
3398 chars.reverse();
3399 Some(chars.iter().collect())
3400 }
3401
3402 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3403 self.transact(window, cx, |this, window, cx| {
3404 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
3405 let selections = this.selections.all::<usize>(cx);
3406 let multi_buffer = this.buffer.read(cx);
3407 let buffer = multi_buffer.snapshot(cx);
3408 selections
3409 .iter()
3410 .map(|selection| {
3411 let start_point = selection.start.to_point(&buffer);
3412 let mut indent =
3413 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3414 indent.len = cmp::min(indent.len, start_point.column);
3415 let start = selection.start;
3416 let end = selection.end;
3417 let selection_is_empty = start == end;
3418 let language_scope = buffer.language_scope_at(start);
3419 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
3420 &language_scope
3421 {
3422 let insert_extra_newline =
3423 insert_extra_newline_brackets(&buffer, start..end, language)
3424 || insert_extra_newline_tree_sitter(&buffer, start..end);
3425
3426 // Comment extension on newline is allowed only for cursor selections
3427 let comment_delimiter = maybe!({
3428 if !selection_is_empty {
3429 return None;
3430 }
3431
3432 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3433 return None;
3434 }
3435
3436 let delimiters = language.line_comment_prefixes();
3437 let max_len_of_delimiter =
3438 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3439 let (snapshot, range) =
3440 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3441
3442 let mut index_of_first_non_whitespace = 0;
3443 let comment_candidate = snapshot
3444 .chars_for_range(range)
3445 .skip_while(|c| {
3446 let should_skip = c.is_whitespace();
3447 if should_skip {
3448 index_of_first_non_whitespace += 1;
3449 }
3450 should_skip
3451 })
3452 .take(max_len_of_delimiter)
3453 .collect::<String>();
3454 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3455 comment_candidate.starts_with(comment_prefix.as_ref())
3456 })?;
3457 let cursor_is_placed_after_comment_marker =
3458 index_of_first_non_whitespace + comment_prefix.len()
3459 <= start_point.column as usize;
3460 if cursor_is_placed_after_comment_marker {
3461 Some(comment_prefix.clone())
3462 } else {
3463 None
3464 }
3465 });
3466 (comment_delimiter, insert_extra_newline)
3467 } else {
3468 (None, false)
3469 };
3470
3471 let capacity_for_delimiter = comment_delimiter
3472 .as_deref()
3473 .map(str::len)
3474 .unwrap_or_default();
3475 let mut new_text =
3476 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3477 new_text.push('\n');
3478 new_text.extend(indent.chars());
3479 if let Some(delimiter) = &comment_delimiter {
3480 new_text.push_str(delimiter);
3481 }
3482 if insert_extra_newline {
3483 new_text = new_text.repeat(2);
3484 }
3485
3486 let anchor = buffer.anchor_after(end);
3487 let new_selection = selection.map(|_| anchor);
3488 (
3489 (start..end, new_text),
3490 (insert_extra_newline, new_selection),
3491 )
3492 })
3493 .unzip()
3494 };
3495
3496 this.edit_with_autoindent(edits, cx);
3497 let buffer = this.buffer.read(cx).snapshot(cx);
3498 let new_selections = selection_fixup_info
3499 .into_iter()
3500 .map(|(extra_newline_inserted, new_selection)| {
3501 let mut cursor = new_selection.end.to_point(&buffer);
3502 if extra_newline_inserted {
3503 cursor.row -= 1;
3504 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
3505 }
3506 new_selection.map(|_| cursor)
3507 })
3508 .collect();
3509
3510 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3511 s.select(new_selections)
3512 });
3513 this.refresh_inline_completion(true, false, window, cx);
3514 });
3515 }
3516
3517 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
3518 let buffer = self.buffer.read(cx);
3519 let snapshot = buffer.snapshot(cx);
3520
3521 let mut edits = Vec::new();
3522 let mut rows = Vec::new();
3523
3524 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
3525 let cursor = selection.head();
3526 let row = cursor.row;
3527
3528 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
3529
3530 let newline = "\n".to_string();
3531 edits.push((start_of_line..start_of_line, newline));
3532
3533 rows.push(row + rows_inserted as u32);
3534 }
3535
3536 self.transact(window, cx, |editor, window, cx| {
3537 editor.edit(edits, cx);
3538
3539 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3540 let mut index = 0;
3541 s.move_cursors_with(|map, _, _| {
3542 let row = rows[index];
3543 index += 1;
3544
3545 let point = Point::new(row, 0);
3546 let boundary = map.next_line_boundary(point).1;
3547 let clipped = map.clip_point(boundary, Bias::Left);
3548
3549 (clipped, SelectionGoal::None)
3550 });
3551 });
3552
3553 let mut indent_edits = Vec::new();
3554 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3555 for row in rows {
3556 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3557 for (row, indent) in indents {
3558 if indent.len == 0 {
3559 continue;
3560 }
3561
3562 let text = match indent.kind {
3563 IndentKind::Space => " ".repeat(indent.len as usize),
3564 IndentKind::Tab => "\t".repeat(indent.len as usize),
3565 };
3566 let point = Point::new(row.0, 0);
3567 indent_edits.push((point..point, text));
3568 }
3569 }
3570 editor.edit(indent_edits, cx);
3571 });
3572 }
3573
3574 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
3575 let buffer = self.buffer.read(cx);
3576 let snapshot = buffer.snapshot(cx);
3577
3578 let mut edits = Vec::new();
3579 let mut rows = Vec::new();
3580 let mut rows_inserted = 0;
3581
3582 for selection in self.selections.all_adjusted(cx) {
3583 let cursor = selection.head();
3584 let row = cursor.row;
3585
3586 let point = Point::new(row + 1, 0);
3587 let start_of_line = snapshot.clip_point(point, Bias::Left);
3588
3589 let newline = "\n".to_string();
3590 edits.push((start_of_line..start_of_line, newline));
3591
3592 rows_inserted += 1;
3593 rows.push(row + rows_inserted);
3594 }
3595
3596 self.transact(window, cx, |editor, window, cx| {
3597 editor.edit(edits, cx);
3598
3599 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3600 let mut index = 0;
3601 s.move_cursors_with(|map, _, _| {
3602 let row = rows[index];
3603 index += 1;
3604
3605 let point = Point::new(row, 0);
3606 let boundary = map.next_line_boundary(point).1;
3607 let clipped = map.clip_point(boundary, Bias::Left);
3608
3609 (clipped, SelectionGoal::None)
3610 });
3611 });
3612
3613 let mut indent_edits = Vec::new();
3614 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3615 for row in rows {
3616 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3617 for (row, indent) in indents {
3618 if indent.len == 0 {
3619 continue;
3620 }
3621
3622 let text = match indent.kind {
3623 IndentKind::Space => " ".repeat(indent.len as usize),
3624 IndentKind::Tab => "\t".repeat(indent.len as usize),
3625 };
3626 let point = Point::new(row.0, 0);
3627 indent_edits.push((point..point, text));
3628 }
3629 }
3630 editor.edit(indent_edits, cx);
3631 });
3632 }
3633
3634 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3635 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
3636 original_indent_columns: Vec::new(),
3637 });
3638 self.insert_with_autoindent_mode(text, autoindent, window, cx);
3639 }
3640
3641 fn insert_with_autoindent_mode(
3642 &mut self,
3643 text: &str,
3644 autoindent_mode: Option<AutoindentMode>,
3645 window: &mut Window,
3646 cx: &mut Context<Self>,
3647 ) {
3648 if self.read_only(cx) {
3649 return;
3650 }
3651
3652 let text: Arc<str> = text.into();
3653 self.transact(window, cx, |this, window, cx| {
3654 let old_selections = this.selections.all_adjusted(cx);
3655 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
3656 let anchors = {
3657 let snapshot = buffer.read(cx);
3658 old_selections
3659 .iter()
3660 .map(|s| {
3661 let anchor = snapshot.anchor_after(s.head());
3662 s.map(|_| anchor)
3663 })
3664 .collect::<Vec<_>>()
3665 };
3666 buffer.edit(
3667 old_selections
3668 .iter()
3669 .map(|s| (s.start..s.end, text.clone())),
3670 autoindent_mode,
3671 cx,
3672 );
3673 anchors
3674 });
3675
3676 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3677 s.select_anchors(selection_anchors);
3678 });
3679
3680 cx.notify();
3681 });
3682 }
3683
3684 fn trigger_completion_on_input(
3685 &mut self,
3686 text: &str,
3687 trigger_in_words: bool,
3688 window: &mut Window,
3689 cx: &mut Context<Self>,
3690 ) {
3691 let ignore_completion_provider = self
3692 .context_menu
3693 .borrow()
3694 .as_ref()
3695 .map(|menu| match menu {
3696 CodeContextMenu::Completions(completions_menu) => {
3697 completions_menu.ignore_completion_provider
3698 }
3699 CodeContextMenu::CodeActions(_) => false,
3700 })
3701 .unwrap_or(false);
3702
3703 if ignore_completion_provider {
3704 self.show_word_completions(&ShowWordCompletions, window, cx);
3705 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
3706 self.show_completions(
3707 &ShowCompletions {
3708 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
3709 },
3710 window,
3711 cx,
3712 );
3713 } else {
3714 self.hide_context_menu(window, cx);
3715 }
3716 }
3717
3718 fn is_completion_trigger(
3719 &self,
3720 text: &str,
3721 trigger_in_words: bool,
3722 cx: &mut Context<Self>,
3723 ) -> bool {
3724 let position = self.selections.newest_anchor().head();
3725 let multibuffer = self.buffer.read(cx);
3726 let Some(buffer) = position
3727 .buffer_id
3728 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
3729 else {
3730 return false;
3731 };
3732
3733 if let Some(completion_provider) = &self.completion_provider {
3734 completion_provider.is_completion_trigger(
3735 &buffer,
3736 position.text_anchor,
3737 text,
3738 trigger_in_words,
3739 cx,
3740 )
3741 } else {
3742 false
3743 }
3744 }
3745
3746 /// If any empty selections is touching the start of its innermost containing autoclose
3747 /// region, expand it to select the brackets.
3748 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3749 let selections = self.selections.all::<usize>(cx);
3750 let buffer = self.buffer.read(cx).read(cx);
3751 let new_selections = self
3752 .selections_with_autoclose_regions(selections, &buffer)
3753 .map(|(mut selection, region)| {
3754 if !selection.is_empty() {
3755 return selection;
3756 }
3757
3758 if let Some(region) = region {
3759 let mut range = region.range.to_offset(&buffer);
3760 if selection.start == range.start && range.start >= region.pair.start.len() {
3761 range.start -= region.pair.start.len();
3762 if buffer.contains_str_at(range.start, ®ion.pair.start)
3763 && buffer.contains_str_at(range.end, ®ion.pair.end)
3764 {
3765 range.end += region.pair.end.len();
3766 selection.start = range.start;
3767 selection.end = range.end;
3768
3769 return selection;
3770 }
3771 }
3772 }
3773
3774 let always_treat_brackets_as_autoclosed = buffer
3775 .language_settings_at(selection.start, cx)
3776 .always_treat_brackets_as_autoclosed;
3777
3778 if !always_treat_brackets_as_autoclosed {
3779 return selection;
3780 }
3781
3782 if let Some(scope) = buffer.language_scope_at(selection.start) {
3783 for (pair, enabled) in scope.brackets() {
3784 if !enabled || !pair.close {
3785 continue;
3786 }
3787
3788 if buffer.contains_str_at(selection.start, &pair.end) {
3789 let pair_start_len = pair.start.len();
3790 if buffer.contains_str_at(
3791 selection.start.saturating_sub(pair_start_len),
3792 &pair.start,
3793 ) {
3794 selection.start -= pair_start_len;
3795 selection.end += pair.end.len();
3796
3797 return selection;
3798 }
3799 }
3800 }
3801 }
3802
3803 selection
3804 })
3805 .collect();
3806
3807 drop(buffer);
3808 self.change_selections(None, window, cx, |selections| {
3809 selections.select(new_selections)
3810 });
3811 }
3812
3813 /// Iterate the given selections, and for each one, find the smallest surrounding
3814 /// autoclose region. This uses the ordering of the selections and the autoclose
3815 /// regions to avoid repeated comparisons.
3816 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
3817 &'a self,
3818 selections: impl IntoIterator<Item = Selection<D>>,
3819 buffer: &'a MultiBufferSnapshot,
3820 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
3821 let mut i = 0;
3822 let mut regions = self.autoclose_regions.as_slice();
3823 selections.into_iter().map(move |selection| {
3824 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
3825
3826 let mut enclosing = None;
3827 while let Some(pair_state) = regions.get(i) {
3828 if pair_state.range.end.to_offset(buffer) < range.start {
3829 regions = ®ions[i + 1..];
3830 i = 0;
3831 } else if pair_state.range.start.to_offset(buffer) > range.end {
3832 break;
3833 } else {
3834 if pair_state.selection_id == selection.id {
3835 enclosing = Some(pair_state);
3836 }
3837 i += 1;
3838 }
3839 }
3840
3841 (selection, enclosing)
3842 })
3843 }
3844
3845 /// Remove any autoclose regions that no longer contain their selection.
3846 fn invalidate_autoclose_regions(
3847 &mut self,
3848 mut selections: &[Selection<Anchor>],
3849 buffer: &MultiBufferSnapshot,
3850 ) {
3851 self.autoclose_regions.retain(|state| {
3852 let mut i = 0;
3853 while let Some(selection) = selections.get(i) {
3854 if selection.end.cmp(&state.range.start, buffer).is_lt() {
3855 selections = &selections[1..];
3856 continue;
3857 }
3858 if selection.start.cmp(&state.range.end, buffer).is_gt() {
3859 break;
3860 }
3861 if selection.id == state.selection_id {
3862 return true;
3863 } else {
3864 i += 1;
3865 }
3866 }
3867 false
3868 });
3869 }
3870
3871 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
3872 let offset = position.to_offset(buffer);
3873 let (word_range, kind) = buffer.surrounding_word(offset, true);
3874 if offset > word_range.start && kind == Some(CharKind::Word) {
3875 Some(
3876 buffer
3877 .text_for_range(word_range.start..offset)
3878 .collect::<String>(),
3879 )
3880 } else {
3881 None
3882 }
3883 }
3884
3885 pub fn toggle_inlay_hints(
3886 &mut self,
3887 _: &ToggleInlayHints,
3888 _: &mut Window,
3889 cx: &mut Context<Self>,
3890 ) {
3891 self.refresh_inlay_hints(
3892 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
3893 cx,
3894 );
3895 }
3896
3897 pub fn inlay_hints_enabled(&self) -> bool {
3898 self.inlay_hint_cache.enabled
3899 }
3900
3901 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
3902 if self.semantics_provider.is_none() || self.mode != EditorMode::Full {
3903 return;
3904 }
3905
3906 let reason_description = reason.description();
3907 let ignore_debounce = matches!(
3908 reason,
3909 InlayHintRefreshReason::SettingsChange(_)
3910 | InlayHintRefreshReason::Toggle(_)
3911 | InlayHintRefreshReason::ExcerptsRemoved(_)
3912 | InlayHintRefreshReason::ModifiersChanged(_)
3913 );
3914 let (invalidate_cache, required_languages) = match reason {
3915 InlayHintRefreshReason::ModifiersChanged(enabled) => {
3916 match self.inlay_hint_cache.modifiers_override(enabled) {
3917 Some(enabled) => {
3918 if enabled {
3919 (InvalidationStrategy::RefreshRequested, None)
3920 } else {
3921 self.splice_inlays(
3922 &self
3923 .visible_inlay_hints(cx)
3924 .iter()
3925 .map(|inlay| inlay.id)
3926 .collect::<Vec<InlayId>>(),
3927 Vec::new(),
3928 cx,
3929 );
3930 return;
3931 }
3932 }
3933 None => return,
3934 }
3935 }
3936 InlayHintRefreshReason::Toggle(enabled) => {
3937 if self.inlay_hint_cache.toggle(enabled) {
3938 if enabled {
3939 (InvalidationStrategy::RefreshRequested, None)
3940 } else {
3941 self.splice_inlays(
3942 &self
3943 .visible_inlay_hints(cx)
3944 .iter()
3945 .map(|inlay| inlay.id)
3946 .collect::<Vec<InlayId>>(),
3947 Vec::new(),
3948 cx,
3949 );
3950 return;
3951 }
3952 } else {
3953 return;
3954 }
3955 }
3956 InlayHintRefreshReason::SettingsChange(new_settings) => {
3957 match self.inlay_hint_cache.update_settings(
3958 &self.buffer,
3959 new_settings,
3960 self.visible_inlay_hints(cx),
3961 cx,
3962 ) {
3963 ControlFlow::Break(Some(InlaySplice {
3964 to_remove,
3965 to_insert,
3966 })) => {
3967 self.splice_inlays(&to_remove, to_insert, cx);
3968 return;
3969 }
3970 ControlFlow::Break(None) => return,
3971 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
3972 }
3973 }
3974 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
3975 if let Some(InlaySplice {
3976 to_remove,
3977 to_insert,
3978 }) = self.inlay_hint_cache.remove_excerpts(excerpts_removed)
3979 {
3980 self.splice_inlays(&to_remove, to_insert, cx);
3981 }
3982 return;
3983 }
3984 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
3985 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
3986 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
3987 }
3988 InlayHintRefreshReason::RefreshRequested => {
3989 (InvalidationStrategy::RefreshRequested, None)
3990 }
3991 };
3992
3993 if let Some(InlaySplice {
3994 to_remove,
3995 to_insert,
3996 }) = self.inlay_hint_cache.spawn_hint_refresh(
3997 reason_description,
3998 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
3999 invalidate_cache,
4000 ignore_debounce,
4001 cx,
4002 ) {
4003 self.splice_inlays(&to_remove, to_insert, cx);
4004 }
4005 }
4006
4007 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4008 self.display_map
4009 .read(cx)
4010 .current_inlays()
4011 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4012 .cloned()
4013 .collect()
4014 }
4015
4016 pub fn excerpts_for_inlay_hints_query(
4017 &self,
4018 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4019 cx: &mut Context<Editor>,
4020 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4021 let Some(project) = self.project.as_ref() else {
4022 return HashMap::default();
4023 };
4024 let project = project.read(cx);
4025 let multi_buffer = self.buffer().read(cx);
4026 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4027 let multi_buffer_visible_start = self
4028 .scroll_manager
4029 .anchor()
4030 .anchor
4031 .to_point(&multi_buffer_snapshot);
4032 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4033 multi_buffer_visible_start
4034 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4035 Bias::Left,
4036 );
4037 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4038 multi_buffer_snapshot
4039 .range_to_buffer_ranges(multi_buffer_visible_range)
4040 .into_iter()
4041 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4042 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4043 let buffer_file = project::File::from_dyn(buffer.file())?;
4044 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4045 let worktree_entry = buffer_worktree
4046 .read(cx)
4047 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4048 if worktree_entry.is_ignored {
4049 return None;
4050 }
4051
4052 let language = buffer.language()?;
4053 if let Some(restrict_to_languages) = restrict_to_languages {
4054 if !restrict_to_languages.contains(language) {
4055 return None;
4056 }
4057 }
4058 Some((
4059 excerpt_id,
4060 (
4061 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4062 buffer.version().clone(),
4063 excerpt_visible_range,
4064 ),
4065 ))
4066 })
4067 .collect()
4068 }
4069
4070 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4071 TextLayoutDetails {
4072 text_system: window.text_system().clone(),
4073 editor_style: self.style.clone().unwrap(),
4074 rem_size: window.rem_size(),
4075 scroll_anchor: self.scroll_manager.anchor(),
4076 visible_rows: self.visible_line_count(),
4077 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4078 }
4079 }
4080
4081 pub fn splice_inlays(
4082 &self,
4083 to_remove: &[InlayId],
4084 to_insert: Vec<Inlay>,
4085 cx: &mut Context<Self>,
4086 ) {
4087 self.display_map.update(cx, |display_map, cx| {
4088 display_map.splice_inlays(to_remove, to_insert, cx)
4089 });
4090 cx.notify();
4091 }
4092
4093 fn trigger_on_type_formatting(
4094 &self,
4095 input: String,
4096 window: &mut Window,
4097 cx: &mut Context<Self>,
4098 ) -> Option<Task<Result<()>>> {
4099 if input.len() != 1 {
4100 return None;
4101 }
4102
4103 let project = self.project.as_ref()?;
4104 let position = self.selections.newest_anchor().head();
4105 let (buffer, buffer_position) = self
4106 .buffer
4107 .read(cx)
4108 .text_anchor_for_position(position, cx)?;
4109
4110 let settings = language_settings::language_settings(
4111 buffer
4112 .read(cx)
4113 .language_at(buffer_position)
4114 .map(|l| l.name()),
4115 buffer.read(cx).file(),
4116 cx,
4117 );
4118 if !settings.use_on_type_format {
4119 return None;
4120 }
4121
4122 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4123 // hence we do LSP request & edit on host side only — add formats to host's history.
4124 let push_to_lsp_host_history = true;
4125 // If this is not the host, append its history with new edits.
4126 let push_to_client_history = project.read(cx).is_via_collab();
4127
4128 let on_type_formatting = project.update(cx, |project, cx| {
4129 project.on_type_format(
4130 buffer.clone(),
4131 buffer_position,
4132 input,
4133 push_to_lsp_host_history,
4134 cx,
4135 )
4136 });
4137 Some(cx.spawn_in(window, async move |editor, cx| {
4138 if let Some(transaction) = on_type_formatting.await? {
4139 if push_to_client_history {
4140 buffer
4141 .update(cx, |buffer, _| {
4142 buffer.push_transaction(transaction, Instant::now());
4143 })
4144 .ok();
4145 }
4146 editor.update(cx, |editor, cx| {
4147 editor.refresh_document_highlights(cx);
4148 })?;
4149 }
4150 Ok(())
4151 }))
4152 }
4153
4154 pub fn show_word_completions(
4155 &mut self,
4156 _: &ShowWordCompletions,
4157 window: &mut Window,
4158 cx: &mut Context<Self>,
4159 ) {
4160 self.open_completions_menu(true, None, window, cx);
4161 }
4162
4163 pub fn show_completions(
4164 &mut self,
4165 options: &ShowCompletions,
4166 window: &mut Window,
4167 cx: &mut Context<Self>,
4168 ) {
4169 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4170 }
4171
4172 fn open_completions_menu(
4173 &mut self,
4174 ignore_completion_provider: bool,
4175 trigger: Option<&str>,
4176 window: &mut Window,
4177 cx: &mut Context<Self>,
4178 ) {
4179 if self.pending_rename.is_some() {
4180 return;
4181 }
4182 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4183 return;
4184 }
4185
4186 let position = self.selections.newest_anchor().head();
4187 if position.diff_base_anchor.is_some() {
4188 return;
4189 }
4190 let (buffer, buffer_position) =
4191 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4192 output
4193 } else {
4194 return;
4195 };
4196 let buffer_snapshot = buffer.read(cx).snapshot();
4197 let show_completion_documentation = buffer_snapshot
4198 .settings_at(buffer_position, cx)
4199 .show_completion_documentation;
4200
4201 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4202
4203 let trigger_kind = match trigger {
4204 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4205 CompletionTriggerKind::TRIGGER_CHARACTER
4206 }
4207 _ => CompletionTriggerKind::INVOKED,
4208 };
4209 let completion_context = CompletionContext {
4210 trigger_character: trigger.and_then(|trigger| {
4211 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4212 Some(String::from(trigger))
4213 } else {
4214 None
4215 }
4216 }),
4217 trigger_kind,
4218 };
4219
4220 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4221 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4222 let word_to_exclude = buffer_snapshot
4223 .text_for_range(old_range.clone())
4224 .collect::<String>();
4225 (
4226 buffer_snapshot.anchor_before(old_range.start)
4227 ..buffer_snapshot.anchor_after(old_range.end),
4228 Some(word_to_exclude),
4229 )
4230 } else {
4231 (buffer_position..buffer_position, None)
4232 };
4233
4234 let completion_settings = language_settings(
4235 buffer_snapshot
4236 .language_at(buffer_position)
4237 .map(|language| language.name()),
4238 buffer_snapshot.file(),
4239 cx,
4240 )
4241 .completions;
4242
4243 // The document can be large, so stay in reasonable bounds when searching for words,
4244 // otherwise completion pop-up might be slow to appear.
4245 const WORD_LOOKUP_ROWS: u32 = 5_000;
4246 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4247 let min_word_search = buffer_snapshot.clip_point(
4248 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4249 Bias::Left,
4250 );
4251 let max_word_search = buffer_snapshot.clip_point(
4252 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4253 Bias::Right,
4254 );
4255 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4256 ..buffer_snapshot.point_to_offset(max_word_search);
4257
4258 let provider = self
4259 .completion_provider
4260 .as_ref()
4261 .filter(|_| !ignore_completion_provider);
4262 let skip_digits = query
4263 .as_ref()
4264 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4265
4266 let (mut words, provided_completions) = match provider {
4267 Some(provider) => {
4268 let completions = provider.completions(
4269 position.excerpt_id,
4270 &buffer,
4271 buffer_position,
4272 completion_context,
4273 window,
4274 cx,
4275 );
4276
4277 let words = match completion_settings.words {
4278 WordsCompletionMode::Disabled => Task::ready(HashMap::default()),
4279 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4280 .background_spawn(async move {
4281 buffer_snapshot.words_in_range(WordsQuery {
4282 fuzzy_contents: None,
4283 range: word_search_range,
4284 skip_digits,
4285 })
4286 }),
4287 };
4288
4289 (words, completions)
4290 }
4291 None => (
4292 cx.background_spawn(async move {
4293 buffer_snapshot.words_in_range(WordsQuery {
4294 fuzzy_contents: None,
4295 range: word_search_range,
4296 skip_digits,
4297 })
4298 }),
4299 Task::ready(Ok(None)),
4300 ),
4301 };
4302
4303 let sort_completions = provider
4304 .as_ref()
4305 .map_or(true, |provider| provider.sort_completions());
4306
4307 let id = post_inc(&mut self.next_completion_id);
4308 let task = cx.spawn_in(window, async move |editor, cx| {
4309 async move {
4310 editor.update(cx, |this, _| {
4311 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4312 })?;
4313
4314 let mut completions = Vec::new();
4315 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
4316 completions.extend(provided_completions);
4317 if completion_settings.words == WordsCompletionMode::Fallback {
4318 words = Task::ready(HashMap::default());
4319 }
4320 }
4321
4322 let mut words = words.await;
4323 if let Some(word_to_exclude) = &word_to_exclude {
4324 words.remove(word_to_exclude);
4325 }
4326 for lsp_completion in &completions {
4327 words.remove(&lsp_completion.new_text);
4328 }
4329 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
4330 old_range: old_range.clone(),
4331 new_text: word.clone(),
4332 label: CodeLabel::plain(word, None),
4333 icon_path: None,
4334 documentation: None,
4335 source: CompletionSource::BufferWord {
4336 word_range,
4337 resolved: false,
4338 },
4339 confirm: None,
4340 }));
4341
4342 let menu = if completions.is_empty() {
4343 None
4344 } else {
4345 let mut menu = CompletionsMenu::new(
4346 id,
4347 sort_completions,
4348 show_completion_documentation,
4349 ignore_completion_provider,
4350 position,
4351 buffer.clone(),
4352 completions.into(),
4353 );
4354
4355 menu.filter(query.as_deref(), cx.background_executor().clone())
4356 .await;
4357
4358 menu.visible().then_some(menu)
4359 };
4360
4361 editor.update_in(cx, |editor, window, cx| {
4362 match editor.context_menu.borrow().as_ref() {
4363 None => {}
4364 Some(CodeContextMenu::Completions(prev_menu)) => {
4365 if prev_menu.id > id {
4366 return;
4367 }
4368 }
4369 _ => return,
4370 }
4371
4372 if editor.focus_handle.is_focused(window) && menu.is_some() {
4373 let mut menu = menu.unwrap();
4374 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
4375
4376 *editor.context_menu.borrow_mut() =
4377 Some(CodeContextMenu::Completions(menu));
4378
4379 if editor.show_edit_predictions_in_menu() {
4380 editor.update_visible_inline_completion(window, cx);
4381 } else {
4382 editor.discard_inline_completion(false, cx);
4383 }
4384
4385 cx.notify();
4386 } else if editor.completion_tasks.len() <= 1 {
4387 // If there are no more completion tasks and the last menu was
4388 // empty, we should hide it.
4389 let was_hidden = editor.hide_context_menu(window, cx).is_none();
4390 // If it was already hidden and we don't show inline
4391 // completions in the menu, we should also show the
4392 // inline-completion when available.
4393 if was_hidden && editor.show_edit_predictions_in_menu() {
4394 editor.update_visible_inline_completion(window, cx);
4395 }
4396 }
4397 })?;
4398
4399 anyhow::Ok(())
4400 }
4401 .log_err()
4402 .await
4403 });
4404
4405 self.completion_tasks.push((id, task));
4406 }
4407
4408 #[cfg(feature = "test-support")]
4409 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
4410 let menu = self.context_menu.borrow();
4411 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
4412 let completions = menu.completions.borrow();
4413 Some(completions.to_vec())
4414 } else {
4415 None
4416 }
4417 }
4418
4419 pub fn confirm_completion(
4420 &mut self,
4421 action: &ConfirmCompletion,
4422 window: &mut Window,
4423 cx: &mut Context<Self>,
4424 ) -> Option<Task<Result<()>>> {
4425 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
4426 }
4427
4428 pub fn compose_completion(
4429 &mut self,
4430 action: &ComposeCompletion,
4431 window: &mut Window,
4432 cx: &mut Context<Self>,
4433 ) -> Option<Task<Result<()>>> {
4434 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
4435 }
4436
4437 fn do_completion(
4438 &mut self,
4439 item_ix: Option<usize>,
4440 intent: CompletionIntent,
4441 window: &mut Window,
4442 cx: &mut Context<Editor>,
4443 ) -> Option<Task<Result<()>>> {
4444 let completions_menu =
4445 if let CodeContextMenu::Completions(menu) = self.hide_context_menu(window, cx)? {
4446 menu
4447 } else {
4448 return None;
4449 };
4450
4451 let candidate_id = {
4452 let entries = completions_menu.entries.borrow();
4453 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
4454 if self.show_edit_predictions_in_menu() {
4455 self.discard_inline_completion(true, cx);
4456 }
4457 mat.candidate_id
4458 };
4459
4460 let buffer_handle = completions_menu.buffer;
4461 let completion = completions_menu
4462 .completions
4463 .borrow()
4464 .get(candidate_id)?
4465 .clone();
4466 cx.stop_propagation();
4467
4468 if self.selections.newest_anchor().start.buffer_id
4469 != Some(buffer_handle.read(cx).remote_id())
4470 {
4471 return None;
4472 }
4473
4474 let snippet;
4475 let new_text;
4476 if completion.is_snippet() {
4477 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
4478 new_text = snippet.as_ref().unwrap().text.clone();
4479 } else {
4480 snippet = None;
4481 new_text = completion.new_text.clone();
4482 };
4483
4484 let newest_selection = self.selections.newest::<usize>(cx);
4485 let selections = self.selections.all::<usize>(cx);
4486 let buffer = buffer_handle.read(cx);
4487 let old_range = completion.old_range.to_offset(buffer);
4488 let old_text = buffer.text_for_range(old_range.clone()).collect::<String>();
4489
4490 let start_distance = newest_selection.start.saturating_sub(old_range.start);
4491 let end_distance = old_range.end.saturating_sub(newest_selection.end);
4492 let mut common_prefix_len = old_text
4493 .bytes()
4494 .zip(new_text.bytes())
4495 .take_while(|(a, b)| a == b)
4496 .count();
4497
4498 let snapshot = self.buffer.read(cx).snapshot(cx);
4499 let mut range_to_replace: Option<Range<isize>> = None;
4500 let mut ranges = Vec::new();
4501 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4502 for selection in &selections {
4503 if snapshot.contains_str_at(selection.start.saturating_sub(start_distance), &old_text) {
4504 let start = selection.start.saturating_sub(start_distance);
4505 let end = selection.end + end_distance;
4506 if selection.id == newest_selection.id {
4507 range_to_replace = Some(
4508 ((start + common_prefix_len) as isize - selection.start as isize)
4509 ..(end as isize - selection.start as isize),
4510 );
4511 }
4512 ranges.push(start + common_prefix_len..end);
4513 } else {
4514 common_prefix_len = 0;
4515 ranges.clear();
4516 ranges.extend(selections.iter().map(|s| {
4517 if s.id == newest_selection.id {
4518 range_to_replace = Some(
4519 old_range.start.to_offset_utf16(&snapshot).0 as isize
4520 - selection.start as isize
4521 ..old_range.end.to_offset_utf16(&snapshot).0 as isize
4522 - selection.start as isize,
4523 );
4524 old_range.clone()
4525 } else {
4526 s.start..s.end
4527 }
4528 }));
4529 break;
4530 }
4531 if !self.linked_edit_ranges.is_empty() {
4532 let start_anchor = snapshot.anchor_before(selection.head());
4533 let end_anchor = snapshot.anchor_after(selection.tail());
4534 if let Some(ranges) = self
4535 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
4536 {
4537 for (buffer, edits) in ranges {
4538 linked_edits.entry(buffer.clone()).or_default().extend(
4539 edits
4540 .into_iter()
4541 .map(|range| (range, new_text[common_prefix_len..].to_owned())),
4542 );
4543 }
4544 }
4545 }
4546 }
4547 let text = &new_text[common_prefix_len..];
4548
4549 cx.emit(EditorEvent::InputHandled {
4550 utf16_range_to_replace: range_to_replace,
4551 text: text.into(),
4552 });
4553
4554 self.transact(window, cx, |this, window, cx| {
4555 if let Some(mut snippet) = snippet {
4556 snippet.text = text.to_string();
4557 for tabstop in snippet
4558 .tabstops
4559 .iter_mut()
4560 .flat_map(|tabstop| tabstop.ranges.iter_mut())
4561 {
4562 tabstop.start -= common_prefix_len as isize;
4563 tabstop.end -= common_prefix_len as isize;
4564 }
4565
4566 this.insert_snippet(&ranges, snippet, window, cx).log_err();
4567 } else {
4568 this.buffer.update(cx, |buffer, cx| {
4569 let edits = ranges.iter().map(|range| (range.clone(), text));
4570 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4571 });
4572 }
4573 for (buffer, edits) in linked_edits {
4574 buffer.update(cx, |buffer, cx| {
4575 let snapshot = buffer.snapshot();
4576 let edits = edits
4577 .into_iter()
4578 .map(|(range, text)| {
4579 use text::ToPoint as TP;
4580 let end_point = TP::to_point(&range.end, &snapshot);
4581 let start_point = TP::to_point(&range.start, &snapshot);
4582 (start_point..end_point, text)
4583 })
4584 .sorted_by_key(|(range, _)| range.start);
4585 buffer.edit(edits, None, cx);
4586 })
4587 }
4588
4589 this.refresh_inline_completion(true, false, window, cx);
4590 });
4591
4592 let show_new_completions_on_confirm = completion
4593 .confirm
4594 .as_ref()
4595 .map_or(false, |confirm| confirm(intent, window, cx));
4596 if show_new_completions_on_confirm {
4597 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
4598 }
4599
4600 let provider = self.completion_provider.as_ref()?;
4601 drop(completion);
4602 let apply_edits = provider.apply_additional_edits_for_completion(
4603 buffer_handle,
4604 completions_menu.completions.clone(),
4605 candidate_id,
4606 true,
4607 cx,
4608 );
4609
4610 let editor_settings = EditorSettings::get_global(cx);
4611 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
4612 // After the code completion is finished, users often want to know what signatures are needed.
4613 // so we should automatically call signature_help
4614 self.show_signature_help(&ShowSignatureHelp, window, cx);
4615 }
4616
4617 Some(cx.foreground_executor().spawn(async move {
4618 apply_edits.await?;
4619 Ok(())
4620 }))
4621 }
4622
4623 pub fn toggle_code_actions(
4624 &mut self,
4625 action: &ToggleCodeActions,
4626 window: &mut Window,
4627 cx: &mut Context<Self>,
4628 ) {
4629 let mut context_menu = self.context_menu.borrow_mut();
4630 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
4631 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
4632 // Toggle if we're selecting the same one
4633 *context_menu = None;
4634 cx.notify();
4635 return;
4636 } else {
4637 // Otherwise, clear it and start a new one
4638 *context_menu = None;
4639 cx.notify();
4640 }
4641 }
4642 drop(context_menu);
4643 let snapshot = self.snapshot(window, cx);
4644 let deployed_from_indicator = action.deployed_from_indicator;
4645 let mut task = self.code_actions_task.take();
4646 let action = action.clone();
4647 cx.spawn_in(window, async move |editor, cx| {
4648 while let Some(prev_task) = task {
4649 prev_task.await.log_err();
4650 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
4651 }
4652
4653 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
4654 if editor.focus_handle.is_focused(window) {
4655 let multibuffer_point = action
4656 .deployed_from_indicator
4657 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
4658 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
4659 let (buffer, buffer_row) = snapshot
4660 .buffer_snapshot
4661 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
4662 .and_then(|(buffer_snapshot, range)| {
4663 editor
4664 .buffer
4665 .read(cx)
4666 .buffer(buffer_snapshot.remote_id())
4667 .map(|buffer| (buffer, range.start.row))
4668 })?;
4669 let (_, code_actions) = editor
4670 .available_code_actions
4671 .clone()
4672 .and_then(|(location, code_actions)| {
4673 let snapshot = location.buffer.read(cx).snapshot();
4674 let point_range = location.range.to_point(&snapshot);
4675 let point_range = point_range.start.row..=point_range.end.row;
4676 if point_range.contains(&buffer_row) {
4677 Some((location, code_actions))
4678 } else {
4679 None
4680 }
4681 })
4682 .unzip();
4683 let buffer_id = buffer.read(cx).remote_id();
4684 let tasks = editor
4685 .tasks
4686 .get(&(buffer_id, buffer_row))
4687 .map(|t| Arc::new(t.to_owned()));
4688 if tasks.is_none() && code_actions.is_none() {
4689 return None;
4690 }
4691
4692 editor.completion_tasks.clear();
4693 editor.discard_inline_completion(false, cx);
4694 let task_context =
4695 tasks
4696 .as_ref()
4697 .zip(editor.project.clone())
4698 .map(|(tasks, project)| {
4699 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
4700 });
4701
4702 Some(cx.spawn_in(window, async move |editor, cx| {
4703 let task_context = match task_context {
4704 Some(task_context) => task_context.await,
4705 None => None,
4706 };
4707 let resolved_tasks =
4708 tasks.zip(task_context).map(|(tasks, task_context)| {
4709 Rc::new(ResolvedTasks {
4710 templates: tasks.resolve(&task_context).collect(),
4711 position: snapshot.buffer_snapshot.anchor_before(Point::new(
4712 multibuffer_point.row,
4713 tasks.column,
4714 )),
4715 })
4716 });
4717 let spawn_straight_away = resolved_tasks
4718 .as_ref()
4719 .map_or(false, |tasks| tasks.templates.len() == 1)
4720 && code_actions
4721 .as_ref()
4722 .map_or(true, |actions| actions.is_empty());
4723 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
4724 *editor.context_menu.borrow_mut() =
4725 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
4726 buffer,
4727 actions: CodeActionContents {
4728 tasks: resolved_tasks,
4729 actions: code_actions,
4730 },
4731 selected_item: Default::default(),
4732 scroll_handle: UniformListScrollHandle::default(),
4733 deployed_from_indicator,
4734 }));
4735 if spawn_straight_away {
4736 if let Some(task) = editor.confirm_code_action(
4737 &ConfirmCodeAction { item_ix: Some(0) },
4738 window,
4739 cx,
4740 ) {
4741 cx.notify();
4742 return task;
4743 }
4744 }
4745 cx.notify();
4746 Task::ready(Ok(()))
4747 }) {
4748 task.await
4749 } else {
4750 Ok(())
4751 }
4752 }))
4753 } else {
4754 Some(Task::ready(Ok(())))
4755 }
4756 })?;
4757 if let Some(task) = spawned_test_task {
4758 task.await?;
4759 }
4760
4761 Ok::<_, anyhow::Error>(())
4762 })
4763 .detach_and_log_err(cx);
4764 }
4765
4766 pub fn confirm_code_action(
4767 &mut self,
4768 action: &ConfirmCodeAction,
4769 window: &mut Window,
4770 cx: &mut Context<Self>,
4771 ) -> Option<Task<Result<()>>> {
4772 let actions_menu =
4773 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
4774 menu
4775 } else {
4776 return None;
4777 };
4778 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
4779 let action = actions_menu.actions.get(action_ix)?;
4780 let title = action.label();
4781 let buffer = actions_menu.buffer;
4782 let workspace = self.workspace()?;
4783
4784 match action {
4785 CodeActionsItem::Task(task_source_kind, resolved_task) => {
4786 workspace.update(cx, |workspace, cx| {
4787 workspace::tasks::schedule_resolved_task(
4788 workspace,
4789 task_source_kind,
4790 resolved_task,
4791 false,
4792 cx,
4793 );
4794
4795 Some(Task::ready(Ok(())))
4796 })
4797 }
4798 CodeActionsItem::CodeAction {
4799 excerpt_id,
4800 action,
4801 provider,
4802 } => {
4803 let apply_code_action =
4804 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
4805 let workspace = workspace.downgrade();
4806 Some(cx.spawn_in(window, async move |editor, cx| {
4807 let project_transaction = apply_code_action.await?;
4808 Self::open_project_transaction(
4809 &editor,
4810 workspace,
4811 project_transaction,
4812 title,
4813 cx,
4814 )
4815 .await
4816 }))
4817 }
4818 }
4819 }
4820
4821 pub async fn open_project_transaction(
4822 this: &WeakEntity<Editor>,
4823 workspace: WeakEntity<Workspace>,
4824 transaction: ProjectTransaction,
4825 title: String,
4826 cx: &mut AsyncWindowContext,
4827 ) -> Result<()> {
4828 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
4829 cx.update(|_, cx| {
4830 entries.sort_unstable_by_key(|(buffer, _)| {
4831 buffer.read(cx).file().map(|f| f.path().clone())
4832 });
4833 })?;
4834
4835 // If the project transaction's edits are all contained within this editor, then
4836 // avoid opening a new editor to display them.
4837
4838 if let Some((buffer, transaction)) = entries.first() {
4839 if entries.len() == 1 {
4840 let excerpt = this.update(cx, |editor, cx| {
4841 editor
4842 .buffer()
4843 .read(cx)
4844 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
4845 })?;
4846 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
4847 if excerpted_buffer == *buffer {
4848 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
4849 let excerpt_range = excerpt_range.to_offset(buffer);
4850 buffer
4851 .edited_ranges_for_transaction::<usize>(transaction)
4852 .all(|range| {
4853 excerpt_range.start <= range.start
4854 && excerpt_range.end >= range.end
4855 })
4856 })?;
4857
4858 if all_edits_within_excerpt {
4859 return Ok(());
4860 }
4861 }
4862 }
4863 }
4864 } else {
4865 return Ok(());
4866 }
4867
4868 let mut ranges_to_highlight = Vec::new();
4869 let excerpt_buffer = cx.new(|cx| {
4870 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
4871 for (buffer_handle, transaction) in &entries {
4872 let buffer = buffer_handle.read(cx);
4873 ranges_to_highlight.extend(
4874 multibuffer.push_excerpts_with_context_lines(
4875 buffer_handle.clone(),
4876 buffer
4877 .edited_ranges_for_transaction::<usize>(transaction)
4878 .collect(),
4879 DEFAULT_MULTIBUFFER_CONTEXT,
4880 cx,
4881 ),
4882 );
4883 }
4884 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
4885 multibuffer
4886 })?;
4887
4888 workspace.update_in(cx, |workspace, window, cx| {
4889 let project = workspace.project().clone();
4890 let editor =
4891 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
4892 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
4893 editor.update(cx, |editor, cx| {
4894 editor.highlight_background::<Self>(
4895 &ranges_to_highlight,
4896 |theme| theme.editor_highlighted_line_background,
4897 cx,
4898 );
4899 });
4900 })?;
4901
4902 Ok(())
4903 }
4904
4905 pub fn clear_code_action_providers(&mut self) {
4906 self.code_action_providers.clear();
4907 self.available_code_actions.take();
4908 }
4909
4910 pub fn add_code_action_provider(
4911 &mut self,
4912 provider: Rc<dyn CodeActionProvider>,
4913 window: &mut Window,
4914 cx: &mut Context<Self>,
4915 ) {
4916 if self
4917 .code_action_providers
4918 .iter()
4919 .any(|existing_provider| existing_provider.id() == provider.id())
4920 {
4921 return;
4922 }
4923
4924 self.code_action_providers.push(provider);
4925 self.refresh_code_actions(window, cx);
4926 }
4927
4928 pub fn remove_code_action_provider(
4929 &mut self,
4930 id: Arc<str>,
4931 window: &mut Window,
4932 cx: &mut Context<Self>,
4933 ) {
4934 self.code_action_providers
4935 .retain(|provider| provider.id() != id);
4936 self.refresh_code_actions(window, cx);
4937 }
4938
4939 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
4940 let buffer = self.buffer.read(cx);
4941 let newest_selection = self.selections.newest_anchor().clone();
4942 if newest_selection.head().diff_base_anchor.is_some() {
4943 return None;
4944 }
4945 let (start_buffer, start) = buffer.text_anchor_for_position(newest_selection.start, cx)?;
4946 let (end_buffer, end) = buffer.text_anchor_for_position(newest_selection.end, cx)?;
4947 if start_buffer != end_buffer {
4948 return None;
4949 }
4950
4951 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
4952 cx.background_executor()
4953 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
4954 .await;
4955
4956 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
4957 let providers = this.code_action_providers.clone();
4958 let tasks = this
4959 .code_action_providers
4960 .iter()
4961 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
4962 .collect::<Vec<_>>();
4963 (providers, tasks)
4964 })?;
4965
4966 let mut actions = Vec::new();
4967 for (provider, provider_actions) in
4968 providers.into_iter().zip(future::join_all(tasks).await)
4969 {
4970 if let Some(provider_actions) = provider_actions.log_err() {
4971 actions.extend(provider_actions.into_iter().map(|action| {
4972 AvailableCodeAction {
4973 excerpt_id: newest_selection.start.excerpt_id,
4974 action,
4975 provider: provider.clone(),
4976 }
4977 }));
4978 }
4979 }
4980
4981 this.update(cx, |this, cx| {
4982 this.available_code_actions = if actions.is_empty() {
4983 None
4984 } else {
4985 Some((
4986 Location {
4987 buffer: start_buffer,
4988 range: start..end,
4989 },
4990 actions.into(),
4991 ))
4992 };
4993 cx.notify();
4994 })
4995 }));
4996 None
4997 }
4998
4999 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5000 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5001 self.show_git_blame_inline = false;
5002
5003 self.show_git_blame_inline_delay_task =
5004 Some(cx.spawn_in(window, async move |this, cx| {
5005 cx.background_executor().timer(delay).await;
5006
5007 this.update(cx, |this, cx| {
5008 this.show_git_blame_inline = true;
5009 cx.notify();
5010 })
5011 .log_err();
5012 }));
5013 }
5014 }
5015
5016 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5017 if self.pending_rename.is_some() {
5018 return None;
5019 }
5020
5021 let provider = self.semantics_provider.clone()?;
5022 let buffer = self.buffer.read(cx);
5023 let newest_selection = self.selections.newest_anchor().clone();
5024 let cursor_position = newest_selection.head();
5025 let (cursor_buffer, cursor_buffer_position) =
5026 buffer.text_anchor_for_position(cursor_position, cx)?;
5027 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5028 if cursor_buffer != tail_buffer {
5029 return None;
5030 }
5031 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5032 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5033 cx.background_executor()
5034 .timer(Duration::from_millis(debounce))
5035 .await;
5036
5037 let highlights = if let Some(highlights) = cx
5038 .update(|cx| {
5039 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5040 })
5041 .ok()
5042 .flatten()
5043 {
5044 highlights.await.log_err()
5045 } else {
5046 None
5047 };
5048
5049 if let Some(highlights) = highlights {
5050 this.update(cx, |this, cx| {
5051 if this.pending_rename.is_some() {
5052 return;
5053 }
5054
5055 let buffer_id = cursor_position.buffer_id;
5056 let buffer = this.buffer.read(cx);
5057 if !buffer
5058 .text_anchor_for_position(cursor_position, cx)
5059 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5060 {
5061 return;
5062 }
5063
5064 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5065 let mut write_ranges = Vec::new();
5066 let mut read_ranges = Vec::new();
5067 for highlight in highlights {
5068 for (excerpt_id, excerpt_range) in
5069 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5070 {
5071 let start = highlight
5072 .range
5073 .start
5074 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5075 let end = highlight
5076 .range
5077 .end
5078 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5079 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5080 continue;
5081 }
5082
5083 let range = Anchor {
5084 buffer_id,
5085 excerpt_id,
5086 text_anchor: start,
5087 diff_base_anchor: None,
5088 }..Anchor {
5089 buffer_id,
5090 excerpt_id,
5091 text_anchor: end,
5092 diff_base_anchor: None,
5093 };
5094 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5095 write_ranges.push(range);
5096 } else {
5097 read_ranges.push(range);
5098 }
5099 }
5100 }
5101
5102 this.highlight_background::<DocumentHighlightRead>(
5103 &read_ranges,
5104 |theme| theme.editor_document_highlight_read_background,
5105 cx,
5106 );
5107 this.highlight_background::<DocumentHighlightWrite>(
5108 &write_ranges,
5109 |theme| theme.editor_document_highlight_write_background,
5110 cx,
5111 );
5112 cx.notify();
5113 })
5114 .log_err();
5115 }
5116 }));
5117 None
5118 }
5119
5120 pub fn refresh_selected_text_highlights(
5121 &mut self,
5122 window: &mut Window,
5123 cx: &mut Context<Editor>,
5124 ) {
5125 if matches!(self.mode, EditorMode::SingleLine { .. }) {
5126 return;
5127 }
5128 self.selection_highlight_task.take();
5129 if !EditorSettings::get_global(cx).selection_highlight {
5130 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5131 return;
5132 }
5133 if self.selections.count() != 1 || self.selections.line_mode {
5134 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5135 return;
5136 }
5137 let selection = self.selections.newest::<Point>(cx);
5138 if selection.is_empty() || selection.start.row != selection.end.row {
5139 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5140 return;
5141 }
5142 let debounce = EditorSettings::get_global(cx).selection_highlight_debounce;
5143 self.selection_highlight_task = Some(cx.spawn_in(window, async move |editor, cx| {
5144 cx.background_executor()
5145 .timer(Duration::from_millis(debounce))
5146 .await;
5147 let Some(Some(matches_task)) = editor
5148 .update_in(cx, |editor, _, cx| {
5149 if editor.selections.count() != 1 || editor.selections.line_mode {
5150 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5151 return None;
5152 }
5153 let selection = editor.selections.newest::<Point>(cx);
5154 if selection.is_empty() || selection.start.row != selection.end.row {
5155 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5156 return None;
5157 }
5158 let buffer = editor.buffer().read(cx).snapshot(cx);
5159 let query = buffer.text_for_range(selection.range()).collect::<String>();
5160 if query.trim().is_empty() {
5161 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5162 return None;
5163 }
5164 Some(cx.background_spawn(async move {
5165 let mut ranges = Vec::new();
5166 let selection_anchors = selection.range().to_anchors(&buffer);
5167 for range in [buffer.anchor_before(0)..buffer.anchor_after(buffer.len())] {
5168 for (search_buffer, search_range, excerpt_id) in
5169 buffer.range_to_buffer_ranges(range)
5170 {
5171 ranges.extend(
5172 project::search::SearchQuery::text(
5173 query.clone(),
5174 false,
5175 false,
5176 false,
5177 Default::default(),
5178 Default::default(),
5179 None,
5180 )
5181 .unwrap()
5182 .search(search_buffer, Some(search_range.clone()))
5183 .await
5184 .into_iter()
5185 .filter_map(
5186 |match_range| {
5187 let start = search_buffer.anchor_after(
5188 search_range.start + match_range.start,
5189 );
5190 let end = search_buffer.anchor_before(
5191 search_range.start + match_range.end,
5192 );
5193 let range = Anchor::range_in_buffer(
5194 excerpt_id,
5195 search_buffer.remote_id(),
5196 start..end,
5197 );
5198 (range != selection_anchors).then_some(range)
5199 },
5200 ),
5201 );
5202 }
5203 }
5204 ranges
5205 }))
5206 })
5207 .log_err()
5208 else {
5209 return;
5210 };
5211 let matches = matches_task.await;
5212 editor
5213 .update_in(cx, |editor, _, cx| {
5214 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5215 if !matches.is_empty() {
5216 editor.highlight_background::<SelectedTextHighlight>(
5217 &matches,
5218 |theme| theme.editor_document_highlight_bracket_background,
5219 cx,
5220 )
5221 }
5222 })
5223 .log_err();
5224 }));
5225 }
5226
5227 pub fn refresh_inline_completion(
5228 &mut self,
5229 debounce: bool,
5230 user_requested: bool,
5231 window: &mut Window,
5232 cx: &mut Context<Self>,
5233 ) -> Option<()> {
5234 let provider = self.edit_prediction_provider()?;
5235 let cursor = self.selections.newest_anchor().head();
5236 let (buffer, cursor_buffer_position) =
5237 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5238
5239 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
5240 self.discard_inline_completion(false, cx);
5241 return None;
5242 }
5243
5244 if !user_requested
5245 && (!self.should_show_edit_predictions()
5246 || !self.is_focused(window)
5247 || buffer.read(cx).is_empty())
5248 {
5249 self.discard_inline_completion(false, cx);
5250 return None;
5251 }
5252
5253 self.update_visible_inline_completion(window, cx);
5254 provider.refresh(
5255 self.project.clone(),
5256 buffer,
5257 cursor_buffer_position,
5258 debounce,
5259 cx,
5260 );
5261 Some(())
5262 }
5263
5264 fn show_edit_predictions_in_menu(&self) -> bool {
5265 match self.edit_prediction_settings {
5266 EditPredictionSettings::Disabled => false,
5267 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
5268 }
5269 }
5270
5271 pub fn edit_predictions_enabled(&self) -> bool {
5272 match self.edit_prediction_settings {
5273 EditPredictionSettings::Disabled => false,
5274 EditPredictionSettings::Enabled { .. } => true,
5275 }
5276 }
5277
5278 fn edit_prediction_requires_modifier(&self) -> bool {
5279 match self.edit_prediction_settings {
5280 EditPredictionSettings::Disabled => false,
5281 EditPredictionSettings::Enabled {
5282 preview_requires_modifier,
5283 ..
5284 } => preview_requires_modifier,
5285 }
5286 }
5287
5288 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
5289 if self.edit_prediction_provider.is_none() {
5290 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5291 } else {
5292 let selection = self.selections.newest_anchor();
5293 let cursor = selection.head();
5294
5295 if let Some((buffer, cursor_buffer_position)) =
5296 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5297 {
5298 self.edit_prediction_settings =
5299 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5300 }
5301 }
5302 }
5303
5304 fn edit_prediction_settings_at_position(
5305 &self,
5306 buffer: &Entity<Buffer>,
5307 buffer_position: language::Anchor,
5308 cx: &App,
5309 ) -> EditPredictionSettings {
5310 if self.mode != EditorMode::Full
5311 || !self.show_inline_completions_override.unwrap_or(true)
5312 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
5313 {
5314 return EditPredictionSettings::Disabled;
5315 }
5316
5317 let buffer = buffer.read(cx);
5318
5319 let file = buffer.file();
5320
5321 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
5322 return EditPredictionSettings::Disabled;
5323 };
5324
5325 let by_provider = matches!(
5326 self.menu_inline_completions_policy,
5327 MenuInlineCompletionsPolicy::ByProvider
5328 );
5329
5330 let show_in_menu = by_provider
5331 && self
5332 .edit_prediction_provider
5333 .as_ref()
5334 .map_or(false, |provider| {
5335 provider.provider.show_completions_in_menu()
5336 });
5337
5338 let preview_requires_modifier =
5339 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
5340
5341 EditPredictionSettings::Enabled {
5342 show_in_menu,
5343 preview_requires_modifier,
5344 }
5345 }
5346
5347 fn should_show_edit_predictions(&self) -> bool {
5348 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
5349 }
5350
5351 pub fn edit_prediction_preview_is_active(&self) -> bool {
5352 matches!(
5353 self.edit_prediction_preview,
5354 EditPredictionPreview::Active { .. }
5355 )
5356 }
5357
5358 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
5359 let cursor = self.selections.newest_anchor().head();
5360 if let Some((buffer, cursor_position)) =
5361 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5362 {
5363 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
5364 } else {
5365 false
5366 }
5367 }
5368
5369 fn edit_predictions_enabled_in_buffer(
5370 &self,
5371 buffer: &Entity<Buffer>,
5372 buffer_position: language::Anchor,
5373 cx: &App,
5374 ) -> bool {
5375 maybe!({
5376 if self.read_only(cx) {
5377 return Some(false);
5378 }
5379 let provider = self.edit_prediction_provider()?;
5380 if !provider.is_enabled(&buffer, buffer_position, cx) {
5381 return Some(false);
5382 }
5383 let buffer = buffer.read(cx);
5384 let Some(file) = buffer.file() else {
5385 return Some(true);
5386 };
5387 let settings = all_language_settings(Some(file), cx);
5388 Some(settings.edit_predictions_enabled_for_file(file, cx))
5389 })
5390 .unwrap_or(false)
5391 }
5392
5393 fn cycle_inline_completion(
5394 &mut self,
5395 direction: Direction,
5396 window: &mut Window,
5397 cx: &mut Context<Self>,
5398 ) -> Option<()> {
5399 let provider = self.edit_prediction_provider()?;
5400 let cursor = self.selections.newest_anchor().head();
5401 let (buffer, cursor_buffer_position) =
5402 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5403 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
5404 return None;
5405 }
5406
5407 provider.cycle(buffer, cursor_buffer_position, direction, cx);
5408 self.update_visible_inline_completion(window, cx);
5409
5410 Some(())
5411 }
5412
5413 pub fn show_inline_completion(
5414 &mut self,
5415 _: &ShowEditPrediction,
5416 window: &mut Window,
5417 cx: &mut Context<Self>,
5418 ) {
5419 if !self.has_active_inline_completion() {
5420 self.refresh_inline_completion(false, true, window, cx);
5421 return;
5422 }
5423
5424 self.update_visible_inline_completion(window, cx);
5425 }
5426
5427 pub fn display_cursor_names(
5428 &mut self,
5429 _: &DisplayCursorNames,
5430 window: &mut Window,
5431 cx: &mut Context<Self>,
5432 ) {
5433 self.show_cursor_names(window, cx);
5434 }
5435
5436 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5437 self.show_cursor_names = true;
5438 cx.notify();
5439 cx.spawn_in(window, async move |this, cx| {
5440 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
5441 this.update(cx, |this, cx| {
5442 this.show_cursor_names = false;
5443 cx.notify()
5444 })
5445 .ok()
5446 })
5447 .detach();
5448 }
5449
5450 pub fn next_edit_prediction(
5451 &mut self,
5452 _: &NextEditPrediction,
5453 window: &mut Window,
5454 cx: &mut Context<Self>,
5455 ) {
5456 if self.has_active_inline_completion() {
5457 self.cycle_inline_completion(Direction::Next, window, cx);
5458 } else {
5459 let is_copilot_disabled = self
5460 .refresh_inline_completion(false, true, window, cx)
5461 .is_none();
5462 if is_copilot_disabled {
5463 cx.propagate();
5464 }
5465 }
5466 }
5467
5468 pub fn previous_edit_prediction(
5469 &mut self,
5470 _: &PreviousEditPrediction,
5471 window: &mut Window,
5472 cx: &mut Context<Self>,
5473 ) {
5474 if self.has_active_inline_completion() {
5475 self.cycle_inline_completion(Direction::Prev, window, cx);
5476 } else {
5477 let is_copilot_disabled = self
5478 .refresh_inline_completion(false, true, window, cx)
5479 .is_none();
5480 if is_copilot_disabled {
5481 cx.propagate();
5482 }
5483 }
5484 }
5485
5486 pub fn accept_edit_prediction(
5487 &mut self,
5488 _: &AcceptEditPrediction,
5489 window: &mut Window,
5490 cx: &mut Context<Self>,
5491 ) {
5492 if self.show_edit_predictions_in_menu() {
5493 self.hide_context_menu(window, cx);
5494 }
5495
5496 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5497 return;
5498 };
5499
5500 self.report_inline_completion_event(
5501 active_inline_completion.completion_id.clone(),
5502 true,
5503 cx,
5504 );
5505
5506 match &active_inline_completion.completion {
5507 InlineCompletion::Move { target, .. } => {
5508 let target = *target;
5509
5510 if let Some(position_map) = &self.last_position_map {
5511 if position_map
5512 .visible_row_range
5513 .contains(&target.to_display_point(&position_map.snapshot).row())
5514 || !self.edit_prediction_requires_modifier()
5515 {
5516 self.unfold_ranges(&[target..target], true, false, cx);
5517 // Note that this is also done in vim's handler of the Tab action.
5518 self.change_selections(
5519 Some(Autoscroll::newest()),
5520 window,
5521 cx,
5522 |selections| {
5523 selections.select_anchor_ranges([target..target]);
5524 },
5525 );
5526 self.clear_row_highlights::<EditPredictionPreview>();
5527
5528 self.edit_prediction_preview
5529 .set_previous_scroll_position(None);
5530 } else {
5531 self.edit_prediction_preview
5532 .set_previous_scroll_position(Some(
5533 position_map.snapshot.scroll_anchor,
5534 ));
5535
5536 self.highlight_rows::<EditPredictionPreview>(
5537 target..target,
5538 cx.theme().colors().editor_highlighted_line_background,
5539 true,
5540 cx,
5541 );
5542 self.request_autoscroll(Autoscroll::fit(), cx);
5543 }
5544 }
5545 }
5546 InlineCompletion::Edit { edits, .. } => {
5547 if let Some(provider) = self.edit_prediction_provider() {
5548 provider.accept(cx);
5549 }
5550
5551 let snapshot = self.buffer.read(cx).snapshot(cx);
5552 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
5553
5554 self.buffer.update(cx, |buffer, cx| {
5555 buffer.edit(edits.iter().cloned(), None, cx)
5556 });
5557
5558 self.change_selections(None, window, cx, |s| {
5559 s.select_anchor_ranges([last_edit_end..last_edit_end])
5560 });
5561
5562 self.update_visible_inline_completion(window, cx);
5563 if self.active_inline_completion.is_none() {
5564 self.refresh_inline_completion(true, true, window, cx);
5565 }
5566
5567 cx.notify();
5568 }
5569 }
5570
5571 self.edit_prediction_requires_modifier_in_indent_conflict = false;
5572 }
5573
5574 pub fn accept_partial_inline_completion(
5575 &mut self,
5576 _: &AcceptPartialEditPrediction,
5577 window: &mut Window,
5578 cx: &mut Context<Self>,
5579 ) {
5580 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5581 return;
5582 };
5583 if self.selections.count() != 1 {
5584 return;
5585 }
5586
5587 self.report_inline_completion_event(
5588 active_inline_completion.completion_id.clone(),
5589 true,
5590 cx,
5591 );
5592
5593 match &active_inline_completion.completion {
5594 InlineCompletion::Move { target, .. } => {
5595 let target = *target;
5596 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
5597 selections.select_anchor_ranges([target..target]);
5598 });
5599 }
5600 InlineCompletion::Edit { edits, .. } => {
5601 // Find an insertion that starts at the cursor position.
5602 let snapshot = self.buffer.read(cx).snapshot(cx);
5603 let cursor_offset = self.selections.newest::<usize>(cx).head();
5604 let insertion = edits.iter().find_map(|(range, text)| {
5605 let range = range.to_offset(&snapshot);
5606 if range.is_empty() && range.start == cursor_offset {
5607 Some(text)
5608 } else {
5609 None
5610 }
5611 });
5612
5613 if let Some(text) = insertion {
5614 let mut partial_completion = text
5615 .chars()
5616 .by_ref()
5617 .take_while(|c| c.is_alphabetic())
5618 .collect::<String>();
5619 if partial_completion.is_empty() {
5620 partial_completion = text
5621 .chars()
5622 .by_ref()
5623 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
5624 .collect::<String>();
5625 }
5626
5627 cx.emit(EditorEvent::InputHandled {
5628 utf16_range_to_replace: None,
5629 text: partial_completion.clone().into(),
5630 });
5631
5632 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
5633
5634 self.refresh_inline_completion(true, true, window, cx);
5635 cx.notify();
5636 } else {
5637 self.accept_edit_prediction(&Default::default(), window, cx);
5638 }
5639 }
5640 }
5641 }
5642
5643 fn discard_inline_completion(
5644 &mut self,
5645 should_report_inline_completion_event: bool,
5646 cx: &mut Context<Self>,
5647 ) -> bool {
5648 if should_report_inline_completion_event {
5649 let completion_id = self
5650 .active_inline_completion
5651 .as_ref()
5652 .and_then(|active_completion| active_completion.completion_id.clone());
5653
5654 self.report_inline_completion_event(completion_id, false, cx);
5655 }
5656
5657 if let Some(provider) = self.edit_prediction_provider() {
5658 provider.discard(cx);
5659 }
5660
5661 self.take_active_inline_completion(cx)
5662 }
5663
5664 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
5665 let Some(provider) = self.edit_prediction_provider() else {
5666 return;
5667 };
5668
5669 let Some((_, buffer, _)) = self
5670 .buffer
5671 .read(cx)
5672 .excerpt_containing(self.selections.newest_anchor().head(), cx)
5673 else {
5674 return;
5675 };
5676
5677 let extension = buffer
5678 .read(cx)
5679 .file()
5680 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
5681
5682 let event_type = match accepted {
5683 true => "Edit Prediction Accepted",
5684 false => "Edit Prediction Discarded",
5685 };
5686 telemetry::event!(
5687 event_type,
5688 provider = provider.name(),
5689 prediction_id = id,
5690 suggestion_accepted = accepted,
5691 file_extension = extension,
5692 );
5693 }
5694
5695 pub fn has_active_inline_completion(&self) -> bool {
5696 self.active_inline_completion.is_some()
5697 }
5698
5699 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
5700 let Some(active_inline_completion) = self.active_inline_completion.take() else {
5701 return false;
5702 };
5703
5704 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
5705 self.clear_highlights::<InlineCompletionHighlight>(cx);
5706 self.stale_inline_completion_in_menu = Some(active_inline_completion);
5707 true
5708 }
5709
5710 /// Returns true when we're displaying the edit prediction popover below the cursor
5711 /// like we are not previewing and the LSP autocomplete menu is visible
5712 /// or we are in `when_holding_modifier` mode.
5713 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
5714 if self.edit_prediction_preview_is_active()
5715 || !self.show_edit_predictions_in_menu()
5716 || !self.edit_predictions_enabled()
5717 {
5718 return false;
5719 }
5720
5721 if self.has_visible_completions_menu() {
5722 return true;
5723 }
5724
5725 has_completion && self.edit_prediction_requires_modifier()
5726 }
5727
5728 fn handle_modifiers_changed(
5729 &mut self,
5730 modifiers: Modifiers,
5731 position_map: &PositionMap,
5732 window: &mut Window,
5733 cx: &mut Context<Self>,
5734 ) {
5735 if self.show_edit_predictions_in_menu() {
5736 self.update_edit_prediction_preview(&modifiers, window, cx);
5737 }
5738
5739 self.update_selection_mode(&modifiers, position_map, window, cx);
5740
5741 let mouse_position = window.mouse_position();
5742 if !position_map.text_hitbox.is_hovered(window) {
5743 return;
5744 }
5745
5746 self.update_hovered_link(
5747 position_map.point_for_position(mouse_position),
5748 &position_map.snapshot,
5749 modifiers,
5750 window,
5751 cx,
5752 )
5753 }
5754
5755 fn update_selection_mode(
5756 &mut self,
5757 modifiers: &Modifiers,
5758 position_map: &PositionMap,
5759 window: &mut Window,
5760 cx: &mut Context<Self>,
5761 ) {
5762 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
5763 return;
5764 }
5765
5766 let mouse_position = window.mouse_position();
5767 let point_for_position = position_map.point_for_position(mouse_position);
5768 let position = point_for_position.previous_valid;
5769
5770 self.select(
5771 SelectPhase::BeginColumnar {
5772 position,
5773 reset: false,
5774 goal_column: point_for_position.exact_unclipped.column(),
5775 },
5776 window,
5777 cx,
5778 );
5779 }
5780
5781 fn update_edit_prediction_preview(
5782 &mut self,
5783 modifiers: &Modifiers,
5784 window: &mut Window,
5785 cx: &mut Context<Self>,
5786 ) {
5787 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
5788 let Some(accept_keystroke) = accept_keybind.keystroke() else {
5789 return;
5790 };
5791
5792 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
5793 if matches!(
5794 self.edit_prediction_preview,
5795 EditPredictionPreview::Inactive { .. }
5796 ) {
5797 self.edit_prediction_preview = EditPredictionPreview::Active {
5798 previous_scroll_position: None,
5799 since: Instant::now(),
5800 };
5801
5802 self.update_visible_inline_completion(window, cx);
5803 cx.notify();
5804 }
5805 } else if let EditPredictionPreview::Active {
5806 previous_scroll_position,
5807 since,
5808 } = self.edit_prediction_preview
5809 {
5810 if let (Some(previous_scroll_position), Some(position_map)) =
5811 (previous_scroll_position, self.last_position_map.as_ref())
5812 {
5813 self.set_scroll_position(
5814 previous_scroll_position
5815 .scroll_position(&position_map.snapshot.display_snapshot),
5816 window,
5817 cx,
5818 );
5819 }
5820
5821 self.edit_prediction_preview = EditPredictionPreview::Inactive {
5822 released_too_fast: since.elapsed() < Duration::from_millis(200),
5823 };
5824 self.clear_row_highlights::<EditPredictionPreview>();
5825 self.update_visible_inline_completion(window, cx);
5826 cx.notify();
5827 }
5828 }
5829
5830 fn update_visible_inline_completion(
5831 &mut self,
5832 _window: &mut Window,
5833 cx: &mut Context<Self>,
5834 ) -> Option<()> {
5835 let selection = self.selections.newest_anchor();
5836 let cursor = selection.head();
5837 let multibuffer = self.buffer.read(cx).snapshot(cx);
5838 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
5839 let excerpt_id = cursor.excerpt_id;
5840
5841 let show_in_menu = self.show_edit_predictions_in_menu();
5842 let completions_menu_has_precedence = !show_in_menu
5843 && (self.context_menu.borrow().is_some()
5844 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
5845
5846 if completions_menu_has_precedence
5847 || !offset_selection.is_empty()
5848 || self
5849 .active_inline_completion
5850 .as_ref()
5851 .map_or(false, |completion| {
5852 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
5853 let invalidation_range = invalidation_range.start..=invalidation_range.end;
5854 !invalidation_range.contains(&offset_selection.head())
5855 })
5856 {
5857 self.discard_inline_completion(false, cx);
5858 return None;
5859 }
5860
5861 self.take_active_inline_completion(cx);
5862 let Some(provider) = self.edit_prediction_provider() else {
5863 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5864 return None;
5865 };
5866
5867 let (buffer, cursor_buffer_position) =
5868 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5869
5870 self.edit_prediction_settings =
5871 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5872
5873 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
5874
5875 if self.edit_prediction_indent_conflict {
5876 let cursor_point = cursor.to_point(&multibuffer);
5877
5878 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
5879
5880 if let Some((_, indent)) = indents.iter().next() {
5881 if indent.len == cursor_point.column {
5882 self.edit_prediction_indent_conflict = false;
5883 }
5884 }
5885 }
5886
5887 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
5888 let edits = inline_completion
5889 .edits
5890 .into_iter()
5891 .flat_map(|(range, new_text)| {
5892 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
5893 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
5894 Some((start..end, new_text))
5895 })
5896 .collect::<Vec<_>>();
5897 if edits.is_empty() {
5898 return None;
5899 }
5900
5901 let first_edit_start = edits.first().unwrap().0.start;
5902 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
5903 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
5904
5905 let last_edit_end = edits.last().unwrap().0.end;
5906 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
5907 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
5908
5909 let cursor_row = cursor.to_point(&multibuffer).row;
5910
5911 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
5912
5913 let mut inlay_ids = Vec::new();
5914 let invalidation_row_range;
5915 let move_invalidation_row_range = if cursor_row < edit_start_row {
5916 Some(cursor_row..edit_end_row)
5917 } else if cursor_row > edit_end_row {
5918 Some(edit_start_row..cursor_row)
5919 } else {
5920 None
5921 };
5922 let is_move =
5923 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
5924 let completion = if is_move {
5925 invalidation_row_range =
5926 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
5927 let target = first_edit_start;
5928 InlineCompletion::Move { target, snapshot }
5929 } else {
5930 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
5931 && !self.inline_completions_hidden_for_vim_mode;
5932
5933 if show_completions_in_buffer {
5934 if edits
5935 .iter()
5936 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
5937 {
5938 let mut inlays = Vec::new();
5939 for (range, new_text) in &edits {
5940 let inlay = Inlay::inline_completion(
5941 post_inc(&mut self.next_inlay_id),
5942 range.start,
5943 new_text.as_str(),
5944 );
5945 inlay_ids.push(inlay.id);
5946 inlays.push(inlay);
5947 }
5948
5949 self.splice_inlays(&[], inlays, cx);
5950 } else {
5951 let background_color = cx.theme().status().deleted_background;
5952 self.highlight_text::<InlineCompletionHighlight>(
5953 edits.iter().map(|(range, _)| range.clone()).collect(),
5954 HighlightStyle {
5955 background_color: Some(background_color),
5956 ..Default::default()
5957 },
5958 cx,
5959 );
5960 }
5961 }
5962
5963 invalidation_row_range = edit_start_row..edit_end_row;
5964
5965 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
5966 if provider.show_tab_accept_marker() {
5967 EditDisplayMode::TabAccept
5968 } else {
5969 EditDisplayMode::Inline
5970 }
5971 } else {
5972 EditDisplayMode::DiffPopover
5973 };
5974
5975 InlineCompletion::Edit {
5976 edits,
5977 edit_preview: inline_completion.edit_preview,
5978 display_mode,
5979 snapshot,
5980 }
5981 };
5982
5983 let invalidation_range = multibuffer
5984 .anchor_before(Point::new(invalidation_row_range.start, 0))
5985 ..multibuffer.anchor_after(Point::new(
5986 invalidation_row_range.end,
5987 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
5988 ));
5989
5990 self.stale_inline_completion_in_menu = None;
5991 self.active_inline_completion = Some(InlineCompletionState {
5992 inlay_ids,
5993 completion,
5994 completion_id: inline_completion.id,
5995 invalidation_range,
5996 });
5997
5998 cx.notify();
5999
6000 Some(())
6001 }
6002
6003 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6004 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6005 }
6006
6007 fn render_code_actions_indicator(
6008 &self,
6009 _style: &EditorStyle,
6010 row: DisplayRow,
6011 is_active: bool,
6012 breakpoint: Option<&(Anchor, Breakpoint)>,
6013 cx: &mut Context<Self>,
6014 ) -> Option<IconButton> {
6015 let color = Color::Muted;
6016
6017 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6018 let bp_kind = Arc::new(
6019 breakpoint
6020 .map(|(_, bp)| bp.kind.clone())
6021 .unwrap_or(BreakpointKind::Standard),
6022 );
6023
6024 if self.available_code_actions.is_some() {
6025 Some(
6026 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
6027 .shape(ui::IconButtonShape::Square)
6028 .icon_size(IconSize::XSmall)
6029 .icon_color(color)
6030 .toggle_state(is_active)
6031 .tooltip({
6032 let focus_handle = self.focus_handle.clone();
6033 move |window, cx| {
6034 Tooltip::for_action_in(
6035 "Toggle Code Actions",
6036 &ToggleCodeActions {
6037 deployed_from_indicator: None,
6038 },
6039 &focus_handle,
6040 window,
6041 cx,
6042 )
6043 }
6044 })
6045 .on_click(cx.listener(move |editor, _e, window, cx| {
6046 window.focus(&editor.focus_handle(cx));
6047 editor.toggle_code_actions(
6048 &ToggleCodeActions {
6049 deployed_from_indicator: Some(row),
6050 },
6051 window,
6052 cx,
6053 );
6054 }))
6055 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6056 editor.set_breakpoint_context_menu(
6057 row,
6058 position,
6059 bp_kind.clone(),
6060 event.down.position,
6061 window,
6062 cx,
6063 );
6064 })),
6065 )
6066 } else {
6067 None
6068 }
6069 }
6070
6071 fn clear_tasks(&mut self) {
6072 self.tasks.clear()
6073 }
6074
6075 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6076 if self.tasks.insert(key, value).is_some() {
6077 // This case should hopefully be rare, but just in case...
6078 log::error!("multiple different run targets found on a single line, only the last target will be rendered")
6079 }
6080 }
6081
6082 /// Get all display points of breakpoints that will be rendered within editor
6083 ///
6084 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6085 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6086 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6087 fn active_breakpoints(
6088 &mut self,
6089 range: Range<DisplayRow>,
6090 window: &mut Window,
6091 cx: &mut Context<Self>,
6092 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
6093 let mut breakpoint_display_points = HashMap::default();
6094
6095 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
6096 return breakpoint_display_points;
6097 };
6098
6099 let snapshot = self.snapshot(window, cx);
6100
6101 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
6102 let Some(project) = self.project.as_ref() else {
6103 return breakpoint_display_points;
6104 };
6105
6106 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
6107 let buffer_snapshot = buffer.read(cx).snapshot();
6108
6109 for breakpoint in
6110 breakpoint_store
6111 .read(cx)
6112 .breakpoints(&buffer, None, buffer_snapshot.clone(), cx)
6113 {
6114 let point = buffer_snapshot.summary_for_anchor::<Point>(&breakpoint.0);
6115 let mut anchor = multi_buffer_snapshot.anchor_before(point);
6116 anchor.text_anchor = breakpoint.0;
6117
6118 breakpoint_display_points.insert(
6119 snapshot
6120 .point_to_display_point(
6121 MultiBufferPoint {
6122 row: point.row,
6123 column: point.column,
6124 },
6125 Bias::Left,
6126 )
6127 .row(),
6128 (anchor, breakpoint.1.clone()),
6129 );
6130 }
6131
6132 return breakpoint_display_points;
6133 }
6134
6135 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
6136 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
6137 for excerpt_boundary in multi_buffer_snapshot.excerpt_boundaries_in_range(range) {
6138 let info = excerpt_boundary.next;
6139
6140 let Some(excerpt_ranges) = multi_buffer_snapshot.range_for_excerpt(info.id) else {
6141 continue;
6142 };
6143
6144 let Some(buffer) =
6145 project.read_with(cx, |this, cx| this.buffer_for_id(info.buffer_id, cx))
6146 else {
6147 continue;
6148 };
6149
6150 if buffer.read(cx).file().is_none() {
6151 continue;
6152 }
6153 let breakpoints = breakpoint_store.read(cx).breakpoints(
6154 &buffer,
6155 Some(info.range.context.start..info.range.context.end),
6156 info.buffer.clone(),
6157 cx,
6158 );
6159
6160 // To translate a breakpoint's position within a singular buffer to a multi buffer
6161 // position we need to know it's excerpt starting location, it's position within
6162 // the singular buffer, and if that position is within the excerpt's range.
6163 let excerpt_head = excerpt_ranges
6164 .start
6165 .to_display_point(&snapshot.display_snapshot);
6166
6167 let buffer_start = info
6168 .buffer
6169 .summary_for_anchor::<Point>(&info.range.context.start);
6170
6171 for (anchor, breakpoint) in breakpoints {
6172 let as_row = info.buffer.summary_for_anchor::<Point>(&anchor).row;
6173 let delta = as_row - buffer_start.row;
6174
6175 let position = excerpt_head + DisplayPoint::new(DisplayRow(delta), 0);
6176
6177 let anchor = snapshot.display_point_to_anchor(position, Bias::Left);
6178
6179 breakpoint_display_points.insert(position.row(), (anchor, breakpoint.clone()));
6180 }
6181 }
6182
6183 breakpoint_display_points
6184 }
6185
6186 fn breakpoint_context_menu(
6187 &self,
6188 anchor: Anchor,
6189 kind: Arc<BreakpointKind>,
6190 window: &mut Window,
6191 cx: &mut Context<Self>,
6192 ) -> Entity<ui::ContextMenu> {
6193 let weak_editor = cx.weak_entity();
6194 let focus_handle = self.focus_handle(cx);
6195
6196 let second_entry_msg = if kind.log_message().is_some() {
6197 "Edit Log Breakpoint"
6198 } else {
6199 "Add Log Breakpoint"
6200 };
6201
6202 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6203 menu.on_blur_subscription(Subscription::new(|| {}))
6204 .context(focus_handle)
6205 .entry("Toggle Breakpoint", None, {
6206 let weak_editor = weak_editor.clone();
6207 move |_window, cx| {
6208 weak_editor
6209 .update(cx, |this, cx| {
6210 this.edit_breakpoint_at_anchor(
6211 anchor,
6212 BreakpointKind::Standard,
6213 BreakpointEditAction::Toggle,
6214 cx,
6215 );
6216 })
6217 .log_err();
6218 }
6219 })
6220 .entry(second_entry_msg, None, move |window, cx| {
6221 weak_editor
6222 .update(cx, |this, cx| {
6223 this.add_edit_breakpoint_block(anchor, kind.as_ref(), window, cx);
6224 })
6225 .log_err();
6226 })
6227 })
6228 }
6229
6230 fn render_breakpoint(
6231 &self,
6232 position: Anchor,
6233 row: DisplayRow,
6234 kind: &BreakpointKind,
6235 cx: &mut Context<Self>,
6236 ) -> IconButton {
6237 let color = if self
6238 .gutter_breakpoint_indicator
6239 .is_some_and(|gutter_bp| gutter_bp.row() == row)
6240 {
6241 Color::Hint
6242 } else {
6243 Color::Debugger
6244 };
6245
6246 let icon = match &kind {
6247 BreakpointKind::Standard => ui::IconName::DebugBreakpoint,
6248 BreakpointKind::Log(_) => ui::IconName::DebugLogBreakpoint,
6249 };
6250 let arc_kind = Arc::new(kind.clone());
6251 let arc_kind2 = arc_kind.clone();
6252
6253 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
6254 .icon_size(IconSize::XSmall)
6255 .size(ui::ButtonSize::None)
6256 .icon_color(color)
6257 .style(ButtonStyle::Transparent)
6258 .on_click(cx.listener(move |editor, _e, window, cx| {
6259 window.focus(&editor.focus_handle(cx));
6260 editor.edit_breakpoint_at_anchor(
6261 position,
6262 arc_kind.as_ref().clone(),
6263 BreakpointEditAction::Toggle,
6264 cx,
6265 );
6266 }))
6267 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6268 editor.set_breakpoint_context_menu(
6269 row,
6270 Some(position),
6271 arc_kind2.clone(),
6272 event.down.position,
6273 window,
6274 cx,
6275 );
6276 }))
6277 }
6278
6279 fn build_tasks_context(
6280 project: &Entity<Project>,
6281 buffer: &Entity<Buffer>,
6282 buffer_row: u32,
6283 tasks: &Arc<RunnableTasks>,
6284 cx: &mut Context<Self>,
6285 ) -> Task<Option<task::TaskContext>> {
6286 let position = Point::new(buffer_row, tasks.column);
6287 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
6288 let location = Location {
6289 buffer: buffer.clone(),
6290 range: range_start..range_start,
6291 };
6292 // Fill in the environmental variables from the tree-sitter captures
6293 let mut captured_task_variables = TaskVariables::default();
6294 for (capture_name, value) in tasks.extra_variables.clone() {
6295 captured_task_variables.insert(
6296 task::VariableName::Custom(capture_name.into()),
6297 value.clone(),
6298 );
6299 }
6300 project.update(cx, |project, cx| {
6301 project.task_store().update(cx, |task_store, cx| {
6302 task_store.task_context_for_location(captured_task_variables, location, cx)
6303 })
6304 })
6305 }
6306
6307 pub fn spawn_nearest_task(
6308 &mut self,
6309 action: &SpawnNearestTask,
6310 window: &mut Window,
6311 cx: &mut Context<Self>,
6312 ) {
6313 let Some((workspace, _)) = self.workspace.clone() else {
6314 return;
6315 };
6316 let Some(project) = self.project.clone() else {
6317 return;
6318 };
6319
6320 // Try to find a closest, enclosing node using tree-sitter that has a
6321 // task
6322 let Some((buffer, buffer_row, tasks)) = self
6323 .find_enclosing_node_task(cx)
6324 // Or find the task that's closest in row-distance.
6325 .or_else(|| self.find_closest_task(cx))
6326 else {
6327 return;
6328 };
6329
6330 let reveal_strategy = action.reveal;
6331 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6332 cx.spawn_in(window, async move |_, cx| {
6333 let context = task_context.await?;
6334 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
6335
6336 let resolved = resolved_task.resolved.as_mut()?;
6337 resolved.reveal = reveal_strategy;
6338
6339 workspace
6340 .update(cx, |workspace, cx| {
6341 workspace::tasks::schedule_resolved_task(
6342 workspace,
6343 task_source_kind,
6344 resolved_task,
6345 false,
6346 cx,
6347 );
6348 })
6349 .ok()
6350 })
6351 .detach();
6352 }
6353
6354 fn find_closest_task(
6355 &mut self,
6356 cx: &mut Context<Self>,
6357 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6358 let cursor_row = self.selections.newest_adjusted(cx).head().row;
6359
6360 let ((buffer_id, row), tasks) = self
6361 .tasks
6362 .iter()
6363 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
6364
6365 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
6366 let tasks = Arc::new(tasks.to_owned());
6367 Some((buffer, *row, tasks))
6368 }
6369
6370 fn find_enclosing_node_task(
6371 &mut self,
6372 cx: &mut Context<Self>,
6373 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6374 let snapshot = self.buffer.read(cx).snapshot(cx);
6375 let offset = self.selections.newest::<usize>(cx).head();
6376 let excerpt = snapshot.excerpt_containing(offset..offset)?;
6377 let buffer_id = excerpt.buffer().remote_id();
6378
6379 let layer = excerpt.buffer().syntax_layer_at(offset)?;
6380 let mut cursor = layer.node().walk();
6381
6382 while cursor.goto_first_child_for_byte(offset).is_some() {
6383 if cursor.node().end_byte() == offset {
6384 cursor.goto_next_sibling();
6385 }
6386 }
6387
6388 // Ascend to the smallest ancestor that contains the range and has a task.
6389 loop {
6390 let node = cursor.node();
6391 let node_range = node.byte_range();
6392 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
6393
6394 // Check if this node contains our offset
6395 if node_range.start <= offset && node_range.end >= offset {
6396 // If it contains offset, check for task
6397 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
6398 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
6399 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
6400 }
6401 }
6402
6403 if !cursor.goto_parent() {
6404 break;
6405 }
6406 }
6407 None
6408 }
6409
6410 fn render_run_indicator(
6411 &self,
6412 _style: &EditorStyle,
6413 is_active: bool,
6414 row: DisplayRow,
6415 breakpoint: Option<(Anchor, Breakpoint)>,
6416 cx: &mut Context<Self>,
6417 ) -> IconButton {
6418 let color = Color::Muted;
6419
6420 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6421 let bp_kind = Arc::new(
6422 breakpoint
6423 .map(|(_, bp)| bp.kind)
6424 .unwrap_or(BreakpointKind::Standard),
6425 );
6426
6427 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
6428 .shape(ui::IconButtonShape::Square)
6429 .icon_size(IconSize::XSmall)
6430 .icon_color(color)
6431 .toggle_state(is_active)
6432 .on_click(cx.listener(move |editor, _e, window, cx| {
6433 window.focus(&editor.focus_handle(cx));
6434 editor.toggle_code_actions(
6435 &ToggleCodeActions {
6436 deployed_from_indicator: Some(row),
6437 },
6438 window,
6439 cx,
6440 );
6441 }))
6442 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6443 editor.set_breakpoint_context_menu(
6444 row,
6445 position,
6446 bp_kind.clone(),
6447 event.down.position,
6448 window,
6449 cx,
6450 );
6451 }))
6452 }
6453
6454 pub fn context_menu_visible(&self) -> bool {
6455 !self.edit_prediction_preview_is_active()
6456 && self
6457 .context_menu
6458 .borrow()
6459 .as_ref()
6460 .map_or(false, |menu| menu.visible())
6461 }
6462
6463 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
6464 self.context_menu
6465 .borrow()
6466 .as_ref()
6467 .map(|menu| menu.origin())
6468 }
6469
6470 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
6471 self.context_menu_options = Some(options);
6472 }
6473
6474 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
6475 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
6476
6477 fn render_edit_prediction_popover(
6478 &mut self,
6479 text_bounds: &Bounds<Pixels>,
6480 content_origin: gpui::Point<Pixels>,
6481 editor_snapshot: &EditorSnapshot,
6482 visible_row_range: Range<DisplayRow>,
6483 scroll_top: f32,
6484 scroll_bottom: f32,
6485 line_layouts: &[LineWithInvisibles],
6486 line_height: Pixels,
6487 scroll_pixel_position: gpui::Point<Pixels>,
6488 newest_selection_head: Option<DisplayPoint>,
6489 editor_width: Pixels,
6490 style: &EditorStyle,
6491 window: &mut Window,
6492 cx: &mut App,
6493 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6494 let active_inline_completion = self.active_inline_completion.as_ref()?;
6495
6496 if self.edit_prediction_visible_in_cursor_popover(true) {
6497 return None;
6498 }
6499
6500 match &active_inline_completion.completion {
6501 InlineCompletion::Move { target, .. } => {
6502 let target_display_point = target.to_display_point(editor_snapshot);
6503
6504 if self.edit_prediction_requires_modifier() {
6505 if !self.edit_prediction_preview_is_active() {
6506 return None;
6507 }
6508
6509 self.render_edit_prediction_modifier_jump_popover(
6510 text_bounds,
6511 content_origin,
6512 visible_row_range,
6513 line_layouts,
6514 line_height,
6515 scroll_pixel_position,
6516 newest_selection_head,
6517 target_display_point,
6518 window,
6519 cx,
6520 )
6521 } else {
6522 self.render_edit_prediction_eager_jump_popover(
6523 text_bounds,
6524 content_origin,
6525 editor_snapshot,
6526 visible_row_range,
6527 scroll_top,
6528 scroll_bottom,
6529 line_height,
6530 scroll_pixel_position,
6531 target_display_point,
6532 editor_width,
6533 window,
6534 cx,
6535 )
6536 }
6537 }
6538 InlineCompletion::Edit {
6539 display_mode: EditDisplayMode::Inline,
6540 ..
6541 } => None,
6542 InlineCompletion::Edit {
6543 display_mode: EditDisplayMode::TabAccept,
6544 edits,
6545 ..
6546 } => {
6547 let range = &edits.first()?.0;
6548 let target_display_point = range.end.to_display_point(editor_snapshot);
6549
6550 self.render_edit_prediction_end_of_line_popover(
6551 "Accept",
6552 editor_snapshot,
6553 visible_row_range,
6554 target_display_point,
6555 line_height,
6556 scroll_pixel_position,
6557 content_origin,
6558 editor_width,
6559 window,
6560 cx,
6561 )
6562 }
6563 InlineCompletion::Edit {
6564 edits,
6565 edit_preview,
6566 display_mode: EditDisplayMode::DiffPopover,
6567 snapshot,
6568 } => self.render_edit_prediction_diff_popover(
6569 text_bounds,
6570 content_origin,
6571 editor_snapshot,
6572 visible_row_range,
6573 line_layouts,
6574 line_height,
6575 scroll_pixel_position,
6576 newest_selection_head,
6577 editor_width,
6578 style,
6579 edits,
6580 edit_preview,
6581 snapshot,
6582 window,
6583 cx,
6584 ),
6585 }
6586 }
6587
6588 fn render_edit_prediction_modifier_jump_popover(
6589 &mut self,
6590 text_bounds: &Bounds<Pixels>,
6591 content_origin: gpui::Point<Pixels>,
6592 visible_row_range: Range<DisplayRow>,
6593 line_layouts: &[LineWithInvisibles],
6594 line_height: Pixels,
6595 scroll_pixel_position: gpui::Point<Pixels>,
6596 newest_selection_head: Option<DisplayPoint>,
6597 target_display_point: DisplayPoint,
6598 window: &mut Window,
6599 cx: &mut App,
6600 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6601 let scrolled_content_origin =
6602 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
6603
6604 const SCROLL_PADDING_Y: Pixels = px(12.);
6605
6606 if target_display_point.row() < visible_row_range.start {
6607 return self.render_edit_prediction_scroll_popover(
6608 |_| SCROLL_PADDING_Y,
6609 IconName::ArrowUp,
6610 visible_row_range,
6611 line_layouts,
6612 newest_selection_head,
6613 scrolled_content_origin,
6614 window,
6615 cx,
6616 );
6617 } else if target_display_point.row() >= visible_row_range.end {
6618 return self.render_edit_prediction_scroll_popover(
6619 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
6620 IconName::ArrowDown,
6621 visible_row_range,
6622 line_layouts,
6623 newest_selection_head,
6624 scrolled_content_origin,
6625 window,
6626 cx,
6627 );
6628 }
6629
6630 const POLE_WIDTH: Pixels = px(2.);
6631
6632 let line_layout =
6633 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
6634 let target_column = target_display_point.column() as usize;
6635
6636 let target_x = line_layout.x_for_index(target_column);
6637 let target_y =
6638 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
6639
6640 let flag_on_right = target_x < text_bounds.size.width / 2.;
6641
6642 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
6643 border_color.l += 0.001;
6644
6645 let mut element = v_flex()
6646 .items_end()
6647 .when(flag_on_right, |el| el.items_start())
6648 .child(if flag_on_right {
6649 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
6650 .rounded_bl(px(0.))
6651 .rounded_tl(px(0.))
6652 .border_l_2()
6653 .border_color(border_color)
6654 } else {
6655 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
6656 .rounded_br(px(0.))
6657 .rounded_tr(px(0.))
6658 .border_r_2()
6659 .border_color(border_color)
6660 })
6661 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
6662 .into_any();
6663
6664 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6665
6666 let mut origin = scrolled_content_origin + point(target_x, target_y)
6667 - point(
6668 if flag_on_right {
6669 POLE_WIDTH
6670 } else {
6671 size.width - POLE_WIDTH
6672 },
6673 size.height - line_height,
6674 );
6675
6676 origin.x = origin.x.max(content_origin.x);
6677
6678 element.prepaint_at(origin, window, cx);
6679
6680 Some((element, origin))
6681 }
6682
6683 fn render_edit_prediction_scroll_popover(
6684 &mut self,
6685 to_y: impl Fn(Size<Pixels>) -> Pixels,
6686 scroll_icon: IconName,
6687 visible_row_range: Range<DisplayRow>,
6688 line_layouts: &[LineWithInvisibles],
6689 newest_selection_head: Option<DisplayPoint>,
6690 scrolled_content_origin: gpui::Point<Pixels>,
6691 window: &mut Window,
6692 cx: &mut App,
6693 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6694 let mut element = self
6695 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
6696 .into_any();
6697
6698 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6699
6700 let cursor = newest_selection_head?;
6701 let cursor_row_layout =
6702 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
6703 let cursor_column = cursor.column() as usize;
6704
6705 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
6706
6707 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
6708
6709 element.prepaint_at(origin, window, cx);
6710 Some((element, origin))
6711 }
6712
6713 fn render_edit_prediction_eager_jump_popover(
6714 &mut self,
6715 text_bounds: &Bounds<Pixels>,
6716 content_origin: gpui::Point<Pixels>,
6717 editor_snapshot: &EditorSnapshot,
6718 visible_row_range: Range<DisplayRow>,
6719 scroll_top: f32,
6720 scroll_bottom: f32,
6721 line_height: Pixels,
6722 scroll_pixel_position: gpui::Point<Pixels>,
6723 target_display_point: DisplayPoint,
6724 editor_width: Pixels,
6725 window: &mut Window,
6726 cx: &mut App,
6727 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6728 if target_display_point.row().as_f32() < scroll_top {
6729 let mut element = self
6730 .render_edit_prediction_line_popover(
6731 "Jump to Edit",
6732 Some(IconName::ArrowUp),
6733 window,
6734 cx,
6735 )?
6736 .into_any();
6737
6738 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6739 let offset = point(
6740 (text_bounds.size.width - size.width) / 2.,
6741 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
6742 );
6743
6744 let origin = text_bounds.origin + offset;
6745 element.prepaint_at(origin, window, cx);
6746 Some((element, origin))
6747 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
6748 let mut element = self
6749 .render_edit_prediction_line_popover(
6750 "Jump to Edit",
6751 Some(IconName::ArrowDown),
6752 window,
6753 cx,
6754 )?
6755 .into_any();
6756
6757 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6758 let offset = point(
6759 (text_bounds.size.width - size.width) / 2.,
6760 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
6761 );
6762
6763 let origin = text_bounds.origin + offset;
6764 element.prepaint_at(origin, window, cx);
6765 Some((element, origin))
6766 } else {
6767 self.render_edit_prediction_end_of_line_popover(
6768 "Jump to Edit",
6769 editor_snapshot,
6770 visible_row_range,
6771 target_display_point,
6772 line_height,
6773 scroll_pixel_position,
6774 content_origin,
6775 editor_width,
6776 window,
6777 cx,
6778 )
6779 }
6780 }
6781
6782 fn render_edit_prediction_end_of_line_popover(
6783 self: &mut Editor,
6784 label: &'static str,
6785 editor_snapshot: &EditorSnapshot,
6786 visible_row_range: Range<DisplayRow>,
6787 target_display_point: DisplayPoint,
6788 line_height: Pixels,
6789 scroll_pixel_position: gpui::Point<Pixels>,
6790 content_origin: gpui::Point<Pixels>,
6791 editor_width: Pixels,
6792 window: &mut Window,
6793 cx: &mut App,
6794 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6795 let target_line_end = DisplayPoint::new(
6796 target_display_point.row(),
6797 editor_snapshot.line_len(target_display_point.row()),
6798 );
6799
6800 let mut element = self
6801 .render_edit_prediction_line_popover(label, None, window, cx)?
6802 .into_any();
6803
6804 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6805
6806 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
6807
6808 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
6809 let mut origin = start_point
6810 + line_origin
6811 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
6812 origin.x = origin.x.max(content_origin.x);
6813
6814 let max_x = content_origin.x + editor_width - size.width;
6815
6816 if origin.x > max_x {
6817 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
6818
6819 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
6820 origin.y += offset;
6821 IconName::ArrowUp
6822 } else {
6823 origin.y -= offset;
6824 IconName::ArrowDown
6825 };
6826
6827 element = self
6828 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
6829 .into_any();
6830
6831 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6832
6833 origin.x = content_origin.x + editor_width - size.width - px(2.);
6834 }
6835
6836 element.prepaint_at(origin, window, cx);
6837 Some((element, origin))
6838 }
6839
6840 fn render_edit_prediction_diff_popover(
6841 self: &Editor,
6842 text_bounds: &Bounds<Pixels>,
6843 content_origin: gpui::Point<Pixels>,
6844 editor_snapshot: &EditorSnapshot,
6845 visible_row_range: Range<DisplayRow>,
6846 line_layouts: &[LineWithInvisibles],
6847 line_height: Pixels,
6848 scroll_pixel_position: gpui::Point<Pixels>,
6849 newest_selection_head: Option<DisplayPoint>,
6850 editor_width: Pixels,
6851 style: &EditorStyle,
6852 edits: &Vec<(Range<Anchor>, String)>,
6853 edit_preview: &Option<language::EditPreview>,
6854 snapshot: &language::BufferSnapshot,
6855 window: &mut Window,
6856 cx: &mut App,
6857 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6858 let edit_start = edits
6859 .first()
6860 .unwrap()
6861 .0
6862 .start
6863 .to_display_point(editor_snapshot);
6864 let edit_end = edits
6865 .last()
6866 .unwrap()
6867 .0
6868 .end
6869 .to_display_point(editor_snapshot);
6870
6871 let is_visible = visible_row_range.contains(&edit_start.row())
6872 || visible_row_range.contains(&edit_end.row());
6873 if !is_visible {
6874 return None;
6875 }
6876
6877 let highlighted_edits =
6878 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
6879
6880 let styled_text = highlighted_edits.to_styled_text(&style.text);
6881 let line_count = highlighted_edits.text.lines().count();
6882
6883 const BORDER_WIDTH: Pixels = px(1.);
6884
6885 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
6886 let has_keybind = keybind.is_some();
6887
6888 let mut element = h_flex()
6889 .items_start()
6890 .child(
6891 h_flex()
6892 .bg(cx.theme().colors().editor_background)
6893 .border(BORDER_WIDTH)
6894 .shadow_sm()
6895 .border_color(cx.theme().colors().border)
6896 .rounded_l_lg()
6897 .when(line_count > 1, |el| el.rounded_br_lg())
6898 .pr_1()
6899 .child(styled_text),
6900 )
6901 .child(
6902 h_flex()
6903 .h(line_height + BORDER_WIDTH * px(2.))
6904 .px_1p5()
6905 .gap_1()
6906 // Workaround: For some reason, there's a gap if we don't do this
6907 .ml(-BORDER_WIDTH)
6908 .shadow(smallvec![gpui::BoxShadow {
6909 color: gpui::black().opacity(0.05),
6910 offset: point(px(1.), px(1.)),
6911 blur_radius: px(2.),
6912 spread_radius: px(0.),
6913 }])
6914 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
6915 .border(BORDER_WIDTH)
6916 .border_color(cx.theme().colors().border)
6917 .rounded_r_lg()
6918 .id("edit_prediction_diff_popover_keybind")
6919 .when(!has_keybind, |el| {
6920 let status_colors = cx.theme().status();
6921
6922 el.bg(status_colors.error_background)
6923 .border_color(status_colors.error.opacity(0.6))
6924 .child(Icon::new(IconName::Info).color(Color::Error))
6925 .cursor_default()
6926 .hoverable_tooltip(move |_window, cx| {
6927 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
6928 })
6929 })
6930 .children(keybind),
6931 )
6932 .into_any();
6933
6934 let longest_row =
6935 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
6936 let longest_line_width = if visible_row_range.contains(&longest_row) {
6937 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
6938 } else {
6939 layout_line(
6940 longest_row,
6941 editor_snapshot,
6942 style,
6943 editor_width,
6944 |_| false,
6945 window,
6946 cx,
6947 )
6948 .width
6949 };
6950
6951 let viewport_bounds =
6952 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
6953 right: -EditorElement::SCROLLBAR_WIDTH,
6954 ..Default::default()
6955 });
6956
6957 let x_after_longest =
6958 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
6959 - scroll_pixel_position.x;
6960
6961 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6962
6963 // Fully visible if it can be displayed within the window (allow overlapping other
6964 // panes). However, this is only allowed if the popover starts within text_bounds.
6965 let can_position_to_the_right = x_after_longest < text_bounds.right()
6966 && x_after_longest + element_bounds.width < viewport_bounds.right();
6967
6968 let mut origin = if can_position_to_the_right {
6969 point(
6970 x_after_longest,
6971 text_bounds.origin.y + edit_start.row().as_f32() * line_height
6972 - scroll_pixel_position.y,
6973 )
6974 } else {
6975 let cursor_row = newest_selection_head.map(|head| head.row());
6976 let above_edit = edit_start
6977 .row()
6978 .0
6979 .checked_sub(line_count as u32)
6980 .map(DisplayRow);
6981 let below_edit = Some(edit_end.row() + 1);
6982 let above_cursor =
6983 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
6984 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
6985
6986 // Place the edit popover adjacent to the edit if there is a location
6987 // available that is onscreen and does not obscure the cursor. Otherwise,
6988 // place it adjacent to the cursor.
6989 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
6990 .into_iter()
6991 .flatten()
6992 .find(|&start_row| {
6993 let end_row = start_row + line_count as u32;
6994 visible_row_range.contains(&start_row)
6995 && visible_row_range.contains(&end_row)
6996 && cursor_row.map_or(true, |cursor_row| {
6997 !((start_row..end_row).contains(&cursor_row))
6998 })
6999 })?;
7000
7001 content_origin
7002 + point(
7003 -scroll_pixel_position.x,
7004 row_target.as_f32() * line_height - scroll_pixel_position.y,
7005 )
7006 };
7007
7008 origin.x -= BORDER_WIDTH;
7009
7010 window.defer_draw(element, origin, 1);
7011
7012 // Do not return an element, since it will already be drawn due to defer_draw.
7013 None
7014 }
7015
7016 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
7017 px(30.)
7018 }
7019
7020 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
7021 if self.read_only(cx) {
7022 cx.theme().players().read_only()
7023 } else {
7024 self.style.as_ref().unwrap().local_player
7025 }
7026 }
7027
7028 fn render_edit_prediction_accept_keybind(
7029 &self,
7030 window: &mut Window,
7031 cx: &App,
7032 ) -> Option<AnyElement> {
7033 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
7034 let accept_keystroke = accept_binding.keystroke()?;
7035
7036 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7037
7038 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
7039 Color::Accent
7040 } else {
7041 Color::Muted
7042 };
7043
7044 h_flex()
7045 .px_0p5()
7046 .when(is_platform_style_mac, |parent| parent.gap_0p5())
7047 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7048 .text_size(TextSize::XSmall.rems(cx))
7049 .child(h_flex().children(ui::render_modifiers(
7050 &accept_keystroke.modifiers,
7051 PlatformStyle::platform(),
7052 Some(modifiers_color),
7053 Some(IconSize::XSmall.rems().into()),
7054 true,
7055 )))
7056 .when(is_platform_style_mac, |parent| {
7057 parent.child(accept_keystroke.key.clone())
7058 })
7059 .when(!is_platform_style_mac, |parent| {
7060 parent.child(
7061 Key::new(
7062 util::capitalize(&accept_keystroke.key),
7063 Some(Color::Default),
7064 )
7065 .size(Some(IconSize::XSmall.rems().into())),
7066 )
7067 })
7068 .into_any()
7069 .into()
7070 }
7071
7072 fn render_edit_prediction_line_popover(
7073 &self,
7074 label: impl Into<SharedString>,
7075 icon: Option<IconName>,
7076 window: &mut Window,
7077 cx: &App,
7078 ) -> Option<Stateful<Div>> {
7079 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
7080
7081 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7082 let has_keybind = keybind.is_some();
7083
7084 let result = h_flex()
7085 .id("ep-line-popover")
7086 .py_0p5()
7087 .pl_1()
7088 .pr(padding_right)
7089 .gap_1()
7090 .rounded_md()
7091 .border_1()
7092 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7093 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7094 .shadow_sm()
7095 .when(!has_keybind, |el| {
7096 let status_colors = cx.theme().status();
7097
7098 el.bg(status_colors.error_background)
7099 .border_color(status_colors.error.opacity(0.6))
7100 .pl_2()
7101 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7102 .cursor_default()
7103 .hoverable_tooltip(move |_window, cx| {
7104 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7105 })
7106 })
7107 .children(keybind)
7108 .child(
7109 Label::new(label)
7110 .size(LabelSize::Small)
7111 .when(!has_keybind, |el| {
7112 el.color(cx.theme().status().error.into()).strikethrough()
7113 }),
7114 )
7115 .when(!has_keybind, |el| {
7116 el.child(
7117 h_flex().ml_1().child(
7118 Icon::new(IconName::Info)
7119 .size(IconSize::Small)
7120 .color(cx.theme().status().error.into()),
7121 ),
7122 )
7123 })
7124 .when_some(icon, |element, icon| {
7125 element.child(
7126 div()
7127 .mt(px(1.5))
7128 .child(Icon::new(icon).size(IconSize::Small)),
7129 )
7130 });
7131
7132 Some(result)
7133 }
7134
7135 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
7136 let accent_color = cx.theme().colors().text_accent;
7137 let editor_bg_color = cx.theme().colors().editor_background;
7138 editor_bg_color.blend(accent_color.opacity(0.1))
7139 }
7140
7141 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
7142 let accent_color = cx.theme().colors().text_accent;
7143 let editor_bg_color = cx.theme().colors().editor_background;
7144 editor_bg_color.blend(accent_color.opacity(0.6))
7145 }
7146
7147 fn render_edit_prediction_cursor_popover(
7148 &self,
7149 min_width: Pixels,
7150 max_width: Pixels,
7151 cursor_point: Point,
7152 style: &EditorStyle,
7153 accept_keystroke: Option<&gpui::Keystroke>,
7154 _window: &Window,
7155 cx: &mut Context<Editor>,
7156 ) -> Option<AnyElement> {
7157 let provider = self.edit_prediction_provider.as_ref()?;
7158
7159 if provider.provider.needs_terms_acceptance(cx) {
7160 return Some(
7161 h_flex()
7162 .min_w(min_width)
7163 .flex_1()
7164 .px_2()
7165 .py_1()
7166 .gap_3()
7167 .elevation_2(cx)
7168 .hover(|style| style.bg(cx.theme().colors().element_hover))
7169 .id("accept-terms")
7170 .cursor_pointer()
7171 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
7172 .on_click(cx.listener(|this, _event, window, cx| {
7173 cx.stop_propagation();
7174 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
7175 window.dispatch_action(
7176 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
7177 cx,
7178 );
7179 }))
7180 .child(
7181 h_flex()
7182 .flex_1()
7183 .gap_2()
7184 .child(Icon::new(IconName::ZedPredict))
7185 .child(Label::new("Accept Terms of Service"))
7186 .child(div().w_full())
7187 .child(
7188 Icon::new(IconName::ArrowUpRight)
7189 .color(Color::Muted)
7190 .size(IconSize::Small),
7191 )
7192 .into_any_element(),
7193 )
7194 .into_any(),
7195 );
7196 }
7197
7198 let is_refreshing = provider.provider.is_refreshing(cx);
7199
7200 fn pending_completion_container() -> Div {
7201 h_flex()
7202 .h_full()
7203 .flex_1()
7204 .gap_2()
7205 .child(Icon::new(IconName::ZedPredict))
7206 }
7207
7208 let completion = match &self.active_inline_completion {
7209 Some(prediction) => {
7210 if !self.has_visible_completions_menu() {
7211 const RADIUS: Pixels = px(6.);
7212 const BORDER_WIDTH: Pixels = px(1.);
7213
7214 return Some(
7215 h_flex()
7216 .elevation_2(cx)
7217 .border(BORDER_WIDTH)
7218 .border_color(cx.theme().colors().border)
7219 .when(accept_keystroke.is_none(), |el| {
7220 el.border_color(cx.theme().status().error)
7221 })
7222 .rounded(RADIUS)
7223 .rounded_tl(px(0.))
7224 .overflow_hidden()
7225 .child(div().px_1p5().child(match &prediction.completion {
7226 InlineCompletion::Move { target, snapshot } => {
7227 use text::ToPoint as _;
7228 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
7229 {
7230 Icon::new(IconName::ZedPredictDown)
7231 } else {
7232 Icon::new(IconName::ZedPredictUp)
7233 }
7234 }
7235 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
7236 }))
7237 .child(
7238 h_flex()
7239 .gap_1()
7240 .py_1()
7241 .px_2()
7242 .rounded_r(RADIUS - BORDER_WIDTH)
7243 .border_l_1()
7244 .border_color(cx.theme().colors().border)
7245 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7246 .when(self.edit_prediction_preview.released_too_fast(), |el| {
7247 el.child(
7248 Label::new("Hold")
7249 .size(LabelSize::Small)
7250 .when(accept_keystroke.is_none(), |el| {
7251 el.strikethrough()
7252 })
7253 .line_height_style(LineHeightStyle::UiLabel),
7254 )
7255 })
7256 .id("edit_prediction_cursor_popover_keybind")
7257 .when(accept_keystroke.is_none(), |el| {
7258 let status_colors = cx.theme().status();
7259
7260 el.bg(status_colors.error_background)
7261 .border_color(status_colors.error.opacity(0.6))
7262 .child(Icon::new(IconName::Info).color(Color::Error))
7263 .cursor_default()
7264 .hoverable_tooltip(move |_window, cx| {
7265 cx.new(|_| MissingEditPredictionKeybindingTooltip)
7266 .into()
7267 })
7268 })
7269 .when_some(
7270 accept_keystroke.as_ref(),
7271 |el, accept_keystroke| {
7272 el.child(h_flex().children(ui::render_modifiers(
7273 &accept_keystroke.modifiers,
7274 PlatformStyle::platform(),
7275 Some(Color::Default),
7276 Some(IconSize::XSmall.rems().into()),
7277 false,
7278 )))
7279 },
7280 ),
7281 )
7282 .into_any(),
7283 );
7284 }
7285
7286 self.render_edit_prediction_cursor_popover_preview(
7287 prediction,
7288 cursor_point,
7289 style,
7290 cx,
7291 )?
7292 }
7293
7294 None if is_refreshing => match &self.stale_inline_completion_in_menu {
7295 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
7296 stale_completion,
7297 cursor_point,
7298 style,
7299 cx,
7300 )?,
7301
7302 None => {
7303 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
7304 }
7305 },
7306
7307 None => pending_completion_container().child(Label::new("No Prediction")),
7308 };
7309
7310 let completion = if is_refreshing {
7311 completion
7312 .with_animation(
7313 "loading-completion",
7314 Animation::new(Duration::from_secs(2))
7315 .repeat()
7316 .with_easing(pulsating_between(0.4, 0.8)),
7317 |label, delta| label.opacity(delta),
7318 )
7319 .into_any_element()
7320 } else {
7321 completion.into_any_element()
7322 };
7323
7324 let has_completion = self.active_inline_completion.is_some();
7325
7326 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7327 Some(
7328 h_flex()
7329 .min_w(min_width)
7330 .max_w(max_width)
7331 .flex_1()
7332 .elevation_2(cx)
7333 .border_color(cx.theme().colors().border)
7334 .child(
7335 div()
7336 .flex_1()
7337 .py_1()
7338 .px_2()
7339 .overflow_hidden()
7340 .child(completion),
7341 )
7342 .when_some(accept_keystroke, |el, accept_keystroke| {
7343 if !accept_keystroke.modifiers.modified() {
7344 return el;
7345 }
7346
7347 el.child(
7348 h_flex()
7349 .h_full()
7350 .border_l_1()
7351 .rounded_r_lg()
7352 .border_color(cx.theme().colors().border)
7353 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7354 .gap_1()
7355 .py_1()
7356 .px_2()
7357 .child(
7358 h_flex()
7359 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7360 .when(is_platform_style_mac, |parent| parent.gap_1())
7361 .child(h_flex().children(ui::render_modifiers(
7362 &accept_keystroke.modifiers,
7363 PlatformStyle::platform(),
7364 Some(if !has_completion {
7365 Color::Muted
7366 } else {
7367 Color::Default
7368 }),
7369 None,
7370 false,
7371 ))),
7372 )
7373 .child(Label::new("Preview").into_any_element())
7374 .opacity(if has_completion { 1.0 } else { 0.4 }),
7375 )
7376 })
7377 .into_any(),
7378 )
7379 }
7380
7381 fn render_edit_prediction_cursor_popover_preview(
7382 &self,
7383 completion: &InlineCompletionState,
7384 cursor_point: Point,
7385 style: &EditorStyle,
7386 cx: &mut Context<Editor>,
7387 ) -> Option<Div> {
7388 use text::ToPoint as _;
7389
7390 fn render_relative_row_jump(
7391 prefix: impl Into<String>,
7392 current_row: u32,
7393 target_row: u32,
7394 ) -> Div {
7395 let (row_diff, arrow) = if target_row < current_row {
7396 (current_row - target_row, IconName::ArrowUp)
7397 } else {
7398 (target_row - current_row, IconName::ArrowDown)
7399 };
7400
7401 h_flex()
7402 .child(
7403 Label::new(format!("{}{}", prefix.into(), row_diff))
7404 .color(Color::Muted)
7405 .size(LabelSize::Small),
7406 )
7407 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
7408 }
7409
7410 match &completion.completion {
7411 InlineCompletion::Move {
7412 target, snapshot, ..
7413 } => Some(
7414 h_flex()
7415 .px_2()
7416 .gap_2()
7417 .flex_1()
7418 .child(
7419 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
7420 Icon::new(IconName::ZedPredictDown)
7421 } else {
7422 Icon::new(IconName::ZedPredictUp)
7423 },
7424 )
7425 .child(Label::new("Jump to Edit")),
7426 ),
7427
7428 InlineCompletion::Edit {
7429 edits,
7430 edit_preview,
7431 snapshot,
7432 display_mode: _,
7433 } => {
7434 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
7435
7436 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
7437 &snapshot,
7438 &edits,
7439 edit_preview.as_ref()?,
7440 true,
7441 cx,
7442 )
7443 .first_line_preview();
7444
7445 let styled_text = gpui::StyledText::new(highlighted_edits.text)
7446 .with_default_highlights(&style.text, highlighted_edits.highlights);
7447
7448 let preview = h_flex()
7449 .gap_1()
7450 .min_w_16()
7451 .child(styled_text)
7452 .when(has_more_lines, |parent| parent.child("…"));
7453
7454 let left = if first_edit_row != cursor_point.row {
7455 render_relative_row_jump("", cursor_point.row, first_edit_row)
7456 .into_any_element()
7457 } else {
7458 Icon::new(IconName::ZedPredict).into_any_element()
7459 };
7460
7461 Some(
7462 h_flex()
7463 .h_full()
7464 .flex_1()
7465 .gap_2()
7466 .pr_1()
7467 .overflow_x_hidden()
7468 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7469 .child(left)
7470 .child(preview),
7471 )
7472 }
7473 }
7474 }
7475
7476 fn render_context_menu(
7477 &self,
7478 style: &EditorStyle,
7479 max_height_in_lines: u32,
7480 y_flipped: bool,
7481 window: &mut Window,
7482 cx: &mut Context<Editor>,
7483 ) -> Option<AnyElement> {
7484 let menu = self.context_menu.borrow();
7485 let menu = menu.as_ref()?;
7486 if !menu.visible() {
7487 return None;
7488 };
7489 Some(menu.render(style, max_height_in_lines, y_flipped, window, cx))
7490 }
7491
7492 fn render_context_menu_aside(
7493 &mut self,
7494 max_size: Size<Pixels>,
7495 window: &mut Window,
7496 cx: &mut Context<Editor>,
7497 ) -> Option<AnyElement> {
7498 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
7499 if menu.visible() {
7500 menu.render_aside(self, max_size, window, cx)
7501 } else {
7502 None
7503 }
7504 })
7505 }
7506
7507 fn hide_context_menu(
7508 &mut self,
7509 window: &mut Window,
7510 cx: &mut Context<Self>,
7511 ) -> Option<CodeContextMenu> {
7512 cx.notify();
7513 self.completion_tasks.clear();
7514 let context_menu = self.context_menu.borrow_mut().take();
7515 self.stale_inline_completion_in_menu.take();
7516 self.update_visible_inline_completion(window, cx);
7517 context_menu
7518 }
7519
7520 fn show_snippet_choices(
7521 &mut self,
7522 choices: &Vec<String>,
7523 selection: Range<Anchor>,
7524 cx: &mut Context<Self>,
7525 ) {
7526 if selection.start.buffer_id.is_none() {
7527 return;
7528 }
7529 let buffer_id = selection.start.buffer_id.unwrap();
7530 let buffer = self.buffer().read(cx).buffer(buffer_id);
7531 let id = post_inc(&mut self.next_completion_id);
7532
7533 if let Some(buffer) = buffer {
7534 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
7535 CompletionsMenu::new_snippet_choices(id, true, choices, selection, buffer),
7536 ));
7537 }
7538 }
7539
7540 pub fn insert_snippet(
7541 &mut self,
7542 insertion_ranges: &[Range<usize>],
7543 snippet: Snippet,
7544 window: &mut Window,
7545 cx: &mut Context<Self>,
7546 ) -> Result<()> {
7547 struct Tabstop<T> {
7548 is_end_tabstop: bool,
7549 ranges: Vec<Range<T>>,
7550 choices: Option<Vec<String>>,
7551 }
7552
7553 let tabstops = self.buffer.update(cx, |buffer, cx| {
7554 let snippet_text: Arc<str> = snippet.text.clone().into();
7555 let edits = insertion_ranges
7556 .iter()
7557 .cloned()
7558 .map(|range| (range, snippet_text.clone()));
7559 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
7560
7561 let snapshot = &*buffer.read(cx);
7562 let snippet = &snippet;
7563 snippet
7564 .tabstops
7565 .iter()
7566 .map(|tabstop| {
7567 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
7568 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
7569 });
7570 let mut tabstop_ranges = tabstop
7571 .ranges
7572 .iter()
7573 .flat_map(|tabstop_range| {
7574 let mut delta = 0_isize;
7575 insertion_ranges.iter().map(move |insertion_range| {
7576 let insertion_start = insertion_range.start as isize + delta;
7577 delta +=
7578 snippet.text.len() as isize - insertion_range.len() as isize;
7579
7580 let start = ((insertion_start + tabstop_range.start) as usize)
7581 .min(snapshot.len());
7582 let end = ((insertion_start + tabstop_range.end) as usize)
7583 .min(snapshot.len());
7584 snapshot.anchor_before(start)..snapshot.anchor_after(end)
7585 })
7586 })
7587 .collect::<Vec<_>>();
7588 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
7589
7590 Tabstop {
7591 is_end_tabstop,
7592 ranges: tabstop_ranges,
7593 choices: tabstop.choices.clone(),
7594 }
7595 })
7596 .collect::<Vec<_>>()
7597 });
7598 if let Some(tabstop) = tabstops.first() {
7599 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7600 s.select_ranges(tabstop.ranges.iter().cloned());
7601 });
7602
7603 if let Some(choices) = &tabstop.choices {
7604 if let Some(selection) = tabstop.ranges.first() {
7605 self.show_snippet_choices(choices, selection.clone(), cx)
7606 }
7607 }
7608
7609 // If we're already at the last tabstop and it's at the end of the snippet,
7610 // we're done, we don't need to keep the state around.
7611 if !tabstop.is_end_tabstop {
7612 let choices = tabstops
7613 .iter()
7614 .map(|tabstop| tabstop.choices.clone())
7615 .collect();
7616
7617 let ranges = tabstops
7618 .into_iter()
7619 .map(|tabstop| tabstop.ranges)
7620 .collect::<Vec<_>>();
7621
7622 self.snippet_stack.push(SnippetState {
7623 active_index: 0,
7624 ranges,
7625 choices,
7626 });
7627 }
7628
7629 // Check whether the just-entered snippet ends with an auto-closable bracket.
7630 if self.autoclose_regions.is_empty() {
7631 let snapshot = self.buffer.read(cx).snapshot(cx);
7632 for selection in &mut self.selections.all::<Point>(cx) {
7633 let selection_head = selection.head();
7634 let Some(scope) = snapshot.language_scope_at(selection_head) else {
7635 continue;
7636 };
7637
7638 let mut bracket_pair = None;
7639 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
7640 let prev_chars = snapshot
7641 .reversed_chars_at(selection_head)
7642 .collect::<String>();
7643 for (pair, enabled) in scope.brackets() {
7644 if enabled
7645 && pair.close
7646 && prev_chars.starts_with(pair.start.as_str())
7647 && next_chars.starts_with(pair.end.as_str())
7648 {
7649 bracket_pair = Some(pair.clone());
7650 break;
7651 }
7652 }
7653 if let Some(pair) = bracket_pair {
7654 let start = snapshot.anchor_after(selection_head);
7655 let end = snapshot.anchor_after(selection_head);
7656 self.autoclose_regions.push(AutocloseRegion {
7657 selection_id: selection.id,
7658 range: start..end,
7659 pair,
7660 });
7661 }
7662 }
7663 }
7664 }
7665 Ok(())
7666 }
7667
7668 pub fn move_to_next_snippet_tabstop(
7669 &mut self,
7670 window: &mut Window,
7671 cx: &mut Context<Self>,
7672 ) -> bool {
7673 self.move_to_snippet_tabstop(Bias::Right, window, cx)
7674 }
7675
7676 pub fn move_to_prev_snippet_tabstop(
7677 &mut self,
7678 window: &mut Window,
7679 cx: &mut Context<Self>,
7680 ) -> bool {
7681 self.move_to_snippet_tabstop(Bias::Left, window, cx)
7682 }
7683
7684 pub fn move_to_snippet_tabstop(
7685 &mut self,
7686 bias: Bias,
7687 window: &mut Window,
7688 cx: &mut Context<Self>,
7689 ) -> bool {
7690 if let Some(mut snippet) = self.snippet_stack.pop() {
7691 match bias {
7692 Bias::Left => {
7693 if snippet.active_index > 0 {
7694 snippet.active_index -= 1;
7695 } else {
7696 self.snippet_stack.push(snippet);
7697 return false;
7698 }
7699 }
7700 Bias::Right => {
7701 if snippet.active_index + 1 < snippet.ranges.len() {
7702 snippet.active_index += 1;
7703 } else {
7704 self.snippet_stack.push(snippet);
7705 return false;
7706 }
7707 }
7708 }
7709 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
7710 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7711 s.select_anchor_ranges(current_ranges.iter().cloned())
7712 });
7713
7714 if let Some(choices) = &snippet.choices[snippet.active_index] {
7715 if let Some(selection) = current_ranges.first() {
7716 self.show_snippet_choices(&choices, selection.clone(), cx);
7717 }
7718 }
7719
7720 // If snippet state is not at the last tabstop, push it back on the stack
7721 if snippet.active_index + 1 < snippet.ranges.len() {
7722 self.snippet_stack.push(snippet);
7723 }
7724 return true;
7725 }
7726 }
7727
7728 false
7729 }
7730
7731 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7732 self.transact(window, cx, |this, window, cx| {
7733 this.select_all(&SelectAll, window, cx);
7734 this.insert("", window, cx);
7735 });
7736 }
7737
7738 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
7739 self.transact(window, cx, |this, window, cx| {
7740 this.select_autoclose_pair(window, cx);
7741 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
7742 if !this.linked_edit_ranges.is_empty() {
7743 let selections = this.selections.all::<MultiBufferPoint>(cx);
7744 let snapshot = this.buffer.read(cx).snapshot(cx);
7745
7746 for selection in selections.iter() {
7747 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
7748 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
7749 if selection_start.buffer_id != selection_end.buffer_id {
7750 continue;
7751 }
7752 if let Some(ranges) =
7753 this.linked_editing_ranges_for(selection_start..selection_end, cx)
7754 {
7755 for (buffer, entries) in ranges {
7756 linked_ranges.entry(buffer).or_default().extend(entries);
7757 }
7758 }
7759 }
7760 }
7761
7762 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
7763 if !this.selections.line_mode {
7764 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
7765 for selection in &mut selections {
7766 if selection.is_empty() {
7767 let old_head = selection.head();
7768 let mut new_head =
7769 movement::left(&display_map, old_head.to_display_point(&display_map))
7770 .to_point(&display_map);
7771 if let Some((buffer, line_buffer_range)) = display_map
7772 .buffer_snapshot
7773 .buffer_line_for_row(MultiBufferRow(old_head.row))
7774 {
7775 let indent_size =
7776 buffer.indent_size_for_line(line_buffer_range.start.row);
7777 let indent_len = match indent_size.kind {
7778 IndentKind::Space => {
7779 buffer.settings_at(line_buffer_range.start, cx).tab_size
7780 }
7781 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
7782 };
7783 if old_head.column <= indent_size.len && old_head.column > 0 {
7784 let indent_len = indent_len.get();
7785 new_head = cmp::min(
7786 new_head,
7787 MultiBufferPoint::new(
7788 old_head.row,
7789 ((old_head.column - 1) / indent_len) * indent_len,
7790 ),
7791 );
7792 }
7793 }
7794
7795 selection.set_head(new_head, SelectionGoal::None);
7796 }
7797 }
7798 }
7799
7800 this.signature_help_state.set_backspace_pressed(true);
7801 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7802 s.select(selections)
7803 });
7804 this.insert("", window, cx);
7805 let empty_str: Arc<str> = Arc::from("");
7806 for (buffer, edits) in linked_ranges {
7807 let snapshot = buffer.read(cx).snapshot();
7808 use text::ToPoint as TP;
7809
7810 let edits = edits
7811 .into_iter()
7812 .map(|range| {
7813 let end_point = TP::to_point(&range.end, &snapshot);
7814 let mut start_point = TP::to_point(&range.start, &snapshot);
7815
7816 if end_point == start_point {
7817 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
7818 .saturating_sub(1);
7819 start_point =
7820 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
7821 };
7822
7823 (start_point..end_point, empty_str.clone())
7824 })
7825 .sorted_by_key(|(range, _)| range.start)
7826 .collect::<Vec<_>>();
7827 buffer.update(cx, |this, cx| {
7828 this.edit(edits, None, cx);
7829 })
7830 }
7831 this.refresh_inline_completion(true, false, window, cx);
7832 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
7833 });
7834 }
7835
7836 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
7837 self.transact(window, cx, |this, window, cx| {
7838 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7839 let line_mode = s.line_mode;
7840 s.move_with(|map, selection| {
7841 if selection.is_empty() && !line_mode {
7842 let cursor = movement::right(map, selection.head());
7843 selection.end = cursor;
7844 selection.reversed = true;
7845 selection.goal = SelectionGoal::None;
7846 }
7847 })
7848 });
7849 this.insert("", window, cx);
7850 this.refresh_inline_completion(true, false, window, cx);
7851 });
7852 }
7853
7854 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
7855 if self.move_to_prev_snippet_tabstop(window, cx) {
7856 return;
7857 }
7858
7859 self.outdent(&Outdent, window, cx);
7860 }
7861
7862 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
7863 if self.move_to_next_snippet_tabstop(window, cx) || self.read_only(cx) {
7864 return;
7865 }
7866
7867 let mut selections = self.selections.all_adjusted(cx);
7868 let buffer = self.buffer.read(cx);
7869 let snapshot = buffer.snapshot(cx);
7870 let rows_iter = selections.iter().map(|s| s.head().row);
7871 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
7872
7873 let mut edits = Vec::new();
7874 let mut prev_edited_row = 0;
7875 let mut row_delta = 0;
7876 for selection in &mut selections {
7877 if selection.start.row != prev_edited_row {
7878 row_delta = 0;
7879 }
7880 prev_edited_row = selection.end.row;
7881
7882 // If the selection is non-empty, then increase the indentation of the selected lines.
7883 if !selection.is_empty() {
7884 row_delta =
7885 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
7886 continue;
7887 }
7888
7889 // If the selection is empty and the cursor is in the leading whitespace before the
7890 // suggested indentation, then auto-indent the line.
7891 let cursor = selection.head();
7892 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
7893 if let Some(suggested_indent) =
7894 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
7895 {
7896 if cursor.column < suggested_indent.len
7897 && cursor.column <= current_indent.len
7898 && current_indent.len <= suggested_indent.len
7899 {
7900 selection.start = Point::new(cursor.row, suggested_indent.len);
7901 selection.end = selection.start;
7902 if row_delta == 0 {
7903 edits.extend(Buffer::edit_for_indent_size_adjustment(
7904 cursor.row,
7905 current_indent,
7906 suggested_indent,
7907 ));
7908 row_delta = suggested_indent.len - current_indent.len;
7909 }
7910 continue;
7911 }
7912 }
7913
7914 // Otherwise, insert a hard or soft tab.
7915 let settings = buffer.language_settings_at(cursor, cx);
7916 let tab_size = if settings.hard_tabs {
7917 IndentSize::tab()
7918 } else {
7919 let tab_size = settings.tab_size.get();
7920 let char_column = snapshot
7921 .text_for_range(Point::new(cursor.row, 0)..cursor)
7922 .flat_map(str::chars)
7923 .count()
7924 + row_delta as usize;
7925 let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size);
7926 IndentSize::spaces(chars_to_next_tab_stop)
7927 };
7928 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
7929 selection.end = selection.start;
7930 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
7931 row_delta += tab_size.len;
7932 }
7933
7934 self.transact(window, cx, |this, window, cx| {
7935 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
7936 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7937 s.select(selections)
7938 });
7939 this.refresh_inline_completion(true, false, window, cx);
7940 });
7941 }
7942
7943 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
7944 if self.read_only(cx) {
7945 return;
7946 }
7947 let mut selections = self.selections.all::<Point>(cx);
7948 let mut prev_edited_row = 0;
7949 let mut row_delta = 0;
7950 let mut edits = Vec::new();
7951 let buffer = self.buffer.read(cx);
7952 let snapshot = buffer.snapshot(cx);
7953 for selection in &mut selections {
7954 if selection.start.row != prev_edited_row {
7955 row_delta = 0;
7956 }
7957 prev_edited_row = selection.end.row;
7958
7959 row_delta =
7960 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
7961 }
7962
7963 self.transact(window, cx, |this, window, cx| {
7964 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
7965 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7966 s.select(selections)
7967 });
7968 });
7969 }
7970
7971 fn indent_selection(
7972 buffer: &MultiBuffer,
7973 snapshot: &MultiBufferSnapshot,
7974 selection: &mut Selection<Point>,
7975 edits: &mut Vec<(Range<Point>, String)>,
7976 delta_for_start_row: u32,
7977 cx: &App,
7978 ) -> u32 {
7979 let settings = buffer.language_settings_at(selection.start, cx);
7980 let tab_size = settings.tab_size.get();
7981 let indent_kind = if settings.hard_tabs {
7982 IndentKind::Tab
7983 } else {
7984 IndentKind::Space
7985 };
7986 let mut start_row = selection.start.row;
7987 let mut end_row = selection.end.row + 1;
7988
7989 // If a selection ends at the beginning of a line, don't indent
7990 // that last line.
7991 if selection.end.column == 0 && selection.end.row > selection.start.row {
7992 end_row -= 1;
7993 }
7994
7995 // Avoid re-indenting a row that has already been indented by a
7996 // previous selection, but still update this selection's column
7997 // to reflect that indentation.
7998 if delta_for_start_row > 0 {
7999 start_row += 1;
8000 selection.start.column += delta_for_start_row;
8001 if selection.end.row == selection.start.row {
8002 selection.end.column += delta_for_start_row;
8003 }
8004 }
8005
8006 let mut delta_for_end_row = 0;
8007 let has_multiple_rows = start_row + 1 != end_row;
8008 for row in start_row..end_row {
8009 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
8010 let indent_delta = match (current_indent.kind, indent_kind) {
8011 (IndentKind::Space, IndentKind::Space) => {
8012 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
8013 IndentSize::spaces(columns_to_next_tab_stop)
8014 }
8015 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
8016 (_, IndentKind::Tab) => IndentSize::tab(),
8017 };
8018
8019 let start = if has_multiple_rows || current_indent.len < selection.start.column {
8020 0
8021 } else {
8022 selection.start.column
8023 };
8024 let row_start = Point::new(row, start);
8025 edits.push((
8026 row_start..row_start,
8027 indent_delta.chars().collect::<String>(),
8028 ));
8029
8030 // Update this selection's endpoints to reflect the indentation.
8031 if row == selection.start.row {
8032 selection.start.column += indent_delta.len;
8033 }
8034 if row == selection.end.row {
8035 selection.end.column += indent_delta.len;
8036 delta_for_end_row = indent_delta.len;
8037 }
8038 }
8039
8040 if selection.start.row == selection.end.row {
8041 delta_for_start_row + delta_for_end_row
8042 } else {
8043 delta_for_end_row
8044 }
8045 }
8046
8047 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
8048 if self.read_only(cx) {
8049 return;
8050 }
8051 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8052 let selections = self.selections.all::<Point>(cx);
8053 let mut deletion_ranges = Vec::new();
8054 let mut last_outdent = None;
8055 {
8056 let buffer = self.buffer.read(cx);
8057 let snapshot = buffer.snapshot(cx);
8058 for selection in &selections {
8059 let settings = buffer.language_settings_at(selection.start, cx);
8060 let tab_size = settings.tab_size.get();
8061 let mut rows = selection.spanned_rows(false, &display_map);
8062
8063 // Avoid re-outdenting a row that has already been outdented by a
8064 // previous selection.
8065 if let Some(last_row) = last_outdent {
8066 if last_row == rows.start {
8067 rows.start = rows.start.next_row();
8068 }
8069 }
8070 let has_multiple_rows = rows.len() > 1;
8071 for row in rows.iter_rows() {
8072 let indent_size = snapshot.indent_size_for_line(row);
8073 if indent_size.len > 0 {
8074 let deletion_len = match indent_size.kind {
8075 IndentKind::Space => {
8076 let columns_to_prev_tab_stop = indent_size.len % tab_size;
8077 if columns_to_prev_tab_stop == 0 {
8078 tab_size
8079 } else {
8080 columns_to_prev_tab_stop
8081 }
8082 }
8083 IndentKind::Tab => 1,
8084 };
8085 let start = if has_multiple_rows
8086 || deletion_len > selection.start.column
8087 || indent_size.len < selection.start.column
8088 {
8089 0
8090 } else {
8091 selection.start.column - deletion_len
8092 };
8093 deletion_ranges.push(
8094 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
8095 );
8096 last_outdent = Some(row);
8097 }
8098 }
8099 }
8100 }
8101
8102 self.transact(window, cx, |this, window, cx| {
8103 this.buffer.update(cx, |buffer, cx| {
8104 let empty_str: Arc<str> = Arc::default();
8105 buffer.edit(
8106 deletion_ranges
8107 .into_iter()
8108 .map(|range| (range, empty_str.clone())),
8109 None,
8110 cx,
8111 );
8112 });
8113 let selections = this.selections.all::<usize>(cx);
8114 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8115 s.select(selections)
8116 });
8117 });
8118 }
8119
8120 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
8121 if self.read_only(cx) {
8122 return;
8123 }
8124 let selections = self
8125 .selections
8126 .all::<usize>(cx)
8127 .into_iter()
8128 .map(|s| s.range());
8129
8130 self.transact(window, cx, |this, window, cx| {
8131 this.buffer.update(cx, |buffer, cx| {
8132 buffer.autoindent_ranges(selections, cx);
8133 });
8134 let selections = this.selections.all::<usize>(cx);
8135 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8136 s.select(selections)
8137 });
8138 });
8139 }
8140
8141 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
8142 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8143 let selections = self.selections.all::<Point>(cx);
8144
8145 let mut new_cursors = Vec::new();
8146 let mut edit_ranges = Vec::new();
8147 let mut selections = selections.iter().peekable();
8148 while let Some(selection) = selections.next() {
8149 let mut rows = selection.spanned_rows(false, &display_map);
8150 let goal_display_column = selection.head().to_display_point(&display_map).column();
8151
8152 // Accumulate contiguous regions of rows that we want to delete.
8153 while let Some(next_selection) = selections.peek() {
8154 let next_rows = next_selection.spanned_rows(false, &display_map);
8155 if next_rows.start <= rows.end {
8156 rows.end = next_rows.end;
8157 selections.next().unwrap();
8158 } else {
8159 break;
8160 }
8161 }
8162
8163 let buffer = &display_map.buffer_snapshot;
8164 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
8165 let edit_end;
8166 let cursor_buffer_row;
8167 if buffer.max_point().row >= rows.end.0 {
8168 // If there's a line after the range, delete the \n from the end of the row range
8169 // and position the cursor on the next line.
8170 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
8171 cursor_buffer_row = rows.end;
8172 } else {
8173 // If there isn't a line after the range, delete the \n from the line before the
8174 // start of the row range and position the cursor there.
8175 edit_start = edit_start.saturating_sub(1);
8176 edit_end = buffer.len();
8177 cursor_buffer_row = rows.start.previous_row();
8178 }
8179
8180 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
8181 *cursor.column_mut() =
8182 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
8183
8184 new_cursors.push((
8185 selection.id,
8186 buffer.anchor_after(cursor.to_point(&display_map)),
8187 ));
8188 edit_ranges.push(edit_start..edit_end);
8189 }
8190
8191 self.transact(window, cx, |this, window, cx| {
8192 let buffer = this.buffer.update(cx, |buffer, cx| {
8193 let empty_str: Arc<str> = Arc::default();
8194 buffer.edit(
8195 edit_ranges
8196 .into_iter()
8197 .map(|range| (range, empty_str.clone())),
8198 None,
8199 cx,
8200 );
8201 buffer.snapshot(cx)
8202 });
8203 let new_selections = new_cursors
8204 .into_iter()
8205 .map(|(id, cursor)| {
8206 let cursor = cursor.to_point(&buffer);
8207 Selection {
8208 id,
8209 start: cursor,
8210 end: cursor,
8211 reversed: false,
8212 goal: SelectionGoal::None,
8213 }
8214 })
8215 .collect();
8216
8217 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8218 s.select(new_selections);
8219 });
8220 });
8221 }
8222
8223 pub fn join_lines_impl(
8224 &mut self,
8225 insert_whitespace: bool,
8226 window: &mut Window,
8227 cx: &mut Context<Self>,
8228 ) {
8229 if self.read_only(cx) {
8230 return;
8231 }
8232 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
8233 for selection in self.selections.all::<Point>(cx) {
8234 let start = MultiBufferRow(selection.start.row);
8235 // Treat single line selections as if they include the next line. Otherwise this action
8236 // would do nothing for single line selections individual cursors.
8237 let end = if selection.start.row == selection.end.row {
8238 MultiBufferRow(selection.start.row + 1)
8239 } else {
8240 MultiBufferRow(selection.end.row)
8241 };
8242
8243 if let Some(last_row_range) = row_ranges.last_mut() {
8244 if start <= last_row_range.end {
8245 last_row_range.end = end;
8246 continue;
8247 }
8248 }
8249 row_ranges.push(start..end);
8250 }
8251
8252 let snapshot = self.buffer.read(cx).snapshot(cx);
8253 let mut cursor_positions = Vec::new();
8254 for row_range in &row_ranges {
8255 let anchor = snapshot.anchor_before(Point::new(
8256 row_range.end.previous_row().0,
8257 snapshot.line_len(row_range.end.previous_row()),
8258 ));
8259 cursor_positions.push(anchor..anchor);
8260 }
8261
8262 self.transact(window, cx, |this, window, cx| {
8263 for row_range in row_ranges.into_iter().rev() {
8264 for row in row_range.iter_rows().rev() {
8265 let end_of_line = Point::new(row.0, snapshot.line_len(row));
8266 let next_line_row = row.next_row();
8267 let indent = snapshot.indent_size_for_line(next_line_row);
8268 let start_of_next_line = Point::new(next_line_row.0, indent.len);
8269
8270 let replace =
8271 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
8272 " "
8273 } else {
8274 ""
8275 };
8276
8277 this.buffer.update(cx, |buffer, cx| {
8278 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
8279 });
8280 }
8281 }
8282
8283 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8284 s.select_anchor_ranges(cursor_positions)
8285 });
8286 });
8287 }
8288
8289 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
8290 self.join_lines_impl(true, window, cx);
8291 }
8292
8293 pub fn sort_lines_case_sensitive(
8294 &mut self,
8295 _: &SortLinesCaseSensitive,
8296 window: &mut Window,
8297 cx: &mut Context<Self>,
8298 ) {
8299 self.manipulate_lines(window, cx, |lines| lines.sort())
8300 }
8301
8302 pub fn sort_lines_case_insensitive(
8303 &mut self,
8304 _: &SortLinesCaseInsensitive,
8305 window: &mut Window,
8306 cx: &mut Context<Self>,
8307 ) {
8308 self.manipulate_lines(window, cx, |lines| {
8309 lines.sort_by_key(|line| line.to_lowercase())
8310 })
8311 }
8312
8313 pub fn unique_lines_case_insensitive(
8314 &mut self,
8315 _: &UniqueLinesCaseInsensitive,
8316 window: &mut Window,
8317 cx: &mut Context<Self>,
8318 ) {
8319 self.manipulate_lines(window, cx, |lines| {
8320 let mut seen = HashSet::default();
8321 lines.retain(|line| seen.insert(line.to_lowercase()));
8322 })
8323 }
8324
8325 pub fn unique_lines_case_sensitive(
8326 &mut self,
8327 _: &UniqueLinesCaseSensitive,
8328 window: &mut Window,
8329 cx: &mut Context<Self>,
8330 ) {
8331 self.manipulate_lines(window, cx, |lines| {
8332 let mut seen = HashSet::default();
8333 lines.retain(|line| seen.insert(*line));
8334 })
8335 }
8336
8337 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
8338 let Some(project) = self.project.clone() else {
8339 return;
8340 };
8341 self.reload(project, window, cx)
8342 .detach_and_notify_err(window, cx);
8343 }
8344
8345 pub fn restore_file(
8346 &mut self,
8347 _: &::git::RestoreFile,
8348 window: &mut Window,
8349 cx: &mut Context<Self>,
8350 ) {
8351 let mut buffer_ids = HashSet::default();
8352 let snapshot = self.buffer().read(cx).snapshot(cx);
8353 for selection in self.selections.all::<usize>(cx) {
8354 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
8355 }
8356
8357 let buffer = self.buffer().read(cx);
8358 let ranges = buffer_ids
8359 .into_iter()
8360 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
8361 .collect::<Vec<_>>();
8362
8363 self.restore_hunks_in_ranges(ranges, window, cx);
8364 }
8365
8366 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
8367 let selections = self
8368 .selections
8369 .all(cx)
8370 .into_iter()
8371 .map(|s| s.range())
8372 .collect();
8373 self.restore_hunks_in_ranges(selections, window, cx);
8374 }
8375
8376 fn restore_hunks_in_ranges(
8377 &mut self,
8378 ranges: Vec<Range<Point>>,
8379 window: &mut Window,
8380 cx: &mut Context<Editor>,
8381 ) {
8382 let mut revert_changes = HashMap::default();
8383 let chunk_by = self
8384 .snapshot(window, cx)
8385 .hunks_for_ranges(ranges)
8386 .into_iter()
8387 .chunk_by(|hunk| hunk.buffer_id);
8388 for (buffer_id, hunks) in &chunk_by {
8389 let hunks = hunks.collect::<Vec<_>>();
8390 for hunk in &hunks {
8391 self.prepare_restore_change(&mut revert_changes, hunk, cx);
8392 }
8393 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
8394 }
8395 drop(chunk_by);
8396 if !revert_changes.is_empty() {
8397 self.transact(window, cx, |editor, window, cx| {
8398 editor.restore(revert_changes, window, cx);
8399 });
8400 }
8401 }
8402
8403 pub fn open_active_item_in_terminal(
8404 &mut self,
8405 _: &OpenInTerminal,
8406 window: &mut Window,
8407 cx: &mut Context<Self>,
8408 ) {
8409 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
8410 let project_path = buffer.read(cx).project_path(cx)?;
8411 let project = self.project.as_ref()?.read(cx);
8412 let entry = project.entry_for_path(&project_path, cx)?;
8413 let parent = match &entry.canonical_path {
8414 Some(canonical_path) => canonical_path.to_path_buf(),
8415 None => project.absolute_path(&project_path, cx)?,
8416 }
8417 .parent()?
8418 .to_path_buf();
8419 Some(parent)
8420 }) {
8421 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
8422 }
8423 }
8424
8425 fn set_breakpoint_context_menu(
8426 &mut self,
8427 row: DisplayRow,
8428 position: Option<Anchor>,
8429 kind: Arc<BreakpointKind>,
8430 clicked_point: gpui::Point<Pixels>,
8431 window: &mut Window,
8432 cx: &mut Context<Self>,
8433 ) {
8434 if !cx.has_flag::<Debugger>() {
8435 return;
8436 }
8437 let source = self
8438 .buffer
8439 .read(cx)
8440 .snapshot(cx)
8441 .anchor_before(Point::new(row.0, 0u32));
8442
8443 let context_menu =
8444 self.breakpoint_context_menu(position.unwrap_or(source), kind, window, cx);
8445
8446 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
8447 self,
8448 source,
8449 clicked_point,
8450 context_menu,
8451 window,
8452 cx,
8453 );
8454 }
8455
8456 fn add_edit_breakpoint_block(
8457 &mut self,
8458 anchor: Anchor,
8459 kind: &BreakpointKind,
8460 window: &mut Window,
8461 cx: &mut Context<Self>,
8462 ) {
8463 let weak_editor = cx.weak_entity();
8464 let bp_prompt =
8465 cx.new(|cx| BreakpointPromptEditor::new(weak_editor, anchor, kind.clone(), window, cx));
8466
8467 let height = bp_prompt.update(cx, |this, cx| {
8468 this.prompt
8469 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
8470 });
8471 let cloned_prompt = bp_prompt.clone();
8472 let blocks = vec![BlockProperties {
8473 style: BlockStyle::Sticky,
8474 placement: BlockPlacement::Above(anchor),
8475 height,
8476 render: Arc::new(move |cx| {
8477 *cloned_prompt.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
8478 cloned_prompt.clone().into_any_element()
8479 }),
8480 priority: 0,
8481 }];
8482
8483 let focus_handle = bp_prompt.focus_handle(cx);
8484 window.focus(&focus_handle);
8485
8486 let block_ids = self.insert_blocks(blocks, None, cx);
8487 bp_prompt.update(cx, |prompt, _| {
8488 prompt.add_block_ids(block_ids);
8489 });
8490 }
8491
8492 pub(crate) fn breakpoint_at_cursor_head(
8493 &self,
8494 window: &mut Window,
8495 cx: &mut Context<Self>,
8496 ) -> Option<(Anchor, Breakpoint)> {
8497 let cursor_position: Point = self.selections.newest(cx).head();
8498 let snapshot = self.snapshot(window, cx);
8499 // We Set the column position to zero so this function interacts correctly
8500 // between calls by clicking on the gutter & using an action to toggle a
8501 // breakpoint. Otherwise, toggling a breakpoint through an action wouldn't
8502 // untoggle a breakpoint that was added through clicking on the gutter
8503 let cursor_position = snapshot
8504 .display_snapshot
8505 .buffer_snapshot
8506 .anchor_before(Point::new(cursor_position.row, 0));
8507
8508 let project = self.project.clone();
8509
8510 let buffer_id = cursor_position.text_anchor.buffer_id?;
8511 let enclosing_excerpt = snapshot
8512 .buffer_snapshot
8513 .excerpt_ids_for_range(cursor_position..cursor_position)
8514 .next()?;
8515 let buffer = project?.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
8516 let buffer_snapshot = buffer.read(cx).snapshot();
8517
8518 let row = buffer_snapshot
8519 .summary_for_anchor::<text::PointUtf16>(&cursor_position.text_anchor)
8520 .row;
8521
8522 let bp = self
8523 .breakpoint_store
8524 .as_ref()?
8525 .read_with(cx, |breakpoint_store, cx| {
8526 breakpoint_store
8527 .breakpoints(
8528 &buffer,
8529 Some(cursor_position.text_anchor..(text::Anchor::MAX)),
8530 buffer_snapshot.clone(),
8531 cx,
8532 )
8533 .next()
8534 .and_then(move |(anchor, bp)| {
8535 let breakpoint_row = buffer_snapshot
8536 .summary_for_anchor::<text::PointUtf16>(anchor)
8537 .row;
8538
8539 if breakpoint_row == row {
8540 snapshot
8541 .buffer_snapshot
8542 .anchor_in_excerpt(enclosing_excerpt, *anchor)
8543 .map(|anchor| (anchor, bp.clone()))
8544 } else {
8545 None
8546 }
8547 })
8548 });
8549 bp
8550 }
8551
8552 pub fn edit_log_breakpoint(
8553 &mut self,
8554 _: &EditLogBreakpoint,
8555 window: &mut Window,
8556 cx: &mut Context<Self>,
8557 ) {
8558 let (anchor, bp) = self
8559 .breakpoint_at_cursor_head(window, cx)
8560 .unwrap_or_else(|| {
8561 let cursor_position: Point = self.selections.newest(cx).head();
8562
8563 let breakpoint_position = self
8564 .snapshot(window, cx)
8565 .display_snapshot
8566 .buffer_snapshot
8567 .anchor_before(Point::new(cursor_position.row, 0));
8568
8569 (
8570 breakpoint_position,
8571 Breakpoint {
8572 kind: BreakpointKind::Standard,
8573 },
8574 )
8575 });
8576
8577 self.add_edit_breakpoint_block(anchor, &bp.kind, window, cx);
8578 }
8579
8580 pub fn toggle_breakpoint(
8581 &mut self,
8582 _: &crate::actions::ToggleBreakpoint,
8583 window: &mut Window,
8584 cx: &mut Context<Self>,
8585 ) {
8586 let edit_action = BreakpointEditAction::Toggle;
8587
8588 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8589 self.edit_breakpoint_at_anchor(anchor, breakpoint.kind, edit_action, cx);
8590 } else {
8591 let cursor_position: Point = self.selections.newest(cx).head();
8592
8593 let breakpoint_position = self
8594 .snapshot(window, cx)
8595 .display_snapshot
8596 .buffer_snapshot
8597 .anchor_before(Point::new(cursor_position.row, 0));
8598
8599 self.edit_breakpoint_at_anchor(
8600 breakpoint_position,
8601 BreakpointKind::Standard,
8602 edit_action,
8603 cx,
8604 );
8605 }
8606 }
8607
8608 pub fn edit_breakpoint_at_anchor(
8609 &mut self,
8610 breakpoint_position: Anchor,
8611 kind: BreakpointKind,
8612 edit_action: BreakpointEditAction,
8613 cx: &mut Context<Self>,
8614 ) {
8615 let Some(breakpoint_store) = &self.breakpoint_store else {
8616 return;
8617 };
8618
8619 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
8620 if breakpoint_position == Anchor::min() {
8621 self.buffer()
8622 .read(cx)
8623 .excerpt_buffer_ids()
8624 .into_iter()
8625 .next()
8626 } else {
8627 None
8628 }
8629 }) else {
8630 return;
8631 };
8632
8633 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
8634 return;
8635 };
8636
8637 breakpoint_store.update(cx, |breakpoint_store, cx| {
8638 breakpoint_store.toggle_breakpoint(
8639 buffer,
8640 (breakpoint_position.text_anchor, Breakpoint { kind }),
8641 edit_action,
8642 cx,
8643 );
8644 });
8645
8646 cx.notify();
8647 }
8648
8649 #[cfg(any(test, feature = "test-support"))]
8650 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
8651 self.breakpoint_store.clone()
8652 }
8653
8654 pub fn prepare_restore_change(
8655 &self,
8656 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
8657 hunk: &MultiBufferDiffHunk,
8658 cx: &mut App,
8659 ) -> Option<()> {
8660 if hunk.is_created_file() {
8661 return None;
8662 }
8663 let buffer = self.buffer.read(cx);
8664 let diff = buffer.diff_for(hunk.buffer_id)?;
8665 let buffer = buffer.buffer(hunk.buffer_id)?;
8666 let buffer = buffer.read(cx);
8667 let original_text = diff
8668 .read(cx)
8669 .base_text()
8670 .as_rope()
8671 .slice(hunk.diff_base_byte_range.clone());
8672 let buffer_snapshot = buffer.snapshot();
8673 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
8674 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
8675 probe
8676 .0
8677 .start
8678 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
8679 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
8680 }) {
8681 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
8682 Some(())
8683 } else {
8684 None
8685 }
8686 }
8687
8688 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
8689 self.manipulate_lines(window, cx, |lines| lines.reverse())
8690 }
8691
8692 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
8693 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
8694 }
8695
8696 fn manipulate_lines<Fn>(
8697 &mut self,
8698 window: &mut Window,
8699 cx: &mut Context<Self>,
8700 mut callback: Fn,
8701 ) where
8702 Fn: FnMut(&mut Vec<&str>),
8703 {
8704 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8705 let buffer = self.buffer.read(cx).snapshot(cx);
8706
8707 let mut edits = Vec::new();
8708
8709 let selections = self.selections.all::<Point>(cx);
8710 let mut selections = selections.iter().peekable();
8711 let mut contiguous_row_selections = Vec::new();
8712 let mut new_selections = Vec::new();
8713 let mut added_lines = 0;
8714 let mut removed_lines = 0;
8715
8716 while let Some(selection) = selections.next() {
8717 let (start_row, end_row) = consume_contiguous_rows(
8718 &mut contiguous_row_selections,
8719 selection,
8720 &display_map,
8721 &mut selections,
8722 );
8723
8724 let start_point = Point::new(start_row.0, 0);
8725 let end_point = Point::new(
8726 end_row.previous_row().0,
8727 buffer.line_len(end_row.previous_row()),
8728 );
8729 let text = buffer
8730 .text_for_range(start_point..end_point)
8731 .collect::<String>();
8732
8733 let mut lines = text.split('\n').collect_vec();
8734
8735 let lines_before = lines.len();
8736 callback(&mut lines);
8737 let lines_after = lines.len();
8738
8739 edits.push((start_point..end_point, lines.join("\n")));
8740
8741 // Selections must change based on added and removed line count
8742 let start_row =
8743 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
8744 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
8745 new_selections.push(Selection {
8746 id: selection.id,
8747 start: start_row,
8748 end: end_row,
8749 goal: SelectionGoal::None,
8750 reversed: selection.reversed,
8751 });
8752
8753 if lines_after > lines_before {
8754 added_lines += lines_after - lines_before;
8755 } else if lines_before > lines_after {
8756 removed_lines += lines_before - lines_after;
8757 }
8758 }
8759
8760 self.transact(window, cx, |this, window, cx| {
8761 let buffer = this.buffer.update(cx, |buffer, cx| {
8762 buffer.edit(edits, None, cx);
8763 buffer.snapshot(cx)
8764 });
8765
8766 // Recalculate offsets on newly edited buffer
8767 let new_selections = new_selections
8768 .iter()
8769 .map(|s| {
8770 let start_point = Point::new(s.start.0, 0);
8771 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
8772 Selection {
8773 id: s.id,
8774 start: buffer.point_to_offset(start_point),
8775 end: buffer.point_to_offset(end_point),
8776 goal: s.goal,
8777 reversed: s.reversed,
8778 }
8779 })
8780 .collect();
8781
8782 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8783 s.select(new_selections);
8784 });
8785
8786 this.request_autoscroll(Autoscroll::fit(), cx);
8787 });
8788 }
8789
8790 pub fn convert_to_upper_case(
8791 &mut self,
8792 _: &ConvertToUpperCase,
8793 window: &mut Window,
8794 cx: &mut Context<Self>,
8795 ) {
8796 self.manipulate_text(window, cx, |text| text.to_uppercase())
8797 }
8798
8799 pub fn convert_to_lower_case(
8800 &mut self,
8801 _: &ConvertToLowerCase,
8802 window: &mut Window,
8803 cx: &mut Context<Self>,
8804 ) {
8805 self.manipulate_text(window, cx, |text| text.to_lowercase())
8806 }
8807
8808 pub fn convert_to_title_case(
8809 &mut self,
8810 _: &ConvertToTitleCase,
8811 window: &mut Window,
8812 cx: &mut Context<Self>,
8813 ) {
8814 self.manipulate_text(window, cx, |text| {
8815 text.split('\n')
8816 .map(|line| line.to_case(Case::Title))
8817 .join("\n")
8818 })
8819 }
8820
8821 pub fn convert_to_snake_case(
8822 &mut self,
8823 _: &ConvertToSnakeCase,
8824 window: &mut Window,
8825 cx: &mut Context<Self>,
8826 ) {
8827 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
8828 }
8829
8830 pub fn convert_to_kebab_case(
8831 &mut self,
8832 _: &ConvertToKebabCase,
8833 window: &mut Window,
8834 cx: &mut Context<Self>,
8835 ) {
8836 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
8837 }
8838
8839 pub fn convert_to_upper_camel_case(
8840 &mut self,
8841 _: &ConvertToUpperCamelCase,
8842 window: &mut Window,
8843 cx: &mut Context<Self>,
8844 ) {
8845 self.manipulate_text(window, cx, |text| {
8846 text.split('\n')
8847 .map(|line| line.to_case(Case::UpperCamel))
8848 .join("\n")
8849 })
8850 }
8851
8852 pub fn convert_to_lower_camel_case(
8853 &mut self,
8854 _: &ConvertToLowerCamelCase,
8855 window: &mut Window,
8856 cx: &mut Context<Self>,
8857 ) {
8858 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
8859 }
8860
8861 pub fn convert_to_opposite_case(
8862 &mut self,
8863 _: &ConvertToOppositeCase,
8864 window: &mut Window,
8865 cx: &mut Context<Self>,
8866 ) {
8867 self.manipulate_text(window, cx, |text| {
8868 text.chars()
8869 .fold(String::with_capacity(text.len()), |mut t, c| {
8870 if c.is_uppercase() {
8871 t.extend(c.to_lowercase());
8872 } else {
8873 t.extend(c.to_uppercase());
8874 }
8875 t
8876 })
8877 })
8878 }
8879
8880 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
8881 where
8882 Fn: FnMut(&str) -> String,
8883 {
8884 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8885 let buffer = self.buffer.read(cx).snapshot(cx);
8886
8887 let mut new_selections = Vec::new();
8888 let mut edits = Vec::new();
8889 let mut selection_adjustment = 0i32;
8890
8891 for selection in self.selections.all::<usize>(cx) {
8892 let selection_is_empty = selection.is_empty();
8893
8894 let (start, end) = if selection_is_empty {
8895 let word_range = movement::surrounding_word(
8896 &display_map,
8897 selection.start.to_display_point(&display_map),
8898 );
8899 let start = word_range.start.to_offset(&display_map, Bias::Left);
8900 let end = word_range.end.to_offset(&display_map, Bias::Left);
8901 (start, end)
8902 } else {
8903 (selection.start, selection.end)
8904 };
8905
8906 let text = buffer.text_for_range(start..end).collect::<String>();
8907 let old_length = text.len() as i32;
8908 let text = callback(&text);
8909
8910 new_selections.push(Selection {
8911 start: (start as i32 - selection_adjustment) as usize,
8912 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
8913 goal: SelectionGoal::None,
8914 ..selection
8915 });
8916
8917 selection_adjustment += old_length - text.len() as i32;
8918
8919 edits.push((start..end, text));
8920 }
8921
8922 self.transact(window, cx, |this, window, cx| {
8923 this.buffer.update(cx, |buffer, cx| {
8924 buffer.edit(edits, None, cx);
8925 });
8926
8927 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8928 s.select(new_selections);
8929 });
8930
8931 this.request_autoscroll(Autoscroll::fit(), cx);
8932 });
8933 }
8934
8935 pub fn duplicate(
8936 &mut self,
8937 upwards: bool,
8938 whole_lines: bool,
8939 window: &mut Window,
8940 cx: &mut Context<Self>,
8941 ) {
8942 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8943 let buffer = &display_map.buffer_snapshot;
8944 let selections = self.selections.all::<Point>(cx);
8945
8946 let mut edits = Vec::new();
8947 let mut selections_iter = selections.iter().peekable();
8948 while let Some(selection) = selections_iter.next() {
8949 let mut rows = selection.spanned_rows(false, &display_map);
8950 // duplicate line-wise
8951 if whole_lines || selection.start == selection.end {
8952 // Avoid duplicating the same lines twice.
8953 while let Some(next_selection) = selections_iter.peek() {
8954 let next_rows = next_selection.spanned_rows(false, &display_map);
8955 if next_rows.start < rows.end {
8956 rows.end = next_rows.end;
8957 selections_iter.next().unwrap();
8958 } else {
8959 break;
8960 }
8961 }
8962
8963 // Copy the text from the selected row region and splice it either at the start
8964 // or end of the region.
8965 let start = Point::new(rows.start.0, 0);
8966 let end = Point::new(
8967 rows.end.previous_row().0,
8968 buffer.line_len(rows.end.previous_row()),
8969 );
8970 let text = buffer
8971 .text_for_range(start..end)
8972 .chain(Some("\n"))
8973 .collect::<String>();
8974 let insert_location = if upwards {
8975 Point::new(rows.end.0, 0)
8976 } else {
8977 start
8978 };
8979 edits.push((insert_location..insert_location, text));
8980 } else {
8981 // duplicate character-wise
8982 let start = selection.start;
8983 let end = selection.end;
8984 let text = buffer.text_for_range(start..end).collect::<String>();
8985 edits.push((selection.end..selection.end, text));
8986 }
8987 }
8988
8989 self.transact(window, cx, |this, _, cx| {
8990 this.buffer.update(cx, |buffer, cx| {
8991 buffer.edit(edits, None, cx);
8992 });
8993
8994 this.request_autoscroll(Autoscroll::fit(), cx);
8995 });
8996 }
8997
8998 pub fn duplicate_line_up(
8999 &mut self,
9000 _: &DuplicateLineUp,
9001 window: &mut Window,
9002 cx: &mut Context<Self>,
9003 ) {
9004 self.duplicate(true, true, window, cx);
9005 }
9006
9007 pub fn duplicate_line_down(
9008 &mut self,
9009 _: &DuplicateLineDown,
9010 window: &mut Window,
9011 cx: &mut Context<Self>,
9012 ) {
9013 self.duplicate(false, true, window, cx);
9014 }
9015
9016 pub fn duplicate_selection(
9017 &mut self,
9018 _: &DuplicateSelection,
9019 window: &mut Window,
9020 cx: &mut Context<Self>,
9021 ) {
9022 self.duplicate(false, false, window, cx);
9023 }
9024
9025 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
9026 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9027 let buffer = self.buffer.read(cx).snapshot(cx);
9028
9029 let mut edits = Vec::new();
9030 let mut unfold_ranges = Vec::new();
9031 let mut refold_creases = Vec::new();
9032
9033 let selections = self.selections.all::<Point>(cx);
9034 let mut selections = selections.iter().peekable();
9035 let mut contiguous_row_selections = Vec::new();
9036 let mut new_selections = Vec::new();
9037
9038 while let Some(selection) = selections.next() {
9039 // Find all the selections that span a contiguous row range
9040 let (start_row, end_row) = consume_contiguous_rows(
9041 &mut contiguous_row_selections,
9042 selection,
9043 &display_map,
9044 &mut selections,
9045 );
9046
9047 // Move the text spanned by the row range to be before the line preceding the row range
9048 if start_row.0 > 0 {
9049 let range_to_move = Point::new(
9050 start_row.previous_row().0,
9051 buffer.line_len(start_row.previous_row()),
9052 )
9053 ..Point::new(
9054 end_row.previous_row().0,
9055 buffer.line_len(end_row.previous_row()),
9056 );
9057 let insertion_point = display_map
9058 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
9059 .0;
9060
9061 // Don't move lines across excerpts
9062 if buffer
9063 .excerpt_containing(insertion_point..range_to_move.end)
9064 .is_some()
9065 {
9066 let text = buffer
9067 .text_for_range(range_to_move.clone())
9068 .flat_map(|s| s.chars())
9069 .skip(1)
9070 .chain(['\n'])
9071 .collect::<String>();
9072
9073 edits.push((
9074 buffer.anchor_after(range_to_move.start)
9075 ..buffer.anchor_before(range_to_move.end),
9076 String::new(),
9077 ));
9078 let insertion_anchor = buffer.anchor_after(insertion_point);
9079 edits.push((insertion_anchor..insertion_anchor, text));
9080
9081 let row_delta = range_to_move.start.row - insertion_point.row + 1;
9082
9083 // Move selections up
9084 new_selections.extend(contiguous_row_selections.drain(..).map(
9085 |mut selection| {
9086 selection.start.row -= row_delta;
9087 selection.end.row -= row_delta;
9088 selection
9089 },
9090 ));
9091
9092 // Move folds up
9093 unfold_ranges.push(range_to_move.clone());
9094 for fold in display_map.folds_in_range(
9095 buffer.anchor_before(range_to_move.start)
9096 ..buffer.anchor_after(range_to_move.end),
9097 ) {
9098 let mut start = fold.range.start.to_point(&buffer);
9099 let mut end = fold.range.end.to_point(&buffer);
9100 start.row -= row_delta;
9101 end.row -= row_delta;
9102 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9103 }
9104 }
9105 }
9106
9107 // If we didn't move line(s), preserve the existing selections
9108 new_selections.append(&mut contiguous_row_selections);
9109 }
9110
9111 self.transact(window, cx, |this, window, cx| {
9112 this.unfold_ranges(&unfold_ranges, true, true, cx);
9113 this.buffer.update(cx, |buffer, cx| {
9114 for (range, text) in edits {
9115 buffer.edit([(range, text)], None, cx);
9116 }
9117 });
9118 this.fold_creases(refold_creases, true, window, cx);
9119 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9120 s.select(new_selections);
9121 })
9122 });
9123 }
9124
9125 pub fn move_line_down(
9126 &mut self,
9127 _: &MoveLineDown,
9128 window: &mut Window,
9129 cx: &mut Context<Self>,
9130 ) {
9131 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9132 let buffer = self.buffer.read(cx).snapshot(cx);
9133
9134 let mut edits = Vec::new();
9135 let mut unfold_ranges = Vec::new();
9136 let mut refold_creases = Vec::new();
9137
9138 let selections = self.selections.all::<Point>(cx);
9139 let mut selections = selections.iter().peekable();
9140 let mut contiguous_row_selections = Vec::new();
9141 let mut new_selections = Vec::new();
9142
9143 while let Some(selection) = selections.next() {
9144 // Find all the selections that span a contiguous row range
9145 let (start_row, end_row) = consume_contiguous_rows(
9146 &mut contiguous_row_selections,
9147 selection,
9148 &display_map,
9149 &mut selections,
9150 );
9151
9152 // Move the text spanned by the row range to be after the last line of the row range
9153 if end_row.0 <= buffer.max_point().row {
9154 let range_to_move =
9155 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
9156 let insertion_point = display_map
9157 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
9158 .0;
9159
9160 // Don't move lines across excerpt boundaries
9161 if buffer
9162 .excerpt_containing(range_to_move.start..insertion_point)
9163 .is_some()
9164 {
9165 let mut text = String::from("\n");
9166 text.extend(buffer.text_for_range(range_to_move.clone()));
9167 text.pop(); // Drop trailing newline
9168 edits.push((
9169 buffer.anchor_after(range_to_move.start)
9170 ..buffer.anchor_before(range_to_move.end),
9171 String::new(),
9172 ));
9173 let insertion_anchor = buffer.anchor_after(insertion_point);
9174 edits.push((insertion_anchor..insertion_anchor, text));
9175
9176 let row_delta = insertion_point.row - range_to_move.end.row + 1;
9177
9178 // Move selections down
9179 new_selections.extend(contiguous_row_selections.drain(..).map(
9180 |mut selection| {
9181 selection.start.row += row_delta;
9182 selection.end.row += row_delta;
9183 selection
9184 },
9185 ));
9186
9187 // Move folds down
9188 unfold_ranges.push(range_to_move.clone());
9189 for fold in display_map.folds_in_range(
9190 buffer.anchor_before(range_to_move.start)
9191 ..buffer.anchor_after(range_to_move.end),
9192 ) {
9193 let mut start = fold.range.start.to_point(&buffer);
9194 let mut end = fold.range.end.to_point(&buffer);
9195 start.row += row_delta;
9196 end.row += row_delta;
9197 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9198 }
9199 }
9200 }
9201
9202 // If we didn't move line(s), preserve the existing selections
9203 new_selections.append(&mut contiguous_row_selections);
9204 }
9205
9206 self.transact(window, cx, |this, window, cx| {
9207 this.unfold_ranges(&unfold_ranges, true, true, cx);
9208 this.buffer.update(cx, |buffer, cx| {
9209 for (range, text) in edits {
9210 buffer.edit([(range, text)], None, cx);
9211 }
9212 });
9213 this.fold_creases(refold_creases, true, window, cx);
9214 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9215 s.select(new_selections)
9216 });
9217 });
9218 }
9219
9220 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
9221 let text_layout_details = &self.text_layout_details(window);
9222 self.transact(window, cx, |this, window, cx| {
9223 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9224 let mut edits: Vec<(Range<usize>, String)> = Default::default();
9225 let line_mode = s.line_mode;
9226 s.move_with(|display_map, selection| {
9227 if !selection.is_empty() || line_mode {
9228 return;
9229 }
9230
9231 let mut head = selection.head();
9232 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
9233 if head.column() == display_map.line_len(head.row()) {
9234 transpose_offset = display_map
9235 .buffer_snapshot
9236 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9237 }
9238
9239 if transpose_offset == 0 {
9240 return;
9241 }
9242
9243 *head.column_mut() += 1;
9244 head = display_map.clip_point(head, Bias::Right);
9245 let goal = SelectionGoal::HorizontalPosition(
9246 display_map
9247 .x_for_display_point(head, text_layout_details)
9248 .into(),
9249 );
9250 selection.collapse_to(head, goal);
9251
9252 let transpose_start = display_map
9253 .buffer_snapshot
9254 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9255 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
9256 let transpose_end = display_map
9257 .buffer_snapshot
9258 .clip_offset(transpose_offset + 1, Bias::Right);
9259 if let Some(ch) =
9260 display_map.buffer_snapshot.chars_at(transpose_start).next()
9261 {
9262 edits.push((transpose_start..transpose_offset, String::new()));
9263 edits.push((transpose_end..transpose_end, ch.to_string()));
9264 }
9265 }
9266 });
9267 edits
9268 });
9269 this.buffer
9270 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9271 let selections = this.selections.all::<usize>(cx);
9272 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9273 s.select(selections);
9274 });
9275 });
9276 }
9277
9278 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
9279 self.rewrap_impl(RewrapOptions::default(), cx)
9280 }
9281
9282 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
9283 let buffer = self.buffer.read(cx).snapshot(cx);
9284 let selections = self.selections.all::<Point>(cx);
9285 let mut selections = selections.iter().peekable();
9286
9287 let mut edits = Vec::new();
9288 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
9289
9290 while let Some(selection) = selections.next() {
9291 let mut start_row = selection.start.row;
9292 let mut end_row = selection.end.row;
9293
9294 // Skip selections that overlap with a range that has already been rewrapped.
9295 let selection_range = start_row..end_row;
9296 if rewrapped_row_ranges
9297 .iter()
9298 .any(|range| range.overlaps(&selection_range))
9299 {
9300 continue;
9301 }
9302
9303 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
9304
9305 // Since not all lines in the selection may be at the same indent
9306 // level, choose the indent size that is the most common between all
9307 // of the lines.
9308 //
9309 // If there is a tie, we use the deepest indent.
9310 let (indent_size, indent_end) = {
9311 let mut indent_size_occurrences = HashMap::default();
9312 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
9313
9314 for row in start_row..=end_row {
9315 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
9316 rows_by_indent_size.entry(indent).or_default().push(row);
9317 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
9318 }
9319
9320 let indent_size = indent_size_occurrences
9321 .into_iter()
9322 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
9323 .map(|(indent, _)| indent)
9324 .unwrap_or_default();
9325 let row = rows_by_indent_size[&indent_size][0];
9326 let indent_end = Point::new(row, indent_size.len);
9327
9328 (indent_size, indent_end)
9329 };
9330
9331 let mut line_prefix = indent_size.chars().collect::<String>();
9332
9333 let mut inside_comment = false;
9334 if let Some(comment_prefix) =
9335 buffer
9336 .language_scope_at(selection.head())
9337 .and_then(|language| {
9338 language
9339 .line_comment_prefixes()
9340 .iter()
9341 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
9342 .cloned()
9343 })
9344 {
9345 line_prefix.push_str(&comment_prefix);
9346 inside_comment = true;
9347 }
9348
9349 let language_settings = buffer.language_settings_at(selection.head(), cx);
9350 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
9351 RewrapBehavior::InComments => inside_comment,
9352 RewrapBehavior::InSelections => !selection.is_empty(),
9353 RewrapBehavior::Anywhere => true,
9354 };
9355
9356 let should_rewrap = options.override_language_settings
9357 || allow_rewrap_based_on_language
9358 || self.hard_wrap.is_some();
9359 if !should_rewrap {
9360 continue;
9361 }
9362
9363 if selection.is_empty() {
9364 'expand_upwards: while start_row > 0 {
9365 let prev_row = start_row - 1;
9366 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
9367 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
9368 {
9369 start_row = prev_row;
9370 } else {
9371 break 'expand_upwards;
9372 }
9373 }
9374
9375 'expand_downwards: while end_row < buffer.max_point().row {
9376 let next_row = end_row + 1;
9377 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
9378 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
9379 {
9380 end_row = next_row;
9381 } else {
9382 break 'expand_downwards;
9383 }
9384 }
9385 }
9386
9387 let start = Point::new(start_row, 0);
9388 let start_offset = start.to_offset(&buffer);
9389 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
9390 let selection_text = buffer.text_for_range(start..end).collect::<String>();
9391 let Some(lines_without_prefixes) = selection_text
9392 .lines()
9393 .map(|line| {
9394 line.strip_prefix(&line_prefix)
9395 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
9396 .ok_or_else(|| {
9397 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
9398 })
9399 })
9400 .collect::<Result<Vec<_>, _>>()
9401 .log_err()
9402 else {
9403 continue;
9404 };
9405
9406 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
9407 buffer
9408 .language_settings_at(Point::new(start_row, 0), cx)
9409 .preferred_line_length as usize
9410 });
9411 let wrapped_text = wrap_with_prefix(
9412 line_prefix,
9413 lines_without_prefixes.join("\n"),
9414 wrap_column,
9415 tab_size,
9416 options.preserve_existing_whitespace,
9417 );
9418
9419 // TODO: should always use char-based diff while still supporting cursor behavior that
9420 // matches vim.
9421 let mut diff_options = DiffOptions::default();
9422 if options.override_language_settings {
9423 diff_options.max_word_diff_len = 0;
9424 diff_options.max_word_diff_line_count = 0;
9425 } else {
9426 diff_options.max_word_diff_len = usize::MAX;
9427 diff_options.max_word_diff_line_count = usize::MAX;
9428 }
9429
9430 for (old_range, new_text) in
9431 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
9432 {
9433 let edit_start = buffer.anchor_after(start_offset + old_range.start);
9434 let edit_end = buffer.anchor_after(start_offset + old_range.end);
9435 edits.push((edit_start..edit_end, new_text));
9436 }
9437
9438 rewrapped_row_ranges.push(start_row..=end_row);
9439 }
9440
9441 self.buffer
9442 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9443 }
9444
9445 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
9446 let mut text = String::new();
9447 let buffer = self.buffer.read(cx).snapshot(cx);
9448 let mut selections = self.selections.all::<Point>(cx);
9449 let mut clipboard_selections = Vec::with_capacity(selections.len());
9450 {
9451 let max_point = buffer.max_point();
9452 let mut is_first = true;
9453 for selection in &mut selections {
9454 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9455 if is_entire_line {
9456 selection.start = Point::new(selection.start.row, 0);
9457 if !selection.is_empty() && selection.end.column == 0 {
9458 selection.end = cmp::min(max_point, selection.end);
9459 } else {
9460 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
9461 }
9462 selection.goal = SelectionGoal::None;
9463 }
9464 if is_first {
9465 is_first = false;
9466 } else {
9467 text += "\n";
9468 }
9469 let mut len = 0;
9470 for chunk in buffer.text_for_range(selection.start..selection.end) {
9471 text.push_str(chunk);
9472 len += chunk.len();
9473 }
9474 clipboard_selections.push(ClipboardSelection {
9475 len,
9476 is_entire_line,
9477 first_line_indent: buffer
9478 .indent_size_for_line(MultiBufferRow(selection.start.row))
9479 .len,
9480 });
9481 }
9482 }
9483
9484 self.transact(window, cx, |this, window, cx| {
9485 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9486 s.select(selections);
9487 });
9488 this.insert("", window, cx);
9489 });
9490 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
9491 }
9492
9493 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
9494 let item = self.cut_common(window, cx);
9495 cx.write_to_clipboard(item);
9496 }
9497
9498 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
9499 self.change_selections(None, window, cx, |s| {
9500 s.move_with(|snapshot, sel| {
9501 if sel.is_empty() {
9502 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
9503 }
9504 });
9505 });
9506 let item = self.cut_common(window, cx);
9507 cx.set_global(KillRing(item))
9508 }
9509
9510 pub fn kill_ring_yank(
9511 &mut self,
9512 _: &KillRingYank,
9513 window: &mut Window,
9514 cx: &mut Context<Self>,
9515 ) {
9516 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
9517 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
9518 (kill_ring.text().to_string(), kill_ring.metadata_json())
9519 } else {
9520 return;
9521 }
9522 } else {
9523 return;
9524 };
9525 self.do_paste(&text, metadata, false, window, cx);
9526 }
9527
9528 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
9529 self.do_copy(true, cx);
9530 }
9531
9532 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
9533 self.do_copy(false, cx);
9534 }
9535
9536 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
9537 let selections = self.selections.all::<Point>(cx);
9538 let buffer = self.buffer.read(cx).read(cx);
9539 let mut text = String::new();
9540
9541 let mut clipboard_selections = Vec::with_capacity(selections.len());
9542 {
9543 let max_point = buffer.max_point();
9544 let mut is_first = true;
9545 for selection in &selections {
9546 let mut start = selection.start;
9547 let mut end = selection.end;
9548 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9549 if is_entire_line {
9550 start = Point::new(start.row, 0);
9551 end = cmp::min(max_point, Point::new(end.row + 1, 0));
9552 }
9553
9554 let mut trimmed_selections = Vec::new();
9555 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
9556 let row = MultiBufferRow(start.row);
9557 let first_indent = buffer.indent_size_for_line(row);
9558 if first_indent.len == 0 || start.column > first_indent.len {
9559 trimmed_selections.push(start..end);
9560 } else {
9561 trimmed_selections.push(
9562 Point::new(row.0, first_indent.len)
9563 ..Point::new(row.0, buffer.line_len(row)),
9564 );
9565 for row in start.row + 1..=end.row {
9566 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
9567 if row_indent_size.len >= first_indent.len {
9568 trimmed_selections.push(
9569 Point::new(row, first_indent.len)
9570 ..Point::new(row, buffer.line_len(MultiBufferRow(row))),
9571 );
9572 } else {
9573 trimmed_selections.clear();
9574 trimmed_selections.push(start..end);
9575 break;
9576 }
9577 }
9578 }
9579 } else {
9580 trimmed_selections.push(start..end);
9581 }
9582
9583 for trimmed_range in trimmed_selections {
9584 if is_first {
9585 is_first = false;
9586 } else {
9587 text += "\n";
9588 }
9589 let mut len = 0;
9590 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
9591 text.push_str(chunk);
9592 len += chunk.len();
9593 }
9594 clipboard_selections.push(ClipboardSelection {
9595 len,
9596 is_entire_line,
9597 first_line_indent: buffer
9598 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
9599 .len,
9600 });
9601 }
9602 }
9603 }
9604
9605 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
9606 text,
9607 clipboard_selections,
9608 ));
9609 }
9610
9611 pub fn do_paste(
9612 &mut self,
9613 text: &String,
9614 clipboard_selections: Option<Vec<ClipboardSelection>>,
9615 handle_entire_lines: bool,
9616 window: &mut Window,
9617 cx: &mut Context<Self>,
9618 ) {
9619 if self.read_only(cx) {
9620 return;
9621 }
9622
9623 let clipboard_text = Cow::Borrowed(text);
9624
9625 self.transact(window, cx, |this, window, cx| {
9626 if let Some(mut clipboard_selections) = clipboard_selections {
9627 let old_selections = this.selections.all::<usize>(cx);
9628 let all_selections_were_entire_line =
9629 clipboard_selections.iter().all(|s| s.is_entire_line);
9630 let first_selection_indent_column =
9631 clipboard_selections.first().map(|s| s.first_line_indent);
9632 if clipboard_selections.len() != old_selections.len() {
9633 clipboard_selections.drain(..);
9634 }
9635 let cursor_offset = this.selections.last::<usize>(cx).head();
9636 let mut auto_indent_on_paste = true;
9637
9638 this.buffer.update(cx, |buffer, cx| {
9639 let snapshot = buffer.read(cx);
9640 auto_indent_on_paste = snapshot
9641 .language_settings_at(cursor_offset, cx)
9642 .auto_indent_on_paste;
9643
9644 let mut start_offset = 0;
9645 let mut edits = Vec::new();
9646 let mut original_indent_columns = Vec::new();
9647 for (ix, selection) in old_selections.iter().enumerate() {
9648 let to_insert;
9649 let entire_line;
9650 let original_indent_column;
9651 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
9652 let end_offset = start_offset + clipboard_selection.len;
9653 to_insert = &clipboard_text[start_offset..end_offset];
9654 entire_line = clipboard_selection.is_entire_line;
9655 start_offset = end_offset + 1;
9656 original_indent_column = Some(clipboard_selection.first_line_indent);
9657 } else {
9658 to_insert = clipboard_text.as_str();
9659 entire_line = all_selections_were_entire_line;
9660 original_indent_column = first_selection_indent_column
9661 }
9662
9663 // If the corresponding selection was empty when this slice of the
9664 // clipboard text was written, then the entire line containing the
9665 // selection was copied. If this selection is also currently empty,
9666 // then paste the line before the current line of the buffer.
9667 let range = if selection.is_empty() && handle_entire_lines && entire_line {
9668 let column = selection.start.to_point(&snapshot).column as usize;
9669 let line_start = selection.start - column;
9670 line_start..line_start
9671 } else {
9672 selection.range()
9673 };
9674
9675 edits.push((range, to_insert));
9676 original_indent_columns.push(original_indent_column);
9677 }
9678 drop(snapshot);
9679
9680 buffer.edit(
9681 edits,
9682 if auto_indent_on_paste {
9683 Some(AutoindentMode::Block {
9684 original_indent_columns,
9685 })
9686 } else {
9687 None
9688 },
9689 cx,
9690 );
9691 });
9692
9693 let selections = this.selections.all::<usize>(cx);
9694 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9695 s.select(selections)
9696 });
9697 } else {
9698 this.insert(&clipboard_text, window, cx);
9699 }
9700 });
9701 }
9702
9703 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
9704 if let Some(item) = cx.read_from_clipboard() {
9705 let entries = item.entries();
9706
9707 match entries.first() {
9708 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
9709 // of all the pasted entries.
9710 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
9711 .do_paste(
9712 clipboard_string.text(),
9713 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
9714 true,
9715 window,
9716 cx,
9717 ),
9718 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
9719 }
9720 }
9721 }
9722
9723 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
9724 if self.read_only(cx) {
9725 return;
9726 }
9727
9728 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
9729 if let Some((selections, _)) =
9730 self.selection_history.transaction(transaction_id).cloned()
9731 {
9732 self.change_selections(None, window, cx, |s| {
9733 s.select_anchors(selections.to_vec());
9734 });
9735 } else {
9736 log::error!(
9737 "No entry in selection_history found for undo. \
9738 This may correspond to a bug where undo does not update the selection. \
9739 If this is occurring, please add details to \
9740 https://github.com/zed-industries/zed/issues/22692"
9741 );
9742 }
9743 self.request_autoscroll(Autoscroll::fit(), cx);
9744 self.unmark_text(window, cx);
9745 self.refresh_inline_completion(true, false, window, cx);
9746 cx.emit(EditorEvent::Edited { transaction_id });
9747 cx.emit(EditorEvent::TransactionUndone { transaction_id });
9748 }
9749 }
9750
9751 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
9752 if self.read_only(cx) {
9753 return;
9754 }
9755
9756 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
9757 if let Some((_, Some(selections))) =
9758 self.selection_history.transaction(transaction_id).cloned()
9759 {
9760 self.change_selections(None, window, cx, |s| {
9761 s.select_anchors(selections.to_vec());
9762 });
9763 } else {
9764 log::error!(
9765 "No entry in selection_history found for redo. \
9766 This may correspond to a bug where undo does not update the selection. \
9767 If this is occurring, please add details to \
9768 https://github.com/zed-industries/zed/issues/22692"
9769 );
9770 }
9771 self.request_autoscroll(Autoscroll::fit(), cx);
9772 self.unmark_text(window, cx);
9773 self.refresh_inline_completion(true, false, window, cx);
9774 cx.emit(EditorEvent::Edited { transaction_id });
9775 }
9776 }
9777
9778 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
9779 self.buffer
9780 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
9781 }
9782
9783 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
9784 self.buffer
9785 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
9786 }
9787
9788 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
9789 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9790 let line_mode = s.line_mode;
9791 s.move_with(|map, selection| {
9792 let cursor = if selection.is_empty() && !line_mode {
9793 movement::left(map, selection.start)
9794 } else {
9795 selection.start
9796 };
9797 selection.collapse_to(cursor, SelectionGoal::None);
9798 });
9799 })
9800 }
9801
9802 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
9803 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9804 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
9805 })
9806 }
9807
9808 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
9809 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9810 let line_mode = s.line_mode;
9811 s.move_with(|map, selection| {
9812 let cursor = if selection.is_empty() && !line_mode {
9813 movement::right(map, selection.end)
9814 } else {
9815 selection.end
9816 };
9817 selection.collapse_to(cursor, SelectionGoal::None)
9818 });
9819 })
9820 }
9821
9822 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
9823 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9824 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
9825 })
9826 }
9827
9828 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
9829 if self.take_rename(true, window, cx).is_some() {
9830 return;
9831 }
9832
9833 if matches!(self.mode, EditorMode::SingleLine { .. }) {
9834 cx.propagate();
9835 return;
9836 }
9837
9838 let text_layout_details = &self.text_layout_details(window);
9839 let selection_count = self.selections.count();
9840 let first_selection = self.selections.first_anchor();
9841
9842 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9843 let line_mode = s.line_mode;
9844 s.move_with(|map, selection| {
9845 if !selection.is_empty() && !line_mode {
9846 selection.goal = SelectionGoal::None;
9847 }
9848 let (cursor, goal) = movement::up(
9849 map,
9850 selection.start,
9851 selection.goal,
9852 false,
9853 text_layout_details,
9854 );
9855 selection.collapse_to(cursor, goal);
9856 });
9857 });
9858
9859 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
9860 {
9861 cx.propagate();
9862 }
9863 }
9864
9865 pub fn move_up_by_lines(
9866 &mut self,
9867 action: &MoveUpByLines,
9868 window: &mut Window,
9869 cx: &mut Context<Self>,
9870 ) {
9871 if self.take_rename(true, window, cx).is_some() {
9872 return;
9873 }
9874
9875 if matches!(self.mode, EditorMode::SingleLine { .. }) {
9876 cx.propagate();
9877 return;
9878 }
9879
9880 let text_layout_details = &self.text_layout_details(window);
9881
9882 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9883 let line_mode = s.line_mode;
9884 s.move_with(|map, selection| {
9885 if !selection.is_empty() && !line_mode {
9886 selection.goal = SelectionGoal::None;
9887 }
9888 let (cursor, goal) = movement::up_by_rows(
9889 map,
9890 selection.start,
9891 action.lines,
9892 selection.goal,
9893 false,
9894 text_layout_details,
9895 );
9896 selection.collapse_to(cursor, goal);
9897 });
9898 })
9899 }
9900
9901 pub fn move_down_by_lines(
9902 &mut self,
9903 action: &MoveDownByLines,
9904 window: &mut Window,
9905 cx: &mut Context<Self>,
9906 ) {
9907 if self.take_rename(true, window, cx).is_some() {
9908 return;
9909 }
9910
9911 if matches!(self.mode, EditorMode::SingleLine { .. }) {
9912 cx.propagate();
9913 return;
9914 }
9915
9916 let text_layout_details = &self.text_layout_details(window);
9917
9918 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9919 let line_mode = s.line_mode;
9920 s.move_with(|map, selection| {
9921 if !selection.is_empty() && !line_mode {
9922 selection.goal = SelectionGoal::None;
9923 }
9924 let (cursor, goal) = movement::down_by_rows(
9925 map,
9926 selection.start,
9927 action.lines,
9928 selection.goal,
9929 false,
9930 text_layout_details,
9931 );
9932 selection.collapse_to(cursor, goal);
9933 });
9934 })
9935 }
9936
9937 pub fn select_down_by_lines(
9938 &mut self,
9939 action: &SelectDownByLines,
9940 window: &mut Window,
9941 cx: &mut Context<Self>,
9942 ) {
9943 let text_layout_details = &self.text_layout_details(window);
9944 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9945 s.move_heads_with(|map, head, goal| {
9946 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
9947 })
9948 })
9949 }
9950
9951 pub fn select_up_by_lines(
9952 &mut self,
9953 action: &SelectUpByLines,
9954 window: &mut Window,
9955 cx: &mut Context<Self>,
9956 ) {
9957 let text_layout_details = &self.text_layout_details(window);
9958 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9959 s.move_heads_with(|map, head, goal| {
9960 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
9961 })
9962 })
9963 }
9964
9965 pub fn select_page_up(
9966 &mut self,
9967 _: &SelectPageUp,
9968 window: &mut Window,
9969 cx: &mut Context<Self>,
9970 ) {
9971 let Some(row_count) = self.visible_row_count() else {
9972 return;
9973 };
9974
9975 let text_layout_details = &self.text_layout_details(window);
9976
9977 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9978 s.move_heads_with(|map, head, goal| {
9979 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
9980 })
9981 })
9982 }
9983
9984 pub fn move_page_up(
9985 &mut self,
9986 action: &MovePageUp,
9987 window: &mut Window,
9988 cx: &mut Context<Self>,
9989 ) {
9990 if self.take_rename(true, window, cx).is_some() {
9991 return;
9992 }
9993
9994 if self
9995 .context_menu
9996 .borrow_mut()
9997 .as_mut()
9998 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
9999 .unwrap_or(false)
10000 {
10001 return;
10002 }
10003
10004 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10005 cx.propagate();
10006 return;
10007 }
10008
10009 let Some(row_count) = self.visible_row_count() else {
10010 return;
10011 };
10012
10013 let autoscroll = if action.center_cursor {
10014 Autoscroll::center()
10015 } else {
10016 Autoscroll::fit()
10017 };
10018
10019 let text_layout_details = &self.text_layout_details(window);
10020
10021 self.change_selections(Some(autoscroll), window, cx, |s| {
10022 let line_mode = s.line_mode;
10023 s.move_with(|map, selection| {
10024 if !selection.is_empty() && !line_mode {
10025 selection.goal = SelectionGoal::None;
10026 }
10027 let (cursor, goal) = movement::up_by_rows(
10028 map,
10029 selection.end,
10030 row_count,
10031 selection.goal,
10032 false,
10033 text_layout_details,
10034 );
10035 selection.collapse_to(cursor, goal);
10036 });
10037 });
10038 }
10039
10040 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
10041 let text_layout_details = &self.text_layout_details(window);
10042 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10043 s.move_heads_with(|map, head, goal| {
10044 movement::up(map, head, goal, false, text_layout_details)
10045 })
10046 })
10047 }
10048
10049 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
10050 self.take_rename(true, window, cx);
10051
10052 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10053 cx.propagate();
10054 return;
10055 }
10056
10057 let text_layout_details = &self.text_layout_details(window);
10058 let selection_count = self.selections.count();
10059 let first_selection = self.selections.first_anchor();
10060
10061 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10062 let line_mode = s.line_mode;
10063 s.move_with(|map, selection| {
10064 if !selection.is_empty() && !line_mode {
10065 selection.goal = SelectionGoal::None;
10066 }
10067 let (cursor, goal) = movement::down(
10068 map,
10069 selection.end,
10070 selection.goal,
10071 false,
10072 text_layout_details,
10073 );
10074 selection.collapse_to(cursor, goal);
10075 });
10076 });
10077
10078 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10079 {
10080 cx.propagate();
10081 }
10082 }
10083
10084 pub fn select_page_down(
10085 &mut self,
10086 _: &SelectPageDown,
10087 window: &mut Window,
10088 cx: &mut Context<Self>,
10089 ) {
10090 let Some(row_count) = self.visible_row_count() else {
10091 return;
10092 };
10093
10094 let text_layout_details = &self.text_layout_details(window);
10095
10096 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10097 s.move_heads_with(|map, head, goal| {
10098 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
10099 })
10100 })
10101 }
10102
10103 pub fn move_page_down(
10104 &mut self,
10105 action: &MovePageDown,
10106 window: &mut Window,
10107 cx: &mut Context<Self>,
10108 ) {
10109 if self.take_rename(true, window, cx).is_some() {
10110 return;
10111 }
10112
10113 if self
10114 .context_menu
10115 .borrow_mut()
10116 .as_mut()
10117 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
10118 .unwrap_or(false)
10119 {
10120 return;
10121 }
10122
10123 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10124 cx.propagate();
10125 return;
10126 }
10127
10128 let Some(row_count) = self.visible_row_count() else {
10129 return;
10130 };
10131
10132 let autoscroll = if action.center_cursor {
10133 Autoscroll::center()
10134 } else {
10135 Autoscroll::fit()
10136 };
10137
10138 let text_layout_details = &self.text_layout_details(window);
10139 self.change_selections(Some(autoscroll), window, cx, |s| {
10140 let line_mode = s.line_mode;
10141 s.move_with(|map, selection| {
10142 if !selection.is_empty() && !line_mode {
10143 selection.goal = SelectionGoal::None;
10144 }
10145 let (cursor, goal) = movement::down_by_rows(
10146 map,
10147 selection.end,
10148 row_count,
10149 selection.goal,
10150 false,
10151 text_layout_details,
10152 );
10153 selection.collapse_to(cursor, goal);
10154 });
10155 });
10156 }
10157
10158 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
10159 let text_layout_details = &self.text_layout_details(window);
10160 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10161 s.move_heads_with(|map, head, goal| {
10162 movement::down(map, head, goal, false, text_layout_details)
10163 })
10164 });
10165 }
10166
10167 pub fn context_menu_first(
10168 &mut self,
10169 _: &ContextMenuFirst,
10170 _window: &mut Window,
10171 cx: &mut Context<Self>,
10172 ) {
10173 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10174 context_menu.select_first(self.completion_provider.as_deref(), cx);
10175 }
10176 }
10177
10178 pub fn context_menu_prev(
10179 &mut self,
10180 _: &ContextMenuPrevious,
10181 _window: &mut Window,
10182 cx: &mut Context<Self>,
10183 ) {
10184 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10185 context_menu.select_prev(self.completion_provider.as_deref(), cx);
10186 }
10187 }
10188
10189 pub fn context_menu_next(
10190 &mut self,
10191 _: &ContextMenuNext,
10192 _window: &mut Window,
10193 cx: &mut Context<Self>,
10194 ) {
10195 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10196 context_menu.select_next(self.completion_provider.as_deref(), cx);
10197 }
10198 }
10199
10200 pub fn context_menu_last(
10201 &mut self,
10202 _: &ContextMenuLast,
10203 _window: &mut Window,
10204 cx: &mut Context<Self>,
10205 ) {
10206 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10207 context_menu.select_last(self.completion_provider.as_deref(), cx);
10208 }
10209 }
10210
10211 pub fn move_to_previous_word_start(
10212 &mut self,
10213 _: &MoveToPreviousWordStart,
10214 window: &mut Window,
10215 cx: &mut Context<Self>,
10216 ) {
10217 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10218 s.move_cursors_with(|map, head, _| {
10219 (
10220 movement::previous_word_start(map, head),
10221 SelectionGoal::None,
10222 )
10223 });
10224 })
10225 }
10226
10227 pub fn move_to_previous_subword_start(
10228 &mut self,
10229 _: &MoveToPreviousSubwordStart,
10230 window: &mut Window,
10231 cx: &mut Context<Self>,
10232 ) {
10233 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10234 s.move_cursors_with(|map, head, _| {
10235 (
10236 movement::previous_subword_start(map, head),
10237 SelectionGoal::None,
10238 )
10239 });
10240 })
10241 }
10242
10243 pub fn select_to_previous_word_start(
10244 &mut self,
10245 _: &SelectToPreviousWordStart,
10246 window: &mut Window,
10247 cx: &mut Context<Self>,
10248 ) {
10249 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10250 s.move_heads_with(|map, head, _| {
10251 (
10252 movement::previous_word_start(map, head),
10253 SelectionGoal::None,
10254 )
10255 });
10256 })
10257 }
10258
10259 pub fn select_to_previous_subword_start(
10260 &mut self,
10261 _: &SelectToPreviousSubwordStart,
10262 window: &mut Window,
10263 cx: &mut Context<Self>,
10264 ) {
10265 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10266 s.move_heads_with(|map, head, _| {
10267 (
10268 movement::previous_subword_start(map, head),
10269 SelectionGoal::None,
10270 )
10271 });
10272 })
10273 }
10274
10275 pub fn delete_to_previous_word_start(
10276 &mut self,
10277 action: &DeleteToPreviousWordStart,
10278 window: &mut Window,
10279 cx: &mut Context<Self>,
10280 ) {
10281 self.transact(window, cx, |this, window, cx| {
10282 this.select_autoclose_pair(window, cx);
10283 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10284 let line_mode = s.line_mode;
10285 s.move_with(|map, selection| {
10286 if selection.is_empty() && !line_mode {
10287 let cursor = if action.ignore_newlines {
10288 movement::previous_word_start(map, selection.head())
10289 } else {
10290 movement::previous_word_start_or_newline(map, selection.head())
10291 };
10292 selection.set_head(cursor, SelectionGoal::None);
10293 }
10294 });
10295 });
10296 this.insert("", window, cx);
10297 });
10298 }
10299
10300 pub fn delete_to_previous_subword_start(
10301 &mut self,
10302 _: &DeleteToPreviousSubwordStart,
10303 window: &mut Window,
10304 cx: &mut Context<Self>,
10305 ) {
10306 self.transact(window, cx, |this, window, cx| {
10307 this.select_autoclose_pair(window, cx);
10308 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10309 let line_mode = s.line_mode;
10310 s.move_with(|map, selection| {
10311 if selection.is_empty() && !line_mode {
10312 let cursor = movement::previous_subword_start(map, selection.head());
10313 selection.set_head(cursor, SelectionGoal::None);
10314 }
10315 });
10316 });
10317 this.insert("", window, cx);
10318 });
10319 }
10320
10321 pub fn move_to_next_word_end(
10322 &mut self,
10323 _: &MoveToNextWordEnd,
10324 window: &mut Window,
10325 cx: &mut Context<Self>,
10326 ) {
10327 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10328 s.move_cursors_with(|map, head, _| {
10329 (movement::next_word_end(map, head), SelectionGoal::None)
10330 });
10331 })
10332 }
10333
10334 pub fn move_to_next_subword_end(
10335 &mut self,
10336 _: &MoveToNextSubwordEnd,
10337 window: &mut Window,
10338 cx: &mut Context<Self>,
10339 ) {
10340 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10341 s.move_cursors_with(|map, head, _| {
10342 (movement::next_subword_end(map, head), SelectionGoal::None)
10343 });
10344 })
10345 }
10346
10347 pub fn select_to_next_word_end(
10348 &mut self,
10349 _: &SelectToNextWordEnd,
10350 window: &mut Window,
10351 cx: &mut Context<Self>,
10352 ) {
10353 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10354 s.move_heads_with(|map, head, _| {
10355 (movement::next_word_end(map, head), SelectionGoal::None)
10356 });
10357 })
10358 }
10359
10360 pub fn select_to_next_subword_end(
10361 &mut self,
10362 _: &SelectToNextSubwordEnd,
10363 window: &mut Window,
10364 cx: &mut Context<Self>,
10365 ) {
10366 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10367 s.move_heads_with(|map, head, _| {
10368 (movement::next_subword_end(map, head), SelectionGoal::None)
10369 });
10370 })
10371 }
10372
10373 pub fn delete_to_next_word_end(
10374 &mut self,
10375 action: &DeleteToNextWordEnd,
10376 window: &mut Window,
10377 cx: &mut Context<Self>,
10378 ) {
10379 self.transact(window, cx, |this, window, cx| {
10380 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10381 let line_mode = s.line_mode;
10382 s.move_with(|map, selection| {
10383 if selection.is_empty() && !line_mode {
10384 let cursor = if action.ignore_newlines {
10385 movement::next_word_end(map, selection.head())
10386 } else {
10387 movement::next_word_end_or_newline(map, selection.head())
10388 };
10389 selection.set_head(cursor, SelectionGoal::None);
10390 }
10391 });
10392 });
10393 this.insert("", window, cx);
10394 });
10395 }
10396
10397 pub fn delete_to_next_subword_end(
10398 &mut self,
10399 _: &DeleteToNextSubwordEnd,
10400 window: &mut Window,
10401 cx: &mut Context<Self>,
10402 ) {
10403 self.transact(window, cx, |this, window, cx| {
10404 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10405 s.move_with(|map, selection| {
10406 if selection.is_empty() {
10407 let cursor = movement::next_subword_end(map, selection.head());
10408 selection.set_head(cursor, SelectionGoal::None);
10409 }
10410 });
10411 });
10412 this.insert("", window, cx);
10413 });
10414 }
10415
10416 pub fn move_to_beginning_of_line(
10417 &mut self,
10418 action: &MoveToBeginningOfLine,
10419 window: &mut Window,
10420 cx: &mut Context<Self>,
10421 ) {
10422 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10423 s.move_cursors_with(|map, head, _| {
10424 (
10425 movement::indented_line_beginning(
10426 map,
10427 head,
10428 action.stop_at_soft_wraps,
10429 action.stop_at_indent,
10430 ),
10431 SelectionGoal::None,
10432 )
10433 });
10434 })
10435 }
10436
10437 pub fn select_to_beginning_of_line(
10438 &mut self,
10439 action: &SelectToBeginningOfLine,
10440 window: &mut Window,
10441 cx: &mut Context<Self>,
10442 ) {
10443 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10444 s.move_heads_with(|map, head, _| {
10445 (
10446 movement::indented_line_beginning(
10447 map,
10448 head,
10449 action.stop_at_soft_wraps,
10450 action.stop_at_indent,
10451 ),
10452 SelectionGoal::None,
10453 )
10454 });
10455 });
10456 }
10457
10458 pub fn delete_to_beginning_of_line(
10459 &mut self,
10460 action: &DeleteToBeginningOfLine,
10461 window: &mut Window,
10462 cx: &mut Context<Self>,
10463 ) {
10464 self.transact(window, cx, |this, window, cx| {
10465 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10466 s.move_with(|_, selection| {
10467 selection.reversed = true;
10468 });
10469 });
10470
10471 this.select_to_beginning_of_line(
10472 &SelectToBeginningOfLine {
10473 stop_at_soft_wraps: false,
10474 stop_at_indent: action.stop_at_indent,
10475 },
10476 window,
10477 cx,
10478 );
10479 this.backspace(&Backspace, window, cx);
10480 });
10481 }
10482
10483 pub fn move_to_end_of_line(
10484 &mut self,
10485 action: &MoveToEndOfLine,
10486 window: &mut Window,
10487 cx: &mut Context<Self>,
10488 ) {
10489 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10490 s.move_cursors_with(|map, head, _| {
10491 (
10492 movement::line_end(map, head, action.stop_at_soft_wraps),
10493 SelectionGoal::None,
10494 )
10495 });
10496 })
10497 }
10498
10499 pub fn select_to_end_of_line(
10500 &mut self,
10501 action: &SelectToEndOfLine,
10502 window: &mut Window,
10503 cx: &mut Context<Self>,
10504 ) {
10505 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10506 s.move_heads_with(|map, head, _| {
10507 (
10508 movement::line_end(map, head, action.stop_at_soft_wraps),
10509 SelectionGoal::None,
10510 )
10511 });
10512 })
10513 }
10514
10515 pub fn delete_to_end_of_line(
10516 &mut self,
10517 _: &DeleteToEndOfLine,
10518 window: &mut Window,
10519 cx: &mut Context<Self>,
10520 ) {
10521 self.transact(window, cx, |this, window, cx| {
10522 this.select_to_end_of_line(
10523 &SelectToEndOfLine {
10524 stop_at_soft_wraps: false,
10525 },
10526 window,
10527 cx,
10528 );
10529 this.delete(&Delete, window, cx);
10530 });
10531 }
10532
10533 pub fn cut_to_end_of_line(
10534 &mut self,
10535 _: &CutToEndOfLine,
10536 window: &mut Window,
10537 cx: &mut Context<Self>,
10538 ) {
10539 self.transact(window, cx, |this, window, cx| {
10540 this.select_to_end_of_line(
10541 &SelectToEndOfLine {
10542 stop_at_soft_wraps: false,
10543 },
10544 window,
10545 cx,
10546 );
10547 this.cut(&Cut, window, cx);
10548 });
10549 }
10550
10551 pub fn move_to_start_of_paragraph(
10552 &mut self,
10553 _: &MoveToStartOfParagraph,
10554 window: &mut Window,
10555 cx: &mut Context<Self>,
10556 ) {
10557 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10558 cx.propagate();
10559 return;
10560 }
10561
10562 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10563 s.move_with(|map, selection| {
10564 selection.collapse_to(
10565 movement::start_of_paragraph(map, selection.head(), 1),
10566 SelectionGoal::None,
10567 )
10568 });
10569 })
10570 }
10571
10572 pub fn move_to_end_of_paragraph(
10573 &mut self,
10574 _: &MoveToEndOfParagraph,
10575 window: &mut Window,
10576 cx: &mut Context<Self>,
10577 ) {
10578 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10579 cx.propagate();
10580 return;
10581 }
10582
10583 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10584 s.move_with(|map, selection| {
10585 selection.collapse_to(
10586 movement::end_of_paragraph(map, selection.head(), 1),
10587 SelectionGoal::None,
10588 )
10589 });
10590 })
10591 }
10592
10593 pub fn select_to_start_of_paragraph(
10594 &mut self,
10595 _: &SelectToStartOfParagraph,
10596 window: &mut Window,
10597 cx: &mut Context<Self>,
10598 ) {
10599 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10600 cx.propagate();
10601 return;
10602 }
10603
10604 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10605 s.move_heads_with(|map, head, _| {
10606 (
10607 movement::start_of_paragraph(map, head, 1),
10608 SelectionGoal::None,
10609 )
10610 });
10611 })
10612 }
10613
10614 pub fn select_to_end_of_paragraph(
10615 &mut self,
10616 _: &SelectToEndOfParagraph,
10617 window: &mut Window,
10618 cx: &mut Context<Self>,
10619 ) {
10620 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10621 cx.propagate();
10622 return;
10623 }
10624
10625 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10626 s.move_heads_with(|map, head, _| {
10627 (
10628 movement::end_of_paragraph(map, head, 1),
10629 SelectionGoal::None,
10630 )
10631 });
10632 })
10633 }
10634
10635 pub fn move_to_start_of_excerpt(
10636 &mut self,
10637 _: &MoveToStartOfExcerpt,
10638 window: &mut Window,
10639 cx: &mut Context<Self>,
10640 ) {
10641 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10642 cx.propagate();
10643 return;
10644 }
10645
10646 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10647 s.move_with(|map, selection| {
10648 selection.collapse_to(
10649 movement::start_of_excerpt(
10650 map,
10651 selection.head(),
10652 workspace::searchable::Direction::Prev,
10653 ),
10654 SelectionGoal::None,
10655 )
10656 });
10657 })
10658 }
10659
10660 pub fn move_to_start_of_next_excerpt(
10661 &mut self,
10662 _: &MoveToStartOfNextExcerpt,
10663 window: &mut Window,
10664 cx: &mut Context<Self>,
10665 ) {
10666 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10667 cx.propagate();
10668 return;
10669 }
10670
10671 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10672 s.move_with(|map, selection| {
10673 selection.collapse_to(
10674 movement::start_of_excerpt(
10675 map,
10676 selection.head(),
10677 workspace::searchable::Direction::Next,
10678 ),
10679 SelectionGoal::None,
10680 )
10681 });
10682 })
10683 }
10684
10685 pub fn move_to_end_of_excerpt(
10686 &mut self,
10687 _: &MoveToEndOfExcerpt,
10688 window: &mut Window,
10689 cx: &mut Context<Self>,
10690 ) {
10691 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10692 cx.propagate();
10693 return;
10694 }
10695
10696 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10697 s.move_with(|map, selection| {
10698 selection.collapse_to(
10699 movement::end_of_excerpt(
10700 map,
10701 selection.head(),
10702 workspace::searchable::Direction::Next,
10703 ),
10704 SelectionGoal::None,
10705 )
10706 });
10707 })
10708 }
10709
10710 pub fn move_to_end_of_previous_excerpt(
10711 &mut self,
10712 _: &MoveToEndOfPreviousExcerpt,
10713 window: &mut Window,
10714 cx: &mut Context<Self>,
10715 ) {
10716 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10717 cx.propagate();
10718 return;
10719 }
10720
10721 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10722 s.move_with(|map, selection| {
10723 selection.collapse_to(
10724 movement::end_of_excerpt(
10725 map,
10726 selection.head(),
10727 workspace::searchable::Direction::Prev,
10728 ),
10729 SelectionGoal::None,
10730 )
10731 });
10732 })
10733 }
10734
10735 pub fn select_to_start_of_excerpt(
10736 &mut self,
10737 _: &SelectToStartOfExcerpt,
10738 window: &mut Window,
10739 cx: &mut Context<Self>,
10740 ) {
10741 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10742 cx.propagate();
10743 return;
10744 }
10745
10746 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10747 s.move_heads_with(|map, head, _| {
10748 (
10749 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
10750 SelectionGoal::None,
10751 )
10752 });
10753 })
10754 }
10755
10756 pub fn select_to_start_of_next_excerpt(
10757 &mut self,
10758 _: &SelectToStartOfNextExcerpt,
10759 window: &mut Window,
10760 cx: &mut Context<Self>,
10761 ) {
10762 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10763 cx.propagate();
10764 return;
10765 }
10766
10767 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10768 s.move_heads_with(|map, head, _| {
10769 (
10770 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
10771 SelectionGoal::None,
10772 )
10773 });
10774 })
10775 }
10776
10777 pub fn select_to_end_of_excerpt(
10778 &mut self,
10779 _: &SelectToEndOfExcerpt,
10780 window: &mut Window,
10781 cx: &mut Context<Self>,
10782 ) {
10783 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10784 cx.propagate();
10785 return;
10786 }
10787
10788 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10789 s.move_heads_with(|map, head, _| {
10790 (
10791 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
10792 SelectionGoal::None,
10793 )
10794 });
10795 })
10796 }
10797
10798 pub fn select_to_end_of_previous_excerpt(
10799 &mut self,
10800 _: &SelectToEndOfPreviousExcerpt,
10801 window: &mut Window,
10802 cx: &mut Context<Self>,
10803 ) {
10804 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10805 cx.propagate();
10806 return;
10807 }
10808
10809 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10810 s.move_heads_with(|map, head, _| {
10811 (
10812 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
10813 SelectionGoal::None,
10814 )
10815 });
10816 })
10817 }
10818
10819 pub fn move_to_beginning(
10820 &mut self,
10821 _: &MoveToBeginning,
10822 window: &mut Window,
10823 cx: &mut Context<Self>,
10824 ) {
10825 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10826 cx.propagate();
10827 return;
10828 }
10829
10830 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10831 s.select_ranges(vec![0..0]);
10832 });
10833 }
10834
10835 pub fn select_to_beginning(
10836 &mut self,
10837 _: &SelectToBeginning,
10838 window: &mut Window,
10839 cx: &mut Context<Self>,
10840 ) {
10841 let mut selection = self.selections.last::<Point>(cx);
10842 selection.set_head(Point::zero(), SelectionGoal::None);
10843
10844 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10845 s.select(vec![selection]);
10846 });
10847 }
10848
10849 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
10850 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10851 cx.propagate();
10852 return;
10853 }
10854
10855 let cursor = self.buffer.read(cx).read(cx).len();
10856 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10857 s.select_ranges(vec![cursor..cursor])
10858 });
10859 }
10860
10861 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
10862 self.nav_history = nav_history;
10863 }
10864
10865 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
10866 self.nav_history.as_ref()
10867 }
10868
10869 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
10870 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
10871 }
10872
10873 fn push_to_nav_history(
10874 &mut self,
10875 cursor_anchor: Anchor,
10876 new_position: Option<Point>,
10877 is_deactivate: bool,
10878 cx: &mut Context<Self>,
10879 ) {
10880 if let Some(nav_history) = self.nav_history.as_mut() {
10881 let buffer = self.buffer.read(cx).read(cx);
10882 let cursor_position = cursor_anchor.to_point(&buffer);
10883 let scroll_state = self.scroll_manager.anchor();
10884 let scroll_top_row = scroll_state.top_row(&buffer);
10885 drop(buffer);
10886
10887 if let Some(new_position) = new_position {
10888 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
10889 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
10890 return;
10891 }
10892 }
10893
10894 nav_history.push(
10895 Some(NavigationData {
10896 cursor_anchor,
10897 cursor_position,
10898 scroll_anchor: scroll_state,
10899 scroll_top_row,
10900 }),
10901 cx,
10902 );
10903 cx.emit(EditorEvent::PushedToNavHistory {
10904 anchor: cursor_anchor,
10905 is_deactivate,
10906 })
10907 }
10908 }
10909
10910 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
10911 let buffer = self.buffer.read(cx).snapshot(cx);
10912 let mut selection = self.selections.first::<usize>(cx);
10913 selection.set_head(buffer.len(), SelectionGoal::None);
10914 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10915 s.select(vec![selection]);
10916 });
10917 }
10918
10919 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
10920 let end = self.buffer.read(cx).read(cx).len();
10921 self.change_selections(None, window, cx, |s| {
10922 s.select_ranges(vec![0..end]);
10923 });
10924 }
10925
10926 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
10927 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10928 let mut selections = self.selections.all::<Point>(cx);
10929 let max_point = display_map.buffer_snapshot.max_point();
10930 for selection in &mut selections {
10931 let rows = selection.spanned_rows(true, &display_map);
10932 selection.start = Point::new(rows.start.0, 0);
10933 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
10934 selection.reversed = false;
10935 }
10936 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10937 s.select(selections);
10938 });
10939 }
10940
10941 pub fn split_selection_into_lines(
10942 &mut self,
10943 _: &SplitSelectionIntoLines,
10944 window: &mut Window,
10945 cx: &mut Context<Self>,
10946 ) {
10947 let selections = self
10948 .selections
10949 .all::<Point>(cx)
10950 .into_iter()
10951 .map(|selection| selection.start..selection.end)
10952 .collect::<Vec<_>>();
10953 self.unfold_ranges(&selections, true, true, cx);
10954
10955 let mut new_selection_ranges = Vec::new();
10956 {
10957 let buffer = self.buffer.read(cx).read(cx);
10958 for selection in selections {
10959 for row in selection.start.row..selection.end.row {
10960 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
10961 new_selection_ranges.push(cursor..cursor);
10962 }
10963
10964 let is_multiline_selection = selection.start.row != selection.end.row;
10965 // Don't insert last one if it's a multi-line selection ending at the start of a line,
10966 // so this action feels more ergonomic when paired with other selection operations
10967 let should_skip_last = is_multiline_selection && selection.end.column == 0;
10968 if !should_skip_last {
10969 new_selection_ranges.push(selection.end..selection.end);
10970 }
10971 }
10972 }
10973 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10974 s.select_ranges(new_selection_ranges);
10975 });
10976 }
10977
10978 pub fn add_selection_above(
10979 &mut self,
10980 _: &AddSelectionAbove,
10981 window: &mut Window,
10982 cx: &mut Context<Self>,
10983 ) {
10984 self.add_selection(true, window, cx);
10985 }
10986
10987 pub fn add_selection_below(
10988 &mut self,
10989 _: &AddSelectionBelow,
10990 window: &mut Window,
10991 cx: &mut Context<Self>,
10992 ) {
10993 self.add_selection(false, window, cx);
10994 }
10995
10996 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
10997 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10998 let mut selections = self.selections.all::<Point>(cx);
10999 let text_layout_details = self.text_layout_details(window);
11000 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
11001 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
11002 let range = oldest_selection.display_range(&display_map).sorted();
11003
11004 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
11005 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
11006 let positions = start_x.min(end_x)..start_x.max(end_x);
11007
11008 selections.clear();
11009 let mut stack = Vec::new();
11010 for row in range.start.row().0..=range.end.row().0 {
11011 if let Some(selection) = self.selections.build_columnar_selection(
11012 &display_map,
11013 DisplayRow(row),
11014 &positions,
11015 oldest_selection.reversed,
11016 &text_layout_details,
11017 ) {
11018 stack.push(selection.id);
11019 selections.push(selection);
11020 }
11021 }
11022
11023 if above {
11024 stack.reverse();
11025 }
11026
11027 AddSelectionsState { above, stack }
11028 });
11029
11030 let last_added_selection = *state.stack.last().unwrap();
11031 let mut new_selections = Vec::new();
11032 if above == state.above {
11033 let end_row = if above {
11034 DisplayRow(0)
11035 } else {
11036 display_map.max_point().row()
11037 };
11038
11039 'outer: for selection in selections {
11040 if selection.id == last_added_selection {
11041 let range = selection.display_range(&display_map).sorted();
11042 debug_assert_eq!(range.start.row(), range.end.row());
11043 let mut row = range.start.row();
11044 let positions =
11045 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
11046 px(start)..px(end)
11047 } else {
11048 let start_x =
11049 display_map.x_for_display_point(range.start, &text_layout_details);
11050 let end_x =
11051 display_map.x_for_display_point(range.end, &text_layout_details);
11052 start_x.min(end_x)..start_x.max(end_x)
11053 };
11054
11055 while row != end_row {
11056 if above {
11057 row.0 -= 1;
11058 } else {
11059 row.0 += 1;
11060 }
11061
11062 if let Some(new_selection) = self.selections.build_columnar_selection(
11063 &display_map,
11064 row,
11065 &positions,
11066 selection.reversed,
11067 &text_layout_details,
11068 ) {
11069 state.stack.push(new_selection.id);
11070 if above {
11071 new_selections.push(new_selection);
11072 new_selections.push(selection);
11073 } else {
11074 new_selections.push(selection);
11075 new_selections.push(new_selection);
11076 }
11077
11078 continue 'outer;
11079 }
11080 }
11081 }
11082
11083 new_selections.push(selection);
11084 }
11085 } else {
11086 new_selections = selections;
11087 new_selections.retain(|s| s.id != last_added_selection);
11088 state.stack.pop();
11089 }
11090
11091 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11092 s.select(new_selections);
11093 });
11094 if state.stack.len() > 1 {
11095 self.add_selections_state = Some(state);
11096 }
11097 }
11098
11099 pub fn select_next_match_internal(
11100 &mut self,
11101 display_map: &DisplaySnapshot,
11102 replace_newest: bool,
11103 autoscroll: Option<Autoscroll>,
11104 window: &mut Window,
11105 cx: &mut Context<Self>,
11106 ) -> Result<()> {
11107 fn select_next_match_ranges(
11108 this: &mut Editor,
11109 range: Range<usize>,
11110 replace_newest: bool,
11111 auto_scroll: Option<Autoscroll>,
11112 window: &mut Window,
11113 cx: &mut Context<Editor>,
11114 ) {
11115 this.unfold_ranges(&[range.clone()], false, true, cx);
11116 this.change_selections(auto_scroll, window, cx, |s| {
11117 if replace_newest {
11118 s.delete(s.newest_anchor().id);
11119 }
11120 s.insert_range(range.clone());
11121 });
11122 }
11123
11124 let buffer = &display_map.buffer_snapshot;
11125 let mut selections = self.selections.all::<usize>(cx);
11126 if let Some(mut select_next_state) = self.select_next_state.take() {
11127 let query = &select_next_state.query;
11128 if !select_next_state.done {
11129 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11130 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11131 let mut next_selected_range = None;
11132
11133 let bytes_after_last_selection =
11134 buffer.bytes_in_range(last_selection.end..buffer.len());
11135 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
11136 let query_matches = query
11137 .stream_find_iter(bytes_after_last_selection)
11138 .map(|result| (last_selection.end, result))
11139 .chain(
11140 query
11141 .stream_find_iter(bytes_before_first_selection)
11142 .map(|result| (0, result)),
11143 );
11144
11145 for (start_offset, query_match) in query_matches {
11146 let query_match = query_match.unwrap(); // can only fail due to I/O
11147 let offset_range =
11148 start_offset + query_match.start()..start_offset + query_match.end();
11149 let display_range = offset_range.start.to_display_point(display_map)
11150 ..offset_range.end.to_display_point(display_map);
11151
11152 if !select_next_state.wordwise
11153 || (!movement::is_inside_word(display_map, display_range.start)
11154 && !movement::is_inside_word(display_map, display_range.end))
11155 {
11156 // TODO: This is n^2, because we might check all the selections
11157 if !selections
11158 .iter()
11159 .any(|selection| selection.range().overlaps(&offset_range))
11160 {
11161 next_selected_range = Some(offset_range);
11162 break;
11163 }
11164 }
11165 }
11166
11167 if let Some(next_selected_range) = next_selected_range {
11168 select_next_match_ranges(
11169 self,
11170 next_selected_range,
11171 replace_newest,
11172 autoscroll,
11173 window,
11174 cx,
11175 );
11176 } else {
11177 select_next_state.done = true;
11178 }
11179 }
11180
11181 self.select_next_state = Some(select_next_state);
11182 } else {
11183 let mut only_carets = true;
11184 let mut same_text_selected = true;
11185 let mut selected_text = None;
11186
11187 let mut selections_iter = selections.iter().peekable();
11188 while let Some(selection) = selections_iter.next() {
11189 if selection.start != selection.end {
11190 only_carets = false;
11191 }
11192
11193 if same_text_selected {
11194 if selected_text.is_none() {
11195 selected_text =
11196 Some(buffer.text_for_range(selection.range()).collect::<String>());
11197 }
11198
11199 if let Some(next_selection) = selections_iter.peek() {
11200 if next_selection.range().len() == selection.range().len() {
11201 let next_selected_text = buffer
11202 .text_for_range(next_selection.range())
11203 .collect::<String>();
11204 if Some(next_selected_text) != selected_text {
11205 same_text_selected = false;
11206 selected_text = None;
11207 }
11208 } else {
11209 same_text_selected = false;
11210 selected_text = None;
11211 }
11212 }
11213 }
11214 }
11215
11216 if only_carets {
11217 for selection in &mut selections {
11218 let word_range = movement::surrounding_word(
11219 display_map,
11220 selection.start.to_display_point(display_map),
11221 );
11222 selection.start = word_range.start.to_offset(display_map, Bias::Left);
11223 selection.end = word_range.end.to_offset(display_map, Bias::Left);
11224 selection.goal = SelectionGoal::None;
11225 selection.reversed = false;
11226 select_next_match_ranges(
11227 self,
11228 selection.start..selection.end,
11229 replace_newest,
11230 autoscroll,
11231 window,
11232 cx,
11233 );
11234 }
11235
11236 if selections.len() == 1 {
11237 let selection = selections
11238 .last()
11239 .expect("ensured that there's only one selection");
11240 let query = buffer
11241 .text_for_range(selection.start..selection.end)
11242 .collect::<String>();
11243 let is_empty = query.is_empty();
11244 let select_state = SelectNextState {
11245 query: AhoCorasick::new(&[query])?,
11246 wordwise: true,
11247 done: is_empty,
11248 };
11249 self.select_next_state = Some(select_state);
11250 } else {
11251 self.select_next_state = None;
11252 }
11253 } else if let Some(selected_text) = selected_text {
11254 self.select_next_state = Some(SelectNextState {
11255 query: AhoCorasick::new(&[selected_text])?,
11256 wordwise: false,
11257 done: false,
11258 });
11259 self.select_next_match_internal(
11260 display_map,
11261 replace_newest,
11262 autoscroll,
11263 window,
11264 cx,
11265 )?;
11266 }
11267 }
11268 Ok(())
11269 }
11270
11271 pub fn select_all_matches(
11272 &mut self,
11273 _action: &SelectAllMatches,
11274 window: &mut Window,
11275 cx: &mut Context<Self>,
11276 ) -> Result<()> {
11277 self.push_to_selection_history();
11278 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11279
11280 self.select_next_match_internal(&display_map, false, None, window, cx)?;
11281 let Some(select_next_state) = self.select_next_state.as_mut() else {
11282 return Ok(());
11283 };
11284 if select_next_state.done {
11285 return Ok(());
11286 }
11287
11288 let mut new_selections = self.selections.all::<usize>(cx);
11289
11290 let buffer = &display_map.buffer_snapshot;
11291 let query_matches = select_next_state
11292 .query
11293 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
11294
11295 for query_match in query_matches {
11296 let query_match = query_match.unwrap(); // can only fail due to I/O
11297 let offset_range = query_match.start()..query_match.end();
11298 let display_range = offset_range.start.to_display_point(&display_map)
11299 ..offset_range.end.to_display_point(&display_map);
11300
11301 if !select_next_state.wordwise
11302 || (!movement::is_inside_word(&display_map, display_range.start)
11303 && !movement::is_inside_word(&display_map, display_range.end))
11304 {
11305 self.selections.change_with(cx, |selections| {
11306 new_selections.push(Selection {
11307 id: selections.new_selection_id(),
11308 start: offset_range.start,
11309 end: offset_range.end,
11310 reversed: false,
11311 goal: SelectionGoal::None,
11312 });
11313 });
11314 }
11315 }
11316
11317 new_selections.sort_by_key(|selection| selection.start);
11318 let mut ix = 0;
11319 while ix + 1 < new_selections.len() {
11320 let current_selection = &new_selections[ix];
11321 let next_selection = &new_selections[ix + 1];
11322 if current_selection.range().overlaps(&next_selection.range()) {
11323 if current_selection.id < next_selection.id {
11324 new_selections.remove(ix + 1);
11325 } else {
11326 new_selections.remove(ix);
11327 }
11328 } else {
11329 ix += 1;
11330 }
11331 }
11332
11333 let reversed = self.selections.oldest::<usize>(cx).reversed;
11334
11335 for selection in new_selections.iter_mut() {
11336 selection.reversed = reversed;
11337 }
11338
11339 select_next_state.done = true;
11340 self.unfold_ranges(
11341 &new_selections
11342 .iter()
11343 .map(|selection| selection.range())
11344 .collect::<Vec<_>>(),
11345 false,
11346 false,
11347 cx,
11348 );
11349 self.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
11350 selections.select(new_selections)
11351 });
11352
11353 Ok(())
11354 }
11355
11356 pub fn select_next(
11357 &mut self,
11358 action: &SelectNext,
11359 window: &mut Window,
11360 cx: &mut Context<Self>,
11361 ) -> Result<()> {
11362 self.push_to_selection_history();
11363 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11364 self.select_next_match_internal(
11365 &display_map,
11366 action.replace_newest,
11367 Some(Autoscroll::newest()),
11368 window,
11369 cx,
11370 )?;
11371 Ok(())
11372 }
11373
11374 pub fn select_previous(
11375 &mut self,
11376 action: &SelectPrevious,
11377 window: &mut Window,
11378 cx: &mut Context<Self>,
11379 ) -> Result<()> {
11380 self.push_to_selection_history();
11381 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11382 let buffer = &display_map.buffer_snapshot;
11383 let mut selections = self.selections.all::<usize>(cx);
11384 if let Some(mut select_prev_state) = self.select_prev_state.take() {
11385 let query = &select_prev_state.query;
11386 if !select_prev_state.done {
11387 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11388 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11389 let mut next_selected_range = None;
11390 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
11391 let bytes_before_last_selection =
11392 buffer.reversed_bytes_in_range(0..last_selection.start);
11393 let bytes_after_first_selection =
11394 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
11395 let query_matches = query
11396 .stream_find_iter(bytes_before_last_selection)
11397 .map(|result| (last_selection.start, result))
11398 .chain(
11399 query
11400 .stream_find_iter(bytes_after_first_selection)
11401 .map(|result| (buffer.len(), result)),
11402 );
11403 for (end_offset, query_match) in query_matches {
11404 let query_match = query_match.unwrap(); // can only fail due to I/O
11405 let offset_range =
11406 end_offset - query_match.end()..end_offset - query_match.start();
11407 let display_range = offset_range.start.to_display_point(&display_map)
11408 ..offset_range.end.to_display_point(&display_map);
11409
11410 if !select_prev_state.wordwise
11411 || (!movement::is_inside_word(&display_map, display_range.start)
11412 && !movement::is_inside_word(&display_map, display_range.end))
11413 {
11414 next_selected_range = Some(offset_range);
11415 break;
11416 }
11417 }
11418
11419 if let Some(next_selected_range) = next_selected_range {
11420 self.unfold_ranges(&[next_selected_range.clone()], false, true, cx);
11421 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
11422 if action.replace_newest {
11423 s.delete(s.newest_anchor().id);
11424 }
11425 s.insert_range(next_selected_range);
11426 });
11427 } else {
11428 select_prev_state.done = true;
11429 }
11430 }
11431
11432 self.select_prev_state = Some(select_prev_state);
11433 } else {
11434 let mut only_carets = true;
11435 let mut same_text_selected = true;
11436 let mut selected_text = None;
11437
11438 let mut selections_iter = selections.iter().peekable();
11439 while let Some(selection) = selections_iter.next() {
11440 if selection.start != selection.end {
11441 only_carets = false;
11442 }
11443
11444 if same_text_selected {
11445 if selected_text.is_none() {
11446 selected_text =
11447 Some(buffer.text_for_range(selection.range()).collect::<String>());
11448 }
11449
11450 if let Some(next_selection) = selections_iter.peek() {
11451 if next_selection.range().len() == selection.range().len() {
11452 let next_selected_text = buffer
11453 .text_for_range(next_selection.range())
11454 .collect::<String>();
11455 if Some(next_selected_text) != selected_text {
11456 same_text_selected = false;
11457 selected_text = None;
11458 }
11459 } else {
11460 same_text_selected = false;
11461 selected_text = None;
11462 }
11463 }
11464 }
11465 }
11466
11467 if only_carets {
11468 for selection in &mut selections {
11469 let word_range = movement::surrounding_word(
11470 &display_map,
11471 selection.start.to_display_point(&display_map),
11472 );
11473 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
11474 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
11475 selection.goal = SelectionGoal::None;
11476 selection.reversed = false;
11477 }
11478 if selections.len() == 1 {
11479 let selection = selections
11480 .last()
11481 .expect("ensured that there's only one selection");
11482 let query = buffer
11483 .text_for_range(selection.start..selection.end)
11484 .collect::<String>();
11485 let is_empty = query.is_empty();
11486 let select_state = SelectNextState {
11487 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
11488 wordwise: true,
11489 done: is_empty,
11490 };
11491 self.select_prev_state = Some(select_state);
11492 } else {
11493 self.select_prev_state = None;
11494 }
11495
11496 self.unfold_ranges(
11497 &selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
11498 false,
11499 true,
11500 cx,
11501 );
11502 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
11503 s.select(selections);
11504 });
11505 } else if let Some(selected_text) = selected_text {
11506 self.select_prev_state = Some(SelectNextState {
11507 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
11508 wordwise: false,
11509 done: false,
11510 });
11511 self.select_previous(action, window, cx)?;
11512 }
11513 }
11514 Ok(())
11515 }
11516
11517 pub fn toggle_comments(
11518 &mut self,
11519 action: &ToggleComments,
11520 window: &mut Window,
11521 cx: &mut Context<Self>,
11522 ) {
11523 if self.read_only(cx) {
11524 return;
11525 }
11526 let text_layout_details = &self.text_layout_details(window);
11527 self.transact(window, cx, |this, window, cx| {
11528 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
11529 let mut edits = Vec::new();
11530 let mut selection_edit_ranges = Vec::new();
11531 let mut last_toggled_row = None;
11532 let snapshot = this.buffer.read(cx).read(cx);
11533 let empty_str: Arc<str> = Arc::default();
11534 let mut suffixes_inserted = Vec::new();
11535 let ignore_indent = action.ignore_indent;
11536
11537 fn comment_prefix_range(
11538 snapshot: &MultiBufferSnapshot,
11539 row: MultiBufferRow,
11540 comment_prefix: &str,
11541 comment_prefix_whitespace: &str,
11542 ignore_indent: bool,
11543 ) -> Range<Point> {
11544 let indent_size = if ignore_indent {
11545 0
11546 } else {
11547 snapshot.indent_size_for_line(row).len
11548 };
11549
11550 let start = Point::new(row.0, indent_size);
11551
11552 let mut line_bytes = snapshot
11553 .bytes_in_range(start..snapshot.max_point())
11554 .flatten()
11555 .copied();
11556
11557 // If this line currently begins with the line comment prefix, then record
11558 // the range containing the prefix.
11559 if line_bytes
11560 .by_ref()
11561 .take(comment_prefix.len())
11562 .eq(comment_prefix.bytes())
11563 {
11564 // Include any whitespace that matches the comment prefix.
11565 let matching_whitespace_len = line_bytes
11566 .zip(comment_prefix_whitespace.bytes())
11567 .take_while(|(a, b)| a == b)
11568 .count() as u32;
11569 let end = Point::new(
11570 start.row,
11571 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
11572 );
11573 start..end
11574 } else {
11575 start..start
11576 }
11577 }
11578
11579 fn comment_suffix_range(
11580 snapshot: &MultiBufferSnapshot,
11581 row: MultiBufferRow,
11582 comment_suffix: &str,
11583 comment_suffix_has_leading_space: bool,
11584 ) -> Range<Point> {
11585 let end = Point::new(row.0, snapshot.line_len(row));
11586 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
11587
11588 let mut line_end_bytes = snapshot
11589 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
11590 .flatten()
11591 .copied();
11592
11593 let leading_space_len = if suffix_start_column > 0
11594 && line_end_bytes.next() == Some(b' ')
11595 && comment_suffix_has_leading_space
11596 {
11597 1
11598 } else {
11599 0
11600 };
11601
11602 // If this line currently begins with the line comment prefix, then record
11603 // the range containing the prefix.
11604 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
11605 let start = Point::new(end.row, suffix_start_column - leading_space_len);
11606 start..end
11607 } else {
11608 end..end
11609 }
11610 }
11611
11612 // TODO: Handle selections that cross excerpts
11613 for selection in &mut selections {
11614 let start_column = snapshot
11615 .indent_size_for_line(MultiBufferRow(selection.start.row))
11616 .len;
11617 let language = if let Some(language) =
11618 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
11619 {
11620 language
11621 } else {
11622 continue;
11623 };
11624
11625 selection_edit_ranges.clear();
11626
11627 // If multiple selections contain a given row, avoid processing that
11628 // row more than once.
11629 let mut start_row = MultiBufferRow(selection.start.row);
11630 if last_toggled_row == Some(start_row) {
11631 start_row = start_row.next_row();
11632 }
11633 let end_row =
11634 if selection.end.row > selection.start.row && selection.end.column == 0 {
11635 MultiBufferRow(selection.end.row - 1)
11636 } else {
11637 MultiBufferRow(selection.end.row)
11638 };
11639 last_toggled_row = Some(end_row);
11640
11641 if start_row > end_row {
11642 continue;
11643 }
11644
11645 // If the language has line comments, toggle those.
11646 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
11647
11648 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
11649 if ignore_indent {
11650 full_comment_prefixes = full_comment_prefixes
11651 .into_iter()
11652 .map(|s| Arc::from(s.trim_end()))
11653 .collect();
11654 }
11655
11656 if !full_comment_prefixes.is_empty() {
11657 let first_prefix = full_comment_prefixes
11658 .first()
11659 .expect("prefixes is non-empty");
11660 let prefix_trimmed_lengths = full_comment_prefixes
11661 .iter()
11662 .map(|p| p.trim_end_matches(' ').len())
11663 .collect::<SmallVec<[usize; 4]>>();
11664
11665 let mut all_selection_lines_are_comments = true;
11666
11667 for row in start_row.0..=end_row.0 {
11668 let row = MultiBufferRow(row);
11669 if start_row < end_row && snapshot.is_line_blank(row) {
11670 continue;
11671 }
11672
11673 let prefix_range = full_comment_prefixes
11674 .iter()
11675 .zip(prefix_trimmed_lengths.iter().copied())
11676 .map(|(prefix, trimmed_prefix_len)| {
11677 comment_prefix_range(
11678 snapshot.deref(),
11679 row,
11680 &prefix[..trimmed_prefix_len],
11681 &prefix[trimmed_prefix_len..],
11682 ignore_indent,
11683 )
11684 })
11685 .max_by_key(|range| range.end.column - range.start.column)
11686 .expect("prefixes is non-empty");
11687
11688 if prefix_range.is_empty() {
11689 all_selection_lines_are_comments = false;
11690 }
11691
11692 selection_edit_ranges.push(prefix_range);
11693 }
11694
11695 if all_selection_lines_are_comments {
11696 edits.extend(
11697 selection_edit_ranges
11698 .iter()
11699 .cloned()
11700 .map(|range| (range, empty_str.clone())),
11701 );
11702 } else {
11703 let min_column = selection_edit_ranges
11704 .iter()
11705 .map(|range| range.start.column)
11706 .min()
11707 .unwrap_or(0);
11708 edits.extend(selection_edit_ranges.iter().map(|range| {
11709 let position = Point::new(range.start.row, min_column);
11710 (position..position, first_prefix.clone())
11711 }));
11712 }
11713 } else if let Some((full_comment_prefix, comment_suffix)) =
11714 language.block_comment_delimiters()
11715 {
11716 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
11717 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
11718 let prefix_range = comment_prefix_range(
11719 snapshot.deref(),
11720 start_row,
11721 comment_prefix,
11722 comment_prefix_whitespace,
11723 ignore_indent,
11724 );
11725 let suffix_range = comment_suffix_range(
11726 snapshot.deref(),
11727 end_row,
11728 comment_suffix.trim_start_matches(' '),
11729 comment_suffix.starts_with(' '),
11730 );
11731
11732 if prefix_range.is_empty() || suffix_range.is_empty() {
11733 edits.push((
11734 prefix_range.start..prefix_range.start,
11735 full_comment_prefix.clone(),
11736 ));
11737 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
11738 suffixes_inserted.push((end_row, comment_suffix.len()));
11739 } else {
11740 edits.push((prefix_range, empty_str.clone()));
11741 edits.push((suffix_range, empty_str.clone()));
11742 }
11743 } else {
11744 continue;
11745 }
11746 }
11747
11748 drop(snapshot);
11749 this.buffer.update(cx, |buffer, cx| {
11750 buffer.edit(edits, None, cx);
11751 });
11752
11753 // Adjust selections so that they end before any comment suffixes that
11754 // were inserted.
11755 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
11756 let mut selections = this.selections.all::<Point>(cx);
11757 let snapshot = this.buffer.read(cx).read(cx);
11758 for selection in &mut selections {
11759 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
11760 match row.cmp(&MultiBufferRow(selection.end.row)) {
11761 Ordering::Less => {
11762 suffixes_inserted.next();
11763 continue;
11764 }
11765 Ordering::Greater => break,
11766 Ordering::Equal => {
11767 if selection.end.column == snapshot.line_len(row) {
11768 if selection.is_empty() {
11769 selection.start.column -= suffix_len as u32;
11770 }
11771 selection.end.column -= suffix_len as u32;
11772 }
11773 break;
11774 }
11775 }
11776 }
11777 }
11778
11779 drop(snapshot);
11780 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11781 s.select(selections)
11782 });
11783
11784 let selections = this.selections.all::<Point>(cx);
11785 let selections_on_single_row = selections.windows(2).all(|selections| {
11786 selections[0].start.row == selections[1].start.row
11787 && selections[0].end.row == selections[1].end.row
11788 && selections[0].start.row == selections[0].end.row
11789 });
11790 let selections_selecting = selections
11791 .iter()
11792 .any(|selection| selection.start != selection.end);
11793 let advance_downwards = action.advance_downwards
11794 && selections_on_single_row
11795 && !selections_selecting
11796 && !matches!(this.mode, EditorMode::SingleLine { .. });
11797
11798 if advance_downwards {
11799 let snapshot = this.buffer.read(cx).snapshot(cx);
11800
11801 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11802 s.move_cursors_with(|display_snapshot, display_point, _| {
11803 let mut point = display_point.to_point(display_snapshot);
11804 point.row += 1;
11805 point = snapshot.clip_point(point, Bias::Left);
11806 let display_point = point.to_display_point(display_snapshot);
11807 let goal = SelectionGoal::HorizontalPosition(
11808 display_snapshot
11809 .x_for_display_point(display_point, text_layout_details)
11810 .into(),
11811 );
11812 (display_point, goal)
11813 })
11814 });
11815 }
11816 });
11817 }
11818
11819 pub fn select_enclosing_symbol(
11820 &mut self,
11821 _: &SelectEnclosingSymbol,
11822 window: &mut Window,
11823 cx: &mut Context<Self>,
11824 ) {
11825 let buffer = self.buffer.read(cx).snapshot(cx);
11826 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
11827
11828 fn update_selection(
11829 selection: &Selection<usize>,
11830 buffer_snap: &MultiBufferSnapshot,
11831 ) -> Option<Selection<usize>> {
11832 let cursor = selection.head();
11833 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
11834 for symbol in symbols.iter().rev() {
11835 let start = symbol.range.start.to_offset(buffer_snap);
11836 let end = symbol.range.end.to_offset(buffer_snap);
11837 let new_range = start..end;
11838 if start < selection.start || end > selection.end {
11839 return Some(Selection {
11840 id: selection.id,
11841 start: new_range.start,
11842 end: new_range.end,
11843 goal: SelectionGoal::None,
11844 reversed: selection.reversed,
11845 });
11846 }
11847 }
11848 None
11849 }
11850
11851 let mut selected_larger_symbol = false;
11852 let new_selections = old_selections
11853 .iter()
11854 .map(|selection| match update_selection(selection, &buffer) {
11855 Some(new_selection) => {
11856 if new_selection.range() != selection.range() {
11857 selected_larger_symbol = true;
11858 }
11859 new_selection
11860 }
11861 None => selection.clone(),
11862 })
11863 .collect::<Vec<_>>();
11864
11865 if selected_larger_symbol {
11866 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11867 s.select(new_selections);
11868 });
11869 }
11870 }
11871
11872 pub fn select_larger_syntax_node(
11873 &mut self,
11874 _: &SelectLargerSyntaxNode,
11875 window: &mut Window,
11876 cx: &mut Context<Self>,
11877 ) {
11878 let Some(visible_row_count) = self.visible_row_count() else {
11879 return;
11880 };
11881 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
11882 if old_selections.is_empty() {
11883 return;
11884 }
11885
11886 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11887 let buffer = self.buffer.read(cx).snapshot(cx);
11888
11889 let mut selected_larger_node = false;
11890 let mut new_selections = old_selections
11891 .iter()
11892 .map(|selection| {
11893 let old_range = selection.start..selection.end;
11894 let mut new_range = old_range.clone();
11895 let mut new_node = None;
11896 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
11897 {
11898 new_node = Some(node);
11899 new_range = match containing_range {
11900 MultiOrSingleBufferOffsetRange::Single(_) => break,
11901 MultiOrSingleBufferOffsetRange::Multi(range) => range,
11902 };
11903 if !display_map.intersects_fold(new_range.start)
11904 && !display_map.intersects_fold(new_range.end)
11905 {
11906 break;
11907 }
11908 }
11909
11910 if let Some(node) = new_node {
11911 // Log the ancestor, to support using this action as a way to explore TreeSitter
11912 // nodes. Parent and grandparent are also logged because this operation will not
11913 // visit nodes that have the same range as their parent.
11914 log::info!("Node: {node:?}");
11915 let parent = node.parent();
11916 log::info!("Parent: {parent:?}");
11917 let grandparent = parent.and_then(|x| x.parent());
11918 log::info!("Grandparent: {grandparent:?}");
11919 }
11920
11921 selected_larger_node |= new_range != old_range;
11922 Selection {
11923 id: selection.id,
11924 start: new_range.start,
11925 end: new_range.end,
11926 goal: SelectionGoal::None,
11927 reversed: selection.reversed,
11928 }
11929 })
11930 .collect::<Vec<_>>();
11931
11932 if !selected_larger_node {
11933 return; // don't put this call in the history
11934 }
11935
11936 // scroll based on transformation done to the last selection created by the user
11937 let (last_old, last_new) = old_selections
11938 .last()
11939 .zip(new_selections.last().cloned())
11940 .expect("old_selections isn't empty");
11941
11942 // revert selection
11943 let is_selection_reversed = {
11944 let should_newest_selection_be_reversed = last_old.start != last_new.start;
11945 new_selections.last_mut().expect("checked above").reversed =
11946 should_newest_selection_be_reversed;
11947 should_newest_selection_be_reversed
11948 };
11949
11950 if selected_larger_node {
11951 self.select_syntax_node_history.disable_clearing = true;
11952 self.change_selections(None, window, cx, |s| {
11953 s.select(new_selections.clone());
11954 });
11955 self.select_syntax_node_history.disable_clearing = false;
11956 }
11957
11958 let start_row = last_new.start.to_display_point(&display_map).row().0;
11959 let end_row = last_new.end.to_display_point(&display_map).row().0;
11960 let selection_height = end_row - start_row + 1;
11961 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
11962
11963 // if fits on screen (considering margin), keep it in the middle, else, scroll to selection head
11964 let scroll_behavior = if visible_row_count >= selection_height + scroll_margin_rows * 2 {
11965 let middle_row = (end_row + start_row) / 2;
11966 let selection_center = middle_row.saturating_sub(visible_row_count / 2);
11967 self.set_scroll_top_row(DisplayRow(selection_center), window, cx);
11968 SelectSyntaxNodeScrollBehavior::CenterSelection
11969 } else if is_selection_reversed {
11970 self.scroll_cursor_top(&Default::default(), window, cx);
11971 SelectSyntaxNodeScrollBehavior::CursorTop
11972 } else {
11973 self.scroll_cursor_bottom(&Default::default(), window, cx);
11974 SelectSyntaxNodeScrollBehavior::CursorBottom
11975 };
11976
11977 self.select_syntax_node_history.push((
11978 old_selections,
11979 scroll_behavior,
11980 is_selection_reversed,
11981 ));
11982 }
11983
11984 pub fn select_smaller_syntax_node(
11985 &mut self,
11986 _: &SelectSmallerSyntaxNode,
11987 window: &mut Window,
11988 cx: &mut Context<Self>,
11989 ) {
11990 let Some(visible_row_count) = self.visible_row_count() else {
11991 return;
11992 };
11993
11994 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
11995 self.select_syntax_node_history.pop()
11996 {
11997 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11998
11999 if let Some(selection) = selections.last_mut() {
12000 selection.reversed = is_selection_reversed;
12001 }
12002
12003 self.select_syntax_node_history.disable_clearing = true;
12004 self.change_selections(None, window, cx, |s| {
12005 s.select(selections.to_vec());
12006 });
12007 self.select_syntax_node_history.disable_clearing = false;
12008
12009 let newest = self.selections.newest::<usize>(cx);
12010 let start_row = newest.start.to_display_point(&display_map).row().0;
12011 let end_row = newest.end.to_display_point(&display_map).row().0;
12012
12013 match scroll_behavior {
12014 SelectSyntaxNodeScrollBehavior::CursorTop => {
12015 self.scroll_cursor_top(&Default::default(), window, cx);
12016 }
12017 SelectSyntaxNodeScrollBehavior::CenterSelection => {
12018 let middle_row = (end_row + start_row) / 2;
12019 let selection_center = middle_row.saturating_sub(visible_row_count / 2);
12020 // centralize the selection, not the cursor
12021 self.set_scroll_top_row(DisplayRow(selection_center), window, cx);
12022 }
12023 SelectSyntaxNodeScrollBehavior::CursorBottom => {
12024 self.scroll_cursor_bottom(&Default::default(), window, cx);
12025 }
12026 }
12027 }
12028 }
12029
12030 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
12031 if !EditorSettings::get_global(cx).gutter.runnables {
12032 self.clear_tasks();
12033 return Task::ready(());
12034 }
12035 let project = self.project.as_ref().map(Entity::downgrade);
12036 cx.spawn_in(window, async move |this, cx| {
12037 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
12038 let Some(project) = project.and_then(|p| p.upgrade()) else {
12039 return;
12040 };
12041 let Ok(display_snapshot) = this.update(cx, |this, cx| {
12042 this.display_map.update(cx, |map, cx| map.snapshot(cx))
12043 }) else {
12044 return;
12045 };
12046
12047 let hide_runnables = project
12048 .update(cx, |project, cx| {
12049 // Do not display any test indicators in non-dev server remote projects.
12050 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
12051 })
12052 .unwrap_or(true);
12053 if hide_runnables {
12054 return;
12055 }
12056 let new_rows =
12057 cx.background_spawn({
12058 let snapshot = display_snapshot.clone();
12059 async move {
12060 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
12061 }
12062 })
12063 .await;
12064
12065 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
12066 this.update(cx, |this, _| {
12067 this.clear_tasks();
12068 for (key, value) in rows {
12069 this.insert_tasks(key, value);
12070 }
12071 })
12072 .ok();
12073 })
12074 }
12075 fn fetch_runnable_ranges(
12076 snapshot: &DisplaySnapshot,
12077 range: Range<Anchor>,
12078 ) -> Vec<language::RunnableRange> {
12079 snapshot.buffer_snapshot.runnable_ranges(range).collect()
12080 }
12081
12082 fn runnable_rows(
12083 project: Entity<Project>,
12084 snapshot: DisplaySnapshot,
12085 runnable_ranges: Vec<RunnableRange>,
12086 mut cx: AsyncWindowContext,
12087 ) -> Vec<((BufferId, u32), RunnableTasks)> {
12088 runnable_ranges
12089 .into_iter()
12090 .filter_map(|mut runnable| {
12091 let tasks = cx
12092 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
12093 .ok()?;
12094 if tasks.is_empty() {
12095 return None;
12096 }
12097
12098 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
12099
12100 let row = snapshot
12101 .buffer_snapshot
12102 .buffer_line_for_row(MultiBufferRow(point.row))?
12103 .1
12104 .start
12105 .row;
12106
12107 let context_range =
12108 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
12109 Some((
12110 (runnable.buffer_id, row),
12111 RunnableTasks {
12112 templates: tasks,
12113 offset: snapshot
12114 .buffer_snapshot
12115 .anchor_before(runnable.run_range.start),
12116 context_range,
12117 column: point.column,
12118 extra_variables: runnable.extra_captures,
12119 },
12120 ))
12121 })
12122 .collect()
12123 }
12124
12125 fn templates_with_tags(
12126 project: &Entity<Project>,
12127 runnable: &mut Runnable,
12128 cx: &mut App,
12129 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
12130 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
12131 let (worktree_id, file) = project
12132 .buffer_for_id(runnable.buffer, cx)
12133 .and_then(|buffer| buffer.read(cx).file())
12134 .map(|file| (file.worktree_id(cx), file.clone()))
12135 .unzip();
12136
12137 (
12138 project.task_store().read(cx).task_inventory().cloned(),
12139 worktree_id,
12140 file,
12141 )
12142 });
12143
12144 let tags = mem::take(&mut runnable.tags);
12145 let mut tags: Vec<_> = tags
12146 .into_iter()
12147 .flat_map(|tag| {
12148 let tag = tag.0.clone();
12149 inventory
12150 .as_ref()
12151 .into_iter()
12152 .flat_map(|inventory| {
12153 inventory.read(cx).list_tasks(
12154 file.clone(),
12155 Some(runnable.language.clone()),
12156 worktree_id,
12157 cx,
12158 )
12159 })
12160 .filter(move |(_, template)| {
12161 template.tags.iter().any(|source_tag| source_tag == &tag)
12162 })
12163 })
12164 .sorted_by_key(|(kind, _)| kind.to_owned())
12165 .collect();
12166 if let Some((leading_tag_source, _)) = tags.first() {
12167 // Strongest source wins; if we have worktree tag binding, prefer that to
12168 // global and language bindings;
12169 // if we have a global binding, prefer that to language binding.
12170 let first_mismatch = tags
12171 .iter()
12172 .position(|(tag_source, _)| tag_source != leading_tag_source);
12173 if let Some(index) = first_mismatch {
12174 tags.truncate(index);
12175 }
12176 }
12177
12178 tags
12179 }
12180
12181 pub fn move_to_enclosing_bracket(
12182 &mut self,
12183 _: &MoveToEnclosingBracket,
12184 window: &mut Window,
12185 cx: &mut Context<Self>,
12186 ) {
12187 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12188 s.move_offsets_with(|snapshot, selection| {
12189 let Some(enclosing_bracket_ranges) =
12190 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
12191 else {
12192 return;
12193 };
12194
12195 let mut best_length = usize::MAX;
12196 let mut best_inside = false;
12197 let mut best_in_bracket_range = false;
12198 let mut best_destination = None;
12199 for (open, close) in enclosing_bracket_ranges {
12200 let close = close.to_inclusive();
12201 let length = close.end() - open.start;
12202 let inside = selection.start >= open.end && selection.end <= *close.start();
12203 let in_bracket_range = open.to_inclusive().contains(&selection.head())
12204 || close.contains(&selection.head());
12205
12206 // If best is next to a bracket and current isn't, skip
12207 if !in_bracket_range && best_in_bracket_range {
12208 continue;
12209 }
12210
12211 // Prefer smaller lengths unless best is inside and current isn't
12212 if length > best_length && (best_inside || !inside) {
12213 continue;
12214 }
12215
12216 best_length = length;
12217 best_inside = inside;
12218 best_in_bracket_range = in_bracket_range;
12219 best_destination = Some(
12220 if close.contains(&selection.start) && close.contains(&selection.end) {
12221 if inside {
12222 open.end
12223 } else {
12224 open.start
12225 }
12226 } else if inside {
12227 *close.start()
12228 } else {
12229 *close.end()
12230 },
12231 );
12232 }
12233
12234 if let Some(destination) = best_destination {
12235 selection.collapse_to(destination, SelectionGoal::None);
12236 }
12237 })
12238 });
12239 }
12240
12241 pub fn undo_selection(
12242 &mut self,
12243 _: &UndoSelection,
12244 window: &mut Window,
12245 cx: &mut Context<Self>,
12246 ) {
12247 self.end_selection(window, cx);
12248 self.selection_history.mode = SelectionHistoryMode::Undoing;
12249 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
12250 self.change_selections(None, window, cx, |s| {
12251 s.select_anchors(entry.selections.to_vec())
12252 });
12253 self.select_next_state = entry.select_next_state;
12254 self.select_prev_state = entry.select_prev_state;
12255 self.add_selections_state = entry.add_selections_state;
12256 self.request_autoscroll(Autoscroll::newest(), cx);
12257 }
12258 self.selection_history.mode = SelectionHistoryMode::Normal;
12259 }
12260
12261 pub fn redo_selection(
12262 &mut self,
12263 _: &RedoSelection,
12264 window: &mut Window,
12265 cx: &mut Context<Self>,
12266 ) {
12267 self.end_selection(window, cx);
12268 self.selection_history.mode = SelectionHistoryMode::Redoing;
12269 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
12270 self.change_selections(None, window, cx, |s| {
12271 s.select_anchors(entry.selections.to_vec())
12272 });
12273 self.select_next_state = entry.select_next_state;
12274 self.select_prev_state = entry.select_prev_state;
12275 self.add_selections_state = entry.add_selections_state;
12276 self.request_autoscroll(Autoscroll::newest(), cx);
12277 }
12278 self.selection_history.mode = SelectionHistoryMode::Normal;
12279 }
12280
12281 pub fn expand_excerpts(
12282 &mut self,
12283 action: &ExpandExcerpts,
12284 _: &mut Window,
12285 cx: &mut Context<Self>,
12286 ) {
12287 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
12288 }
12289
12290 pub fn expand_excerpts_down(
12291 &mut self,
12292 action: &ExpandExcerptsDown,
12293 _: &mut Window,
12294 cx: &mut Context<Self>,
12295 ) {
12296 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
12297 }
12298
12299 pub fn expand_excerpts_up(
12300 &mut self,
12301 action: &ExpandExcerptsUp,
12302 _: &mut Window,
12303 cx: &mut Context<Self>,
12304 ) {
12305 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
12306 }
12307
12308 pub fn expand_excerpts_for_direction(
12309 &mut self,
12310 lines: u32,
12311 direction: ExpandExcerptDirection,
12312
12313 cx: &mut Context<Self>,
12314 ) {
12315 let selections = self.selections.disjoint_anchors();
12316
12317 let lines = if lines == 0 {
12318 EditorSettings::get_global(cx).expand_excerpt_lines
12319 } else {
12320 lines
12321 };
12322
12323 self.buffer.update(cx, |buffer, cx| {
12324 let snapshot = buffer.snapshot(cx);
12325 let mut excerpt_ids = selections
12326 .iter()
12327 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
12328 .collect::<Vec<_>>();
12329 excerpt_ids.sort();
12330 excerpt_ids.dedup();
12331 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
12332 })
12333 }
12334
12335 pub fn expand_excerpt(
12336 &mut self,
12337 excerpt: ExcerptId,
12338 direction: ExpandExcerptDirection,
12339 window: &mut Window,
12340 cx: &mut Context<Self>,
12341 ) {
12342 let current_scroll_position = self.scroll_position(cx);
12343 let lines = EditorSettings::get_global(cx).expand_excerpt_lines;
12344 self.buffer.update(cx, |buffer, cx| {
12345 buffer.expand_excerpts([excerpt], lines, direction, cx)
12346 });
12347 if direction == ExpandExcerptDirection::Down {
12348 let new_scroll_position = current_scroll_position + gpui::Point::new(0.0, lines as f32);
12349 self.set_scroll_position(new_scroll_position, window, cx);
12350 }
12351 }
12352
12353 pub fn go_to_singleton_buffer_point(
12354 &mut self,
12355 point: Point,
12356 window: &mut Window,
12357 cx: &mut Context<Self>,
12358 ) {
12359 self.go_to_singleton_buffer_range(point..point, window, cx);
12360 }
12361
12362 pub fn go_to_singleton_buffer_range(
12363 &mut self,
12364 range: Range<Point>,
12365 window: &mut Window,
12366 cx: &mut Context<Self>,
12367 ) {
12368 let multibuffer = self.buffer().read(cx);
12369 let Some(buffer) = multibuffer.as_singleton() else {
12370 return;
12371 };
12372 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
12373 return;
12374 };
12375 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
12376 return;
12377 };
12378 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
12379 s.select_anchor_ranges([start..end])
12380 });
12381 }
12382
12383 fn go_to_diagnostic(
12384 &mut self,
12385 _: &GoToDiagnostic,
12386 window: &mut Window,
12387 cx: &mut Context<Self>,
12388 ) {
12389 self.go_to_diagnostic_impl(Direction::Next, window, cx)
12390 }
12391
12392 fn go_to_prev_diagnostic(
12393 &mut self,
12394 _: &GoToPreviousDiagnostic,
12395 window: &mut Window,
12396 cx: &mut Context<Self>,
12397 ) {
12398 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
12399 }
12400
12401 pub fn go_to_diagnostic_impl(
12402 &mut self,
12403 direction: Direction,
12404 window: &mut Window,
12405 cx: &mut Context<Self>,
12406 ) {
12407 let buffer = self.buffer.read(cx).snapshot(cx);
12408 let selection = self.selections.newest::<usize>(cx);
12409
12410 // If there is an active Diagnostic Popover jump to its diagnostic instead.
12411 if direction == Direction::Next {
12412 if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() {
12413 let Some(buffer_id) = popover.local_diagnostic.range.start.buffer_id else {
12414 return;
12415 };
12416 self.activate_diagnostics(
12417 buffer_id,
12418 popover.local_diagnostic.diagnostic.group_id,
12419 window,
12420 cx,
12421 );
12422 if let Some(active_diagnostics) = self.active_diagnostics.as_ref() {
12423 let primary_range_start = active_diagnostics.primary_range.start;
12424 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12425 let mut new_selection = s.newest_anchor().clone();
12426 new_selection.collapse_to(primary_range_start, SelectionGoal::None);
12427 s.select_anchors(vec![new_selection.clone()]);
12428 });
12429 self.refresh_inline_completion(false, true, window, cx);
12430 }
12431 return;
12432 }
12433 }
12434
12435 let active_group_id = self
12436 .active_diagnostics
12437 .as_ref()
12438 .map(|active_group| active_group.group_id);
12439 let active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| {
12440 active_diagnostics
12441 .primary_range
12442 .to_offset(&buffer)
12443 .to_inclusive()
12444 });
12445 let search_start = if let Some(active_primary_range) = active_primary_range.as_ref() {
12446 if active_primary_range.contains(&selection.head()) {
12447 *active_primary_range.start()
12448 } else {
12449 selection.head()
12450 }
12451 } else {
12452 selection.head()
12453 };
12454
12455 let snapshot = self.snapshot(window, cx);
12456 let primary_diagnostics_before = buffer
12457 .diagnostics_in_range::<usize>(0..search_start)
12458 .filter(|entry| entry.diagnostic.is_primary)
12459 .filter(|entry| entry.range.start != entry.range.end)
12460 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
12461 .filter(|entry| !snapshot.intersects_fold(entry.range.start))
12462 .collect::<Vec<_>>();
12463 let last_same_group_diagnostic_before = active_group_id.and_then(|active_group_id| {
12464 primary_diagnostics_before
12465 .iter()
12466 .position(|entry| entry.diagnostic.group_id == active_group_id)
12467 });
12468
12469 let primary_diagnostics_after = buffer
12470 .diagnostics_in_range::<usize>(search_start..buffer.len())
12471 .filter(|entry| entry.diagnostic.is_primary)
12472 .filter(|entry| entry.range.start != entry.range.end)
12473 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
12474 .filter(|diagnostic| !snapshot.intersects_fold(diagnostic.range.start))
12475 .collect::<Vec<_>>();
12476 let last_same_group_diagnostic_after = active_group_id.and_then(|active_group_id| {
12477 primary_diagnostics_after
12478 .iter()
12479 .enumerate()
12480 .rev()
12481 .find_map(|(i, entry)| {
12482 if entry.diagnostic.group_id == active_group_id {
12483 Some(i)
12484 } else {
12485 None
12486 }
12487 })
12488 });
12489
12490 let next_primary_diagnostic = match direction {
12491 Direction::Prev => primary_diagnostics_before
12492 .iter()
12493 .take(last_same_group_diagnostic_before.unwrap_or(usize::MAX))
12494 .rev()
12495 .next(),
12496 Direction::Next => primary_diagnostics_after
12497 .iter()
12498 .skip(
12499 last_same_group_diagnostic_after
12500 .map(|index| index + 1)
12501 .unwrap_or(0),
12502 )
12503 .next(),
12504 };
12505
12506 // Cycle around to the start of the buffer, potentially moving back to the start of
12507 // the currently active diagnostic.
12508 let cycle_around = || match direction {
12509 Direction::Prev => primary_diagnostics_after
12510 .iter()
12511 .rev()
12512 .chain(primary_diagnostics_before.iter().rev())
12513 .next(),
12514 Direction::Next => primary_diagnostics_before
12515 .iter()
12516 .chain(primary_diagnostics_after.iter())
12517 .next(),
12518 };
12519
12520 if let Some((primary_range, group_id)) = next_primary_diagnostic
12521 .or_else(cycle_around)
12522 .map(|entry| (&entry.range, entry.diagnostic.group_id))
12523 {
12524 let Some(buffer_id) = buffer.anchor_after(primary_range.start).buffer_id else {
12525 return;
12526 };
12527 self.activate_diagnostics(buffer_id, group_id, window, cx);
12528 if self.active_diagnostics.is_some() {
12529 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12530 s.select(vec![Selection {
12531 id: selection.id,
12532 start: primary_range.start,
12533 end: primary_range.start,
12534 reversed: false,
12535 goal: SelectionGoal::None,
12536 }]);
12537 });
12538 self.refresh_inline_completion(false, true, window, cx);
12539 }
12540 }
12541 }
12542
12543 fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
12544 let snapshot = self.snapshot(window, cx);
12545 let selection = self.selections.newest::<Point>(cx);
12546 self.go_to_hunk_before_or_after_position(
12547 &snapshot,
12548 selection.head(),
12549 Direction::Next,
12550 window,
12551 cx,
12552 );
12553 }
12554
12555 fn go_to_hunk_before_or_after_position(
12556 &mut self,
12557 snapshot: &EditorSnapshot,
12558 position: Point,
12559 direction: Direction,
12560 window: &mut Window,
12561 cx: &mut Context<Editor>,
12562 ) {
12563 let row = if direction == Direction::Next {
12564 self.hunk_after_position(snapshot, position)
12565 .map(|hunk| hunk.row_range.start)
12566 } else {
12567 self.hunk_before_position(snapshot, position)
12568 };
12569
12570 if let Some(row) = row {
12571 let destination = Point::new(row.0, 0);
12572 let autoscroll = Autoscroll::center();
12573
12574 self.unfold_ranges(&[destination..destination], false, false, cx);
12575 self.change_selections(Some(autoscroll), window, cx, |s| {
12576 s.select_ranges([destination..destination]);
12577 });
12578 }
12579 }
12580
12581 fn hunk_after_position(
12582 &mut self,
12583 snapshot: &EditorSnapshot,
12584 position: Point,
12585 ) -> Option<MultiBufferDiffHunk> {
12586 snapshot
12587 .buffer_snapshot
12588 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
12589 .find(|hunk| hunk.row_range.start.0 > position.row)
12590 .or_else(|| {
12591 snapshot
12592 .buffer_snapshot
12593 .diff_hunks_in_range(Point::zero()..position)
12594 .find(|hunk| hunk.row_range.end.0 < position.row)
12595 })
12596 }
12597
12598 fn go_to_prev_hunk(
12599 &mut self,
12600 _: &GoToPreviousHunk,
12601 window: &mut Window,
12602 cx: &mut Context<Self>,
12603 ) {
12604 let snapshot = self.snapshot(window, cx);
12605 let selection = self.selections.newest::<Point>(cx);
12606 self.go_to_hunk_before_or_after_position(
12607 &snapshot,
12608 selection.head(),
12609 Direction::Prev,
12610 window,
12611 cx,
12612 );
12613 }
12614
12615 fn hunk_before_position(
12616 &mut self,
12617 snapshot: &EditorSnapshot,
12618 position: Point,
12619 ) -> Option<MultiBufferRow> {
12620 snapshot
12621 .buffer_snapshot
12622 .diff_hunk_before(position)
12623 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
12624 }
12625
12626 fn go_to_line<T: 'static>(
12627 &mut self,
12628 position: Anchor,
12629 highlight_color: Option<Hsla>,
12630 window: &mut Window,
12631 cx: &mut Context<Self>,
12632 ) {
12633 let snapshot = self.snapshot(window, cx).display_snapshot;
12634 let position = position.to_point(&snapshot.buffer_snapshot);
12635 let start = snapshot
12636 .buffer_snapshot
12637 .clip_point(Point::new(position.row, 0), Bias::Left);
12638 let end = start + Point::new(1, 0);
12639 let start = snapshot.buffer_snapshot.anchor_before(start);
12640 let end = snapshot.buffer_snapshot.anchor_before(end);
12641
12642 self.clear_row_highlights::<T>();
12643 self.highlight_rows::<T>(
12644 start..end,
12645 highlight_color
12646 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
12647 true,
12648 cx,
12649 );
12650 self.request_autoscroll(Autoscroll::center(), cx);
12651 }
12652
12653 pub fn go_to_definition(
12654 &mut self,
12655 _: &GoToDefinition,
12656 window: &mut Window,
12657 cx: &mut Context<Self>,
12658 ) -> Task<Result<Navigated>> {
12659 let definition =
12660 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
12661 cx.spawn_in(window, async move |editor, cx| {
12662 if definition.await? == Navigated::Yes {
12663 return Ok(Navigated::Yes);
12664 }
12665 match editor.update_in(cx, |editor, window, cx| {
12666 editor.find_all_references(&FindAllReferences, window, cx)
12667 })? {
12668 Some(references) => references.await,
12669 None => Ok(Navigated::No),
12670 }
12671 })
12672 }
12673
12674 pub fn go_to_declaration(
12675 &mut self,
12676 _: &GoToDeclaration,
12677 window: &mut Window,
12678 cx: &mut Context<Self>,
12679 ) -> Task<Result<Navigated>> {
12680 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
12681 }
12682
12683 pub fn go_to_declaration_split(
12684 &mut self,
12685 _: &GoToDeclaration,
12686 window: &mut Window,
12687 cx: &mut Context<Self>,
12688 ) -> Task<Result<Navigated>> {
12689 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
12690 }
12691
12692 pub fn go_to_implementation(
12693 &mut self,
12694 _: &GoToImplementation,
12695 window: &mut Window,
12696 cx: &mut Context<Self>,
12697 ) -> Task<Result<Navigated>> {
12698 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
12699 }
12700
12701 pub fn go_to_implementation_split(
12702 &mut self,
12703 _: &GoToImplementationSplit,
12704 window: &mut Window,
12705 cx: &mut Context<Self>,
12706 ) -> Task<Result<Navigated>> {
12707 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
12708 }
12709
12710 pub fn go_to_type_definition(
12711 &mut self,
12712 _: &GoToTypeDefinition,
12713 window: &mut Window,
12714 cx: &mut Context<Self>,
12715 ) -> Task<Result<Navigated>> {
12716 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
12717 }
12718
12719 pub fn go_to_definition_split(
12720 &mut self,
12721 _: &GoToDefinitionSplit,
12722 window: &mut Window,
12723 cx: &mut Context<Self>,
12724 ) -> Task<Result<Navigated>> {
12725 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
12726 }
12727
12728 pub fn go_to_type_definition_split(
12729 &mut self,
12730 _: &GoToTypeDefinitionSplit,
12731 window: &mut Window,
12732 cx: &mut Context<Self>,
12733 ) -> Task<Result<Navigated>> {
12734 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
12735 }
12736
12737 fn go_to_definition_of_kind(
12738 &mut self,
12739 kind: GotoDefinitionKind,
12740 split: bool,
12741 window: &mut Window,
12742 cx: &mut Context<Self>,
12743 ) -> Task<Result<Navigated>> {
12744 let Some(provider) = self.semantics_provider.clone() else {
12745 return Task::ready(Ok(Navigated::No));
12746 };
12747 let head = self.selections.newest::<usize>(cx).head();
12748 let buffer = self.buffer.read(cx);
12749 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
12750 text_anchor
12751 } else {
12752 return Task::ready(Ok(Navigated::No));
12753 };
12754
12755 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
12756 return Task::ready(Ok(Navigated::No));
12757 };
12758
12759 cx.spawn_in(window, async move |editor, cx| {
12760 let definitions = definitions.await?;
12761 let navigated = editor
12762 .update_in(cx, |editor, window, cx| {
12763 editor.navigate_to_hover_links(
12764 Some(kind),
12765 definitions
12766 .into_iter()
12767 .filter(|location| {
12768 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
12769 })
12770 .map(HoverLink::Text)
12771 .collect::<Vec<_>>(),
12772 split,
12773 window,
12774 cx,
12775 )
12776 })?
12777 .await?;
12778 anyhow::Ok(navigated)
12779 })
12780 }
12781
12782 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
12783 let selection = self.selections.newest_anchor();
12784 let head = selection.head();
12785 let tail = selection.tail();
12786
12787 let Some((buffer, start_position)) =
12788 self.buffer.read(cx).text_anchor_for_position(head, cx)
12789 else {
12790 return;
12791 };
12792
12793 let end_position = if head != tail {
12794 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
12795 return;
12796 };
12797 Some(pos)
12798 } else {
12799 None
12800 };
12801
12802 let url_finder = cx.spawn_in(window, async move |editor, cx| {
12803 let url = if let Some(end_pos) = end_position {
12804 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
12805 } else {
12806 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
12807 };
12808
12809 if let Some(url) = url {
12810 editor.update(cx, |_, cx| {
12811 cx.open_url(&url);
12812 })
12813 } else {
12814 Ok(())
12815 }
12816 });
12817
12818 url_finder.detach();
12819 }
12820
12821 pub fn open_selected_filename(
12822 &mut self,
12823 _: &OpenSelectedFilename,
12824 window: &mut Window,
12825 cx: &mut Context<Self>,
12826 ) {
12827 let Some(workspace) = self.workspace() else {
12828 return;
12829 };
12830
12831 let position = self.selections.newest_anchor().head();
12832
12833 let Some((buffer, buffer_position)) =
12834 self.buffer.read(cx).text_anchor_for_position(position, cx)
12835 else {
12836 return;
12837 };
12838
12839 let project = self.project.clone();
12840
12841 cx.spawn_in(window, async move |_, cx| {
12842 let result = find_file(&buffer, project, buffer_position, cx).await;
12843
12844 if let Some((_, path)) = result {
12845 workspace
12846 .update_in(cx, |workspace, window, cx| {
12847 workspace.open_resolved_path(path, window, cx)
12848 })?
12849 .await?;
12850 }
12851 anyhow::Ok(())
12852 })
12853 .detach();
12854 }
12855
12856 pub(crate) fn navigate_to_hover_links(
12857 &mut self,
12858 kind: Option<GotoDefinitionKind>,
12859 mut definitions: Vec<HoverLink>,
12860 split: bool,
12861 window: &mut Window,
12862 cx: &mut Context<Editor>,
12863 ) -> Task<Result<Navigated>> {
12864 // If there is one definition, just open it directly
12865 if definitions.len() == 1 {
12866 let definition = definitions.pop().unwrap();
12867
12868 enum TargetTaskResult {
12869 Location(Option<Location>),
12870 AlreadyNavigated,
12871 }
12872
12873 let target_task = match definition {
12874 HoverLink::Text(link) => {
12875 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
12876 }
12877 HoverLink::InlayHint(lsp_location, server_id) => {
12878 let computation =
12879 self.compute_target_location(lsp_location, server_id, window, cx);
12880 cx.background_spawn(async move {
12881 let location = computation.await?;
12882 Ok(TargetTaskResult::Location(location))
12883 })
12884 }
12885 HoverLink::Url(url) => {
12886 cx.open_url(&url);
12887 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
12888 }
12889 HoverLink::File(path) => {
12890 if let Some(workspace) = self.workspace() {
12891 cx.spawn_in(window, async move |_, cx| {
12892 workspace
12893 .update_in(cx, |workspace, window, cx| {
12894 workspace.open_resolved_path(path, window, cx)
12895 })?
12896 .await
12897 .map(|_| TargetTaskResult::AlreadyNavigated)
12898 })
12899 } else {
12900 Task::ready(Ok(TargetTaskResult::Location(None)))
12901 }
12902 }
12903 };
12904 cx.spawn_in(window, async move |editor, cx| {
12905 let target = match target_task.await.context("target resolution task")? {
12906 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
12907 TargetTaskResult::Location(None) => return Ok(Navigated::No),
12908 TargetTaskResult::Location(Some(target)) => target,
12909 };
12910
12911 editor.update_in(cx, |editor, window, cx| {
12912 let Some(workspace) = editor.workspace() else {
12913 return Navigated::No;
12914 };
12915 let pane = workspace.read(cx).active_pane().clone();
12916
12917 let range = target.range.to_point(target.buffer.read(cx));
12918 let range = editor.range_for_match(&range);
12919 let range = collapse_multiline_range(range);
12920
12921 if !split
12922 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
12923 {
12924 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
12925 } else {
12926 window.defer(cx, move |window, cx| {
12927 let target_editor: Entity<Self> =
12928 workspace.update(cx, |workspace, cx| {
12929 let pane = if split {
12930 workspace.adjacent_pane(window, cx)
12931 } else {
12932 workspace.active_pane().clone()
12933 };
12934
12935 workspace.open_project_item(
12936 pane,
12937 target.buffer.clone(),
12938 true,
12939 true,
12940 window,
12941 cx,
12942 )
12943 });
12944 target_editor.update(cx, |target_editor, cx| {
12945 // When selecting a definition in a different buffer, disable the nav history
12946 // to avoid creating a history entry at the previous cursor location.
12947 pane.update(cx, |pane, _| pane.disable_history());
12948 target_editor.go_to_singleton_buffer_range(range, window, cx);
12949 pane.update(cx, |pane, _| pane.enable_history());
12950 });
12951 });
12952 }
12953 Navigated::Yes
12954 })
12955 })
12956 } else if !definitions.is_empty() {
12957 cx.spawn_in(window, async move |editor, cx| {
12958 let (title, location_tasks, workspace) = editor
12959 .update_in(cx, |editor, window, cx| {
12960 let tab_kind = match kind {
12961 Some(GotoDefinitionKind::Implementation) => "Implementations",
12962 _ => "Definitions",
12963 };
12964 let title = definitions
12965 .iter()
12966 .find_map(|definition| match definition {
12967 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
12968 let buffer = origin.buffer.read(cx);
12969 format!(
12970 "{} for {}",
12971 tab_kind,
12972 buffer
12973 .text_for_range(origin.range.clone())
12974 .collect::<String>()
12975 )
12976 }),
12977 HoverLink::InlayHint(_, _) => None,
12978 HoverLink::Url(_) => None,
12979 HoverLink::File(_) => None,
12980 })
12981 .unwrap_or(tab_kind.to_string());
12982 let location_tasks = definitions
12983 .into_iter()
12984 .map(|definition| match definition {
12985 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
12986 HoverLink::InlayHint(lsp_location, server_id) => editor
12987 .compute_target_location(lsp_location, server_id, window, cx),
12988 HoverLink::Url(_) => Task::ready(Ok(None)),
12989 HoverLink::File(_) => Task::ready(Ok(None)),
12990 })
12991 .collect::<Vec<_>>();
12992 (title, location_tasks, editor.workspace().clone())
12993 })
12994 .context("location tasks preparation")?;
12995
12996 let locations = future::join_all(location_tasks)
12997 .await
12998 .into_iter()
12999 .filter_map(|location| location.transpose())
13000 .collect::<Result<_>>()
13001 .context("location tasks")?;
13002
13003 let Some(workspace) = workspace else {
13004 return Ok(Navigated::No);
13005 };
13006 let opened = workspace
13007 .update_in(cx, |workspace, window, cx| {
13008 Self::open_locations_in_multibuffer(
13009 workspace,
13010 locations,
13011 title,
13012 split,
13013 MultibufferSelectionMode::First,
13014 window,
13015 cx,
13016 )
13017 })
13018 .ok();
13019
13020 anyhow::Ok(Navigated::from_bool(opened.is_some()))
13021 })
13022 } else {
13023 Task::ready(Ok(Navigated::No))
13024 }
13025 }
13026
13027 fn compute_target_location(
13028 &self,
13029 lsp_location: lsp::Location,
13030 server_id: LanguageServerId,
13031 window: &mut Window,
13032 cx: &mut Context<Self>,
13033 ) -> Task<anyhow::Result<Option<Location>>> {
13034 let Some(project) = self.project.clone() else {
13035 return Task::ready(Ok(None));
13036 };
13037
13038 cx.spawn_in(window, async move |editor, cx| {
13039 let location_task = editor.update(cx, |_, cx| {
13040 project.update(cx, |project, cx| {
13041 let language_server_name = project
13042 .language_server_statuses(cx)
13043 .find(|(id, _)| server_id == *id)
13044 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
13045 language_server_name.map(|language_server_name| {
13046 project.open_local_buffer_via_lsp(
13047 lsp_location.uri.clone(),
13048 server_id,
13049 language_server_name,
13050 cx,
13051 )
13052 })
13053 })
13054 })?;
13055 let location = match location_task {
13056 Some(task) => Some({
13057 let target_buffer_handle = task.await.context("open local buffer")?;
13058 let range = target_buffer_handle.update(cx, |target_buffer, _| {
13059 let target_start = target_buffer
13060 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
13061 let target_end = target_buffer
13062 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
13063 target_buffer.anchor_after(target_start)
13064 ..target_buffer.anchor_before(target_end)
13065 })?;
13066 Location {
13067 buffer: target_buffer_handle,
13068 range,
13069 }
13070 }),
13071 None => None,
13072 };
13073 Ok(location)
13074 })
13075 }
13076
13077 pub fn find_all_references(
13078 &mut self,
13079 _: &FindAllReferences,
13080 window: &mut Window,
13081 cx: &mut Context<Self>,
13082 ) -> Option<Task<Result<Navigated>>> {
13083 let selection = self.selections.newest::<usize>(cx);
13084 let multi_buffer = self.buffer.read(cx);
13085 let head = selection.head();
13086
13087 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
13088 let head_anchor = multi_buffer_snapshot.anchor_at(
13089 head,
13090 if head < selection.tail() {
13091 Bias::Right
13092 } else {
13093 Bias::Left
13094 },
13095 );
13096
13097 match self
13098 .find_all_references_task_sources
13099 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13100 {
13101 Ok(_) => {
13102 log::info!(
13103 "Ignoring repeated FindAllReferences invocation with the position of already running task"
13104 );
13105 return None;
13106 }
13107 Err(i) => {
13108 self.find_all_references_task_sources.insert(i, head_anchor);
13109 }
13110 }
13111
13112 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
13113 let workspace = self.workspace()?;
13114 let project = workspace.read(cx).project().clone();
13115 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
13116 Some(cx.spawn_in(window, async move |editor, cx| {
13117 let _cleanup = cx.on_drop(&editor, move |editor, _| {
13118 if let Ok(i) = editor
13119 .find_all_references_task_sources
13120 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13121 {
13122 editor.find_all_references_task_sources.remove(i);
13123 }
13124 });
13125
13126 let locations = references.await?;
13127 if locations.is_empty() {
13128 return anyhow::Ok(Navigated::No);
13129 }
13130
13131 workspace.update_in(cx, |workspace, window, cx| {
13132 let title = locations
13133 .first()
13134 .as_ref()
13135 .map(|location| {
13136 let buffer = location.buffer.read(cx);
13137 format!(
13138 "References to `{}`",
13139 buffer
13140 .text_for_range(location.range.clone())
13141 .collect::<String>()
13142 )
13143 })
13144 .unwrap();
13145 Self::open_locations_in_multibuffer(
13146 workspace,
13147 locations,
13148 title,
13149 false,
13150 MultibufferSelectionMode::First,
13151 window,
13152 cx,
13153 );
13154 Navigated::Yes
13155 })
13156 }))
13157 }
13158
13159 /// Opens a multibuffer with the given project locations in it
13160 pub fn open_locations_in_multibuffer(
13161 workspace: &mut Workspace,
13162 mut locations: Vec<Location>,
13163 title: String,
13164 split: bool,
13165 multibuffer_selection_mode: MultibufferSelectionMode,
13166 window: &mut Window,
13167 cx: &mut Context<Workspace>,
13168 ) {
13169 // If there are multiple definitions, open them in a multibuffer
13170 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
13171 let mut locations = locations.into_iter().peekable();
13172 let mut ranges = Vec::new();
13173 let capability = workspace.project().read(cx).capability();
13174
13175 let excerpt_buffer = cx.new(|cx| {
13176 let mut multibuffer = MultiBuffer::new(capability);
13177 while let Some(location) = locations.next() {
13178 let buffer = location.buffer.read(cx);
13179 let mut ranges_for_buffer = Vec::new();
13180 let range = location.range.to_offset(buffer);
13181 ranges_for_buffer.push(range.clone());
13182
13183 while let Some(next_location) = locations.peek() {
13184 if next_location.buffer == location.buffer {
13185 ranges_for_buffer.push(next_location.range.to_offset(buffer));
13186 locations.next();
13187 } else {
13188 break;
13189 }
13190 }
13191
13192 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
13193 ranges.extend(multibuffer.push_excerpts_with_context_lines(
13194 location.buffer.clone(),
13195 ranges_for_buffer,
13196 DEFAULT_MULTIBUFFER_CONTEXT,
13197 cx,
13198 ))
13199 }
13200
13201 multibuffer.with_title(title)
13202 });
13203
13204 let editor = cx.new(|cx| {
13205 Editor::for_multibuffer(
13206 excerpt_buffer,
13207 Some(workspace.project().clone()),
13208 window,
13209 cx,
13210 )
13211 });
13212 editor.update(cx, |editor, cx| {
13213 match multibuffer_selection_mode {
13214 MultibufferSelectionMode::First => {
13215 if let Some(first_range) = ranges.first() {
13216 editor.change_selections(None, window, cx, |selections| {
13217 selections.clear_disjoint();
13218 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
13219 });
13220 }
13221 editor.highlight_background::<Self>(
13222 &ranges,
13223 |theme| theme.editor_highlighted_line_background,
13224 cx,
13225 );
13226 }
13227 MultibufferSelectionMode::All => {
13228 editor.change_selections(None, window, cx, |selections| {
13229 selections.clear_disjoint();
13230 selections.select_anchor_ranges(ranges);
13231 });
13232 }
13233 }
13234 editor.register_buffers_with_language_servers(cx);
13235 });
13236
13237 let item = Box::new(editor);
13238 let item_id = item.item_id();
13239
13240 if split {
13241 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
13242 } else {
13243 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
13244 let (preview_item_id, preview_item_idx) =
13245 workspace.active_pane().update(cx, |pane, _| {
13246 (pane.preview_item_id(), pane.preview_item_idx())
13247 });
13248
13249 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
13250
13251 if let Some(preview_item_id) = preview_item_id {
13252 workspace.active_pane().update(cx, |pane, cx| {
13253 pane.remove_item(preview_item_id, false, false, window, cx);
13254 });
13255 }
13256 } else {
13257 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
13258 }
13259 }
13260 workspace.active_pane().update(cx, |pane, cx| {
13261 pane.set_preview_item_id(Some(item_id), cx);
13262 });
13263 }
13264
13265 pub fn rename(
13266 &mut self,
13267 _: &Rename,
13268 window: &mut Window,
13269 cx: &mut Context<Self>,
13270 ) -> Option<Task<Result<()>>> {
13271 use language::ToOffset as _;
13272
13273 let provider = self.semantics_provider.clone()?;
13274 let selection = self.selections.newest_anchor().clone();
13275 let (cursor_buffer, cursor_buffer_position) = self
13276 .buffer
13277 .read(cx)
13278 .text_anchor_for_position(selection.head(), cx)?;
13279 let (tail_buffer, cursor_buffer_position_end) = self
13280 .buffer
13281 .read(cx)
13282 .text_anchor_for_position(selection.tail(), cx)?;
13283 if tail_buffer != cursor_buffer {
13284 return None;
13285 }
13286
13287 let snapshot = cursor_buffer.read(cx).snapshot();
13288 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
13289 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
13290 let prepare_rename = provider
13291 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
13292 .unwrap_or_else(|| Task::ready(Ok(None)));
13293 drop(snapshot);
13294
13295 Some(cx.spawn_in(window, async move |this, cx| {
13296 let rename_range = if let Some(range) = prepare_rename.await? {
13297 Some(range)
13298 } else {
13299 this.update(cx, |this, cx| {
13300 let buffer = this.buffer.read(cx).snapshot(cx);
13301 let mut buffer_highlights = this
13302 .document_highlights_for_position(selection.head(), &buffer)
13303 .filter(|highlight| {
13304 highlight.start.excerpt_id == selection.head().excerpt_id
13305 && highlight.end.excerpt_id == selection.head().excerpt_id
13306 });
13307 buffer_highlights
13308 .next()
13309 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
13310 })?
13311 };
13312 if let Some(rename_range) = rename_range {
13313 this.update_in(cx, |this, window, cx| {
13314 let snapshot = cursor_buffer.read(cx).snapshot();
13315 let rename_buffer_range = rename_range.to_offset(&snapshot);
13316 let cursor_offset_in_rename_range =
13317 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
13318 let cursor_offset_in_rename_range_end =
13319 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
13320
13321 this.take_rename(false, window, cx);
13322 let buffer = this.buffer.read(cx).read(cx);
13323 let cursor_offset = selection.head().to_offset(&buffer);
13324 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
13325 let rename_end = rename_start + rename_buffer_range.len();
13326 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
13327 let mut old_highlight_id = None;
13328 let old_name: Arc<str> = buffer
13329 .chunks(rename_start..rename_end, true)
13330 .map(|chunk| {
13331 if old_highlight_id.is_none() {
13332 old_highlight_id = chunk.syntax_highlight_id;
13333 }
13334 chunk.text
13335 })
13336 .collect::<String>()
13337 .into();
13338
13339 drop(buffer);
13340
13341 // Position the selection in the rename editor so that it matches the current selection.
13342 this.show_local_selections = false;
13343 let rename_editor = cx.new(|cx| {
13344 let mut editor = Editor::single_line(window, cx);
13345 editor.buffer.update(cx, |buffer, cx| {
13346 buffer.edit([(0..0, old_name.clone())], None, cx)
13347 });
13348 let rename_selection_range = match cursor_offset_in_rename_range
13349 .cmp(&cursor_offset_in_rename_range_end)
13350 {
13351 Ordering::Equal => {
13352 editor.select_all(&SelectAll, window, cx);
13353 return editor;
13354 }
13355 Ordering::Less => {
13356 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
13357 }
13358 Ordering::Greater => {
13359 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
13360 }
13361 };
13362 if rename_selection_range.end > old_name.len() {
13363 editor.select_all(&SelectAll, window, cx);
13364 } else {
13365 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13366 s.select_ranges([rename_selection_range]);
13367 });
13368 }
13369 editor
13370 });
13371 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
13372 if e == &EditorEvent::Focused {
13373 cx.emit(EditorEvent::FocusedIn)
13374 }
13375 })
13376 .detach();
13377
13378 let write_highlights =
13379 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
13380 let read_highlights =
13381 this.clear_background_highlights::<DocumentHighlightRead>(cx);
13382 let ranges = write_highlights
13383 .iter()
13384 .flat_map(|(_, ranges)| ranges.iter())
13385 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
13386 .cloned()
13387 .collect();
13388
13389 this.highlight_text::<Rename>(
13390 ranges,
13391 HighlightStyle {
13392 fade_out: Some(0.6),
13393 ..Default::default()
13394 },
13395 cx,
13396 );
13397 let rename_focus_handle = rename_editor.focus_handle(cx);
13398 window.focus(&rename_focus_handle);
13399 let block_id = this.insert_blocks(
13400 [BlockProperties {
13401 style: BlockStyle::Flex,
13402 placement: BlockPlacement::Below(range.start),
13403 height: 1,
13404 render: Arc::new({
13405 let rename_editor = rename_editor.clone();
13406 move |cx: &mut BlockContext| {
13407 let mut text_style = cx.editor_style.text.clone();
13408 if let Some(highlight_style) = old_highlight_id
13409 .and_then(|h| h.style(&cx.editor_style.syntax))
13410 {
13411 text_style = text_style.highlight(highlight_style);
13412 }
13413 div()
13414 .block_mouse_down()
13415 .pl(cx.anchor_x)
13416 .child(EditorElement::new(
13417 &rename_editor,
13418 EditorStyle {
13419 background: cx.theme().system().transparent,
13420 local_player: cx.editor_style.local_player,
13421 text: text_style,
13422 scrollbar_width: cx.editor_style.scrollbar_width,
13423 syntax: cx.editor_style.syntax.clone(),
13424 status: cx.editor_style.status.clone(),
13425 inlay_hints_style: HighlightStyle {
13426 font_weight: Some(FontWeight::BOLD),
13427 ..make_inlay_hints_style(cx.app)
13428 },
13429 inline_completion_styles: make_suggestion_styles(
13430 cx.app,
13431 ),
13432 ..EditorStyle::default()
13433 },
13434 ))
13435 .into_any_element()
13436 }
13437 }),
13438 priority: 0,
13439 }],
13440 Some(Autoscroll::fit()),
13441 cx,
13442 )[0];
13443 this.pending_rename = Some(RenameState {
13444 range,
13445 old_name,
13446 editor: rename_editor,
13447 block_id,
13448 });
13449 })?;
13450 }
13451
13452 Ok(())
13453 }))
13454 }
13455
13456 pub fn confirm_rename(
13457 &mut self,
13458 _: &ConfirmRename,
13459 window: &mut Window,
13460 cx: &mut Context<Self>,
13461 ) -> Option<Task<Result<()>>> {
13462 let rename = self.take_rename(false, window, cx)?;
13463 let workspace = self.workspace()?.downgrade();
13464 let (buffer, start) = self
13465 .buffer
13466 .read(cx)
13467 .text_anchor_for_position(rename.range.start, cx)?;
13468 let (end_buffer, _) = self
13469 .buffer
13470 .read(cx)
13471 .text_anchor_for_position(rename.range.end, cx)?;
13472 if buffer != end_buffer {
13473 return None;
13474 }
13475
13476 let old_name = rename.old_name;
13477 let new_name = rename.editor.read(cx).text(cx);
13478
13479 let rename = self.semantics_provider.as_ref()?.perform_rename(
13480 &buffer,
13481 start,
13482 new_name.clone(),
13483 cx,
13484 )?;
13485
13486 Some(cx.spawn_in(window, async move |editor, cx| {
13487 let project_transaction = rename.await?;
13488 Self::open_project_transaction(
13489 &editor,
13490 workspace,
13491 project_transaction,
13492 format!("Rename: {} → {}", old_name, new_name),
13493 cx,
13494 )
13495 .await?;
13496
13497 editor.update(cx, |editor, cx| {
13498 editor.refresh_document_highlights(cx);
13499 })?;
13500 Ok(())
13501 }))
13502 }
13503
13504 fn take_rename(
13505 &mut self,
13506 moving_cursor: bool,
13507 window: &mut Window,
13508 cx: &mut Context<Self>,
13509 ) -> Option<RenameState> {
13510 let rename = self.pending_rename.take()?;
13511 if rename.editor.focus_handle(cx).is_focused(window) {
13512 window.focus(&self.focus_handle);
13513 }
13514
13515 self.remove_blocks(
13516 [rename.block_id].into_iter().collect(),
13517 Some(Autoscroll::fit()),
13518 cx,
13519 );
13520 self.clear_highlights::<Rename>(cx);
13521 self.show_local_selections = true;
13522
13523 if moving_cursor {
13524 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
13525 editor.selections.newest::<usize>(cx).head()
13526 });
13527
13528 // Update the selection to match the position of the selection inside
13529 // the rename editor.
13530 let snapshot = self.buffer.read(cx).read(cx);
13531 let rename_range = rename.range.to_offset(&snapshot);
13532 let cursor_in_editor = snapshot
13533 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
13534 .min(rename_range.end);
13535 drop(snapshot);
13536
13537 self.change_selections(None, window, cx, |s| {
13538 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
13539 });
13540 } else {
13541 self.refresh_document_highlights(cx);
13542 }
13543
13544 Some(rename)
13545 }
13546
13547 pub fn pending_rename(&self) -> Option<&RenameState> {
13548 self.pending_rename.as_ref()
13549 }
13550
13551 fn format(
13552 &mut self,
13553 _: &Format,
13554 window: &mut Window,
13555 cx: &mut Context<Self>,
13556 ) -> Option<Task<Result<()>>> {
13557 let project = match &self.project {
13558 Some(project) => project.clone(),
13559 None => return None,
13560 };
13561
13562 Some(self.perform_format(
13563 project,
13564 FormatTrigger::Manual,
13565 FormatTarget::Buffers,
13566 window,
13567 cx,
13568 ))
13569 }
13570
13571 fn format_selections(
13572 &mut self,
13573 _: &FormatSelections,
13574 window: &mut Window,
13575 cx: &mut Context<Self>,
13576 ) -> Option<Task<Result<()>>> {
13577 let project = match &self.project {
13578 Some(project) => project.clone(),
13579 None => return None,
13580 };
13581
13582 let ranges = self
13583 .selections
13584 .all_adjusted(cx)
13585 .into_iter()
13586 .map(|selection| selection.range())
13587 .collect_vec();
13588
13589 Some(self.perform_format(
13590 project,
13591 FormatTrigger::Manual,
13592 FormatTarget::Ranges(ranges),
13593 window,
13594 cx,
13595 ))
13596 }
13597
13598 fn perform_format(
13599 &mut self,
13600 project: Entity<Project>,
13601 trigger: FormatTrigger,
13602 target: FormatTarget,
13603 window: &mut Window,
13604 cx: &mut Context<Self>,
13605 ) -> Task<Result<()>> {
13606 let buffer = self.buffer.clone();
13607 let (buffers, target) = match target {
13608 FormatTarget::Buffers => {
13609 let mut buffers = buffer.read(cx).all_buffers();
13610 if trigger == FormatTrigger::Save {
13611 buffers.retain(|buffer| buffer.read(cx).is_dirty());
13612 }
13613 (buffers, LspFormatTarget::Buffers)
13614 }
13615 FormatTarget::Ranges(selection_ranges) => {
13616 let multi_buffer = buffer.read(cx);
13617 let snapshot = multi_buffer.read(cx);
13618 let mut buffers = HashSet::default();
13619 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
13620 BTreeMap::new();
13621 for selection_range in selection_ranges {
13622 for (buffer, buffer_range, _) in
13623 snapshot.range_to_buffer_ranges(selection_range)
13624 {
13625 let buffer_id = buffer.remote_id();
13626 let start = buffer.anchor_before(buffer_range.start);
13627 let end = buffer.anchor_after(buffer_range.end);
13628 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
13629 buffer_id_to_ranges
13630 .entry(buffer_id)
13631 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
13632 .or_insert_with(|| vec![start..end]);
13633 }
13634 }
13635 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
13636 }
13637 };
13638
13639 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
13640 let format = project.update(cx, |project, cx| {
13641 project.format(buffers, target, true, trigger, cx)
13642 });
13643
13644 cx.spawn_in(window, async move |_, cx| {
13645 let transaction = futures::select_biased! {
13646 transaction = format.log_err().fuse() => transaction,
13647 () = timeout => {
13648 log::warn!("timed out waiting for formatting");
13649 None
13650 }
13651 };
13652
13653 buffer
13654 .update(cx, |buffer, cx| {
13655 if let Some(transaction) = transaction {
13656 if !buffer.is_singleton() {
13657 buffer.push_transaction(&transaction.0, cx);
13658 }
13659 }
13660 cx.notify();
13661 })
13662 .ok();
13663
13664 Ok(())
13665 })
13666 }
13667
13668 fn organize_imports(
13669 &mut self,
13670 _: &OrganizeImports,
13671 window: &mut Window,
13672 cx: &mut Context<Self>,
13673 ) -> Option<Task<Result<()>>> {
13674 let project = match &self.project {
13675 Some(project) => project.clone(),
13676 None => return None,
13677 };
13678 Some(self.perform_code_action_kind(
13679 project,
13680 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
13681 window,
13682 cx,
13683 ))
13684 }
13685
13686 fn perform_code_action_kind(
13687 &mut self,
13688 project: Entity<Project>,
13689 kind: CodeActionKind,
13690 window: &mut Window,
13691 cx: &mut Context<Self>,
13692 ) -> Task<Result<()>> {
13693 let buffer = self.buffer.clone();
13694 let buffers = buffer.read(cx).all_buffers();
13695 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
13696 let apply_action = project.update(cx, |project, cx| {
13697 project.apply_code_action_kind(buffers, kind, true, cx)
13698 });
13699 cx.spawn_in(window, async move |_, cx| {
13700 let transaction = futures::select_biased! {
13701 () = timeout => {
13702 log::warn!("timed out waiting for executing code action");
13703 None
13704 }
13705 transaction = apply_action.log_err().fuse() => transaction,
13706 };
13707 buffer
13708 .update(cx, |buffer, cx| {
13709 // check if we need this
13710 if let Some(transaction) = transaction {
13711 if !buffer.is_singleton() {
13712 buffer.push_transaction(&transaction.0, cx);
13713 }
13714 }
13715 cx.notify();
13716 })
13717 .ok();
13718 Ok(())
13719 })
13720 }
13721
13722 fn restart_language_server(
13723 &mut self,
13724 _: &RestartLanguageServer,
13725 _: &mut Window,
13726 cx: &mut Context<Self>,
13727 ) {
13728 if let Some(project) = self.project.clone() {
13729 self.buffer.update(cx, |multi_buffer, cx| {
13730 project.update(cx, |project, cx| {
13731 project.restart_language_servers_for_buffers(
13732 multi_buffer.all_buffers().into_iter().collect(),
13733 cx,
13734 );
13735 });
13736 })
13737 }
13738 }
13739
13740 fn cancel_language_server_work(
13741 workspace: &mut Workspace,
13742 _: &actions::CancelLanguageServerWork,
13743 _: &mut Window,
13744 cx: &mut Context<Workspace>,
13745 ) {
13746 let project = workspace.project();
13747 let buffers = workspace
13748 .active_item(cx)
13749 .and_then(|item| item.act_as::<Editor>(cx))
13750 .map_or(HashSet::default(), |editor| {
13751 editor.read(cx).buffer.read(cx).all_buffers()
13752 });
13753 project.update(cx, |project, cx| {
13754 project.cancel_language_server_work_for_buffers(buffers, cx);
13755 });
13756 }
13757
13758 fn show_character_palette(
13759 &mut self,
13760 _: &ShowCharacterPalette,
13761 window: &mut Window,
13762 _: &mut Context<Self>,
13763 ) {
13764 window.show_character_palette();
13765 }
13766
13767 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
13768 if let Some(active_diagnostics) = self.active_diagnostics.as_mut() {
13769 let buffer = self.buffer.read(cx).snapshot(cx);
13770 let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer);
13771 let primary_range_end = active_diagnostics.primary_range.end.to_offset(&buffer);
13772 let is_valid = buffer
13773 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
13774 .any(|entry| {
13775 entry.diagnostic.is_primary
13776 && !entry.range.is_empty()
13777 && entry.range.start == primary_range_start
13778 && entry.diagnostic.message == active_diagnostics.primary_message
13779 });
13780
13781 if is_valid != active_diagnostics.is_valid {
13782 active_diagnostics.is_valid = is_valid;
13783 if is_valid {
13784 let mut new_styles = HashMap::default();
13785 for (block_id, diagnostic) in &active_diagnostics.blocks {
13786 new_styles.insert(
13787 *block_id,
13788 diagnostic_block_renderer(diagnostic.clone(), None, true),
13789 );
13790 }
13791 self.display_map.update(cx, |display_map, _cx| {
13792 display_map.replace_blocks(new_styles);
13793 });
13794 } else {
13795 self.dismiss_diagnostics(cx);
13796 }
13797 }
13798 }
13799 }
13800
13801 fn activate_diagnostics(
13802 &mut self,
13803 buffer_id: BufferId,
13804 group_id: usize,
13805 window: &mut Window,
13806 cx: &mut Context<Self>,
13807 ) {
13808 self.dismiss_diagnostics(cx);
13809 let snapshot = self.snapshot(window, cx);
13810 self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
13811 let buffer = self.buffer.read(cx).snapshot(cx);
13812
13813 let mut primary_range = None;
13814 let mut primary_message = None;
13815 let diagnostic_group = buffer
13816 .diagnostic_group(buffer_id, group_id)
13817 .filter_map(|entry| {
13818 let start = entry.range.start;
13819 let end = entry.range.end;
13820 if snapshot.is_line_folded(MultiBufferRow(start.row))
13821 && (start.row == end.row
13822 || snapshot.is_line_folded(MultiBufferRow(end.row)))
13823 {
13824 return None;
13825 }
13826 if entry.diagnostic.is_primary {
13827 primary_range = Some(entry.range.clone());
13828 primary_message = Some(entry.diagnostic.message.clone());
13829 }
13830 Some(entry)
13831 })
13832 .collect::<Vec<_>>();
13833 let primary_range = primary_range?;
13834 let primary_message = primary_message?;
13835
13836 let blocks = display_map
13837 .insert_blocks(
13838 diagnostic_group.iter().map(|entry| {
13839 let diagnostic = entry.diagnostic.clone();
13840 let message_height = diagnostic.message.matches('\n').count() as u32 + 1;
13841 BlockProperties {
13842 style: BlockStyle::Fixed,
13843 placement: BlockPlacement::Below(
13844 buffer.anchor_after(entry.range.start),
13845 ),
13846 height: message_height,
13847 render: diagnostic_block_renderer(diagnostic, None, true),
13848 priority: 0,
13849 }
13850 }),
13851 cx,
13852 )
13853 .into_iter()
13854 .zip(diagnostic_group.into_iter().map(|entry| entry.diagnostic))
13855 .collect();
13856
13857 Some(ActiveDiagnosticGroup {
13858 primary_range: buffer.anchor_before(primary_range.start)
13859 ..buffer.anchor_after(primary_range.end),
13860 primary_message,
13861 group_id,
13862 blocks,
13863 is_valid: true,
13864 })
13865 });
13866 }
13867
13868 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
13869 if let Some(active_diagnostic_group) = self.active_diagnostics.take() {
13870 self.display_map.update(cx, |display_map, cx| {
13871 display_map.remove_blocks(active_diagnostic_group.blocks.into_keys().collect(), cx);
13872 });
13873 cx.notify();
13874 }
13875 }
13876
13877 /// Disable inline diagnostics rendering for this editor.
13878 pub fn disable_inline_diagnostics(&mut self) {
13879 self.inline_diagnostics_enabled = false;
13880 self.inline_diagnostics_update = Task::ready(());
13881 self.inline_diagnostics.clear();
13882 }
13883
13884 pub fn inline_diagnostics_enabled(&self) -> bool {
13885 self.inline_diagnostics_enabled
13886 }
13887
13888 pub fn show_inline_diagnostics(&self) -> bool {
13889 self.show_inline_diagnostics
13890 }
13891
13892 pub fn toggle_inline_diagnostics(
13893 &mut self,
13894 _: &ToggleInlineDiagnostics,
13895 window: &mut Window,
13896 cx: &mut Context<'_, Editor>,
13897 ) {
13898 self.show_inline_diagnostics = !self.show_inline_diagnostics;
13899 self.refresh_inline_diagnostics(false, window, cx);
13900 }
13901
13902 fn refresh_inline_diagnostics(
13903 &mut self,
13904 debounce: bool,
13905 window: &mut Window,
13906 cx: &mut Context<Self>,
13907 ) {
13908 if !self.inline_diagnostics_enabled || !self.show_inline_diagnostics {
13909 self.inline_diagnostics_update = Task::ready(());
13910 self.inline_diagnostics.clear();
13911 return;
13912 }
13913
13914 let debounce_ms = ProjectSettings::get_global(cx)
13915 .diagnostics
13916 .inline
13917 .update_debounce_ms;
13918 let debounce = if debounce && debounce_ms > 0 {
13919 Some(Duration::from_millis(debounce_ms))
13920 } else {
13921 None
13922 };
13923 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
13924 if let Some(debounce) = debounce {
13925 cx.background_executor().timer(debounce).await;
13926 }
13927 let Some(snapshot) = editor
13928 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
13929 .ok()
13930 else {
13931 return;
13932 };
13933
13934 let new_inline_diagnostics = cx
13935 .background_spawn(async move {
13936 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
13937 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
13938 let message = diagnostic_entry
13939 .diagnostic
13940 .message
13941 .split_once('\n')
13942 .map(|(line, _)| line)
13943 .map(SharedString::new)
13944 .unwrap_or_else(|| {
13945 SharedString::from(diagnostic_entry.diagnostic.message)
13946 });
13947 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
13948 let (Ok(i) | Err(i)) = inline_diagnostics
13949 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
13950 inline_diagnostics.insert(
13951 i,
13952 (
13953 start_anchor,
13954 InlineDiagnostic {
13955 message,
13956 group_id: diagnostic_entry.diagnostic.group_id,
13957 start: diagnostic_entry.range.start.to_point(&snapshot),
13958 is_primary: diagnostic_entry.diagnostic.is_primary,
13959 severity: diagnostic_entry.diagnostic.severity,
13960 },
13961 ),
13962 );
13963 }
13964 inline_diagnostics
13965 })
13966 .await;
13967
13968 editor
13969 .update(cx, |editor, cx| {
13970 editor.inline_diagnostics = new_inline_diagnostics;
13971 cx.notify();
13972 })
13973 .ok();
13974 });
13975 }
13976
13977 pub fn set_selections_from_remote(
13978 &mut self,
13979 selections: Vec<Selection<Anchor>>,
13980 pending_selection: Option<Selection<Anchor>>,
13981 window: &mut Window,
13982 cx: &mut Context<Self>,
13983 ) {
13984 let old_cursor_position = self.selections.newest_anchor().head();
13985 self.selections.change_with(cx, |s| {
13986 s.select_anchors(selections);
13987 if let Some(pending_selection) = pending_selection {
13988 s.set_pending(pending_selection, SelectMode::Character);
13989 } else {
13990 s.clear_pending();
13991 }
13992 });
13993 self.selections_did_change(false, &old_cursor_position, true, window, cx);
13994 }
13995
13996 fn push_to_selection_history(&mut self) {
13997 self.selection_history.push(SelectionHistoryEntry {
13998 selections: self.selections.disjoint_anchors(),
13999 select_next_state: self.select_next_state.clone(),
14000 select_prev_state: self.select_prev_state.clone(),
14001 add_selections_state: self.add_selections_state.clone(),
14002 });
14003 }
14004
14005 pub fn transact(
14006 &mut self,
14007 window: &mut Window,
14008 cx: &mut Context<Self>,
14009 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
14010 ) -> Option<TransactionId> {
14011 self.start_transaction_at(Instant::now(), window, cx);
14012 update(self, window, cx);
14013 self.end_transaction_at(Instant::now(), cx)
14014 }
14015
14016 pub fn start_transaction_at(
14017 &mut self,
14018 now: Instant,
14019 window: &mut Window,
14020 cx: &mut Context<Self>,
14021 ) {
14022 self.end_selection(window, cx);
14023 if let Some(tx_id) = self
14024 .buffer
14025 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
14026 {
14027 self.selection_history
14028 .insert_transaction(tx_id, self.selections.disjoint_anchors());
14029 cx.emit(EditorEvent::TransactionBegun {
14030 transaction_id: tx_id,
14031 })
14032 }
14033 }
14034
14035 pub fn end_transaction_at(
14036 &mut self,
14037 now: Instant,
14038 cx: &mut Context<Self>,
14039 ) -> Option<TransactionId> {
14040 if let Some(transaction_id) = self
14041 .buffer
14042 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
14043 {
14044 if let Some((_, end_selections)) =
14045 self.selection_history.transaction_mut(transaction_id)
14046 {
14047 *end_selections = Some(self.selections.disjoint_anchors());
14048 } else {
14049 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
14050 }
14051
14052 cx.emit(EditorEvent::Edited { transaction_id });
14053 Some(transaction_id)
14054 } else {
14055 None
14056 }
14057 }
14058
14059 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
14060 if self.selection_mark_mode {
14061 self.change_selections(None, window, cx, |s| {
14062 s.move_with(|_, sel| {
14063 sel.collapse_to(sel.head(), SelectionGoal::None);
14064 });
14065 })
14066 }
14067 self.selection_mark_mode = true;
14068 cx.notify();
14069 }
14070
14071 pub fn swap_selection_ends(
14072 &mut self,
14073 _: &actions::SwapSelectionEnds,
14074 window: &mut Window,
14075 cx: &mut Context<Self>,
14076 ) {
14077 self.change_selections(None, window, cx, |s| {
14078 s.move_with(|_, sel| {
14079 if sel.start != sel.end {
14080 sel.reversed = !sel.reversed
14081 }
14082 });
14083 });
14084 self.request_autoscroll(Autoscroll::newest(), cx);
14085 cx.notify();
14086 }
14087
14088 pub fn toggle_fold(
14089 &mut self,
14090 _: &actions::ToggleFold,
14091 window: &mut Window,
14092 cx: &mut Context<Self>,
14093 ) {
14094 if self.is_singleton(cx) {
14095 let selection = self.selections.newest::<Point>(cx);
14096
14097 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14098 let range = if selection.is_empty() {
14099 let point = selection.head().to_display_point(&display_map);
14100 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14101 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14102 .to_point(&display_map);
14103 start..end
14104 } else {
14105 selection.range()
14106 };
14107 if display_map.folds_in_range(range).next().is_some() {
14108 self.unfold_lines(&Default::default(), window, cx)
14109 } else {
14110 self.fold(&Default::default(), window, cx)
14111 }
14112 } else {
14113 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14114 let buffer_ids: HashSet<_> = self
14115 .selections
14116 .disjoint_anchor_ranges()
14117 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14118 .collect();
14119
14120 let should_unfold = buffer_ids
14121 .iter()
14122 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
14123
14124 for buffer_id in buffer_ids {
14125 if should_unfold {
14126 self.unfold_buffer(buffer_id, cx);
14127 } else {
14128 self.fold_buffer(buffer_id, cx);
14129 }
14130 }
14131 }
14132 }
14133
14134 pub fn toggle_fold_recursive(
14135 &mut self,
14136 _: &actions::ToggleFoldRecursive,
14137 window: &mut Window,
14138 cx: &mut Context<Self>,
14139 ) {
14140 let selection = self.selections.newest::<Point>(cx);
14141
14142 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14143 let range = if selection.is_empty() {
14144 let point = selection.head().to_display_point(&display_map);
14145 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14146 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14147 .to_point(&display_map);
14148 start..end
14149 } else {
14150 selection.range()
14151 };
14152 if display_map.folds_in_range(range).next().is_some() {
14153 self.unfold_recursive(&Default::default(), window, cx)
14154 } else {
14155 self.fold_recursive(&Default::default(), window, cx)
14156 }
14157 }
14158
14159 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
14160 if self.is_singleton(cx) {
14161 let mut to_fold = Vec::new();
14162 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14163 let selections = self.selections.all_adjusted(cx);
14164
14165 for selection in selections {
14166 let range = selection.range().sorted();
14167 let buffer_start_row = range.start.row;
14168
14169 if range.start.row != range.end.row {
14170 let mut found = false;
14171 let mut row = range.start.row;
14172 while row <= range.end.row {
14173 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
14174 {
14175 found = true;
14176 row = crease.range().end.row + 1;
14177 to_fold.push(crease);
14178 } else {
14179 row += 1
14180 }
14181 }
14182 if found {
14183 continue;
14184 }
14185 }
14186
14187 for row in (0..=range.start.row).rev() {
14188 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14189 if crease.range().end.row >= buffer_start_row {
14190 to_fold.push(crease);
14191 if row <= range.start.row {
14192 break;
14193 }
14194 }
14195 }
14196 }
14197 }
14198
14199 self.fold_creases(to_fold, true, window, cx);
14200 } else {
14201 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14202 let buffer_ids = self
14203 .selections
14204 .disjoint_anchor_ranges()
14205 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14206 .collect::<HashSet<_>>();
14207 for buffer_id in buffer_ids {
14208 self.fold_buffer(buffer_id, cx);
14209 }
14210 }
14211 }
14212
14213 fn fold_at_level(
14214 &mut self,
14215 fold_at: &FoldAtLevel,
14216 window: &mut Window,
14217 cx: &mut Context<Self>,
14218 ) {
14219 if !self.buffer.read(cx).is_singleton() {
14220 return;
14221 }
14222
14223 let fold_at_level = fold_at.0;
14224 let snapshot = self.buffer.read(cx).snapshot(cx);
14225 let mut to_fold = Vec::new();
14226 let mut stack = vec![(0, snapshot.max_row().0, 1)];
14227
14228 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
14229 while start_row < end_row {
14230 match self
14231 .snapshot(window, cx)
14232 .crease_for_buffer_row(MultiBufferRow(start_row))
14233 {
14234 Some(crease) => {
14235 let nested_start_row = crease.range().start.row + 1;
14236 let nested_end_row = crease.range().end.row;
14237
14238 if current_level < fold_at_level {
14239 stack.push((nested_start_row, nested_end_row, current_level + 1));
14240 } else if current_level == fold_at_level {
14241 to_fold.push(crease);
14242 }
14243
14244 start_row = nested_end_row + 1;
14245 }
14246 None => start_row += 1,
14247 }
14248 }
14249 }
14250
14251 self.fold_creases(to_fold, true, window, cx);
14252 }
14253
14254 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
14255 if self.buffer.read(cx).is_singleton() {
14256 let mut fold_ranges = Vec::new();
14257 let snapshot = self.buffer.read(cx).snapshot(cx);
14258
14259 for row in 0..snapshot.max_row().0 {
14260 if let Some(foldable_range) = self
14261 .snapshot(window, cx)
14262 .crease_for_buffer_row(MultiBufferRow(row))
14263 {
14264 fold_ranges.push(foldable_range);
14265 }
14266 }
14267
14268 self.fold_creases(fold_ranges, true, window, cx);
14269 } else {
14270 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
14271 editor
14272 .update_in(cx, |editor, _, cx| {
14273 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
14274 editor.fold_buffer(buffer_id, cx);
14275 }
14276 })
14277 .ok();
14278 });
14279 }
14280 }
14281
14282 pub fn fold_function_bodies(
14283 &mut self,
14284 _: &actions::FoldFunctionBodies,
14285 window: &mut Window,
14286 cx: &mut Context<Self>,
14287 ) {
14288 let snapshot = self.buffer.read(cx).snapshot(cx);
14289
14290 let ranges = snapshot
14291 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
14292 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
14293 .collect::<Vec<_>>();
14294
14295 let creases = ranges
14296 .into_iter()
14297 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
14298 .collect();
14299
14300 self.fold_creases(creases, true, window, cx);
14301 }
14302
14303 pub fn fold_recursive(
14304 &mut self,
14305 _: &actions::FoldRecursive,
14306 window: &mut Window,
14307 cx: &mut Context<Self>,
14308 ) {
14309 let mut to_fold = Vec::new();
14310 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14311 let selections = self.selections.all_adjusted(cx);
14312
14313 for selection in selections {
14314 let range = selection.range().sorted();
14315 let buffer_start_row = range.start.row;
14316
14317 if range.start.row != range.end.row {
14318 let mut found = false;
14319 for row in range.start.row..=range.end.row {
14320 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14321 found = true;
14322 to_fold.push(crease);
14323 }
14324 }
14325 if found {
14326 continue;
14327 }
14328 }
14329
14330 for row in (0..=range.start.row).rev() {
14331 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14332 if crease.range().end.row >= buffer_start_row {
14333 to_fold.push(crease);
14334 } else {
14335 break;
14336 }
14337 }
14338 }
14339 }
14340
14341 self.fold_creases(to_fold, true, window, cx);
14342 }
14343
14344 pub fn fold_at(&mut self, fold_at: &FoldAt, window: &mut Window, cx: &mut Context<Self>) {
14345 let buffer_row = fold_at.buffer_row;
14346 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14347
14348 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
14349 let autoscroll = self
14350 .selections
14351 .all::<Point>(cx)
14352 .iter()
14353 .any(|selection| crease.range().overlaps(&selection.range()));
14354
14355 self.fold_creases(vec![crease], autoscroll, window, cx);
14356 }
14357 }
14358
14359 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
14360 if self.is_singleton(cx) {
14361 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14362 let buffer = &display_map.buffer_snapshot;
14363 let selections = self.selections.all::<Point>(cx);
14364 let ranges = selections
14365 .iter()
14366 .map(|s| {
14367 let range = s.display_range(&display_map).sorted();
14368 let mut start = range.start.to_point(&display_map);
14369 let mut end = range.end.to_point(&display_map);
14370 start.column = 0;
14371 end.column = buffer.line_len(MultiBufferRow(end.row));
14372 start..end
14373 })
14374 .collect::<Vec<_>>();
14375
14376 self.unfold_ranges(&ranges, true, true, cx);
14377 } else {
14378 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14379 let buffer_ids = self
14380 .selections
14381 .disjoint_anchor_ranges()
14382 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14383 .collect::<HashSet<_>>();
14384 for buffer_id in buffer_ids {
14385 self.unfold_buffer(buffer_id, cx);
14386 }
14387 }
14388 }
14389
14390 pub fn unfold_recursive(
14391 &mut self,
14392 _: &UnfoldRecursive,
14393 _window: &mut Window,
14394 cx: &mut Context<Self>,
14395 ) {
14396 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14397 let selections = self.selections.all::<Point>(cx);
14398 let ranges = selections
14399 .iter()
14400 .map(|s| {
14401 let mut range = s.display_range(&display_map).sorted();
14402 *range.start.column_mut() = 0;
14403 *range.end.column_mut() = display_map.line_len(range.end.row());
14404 let start = range.start.to_point(&display_map);
14405 let end = range.end.to_point(&display_map);
14406 start..end
14407 })
14408 .collect::<Vec<_>>();
14409
14410 self.unfold_ranges(&ranges, true, true, cx);
14411 }
14412
14413 pub fn unfold_at(
14414 &mut self,
14415 unfold_at: &UnfoldAt,
14416 _window: &mut Window,
14417 cx: &mut Context<Self>,
14418 ) {
14419 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14420
14421 let intersection_range = Point::new(unfold_at.buffer_row.0, 0)
14422 ..Point::new(
14423 unfold_at.buffer_row.0,
14424 display_map.buffer_snapshot.line_len(unfold_at.buffer_row),
14425 );
14426
14427 let autoscroll = self
14428 .selections
14429 .all::<Point>(cx)
14430 .iter()
14431 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
14432
14433 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
14434 }
14435
14436 pub fn unfold_all(
14437 &mut self,
14438 _: &actions::UnfoldAll,
14439 _window: &mut Window,
14440 cx: &mut Context<Self>,
14441 ) {
14442 if self.buffer.read(cx).is_singleton() {
14443 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14444 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
14445 } else {
14446 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
14447 editor
14448 .update(cx, |editor, cx| {
14449 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
14450 editor.unfold_buffer(buffer_id, cx);
14451 }
14452 })
14453 .ok();
14454 });
14455 }
14456 }
14457
14458 pub fn fold_selected_ranges(
14459 &mut self,
14460 _: &FoldSelectedRanges,
14461 window: &mut Window,
14462 cx: &mut Context<Self>,
14463 ) {
14464 let selections = self.selections.all::<Point>(cx);
14465 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14466 let line_mode = self.selections.line_mode;
14467 let ranges = selections
14468 .into_iter()
14469 .map(|s| {
14470 if line_mode {
14471 let start = Point::new(s.start.row, 0);
14472 let end = Point::new(
14473 s.end.row,
14474 display_map
14475 .buffer_snapshot
14476 .line_len(MultiBufferRow(s.end.row)),
14477 );
14478 Crease::simple(start..end, display_map.fold_placeholder.clone())
14479 } else {
14480 Crease::simple(s.start..s.end, display_map.fold_placeholder.clone())
14481 }
14482 })
14483 .collect::<Vec<_>>();
14484 self.fold_creases(ranges, true, window, cx);
14485 }
14486
14487 pub fn fold_ranges<T: ToOffset + Clone>(
14488 &mut self,
14489 ranges: Vec<Range<T>>,
14490 auto_scroll: bool,
14491 window: &mut Window,
14492 cx: &mut Context<Self>,
14493 ) {
14494 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14495 let ranges = ranges
14496 .into_iter()
14497 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
14498 .collect::<Vec<_>>();
14499 self.fold_creases(ranges, auto_scroll, window, cx);
14500 }
14501
14502 pub fn fold_creases<T: ToOffset + Clone>(
14503 &mut self,
14504 creases: Vec<Crease<T>>,
14505 auto_scroll: bool,
14506 window: &mut Window,
14507 cx: &mut Context<Self>,
14508 ) {
14509 if creases.is_empty() {
14510 return;
14511 }
14512
14513 let mut buffers_affected = HashSet::default();
14514 let multi_buffer = self.buffer().read(cx);
14515 for crease in &creases {
14516 if let Some((_, buffer, _)) =
14517 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
14518 {
14519 buffers_affected.insert(buffer.read(cx).remote_id());
14520 };
14521 }
14522
14523 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
14524
14525 if auto_scroll {
14526 self.request_autoscroll(Autoscroll::fit(), cx);
14527 }
14528
14529 cx.notify();
14530
14531 if let Some(active_diagnostics) = self.active_diagnostics.take() {
14532 // Clear diagnostics block when folding a range that contains it.
14533 let snapshot = self.snapshot(window, cx);
14534 if snapshot.intersects_fold(active_diagnostics.primary_range.start) {
14535 drop(snapshot);
14536 self.active_diagnostics = Some(active_diagnostics);
14537 self.dismiss_diagnostics(cx);
14538 } else {
14539 self.active_diagnostics = Some(active_diagnostics);
14540 }
14541 }
14542
14543 self.scrollbar_marker_state.dirty = true;
14544 self.folds_did_change(cx);
14545 }
14546
14547 /// Removes any folds whose ranges intersect any of the given ranges.
14548 pub fn unfold_ranges<T: ToOffset + Clone>(
14549 &mut self,
14550 ranges: &[Range<T>],
14551 inclusive: bool,
14552 auto_scroll: bool,
14553 cx: &mut Context<Self>,
14554 ) {
14555 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
14556 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
14557 });
14558 self.folds_did_change(cx);
14559 }
14560
14561 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
14562 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
14563 return;
14564 }
14565 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
14566 self.display_map.update(cx, |display_map, cx| {
14567 display_map.fold_buffers([buffer_id], cx)
14568 });
14569 cx.emit(EditorEvent::BufferFoldToggled {
14570 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
14571 folded: true,
14572 });
14573 cx.notify();
14574 }
14575
14576 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
14577 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
14578 return;
14579 }
14580 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
14581 self.display_map.update(cx, |display_map, cx| {
14582 display_map.unfold_buffers([buffer_id], cx);
14583 });
14584 cx.emit(EditorEvent::BufferFoldToggled {
14585 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
14586 folded: false,
14587 });
14588 cx.notify();
14589 }
14590
14591 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
14592 self.display_map.read(cx).is_buffer_folded(buffer)
14593 }
14594
14595 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
14596 self.display_map.read(cx).folded_buffers()
14597 }
14598
14599 /// Removes any folds with the given ranges.
14600 pub fn remove_folds_with_type<T: ToOffset + Clone>(
14601 &mut self,
14602 ranges: &[Range<T>],
14603 type_id: TypeId,
14604 auto_scroll: bool,
14605 cx: &mut Context<Self>,
14606 ) {
14607 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
14608 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
14609 });
14610 self.folds_did_change(cx);
14611 }
14612
14613 fn remove_folds_with<T: ToOffset + Clone>(
14614 &mut self,
14615 ranges: &[Range<T>],
14616 auto_scroll: bool,
14617 cx: &mut Context<Self>,
14618 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
14619 ) {
14620 if ranges.is_empty() {
14621 return;
14622 }
14623
14624 let mut buffers_affected = HashSet::default();
14625 let multi_buffer = self.buffer().read(cx);
14626 for range in ranges {
14627 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
14628 buffers_affected.insert(buffer.read(cx).remote_id());
14629 };
14630 }
14631
14632 self.display_map.update(cx, update);
14633
14634 if auto_scroll {
14635 self.request_autoscroll(Autoscroll::fit(), cx);
14636 }
14637
14638 cx.notify();
14639 self.scrollbar_marker_state.dirty = true;
14640 self.active_indent_guides_state.dirty = true;
14641 }
14642
14643 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
14644 self.display_map.read(cx).fold_placeholder.clone()
14645 }
14646
14647 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
14648 self.buffer.update(cx, |buffer, cx| {
14649 buffer.set_all_diff_hunks_expanded(cx);
14650 });
14651 }
14652
14653 pub fn expand_all_diff_hunks(
14654 &mut self,
14655 _: &ExpandAllDiffHunks,
14656 _window: &mut Window,
14657 cx: &mut Context<Self>,
14658 ) {
14659 self.buffer.update(cx, |buffer, cx| {
14660 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
14661 });
14662 }
14663
14664 pub fn toggle_selected_diff_hunks(
14665 &mut self,
14666 _: &ToggleSelectedDiffHunks,
14667 _window: &mut Window,
14668 cx: &mut Context<Self>,
14669 ) {
14670 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
14671 self.toggle_diff_hunks_in_ranges(ranges, cx);
14672 }
14673
14674 pub fn diff_hunks_in_ranges<'a>(
14675 &'a self,
14676 ranges: &'a [Range<Anchor>],
14677 buffer: &'a MultiBufferSnapshot,
14678 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
14679 ranges.iter().flat_map(move |range| {
14680 let end_excerpt_id = range.end.excerpt_id;
14681 let range = range.to_point(buffer);
14682 let mut peek_end = range.end;
14683 if range.end.row < buffer.max_row().0 {
14684 peek_end = Point::new(range.end.row + 1, 0);
14685 }
14686 buffer
14687 .diff_hunks_in_range(range.start..peek_end)
14688 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
14689 })
14690 }
14691
14692 pub fn has_stageable_diff_hunks_in_ranges(
14693 &self,
14694 ranges: &[Range<Anchor>],
14695 snapshot: &MultiBufferSnapshot,
14696 ) -> bool {
14697 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
14698 hunks.any(|hunk| hunk.status().has_secondary_hunk())
14699 }
14700
14701 pub fn toggle_staged_selected_diff_hunks(
14702 &mut self,
14703 _: &::git::ToggleStaged,
14704 _: &mut Window,
14705 cx: &mut Context<Self>,
14706 ) {
14707 let snapshot = self.buffer.read(cx).snapshot(cx);
14708 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
14709 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
14710 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
14711 }
14712
14713 pub fn stage_and_next(
14714 &mut self,
14715 _: &::git::StageAndNext,
14716 window: &mut Window,
14717 cx: &mut Context<Self>,
14718 ) {
14719 self.do_stage_or_unstage_and_next(true, window, cx);
14720 }
14721
14722 pub fn unstage_and_next(
14723 &mut self,
14724 _: &::git::UnstageAndNext,
14725 window: &mut Window,
14726 cx: &mut Context<Self>,
14727 ) {
14728 self.do_stage_or_unstage_and_next(false, window, cx);
14729 }
14730
14731 pub fn stage_or_unstage_diff_hunks(
14732 &mut self,
14733 stage: bool,
14734 ranges: Vec<Range<Anchor>>,
14735 cx: &mut Context<Self>,
14736 ) {
14737 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
14738 cx.spawn(async move |this, cx| {
14739 task.await?;
14740 this.update(cx, |this, cx| {
14741 let snapshot = this.buffer.read(cx).snapshot(cx);
14742 let chunk_by = this
14743 .diff_hunks_in_ranges(&ranges, &snapshot)
14744 .chunk_by(|hunk| hunk.buffer_id);
14745 for (buffer_id, hunks) in &chunk_by {
14746 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
14747 }
14748 })
14749 })
14750 .detach_and_log_err(cx);
14751 }
14752
14753 fn save_buffers_for_ranges_if_needed(
14754 &mut self,
14755 ranges: &[Range<Anchor>],
14756 cx: &mut Context<'_, Editor>,
14757 ) -> Task<Result<()>> {
14758 let multibuffer = self.buffer.read(cx);
14759 let snapshot = multibuffer.read(cx);
14760 let buffer_ids: HashSet<_> = ranges
14761 .iter()
14762 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
14763 .collect();
14764 drop(snapshot);
14765
14766 let mut buffers = HashSet::default();
14767 for buffer_id in buffer_ids {
14768 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
14769 let buffer = buffer_entity.read(cx);
14770 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
14771 {
14772 buffers.insert(buffer_entity);
14773 }
14774 }
14775 }
14776
14777 if let Some(project) = &self.project {
14778 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
14779 } else {
14780 Task::ready(Ok(()))
14781 }
14782 }
14783
14784 fn do_stage_or_unstage_and_next(
14785 &mut self,
14786 stage: bool,
14787 window: &mut Window,
14788 cx: &mut Context<Self>,
14789 ) {
14790 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
14791
14792 if ranges.iter().any(|range| range.start != range.end) {
14793 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
14794 return;
14795 }
14796
14797 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
14798 let snapshot = self.snapshot(window, cx);
14799 let position = self.selections.newest::<Point>(cx).head();
14800 let mut row = snapshot
14801 .buffer_snapshot
14802 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14803 .find(|hunk| hunk.row_range.start.0 > position.row)
14804 .map(|hunk| hunk.row_range.start);
14805
14806 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
14807 // Outside of the project diff editor, wrap around to the beginning.
14808 if !all_diff_hunks_expanded {
14809 row = row.or_else(|| {
14810 snapshot
14811 .buffer_snapshot
14812 .diff_hunks_in_range(Point::zero()..position)
14813 .find(|hunk| hunk.row_range.end.0 < position.row)
14814 .map(|hunk| hunk.row_range.start)
14815 });
14816 }
14817
14818 if let Some(row) = row {
14819 let destination = Point::new(row.0, 0);
14820 let autoscroll = Autoscroll::center();
14821
14822 self.unfold_ranges(&[destination..destination], false, false, cx);
14823 self.change_selections(Some(autoscroll), window, cx, |s| {
14824 s.select_ranges([destination..destination]);
14825 });
14826 }
14827 }
14828
14829 fn do_stage_or_unstage(
14830 &self,
14831 stage: bool,
14832 buffer_id: BufferId,
14833 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
14834 cx: &mut App,
14835 ) -> Option<()> {
14836 let project = self.project.as_ref()?;
14837 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
14838 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
14839 let buffer_snapshot = buffer.read(cx).snapshot();
14840 let file_exists = buffer_snapshot
14841 .file()
14842 .is_some_and(|file| file.disk_state().exists());
14843 diff.update(cx, |diff, cx| {
14844 diff.stage_or_unstage_hunks(
14845 stage,
14846 &hunks
14847 .map(|hunk| buffer_diff::DiffHunk {
14848 buffer_range: hunk.buffer_range,
14849 diff_base_byte_range: hunk.diff_base_byte_range,
14850 secondary_status: hunk.secondary_status,
14851 range: Point::zero()..Point::zero(), // unused
14852 })
14853 .collect::<Vec<_>>(),
14854 &buffer_snapshot,
14855 file_exists,
14856 cx,
14857 )
14858 });
14859 None
14860 }
14861
14862 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
14863 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
14864 self.buffer
14865 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
14866 }
14867
14868 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
14869 self.buffer.update(cx, |buffer, cx| {
14870 let ranges = vec![Anchor::min()..Anchor::max()];
14871 if !buffer.all_diff_hunks_expanded()
14872 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
14873 {
14874 buffer.collapse_diff_hunks(ranges, cx);
14875 true
14876 } else {
14877 false
14878 }
14879 })
14880 }
14881
14882 fn toggle_diff_hunks_in_ranges(
14883 &mut self,
14884 ranges: Vec<Range<Anchor>>,
14885 cx: &mut Context<'_, Editor>,
14886 ) {
14887 self.buffer.update(cx, |buffer, cx| {
14888 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
14889 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
14890 })
14891 }
14892
14893 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
14894 self.buffer.update(cx, |buffer, cx| {
14895 let snapshot = buffer.snapshot(cx);
14896 let excerpt_id = range.end.excerpt_id;
14897 let point_range = range.to_point(&snapshot);
14898 let expand = !buffer.single_hunk_is_expanded(range, cx);
14899 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
14900 })
14901 }
14902
14903 pub(crate) fn apply_all_diff_hunks(
14904 &mut self,
14905 _: &ApplyAllDiffHunks,
14906 window: &mut Window,
14907 cx: &mut Context<Self>,
14908 ) {
14909 let buffers = self.buffer.read(cx).all_buffers();
14910 for branch_buffer in buffers {
14911 branch_buffer.update(cx, |branch_buffer, cx| {
14912 branch_buffer.merge_into_base(Vec::new(), cx);
14913 });
14914 }
14915
14916 if let Some(project) = self.project.clone() {
14917 self.save(true, project, window, cx).detach_and_log_err(cx);
14918 }
14919 }
14920
14921 pub(crate) fn apply_selected_diff_hunks(
14922 &mut self,
14923 _: &ApplyDiffHunk,
14924 window: &mut Window,
14925 cx: &mut Context<Self>,
14926 ) {
14927 let snapshot = self.snapshot(window, cx);
14928 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
14929 let mut ranges_by_buffer = HashMap::default();
14930 self.transact(window, cx, |editor, _window, cx| {
14931 for hunk in hunks {
14932 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
14933 ranges_by_buffer
14934 .entry(buffer.clone())
14935 .or_insert_with(Vec::new)
14936 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
14937 }
14938 }
14939
14940 for (buffer, ranges) in ranges_by_buffer {
14941 buffer.update(cx, |buffer, cx| {
14942 buffer.merge_into_base(ranges, cx);
14943 });
14944 }
14945 });
14946
14947 if let Some(project) = self.project.clone() {
14948 self.save(true, project, window, cx).detach_and_log_err(cx);
14949 }
14950 }
14951
14952 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
14953 if hovered != self.gutter_hovered {
14954 self.gutter_hovered = hovered;
14955 cx.notify();
14956 }
14957 }
14958
14959 pub fn insert_blocks(
14960 &mut self,
14961 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
14962 autoscroll: Option<Autoscroll>,
14963 cx: &mut Context<Self>,
14964 ) -> Vec<CustomBlockId> {
14965 let blocks = self
14966 .display_map
14967 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
14968 if let Some(autoscroll) = autoscroll {
14969 self.request_autoscroll(autoscroll, cx);
14970 }
14971 cx.notify();
14972 blocks
14973 }
14974
14975 pub fn resize_blocks(
14976 &mut self,
14977 heights: HashMap<CustomBlockId, u32>,
14978 autoscroll: Option<Autoscroll>,
14979 cx: &mut Context<Self>,
14980 ) {
14981 self.display_map
14982 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
14983 if let Some(autoscroll) = autoscroll {
14984 self.request_autoscroll(autoscroll, cx);
14985 }
14986 cx.notify();
14987 }
14988
14989 pub fn replace_blocks(
14990 &mut self,
14991 renderers: HashMap<CustomBlockId, RenderBlock>,
14992 autoscroll: Option<Autoscroll>,
14993 cx: &mut Context<Self>,
14994 ) {
14995 self.display_map
14996 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
14997 if let Some(autoscroll) = autoscroll {
14998 self.request_autoscroll(autoscroll, cx);
14999 }
15000 cx.notify();
15001 }
15002
15003 pub fn remove_blocks(
15004 &mut self,
15005 block_ids: HashSet<CustomBlockId>,
15006 autoscroll: Option<Autoscroll>,
15007 cx: &mut Context<Self>,
15008 ) {
15009 self.display_map.update(cx, |display_map, cx| {
15010 display_map.remove_blocks(block_ids, cx)
15011 });
15012 if let Some(autoscroll) = autoscroll {
15013 self.request_autoscroll(autoscroll, cx);
15014 }
15015 cx.notify();
15016 }
15017
15018 pub fn row_for_block(
15019 &self,
15020 block_id: CustomBlockId,
15021 cx: &mut Context<Self>,
15022 ) -> Option<DisplayRow> {
15023 self.display_map
15024 .update(cx, |map, cx| map.row_for_block(block_id, cx))
15025 }
15026
15027 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
15028 self.focused_block = Some(focused_block);
15029 }
15030
15031 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
15032 self.focused_block.take()
15033 }
15034
15035 pub fn insert_creases(
15036 &mut self,
15037 creases: impl IntoIterator<Item = Crease<Anchor>>,
15038 cx: &mut Context<Self>,
15039 ) -> Vec<CreaseId> {
15040 self.display_map
15041 .update(cx, |map, cx| map.insert_creases(creases, cx))
15042 }
15043
15044 pub fn remove_creases(
15045 &mut self,
15046 ids: impl IntoIterator<Item = CreaseId>,
15047 cx: &mut Context<Self>,
15048 ) {
15049 self.display_map
15050 .update(cx, |map, cx| map.remove_creases(ids, cx));
15051 }
15052
15053 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
15054 self.display_map
15055 .update(cx, |map, cx| map.snapshot(cx))
15056 .longest_row()
15057 }
15058
15059 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
15060 self.display_map
15061 .update(cx, |map, cx| map.snapshot(cx))
15062 .max_point()
15063 }
15064
15065 pub fn text(&self, cx: &App) -> String {
15066 self.buffer.read(cx).read(cx).text()
15067 }
15068
15069 pub fn is_empty(&self, cx: &App) -> bool {
15070 self.buffer.read(cx).read(cx).is_empty()
15071 }
15072
15073 pub fn text_option(&self, cx: &App) -> Option<String> {
15074 let text = self.text(cx);
15075 let text = text.trim();
15076
15077 if text.is_empty() {
15078 return None;
15079 }
15080
15081 Some(text.to_string())
15082 }
15083
15084 pub fn set_text(
15085 &mut self,
15086 text: impl Into<Arc<str>>,
15087 window: &mut Window,
15088 cx: &mut Context<Self>,
15089 ) {
15090 self.transact(window, cx, |this, _, cx| {
15091 this.buffer
15092 .read(cx)
15093 .as_singleton()
15094 .expect("you can only call set_text on editors for singleton buffers")
15095 .update(cx, |buffer, cx| buffer.set_text(text, cx));
15096 });
15097 }
15098
15099 pub fn display_text(&self, cx: &mut App) -> String {
15100 self.display_map
15101 .update(cx, |map, cx| map.snapshot(cx))
15102 .text()
15103 }
15104
15105 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
15106 let mut wrap_guides = smallvec::smallvec![];
15107
15108 if self.show_wrap_guides == Some(false) {
15109 return wrap_guides;
15110 }
15111
15112 let settings = self.buffer.read(cx).language_settings(cx);
15113 if settings.show_wrap_guides {
15114 match self.soft_wrap_mode(cx) {
15115 SoftWrap::Column(soft_wrap) => {
15116 wrap_guides.push((soft_wrap as usize, true));
15117 }
15118 SoftWrap::Bounded(soft_wrap) => {
15119 wrap_guides.push((soft_wrap as usize, true));
15120 }
15121 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
15122 }
15123 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
15124 }
15125
15126 wrap_guides
15127 }
15128
15129 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
15130 let settings = self.buffer.read(cx).language_settings(cx);
15131 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
15132 match mode {
15133 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
15134 SoftWrap::None
15135 }
15136 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
15137 language_settings::SoftWrap::PreferredLineLength => {
15138 SoftWrap::Column(settings.preferred_line_length)
15139 }
15140 language_settings::SoftWrap::Bounded => {
15141 SoftWrap::Bounded(settings.preferred_line_length)
15142 }
15143 }
15144 }
15145
15146 pub fn set_soft_wrap_mode(
15147 &mut self,
15148 mode: language_settings::SoftWrap,
15149
15150 cx: &mut Context<Self>,
15151 ) {
15152 self.soft_wrap_mode_override = Some(mode);
15153 cx.notify();
15154 }
15155
15156 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
15157 self.hard_wrap = hard_wrap;
15158 cx.notify();
15159 }
15160
15161 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
15162 self.text_style_refinement = Some(style);
15163 }
15164
15165 /// called by the Element so we know what style we were most recently rendered with.
15166 pub(crate) fn set_style(
15167 &mut self,
15168 style: EditorStyle,
15169 window: &mut Window,
15170 cx: &mut Context<Self>,
15171 ) {
15172 let rem_size = window.rem_size();
15173 self.display_map.update(cx, |map, cx| {
15174 map.set_font(
15175 style.text.font(),
15176 style.text.font_size.to_pixels(rem_size),
15177 cx,
15178 )
15179 });
15180 self.style = Some(style);
15181 }
15182
15183 pub fn style(&self) -> Option<&EditorStyle> {
15184 self.style.as_ref()
15185 }
15186
15187 // Called by the element. This method is not designed to be called outside of the editor
15188 // element's layout code because it does not notify when rewrapping is computed synchronously.
15189 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
15190 self.display_map
15191 .update(cx, |map, cx| map.set_wrap_width(width, cx))
15192 }
15193
15194 pub fn set_soft_wrap(&mut self) {
15195 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
15196 }
15197
15198 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
15199 if self.soft_wrap_mode_override.is_some() {
15200 self.soft_wrap_mode_override.take();
15201 } else {
15202 let soft_wrap = match self.soft_wrap_mode(cx) {
15203 SoftWrap::GitDiff => return,
15204 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
15205 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
15206 language_settings::SoftWrap::None
15207 }
15208 };
15209 self.soft_wrap_mode_override = Some(soft_wrap);
15210 }
15211 cx.notify();
15212 }
15213
15214 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
15215 let Some(workspace) = self.workspace() else {
15216 return;
15217 };
15218 let fs = workspace.read(cx).app_state().fs.clone();
15219 let current_show = TabBarSettings::get_global(cx).show;
15220 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
15221 setting.show = Some(!current_show);
15222 });
15223 }
15224
15225 pub fn toggle_indent_guides(
15226 &mut self,
15227 _: &ToggleIndentGuides,
15228 _: &mut Window,
15229 cx: &mut Context<Self>,
15230 ) {
15231 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
15232 self.buffer
15233 .read(cx)
15234 .language_settings(cx)
15235 .indent_guides
15236 .enabled
15237 });
15238 self.show_indent_guides = Some(!currently_enabled);
15239 cx.notify();
15240 }
15241
15242 fn should_show_indent_guides(&self) -> Option<bool> {
15243 self.show_indent_guides
15244 }
15245
15246 pub fn toggle_line_numbers(
15247 &mut self,
15248 _: &ToggleLineNumbers,
15249 _: &mut Window,
15250 cx: &mut Context<Self>,
15251 ) {
15252 let mut editor_settings = EditorSettings::get_global(cx).clone();
15253 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
15254 EditorSettings::override_global(editor_settings, cx);
15255 }
15256
15257 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
15258 if let Some(show_line_numbers) = self.show_line_numbers {
15259 return show_line_numbers;
15260 }
15261 EditorSettings::get_global(cx).gutter.line_numbers
15262 }
15263
15264 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
15265 self.use_relative_line_numbers
15266 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
15267 }
15268
15269 pub fn toggle_relative_line_numbers(
15270 &mut self,
15271 _: &ToggleRelativeLineNumbers,
15272 _: &mut Window,
15273 cx: &mut Context<Self>,
15274 ) {
15275 let is_relative = self.should_use_relative_line_numbers(cx);
15276 self.set_relative_line_number(Some(!is_relative), cx)
15277 }
15278
15279 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
15280 self.use_relative_line_numbers = is_relative;
15281 cx.notify();
15282 }
15283
15284 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
15285 self.show_gutter = show_gutter;
15286 cx.notify();
15287 }
15288
15289 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
15290 self.show_scrollbars = show_scrollbars;
15291 cx.notify();
15292 }
15293
15294 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
15295 self.show_line_numbers = Some(show_line_numbers);
15296 cx.notify();
15297 }
15298
15299 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
15300 self.show_git_diff_gutter = Some(show_git_diff_gutter);
15301 cx.notify();
15302 }
15303
15304 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
15305 self.show_code_actions = Some(show_code_actions);
15306 cx.notify();
15307 }
15308
15309 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
15310 self.show_runnables = Some(show_runnables);
15311 cx.notify();
15312 }
15313
15314 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
15315 self.show_breakpoints = Some(show_breakpoints);
15316 cx.notify();
15317 }
15318
15319 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
15320 if self.display_map.read(cx).masked != masked {
15321 self.display_map.update(cx, |map, _| map.masked = masked);
15322 }
15323 cx.notify()
15324 }
15325
15326 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
15327 self.show_wrap_guides = Some(show_wrap_guides);
15328 cx.notify();
15329 }
15330
15331 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
15332 self.show_indent_guides = Some(show_indent_guides);
15333 cx.notify();
15334 }
15335
15336 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
15337 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
15338 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
15339 if let Some(dir) = file.abs_path(cx).parent() {
15340 return Some(dir.to_owned());
15341 }
15342 }
15343
15344 if let Some(project_path) = buffer.read(cx).project_path(cx) {
15345 return Some(project_path.path.to_path_buf());
15346 }
15347 }
15348
15349 None
15350 }
15351
15352 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
15353 self.active_excerpt(cx)?
15354 .1
15355 .read(cx)
15356 .file()
15357 .and_then(|f| f.as_local())
15358 }
15359
15360 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15361 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15362 let buffer = buffer.read(cx);
15363 if let Some(project_path) = buffer.project_path(cx) {
15364 let project = self.project.as_ref()?.read(cx);
15365 project.absolute_path(&project_path, cx)
15366 } else {
15367 buffer
15368 .file()
15369 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
15370 }
15371 })
15372 }
15373
15374 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15375 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15376 let project_path = buffer.read(cx).project_path(cx)?;
15377 let project = self.project.as_ref()?.read(cx);
15378 let entry = project.entry_for_path(&project_path, cx)?;
15379 let path = entry.path.to_path_buf();
15380 Some(path)
15381 })
15382 }
15383
15384 pub fn reveal_in_finder(
15385 &mut self,
15386 _: &RevealInFileManager,
15387 _window: &mut Window,
15388 cx: &mut Context<Self>,
15389 ) {
15390 if let Some(target) = self.target_file(cx) {
15391 cx.reveal_path(&target.abs_path(cx));
15392 }
15393 }
15394
15395 pub fn copy_path(
15396 &mut self,
15397 _: &zed_actions::workspace::CopyPath,
15398 _window: &mut Window,
15399 cx: &mut Context<Self>,
15400 ) {
15401 if let Some(path) = self.target_file_abs_path(cx) {
15402 if let Some(path) = path.to_str() {
15403 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15404 }
15405 }
15406 }
15407
15408 pub fn copy_relative_path(
15409 &mut self,
15410 _: &zed_actions::workspace::CopyRelativePath,
15411 _window: &mut Window,
15412 cx: &mut Context<Self>,
15413 ) {
15414 if let Some(path) = self.target_file_path(cx) {
15415 if let Some(path) = path.to_str() {
15416 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15417 }
15418 }
15419 }
15420
15421 pub fn project_path(&self, cx: &mut Context<Self>) -> Option<ProjectPath> {
15422 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
15423 buffer.read_with(cx, |buffer, cx| buffer.project_path(cx))
15424 } else {
15425 None
15426 }
15427 }
15428
15429 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) {
15430 let _ = maybe!({
15431 let breakpoint_store = self.breakpoint_store.as_ref()?;
15432
15433 let Some((_, _, active_position)) =
15434 breakpoint_store.read(cx).active_position().cloned()
15435 else {
15436 self.clear_row_highlights::<DebugCurrentRowHighlight>();
15437 return None;
15438 };
15439
15440 let snapshot = self
15441 .project
15442 .as_ref()?
15443 .read(cx)
15444 .buffer_for_id(active_position.buffer_id?, cx)?
15445 .read(cx)
15446 .snapshot();
15447
15448 for (id, ExcerptRange { context, .. }) in self
15449 .buffer
15450 .read(cx)
15451 .excerpts_for_buffer(active_position.buffer_id?, cx)
15452 {
15453 if context.start.cmp(&active_position, &snapshot).is_ge()
15454 || context.end.cmp(&active_position, &snapshot).is_lt()
15455 {
15456 continue;
15457 }
15458 let snapshot = self.buffer.read(cx).snapshot(cx);
15459 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, active_position)?;
15460
15461 self.clear_row_highlights::<DebugCurrentRowHighlight>();
15462 self.go_to_line::<DebugCurrentRowHighlight>(
15463 multibuffer_anchor,
15464 Some(cx.theme().colors().editor_debugger_active_line_background),
15465 window,
15466 cx,
15467 );
15468
15469 cx.notify();
15470 }
15471
15472 Some(())
15473 });
15474 }
15475
15476 pub fn copy_file_name_without_extension(
15477 &mut self,
15478 _: &CopyFileNameWithoutExtension,
15479 _: &mut Window,
15480 cx: &mut Context<Self>,
15481 ) {
15482 if let Some(file) = self.target_file(cx) {
15483 if let Some(file_stem) = file.path().file_stem() {
15484 if let Some(name) = file_stem.to_str() {
15485 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
15486 }
15487 }
15488 }
15489 }
15490
15491 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
15492 if let Some(file) = self.target_file(cx) {
15493 if let Some(file_name) = file.path().file_name() {
15494 if let Some(name) = file_name.to_str() {
15495 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
15496 }
15497 }
15498 }
15499 }
15500
15501 pub fn toggle_git_blame(
15502 &mut self,
15503 _: &::git::Blame,
15504 window: &mut Window,
15505 cx: &mut Context<Self>,
15506 ) {
15507 self.show_git_blame_gutter = !self.show_git_blame_gutter;
15508
15509 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
15510 self.start_git_blame(true, window, cx);
15511 }
15512
15513 cx.notify();
15514 }
15515
15516 pub fn toggle_git_blame_inline(
15517 &mut self,
15518 _: &ToggleGitBlameInline,
15519 window: &mut Window,
15520 cx: &mut Context<Self>,
15521 ) {
15522 self.toggle_git_blame_inline_internal(true, window, cx);
15523 cx.notify();
15524 }
15525
15526 pub fn git_blame_inline_enabled(&self) -> bool {
15527 self.git_blame_inline_enabled
15528 }
15529
15530 pub fn toggle_selection_menu(
15531 &mut self,
15532 _: &ToggleSelectionMenu,
15533 _: &mut Window,
15534 cx: &mut Context<Self>,
15535 ) {
15536 self.show_selection_menu = self
15537 .show_selection_menu
15538 .map(|show_selections_menu| !show_selections_menu)
15539 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
15540
15541 cx.notify();
15542 }
15543
15544 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
15545 self.show_selection_menu
15546 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
15547 }
15548
15549 fn start_git_blame(
15550 &mut self,
15551 user_triggered: bool,
15552 window: &mut Window,
15553 cx: &mut Context<Self>,
15554 ) {
15555 if let Some(project) = self.project.as_ref() {
15556 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
15557 return;
15558 };
15559
15560 if buffer.read(cx).file().is_none() {
15561 return;
15562 }
15563
15564 let focused = self.focus_handle(cx).contains_focused(window, cx);
15565
15566 let project = project.clone();
15567 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
15568 self.blame_subscription =
15569 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
15570 self.blame = Some(blame);
15571 }
15572 }
15573
15574 fn toggle_git_blame_inline_internal(
15575 &mut self,
15576 user_triggered: bool,
15577 window: &mut Window,
15578 cx: &mut Context<Self>,
15579 ) {
15580 if self.git_blame_inline_enabled {
15581 self.git_blame_inline_enabled = false;
15582 self.show_git_blame_inline = false;
15583 self.show_git_blame_inline_delay_task.take();
15584 } else {
15585 self.git_blame_inline_enabled = true;
15586 self.start_git_blame_inline(user_triggered, window, cx);
15587 }
15588
15589 cx.notify();
15590 }
15591
15592 fn start_git_blame_inline(
15593 &mut self,
15594 user_triggered: bool,
15595 window: &mut Window,
15596 cx: &mut Context<Self>,
15597 ) {
15598 self.start_git_blame(user_triggered, window, cx);
15599
15600 if ProjectSettings::get_global(cx)
15601 .git
15602 .inline_blame_delay()
15603 .is_some()
15604 {
15605 self.start_inline_blame_timer(window, cx);
15606 } else {
15607 self.show_git_blame_inline = true
15608 }
15609 }
15610
15611 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
15612 self.blame.as_ref()
15613 }
15614
15615 pub fn show_git_blame_gutter(&self) -> bool {
15616 self.show_git_blame_gutter
15617 }
15618
15619 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
15620 self.show_git_blame_gutter && self.has_blame_entries(cx)
15621 }
15622
15623 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
15624 self.show_git_blame_inline
15625 && (self.focus_handle.is_focused(window)
15626 || self
15627 .git_blame_inline_tooltip
15628 .as_ref()
15629 .and_then(|t| t.upgrade())
15630 .is_some())
15631 && !self.newest_selection_head_on_empty_line(cx)
15632 && self.has_blame_entries(cx)
15633 }
15634
15635 fn has_blame_entries(&self, cx: &App) -> bool {
15636 self.blame()
15637 .map_or(false, |blame| blame.read(cx).has_generated_entries())
15638 }
15639
15640 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
15641 let cursor_anchor = self.selections.newest_anchor().head();
15642
15643 let snapshot = self.buffer.read(cx).snapshot(cx);
15644 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
15645
15646 snapshot.line_len(buffer_row) == 0
15647 }
15648
15649 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
15650 let buffer_and_selection = maybe!({
15651 let selection = self.selections.newest::<Point>(cx);
15652 let selection_range = selection.range();
15653
15654 let multi_buffer = self.buffer().read(cx);
15655 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15656 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
15657
15658 let (buffer, range, _) = if selection.reversed {
15659 buffer_ranges.first()
15660 } else {
15661 buffer_ranges.last()
15662 }?;
15663
15664 let selection = text::ToPoint::to_point(&range.start, &buffer).row
15665 ..text::ToPoint::to_point(&range.end, &buffer).row;
15666 Some((
15667 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
15668 selection,
15669 ))
15670 });
15671
15672 let Some((buffer, selection)) = buffer_and_selection else {
15673 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
15674 };
15675
15676 let Some(project) = self.project.as_ref() else {
15677 return Task::ready(Err(anyhow!("editor does not have project")));
15678 };
15679
15680 project.update(cx, |project, cx| {
15681 project.get_permalink_to_line(&buffer, selection, cx)
15682 })
15683 }
15684
15685 pub fn copy_permalink_to_line(
15686 &mut self,
15687 _: &CopyPermalinkToLine,
15688 window: &mut Window,
15689 cx: &mut Context<Self>,
15690 ) {
15691 let permalink_task = self.get_permalink_to_line(cx);
15692 let workspace = self.workspace();
15693
15694 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
15695 Ok(permalink) => {
15696 cx.update(|_, cx| {
15697 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
15698 })
15699 .ok();
15700 }
15701 Err(err) => {
15702 let message = format!("Failed to copy permalink: {err}");
15703
15704 Err::<(), anyhow::Error>(err).log_err();
15705
15706 if let Some(workspace) = workspace {
15707 workspace
15708 .update_in(cx, |workspace, _, cx| {
15709 struct CopyPermalinkToLine;
15710
15711 workspace.show_toast(
15712 Toast::new(
15713 NotificationId::unique::<CopyPermalinkToLine>(),
15714 message,
15715 ),
15716 cx,
15717 )
15718 })
15719 .ok();
15720 }
15721 }
15722 })
15723 .detach();
15724 }
15725
15726 pub fn copy_file_location(
15727 &mut self,
15728 _: &CopyFileLocation,
15729 _: &mut Window,
15730 cx: &mut Context<Self>,
15731 ) {
15732 let selection = self.selections.newest::<Point>(cx).start.row + 1;
15733 if let Some(file) = self.target_file(cx) {
15734 if let Some(path) = file.path().to_str() {
15735 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
15736 }
15737 }
15738 }
15739
15740 pub fn open_permalink_to_line(
15741 &mut self,
15742 _: &OpenPermalinkToLine,
15743 window: &mut Window,
15744 cx: &mut Context<Self>,
15745 ) {
15746 let permalink_task = self.get_permalink_to_line(cx);
15747 let workspace = self.workspace();
15748
15749 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
15750 Ok(permalink) => {
15751 cx.update(|_, cx| {
15752 cx.open_url(permalink.as_ref());
15753 })
15754 .ok();
15755 }
15756 Err(err) => {
15757 let message = format!("Failed to open permalink: {err}");
15758
15759 Err::<(), anyhow::Error>(err).log_err();
15760
15761 if let Some(workspace) = workspace {
15762 workspace
15763 .update(cx, |workspace, cx| {
15764 struct OpenPermalinkToLine;
15765
15766 workspace.show_toast(
15767 Toast::new(
15768 NotificationId::unique::<OpenPermalinkToLine>(),
15769 message,
15770 ),
15771 cx,
15772 )
15773 })
15774 .ok();
15775 }
15776 }
15777 })
15778 .detach();
15779 }
15780
15781 pub fn insert_uuid_v4(
15782 &mut self,
15783 _: &InsertUuidV4,
15784 window: &mut Window,
15785 cx: &mut Context<Self>,
15786 ) {
15787 self.insert_uuid(UuidVersion::V4, window, cx);
15788 }
15789
15790 pub fn insert_uuid_v7(
15791 &mut self,
15792 _: &InsertUuidV7,
15793 window: &mut Window,
15794 cx: &mut Context<Self>,
15795 ) {
15796 self.insert_uuid(UuidVersion::V7, window, cx);
15797 }
15798
15799 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
15800 self.transact(window, cx, |this, window, cx| {
15801 let edits = this
15802 .selections
15803 .all::<Point>(cx)
15804 .into_iter()
15805 .map(|selection| {
15806 let uuid = match version {
15807 UuidVersion::V4 => uuid::Uuid::new_v4(),
15808 UuidVersion::V7 => uuid::Uuid::now_v7(),
15809 };
15810
15811 (selection.range(), uuid.to_string())
15812 });
15813 this.edit(edits, cx);
15814 this.refresh_inline_completion(true, false, window, cx);
15815 });
15816 }
15817
15818 pub fn open_selections_in_multibuffer(
15819 &mut self,
15820 _: &OpenSelectionsInMultibuffer,
15821 window: &mut Window,
15822 cx: &mut Context<Self>,
15823 ) {
15824 let multibuffer = self.buffer.read(cx);
15825
15826 let Some(buffer) = multibuffer.as_singleton() else {
15827 return;
15828 };
15829
15830 let Some(workspace) = self.workspace() else {
15831 return;
15832 };
15833
15834 let locations = self
15835 .selections
15836 .disjoint_anchors()
15837 .iter()
15838 .map(|range| Location {
15839 buffer: buffer.clone(),
15840 range: range.start.text_anchor..range.end.text_anchor,
15841 })
15842 .collect::<Vec<_>>();
15843
15844 let title = multibuffer.title(cx).to_string();
15845
15846 cx.spawn_in(window, async move |_, cx| {
15847 workspace.update_in(cx, |workspace, window, cx| {
15848 Self::open_locations_in_multibuffer(
15849 workspace,
15850 locations,
15851 format!("Selections for '{title}'"),
15852 false,
15853 MultibufferSelectionMode::All,
15854 window,
15855 cx,
15856 );
15857 })
15858 })
15859 .detach();
15860 }
15861
15862 /// Adds a row highlight for the given range. If a row has multiple highlights, the
15863 /// last highlight added will be used.
15864 ///
15865 /// If the range ends at the beginning of a line, then that line will not be highlighted.
15866 pub fn highlight_rows<T: 'static>(
15867 &mut self,
15868 range: Range<Anchor>,
15869 color: Hsla,
15870 should_autoscroll: bool,
15871 cx: &mut Context<Self>,
15872 ) {
15873 let snapshot = self.buffer().read(cx).snapshot(cx);
15874 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
15875 let ix = row_highlights.binary_search_by(|highlight| {
15876 Ordering::Equal
15877 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
15878 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
15879 });
15880
15881 if let Err(mut ix) = ix {
15882 let index = post_inc(&mut self.highlight_order);
15883
15884 // If this range intersects with the preceding highlight, then merge it with
15885 // the preceding highlight. Otherwise insert a new highlight.
15886 let mut merged = false;
15887 if ix > 0 {
15888 let prev_highlight = &mut row_highlights[ix - 1];
15889 if prev_highlight
15890 .range
15891 .end
15892 .cmp(&range.start, &snapshot)
15893 .is_ge()
15894 {
15895 ix -= 1;
15896 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
15897 prev_highlight.range.end = range.end;
15898 }
15899 merged = true;
15900 prev_highlight.index = index;
15901 prev_highlight.color = color;
15902 prev_highlight.should_autoscroll = should_autoscroll;
15903 }
15904 }
15905
15906 if !merged {
15907 row_highlights.insert(
15908 ix,
15909 RowHighlight {
15910 range: range.clone(),
15911 index,
15912 color,
15913 should_autoscroll,
15914 },
15915 );
15916 }
15917
15918 // If any of the following highlights intersect with this one, merge them.
15919 while let Some(next_highlight) = row_highlights.get(ix + 1) {
15920 let highlight = &row_highlights[ix];
15921 if next_highlight
15922 .range
15923 .start
15924 .cmp(&highlight.range.end, &snapshot)
15925 .is_le()
15926 {
15927 if next_highlight
15928 .range
15929 .end
15930 .cmp(&highlight.range.end, &snapshot)
15931 .is_gt()
15932 {
15933 row_highlights[ix].range.end = next_highlight.range.end;
15934 }
15935 row_highlights.remove(ix + 1);
15936 } else {
15937 break;
15938 }
15939 }
15940 }
15941 }
15942
15943 /// Remove any highlighted row ranges of the given type that intersect the
15944 /// given ranges.
15945 pub fn remove_highlighted_rows<T: 'static>(
15946 &mut self,
15947 ranges_to_remove: Vec<Range<Anchor>>,
15948 cx: &mut Context<Self>,
15949 ) {
15950 let snapshot = self.buffer().read(cx).snapshot(cx);
15951 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
15952 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
15953 row_highlights.retain(|highlight| {
15954 while let Some(range_to_remove) = ranges_to_remove.peek() {
15955 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
15956 Ordering::Less | Ordering::Equal => {
15957 ranges_to_remove.next();
15958 }
15959 Ordering::Greater => {
15960 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
15961 Ordering::Less | Ordering::Equal => {
15962 return false;
15963 }
15964 Ordering::Greater => break,
15965 }
15966 }
15967 }
15968 }
15969
15970 true
15971 })
15972 }
15973
15974 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
15975 pub fn clear_row_highlights<T: 'static>(&mut self) {
15976 self.highlighted_rows.remove(&TypeId::of::<T>());
15977 }
15978
15979 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
15980 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
15981 self.highlighted_rows
15982 .get(&TypeId::of::<T>())
15983 .map_or(&[] as &[_], |vec| vec.as_slice())
15984 .iter()
15985 .map(|highlight| (highlight.range.clone(), highlight.color))
15986 }
15987
15988 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
15989 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
15990 /// Allows to ignore certain kinds of highlights.
15991 pub fn highlighted_display_rows(
15992 &self,
15993 window: &mut Window,
15994 cx: &mut App,
15995 ) -> BTreeMap<DisplayRow, LineHighlight> {
15996 let snapshot = self.snapshot(window, cx);
15997 let mut used_highlight_orders = HashMap::default();
15998 self.highlighted_rows
15999 .iter()
16000 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
16001 .fold(
16002 BTreeMap::<DisplayRow, LineHighlight>::new(),
16003 |mut unique_rows, highlight| {
16004 let start = highlight.range.start.to_display_point(&snapshot);
16005 let end = highlight.range.end.to_display_point(&snapshot);
16006 let start_row = start.row().0;
16007 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
16008 && end.column() == 0
16009 {
16010 end.row().0.saturating_sub(1)
16011 } else {
16012 end.row().0
16013 };
16014 for row in start_row..=end_row {
16015 let used_index =
16016 used_highlight_orders.entry(row).or_insert(highlight.index);
16017 if highlight.index >= *used_index {
16018 *used_index = highlight.index;
16019 unique_rows.insert(DisplayRow(row), highlight.color.into());
16020 }
16021 }
16022 unique_rows
16023 },
16024 )
16025 }
16026
16027 pub fn highlighted_display_row_for_autoscroll(
16028 &self,
16029 snapshot: &DisplaySnapshot,
16030 ) -> Option<DisplayRow> {
16031 self.highlighted_rows
16032 .values()
16033 .flat_map(|highlighted_rows| highlighted_rows.iter())
16034 .filter_map(|highlight| {
16035 if highlight.should_autoscroll {
16036 Some(highlight.range.start.to_display_point(snapshot).row())
16037 } else {
16038 None
16039 }
16040 })
16041 .min()
16042 }
16043
16044 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
16045 self.highlight_background::<SearchWithinRange>(
16046 ranges,
16047 |colors| colors.editor_document_highlight_read_background,
16048 cx,
16049 )
16050 }
16051
16052 pub fn set_breadcrumb_header(&mut self, new_header: String) {
16053 self.breadcrumb_header = Some(new_header);
16054 }
16055
16056 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
16057 self.clear_background_highlights::<SearchWithinRange>(cx);
16058 }
16059
16060 pub fn highlight_background<T: 'static>(
16061 &mut self,
16062 ranges: &[Range<Anchor>],
16063 color_fetcher: fn(&ThemeColors) -> Hsla,
16064 cx: &mut Context<Self>,
16065 ) {
16066 self.background_highlights
16067 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16068 self.scrollbar_marker_state.dirty = true;
16069 cx.notify();
16070 }
16071
16072 pub fn clear_background_highlights<T: 'static>(
16073 &mut self,
16074 cx: &mut Context<Self>,
16075 ) -> Option<BackgroundHighlight> {
16076 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
16077 if !text_highlights.1.is_empty() {
16078 self.scrollbar_marker_state.dirty = true;
16079 cx.notify();
16080 }
16081 Some(text_highlights)
16082 }
16083
16084 pub fn highlight_gutter<T: 'static>(
16085 &mut self,
16086 ranges: &[Range<Anchor>],
16087 color_fetcher: fn(&App) -> Hsla,
16088 cx: &mut Context<Self>,
16089 ) {
16090 self.gutter_highlights
16091 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16092 cx.notify();
16093 }
16094
16095 pub fn clear_gutter_highlights<T: 'static>(
16096 &mut self,
16097 cx: &mut Context<Self>,
16098 ) -> Option<GutterHighlight> {
16099 cx.notify();
16100 self.gutter_highlights.remove(&TypeId::of::<T>())
16101 }
16102
16103 #[cfg(feature = "test-support")]
16104 pub fn all_text_background_highlights(
16105 &self,
16106 window: &mut Window,
16107 cx: &mut Context<Self>,
16108 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16109 let snapshot = self.snapshot(window, cx);
16110 let buffer = &snapshot.buffer_snapshot;
16111 let start = buffer.anchor_before(0);
16112 let end = buffer.anchor_after(buffer.len());
16113 let theme = cx.theme().colors();
16114 self.background_highlights_in_range(start..end, &snapshot, theme)
16115 }
16116
16117 #[cfg(feature = "test-support")]
16118 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
16119 let snapshot = self.buffer().read(cx).snapshot(cx);
16120
16121 let highlights = self
16122 .background_highlights
16123 .get(&TypeId::of::<items::BufferSearchHighlights>());
16124
16125 if let Some((_color, ranges)) = highlights {
16126 ranges
16127 .iter()
16128 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
16129 .collect_vec()
16130 } else {
16131 vec![]
16132 }
16133 }
16134
16135 fn document_highlights_for_position<'a>(
16136 &'a self,
16137 position: Anchor,
16138 buffer: &'a MultiBufferSnapshot,
16139 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
16140 let read_highlights = self
16141 .background_highlights
16142 .get(&TypeId::of::<DocumentHighlightRead>())
16143 .map(|h| &h.1);
16144 let write_highlights = self
16145 .background_highlights
16146 .get(&TypeId::of::<DocumentHighlightWrite>())
16147 .map(|h| &h.1);
16148 let left_position = position.bias_left(buffer);
16149 let right_position = position.bias_right(buffer);
16150 read_highlights
16151 .into_iter()
16152 .chain(write_highlights)
16153 .flat_map(move |ranges| {
16154 let start_ix = match ranges.binary_search_by(|probe| {
16155 let cmp = probe.end.cmp(&left_position, buffer);
16156 if cmp.is_ge() {
16157 Ordering::Greater
16158 } else {
16159 Ordering::Less
16160 }
16161 }) {
16162 Ok(i) | Err(i) => i,
16163 };
16164
16165 ranges[start_ix..]
16166 .iter()
16167 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
16168 })
16169 }
16170
16171 pub fn has_background_highlights<T: 'static>(&self) -> bool {
16172 self.background_highlights
16173 .get(&TypeId::of::<T>())
16174 .map_or(false, |(_, highlights)| !highlights.is_empty())
16175 }
16176
16177 pub fn background_highlights_in_range(
16178 &self,
16179 search_range: Range<Anchor>,
16180 display_snapshot: &DisplaySnapshot,
16181 theme: &ThemeColors,
16182 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16183 let mut results = Vec::new();
16184 for (color_fetcher, ranges) in self.background_highlights.values() {
16185 let color = color_fetcher(theme);
16186 let start_ix = match ranges.binary_search_by(|probe| {
16187 let cmp = probe
16188 .end
16189 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16190 if cmp.is_gt() {
16191 Ordering::Greater
16192 } else {
16193 Ordering::Less
16194 }
16195 }) {
16196 Ok(i) | Err(i) => i,
16197 };
16198 for range in &ranges[start_ix..] {
16199 if range
16200 .start
16201 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16202 .is_ge()
16203 {
16204 break;
16205 }
16206
16207 let start = range.start.to_display_point(display_snapshot);
16208 let end = range.end.to_display_point(display_snapshot);
16209 results.push((start..end, color))
16210 }
16211 }
16212 results
16213 }
16214
16215 pub fn background_highlight_row_ranges<T: 'static>(
16216 &self,
16217 search_range: Range<Anchor>,
16218 display_snapshot: &DisplaySnapshot,
16219 count: usize,
16220 ) -> Vec<RangeInclusive<DisplayPoint>> {
16221 let mut results = Vec::new();
16222 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
16223 return vec![];
16224 };
16225
16226 let start_ix = match ranges.binary_search_by(|probe| {
16227 let cmp = probe
16228 .end
16229 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16230 if cmp.is_gt() {
16231 Ordering::Greater
16232 } else {
16233 Ordering::Less
16234 }
16235 }) {
16236 Ok(i) | Err(i) => i,
16237 };
16238 let mut push_region = |start: Option<Point>, end: Option<Point>| {
16239 if let (Some(start_display), Some(end_display)) = (start, end) {
16240 results.push(
16241 start_display.to_display_point(display_snapshot)
16242 ..=end_display.to_display_point(display_snapshot),
16243 );
16244 }
16245 };
16246 let mut start_row: Option<Point> = None;
16247 let mut end_row: Option<Point> = None;
16248 if ranges.len() > count {
16249 return Vec::new();
16250 }
16251 for range in &ranges[start_ix..] {
16252 if range
16253 .start
16254 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16255 .is_ge()
16256 {
16257 break;
16258 }
16259 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
16260 if let Some(current_row) = &end_row {
16261 if end.row == current_row.row {
16262 continue;
16263 }
16264 }
16265 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
16266 if start_row.is_none() {
16267 assert_eq!(end_row, None);
16268 start_row = Some(start);
16269 end_row = Some(end);
16270 continue;
16271 }
16272 if let Some(current_end) = end_row.as_mut() {
16273 if start.row > current_end.row + 1 {
16274 push_region(start_row, end_row);
16275 start_row = Some(start);
16276 end_row = Some(end);
16277 } else {
16278 // Merge two hunks.
16279 *current_end = end;
16280 }
16281 } else {
16282 unreachable!();
16283 }
16284 }
16285 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
16286 push_region(start_row, end_row);
16287 results
16288 }
16289
16290 pub fn gutter_highlights_in_range(
16291 &self,
16292 search_range: Range<Anchor>,
16293 display_snapshot: &DisplaySnapshot,
16294 cx: &App,
16295 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16296 let mut results = Vec::new();
16297 for (color_fetcher, ranges) in self.gutter_highlights.values() {
16298 let color = color_fetcher(cx);
16299 let start_ix = match ranges.binary_search_by(|probe| {
16300 let cmp = probe
16301 .end
16302 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16303 if cmp.is_gt() {
16304 Ordering::Greater
16305 } else {
16306 Ordering::Less
16307 }
16308 }) {
16309 Ok(i) | Err(i) => i,
16310 };
16311 for range in &ranges[start_ix..] {
16312 if range
16313 .start
16314 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16315 .is_ge()
16316 {
16317 break;
16318 }
16319
16320 let start = range.start.to_display_point(display_snapshot);
16321 let end = range.end.to_display_point(display_snapshot);
16322 results.push((start..end, color))
16323 }
16324 }
16325 results
16326 }
16327
16328 /// Get the text ranges corresponding to the redaction query
16329 pub fn redacted_ranges(
16330 &self,
16331 search_range: Range<Anchor>,
16332 display_snapshot: &DisplaySnapshot,
16333 cx: &App,
16334 ) -> Vec<Range<DisplayPoint>> {
16335 display_snapshot
16336 .buffer_snapshot
16337 .redacted_ranges(search_range, |file| {
16338 if let Some(file) = file {
16339 file.is_private()
16340 && EditorSettings::get(
16341 Some(SettingsLocation {
16342 worktree_id: file.worktree_id(cx),
16343 path: file.path().as_ref(),
16344 }),
16345 cx,
16346 )
16347 .redact_private_values
16348 } else {
16349 false
16350 }
16351 })
16352 .map(|range| {
16353 range.start.to_display_point(display_snapshot)
16354 ..range.end.to_display_point(display_snapshot)
16355 })
16356 .collect()
16357 }
16358
16359 pub fn highlight_text<T: 'static>(
16360 &mut self,
16361 ranges: Vec<Range<Anchor>>,
16362 style: HighlightStyle,
16363 cx: &mut Context<Self>,
16364 ) {
16365 self.display_map.update(cx, |map, _| {
16366 map.highlight_text(TypeId::of::<T>(), ranges, style)
16367 });
16368 cx.notify();
16369 }
16370
16371 pub(crate) fn highlight_inlays<T: 'static>(
16372 &mut self,
16373 highlights: Vec<InlayHighlight>,
16374 style: HighlightStyle,
16375 cx: &mut Context<Self>,
16376 ) {
16377 self.display_map.update(cx, |map, _| {
16378 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
16379 });
16380 cx.notify();
16381 }
16382
16383 pub fn text_highlights<'a, T: 'static>(
16384 &'a self,
16385 cx: &'a App,
16386 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
16387 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
16388 }
16389
16390 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
16391 let cleared = self
16392 .display_map
16393 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
16394 if cleared {
16395 cx.notify();
16396 }
16397 }
16398
16399 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
16400 (self.read_only(cx) || self.blink_manager.read(cx).visible())
16401 && self.focus_handle.is_focused(window)
16402 }
16403
16404 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
16405 self.show_cursor_when_unfocused = is_enabled;
16406 cx.notify();
16407 }
16408
16409 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
16410 cx.notify();
16411 }
16412
16413 fn on_buffer_event(
16414 &mut self,
16415 multibuffer: &Entity<MultiBuffer>,
16416 event: &multi_buffer::Event,
16417 window: &mut Window,
16418 cx: &mut Context<Self>,
16419 ) {
16420 match event {
16421 multi_buffer::Event::Edited {
16422 singleton_buffer_edited,
16423 edited_buffer: buffer_edited,
16424 } => {
16425 self.scrollbar_marker_state.dirty = true;
16426 self.active_indent_guides_state.dirty = true;
16427 self.refresh_active_diagnostics(cx);
16428 self.refresh_code_actions(window, cx);
16429 if self.has_active_inline_completion() {
16430 self.update_visible_inline_completion(window, cx);
16431 }
16432 if let Some(buffer) = buffer_edited {
16433 let buffer_id = buffer.read(cx).remote_id();
16434 if !self.registered_buffers.contains_key(&buffer_id) {
16435 if let Some(project) = self.project.as_ref() {
16436 project.update(cx, |project, cx| {
16437 self.registered_buffers.insert(
16438 buffer_id,
16439 project.register_buffer_with_language_servers(&buffer, cx),
16440 );
16441 })
16442 }
16443 }
16444 }
16445 cx.emit(EditorEvent::BufferEdited);
16446 cx.emit(SearchEvent::MatchesInvalidated);
16447 if *singleton_buffer_edited {
16448 if let Some(project) = &self.project {
16449 #[allow(clippy::mutable_key_type)]
16450 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
16451 multibuffer
16452 .all_buffers()
16453 .into_iter()
16454 .filter_map(|buffer| {
16455 buffer.update(cx, |buffer, cx| {
16456 let language = buffer.language()?;
16457 let should_discard = project.update(cx, |project, cx| {
16458 project.is_local()
16459 && !project.has_language_servers_for(buffer, cx)
16460 });
16461 should_discard.not().then_some(language.clone())
16462 })
16463 })
16464 .collect::<HashSet<_>>()
16465 });
16466 if !languages_affected.is_empty() {
16467 self.refresh_inlay_hints(
16468 InlayHintRefreshReason::BufferEdited(languages_affected),
16469 cx,
16470 );
16471 }
16472 }
16473 }
16474
16475 let Some(project) = &self.project else { return };
16476 let (telemetry, is_via_ssh) = {
16477 let project = project.read(cx);
16478 let telemetry = project.client().telemetry().clone();
16479 let is_via_ssh = project.is_via_ssh();
16480 (telemetry, is_via_ssh)
16481 };
16482 refresh_linked_ranges(self, window, cx);
16483 telemetry.log_edit_event("editor", is_via_ssh);
16484 }
16485 multi_buffer::Event::ExcerptsAdded {
16486 buffer,
16487 predecessor,
16488 excerpts,
16489 } => {
16490 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16491 let buffer_id = buffer.read(cx).remote_id();
16492 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
16493 if let Some(project) = &self.project {
16494 get_uncommitted_diff_for_buffer(
16495 project,
16496 [buffer.clone()],
16497 self.buffer.clone(),
16498 cx,
16499 )
16500 .detach();
16501 }
16502 }
16503 cx.emit(EditorEvent::ExcerptsAdded {
16504 buffer: buffer.clone(),
16505 predecessor: *predecessor,
16506 excerpts: excerpts.clone(),
16507 });
16508 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
16509 }
16510 multi_buffer::Event::ExcerptsRemoved { ids } => {
16511 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
16512 let buffer = self.buffer.read(cx);
16513 self.registered_buffers
16514 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
16515 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
16516 cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
16517 }
16518 multi_buffer::Event::ExcerptsEdited {
16519 excerpt_ids,
16520 buffer_ids,
16521 } => {
16522 self.display_map.update(cx, |map, cx| {
16523 map.unfold_buffers(buffer_ids.iter().copied(), cx)
16524 });
16525 cx.emit(EditorEvent::ExcerptsEdited {
16526 ids: excerpt_ids.clone(),
16527 })
16528 }
16529 multi_buffer::Event::ExcerptsExpanded { ids } => {
16530 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
16531 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
16532 }
16533 multi_buffer::Event::Reparsed(buffer_id) => {
16534 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16535 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
16536
16537 cx.emit(EditorEvent::Reparsed(*buffer_id));
16538 }
16539 multi_buffer::Event::DiffHunksToggled => {
16540 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16541 }
16542 multi_buffer::Event::LanguageChanged(buffer_id) => {
16543 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
16544 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
16545 cx.emit(EditorEvent::Reparsed(*buffer_id));
16546 cx.notify();
16547 }
16548 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
16549 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
16550 multi_buffer::Event::FileHandleChanged
16551 | multi_buffer::Event::Reloaded
16552 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
16553 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
16554 multi_buffer::Event::DiagnosticsUpdated => {
16555 self.refresh_active_diagnostics(cx);
16556 self.refresh_inline_diagnostics(true, window, cx);
16557 self.scrollbar_marker_state.dirty = true;
16558 cx.notify();
16559 }
16560 _ => {}
16561 };
16562 }
16563
16564 fn on_display_map_changed(
16565 &mut self,
16566 _: Entity<DisplayMap>,
16567 _: &mut Window,
16568 cx: &mut Context<Self>,
16569 ) {
16570 cx.notify();
16571 }
16572
16573 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16574 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16575 self.update_edit_prediction_settings(cx);
16576 self.refresh_inline_completion(true, false, window, cx);
16577 self.refresh_inlay_hints(
16578 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
16579 self.selections.newest_anchor().head(),
16580 &self.buffer.read(cx).snapshot(cx),
16581 cx,
16582 )),
16583 cx,
16584 );
16585
16586 let old_cursor_shape = self.cursor_shape;
16587
16588 {
16589 let editor_settings = EditorSettings::get_global(cx);
16590 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
16591 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
16592 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
16593 }
16594
16595 if old_cursor_shape != self.cursor_shape {
16596 cx.emit(EditorEvent::CursorShapeChanged);
16597 }
16598
16599 let project_settings = ProjectSettings::get_global(cx);
16600 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
16601
16602 if self.mode == EditorMode::Full {
16603 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
16604 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
16605 if self.show_inline_diagnostics != show_inline_diagnostics {
16606 self.show_inline_diagnostics = show_inline_diagnostics;
16607 self.refresh_inline_diagnostics(false, window, cx);
16608 }
16609
16610 if self.git_blame_inline_enabled != inline_blame_enabled {
16611 self.toggle_git_blame_inline_internal(false, window, cx);
16612 }
16613 }
16614
16615 cx.notify();
16616 }
16617
16618 pub fn set_searchable(&mut self, searchable: bool) {
16619 self.searchable = searchable;
16620 }
16621
16622 pub fn searchable(&self) -> bool {
16623 self.searchable
16624 }
16625
16626 fn open_proposed_changes_editor(
16627 &mut self,
16628 _: &OpenProposedChangesEditor,
16629 window: &mut Window,
16630 cx: &mut Context<Self>,
16631 ) {
16632 let Some(workspace) = self.workspace() else {
16633 cx.propagate();
16634 return;
16635 };
16636
16637 let selections = self.selections.all::<usize>(cx);
16638 let multi_buffer = self.buffer.read(cx);
16639 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16640 let mut new_selections_by_buffer = HashMap::default();
16641 for selection in selections {
16642 for (buffer, range, _) in
16643 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
16644 {
16645 let mut range = range.to_point(buffer);
16646 range.start.column = 0;
16647 range.end.column = buffer.line_len(range.end.row);
16648 new_selections_by_buffer
16649 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
16650 .or_insert(Vec::new())
16651 .push(range)
16652 }
16653 }
16654
16655 let proposed_changes_buffers = new_selections_by_buffer
16656 .into_iter()
16657 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
16658 .collect::<Vec<_>>();
16659 let proposed_changes_editor = cx.new(|cx| {
16660 ProposedChangesEditor::new(
16661 "Proposed changes",
16662 proposed_changes_buffers,
16663 self.project.clone(),
16664 window,
16665 cx,
16666 )
16667 });
16668
16669 window.defer(cx, move |window, cx| {
16670 workspace.update(cx, |workspace, cx| {
16671 workspace.active_pane().update(cx, |pane, cx| {
16672 pane.add_item(
16673 Box::new(proposed_changes_editor),
16674 true,
16675 true,
16676 None,
16677 window,
16678 cx,
16679 );
16680 });
16681 });
16682 });
16683 }
16684
16685 pub fn open_excerpts_in_split(
16686 &mut self,
16687 _: &OpenExcerptsSplit,
16688 window: &mut Window,
16689 cx: &mut Context<Self>,
16690 ) {
16691 self.open_excerpts_common(None, true, window, cx)
16692 }
16693
16694 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
16695 self.open_excerpts_common(None, false, window, cx)
16696 }
16697
16698 fn open_excerpts_common(
16699 &mut self,
16700 jump_data: Option<JumpData>,
16701 split: bool,
16702 window: &mut Window,
16703 cx: &mut Context<Self>,
16704 ) {
16705 let Some(workspace) = self.workspace() else {
16706 cx.propagate();
16707 return;
16708 };
16709
16710 if self.buffer.read(cx).is_singleton() {
16711 cx.propagate();
16712 return;
16713 }
16714
16715 let mut new_selections_by_buffer = HashMap::default();
16716 match &jump_data {
16717 Some(JumpData::MultiBufferPoint {
16718 excerpt_id,
16719 position,
16720 anchor,
16721 line_offset_from_top,
16722 }) => {
16723 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16724 if let Some(buffer) = multi_buffer_snapshot
16725 .buffer_id_for_excerpt(*excerpt_id)
16726 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
16727 {
16728 let buffer_snapshot = buffer.read(cx).snapshot();
16729 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
16730 language::ToPoint::to_point(anchor, &buffer_snapshot)
16731 } else {
16732 buffer_snapshot.clip_point(*position, Bias::Left)
16733 };
16734 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
16735 new_selections_by_buffer.insert(
16736 buffer,
16737 (
16738 vec![jump_to_offset..jump_to_offset],
16739 Some(*line_offset_from_top),
16740 ),
16741 );
16742 }
16743 }
16744 Some(JumpData::MultiBufferRow {
16745 row,
16746 line_offset_from_top,
16747 }) => {
16748 let point = MultiBufferPoint::new(row.0, 0);
16749 if let Some((buffer, buffer_point, _)) =
16750 self.buffer.read(cx).point_to_buffer_point(point, cx)
16751 {
16752 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
16753 new_selections_by_buffer
16754 .entry(buffer)
16755 .or_insert((Vec::new(), Some(*line_offset_from_top)))
16756 .0
16757 .push(buffer_offset..buffer_offset)
16758 }
16759 }
16760 None => {
16761 let selections = self.selections.all::<usize>(cx);
16762 let multi_buffer = self.buffer.read(cx);
16763 for selection in selections {
16764 for (snapshot, range, _, anchor) in multi_buffer
16765 .snapshot(cx)
16766 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
16767 {
16768 if let Some(anchor) = anchor {
16769 // selection is in a deleted hunk
16770 let Some(buffer_id) = anchor.buffer_id else {
16771 continue;
16772 };
16773 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
16774 continue;
16775 };
16776 let offset = text::ToOffset::to_offset(
16777 &anchor.text_anchor,
16778 &buffer_handle.read(cx).snapshot(),
16779 );
16780 let range = offset..offset;
16781 new_selections_by_buffer
16782 .entry(buffer_handle)
16783 .or_insert((Vec::new(), None))
16784 .0
16785 .push(range)
16786 } else {
16787 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
16788 else {
16789 continue;
16790 };
16791 new_selections_by_buffer
16792 .entry(buffer_handle)
16793 .or_insert((Vec::new(), None))
16794 .0
16795 .push(range)
16796 }
16797 }
16798 }
16799 }
16800 }
16801
16802 if new_selections_by_buffer.is_empty() {
16803 return;
16804 }
16805
16806 // We defer the pane interaction because we ourselves are a workspace item
16807 // and activating a new item causes the pane to call a method on us reentrantly,
16808 // which panics if we're on the stack.
16809 window.defer(cx, move |window, cx| {
16810 workspace.update(cx, |workspace, cx| {
16811 let pane = if split {
16812 workspace.adjacent_pane(window, cx)
16813 } else {
16814 workspace.active_pane().clone()
16815 };
16816
16817 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
16818 let editor = buffer
16819 .read(cx)
16820 .file()
16821 .is_none()
16822 .then(|| {
16823 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
16824 // so `workspace.open_project_item` will never find them, always opening a new editor.
16825 // Instead, we try to activate the existing editor in the pane first.
16826 let (editor, pane_item_index) =
16827 pane.read(cx).items().enumerate().find_map(|(i, item)| {
16828 let editor = item.downcast::<Editor>()?;
16829 let singleton_buffer =
16830 editor.read(cx).buffer().read(cx).as_singleton()?;
16831 if singleton_buffer == buffer {
16832 Some((editor, i))
16833 } else {
16834 None
16835 }
16836 })?;
16837 pane.update(cx, |pane, cx| {
16838 pane.activate_item(pane_item_index, true, true, window, cx)
16839 });
16840 Some(editor)
16841 })
16842 .flatten()
16843 .unwrap_or_else(|| {
16844 workspace.open_project_item::<Self>(
16845 pane.clone(),
16846 buffer,
16847 true,
16848 true,
16849 window,
16850 cx,
16851 )
16852 });
16853
16854 editor.update(cx, |editor, cx| {
16855 let autoscroll = match scroll_offset {
16856 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
16857 None => Autoscroll::newest(),
16858 };
16859 let nav_history = editor.nav_history.take();
16860 editor.change_selections(Some(autoscroll), window, cx, |s| {
16861 s.select_ranges(ranges);
16862 });
16863 editor.nav_history = nav_history;
16864 });
16865 }
16866 })
16867 });
16868 }
16869
16870 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
16871 let snapshot = self.buffer.read(cx).read(cx);
16872 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
16873 Some(
16874 ranges
16875 .iter()
16876 .map(move |range| {
16877 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
16878 })
16879 .collect(),
16880 )
16881 }
16882
16883 fn selection_replacement_ranges(
16884 &self,
16885 range: Range<OffsetUtf16>,
16886 cx: &mut App,
16887 ) -> Vec<Range<OffsetUtf16>> {
16888 let selections = self.selections.all::<OffsetUtf16>(cx);
16889 let newest_selection = selections
16890 .iter()
16891 .max_by_key(|selection| selection.id)
16892 .unwrap();
16893 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
16894 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
16895 let snapshot = self.buffer.read(cx).read(cx);
16896 selections
16897 .into_iter()
16898 .map(|mut selection| {
16899 selection.start.0 =
16900 (selection.start.0 as isize).saturating_add(start_delta) as usize;
16901 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
16902 snapshot.clip_offset_utf16(selection.start, Bias::Left)
16903 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
16904 })
16905 .collect()
16906 }
16907
16908 fn report_editor_event(
16909 &self,
16910 event_type: &'static str,
16911 file_extension: Option<String>,
16912 cx: &App,
16913 ) {
16914 if cfg!(any(test, feature = "test-support")) {
16915 return;
16916 }
16917
16918 let Some(project) = &self.project else { return };
16919
16920 // If None, we are in a file without an extension
16921 let file = self
16922 .buffer
16923 .read(cx)
16924 .as_singleton()
16925 .and_then(|b| b.read(cx).file());
16926 let file_extension = file_extension.or(file
16927 .as_ref()
16928 .and_then(|file| Path::new(file.file_name(cx)).extension())
16929 .and_then(|e| e.to_str())
16930 .map(|a| a.to_string()));
16931
16932 let vim_mode = cx
16933 .global::<SettingsStore>()
16934 .raw_user_settings()
16935 .get("vim_mode")
16936 == Some(&serde_json::Value::Bool(true));
16937
16938 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
16939 let copilot_enabled = edit_predictions_provider
16940 == language::language_settings::EditPredictionProvider::Copilot;
16941 let copilot_enabled_for_language = self
16942 .buffer
16943 .read(cx)
16944 .language_settings(cx)
16945 .show_edit_predictions;
16946
16947 let project = project.read(cx);
16948 telemetry::event!(
16949 event_type,
16950 file_extension,
16951 vim_mode,
16952 copilot_enabled,
16953 copilot_enabled_for_language,
16954 edit_predictions_provider,
16955 is_via_ssh = project.is_via_ssh(),
16956 );
16957 }
16958
16959 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
16960 /// with each line being an array of {text, highlight} objects.
16961 fn copy_highlight_json(
16962 &mut self,
16963 _: &CopyHighlightJson,
16964 window: &mut Window,
16965 cx: &mut Context<Self>,
16966 ) {
16967 #[derive(Serialize)]
16968 struct Chunk<'a> {
16969 text: String,
16970 highlight: Option<&'a str>,
16971 }
16972
16973 let snapshot = self.buffer.read(cx).snapshot(cx);
16974 let range = self
16975 .selected_text_range(false, window, cx)
16976 .and_then(|selection| {
16977 if selection.range.is_empty() {
16978 None
16979 } else {
16980 Some(selection.range)
16981 }
16982 })
16983 .unwrap_or_else(|| 0..snapshot.len());
16984
16985 let chunks = snapshot.chunks(range, true);
16986 let mut lines = Vec::new();
16987 let mut line: VecDeque<Chunk> = VecDeque::new();
16988
16989 let Some(style) = self.style.as_ref() else {
16990 return;
16991 };
16992
16993 for chunk in chunks {
16994 let highlight = chunk
16995 .syntax_highlight_id
16996 .and_then(|id| id.name(&style.syntax));
16997 let mut chunk_lines = chunk.text.split('\n').peekable();
16998 while let Some(text) = chunk_lines.next() {
16999 let mut merged_with_last_token = false;
17000 if let Some(last_token) = line.back_mut() {
17001 if last_token.highlight == highlight {
17002 last_token.text.push_str(text);
17003 merged_with_last_token = true;
17004 }
17005 }
17006
17007 if !merged_with_last_token {
17008 line.push_back(Chunk {
17009 text: text.into(),
17010 highlight,
17011 });
17012 }
17013
17014 if chunk_lines.peek().is_some() {
17015 if line.len() > 1 && line.front().unwrap().text.is_empty() {
17016 line.pop_front();
17017 }
17018 if line.len() > 1 && line.back().unwrap().text.is_empty() {
17019 line.pop_back();
17020 }
17021
17022 lines.push(mem::take(&mut line));
17023 }
17024 }
17025 }
17026
17027 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
17028 return;
17029 };
17030 cx.write_to_clipboard(ClipboardItem::new_string(lines));
17031 }
17032
17033 pub fn open_context_menu(
17034 &mut self,
17035 _: &OpenContextMenu,
17036 window: &mut Window,
17037 cx: &mut Context<Self>,
17038 ) {
17039 self.request_autoscroll(Autoscroll::newest(), cx);
17040 let position = self.selections.newest_display(cx).start;
17041 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
17042 }
17043
17044 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
17045 &self.inlay_hint_cache
17046 }
17047
17048 pub fn replay_insert_event(
17049 &mut self,
17050 text: &str,
17051 relative_utf16_range: Option<Range<isize>>,
17052 window: &mut Window,
17053 cx: &mut Context<Self>,
17054 ) {
17055 if !self.input_enabled {
17056 cx.emit(EditorEvent::InputIgnored { text: text.into() });
17057 return;
17058 }
17059 if let Some(relative_utf16_range) = relative_utf16_range {
17060 let selections = self.selections.all::<OffsetUtf16>(cx);
17061 self.change_selections(None, window, cx, |s| {
17062 let new_ranges = selections.into_iter().map(|range| {
17063 let start = OffsetUtf16(
17064 range
17065 .head()
17066 .0
17067 .saturating_add_signed(relative_utf16_range.start),
17068 );
17069 let end = OffsetUtf16(
17070 range
17071 .head()
17072 .0
17073 .saturating_add_signed(relative_utf16_range.end),
17074 );
17075 start..end
17076 });
17077 s.select_ranges(new_ranges);
17078 });
17079 }
17080
17081 self.handle_input(text, window, cx);
17082 }
17083
17084 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
17085 let Some(provider) = self.semantics_provider.as_ref() else {
17086 return false;
17087 };
17088
17089 let mut supports = false;
17090 self.buffer().update(cx, |this, cx| {
17091 this.for_each_buffer(|buffer| {
17092 supports |= provider.supports_inlay_hints(buffer, cx);
17093 });
17094 });
17095
17096 supports
17097 }
17098
17099 pub fn is_focused(&self, window: &Window) -> bool {
17100 self.focus_handle.is_focused(window)
17101 }
17102
17103 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17104 cx.emit(EditorEvent::Focused);
17105
17106 if let Some(descendant) = self
17107 .last_focused_descendant
17108 .take()
17109 .and_then(|descendant| descendant.upgrade())
17110 {
17111 window.focus(&descendant);
17112 } else {
17113 if let Some(blame) = self.blame.as_ref() {
17114 blame.update(cx, GitBlame::focus)
17115 }
17116
17117 self.blink_manager.update(cx, BlinkManager::enable);
17118 self.show_cursor_names(window, cx);
17119 self.buffer.update(cx, |buffer, cx| {
17120 buffer.finalize_last_transaction(cx);
17121 if self.leader_peer_id.is_none() {
17122 buffer.set_active_selections(
17123 &self.selections.disjoint_anchors(),
17124 self.selections.line_mode,
17125 self.cursor_shape,
17126 cx,
17127 );
17128 }
17129 });
17130 }
17131 }
17132
17133 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
17134 cx.emit(EditorEvent::FocusedIn)
17135 }
17136
17137 fn handle_focus_out(
17138 &mut self,
17139 event: FocusOutEvent,
17140 _window: &mut Window,
17141 cx: &mut Context<Self>,
17142 ) {
17143 if event.blurred != self.focus_handle {
17144 self.last_focused_descendant = Some(event.blurred);
17145 }
17146 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
17147 }
17148
17149 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17150 self.blink_manager.update(cx, BlinkManager::disable);
17151 self.buffer
17152 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
17153
17154 if let Some(blame) = self.blame.as_ref() {
17155 blame.update(cx, GitBlame::blur)
17156 }
17157 if !self.hover_state.focused(window, cx) {
17158 hide_hover(self, cx);
17159 }
17160 if !self
17161 .context_menu
17162 .borrow()
17163 .as_ref()
17164 .is_some_and(|context_menu| context_menu.focused(window, cx))
17165 {
17166 self.hide_context_menu(window, cx);
17167 }
17168 self.discard_inline_completion(false, cx);
17169 cx.emit(EditorEvent::Blurred);
17170 cx.notify();
17171 }
17172
17173 pub fn register_action<A: Action>(
17174 &mut self,
17175 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
17176 ) -> Subscription {
17177 let id = self.next_editor_action_id.post_inc();
17178 let listener = Arc::new(listener);
17179 self.editor_actions.borrow_mut().insert(
17180 id,
17181 Box::new(move |window, _| {
17182 let listener = listener.clone();
17183 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
17184 let action = action.downcast_ref().unwrap();
17185 if phase == DispatchPhase::Bubble {
17186 listener(action, window, cx)
17187 }
17188 })
17189 }),
17190 );
17191
17192 let editor_actions = self.editor_actions.clone();
17193 Subscription::new(move || {
17194 editor_actions.borrow_mut().remove(&id);
17195 })
17196 }
17197
17198 pub fn file_header_size(&self) -> u32 {
17199 FILE_HEADER_HEIGHT
17200 }
17201
17202 pub fn restore(
17203 &mut self,
17204 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
17205 window: &mut Window,
17206 cx: &mut Context<Self>,
17207 ) {
17208 let workspace = self.workspace();
17209 let project = self.project.as_ref();
17210 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
17211 let mut tasks = Vec::new();
17212 for (buffer_id, changes) in revert_changes {
17213 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
17214 buffer.update(cx, |buffer, cx| {
17215 buffer.edit(
17216 changes
17217 .into_iter()
17218 .map(|(range, text)| (range, text.to_string())),
17219 None,
17220 cx,
17221 );
17222 });
17223
17224 if let Some(project) =
17225 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
17226 {
17227 project.update(cx, |project, cx| {
17228 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
17229 })
17230 }
17231 }
17232 }
17233 tasks
17234 });
17235 cx.spawn_in(window, async move |_, cx| {
17236 for (buffer, task) in save_tasks {
17237 let result = task.await;
17238 if result.is_err() {
17239 let Some(path) = buffer
17240 .read_with(cx, |buffer, cx| buffer.project_path(cx))
17241 .ok()
17242 else {
17243 continue;
17244 };
17245 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
17246 let Some(task) = cx
17247 .update_window_entity(&workspace, |workspace, window, cx| {
17248 workspace
17249 .open_path_preview(path, None, false, false, false, window, cx)
17250 })
17251 .ok()
17252 else {
17253 continue;
17254 };
17255 task.await.log_err();
17256 }
17257 }
17258 }
17259 })
17260 .detach();
17261 self.change_selections(None, window, cx, |selections| selections.refresh());
17262 }
17263
17264 pub fn to_pixel_point(
17265 &self,
17266 source: multi_buffer::Anchor,
17267 editor_snapshot: &EditorSnapshot,
17268 window: &mut Window,
17269 ) -> Option<gpui::Point<Pixels>> {
17270 let source_point = source.to_display_point(editor_snapshot);
17271 self.display_to_pixel_point(source_point, editor_snapshot, window)
17272 }
17273
17274 pub fn display_to_pixel_point(
17275 &self,
17276 source: DisplayPoint,
17277 editor_snapshot: &EditorSnapshot,
17278 window: &mut Window,
17279 ) -> Option<gpui::Point<Pixels>> {
17280 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
17281 let text_layout_details = self.text_layout_details(window);
17282 let scroll_top = text_layout_details
17283 .scroll_anchor
17284 .scroll_position(editor_snapshot)
17285 .y;
17286
17287 if source.row().as_f32() < scroll_top.floor() {
17288 return None;
17289 }
17290 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
17291 let source_y = line_height * (source.row().as_f32() - scroll_top);
17292 Some(gpui::Point::new(source_x, source_y))
17293 }
17294
17295 pub fn has_visible_completions_menu(&self) -> bool {
17296 !self.edit_prediction_preview_is_active()
17297 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
17298 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
17299 })
17300 }
17301
17302 pub fn register_addon<T: Addon>(&mut self, instance: T) {
17303 self.addons
17304 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
17305 }
17306
17307 pub fn unregister_addon<T: Addon>(&mut self) {
17308 self.addons.remove(&std::any::TypeId::of::<T>());
17309 }
17310
17311 pub fn addon<T: Addon>(&self) -> Option<&T> {
17312 let type_id = std::any::TypeId::of::<T>();
17313 self.addons
17314 .get(&type_id)
17315 .and_then(|item| item.to_any().downcast_ref::<T>())
17316 }
17317
17318 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
17319 let text_layout_details = self.text_layout_details(window);
17320 let style = &text_layout_details.editor_style;
17321 let font_id = window.text_system().resolve_font(&style.text.font());
17322 let font_size = style.text.font_size.to_pixels(window.rem_size());
17323 let line_height = style.text.line_height_in_pixels(window.rem_size());
17324 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
17325
17326 gpui::Size::new(em_width, line_height)
17327 }
17328
17329 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
17330 self.load_diff_task.clone()
17331 }
17332
17333 fn read_metadata_from_db(
17334 &mut self,
17335 item_id: u64,
17336 workspace_id: WorkspaceId,
17337 window: &mut Window,
17338 cx: &mut Context<Editor>,
17339 ) {
17340 if self.is_singleton(cx)
17341 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
17342 {
17343 let buffer_snapshot = OnceCell::new();
17344
17345 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
17346 if !selections.is_empty() {
17347 let snapshot =
17348 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
17349 self.change_selections(None, window, cx, |s| {
17350 s.select_ranges(selections.into_iter().map(|(start, end)| {
17351 snapshot.clip_offset(start, Bias::Left)
17352 ..snapshot.clip_offset(end, Bias::Right)
17353 }));
17354 });
17355 }
17356 };
17357
17358 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
17359 if !folds.is_empty() {
17360 let snapshot =
17361 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
17362 self.fold_ranges(
17363 folds
17364 .into_iter()
17365 .map(|(start, end)| {
17366 snapshot.clip_offset(start, Bias::Left)
17367 ..snapshot.clip_offset(end, Bias::Right)
17368 })
17369 .collect(),
17370 false,
17371 window,
17372 cx,
17373 );
17374 }
17375 }
17376 }
17377
17378 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
17379 }
17380}
17381
17382fn insert_extra_newline_brackets(
17383 buffer: &MultiBufferSnapshot,
17384 range: Range<usize>,
17385 language: &language::LanguageScope,
17386) -> bool {
17387 let leading_whitespace_len = buffer
17388 .reversed_chars_at(range.start)
17389 .take_while(|c| c.is_whitespace() && *c != '\n')
17390 .map(|c| c.len_utf8())
17391 .sum::<usize>();
17392 let trailing_whitespace_len = buffer
17393 .chars_at(range.end)
17394 .take_while(|c| c.is_whitespace() && *c != '\n')
17395 .map(|c| c.len_utf8())
17396 .sum::<usize>();
17397 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
17398
17399 language.brackets().any(|(pair, enabled)| {
17400 let pair_start = pair.start.trim_end();
17401 let pair_end = pair.end.trim_start();
17402
17403 enabled
17404 && pair.newline
17405 && buffer.contains_str_at(range.end, pair_end)
17406 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
17407 })
17408}
17409
17410fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
17411 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
17412 [(buffer, range, _)] => (*buffer, range.clone()),
17413 _ => return false,
17414 };
17415 let pair = {
17416 let mut result: Option<BracketMatch> = None;
17417
17418 for pair in buffer
17419 .all_bracket_ranges(range.clone())
17420 .filter(move |pair| {
17421 pair.open_range.start <= range.start && pair.close_range.end >= range.end
17422 })
17423 {
17424 let len = pair.close_range.end - pair.open_range.start;
17425
17426 if let Some(existing) = &result {
17427 let existing_len = existing.close_range.end - existing.open_range.start;
17428 if len > existing_len {
17429 continue;
17430 }
17431 }
17432
17433 result = Some(pair);
17434 }
17435
17436 result
17437 };
17438 let Some(pair) = pair else {
17439 return false;
17440 };
17441 pair.newline_only
17442 && buffer
17443 .chars_for_range(pair.open_range.end..range.start)
17444 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
17445 .all(|c| c.is_whitespace() && c != '\n')
17446}
17447
17448fn get_uncommitted_diff_for_buffer(
17449 project: &Entity<Project>,
17450 buffers: impl IntoIterator<Item = Entity<Buffer>>,
17451 buffer: Entity<MultiBuffer>,
17452 cx: &mut App,
17453) -> Task<()> {
17454 let mut tasks = Vec::new();
17455 project.update(cx, |project, cx| {
17456 for buffer in buffers {
17457 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
17458 }
17459 });
17460 cx.spawn(async move |cx| {
17461 let diffs = future::join_all(tasks).await;
17462 buffer
17463 .update(cx, |buffer, cx| {
17464 for diff in diffs.into_iter().flatten() {
17465 buffer.add_diff(diff, cx);
17466 }
17467 })
17468 .ok();
17469 })
17470}
17471
17472fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
17473 let tab_size = tab_size.get() as usize;
17474 let mut width = offset;
17475
17476 for ch in text.chars() {
17477 width += if ch == '\t' {
17478 tab_size - (width % tab_size)
17479 } else {
17480 1
17481 };
17482 }
17483
17484 width - offset
17485}
17486
17487#[cfg(test)]
17488mod tests {
17489 use super::*;
17490
17491 #[test]
17492 fn test_string_size_with_expanded_tabs() {
17493 let nz = |val| NonZeroU32::new(val).unwrap();
17494 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
17495 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
17496 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
17497 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
17498 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
17499 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
17500 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
17501 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
17502 }
17503}
17504
17505/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
17506struct WordBreakingTokenizer<'a> {
17507 input: &'a str,
17508}
17509
17510impl<'a> WordBreakingTokenizer<'a> {
17511 fn new(input: &'a str) -> Self {
17512 Self { input }
17513 }
17514}
17515
17516fn is_char_ideographic(ch: char) -> bool {
17517 use unicode_script::Script::*;
17518 use unicode_script::UnicodeScript;
17519 matches!(ch.script(), Han | Tangut | Yi)
17520}
17521
17522fn is_grapheme_ideographic(text: &str) -> bool {
17523 text.chars().any(is_char_ideographic)
17524}
17525
17526fn is_grapheme_whitespace(text: &str) -> bool {
17527 text.chars().any(|x| x.is_whitespace())
17528}
17529
17530fn should_stay_with_preceding_ideograph(text: &str) -> bool {
17531 text.chars().next().map_or(false, |ch| {
17532 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
17533 })
17534}
17535
17536#[derive(PartialEq, Eq, Debug, Clone, Copy)]
17537enum WordBreakToken<'a> {
17538 Word { token: &'a str, grapheme_len: usize },
17539 InlineWhitespace { token: &'a str, grapheme_len: usize },
17540 Newline,
17541}
17542
17543impl<'a> Iterator for WordBreakingTokenizer<'a> {
17544 /// Yields a span, the count of graphemes in the token, and whether it was
17545 /// whitespace. Note that it also breaks at word boundaries.
17546 type Item = WordBreakToken<'a>;
17547
17548 fn next(&mut self) -> Option<Self::Item> {
17549 use unicode_segmentation::UnicodeSegmentation;
17550 if self.input.is_empty() {
17551 return None;
17552 }
17553
17554 let mut iter = self.input.graphemes(true).peekable();
17555 let mut offset = 0;
17556 let mut grapheme_len = 0;
17557 if let Some(first_grapheme) = iter.next() {
17558 let is_newline = first_grapheme == "\n";
17559 let is_whitespace = is_grapheme_whitespace(first_grapheme);
17560 offset += first_grapheme.len();
17561 grapheme_len += 1;
17562 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
17563 if let Some(grapheme) = iter.peek().copied() {
17564 if should_stay_with_preceding_ideograph(grapheme) {
17565 offset += grapheme.len();
17566 grapheme_len += 1;
17567 }
17568 }
17569 } else {
17570 let mut words = self.input[offset..].split_word_bound_indices().peekable();
17571 let mut next_word_bound = words.peek().copied();
17572 if next_word_bound.map_or(false, |(i, _)| i == 0) {
17573 next_word_bound = words.next();
17574 }
17575 while let Some(grapheme) = iter.peek().copied() {
17576 if next_word_bound.map_or(false, |(i, _)| i == offset) {
17577 break;
17578 };
17579 if is_grapheme_whitespace(grapheme) != is_whitespace
17580 || (grapheme == "\n") != is_newline
17581 {
17582 break;
17583 };
17584 offset += grapheme.len();
17585 grapheme_len += 1;
17586 iter.next();
17587 }
17588 }
17589 let token = &self.input[..offset];
17590 self.input = &self.input[offset..];
17591 if token == "\n" {
17592 Some(WordBreakToken::Newline)
17593 } else if is_whitespace {
17594 Some(WordBreakToken::InlineWhitespace {
17595 token,
17596 grapheme_len,
17597 })
17598 } else {
17599 Some(WordBreakToken::Word {
17600 token,
17601 grapheme_len,
17602 })
17603 }
17604 } else {
17605 None
17606 }
17607 }
17608}
17609
17610#[test]
17611fn test_word_breaking_tokenizer() {
17612 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
17613 ("", &[]),
17614 (" ", &[whitespace(" ", 2)]),
17615 ("Ʒ", &[word("Ʒ", 1)]),
17616 ("Ǽ", &[word("Ǽ", 1)]),
17617 ("⋑", &[word("⋑", 1)]),
17618 ("⋑⋑", &[word("⋑⋑", 2)]),
17619 (
17620 "原理,进而",
17621 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
17622 ),
17623 (
17624 "hello world",
17625 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
17626 ),
17627 (
17628 "hello, world",
17629 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
17630 ),
17631 (
17632 " hello world",
17633 &[
17634 whitespace(" ", 2),
17635 word("hello", 5),
17636 whitespace(" ", 1),
17637 word("world", 5),
17638 ],
17639 ),
17640 (
17641 "这是什么 \n 钢笔",
17642 &[
17643 word("这", 1),
17644 word("是", 1),
17645 word("什", 1),
17646 word("么", 1),
17647 whitespace(" ", 1),
17648 newline(),
17649 whitespace(" ", 1),
17650 word("钢", 1),
17651 word("笔", 1),
17652 ],
17653 ),
17654 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
17655 ];
17656
17657 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
17658 WordBreakToken::Word {
17659 token,
17660 grapheme_len,
17661 }
17662 }
17663
17664 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
17665 WordBreakToken::InlineWhitespace {
17666 token,
17667 grapheme_len,
17668 }
17669 }
17670
17671 fn newline() -> WordBreakToken<'static> {
17672 WordBreakToken::Newline
17673 }
17674
17675 for (input, result) in tests {
17676 assert_eq!(
17677 WordBreakingTokenizer::new(input)
17678 .collect::<Vec<_>>()
17679 .as_slice(),
17680 *result,
17681 );
17682 }
17683}
17684
17685fn wrap_with_prefix(
17686 line_prefix: String,
17687 unwrapped_text: String,
17688 wrap_column: usize,
17689 tab_size: NonZeroU32,
17690 preserve_existing_whitespace: bool,
17691) -> String {
17692 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
17693 let mut wrapped_text = String::new();
17694 let mut current_line = line_prefix.clone();
17695
17696 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
17697 let mut current_line_len = line_prefix_len;
17698 let mut in_whitespace = false;
17699 for token in tokenizer {
17700 let have_preceding_whitespace = in_whitespace;
17701 match token {
17702 WordBreakToken::Word {
17703 token,
17704 grapheme_len,
17705 } => {
17706 in_whitespace = false;
17707 if current_line_len + grapheme_len > wrap_column
17708 && current_line_len != line_prefix_len
17709 {
17710 wrapped_text.push_str(current_line.trim_end());
17711 wrapped_text.push('\n');
17712 current_line.truncate(line_prefix.len());
17713 current_line_len = line_prefix_len;
17714 }
17715 current_line.push_str(token);
17716 current_line_len += grapheme_len;
17717 }
17718 WordBreakToken::InlineWhitespace {
17719 mut token,
17720 mut grapheme_len,
17721 } => {
17722 in_whitespace = true;
17723 if have_preceding_whitespace && !preserve_existing_whitespace {
17724 continue;
17725 }
17726 if !preserve_existing_whitespace {
17727 token = " ";
17728 grapheme_len = 1;
17729 }
17730 if current_line_len + grapheme_len > wrap_column {
17731 wrapped_text.push_str(current_line.trim_end());
17732 wrapped_text.push('\n');
17733 current_line.truncate(line_prefix.len());
17734 current_line_len = line_prefix_len;
17735 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
17736 current_line.push_str(token);
17737 current_line_len += grapheme_len;
17738 }
17739 }
17740 WordBreakToken::Newline => {
17741 in_whitespace = true;
17742 if preserve_existing_whitespace {
17743 wrapped_text.push_str(current_line.trim_end());
17744 wrapped_text.push('\n');
17745 current_line.truncate(line_prefix.len());
17746 current_line_len = line_prefix_len;
17747 } else if have_preceding_whitespace {
17748 continue;
17749 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
17750 {
17751 wrapped_text.push_str(current_line.trim_end());
17752 wrapped_text.push('\n');
17753 current_line.truncate(line_prefix.len());
17754 current_line_len = line_prefix_len;
17755 } else if current_line_len != line_prefix_len {
17756 current_line.push(' ');
17757 current_line_len += 1;
17758 }
17759 }
17760 }
17761 }
17762
17763 if !current_line.is_empty() {
17764 wrapped_text.push_str(¤t_line);
17765 }
17766 wrapped_text
17767}
17768
17769#[test]
17770fn test_wrap_with_prefix() {
17771 assert_eq!(
17772 wrap_with_prefix(
17773 "# ".to_string(),
17774 "abcdefg".to_string(),
17775 4,
17776 NonZeroU32::new(4).unwrap(),
17777 false,
17778 ),
17779 "# abcdefg"
17780 );
17781 assert_eq!(
17782 wrap_with_prefix(
17783 "".to_string(),
17784 "\thello world".to_string(),
17785 8,
17786 NonZeroU32::new(4).unwrap(),
17787 false,
17788 ),
17789 "hello\nworld"
17790 );
17791 assert_eq!(
17792 wrap_with_prefix(
17793 "// ".to_string(),
17794 "xx \nyy zz aa bb cc".to_string(),
17795 12,
17796 NonZeroU32::new(4).unwrap(),
17797 false,
17798 ),
17799 "// xx yy zz\n// aa bb cc"
17800 );
17801 assert_eq!(
17802 wrap_with_prefix(
17803 String::new(),
17804 "这是什么 \n 钢笔".to_string(),
17805 3,
17806 NonZeroU32::new(4).unwrap(),
17807 false,
17808 ),
17809 "这是什\n么 钢\n笔"
17810 );
17811}
17812
17813pub trait CollaborationHub {
17814 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
17815 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
17816 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
17817}
17818
17819impl CollaborationHub for Entity<Project> {
17820 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
17821 self.read(cx).collaborators()
17822 }
17823
17824 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
17825 self.read(cx).user_store().read(cx).participant_indices()
17826 }
17827
17828 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
17829 let this = self.read(cx);
17830 let user_ids = this.collaborators().values().map(|c| c.user_id);
17831 this.user_store().read_with(cx, |user_store, cx| {
17832 user_store.participant_names(user_ids, cx)
17833 })
17834 }
17835}
17836
17837pub trait SemanticsProvider {
17838 fn hover(
17839 &self,
17840 buffer: &Entity<Buffer>,
17841 position: text::Anchor,
17842 cx: &mut App,
17843 ) -> Option<Task<Vec<project::Hover>>>;
17844
17845 fn inlay_hints(
17846 &self,
17847 buffer_handle: Entity<Buffer>,
17848 range: Range<text::Anchor>,
17849 cx: &mut App,
17850 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
17851
17852 fn resolve_inlay_hint(
17853 &self,
17854 hint: InlayHint,
17855 buffer_handle: Entity<Buffer>,
17856 server_id: LanguageServerId,
17857 cx: &mut App,
17858 ) -> Option<Task<anyhow::Result<InlayHint>>>;
17859
17860 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
17861
17862 fn document_highlights(
17863 &self,
17864 buffer: &Entity<Buffer>,
17865 position: text::Anchor,
17866 cx: &mut App,
17867 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
17868
17869 fn definitions(
17870 &self,
17871 buffer: &Entity<Buffer>,
17872 position: text::Anchor,
17873 kind: GotoDefinitionKind,
17874 cx: &mut App,
17875 ) -> Option<Task<Result<Vec<LocationLink>>>>;
17876
17877 fn range_for_rename(
17878 &self,
17879 buffer: &Entity<Buffer>,
17880 position: text::Anchor,
17881 cx: &mut App,
17882 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
17883
17884 fn perform_rename(
17885 &self,
17886 buffer: &Entity<Buffer>,
17887 position: text::Anchor,
17888 new_name: String,
17889 cx: &mut App,
17890 ) -> Option<Task<Result<ProjectTransaction>>>;
17891}
17892
17893pub trait CompletionProvider {
17894 fn completions(
17895 &self,
17896 excerpt_id: ExcerptId,
17897 buffer: &Entity<Buffer>,
17898 buffer_position: text::Anchor,
17899 trigger: CompletionContext,
17900 window: &mut Window,
17901 cx: &mut Context<Editor>,
17902 ) -> Task<Result<Option<Vec<Completion>>>>;
17903
17904 fn resolve_completions(
17905 &self,
17906 buffer: Entity<Buffer>,
17907 completion_indices: Vec<usize>,
17908 completions: Rc<RefCell<Box<[Completion]>>>,
17909 cx: &mut Context<Editor>,
17910 ) -> Task<Result<bool>>;
17911
17912 fn apply_additional_edits_for_completion(
17913 &self,
17914 _buffer: Entity<Buffer>,
17915 _completions: Rc<RefCell<Box<[Completion]>>>,
17916 _completion_index: usize,
17917 _push_to_history: bool,
17918 _cx: &mut Context<Editor>,
17919 ) -> Task<Result<Option<language::Transaction>>> {
17920 Task::ready(Ok(None))
17921 }
17922
17923 fn is_completion_trigger(
17924 &self,
17925 buffer: &Entity<Buffer>,
17926 position: language::Anchor,
17927 text: &str,
17928 trigger_in_words: bool,
17929 cx: &mut Context<Editor>,
17930 ) -> bool;
17931
17932 fn sort_completions(&self) -> bool {
17933 true
17934 }
17935}
17936
17937pub trait CodeActionProvider {
17938 fn id(&self) -> Arc<str>;
17939
17940 fn code_actions(
17941 &self,
17942 buffer: &Entity<Buffer>,
17943 range: Range<text::Anchor>,
17944 window: &mut Window,
17945 cx: &mut App,
17946 ) -> Task<Result<Vec<CodeAction>>>;
17947
17948 fn apply_code_action(
17949 &self,
17950 buffer_handle: Entity<Buffer>,
17951 action: CodeAction,
17952 excerpt_id: ExcerptId,
17953 push_to_history: bool,
17954 window: &mut Window,
17955 cx: &mut App,
17956 ) -> Task<Result<ProjectTransaction>>;
17957}
17958
17959impl CodeActionProvider for Entity<Project> {
17960 fn id(&self) -> Arc<str> {
17961 "project".into()
17962 }
17963
17964 fn code_actions(
17965 &self,
17966 buffer: &Entity<Buffer>,
17967 range: Range<text::Anchor>,
17968 _window: &mut Window,
17969 cx: &mut App,
17970 ) -> Task<Result<Vec<CodeAction>>> {
17971 self.update(cx, |project, cx| {
17972 let code_lens = project.code_lens(buffer, range.clone(), cx);
17973 let code_actions = project.code_actions(buffer, range, None, cx);
17974 cx.background_spawn(async move {
17975 let (code_lens, code_actions) = join(code_lens, code_actions).await;
17976 Ok(code_lens
17977 .context("code lens fetch")?
17978 .into_iter()
17979 .chain(code_actions.context("code action fetch")?)
17980 .collect())
17981 })
17982 })
17983 }
17984
17985 fn apply_code_action(
17986 &self,
17987 buffer_handle: Entity<Buffer>,
17988 action: CodeAction,
17989 _excerpt_id: ExcerptId,
17990 push_to_history: bool,
17991 _window: &mut Window,
17992 cx: &mut App,
17993 ) -> Task<Result<ProjectTransaction>> {
17994 self.update(cx, |project, cx| {
17995 project.apply_code_action(buffer_handle, action, push_to_history, cx)
17996 })
17997 }
17998}
17999
18000fn snippet_completions(
18001 project: &Project,
18002 buffer: &Entity<Buffer>,
18003 buffer_position: text::Anchor,
18004 cx: &mut App,
18005) -> Task<Result<Vec<Completion>>> {
18006 let language = buffer.read(cx).language_at(buffer_position);
18007 let language_name = language.as_ref().map(|language| language.lsp_id());
18008 let snippet_store = project.snippets().read(cx);
18009 let snippets = snippet_store.snippets_for(language_name, cx);
18010
18011 if snippets.is_empty() {
18012 return Task::ready(Ok(vec![]));
18013 }
18014 let snapshot = buffer.read(cx).text_snapshot();
18015 let chars: String = snapshot
18016 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
18017 .collect();
18018
18019 let scope = language.map(|language| language.default_scope());
18020 let executor = cx.background_executor().clone();
18021
18022 cx.background_spawn(async move {
18023 let classifier = CharClassifier::new(scope).for_completion(true);
18024 let mut last_word = chars
18025 .chars()
18026 .take_while(|c| classifier.is_word(*c))
18027 .collect::<String>();
18028 last_word = last_word.chars().rev().collect();
18029
18030 if last_word.is_empty() {
18031 return Ok(vec![]);
18032 }
18033
18034 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
18035 let to_lsp = |point: &text::Anchor| {
18036 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
18037 point_to_lsp(end)
18038 };
18039 let lsp_end = to_lsp(&buffer_position);
18040
18041 let candidates = snippets
18042 .iter()
18043 .enumerate()
18044 .flat_map(|(ix, snippet)| {
18045 snippet
18046 .prefix
18047 .iter()
18048 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
18049 })
18050 .collect::<Vec<StringMatchCandidate>>();
18051
18052 let mut matches = fuzzy::match_strings(
18053 &candidates,
18054 &last_word,
18055 last_word.chars().any(|c| c.is_uppercase()),
18056 100,
18057 &Default::default(),
18058 executor,
18059 )
18060 .await;
18061
18062 // Remove all candidates where the query's start does not match the start of any word in the candidate
18063 if let Some(query_start) = last_word.chars().next() {
18064 matches.retain(|string_match| {
18065 split_words(&string_match.string).any(|word| {
18066 // Check that the first codepoint of the word as lowercase matches the first
18067 // codepoint of the query as lowercase
18068 word.chars()
18069 .flat_map(|codepoint| codepoint.to_lowercase())
18070 .zip(query_start.to_lowercase())
18071 .all(|(word_cp, query_cp)| word_cp == query_cp)
18072 })
18073 });
18074 }
18075
18076 let matched_strings = matches
18077 .into_iter()
18078 .map(|m| m.string)
18079 .collect::<HashSet<_>>();
18080
18081 let result: Vec<Completion> = snippets
18082 .into_iter()
18083 .filter_map(|snippet| {
18084 let matching_prefix = snippet
18085 .prefix
18086 .iter()
18087 .find(|prefix| matched_strings.contains(*prefix))?;
18088 let start = as_offset - last_word.len();
18089 let start = snapshot.anchor_before(start);
18090 let range = start..buffer_position;
18091 let lsp_start = to_lsp(&start);
18092 let lsp_range = lsp::Range {
18093 start: lsp_start,
18094 end: lsp_end,
18095 };
18096 Some(Completion {
18097 old_range: range,
18098 new_text: snippet.body.clone(),
18099 source: CompletionSource::Lsp {
18100 server_id: LanguageServerId(usize::MAX),
18101 resolved: true,
18102 lsp_completion: Box::new(lsp::CompletionItem {
18103 label: snippet.prefix.first().unwrap().clone(),
18104 kind: Some(CompletionItemKind::SNIPPET),
18105 label_details: snippet.description.as_ref().map(|description| {
18106 lsp::CompletionItemLabelDetails {
18107 detail: Some(description.clone()),
18108 description: None,
18109 }
18110 }),
18111 insert_text_format: Some(InsertTextFormat::SNIPPET),
18112 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18113 lsp::InsertReplaceEdit {
18114 new_text: snippet.body.clone(),
18115 insert: lsp_range,
18116 replace: lsp_range,
18117 },
18118 )),
18119 filter_text: Some(snippet.body.clone()),
18120 sort_text: Some(char::MAX.to_string()),
18121 ..lsp::CompletionItem::default()
18122 }),
18123 lsp_defaults: None,
18124 },
18125 label: CodeLabel {
18126 text: matching_prefix.clone(),
18127 runs: Vec::new(),
18128 filter_range: 0..matching_prefix.len(),
18129 },
18130 icon_path: None,
18131 documentation: snippet
18132 .description
18133 .clone()
18134 .map(|description| CompletionDocumentation::SingleLine(description.into())),
18135 confirm: None,
18136 })
18137 })
18138 .collect();
18139
18140 Ok(result)
18141 })
18142}
18143
18144impl CompletionProvider for Entity<Project> {
18145 fn completions(
18146 &self,
18147 _excerpt_id: ExcerptId,
18148 buffer: &Entity<Buffer>,
18149 buffer_position: text::Anchor,
18150 options: CompletionContext,
18151 _window: &mut Window,
18152 cx: &mut Context<Editor>,
18153 ) -> Task<Result<Option<Vec<Completion>>>> {
18154 self.update(cx, |project, cx| {
18155 let snippets = snippet_completions(project, buffer, buffer_position, cx);
18156 let project_completions = project.completions(buffer, buffer_position, options, cx);
18157 cx.background_spawn(async move {
18158 let snippets_completions = snippets.await?;
18159 match project_completions.await? {
18160 Some(mut completions) => {
18161 completions.extend(snippets_completions);
18162 Ok(Some(completions))
18163 }
18164 None => {
18165 if snippets_completions.is_empty() {
18166 Ok(None)
18167 } else {
18168 Ok(Some(snippets_completions))
18169 }
18170 }
18171 }
18172 })
18173 })
18174 }
18175
18176 fn resolve_completions(
18177 &self,
18178 buffer: Entity<Buffer>,
18179 completion_indices: Vec<usize>,
18180 completions: Rc<RefCell<Box<[Completion]>>>,
18181 cx: &mut Context<Editor>,
18182 ) -> Task<Result<bool>> {
18183 self.update(cx, |project, cx| {
18184 project.lsp_store().update(cx, |lsp_store, cx| {
18185 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
18186 })
18187 })
18188 }
18189
18190 fn apply_additional_edits_for_completion(
18191 &self,
18192 buffer: Entity<Buffer>,
18193 completions: Rc<RefCell<Box<[Completion]>>>,
18194 completion_index: usize,
18195 push_to_history: bool,
18196 cx: &mut Context<Editor>,
18197 ) -> Task<Result<Option<language::Transaction>>> {
18198 self.update(cx, |project, cx| {
18199 project.lsp_store().update(cx, |lsp_store, cx| {
18200 lsp_store.apply_additional_edits_for_completion(
18201 buffer,
18202 completions,
18203 completion_index,
18204 push_to_history,
18205 cx,
18206 )
18207 })
18208 })
18209 }
18210
18211 fn is_completion_trigger(
18212 &self,
18213 buffer: &Entity<Buffer>,
18214 position: language::Anchor,
18215 text: &str,
18216 trigger_in_words: bool,
18217 cx: &mut Context<Editor>,
18218 ) -> bool {
18219 let mut chars = text.chars();
18220 let char = if let Some(char) = chars.next() {
18221 char
18222 } else {
18223 return false;
18224 };
18225 if chars.next().is_some() {
18226 return false;
18227 }
18228
18229 let buffer = buffer.read(cx);
18230 let snapshot = buffer.snapshot();
18231 if !snapshot.settings_at(position, cx).show_completions_on_input {
18232 return false;
18233 }
18234 let classifier = snapshot.char_classifier_at(position).for_completion(true);
18235 if trigger_in_words && classifier.is_word(char) {
18236 return true;
18237 }
18238
18239 buffer.completion_triggers().contains(text)
18240 }
18241}
18242
18243impl SemanticsProvider for Entity<Project> {
18244 fn hover(
18245 &self,
18246 buffer: &Entity<Buffer>,
18247 position: text::Anchor,
18248 cx: &mut App,
18249 ) -> Option<Task<Vec<project::Hover>>> {
18250 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
18251 }
18252
18253 fn document_highlights(
18254 &self,
18255 buffer: &Entity<Buffer>,
18256 position: text::Anchor,
18257 cx: &mut App,
18258 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
18259 Some(self.update(cx, |project, cx| {
18260 project.document_highlights(buffer, position, cx)
18261 }))
18262 }
18263
18264 fn definitions(
18265 &self,
18266 buffer: &Entity<Buffer>,
18267 position: text::Anchor,
18268 kind: GotoDefinitionKind,
18269 cx: &mut App,
18270 ) -> Option<Task<Result<Vec<LocationLink>>>> {
18271 Some(self.update(cx, |project, cx| match kind {
18272 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
18273 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
18274 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
18275 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
18276 }))
18277 }
18278
18279 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
18280 // TODO: make this work for remote projects
18281 self.update(cx, |this, cx| {
18282 buffer.update(cx, |buffer, cx| {
18283 this.any_language_server_supports_inlay_hints(buffer, cx)
18284 })
18285 })
18286 }
18287
18288 fn inlay_hints(
18289 &self,
18290 buffer_handle: Entity<Buffer>,
18291 range: Range<text::Anchor>,
18292 cx: &mut App,
18293 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
18294 Some(self.update(cx, |project, cx| {
18295 project.inlay_hints(buffer_handle, range, cx)
18296 }))
18297 }
18298
18299 fn resolve_inlay_hint(
18300 &self,
18301 hint: InlayHint,
18302 buffer_handle: Entity<Buffer>,
18303 server_id: LanguageServerId,
18304 cx: &mut App,
18305 ) -> Option<Task<anyhow::Result<InlayHint>>> {
18306 Some(self.update(cx, |project, cx| {
18307 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
18308 }))
18309 }
18310
18311 fn range_for_rename(
18312 &self,
18313 buffer: &Entity<Buffer>,
18314 position: text::Anchor,
18315 cx: &mut App,
18316 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
18317 Some(self.update(cx, |project, cx| {
18318 let buffer = buffer.clone();
18319 let task = project.prepare_rename(buffer.clone(), position, cx);
18320 cx.spawn(async move |_, cx| {
18321 Ok(match task.await? {
18322 PrepareRenameResponse::Success(range) => Some(range),
18323 PrepareRenameResponse::InvalidPosition => None,
18324 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
18325 // Fallback on using TreeSitter info to determine identifier range
18326 buffer.update(cx, |buffer, _| {
18327 let snapshot = buffer.snapshot();
18328 let (range, kind) = snapshot.surrounding_word(position);
18329 if kind != Some(CharKind::Word) {
18330 return None;
18331 }
18332 Some(
18333 snapshot.anchor_before(range.start)
18334 ..snapshot.anchor_after(range.end),
18335 )
18336 })?
18337 }
18338 })
18339 })
18340 }))
18341 }
18342
18343 fn perform_rename(
18344 &self,
18345 buffer: &Entity<Buffer>,
18346 position: text::Anchor,
18347 new_name: String,
18348 cx: &mut App,
18349 ) -> Option<Task<Result<ProjectTransaction>>> {
18350 Some(self.update(cx, |project, cx| {
18351 project.perform_rename(buffer.clone(), position, new_name, cx)
18352 }))
18353 }
18354}
18355
18356fn inlay_hint_settings(
18357 location: Anchor,
18358 snapshot: &MultiBufferSnapshot,
18359 cx: &mut Context<Editor>,
18360) -> InlayHintSettings {
18361 let file = snapshot.file_at(location);
18362 let language = snapshot.language_at(location).map(|l| l.name());
18363 language_settings(language, file, cx).inlay_hints
18364}
18365
18366fn consume_contiguous_rows(
18367 contiguous_row_selections: &mut Vec<Selection<Point>>,
18368 selection: &Selection<Point>,
18369 display_map: &DisplaySnapshot,
18370 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
18371) -> (MultiBufferRow, MultiBufferRow) {
18372 contiguous_row_selections.push(selection.clone());
18373 let start_row = MultiBufferRow(selection.start.row);
18374 let mut end_row = ending_row(selection, display_map);
18375
18376 while let Some(next_selection) = selections.peek() {
18377 if next_selection.start.row <= end_row.0 {
18378 end_row = ending_row(next_selection, display_map);
18379 contiguous_row_selections.push(selections.next().unwrap().clone());
18380 } else {
18381 break;
18382 }
18383 }
18384 (start_row, end_row)
18385}
18386
18387fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
18388 if next_selection.end.column > 0 || next_selection.is_empty() {
18389 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
18390 } else {
18391 MultiBufferRow(next_selection.end.row)
18392 }
18393}
18394
18395impl EditorSnapshot {
18396 pub fn remote_selections_in_range<'a>(
18397 &'a self,
18398 range: &'a Range<Anchor>,
18399 collaboration_hub: &dyn CollaborationHub,
18400 cx: &'a App,
18401 ) -> impl 'a + Iterator<Item = RemoteSelection> {
18402 let participant_names = collaboration_hub.user_names(cx);
18403 let participant_indices = collaboration_hub.user_participant_indices(cx);
18404 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
18405 let collaborators_by_replica_id = collaborators_by_peer_id
18406 .iter()
18407 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
18408 .collect::<HashMap<_, _>>();
18409 self.buffer_snapshot
18410 .selections_in_range(range, false)
18411 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
18412 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
18413 let participant_index = participant_indices.get(&collaborator.user_id).copied();
18414 let user_name = participant_names.get(&collaborator.user_id).cloned();
18415 Some(RemoteSelection {
18416 replica_id,
18417 selection,
18418 cursor_shape,
18419 line_mode,
18420 participant_index,
18421 peer_id: collaborator.peer_id,
18422 user_name,
18423 })
18424 })
18425 }
18426
18427 pub fn hunks_for_ranges(
18428 &self,
18429 ranges: impl IntoIterator<Item = Range<Point>>,
18430 ) -> Vec<MultiBufferDiffHunk> {
18431 let mut hunks = Vec::new();
18432 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
18433 HashMap::default();
18434 for query_range in ranges {
18435 let query_rows =
18436 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
18437 for hunk in self.buffer_snapshot.diff_hunks_in_range(
18438 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
18439 ) {
18440 // Include deleted hunks that are adjacent to the query range, because
18441 // otherwise they would be missed.
18442 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
18443 if hunk.status().is_deleted() {
18444 intersects_range |= hunk.row_range.start == query_rows.end;
18445 intersects_range |= hunk.row_range.end == query_rows.start;
18446 }
18447 if intersects_range {
18448 if !processed_buffer_rows
18449 .entry(hunk.buffer_id)
18450 .or_default()
18451 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
18452 {
18453 continue;
18454 }
18455 hunks.push(hunk);
18456 }
18457 }
18458 }
18459
18460 hunks
18461 }
18462
18463 fn display_diff_hunks_for_rows<'a>(
18464 &'a self,
18465 display_rows: Range<DisplayRow>,
18466 folded_buffers: &'a HashSet<BufferId>,
18467 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
18468 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
18469 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
18470
18471 self.buffer_snapshot
18472 .diff_hunks_in_range(buffer_start..buffer_end)
18473 .filter_map(|hunk| {
18474 if folded_buffers.contains(&hunk.buffer_id) {
18475 return None;
18476 }
18477
18478 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
18479 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
18480
18481 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
18482 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
18483
18484 let display_hunk = if hunk_display_start.column() != 0 {
18485 DisplayDiffHunk::Folded {
18486 display_row: hunk_display_start.row(),
18487 }
18488 } else {
18489 let mut end_row = hunk_display_end.row();
18490 if hunk_display_end.column() > 0 {
18491 end_row.0 += 1;
18492 }
18493 let is_created_file = hunk.is_created_file();
18494 DisplayDiffHunk::Unfolded {
18495 status: hunk.status(),
18496 diff_base_byte_range: hunk.diff_base_byte_range,
18497 display_row_range: hunk_display_start.row()..end_row,
18498 multi_buffer_range: Anchor::range_in_buffer(
18499 hunk.excerpt_id,
18500 hunk.buffer_id,
18501 hunk.buffer_range,
18502 ),
18503 is_created_file,
18504 }
18505 };
18506
18507 Some(display_hunk)
18508 })
18509 }
18510
18511 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
18512 self.display_snapshot.buffer_snapshot.language_at(position)
18513 }
18514
18515 pub fn is_focused(&self) -> bool {
18516 self.is_focused
18517 }
18518
18519 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
18520 self.placeholder_text.as_ref()
18521 }
18522
18523 pub fn scroll_position(&self) -> gpui::Point<f32> {
18524 self.scroll_anchor.scroll_position(&self.display_snapshot)
18525 }
18526
18527 fn gutter_dimensions(
18528 &self,
18529 font_id: FontId,
18530 font_size: Pixels,
18531 max_line_number_width: Pixels,
18532 cx: &App,
18533 ) -> Option<GutterDimensions> {
18534 if !self.show_gutter {
18535 return None;
18536 }
18537
18538 let descent = cx.text_system().descent(font_id, font_size);
18539 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
18540 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
18541
18542 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
18543 matches!(
18544 ProjectSettings::get_global(cx).git.git_gutter,
18545 Some(GitGutterSetting::TrackedFiles)
18546 )
18547 });
18548 let gutter_settings = EditorSettings::get_global(cx).gutter;
18549 let show_line_numbers = self
18550 .show_line_numbers
18551 .unwrap_or(gutter_settings.line_numbers);
18552 let line_gutter_width = if show_line_numbers {
18553 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
18554 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
18555 max_line_number_width.max(min_width_for_number_on_gutter)
18556 } else {
18557 0.0.into()
18558 };
18559
18560 let show_code_actions = self
18561 .show_code_actions
18562 .unwrap_or(gutter_settings.code_actions);
18563
18564 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
18565 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
18566
18567 let git_blame_entries_width =
18568 self.git_blame_gutter_max_author_length
18569 .map(|max_author_length| {
18570 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
18571
18572 /// The number of characters to dedicate to gaps and margins.
18573 const SPACING_WIDTH: usize = 4;
18574
18575 let max_char_count = max_author_length
18576 .min(GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED)
18577 + ::git::SHORT_SHA_LENGTH
18578 + MAX_RELATIVE_TIMESTAMP.len()
18579 + SPACING_WIDTH;
18580
18581 em_advance * max_char_count
18582 });
18583
18584 let is_singleton = self.buffer_snapshot.is_singleton();
18585
18586 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
18587 left_padding += if !is_singleton {
18588 em_width * 4.0
18589 } else if show_code_actions || show_runnables || show_breakpoints {
18590 em_width * 3.0
18591 } else if show_git_gutter && show_line_numbers {
18592 em_width * 2.0
18593 } else if show_git_gutter || show_line_numbers {
18594 em_width
18595 } else {
18596 px(0.)
18597 };
18598
18599 let shows_folds = is_singleton && gutter_settings.folds;
18600
18601 let right_padding = if shows_folds && show_line_numbers {
18602 em_width * 4.0
18603 } else if shows_folds || (!is_singleton && show_line_numbers) {
18604 em_width * 3.0
18605 } else if show_line_numbers {
18606 em_width
18607 } else {
18608 px(0.)
18609 };
18610
18611 Some(GutterDimensions {
18612 left_padding,
18613 right_padding,
18614 width: line_gutter_width + left_padding + right_padding,
18615 margin: -descent,
18616 git_blame_entries_width,
18617 })
18618 }
18619
18620 pub fn render_crease_toggle(
18621 &self,
18622 buffer_row: MultiBufferRow,
18623 row_contains_cursor: bool,
18624 editor: Entity<Editor>,
18625 window: &mut Window,
18626 cx: &mut App,
18627 ) -> Option<AnyElement> {
18628 let folded = self.is_line_folded(buffer_row);
18629 let mut is_foldable = false;
18630
18631 if let Some(crease) = self
18632 .crease_snapshot
18633 .query_row(buffer_row, &self.buffer_snapshot)
18634 {
18635 is_foldable = true;
18636 match crease {
18637 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
18638 if let Some(render_toggle) = render_toggle {
18639 let toggle_callback =
18640 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
18641 if folded {
18642 editor.update(cx, |editor, cx| {
18643 editor.fold_at(&crate::FoldAt { buffer_row }, window, cx)
18644 });
18645 } else {
18646 editor.update(cx, |editor, cx| {
18647 editor.unfold_at(
18648 &crate::UnfoldAt { buffer_row },
18649 window,
18650 cx,
18651 )
18652 });
18653 }
18654 });
18655 return Some((render_toggle)(
18656 buffer_row,
18657 folded,
18658 toggle_callback,
18659 window,
18660 cx,
18661 ));
18662 }
18663 }
18664 }
18665 }
18666
18667 is_foldable |= self.starts_indent(buffer_row);
18668
18669 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
18670 Some(
18671 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
18672 .toggle_state(folded)
18673 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
18674 if folded {
18675 this.unfold_at(&UnfoldAt { buffer_row }, window, cx);
18676 } else {
18677 this.fold_at(&FoldAt { buffer_row }, window, cx);
18678 }
18679 }))
18680 .into_any_element(),
18681 )
18682 } else {
18683 None
18684 }
18685 }
18686
18687 pub fn render_crease_trailer(
18688 &self,
18689 buffer_row: MultiBufferRow,
18690 window: &mut Window,
18691 cx: &mut App,
18692 ) -> Option<AnyElement> {
18693 let folded = self.is_line_folded(buffer_row);
18694 if let Crease::Inline { render_trailer, .. } = self
18695 .crease_snapshot
18696 .query_row(buffer_row, &self.buffer_snapshot)?
18697 {
18698 let render_trailer = render_trailer.as_ref()?;
18699 Some(render_trailer(buffer_row, folded, window, cx))
18700 } else {
18701 None
18702 }
18703 }
18704}
18705
18706impl Deref for EditorSnapshot {
18707 type Target = DisplaySnapshot;
18708
18709 fn deref(&self) -> &Self::Target {
18710 &self.display_snapshot
18711 }
18712}
18713
18714#[derive(Clone, Debug, PartialEq, Eq)]
18715pub enum EditorEvent {
18716 InputIgnored {
18717 text: Arc<str>,
18718 },
18719 InputHandled {
18720 utf16_range_to_replace: Option<Range<isize>>,
18721 text: Arc<str>,
18722 },
18723 ExcerptsAdded {
18724 buffer: Entity<Buffer>,
18725 predecessor: ExcerptId,
18726 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
18727 },
18728 ExcerptsRemoved {
18729 ids: Vec<ExcerptId>,
18730 },
18731 BufferFoldToggled {
18732 ids: Vec<ExcerptId>,
18733 folded: bool,
18734 },
18735 ExcerptsEdited {
18736 ids: Vec<ExcerptId>,
18737 },
18738 ExcerptsExpanded {
18739 ids: Vec<ExcerptId>,
18740 },
18741 BufferEdited,
18742 Edited {
18743 transaction_id: clock::Lamport,
18744 },
18745 Reparsed(BufferId),
18746 Focused,
18747 FocusedIn,
18748 Blurred,
18749 DirtyChanged,
18750 Saved,
18751 TitleChanged,
18752 DiffBaseChanged,
18753 SelectionsChanged {
18754 local: bool,
18755 },
18756 ScrollPositionChanged {
18757 local: bool,
18758 autoscroll: bool,
18759 },
18760 Closed,
18761 TransactionUndone {
18762 transaction_id: clock::Lamport,
18763 },
18764 TransactionBegun {
18765 transaction_id: clock::Lamport,
18766 },
18767 Reloaded,
18768 CursorShapeChanged,
18769 PushedToNavHistory {
18770 anchor: Anchor,
18771 is_deactivate: bool,
18772 },
18773}
18774
18775impl EventEmitter<EditorEvent> for Editor {}
18776
18777impl Focusable for Editor {
18778 fn focus_handle(&self, _cx: &App) -> FocusHandle {
18779 self.focus_handle.clone()
18780 }
18781}
18782
18783impl Render for Editor {
18784 fn render(&mut self, _: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
18785 let settings = ThemeSettings::get_global(cx);
18786
18787 let mut text_style = match self.mode {
18788 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
18789 color: cx.theme().colors().editor_foreground,
18790 font_family: settings.ui_font.family.clone(),
18791 font_features: settings.ui_font.features.clone(),
18792 font_fallbacks: settings.ui_font.fallbacks.clone(),
18793 font_size: rems(0.875).into(),
18794 font_weight: settings.ui_font.weight,
18795 line_height: relative(settings.buffer_line_height.value()),
18796 ..Default::default()
18797 },
18798 EditorMode::Full => TextStyle {
18799 color: cx.theme().colors().editor_foreground,
18800 font_family: settings.buffer_font.family.clone(),
18801 font_features: settings.buffer_font.features.clone(),
18802 font_fallbacks: settings.buffer_font.fallbacks.clone(),
18803 font_size: settings.buffer_font_size(cx).into(),
18804 font_weight: settings.buffer_font.weight,
18805 line_height: relative(settings.buffer_line_height.value()),
18806 ..Default::default()
18807 },
18808 };
18809 if let Some(text_style_refinement) = &self.text_style_refinement {
18810 text_style.refine(text_style_refinement)
18811 }
18812
18813 let background = match self.mode {
18814 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
18815 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
18816 EditorMode::Full => cx.theme().colors().editor_background,
18817 };
18818
18819 EditorElement::new(
18820 &cx.entity(),
18821 EditorStyle {
18822 background,
18823 local_player: cx.theme().players().local(),
18824 text: text_style,
18825 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
18826 syntax: cx.theme().syntax().clone(),
18827 status: cx.theme().status().clone(),
18828 inlay_hints_style: make_inlay_hints_style(cx),
18829 inline_completion_styles: make_suggestion_styles(cx),
18830 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
18831 },
18832 )
18833 }
18834}
18835
18836impl EntityInputHandler for Editor {
18837 fn text_for_range(
18838 &mut self,
18839 range_utf16: Range<usize>,
18840 adjusted_range: &mut Option<Range<usize>>,
18841 _: &mut Window,
18842 cx: &mut Context<Self>,
18843 ) -> Option<String> {
18844 let snapshot = self.buffer.read(cx).read(cx);
18845 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
18846 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
18847 if (start.0..end.0) != range_utf16 {
18848 adjusted_range.replace(start.0..end.0);
18849 }
18850 Some(snapshot.text_for_range(start..end).collect())
18851 }
18852
18853 fn selected_text_range(
18854 &mut self,
18855 ignore_disabled_input: bool,
18856 _: &mut Window,
18857 cx: &mut Context<Self>,
18858 ) -> Option<UTF16Selection> {
18859 // Prevent the IME menu from appearing when holding down an alphabetic key
18860 // while input is disabled.
18861 if !ignore_disabled_input && !self.input_enabled {
18862 return None;
18863 }
18864
18865 let selection = self.selections.newest::<OffsetUtf16>(cx);
18866 let range = selection.range();
18867
18868 Some(UTF16Selection {
18869 range: range.start.0..range.end.0,
18870 reversed: selection.reversed,
18871 })
18872 }
18873
18874 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
18875 let snapshot = self.buffer.read(cx).read(cx);
18876 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
18877 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
18878 }
18879
18880 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18881 self.clear_highlights::<InputComposition>(cx);
18882 self.ime_transaction.take();
18883 }
18884
18885 fn replace_text_in_range(
18886 &mut self,
18887 range_utf16: Option<Range<usize>>,
18888 text: &str,
18889 window: &mut Window,
18890 cx: &mut Context<Self>,
18891 ) {
18892 if !self.input_enabled {
18893 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18894 return;
18895 }
18896
18897 self.transact(window, cx, |this, window, cx| {
18898 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
18899 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
18900 Some(this.selection_replacement_ranges(range_utf16, cx))
18901 } else {
18902 this.marked_text_ranges(cx)
18903 };
18904
18905 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
18906 let newest_selection_id = this.selections.newest_anchor().id;
18907 this.selections
18908 .all::<OffsetUtf16>(cx)
18909 .iter()
18910 .zip(ranges_to_replace.iter())
18911 .find_map(|(selection, range)| {
18912 if selection.id == newest_selection_id {
18913 Some(
18914 (range.start.0 as isize - selection.head().0 as isize)
18915 ..(range.end.0 as isize - selection.head().0 as isize),
18916 )
18917 } else {
18918 None
18919 }
18920 })
18921 });
18922
18923 cx.emit(EditorEvent::InputHandled {
18924 utf16_range_to_replace: range_to_replace,
18925 text: text.into(),
18926 });
18927
18928 if let Some(new_selected_ranges) = new_selected_ranges {
18929 this.change_selections(None, window, cx, |selections| {
18930 selections.select_ranges(new_selected_ranges)
18931 });
18932 this.backspace(&Default::default(), window, cx);
18933 }
18934
18935 this.handle_input(text, window, cx);
18936 });
18937
18938 if let Some(transaction) = self.ime_transaction {
18939 self.buffer.update(cx, |buffer, cx| {
18940 buffer.group_until_transaction(transaction, cx);
18941 });
18942 }
18943
18944 self.unmark_text(window, cx);
18945 }
18946
18947 fn replace_and_mark_text_in_range(
18948 &mut self,
18949 range_utf16: Option<Range<usize>>,
18950 text: &str,
18951 new_selected_range_utf16: Option<Range<usize>>,
18952 window: &mut Window,
18953 cx: &mut Context<Self>,
18954 ) {
18955 if !self.input_enabled {
18956 return;
18957 }
18958
18959 let transaction = self.transact(window, cx, |this, window, cx| {
18960 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
18961 let snapshot = this.buffer.read(cx).read(cx);
18962 if let Some(relative_range_utf16) = range_utf16.as_ref() {
18963 for marked_range in &mut marked_ranges {
18964 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
18965 marked_range.start.0 += relative_range_utf16.start;
18966 marked_range.start =
18967 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
18968 marked_range.end =
18969 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
18970 }
18971 }
18972 Some(marked_ranges)
18973 } else if let Some(range_utf16) = range_utf16 {
18974 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
18975 Some(this.selection_replacement_ranges(range_utf16, cx))
18976 } else {
18977 None
18978 };
18979
18980 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
18981 let newest_selection_id = this.selections.newest_anchor().id;
18982 this.selections
18983 .all::<OffsetUtf16>(cx)
18984 .iter()
18985 .zip(ranges_to_replace.iter())
18986 .find_map(|(selection, range)| {
18987 if selection.id == newest_selection_id {
18988 Some(
18989 (range.start.0 as isize - selection.head().0 as isize)
18990 ..(range.end.0 as isize - selection.head().0 as isize),
18991 )
18992 } else {
18993 None
18994 }
18995 })
18996 });
18997
18998 cx.emit(EditorEvent::InputHandled {
18999 utf16_range_to_replace: range_to_replace,
19000 text: text.into(),
19001 });
19002
19003 if let Some(ranges) = ranges_to_replace {
19004 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
19005 }
19006
19007 let marked_ranges = {
19008 let snapshot = this.buffer.read(cx).read(cx);
19009 this.selections
19010 .disjoint_anchors()
19011 .iter()
19012 .map(|selection| {
19013 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
19014 })
19015 .collect::<Vec<_>>()
19016 };
19017
19018 if text.is_empty() {
19019 this.unmark_text(window, cx);
19020 } else {
19021 this.highlight_text::<InputComposition>(
19022 marked_ranges.clone(),
19023 HighlightStyle {
19024 underline: Some(UnderlineStyle {
19025 thickness: px(1.),
19026 color: None,
19027 wavy: false,
19028 }),
19029 ..Default::default()
19030 },
19031 cx,
19032 );
19033 }
19034
19035 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
19036 let use_autoclose = this.use_autoclose;
19037 let use_auto_surround = this.use_auto_surround;
19038 this.set_use_autoclose(false);
19039 this.set_use_auto_surround(false);
19040 this.handle_input(text, window, cx);
19041 this.set_use_autoclose(use_autoclose);
19042 this.set_use_auto_surround(use_auto_surround);
19043
19044 if let Some(new_selected_range) = new_selected_range_utf16 {
19045 let snapshot = this.buffer.read(cx).read(cx);
19046 let new_selected_ranges = marked_ranges
19047 .into_iter()
19048 .map(|marked_range| {
19049 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
19050 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
19051 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
19052 snapshot.clip_offset_utf16(new_start, Bias::Left)
19053 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
19054 })
19055 .collect::<Vec<_>>();
19056
19057 drop(snapshot);
19058 this.change_selections(None, window, cx, |selections| {
19059 selections.select_ranges(new_selected_ranges)
19060 });
19061 }
19062 });
19063
19064 self.ime_transaction = self.ime_transaction.or(transaction);
19065 if let Some(transaction) = self.ime_transaction {
19066 self.buffer.update(cx, |buffer, cx| {
19067 buffer.group_until_transaction(transaction, cx);
19068 });
19069 }
19070
19071 if self.text_highlights::<InputComposition>(cx).is_none() {
19072 self.ime_transaction.take();
19073 }
19074 }
19075
19076 fn bounds_for_range(
19077 &mut self,
19078 range_utf16: Range<usize>,
19079 element_bounds: gpui::Bounds<Pixels>,
19080 window: &mut Window,
19081 cx: &mut Context<Self>,
19082 ) -> Option<gpui::Bounds<Pixels>> {
19083 let text_layout_details = self.text_layout_details(window);
19084 let gpui::Size {
19085 width: em_width,
19086 height: line_height,
19087 } = self.character_size(window);
19088
19089 let snapshot = self.snapshot(window, cx);
19090 let scroll_position = snapshot.scroll_position();
19091 let scroll_left = scroll_position.x * em_width;
19092
19093 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
19094 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
19095 + self.gutter_dimensions.width
19096 + self.gutter_dimensions.margin;
19097 let y = line_height * (start.row().as_f32() - scroll_position.y);
19098
19099 Some(Bounds {
19100 origin: element_bounds.origin + point(x, y),
19101 size: size(em_width, line_height),
19102 })
19103 }
19104
19105 fn character_index_for_point(
19106 &mut self,
19107 point: gpui::Point<Pixels>,
19108 _window: &mut Window,
19109 _cx: &mut Context<Self>,
19110 ) -> Option<usize> {
19111 let position_map = self.last_position_map.as_ref()?;
19112 if !position_map.text_hitbox.contains(&point) {
19113 return None;
19114 }
19115 let display_point = position_map.point_for_position(point).previous_valid;
19116 let anchor = position_map
19117 .snapshot
19118 .display_point_to_anchor(display_point, Bias::Left);
19119 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
19120 Some(utf16_offset.0)
19121 }
19122}
19123
19124trait SelectionExt {
19125 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
19126 fn spanned_rows(
19127 &self,
19128 include_end_if_at_line_start: bool,
19129 map: &DisplaySnapshot,
19130 ) -> Range<MultiBufferRow>;
19131}
19132
19133impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
19134 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
19135 let start = self
19136 .start
19137 .to_point(&map.buffer_snapshot)
19138 .to_display_point(map);
19139 let end = self
19140 .end
19141 .to_point(&map.buffer_snapshot)
19142 .to_display_point(map);
19143 if self.reversed {
19144 end..start
19145 } else {
19146 start..end
19147 }
19148 }
19149
19150 fn spanned_rows(
19151 &self,
19152 include_end_if_at_line_start: bool,
19153 map: &DisplaySnapshot,
19154 ) -> Range<MultiBufferRow> {
19155 let start = self.start.to_point(&map.buffer_snapshot);
19156 let mut end = self.end.to_point(&map.buffer_snapshot);
19157 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
19158 end.row -= 1;
19159 }
19160
19161 let buffer_start = map.prev_line_boundary(start).0;
19162 let buffer_end = map.next_line_boundary(end).0;
19163 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
19164 }
19165}
19166
19167impl<T: InvalidationRegion> InvalidationStack<T> {
19168 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
19169 where
19170 S: Clone + ToOffset,
19171 {
19172 while let Some(region) = self.last() {
19173 let all_selections_inside_invalidation_ranges =
19174 if selections.len() == region.ranges().len() {
19175 selections
19176 .iter()
19177 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
19178 .all(|(selection, invalidation_range)| {
19179 let head = selection.head().to_offset(buffer);
19180 invalidation_range.start <= head && invalidation_range.end >= head
19181 })
19182 } else {
19183 false
19184 };
19185
19186 if all_selections_inside_invalidation_ranges {
19187 break;
19188 } else {
19189 self.pop();
19190 }
19191 }
19192 }
19193}
19194
19195impl<T> Default for InvalidationStack<T> {
19196 fn default() -> Self {
19197 Self(Default::default())
19198 }
19199}
19200
19201impl<T> Deref for InvalidationStack<T> {
19202 type Target = Vec<T>;
19203
19204 fn deref(&self) -> &Self::Target {
19205 &self.0
19206 }
19207}
19208
19209impl<T> DerefMut for InvalidationStack<T> {
19210 fn deref_mut(&mut self) -> &mut Self::Target {
19211 &mut self.0
19212 }
19213}
19214
19215impl InvalidationRegion for SnippetState {
19216 fn ranges(&self) -> &[Range<Anchor>] {
19217 &self.ranges[self.active_index]
19218 }
19219}
19220
19221pub fn diagnostic_block_renderer(
19222 diagnostic: Diagnostic,
19223 max_message_rows: Option<u8>,
19224 allow_closing: bool,
19225) -> RenderBlock {
19226 let (text_without_backticks, code_ranges) =
19227 highlight_diagnostic_message(&diagnostic, max_message_rows);
19228
19229 Arc::new(move |cx: &mut BlockContext| {
19230 let group_id: SharedString = cx.block_id.to_string().into();
19231
19232 let mut text_style = cx.window.text_style().clone();
19233 text_style.color = diagnostic_style(diagnostic.severity, cx.theme().status());
19234 let theme_settings = ThemeSettings::get_global(cx);
19235 text_style.font_family = theme_settings.buffer_font.family.clone();
19236 text_style.font_style = theme_settings.buffer_font.style;
19237 text_style.font_features = theme_settings.buffer_font.features.clone();
19238 text_style.font_weight = theme_settings.buffer_font.weight;
19239
19240 let multi_line_diagnostic = diagnostic.message.contains('\n');
19241
19242 let buttons = |diagnostic: &Diagnostic| {
19243 if multi_line_diagnostic {
19244 v_flex()
19245 } else {
19246 h_flex()
19247 }
19248 .when(allow_closing, |div| {
19249 div.children(diagnostic.is_primary.then(|| {
19250 IconButton::new("close-block", IconName::XCircle)
19251 .icon_color(Color::Muted)
19252 .size(ButtonSize::Compact)
19253 .style(ButtonStyle::Transparent)
19254 .visible_on_hover(group_id.clone())
19255 .on_click(move |_click, window, cx| {
19256 window.dispatch_action(Box::new(Cancel), cx)
19257 })
19258 .tooltip(|window, cx| {
19259 Tooltip::for_action("Close Diagnostics", &Cancel, window, cx)
19260 })
19261 }))
19262 })
19263 .child(
19264 IconButton::new("copy-block", IconName::Copy)
19265 .icon_color(Color::Muted)
19266 .size(ButtonSize::Compact)
19267 .style(ButtonStyle::Transparent)
19268 .visible_on_hover(group_id.clone())
19269 .on_click({
19270 let message = diagnostic.message.clone();
19271 move |_click, _, cx| {
19272 cx.write_to_clipboard(ClipboardItem::new_string(message.clone()))
19273 }
19274 })
19275 .tooltip(Tooltip::text("Copy diagnostic message")),
19276 )
19277 };
19278
19279 let icon_size = buttons(&diagnostic).into_any_element().layout_as_root(
19280 AvailableSpace::min_size(),
19281 cx.window,
19282 cx.app,
19283 );
19284
19285 h_flex()
19286 .id(cx.block_id)
19287 .group(group_id.clone())
19288 .relative()
19289 .size_full()
19290 .block_mouse_down()
19291 .pl(cx.gutter_dimensions.width)
19292 .w(cx.max_width - cx.gutter_dimensions.full_width())
19293 .child(
19294 div()
19295 .flex()
19296 .w(cx.anchor_x - cx.gutter_dimensions.width - icon_size.width)
19297 .flex_shrink(),
19298 )
19299 .child(buttons(&diagnostic))
19300 .child(div().flex().flex_shrink_0().child(
19301 StyledText::new(text_without_backticks.clone()).with_default_highlights(
19302 &text_style,
19303 code_ranges.iter().map(|range| {
19304 (
19305 range.clone(),
19306 HighlightStyle {
19307 font_weight: Some(FontWeight::BOLD),
19308 ..Default::default()
19309 },
19310 )
19311 }),
19312 ),
19313 ))
19314 .into_any_element()
19315 })
19316}
19317
19318fn inline_completion_edit_text(
19319 current_snapshot: &BufferSnapshot,
19320 edits: &[(Range<Anchor>, String)],
19321 edit_preview: &EditPreview,
19322 include_deletions: bool,
19323 cx: &App,
19324) -> HighlightedText {
19325 let edits = edits
19326 .iter()
19327 .map(|(anchor, text)| {
19328 (
19329 anchor.start.text_anchor..anchor.end.text_anchor,
19330 text.clone(),
19331 )
19332 })
19333 .collect::<Vec<_>>();
19334
19335 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
19336}
19337
19338pub fn highlight_diagnostic_message(
19339 diagnostic: &Diagnostic,
19340 mut max_message_rows: Option<u8>,
19341) -> (SharedString, Vec<Range<usize>>) {
19342 let mut text_without_backticks = String::new();
19343 let mut code_ranges = Vec::new();
19344
19345 if let Some(source) = &diagnostic.source {
19346 text_without_backticks.push_str(source);
19347 code_ranges.push(0..source.len());
19348 text_without_backticks.push_str(": ");
19349 }
19350
19351 let mut prev_offset = 0;
19352 let mut in_code_block = false;
19353 let has_row_limit = max_message_rows.is_some();
19354 let mut newline_indices = diagnostic
19355 .message
19356 .match_indices('\n')
19357 .filter(|_| has_row_limit)
19358 .map(|(ix, _)| ix)
19359 .fuse()
19360 .peekable();
19361
19362 for (quote_ix, _) in diagnostic
19363 .message
19364 .match_indices('`')
19365 .chain([(diagnostic.message.len(), "")])
19366 {
19367 let mut first_newline_ix = None;
19368 let mut last_newline_ix = None;
19369 while let Some(newline_ix) = newline_indices.peek() {
19370 if *newline_ix < quote_ix {
19371 if first_newline_ix.is_none() {
19372 first_newline_ix = Some(*newline_ix);
19373 }
19374 last_newline_ix = Some(*newline_ix);
19375
19376 if let Some(rows_left) = &mut max_message_rows {
19377 if *rows_left == 0 {
19378 break;
19379 } else {
19380 *rows_left -= 1;
19381 }
19382 }
19383 let _ = newline_indices.next();
19384 } else {
19385 break;
19386 }
19387 }
19388 let prev_len = text_without_backticks.len();
19389 let new_text = &diagnostic.message[prev_offset..first_newline_ix.unwrap_or(quote_ix)];
19390 text_without_backticks.push_str(new_text);
19391 if in_code_block {
19392 code_ranges.push(prev_len..text_without_backticks.len());
19393 }
19394 prev_offset = last_newline_ix.unwrap_or(quote_ix) + 1;
19395 in_code_block = !in_code_block;
19396 if first_newline_ix.map_or(false, |newline_ix| newline_ix < quote_ix) {
19397 text_without_backticks.push_str("...");
19398 break;
19399 }
19400 }
19401
19402 (text_without_backticks.into(), code_ranges)
19403}
19404
19405fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
19406 match severity {
19407 DiagnosticSeverity::ERROR => colors.error,
19408 DiagnosticSeverity::WARNING => colors.warning,
19409 DiagnosticSeverity::INFORMATION => colors.info,
19410 DiagnosticSeverity::HINT => colors.info,
19411 _ => colors.ignored,
19412 }
19413}
19414
19415pub fn styled_runs_for_code_label<'a>(
19416 label: &'a CodeLabel,
19417 syntax_theme: &'a theme::SyntaxTheme,
19418) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
19419 let fade_out = HighlightStyle {
19420 fade_out: Some(0.35),
19421 ..Default::default()
19422 };
19423
19424 let mut prev_end = label.filter_range.end;
19425 label
19426 .runs
19427 .iter()
19428 .enumerate()
19429 .flat_map(move |(ix, (range, highlight_id))| {
19430 let style = if let Some(style) = highlight_id.style(syntax_theme) {
19431 style
19432 } else {
19433 return Default::default();
19434 };
19435 let mut muted_style = style;
19436 muted_style.highlight(fade_out);
19437
19438 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
19439 if range.start >= label.filter_range.end {
19440 if range.start > prev_end {
19441 runs.push((prev_end..range.start, fade_out));
19442 }
19443 runs.push((range.clone(), muted_style));
19444 } else if range.end <= label.filter_range.end {
19445 runs.push((range.clone(), style));
19446 } else {
19447 runs.push((range.start..label.filter_range.end, style));
19448 runs.push((label.filter_range.end..range.end, muted_style));
19449 }
19450 prev_end = cmp::max(prev_end, range.end);
19451
19452 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
19453 runs.push((prev_end..label.text.len(), fade_out));
19454 }
19455
19456 runs
19457 })
19458}
19459
19460pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
19461 let mut prev_index = 0;
19462 let mut prev_codepoint: Option<char> = None;
19463 text.char_indices()
19464 .chain([(text.len(), '\0')])
19465 .filter_map(move |(index, codepoint)| {
19466 let prev_codepoint = prev_codepoint.replace(codepoint)?;
19467 let is_boundary = index == text.len()
19468 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
19469 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
19470 if is_boundary {
19471 let chunk = &text[prev_index..index];
19472 prev_index = index;
19473 Some(chunk)
19474 } else {
19475 None
19476 }
19477 })
19478}
19479
19480pub trait RangeToAnchorExt: Sized {
19481 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
19482
19483 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
19484 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
19485 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
19486 }
19487}
19488
19489impl<T: ToOffset> RangeToAnchorExt for Range<T> {
19490 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
19491 let start_offset = self.start.to_offset(snapshot);
19492 let end_offset = self.end.to_offset(snapshot);
19493 if start_offset == end_offset {
19494 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
19495 } else {
19496 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
19497 }
19498 }
19499}
19500
19501pub trait RowExt {
19502 fn as_f32(&self) -> f32;
19503
19504 fn next_row(&self) -> Self;
19505
19506 fn previous_row(&self) -> Self;
19507
19508 fn minus(&self, other: Self) -> u32;
19509}
19510
19511impl RowExt for DisplayRow {
19512 fn as_f32(&self) -> f32 {
19513 self.0 as f32
19514 }
19515
19516 fn next_row(&self) -> Self {
19517 Self(self.0 + 1)
19518 }
19519
19520 fn previous_row(&self) -> Self {
19521 Self(self.0.saturating_sub(1))
19522 }
19523
19524 fn minus(&self, other: Self) -> u32 {
19525 self.0 - other.0
19526 }
19527}
19528
19529impl RowExt for MultiBufferRow {
19530 fn as_f32(&self) -> f32 {
19531 self.0 as f32
19532 }
19533
19534 fn next_row(&self) -> Self {
19535 Self(self.0 + 1)
19536 }
19537
19538 fn previous_row(&self) -> Self {
19539 Self(self.0.saturating_sub(1))
19540 }
19541
19542 fn minus(&self, other: Self) -> u32 {
19543 self.0 - other.0
19544 }
19545}
19546
19547trait RowRangeExt {
19548 type Row;
19549
19550 fn len(&self) -> usize;
19551
19552 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
19553}
19554
19555impl RowRangeExt for Range<MultiBufferRow> {
19556 type Row = MultiBufferRow;
19557
19558 fn len(&self) -> usize {
19559 (self.end.0 - self.start.0) as usize
19560 }
19561
19562 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
19563 (self.start.0..self.end.0).map(MultiBufferRow)
19564 }
19565}
19566
19567impl RowRangeExt for Range<DisplayRow> {
19568 type Row = DisplayRow;
19569
19570 fn len(&self) -> usize {
19571 (self.end.0 - self.start.0) as usize
19572 }
19573
19574 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
19575 (self.start.0..self.end.0).map(DisplayRow)
19576 }
19577}
19578
19579/// If select range has more than one line, we
19580/// just point the cursor to range.start.
19581fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
19582 if range.start.row == range.end.row {
19583 range
19584 } else {
19585 range.start..range.start
19586 }
19587}
19588pub struct KillRing(ClipboardItem);
19589impl Global for KillRing {}
19590
19591const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
19592
19593struct BreakpointPromptEditor {
19594 pub(crate) prompt: Entity<Editor>,
19595 editor: WeakEntity<Editor>,
19596 breakpoint_anchor: Anchor,
19597 kind: BreakpointKind,
19598 block_ids: HashSet<CustomBlockId>,
19599 gutter_dimensions: Arc<Mutex<GutterDimensions>>,
19600 _subscriptions: Vec<Subscription>,
19601}
19602
19603impl BreakpointPromptEditor {
19604 const MAX_LINES: u8 = 4;
19605
19606 fn new(
19607 editor: WeakEntity<Editor>,
19608 breakpoint_anchor: Anchor,
19609 kind: BreakpointKind,
19610 window: &mut Window,
19611 cx: &mut Context<Self>,
19612 ) -> Self {
19613 let buffer = cx.new(|cx| {
19614 Buffer::local(
19615 kind.log_message()
19616 .map(|msg| msg.to_string())
19617 .unwrap_or_default(),
19618 cx,
19619 )
19620 });
19621 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19622
19623 let prompt = cx.new(|cx| {
19624 let mut prompt = Editor::new(
19625 EditorMode::AutoHeight {
19626 max_lines: Self::MAX_LINES as usize,
19627 },
19628 buffer,
19629 None,
19630 window,
19631 cx,
19632 );
19633 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
19634 prompt.set_show_cursor_when_unfocused(false, cx);
19635 prompt.set_placeholder_text(
19636 "Message to log when breakpoint is hit. Expressions within {} are interpolated.",
19637 cx,
19638 );
19639
19640 prompt
19641 });
19642
19643 Self {
19644 prompt,
19645 editor,
19646 breakpoint_anchor,
19647 kind,
19648 gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
19649 block_ids: Default::default(),
19650 _subscriptions: vec![],
19651 }
19652 }
19653
19654 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
19655 self.block_ids.extend(block_ids)
19656 }
19657
19658 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
19659 if let Some(editor) = self.editor.upgrade() {
19660 let log_message = self
19661 .prompt
19662 .read(cx)
19663 .buffer
19664 .read(cx)
19665 .as_singleton()
19666 .expect("A multi buffer in breakpoint prompt isn't possible")
19667 .read(cx)
19668 .as_rope()
19669 .to_string();
19670
19671 editor.update(cx, |editor, cx| {
19672 editor.edit_breakpoint_at_anchor(
19673 self.breakpoint_anchor,
19674 self.kind.clone(),
19675 BreakpointEditAction::EditLogMessage(log_message.into()),
19676 cx,
19677 );
19678
19679 editor.remove_blocks(self.block_ids.clone(), None, cx);
19680 cx.focus_self(window);
19681 });
19682 }
19683 }
19684
19685 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
19686 self.editor
19687 .update(cx, |editor, cx| {
19688 editor.remove_blocks(self.block_ids.clone(), None, cx);
19689 window.focus(&editor.focus_handle);
19690 })
19691 .log_err();
19692 }
19693
19694 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
19695 let settings = ThemeSettings::get_global(cx);
19696 let text_style = TextStyle {
19697 color: if self.prompt.read(cx).read_only(cx) {
19698 cx.theme().colors().text_disabled
19699 } else {
19700 cx.theme().colors().text
19701 },
19702 font_family: settings.buffer_font.family.clone(),
19703 font_fallbacks: settings.buffer_font.fallbacks.clone(),
19704 font_size: settings.buffer_font_size(cx).into(),
19705 font_weight: settings.buffer_font.weight,
19706 line_height: relative(settings.buffer_line_height.value()),
19707 ..Default::default()
19708 };
19709 EditorElement::new(
19710 &self.prompt,
19711 EditorStyle {
19712 background: cx.theme().colors().editor_background,
19713 local_player: cx.theme().players().local(),
19714 text: text_style,
19715 ..Default::default()
19716 },
19717 )
19718 }
19719}
19720
19721impl Render for BreakpointPromptEditor {
19722 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
19723 let gutter_dimensions = *self.gutter_dimensions.lock();
19724 h_flex()
19725 .key_context("Editor")
19726 .bg(cx.theme().colors().editor_background)
19727 .border_y_1()
19728 .border_color(cx.theme().status().info_border)
19729 .size_full()
19730 .py(window.line_height() / 2.5)
19731 .on_action(cx.listener(Self::confirm))
19732 .on_action(cx.listener(Self::cancel))
19733 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
19734 .child(div().flex_1().child(self.render_prompt_editor(cx)))
19735 }
19736}
19737
19738impl Focusable for BreakpointPromptEditor {
19739 fn focus_handle(&self, cx: &App) -> FocusHandle {
19740 self.prompt.focus_handle(cx)
19741 }
19742}
19743
19744fn all_edits_insertions_or_deletions(
19745 edits: &Vec<(Range<Anchor>, String)>,
19746 snapshot: &MultiBufferSnapshot,
19747) -> bool {
19748 let mut all_insertions = true;
19749 let mut all_deletions = true;
19750
19751 for (range, new_text) in edits.iter() {
19752 let range_is_empty = range.to_offset(&snapshot).is_empty();
19753 let text_is_empty = new_text.is_empty();
19754
19755 if range_is_empty != text_is_empty {
19756 if range_is_empty {
19757 all_deletions = false;
19758 } else {
19759 all_insertions = false;
19760 }
19761 } else {
19762 return false;
19763 }
19764
19765 if !all_insertions && !all_deletions {
19766 return false;
19767 }
19768 }
19769 all_insertions || all_deletions
19770}
19771
19772struct MissingEditPredictionKeybindingTooltip;
19773
19774impl Render for MissingEditPredictionKeybindingTooltip {
19775 fn render(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
19776 ui::tooltip_container(window, cx, |container, _, cx| {
19777 container
19778 .flex_shrink_0()
19779 .max_w_80()
19780 .min_h(rems_from_px(124.))
19781 .justify_between()
19782 .child(
19783 v_flex()
19784 .flex_1()
19785 .text_ui_sm(cx)
19786 .child(Label::new("Conflict with Accept Keybinding"))
19787 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
19788 )
19789 .child(
19790 h_flex()
19791 .pb_1()
19792 .gap_1()
19793 .items_end()
19794 .w_full()
19795 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
19796 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
19797 }))
19798 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
19799 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
19800 })),
19801 )
19802 })
19803 }
19804}
19805
19806#[derive(Debug, Clone, Copy, PartialEq)]
19807pub struct LineHighlight {
19808 pub background: Background,
19809 pub border: Option<gpui::Hsla>,
19810}
19811
19812impl From<Hsla> for LineHighlight {
19813 fn from(hsla: Hsla) -> Self {
19814 Self {
19815 background: hsla.into(),
19816 border: None,
19817 }
19818 }
19819}
19820
19821impl From<Background> for LineHighlight {
19822 fn from(background: Background) -> Self {
19823 Self {
19824 background,
19825 border: None,
19826 }
19827 }
19828}