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::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 ItemId, RestoreOnStartupBehavior,
192};
193use workspace::{
194 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
195 WorkspaceSettings,
196};
197use workspace::{
198 searchable::SearchEvent, ItemNavHistory, SplitDirection, ViewId, Workspace, WorkspaceId,
199};
200use workspace::{Item as WorkspaceItem, OpenInTerminal, OpenTerminal, TabBarSettings, Toast};
201
202use crate::hover_links::{find_url, find_url_from_range};
203use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
204
205pub const FILE_HEADER_HEIGHT: u32 = 2;
206pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
207pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
208const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
209const MAX_LINE_LEN: usize = 1024;
210const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
211const MAX_SELECTION_HISTORY_LEN: usize = 1024;
212pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
213#[doc(hidden)]
214pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
215
216pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
217pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
218pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
219
220pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
221pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
222pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
223
224const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
225 alt: true,
226 shift: true,
227 control: false,
228 platform: false,
229 function: false,
230};
231
232#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
233pub enum InlayId {
234 InlineCompletion(usize),
235 Hint(usize),
236}
237
238impl InlayId {
239 fn id(&self) -> usize {
240 match self {
241 Self::InlineCompletion(id) => *id,
242 Self::Hint(id) => *id,
243 }
244 }
245}
246
247pub enum DebugCurrentRowHighlight {}
248enum DocumentHighlightRead {}
249enum DocumentHighlightWrite {}
250enum InputComposition {}
251enum SelectedTextHighlight {}
252
253#[derive(Debug, Copy, Clone, PartialEq, Eq)]
254pub enum Navigated {
255 Yes,
256 No,
257}
258
259impl Navigated {
260 pub fn from_bool(yes: bool) -> Navigated {
261 if yes {
262 Navigated::Yes
263 } else {
264 Navigated::No
265 }
266 }
267}
268
269#[derive(Debug, Clone, PartialEq, Eq)]
270enum DisplayDiffHunk {
271 Folded {
272 display_row: DisplayRow,
273 },
274 Unfolded {
275 is_created_file: bool,
276 diff_base_byte_range: Range<usize>,
277 display_row_range: Range<DisplayRow>,
278 multi_buffer_range: Range<Anchor>,
279 status: DiffHunkStatus,
280 },
281}
282
283pub fn init_settings(cx: &mut App) {
284 EditorSettings::register(cx);
285}
286
287pub fn init(cx: &mut App) {
288 init_settings(cx);
289
290 workspace::register_project_item::<Editor>(cx);
291 workspace::FollowableViewRegistry::register::<Editor>(cx);
292 workspace::register_serializable_item::<Editor>(cx);
293
294 cx.observe_new(
295 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
296 workspace.register_action(Editor::new_file);
297 workspace.register_action(Editor::new_file_vertical);
298 workspace.register_action(Editor::new_file_horizontal);
299 workspace.register_action(Editor::cancel_language_server_work);
300 },
301 )
302 .detach();
303
304 cx.on_action(move |_: &workspace::NewFile, cx| {
305 let app_state = workspace::AppState::global(cx);
306 if let Some(app_state) = app_state.upgrade() {
307 workspace::open_new(
308 Default::default(),
309 app_state,
310 cx,
311 |workspace, window, cx| {
312 Editor::new_file(workspace, &Default::default(), window, cx)
313 },
314 )
315 .detach();
316 }
317 });
318 cx.on_action(move |_: &workspace::NewWindow, cx| {
319 let app_state = workspace::AppState::global(cx);
320 if let Some(app_state) = app_state.upgrade() {
321 workspace::open_new(
322 Default::default(),
323 app_state,
324 cx,
325 |workspace, window, cx| {
326 cx.activate(true);
327 Editor::new_file(workspace, &Default::default(), window, cx)
328 },
329 )
330 .detach();
331 }
332 });
333}
334
335pub struct SearchWithinRange;
336
337trait InvalidationRegion {
338 fn ranges(&self) -> &[Range<Anchor>];
339}
340
341#[derive(Clone, Debug, PartialEq)]
342pub enum SelectPhase {
343 Begin {
344 position: DisplayPoint,
345 add: bool,
346 click_count: usize,
347 },
348 BeginColumnar {
349 position: DisplayPoint,
350 reset: bool,
351 goal_column: u32,
352 },
353 Extend {
354 position: DisplayPoint,
355 click_count: usize,
356 },
357 Update {
358 position: DisplayPoint,
359 goal_column: u32,
360 scroll_delta: gpui::Point<f32>,
361 },
362 End,
363}
364
365#[derive(Clone, Debug)]
366pub enum SelectMode {
367 Character,
368 Word(Range<Anchor>),
369 Line(Range<Anchor>),
370 All,
371}
372
373#[derive(Copy, Clone, PartialEq, Eq, Debug)]
374pub enum EditorMode {
375 SingleLine { auto_width: bool },
376 AutoHeight { max_lines: usize },
377 Full,
378}
379
380#[derive(Copy, Clone, Debug)]
381pub enum SoftWrap {
382 /// Prefer not to wrap at all.
383 ///
384 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
385 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
386 GitDiff,
387 /// Prefer a single line generally, unless an overly long line is encountered.
388 None,
389 /// Soft wrap lines that exceed the editor width.
390 EditorWidth,
391 /// Soft wrap lines at the preferred line length.
392 Column(u32),
393 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
394 Bounded(u32),
395}
396
397#[derive(Clone)]
398pub struct EditorStyle {
399 pub background: Hsla,
400 pub local_player: PlayerColor,
401 pub text: TextStyle,
402 pub scrollbar_width: Pixels,
403 pub syntax: Arc<SyntaxTheme>,
404 pub status: StatusColors,
405 pub inlay_hints_style: HighlightStyle,
406 pub inline_completion_styles: InlineCompletionStyles,
407 pub unnecessary_code_fade: f32,
408}
409
410impl Default for EditorStyle {
411 fn default() -> Self {
412 Self {
413 background: Hsla::default(),
414 local_player: PlayerColor::default(),
415 text: TextStyle::default(),
416 scrollbar_width: Pixels::default(),
417 syntax: Default::default(),
418 // HACK: Status colors don't have a real default.
419 // We should look into removing the status colors from the editor
420 // style and retrieve them directly from the theme.
421 status: StatusColors::dark(),
422 inlay_hints_style: HighlightStyle::default(),
423 inline_completion_styles: InlineCompletionStyles {
424 insertion: HighlightStyle::default(),
425 whitespace: HighlightStyle::default(),
426 },
427 unnecessary_code_fade: Default::default(),
428 }
429 }
430}
431
432pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
433 let show_background = language_settings::language_settings(None, None, cx)
434 .inlay_hints
435 .show_background;
436
437 HighlightStyle {
438 color: Some(cx.theme().status().hint),
439 background_color: show_background.then(|| cx.theme().status().hint_background),
440 ..HighlightStyle::default()
441 }
442}
443
444pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
445 InlineCompletionStyles {
446 insertion: HighlightStyle {
447 color: Some(cx.theme().status().predictive),
448 ..HighlightStyle::default()
449 },
450 whitespace: HighlightStyle {
451 background_color: Some(cx.theme().status().created_background),
452 ..HighlightStyle::default()
453 },
454 }
455}
456
457type CompletionId = usize;
458
459pub(crate) enum EditDisplayMode {
460 TabAccept,
461 DiffPopover,
462 Inline,
463}
464
465enum InlineCompletion {
466 Edit {
467 edits: Vec<(Range<Anchor>, String)>,
468 edit_preview: Option<EditPreview>,
469 display_mode: EditDisplayMode,
470 snapshot: BufferSnapshot,
471 },
472 Move {
473 target: Anchor,
474 snapshot: BufferSnapshot,
475 },
476}
477
478struct InlineCompletionState {
479 inlay_ids: Vec<InlayId>,
480 completion: InlineCompletion,
481 completion_id: Option<SharedString>,
482 invalidation_range: Range<Anchor>,
483}
484
485enum EditPredictionSettings {
486 Disabled,
487 Enabled {
488 show_in_menu: bool,
489 preview_requires_modifier: bool,
490 },
491}
492
493enum InlineCompletionHighlight {}
494
495#[derive(Debug, Clone)]
496struct InlineDiagnostic {
497 message: SharedString,
498 group_id: usize,
499 is_primary: bool,
500 start: Point,
501 severity: DiagnosticSeverity,
502}
503
504pub enum MenuInlineCompletionsPolicy {
505 Never,
506 ByProvider,
507}
508
509pub enum EditPredictionPreview {
510 /// Modifier is not pressed
511 Inactive { released_too_fast: bool },
512 /// Modifier pressed
513 Active {
514 since: Instant,
515 previous_scroll_position: Option<ScrollAnchor>,
516 },
517}
518
519impl EditPredictionPreview {
520 pub fn released_too_fast(&self) -> bool {
521 match self {
522 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
523 EditPredictionPreview::Active { .. } => false,
524 }
525 }
526
527 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
528 if let EditPredictionPreview::Active {
529 previous_scroll_position,
530 ..
531 } = self
532 {
533 *previous_scroll_position = scroll_position;
534 }
535 }
536}
537
538#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
539struct EditorActionId(usize);
540
541impl EditorActionId {
542 pub fn post_inc(&mut self) -> Self {
543 let answer = self.0;
544
545 *self = Self(answer + 1);
546
547 Self(answer)
548 }
549}
550
551// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
552// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
553
554type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
555type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
556
557#[derive(Default)]
558struct ScrollbarMarkerState {
559 scrollbar_size: Size<Pixels>,
560 dirty: bool,
561 markers: Arc<[PaintQuad]>,
562 pending_refresh: Option<Task<Result<()>>>,
563}
564
565impl ScrollbarMarkerState {
566 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
567 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
568 }
569}
570
571#[derive(Clone, Debug)]
572struct RunnableTasks {
573 templates: Vec<(TaskSourceKind, TaskTemplate)>,
574 offset: multi_buffer::Anchor,
575 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
576 column: u32,
577 // Values of all named captures, including those starting with '_'
578 extra_variables: HashMap<String, String>,
579 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
580 context_range: Range<BufferOffset>,
581}
582
583impl RunnableTasks {
584 fn resolve<'a>(
585 &'a self,
586 cx: &'a task::TaskContext,
587 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
588 self.templates.iter().filter_map(|(kind, template)| {
589 template
590 .resolve_task(&kind.to_id_base(), cx)
591 .map(|task| (kind.clone(), task))
592 })
593 }
594}
595
596#[derive(Clone)]
597struct ResolvedTasks {
598 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
599 position: Anchor,
600}
601
602#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
603struct BufferOffset(usize);
604
605// Addons allow storing per-editor state in other crates (e.g. Vim)
606pub trait Addon: 'static {
607 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
608
609 fn render_buffer_header_controls(
610 &self,
611 _: &ExcerptInfo,
612 _: &Window,
613 _: &App,
614 ) -> Option<AnyElement> {
615 None
616 }
617
618 fn to_any(&self) -> &dyn std::any::Any;
619}
620
621/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
622///
623/// See the [module level documentation](self) for more information.
624pub struct Editor {
625 focus_handle: FocusHandle,
626 last_focused_descendant: Option<WeakFocusHandle>,
627 /// The text buffer being edited
628 buffer: Entity<MultiBuffer>,
629 /// Map of how text in the buffer should be displayed.
630 /// Handles soft wraps, folds, fake inlay text insertions, etc.
631 pub display_map: Entity<DisplayMap>,
632 pub selections: SelectionsCollection,
633 pub scroll_manager: ScrollManager,
634 /// When inline assist editors are linked, they all render cursors because
635 /// typing enters text into each of them, even the ones that aren't focused.
636 pub(crate) show_cursor_when_unfocused: bool,
637 columnar_selection_tail: Option<Anchor>,
638 add_selections_state: Option<AddSelectionsState>,
639 select_next_state: Option<SelectNextState>,
640 select_prev_state: Option<SelectNextState>,
641 selection_history: SelectionHistory,
642 autoclose_regions: Vec<AutocloseRegion>,
643 snippet_stack: InvalidationStack<SnippetState>,
644 select_larger_syntax_node_stack: Vec<Box<[Selection<usize>]>>,
645 ime_transaction: Option<TransactionId>,
646 active_diagnostics: Option<ActiveDiagnosticGroup>,
647 show_inline_diagnostics: bool,
648 inline_diagnostics_update: Task<()>,
649 inline_diagnostics_enabled: bool,
650 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
651 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
652 hard_wrap: Option<usize>,
653
654 // TODO: make this a access method
655 pub project: Option<Entity<Project>>,
656 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
657 completion_provider: Option<Box<dyn CompletionProvider>>,
658 collaboration_hub: Option<Box<dyn CollaborationHub>>,
659 blink_manager: Entity<BlinkManager>,
660 show_cursor_names: bool,
661 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
662 pub show_local_selections: bool,
663 mode: EditorMode,
664 show_breadcrumbs: bool,
665 show_gutter: bool,
666 show_scrollbars: bool,
667 show_line_numbers: Option<bool>,
668 use_relative_line_numbers: Option<bool>,
669 show_git_diff_gutter: Option<bool>,
670 show_code_actions: Option<bool>,
671 show_runnables: Option<bool>,
672 show_breakpoints: Option<bool>,
673 show_wrap_guides: Option<bool>,
674 show_indent_guides: Option<bool>,
675 placeholder_text: Option<Arc<str>>,
676 highlight_order: usize,
677 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
678 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
679 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
680 scrollbar_marker_state: ScrollbarMarkerState,
681 active_indent_guides_state: ActiveIndentGuidesState,
682 nav_history: Option<ItemNavHistory>,
683 context_menu: RefCell<Option<CodeContextMenu>>,
684 mouse_context_menu: Option<MouseContextMenu>,
685 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
686 signature_help_state: SignatureHelpState,
687 auto_signature_help: Option<bool>,
688 find_all_references_task_sources: Vec<Anchor>,
689 next_completion_id: CompletionId,
690 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
691 code_actions_task: Option<Task<Result<()>>>,
692 selection_highlight_task: Option<Task<()>>,
693 document_highlights_task: Option<Task<()>>,
694 linked_editing_range_task: Option<Task<Option<()>>>,
695 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
696 pending_rename: Option<RenameState>,
697 searchable: bool,
698 cursor_shape: CursorShape,
699 current_line_highlight: Option<CurrentLineHighlight>,
700 collapse_matches: bool,
701 autoindent_mode: Option<AutoindentMode>,
702 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
703 input_enabled: bool,
704 use_modal_editing: bool,
705 read_only: bool,
706 leader_peer_id: Option<PeerId>,
707 remote_id: Option<ViewId>,
708 hover_state: HoverState,
709 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
710 gutter_hovered: bool,
711 hovered_link_state: Option<HoveredLinkState>,
712 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
713 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
714 active_inline_completion: Option<InlineCompletionState>,
715 /// Used to prevent flickering as the user types while the menu is open
716 stale_inline_completion_in_menu: Option<InlineCompletionState>,
717 edit_prediction_settings: EditPredictionSettings,
718 inline_completions_hidden_for_vim_mode: bool,
719 show_inline_completions_override: Option<bool>,
720 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
721 edit_prediction_preview: EditPredictionPreview,
722 edit_prediction_indent_conflict: bool,
723 edit_prediction_requires_modifier_in_indent_conflict: bool,
724 inlay_hint_cache: InlayHintCache,
725 next_inlay_id: usize,
726 _subscriptions: Vec<Subscription>,
727 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
728 gutter_dimensions: GutterDimensions,
729 style: Option<EditorStyle>,
730 text_style_refinement: Option<TextStyleRefinement>,
731 next_editor_action_id: EditorActionId,
732 editor_actions:
733 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
734 use_autoclose: bool,
735 use_auto_surround: bool,
736 auto_replace_emoji_shortcode: bool,
737 jsx_tag_auto_close_enabled_in_any_buffer: bool,
738 show_git_blame_gutter: bool,
739 show_git_blame_inline: bool,
740 show_git_blame_inline_delay_task: Option<Task<()>>,
741 git_blame_inline_tooltip: Option<WeakEntity<crate::commit_tooltip::CommitTooltip>>,
742 git_blame_inline_enabled: bool,
743 serialize_dirty_buffers: bool,
744 show_selection_menu: Option<bool>,
745 blame: Option<Entity<GitBlame>>,
746 blame_subscription: Option<Subscription>,
747 custom_context_menu: Option<
748 Box<
749 dyn 'static
750 + Fn(
751 &mut Self,
752 DisplayPoint,
753 &mut Window,
754 &mut Context<Self>,
755 ) -> Option<Entity<ui::ContextMenu>>,
756 >,
757 >,
758 last_bounds: Option<Bounds<Pixels>>,
759 last_position_map: Option<Rc<PositionMap>>,
760 expect_bounds_change: Option<Bounds<Pixels>>,
761 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
762 tasks_update_task: Option<Task<()>>,
763 pub breakpoint_store: Option<Entity<BreakpointStore>>,
764 /// Allow's a user to create a breakpoint by selecting this indicator
765 /// It should be None while a user is not hovering over the gutter
766 /// Otherwise it represents the point that the breakpoint will be shown
767 pub gutter_breakpoint_indicator: Option<DisplayPoint>,
768 in_project_search: bool,
769 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
770 breadcrumb_header: Option<String>,
771 focused_block: Option<FocusedBlock>,
772 next_scroll_position: NextScrollCursorCenterTopBottom,
773 addons: HashMap<TypeId, Box<dyn Addon>>,
774 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
775 load_diff_task: Option<Shared<Task<()>>>,
776 selection_mark_mode: bool,
777 toggle_fold_multiple_buffers: Task<()>,
778 _scroll_cursor_center_top_bottom_task: Task<()>,
779 serialize_selections: Task<()>,
780 mouse_cursor_hidden: bool,
781 hide_mouse_while_typing: bool,
782}
783
784#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
785enum NextScrollCursorCenterTopBottom {
786 #[default]
787 Center,
788 Top,
789 Bottom,
790}
791
792impl NextScrollCursorCenterTopBottom {
793 fn next(&self) -> Self {
794 match self {
795 Self::Center => Self::Top,
796 Self::Top => Self::Bottom,
797 Self::Bottom => Self::Center,
798 }
799 }
800}
801
802#[derive(Clone)]
803pub struct EditorSnapshot {
804 pub mode: EditorMode,
805 show_gutter: bool,
806 show_line_numbers: Option<bool>,
807 show_git_diff_gutter: Option<bool>,
808 show_code_actions: Option<bool>,
809 show_runnables: Option<bool>,
810 show_breakpoints: Option<bool>,
811 git_blame_gutter_max_author_length: Option<usize>,
812 pub display_snapshot: DisplaySnapshot,
813 pub placeholder_text: Option<Arc<str>>,
814 is_focused: bool,
815 scroll_anchor: ScrollAnchor,
816 ongoing_scroll: OngoingScroll,
817 current_line_highlight: CurrentLineHighlight,
818 gutter_hovered: bool,
819}
820
821const GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED: usize = 20;
822
823#[derive(Default, Debug, Clone, Copy)]
824pub struct GutterDimensions {
825 pub left_padding: Pixels,
826 pub right_padding: Pixels,
827 pub width: Pixels,
828 pub margin: Pixels,
829 pub git_blame_entries_width: Option<Pixels>,
830}
831
832impl GutterDimensions {
833 /// The full width of the space taken up by the gutter.
834 pub fn full_width(&self) -> Pixels {
835 self.margin + self.width
836 }
837
838 /// The width of the space reserved for the fold indicators,
839 /// use alongside 'justify_end' and `gutter_width` to
840 /// right align content with the line numbers
841 pub fn fold_area_width(&self) -> Pixels {
842 self.margin + self.right_padding
843 }
844}
845
846#[derive(Debug)]
847pub struct RemoteSelection {
848 pub replica_id: ReplicaId,
849 pub selection: Selection<Anchor>,
850 pub cursor_shape: CursorShape,
851 pub peer_id: PeerId,
852 pub line_mode: bool,
853 pub participant_index: Option<ParticipantIndex>,
854 pub user_name: Option<SharedString>,
855}
856
857#[derive(Clone, Debug)]
858struct SelectionHistoryEntry {
859 selections: Arc<[Selection<Anchor>]>,
860 select_next_state: Option<SelectNextState>,
861 select_prev_state: Option<SelectNextState>,
862 add_selections_state: Option<AddSelectionsState>,
863}
864
865enum SelectionHistoryMode {
866 Normal,
867 Undoing,
868 Redoing,
869}
870
871#[derive(Clone, PartialEq, Eq, Hash)]
872struct HoveredCursor {
873 replica_id: u16,
874 selection_id: usize,
875}
876
877impl Default for SelectionHistoryMode {
878 fn default() -> Self {
879 Self::Normal
880 }
881}
882
883#[derive(Default)]
884struct SelectionHistory {
885 #[allow(clippy::type_complexity)]
886 selections_by_transaction:
887 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
888 mode: SelectionHistoryMode,
889 undo_stack: VecDeque<SelectionHistoryEntry>,
890 redo_stack: VecDeque<SelectionHistoryEntry>,
891}
892
893impl SelectionHistory {
894 fn insert_transaction(
895 &mut self,
896 transaction_id: TransactionId,
897 selections: Arc<[Selection<Anchor>]>,
898 ) {
899 self.selections_by_transaction
900 .insert(transaction_id, (selections, None));
901 }
902
903 #[allow(clippy::type_complexity)]
904 fn transaction(
905 &self,
906 transaction_id: TransactionId,
907 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
908 self.selections_by_transaction.get(&transaction_id)
909 }
910
911 #[allow(clippy::type_complexity)]
912 fn transaction_mut(
913 &mut self,
914 transaction_id: TransactionId,
915 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
916 self.selections_by_transaction.get_mut(&transaction_id)
917 }
918
919 fn push(&mut self, entry: SelectionHistoryEntry) {
920 if !entry.selections.is_empty() {
921 match self.mode {
922 SelectionHistoryMode::Normal => {
923 self.push_undo(entry);
924 self.redo_stack.clear();
925 }
926 SelectionHistoryMode::Undoing => self.push_redo(entry),
927 SelectionHistoryMode::Redoing => self.push_undo(entry),
928 }
929 }
930 }
931
932 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
933 if self
934 .undo_stack
935 .back()
936 .map_or(true, |e| e.selections != entry.selections)
937 {
938 self.undo_stack.push_back(entry);
939 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
940 self.undo_stack.pop_front();
941 }
942 }
943 }
944
945 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
946 if self
947 .redo_stack
948 .back()
949 .map_or(true, |e| e.selections != entry.selections)
950 {
951 self.redo_stack.push_back(entry);
952 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
953 self.redo_stack.pop_front();
954 }
955 }
956 }
957}
958
959struct RowHighlight {
960 index: usize,
961 range: Range<Anchor>,
962 color: Hsla,
963 should_autoscroll: bool,
964}
965
966#[derive(Clone, Debug)]
967struct AddSelectionsState {
968 above: bool,
969 stack: Vec<usize>,
970}
971
972#[derive(Clone)]
973struct SelectNextState {
974 query: AhoCorasick,
975 wordwise: bool,
976 done: bool,
977}
978
979impl std::fmt::Debug for SelectNextState {
980 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
981 f.debug_struct(std::any::type_name::<Self>())
982 .field("wordwise", &self.wordwise)
983 .field("done", &self.done)
984 .finish()
985 }
986}
987
988#[derive(Debug)]
989struct AutocloseRegion {
990 selection_id: usize,
991 range: Range<Anchor>,
992 pair: BracketPair,
993}
994
995#[derive(Debug)]
996struct SnippetState {
997 ranges: Vec<Vec<Range<Anchor>>>,
998 active_index: usize,
999 choices: Vec<Option<Vec<String>>>,
1000}
1001
1002#[doc(hidden)]
1003pub struct RenameState {
1004 pub range: Range<Anchor>,
1005 pub old_name: Arc<str>,
1006 pub editor: Entity<Editor>,
1007 block_id: CustomBlockId,
1008}
1009
1010struct InvalidationStack<T>(Vec<T>);
1011
1012struct RegisteredInlineCompletionProvider {
1013 provider: Arc<dyn InlineCompletionProviderHandle>,
1014 _subscription: Subscription,
1015}
1016
1017#[derive(Debug, PartialEq, Eq)]
1018struct ActiveDiagnosticGroup {
1019 primary_range: Range<Anchor>,
1020 primary_message: String,
1021 group_id: usize,
1022 blocks: HashMap<CustomBlockId, Diagnostic>,
1023 is_valid: bool,
1024}
1025
1026#[derive(Serialize, Deserialize, Clone, Debug)]
1027pub struct ClipboardSelection {
1028 /// The number of bytes in this selection.
1029 pub len: usize,
1030 /// Whether this was a full-line selection.
1031 pub is_entire_line: bool,
1032 /// The indentation of the first line when this content was originally copied.
1033 pub first_line_indent: u32,
1034}
1035
1036#[derive(Debug)]
1037pub(crate) struct NavigationData {
1038 cursor_anchor: Anchor,
1039 cursor_position: Point,
1040 scroll_anchor: ScrollAnchor,
1041 scroll_top_row: u32,
1042}
1043
1044#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1045pub enum GotoDefinitionKind {
1046 Symbol,
1047 Declaration,
1048 Type,
1049 Implementation,
1050}
1051
1052#[derive(Debug, Clone)]
1053enum InlayHintRefreshReason {
1054 ModifiersChanged(bool),
1055 Toggle(bool),
1056 SettingsChange(InlayHintSettings),
1057 NewLinesShown,
1058 BufferEdited(HashSet<Arc<Language>>),
1059 RefreshRequested,
1060 ExcerptsRemoved(Vec<ExcerptId>),
1061}
1062
1063impl InlayHintRefreshReason {
1064 fn description(&self) -> &'static str {
1065 match self {
1066 Self::ModifiersChanged(_) => "modifiers changed",
1067 Self::Toggle(_) => "toggle",
1068 Self::SettingsChange(_) => "settings change",
1069 Self::NewLinesShown => "new lines shown",
1070 Self::BufferEdited(_) => "buffer edited",
1071 Self::RefreshRequested => "refresh requested",
1072 Self::ExcerptsRemoved(_) => "excerpts removed",
1073 }
1074 }
1075}
1076
1077pub enum FormatTarget {
1078 Buffers,
1079 Ranges(Vec<Range<MultiBufferPoint>>),
1080}
1081
1082pub(crate) struct FocusedBlock {
1083 id: BlockId,
1084 focus_handle: WeakFocusHandle,
1085}
1086
1087#[derive(Clone)]
1088enum JumpData {
1089 MultiBufferRow {
1090 row: MultiBufferRow,
1091 line_offset_from_top: u32,
1092 },
1093 MultiBufferPoint {
1094 excerpt_id: ExcerptId,
1095 position: Point,
1096 anchor: text::Anchor,
1097 line_offset_from_top: u32,
1098 },
1099}
1100
1101pub enum MultibufferSelectionMode {
1102 First,
1103 All,
1104}
1105
1106#[derive(Clone, Copy, Debug, Default)]
1107pub struct RewrapOptions {
1108 pub override_language_settings: bool,
1109 pub preserve_existing_whitespace: bool,
1110}
1111
1112impl Editor {
1113 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1114 let buffer = cx.new(|cx| Buffer::local("", cx));
1115 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1116 Self::new(
1117 EditorMode::SingleLine { auto_width: false },
1118 buffer,
1119 None,
1120 window,
1121 cx,
1122 )
1123 }
1124
1125 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1126 let buffer = cx.new(|cx| Buffer::local("", cx));
1127 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1128 Self::new(EditorMode::Full, buffer, None, window, cx)
1129 }
1130
1131 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1132 let buffer = cx.new(|cx| Buffer::local("", cx));
1133 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1134 Self::new(
1135 EditorMode::SingleLine { auto_width: true },
1136 buffer,
1137 None,
1138 window,
1139 cx,
1140 )
1141 }
1142
1143 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1144 let buffer = cx.new(|cx| Buffer::local("", cx));
1145 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1146 Self::new(
1147 EditorMode::AutoHeight { max_lines },
1148 buffer,
1149 None,
1150 window,
1151 cx,
1152 )
1153 }
1154
1155 pub fn for_buffer(
1156 buffer: Entity<Buffer>,
1157 project: Option<Entity<Project>>,
1158 window: &mut Window,
1159 cx: &mut Context<Self>,
1160 ) -> Self {
1161 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1162 Self::new(EditorMode::Full, buffer, project, window, cx)
1163 }
1164
1165 pub fn for_multibuffer(
1166 buffer: Entity<MultiBuffer>,
1167 project: Option<Entity<Project>>,
1168 window: &mut Window,
1169 cx: &mut Context<Self>,
1170 ) -> Self {
1171 Self::new(EditorMode::Full, buffer, project, window, cx)
1172 }
1173
1174 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1175 let mut clone = Self::new(
1176 self.mode,
1177 self.buffer.clone(),
1178 self.project.clone(),
1179 window,
1180 cx,
1181 );
1182 self.display_map.update(cx, |display_map, cx| {
1183 let snapshot = display_map.snapshot(cx);
1184 clone.display_map.update(cx, |display_map, cx| {
1185 display_map.set_state(&snapshot, cx);
1186 });
1187 });
1188 clone.selections.clone_state(&self.selections);
1189 clone.scroll_manager.clone_state(&self.scroll_manager);
1190 clone.searchable = self.searchable;
1191 clone
1192 }
1193
1194 pub fn new(
1195 mode: EditorMode,
1196 buffer: Entity<MultiBuffer>,
1197 project: Option<Entity<Project>>,
1198 window: &mut Window,
1199 cx: &mut Context<Self>,
1200 ) -> Self {
1201 let style = window.text_style();
1202 let font_size = style.font_size.to_pixels(window.rem_size());
1203 let editor = cx.entity().downgrade();
1204 let fold_placeholder = FoldPlaceholder {
1205 constrain_width: true,
1206 render: Arc::new(move |fold_id, fold_range, cx| {
1207 let editor = editor.clone();
1208 div()
1209 .id(fold_id)
1210 .bg(cx.theme().colors().ghost_element_background)
1211 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1212 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1213 .rounded_xs()
1214 .size_full()
1215 .cursor_pointer()
1216 .child("⋯")
1217 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1218 .on_click(move |_, _window, cx| {
1219 editor
1220 .update(cx, |editor, cx| {
1221 editor.unfold_ranges(
1222 &[fold_range.start..fold_range.end],
1223 true,
1224 false,
1225 cx,
1226 );
1227 cx.stop_propagation();
1228 })
1229 .ok();
1230 })
1231 .into_any()
1232 }),
1233 merge_adjacent: true,
1234 ..Default::default()
1235 };
1236 let display_map = cx.new(|cx| {
1237 DisplayMap::new(
1238 buffer.clone(),
1239 style.font(),
1240 font_size,
1241 None,
1242 FILE_HEADER_HEIGHT,
1243 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1244 fold_placeholder,
1245 cx,
1246 )
1247 });
1248
1249 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1250
1251 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1252
1253 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1254 .then(|| language_settings::SoftWrap::None);
1255
1256 let mut project_subscriptions = Vec::new();
1257 if mode == EditorMode::Full {
1258 if let Some(project) = project.as_ref() {
1259 project_subscriptions.push(cx.subscribe_in(
1260 project,
1261 window,
1262 |editor, _, event, window, cx| match event {
1263 project::Event::RefreshCodeLens => {
1264 // we always query lens with actions, without storing them, always refreshing them
1265 }
1266 project::Event::RefreshInlayHints => {
1267 editor
1268 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1269 }
1270 project::Event::SnippetEdit(id, snippet_edits) => {
1271 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1272 let focus_handle = editor.focus_handle(cx);
1273 if focus_handle.is_focused(window) {
1274 let snapshot = buffer.read(cx).snapshot();
1275 for (range, snippet) in snippet_edits {
1276 let editor_range =
1277 language::range_from_lsp(*range).to_offset(&snapshot);
1278 editor
1279 .insert_snippet(
1280 &[editor_range],
1281 snippet.clone(),
1282 window,
1283 cx,
1284 )
1285 .ok();
1286 }
1287 }
1288 }
1289 }
1290 _ => {}
1291 },
1292 ));
1293 if let Some(task_inventory) = project
1294 .read(cx)
1295 .task_store()
1296 .read(cx)
1297 .task_inventory()
1298 .cloned()
1299 {
1300 project_subscriptions.push(cx.observe_in(
1301 &task_inventory,
1302 window,
1303 |editor, _, window, cx| {
1304 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1305 },
1306 ));
1307 };
1308
1309 project_subscriptions.push(cx.subscribe_in(
1310 &project.read(cx).breakpoint_store(),
1311 window,
1312 |editor, _, event, window, cx| match event {
1313 BreakpointStoreEvent::ActiveDebugLineChanged => {
1314 editor.go_to_active_debug_line(window, cx);
1315 }
1316 _ => {}
1317 },
1318 ));
1319 }
1320 }
1321
1322 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1323
1324 let inlay_hint_settings =
1325 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1326 let focus_handle = cx.focus_handle();
1327 cx.on_focus(&focus_handle, window, Self::handle_focus)
1328 .detach();
1329 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1330 .detach();
1331 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1332 .detach();
1333 cx.on_blur(&focus_handle, window, Self::handle_blur)
1334 .detach();
1335
1336 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1337 Some(false)
1338 } else {
1339 None
1340 };
1341
1342 let breakpoint_store = match (mode, project.as_ref()) {
1343 (EditorMode::Full, Some(project)) => Some(project.read(cx).breakpoint_store()),
1344 _ => None,
1345 };
1346
1347 let mut code_action_providers = Vec::new();
1348 let mut load_uncommitted_diff = None;
1349 if let Some(project) = project.clone() {
1350 load_uncommitted_diff = Some(
1351 get_uncommitted_diff_for_buffer(
1352 &project,
1353 buffer.read(cx).all_buffers(),
1354 buffer.clone(),
1355 cx,
1356 )
1357 .shared(),
1358 );
1359 code_action_providers.push(Rc::new(project) as Rc<_>);
1360 }
1361
1362 let mut this = Self {
1363 focus_handle,
1364 show_cursor_when_unfocused: false,
1365 last_focused_descendant: None,
1366 buffer: buffer.clone(),
1367 display_map: display_map.clone(),
1368 selections,
1369 scroll_manager: ScrollManager::new(cx),
1370 columnar_selection_tail: None,
1371 add_selections_state: None,
1372 select_next_state: None,
1373 select_prev_state: None,
1374 selection_history: Default::default(),
1375 autoclose_regions: Default::default(),
1376 snippet_stack: Default::default(),
1377 select_larger_syntax_node_stack: Vec::new(),
1378 ime_transaction: Default::default(),
1379 active_diagnostics: None,
1380 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1381 inline_diagnostics_update: Task::ready(()),
1382 inline_diagnostics: Vec::new(),
1383 soft_wrap_mode_override,
1384 hard_wrap: None,
1385 completion_provider: project.clone().map(|project| Box::new(project) as _),
1386 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1387 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1388 project,
1389 blink_manager: blink_manager.clone(),
1390 show_local_selections: true,
1391 show_scrollbars: true,
1392 mode,
1393 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1394 show_gutter: mode == EditorMode::Full,
1395 show_line_numbers: None,
1396 use_relative_line_numbers: None,
1397 show_git_diff_gutter: None,
1398 show_code_actions: None,
1399 show_runnables: None,
1400 show_breakpoints: None,
1401 show_wrap_guides: None,
1402 show_indent_guides,
1403 placeholder_text: None,
1404 highlight_order: 0,
1405 highlighted_rows: HashMap::default(),
1406 background_highlights: Default::default(),
1407 gutter_highlights: TreeMap::default(),
1408 scrollbar_marker_state: ScrollbarMarkerState::default(),
1409 active_indent_guides_state: ActiveIndentGuidesState::default(),
1410 nav_history: None,
1411 context_menu: RefCell::new(None),
1412 mouse_context_menu: None,
1413 completion_tasks: Default::default(),
1414 signature_help_state: SignatureHelpState::default(),
1415 auto_signature_help: None,
1416 find_all_references_task_sources: Vec::new(),
1417 next_completion_id: 0,
1418 next_inlay_id: 0,
1419 code_action_providers,
1420 available_code_actions: Default::default(),
1421 code_actions_task: Default::default(),
1422 selection_highlight_task: Default::default(),
1423 document_highlights_task: Default::default(),
1424 linked_editing_range_task: Default::default(),
1425 pending_rename: Default::default(),
1426 searchable: true,
1427 cursor_shape: EditorSettings::get_global(cx)
1428 .cursor_shape
1429 .unwrap_or_default(),
1430 current_line_highlight: None,
1431 autoindent_mode: Some(AutoindentMode::EachLine),
1432 collapse_matches: false,
1433 workspace: None,
1434 input_enabled: true,
1435 use_modal_editing: mode == EditorMode::Full,
1436 read_only: false,
1437 use_autoclose: true,
1438 use_auto_surround: true,
1439 auto_replace_emoji_shortcode: false,
1440 jsx_tag_auto_close_enabled_in_any_buffer: false,
1441 leader_peer_id: None,
1442 remote_id: None,
1443 hover_state: Default::default(),
1444 pending_mouse_down: None,
1445 hovered_link_state: Default::default(),
1446 edit_prediction_provider: None,
1447 active_inline_completion: None,
1448 stale_inline_completion_in_menu: None,
1449 edit_prediction_preview: EditPredictionPreview::Inactive {
1450 released_too_fast: false,
1451 },
1452 inline_diagnostics_enabled: mode == EditorMode::Full,
1453 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1454
1455 gutter_hovered: false,
1456 pixel_position_of_newest_cursor: None,
1457 last_bounds: None,
1458 last_position_map: None,
1459 expect_bounds_change: None,
1460 gutter_dimensions: GutterDimensions::default(),
1461 style: None,
1462 show_cursor_names: false,
1463 hovered_cursors: Default::default(),
1464 next_editor_action_id: EditorActionId::default(),
1465 editor_actions: Rc::default(),
1466 inline_completions_hidden_for_vim_mode: false,
1467 show_inline_completions_override: None,
1468 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1469 edit_prediction_settings: EditPredictionSettings::Disabled,
1470 edit_prediction_indent_conflict: false,
1471 edit_prediction_requires_modifier_in_indent_conflict: true,
1472 custom_context_menu: None,
1473 show_git_blame_gutter: false,
1474 show_git_blame_inline: false,
1475 show_selection_menu: None,
1476 show_git_blame_inline_delay_task: None,
1477 git_blame_inline_tooltip: None,
1478 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1479 serialize_dirty_buffers: ProjectSettings::get_global(cx)
1480 .session
1481 .restore_unsaved_buffers,
1482 blame: None,
1483 blame_subscription: None,
1484 tasks: Default::default(),
1485
1486 breakpoint_store,
1487 gutter_breakpoint_indicator: None,
1488 _subscriptions: vec![
1489 cx.observe(&buffer, Self::on_buffer_changed),
1490 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1491 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1492 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1493 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1494 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1495 cx.observe_window_activation(window, |editor, window, cx| {
1496 let active = window.is_window_active();
1497 editor.blink_manager.update(cx, |blink_manager, cx| {
1498 if active {
1499 blink_manager.enable(cx);
1500 } else {
1501 blink_manager.disable(cx);
1502 }
1503 });
1504 }),
1505 ],
1506 tasks_update_task: None,
1507 linked_edit_ranges: Default::default(),
1508 in_project_search: false,
1509 previous_search_ranges: None,
1510 breadcrumb_header: None,
1511 focused_block: None,
1512 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1513 addons: HashMap::default(),
1514 registered_buffers: HashMap::default(),
1515 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1516 selection_mark_mode: false,
1517 toggle_fold_multiple_buffers: Task::ready(()),
1518 serialize_selections: Task::ready(()),
1519 text_style_refinement: None,
1520 load_diff_task: load_uncommitted_diff,
1521 mouse_cursor_hidden: false,
1522 hide_mouse_while_typing: EditorSettings::get_global(cx)
1523 .hide_mouse_while_typing
1524 .unwrap_or(true),
1525 };
1526 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1527 this._subscriptions
1528 .push(cx.observe(breakpoints, |_, _, cx| {
1529 cx.notify();
1530 }));
1531 }
1532 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1533 this._subscriptions.extend(project_subscriptions);
1534
1535 this.end_selection(window, cx);
1536 this.scroll_manager.show_scrollbar(window, cx);
1537 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1538
1539 if mode == EditorMode::Full {
1540 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1541 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1542
1543 if this.git_blame_inline_enabled {
1544 this.git_blame_inline_enabled = true;
1545 this.start_git_blame_inline(false, window, cx);
1546 }
1547
1548 this.go_to_active_debug_line(window, cx);
1549
1550 if let Some(buffer) = buffer.read(cx).as_singleton() {
1551 if let Some(project) = this.project.as_ref() {
1552 let handle = project.update(cx, |project, cx| {
1553 project.register_buffer_with_language_servers(&buffer, cx)
1554 });
1555 this.registered_buffers
1556 .insert(buffer.read(cx).remote_id(), handle);
1557 }
1558 }
1559 }
1560
1561 this.report_editor_event("Editor Opened", None, cx);
1562 this
1563 }
1564
1565 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
1566 self.mouse_context_menu
1567 .as_ref()
1568 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
1569 }
1570
1571 fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
1572 self.key_context_internal(self.has_active_inline_completion(), window, cx)
1573 }
1574
1575 fn key_context_internal(
1576 &self,
1577 has_active_edit_prediction: bool,
1578 window: &Window,
1579 cx: &App,
1580 ) -> KeyContext {
1581 let mut key_context = KeyContext::new_with_defaults();
1582 key_context.add("Editor");
1583 let mode = match self.mode {
1584 EditorMode::SingleLine { .. } => "single_line",
1585 EditorMode::AutoHeight { .. } => "auto_height",
1586 EditorMode::Full => "full",
1587 };
1588
1589 if EditorSettings::jupyter_enabled(cx) {
1590 key_context.add("jupyter");
1591 }
1592
1593 key_context.set("mode", mode);
1594 if self.pending_rename.is_some() {
1595 key_context.add("renaming");
1596 }
1597
1598 match self.context_menu.borrow().as_ref() {
1599 Some(CodeContextMenu::Completions(_)) => {
1600 key_context.add("menu");
1601 key_context.add("showing_completions");
1602 }
1603 Some(CodeContextMenu::CodeActions(_)) => {
1604 key_context.add("menu");
1605 key_context.add("showing_code_actions")
1606 }
1607 None => {}
1608 }
1609
1610 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
1611 if !self.focus_handle(cx).contains_focused(window, cx)
1612 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
1613 {
1614 for addon in self.addons.values() {
1615 addon.extend_key_context(&mut key_context, cx)
1616 }
1617 }
1618
1619 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
1620 if let Some(extension) = singleton_buffer
1621 .read(cx)
1622 .file()
1623 .and_then(|file| file.path().extension()?.to_str())
1624 {
1625 key_context.set("extension", extension.to_string());
1626 }
1627 } else {
1628 key_context.add("multibuffer");
1629 }
1630
1631 if has_active_edit_prediction {
1632 if self.edit_prediction_in_conflict() {
1633 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
1634 } else {
1635 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
1636 key_context.add("copilot_suggestion");
1637 }
1638 }
1639
1640 if self.selection_mark_mode {
1641 key_context.add("selection_mode");
1642 }
1643
1644 key_context
1645 }
1646
1647 pub fn edit_prediction_in_conflict(&self) -> bool {
1648 if !self.show_edit_predictions_in_menu() {
1649 return false;
1650 }
1651
1652 let showing_completions = self
1653 .context_menu
1654 .borrow()
1655 .as_ref()
1656 .map_or(false, |context| {
1657 matches!(context, CodeContextMenu::Completions(_))
1658 });
1659
1660 showing_completions
1661 || self.edit_prediction_requires_modifier()
1662 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
1663 // bindings to insert tab characters.
1664 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
1665 }
1666
1667 pub fn accept_edit_prediction_keybind(
1668 &self,
1669 window: &Window,
1670 cx: &App,
1671 ) -> AcceptEditPredictionBinding {
1672 let key_context = self.key_context_internal(true, window, cx);
1673 let in_conflict = self.edit_prediction_in_conflict();
1674
1675 AcceptEditPredictionBinding(
1676 window
1677 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
1678 .into_iter()
1679 .filter(|binding| {
1680 !in_conflict
1681 || binding
1682 .keystrokes()
1683 .first()
1684 .map_or(false, |keystroke| keystroke.modifiers.modified())
1685 })
1686 .rev()
1687 .min_by_key(|binding| {
1688 binding
1689 .keystrokes()
1690 .first()
1691 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
1692 }),
1693 )
1694 }
1695
1696 pub fn new_file(
1697 workspace: &mut Workspace,
1698 _: &workspace::NewFile,
1699 window: &mut Window,
1700 cx: &mut Context<Workspace>,
1701 ) {
1702 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
1703 "Failed to create buffer",
1704 window,
1705 cx,
1706 |e, _, _| match e.error_code() {
1707 ErrorCode::RemoteUpgradeRequired => Some(format!(
1708 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1709 e.error_tag("required").unwrap_or("the latest version")
1710 )),
1711 _ => None,
1712 },
1713 );
1714 }
1715
1716 pub fn new_in_workspace(
1717 workspace: &mut Workspace,
1718 window: &mut Window,
1719 cx: &mut Context<Workspace>,
1720 ) -> Task<Result<Entity<Editor>>> {
1721 let project = workspace.project().clone();
1722 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1723
1724 cx.spawn_in(window, async move |workspace, cx| {
1725 let buffer = create.await?;
1726 workspace.update_in(cx, |workspace, window, cx| {
1727 let editor =
1728 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
1729 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
1730 editor
1731 })
1732 })
1733 }
1734
1735 fn new_file_vertical(
1736 workspace: &mut Workspace,
1737 _: &workspace::NewFileSplitVertical,
1738 window: &mut Window,
1739 cx: &mut Context<Workspace>,
1740 ) {
1741 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
1742 }
1743
1744 fn new_file_horizontal(
1745 workspace: &mut Workspace,
1746 _: &workspace::NewFileSplitHorizontal,
1747 window: &mut Window,
1748 cx: &mut Context<Workspace>,
1749 ) {
1750 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
1751 }
1752
1753 fn new_file_in_direction(
1754 workspace: &mut Workspace,
1755 direction: SplitDirection,
1756 window: &mut Window,
1757 cx: &mut Context<Workspace>,
1758 ) {
1759 let project = workspace.project().clone();
1760 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1761
1762 cx.spawn_in(window, async move |workspace, cx| {
1763 let buffer = create.await?;
1764 workspace.update_in(cx, move |workspace, window, cx| {
1765 workspace.split_item(
1766 direction,
1767 Box::new(
1768 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
1769 ),
1770 window,
1771 cx,
1772 )
1773 })?;
1774 anyhow::Ok(())
1775 })
1776 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
1777 match e.error_code() {
1778 ErrorCode::RemoteUpgradeRequired => Some(format!(
1779 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1780 e.error_tag("required").unwrap_or("the latest version")
1781 )),
1782 _ => None,
1783 }
1784 });
1785 }
1786
1787 pub fn leader_peer_id(&self) -> Option<PeerId> {
1788 self.leader_peer_id
1789 }
1790
1791 pub fn buffer(&self) -> &Entity<MultiBuffer> {
1792 &self.buffer
1793 }
1794
1795 pub fn workspace(&self) -> Option<Entity<Workspace>> {
1796 self.workspace.as_ref()?.0.upgrade()
1797 }
1798
1799 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
1800 self.buffer().read(cx).title(cx)
1801 }
1802
1803 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
1804 let git_blame_gutter_max_author_length = self
1805 .render_git_blame_gutter(cx)
1806 .then(|| {
1807 if let Some(blame) = self.blame.as_ref() {
1808 let max_author_length =
1809 blame.update(cx, |blame, cx| blame.max_author_length(cx));
1810 Some(max_author_length)
1811 } else {
1812 None
1813 }
1814 })
1815 .flatten();
1816
1817 EditorSnapshot {
1818 mode: self.mode,
1819 show_gutter: self.show_gutter,
1820 show_line_numbers: self.show_line_numbers,
1821 show_git_diff_gutter: self.show_git_diff_gutter,
1822 show_code_actions: self.show_code_actions,
1823 show_runnables: self.show_runnables,
1824 show_breakpoints: self.show_breakpoints,
1825 git_blame_gutter_max_author_length,
1826 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
1827 scroll_anchor: self.scroll_manager.anchor(),
1828 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
1829 placeholder_text: self.placeholder_text.clone(),
1830 is_focused: self.focus_handle.is_focused(window),
1831 current_line_highlight: self
1832 .current_line_highlight
1833 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
1834 gutter_hovered: self.gutter_hovered,
1835 }
1836 }
1837
1838 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
1839 self.buffer.read(cx).language_at(point, cx)
1840 }
1841
1842 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
1843 self.buffer.read(cx).read(cx).file_at(point).cloned()
1844 }
1845
1846 pub fn active_excerpt(
1847 &self,
1848 cx: &App,
1849 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
1850 self.buffer
1851 .read(cx)
1852 .excerpt_containing(self.selections.newest_anchor().head(), cx)
1853 }
1854
1855 pub fn mode(&self) -> EditorMode {
1856 self.mode
1857 }
1858
1859 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
1860 self.collaboration_hub.as_deref()
1861 }
1862
1863 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
1864 self.collaboration_hub = Some(hub);
1865 }
1866
1867 pub fn set_in_project_search(&mut self, in_project_search: bool) {
1868 self.in_project_search = in_project_search;
1869 }
1870
1871 pub fn set_custom_context_menu(
1872 &mut self,
1873 f: impl 'static
1874 + Fn(
1875 &mut Self,
1876 DisplayPoint,
1877 &mut Window,
1878 &mut Context<Self>,
1879 ) -> Option<Entity<ui::ContextMenu>>,
1880 ) {
1881 self.custom_context_menu = Some(Box::new(f))
1882 }
1883
1884 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
1885 self.completion_provider = provider;
1886 }
1887
1888 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
1889 self.semantics_provider.clone()
1890 }
1891
1892 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
1893 self.semantics_provider = provider;
1894 }
1895
1896 pub fn set_edit_prediction_provider<T>(
1897 &mut self,
1898 provider: Option<Entity<T>>,
1899 window: &mut Window,
1900 cx: &mut Context<Self>,
1901 ) where
1902 T: EditPredictionProvider,
1903 {
1904 self.edit_prediction_provider =
1905 provider.map(|provider| RegisteredInlineCompletionProvider {
1906 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
1907 if this.focus_handle.is_focused(window) {
1908 this.update_visible_inline_completion(window, cx);
1909 }
1910 }),
1911 provider: Arc::new(provider),
1912 });
1913 self.update_edit_prediction_settings(cx);
1914 self.refresh_inline_completion(false, false, window, cx);
1915 }
1916
1917 pub fn placeholder_text(&self) -> Option<&str> {
1918 self.placeholder_text.as_deref()
1919 }
1920
1921 pub fn set_placeholder_text(
1922 &mut self,
1923 placeholder_text: impl Into<Arc<str>>,
1924 cx: &mut Context<Self>,
1925 ) {
1926 let placeholder_text = Some(placeholder_text.into());
1927 if self.placeholder_text != placeholder_text {
1928 self.placeholder_text = placeholder_text;
1929 cx.notify();
1930 }
1931 }
1932
1933 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
1934 self.cursor_shape = cursor_shape;
1935
1936 // Disrupt blink for immediate user feedback that the cursor shape has changed
1937 self.blink_manager.update(cx, BlinkManager::show_cursor);
1938
1939 cx.notify();
1940 }
1941
1942 pub fn set_current_line_highlight(
1943 &mut self,
1944 current_line_highlight: Option<CurrentLineHighlight>,
1945 ) {
1946 self.current_line_highlight = current_line_highlight;
1947 }
1948
1949 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
1950 self.collapse_matches = collapse_matches;
1951 }
1952
1953 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
1954 let buffers = self.buffer.read(cx).all_buffers();
1955 let Some(project) = self.project.as_ref() else {
1956 return;
1957 };
1958 project.update(cx, |project, cx| {
1959 for buffer in buffers {
1960 self.registered_buffers
1961 .entry(buffer.read(cx).remote_id())
1962 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
1963 }
1964 })
1965 }
1966
1967 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
1968 if self.collapse_matches {
1969 return range.start..range.start;
1970 }
1971 range.clone()
1972 }
1973
1974 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
1975 if self.display_map.read(cx).clip_at_line_ends != clip {
1976 self.display_map
1977 .update(cx, |map, _| map.clip_at_line_ends = clip);
1978 }
1979 }
1980
1981 pub fn set_input_enabled(&mut self, input_enabled: bool) {
1982 self.input_enabled = input_enabled;
1983 }
1984
1985 pub fn set_inline_completions_hidden_for_vim_mode(
1986 &mut self,
1987 hidden: bool,
1988 window: &mut Window,
1989 cx: &mut Context<Self>,
1990 ) {
1991 if hidden != self.inline_completions_hidden_for_vim_mode {
1992 self.inline_completions_hidden_for_vim_mode = hidden;
1993 if hidden {
1994 self.update_visible_inline_completion(window, cx);
1995 } else {
1996 self.refresh_inline_completion(true, false, window, cx);
1997 }
1998 }
1999 }
2000
2001 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2002 self.menu_inline_completions_policy = value;
2003 }
2004
2005 pub fn set_autoindent(&mut self, autoindent: bool) {
2006 if autoindent {
2007 self.autoindent_mode = Some(AutoindentMode::EachLine);
2008 } else {
2009 self.autoindent_mode = None;
2010 }
2011 }
2012
2013 pub fn read_only(&self, cx: &App) -> bool {
2014 self.read_only || self.buffer.read(cx).read_only()
2015 }
2016
2017 pub fn set_read_only(&mut self, read_only: bool) {
2018 self.read_only = read_only;
2019 }
2020
2021 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2022 self.use_autoclose = autoclose;
2023 }
2024
2025 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2026 self.use_auto_surround = auto_surround;
2027 }
2028
2029 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2030 self.auto_replace_emoji_shortcode = auto_replace;
2031 }
2032
2033 pub fn toggle_edit_predictions(
2034 &mut self,
2035 _: &ToggleEditPrediction,
2036 window: &mut Window,
2037 cx: &mut Context<Self>,
2038 ) {
2039 if self.show_inline_completions_override.is_some() {
2040 self.set_show_edit_predictions(None, window, cx);
2041 } else {
2042 let show_edit_predictions = !self.edit_predictions_enabled();
2043 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2044 }
2045 }
2046
2047 pub fn set_show_edit_predictions(
2048 &mut self,
2049 show_edit_predictions: Option<bool>,
2050 window: &mut Window,
2051 cx: &mut Context<Self>,
2052 ) {
2053 self.show_inline_completions_override = show_edit_predictions;
2054 self.update_edit_prediction_settings(cx);
2055
2056 if let Some(false) = show_edit_predictions {
2057 self.discard_inline_completion(false, cx);
2058 } else {
2059 self.refresh_inline_completion(false, true, window, cx);
2060 }
2061 }
2062
2063 fn inline_completions_disabled_in_scope(
2064 &self,
2065 buffer: &Entity<Buffer>,
2066 buffer_position: language::Anchor,
2067 cx: &App,
2068 ) -> bool {
2069 let snapshot = buffer.read(cx).snapshot();
2070 let settings = snapshot.settings_at(buffer_position, cx);
2071
2072 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2073 return false;
2074 };
2075
2076 scope.override_name().map_or(false, |scope_name| {
2077 settings
2078 .edit_predictions_disabled_in
2079 .iter()
2080 .any(|s| s == scope_name)
2081 })
2082 }
2083
2084 pub fn set_use_modal_editing(&mut self, to: bool) {
2085 self.use_modal_editing = to;
2086 }
2087
2088 pub fn use_modal_editing(&self) -> bool {
2089 self.use_modal_editing
2090 }
2091
2092 fn selections_did_change(
2093 &mut self,
2094 local: bool,
2095 old_cursor_position: &Anchor,
2096 show_completions: bool,
2097 window: &mut Window,
2098 cx: &mut Context<Self>,
2099 ) {
2100 window.invalidate_character_coordinates();
2101
2102 // Copy selections to primary selection buffer
2103 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2104 if local {
2105 let selections = self.selections.all::<usize>(cx);
2106 let buffer_handle = self.buffer.read(cx).read(cx);
2107
2108 let mut text = String::new();
2109 for (index, selection) in selections.iter().enumerate() {
2110 let text_for_selection = buffer_handle
2111 .text_for_range(selection.start..selection.end)
2112 .collect::<String>();
2113
2114 text.push_str(&text_for_selection);
2115 if index != selections.len() - 1 {
2116 text.push('\n');
2117 }
2118 }
2119
2120 if !text.is_empty() {
2121 cx.write_to_primary(ClipboardItem::new_string(text));
2122 }
2123 }
2124
2125 if self.focus_handle.is_focused(window) && self.leader_peer_id.is_none() {
2126 self.buffer.update(cx, |buffer, cx| {
2127 buffer.set_active_selections(
2128 &self.selections.disjoint_anchors(),
2129 self.selections.line_mode,
2130 self.cursor_shape,
2131 cx,
2132 )
2133 });
2134 }
2135 let display_map = self
2136 .display_map
2137 .update(cx, |display_map, cx| display_map.snapshot(cx));
2138 let buffer = &display_map.buffer_snapshot;
2139 self.add_selections_state = None;
2140 self.select_next_state = None;
2141 self.select_prev_state = None;
2142 self.select_larger_syntax_node_stack.clear();
2143 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2144 self.snippet_stack
2145 .invalidate(&self.selections.disjoint_anchors(), buffer);
2146 self.take_rename(false, window, cx);
2147
2148 let new_cursor_position = self.selections.newest_anchor().head();
2149
2150 self.push_to_nav_history(
2151 *old_cursor_position,
2152 Some(new_cursor_position.to_point(buffer)),
2153 cx,
2154 );
2155
2156 if local {
2157 let new_cursor_position = self.selections.newest_anchor().head();
2158 let mut context_menu = self.context_menu.borrow_mut();
2159 let completion_menu = match context_menu.as_ref() {
2160 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2161 _ => {
2162 *context_menu = None;
2163 None
2164 }
2165 };
2166 if let Some(buffer_id) = new_cursor_position.buffer_id {
2167 if !self.registered_buffers.contains_key(&buffer_id) {
2168 if let Some(project) = self.project.as_ref() {
2169 project.update(cx, |project, cx| {
2170 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2171 return;
2172 };
2173 self.registered_buffers.insert(
2174 buffer_id,
2175 project.register_buffer_with_language_servers(&buffer, cx),
2176 );
2177 })
2178 }
2179 }
2180 }
2181
2182 if let Some(completion_menu) = completion_menu {
2183 let cursor_position = new_cursor_position.to_offset(buffer);
2184 let (word_range, kind) =
2185 buffer.surrounding_word(completion_menu.initial_position, true);
2186 if kind == Some(CharKind::Word)
2187 && word_range.to_inclusive().contains(&cursor_position)
2188 {
2189 let mut completion_menu = completion_menu.clone();
2190 drop(context_menu);
2191
2192 let query = Self::completion_query(buffer, cursor_position);
2193 cx.spawn(async move |this, cx| {
2194 completion_menu
2195 .filter(query.as_deref(), cx.background_executor().clone())
2196 .await;
2197
2198 this.update(cx, |this, cx| {
2199 let mut context_menu = this.context_menu.borrow_mut();
2200 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2201 else {
2202 return;
2203 };
2204
2205 if menu.id > completion_menu.id {
2206 return;
2207 }
2208
2209 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2210 drop(context_menu);
2211 cx.notify();
2212 })
2213 })
2214 .detach();
2215
2216 if show_completions {
2217 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2218 }
2219 } else {
2220 drop(context_menu);
2221 self.hide_context_menu(window, cx);
2222 }
2223 } else {
2224 drop(context_menu);
2225 }
2226
2227 hide_hover(self, cx);
2228
2229 if old_cursor_position.to_display_point(&display_map).row()
2230 != new_cursor_position.to_display_point(&display_map).row()
2231 {
2232 self.available_code_actions.take();
2233 }
2234 self.refresh_code_actions(window, cx);
2235 self.refresh_document_highlights(cx);
2236 self.refresh_selected_text_highlights(window, cx);
2237 refresh_matching_bracket_highlights(self, window, cx);
2238 self.update_visible_inline_completion(window, cx);
2239 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2240 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2241 if self.git_blame_inline_enabled {
2242 self.start_inline_blame_timer(window, cx);
2243 }
2244 }
2245
2246 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2247 cx.emit(EditorEvent::SelectionsChanged { local });
2248
2249 let selections = &self.selections.disjoint;
2250 if selections.len() == 1 {
2251 cx.emit(SearchEvent::ActiveMatchChanged)
2252 }
2253 if local
2254 && self.is_singleton(cx)
2255 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
2256 {
2257 if let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) {
2258 let background_executor = cx.background_executor().clone();
2259 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2260 let snapshot = self.buffer().read(cx).snapshot(cx);
2261 let selections = selections.clone();
2262 self.serialize_selections = cx.background_spawn(async move {
2263 background_executor.timer(Duration::from_millis(100)).await;
2264 let selections = selections
2265 .iter()
2266 .map(|selection| {
2267 (
2268 selection.start.to_offset(&snapshot),
2269 selection.end.to_offset(&snapshot),
2270 )
2271 })
2272 .collect();
2273 DB.save_editor_selections(editor_id, workspace_id, selections)
2274 .await
2275 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2276 .log_err();
2277 });
2278 }
2279 }
2280
2281 cx.notify();
2282 }
2283
2284 pub fn sync_selections(
2285 &mut self,
2286 other: Entity<Editor>,
2287 cx: &mut Context<Self>,
2288 ) -> gpui::Subscription {
2289 let other_selections = other.read(cx).selections.disjoint.to_vec();
2290 self.selections.change_with(cx, |selections| {
2291 selections.select_anchors(other_selections);
2292 });
2293
2294 let other_subscription =
2295 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2296 EditorEvent::SelectionsChanged { local: true } => {
2297 let other_selections = other.read(cx).selections.disjoint.to_vec();
2298 if other_selections.is_empty() {
2299 return;
2300 }
2301 this.selections.change_with(cx, |selections| {
2302 selections.select_anchors(other_selections);
2303 });
2304 }
2305 _ => {}
2306 });
2307
2308 let this_subscription =
2309 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2310 EditorEvent::SelectionsChanged { local: true } => {
2311 let these_selections = this.selections.disjoint.to_vec();
2312 if these_selections.is_empty() {
2313 return;
2314 }
2315 other.update(cx, |other_editor, cx| {
2316 other_editor.selections.change_with(cx, |selections| {
2317 selections.select_anchors(these_selections);
2318 })
2319 });
2320 }
2321 _ => {}
2322 });
2323
2324 Subscription::join(other_subscription, this_subscription)
2325 }
2326
2327 pub fn change_selections<R>(
2328 &mut self,
2329 autoscroll: Option<Autoscroll>,
2330 window: &mut Window,
2331 cx: &mut Context<Self>,
2332 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2333 ) -> R {
2334 self.change_selections_inner(autoscroll, true, window, cx, change)
2335 }
2336
2337 fn change_selections_inner<R>(
2338 &mut self,
2339 autoscroll: Option<Autoscroll>,
2340 request_completions: bool,
2341 window: &mut Window,
2342 cx: &mut Context<Self>,
2343 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2344 ) -> R {
2345 let old_cursor_position = self.selections.newest_anchor().head();
2346 self.push_to_selection_history();
2347
2348 let (changed, result) = self.selections.change_with(cx, change);
2349
2350 if changed {
2351 if let Some(autoscroll) = autoscroll {
2352 self.request_autoscroll(autoscroll, cx);
2353 }
2354 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2355
2356 if self.should_open_signature_help_automatically(
2357 &old_cursor_position,
2358 self.signature_help_state.backspace_pressed(),
2359 cx,
2360 ) {
2361 self.show_signature_help(&ShowSignatureHelp, window, cx);
2362 }
2363 self.signature_help_state.set_backspace_pressed(false);
2364 }
2365
2366 result
2367 }
2368
2369 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2370 where
2371 I: IntoIterator<Item = (Range<S>, T)>,
2372 S: ToOffset,
2373 T: Into<Arc<str>>,
2374 {
2375 if self.read_only(cx) {
2376 return;
2377 }
2378
2379 self.buffer
2380 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2381 }
2382
2383 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2384 where
2385 I: IntoIterator<Item = (Range<S>, T)>,
2386 S: ToOffset,
2387 T: Into<Arc<str>>,
2388 {
2389 if self.read_only(cx) {
2390 return;
2391 }
2392
2393 self.buffer.update(cx, |buffer, cx| {
2394 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2395 });
2396 }
2397
2398 pub fn edit_with_block_indent<I, S, T>(
2399 &mut self,
2400 edits: I,
2401 original_indent_columns: Vec<Option<u32>>,
2402 cx: &mut Context<Self>,
2403 ) where
2404 I: IntoIterator<Item = (Range<S>, T)>,
2405 S: ToOffset,
2406 T: Into<Arc<str>>,
2407 {
2408 if self.read_only(cx) {
2409 return;
2410 }
2411
2412 self.buffer.update(cx, |buffer, cx| {
2413 buffer.edit(
2414 edits,
2415 Some(AutoindentMode::Block {
2416 original_indent_columns,
2417 }),
2418 cx,
2419 )
2420 });
2421 }
2422
2423 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2424 self.hide_context_menu(window, cx);
2425
2426 match phase {
2427 SelectPhase::Begin {
2428 position,
2429 add,
2430 click_count,
2431 } => self.begin_selection(position, add, click_count, window, cx),
2432 SelectPhase::BeginColumnar {
2433 position,
2434 goal_column,
2435 reset,
2436 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
2437 SelectPhase::Extend {
2438 position,
2439 click_count,
2440 } => self.extend_selection(position, click_count, window, cx),
2441 SelectPhase::Update {
2442 position,
2443 goal_column,
2444 scroll_delta,
2445 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
2446 SelectPhase::End => self.end_selection(window, cx),
2447 }
2448 }
2449
2450 fn extend_selection(
2451 &mut self,
2452 position: DisplayPoint,
2453 click_count: usize,
2454 window: &mut Window,
2455 cx: &mut Context<Self>,
2456 ) {
2457 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2458 let tail = self.selections.newest::<usize>(cx).tail();
2459 self.begin_selection(position, false, click_count, window, cx);
2460
2461 let position = position.to_offset(&display_map, Bias::Left);
2462 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
2463
2464 let mut pending_selection = self
2465 .selections
2466 .pending_anchor()
2467 .expect("extend_selection not called with pending selection");
2468 if position >= tail {
2469 pending_selection.start = tail_anchor;
2470 } else {
2471 pending_selection.end = tail_anchor;
2472 pending_selection.reversed = true;
2473 }
2474
2475 let mut pending_mode = self.selections.pending_mode().unwrap();
2476 match &mut pending_mode {
2477 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
2478 _ => {}
2479 }
2480
2481 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
2482 s.set_pending(pending_selection, pending_mode)
2483 });
2484 }
2485
2486 fn begin_selection(
2487 &mut self,
2488 position: DisplayPoint,
2489 add: bool,
2490 click_count: usize,
2491 window: &mut Window,
2492 cx: &mut Context<Self>,
2493 ) {
2494 if !self.focus_handle.is_focused(window) {
2495 self.last_focused_descendant = None;
2496 window.focus(&self.focus_handle);
2497 }
2498
2499 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2500 let buffer = &display_map.buffer_snapshot;
2501 let newest_selection = self.selections.newest_anchor().clone();
2502 let position = display_map.clip_point(position, Bias::Left);
2503
2504 let start;
2505 let end;
2506 let mode;
2507 let mut auto_scroll;
2508 match click_count {
2509 1 => {
2510 start = buffer.anchor_before(position.to_point(&display_map));
2511 end = start;
2512 mode = SelectMode::Character;
2513 auto_scroll = true;
2514 }
2515 2 => {
2516 let range = movement::surrounding_word(&display_map, position);
2517 start = buffer.anchor_before(range.start.to_point(&display_map));
2518 end = buffer.anchor_before(range.end.to_point(&display_map));
2519 mode = SelectMode::Word(start..end);
2520 auto_scroll = true;
2521 }
2522 3 => {
2523 let position = display_map
2524 .clip_point(position, Bias::Left)
2525 .to_point(&display_map);
2526 let line_start = display_map.prev_line_boundary(position).0;
2527 let next_line_start = buffer.clip_point(
2528 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2529 Bias::Left,
2530 );
2531 start = buffer.anchor_before(line_start);
2532 end = buffer.anchor_before(next_line_start);
2533 mode = SelectMode::Line(start..end);
2534 auto_scroll = true;
2535 }
2536 _ => {
2537 start = buffer.anchor_before(0);
2538 end = buffer.anchor_before(buffer.len());
2539 mode = SelectMode::All;
2540 auto_scroll = false;
2541 }
2542 }
2543 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
2544
2545 let point_to_delete: Option<usize> = {
2546 let selected_points: Vec<Selection<Point>> =
2547 self.selections.disjoint_in_range(start..end, cx);
2548
2549 if !add || click_count > 1 {
2550 None
2551 } else if !selected_points.is_empty() {
2552 Some(selected_points[0].id)
2553 } else {
2554 let clicked_point_already_selected =
2555 self.selections.disjoint.iter().find(|selection| {
2556 selection.start.to_point(buffer) == start.to_point(buffer)
2557 || selection.end.to_point(buffer) == end.to_point(buffer)
2558 });
2559
2560 clicked_point_already_selected.map(|selection| selection.id)
2561 }
2562 };
2563
2564 let selections_count = self.selections.count();
2565
2566 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
2567 if let Some(point_to_delete) = point_to_delete {
2568 s.delete(point_to_delete);
2569
2570 if selections_count == 1 {
2571 s.set_pending_anchor_range(start..end, mode);
2572 }
2573 } else {
2574 if !add {
2575 s.clear_disjoint();
2576 } else if click_count > 1 {
2577 s.delete(newest_selection.id)
2578 }
2579
2580 s.set_pending_anchor_range(start..end, mode);
2581 }
2582 });
2583 }
2584
2585 fn begin_columnar_selection(
2586 &mut self,
2587 position: DisplayPoint,
2588 goal_column: u32,
2589 reset: bool,
2590 window: &mut Window,
2591 cx: &mut Context<Self>,
2592 ) {
2593 if !self.focus_handle.is_focused(window) {
2594 self.last_focused_descendant = None;
2595 window.focus(&self.focus_handle);
2596 }
2597
2598 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2599
2600 if reset {
2601 let pointer_position = display_map
2602 .buffer_snapshot
2603 .anchor_before(position.to_point(&display_map));
2604
2605 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
2606 s.clear_disjoint();
2607 s.set_pending_anchor_range(
2608 pointer_position..pointer_position,
2609 SelectMode::Character,
2610 );
2611 });
2612 }
2613
2614 let tail = self.selections.newest::<Point>(cx).tail();
2615 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
2616
2617 if !reset {
2618 self.select_columns(
2619 tail.to_display_point(&display_map),
2620 position,
2621 goal_column,
2622 &display_map,
2623 window,
2624 cx,
2625 );
2626 }
2627 }
2628
2629 fn update_selection(
2630 &mut self,
2631 position: DisplayPoint,
2632 goal_column: u32,
2633 scroll_delta: gpui::Point<f32>,
2634 window: &mut Window,
2635 cx: &mut Context<Self>,
2636 ) {
2637 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2638
2639 if let Some(tail) = self.columnar_selection_tail.as_ref() {
2640 let tail = tail.to_display_point(&display_map);
2641 self.select_columns(tail, position, goal_column, &display_map, window, cx);
2642 } else if let Some(mut pending) = self.selections.pending_anchor() {
2643 let buffer = self.buffer.read(cx).snapshot(cx);
2644 let head;
2645 let tail;
2646 let mode = self.selections.pending_mode().unwrap();
2647 match &mode {
2648 SelectMode::Character => {
2649 head = position.to_point(&display_map);
2650 tail = pending.tail().to_point(&buffer);
2651 }
2652 SelectMode::Word(original_range) => {
2653 let original_display_range = original_range.start.to_display_point(&display_map)
2654 ..original_range.end.to_display_point(&display_map);
2655 let original_buffer_range = original_display_range.start.to_point(&display_map)
2656 ..original_display_range.end.to_point(&display_map);
2657 if movement::is_inside_word(&display_map, position)
2658 || original_display_range.contains(&position)
2659 {
2660 let word_range = movement::surrounding_word(&display_map, position);
2661 if word_range.start < original_display_range.start {
2662 head = word_range.start.to_point(&display_map);
2663 } else {
2664 head = word_range.end.to_point(&display_map);
2665 }
2666 } else {
2667 head = position.to_point(&display_map);
2668 }
2669
2670 if head <= original_buffer_range.start {
2671 tail = original_buffer_range.end;
2672 } else {
2673 tail = original_buffer_range.start;
2674 }
2675 }
2676 SelectMode::Line(original_range) => {
2677 let original_range = original_range.to_point(&display_map.buffer_snapshot);
2678
2679 let position = display_map
2680 .clip_point(position, Bias::Left)
2681 .to_point(&display_map);
2682 let line_start = display_map.prev_line_boundary(position).0;
2683 let next_line_start = buffer.clip_point(
2684 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2685 Bias::Left,
2686 );
2687
2688 if line_start < original_range.start {
2689 head = line_start
2690 } else {
2691 head = next_line_start
2692 }
2693
2694 if head <= original_range.start {
2695 tail = original_range.end;
2696 } else {
2697 tail = original_range.start;
2698 }
2699 }
2700 SelectMode::All => {
2701 return;
2702 }
2703 };
2704
2705 if head < tail {
2706 pending.start = buffer.anchor_before(head);
2707 pending.end = buffer.anchor_before(tail);
2708 pending.reversed = true;
2709 } else {
2710 pending.start = buffer.anchor_before(tail);
2711 pending.end = buffer.anchor_before(head);
2712 pending.reversed = false;
2713 }
2714
2715 self.change_selections(None, window, cx, |s| {
2716 s.set_pending(pending, mode);
2717 });
2718 } else {
2719 log::error!("update_selection dispatched with no pending selection");
2720 return;
2721 }
2722
2723 self.apply_scroll_delta(scroll_delta, window, cx);
2724 cx.notify();
2725 }
2726
2727 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2728 self.columnar_selection_tail.take();
2729 if self.selections.pending_anchor().is_some() {
2730 let selections = self.selections.all::<usize>(cx);
2731 self.change_selections(None, window, cx, |s| {
2732 s.select(selections);
2733 s.clear_pending();
2734 });
2735 }
2736 }
2737
2738 fn select_columns(
2739 &mut self,
2740 tail: DisplayPoint,
2741 head: DisplayPoint,
2742 goal_column: u32,
2743 display_map: &DisplaySnapshot,
2744 window: &mut Window,
2745 cx: &mut Context<Self>,
2746 ) {
2747 let start_row = cmp::min(tail.row(), head.row());
2748 let end_row = cmp::max(tail.row(), head.row());
2749 let start_column = cmp::min(tail.column(), goal_column);
2750 let end_column = cmp::max(tail.column(), goal_column);
2751 let reversed = start_column < tail.column();
2752
2753 let selection_ranges = (start_row.0..=end_row.0)
2754 .map(DisplayRow)
2755 .filter_map(|row| {
2756 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
2757 let start = display_map
2758 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
2759 .to_point(display_map);
2760 let end = display_map
2761 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
2762 .to_point(display_map);
2763 if reversed {
2764 Some(end..start)
2765 } else {
2766 Some(start..end)
2767 }
2768 } else {
2769 None
2770 }
2771 })
2772 .collect::<Vec<_>>();
2773
2774 self.change_selections(None, window, cx, |s| {
2775 s.select_ranges(selection_ranges);
2776 });
2777 cx.notify();
2778 }
2779
2780 pub fn has_pending_nonempty_selection(&self) -> bool {
2781 let pending_nonempty_selection = match self.selections.pending_anchor() {
2782 Some(Selection { start, end, .. }) => start != end,
2783 None => false,
2784 };
2785
2786 pending_nonempty_selection
2787 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
2788 }
2789
2790 pub fn has_pending_selection(&self) -> bool {
2791 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
2792 }
2793
2794 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
2795 self.selection_mark_mode = false;
2796
2797 if self.clear_expanded_diff_hunks(cx) {
2798 cx.notify();
2799 return;
2800 }
2801 if self.dismiss_menus_and_popups(true, window, cx) {
2802 return;
2803 }
2804
2805 if self.mode == EditorMode::Full
2806 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
2807 {
2808 return;
2809 }
2810
2811 cx.propagate();
2812 }
2813
2814 pub fn dismiss_menus_and_popups(
2815 &mut self,
2816 is_user_requested: bool,
2817 window: &mut Window,
2818 cx: &mut Context<Self>,
2819 ) -> bool {
2820 if self.take_rename(false, window, cx).is_some() {
2821 return true;
2822 }
2823
2824 if hide_hover(self, cx) {
2825 return true;
2826 }
2827
2828 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
2829 return true;
2830 }
2831
2832 if self.hide_context_menu(window, cx).is_some() {
2833 return true;
2834 }
2835
2836 if self.mouse_context_menu.take().is_some() {
2837 return true;
2838 }
2839
2840 if is_user_requested && self.discard_inline_completion(true, cx) {
2841 return true;
2842 }
2843
2844 if self.snippet_stack.pop().is_some() {
2845 return true;
2846 }
2847
2848 if self.mode == EditorMode::Full && self.active_diagnostics.is_some() {
2849 self.dismiss_diagnostics(cx);
2850 return true;
2851 }
2852
2853 false
2854 }
2855
2856 fn linked_editing_ranges_for(
2857 &self,
2858 selection: Range<text::Anchor>,
2859 cx: &App,
2860 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
2861 if self.linked_edit_ranges.is_empty() {
2862 return None;
2863 }
2864 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
2865 selection.end.buffer_id.and_then(|end_buffer_id| {
2866 if selection.start.buffer_id != Some(end_buffer_id) {
2867 return None;
2868 }
2869 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
2870 let snapshot = buffer.read(cx).snapshot();
2871 self.linked_edit_ranges
2872 .get(end_buffer_id, selection.start..selection.end, &snapshot)
2873 .map(|ranges| (ranges, snapshot, buffer))
2874 })?;
2875 use text::ToOffset as TO;
2876 // find offset from the start of current range to current cursor position
2877 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
2878
2879 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
2880 let start_difference = start_offset - start_byte_offset;
2881 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
2882 let end_difference = end_offset - start_byte_offset;
2883 // Current range has associated linked ranges.
2884 let mut linked_edits = HashMap::<_, Vec<_>>::default();
2885 for range in linked_ranges.iter() {
2886 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
2887 let end_offset = start_offset + end_difference;
2888 let start_offset = start_offset + start_difference;
2889 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
2890 continue;
2891 }
2892 if self.selections.disjoint_anchor_ranges().any(|s| {
2893 if s.start.buffer_id != selection.start.buffer_id
2894 || s.end.buffer_id != selection.end.buffer_id
2895 {
2896 return false;
2897 }
2898 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
2899 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
2900 }) {
2901 continue;
2902 }
2903 let start = buffer_snapshot.anchor_after(start_offset);
2904 let end = buffer_snapshot.anchor_after(end_offset);
2905 linked_edits
2906 .entry(buffer.clone())
2907 .or_default()
2908 .push(start..end);
2909 }
2910 Some(linked_edits)
2911 }
2912
2913 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
2914 let text: Arc<str> = text.into();
2915
2916 if self.read_only(cx) {
2917 return;
2918 }
2919
2920 self.mouse_cursor_hidden = self.hide_mouse_while_typing;
2921
2922 let selections = self.selections.all_adjusted(cx);
2923 let mut bracket_inserted = false;
2924 let mut edits = Vec::new();
2925 let mut linked_edits = HashMap::<_, Vec<_>>::default();
2926 let mut new_selections = Vec::with_capacity(selections.len());
2927 let mut new_autoclose_regions = Vec::new();
2928 let snapshot = self.buffer.read(cx).read(cx);
2929
2930 for (selection, autoclose_region) in
2931 self.selections_with_autoclose_regions(selections, &snapshot)
2932 {
2933 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
2934 // Determine if the inserted text matches the opening or closing
2935 // bracket of any of this language's bracket pairs.
2936 let mut bracket_pair = None;
2937 let mut is_bracket_pair_start = false;
2938 let mut is_bracket_pair_end = false;
2939 if !text.is_empty() {
2940 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
2941 // and they are removing the character that triggered IME popup.
2942 for (pair, enabled) in scope.brackets() {
2943 if !pair.close && !pair.surround {
2944 continue;
2945 }
2946
2947 if enabled && pair.start.ends_with(text.as_ref()) {
2948 let prefix_len = pair.start.len() - text.len();
2949 let preceding_text_matches_prefix = prefix_len == 0
2950 || (selection.start.column >= (prefix_len as u32)
2951 && snapshot.contains_str_at(
2952 Point::new(
2953 selection.start.row,
2954 selection.start.column - (prefix_len as u32),
2955 ),
2956 &pair.start[..prefix_len],
2957 ));
2958 if preceding_text_matches_prefix {
2959 bracket_pair = Some(pair.clone());
2960 is_bracket_pair_start = true;
2961 break;
2962 }
2963 }
2964 if pair.end.as_str() == text.as_ref() {
2965 bracket_pair = Some(pair.clone());
2966 is_bracket_pair_end = true;
2967 break;
2968 }
2969 }
2970 }
2971
2972 if let Some(bracket_pair) = bracket_pair {
2973 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
2974 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
2975 let auto_surround =
2976 self.use_auto_surround && snapshot_settings.use_auto_surround;
2977 if selection.is_empty() {
2978 if is_bracket_pair_start {
2979 // If the inserted text is a suffix of an opening bracket and the
2980 // selection is preceded by the rest of the opening bracket, then
2981 // insert the closing bracket.
2982 let following_text_allows_autoclose = snapshot
2983 .chars_at(selection.start)
2984 .next()
2985 .map_or(true, |c| scope.should_autoclose_before(c));
2986
2987 let preceding_text_allows_autoclose = selection.start.column == 0
2988 || snapshot.reversed_chars_at(selection.start).next().map_or(
2989 true,
2990 |c| {
2991 bracket_pair.start != bracket_pair.end
2992 || !snapshot
2993 .char_classifier_at(selection.start)
2994 .is_word(c)
2995 },
2996 );
2997
2998 let is_closing_quote = if bracket_pair.end == bracket_pair.start
2999 && bracket_pair.start.len() == 1
3000 {
3001 let target = bracket_pair.start.chars().next().unwrap();
3002 let current_line_count = snapshot
3003 .reversed_chars_at(selection.start)
3004 .take_while(|&c| c != '\n')
3005 .filter(|&c| c == target)
3006 .count();
3007 current_line_count % 2 == 1
3008 } else {
3009 false
3010 };
3011
3012 if autoclose
3013 && bracket_pair.close
3014 && following_text_allows_autoclose
3015 && preceding_text_allows_autoclose
3016 && !is_closing_quote
3017 {
3018 let anchor = snapshot.anchor_before(selection.end);
3019 new_selections.push((selection.map(|_| anchor), text.len()));
3020 new_autoclose_regions.push((
3021 anchor,
3022 text.len(),
3023 selection.id,
3024 bracket_pair.clone(),
3025 ));
3026 edits.push((
3027 selection.range(),
3028 format!("{}{}", text, bracket_pair.end).into(),
3029 ));
3030 bracket_inserted = true;
3031 continue;
3032 }
3033 }
3034
3035 if let Some(region) = autoclose_region {
3036 // If the selection is followed by an auto-inserted closing bracket,
3037 // then don't insert that closing bracket again; just move the selection
3038 // past the closing bracket.
3039 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3040 && text.as_ref() == region.pair.end.as_str();
3041 if should_skip {
3042 let anchor = snapshot.anchor_after(selection.end);
3043 new_selections
3044 .push((selection.map(|_| anchor), region.pair.end.len()));
3045 continue;
3046 }
3047 }
3048
3049 let always_treat_brackets_as_autoclosed = snapshot
3050 .language_settings_at(selection.start, cx)
3051 .always_treat_brackets_as_autoclosed;
3052 if always_treat_brackets_as_autoclosed
3053 && is_bracket_pair_end
3054 && snapshot.contains_str_at(selection.end, text.as_ref())
3055 {
3056 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3057 // and the inserted text is a closing bracket and the selection is followed
3058 // by the closing bracket then move the selection past the closing bracket.
3059 let anchor = snapshot.anchor_after(selection.end);
3060 new_selections.push((selection.map(|_| anchor), text.len()));
3061 continue;
3062 }
3063 }
3064 // If an opening bracket is 1 character long and is typed while
3065 // text is selected, then surround that text with the bracket pair.
3066 else if auto_surround
3067 && bracket_pair.surround
3068 && is_bracket_pair_start
3069 && bracket_pair.start.chars().count() == 1
3070 {
3071 edits.push((selection.start..selection.start, text.clone()));
3072 edits.push((
3073 selection.end..selection.end,
3074 bracket_pair.end.as_str().into(),
3075 ));
3076 bracket_inserted = true;
3077 new_selections.push((
3078 Selection {
3079 id: selection.id,
3080 start: snapshot.anchor_after(selection.start),
3081 end: snapshot.anchor_before(selection.end),
3082 reversed: selection.reversed,
3083 goal: selection.goal,
3084 },
3085 0,
3086 ));
3087 continue;
3088 }
3089 }
3090 }
3091
3092 if self.auto_replace_emoji_shortcode
3093 && selection.is_empty()
3094 && text.as_ref().ends_with(':')
3095 {
3096 if let Some(possible_emoji_short_code) =
3097 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3098 {
3099 if !possible_emoji_short_code.is_empty() {
3100 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3101 let emoji_shortcode_start = Point::new(
3102 selection.start.row,
3103 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3104 );
3105
3106 // Remove shortcode from buffer
3107 edits.push((
3108 emoji_shortcode_start..selection.start,
3109 "".to_string().into(),
3110 ));
3111 new_selections.push((
3112 Selection {
3113 id: selection.id,
3114 start: snapshot.anchor_after(emoji_shortcode_start),
3115 end: snapshot.anchor_before(selection.start),
3116 reversed: selection.reversed,
3117 goal: selection.goal,
3118 },
3119 0,
3120 ));
3121
3122 // Insert emoji
3123 let selection_start_anchor = snapshot.anchor_after(selection.start);
3124 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3125 edits.push((selection.start..selection.end, emoji.to_string().into()));
3126
3127 continue;
3128 }
3129 }
3130 }
3131 }
3132
3133 // If not handling any auto-close operation, then just replace the selected
3134 // text with the given input and move the selection to the end of the
3135 // newly inserted text.
3136 let anchor = snapshot.anchor_after(selection.end);
3137 if !self.linked_edit_ranges.is_empty() {
3138 let start_anchor = snapshot.anchor_before(selection.start);
3139
3140 let is_word_char = text.chars().next().map_or(true, |char| {
3141 let classifier = snapshot.char_classifier_at(start_anchor.to_offset(&snapshot));
3142 classifier.is_word(char)
3143 });
3144
3145 if is_word_char {
3146 if let Some(ranges) = self
3147 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3148 {
3149 for (buffer, edits) in ranges {
3150 linked_edits
3151 .entry(buffer.clone())
3152 .or_default()
3153 .extend(edits.into_iter().map(|range| (range, text.clone())));
3154 }
3155 }
3156 }
3157 }
3158
3159 new_selections.push((selection.map(|_| anchor), 0));
3160 edits.push((selection.start..selection.end, text.clone()));
3161 }
3162
3163 drop(snapshot);
3164
3165 self.transact(window, cx, |this, window, cx| {
3166 let initial_buffer_versions =
3167 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3168
3169 this.buffer.update(cx, |buffer, cx| {
3170 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3171 });
3172 for (buffer, edits) in linked_edits {
3173 buffer.update(cx, |buffer, cx| {
3174 let snapshot = buffer.snapshot();
3175 let edits = edits
3176 .into_iter()
3177 .map(|(range, text)| {
3178 use text::ToPoint as TP;
3179 let end_point = TP::to_point(&range.end, &snapshot);
3180 let start_point = TP::to_point(&range.start, &snapshot);
3181 (start_point..end_point, text)
3182 })
3183 .sorted_by_key(|(range, _)| range.start)
3184 .collect::<Vec<_>>();
3185 buffer.edit(edits, None, cx);
3186 })
3187 }
3188 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3189 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3190 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3191 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3192 .zip(new_selection_deltas)
3193 .map(|(selection, delta)| Selection {
3194 id: selection.id,
3195 start: selection.start + delta,
3196 end: selection.end + delta,
3197 reversed: selection.reversed,
3198 goal: SelectionGoal::None,
3199 })
3200 .collect::<Vec<_>>();
3201
3202 let mut i = 0;
3203 for (position, delta, selection_id, pair) in new_autoclose_regions {
3204 let position = position.to_offset(&map.buffer_snapshot) + delta;
3205 let start = map.buffer_snapshot.anchor_before(position);
3206 let end = map.buffer_snapshot.anchor_after(position);
3207 while let Some(existing_state) = this.autoclose_regions.get(i) {
3208 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3209 Ordering::Less => i += 1,
3210 Ordering::Greater => break,
3211 Ordering::Equal => {
3212 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3213 Ordering::Less => i += 1,
3214 Ordering::Equal => break,
3215 Ordering::Greater => break,
3216 }
3217 }
3218 }
3219 }
3220 this.autoclose_regions.insert(
3221 i,
3222 AutocloseRegion {
3223 selection_id,
3224 range: start..end,
3225 pair,
3226 },
3227 );
3228 }
3229
3230 let had_active_inline_completion = this.has_active_inline_completion();
3231 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3232 s.select(new_selections)
3233 });
3234
3235 if !bracket_inserted {
3236 if let Some(on_type_format_task) =
3237 this.trigger_on_type_formatting(text.to_string(), window, cx)
3238 {
3239 on_type_format_task.detach_and_log_err(cx);
3240 }
3241 }
3242
3243 let editor_settings = EditorSettings::get_global(cx);
3244 if bracket_inserted
3245 && (editor_settings.auto_signature_help
3246 || editor_settings.show_signature_help_after_edits)
3247 {
3248 this.show_signature_help(&ShowSignatureHelp, window, cx);
3249 }
3250
3251 let trigger_in_words =
3252 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3253 if this.hard_wrap.is_some() {
3254 let latest: Range<Point> = this.selections.newest(cx).range();
3255 if latest.is_empty()
3256 && this
3257 .buffer()
3258 .read(cx)
3259 .snapshot(cx)
3260 .line_len(MultiBufferRow(latest.start.row))
3261 == latest.start.column
3262 {
3263 this.rewrap_impl(
3264 RewrapOptions {
3265 override_language_settings: true,
3266 preserve_existing_whitespace: true,
3267 },
3268 cx,
3269 )
3270 }
3271 }
3272 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3273 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3274 this.refresh_inline_completion(true, false, window, cx);
3275 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3276 });
3277 }
3278
3279 fn find_possible_emoji_shortcode_at_position(
3280 snapshot: &MultiBufferSnapshot,
3281 position: Point,
3282 ) -> Option<String> {
3283 let mut chars = Vec::new();
3284 let mut found_colon = false;
3285 for char in snapshot.reversed_chars_at(position).take(100) {
3286 // Found a possible emoji shortcode in the middle of the buffer
3287 if found_colon {
3288 if char.is_whitespace() {
3289 chars.reverse();
3290 return Some(chars.iter().collect());
3291 }
3292 // If the previous character is not a whitespace, we are in the middle of a word
3293 // and we only want to complete the shortcode if the word is made up of other emojis
3294 let mut containing_word = String::new();
3295 for ch in snapshot
3296 .reversed_chars_at(position)
3297 .skip(chars.len() + 1)
3298 .take(100)
3299 {
3300 if ch.is_whitespace() {
3301 break;
3302 }
3303 containing_word.push(ch);
3304 }
3305 let containing_word = containing_word.chars().rev().collect::<String>();
3306 if util::word_consists_of_emojis(containing_word.as_str()) {
3307 chars.reverse();
3308 return Some(chars.iter().collect());
3309 }
3310 }
3311
3312 if char.is_whitespace() || !char.is_ascii() {
3313 return None;
3314 }
3315 if char == ':' {
3316 found_colon = true;
3317 } else {
3318 chars.push(char);
3319 }
3320 }
3321 // Found a possible emoji shortcode at the beginning of the buffer
3322 chars.reverse();
3323 Some(chars.iter().collect())
3324 }
3325
3326 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3327 self.mouse_cursor_hidden = self.hide_mouse_while_typing;
3328 self.transact(window, cx, |this, window, cx| {
3329 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
3330 let selections = this.selections.all::<usize>(cx);
3331 let multi_buffer = this.buffer.read(cx);
3332 let buffer = multi_buffer.snapshot(cx);
3333 selections
3334 .iter()
3335 .map(|selection| {
3336 let start_point = selection.start.to_point(&buffer);
3337 let mut indent =
3338 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3339 indent.len = cmp::min(indent.len, start_point.column);
3340 let start = selection.start;
3341 let end = selection.end;
3342 let selection_is_empty = start == end;
3343 let language_scope = buffer.language_scope_at(start);
3344 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
3345 &language_scope
3346 {
3347 let insert_extra_newline =
3348 insert_extra_newline_brackets(&buffer, start..end, language)
3349 || insert_extra_newline_tree_sitter(&buffer, start..end);
3350
3351 // Comment extension on newline is allowed only for cursor selections
3352 let comment_delimiter = maybe!({
3353 if !selection_is_empty {
3354 return None;
3355 }
3356
3357 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3358 return None;
3359 }
3360
3361 let delimiters = language.line_comment_prefixes();
3362 let max_len_of_delimiter =
3363 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3364 let (snapshot, range) =
3365 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3366
3367 let mut index_of_first_non_whitespace = 0;
3368 let comment_candidate = snapshot
3369 .chars_for_range(range)
3370 .skip_while(|c| {
3371 let should_skip = c.is_whitespace();
3372 if should_skip {
3373 index_of_first_non_whitespace += 1;
3374 }
3375 should_skip
3376 })
3377 .take(max_len_of_delimiter)
3378 .collect::<String>();
3379 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3380 comment_candidate.starts_with(comment_prefix.as_ref())
3381 })?;
3382 let cursor_is_placed_after_comment_marker =
3383 index_of_first_non_whitespace + comment_prefix.len()
3384 <= start_point.column as usize;
3385 if cursor_is_placed_after_comment_marker {
3386 Some(comment_prefix.clone())
3387 } else {
3388 None
3389 }
3390 });
3391 (comment_delimiter, insert_extra_newline)
3392 } else {
3393 (None, false)
3394 };
3395
3396 let capacity_for_delimiter = comment_delimiter
3397 .as_deref()
3398 .map(str::len)
3399 .unwrap_or_default();
3400 let mut new_text =
3401 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3402 new_text.push('\n');
3403 new_text.extend(indent.chars());
3404 if let Some(delimiter) = &comment_delimiter {
3405 new_text.push_str(delimiter);
3406 }
3407 if insert_extra_newline {
3408 new_text = new_text.repeat(2);
3409 }
3410
3411 let anchor = buffer.anchor_after(end);
3412 let new_selection = selection.map(|_| anchor);
3413 (
3414 (start..end, new_text),
3415 (insert_extra_newline, new_selection),
3416 )
3417 })
3418 .unzip()
3419 };
3420
3421 this.edit_with_autoindent(edits, cx);
3422 let buffer = this.buffer.read(cx).snapshot(cx);
3423 let new_selections = selection_fixup_info
3424 .into_iter()
3425 .map(|(extra_newline_inserted, new_selection)| {
3426 let mut cursor = new_selection.end.to_point(&buffer);
3427 if extra_newline_inserted {
3428 cursor.row -= 1;
3429 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
3430 }
3431 new_selection.map(|_| cursor)
3432 })
3433 .collect();
3434
3435 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3436 s.select(new_selections)
3437 });
3438 this.refresh_inline_completion(true, false, window, cx);
3439 });
3440 }
3441
3442 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
3443 self.mouse_cursor_hidden = self.hide_mouse_while_typing;
3444
3445 let buffer = self.buffer.read(cx);
3446 let snapshot = buffer.snapshot(cx);
3447
3448 let mut edits = Vec::new();
3449 let mut rows = Vec::new();
3450
3451 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
3452 let cursor = selection.head();
3453 let row = cursor.row;
3454
3455 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
3456
3457 let newline = "\n".to_string();
3458 edits.push((start_of_line..start_of_line, newline));
3459
3460 rows.push(row + rows_inserted as u32);
3461 }
3462
3463 self.transact(window, cx, |editor, window, cx| {
3464 editor.edit(edits, cx);
3465
3466 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3467 let mut index = 0;
3468 s.move_cursors_with(|map, _, _| {
3469 let row = rows[index];
3470 index += 1;
3471
3472 let point = Point::new(row, 0);
3473 let boundary = map.next_line_boundary(point).1;
3474 let clipped = map.clip_point(boundary, Bias::Left);
3475
3476 (clipped, SelectionGoal::None)
3477 });
3478 });
3479
3480 let mut indent_edits = Vec::new();
3481 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3482 for row in rows {
3483 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3484 for (row, indent) in indents {
3485 if indent.len == 0 {
3486 continue;
3487 }
3488
3489 let text = match indent.kind {
3490 IndentKind::Space => " ".repeat(indent.len as usize),
3491 IndentKind::Tab => "\t".repeat(indent.len as usize),
3492 };
3493 let point = Point::new(row.0, 0);
3494 indent_edits.push((point..point, text));
3495 }
3496 }
3497 editor.edit(indent_edits, cx);
3498 });
3499 }
3500
3501 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
3502 self.mouse_cursor_hidden = self.hide_mouse_while_typing;
3503
3504 let buffer = self.buffer.read(cx);
3505 let snapshot = buffer.snapshot(cx);
3506
3507 let mut edits = Vec::new();
3508 let mut rows = Vec::new();
3509 let mut rows_inserted = 0;
3510
3511 for selection in self.selections.all_adjusted(cx) {
3512 let cursor = selection.head();
3513 let row = cursor.row;
3514
3515 let point = Point::new(row + 1, 0);
3516 let start_of_line = snapshot.clip_point(point, Bias::Left);
3517
3518 let newline = "\n".to_string();
3519 edits.push((start_of_line..start_of_line, newline));
3520
3521 rows_inserted += 1;
3522 rows.push(row + rows_inserted);
3523 }
3524
3525 self.transact(window, cx, |editor, window, cx| {
3526 editor.edit(edits, cx);
3527
3528 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3529 let mut index = 0;
3530 s.move_cursors_with(|map, _, _| {
3531 let row = rows[index];
3532 index += 1;
3533
3534 let point = Point::new(row, 0);
3535 let boundary = map.next_line_boundary(point).1;
3536 let clipped = map.clip_point(boundary, Bias::Left);
3537
3538 (clipped, SelectionGoal::None)
3539 });
3540 });
3541
3542 let mut indent_edits = Vec::new();
3543 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3544 for row in rows {
3545 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3546 for (row, indent) in indents {
3547 if indent.len == 0 {
3548 continue;
3549 }
3550
3551 let text = match indent.kind {
3552 IndentKind::Space => " ".repeat(indent.len as usize),
3553 IndentKind::Tab => "\t".repeat(indent.len as usize),
3554 };
3555 let point = Point::new(row.0, 0);
3556 indent_edits.push((point..point, text));
3557 }
3558 }
3559 editor.edit(indent_edits, cx);
3560 });
3561 }
3562
3563 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3564 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
3565 original_indent_columns: Vec::new(),
3566 });
3567 self.insert_with_autoindent_mode(text, autoindent, window, cx);
3568 }
3569
3570 fn insert_with_autoindent_mode(
3571 &mut self,
3572 text: &str,
3573 autoindent_mode: Option<AutoindentMode>,
3574 window: &mut Window,
3575 cx: &mut Context<Self>,
3576 ) {
3577 if self.read_only(cx) {
3578 return;
3579 }
3580
3581 let text: Arc<str> = text.into();
3582 self.transact(window, cx, |this, window, cx| {
3583 let old_selections = this.selections.all_adjusted(cx);
3584 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
3585 let anchors = {
3586 let snapshot = buffer.read(cx);
3587 old_selections
3588 .iter()
3589 .map(|s| {
3590 let anchor = snapshot.anchor_after(s.head());
3591 s.map(|_| anchor)
3592 })
3593 .collect::<Vec<_>>()
3594 };
3595 buffer.edit(
3596 old_selections
3597 .iter()
3598 .map(|s| (s.start..s.end, text.clone())),
3599 autoindent_mode,
3600 cx,
3601 );
3602 anchors
3603 });
3604
3605 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3606 s.select_anchors(selection_anchors);
3607 });
3608
3609 cx.notify();
3610 });
3611 }
3612
3613 fn trigger_completion_on_input(
3614 &mut self,
3615 text: &str,
3616 trigger_in_words: bool,
3617 window: &mut Window,
3618 cx: &mut Context<Self>,
3619 ) {
3620 let ignore_completion_provider = self
3621 .context_menu
3622 .borrow()
3623 .as_ref()
3624 .map(|menu| match menu {
3625 CodeContextMenu::Completions(completions_menu) => {
3626 completions_menu.ignore_completion_provider
3627 }
3628 CodeContextMenu::CodeActions(_) => false,
3629 })
3630 .unwrap_or(false);
3631
3632 if ignore_completion_provider {
3633 self.show_word_completions(&ShowWordCompletions, window, cx);
3634 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
3635 self.show_completions(
3636 &ShowCompletions {
3637 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
3638 },
3639 window,
3640 cx,
3641 );
3642 } else {
3643 self.hide_context_menu(window, cx);
3644 }
3645 }
3646
3647 fn is_completion_trigger(
3648 &self,
3649 text: &str,
3650 trigger_in_words: bool,
3651 cx: &mut Context<Self>,
3652 ) -> bool {
3653 let position = self.selections.newest_anchor().head();
3654 let multibuffer = self.buffer.read(cx);
3655 let Some(buffer) = position
3656 .buffer_id
3657 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
3658 else {
3659 return false;
3660 };
3661
3662 if let Some(completion_provider) = &self.completion_provider {
3663 completion_provider.is_completion_trigger(
3664 &buffer,
3665 position.text_anchor,
3666 text,
3667 trigger_in_words,
3668 cx,
3669 )
3670 } else {
3671 false
3672 }
3673 }
3674
3675 /// If any empty selections is touching the start of its innermost containing autoclose
3676 /// region, expand it to select the brackets.
3677 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3678 let selections = self.selections.all::<usize>(cx);
3679 let buffer = self.buffer.read(cx).read(cx);
3680 let new_selections = self
3681 .selections_with_autoclose_regions(selections, &buffer)
3682 .map(|(mut selection, region)| {
3683 if !selection.is_empty() {
3684 return selection;
3685 }
3686
3687 if let Some(region) = region {
3688 let mut range = region.range.to_offset(&buffer);
3689 if selection.start == range.start && range.start >= region.pair.start.len() {
3690 range.start -= region.pair.start.len();
3691 if buffer.contains_str_at(range.start, ®ion.pair.start)
3692 && buffer.contains_str_at(range.end, ®ion.pair.end)
3693 {
3694 range.end += region.pair.end.len();
3695 selection.start = range.start;
3696 selection.end = range.end;
3697
3698 return selection;
3699 }
3700 }
3701 }
3702
3703 let always_treat_brackets_as_autoclosed = buffer
3704 .language_settings_at(selection.start, cx)
3705 .always_treat_brackets_as_autoclosed;
3706
3707 if !always_treat_brackets_as_autoclosed {
3708 return selection;
3709 }
3710
3711 if let Some(scope) = buffer.language_scope_at(selection.start) {
3712 for (pair, enabled) in scope.brackets() {
3713 if !enabled || !pair.close {
3714 continue;
3715 }
3716
3717 if buffer.contains_str_at(selection.start, &pair.end) {
3718 let pair_start_len = pair.start.len();
3719 if buffer.contains_str_at(
3720 selection.start.saturating_sub(pair_start_len),
3721 &pair.start,
3722 ) {
3723 selection.start -= pair_start_len;
3724 selection.end += pair.end.len();
3725
3726 return selection;
3727 }
3728 }
3729 }
3730 }
3731
3732 selection
3733 })
3734 .collect();
3735
3736 drop(buffer);
3737 self.change_selections(None, window, cx, |selections| {
3738 selections.select(new_selections)
3739 });
3740 }
3741
3742 /// Iterate the given selections, and for each one, find the smallest surrounding
3743 /// autoclose region. This uses the ordering of the selections and the autoclose
3744 /// regions to avoid repeated comparisons.
3745 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
3746 &'a self,
3747 selections: impl IntoIterator<Item = Selection<D>>,
3748 buffer: &'a MultiBufferSnapshot,
3749 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
3750 let mut i = 0;
3751 let mut regions = self.autoclose_regions.as_slice();
3752 selections.into_iter().map(move |selection| {
3753 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
3754
3755 let mut enclosing = None;
3756 while let Some(pair_state) = regions.get(i) {
3757 if pair_state.range.end.to_offset(buffer) < range.start {
3758 regions = ®ions[i + 1..];
3759 i = 0;
3760 } else if pair_state.range.start.to_offset(buffer) > range.end {
3761 break;
3762 } else {
3763 if pair_state.selection_id == selection.id {
3764 enclosing = Some(pair_state);
3765 }
3766 i += 1;
3767 }
3768 }
3769
3770 (selection, enclosing)
3771 })
3772 }
3773
3774 /// Remove any autoclose regions that no longer contain their selection.
3775 fn invalidate_autoclose_regions(
3776 &mut self,
3777 mut selections: &[Selection<Anchor>],
3778 buffer: &MultiBufferSnapshot,
3779 ) {
3780 self.autoclose_regions.retain(|state| {
3781 let mut i = 0;
3782 while let Some(selection) = selections.get(i) {
3783 if selection.end.cmp(&state.range.start, buffer).is_lt() {
3784 selections = &selections[1..];
3785 continue;
3786 }
3787 if selection.start.cmp(&state.range.end, buffer).is_gt() {
3788 break;
3789 }
3790 if selection.id == state.selection_id {
3791 return true;
3792 } else {
3793 i += 1;
3794 }
3795 }
3796 false
3797 });
3798 }
3799
3800 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
3801 let offset = position.to_offset(buffer);
3802 let (word_range, kind) = buffer.surrounding_word(offset, true);
3803 if offset > word_range.start && kind == Some(CharKind::Word) {
3804 Some(
3805 buffer
3806 .text_for_range(word_range.start..offset)
3807 .collect::<String>(),
3808 )
3809 } else {
3810 None
3811 }
3812 }
3813
3814 pub fn toggle_inlay_hints(
3815 &mut self,
3816 _: &ToggleInlayHints,
3817 _: &mut Window,
3818 cx: &mut Context<Self>,
3819 ) {
3820 self.refresh_inlay_hints(
3821 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
3822 cx,
3823 );
3824 }
3825
3826 pub fn inlay_hints_enabled(&self) -> bool {
3827 self.inlay_hint_cache.enabled
3828 }
3829
3830 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
3831 if self.semantics_provider.is_none() || self.mode != EditorMode::Full {
3832 return;
3833 }
3834
3835 let reason_description = reason.description();
3836 let ignore_debounce = matches!(
3837 reason,
3838 InlayHintRefreshReason::SettingsChange(_)
3839 | InlayHintRefreshReason::Toggle(_)
3840 | InlayHintRefreshReason::ExcerptsRemoved(_)
3841 | InlayHintRefreshReason::ModifiersChanged(_)
3842 );
3843 let (invalidate_cache, required_languages) = match reason {
3844 InlayHintRefreshReason::ModifiersChanged(enabled) => {
3845 match self.inlay_hint_cache.modifiers_override(enabled) {
3846 Some(enabled) => {
3847 if enabled {
3848 (InvalidationStrategy::RefreshRequested, None)
3849 } else {
3850 self.splice_inlays(
3851 &self
3852 .visible_inlay_hints(cx)
3853 .iter()
3854 .map(|inlay| inlay.id)
3855 .collect::<Vec<InlayId>>(),
3856 Vec::new(),
3857 cx,
3858 );
3859 return;
3860 }
3861 }
3862 None => return,
3863 }
3864 }
3865 InlayHintRefreshReason::Toggle(enabled) => {
3866 if self.inlay_hint_cache.toggle(enabled) {
3867 if enabled {
3868 (InvalidationStrategy::RefreshRequested, None)
3869 } else {
3870 self.splice_inlays(
3871 &self
3872 .visible_inlay_hints(cx)
3873 .iter()
3874 .map(|inlay| inlay.id)
3875 .collect::<Vec<InlayId>>(),
3876 Vec::new(),
3877 cx,
3878 );
3879 return;
3880 }
3881 } else {
3882 return;
3883 }
3884 }
3885 InlayHintRefreshReason::SettingsChange(new_settings) => {
3886 match self.inlay_hint_cache.update_settings(
3887 &self.buffer,
3888 new_settings,
3889 self.visible_inlay_hints(cx),
3890 cx,
3891 ) {
3892 ControlFlow::Break(Some(InlaySplice {
3893 to_remove,
3894 to_insert,
3895 })) => {
3896 self.splice_inlays(&to_remove, to_insert, cx);
3897 return;
3898 }
3899 ControlFlow::Break(None) => return,
3900 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
3901 }
3902 }
3903 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
3904 if let Some(InlaySplice {
3905 to_remove,
3906 to_insert,
3907 }) = self.inlay_hint_cache.remove_excerpts(excerpts_removed)
3908 {
3909 self.splice_inlays(&to_remove, to_insert, cx);
3910 }
3911 return;
3912 }
3913 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
3914 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
3915 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
3916 }
3917 InlayHintRefreshReason::RefreshRequested => {
3918 (InvalidationStrategy::RefreshRequested, None)
3919 }
3920 };
3921
3922 if let Some(InlaySplice {
3923 to_remove,
3924 to_insert,
3925 }) = self.inlay_hint_cache.spawn_hint_refresh(
3926 reason_description,
3927 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
3928 invalidate_cache,
3929 ignore_debounce,
3930 cx,
3931 ) {
3932 self.splice_inlays(&to_remove, to_insert, cx);
3933 }
3934 }
3935
3936 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
3937 self.display_map
3938 .read(cx)
3939 .current_inlays()
3940 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
3941 .cloned()
3942 .collect()
3943 }
3944
3945 pub fn excerpts_for_inlay_hints_query(
3946 &self,
3947 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
3948 cx: &mut Context<Editor>,
3949 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
3950 let Some(project) = self.project.as_ref() else {
3951 return HashMap::default();
3952 };
3953 let project = project.read(cx);
3954 let multi_buffer = self.buffer().read(cx);
3955 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
3956 let multi_buffer_visible_start = self
3957 .scroll_manager
3958 .anchor()
3959 .anchor
3960 .to_point(&multi_buffer_snapshot);
3961 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
3962 multi_buffer_visible_start
3963 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
3964 Bias::Left,
3965 );
3966 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
3967 multi_buffer_snapshot
3968 .range_to_buffer_ranges(multi_buffer_visible_range)
3969 .into_iter()
3970 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
3971 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
3972 let buffer_file = project::File::from_dyn(buffer.file())?;
3973 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
3974 let worktree_entry = buffer_worktree
3975 .read(cx)
3976 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
3977 if worktree_entry.is_ignored {
3978 return None;
3979 }
3980
3981 let language = buffer.language()?;
3982 if let Some(restrict_to_languages) = restrict_to_languages {
3983 if !restrict_to_languages.contains(language) {
3984 return None;
3985 }
3986 }
3987 Some((
3988 excerpt_id,
3989 (
3990 multi_buffer.buffer(buffer.remote_id()).unwrap(),
3991 buffer.version().clone(),
3992 excerpt_visible_range,
3993 ),
3994 ))
3995 })
3996 .collect()
3997 }
3998
3999 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4000 TextLayoutDetails {
4001 text_system: window.text_system().clone(),
4002 editor_style: self.style.clone().unwrap(),
4003 rem_size: window.rem_size(),
4004 scroll_anchor: self.scroll_manager.anchor(),
4005 visible_rows: self.visible_line_count(),
4006 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4007 }
4008 }
4009
4010 pub fn splice_inlays(
4011 &self,
4012 to_remove: &[InlayId],
4013 to_insert: Vec<Inlay>,
4014 cx: &mut Context<Self>,
4015 ) {
4016 self.display_map.update(cx, |display_map, cx| {
4017 display_map.splice_inlays(to_remove, to_insert, cx)
4018 });
4019 cx.notify();
4020 }
4021
4022 fn trigger_on_type_formatting(
4023 &self,
4024 input: String,
4025 window: &mut Window,
4026 cx: &mut Context<Self>,
4027 ) -> Option<Task<Result<()>>> {
4028 if input.len() != 1 {
4029 return None;
4030 }
4031
4032 let project = self.project.as_ref()?;
4033 let position = self.selections.newest_anchor().head();
4034 let (buffer, buffer_position) = self
4035 .buffer
4036 .read(cx)
4037 .text_anchor_for_position(position, cx)?;
4038
4039 let settings = language_settings::language_settings(
4040 buffer
4041 .read(cx)
4042 .language_at(buffer_position)
4043 .map(|l| l.name()),
4044 buffer.read(cx).file(),
4045 cx,
4046 );
4047 if !settings.use_on_type_format {
4048 return None;
4049 }
4050
4051 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4052 // hence we do LSP request & edit on host side only — add formats to host's history.
4053 let push_to_lsp_host_history = true;
4054 // If this is not the host, append its history with new edits.
4055 let push_to_client_history = project.read(cx).is_via_collab();
4056
4057 let on_type_formatting = project.update(cx, |project, cx| {
4058 project.on_type_format(
4059 buffer.clone(),
4060 buffer_position,
4061 input,
4062 push_to_lsp_host_history,
4063 cx,
4064 )
4065 });
4066 Some(cx.spawn_in(window, async move |editor, cx| {
4067 if let Some(transaction) = on_type_formatting.await? {
4068 if push_to_client_history {
4069 buffer
4070 .update(cx, |buffer, _| {
4071 buffer.push_transaction(transaction, Instant::now());
4072 })
4073 .ok();
4074 }
4075 editor.update(cx, |editor, cx| {
4076 editor.refresh_document_highlights(cx);
4077 })?;
4078 }
4079 Ok(())
4080 }))
4081 }
4082
4083 pub fn show_word_completions(
4084 &mut self,
4085 _: &ShowWordCompletions,
4086 window: &mut Window,
4087 cx: &mut Context<Self>,
4088 ) {
4089 self.open_completions_menu(true, None, window, cx);
4090 }
4091
4092 pub fn show_completions(
4093 &mut self,
4094 options: &ShowCompletions,
4095 window: &mut Window,
4096 cx: &mut Context<Self>,
4097 ) {
4098 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4099 }
4100
4101 fn open_completions_menu(
4102 &mut self,
4103 ignore_completion_provider: bool,
4104 trigger: Option<&str>,
4105 window: &mut Window,
4106 cx: &mut Context<Self>,
4107 ) {
4108 if self.pending_rename.is_some() {
4109 return;
4110 }
4111 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4112 return;
4113 }
4114
4115 let position = self.selections.newest_anchor().head();
4116 if position.diff_base_anchor.is_some() {
4117 return;
4118 }
4119 let (buffer, buffer_position) =
4120 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4121 output
4122 } else {
4123 return;
4124 };
4125 let buffer_snapshot = buffer.read(cx).snapshot();
4126 let show_completion_documentation = buffer_snapshot
4127 .settings_at(buffer_position, cx)
4128 .show_completion_documentation;
4129
4130 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4131
4132 let trigger_kind = match trigger {
4133 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4134 CompletionTriggerKind::TRIGGER_CHARACTER
4135 }
4136 _ => CompletionTriggerKind::INVOKED,
4137 };
4138 let completion_context = CompletionContext {
4139 trigger_character: trigger.and_then(|trigger| {
4140 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4141 Some(String::from(trigger))
4142 } else {
4143 None
4144 }
4145 }),
4146 trigger_kind,
4147 };
4148
4149 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4150 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4151 let word_to_exclude = buffer_snapshot
4152 .text_for_range(old_range.clone())
4153 .collect::<String>();
4154 (
4155 buffer_snapshot.anchor_before(old_range.start)
4156 ..buffer_snapshot.anchor_after(old_range.end),
4157 Some(word_to_exclude),
4158 )
4159 } else {
4160 (buffer_position..buffer_position, None)
4161 };
4162
4163 let completion_settings = language_settings(
4164 buffer_snapshot
4165 .language_at(buffer_position)
4166 .map(|language| language.name()),
4167 buffer_snapshot.file(),
4168 cx,
4169 )
4170 .completions;
4171
4172 // The document can be large, so stay in reasonable bounds when searching for words,
4173 // otherwise completion pop-up might be slow to appear.
4174 const WORD_LOOKUP_ROWS: u32 = 5_000;
4175 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4176 let min_word_search = buffer_snapshot.clip_point(
4177 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4178 Bias::Left,
4179 );
4180 let max_word_search = buffer_snapshot.clip_point(
4181 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4182 Bias::Right,
4183 );
4184 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4185 ..buffer_snapshot.point_to_offset(max_word_search);
4186
4187 let provider = self
4188 .completion_provider
4189 .as_ref()
4190 .filter(|_| !ignore_completion_provider);
4191 let skip_digits = query
4192 .as_ref()
4193 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4194
4195 let (mut words, provided_completions) = match provider {
4196 Some(provider) => {
4197 let completions =
4198 provider.completions(&buffer, buffer_position, completion_context, window, cx);
4199
4200 let words = match completion_settings.words {
4201 WordsCompletionMode::Disabled => Task::ready(HashMap::default()),
4202 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4203 .background_spawn(async move {
4204 buffer_snapshot.words_in_range(WordsQuery {
4205 fuzzy_contents: None,
4206 range: word_search_range,
4207 skip_digits,
4208 })
4209 }),
4210 };
4211
4212 (words, completions)
4213 }
4214 None => (
4215 cx.background_spawn(async move {
4216 buffer_snapshot.words_in_range(WordsQuery {
4217 fuzzy_contents: None,
4218 range: word_search_range,
4219 skip_digits,
4220 })
4221 }),
4222 Task::ready(Ok(None)),
4223 ),
4224 };
4225
4226 let sort_completions = provider
4227 .as_ref()
4228 .map_or(true, |provider| provider.sort_completions());
4229
4230 let id = post_inc(&mut self.next_completion_id);
4231 let task = cx.spawn_in(window, async move |editor, cx| {
4232 async move {
4233 editor.update(cx, |this, _| {
4234 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4235 })?;
4236
4237 let mut completions = Vec::new();
4238 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
4239 completions.extend(provided_completions);
4240 if completion_settings.words == WordsCompletionMode::Fallback {
4241 words = Task::ready(HashMap::default());
4242 }
4243 }
4244
4245 let mut words = words.await;
4246 if let Some(word_to_exclude) = &word_to_exclude {
4247 words.remove(word_to_exclude);
4248 }
4249 for lsp_completion in &completions {
4250 words.remove(&lsp_completion.new_text);
4251 }
4252 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
4253 old_range: old_range.clone(),
4254 new_text: word.clone(),
4255 label: CodeLabel::plain(word, None),
4256 documentation: None,
4257 source: CompletionSource::BufferWord {
4258 word_range,
4259 resolved: false,
4260 },
4261 confirm: None,
4262 }));
4263
4264 let menu = if completions.is_empty() {
4265 None
4266 } else {
4267 let mut menu = CompletionsMenu::new(
4268 id,
4269 sort_completions,
4270 show_completion_documentation,
4271 ignore_completion_provider,
4272 position,
4273 buffer.clone(),
4274 completions.into(),
4275 );
4276
4277 menu.filter(query.as_deref(), cx.background_executor().clone())
4278 .await;
4279
4280 menu.visible().then_some(menu)
4281 };
4282
4283 editor.update_in(cx, |editor, window, cx| {
4284 match editor.context_menu.borrow().as_ref() {
4285 None => {}
4286 Some(CodeContextMenu::Completions(prev_menu)) => {
4287 if prev_menu.id > id {
4288 return;
4289 }
4290 }
4291 _ => return,
4292 }
4293
4294 if editor.focus_handle.is_focused(window) && menu.is_some() {
4295 let mut menu = menu.unwrap();
4296 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
4297
4298 *editor.context_menu.borrow_mut() =
4299 Some(CodeContextMenu::Completions(menu));
4300
4301 if editor.show_edit_predictions_in_menu() {
4302 editor.update_visible_inline_completion(window, cx);
4303 } else {
4304 editor.discard_inline_completion(false, cx);
4305 }
4306
4307 cx.notify();
4308 } else if editor.completion_tasks.len() <= 1 {
4309 // If there are no more completion tasks and the last menu was
4310 // empty, we should hide it.
4311 let was_hidden = editor.hide_context_menu(window, cx).is_none();
4312 // If it was already hidden and we don't show inline
4313 // completions in the menu, we should also show the
4314 // inline-completion when available.
4315 if was_hidden && editor.show_edit_predictions_in_menu() {
4316 editor.update_visible_inline_completion(window, cx);
4317 }
4318 }
4319 })?;
4320
4321 anyhow::Ok(())
4322 }
4323 .log_err()
4324 .await
4325 });
4326
4327 self.completion_tasks.push((id, task));
4328 }
4329
4330 pub fn confirm_completion(
4331 &mut self,
4332 action: &ConfirmCompletion,
4333 window: &mut Window,
4334 cx: &mut Context<Self>,
4335 ) -> Option<Task<Result<()>>> {
4336 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
4337 }
4338
4339 pub fn compose_completion(
4340 &mut self,
4341 action: &ComposeCompletion,
4342 window: &mut Window,
4343 cx: &mut Context<Self>,
4344 ) -> Option<Task<Result<()>>> {
4345 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
4346 }
4347
4348 fn do_completion(
4349 &mut self,
4350 item_ix: Option<usize>,
4351 intent: CompletionIntent,
4352 window: &mut Window,
4353 cx: &mut Context<Editor>,
4354 ) -> Option<Task<std::result::Result<(), anyhow::Error>>> {
4355 use language::ToOffset as _;
4356
4357 let completions_menu =
4358 if let CodeContextMenu::Completions(menu) = self.hide_context_menu(window, cx)? {
4359 menu
4360 } else {
4361 return None;
4362 };
4363
4364 let entries = completions_menu.entries.borrow();
4365 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
4366 if self.show_edit_predictions_in_menu() {
4367 self.discard_inline_completion(true, cx);
4368 }
4369 let candidate_id = mat.candidate_id;
4370 drop(entries);
4371
4372 let buffer_handle = completions_menu.buffer;
4373 let completion = completions_menu
4374 .completions
4375 .borrow()
4376 .get(candidate_id)?
4377 .clone();
4378 cx.stop_propagation();
4379
4380 let snippet;
4381 let text;
4382
4383 if completion.is_snippet() {
4384 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
4385 text = snippet.as_ref().unwrap().text.clone();
4386 } else {
4387 snippet = None;
4388 text = completion.new_text.clone();
4389 };
4390 let selections = self.selections.all::<usize>(cx);
4391 let buffer = buffer_handle.read(cx);
4392 let old_range = completion.old_range.to_offset(buffer);
4393 let old_text = buffer.text_for_range(old_range.clone()).collect::<String>();
4394
4395 let newest_selection = self.selections.newest_anchor();
4396 if newest_selection.start.buffer_id != Some(buffer_handle.read(cx).remote_id()) {
4397 return None;
4398 }
4399
4400 let lookbehind = newest_selection
4401 .start
4402 .text_anchor
4403 .to_offset(buffer)
4404 .saturating_sub(old_range.start);
4405 let lookahead = old_range
4406 .end
4407 .saturating_sub(newest_selection.end.text_anchor.to_offset(buffer));
4408 let mut common_prefix_len = old_text
4409 .bytes()
4410 .zip(text.bytes())
4411 .take_while(|(a, b)| a == b)
4412 .count();
4413
4414 let snapshot = self.buffer.read(cx).snapshot(cx);
4415 let mut range_to_replace: Option<Range<isize>> = None;
4416 let mut ranges = Vec::new();
4417 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4418 for selection in &selections {
4419 if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) {
4420 let start = selection.start.saturating_sub(lookbehind);
4421 let end = selection.end + lookahead;
4422 if selection.id == newest_selection.id {
4423 range_to_replace = Some(
4424 ((start + common_prefix_len) as isize - selection.start as isize)
4425 ..(end as isize - selection.start as isize),
4426 );
4427 }
4428 ranges.push(start + common_prefix_len..end);
4429 } else {
4430 common_prefix_len = 0;
4431 ranges.clear();
4432 ranges.extend(selections.iter().map(|s| {
4433 if s.id == newest_selection.id {
4434 range_to_replace = Some(
4435 old_range.start.to_offset_utf16(&snapshot).0 as isize
4436 - selection.start as isize
4437 ..old_range.end.to_offset_utf16(&snapshot).0 as isize
4438 - selection.start as isize,
4439 );
4440 old_range.clone()
4441 } else {
4442 s.start..s.end
4443 }
4444 }));
4445 break;
4446 }
4447 if !self.linked_edit_ranges.is_empty() {
4448 let start_anchor = snapshot.anchor_before(selection.head());
4449 let end_anchor = snapshot.anchor_after(selection.tail());
4450 if let Some(ranges) = self
4451 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
4452 {
4453 for (buffer, edits) in ranges {
4454 linked_edits.entry(buffer.clone()).or_default().extend(
4455 edits
4456 .into_iter()
4457 .map(|range| (range, text[common_prefix_len..].to_owned())),
4458 );
4459 }
4460 }
4461 }
4462 }
4463 let text = &text[common_prefix_len..];
4464
4465 cx.emit(EditorEvent::InputHandled {
4466 utf16_range_to_replace: range_to_replace,
4467 text: text.into(),
4468 });
4469
4470 self.transact(window, cx, |this, window, cx| {
4471 if let Some(mut snippet) = snippet {
4472 snippet.text = text.to_string();
4473 for tabstop in snippet
4474 .tabstops
4475 .iter_mut()
4476 .flat_map(|tabstop| tabstop.ranges.iter_mut())
4477 {
4478 tabstop.start -= common_prefix_len as isize;
4479 tabstop.end -= common_prefix_len as isize;
4480 }
4481
4482 this.insert_snippet(&ranges, snippet, window, cx).log_err();
4483 } else {
4484 this.buffer.update(cx, |buffer, cx| {
4485 buffer.edit(
4486 ranges.iter().map(|range| (range.clone(), text)),
4487 this.autoindent_mode.clone(),
4488 cx,
4489 );
4490 });
4491 }
4492 for (buffer, edits) in linked_edits {
4493 buffer.update(cx, |buffer, cx| {
4494 let snapshot = buffer.snapshot();
4495 let edits = edits
4496 .into_iter()
4497 .map(|(range, text)| {
4498 use text::ToPoint as TP;
4499 let end_point = TP::to_point(&range.end, &snapshot);
4500 let start_point = TP::to_point(&range.start, &snapshot);
4501 (start_point..end_point, text)
4502 })
4503 .sorted_by_key(|(range, _)| range.start)
4504 .collect::<Vec<_>>();
4505 buffer.edit(edits, None, cx);
4506 })
4507 }
4508
4509 this.refresh_inline_completion(true, false, window, cx);
4510 });
4511
4512 let show_new_completions_on_confirm = completion
4513 .confirm
4514 .as_ref()
4515 .map_or(false, |confirm| confirm(intent, window, cx));
4516 if show_new_completions_on_confirm {
4517 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
4518 }
4519
4520 let provider = self.completion_provider.as_ref()?;
4521 drop(completion);
4522 let apply_edits = provider.apply_additional_edits_for_completion(
4523 buffer_handle,
4524 completions_menu.completions.clone(),
4525 candidate_id,
4526 true,
4527 cx,
4528 );
4529
4530 let editor_settings = EditorSettings::get_global(cx);
4531 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
4532 // After the code completion is finished, users often want to know what signatures are needed.
4533 // so we should automatically call signature_help
4534 self.show_signature_help(&ShowSignatureHelp, window, cx);
4535 }
4536
4537 Some(cx.foreground_executor().spawn(async move {
4538 apply_edits.await?;
4539 Ok(())
4540 }))
4541 }
4542
4543 pub fn toggle_code_actions(
4544 &mut self,
4545 action: &ToggleCodeActions,
4546 window: &mut Window,
4547 cx: &mut Context<Self>,
4548 ) {
4549 let mut context_menu = self.context_menu.borrow_mut();
4550 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
4551 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
4552 // Toggle if we're selecting the same one
4553 *context_menu = None;
4554 cx.notify();
4555 return;
4556 } else {
4557 // Otherwise, clear it and start a new one
4558 *context_menu = None;
4559 cx.notify();
4560 }
4561 }
4562 drop(context_menu);
4563 let snapshot = self.snapshot(window, cx);
4564 let deployed_from_indicator = action.deployed_from_indicator;
4565 let mut task = self.code_actions_task.take();
4566 let action = action.clone();
4567 cx.spawn_in(window, async move |editor, cx| {
4568 while let Some(prev_task) = task {
4569 prev_task.await.log_err();
4570 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
4571 }
4572
4573 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
4574 if editor.focus_handle.is_focused(window) {
4575 let multibuffer_point = action
4576 .deployed_from_indicator
4577 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
4578 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
4579 let (buffer, buffer_row) = snapshot
4580 .buffer_snapshot
4581 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
4582 .and_then(|(buffer_snapshot, range)| {
4583 editor
4584 .buffer
4585 .read(cx)
4586 .buffer(buffer_snapshot.remote_id())
4587 .map(|buffer| (buffer, range.start.row))
4588 })?;
4589 let (_, code_actions) = editor
4590 .available_code_actions
4591 .clone()
4592 .and_then(|(location, code_actions)| {
4593 let snapshot = location.buffer.read(cx).snapshot();
4594 let point_range = location.range.to_point(&snapshot);
4595 let point_range = point_range.start.row..=point_range.end.row;
4596 if point_range.contains(&buffer_row) {
4597 Some((location, code_actions))
4598 } else {
4599 None
4600 }
4601 })
4602 .unzip();
4603 let buffer_id = buffer.read(cx).remote_id();
4604 let tasks = editor
4605 .tasks
4606 .get(&(buffer_id, buffer_row))
4607 .map(|t| Arc::new(t.to_owned()));
4608 if tasks.is_none() && code_actions.is_none() {
4609 return None;
4610 }
4611
4612 editor.completion_tasks.clear();
4613 editor.discard_inline_completion(false, cx);
4614 let task_context =
4615 tasks
4616 .as_ref()
4617 .zip(editor.project.clone())
4618 .map(|(tasks, project)| {
4619 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
4620 });
4621
4622 Some(cx.spawn_in(window, async move |editor, cx| {
4623 let task_context = match task_context {
4624 Some(task_context) => task_context.await,
4625 None => None,
4626 };
4627 let resolved_tasks =
4628 tasks.zip(task_context).map(|(tasks, task_context)| {
4629 Rc::new(ResolvedTasks {
4630 templates: tasks.resolve(&task_context).collect(),
4631 position: snapshot.buffer_snapshot.anchor_before(Point::new(
4632 multibuffer_point.row,
4633 tasks.column,
4634 )),
4635 })
4636 });
4637 let spawn_straight_away = resolved_tasks
4638 .as_ref()
4639 .map_or(false, |tasks| tasks.templates.len() == 1)
4640 && code_actions
4641 .as_ref()
4642 .map_or(true, |actions| actions.is_empty());
4643 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
4644 *editor.context_menu.borrow_mut() =
4645 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
4646 buffer,
4647 actions: CodeActionContents {
4648 tasks: resolved_tasks,
4649 actions: code_actions,
4650 },
4651 selected_item: Default::default(),
4652 scroll_handle: UniformListScrollHandle::default(),
4653 deployed_from_indicator,
4654 }));
4655 if spawn_straight_away {
4656 if let Some(task) = editor.confirm_code_action(
4657 &ConfirmCodeAction { item_ix: Some(0) },
4658 window,
4659 cx,
4660 ) {
4661 cx.notify();
4662 return task;
4663 }
4664 }
4665 cx.notify();
4666 Task::ready(Ok(()))
4667 }) {
4668 task.await
4669 } else {
4670 Ok(())
4671 }
4672 }))
4673 } else {
4674 Some(Task::ready(Ok(())))
4675 }
4676 })?;
4677 if let Some(task) = spawned_test_task {
4678 task.await?;
4679 }
4680
4681 Ok::<_, anyhow::Error>(())
4682 })
4683 .detach_and_log_err(cx);
4684 }
4685
4686 pub fn confirm_code_action(
4687 &mut self,
4688 action: &ConfirmCodeAction,
4689 window: &mut Window,
4690 cx: &mut Context<Self>,
4691 ) -> Option<Task<Result<()>>> {
4692 let actions_menu =
4693 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
4694 menu
4695 } else {
4696 return None;
4697 };
4698 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
4699 let action = actions_menu.actions.get(action_ix)?;
4700 let title = action.label();
4701 let buffer = actions_menu.buffer;
4702 let workspace = self.workspace()?;
4703
4704 match action {
4705 CodeActionsItem::Task(task_source_kind, resolved_task) => {
4706 workspace.update(cx, |workspace, cx| {
4707 workspace::tasks::schedule_resolved_task(
4708 workspace,
4709 task_source_kind,
4710 resolved_task,
4711 false,
4712 cx,
4713 );
4714
4715 Some(Task::ready(Ok(())))
4716 })
4717 }
4718 CodeActionsItem::CodeAction {
4719 excerpt_id,
4720 action,
4721 provider,
4722 } => {
4723 let apply_code_action =
4724 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
4725 let workspace = workspace.downgrade();
4726 Some(cx.spawn_in(window, async move |editor, cx| {
4727 let project_transaction = apply_code_action.await?;
4728 Self::open_project_transaction(
4729 &editor,
4730 workspace,
4731 project_transaction,
4732 title,
4733 cx,
4734 )
4735 .await
4736 }))
4737 }
4738 }
4739 }
4740
4741 pub async fn open_project_transaction(
4742 this: &WeakEntity<Editor>,
4743 workspace: WeakEntity<Workspace>,
4744 transaction: ProjectTransaction,
4745 title: String,
4746 cx: &mut AsyncWindowContext,
4747 ) -> Result<()> {
4748 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
4749 cx.update(|_, cx| {
4750 entries.sort_unstable_by_key(|(buffer, _)| {
4751 buffer.read(cx).file().map(|f| f.path().clone())
4752 });
4753 })?;
4754
4755 // If the project transaction's edits are all contained within this editor, then
4756 // avoid opening a new editor to display them.
4757
4758 if let Some((buffer, transaction)) = entries.first() {
4759 if entries.len() == 1 {
4760 let excerpt = this.update(cx, |editor, cx| {
4761 editor
4762 .buffer()
4763 .read(cx)
4764 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
4765 })?;
4766 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
4767 if excerpted_buffer == *buffer {
4768 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
4769 let excerpt_range = excerpt_range.to_offset(buffer);
4770 buffer
4771 .edited_ranges_for_transaction::<usize>(transaction)
4772 .all(|range| {
4773 excerpt_range.start <= range.start
4774 && excerpt_range.end >= range.end
4775 })
4776 })?;
4777
4778 if all_edits_within_excerpt {
4779 return Ok(());
4780 }
4781 }
4782 }
4783 }
4784 } else {
4785 return Ok(());
4786 }
4787
4788 let mut ranges_to_highlight = Vec::new();
4789 let excerpt_buffer = cx.new(|cx| {
4790 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
4791 for (buffer_handle, transaction) in &entries {
4792 let buffer = buffer_handle.read(cx);
4793 ranges_to_highlight.extend(
4794 multibuffer.push_excerpts_with_context_lines(
4795 buffer_handle.clone(),
4796 buffer
4797 .edited_ranges_for_transaction::<usize>(transaction)
4798 .collect(),
4799 DEFAULT_MULTIBUFFER_CONTEXT,
4800 cx,
4801 ),
4802 );
4803 }
4804 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
4805 multibuffer
4806 })?;
4807
4808 workspace.update_in(cx, |workspace, window, cx| {
4809 let project = workspace.project().clone();
4810 let editor =
4811 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
4812 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
4813 editor.update(cx, |editor, cx| {
4814 editor.highlight_background::<Self>(
4815 &ranges_to_highlight,
4816 |theme| theme.editor_highlighted_line_background,
4817 cx,
4818 );
4819 });
4820 })?;
4821
4822 Ok(())
4823 }
4824
4825 pub fn clear_code_action_providers(&mut self) {
4826 self.code_action_providers.clear();
4827 self.available_code_actions.take();
4828 }
4829
4830 pub fn add_code_action_provider(
4831 &mut self,
4832 provider: Rc<dyn CodeActionProvider>,
4833 window: &mut Window,
4834 cx: &mut Context<Self>,
4835 ) {
4836 if self
4837 .code_action_providers
4838 .iter()
4839 .any(|existing_provider| existing_provider.id() == provider.id())
4840 {
4841 return;
4842 }
4843
4844 self.code_action_providers.push(provider);
4845 self.refresh_code_actions(window, cx);
4846 }
4847
4848 pub fn remove_code_action_provider(
4849 &mut self,
4850 id: Arc<str>,
4851 window: &mut Window,
4852 cx: &mut Context<Self>,
4853 ) {
4854 self.code_action_providers
4855 .retain(|provider| provider.id() != id);
4856 self.refresh_code_actions(window, cx);
4857 }
4858
4859 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
4860 let buffer = self.buffer.read(cx);
4861 let newest_selection = self.selections.newest_anchor().clone();
4862 if newest_selection.head().diff_base_anchor.is_some() {
4863 return None;
4864 }
4865 let (start_buffer, start) = buffer.text_anchor_for_position(newest_selection.start, cx)?;
4866 let (end_buffer, end) = buffer.text_anchor_for_position(newest_selection.end, cx)?;
4867 if start_buffer != end_buffer {
4868 return None;
4869 }
4870
4871 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
4872 cx.background_executor()
4873 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
4874 .await;
4875
4876 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
4877 let providers = this.code_action_providers.clone();
4878 let tasks = this
4879 .code_action_providers
4880 .iter()
4881 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
4882 .collect::<Vec<_>>();
4883 (providers, tasks)
4884 })?;
4885
4886 let mut actions = Vec::new();
4887 for (provider, provider_actions) in
4888 providers.into_iter().zip(future::join_all(tasks).await)
4889 {
4890 if let Some(provider_actions) = provider_actions.log_err() {
4891 actions.extend(provider_actions.into_iter().map(|action| {
4892 AvailableCodeAction {
4893 excerpt_id: newest_selection.start.excerpt_id,
4894 action,
4895 provider: provider.clone(),
4896 }
4897 }));
4898 }
4899 }
4900
4901 this.update(cx, |this, cx| {
4902 this.available_code_actions = if actions.is_empty() {
4903 None
4904 } else {
4905 Some((
4906 Location {
4907 buffer: start_buffer,
4908 range: start..end,
4909 },
4910 actions.into(),
4911 ))
4912 };
4913 cx.notify();
4914 })
4915 }));
4916 None
4917 }
4918
4919 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4920 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
4921 self.show_git_blame_inline = false;
4922
4923 self.show_git_blame_inline_delay_task =
4924 Some(cx.spawn_in(window, async move |this, cx| {
4925 cx.background_executor().timer(delay).await;
4926
4927 this.update(cx, |this, cx| {
4928 this.show_git_blame_inline = true;
4929 cx.notify();
4930 })
4931 .log_err();
4932 }));
4933 }
4934 }
4935
4936 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
4937 if self.pending_rename.is_some() {
4938 return None;
4939 }
4940
4941 let provider = self.semantics_provider.clone()?;
4942 let buffer = self.buffer.read(cx);
4943 let newest_selection = self.selections.newest_anchor().clone();
4944 let cursor_position = newest_selection.head();
4945 let (cursor_buffer, cursor_buffer_position) =
4946 buffer.text_anchor_for_position(cursor_position, cx)?;
4947 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
4948 if cursor_buffer != tail_buffer {
4949 return None;
4950 }
4951 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
4952 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
4953 cx.background_executor()
4954 .timer(Duration::from_millis(debounce))
4955 .await;
4956
4957 let highlights = if let Some(highlights) = cx
4958 .update(|cx| {
4959 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
4960 })
4961 .ok()
4962 .flatten()
4963 {
4964 highlights.await.log_err()
4965 } else {
4966 None
4967 };
4968
4969 if let Some(highlights) = highlights {
4970 this.update(cx, |this, cx| {
4971 if this.pending_rename.is_some() {
4972 return;
4973 }
4974
4975 let buffer_id = cursor_position.buffer_id;
4976 let buffer = this.buffer.read(cx);
4977 if !buffer
4978 .text_anchor_for_position(cursor_position, cx)
4979 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
4980 {
4981 return;
4982 }
4983
4984 let cursor_buffer_snapshot = cursor_buffer.read(cx);
4985 let mut write_ranges = Vec::new();
4986 let mut read_ranges = Vec::new();
4987 for highlight in highlights {
4988 for (excerpt_id, excerpt_range) in
4989 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
4990 {
4991 let start = highlight
4992 .range
4993 .start
4994 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
4995 let end = highlight
4996 .range
4997 .end
4998 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
4999 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5000 continue;
5001 }
5002
5003 let range = Anchor {
5004 buffer_id,
5005 excerpt_id,
5006 text_anchor: start,
5007 diff_base_anchor: None,
5008 }..Anchor {
5009 buffer_id,
5010 excerpt_id,
5011 text_anchor: end,
5012 diff_base_anchor: None,
5013 };
5014 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5015 write_ranges.push(range);
5016 } else {
5017 read_ranges.push(range);
5018 }
5019 }
5020 }
5021
5022 this.highlight_background::<DocumentHighlightRead>(
5023 &read_ranges,
5024 |theme| theme.editor_document_highlight_read_background,
5025 cx,
5026 );
5027 this.highlight_background::<DocumentHighlightWrite>(
5028 &write_ranges,
5029 |theme| theme.editor_document_highlight_write_background,
5030 cx,
5031 );
5032 cx.notify();
5033 })
5034 .log_err();
5035 }
5036 }));
5037 None
5038 }
5039
5040 pub fn refresh_selected_text_highlights(
5041 &mut self,
5042 window: &mut Window,
5043 cx: &mut Context<Editor>,
5044 ) {
5045 if matches!(self.mode, EditorMode::SingleLine { .. }) {
5046 return;
5047 }
5048 self.selection_highlight_task.take();
5049 if !EditorSettings::get_global(cx).selection_highlight {
5050 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5051 return;
5052 }
5053 if self.selections.count() != 1 || self.selections.line_mode {
5054 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5055 return;
5056 }
5057 let selection = self.selections.newest::<Point>(cx);
5058 if selection.is_empty() || selection.start.row != selection.end.row {
5059 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5060 return;
5061 }
5062 let debounce = EditorSettings::get_global(cx).selection_highlight_debounce;
5063 self.selection_highlight_task = Some(cx.spawn_in(window, async move |editor, cx| {
5064 cx.background_executor()
5065 .timer(Duration::from_millis(debounce))
5066 .await;
5067 let Some(Some(matches_task)) = editor
5068 .update_in(cx, |editor, _, cx| {
5069 if editor.selections.count() != 1 || editor.selections.line_mode {
5070 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5071 return None;
5072 }
5073 let selection = editor.selections.newest::<Point>(cx);
5074 if selection.is_empty() || selection.start.row != selection.end.row {
5075 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5076 return None;
5077 }
5078 let buffer = editor.buffer().read(cx).snapshot(cx);
5079 let query = buffer.text_for_range(selection.range()).collect::<String>();
5080 if query.trim().is_empty() {
5081 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5082 return None;
5083 }
5084 Some(cx.background_spawn(async move {
5085 let mut ranges = Vec::new();
5086 let selection_anchors = selection.range().to_anchors(&buffer);
5087 for range in [buffer.anchor_before(0)..buffer.anchor_after(buffer.len())] {
5088 for (search_buffer, search_range, excerpt_id) in
5089 buffer.range_to_buffer_ranges(range)
5090 {
5091 ranges.extend(
5092 project::search::SearchQuery::text(
5093 query.clone(),
5094 false,
5095 false,
5096 false,
5097 Default::default(),
5098 Default::default(),
5099 None,
5100 )
5101 .unwrap()
5102 .search(search_buffer, Some(search_range.clone()))
5103 .await
5104 .into_iter()
5105 .filter_map(
5106 |match_range| {
5107 let start = search_buffer.anchor_after(
5108 search_range.start + match_range.start,
5109 );
5110 let end = search_buffer.anchor_before(
5111 search_range.start + match_range.end,
5112 );
5113 let range = Anchor::range_in_buffer(
5114 excerpt_id,
5115 search_buffer.remote_id(),
5116 start..end,
5117 );
5118 (range != selection_anchors).then_some(range)
5119 },
5120 ),
5121 );
5122 }
5123 }
5124 ranges
5125 }))
5126 })
5127 .log_err()
5128 else {
5129 return;
5130 };
5131 let matches = matches_task.await;
5132 editor
5133 .update_in(cx, |editor, _, cx| {
5134 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5135 if !matches.is_empty() {
5136 editor.highlight_background::<SelectedTextHighlight>(
5137 &matches,
5138 |theme| theme.editor_document_highlight_bracket_background,
5139 cx,
5140 )
5141 }
5142 })
5143 .log_err();
5144 }));
5145 }
5146
5147 pub fn refresh_inline_completion(
5148 &mut self,
5149 debounce: bool,
5150 user_requested: bool,
5151 window: &mut Window,
5152 cx: &mut Context<Self>,
5153 ) -> Option<()> {
5154 let provider = self.edit_prediction_provider()?;
5155 let cursor = self.selections.newest_anchor().head();
5156 let (buffer, cursor_buffer_position) =
5157 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5158
5159 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
5160 self.discard_inline_completion(false, cx);
5161 return None;
5162 }
5163
5164 if !user_requested
5165 && (!self.should_show_edit_predictions()
5166 || !self.is_focused(window)
5167 || buffer.read(cx).is_empty())
5168 {
5169 self.discard_inline_completion(false, cx);
5170 return None;
5171 }
5172
5173 self.update_visible_inline_completion(window, cx);
5174 provider.refresh(
5175 self.project.clone(),
5176 buffer,
5177 cursor_buffer_position,
5178 debounce,
5179 cx,
5180 );
5181 Some(())
5182 }
5183
5184 fn show_edit_predictions_in_menu(&self) -> bool {
5185 match self.edit_prediction_settings {
5186 EditPredictionSettings::Disabled => false,
5187 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
5188 }
5189 }
5190
5191 pub fn edit_predictions_enabled(&self) -> bool {
5192 match self.edit_prediction_settings {
5193 EditPredictionSettings::Disabled => false,
5194 EditPredictionSettings::Enabled { .. } => true,
5195 }
5196 }
5197
5198 fn edit_prediction_requires_modifier(&self) -> bool {
5199 match self.edit_prediction_settings {
5200 EditPredictionSettings::Disabled => false,
5201 EditPredictionSettings::Enabled {
5202 preview_requires_modifier,
5203 ..
5204 } => preview_requires_modifier,
5205 }
5206 }
5207
5208 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
5209 if self.edit_prediction_provider.is_none() {
5210 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5211 } else {
5212 let selection = self.selections.newest_anchor();
5213 let cursor = selection.head();
5214
5215 if let Some((buffer, cursor_buffer_position)) =
5216 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5217 {
5218 self.edit_prediction_settings =
5219 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5220 }
5221 }
5222 }
5223
5224 fn edit_prediction_settings_at_position(
5225 &self,
5226 buffer: &Entity<Buffer>,
5227 buffer_position: language::Anchor,
5228 cx: &App,
5229 ) -> EditPredictionSettings {
5230 if self.mode != EditorMode::Full
5231 || !self.show_inline_completions_override.unwrap_or(true)
5232 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
5233 {
5234 return EditPredictionSettings::Disabled;
5235 }
5236
5237 let buffer = buffer.read(cx);
5238
5239 let file = buffer.file();
5240
5241 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
5242 return EditPredictionSettings::Disabled;
5243 };
5244
5245 let by_provider = matches!(
5246 self.menu_inline_completions_policy,
5247 MenuInlineCompletionsPolicy::ByProvider
5248 );
5249
5250 let show_in_menu = by_provider
5251 && self
5252 .edit_prediction_provider
5253 .as_ref()
5254 .map_or(false, |provider| {
5255 provider.provider.show_completions_in_menu()
5256 });
5257
5258 let preview_requires_modifier =
5259 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
5260
5261 EditPredictionSettings::Enabled {
5262 show_in_menu,
5263 preview_requires_modifier,
5264 }
5265 }
5266
5267 fn should_show_edit_predictions(&self) -> bool {
5268 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
5269 }
5270
5271 pub fn edit_prediction_preview_is_active(&self) -> bool {
5272 matches!(
5273 self.edit_prediction_preview,
5274 EditPredictionPreview::Active { .. }
5275 )
5276 }
5277
5278 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
5279 let cursor = self.selections.newest_anchor().head();
5280 if let Some((buffer, cursor_position)) =
5281 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5282 {
5283 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
5284 } else {
5285 false
5286 }
5287 }
5288
5289 fn edit_predictions_enabled_in_buffer(
5290 &self,
5291 buffer: &Entity<Buffer>,
5292 buffer_position: language::Anchor,
5293 cx: &App,
5294 ) -> bool {
5295 maybe!({
5296 if self.read_only(cx) {
5297 return Some(false);
5298 }
5299 let provider = self.edit_prediction_provider()?;
5300 if !provider.is_enabled(&buffer, buffer_position, cx) {
5301 return Some(false);
5302 }
5303 let buffer = buffer.read(cx);
5304 let Some(file) = buffer.file() else {
5305 return Some(true);
5306 };
5307 let settings = all_language_settings(Some(file), cx);
5308 Some(settings.edit_predictions_enabled_for_file(file, cx))
5309 })
5310 .unwrap_or(false)
5311 }
5312
5313 fn cycle_inline_completion(
5314 &mut self,
5315 direction: Direction,
5316 window: &mut Window,
5317 cx: &mut Context<Self>,
5318 ) -> Option<()> {
5319 let provider = self.edit_prediction_provider()?;
5320 let cursor = self.selections.newest_anchor().head();
5321 let (buffer, cursor_buffer_position) =
5322 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5323 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
5324 return None;
5325 }
5326
5327 provider.cycle(buffer, cursor_buffer_position, direction, cx);
5328 self.update_visible_inline_completion(window, cx);
5329
5330 Some(())
5331 }
5332
5333 pub fn show_inline_completion(
5334 &mut self,
5335 _: &ShowEditPrediction,
5336 window: &mut Window,
5337 cx: &mut Context<Self>,
5338 ) {
5339 if !self.has_active_inline_completion() {
5340 self.refresh_inline_completion(false, true, window, cx);
5341 return;
5342 }
5343
5344 self.update_visible_inline_completion(window, cx);
5345 }
5346
5347 pub fn display_cursor_names(
5348 &mut self,
5349 _: &DisplayCursorNames,
5350 window: &mut Window,
5351 cx: &mut Context<Self>,
5352 ) {
5353 self.show_cursor_names(window, cx);
5354 }
5355
5356 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5357 self.show_cursor_names = true;
5358 cx.notify();
5359 cx.spawn_in(window, async move |this, cx| {
5360 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
5361 this.update(cx, |this, cx| {
5362 this.show_cursor_names = false;
5363 cx.notify()
5364 })
5365 .ok()
5366 })
5367 .detach();
5368 }
5369
5370 pub fn next_edit_prediction(
5371 &mut self,
5372 _: &NextEditPrediction,
5373 window: &mut Window,
5374 cx: &mut Context<Self>,
5375 ) {
5376 if self.has_active_inline_completion() {
5377 self.cycle_inline_completion(Direction::Next, window, cx);
5378 } else {
5379 let is_copilot_disabled = self
5380 .refresh_inline_completion(false, true, window, cx)
5381 .is_none();
5382 if is_copilot_disabled {
5383 cx.propagate();
5384 }
5385 }
5386 }
5387
5388 pub fn previous_edit_prediction(
5389 &mut self,
5390 _: &PreviousEditPrediction,
5391 window: &mut Window,
5392 cx: &mut Context<Self>,
5393 ) {
5394 if self.has_active_inline_completion() {
5395 self.cycle_inline_completion(Direction::Prev, window, cx);
5396 } else {
5397 let is_copilot_disabled = self
5398 .refresh_inline_completion(false, true, window, cx)
5399 .is_none();
5400 if is_copilot_disabled {
5401 cx.propagate();
5402 }
5403 }
5404 }
5405
5406 pub fn accept_edit_prediction(
5407 &mut self,
5408 _: &AcceptEditPrediction,
5409 window: &mut Window,
5410 cx: &mut Context<Self>,
5411 ) {
5412 if self.show_edit_predictions_in_menu() {
5413 self.hide_context_menu(window, cx);
5414 }
5415
5416 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5417 return;
5418 };
5419
5420 self.report_inline_completion_event(
5421 active_inline_completion.completion_id.clone(),
5422 true,
5423 cx,
5424 );
5425
5426 match &active_inline_completion.completion {
5427 InlineCompletion::Move { target, .. } => {
5428 let target = *target;
5429
5430 if let Some(position_map) = &self.last_position_map {
5431 if position_map
5432 .visible_row_range
5433 .contains(&target.to_display_point(&position_map.snapshot).row())
5434 || !self.edit_prediction_requires_modifier()
5435 {
5436 self.unfold_ranges(&[target..target], true, false, cx);
5437 // Note that this is also done in vim's handler of the Tab action.
5438 self.change_selections(
5439 Some(Autoscroll::newest()),
5440 window,
5441 cx,
5442 |selections| {
5443 selections.select_anchor_ranges([target..target]);
5444 },
5445 );
5446 self.clear_row_highlights::<EditPredictionPreview>();
5447
5448 self.edit_prediction_preview
5449 .set_previous_scroll_position(None);
5450 } else {
5451 self.edit_prediction_preview
5452 .set_previous_scroll_position(Some(
5453 position_map.snapshot.scroll_anchor,
5454 ));
5455
5456 self.highlight_rows::<EditPredictionPreview>(
5457 target..target,
5458 cx.theme().colors().editor_highlighted_line_background,
5459 true,
5460 cx,
5461 );
5462 self.request_autoscroll(Autoscroll::fit(), cx);
5463 }
5464 }
5465 }
5466 InlineCompletion::Edit { edits, .. } => {
5467 if let Some(provider) = self.edit_prediction_provider() {
5468 provider.accept(cx);
5469 }
5470
5471 let snapshot = self.buffer.read(cx).snapshot(cx);
5472 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
5473
5474 self.buffer.update(cx, |buffer, cx| {
5475 buffer.edit(edits.iter().cloned(), None, cx)
5476 });
5477
5478 self.change_selections(None, window, cx, |s| {
5479 s.select_anchor_ranges([last_edit_end..last_edit_end])
5480 });
5481
5482 self.update_visible_inline_completion(window, cx);
5483 if self.active_inline_completion.is_none() {
5484 self.refresh_inline_completion(true, true, window, cx);
5485 }
5486
5487 cx.notify();
5488 }
5489 }
5490
5491 self.edit_prediction_requires_modifier_in_indent_conflict = false;
5492 }
5493
5494 pub fn accept_partial_inline_completion(
5495 &mut self,
5496 _: &AcceptPartialEditPrediction,
5497 window: &mut Window,
5498 cx: &mut Context<Self>,
5499 ) {
5500 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5501 return;
5502 };
5503 if self.selections.count() != 1 {
5504 return;
5505 }
5506
5507 self.report_inline_completion_event(
5508 active_inline_completion.completion_id.clone(),
5509 true,
5510 cx,
5511 );
5512
5513 match &active_inline_completion.completion {
5514 InlineCompletion::Move { target, .. } => {
5515 let target = *target;
5516 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
5517 selections.select_anchor_ranges([target..target]);
5518 });
5519 }
5520 InlineCompletion::Edit { edits, .. } => {
5521 // Find an insertion that starts at the cursor position.
5522 let snapshot = self.buffer.read(cx).snapshot(cx);
5523 let cursor_offset = self.selections.newest::<usize>(cx).head();
5524 let insertion = edits.iter().find_map(|(range, text)| {
5525 let range = range.to_offset(&snapshot);
5526 if range.is_empty() && range.start == cursor_offset {
5527 Some(text)
5528 } else {
5529 None
5530 }
5531 });
5532
5533 if let Some(text) = insertion {
5534 let mut partial_completion = text
5535 .chars()
5536 .by_ref()
5537 .take_while(|c| c.is_alphabetic())
5538 .collect::<String>();
5539 if partial_completion.is_empty() {
5540 partial_completion = text
5541 .chars()
5542 .by_ref()
5543 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
5544 .collect::<String>();
5545 }
5546
5547 cx.emit(EditorEvent::InputHandled {
5548 utf16_range_to_replace: None,
5549 text: partial_completion.clone().into(),
5550 });
5551
5552 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
5553
5554 self.refresh_inline_completion(true, true, window, cx);
5555 cx.notify();
5556 } else {
5557 self.accept_edit_prediction(&Default::default(), window, cx);
5558 }
5559 }
5560 }
5561 }
5562
5563 fn discard_inline_completion(
5564 &mut self,
5565 should_report_inline_completion_event: bool,
5566 cx: &mut Context<Self>,
5567 ) -> bool {
5568 if should_report_inline_completion_event {
5569 let completion_id = self
5570 .active_inline_completion
5571 .as_ref()
5572 .and_then(|active_completion| active_completion.completion_id.clone());
5573
5574 self.report_inline_completion_event(completion_id, false, cx);
5575 }
5576
5577 if let Some(provider) = self.edit_prediction_provider() {
5578 provider.discard(cx);
5579 }
5580
5581 self.take_active_inline_completion(cx)
5582 }
5583
5584 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
5585 let Some(provider) = self.edit_prediction_provider() else {
5586 return;
5587 };
5588
5589 let Some((_, buffer, _)) = self
5590 .buffer
5591 .read(cx)
5592 .excerpt_containing(self.selections.newest_anchor().head(), cx)
5593 else {
5594 return;
5595 };
5596
5597 let extension = buffer
5598 .read(cx)
5599 .file()
5600 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
5601
5602 let event_type = match accepted {
5603 true => "Edit Prediction Accepted",
5604 false => "Edit Prediction Discarded",
5605 };
5606 telemetry::event!(
5607 event_type,
5608 provider = provider.name(),
5609 prediction_id = id,
5610 suggestion_accepted = accepted,
5611 file_extension = extension,
5612 );
5613 }
5614
5615 pub fn has_active_inline_completion(&self) -> bool {
5616 self.active_inline_completion.is_some()
5617 }
5618
5619 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
5620 let Some(active_inline_completion) = self.active_inline_completion.take() else {
5621 return false;
5622 };
5623
5624 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
5625 self.clear_highlights::<InlineCompletionHighlight>(cx);
5626 self.stale_inline_completion_in_menu = Some(active_inline_completion);
5627 true
5628 }
5629
5630 /// Returns true when we're displaying the edit prediction popover below the cursor
5631 /// like we are not previewing and the LSP autocomplete menu is visible
5632 /// or we are in `when_holding_modifier` mode.
5633 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
5634 if self.edit_prediction_preview_is_active()
5635 || !self.show_edit_predictions_in_menu()
5636 || !self.edit_predictions_enabled()
5637 {
5638 return false;
5639 }
5640
5641 if self.has_visible_completions_menu() {
5642 return true;
5643 }
5644
5645 has_completion && self.edit_prediction_requires_modifier()
5646 }
5647
5648 fn handle_modifiers_changed(
5649 &mut self,
5650 modifiers: Modifiers,
5651 position_map: &PositionMap,
5652 window: &mut Window,
5653 cx: &mut Context<Self>,
5654 ) {
5655 if self.show_edit_predictions_in_menu() {
5656 self.update_edit_prediction_preview(&modifiers, window, cx);
5657 }
5658
5659 self.update_selection_mode(&modifiers, position_map, window, cx);
5660
5661 let mouse_position = window.mouse_position();
5662 if !position_map.text_hitbox.is_hovered(window) {
5663 return;
5664 }
5665
5666 self.update_hovered_link(
5667 position_map.point_for_position(mouse_position),
5668 &position_map.snapshot,
5669 modifiers,
5670 window,
5671 cx,
5672 )
5673 }
5674
5675 fn update_selection_mode(
5676 &mut self,
5677 modifiers: &Modifiers,
5678 position_map: &PositionMap,
5679 window: &mut Window,
5680 cx: &mut Context<Self>,
5681 ) {
5682 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
5683 return;
5684 }
5685
5686 let mouse_position = window.mouse_position();
5687 let point_for_position = position_map.point_for_position(mouse_position);
5688 let position = point_for_position.previous_valid;
5689
5690 self.select(
5691 SelectPhase::BeginColumnar {
5692 position,
5693 reset: false,
5694 goal_column: point_for_position.exact_unclipped.column(),
5695 },
5696 window,
5697 cx,
5698 );
5699 }
5700
5701 fn update_edit_prediction_preview(
5702 &mut self,
5703 modifiers: &Modifiers,
5704 window: &mut Window,
5705 cx: &mut Context<Self>,
5706 ) {
5707 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
5708 let Some(accept_keystroke) = accept_keybind.keystroke() else {
5709 return;
5710 };
5711
5712 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
5713 if matches!(
5714 self.edit_prediction_preview,
5715 EditPredictionPreview::Inactive { .. }
5716 ) {
5717 self.edit_prediction_preview = EditPredictionPreview::Active {
5718 previous_scroll_position: None,
5719 since: Instant::now(),
5720 };
5721
5722 self.update_visible_inline_completion(window, cx);
5723 cx.notify();
5724 }
5725 } else if let EditPredictionPreview::Active {
5726 previous_scroll_position,
5727 since,
5728 } = self.edit_prediction_preview
5729 {
5730 if let (Some(previous_scroll_position), Some(position_map)) =
5731 (previous_scroll_position, self.last_position_map.as_ref())
5732 {
5733 self.set_scroll_position(
5734 previous_scroll_position
5735 .scroll_position(&position_map.snapshot.display_snapshot),
5736 window,
5737 cx,
5738 );
5739 }
5740
5741 self.edit_prediction_preview = EditPredictionPreview::Inactive {
5742 released_too_fast: since.elapsed() < Duration::from_millis(200),
5743 };
5744 self.clear_row_highlights::<EditPredictionPreview>();
5745 self.update_visible_inline_completion(window, cx);
5746 cx.notify();
5747 }
5748 }
5749
5750 fn update_visible_inline_completion(
5751 &mut self,
5752 _window: &mut Window,
5753 cx: &mut Context<Self>,
5754 ) -> Option<()> {
5755 let selection = self.selections.newest_anchor();
5756 let cursor = selection.head();
5757 let multibuffer = self.buffer.read(cx).snapshot(cx);
5758 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
5759 let excerpt_id = cursor.excerpt_id;
5760
5761 let show_in_menu = self.show_edit_predictions_in_menu();
5762 let completions_menu_has_precedence = !show_in_menu
5763 && (self.context_menu.borrow().is_some()
5764 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
5765
5766 if completions_menu_has_precedence
5767 || !offset_selection.is_empty()
5768 || self
5769 .active_inline_completion
5770 .as_ref()
5771 .map_or(false, |completion| {
5772 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
5773 let invalidation_range = invalidation_range.start..=invalidation_range.end;
5774 !invalidation_range.contains(&offset_selection.head())
5775 })
5776 {
5777 self.discard_inline_completion(false, cx);
5778 return None;
5779 }
5780
5781 self.take_active_inline_completion(cx);
5782 let Some(provider) = self.edit_prediction_provider() else {
5783 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5784 return None;
5785 };
5786
5787 let (buffer, cursor_buffer_position) =
5788 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5789
5790 self.edit_prediction_settings =
5791 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5792
5793 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
5794
5795 if self.edit_prediction_indent_conflict {
5796 let cursor_point = cursor.to_point(&multibuffer);
5797
5798 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
5799
5800 if let Some((_, indent)) = indents.iter().next() {
5801 if indent.len == cursor_point.column {
5802 self.edit_prediction_indent_conflict = false;
5803 }
5804 }
5805 }
5806
5807 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
5808 let edits = inline_completion
5809 .edits
5810 .into_iter()
5811 .flat_map(|(range, new_text)| {
5812 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
5813 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
5814 Some((start..end, new_text))
5815 })
5816 .collect::<Vec<_>>();
5817 if edits.is_empty() {
5818 return None;
5819 }
5820
5821 let first_edit_start = edits.first().unwrap().0.start;
5822 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
5823 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
5824
5825 let last_edit_end = edits.last().unwrap().0.end;
5826 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
5827 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
5828
5829 let cursor_row = cursor.to_point(&multibuffer).row;
5830
5831 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
5832
5833 let mut inlay_ids = Vec::new();
5834 let invalidation_row_range;
5835 let move_invalidation_row_range = if cursor_row < edit_start_row {
5836 Some(cursor_row..edit_end_row)
5837 } else if cursor_row > edit_end_row {
5838 Some(edit_start_row..cursor_row)
5839 } else {
5840 None
5841 };
5842 let is_move =
5843 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
5844 let completion = if is_move {
5845 invalidation_row_range =
5846 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
5847 let target = first_edit_start;
5848 InlineCompletion::Move { target, snapshot }
5849 } else {
5850 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
5851 && !self.inline_completions_hidden_for_vim_mode;
5852
5853 if show_completions_in_buffer {
5854 if edits
5855 .iter()
5856 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
5857 {
5858 let mut inlays = Vec::new();
5859 for (range, new_text) in &edits {
5860 let inlay = Inlay::inline_completion(
5861 post_inc(&mut self.next_inlay_id),
5862 range.start,
5863 new_text.as_str(),
5864 );
5865 inlay_ids.push(inlay.id);
5866 inlays.push(inlay);
5867 }
5868
5869 self.splice_inlays(&[], inlays, cx);
5870 } else {
5871 let background_color = cx.theme().status().deleted_background;
5872 self.highlight_text::<InlineCompletionHighlight>(
5873 edits.iter().map(|(range, _)| range.clone()).collect(),
5874 HighlightStyle {
5875 background_color: Some(background_color),
5876 ..Default::default()
5877 },
5878 cx,
5879 );
5880 }
5881 }
5882
5883 invalidation_row_range = edit_start_row..edit_end_row;
5884
5885 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
5886 if provider.show_tab_accept_marker() {
5887 EditDisplayMode::TabAccept
5888 } else {
5889 EditDisplayMode::Inline
5890 }
5891 } else {
5892 EditDisplayMode::DiffPopover
5893 };
5894
5895 InlineCompletion::Edit {
5896 edits,
5897 edit_preview: inline_completion.edit_preview,
5898 display_mode,
5899 snapshot,
5900 }
5901 };
5902
5903 let invalidation_range = multibuffer
5904 .anchor_before(Point::new(invalidation_row_range.start, 0))
5905 ..multibuffer.anchor_after(Point::new(
5906 invalidation_row_range.end,
5907 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
5908 ));
5909
5910 self.stale_inline_completion_in_menu = None;
5911 self.active_inline_completion = Some(InlineCompletionState {
5912 inlay_ids,
5913 completion,
5914 completion_id: inline_completion.id,
5915 invalidation_range,
5916 });
5917
5918 cx.notify();
5919
5920 Some(())
5921 }
5922
5923 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
5924 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
5925 }
5926
5927 fn render_code_actions_indicator(
5928 &self,
5929 _style: &EditorStyle,
5930 row: DisplayRow,
5931 is_active: bool,
5932 breakpoint: Option<&(Anchor, Breakpoint)>,
5933 cx: &mut Context<Self>,
5934 ) -> Option<IconButton> {
5935 let color = Color::Muted;
5936
5937 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
5938 let bp_kind = Arc::new(
5939 breakpoint
5940 .map(|(_, bp)| bp.kind.clone())
5941 .unwrap_or(BreakpointKind::Standard),
5942 );
5943
5944 if self.available_code_actions.is_some() {
5945 Some(
5946 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
5947 .shape(ui::IconButtonShape::Square)
5948 .icon_size(IconSize::XSmall)
5949 .icon_color(color)
5950 .toggle_state(is_active)
5951 .tooltip({
5952 let focus_handle = self.focus_handle.clone();
5953 move |window, cx| {
5954 Tooltip::for_action_in(
5955 "Toggle Code Actions",
5956 &ToggleCodeActions {
5957 deployed_from_indicator: None,
5958 },
5959 &focus_handle,
5960 window,
5961 cx,
5962 )
5963 }
5964 })
5965 .on_click(cx.listener(move |editor, _e, window, cx| {
5966 window.focus(&editor.focus_handle(cx));
5967 editor.toggle_code_actions(
5968 &ToggleCodeActions {
5969 deployed_from_indicator: Some(row),
5970 },
5971 window,
5972 cx,
5973 );
5974 }))
5975 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
5976 editor.set_breakpoint_context_menu(
5977 row,
5978 position,
5979 bp_kind.clone(),
5980 event.down.position,
5981 window,
5982 cx,
5983 );
5984 })),
5985 )
5986 } else {
5987 None
5988 }
5989 }
5990
5991 fn clear_tasks(&mut self) {
5992 self.tasks.clear()
5993 }
5994
5995 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
5996 if self.tasks.insert(key, value).is_some() {
5997 // This case should hopefully be rare, but just in case...
5998 log::error!("multiple different run targets found on a single line, only the last target will be rendered")
5999 }
6000 }
6001
6002 /// Get all display points of breakpoints that will be rendered within editor
6003 ///
6004 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6005 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6006 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6007 fn active_breakpoints(
6008 &mut self,
6009 range: Range<DisplayRow>,
6010 window: &mut Window,
6011 cx: &mut Context<Self>,
6012 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
6013 let mut breakpoint_display_points = HashMap::default();
6014
6015 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
6016 return breakpoint_display_points;
6017 };
6018
6019 let snapshot = self.snapshot(window, cx);
6020
6021 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
6022 let Some(project) = self.project.as_ref() else {
6023 return breakpoint_display_points;
6024 };
6025
6026 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
6027 let buffer_snapshot = buffer.read(cx).snapshot();
6028
6029 for breakpoint in
6030 breakpoint_store
6031 .read(cx)
6032 .breakpoints(&buffer, None, buffer_snapshot.clone(), cx)
6033 {
6034 let point = buffer_snapshot.summary_for_anchor::<Point>(&breakpoint.0);
6035 let anchor = multi_buffer_snapshot.anchor_before(point);
6036 breakpoint_display_points.insert(
6037 snapshot
6038 .point_to_display_point(
6039 MultiBufferPoint {
6040 row: point.row,
6041 column: point.column,
6042 },
6043 Bias::Left,
6044 )
6045 .row(),
6046 (anchor, breakpoint.1.clone()),
6047 );
6048 }
6049
6050 return breakpoint_display_points;
6051 }
6052
6053 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
6054 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
6055 for excerpt_boundary in multi_buffer_snapshot.excerpt_boundaries_in_range(range) {
6056 let info = excerpt_boundary.next;
6057
6058 let Some(excerpt_ranges) = multi_buffer_snapshot.range_for_excerpt(info.id) else {
6059 continue;
6060 };
6061
6062 let Some(buffer) =
6063 project.read_with(cx, |this, cx| this.buffer_for_id(info.buffer_id, cx))
6064 else {
6065 continue;
6066 };
6067
6068 let breakpoints = breakpoint_store.read(cx).breakpoints(
6069 &buffer,
6070 Some(info.range.context.start..info.range.context.end),
6071 info.buffer.clone(),
6072 cx,
6073 );
6074
6075 // To translate a breakpoint's position within a singular buffer to a multi buffer
6076 // position we need to know it's excerpt starting location, it's position within
6077 // the singular buffer, and if that position is within the excerpt's range.
6078 let excerpt_head = excerpt_ranges
6079 .start
6080 .to_display_point(&snapshot.display_snapshot);
6081
6082 let buffer_start = info
6083 .buffer
6084 .summary_for_anchor::<Point>(&info.range.context.start);
6085
6086 for (anchor, breakpoint) in breakpoints {
6087 let as_row = info.buffer.summary_for_anchor::<Point>(&anchor).row;
6088 let delta = as_row - buffer_start.row;
6089
6090 let position = excerpt_head + DisplayPoint::new(DisplayRow(delta), 0);
6091
6092 let anchor = snapshot.display_point_to_anchor(position, Bias::Left);
6093
6094 breakpoint_display_points.insert(position.row(), (anchor, breakpoint.clone()));
6095 }
6096 }
6097
6098 breakpoint_display_points
6099 }
6100
6101 fn breakpoint_context_menu(
6102 &self,
6103 anchor: Anchor,
6104 kind: Arc<BreakpointKind>,
6105 window: &mut Window,
6106 cx: &mut Context<Self>,
6107 ) -> Entity<ui::ContextMenu> {
6108 let weak_editor = cx.weak_entity();
6109 let focus_handle = self.focus_handle(cx);
6110
6111 let second_entry_msg = if kind.log_message().is_some() {
6112 "Edit Log Breakpoint"
6113 } else {
6114 "Add Log Breakpoint"
6115 };
6116
6117 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6118 menu.on_blur_subscription(Subscription::new(|| {}))
6119 .context(focus_handle)
6120 .entry("Toggle Breakpoint", None, {
6121 let weak_editor = weak_editor.clone();
6122 move |_window, cx| {
6123 weak_editor
6124 .update(cx, |this, cx| {
6125 this.edit_breakpoint_at_anchor(
6126 anchor,
6127 BreakpointKind::Standard,
6128 BreakpointEditAction::Toggle,
6129 cx,
6130 );
6131 })
6132 .log_err();
6133 }
6134 })
6135 .entry(second_entry_msg, None, move |window, cx| {
6136 weak_editor
6137 .update(cx, |this, cx| {
6138 this.add_edit_breakpoint_block(anchor, kind.as_ref(), window, cx);
6139 })
6140 .log_err();
6141 })
6142 })
6143 }
6144
6145 fn render_breakpoint(
6146 &self,
6147 position: Anchor,
6148 row: DisplayRow,
6149 kind: &BreakpointKind,
6150 cx: &mut Context<Self>,
6151 ) -> IconButton {
6152 let color = if self
6153 .gutter_breakpoint_indicator
6154 .is_some_and(|gutter_bp| gutter_bp.row() == row)
6155 {
6156 Color::Hint
6157 } else {
6158 Color::Debugger
6159 };
6160
6161 let icon = match &kind {
6162 BreakpointKind::Standard => ui::IconName::DebugBreakpoint,
6163 BreakpointKind::Log(_) => ui::IconName::DebugLogBreakpoint,
6164 };
6165 let arc_kind = Arc::new(kind.clone());
6166 let arc_kind2 = arc_kind.clone();
6167
6168 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
6169 .icon_size(IconSize::XSmall)
6170 .size(ui::ButtonSize::None)
6171 .icon_color(color)
6172 .style(ButtonStyle::Transparent)
6173 .on_click(cx.listener(move |editor, _e, window, cx| {
6174 window.focus(&editor.focus_handle(cx));
6175 editor.edit_breakpoint_at_anchor(
6176 position,
6177 arc_kind.as_ref().clone(),
6178 BreakpointEditAction::Toggle,
6179 cx,
6180 );
6181 }))
6182 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6183 editor.set_breakpoint_context_menu(
6184 row,
6185 Some(position),
6186 arc_kind2.clone(),
6187 event.down.position,
6188 window,
6189 cx,
6190 );
6191 }))
6192 }
6193
6194 fn build_tasks_context(
6195 project: &Entity<Project>,
6196 buffer: &Entity<Buffer>,
6197 buffer_row: u32,
6198 tasks: &Arc<RunnableTasks>,
6199 cx: &mut Context<Self>,
6200 ) -> Task<Option<task::TaskContext>> {
6201 let position = Point::new(buffer_row, tasks.column);
6202 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
6203 let location = Location {
6204 buffer: buffer.clone(),
6205 range: range_start..range_start,
6206 };
6207 // Fill in the environmental variables from the tree-sitter captures
6208 let mut captured_task_variables = TaskVariables::default();
6209 for (capture_name, value) in tasks.extra_variables.clone() {
6210 captured_task_variables.insert(
6211 task::VariableName::Custom(capture_name.into()),
6212 value.clone(),
6213 );
6214 }
6215 project.update(cx, |project, cx| {
6216 project.task_store().update(cx, |task_store, cx| {
6217 task_store.task_context_for_location(captured_task_variables, location, cx)
6218 })
6219 })
6220 }
6221
6222 pub fn spawn_nearest_task(
6223 &mut self,
6224 action: &SpawnNearestTask,
6225 window: &mut Window,
6226 cx: &mut Context<Self>,
6227 ) {
6228 let Some((workspace, _)) = self.workspace.clone() else {
6229 return;
6230 };
6231 let Some(project) = self.project.clone() else {
6232 return;
6233 };
6234
6235 // Try to find a closest, enclosing node using tree-sitter that has a
6236 // task
6237 let Some((buffer, buffer_row, tasks)) = self
6238 .find_enclosing_node_task(cx)
6239 // Or find the task that's closest in row-distance.
6240 .or_else(|| self.find_closest_task(cx))
6241 else {
6242 return;
6243 };
6244
6245 let reveal_strategy = action.reveal;
6246 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6247 cx.spawn_in(window, async move |_, cx| {
6248 let context = task_context.await?;
6249 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
6250
6251 let resolved = resolved_task.resolved.as_mut()?;
6252 resolved.reveal = reveal_strategy;
6253
6254 workspace
6255 .update(cx, |workspace, cx| {
6256 workspace::tasks::schedule_resolved_task(
6257 workspace,
6258 task_source_kind,
6259 resolved_task,
6260 false,
6261 cx,
6262 );
6263 })
6264 .ok()
6265 })
6266 .detach();
6267 }
6268
6269 fn find_closest_task(
6270 &mut self,
6271 cx: &mut Context<Self>,
6272 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6273 let cursor_row = self.selections.newest_adjusted(cx).head().row;
6274
6275 let ((buffer_id, row), tasks) = self
6276 .tasks
6277 .iter()
6278 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
6279
6280 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
6281 let tasks = Arc::new(tasks.to_owned());
6282 Some((buffer, *row, tasks))
6283 }
6284
6285 fn find_enclosing_node_task(
6286 &mut self,
6287 cx: &mut Context<Self>,
6288 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6289 let snapshot = self.buffer.read(cx).snapshot(cx);
6290 let offset = self.selections.newest::<usize>(cx).head();
6291 let excerpt = snapshot.excerpt_containing(offset..offset)?;
6292 let buffer_id = excerpt.buffer().remote_id();
6293
6294 let layer = excerpt.buffer().syntax_layer_at(offset)?;
6295 let mut cursor = layer.node().walk();
6296
6297 while cursor.goto_first_child_for_byte(offset).is_some() {
6298 if cursor.node().end_byte() == offset {
6299 cursor.goto_next_sibling();
6300 }
6301 }
6302
6303 // Ascend to the smallest ancestor that contains the range and has a task.
6304 loop {
6305 let node = cursor.node();
6306 let node_range = node.byte_range();
6307 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
6308
6309 // Check if this node contains our offset
6310 if node_range.start <= offset && node_range.end >= offset {
6311 // If it contains offset, check for task
6312 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
6313 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
6314 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
6315 }
6316 }
6317
6318 if !cursor.goto_parent() {
6319 break;
6320 }
6321 }
6322 None
6323 }
6324
6325 fn render_run_indicator(
6326 &self,
6327 _style: &EditorStyle,
6328 is_active: bool,
6329 row: DisplayRow,
6330 breakpoint: Option<(Anchor, Breakpoint)>,
6331 cx: &mut Context<Self>,
6332 ) -> IconButton {
6333 let color = Color::Muted;
6334
6335 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6336 let bp_kind = Arc::new(
6337 breakpoint
6338 .map(|(_, bp)| bp.kind)
6339 .unwrap_or(BreakpointKind::Standard),
6340 );
6341
6342 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
6343 .shape(ui::IconButtonShape::Square)
6344 .icon_size(IconSize::XSmall)
6345 .icon_color(color)
6346 .toggle_state(is_active)
6347 .on_click(cx.listener(move |editor, _e, window, cx| {
6348 window.focus(&editor.focus_handle(cx));
6349 editor.toggle_code_actions(
6350 &ToggleCodeActions {
6351 deployed_from_indicator: Some(row),
6352 },
6353 window,
6354 cx,
6355 );
6356 }))
6357 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6358 editor.set_breakpoint_context_menu(
6359 row,
6360 position,
6361 bp_kind.clone(),
6362 event.down.position,
6363 window,
6364 cx,
6365 );
6366 }))
6367 }
6368
6369 pub fn context_menu_visible(&self) -> bool {
6370 !self.edit_prediction_preview_is_active()
6371 && self
6372 .context_menu
6373 .borrow()
6374 .as_ref()
6375 .map_or(false, |menu| menu.visible())
6376 }
6377
6378 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
6379 self.context_menu
6380 .borrow()
6381 .as_ref()
6382 .map(|menu| menu.origin())
6383 }
6384
6385 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
6386 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
6387
6388 fn render_edit_prediction_popover(
6389 &mut self,
6390 text_bounds: &Bounds<Pixels>,
6391 content_origin: gpui::Point<Pixels>,
6392 editor_snapshot: &EditorSnapshot,
6393 visible_row_range: Range<DisplayRow>,
6394 scroll_top: f32,
6395 scroll_bottom: f32,
6396 line_layouts: &[LineWithInvisibles],
6397 line_height: Pixels,
6398 scroll_pixel_position: gpui::Point<Pixels>,
6399 newest_selection_head: Option<DisplayPoint>,
6400 editor_width: Pixels,
6401 style: &EditorStyle,
6402 window: &mut Window,
6403 cx: &mut App,
6404 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6405 let active_inline_completion = self.active_inline_completion.as_ref()?;
6406
6407 if self.edit_prediction_visible_in_cursor_popover(true) {
6408 return None;
6409 }
6410
6411 match &active_inline_completion.completion {
6412 InlineCompletion::Move { target, .. } => {
6413 let target_display_point = target.to_display_point(editor_snapshot);
6414
6415 if self.edit_prediction_requires_modifier() {
6416 if !self.edit_prediction_preview_is_active() {
6417 return None;
6418 }
6419
6420 self.render_edit_prediction_modifier_jump_popover(
6421 text_bounds,
6422 content_origin,
6423 visible_row_range,
6424 line_layouts,
6425 line_height,
6426 scroll_pixel_position,
6427 newest_selection_head,
6428 target_display_point,
6429 window,
6430 cx,
6431 )
6432 } else {
6433 self.render_edit_prediction_eager_jump_popover(
6434 text_bounds,
6435 content_origin,
6436 editor_snapshot,
6437 visible_row_range,
6438 scroll_top,
6439 scroll_bottom,
6440 line_height,
6441 scroll_pixel_position,
6442 target_display_point,
6443 editor_width,
6444 window,
6445 cx,
6446 )
6447 }
6448 }
6449 InlineCompletion::Edit {
6450 display_mode: EditDisplayMode::Inline,
6451 ..
6452 } => None,
6453 InlineCompletion::Edit {
6454 display_mode: EditDisplayMode::TabAccept,
6455 edits,
6456 ..
6457 } => {
6458 let range = &edits.first()?.0;
6459 let target_display_point = range.end.to_display_point(editor_snapshot);
6460
6461 self.render_edit_prediction_end_of_line_popover(
6462 "Accept",
6463 editor_snapshot,
6464 visible_row_range,
6465 target_display_point,
6466 line_height,
6467 scroll_pixel_position,
6468 content_origin,
6469 editor_width,
6470 window,
6471 cx,
6472 )
6473 }
6474 InlineCompletion::Edit {
6475 edits,
6476 edit_preview,
6477 display_mode: EditDisplayMode::DiffPopover,
6478 snapshot,
6479 } => self.render_edit_prediction_diff_popover(
6480 text_bounds,
6481 content_origin,
6482 editor_snapshot,
6483 visible_row_range,
6484 line_layouts,
6485 line_height,
6486 scroll_pixel_position,
6487 newest_selection_head,
6488 editor_width,
6489 style,
6490 edits,
6491 edit_preview,
6492 snapshot,
6493 window,
6494 cx,
6495 ),
6496 }
6497 }
6498
6499 fn render_edit_prediction_modifier_jump_popover(
6500 &mut self,
6501 text_bounds: &Bounds<Pixels>,
6502 content_origin: gpui::Point<Pixels>,
6503 visible_row_range: Range<DisplayRow>,
6504 line_layouts: &[LineWithInvisibles],
6505 line_height: Pixels,
6506 scroll_pixel_position: gpui::Point<Pixels>,
6507 newest_selection_head: Option<DisplayPoint>,
6508 target_display_point: DisplayPoint,
6509 window: &mut Window,
6510 cx: &mut App,
6511 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6512 let scrolled_content_origin =
6513 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
6514
6515 const SCROLL_PADDING_Y: Pixels = px(12.);
6516
6517 if target_display_point.row() < visible_row_range.start {
6518 return self.render_edit_prediction_scroll_popover(
6519 |_| SCROLL_PADDING_Y,
6520 IconName::ArrowUp,
6521 visible_row_range,
6522 line_layouts,
6523 newest_selection_head,
6524 scrolled_content_origin,
6525 window,
6526 cx,
6527 );
6528 } else if target_display_point.row() >= visible_row_range.end {
6529 return self.render_edit_prediction_scroll_popover(
6530 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
6531 IconName::ArrowDown,
6532 visible_row_range,
6533 line_layouts,
6534 newest_selection_head,
6535 scrolled_content_origin,
6536 window,
6537 cx,
6538 );
6539 }
6540
6541 const POLE_WIDTH: Pixels = px(2.);
6542
6543 let line_layout =
6544 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
6545 let target_column = target_display_point.column() as usize;
6546
6547 let target_x = line_layout.x_for_index(target_column);
6548 let target_y =
6549 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
6550
6551 let flag_on_right = target_x < text_bounds.size.width / 2.;
6552
6553 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
6554 border_color.l += 0.001;
6555
6556 let mut element = v_flex()
6557 .items_end()
6558 .when(flag_on_right, |el| el.items_start())
6559 .child(if flag_on_right {
6560 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
6561 .rounded_bl(px(0.))
6562 .rounded_tl(px(0.))
6563 .border_l_2()
6564 .border_color(border_color)
6565 } else {
6566 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
6567 .rounded_br(px(0.))
6568 .rounded_tr(px(0.))
6569 .border_r_2()
6570 .border_color(border_color)
6571 })
6572 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
6573 .into_any();
6574
6575 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6576
6577 let mut origin = scrolled_content_origin + point(target_x, target_y)
6578 - point(
6579 if flag_on_right {
6580 POLE_WIDTH
6581 } else {
6582 size.width - POLE_WIDTH
6583 },
6584 size.height - line_height,
6585 );
6586
6587 origin.x = origin.x.max(content_origin.x);
6588
6589 element.prepaint_at(origin, window, cx);
6590
6591 Some((element, origin))
6592 }
6593
6594 fn render_edit_prediction_scroll_popover(
6595 &mut self,
6596 to_y: impl Fn(Size<Pixels>) -> Pixels,
6597 scroll_icon: IconName,
6598 visible_row_range: Range<DisplayRow>,
6599 line_layouts: &[LineWithInvisibles],
6600 newest_selection_head: Option<DisplayPoint>,
6601 scrolled_content_origin: gpui::Point<Pixels>,
6602 window: &mut Window,
6603 cx: &mut App,
6604 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6605 let mut element = self
6606 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
6607 .into_any();
6608
6609 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6610
6611 let cursor = newest_selection_head?;
6612 let cursor_row_layout =
6613 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
6614 let cursor_column = cursor.column() as usize;
6615
6616 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
6617
6618 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
6619
6620 element.prepaint_at(origin, window, cx);
6621 Some((element, origin))
6622 }
6623
6624 fn render_edit_prediction_eager_jump_popover(
6625 &mut self,
6626 text_bounds: &Bounds<Pixels>,
6627 content_origin: gpui::Point<Pixels>,
6628 editor_snapshot: &EditorSnapshot,
6629 visible_row_range: Range<DisplayRow>,
6630 scroll_top: f32,
6631 scroll_bottom: f32,
6632 line_height: Pixels,
6633 scroll_pixel_position: gpui::Point<Pixels>,
6634 target_display_point: DisplayPoint,
6635 editor_width: Pixels,
6636 window: &mut Window,
6637 cx: &mut App,
6638 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6639 if target_display_point.row().as_f32() < scroll_top {
6640 let mut element = self
6641 .render_edit_prediction_line_popover(
6642 "Jump to Edit",
6643 Some(IconName::ArrowUp),
6644 window,
6645 cx,
6646 )?
6647 .into_any();
6648
6649 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6650 let offset = point(
6651 (text_bounds.size.width - size.width) / 2.,
6652 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
6653 );
6654
6655 let origin = text_bounds.origin + offset;
6656 element.prepaint_at(origin, window, cx);
6657 Some((element, origin))
6658 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
6659 let mut element = self
6660 .render_edit_prediction_line_popover(
6661 "Jump to Edit",
6662 Some(IconName::ArrowDown),
6663 window,
6664 cx,
6665 )?
6666 .into_any();
6667
6668 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6669 let offset = point(
6670 (text_bounds.size.width - size.width) / 2.,
6671 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
6672 );
6673
6674 let origin = text_bounds.origin + offset;
6675 element.prepaint_at(origin, window, cx);
6676 Some((element, origin))
6677 } else {
6678 self.render_edit_prediction_end_of_line_popover(
6679 "Jump to Edit",
6680 editor_snapshot,
6681 visible_row_range,
6682 target_display_point,
6683 line_height,
6684 scroll_pixel_position,
6685 content_origin,
6686 editor_width,
6687 window,
6688 cx,
6689 )
6690 }
6691 }
6692
6693 fn render_edit_prediction_end_of_line_popover(
6694 self: &mut Editor,
6695 label: &'static str,
6696 editor_snapshot: &EditorSnapshot,
6697 visible_row_range: Range<DisplayRow>,
6698 target_display_point: DisplayPoint,
6699 line_height: Pixels,
6700 scroll_pixel_position: gpui::Point<Pixels>,
6701 content_origin: gpui::Point<Pixels>,
6702 editor_width: Pixels,
6703 window: &mut Window,
6704 cx: &mut App,
6705 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6706 let target_line_end = DisplayPoint::new(
6707 target_display_point.row(),
6708 editor_snapshot.line_len(target_display_point.row()),
6709 );
6710
6711 let mut element = self
6712 .render_edit_prediction_line_popover(label, None, window, cx)?
6713 .into_any();
6714
6715 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6716
6717 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
6718
6719 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
6720 let mut origin = start_point
6721 + line_origin
6722 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
6723 origin.x = origin.x.max(content_origin.x);
6724
6725 let max_x = content_origin.x + editor_width - size.width;
6726
6727 if origin.x > max_x {
6728 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
6729
6730 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
6731 origin.y += offset;
6732 IconName::ArrowUp
6733 } else {
6734 origin.y -= offset;
6735 IconName::ArrowDown
6736 };
6737
6738 element = self
6739 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
6740 .into_any();
6741
6742 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6743
6744 origin.x = content_origin.x + editor_width - size.width - px(2.);
6745 }
6746
6747 element.prepaint_at(origin, window, cx);
6748 Some((element, origin))
6749 }
6750
6751 fn render_edit_prediction_diff_popover(
6752 self: &Editor,
6753 text_bounds: &Bounds<Pixels>,
6754 content_origin: gpui::Point<Pixels>,
6755 editor_snapshot: &EditorSnapshot,
6756 visible_row_range: Range<DisplayRow>,
6757 line_layouts: &[LineWithInvisibles],
6758 line_height: Pixels,
6759 scroll_pixel_position: gpui::Point<Pixels>,
6760 newest_selection_head: Option<DisplayPoint>,
6761 editor_width: Pixels,
6762 style: &EditorStyle,
6763 edits: &Vec<(Range<Anchor>, String)>,
6764 edit_preview: &Option<language::EditPreview>,
6765 snapshot: &language::BufferSnapshot,
6766 window: &mut Window,
6767 cx: &mut App,
6768 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6769 let edit_start = edits
6770 .first()
6771 .unwrap()
6772 .0
6773 .start
6774 .to_display_point(editor_snapshot);
6775 let edit_end = edits
6776 .last()
6777 .unwrap()
6778 .0
6779 .end
6780 .to_display_point(editor_snapshot);
6781
6782 let is_visible = visible_row_range.contains(&edit_start.row())
6783 || visible_row_range.contains(&edit_end.row());
6784 if !is_visible {
6785 return None;
6786 }
6787
6788 let highlighted_edits =
6789 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
6790
6791 let styled_text = highlighted_edits.to_styled_text(&style.text);
6792 let line_count = highlighted_edits.text.lines().count();
6793
6794 const BORDER_WIDTH: Pixels = px(1.);
6795
6796 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
6797 let has_keybind = keybind.is_some();
6798
6799 let mut element = h_flex()
6800 .items_start()
6801 .child(
6802 h_flex()
6803 .bg(cx.theme().colors().editor_background)
6804 .border(BORDER_WIDTH)
6805 .shadow_sm()
6806 .border_color(cx.theme().colors().border)
6807 .rounded_l_lg()
6808 .when(line_count > 1, |el| el.rounded_br_lg())
6809 .pr_1()
6810 .child(styled_text),
6811 )
6812 .child(
6813 h_flex()
6814 .h(line_height + BORDER_WIDTH * px(2.))
6815 .px_1p5()
6816 .gap_1()
6817 // Workaround: For some reason, there's a gap if we don't do this
6818 .ml(-BORDER_WIDTH)
6819 .shadow(smallvec![gpui::BoxShadow {
6820 color: gpui::black().opacity(0.05),
6821 offset: point(px(1.), px(1.)),
6822 blur_radius: px(2.),
6823 spread_radius: px(0.),
6824 }])
6825 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
6826 .border(BORDER_WIDTH)
6827 .border_color(cx.theme().colors().border)
6828 .rounded_r_lg()
6829 .id("edit_prediction_diff_popover_keybind")
6830 .when(!has_keybind, |el| {
6831 let status_colors = cx.theme().status();
6832
6833 el.bg(status_colors.error_background)
6834 .border_color(status_colors.error.opacity(0.6))
6835 .child(Icon::new(IconName::Info).color(Color::Error))
6836 .cursor_default()
6837 .hoverable_tooltip(move |_window, cx| {
6838 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
6839 })
6840 })
6841 .children(keybind),
6842 )
6843 .into_any();
6844
6845 let longest_row =
6846 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
6847 let longest_line_width = if visible_row_range.contains(&longest_row) {
6848 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
6849 } else {
6850 layout_line(
6851 longest_row,
6852 editor_snapshot,
6853 style,
6854 editor_width,
6855 |_| false,
6856 window,
6857 cx,
6858 )
6859 .width
6860 };
6861
6862 let viewport_bounds =
6863 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
6864 right: -EditorElement::SCROLLBAR_WIDTH,
6865 ..Default::default()
6866 });
6867
6868 let x_after_longest =
6869 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
6870 - scroll_pixel_position.x;
6871
6872 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6873
6874 // Fully visible if it can be displayed within the window (allow overlapping other
6875 // panes). However, this is only allowed if the popover starts within text_bounds.
6876 let can_position_to_the_right = x_after_longest < text_bounds.right()
6877 && x_after_longest + element_bounds.width < viewport_bounds.right();
6878
6879 let mut origin = if can_position_to_the_right {
6880 point(
6881 x_after_longest,
6882 text_bounds.origin.y + edit_start.row().as_f32() * line_height
6883 - scroll_pixel_position.y,
6884 )
6885 } else {
6886 let cursor_row = newest_selection_head.map(|head| head.row());
6887 let above_edit = edit_start
6888 .row()
6889 .0
6890 .checked_sub(line_count as u32)
6891 .map(DisplayRow);
6892 let below_edit = Some(edit_end.row() + 1);
6893 let above_cursor =
6894 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
6895 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
6896
6897 // Place the edit popover adjacent to the edit if there is a location
6898 // available that is onscreen and does not obscure the cursor. Otherwise,
6899 // place it adjacent to the cursor.
6900 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
6901 .into_iter()
6902 .flatten()
6903 .find(|&start_row| {
6904 let end_row = start_row + line_count as u32;
6905 visible_row_range.contains(&start_row)
6906 && visible_row_range.contains(&end_row)
6907 && cursor_row.map_or(true, |cursor_row| {
6908 !((start_row..end_row).contains(&cursor_row))
6909 })
6910 })?;
6911
6912 content_origin
6913 + point(
6914 -scroll_pixel_position.x,
6915 row_target.as_f32() * line_height - scroll_pixel_position.y,
6916 )
6917 };
6918
6919 origin.x -= BORDER_WIDTH;
6920
6921 window.defer_draw(element, origin, 1);
6922
6923 // Do not return an element, since it will already be drawn due to defer_draw.
6924 None
6925 }
6926
6927 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
6928 px(30.)
6929 }
6930
6931 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
6932 if self.read_only(cx) {
6933 cx.theme().players().read_only()
6934 } else {
6935 self.style.as_ref().unwrap().local_player
6936 }
6937 }
6938
6939 fn render_edit_prediction_accept_keybind(
6940 &self,
6941 window: &mut Window,
6942 cx: &App,
6943 ) -> Option<AnyElement> {
6944 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
6945 let accept_keystroke = accept_binding.keystroke()?;
6946
6947 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
6948
6949 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
6950 Color::Accent
6951 } else {
6952 Color::Muted
6953 };
6954
6955 h_flex()
6956 .px_0p5()
6957 .when(is_platform_style_mac, |parent| parent.gap_0p5())
6958 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
6959 .text_size(TextSize::XSmall.rems(cx))
6960 .child(h_flex().children(ui::render_modifiers(
6961 &accept_keystroke.modifiers,
6962 PlatformStyle::platform(),
6963 Some(modifiers_color),
6964 Some(IconSize::XSmall.rems().into()),
6965 true,
6966 )))
6967 .when(is_platform_style_mac, |parent| {
6968 parent.child(accept_keystroke.key.clone())
6969 })
6970 .when(!is_platform_style_mac, |parent| {
6971 parent.child(
6972 Key::new(
6973 util::capitalize(&accept_keystroke.key),
6974 Some(Color::Default),
6975 )
6976 .size(Some(IconSize::XSmall.rems().into())),
6977 )
6978 })
6979 .into_any()
6980 .into()
6981 }
6982
6983 fn render_edit_prediction_line_popover(
6984 &self,
6985 label: impl Into<SharedString>,
6986 icon: Option<IconName>,
6987 window: &mut Window,
6988 cx: &App,
6989 ) -> Option<Stateful<Div>> {
6990 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
6991
6992 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
6993 let has_keybind = keybind.is_some();
6994
6995 let result = h_flex()
6996 .id("ep-line-popover")
6997 .py_0p5()
6998 .pl_1()
6999 .pr(padding_right)
7000 .gap_1()
7001 .rounded_md()
7002 .border_1()
7003 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7004 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7005 .shadow_sm()
7006 .when(!has_keybind, |el| {
7007 let status_colors = cx.theme().status();
7008
7009 el.bg(status_colors.error_background)
7010 .border_color(status_colors.error.opacity(0.6))
7011 .pl_2()
7012 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7013 .cursor_default()
7014 .hoverable_tooltip(move |_window, cx| {
7015 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7016 })
7017 })
7018 .children(keybind)
7019 .child(
7020 Label::new(label)
7021 .size(LabelSize::Small)
7022 .when(!has_keybind, |el| {
7023 el.color(cx.theme().status().error.into()).strikethrough()
7024 }),
7025 )
7026 .when(!has_keybind, |el| {
7027 el.child(
7028 h_flex().ml_1().child(
7029 Icon::new(IconName::Info)
7030 .size(IconSize::Small)
7031 .color(cx.theme().status().error.into()),
7032 ),
7033 )
7034 })
7035 .when_some(icon, |element, icon| {
7036 element.child(
7037 div()
7038 .mt(px(1.5))
7039 .child(Icon::new(icon).size(IconSize::Small)),
7040 )
7041 });
7042
7043 Some(result)
7044 }
7045
7046 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
7047 let accent_color = cx.theme().colors().text_accent;
7048 let editor_bg_color = cx.theme().colors().editor_background;
7049 editor_bg_color.blend(accent_color.opacity(0.1))
7050 }
7051
7052 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
7053 let accent_color = cx.theme().colors().text_accent;
7054 let editor_bg_color = cx.theme().colors().editor_background;
7055 editor_bg_color.blend(accent_color.opacity(0.6))
7056 }
7057
7058 fn render_edit_prediction_cursor_popover(
7059 &self,
7060 min_width: Pixels,
7061 max_width: Pixels,
7062 cursor_point: Point,
7063 style: &EditorStyle,
7064 accept_keystroke: Option<&gpui::Keystroke>,
7065 _window: &Window,
7066 cx: &mut Context<Editor>,
7067 ) -> Option<AnyElement> {
7068 let provider = self.edit_prediction_provider.as_ref()?;
7069
7070 if provider.provider.needs_terms_acceptance(cx) {
7071 return Some(
7072 h_flex()
7073 .min_w(min_width)
7074 .flex_1()
7075 .px_2()
7076 .py_1()
7077 .gap_3()
7078 .elevation_2(cx)
7079 .hover(|style| style.bg(cx.theme().colors().element_hover))
7080 .id("accept-terms")
7081 .cursor_pointer()
7082 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
7083 .on_click(cx.listener(|this, _event, window, cx| {
7084 cx.stop_propagation();
7085 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
7086 window.dispatch_action(
7087 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
7088 cx,
7089 );
7090 }))
7091 .child(
7092 h_flex()
7093 .flex_1()
7094 .gap_2()
7095 .child(Icon::new(IconName::ZedPredict))
7096 .child(Label::new("Accept Terms of Service"))
7097 .child(div().w_full())
7098 .child(
7099 Icon::new(IconName::ArrowUpRight)
7100 .color(Color::Muted)
7101 .size(IconSize::Small),
7102 )
7103 .into_any_element(),
7104 )
7105 .into_any(),
7106 );
7107 }
7108
7109 let is_refreshing = provider.provider.is_refreshing(cx);
7110
7111 fn pending_completion_container() -> Div {
7112 h_flex()
7113 .h_full()
7114 .flex_1()
7115 .gap_2()
7116 .child(Icon::new(IconName::ZedPredict))
7117 }
7118
7119 let completion = match &self.active_inline_completion {
7120 Some(prediction) => {
7121 if !self.has_visible_completions_menu() {
7122 const RADIUS: Pixels = px(6.);
7123 const BORDER_WIDTH: Pixels = px(1.);
7124
7125 return Some(
7126 h_flex()
7127 .elevation_2(cx)
7128 .border(BORDER_WIDTH)
7129 .border_color(cx.theme().colors().border)
7130 .when(accept_keystroke.is_none(), |el| {
7131 el.border_color(cx.theme().status().error)
7132 })
7133 .rounded(RADIUS)
7134 .rounded_tl(px(0.))
7135 .overflow_hidden()
7136 .child(div().px_1p5().child(match &prediction.completion {
7137 InlineCompletion::Move { target, snapshot } => {
7138 use text::ToPoint as _;
7139 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
7140 {
7141 Icon::new(IconName::ZedPredictDown)
7142 } else {
7143 Icon::new(IconName::ZedPredictUp)
7144 }
7145 }
7146 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
7147 }))
7148 .child(
7149 h_flex()
7150 .gap_1()
7151 .py_1()
7152 .px_2()
7153 .rounded_r(RADIUS - BORDER_WIDTH)
7154 .border_l_1()
7155 .border_color(cx.theme().colors().border)
7156 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7157 .when(self.edit_prediction_preview.released_too_fast(), |el| {
7158 el.child(
7159 Label::new("Hold")
7160 .size(LabelSize::Small)
7161 .when(accept_keystroke.is_none(), |el| {
7162 el.strikethrough()
7163 })
7164 .line_height_style(LineHeightStyle::UiLabel),
7165 )
7166 })
7167 .id("edit_prediction_cursor_popover_keybind")
7168 .when(accept_keystroke.is_none(), |el| {
7169 let status_colors = cx.theme().status();
7170
7171 el.bg(status_colors.error_background)
7172 .border_color(status_colors.error.opacity(0.6))
7173 .child(Icon::new(IconName::Info).color(Color::Error))
7174 .cursor_default()
7175 .hoverable_tooltip(move |_window, cx| {
7176 cx.new(|_| MissingEditPredictionKeybindingTooltip)
7177 .into()
7178 })
7179 })
7180 .when_some(
7181 accept_keystroke.as_ref(),
7182 |el, accept_keystroke| {
7183 el.child(h_flex().children(ui::render_modifiers(
7184 &accept_keystroke.modifiers,
7185 PlatformStyle::platform(),
7186 Some(Color::Default),
7187 Some(IconSize::XSmall.rems().into()),
7188 false,
7189 )))
7190 },
7191 ),
7192 )
7193 .into_any(),
7194 );
7195 }
7196
7197 self.render_edit_prediction_cursor_popover_preview(
7198 prediction,
7199 cursor_point,
7200 style,
7201 cx,
7202 )?
7203 }
7204
7205 None if is_refreshing => match &self.stale_inline_completion_in_menu {
7206 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
7207 stale_completion,
7208 cursor_point,
7209 style,
7210 cx,
7211 )?,
7212
7213 None => {
7214 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
7215 }
7216 },
7217
7218 None => pending_completion_container().child(Label::new("No Prediction")),
7219 };
7220
7221 let completion = if is_refreshing {
7222 completion
7223 .with_animation(
7224 "loading-completion",
7225 Animation::new(Duration::from_secs(2))
7226 .repeat()
7227 .with_easing(pulsating_between(0.4, 0.8)),
7228 |label, delta| label.opacity(delta),
7229 )
7230 .into_any_element()
7231 } else {
7232 completion.into_any_element()
7233 };
7234
7235 let has_completion = self.active_inline_completion.is_some();
7236
7237 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7238 Some(
7239 h_flex()
7240 .min_w(min_width)
7241 .max_w(max_width)
7242 .flex_1()
7243 .elevation_2(cx)
7244 .border_color(cx.theme().colors().border)
7245 .child(
7246 div()
7247 .flex_1()
7248 .py_1()
7249 .px_2()
7250 .overflow_hidden()
7251 .child(completion),
7252 )
7253 .when_some(accept_keystroke, |el, accept_keystroke| {
7254 if !accept_keystroke.modifiers.modified() {
7255 return el;
7256 }
7257
7258 el.child(
7259 h_flex()
7260 .h_full()
7261 .border_l_1()
7262 .rounded_r_lg()
7263 .border_color(cx.theme().colors().border)
7264 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7265 .gap_1()
7266 .py_1()
7267 .px_2()
7268 .child(
7269 h_flex()
7270 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7271 .when(is_platform_style_mac, |parent| parent.gap_1())
7272 .child(h_flex().children(ui::render_modifiers(
7273 &accept_keystroke.modifiers,
7274 PlatformStyle::platform(),
7275 Some(if !has_completion {
7276 Color::Muted
7277 } else {
7278 Color::Default
7279 }),
7280 None,
7281 false,
7282 ))),
7283 )
7284 .child(Label::new("Preview").into_any_element())
7285 .opacity(if has_completion { 1.0 } else { 0.4 }),
7286 )
7287 })
7288 .into_any(),
7289 )
7290 }
7291
7292 fn render_edit_prediction_cursor_popover_preview(
7293 &self,
7294 completion: &InlineCompletionState,
7295 cursor_point: Point,
7296 style: &EditorStyle,
7297 cx: &mut Context<Editor>,
7298 ) -> Option<Div> {
7299 use text::ToPoint as _;
7300
7301 fn render_relative_row_jump(
7302 prefix: impl Into<String>,
7303 current_row: u32,
7304 target_row: u32,
7305 ) -> Div {
7306 let (row_diff, arrow) = if target_row < current_row {
7307 (current_row - target_row, IconName::ArrowUp)
7308 } else {
7309 (target_row - current_row, IconName::ArrowDown)
7310 };
7311
7312 h_flex()
7313 .child(
7314 Label::new(format!("{}{}", prefix.into(), row_diff))
7315 .color(Color::Muted)
7316 .size(LabelSize::Small),
7317 )
7318 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
7319 }
7320
7321 match &completion.completion {
7322 InlineCompletion::Move {
7323 target, snapshot, ..
7324 } => Some(
7325 h_flex()
7326 .px_2()
7327 .gap_2()
7328 .flex_1()
7329 .child(
7330 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
7331 Icon::new(IconName::ZedPredictDown)
7332 } else {
7333 Icon::new(IconName::ZedPredictUp)
7334 },
7335 )
7336 .child(Label::new("Jump to Edit")),
7337 ),
7338
7339 InlineCompletion::Edit {
7340 edits,
7341 edit_preview,
7342 snapshot,
7343 display_mode: _,
7344 } => {
7345 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
7346
7347 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
7348 &snapshot,
7349 &edits,
7350 edit_preview.as_ref()?,
7351 true,
7352 cx,
7353 )
7354 .first_line_preview();
7355
7356 let styled_text = gpui::StyledText::new(highlighted_edits.text)
7357 .with_default_highlights(&style.text, highlighted_edits.highlights);
7358
7359 let preview = h_flex()
7360 .gap_1()
7361 .min_w_16()
7362 .child(styled_text)
7363 .when(has_more_lines, |parent| parent.child("…"));
7364
7365 let left = if first_edit_row != cursor_point.row {
7366 render_relative_row_jump("", cursor_point.row, first_edit_row)
7367 .into_any_element()
7368 } else {
7369 Icon::new(IconName::ZedPredict).into_any_element()
7370 };
7371
7372 Some(
7373 h_flex()
7374 .h_full()
7375 .flex_1()
7376 .gap_2()
7377 .pr_1()
7378 .overflow_x_hidden()
7379 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7380 .child(left)
7381 .child(preview),
7382 )
7383 }
7384 }
7385 }
7386
7387 fn render_context_menu(
7388 &self,
7389 style: &EditorStyle,
7390 max_height_in_lines: u32,
7391 y_flipped: bool,
7392 window: &mut Window,
7393 cx: &mut Context<Editor>,
7394 ) -> Option<AnyElement> {
7395 let menu = self.context_menu.borrow();
7396 let menu = menu.as_ref()?;
7397 if !menu.visible() {
7398 return None;
7399 };
7400 Some(menu.render(style, max_height_in_lines, y_flipped, window, cx))
7401 }
7402
7403 fn render_context_menu_aside(
7404 &mut self,
7405 max_size: Size<Pixels>,
7406 window: &mut Window,
7407 cx: &mut Context<Editor>,
7408 ) -> Option<AnyElement> {
7409 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
7410 if menu.visible() {
7411 menu.render_aside(self, max_size, window, cx)
7412 } else {
7413 None
7414 }
7415 })
7416 }
7417
7418 fn hide_context_menu(
7419 &mut self,
7420 window: &mut Window,
7421 cx: &mut Context<Self>,
7422 ) -> Option<CodeContextMenu> {
7423 cx.notify();
7424 self.completion_tasks.clear();
7425 let context_menu = self.context_menu.borrow_mut().take();
7426 self.stale_inline_completion_in_menu.take();
7427 self.update_visible_inline_completion(window, cx);
7428 context_menu
7429 }
7430
7431 fn show_snippet_choices(
7432 &mut self,
7433 choices: &Vec<String>,
7434 selection: Range<Anchor>,
7435 cx: &mut Context<Self>,
7436 ) {
7437 if selection.start.buffer_id.is_none() {
7438 return;
7439 }
7440 let buffer_id = selection.start.buffer_id.unwrap();
7441 let buffer = self.buffer().read(cx).buffer(buffer_id);
7442 let id = post_inc(&mut self.next_completion_id);
7443
7444 if let Some(buffer) = buffer {
7445 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
7446 CompletionsMenu::new_snippet_choices(id, true, choices, selection, buffer),
7447 ));
7448 }
7449 }
7450
7451 pub fn insert_snippet(
7452 &mut self,
7453 insertion_ranges: &[Range<usize>],
7454 snippet: Snippet,
7455 window: &mut Window,
7456 cx: &mut Context<Self>,
7457 ) -> Result<()> {
7458 struct Tabstop<T> {
7459 is_end_tabstop: bool,
7460 ranges: Vec<Range<T>>,
7461 choices: Option<Vec<String>>,
7462 }
7463
7464 let tabstops = self.buffer.update(cx, |buffer, cx| {
7465 let snippet_text: Arc<str> = snippet.text.clone().into();
7466 buffer.edit(
7467 insertion_ranges
7468 .iter()
7469 .cloned()
7470 .map(|range| (range, snippet_text.clone())),
7471 Some(AutoindentMode::EachLine),
7472 cx,
7473 );
7474
7475 let snapshot = &*buffer.read(cx);
7476 let snippet = &snippet;
7477 snippet
7478 .tabstops
7479 .iter()
7480 .map(|tabstop| {
7481 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
7482 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
7483 });
7484 let mut tabstop_ranges = tabstop
7485 .ranges
7486 .iter()
7487 .flat_map(|tabstop_range| {
7488 let mut delta = 0_isize;
7489 insertion_ranges.iter().map(move |insertion_range| {
7490 let insertion_start = insertion_range.start as isize + delta;
7491 delta +=
7492 snippet.text.len() as isize - insertion_range.len() as isize;
7493
7494 let start = ((insertion_start + tabstop_range.start) as usize)
7495 .min(snapshot.len());
7496 let end = ((insertion_start + tabstop_range.end) as usize)
7497 .min(snapshot.len());
7498 snapshot.anchor_before(start)..snapshot.anchor_after(end)
7499 })
7500 })
7501 .collect::<Vec<_>>();
7502 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
7503
7504 Tabstop {
7505 is_end_tabstop,
7506 ranges: tabstop_ranges,
7507 choices: tabstop.choices.clone(),
7508 }
7509 })
7510 .collect::<Vec<_>>()
7511 });
7512 if let Some(tabstop) = tabstops.first() {
7513 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7514 s.select_ranges(tabstop.ranges.iter().cloned());
7515 });
7516
7517 if let Some(choices) = &tabstop.choices {
7518 if let Some(selection) = tabstop.ranges.first() {
7519 self.show_snippet_choices(choices, selection.clone(), cx)
7520 }
7521 }
7522
7523 // If we're already at the last tabstop and it's at the end of the snippet,
7524 // we're done, we don't need to keep the state around.
7525 if !tabstop.is_end_tabstop {
7526 let choices = tabstops
7527 .iter()
7528 .map(|tabstop| tabstop.choices.clone())
7529 .collect();
7530
7531 let ranges = tabstops
7532 .into_iter()
7533 .map(|tabstop| tabstop.ranges)
7534 .collect::<Vec<_>>();
7535
7536 self.snippet_stack.push(SnippetState {
7537 active_index: 0,
7538 ranges,
7539 choices,
7540 });
7541 }
7542
7543 // Check whether the just-entered snippet ends with an auto-closable bracket.
7544 if self.autoclose_regions.is_empty() {
7545 let snapshot = self.buffer.read(cx).snapshot(cx);
7546 for selection in &mut self.selections.all::<Point>(cx) {
7547 let selection_head = selection.head();
7548 let Some(scope) = snapshot.language_scope_at(selection_head) else {
7549 continue;
7550 };
7551
7552 let mut bracket_pair = None;
7553 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
7554 let prev_chars = snapshot
7555 .reversed_chars_at(selection_head)
7556 .collect::<String>();
7557 for (pair, enabled) in scope.brackets() {
7558 if enabled
7559 && pair.close
7560 && prev_chars.starts_with(pair.start.as_str())
7561 && next_chars.starts_with(pair.end.as_str())
7562 {
7563 bracket_pair = Some(pair.clone());
7564 break;
7565 }
7566 }
7567 if let Some(pair) = bracket_pair {
7568 let start = snapshot.anchor_after(selection_head);
7569 let end = snapshot.anchor_after(selection_head);
7570 self.autoclose_regions.push(AutocloseRegion {
7571 selection_id: selection.id,
7572 range: start..end,
7573 pair,
7574 });
7575 }
7576 }
7577 }
7578 }
7579 Ok(())
7580 }
7581
7582 pub fn move_to_next_snippet_tabstop(
7583 &mut self,
7584 window: &mut Window,
7585 cx: &mut Context<Self>,
7586 ) -> bool {
7587 self.move_to_snippet_tabstop(Bias::Right, window, cx)
7588 }
7589
7590 pub fn move_to_prev_snippet_tabstop(
7591 &mut self,
7592 window: &mut Window,
7593 cx: &mut Context<Self>,
7594 ) -> bool {
7595 self.move_to_snippet_tabstop(Bias::Left, window, cx)
7596 }
7597
7598 pub fn move_to_snippet_tabstop(
7599 &mut self,
7600 bias: Bias,
7601 window: &mut Window,
7602 cx: &mut Context<Self>,
7603 ) -> bool {
7604 if let Some(mut snippet) = self.snippet_stack.pop() {
7605 match bias {
7606 Bias::Left => {
7607 if snippet.active_index > 0 {
7608 snippet.active_index -= 1;
7609 } else {
7610 self.snippet_stack.push(snippet);
7611 return false;
7612 }
7613 }
7614 Bias::Right => {
7615 if snippet.active_index + 1 < snippet.ranges.len() {
7616 snippet.active_index += 1;
7617 } else {
7618 self.snippet_stack.push(snippet);
7619 return false;
7620 }
7621 }
7622 }
7623 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
7624 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7625 s.select_anchor_ranges(current_ranges.iter().cloned())
7626 });
7627
7628 if let Some(choices) = &snippet.choices[snippet.active_index] {
7629 if let Some(selection) = current_ranges.first() {
7630 self.show_snippet_choices(&choices, selection.clone(), cx);
7631 }
7632 }
7633
7634 // If snippet state is not at the last tabstop, push it back on the stack
7635 if snippet.active_index + 1 < snippet.ranges.len() {
7636 self.snippet_stack.push(snippet);
7637 }
7638 return true;
7639 }
7640 }
7641
7642 false
7643 }
7644
7645 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7646 self.transact(window, cx, |this, window, cx| {
7647 this.select_all(&SelectAll, window, cx);
7648 this.insert("", window, cx);
7649 });
7650 }
7651
7652 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
7653 self.mouse_cursor_hidden = self.hide_mouse_while_typing;
7654 self.transact(window, cx, |this, window, cx| {
7655 this.select_autoclose_pair(window, cx);
7656 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
7657 if !this.linked_edit_ranges.is_empty() {
7658 let selections = this.selections.all::<MultiBufferPoint>(cx);
7659 let snapshot = this.buffer.read(cx).snapshot(cx);
7660
7661 for selection in selections.iter() {
7662 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
7663 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
7664 if selection_start.buffer_id != selection_end.buffer_id {
7665 continue;
7666 }
7667 if let Some(ranges) =
7668 this.linked_editing_ranges_for(selection_start..selection_end, cx)
7669 {
7670 for (buffer, entries) in ranges {
7671 linked_ranges.entry(buffer).or_default().extend(entries);
7672 }
7673 }
7674 }
7675 }
7676
7677 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
7678 if !this.selections.line_mode {
7679 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
7680 for selection in &mut selections {
7681 if selection.is_empty() {
7682 let old_head = selection.head();
7683 let mut new_head =
7684 movement::left(&display_map, old_head.to_display_point(&display_map))
7685 .to_point(&display_map);
7686 if let Some((buffer, line_buffer_range)) = display_map
7687 .buffer_snapshot
7688 .buffer_line_for_row(MultiBufferRow(old_head.row))
7689 {
7690 let indent_size =
7691 buffer.indent_size_for_line(line_buffer_range.start.row);
7692 let indent_len = match indent_size.kind {
7693 IndentKind::Space => {
7694 buffer.settings_at(line_buffer_range.start, cx).tab_size
7695 }
7696 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
7697 };
7698 if old_head.column <= indent_size.len && old_head.column > 0 {
7699 let indent_len = indent_len.get();
7700 new_head = cmp::min(
7701 new_head,
7702 MultiBufferPoint::new(
7703 old_head.row,
7704 ((old_head.column - 1) / indent_len) * indent_len,
7705 ),
7706 );
7707 }
7708 }
7709
7710 selection.set_head(new_head, SelectionGoal::None);
7711 }
7712 }
7713 }
7714
7715 this.signature_help_state.set_backspace_pressed(true);
7716 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7717 s.select(selections)
7718 });
7719 this.insert("", window, cx);
7720 let empty_str: Arc<str> = Arc::from("");
7721 for (buffer, edits) in linked_ranges {
7722 let snapshot = buffer.read(cx).snapshot();
7723 use text::ToPoint as TP;
7724
7725 let edits = edits
7726 .into_iter()
7727 .map(|range| {
7728 let end_point = TP::to_point(&range.end, &snapshot);
7729 let mut start_point = TP::to_point(&range.start, &snapshot);
7730
7731 if end_point == start_point {
7732 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
7733 .saturating_sub(1);
7734 start_point =
7735 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
7736 };
7737
7738 (start_point..end_point, empty_str.clone())
7739 })
7740 .sorted_by_key(|(range, _)| range.start)
7741 .collect::<Vec<_>>();
7742 buffer.update(cx, |this, cx| {
7743 this.edit(edits, None, cx);
7744 })
7745 }
7746 this.refresh_inline_completion(true, false, window, cx);
7747 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
7748 });
7749 }
7750
7751 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
7752 self.mouse_cursor_hidden = self.hide_mouse_while_typing;
7753 self.transact(window, cx, |this, window, cx| {
7754 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7755 let line_mode = s.line_mode;
7756 s.move_with(|map, selection| {
7757 if selection.is_empty() && !line_mode {
7758 let cursor = movement::right(map, selection.head());
7759 selection.end = cursor;
7760 selection.reversed = true;
7761 selection.goal = SelectionGoal::None;
7762 }
7763 })
7764 });
7765 this.insert("", window, cx);
7766 this.refresh_inline_completion(true, false, window, cx);
7767 });
7768 }
7769
7770 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
7771 if self.move_to_prev_snippet_tabstop(window, cx) {
7772 return;
7773 }
7774 self.mouse_cursor_hidden = self.hide_mouse_while_typing;
7775 self.outdent(&Outdent, window, cx);
7776 }
7777
7778 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
7779 if self.move_to_next_snippet_tabstop(window, cx) || self.read_only(cx) {
7780 return;
7781 }
7782 self.mouse_cursor_hidden = self.hide_mouse_while_typing;
7783 let mut selections = self.selections.all_adjusted(cx);
7784 let buffer = self.buffer.read(cx);
7785 let snapshot = buffer.snapshot(cx);
7786 let rows_iter = selections.iter().map(|s| s.head().row);
7787 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
7788
7789 let mut edits = Vec::new();
7790 let mut prev_edited_row = 0;
7791 let mut row_delta = 0;
7792 for selection in &mut selections {
7793 if selection.start.row != prev_edited_row {
7794 row_delta = 0;
7795 }
7796 prev_edited_row = selection.end.row;
7797
7798 // If the selection is non-empty, then increase the indentation of the selected lines.
7799 if !selection.is_empty() {
7800 row_delta =
7801 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
7802 continue;
7803 }
7804
7805 // If the selection is empty and the cursor is in the leading whitespace before the
7806 // suggested indentation, then auto-indent the line.
7807 let cursor = selection.head();
7808 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
7809 if let Some(suggested_indent) =
7810 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
7811 {
7812 if cursor.column < suggested_indent.len
7813 && cursor.column <= current_indent.len
7814 && current_indent.len <= suggested_indent.len
7815 {
7816 selection.start = Point::new(cursor.row, suggested_indent.len);
7817 selection.end = selection.start;
7818 if row_delta == 0 {
7819 edits.extend(Buffer::edit_for_indent_size_adjustment(
7820 cursor.row,
7821 current_indent,
7822 suggested_indent,
7823 ));
7824 row_delta = suggested_indent.len - current_indent.len;
7825 }
7826 continue;
7827 }
7828 }
7829
7830 // Otherwise, insert a hard or soft tab.
7831 let settings = buffer.language_settings_at(cursor, cx);
7832 let tab_size = if settings.hard_tabs {
7833 IndentSize::tab()
7834 } else {
7835 let tab_size = settings.tab_size.get();
7836 let char_column = snapshot
7837 .text_for_range(Point::new(cursor.row, 0)..cursor)
7838 .flat_map(str::chars)
7839 .count()
7840 + row_delta as usize;
7841 let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size);
7842 IndentSize::spaces(chars_to_next_tab_stop)
7843 };
7844 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
7845 selection.end = selection.start;
7846 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
7847 row_delta += tab_size.len;
7848 }
7849
7850 self.transact(window, cx, |this, window, cx| {
7851 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
7852 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7853 s.select(selections)
7854 });
7855 this.refresh_inline_completion(true, false, window, cx);
7856 });
7857 }
7858
7859 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
7860 if self.read_only(cx) {
7861 return;
7862 }
7863 let mut selections = self.selections.all::<Point>(cx);
7864 let mut prev_edited_row = 0;
7865 let mut row_delta = 0;
7866 let mut edits = Vec::new();
7867 let buffer = self.buffer.read(cx);
7868 let snapshot = buffer.snapshot(cx);
7869 for selection in &mut selections {
7870 if selection.start.row != prev_edited_row {
7871 row_delta = 0;
7872 }
7873 prev_edited_row = selection.end.row;
7874
7875 row_delta =
7876 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
7877 }
7878
7879 self.transact(window, cx, |this, window, cx| {
7880 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
7881 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7882 s.select(selections)
7883 });
7884 });
7885 }
7886
7887 fn indent_selection(
7888 buffer: &MultiBuffer,
7889 snapshot: &MultiBufferSnapshot,
7890 selection: &mut Selection<Point>,
7891 edits: &mut Vec<(Range<Point>, String)>,
7892 delta_for_start_row: u32,
7893 cx: &App,
7894 ) -> u32 {
7895 let settings = buffer.language_settings_at(selection.start, cx);
7896 let tab_size = settings.tab_size.get();
7897 let indent_kind = if settings.hard_tabs {
7898 IndentKind::Tab
7899 } else {
7900 IndentKind::Space
7901 };
7902 let mut start_row = selection.start.row;
7903 let mut end_row = selection.end.row + 1;
7904
7905 // If a selection ends at the beginning of a line, don't indent
7906 // that last line.
7907 if selection.end.column == 0 && selection.end.row > selection.start.row {
7908 end_row -= 1;
7909 }
7910
7911 // Avoid re-indenting a row that has already been indented by a
7912 // previous selection, but still update this selection's column
7913 // to reflect that indentation.
7914 if delta_for_start_row > 0 {
7915 start_row += 1;
7916 selection.start.column += delta_for_start_row;
7917 if selection.end.row == selection.start.row {
7918 selection.end.column += delta_for_start_row;
7919 }
7920 }
7921
7922 let mut delta_for_end_row = 0;
7923 let has_multiple_rows = start_row + 1 != end_row;
7924 for row in start_row..end_row {
7925 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
7926 let indent_delta = match (current_indent.kind, indent_kind) {
7927 (IndentKind::Space, IndentKind::Space) => {
7928 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
7929 IndentSize::spaces(columns_to_next_tab_stop)
7930 }
7931 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
7932 (_, IndentKind::Tab) => IndentSize::tab(),
7933 };
7934
7935 let start = if has_multiple_rows || current_indent.len < selection.start.column {
7936 0
7937 } else {
7938 selection.start.column
7939 };
7940 let row_start = Point::new(row, start);
7941 edits.push((
7942 row_start..row_start,
7943 indent_delta.chars().collect::<String>(),
7944 ));
7945
7946 // Update this selection's endpoints to reflect the indentation.
7947 if row == selection.start.row {
7948 selection.start.column += indent_delta.len;
7949 }
7950 if row == selection.end.row {
7951 selection.end.column += indent_delta.len;
7952 delta_for_end_row = indent_delta.len;
7953 }
7954 }
7955
7956 if selection.start.row == selection.end.row {
7957 delta_for_start_row + delta_for_end_row
7958 } else {
7959 delta_for_end_row
7960 }
7961 }
7962
7963 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
7964 if self.read_only(cx) {
7965 return;
7966 }
7967 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7968 let selections = self.selections.all::<Point>(cx);
7969 let mut deletion_ranges = Vec::new();
7970 let mut last_outdent = None;
7971 {
7972 let buffer = self.buffer.read(cx);
7973 let snapshot = buffer.snapshot(cx);
7974 for selection in &selections {
7975 let settings = buffer.language_settings_at(selection.start, cx);
7976 let tab_size = settings.tab_size.get();
7977 let mut rows = selection.spanned_rows(false, &display_map);
7978
7979 // Avoid re-outdenting a row that has already been outdented by a
7980 // previous selection.
7981 if let Some(last_row) = last_outdent {
7982 if last_row == rows.start {
7983 rows.start = rows.start.next_row();
7984 }
7985 }
7986 let has_multiple_rows = rows.len() > 1;
7987 for row in rows.iter_rows() {
7988 let indent_size = snapshot.indent_size_for_line(row);
7989 if indent_size.len > 0 {
7990 let deletion_len = match indent_size.kind {
7991 IndentKind::Space => {
7992 let columns_to_prev_tab_stop = indent_size.len % tab_size;
7993 if columns_to_prev_tab_stop == 0 {
7994 tab_size
7995 } else {
7996 columns_to_prev_tab_stop
7997 }
7998 }
7999 IndentKind::Tab => 1,
8000 };
8001 let start = if has_multiple_rows
8002 || deletion_len > selection.start.column
8003 || indent_size.len < selection.start.column
8004 {
8005 0
8006 } else {
8007 selection.start.column - deletion_len
8008 };
8009 deletion_ranges.push(
8010 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
8011 );
8012 last_outdent = Some(row);
8013 }
8014 }
8015 }
8016 }
8017
8018 self.transact(window, cx, |this, window, cx| {
8019 this.buffer.update(cx, |buffer, cx| {
8020 let empty_str: Arc<str> = Arc::default();
8021 buffer.edit(
8022 deletion_ranges
8023 .into_iter()
8024 .map(|range| (range, empty_str.clone())),
8025 None,
8026 cx,
8027 );
8028 });
8029 let selections = this.selections.all::<usize>(cx);
8030 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8031 s.select(selections)
8032 });
8033 });
8034 }
8035
8036 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
8037 if self.read_only(cx) {
8038 return;
8039 }
8040 let selections = self
8041 .selections
8042 .all::<usize>(cx)
8043 .into_iter()
8044 .map(|s| s.range());
8045
8046 self.transact(window, cx, |this, window, cx| {
8047 this.buffer.update(cx, |buffer, cx| {
8048 buffer.autoindent_ranges(selections, cx);
8049 });
8050 let selections = this.selections.all::<usize>(cx);
8051 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8052 s.select(selections)
8053 });
8054 });
8055 }
8056
8057 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
8058 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8059 let selections = self.selections.all::<Point>(cx);
8060
8061 let mut new_cursors = Vec::new();
8062 let mut edit_ranges = Vec::new();
8063 let mut selections = selections.iter().peekable();
8064 while let Some(selection) = selections.next() {
8065 let mut rows = selection.spanned_rows(false, &display_map);
8066 let goal_display_column = selection.head().to_display_point(&display_map).column();
8067
8068 // Accumulate contiguous regions of rows that we want to delete.
8069 while let Some(next_selection) = selections.peek() {
8070 let next_rows = next_selection.spanned_rows(false, &display_map);
8071 if next_rows.start <= rows.end {
8072 rows.end = next_rows.end;
8073 selections.next().unwrap();
8074 } else {
8075 break;
8076 }
8077 }
8078
8079 let buffer = &display_map.buffer_snapshot;
8080 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
8081 let edit_end;
8082 let cursor_buffer_row;
8083 if buffer.max_point().row >= rows.end.0 {
8084 // If there's a line after the range, delete the \n from the end of the row range
8085 // and position the cursor on the next line.
8086 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
8087 cursor_buffer_row = rows.end;
8088 } else {
8089 // If there isn't a line after the range, delete the \n from the line before the
8090 // start of the row range and position the cursor there.
8091 edit_start = edit_start.saturating_sub(1);
8092 edit_end = buffer.len();
8093 cursor_buffer_row = rows.start.previous_row();
8094 }
8095
8096 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
8097 *cursor.column_mut() =
8098 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
8099
8100 new_cursors.push((
8101 selection.id,
8102 buffer.anchor_after(cursor.to_point(&display_map)),
8103 ));
8104 edit_ranges.push(edit_start..edit_end);
8105 }
8106
8107 self.transact(window, cx, |this, window, cx| {
8108 let buffer = this.buffer.update(cx, |buffer, cx| {
8109 let empty_str: Arc<str> = Arc::default();
8110 buffer.edit(
8111 edit_ranges
8112 .into_iter()
8113 .map(|range| (range, empty_str.clone())),
8114 None,
8115 cx,
8116 );
8117 buffer.snapshot(cx)
8118 });
8119 let new_selections = new_cursors
8120 .into_iter()
8121 .map(|(id, cursor)| {
8122 let cursor = cursor.to_point(&buffer);
8123 Selection {
8124 id,
8125 start: cursor,
8126 end: cursor,
8127 reversed: false,
8128 goal: SelectionGoal::None,
8129 }
8130 })
8131 .collect();
8132
8133 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8134 s.select(new_selections);
8135 });
8136 });
8137 }
8138
8139 pub fn join_lines_impl(
8140 &mut self,
8141 insert_whitespace: bool,
8142 window: &mut Window,
8143 cx: &mut Context<Self>,
8144 ) {
8145 if self.read_only(cx) {
8146 return;
8147 }
8148 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
8149 for selection in self.selections.all::<Point>(cx) {
8150 let start = MultiBufferRow(selection.start.row);
8151 // Treat single line selections as if they include the next line. Otherwise this action
8152 // would do nothing for single line selections individual cursors.
8153 let end = if selection.start.row == selection.end.row {
8154 MultiBufferRow(selection.start.row + 1)
8155 } else {
8156 MultiBufferRow(selection.end.row)
8157 };
8158
8159 if let Some(last_row_range) = row_ranges.last_mut() {
8160 if start <= last_row_range.end {
8161 last_row_range.end = end;
8162 continue;
8163 }
8164 }
8165 row_ranges.push(start..end);
8166 }
8167
8168 let snapshot = self.buffer.read(cx).snapshot(cx);
8169 let mut cursor_positions = Vec::new();
8170 for row_range in &row_ranges {
8171 let anchor = snapshot.anchor_before(Point::new(
8172 row_range.end.previous_row().0,
8173 snapshot.line_len(row_range.end.previous_row()),
8174 ));
8175 cursor_positions.push(anchor..anchor);
8176 }
8177
8178 self.transact(window, cx, |this, window, cx| {
8179 for row_range in row_ranges.into_iter().rev() {
8180 for row in row_range.iter_rows().rev() {
8181 let end_of_line = Point::new(row.0, snapshot.line_len(row));
8182 let next_line_row = row.next_row();
8183 let indent = snapshot.indent_size_for_line(next_line_row);
8184 let start_of_next_line = Point::new(next_line_row.0, indent.len);
8185
8186 let replace =
8187 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
8188 " "
8189 } else {
8190 ""
8191 };
8192
8193 this.buffer.update(cx, |buffer, cx| {
8194 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
8195 });
8196 }
8197 }
8198
8199 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8200 s.select_anchor_ranges(cursor_positions)
8201 });
8202 });
8203 }
8204
8205 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
8206 self.join_lines_impl(true, window, cx);
8207 }
8208
8209 pub fn sort_lines_case_sensitive(
8210 &mut self,
8211 _: &SortLinesCaseSensitive,
8212 window: &mut Window,
8213 cx: &mut Context<Self>,
8214 ) {
8215 self.manipulate_lines(window, cx, |lines| lines.sort())
8216 }
8217
8218 pub fn sort_lines_case_insensitive(
8219 &mut self,
8220 _: &SortLinesCaseInsensitive,
8221 window: &mut Window,
8222 cx: &mut Context<Self>,
8223 ) {
8224 self.manipulate_lines(window, cx, |lines| {
8225 lines.sort_by_key(|line| line.to_lowercase())
8226 })
8227 }
8228
8229 pub fn unique_lines_case_insensitive(
8230 &mut self,
8231 _: &UniqueLinesCaseInsensitive,
8232 window: &mut Window,
8233 cx: &mut Context<Self>,
8234 ) {
8235 self.manipulate_lines(window, cx, |lines| {
8236 let mut seen = HashSet::default();
8237 lines.retain(|line| seen.insert(line.to_lowercase()));
8238 })
8239 }
8240
8241 pub fn unique_lines_case_sensitive(
8242 &mut self,
8243 _: &UniqueLinesCaseSensitive,
8244 window: &mut Window,
8245 cx: &mut Context<Self>,
8246 ) {
8247 self.manipulate_lines(window, cx, |lines| {
8248 let mut seen = HashSet::default();
8249 lines.retain(|line| seen.insert(*line));
8250 })
8251 }
8252
8253 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
8254 let Some(project) = self.project.clone() else {
8255 return;
8256 };
8257 self.reload(project, window, cx)
8258 .detach_and_notify_err(window, cx);
8259 }
8260
8261 pub fn restore_file(
8262 &mut self,
8263 _: &::git::RestoreFile,
8264 window: &mut Window,
8265 cx: &mut Context<Self>,
8266 ) {
8267 let mut buffer_ids = HashSet::default();
8268 let snapshot = self.buffer().read(cx).snapshot(cx);
8269 for selection in self.selections.all::<usize>(cx) {
8270 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
8271 }
8272
8273 let buffer = self.buffer().read(cx);
8274 let ranges = buffer_ids
8275 .into_iter()
8276 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
8277 .collect::<Vec<_>>();
8278
8279 self.restore_hunks_in_ranges(ranges, window, cx);
8280 }
8281
8282 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
8283 let selections = self
8284 .selections
8285 .all(cx)
8286 .into_iter()
8287 .map(|s| s.range())
8288 .collect();
8289 self.restore_hunks_in_ranges(selections, window, cx);
8290 }
8291
8292 fn restore_hunks_in_ranges(
8293 &mut self,
8294 ranges: Vec<Range<Point>>,
8295 window: &mut Window,
8296 cx: &mut Context<Editor>,
8297 ) {
8298 let mut revert_changes = HashMap::default();
8299 let chunk_by = self
8300 .snapshot(window, cx)
8301 .hunks_for_ranges(ranges)
8302 .into_iter()
8303 .chunk_by(|hunk| hunk.buffer_id);
8304 for (buffer_id, hunks) in &chunk_by {
8305 let hunks = hunks.collect::<Vec<_>>();
8306 for hunk in &hunks {
8307 self.prepare_restore_change(&mut revert_changes, hunk, cx);
8308 }
8309 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
8310 }
8311 drop(chunk_by);
8312 if !revert_changes.is_empty() {
8313 self.transact(window, cx, |editor, window, cx| {
8314 editor.restore(revert_changes, window, cx);
8315 });
8316 }
8317 }
8318
8319 pub fn open_active_item_in_terminal(
8320 &mut self,
8321 _: &OpenInTerminal,
8322 window: &mut Window,
8323 cx: &mut Context<Self>,
8324 ) {
8325 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
8326 let project_path = buffer.read(cx).project_path(cx)?;
8327 let project = self.project.as_ref()?.read(cx);
8328 let entry = project.entry_for_path(&project_path, cx)?;
8329 let parent = match &entry.canonical_path {
8330 Some(canonical_path) => canonical_path.to_path_buf(),
8331 None => project.absolute_path(&project_path, cx)?,
8332 }
8333 .parent()?
8334 .to_path_buf();
8335 Some(parent)
8336 }) {
8337 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
8338 }
8339 }
8340
8341 fn set_breakpoint_context_menu(
8342 &mut self,
8343 row: DisplayRow,
8344 position: Option<Anchor>,
8345 kind: Arc<BreakpointKind>,
8346 clicked_point: gpui::Point<Pixels>,
8347 window: &mut Window,
8348 cx: &mut Context<Self>,
8349 ) {
8350 if !cx.has_flag::<Debugger>() {
8351 return;
8352 }
8353 let source = self
8354 .buffer
8355 .read(cx)
8356 .snapshot(cx)
8357 .anchor_before(Point::new(row.0, 0u32));
8358
8359 let context_menu =
8360 self.breakpoint_context_menu(position.unwrap_or(source), kind, window, cx);
8361
8362 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
8363 self,
8364 source,
8365 clicked_point,
8366 context_menu,
8367 window,
8368 cx,
8369 );
8370 }
8371
8372 fn add_edit_breakpoint_block(
8373 &mut self,
8374 anchor: Anchor,
8375 kind: &BreakpointKind,
8376 window: &mut Window,
8377 cx: &mut Context<Self>,
8378 ) {
8379 let weak_editor = cx.weak_entity();
8380 let bp_prompt =
8381 cx.new(|cx| BreakpointPromptEditor::new(weak_editor, anchor, kind.clone(), window, cx));
8382
8383 let height = bp_prompt.update(cx, |this, cx| {
8384 this.prompt
8385 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
8386 });
8387 let cloned_prompt = bp_prompt.clone();
8388 let blocks = vec![BlockProperties {
8389 style: BlockStyle::Sticky,
8390 placement: BlockPlacement::Above(anchor),
8391 height,
8392 render: Arc::new(move |cx| {
8393 *cloned_prompt.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
8394 cloned_prompt.clone().into_any_element()
8395 }),
8396 priority: 0,
8397 }];
8398
8399 let focus_handle = bp_prompt.focus_handle(cx);
8400 window.focus(&focus_handle);
8401
8402 let block_ids = self.insert_blocks(blocks, None, cx);
8403 bp_prompt.update(cx, |prompt, _| {
8404 prompt.add_block_ids(block_ids);
8405 });
8406 }
8407
8408 pub(crate) fn breakpoint_at_cursor_head(
8409 &self,
8410 window: &mut Window,
8411 cx: &mut Context<Self>,
8412 ) -> Option<(Anchor, Breakpoint)> {
8413 let cursor_position: Point = self.selections.newest(cx).head();
8414 let snapshot = self.snapshot(window, cx);
8415 // We Set the column position to zero so this function interacts correctly
8416 // between calls by clicking on the gutter & using an action to toggle a
8417 // breakpoint. Otherwise, toggling a breakpoint through an action wouldn't
8418 // untoggle a breakpoint that was added through clicking on the gutter
8419 let cursor_position = snapshot
8420 .display_snapshot
8421 .buffer_snapshot
8422 .anchor_before(Point::new(cursor_position.row, 0));
8423
8424 let project = self.project.clone();
8425
8426 let buffer_id = cursor_position.text_anchor.buffer_id?;
8427 let enclosing_excerpt = snapshot
8428 .buffer_snapshot
8429 .excerpt_ids_for_range(cursor_position..cursor_position)
8430 .next()?;
8431 let buffer = project?.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
8432 let buffer_snapshot = buffer.read(cx).snapshot();
8433
8434 let row = buffer_snapshot
8435 .summary_for_anchor::<text::PointUtf16>(&cursor_position.text_anchor)
8436 .row;
8437
8438 let bp = self
8439 .breakpoint_store
8440 .as_ref()?
8441 .read_with(cx, |breakpoint_store, cx| {
8442 breakpoint_store
8443 .breakpoints(
8444 &buffer,
8445 Some(cursor_position.text_anchor..(text::Anchor::MAX)),
8446 buffer_snapshot.clone(),
8447 cx,
8448 )
8449 .next()
8450 .and_then(move |(anchor, bp)| {
8451 let breakpoint_row = buffer_snapshot
8452 .summary_for_anchor::<text::PointUtf16>(anchor)
8453 .row;
8454
8455 if breakpoint_row == row {
8456 snapshot
8457 .buffer_snapshot
8458 .anchor_in_excerpt(enclosing_excerpt, *anchor)
8459 .map(|anchor| (anchor, bp.clone()))
8460 } else {
8461 None
8462 }
8463 })
8464 });
8465 bp
8466 }
8467
8468 pub fn edit_log_breakpoint(
8469 &mut self,
8470 _: &EditLogBreakpoint,
8471 window: &mut Window,
8472 cx: &mut Context<Self>,
8473 ) {
8474 let (anchor, bp) = self
8475 .breakpoint_at_cursor_head(window, cx)
8476 .unwrap_or_else(|| {
8477 let cursor_position: Point = self.selections.newest(cx).head();
8478
8479 let breakpoint_position = self
8480 .snapshot(window, cx)
8481 .display_snapshot
8482 .buffer_snapshot
8483 .anchor_before(Point::new(cursor_position.row, 0));
8484
8485 (
8486 breakpoint_position,
8487 Breakpoint {
8488 kind: BreakpointKind::Standard,
8489 },
8490 )
8491 });
8492
8493 self.add_edit_breakpoint_block(anchor, &bp.kind, window, cx);
8494 }
8495
8496 pub fn toggle_breakpoint(
8497 &mut self,
8498 _: &crate::actions::ToggleBreakpoint,
8499 window: &mut Window,
8500 cx: &mut Context<Self>,
8501 ) {
8502 let edit_action = BreakpointEditAction::Toggle;
8503
8504 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8505 self.edit_breakpoint_at_anchor(anchor, breakpoint.kind, edit_action, cx);
8506 } else {
8507 let cursor_position: Point = self.selections.newest(cx).head();
8508
8509 let breakpoint_position = self
8510 .snapshot(window, cx)
8511 .display_snapshot
8512 .buffer_snapshot
8513 .anchor_before(Point::new(cursor_position.row, 0));
8514
8515 self.edit_breakpoint_at_anchor(
8516 breakpoint_position,
8517 BreakpointKind::Standard,
8518 edit_action,
8519 cx,
8520 );
8521 }
8522 }
8523
8524 pub fn edit_breakpoint_at_anchor(
8525 &mut self,
8526 breakpoint_position: Anchor,
8527 kind: BreakpointKind,
8528 edit_action: BreakpointEditAction,
8529 cx: &mut Context<Self>,
8530 ) {
8531 let Some(breakpoint_store) = &self.breakpoint_store else {
8532 return;
8533 };
8534
8535 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
8536 if breakpoint_position == Anchor::min() {
8537 self.buffer()
8538 .read(cx)
8539 .excerpt_buffer_ids()
8540 .into_iter()
8541 .next()
8542 } else {
8543 None
8544 }
8545 }) else {
8546 return;
8547 };
8548
8549 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
8550 return;
8551 };
8552
8553 breakpoint_store.update(cx, |breakpoint_store, cx| {
8554 breakpoint_store.toggle_breakpoint(
8555 buffer,
8556 (breakpoint_position.text_anchor, Breakpoint { kind }),
8557 edit_action,
8558 cx,
8559 );
8560 });
8561
8562 cx.notify();
8563 }
8564
8565 #[cfg(any(test, feature = "test-support"))]
8566 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
8567 self.breakpoint_store.clone()
8568 }
8569
8570 pub fn prepare_restore_change(
8571 &self,
8572 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
8573 hunk: &MultiBufferDiffHunk,
8574 cx: &mut App,
8575 ) -> Option<()> {
8576 if hunk.is_created_file() {
8577 return None;
8578 }
8579 let buffer = self.buffer.read(cx);
8580 let diff = buffer.diff_for(hunk.buffer_id)?;
8581 let buffer = buffer.buffer(hunk.buffer_id)?;
8582 let buffer = buffer.read(cx);
8583 let original_text = diff
8584 .read(cx)
8585 .base_text()
8586 .as_rope()
8587 .slice(hunk.diff_base_byte_range.clone());
8588 let buffer_snapshot = buffer.snapshot();
8589 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
8590 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
8591 probe
8592 .0
8593 .start
8594 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
8595 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
8596 }) {
8597 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
8598 Some(())
8599 } else {
8600 None
8601 }
8602 }
8603
8604 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
8605 self.manipulate_lines(window, cx, |lines| lines.reverse())
8606 }
8607
8608 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
8609 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
8610 }
8611
8612 fn manipulate_lines<Fn>(
8613 &mut self,
8614 window: &mut Window,
8615 cx: &mut Context<Self>,
8616 mut callback: Fn,
8617 ) where
8618 Fn: FnMut(&mut Vec<&str>),
8619 {
8620 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8621 let buffer = self.buffer.read(cx).snapshot(cx);
8622
8623 let mut edits = Vec::new();
8624
8625 let selections = self.selections.all::<Point>(cx);
8626 let mut selections = selections.iter().peekable();
8627 let mut contiguous_row_selections = Vec::new();
8628 let mut new_selections = Vec::new();
8629 let mut added_lines = 0;
8630 let mut removed_lines = 0;
8631
8632 while let Some(selection) = selections.next() {
8633 let (start_row, end_row) = consume_contiguous_rows(
8634 &mut contiguous_row_selections,
8635 selection,
8636 &display_map,
8637 &mut selections,
8638 );
8639
8640 let start_point = Point::new(start_row.0, 0);
8641 let end_point = Point::new(
8642 end_row.previous_row().0,
8643 buffer.line_len(end_row.previous_row()),
8644 );
8645 let text = buffer
8646 .text_for_range(start_point..end_point)
8647 .collect::<String>();
8648
8649 let mut lines = text.split('\n').collect_vec();
8650
8651 let lines_before = lines.len();
8652 callback(&mut lines);
8653 let lines_after = lines.len();
8654
8655 edits.push((start_point..end_point, lines.join("\n")));
8656
8657 // Selections must change based on added and removed line count
8658 let start_row =
8659 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
8660 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
8661 new_selections.push(Selection {
8662 id: selection.id,
8663 start: start_row,
8664 end: end_row,
8665 goal: SelectionGoal::None,
8666 reversed: selection.reversed,
8667 });
8668
8669 if lines_after > lines_before {
8670 added_lines += lines_after - lines_before;
8671 } else if lines_before > lines_after {
8672 removed_lines += lines_before - lines_after;
8673 }
8674 }
8675
8676 self.transact(window, cx, |this, window, cx| {
8677 let buffer = this.buffer.update(cx, |buffer, cx| {
8678 buffer.edit(edits, None, cx);
8679 buffer.snapshot(cx)
8680 });
8681
8682 // Recalculate offsets on newly edited buffer
8683 let new_selections = new_selections
8684 .iter()
8685 .map(|s| {
8686 let start_point = Point::new(s.start.0, 0);
8687 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
8688 Selection {
8689 id: s.id,
8690 start: buffer.point_to_offset(start_point),
8691 end: buffer.point_to_offset(end_point),
8692 goal: s.goal,
8693 reversed: s.reversed,
8694 }
8695 })
8696 .collect();
8697
8698 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8699 s.select(new_selections);
8700 });
8701
8702 this.request_autoscroll(Autoscroll::fit(), cx);
8703 });
8704 }
8705
8706 pub fn convert_to_upper_case(
8707 &mut self,
8708 _: &ConvertToUpperCase,
8709 window: &mut Window,
8710 cx: &mut Context<Self>,
8711 ) {
8712 self.manipulate_text(window, cx, |text| text.to_uppercase())
8713 }
8714
8715 pub fn convert_to_lower_case(
8716 &mut self,
8717 _: &ConvertToLowerCase,
8718 window: &mut Window,
8719 cx: &mut Context<Self>,
8720 ) {
8721 self.manipulate_text(window, cx, |text| text.to_lowercase())
8722 }
8723
8724 pub fn convert_to_title_case(
8725 &mut self,
8726 _: &ConvertToTitleCase,
8727 window: &mut Window,
8728 cx: &mut Context<Self>,
8729 ) {
8730 self.manipulate_text(window, cx, |text| {
8731 text.split('\n')
8732 .map(|line| line.to_case(Case::Title))
8733 .join("\n")
8734 })
8735 }
8736
8737 pub fn convert_to_snake_case(
8738 &mut self,
8739 _: &ConvertToSnakeCase,
8740 window: &mut Window,
8741 cx: &mut Context<Self>,
8742 ) {
8743 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
8744 }
8745
8746 pub fn convert_to_kebab_case(
8747 &mut self,
8748 _: &ConvertToKebabCase,
8749 window: &mut Window,
8750 cx: &mut Context<Self>,
8751 ) {
8752 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
8753 }
8754
8755 pub fn convert_to_upper_camel_case(
8756 &mut self,
8757 _: &ConvertToUpperCamelCase,
8758 window: &mut Window,
8759 cx: &mut Context<Self>,
8760 ) {
8761 self.manipulate_text(window, cx, |text| {
8762 text.split('\n')
8763 .map(|line| line.to_case(Case::UpperCamel))
8764 .join("\n")
8765 })
8766 }
8767
8768 pub fn convert_to_lower_camel_case(
8769 &mut self,
8770 _: &ConvertToLowerCamelCase,
8771 window: &mut Window,
8772 cx: &mut Context<Self>,
8773 ) {
8774 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
8775 }
8776
8777 pub fn convert_to_opposite_case(
8778 &mut self,
8779 _: &ConvertToOppositeCase,
8780 window: &mut Window,
8781 cx: &mut Context<Self>,
8782 ) {
8783 self.manipulate_text(window, cx, |text| {
8784 text.chars()
8785 .fold(String::with_capacity(text.len()), |mut t, c| {
8786 if c.is_uppercase() {
8787 t.extend(c.to_lowercase());
8788 } else {
8789 t.extend(c.to_uppercase());
8790 }
8791 t
8792 })
8793 })
8794 }
8795
8796 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
8797 where
8798 Fn: FnMut(&str) -> String,
8799 {
8800 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8801 let buffer = self.buffer.read(cx).snapshot(cx);
8802
8803 let mut new_selections = Vec::new();
8804 let mut edits = Vec::new();
8805 let mut selection_adjustment = 0i32;
8806
8807 for selection in self.selections.all::<usize>(cx) {
8808 let selection_is_empty = selection.is_empty();
8809
8810 let (start, end) = if selection_is_empty {
8811 let word_range = movement::surrounding_word(
8812 &display_map,
8813 selection.start.to_display_point(&display_map),
8814 );
8815 let start = word_range.start.to_offset(&display_map, Bias::Left);
8816 let end = word_range.end.to_offset(&display_map, Bias::Left);
8817 (start, end)
8818 } else {
8819 (selection.start, selection.end)
8820 };
8821
8822 let text = buffer.text_for_range(start..end).collect::<String>();
8823 let old_length = text.len() as i32;
8824 let text = callback(&text);
8825
8826 new_selections.push(Selection {
8827 start: (start as i32 - selection_adjustment) as usize,
8828 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
8829 goal: SelectionGoal::None,
8830 ..selection
8831 });
8832
8833 selection_adjustment += old_length - text.len() as i32;
8834
8835 edits.push((start..end, text));
8836 }
8837
8838 self.transact(window, cx, |this, window, cx| {
8839 this.buffer.update(cx, |buffer, cx| {
8840 buffer.edit(edits, None, cx);
8841 });
8842
8843 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8844 s.select(new_selections);
8845 });
8846
8847 this.request_autoscroll(Autoscroll::fit(), cx);
8848 });
8849 }
8850
8851 pub fn duplicate(
8852 &mut self,
8853 upwards: bool,
8854 whole_lines: bool,
8855 window: &mut Window,
8856 cx: &mut Context<Self>,
8857 ) {
8858 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8859 let buffer = &display_map.buffer_snapshot;
8860 let selections = self.selections.all::<Point>(cx);
8861
8862 let mut edits = Vec::new();
8863 let mut selections_iter = selections.iter().peekable();
8864 while let Some(selection) = selections_iter.next() {
8865 let mut rows = selection.spanned_rows(false, &display_map);
8866 // duplicate line-wise
8867 if whole_lines || selection.start == selection.end {
8868 // Avoid duplicating the same lines twice.
8869 while let Some(next_selection) = selections_iter.peek() {
8870 let next_rows = next_selection.spanned_rows(false, &display_map);
8871 if next_rows.start < rows.end {
8872 rows.end = next_rows.end;
8873 selections_iter.next().unwrap();
8874 } else {
8875 break;
8876 }
8877 }
8878
8879 // Copy the text from the selected row region and splice it either at the start
8880 // or end of the region.
8881 let start = Point::new(rows.start.0, 0);
8882 let end = Point::new(
8883 rows.end.previous_row().0,
8884 buffer.line_len(rows.end.previous_row()),
8885 );
8886 let text = buffer
8887 .text_for_range(start..end)
8888 .chain(Some("\n"))
8889 .collect::<String>();
8890 let insert_location = if upwards {
8891 Point::new(rows.end.0, 0)
8892 } else {
8893 start
8894 };
8895 edits.push((insert_location..insert_location, text));
8896 } else {
8897 // duplicate character-wise
8898 let start = selection.start;
8899 let end = selection.end;
8900 let text = buffer.text_for_range(start..end).collect::<String>();
8901 edits.push((selection.end..selection.end, text));
8902 }
8903 }
8904
8905 self.transact(window, cx, |this, _, cx| {
8906 this.buffer.update(cx, |buffer, cx| {
8907 buffer.edit(edits, None, cx);
8908 });
8909
8910 this.request_autoscroll(Autoscroll::fit(), cx);
8911 });
8912 }
8913
8914 pub fn duplicate_line_up(
8915 &mut self,
8916 _: &DuplicateLineUp,
8917 window: &mut Window,
8918 cx: &mut Context<Self>,
8919 ) {
8920 self.duplicate(true, true, window, cx);
8921 }
8922
8923 pub fn duplicate_line_down(
8924 &mut self,
8925 _: &DuplicateLineDown,
8926 window: &mut Window,
8927 cx: &mut Context<Self>,
8928 ) {
8929 self.duplicate(false, true, window, cx);
8930 }
8931
8932 pub fn duplicate_selection(
8933 &mut self,
8934 _: &DuplicateSelection,
8935 window: &mut Window,
8936 cx: &mut Context<Self>,
8937 ) {
8938 self.duplicate(false, false, window, cx);
8939 }
8940
8941 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
8942 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8943 let buffer = self.buffer.read(cx).snapshot(cx);
8944
8945 let mut edits = Vec::new();
8946 let mut unfold_ranges = Vec::new();
8947 let mut refold_creases = Vec::new();
8948
8949 let selections = self.selections.all::<Point>(cx);
8950 let mut selections = selections.iter().peekable();
8951 let mut contiguous_row_selections = Vec::new();
8952 let mut new_selections = Vec::new();
8953
8954 while let Some(selection) = selections.next() {
8955 // Find all the selections that span a contiguous row range
8956 let (start_row, end_row) = consume_contiguous_rows(
8957 &mut contiguous_row_selections,
8958 selection,
8959 &display_map,
8960 &mut selections,
8961 );
8962
8963 // Move the text spanned by the row range to be before the line preceding the row range
8964 if start_row.0 > 0 {
8965 let range_to_move = Point::new(
8966 start_row.previous_row().0,
8967 buffer.line_len(start_row.previous_row()),
8968 )
8969 ..Point::new(
8970 end_row.previous_row().0,
8971 buffer.line_len(end_row.previous_row()),
8972 );
8973 let insertion_point = display_map
8974 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
8975 .0;
8976
8977 // Don't move lines across excerpts
8978 if buffer
8979 .excerpt_containing(insertion_point..range_to_move.end)
8980 .is_some()
8981 {
8982 let text = buffer
8983 .text_for_range(range_to_move.clone())
8984 .flat_map(|s| s.chars())
8985 .skip(1)
8986 .chain(['\n'])
8987 .collect::<String>();
8988
8989 edits.push((
8990 buffer.anchor_after(range_to_move.start)
8991 ..buffer.anchor_before(range_to_move.end),
8992 String::new(),
8993 ));
8994 let insertion_anchor = buffer.anchor_after(insertion_point);
8995 edits.push((insertion_anchor..insertion_anchor, text));
8996
8997 let row_delta = range_to_move.start.row - insertion_point.row + 1;
8998
8999 // Move selections up
9000 new_selections.extend(contiguous_row_selections.drain(..).map(
9001 |mut selection| {
9002 selection.start.row -= row_delta;
9003 selection.end.row -= row_delta;
9004 selection
9005 },
9006 ));
9007
9008 // Move folds up
9009 unfold_ranges.push(range_to_move.clone());
9010 for fold in display_map.folds_in_range(
9011 buffer.anchor_before(range_to_move.start)
9012 ..buffer.anchor_after(range_to_move.end),
9013 ) {
9014 let mut start = fold.range.start.to_point(&buffer);
9015 let mut end = fold.range.end.to_point(&buffer);
9016 start.row -= row_delta;
9017 end.row -= row_delta;
9018 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9019 }
9020 }
9021 }
9022
9023 // If we didn't move line(s), preserve the existing selections
9024 new_selections.append(&mut contiguous_row_selections);
9025 }
9026
9027 self.transact(window, cx, |this, window, cx| {
9028 this.unfold_ranges(&unfold_ranges, true, true, cx);
9029 this.buffer.update(cx, |buffer, cx| {
9030 for (range, text) in edits {
9031 buffer.edit([(range, text)], None, cx);
9032 }
9033 });
9034 this.fold_creases(refold_creases, true, window, cx);
9035 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9036 s.select(new_selections);
9037 })
9038 });
9039 }
9040
9041 pub fn move_line_down(
9042 &mut self,
9043 _: &MoveLineDown,
9044 window: &mut Window,
9045 cx: &mut Context<Self>,
9046 ) {
9047 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9048 let buffer = self.buffer.read(cx).snapshot(cx);
9049
9050 let mut edits = Vec::new();
9051 let mut unfold_ranges = Vec::new();
9052 let mut refold_creases = Vec::new();
9053
9054 let selections = self.selections.all::<Point>(cx);
9055 let mut selections = selections.iter().peekable();
9056 let mut contiguous_row_selections = Vec::new();
9057 let mut new_selections = Vec::new();
9058
9059 while let Some(selection) = selections.next() {
9060 // Find all the selections that span a contiguous row range
9061 let (start_row, end_row) = consume_contiguous_rows(
9062 &mut contiguous_row_selections,
9063 selection,
9064 &display_map,
9065 &mut selections,
9066 );
9067
9068 // Move the text spanned by the row range to be after the last line of the row range
9069 if end_row.0 <= buffer.max_point().row {
9070 let range_to_move =
9071 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
9072 let insertion_point = display_map
9073 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
9074 .0;
9075
9076 // Don't move lines across excerpt boundaries
9077 if buffer
9078 .excerpt_containing(range_to_move.start..insertion_point)
9079 .is_some()
9080 {
9081 let mut text = String::from("\n");
9082 text.extend(buffer.text_for_range(range_to_move.clone()));
9083 text.pop(); // Drop trailing newline
9084 edits.push((
9085 buffer.anchor_after(range_to_move.start)
9086 ..buffer.anchor_before(range_to_move.end),
9087 String::new(),
9088 ));
9089 let insertion_anchor = buffer.anchor_after(insertion_point);
9090 edits.push((insertion_anchor..insertion_anchor, text));
9091
9092 let row_delta = insertion_point.row - range_to_move.end.row + 1;
9093
9094 // Move selections down
9095 new_selections.extend(contiguous_row_selections.drain(..).map(
9096 |mut selection| {
9097 selection.start.row += row_delta;
9098 selection.end.row += row_delta;
9099 selection
9100 },
9101 ));
9102
9103 // Move folds down
9104 unfold_ranges.push(range_to_move.clone());
9105 for fold in display_map.folds_in_range(
9106 buffer.anchor_before(range_to_move.start)
9107 ..buffer.anchor_after(range_to_move.end),
9108 ) {
9109 let mut start = fold.range.start.to_point(&buffer);
9110 let mut end = fold.range.end.to_point(&buffer);
9111 start.row += row_delta;
9112 end.row += row_delta;
9113 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9114 }
9115 }
9116 }
9117
9118 // If we didn't move line(s), preserve the existing selections
9119 new_selections.append(&mut contiguous_row_selections);
9120 }
9121
9122 self.transact(window, cx, |this, window, cx| {
9123 this.unfold_ranges(&unfold_ranges, true, true, cx);
9124 this.buffer.update(cx, |buffer, cx| {
9125 for (range, text) in edits {
9126 buffer.edit([(range, text)], None, cx);
9127 }
9128 });
9129 this.fold_creases(refold_creases, true, window, cx);
9130 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9131 s.select(new_selections)
9132 });
9133 });
9134 }
9135
9136 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
9137 let text_layout_details = &self.text_layout_details(window);
9138 self.transact(window, cx, |this, window, cx| {
9139 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9140 let mut edits: Vec<(Range<usize>, String)> = Default::default();
9141 let line_mode = s.line_mode;
9142 s.move_with(|display_map, selection| {
9143 if !selection.is_empty() || line_mode {
9144 return;
9145 }
9146
9147 let mut head = selection.head();
9148 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
9149 if head.column() == display_map.line_len(head.row()) {
9150 transpose_offset = display_map
9151 .buffer_snapshot
9152 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9153 }
9154
9155 if transpose_offset == 0 {
9156 return;
9157 }
9158
9159 *head.column_mut() += 1;
9160 head = display_map.clip_point(head, Bias::Right);
9161 let goal = SelectionGoal::HorizontalPosition(
9162 display_map
9163 .x_for_display_point(head, text_layout_details)
9164 .into(),
9165 );
9166 selection.collapse_to(head, goal);
9167
9168 let transpose_start = display_map
9169 .buffer_snapshot
9170 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9171 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
9172 let transpose_end = display_map
9173 .buffer_snapshot
9174 .clip_offset(transpose_offset + 1, Bias::Right);
9175 if let Some(ch) =
9176 display_map.buffer_snapshot.chars_at(transpose_start).next()
9177 {
9178 edits.push((transpose_start..transpose_offset, String::new()));
9179 edits.push((transpose_end..transpose_end, ch.to_string()));
9180 }
9181 }
9182 });
9183 edits
9184 });
9185 this.buffer
9186 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9187 let selections = this.selections.all::<usize>(cx);
9188 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9189 s.select(selections);
9190 });
9191 });
9192 }
9193
9194 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
9195 self.rewrap_impl(RewrapOptions::default(), cx)
9196 }
9197
9198 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
9199 let buffer = self.buffer.read(cx).snapshot(cx);
9200 let selections = self.selections.all::<Point>(cx);
9201 let mut selections = selections.iter().peekable();
9202
9203 let mut edits = Vec::new();
9204 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
9205
9206 while let Some(selection) = selections.next() {
9207 let mut start_row = selection.start.row;
9208 let mut end_row = selection.end.row;
9209
9210 // Skip selections that overlap with a range that has already been rewrapped.
9211 let selection_range = start_row..end_row;
9212 if rewrapped_row_ranges
9213 .iter()
9214 .any(|range| range.overlaps(&selection_range))
9215 {
9216 continue;
9217 }
9218
9219 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
9220
9221 // Since not all lines in the selection may be at the same indent
9222 // level, choose the indent size that is the most common between all
9223 // of the lines.
9224 //
9225 // If there is a tie, we use the deepest indent.
9226 let (indent_size, indent_end) = {
9227 let mut indent_size_occurrences = HashMap::default();
9228 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
9229
9230 for row in start_row..=end_row {
9231 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
9232 rows_by_indent_size.entry(indent).or_default().push(row);
9233 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
9234 }
9235
9236 let indent_size = indent_size_occurrences
9237 .into_iter()
9238 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
9239 .map(|(indent, _)| indent)
9240 .unwrap_or_default();
9241 let row = rows_by_indent_size[&indent_size][0];
9242 let indent_end = Point::new(row, indent_size.len);
9243
9244 (indent_size, indent_end)
9245 };
9246
9247 let mut line_prefix = indent_size.chars().collect::<String>();
9248
9249 let mut inside_comment = false;
9250 if let Some(comment_prefix) =
9251 buffer
9252 .language_scope_at(selection.head())
9253 .and_then(|language| {
9254 language
9255 .line_comment_prefixes()
9256 .iter()
9257 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
9258 .cloned()
9259 })
9260 {
9261 line_prefix.push_str(&comment_prefix);
9262 inside_comment = true;
9263 }
9264
9265 let language_settings = buffer.language_settings_at(selection.head(), cx);
9266 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
9267 RewrapBehavior::InComments => inside_comment,
9268 RewrapBehavior::InSelections => !selection.is_empty(),
9269 RewrapBehavior::Anywhere => true,
9270 };
9271
9272 let should_rewrap = options.override_language_settings
9273 || allow_rewrap_based_on_language
9274 || self.hard_wrap.is_some();
9275 if !should_rewrap {
9276 continue;
9277 }
9278
9279 if selection.is_empty() {
9280 'expand_upwards: while start_row > 0 {
9281 let prev_row = start_row - 1;
9282 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
9283 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
9284 {
9285 start_row = prev_row;
9286 } else {
9287 break 'expand_upwards;
9288 }
9289 }
9290
9291 'expand_downwards: while end_row < buffer.max_point().row {
9292 let next_row = end_row + 1;
9293 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
9294 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
9295 {
9296 end_row = next_row;
9297 } else {
9298 break 'expand_downwards;
9299 }
9300 }
9301 }
9302
9303 let start = Point::new(start_row, 0);
9304 let start_offset = start.to_offset(&buffer);
9305 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
9306 let selection_text = buffer.text_for_range(start..end).collect::<String>();
9307 let Some(lines_without_prefixes) = selection_text
9308 .lines()
9309 .map(|line| {
9310 line.strip_prefix(&line_prefix)
9311 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
9312 .ok_or_else(|| {
9313 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
9314 })
9315 })
9316 .collect::<Result<Vec<_>, _>>()
9317 .log_err()
9318 else {
9319 continue;
9320 };
9321
9322 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
9323 buffer
9324 .language_settings_at(Point::new(start_row, 0), cx)
9325 .preferred_line_length as usize
9326 });
9327 let wrapped_text = wrap_with_prefix(
9328 line_prefix,
9329 lines_without_prefixes.join("\n"),
9330 wrap_column,
9331 tab_size,
9332 options.preserve_existing_whitespace,
9333 );
9334
9335 // TODO: should always use char-based diff while still supporting cursor behavior that
9336 // matches vim.
9337 let mut diff_options = DiffOptions::default();
9338 if options.override_language_settings {
9339 diff_options.max_word_diff_len = 0;
9340 diff_options.max_word_diff_line_count = 0;
9341 } else {
9342 diff_options.max_word_diff_len = usize::MAX;
9343 diff_options.max_word_diff_line_count = usize::MAX;
9344 }
9345
9346 for (old_range, new_text) in
9347 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
9348 {
9349 let edit_start = buffer.anchor_after(start_offset + old_range.start);
9350 let edit_end = buffer.anchor_after(start_offset + old_range.end);
9351 edits.push((edit_start..edit_end, new_text));
9352 }
9353
9354 rewrapped_row_ranges.push(start_row..=end_row);
9355 }
9356
9357 self.buffer
9358 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9359 }
9360
9361 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
9362 let mut text = String::new();
9363 let buffer = self.buffer.read(cx).snapshot(cx);
9364 let mut selections = self.selections.all::<Point>(cx);
9365 let mut clipboard_selections = Vec::with_capacity(selections.len());
9366 {
9367 let max_point = buffer.max_point();
9368 let mut is_first = true;
9369 for selection in &mut selections {
9370 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9371 if is_entire_line {
9372 selection.start = Point::new(selection.start.row, 0);
9373 if !selection.is_empty() && selection.end.column == 0 {
9374 selection.end = cmp::min(max_point, selection.end);
9375 } else {
9376 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
9377 }
9378 selection.goal = SelectionGoal::None;
9379 }
9380 if is_first {
9381 is_first = false;
9382 } else {
9383 text += "\n";
9384 }
9385 let mut len = 0;
9386 for chunk in buffer.text_for_range(selection.start..selection.end) {
9387 text.push_str(chunk);
9388 len += chunk.len();
9389 }
9390 clipboard_selections.push(ClipboardSelection {
9391 len,
9392 is_entire_line,
9393 first_line_indent: buffer
9394 .indent_size_for_line(MultiBufferRow(selection.start.row))
9395 .len,
9396 });
9397 }
9398 }
9399
9400 self.transact(window, cx, |this, window, cx| {
9401 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9402 s.select(selections);
9403 });
9404 this.insert("", window, cx);
9405 });
9406 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
9407 }
9408
9409 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
9410 let item = self.cut_common(window, cx);
9411 cx.write_to_clipboard(item);
9412 }
9413
9414 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
9415 self.change_selections(None, window, cx, |s| {
9416 s.move_with(|snapshot, sel| {
9417 if sel.is_empty() {
9418 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
9419 }
9420 });
9421 });
9422 let item = self.cut_common(window, cx);
9423 cx.set_global(KillRing(item))
9424 }
9425
9426 pub fn kill_ring_yank(
9427 &mut self,
9428 _: &KillRingYank,
9429 window: &mut Window,
9430 cx: &mut Context<Self>,
9431 ) {
9432 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
9433 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
9434 (kill_ring.text().to_string(), kill_ring.metadata_json())
9435 } else {
9436 return;
9437 }
9438 } else {
9439 return;
9440 };
9441 self.do_paste(&text, metadata, false, window, cx);
9442 }
9443
9444 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
9445 let selections = self.selections.all::<Point>(cx);
9446 let buffer = self.buffer.read(cx).read(cx);
9447 let mut text = String::new();
9448
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 selections.iter() {
9454 let mut start = selection.start;
9455 let mut end = selection.end;
9456 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9457 if is_entire_line {
9458 start = Point::new(start.row, 0);
9459 end = cmp::min(max_point, Point::new(end.row + 1, 0));
9460 }
9461 if is_first {
9462 is_first = false;
9463 } else {
9464 text += "\n";
9465 }
9466 let mut len = 0;
9467 for chunk in buffer.text_for_range(start..end) {
9468 text.push_str(chunk);
9469 len += chunk.len();
9470 }
9471 clipboard_selections.push(ClipboardSelection {
9472 len,
9473 is_entire_line,
9474 first_line_indent: buffer.indent_size_for_line(MultiBufferRow(start.row)).len,
9475 });
9476 }
9477 }
9478
9479 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
9480 text,
9481 clipboard_selections,
9482 ));
9483 }
9484
9485 pub fn do_paste(
9486 &mut self,
9487 text: &String,
9488 clipboard_selections: Option<Vec<ClipboardSelection>>,
9489 handle_entire_lines: bool,
9490 window: &mut Window,
9491 cx: &mut Context<Self>,
9492 ) {
9493 if self.read_only(cx) {
9494 return;
9495 }
9496
9497 let clipboard_text = Cow::Borrowed(text);
9498
9499 self.transact(window, cx, |this, window, cx| {
9500 if let Some(mut clipboard_selections) = clipboard_selections {
9501 let old_selections = this.selections.all::<usize>(cx);
9502 let all_selections_were_entire_line =
9503 clipboard_selections.iter().all(|s| s.is_entire_line);
9504 let first_selection_indent_column =
9505 clipboard_selections.first().map(|s| s.first_line_indent);
9506 if clipboard_selections.len() != old_selections.len() {
9507 clipboard_selections.drain(..);
9508 }
9509 let cursor_offset = this.selections.last::<usize>(cx).head();
9510 let mut auto_indent_on_paste = true;
9511
9512 this.buffer.update(cx, |buffer, cx| {
9513 let snapshot = buffer.read(cx);
9514 auto_indent_on_paste = snapshot
9515 .language_settings_at(cursor_offset, cx)
9516 .auto_indent_on_paste;
9517
9518 let mut start_offset = 0;
9519 let mut edits = Vec::new();
9520 let mut original_indent_columns = Vec::new();
9521 for (ix, selection) in old_selections.iter().enumerate() {
9522 let to_insert;
9523 let entire_line;
9524 let original_indent_column;
9525 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
9526 let end_offset = start_offset + clipboard_selection.len;
9527 to_insert = &clipboard_text[start_offset..end_offset];
9528 entire_line = clipboard_selection.is_entire_line;
9529 start_offset = end_offset + 1;
9530 original_indent_column = Some(clipboard_selection.first_line_indent);
9531 } else {
9532 to_insert = clipboard_text.as_str();
9533 entire_line = all_selections_were_entire_line;
9534 original_indent_column = first_selection_indent_column
9535 }
9536
9537 // If the corresponding selection was empty when this slice of the
9538 // clipboard text was written, then the entire line containing the
9539 // selection was copied. If this selection is also currently empty,
9540 // then paste the line before the current line of the buffer.
9541 let range = if selection.is_empty() && handle_entire_lines && entire_line {
9542 let column = selection.start.to_point(&snapshot).column as usize;
9543 let line_start = selection.start - column;
9544 line_start..line_start
9545 } else {
9546 selection.range()
9547 };
9548
9549 edits.push((range, to_insert));
9550 original_indent_columns.push(original_indent_column);
9551 }
9552 drop(snapshot);
9553
9554 buffer.edit(
9555 edits,
9556 if auto_indent_on_paste {
9557 Some(AutoindentMode::Block {
9558 original_indent_columns,
9559 })
9560 } else {
9561 None
9562 },
9563 cx,
9564 );
9565 });
9566
9567 let selections = this.selections.all::<usize>(cx);
9568 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9569 s.select(selections)
9570 });
9571 } else {
9572 this.insert(&clipboard_text, window, cx);
9573 }
9574 });
9575 }
9576
9577 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
9578 if let Some(item) = cx.read_from_clipboard() {
9579 let entries = item.entries();
9580
9581 match entries.first() {
9582 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
9583 // of all the pasted entries.
9584 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
9585 .do_paste(
9586 clipboard_string.text(),
9587 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
9588 true,
9589 window,
9590 cx,
9591 ),
9592 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
9593 }
9594 }
9595 }
9596
9597 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
9598 if self.read_only(cx) {
9599 return;
9600 }
9601
9602 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
9603 if let Some((selections, _)) =
9604 self.selection_history.transaction(transaction_id).cloned()
9605 {
9606 self.change_selections(None, window, cx, |s| {
9607 s.select_anchors(selections.to_vec());
9608 });
9609 } else {
9610 log::error!(
9611 "No entry in selection_history found for undo. \
9612 This may correspond to a bug where undo does not update the selection. \
9613 If this is occurring, please add details to \
9614 https://github.com/zed-industries/zed/issues/22692"
9615 );
9616 }
9617 self.request_autoscroll(Autoscroll::fit(), cx);
9618 self.unmark_text(window, cx);
9619 self.refresh_inline_completion(true, false, window, cx);
9620 cx.emit(EditorEvent::Edited { transaction_id });
9621 cx.emit(EditorEvent::TransactionUndone { transaction_id });
9622 }
9623 }
9624
9625 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
9626 if self.read_only(cx) {
9627 return;
9628 }
9629
9630 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
9631 if let Some((_, Some(selections))) =
9632 self.selection_history.transaction(transaction_id).cloned()
9633 {
9634 self.change_selections(None, window, cx, |s| {
9635 s.select_anchors(selections.to_vec());
9636 });
9637 } else {
9638 log::error!(
9639 "No entry in selection_history found for redo. \
9640 This may correspond to a bug where undo does not update the selection. \
9641 If this is occurring, please add details to \
9642 https://github.com/zed-industries/zed/issues/22692"
9643 );
9644 }
9645 self.request_autoscroll(Autoscroll::fit(), cx);
9646 self.unmark_text(window, cx);
9647 self.refresh_inline_completion(true, false, window, cx);
9648 cx.emit(EditorEvent::Edited { transaction_id });
9649 }
9650 }
9651
9652 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
9653 self.buffer
9654 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
9655 }
9656
9657 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
9658 self.buffer
9659 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
9660 }
9661
9662 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
9663 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9664 let line_mode = s.line_mode;
9665 s.move_with(|map, selection| {
9666 let cursor = if selection.is_empty() && !line_mode {
9667 movement::left(map, selection.start)
9668 } else {
9669 selection.start
9670 };
9671 selection.collapse_to(cursor, SelectionGoal::None);
9672 });
9673 })
9674 }
9675
9676 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
9677 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9678 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
9679 })
9680 }
9681
9682 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
9683 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9684 let line_mode = s.line_mode;
9685 s.move_with(|map, selection| {
9686 let cursor = if selection.is_empty() && !line_mode {
9687 movement::right(map, selection.end)
9688 } else {
9689 selection.end
9690 };
9691 selection.collapse_to(cursor, SelectionGoal::None)
9692 });
9693 })
9694 }
9695
9696 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
9697 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9698 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
9699 })
9700 }
9701
9702 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
9703 if self.take_rename(true, window, cx).is_some() {
9704 return;
9705 }
9706
9707 if matches!(self.mode, EditorMode::SingleLine { .. }) {
9708 cx.propagate();
9709 return;
9710 }
9711
9712 let text_layout_details = &self.text_layout_details(window);
9713 let selection_count = self.selections.count();
9714 let first_selection = self.selections.first_anchor();
9715
9716 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9717 let line_mode = s.line_mode;
9718 s.move_with(|map, selection| {
9719 if !selection.is_empty() && !line_mode {
9720 selection.goal = SelectionGoal::None;
9721 }
9722 let (cursor, goal) = movement::up(
9723 map,
9724 selection.start,
9725 selection.goal,
9726 false,
9727 text_layout_details,
9728 );
9729 selection.collapse_to(cursor, goal);
9730 });
9731 });
9732
9733 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
9734 {
9735 cx.propagate();
9736 }
9737 }
9738
9739 pub fn move_up_by_lines(
9740 &mut self,
9741 action: &MoveUpByLines,
9742 window: &mut Window,
9743 cx: &mut Context<Self>,
9744 ) {
9745 if self.take_rename(true, window, cx).is_some() {
9746 return;
9747 }
9748
9749 if matches!(self.mode, EditorMode::SingleLine { .. }) {
9750 cx.propagate();
9751 return;
9752 }
9753
9754 let text_layout_details = &self.text_layout_details(window);
9755
9756 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9757 let line_mode = s.line_mode;
9758 s.move_with(|map, selection| {
9759 if !selection.is_empty() && !line_mode {
9760 selection.goal = SelectionGoal::None;
9761 }
9762 let (cursor, goal) = movement::up_by_rows(
9763 map,
9764 selection.start,
9765 action.lines,
9766 selection.goal,
9767 false,
9768 text_layout_details,
9769 );
9770 selection.collapse_to(cursor, goal);
9771 });
9772 })
9773 }
9774
9775 pub fn move_down_by_lines(
9776 &mut self,
9777 action: &MoveDownByLines,
9778 window: &mut Window,
9779 cx: &mut Context<Self>,
9780 ) {
9781 if self.take_rename(true, window, cx).is_some() {
9782 return;
9783 }
9784
9785 if matches!(self.mode, EditorMode::SingleLine { .. }) {
9786 cx.propagate();
9787 return;
9788 }
9789
9790 let text_layout_details = &self.text_layout_details(window);
9791
9792 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9793 let line_mode = s.line_mode;
9794 s.move_with(|map, selection| {
9795 if !selection.is_empty() && !line_mode {
9796 selection.goal = SelectionGoal::None;
9797 }
9798 let (cursor, goal) = movement::down_by_rows(
9799 map,
9800 selection.start,
9801 action.lines,
9802 selection.goal,
9803 false,
9804 text_layout_details,
9805 );
9806 selection.collapse_to(cursor, goal);
9807 });
9808 })
9809 }
9810
9811 pub fn select_down_by_lines(
9812 &mut self,
9813 action: &SelectDownByLines,
9814 window: &mut Window,
9815 cx: &mut Context<Self>,
9816 ) {
9817 let text_layout_details = &self.text_layout_details(window);
9818 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9819 s.move_heads_with(|map, head, goal| {
9820 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
9821 })
9822 })
9823 }
9824
9825 pub fn select_up_by_lines(
9826 &mut self,
9827 action: &SelectUpByLines,
9828 window: &mut Window,
9829 cx: &mut Context<Self>,
9830 ) {
9831 let text_layout_details = &self.text_layout_details(window);
9832 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9833 s.move_heads_with(|map, head, goal| {
9834 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
9835 })
9836 })
9837 }
9838
9839 pub fn select_page_up(
9840 &mut self,
9841 _: &SelectPageUp,
9842 window: &mut Window,
9843 cx: &mut Context<Self>,
9844 ) {
9845 let Some(row_count) = self.visible_row_count() else {
9846 return;
9847 };
9848
9849 let text_layout_details = &self.text_layout_details(window);
9850
9851 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9852 s.move_heads_with(|map, head, goal| {
9853 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
9854 })
9855 })
9856 }
9857
9858 pub fn move_page_up(
9859 &mut self,
9860 action: &MovePageUp,
9861 window: &mut Window,
9862 cx: &mut Context<Self>,
9863 ) {
9864 if self.take_rename(true, window, cx).is_some() {
9865 return;
9866 }
9867
9868 if self
9869 .context_menu
9870 .borrow_mut()
9871 .as_mut()
9872 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
9873 .unwrap_or(false)
9874 {
9875 return;
9876 }
9877
9878 if matches!(self.mode, EditorMode::SingleLine { .. }) {
9879 cx.propagate();
9880 return;
9881 }
9882
9883 let Some(row_count) = self.visible_row_count() else {
9884 return;
9885 };
9886
9887 let autoscroll = if action.center_cursor {
9888 Autoscroll::center()
9889 } else {
9890 Autoscroll::fit()
9891 };
9892
9893 let text_layout_details = &self.text_layout_details(window);
9894
9895 self.change_selections(Some(autoscroll), window, cx, |s| {
9896 let line_mode = s.line_mode;
9897 s.move_with(|map, selection| {
9898 if !selection.is_empty() && !line_mode {
9899 selection.goal = SelectionGoal::None;
9900 }
9901 let (cursor, goal) = movement::up_by_rows(
9902 map,
9903 selection.end,
9904 row_count,
9905 selection.goal,
9906 false,
9907 text_layout_details,
9908 );
9909 selection.collapse_to(cursor, goal);
9910 });
9911 });
9912 }
9913
9914 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
9915 let text_layout_details = &self.text_layout_details(window);
9916 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9917 s.move_heads_with(|map, head, goal| {
9918 movement::up(map, head, goal, false, text_layout_details)
9919 })
9920 })
9921 }
9922
9923 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
9924 self.take_rename(true, window, cx);
9925
9926 if matches!(self.mode, EditorMode::SingleLine { .. }) {
9927 cx.propagate();
9928 return;
9929 }
9930
9931 let text_layout_details = &self.text_layout_details(window);
9932 let selection_count = self.selections.count();
9933 let first_selection = self.selections.first_anchor();
9934
9935 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9936 let line_mode = s.line_mode;
9937 s.move_with(|map, selection| {
9938 if !selection.is_empty() && !line_mode {
9939 selection.goal = SelectionGoal::None;
9940 }
9941 let (cursor, goal) = movement::down(
9942 map,
9943 selection.end,
9944 selection.goal,
9945 false,
9946 text_layout_details,
9947 );
9948 selection.collapse_to(cursor, goal);
9949 });
9950 });
9951
9952 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
9953 {
9954 cx.propagate();
9955 }
9956 }
9957
9958 pub fn select_page_down(
9959 &mut self,
9960 _: &SelectPageDown,
9961 window: &mut Window,
9962 cx: &mut Context<Self>,
9963 ) {
9964 let Some(row_count) = self.visible_row_count() else {
9965 return;
9966 };
9967
9968 let text_layout_details = &self.text_layout_details(window);
9969
9970 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9971 s.move_heads_with(|map, head, goal| {
9972 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
9973 })
9974 })
9975 }
9976
9977 pub fn move_page_down(
9978 &mut self,
9979 action: &MovePageDown,
9980 window: &mut Window,
9981 cx: &mut Context<Self>,
9982 ) {
9983 if self.take_rename(true, window, cx).is_some() {
9984 return;
9985 }
9986
9987 if self
9988 .context_menu
9989 .borrow_mut()
9990 .as_mut()
9991 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
9992 .unwrap_or(false)
9993 {
9994 return;
9995 }
9996
9997 if matches!(self.mode, EditorMode::SingleLine { .. }) {
9998 cx.propagate();
9999 return;
10000 }
10001
10002 let Some(row_count) = self.visible_row_count() else {
10003 return;
10004 };
10005
10006 let autoscroll = if action.center_cursor {
10007 Autoscroll::center()
10008 } else {
10009 Autoscroll::fit()
10010 };
10011
10012 let text_layout_details = &self.text_layout_details(window);
10013 self.change_selections(Some(autoscroll), window, cx, |s| {
10014 let line_mode = s.line_mode;
10015 s.move_with(|map, selection| {
10016 if !selection.is_empty() && !line_mode {
10017 selection.goal = SelectionGoal::None;
10018 }
10019 let (cursor, goal) = movement::down_by_rows(
10020 map,
10021 selection.end,
10022 row_count,
10023 selection.goal,
10024 false,
10025 text_layout_details,
10026 );
10027 selection.collapse_to(cursor, goal);
10028 });
10029 });
10030 }
10031
10032 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
10033 let text_layout_details = &self.text_layout_details(window);
10034 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10035 s.move_heads_with(|map, head, goal| {
10036 movement::down(map, head, goal, false, text_layout_details)
10037 })
10038 });
10039 }
10040
10041 pub fn context_menu_first(
10042 &mut self,
10043 _: &ContextMenuFirst,
10044 _window: &mut Window,
10045 cx: &mut Context<Self>,
10046 ) {
10047 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10048 context_menu.select_first(self.completion_provider.as_deref(), cx);
10049 }
10050 }
10051
10052 pub fn context_menu_prev(
10053 &mut self,
10054 _: &ContextMenuPrevious,
10055 _window: &mut Window,
10056 cx: &mut Context<Self>,
10057 ) {
10058 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10059 context_menu.select_prev(self.completion_provider.as_deref(), cx);
10060 }
10061 }
10062
10063 pub fn context_menu_next(
10064 &mut self,
10065 _: &ContextMenuNext,
10066 _window: &mut Window,
10067 cx: &mut Context<Self>,
10068 ) {
10069 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10070 context_menu.select_next(self.completion_provider.as_deref(), cx);
10071 }
10072 }
10073
10074 pub fn context_menu_last(
10075 &mut self,
10076 _: &ContextMenuLast,
10077 _window: &mut Window,
10078 cx: &mut Context<Self>,
10079 ) {
10080 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10081 context_menu.select_last(self.completion_provider.as_deref(), cx);
10082 }
10083 }
10084
10085 pub fn move_to_previous_word_start(
10086 &mut self,
10087 _: &MoveToPreviousWordStart,
10088 window: &mut Window,
10089 cx: &mut Context<Self>,
10090 ) {
10091 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10092 s.move_cursors_with(|map, head, _| {
10093 (
10094 movement::previous_word_start(map, head),
10095 SelectionGoal::None,
10096 )
10097 });
10098 })
10099 }
10100
10101 pub fn move_to_previous_subword_start(
10102 &mut self,
10103 _: &MoveToPreviousSubwordStart,
10104 window: &mut Window,
10105 cx: &mut Context<Self>,
10106 ) {
10107 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10108 s.move_cursors_with(|map, head, _| {
10109 (
10110 movement::previous_subword_start(map, head),
10111 SelectionGoal::None,
10112 )
10113 });
10114 })
10115 }
10116
10117 pub fn select_to_previous_word_start(
10118 &mut self,
10119 _: &SelectToPreviousWordStart,
10120 window: &mut Window,
10121 cx: &mut Context<Self>,
10122 ) {
10123 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10124 s.move_heads_with(|map, head, _| {
10125 (
10126 movement::previous_word_start(map, head),
10127 SelectionGoal::None,
10128 )
10129 });
10130 })
10131 }
10132
10133 pub fn select_to_previous_subword_start(
10134 &mut self,
10135 _: &SelectToPreviousSubwordStart,
10136 window: &mut Window,
10137 cx: &mut Context<Self>,
10138 ) {
10139 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10140 s.move_heads_with(|map, head, _| {
10141 (
10142 movement::previous_subword_start(map, head),
10143 SelectionGoal::None,
10144 )
10145 });
10146 })
10147 }
10148
10149 pub fn delete_to_previous_word_start(
10150 &mut self,
10151 action: &DeleteToPreviousWordStart,
10152 window: &mut Window,
10153 cx: &mut Context<Self>,
10154 ) {
10155 self.transact(window, cx, |this, window, cx| {
10156 this.select_autoclose_pair(window, cx);
10157 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10158 let line_mode = s.line_mode;
10159 s.move_with(|map, selection| {
10160 if selection.is_empty() && !line_mode {
10161 let cursor = if action.ignore_newlines {
10162 movement::previous_word_start(map, selection.head())
10163 } else {
10164 movement::previous_word_start_or_newline(map, selection.head())
10165 };
10166 selection.set_head(cursor, SelectionGoal::None);
10167 }
10168 });
10169 });
10170 this.insert("", window, cx);
10171 });
10172 }
10173
10174 pub fn delete_to_previous_subword_start(
10175 &mut self,
10176 _: &DeleteToPreviousSubwordStart,
10177 window: &mut Window,
10178 cx: &mut Context<Self>,
10179 ) {
10180 self.transact(window, cx, |this, window, cx| {
10181 this.select_autoclose_pair(window, cx);
10182 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10183 let line_mode = s.line_mode;
10184 s.move_with(|map, selection| {
10185 if selection.is_empty() && !line_mode {
10186 let cursor = movement::previous_subword_start(map, selection.head());
10187 selection.set_head(cursor, SelectionGoal::None);
10188 }
10189 });
10190 });
10191 this.insert("", window, cx);
10192 });
10193 }
10194
10195 pub fn move_to_next_word_end(
10196 &mut self,
10197 _: &MoveToNextWordEnd,
10198 window: &mut Window,
10199 cx: &mut Context<Self>,
10200 ) {
10201 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10202 s.move_cursors_with(|map, head, _| {
10203 (movement::next_word_end(map, head), SelectionGoal::None)
10204 });
10205 })
10206 }
10207
10208 pub fn move_to_next_subword_end(
10209 &mut self,
10210 _: &MoveToNextSubwordEnd,
10211 window: &mut Window,
10212 cx: &mut Context<Self>,
10213 ) {
10214 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10215 s.move_cursors_with(|map, head, _| {
10216 (movement::next_subword_end(map, head), SelectionGoal::None)
10217 });
10218 })
10219 }
10220
10221 pub fn select_to_next_word_end(
10222 &mut self,
10223 _: &SelectToNextWordEnd,
10224 window: &mut Window,
10225 cx: &mut Context<Self>,
10226 ) {
10227 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10228 s.move_heads_with(|map, head, _| {
10229 (movement::next_word_end(map, head), SelectionGoal::None)
10230 });
10231 })
10232 }
10233
10234 pub fn select_to_next_subword_end(
10235 &mut self,
10236 _: &SelectToNextSubwordEnd,
10237 window: &mut Window,
10238 cx: &mut Context<Self>,
10239 ) {
10240 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10241 s.move_heads_with(|map, head, _| {
10242 (movement::next_subword_end(map, head), SelectionGoal::None)
10243 });
10244 })
10245 }
10246
10247 pub fn delete_to_next_word_end(
10248 &mut self,
10249 action: &DeleteToNextWordEnd,
10250 window: &mut Window,
10251 cx: &mut Context<Self>,
10252 ) {
10253 self.transact(window, cx, |this, window, cx| {
10254 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10255 let line_mode = s.line_mode;
10256 s.move_with(|map, selection| {
10257 if selection.is_empty() && !line_mode {
10258 let cursor = if action.ignore_newlines {
10259 movement::next_word_end(map, selection.head())
10260 } else {
10261 movement::next_word_end_or_newline(map, selection.head())
10262 };
10263 selection.set_head(cursor, SelectionGoal::None);
10264 }
10265 });
10266 });
10267 this.insert("", window, cx);
10268 });
10269 }
10270
10271 pub fn delete_to_next_subword_end(
10272 &mut self,
10273 _: &DeleteToNextSubwordEnd,
10274 window: &mut Window,
10275 cx: &mut Context<Self>,
10276 ) {
10277 self.transact(window, cx, |this, window, cx| {
10278 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10279 s.move_with(|map, selection| {
10280 if selection.is_empty() {
10281 let cursor = movement::next_subword_end(map, selection.head());
10282 selection.set_head(cursor, SelectionGoal::None);
10283 }
10284 });
10285 });
10286 this.insert("", window, cx);
10287 });
10288 }
10289
10290 pub fn move_to_beginning_of_line(
10291 &mut self,
10292 action: &MoveToBeginningOfLine,
10293 window: &mut Window,
10294 cx: &mut Context<Self>,
10295 ) {
10296 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10297 s.move_cursors_with(|map, head, _| {
10298 (
10299 movement::indented_line_beginning(
10300 map,
10301 head,
10302 action.stop_at_soft_wraps,
10303 action.stop_at_indent,
10304 ),
10305 SelectionGoal::None,
10306 )
10307 });
10308 })
10309 }
10310
10311 pub fn select_to_beginning_of_line(
10312 &mut self,
10313 action: &SelectToBeginningOfLine,
10314 window: &mut Window,
10315 cx: &mut Context<Self>,
10316 ) {
10317 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10318 s.move_heads_with(|map, head, _| {
10319 (
10320 movement::indented_line_beginning(
10321 map,
10322 head,
10323 action.stop_at_soft_wraps,
10324 action.stop_at_indent,
10325 ),
10326 SelectionGoal::None,
10327 )
10328 });
10329 });
10330 }
10331
10332 pub fn delete_to_beginning_of_line(
10333 &mut self,
10334 action: &DeleteToBeginningOfLine,
10335 window: &mut Window,
10336 cx: &mut Context<Self>,
10337 ) {
10338 self.transact(window, cx, |this, window, cx| {
10339 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10340 s.move_with(|_, selection| {
10341 selection.reversed = true;
10342 });
10343 });
10344
10345 this.select_to_beginning_of_line(
10346 &SelectToBeginningOfLine {
10347 stop_at_soft_wraps: false,
10348 stop_at_indent: action.stop_at_indent,
10349 },
10350 window,
10351 cx,
10352 );
10353 this.backspace(&Backspace, window, cx);
10354 });
10355 }
10356
10357 pub fn move_to_end_of_line(
10358 &mut self,
10359 action: &MoveToEndOfLine,
10360 window: &mut Window,
10361 cx: &mut Context<Self>,
10362 ) {
10363 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10364 s.move_cursors_with(|map, head, _| {
10365 (
10366 movement::line_end(map, head, action.stop_at_soft_wraps),
10367 SelectionGoal::None,
10368 )
10369 });
10370 })
10371 }
10372
10373 pub fn select_to_end_of_line(
10374 &mut self,
10375 action: &SelectToEndOfLine,
10376 window: &mut Window,
10377 cx: &mut Context<Self>,
10378 ) {
10379 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10380 s.move_heads_with(|map, head, _| {
10381 (
10382 movement::line_end(map, head, action.stop_at_soft_wraps),
10383 SelectionGoal::None,
10384 )
10385 });
10386 })
10387 }
10388
10389 pub fn delete_to_end_of_line(
10390 &mut self,
10391 _: &DeleteToEndOfLine,
10392 window: &mut Window,
10393 cx: &mut Context<Self>,
10394 ) {
10395 self.transact(window, cx, |this, window, cx| {
10396 this.select_to_end_of_line(
10397 &SelectToEndOfLine {
10398 stop_at_soft_wraps: false,
10399 },
10400 window,
10401 cx,
10402 );
10403 this.delete(&Delete, window, cx);
10404 });
10405 }
10406
10407 pub fn cut_to_end_of_line(
10408 &mut self,
10409 _: &CutToEndOfLine,
10410 window: &mut Window,
10411 cx: &mut Context<Self>,
10412 ) {
10413 self.transact(window, cx, |this, window, cx| {
10414 this.select_to_end_of_line(
10415 &SelectToEndOfLine {
10416 stop_at_soft_wraps: false,
10417 },
10418 window,
10419 cx,
10420 );
10421 this.cut(&Cut, window, cx);
10422 });
10423 }
10424
10425 pub fn move_to_start_of_paragraph(
10426 &mut self,
10427 _: &MoveToStartOfParagraph,
10428 window: &mut Window,
10429 cx: &mut Context<Self>,
10430 ) {
10431 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10432 cx.propagate();
10433 return;
10434 }
10435
10436 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10437 s.move_with(|map, selection| {
10438 selection.collapse_to(
10439 movement::start_of_paragraph(map, selection.head(), 1),
10440 SelectionGoal::None,
10441 )
10442 });
10443 })
10444 }
10445
10446 pub fn move_to_end_of_paragraph(
10447 &mut self,
10448 _: &MoveToEndOfParagraph,
10449 window: &mut Window,
10450 cx: &mut Context<Self>,
10451 ) {
10452 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10453 cx.propagate();
10454 return;
10455 }
10456
10457 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10458 s.move_with(|map, selection| {
10459 selection.collapse_to(
10460 movement::end_of_paragraph(map, selection.head(), 1),
10461 SelectionGoal::None,
10462 )
10463 });
10464 })
10465 }
10466
10467 pub fn select_to_start_of_paragraph(
10468 &mut self,
10469 _: &SelectToStartOfParagraph,
10470 window: &mut Window,
10471 cx: &mut Context<Self>,
10472 ) {
10473 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10474 cx.propagate();
10475 return;
10476 }
10477
10478 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10479 s.move_heads_with(|map, head, _| {
10480 (
10481 movement::start_of_paragraph(map, head, 1),
10482 SelectionGoal::None,
10483 )
10484 });
10485 })
10486 }
10487
10488 pub fn select_to_end_of_paragraph(
10489 &mut self,
10490 _: &SelectToEndOfParagraph,
10491 window: &mut Window,
10492 cx: &mut Context<Self>,
10493 ) {
10494 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10495 cx.propagate();
10496 return;
10497 }
10498
10499 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10500 s.move_heads_with(|map, head, _| {
10501 (
10502 movement::end_of_paragraph(map, head, 1),
10503 SelectionGoal::None,
10504 )
10505 });
10506 })
10507 }
10508
10509 pub fn move_to_start_of_excerpt(
10510 &mut self,
10511 _: &MoveToStartOfExcerpt,
10512 window: &mut Window,
10513 cx: &mut Context<Self>,
10514 ) {
10515 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10516 cx.propagate();
10517 return;
10518 }
10519
10520 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10521 s.move_with(|map, selection| {
10522 selection.collapse_to(
10523 movement::start_of_excerpt(
10524 map,
10525 selection.head(),
10526 workspace::searchable::Direction::Prev,
10527 ),
10528 SelectionGoal::None,
10529 )
10530 });
10531 })
10532 }
10533
10534 pub fn move_to_start_of_next_excerpt(
10535 &mut self,
10536 _: &MoveToStartOfNextExcerpt,
10537 window: &mut Window,
10538 cx: &mut Context<Self>,
10539 ) {
10540 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10541 cx.propagate();
10542 return;
10543 }
10544
10545 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10546 s.move_with(|map, selection| {
10547 selection.collapse_to(
10548 movement::start_of_excerpt(
10549 map,
10550 selection.head(),
10551 workspace::searchable::Direction::Next,
10552 ),
10553 SelectionGoal::None,
10554 )
10555 });
10556 })
10557 }
10558
10559 pub fn move_to_end_of_excerpt(
10560 &mut self,
10561 _: &MoveToEndOfExcerpt,
10562 window: &mut Window,
10563 cx: &mut Context<Self>,
10564 ) {
10565 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10566 cx.propagate();
10567 return;
10568 }
10569
10570 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10571 s.move_with(|map, selection| {
10572 selection.collapse_to(
10573 movement::end_of_excerpt(
10574 map,
10575 selection.head(),
10576 workspace::searchable::Direction::Next,
10577 ),
10578 SelectionGoal::None,
10579 )
10580 });
10581 })
10582 }
10583
10584 pub fn move_to_end_of_previous_excerpt(
10585 &mut self,
10586 _: &MoveToEndOfPreviousExcerpt,
10587 window: &mut Window,
10588 cx: &mut Context<Self>,
10589 ) {
10590 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10591 cx.propagate();
10592 return;
10593 }
10594
10595 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10596 s.move_with(|map, selection| {
10597 selection.collapse_to(
10598 movement::end_of_excerpt(
10599 map,
10600 selection.head(),
10601 workspace::searchable::Direction::Prev,
10602 ),
10603 SelectionGoal::None,
10604 )
10605 });
10606 })
10607 }
10608
10609 pub fn select_to_start_of_excerpt(
10610 &mut self,
10611 _: &SelectToStartOfExcerpt,
10612 window: &mut Window,
10613 cx: &mut Context<Self>,
10614 ) {
10615 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10616 cx.propagate();
10617 return;
10618 }
10619
10620 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10621 s.move_heads_with(|map, head, _| {
10622 (
10623 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
10624 SelectionGoal::None,
10625 )
10626 });
10627 })
10628 }
10629
10630 pub fn select_to_start_of_next_excerpt(
10631 &mut self,
10632 _: &SelectToStartOfNextExcerpt,
10633 window: &mut Window,
10634 cx: &mut Context<Self>,
10635 ) {
10636 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10637 cx.propagate();
10638 return;
10639 }
10640
10641 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10642 s.move_heads_with(|map, head, _| {
10643 (
10644 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
10645 SelectionGoal::None,
10646 )
10647 });
10648 })
10649 }
10650
10651 pub fn select_to_end_of_excerpt(
10652 &mut self,
10653 _: &SelectToEndOfExcerpt,
10654 window: &mut Window,
10655 cx: &mut Context<Self>,
10656 ) {
10657 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10658 cx.propagate();
10659 return;
10660 }
10661
10662 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10663 s.move_heads_with(|map, head, _| {
10664 (
10665 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
10666 SelectionGoal::None,
10667 )
10668 });
10669 })
10670 }
10671
10672 pub fn select_to_end_of_previous_excerpt(
10673 &mut self,
10674 _: &SelectToEndOfPreviousExcerpt,
10675 window: &mut Window,
10676 cx: &mut Context<Self>,
10677 ) {
10678 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10679 cx.propagate();
10680 return;
10681 }
10682
10683 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10684 s.move_heads_with(|map, head, _| {
10685 (
10686 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
10687 SelectionGoal::None,
10688 )
10689 });
10690 })
10691 }
10692
10693 pub fn move_to_beginning(
10694 &mut self,
10695 _: &MoveToBeginning,
10696 window: &mut Window,
10697 cx: &mut Context<Self>,
10698 ) {
10699 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10700 cx.propagate();
10701 return;
10702 }
10703
10704 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10705 s.select_ranges(vec![0..0]);
10706 });
10707 }
10708
10709 pub fn select_to_beginning(
10710 &mut self,
10711 _: &SelectToBeginning,
10712 window: &mut Window,
10713 cx: &mut Context<Self>,
10714 ) {
10715 let mut selection = self.selections.last::<Point>(cx);
10716 selection.set_head(Point::zero(), SelectionGoal::None);
10717
10718 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10719 s.select(vec![selection]);
10720 });
10721 }
10722
10723 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
10724 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10725 cx.propagate();
10726 return;
10727 }
10728
10729 let cursor = self.buffer.read(cx).read(cx).len();
10730 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10731 s.select_ranges(vec![cursor..cursor])
10732 });
10733 }
10734
10735 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
10736 self.nav_history = nav_history;
10737 }
10738
10739 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
10740 self.nav_history.as_ref()
10741 }
10742
10743 fn push_to_nav_history(
10744 &mut self,
10745 cursor_anchor: Anchor,
10746 new_position: Option<Point>,
10747 cx: &mut Context<Self>,
10748 ) {
10749 if let Some(nav_history) = self.nav_history.as_mut() {
10750 let buffer = self.buffer.read(cx).read(cx);
10751 let cursor_position = cursor_anchor.to_point(&buffer);
10752 let scroll_state = self.scroll_manager.anchor();
10753 let scroll_top_row = scroll_state.top_row(&buffer);
10754 drop(buffer);
10755
10756 if let Some(new_position) = new_position {
10757 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
10758 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
10759 return;
10760 }
10761 }
10762
10763 nav_history.push(
10764 Some(NavigationData {
10765 cursor_anchor,
10766 cursor_position,
10767 scroll_anchor: scroll_state,
10768 scroll_top_row,
10769 }),
10770 cx,
10771 );
10772 }
10773 }
10774
10775 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
10776 let buffer = self.buffer.read(cx).snapshot(cx);
10777 let mut selection = self.selections.first::<usize>(cx);
10778 selection.set_head(buffer.len(), SelectionGoal::None);
10779 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10780 s.select(vec![selection]);
10781 });
10782 }
10783
10784 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
10785 let end = self.buffer.read(cx).read(cx).len();
10786 self.change_selections(None, window, cx, |s| {
10787 s.select_ranges(vec![0..end]);
10788 });
10789 }
10790
10791 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
10792 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10793 let mut selections = self.selections.all::<Point>(cx);
10794 let max_point = display_map.buffer_snapshot.max_point();
10795 for selection in &mut selections {
10796 let rows = selection.spanned_rows(true, &display_map);
10797 selection.start = Point::new(rows.start.0, 0);
10798 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
10799 selection.reversed = false;
10800 }
10801 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10802 s.select(selections);
10803 });
10804 }
10805
10806 pub fn split_selection_into_lines(
10807 &mut self,
10808 _: &SplitSelectionIntoLines,
10809 window: &mut Window,
10810 cx: &mut Context<Self>,
10811 ) {
10812 let selections = self
10813 .selections
10814 .all::<Point>(cx)
10815 .into_iter()
10816 .map(|selection| selection.start..selection.end)
10817 .collect::<Vec<_>>();
10818 self.unfold_ranges(&selections, true, true, cx);
10819
10820 let mut new_selection_ranges = Vec::new();
10821 {
10822 let buffer = self.buffer.read(cx).read(cx);
10823 for selection in selections {
10824 for row in selection.start.row..selection.end.row {
10825 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
10826 new_selection_ranges.push(cursor..cursor);
10827 }
10828
10829 let is_multiline_selection = selection.start.row != selection.end.row;
10830 // Don't insert last one if it's a multi-line selection ending at the start of a line,
10831 // so this action feels more ergonomic when paired with other selection operations
10832 let should_skip_last = is_multiline_selection && selection.end.column == 0;
10833 if !should_skip_last {
10834 new_selection_ranges.push(selection.end..selection.end);
10835 }
10836 }
10837 }
10838 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10839 s.select_ranges(new_selection_ranges);
10840 });
10841 }
10842
10843 pub fn add_selection_above(
10844 &mut self,
10845 _: &AddSelectionAbove,
10846 window: &mut Window,
10847 cx: &mut Context<Self>,
10848 ) {
10849 self.add_selection(true, window, cx);
10850 }
10851
10852 pub fn add_selection_below(
10853 &mut self,
10854 _: &AddSelectionBelow,
10855 window: &mut Window,
10856 cx: &mut Context<Self>,
10857 ) {
10858 self.add_selection(false, window, cx);
10859 }
10860
10861 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
10862 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10863 let mut selections = self.selections.all::<Point>(cx);
10864 let text_layout_details = self.text_layout_details(window);
10865 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
10866 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
10867 let range = oldest_selection.display_range(&display_map).sorted();
10868
10869 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
10870 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
10871 let positions = start_x.min(end_x)..start_x.max(end_x);
10872
10873 selections.clear();
10874 let mut stack = Vec::new();
10875 for row in range.start.row().0..=range.end.row().0 {
10876 if let Some(selection) = self.selections.build_columnar_selection(
10877 &display_map,
10878 DisplayRow(row),
10879 &positions,
10880 oldest_selection.reversed,
10881 &text_layout_details,
10882 ) {
10883 stack.push(selection.id);
10884 selections.push(selection);
10885 }
10886 }
10887
10888 if above {
10889 stack.reverse();
10890 }
10891
10892 AddSelectionsState { above, stack }
10893 });
10894
10895 let last_added_selection = *state.stack.last().unwrap();
10896 let mut new_selections = Vec::new();
10897 if above == state.above {
10898 let end_row = if above {
10899 DisplayRow(0)
10900 } else {
10901 display_map.max_point().row()
10902 };
10903
10904 'outer: for selection in selections {
10905 if selection.id == last_added_selection {
10906 let range = selection.display_range(&display_map).sorted();
10907 debug_assert_eq!(range.start.row(), range.end.row());
10908 let mut row = range.start.row();
10909 let positions =
10910 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
10911 px(start)..px(end)
10912 } else {
10913 let start_x =
10914 display_map.x_for_display_point(range.start, &text_layout_details);
10915 let end_x =
10916 display_map.x_for_display_point(range.end, &text_layout_details);
10917 start_x.min(end_x)..start_x.max(end_x)
10918 };
10919
10920 while row != end_row {
10921 if above {
10922 row.0 -= 1;
10923 } else {
10924 row.0 += 1;
10925 }
10926
10927 if let Some(new_selection) = self.selections.build_columnar_selection(
10928 &display_map,
10929 row,
10930 &positions,
10931 selection.reversed,
10932 &text_layout_details,
10933 ) {
10934 state.stack.push(new_selection.id);
10935 if above {
10936 new_selections.push(new_selection);
10937 new_selections.push(selection);
10938 } else {
10939 new_selections.push(selection);
10940 new_selections.push(new_selection);
10941 }
10942
10943 continue 'outer;
10944 }
10945 }
10946 }
10947
10948 new_selections.push(selection);
10949 }
10950 } else {
10951 new_selections = selections;
10952 new_selections.retain(|s| s.id != last_added_selection);
10953 state.stack.pop();
10954 }
10955
10956 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10957 s.select(new_selections);
10958 });
10959 if state.stack.len() > 1 {
10960 self.add_selections_state = Some(state);
10961 }
10962 }
10963
10964 pub fn select_next_match_internal(
10965 &mut self,
10966 display_map: &DisplaySnapshot,
10967 replace_newest: bool,
10968 autoscroll: Option<Autoscroll>,
10969 window: &mut Window,
10970 cx: &mut Context<Self>,
10971 ) -> Result<()> {
10972 fn select_next_match_ranges(
10973 this: &mut Editor,
10974 range: Range<usize>,
10975 replace_newest: bool,
10976 auto_scroll: Option<Autoscroll>,
10977 window: &mut Window,
10978 cx: &mut Context<Editor>,
10979 ) {
10980 this.unfold_ranges(&[range.clone()], false, true, cx);
10981 this.change_selections(auto_scroll, window, cx, |s| {
10982 if replace_newest {
10983 s.delete(s.newest_anchor().id);
10984 }
10985 s.insert_range(range.clone());
10986 });
10987 }
10988
10989 let buffer = &display_map.buffer_snapshot;
10990 let mut selections = self.selections.all::<usize>(cx);
10991 if let Some(mut select_next_state) = self.select_next_state.take() {
10992 let query = &select_next_state.query;
10993 if !select_next_state.done {
10994 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
10995 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
10996 let mut next_selected_range = None;
10997
10998 let bytes_after_last_selection =
10999 buffer.bytes_in_range(last_selection.end..buffer.len());
11000 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
11001 let query_matches = query
11002 .stream_find_iter(bytes_after_last_selection)
11003 .map(|result| (last_selection.end, result))
11004 .chain(
11005 query
11006 .stream_find_iter(bytes_before_first_selection)
11007 .map(|result| (0, result)),
11008 );
11009
11010 for (start_offset, query_match) in query_matches {
11011 let query_match = query_match.unwrap(); // can only fail due to I/O
11012 let offset_range =
11013 start_offset + query_match.start()..start_offset + query_match.end();
11014 let display_range = offset_range.start.to_display_point(display_map)
11015 ..offset_range.end.to_display_point(display_map);
11016
11017 if !select_next_state.wordwise
11018 || (!movement::is_inside_word(display_map, display_range.start)
11019 && !movement::is_inside_word(display_map, display_range.end))
11020 {
11021 // TODO: This is n^2, because we might check all the selections
11022 if !selections
11023 .iter()
11024 .any(|selection| selection.range().overlaps(&offset_range))
11025 {
11026 next_selected_range = Some(offset_range);
11027 break;
11028 }
11029 }
11030 }
11031
11032 if let Some(next_selected_range) = next_selected_range {
11033 select_next_match_ranges(
11034 self,
11035 next_selected_range,
11036 replace_newest,
11037 autoscroll,
11038 window,
11039 cx,
11040 );
11041 } else {
11042 select_next_state.done = true;
11043 }
11044 }
11045
11046 self.select_next_state = Some(select_next_state);
11047 } else {
11048 let mut only_carets = true;
11049 let mut same_text_selected = true;
11050 let mut selected_text = None;
11051
11052 let mut selections_iter = selections.iter().peekable();
11053 while let Some(selection) = selections_iter.next() {
11054 if selection.start != selection.end {
11055 only_carets = false;
11056 }
11057
11058 if same_text_selected {
11059 if selected_text.is_none() {
11060 selected_text =
11061 Some(buffer.text_for_range(selection.range()).collect::<String>());
11062 }
11063
11064 if let Some(next_selection) = selections_iter.peek() {
11065 if next_selection.range().len() == selection.range().len() {
11066 let next_selected_text = buffer
11067 .text_for_range(next_selection.range())
11068 .collect::<String>();
11069 if Some(next_selected_text) != selected_text {
11070 same_text_selected = false;
11071 selected_text = None;
11072 }
11073 } else {
11074 same_text_selected = false;
11075 selected_text = None;
11076 }
11077 }
11078 }
11079 }
11080
11081 if only_carets {
11082 for selection in &mut selections {
11083 let word_range = movement::surrounding_word(
11084 display_map,
11085 selection.start.to_display_point(display_map),
11086 );
11087 selection.start = word_range.start.to_offset(display_map, Bias::Left);
11088 selection.end = word_range.end.to_offset(display_map, Bias::Left);
11089 selection.goal = SelectionGoal::None;
11090 selection.reversed = false;
11091 select_next_match_ranges(
11092 self,
11093 selection.start..selection.end,
11094 replace_newest,
11095 autoscroll,
11096 window,
11097 cx,
11098 );
11099 }
11100
11101 if selections.len() == 1 {
11102 let selection = selections
11103 .last()
11104 .expect("ensured that there's only one selection");
11105 let query = buffer
11106 .text_for_range(selection.start..selection.end)
11107 .collect::<String>();
11108 let is_empty = query.is_empty();
11109 let select_state = SelectNextState {
11110 query: AhoCorasick::new(&[query])?,
11111 wordwise: true,
11112 done: is_empty,
11113 };
11114 self.select_next_state = Some(select_state);
11115 } else {
11116 self.select_next_state = None;
11117 }
11118 } else if let Some(selected_text) = selected_text {
11119 self.select_next_state = Some(SelectNextState {
11120 query: AhoCorasick::new(&[selected_text])?,
11121 wordwise: false,
11122 done: false,
11123 });
11124 self.select_next_match_internal(
11125 display_map,
11126 replace_newest,
11127 autoscroll,
11128 window,
11129 cx,
11130 )?;
11131 }
11132 }
11133 Ok(())
11134 }
11135
11136 pub fn select_all_matches(
11137 &mut self,
11138 _action: &SelectAllMatches,
11139 window: &mut Window,
11140 cx: &mut Context<Self>,
11141 ) -> Result<()> {
11142 self.push_to_selection_history();
11143 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11144
11145 self.select_next_match_internal(&display_map, false, None, window, cx)?;
11146 let Some(select_next_state) = self.select_next_state.as_mut() else {
11147 return Ok(());
11148 };
11149 if select_next_state.done {
11150 return Ok(());
11151 }
11152
11153 let mut new_selections = self.selections.all::<usize>(cx);
11154
11155 let buffer = &display_map.buffer_snapshot;
11156 let query_matches = select_next_state
11157 .query
11158 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
11159
11160 for query_match in query_matches {
11161 let query_match = query_match.unwrap(); // can only fail due to I/O
11162 let offset_range = query_match.start()..query_match.end();
11163 let display_range = offset_range.start.to_display_point(&display_map)
11164 ..offset_range.end.to_display_point(&display_map);
11165
11166 if !select_next_state.wordwise
11167 || (!movement::is_inside_word(&display_map, display_range.start)
11168 && !movement::is_inside_word(&display_map, display_range.end))
11169 {
11170 self.selections.change_with(cx, |selections| {
11171 new_selections.push(Selection {
11172 id: selections.new_selection_id(),
11173 start: offset_range.start,
11174 end: offset_range.end,
11175 reversed: false,
11176 goal: SelectionGoal::None,
11177 });
11178 });
11179 }
11180 }
11181
11182 new_selections.sort_by_key(|selection| selection.start);
11183 let mut ix = 0;
11184 while ix + 1 < new_selections.len() {
11185 let current_selection = &new_selections[ix];
11186 let next_selection = &new_selections[ix + 1];
11187 if current_selection.range().overlaps(&next_selection.range()) {
11188 if current_selection.id < next_selection.id {
11189 new_selections.remove(ix + 1);
11190 } else {
11191 new_selections.remove(ix);
11192 }
11193 } else {
11194 ix += 1;
11195 }
11196 }
11197
11198 let reversed = self.selections.oldest::<usize>(cx).reversed;
11199
11200 for selection in new_selections.iter_mut() {
11201 selection.reversed = reversed;
11202 }
11203
11204 select_next_state.done = true;
11205 self.unfold_ranges(
11206 &new_selections
11207 .iter()
11208 .map(|selection| selection.range())
11209 .collect::<Vec<_>>(),
11210 false,
11211 false,
11212 cx,
11213 );
11214 self.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
11215 selections.select(new_selections)
11216 });
11217
11218 Ok(())
11219 }
11220
11221 pub fn select_next(
11222 &mut self,
11223 action: &SelectNext,
11224 window: &mut Window,
11225 cx: &mut Context<Self>,
11226 ) -> Result<()> {
11227 self.push_to_selection_history();
11228 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11229 self.select_next_match_internal(
11230 &display_map,
11231 action.replace_newest,
11232 Some(Autoscroll::newest()),
11233 window,
11234 cx,
11235 )?;
11236 Ok(())
11237 }
11238
11239 pub fn select_previous(
11240 &mut self,
11241 action: &SelectPrevious,
11242 window: &mut Window,
11243 cx: &mut Context<Self>,
11244 ) -> Result<()> {
11245 self.push_to_selection_history();
11246 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11247 let buffer = &display_map.buffer_snapshot;
11248 let mut selections = self.selections.all::<usize>(cx);
11249 if let Some(mut select_prev_state) = self.select_prev_state.take() {
11250 let query = &select_prev_state.query;
11251 if !select_prev_state.done {
11252 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11253 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11254 let mut next_selected_range = None;
11255 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
11256 let bytes_before_last_selection =
11257 buffer.reversed_bytes_in_range(0..last_selection.start);
11258 let bytes_after_first_selection =
11259 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
11260 let query_matches = query
11261 .stream_find_iter(bytes_before_last_selection)
11262 .map(|result| (last_selection.start, result))
11263 .chain(
11264 query
11265 .stream_find_iter(bytes_after_first_selection)
11266 .map(|result| (buffer.len(), result)),
11267 );
11268 for (end_offset, query_match) in query_matches {
11269 let query_match = query_match.unwrap(); // can only fail due to I/O
11270 let offset_range =
11271 end_offset - query_match.end()..end_offset - query_match.start();
11272 let display_range = offset_range.start.to_display_point(&display_map)
11273 ..offset_range.end.to_display_point(&display_map);
11274
11275 if !select_prev_state.wordwise
11276 || (!movement::is_inside_word(&display_map, display_range.start)
11277 && !movement::is_inside_word(&display_map, display_range.end))
11278 {
11279 next_selected_range = Some(offset_range);
11280 break;
11281 }
11282 }
11283
11284 if let Some(next_selected_range) = next_selected_range {
11285 self.unfold_ranges(&[next_selected_range.clone()], false, true, cx);
11286 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
11287 if action.replace_newest {
11288 s.delete(s.newest_anchor().id);
11289 }
11290 s.insert_range(next_selected_range);
11291 });
11292 } else {
11293 select_prev_state.done = true;
11294 }
11295 }
11296
11297 self.select_prev_state = Some(select_prev_state);
11298 } else {
11299 let mut only_carets = true;
11300 let mut same_text_selected = true;
11301 let mut selected_text = None;
11302
11303 let mut selections_iter = selections.iter().peekable();
11304 while let Some(selection) = selections_iter.next() {
11305 if selection.start != selection.end {
11306 only_carets = false;
11307 }
11308
11309 if same_text_selected {
11310 if selected_text.is_none() {
11311 selected_text =
11312 Some(buffer.text_for_range(selection.range()).collect::<String>());
11313 }
11314
11315 if let Some(next_selection) = selections_iter.peek() {
11316 if next_selection.range().len() == selection.range().len() {
11317 let next_selected_text = buffer
11318 .text_for_range(next_selection.range())
11319 .collect::<String>();
11320 if Some(next_selected_text) != selected_text {
11321 same_text_selected = false;
11322 selected_text = None;
11323 }
11324 } else {
11325 same_text_selected = false;
11326 selected_text = None;
11327 }
11328 }
11329 }
11330 }
11331
11332 if only_carets {
11333 for selection in &mut selections {
11334 let word_range = movement::surrounding_word(
11335 &display_map,
11336 selection.start.to_display_point(&display_map),
11337 );
11338 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
11339 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
11340 selection.goal = SelectionGoal::None;
11341 selection.reversed = false;
11342 }
11343 if selections.len() == 1 {
11344 let selection = selections
11345 .last()
11346 .expect("ensured that there's only one selection");
11347 let query = buffer
11348 .text_for_range(selection.start..selection.end)
11349 .collect::<String>();
11350 let is_empty = query.is_empty();
11351 let select_state = SelectNextState {
11352 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
11353 wordwise: true,
11354 done: is_empty,
11355 };
11356 self.select_prev_state = Some(select_state);
11357 } else {
11358 self.select_prev_state = None;
11359 }
11360
11361 self.unfold_ranges(
11362 &selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
11363 false,
11364 true,
11365 cx,
11366 );
11367 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
11368 s.select(selections);
11369 });
11370 } else if let Some(selected_text) = selected_text {
11371 self.select_prev_state = Some(SelectNextState {
11372 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
11373 wordwise: false,
11374 done: false,
11375 });
11376 self.select_previous(action, window, cx)?;
11377 }
11378 }
11379 Ok(())
11380 }
11381
11382 pub fn toggle_comments(
11383 &mut self,
11384 action: &ToggleComments,
11385 window: &mut Window,
11386 cx: &mut Context<Self>,
11387 ) {
11388 if self.read_only(cx) {
11389 return;
11390 }
11391 let text_layout_details = &self.text_layout_details(window);
11392 self.transact(window, cx, |this, window, cx| {
11393 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
11394 let mut edits = Vec::new();
11395 let mut selection_edit_ranges = Vec::new();
11396 let mut last_toggled_row = None;
11397 let snapshot = this.buffer.read(cx).read(cx);
11398 let empty_str: Arc<str> = Arc::default();
11399 let mut suffixes_inserted = Vec::new();
11400 let ignore_indent = action.ignore_indent;
11401
11402 fn comment_prefix_range(
11403 snapshot: &MultiBufferSnapshot,
11404 row: MultiBufferRow,
11405 comment_prefix: &str,
11406 comment_prefix_whitespace: &str,
11407 ignore_indent: bool,
11408 ) -> Range<Point> {
11409 let indent_size = if ignore_indent {
11410 0
11411 } else {
11412 snapshot.indent_size_for_line(row).len
11413 };
11414
11415 let start = Point::new(row.0, indent_size);
11416
11417 let mut line_bytes = snapshot
11418 .bytes_in_range(start..snapshot.max_point())
11419 .flatten()
11420 .copied();
11421
11422 // If this line currently begins with the line comment prefix, then record
11423 // the range containing the prefix.
11424 if line_bytes
11425 .by_ref()
11426 .take(comment_prefix.len())
11427 .eq(comment_prefix.bytes())
11428 {
11429 // Include any whitespace that matches the comment prefix.
11430 let matching_whitespace_len = line_bytes
11431 .zip(comment_prefix_whitespace.bytes())
11432 .take_while(|(a, b)| a == b)
11433 .count() as u32;
11434 let end = Point::new(
11435 start.row,
11436 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
11437 );
11438 start..end
11439 } else {
11440 start..start
11441 }
11442 }
11443
11444 fn comment_suffix_range(
11445 snapshot: &MultiBufferSnapshot,
11446 row: MultiBufferRow,
11447 comment_suffix: &str,
11448 comment_suffix_has_leading_space: bool,
11449 ) -> Range<Point> {
11450 let end = Point::new(row.0, snapshot.line_len(row));
11451 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
11452
11453 let mut line_end_bytes = snapshot
11454 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
11455 .flatten()
11456 .copied();
11457
11458 let leading_space_len = if suffix_start_column > 0
11459 && line_end_bytes.next() == Some(b' ')
11460 && comment_suffix_has_leading_space
11461 {
11462 1
11463 } else {
11464 0
11465 };
11466
11467 // If this line currently begins with the line comment prefix, then record
11468 // the range containing the prefix.
11469 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
11470 let start = Point::new(end.row, suffix_start_column - leading_space_len);
11471 start..end
11472 } else {
11473 end..end
11474 }
11475 }
11476
11477 // TODO: Handle selections that cross excerpts
11478 for selection in &mut selections {
11479 let start_column = snapshot
11480 .indent_size_for_line(MultiBufferRow(selection.start.row))
11481 .len;
11482 let language = if let Some(language) =
11483 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
11484 {
11485 language
11486 } else {
11487 continue;
11488 };
11489
11490 selection_edit_ranges.clear();
11491
11492 // If multiple selections contain a given row, avoid processing that
11493 // row more than once.
11494 let mut start_row = MultiBufferRow(selection.start.row);
11495 if last_toggled_row == Some(start_row) {
11496 start_row = start_row.next_row();
11497 }
11498 let end_row =
11499 if selection.end.row > selection.start.row && selection.end.column == 0 {
11500 MultiBufferRow(selection.end.row - 1)
11501 } else {
11502 MultiBufferRow(selection.end.row)
11503 };
11504 last_toggled_row = Some(end_row);
11505
11506 if start_row > end_row {
11507 continue;
11508 }
11509
11510 // If the language has line comments, toggle those.
11511 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
11512
11513 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
11514 if ignore_indent {
11515 full_comment_prefixes = full_comment_prefixes
11516 .into_iter()
11517 .map(|s| Arc::from(s.trim_end()))
11518 .collect();
11519 }
11520
11521 if !full_comment_prefixes.is_empty() {
11522 let first_prefix = full_comment_prefixes
11523 .first()
11524 .expect("prefixes is non-empty");
11525 let prefix_trimmed_lengths = full_comment_prefixes
11526 .iter()
11527 .map(|p| p.trim_end_matches(' ').len())
11528 .collect::<SmallVec<[usize; 4]>>();
11529
11530 let mut all_selection_lines_are_comments = true;
11531
11532 for row in start_row.0..=end_row.0 {
11533 let row = MultiBufferRow(row);
11534 if start_row < end_row && snapshot.is_line_blank(row) {
11535 continue;
11536 }
11537
11538 let prefix_range = full_comment_prefixes
11539 .iter()
11540 .zip(prefix_trimmed_lengths.iter().copied())
11541 .map(|(prefix, trimmed_prefix_len)| {
11542 comment_prefix_range(
11543 snapshot.deref(),
11544 row,
11545 &prefix[..trimmed_prefix_len],
11546 &prefix[trimmed_prefix_len..],
11547 ignore_indent,
11548 )
11549 })
11550 .max_by_key(|range| range.end.column - range.start.column)
11551 .expect("prefixes is non-empty");
11552
11553 if prefix_range.is_empty() {
11554 all_selection_lines_are_comments = false;
11555 }
11556
11557 selection_edit_ranges.push(prefix_range);
11558 }
11559
11560 if all_selection_lines_are_comments {
11561 edits.extend(
11562 selection_edit_ranges
11563 .iter()
11564 .cloned()
11565 .map(|range| (range, empty_str.clone())),
11566 );
11567 } else {
11568 let min_column = selection_edit_ranges
11569 .iter()
11570 .map(|range| range.start.column)
11571 .min()
11572 .unwrap_or(0);
11573 edits.extend(selection_edit_ranges.iter().map(|range| {
11574 let position = Point::new(range.start.row, min_column);
11575 (position..position, first_prefix.clone())
11576 }));
11577 }
11578 } else if let Some((full_comment_prefix, comment_suffix)) =
11579 language.block_comment_delimiters()
11580 {
11581 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
11582 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
11583 let prefix_range = comment_prefix_range(
11584 snapshot.deref(),
11585 start_row,
11586 comment_prefix,
11587 comment_prefix_whitespace,
11588 ignore_indent,
11589 );
11590 let suffix_range = comment_suffix_range(
11591 snapshot.deref(),
11592 end_row,
11593 comment_suffix.trim_start_matches(' '),
11594 comment_suffix.starts_with(' '),
11595 );
11596
11597 if prefix_range.is_empty() || suffix_range.is_empty() {
11598 edits.push((
11599 prefix_range.start..prefix_range.start,
11600 full_comment_prefix.clone(),
11601 ));
11602 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
11603 suffixes_inserted.push((end_row, comment_suffix.len()));
11604 } else {
11605 edits.push((prefix_range, empty_str.clone()));
11606 edits.push((suffix_range, empty_str.clone()));
11607 }
11608 } else {
11609 continue;
11610 }
11611 }
11612
11613 drop(snapshot);
11614 this.buffer.update(cx, |buffer, cx| {
11615 buffer.edit(edits, None, cx);
11616 });
11617
11618 // Adjust selections so that they end before any comment suffixes that
11619 // were inserted.
11620 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
11621 let mut selections = this.selections.all::<Point>(cx);
11622 let snapshot = this.buffer.read(cx).read(cx);
11623 for selection in &mut selections {
11624 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
11625 match row.cmp(&MultiBufferRow(selection.end.row)) {
11626 Ordering::Less => {
11627 suffixes_inserted.next();
11628 continue;
11629 }
11630 Ordering::Greater => break,
11631 Ordering::Equal => {
11632 if selection.end.column == snapshot.line_len(row) {
11633 if selection.is_empty() {
11634 selection.start.column -= suffix_len as u32;
11635 }
11636 selection.end.column -= suffix_len as u32;
11637 }
11638 break;
11639 }
11640 }
11641 }
11642 }
11643
11644 drop(snapshot);
11645 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11646 s.select(selections)
11647 });
11648
11649 let selections = this.selections.all::<Point>(cx);
11650 let selections_on_single_row = selections.windows(2).all(|selections| {
11651 selections[0].start.row == selections[1].start.row
11652 && selections[0].end.row == selections[1].end.row
11653 && selections[0].start.row == selections[0].end.row
11654 });
11655 let selections_selecting = selections
11656 .iter()
11657 .any(|selection| selection.start != selection.end);
11658 let advance_downwards = action.advance_downwards
11659 && selections_on_single_row
11660 && !selections_selecting
11661 && !matches!(this.mode, EditorMode::SingleLine { .. });
11662
11663 if advance_downwards {
11664 let snapshot = this.buffer.read(cx).snapshot(cx);
11665
11666 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11667 s.move_cursors_with(|display_snapshot, display_point, _| {
11668 let mut point = display_point.to_point(display_snapshot);
11669 point.row += 1;
11670 point = snapshot.clip_point(point, Bias::Left);
11671 let display_point = point.to_display_point(display_snapshot);
11672 let goal = SelectionGoal::HorizontalPosition(
11673 display_snapshot
11674 .x_for_display_point(display_point, text_layout_details)
11675 .into(),
11676 );
11677 (display_point, goal)
11678 })
11679 });
11680 }
11681 });
11682 }
11683
11684 pub fn select_enclosing_symbol(
11685 &mut self,
11686 _: &SelectEnclosingSymbol,
11687 window: &mut Window,
11688 cx: &mut Context<Self>,
11689 ) {
11690 let buffer = self.buffer.read(cx).snapshot(cx);
11691 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
11692
11693 fn update_selection(
11694 selection: &Selection<usize>,
11695 buffer_snap: &MultiBufferSnapshot,
11696 ) -> Option<Selection<usize>> {
11697 let cursor = selection.head();
11698 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
11699 for symbol in symbols.iter().rev() {
11700 let start = symbol.range.start.to_offset(buffer_snap);
11701 let end = symbol.range.end.to_offset(buffer_snap);
11702 let new_range = start..end;
11703 if start < selection.start || end > selection.end {
11704 return Some(Selection {
11705 id: selection.id,
11706 start: new_range.start,
11707 end: new_range.end,
11708 goal: SelectionGoal::None,
11709 reversed: selection.reversed,
11710 });
11711 }
11712 }
11713 None
11714 }
11715
11716 let mut selected_larger_symbol = false;
11717 let new_selections = old_selections
11718 .iter()
11719 .map(|selection| match update_selection(selection, &buffer) {
11720 Some(new_selection) => {
11721 if new_selection.range() != selection.range() {
11722 selected_larger_symbol = true;
11723 }
11724 new_selection
11725 }
11726 None => selection.clone(),
11727 })
11728 .collect::<Vec<_>>();
11729
11730 if selected_larger_symbol {
11731 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11732 s.select(new_selections);
11733 });
11734 }
11735 }
11736
11737 pub fn select_larger_syntax_node(
11738 &mut self,
11739 _: &SelectLargerSyntaxNode,
11740 window: &mut Window,
11741 cx: &mut Context<Self>,
11742 ) {
11743 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11744 let buffer = self.buffer.read(cx).snapshot(cx);
11745 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
11746
11747 let mut stack = mem::take(&mut self.select_larger_syntax_node_stack);
11748 let mut selected_larger_node = false;
11749 let new_selections = old_selections
11750 .iter()
11751 .map(|selection| {
11752 let old_range = selection.start..selection.end;
11753 let mut new_range = old_range.clone();
11754 let mut new_node = None;
11755 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
11756 {
11757 new_node = Some(node);
11758 new_range = match containing_range {
11759 MultiOrSingleBufferOffsetRange::Single(_) => break,
11760 MultiOrSingleBufferOffsetRange::Multi(range) => range,
11761 };
11762 if !display_map.intersects_fold(new_range.start)
11763 && !display_map.intersects_fold(new_range.end)
11764 {
11765 break;
11766 }
11767 }
11768
11769 if let Some(node) = new_node {
11770 // Log the ancestor, to support using this action as a way to explore TreeSitter
11771 // nodes. Parent and grandparent are also logged because this operation will not
11772 // visit nodes that have the same range as their parent.
11773 log::info!("Node: {node:?}");
11774 let parent = node.parent();
11775 log::info!("Parent: {parent:?}");
11776 let grandparent = parent.and_then(|x| x.parent());
11777 log::info!("Grandparent: {grandparent:?}");
11778 }
11779
11780 selected_larger_node |= new_range != old_range;
11781 Selection {
11782 id: selection.id,
11783 start: new_range.start,
11784 end: new_range.end,
11785 goal: SelectionGoal::None,
11786 reversed: selection.reversed,
11787 }
11788 })
11789 .collect::<Vec<_>>();
11790
11791 if selected_larger_node {
11792 stack.push(old_selections);
11793 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11794 s.select(new_selections);
11795 });
11796 }
11797 self.select_larger_syntax_node_stack = stack;
11798 }
11799
11800 pub fn select_smaller_syntax_node(
11801 &mut self,
11802 _: &SelectSmallerSyntaxNode,
11803 window: &mut Window,
11804 cx: &mut Context<Self>,
11805 ) {
11806 let mut stack = mem::take(&mut self.select_larger_syntax_node_stack);
11807 if let Some(selections) = stack.pop() {
11808 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11809 s.select(selections.to_vec());
11810 });
11811 }
11812 self.select_larger_syntax_node_stack = stack;
11813 }
11814
11815 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
11816 if !EditorSettings::get_global(cx).gutter.runnables {
11817 self.clear_tasks();
11818 return Task::ready(());
11819 }
11820 let project = self.project.as_ref().map(Entity::downgrade);
11821 cx.spawn_in(window, async move |this, cx| {
11822 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
11823 let Some(project) = project.and_then(|p| p.upgrade()) else {
11824 return;
11825 };
11826 let Ok(display_snapshot) = this.update(cx, |this, cx| {
11827 this.display_map.update(cx, |map, cx| map.snapshot(cx))
11828 }) else {
11829 return;
11830 };
11831
11832 let hide_runnables = project
11833 .update(cx, |project, cx| {
11834 // Do not display any test indicators in non-dev server remote projects.
11835 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
11836 })
11837 .unwrap_or(true);
11838 if hide_runnables {
11839 return;
11840 }
11841 let new_rows =
11842 cx.background_spawn({
11843 let snapshot = display_snapshot.clone();
11844 async move {
11845 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
11846 }
11847 })
11848 .await;
11849
11850 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
11851 this.update(cx, |this, _| {
11852 this.clear_tasks();
11853 for (key, value) in rows {
11854 this.insert_tasks(key, value);
11855 }
11856 })
11857 .ok();
11858 })
11859 }
11860 fn fetch_runnable_ranges(
11861 snapshot: &DisplaySnapshot,
11862 range: Range<Anchor>,
11863 ) -> Vec<language::RunnableRange> {
11864 snapshot.buffer_snapshot.runnable_ranges(range).collect()
11865 }
11866
11867 fn runnable_rows(
11868 project: Entity<Project>,
11869 snapshot: DisplaySnapshot,
11870 runnable_ranges: Vec<RunnableRange>,
11871 mut cx: AsyncWindowContext,
11872 ) -> Vec<((BufferId, u32), RunnableTasks)> {
11873 runnable_ranges
11874 .into_iter()
11875 .filter_map(|mut runnable| {
11876 let tasks = cx
11877 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
11878 .ok()?;
11879 if tasks.is_empty() {
11880 return None;
11881 }
11882
11883 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
11884
11885 let row = snapshot
11886 .buffer_snapshot
11887 .buffer_line_for_row(MultiBufferRow(point.row))?
11888 .1
11889 .start
11890 .row;
11891
11892 let context_range =
11893 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
11894 Some((
11895 (runnable.buffer_id, row),
11896 RunnableTasks {
11897 templates: tasks,
11898 offset: snapshot
11899 .buffer_snapshot
11900 .anchor_before(runnable.run_range.start),
11901 context_range,
11902 column: point.column,
11903 extra_variables: runnable.extra_captures,
11904 },
11905 ))
11906 })
11907 .collect()
11908 }
11909
11910 fn templates_with_tags(
11911 project: &Entity<Project>,
11912 runnable: &mut Runnable,
11913 cx: &mut App,
11914 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
11915 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
11916 let (worktree_id, file) = project
11917 .buffer_for_id(runnable.buffer, cx)
11918 .and_then(|buffer| buffer.read(cx).file())
11919 .map(|file| (file.worktree_id(cx), file.clone()))
11920 .unzip();
11921
11922 (
11923 project.task_store().read(cx).task_inventory().cloned(),
11924 worktree_id,
11925 file,
11926 )
11927 });
11928
11929 let tags = mem::take(&mut runnable.tags);
11930 let mut tags: Vec<_> = tags
11931 .into_iter()
11932 .flat_map(|tag| {
11933 let tag = tag.0.clone();
11934 inventory
11935 .as_ref()
11936 .into_iter()
11937 .flat_map(|inventory| {
11938 inventory.read(cx).list_tasks(
11939 file.clone(),
11940 Some(runnable.language.clone()),
11941 worktree_id,
11942 cx,
11943 )
11944 })
11945 .filter(move |(_, template)| {
11946 template.tags.iter().any(|source_tag| source_tag == &tag)
11947 })
11948 })
11949 .sorted_by_key(|(kind, _)| kind.to_owned())
11950 .collect();
11951 if let Some((leading_tag_source, _)) = tags.first() {
11952 // Strongest source wins; if we have worktree tag binding, prefer that to
11953 // global and language bindings;
11954 // if we have a global binding, prefer that to language binding.
11955 let first_mismatch = tags
11956 .iter()
11957 .position(|(tag_source, _)| tag_source != leading_tag_source);
11958 if let Some(index) = first_mismatch {
11959 tags.truncate(index);
11960 }
11961 }
11962
11963 tags
11964 }
11965
11966 pub fn move_to_enclosing_bracket(
11967 &mut self,
11968 _: &MoveToEnclosingBracket,
11969 window: &mut Window,
11970 cx: &mut Context<Self>,
11971 ) {
11972 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11973 s.move_offsets_with(|snapshot, selection| {
11974 let Some(enclosing_bracket_ranges) =
11975 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
11976 else {
11977 return;
11978 };
11979
11980 let mut best_length = usize::MAX;
11981 let mut best_inside = false;
11982 let mut best_in_bracket_range = false;
11983 let mut best_destination = None;
11984 for (open, close) in enclosing_bracket_ranges {
11985 let close = close.to_inclusive();
11986 let length = close.end() - open.start;
11987 let inside = selection.start >= open.end && selection.end <= *close.start();
11988 let in_bracket_range = open.to_inclusive().contains(&selection.head())
11989 || close.contains(&selection.head());
11990
11991 // If best is next to a bracket and current isn't, skip
11992 if !in_bracket_range && best_in_bracket_range {
11993 continue;
11994 }
11995
11996 // Prefer smaller lengths unless best is inside and current isn't
11997 if length > best_length && (best_inside || !inside) {
11998 continue;
11999 }
12000
12001 best_length = length;
12002 best_inside = inside;
12003 best_in_bracket_range = in_bracket_range;
12004 best_destination = Some(
12005 if close.contains(&selection.start) && close.contains(&selection.end) {
12006 if inside {
12007 open.end
12008 } else {
12009 open.start
12010 }
12011 } else if inside {
12012 *close.start()
12013 } else {
12014 *close.end()
12015 },
12016 );
12017 }
12018
12019 if let Some(destination) = best_destination {
12020 selection.collapse_to(destination, SelectionGoal::None);
12021 }
12022 })
12023 });
12024 }
12025
12026 pub fn undo_selection(
12027 &mut self,
12028 _: &UndoSelection,
12029 window: &mut Window,
12030 cx: &mut Context<Self>,
12031 ) {
12032 self.end_selection(window, cx);
12033 self.selection_history.mode = SelectionHistoryMode::Undoing;
12034 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
12035 self.change_selections(None, window, cx, |s| {
12036 s.select_anchors(entry.selections.to_vec())
12037 });
12038 self.select_next_state = entry.select_next_state;
12039 self.select_prev_state = entry.select_prev_state;
12040 self.add_selections_state = entry.add_selections_state;
12041 self.request_autoscroll(Autoscroll::newest(), cx);
12042 }
12043 self.selection_history.mode = SelectionHistoryMode::Normal;
12044 }
12045
12046 pub fn redo_selection(
12047 &mut self,
12048 _: &RedoSelection,
12049 window: &mut Window,
12050 cx: &mut Context<Self>,
12051 ) {
12052 self.end_selection(window, cx);
12053 self.selection_history.mode = SelectionHistoryMode::Redoing;
12054 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
12055 self.change_selections(None, window, cx, |s| {
12056 s.select_anchors(entry.selections.to_vec())
12057 });
12058 self.select_next_state = entry.select_next_state;
12059 self.select_prev_state = entry.select_prev_state;
12060 self.add_selections_state = entry.add_selections_state;
12061 self.request_autoscroll(Autoscroll::newest(), cx);
12062 }
12063 self.selection_history.mode = SelectionHistoryMode::Normal;
12064 }
12065
12066 pub fn expand_excerpts(
12067 &mut self,
12068 action: &ExpandExcerpts,
12069 _: &mut Window,
12070 cx: &mut Context<Self>,
12071 ) {
12072 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
12073 }
12074
12075 pub fn expand_excerpts_down(
12076 &mut self,
12077 action: &ExpandExcerptsDown,
12078 _: &mut Window,
12079 cx: &mut Context<Self>,
12080 ) {
12081 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
12082 }
12083
12084 pub fn expand_excerpts_up(
12085 &mut self,
12086 action: &ExpandExcerptsUp,
12087 _: &mut Window,
12088 cx: &mut Context<Self>,
12089 ) {
12090 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
12091 }
12092
12093 pub fn expand_excerpts_for_direction(
12094 &mut self,
12095 lines: u32,
12096 direction: ExpandExcerptDirection,
12097
12098 cx: &mut Context<Self>,
12099 ) {
12100 let selections = self.selections.disjoint_anchors();
12101
12102 let lines = if lines == 0 {
12103 EditorSettings::get_global(cx).expand_excerpt_lines
12104 } else {
12105 lines
12106 };
12107
12108 self.buffer.update(cx, |buffer, cx| {
12109 let snapshot = buffer.snapshot(cx);
12110 let mut excerpt_ids = selections
12111 .iter()
12112 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
12113 .collect::<Vec<_>>();
12114 excerpt_ids.sort();
12115 excerpt_ids.dedup();
12116 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
12117 })
12118 }
12119
12120 pub fn expand_excerpt(
12121 &mut self,
12122 excerpt: ExcerptId,
12123 direction: ExpandExcerptDirection,
12124 window: &mut Window,
12125 cx: &mut Context<Self>,
12126 ) {
12127 let current_scroll_position = self.scroll_position(cx);
12128 let lines = EditorSettings::get_global(cx).expand_excerpt_lines;
12129 self.buffer.update(cx, |buffer, cx| {
12130 buffer.expand_excerpts([excerpt], lines, direction, cx)
12131 });
12132 if direction == ExpandExcerptDirection::Down {
12133 let new_scroll_position = current_scroll_position + gpui::Point::new(0.0, lines as f32);
12134 self.set_scroll_position(new_scroll_position, window, cx);
12135 }
12136 }
12137
12138 pub fn go_to_singleton_buffer_point(
12139 &mut self,
12140 point: Point,
12141 window: &mut Window,
12142 cx: &mut Context<Self>,
12143 ) {
12144 self.go_to_singleton_buffer_range(point..point, window, cx);
12145 }
12146
12147 pub fn go_to_singleton_buffer_range(
12148 &mut self,
12149 range: Range<Point>,
12150 window: &mut Window,
12151 cx: &mut Context<Self>,
12152 ) {
12153 let multibuffer = self.buffer().read(cx);
12154 let Some(buffer) = multibuffer.as_singleton() else {
12155 return;
12156 };
12157 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
12158 return;
12159 };
12160 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
12161 return;
12162 };
12163 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
12164 s.select_anchor_ranges([start..end])
12165 });
12166 }
12167
12168 fn go_to_diagnostic(
12169 &mut self,
12170 _: &GoToDiagnostic,
12171 window: &mut Window,
12172 cx: &mut Context<Self>,
12173 ) {
12174 self.go_to_diagnostic_impl(Direction::Next, window, cx)
12175 }
12176
12177 fn go_to_prev_diagnostic(
12178 &mut self,
12179 _: &GoToPreviousDiagnostic,
12180 window: &mut Window,
12181 cx: &mut Context<Self>,
12182 ) {
12183 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
12184 }
12185
12186 pub fn go_to_diagnostic_impl(
12187 &mut self,
12188 direction: Direction,
12189 window: &mut Window,
12190 cx: &mut Context<Self>,
12191 ) {
12192 let buffer = self.buffer.read(cx).snapshot(cx);
12193 let selection = self.selections.newest::<usize>(cx);
12194
12195 // If there is an active Diagnostic Popover jump to its diagnostic instead.
12196 if direction == Direction::Next {
12197 if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() {
12198 let Some(buffer_id) = popover.local_diagnostic.range.start.buffer_id else {
12199 return;
12200 };
12201 self.activate_diagnostics(
12202 buffer_id,
12203 popover.local_diagnostic.diagnostic.group_id,
12204 window,
12205 cx,
12206 );
12207 if let Some(active_diagnostics) = self.active_diagnostics.as_ref() {
12208 let primary_range_start = active_diagnostics.primary_range.start;
12209 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12210 let mut new_selection = s.newest_anchor().clone();
12211 new_selection.collapse_to(primary_range_start, SelectionGoal::None);
12212 s.select_anchors(vec![new_selection.clone()]);
12213 });
12214 self.refresh_inline_completion(false, true, window, cx);
12215 }
12216 return;
12217 }
12218 }
12219
12220 let active_group_id = self
12221 .active_diagnostics
12222 .as_ref()
12223 .map(|active_group| active_group.group_id);
12224 let active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| {
12225 active_diagnostics
12226 .primary_range
12227 .to_offset(&buffer)
12228 .to_inclusive()
12229 });
12230 let search_start = if let Some(active_primary_range) = active_primary_range.as_ref() {
12231 if active_primary_range.contains(&selection.head()) {
12232 *active_primary_range.start()
12233 } else {
12234 selection.head()
12235 }
12236 } else {
12237 selection.head()
12238 };
12239
12240 let snapshot = self.snapshot(window, cx);
12241 let primary_diagnostics_before = buffer
12242 .diagnostics_in_range::<usize>(0..search_start)
12243 .filter(|entry| entry.diagnostic.is_primary)
12244 .filter(|entry| entry.range.start != entry.range.end)
12245 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
12246 .filter(|entry| !snapshot.intersects_fold(entry.range.start))
12247 .collect::<Vec<_>>();
12248 let last_same_group_diagnostic_before = active_group_id.and_then(|active_group_id| {
12249 primary_diagnostics_before
12250 .iter()
12251 .position(|entry| entry.diagnostic.group_id == active_group_id)
12252 });
12253
12254 let primary_diagnostics_after = buffer
12255 .diagnostics_in_range::<usize>(search_start..buffer.len())
12256 .filter(|entry| entry.diagnostic.is_primary)
12257 .filter(|entry| entry.range.start != entry.range.end)
12258 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
12259 .filter(|diagnostic| !snapshot.intersects_fold(diagnostic.range.start))
12260 .collect::<Vec<_>>();
12261 let last_same_group_diagnostic_after = active_group_id.and_then(|active_group_id| {
12262 primary_diagnostics_after
12263 .iter()
12264 .enumerate()
12265 .rev()
12266 .find_map(|(i, entry)| {
12267 if entry.diagnostic.group_id == active_group_id {
12268 Some(i)
12269 } else {
12270 None
12271 }
12272 })
12273 });
12274
12275 let next_primary_diagnostic = match direction {
12276 Direction::Prev => primary_diagnostics_before
12277 .iter()
12278 .take(last_same_group_diagnostic_before.unwrap_or(usize::MAX))
12279 .rev()
12280 .next(),
12281 Direction::Next => primary_diagnostics_after
12282 .iter()
12283 .skip(
12284 last_same_group_diagnostic_after
12285 .map(|index| index + 1)
12286 .unwrap_or(0),
12287 )
12288 .next(),
12289 };
12290
12291 // Cycle around to the start of the buffer, potentially moving back to the start of
12292 // the currently active diagnostic.
12293 let cycle_around = || match direction {
12294 Direction::Prev => primary_diagnostics_after
12295 .iter()
12296 .rev()
12297 .chain(primary_diagnostics_before.iter().rev())
12298 .next(),
12299 Direction::Next => primary_diagnostics_before
12300 .iter()
12301 .chain(primary_diagnostics_after.iter())
12302 .next(),
12303 };
12304
12305 if let Some((primary_range, group_id)) = next_primary_diagnostic
12306 .or_else(cycle_around)
12307 .map(|entry| (&entry.range, entry.diagnostic.group_id))
12308 {
12309 let Some(buffer_id) = buffer.anchor_after(primary_range.start).buffer_id else {
12310 return;
12311 };
12312 self.activate_diagnostics(buffer_id, group_id, window, cx);
12313 if self.active_diagnostics.is_some() {
12314 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12315 s.select(vec![Selection {
12316 id: selection.id,
12317 start: primary_range.start,
12318 end: primary_range.start,
12319 reversed: false,
12320 goal: SelectionGoal::None,
12321 }]);
12322 });
12323 self.refresh_inline_completion(false, true, window, cx);
12324 }
12325 }
12326 }
12327
12328 fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
12329 let snapshot = self.snapshot(window, cx);
12330 let selection = self.selections.newest::<Point>(cx);
12331 self.go_to_hunk_before_or_after_position(
12332 &snapshot,
12333 selection.head(),
12334 Direction::Next,
12335 window,
12336 cx,
12337 );
12338 }
12339
12340 fn go_to_hunk_before_or_after_position(
12341 &mut self,
12342 snapshot: &EditorSnapshot,
12343 position: Point,
12344 direction: Direction,
12345 window: &mut Window,
12346 cx: &mut Context<Editor>,
12347 ) {
12348 let row = if direction == Direction::Next {
12349 self.hunk_after_position(snapshot, position)
12350 .map(|hunk| hunk.row_range.start)
12351 } else {
12352 self.hunk_before_position(snapshot, position)
12353 };
12354
12355 if let Some(row) = row {
12356 let destination = Point::new(row.0, 0);
12357 let autoscroll = Autoscroll::center();
12358
12359 self.unfold_ranges(&[destination..destination], false, false, cx);
12360 self.change_selections(Some(autoscroll), window, cx, |s| {
12361 s.select_ranges([destination..destination]);
12362 });
12363 }
12364 }
12365
12366 fn hunk_after_position(
12367 &mut self,
12368 snapshot: &EditorSnapshot,
12369 position: Point,
12370 ) -> Option<MultiBufferDiffHunk> {
12371 snapshot
12372 .buffer_snapshot
12373 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
12374 .find(|hunk| hunk.row_range.start.0 > position.row)
12375 .or_else(|| {
12376 snapshot
12377 .buffer_snapshot
12378 .diff_hunks_in_range(Point::zero()..position)
12379 .find(|hunk| hunk.row_range.end.0 < position.row)
12380 })
12381 }
12382
12383 fn go_to_prev_hunk(
12384 &mut self,
12385 _: &GoToPreviousHunk,
12386 window: &mut Window,
12387 cx: &mut Context<Self>,
12388 ) {
12389 let snapshot = self.snapshot(window, cx);
12390 let selection = self.selections.newest::<Point>(cx);
12391 self.go_to_hunk_before_or_after_position(
12392 &snapshot,
12393 selection.head(),
12394 Direction::Prev,
12395 window,
12396 cx,
12397 );
12398 }
12399
12400 fn hunk_before_position(
12401 &mut self,
12402 snapshot: &EditorSnapshot,
12403 position: Point,
12404 ) -> Option<MultiBufferRow> {
12405 snapshot
12406 .buffer_snapshot
12407 .diff_hunk_before(position)
12408 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
12409 }
12410
12411 fn go_to_line<T: 'static>(
12412 &mut self,
12413 position: Anchor,
12414 highlight_color: Option<Hsla>,
12415 window: &mut Window,
12416 cx: &mut Context<Self>,
12417 ) {
12418 let snapshot = self.snapshot(window, cx).display_snapshot;
12419 let position = position.to_point(&snapshot.buffer_snapshot);
12420 let start = snapshot
12421 .buffer_snapshot
12422 .clip_point(Point::new(position.row, 0), Bias::Left);
12423 let end = start + Point::new(1, 0);
12424 let start = snapshot.buffer_snapshot.anchor_before(start);
12425 let end = snapshot.buffer_snapshot.anchor_before(end);
12426
12427 self.clear_row_highlights::<T>();
12428 self.highlight_rows::<T>(
12429 start..end,
12430 highlight_color
12431 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
12432 true,
12433 cx,
12434 );
12435 self.request_autoscroll(Autoscroll::center(), cx);
12436 }
12437
12438 pub fn go_to_definition(
12439 &mut self,
12440 _: &GoToDefinition,
12441 window: &mut Window,
12442 cx: &mut Context<Self>,
12443 ) -> Task<Result<Navigated>> {
12444 let definition =
12445 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
12446 cx.spawn_in(window, async move |editor, cx| {
12447 if definition.await? == Navigated::Yes {
12448 return Ok(Navigated::Yes);
12449 }
12450 match editor.update_in(cx, |editor, window, cx| {
12451 editor.find_all_references(&FindAllReferences, window, cx)
12452 })? {
12453 Some(references) => references.await,
12454 None => Ok(Navigated::No),
12455 }
12456 })
12457 }
12458
12459 pub fn go_to_declaration(
12460 &mut self,
12461 _: &GoToDeclaration,
12462 window: &mut Window,
12463 cx: &mut Context<Self>,
12464 ) -> Task<Result<Navigated>> {
12465 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
12466 }
12467
12468 pub fn go_to_declaration_split(
12469 &mut self,
12470 _: &GoToDeclaration,
12471 window: &mut Window,
12472 cx: &mut Context<Self>,
12473 ) -> Task<Result<Navigated>> {
12474 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
12475 }
12476
12477 pub fn go_to_implementation(
12478 &mut self,
12479 _: &GoToImplementation,
12480 window: &mut Window,
12481 cx: &mut Context<Self>,
12482 ) -> Task<Result<Navigated>> {
12483 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
12484 }
12485
12486 pub fn go_to_implementation_split(
12487 &mut self,
12488 _: &GoToImplementationSplit,
12489 window: &mut Window,
12490 cx: &mut Context<Self>,
12491 ) -> Task<Result<Navigated>> {
12492 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
12493 }
12494
12495 pub fn go_to_type_definition(
12496 &mut self,
12497 _: &GoToTypeDefinition,
12498 window: &mut Window,
12499 cx: &mut Context<Self>,
12500 ) -> Task<Result<Navigated>> {
12501 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
12502 }
12503
12504 pub fn go_to_definition_split(
12505 &mut self,
12506 _: &GoToDefinitionSplit,
12507 window: &mut Window,
12508 cx: &mut Context<Self>,
12509 ) -> Task<Result<Navigated>> {
12510 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
12511 }
12512
12513 pub fn go_to_type_definition_split(
12514 &mut self,
12515 _: &GoToTypeDefinitionSplit,
12516 window: &mut Window,
12517 cx: &mut Context<Self>,
12518 ) -> Task<Result<Navigated>> {
12519 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
12520 }
12521
12522 fn go_to_definition_of_kind(
12523 &mut self,
12524 kind: GotoDefinitionKind,
12525 split: bool,
12526 window: &mut Window,
12527 cx: &mut Context<Self>,
12528 ) -> Task<Result<Navigated>> {
12529 let Some(provider) = self.semantics_provider.clone() else {
12530 return Task::ready(Ok(Navigated::No));
12531 };
12532 let head = self.selections.newest::<usize>(cx).head();
12533 let buffer = self.buffer.read(cx);
12534 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
12535 text_anchor
12536 } else {
12537 return Task::ready(Ok(Navigated::No));
12538 };
12539
12540 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
12541 return Task::ready(Ok(Navigated::No));
12542 };
12543
12544 cx.spawn_in(window, async move |editor, cx| {
12545 let definitions = definitions.await?;
12546 let navigated = editor
12547 .update_in(cx, |editor, window, cx| {
12548 editor.navigate_to_hover_links(
12549 Some(kind),
12550 definitions
12551 .into_iter()
12552 .filter(|location| {
12553 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
12554 })
12555 .map(HoverLink::Text)
12556 .collect::<Vec<_>>(),
12557 split,
12558 window,
12559 cx,
12560 )
12561 })?
12562 .await?;
12563 anyhow::Ok(navigated)
12564 })
12565 }
12566
12567 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
12568 let selection = self.selections.newest_anchor();
12569 let head = selection.head();
12570 let tail = selection.tail();
12571
12572 let Some((buffer, start_position)) =
12573 self.buffer.read(cx).text_anchor_for_position(head, cx)
12574 else {
12575 return;
12576 };
12577
12578 let end_position = if head != tail {
12579 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
12580 return;
12581 };
12582 Some(pos)
12583 } else {
12584 None
12585 };
12586
12587 let url_finder = cx.spawn_in(window, async move |editor, cx| {
12588 let url = if let Some(end_pos) = end_position {
12589 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
12590 } else {
12591 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
12592 };
12593
12594 if let Some(url) = url {
12595 editor.update(cx, |_, cx| {
12596 cx.open_url(&url);
12597 })
12598 } else {
12599 Ok(())
12600 }
12601 });
12602
12603 url_finder.detach();
12604 }
12605
12606 pub fn open_selected_filename(
12607 &mut self,
12608 _: &OpenSelectedFilename,
12609 window: &mut Window,
12610 cx: &mut Context<Self>,
12611 ) {
12612 let Some(workspace) = self.workspace() else {
12613 return;
12614 };
12615
12616 let position = self.selections.newest_anchor().head();
12617
12618 let Some((buffer, buffer_position)) =
12619 self.buffer.read(cx).text_anchor_for_position(position, cx)
12620 else {
12621 return;
12622 };
12623
12624 let project = self.project.clone();
12625
12626 cx.spawn_in(window, async move |_, cx| {
12627 let result = find_file(&buffer, project, buffer_position, cx).await;
12628
12629 if let Some((_, path)) = result {
12630 workspace
12631 .update_in(cx, |workspace, window, cx| {
12632 workspace.open_resolved_path(path, window, cx)
12633 })?
12634 .await?;
12635 }
12636 anyhow::Ok(())
12637 })
12638 .detach();
12639 }
12640
12641 pub(crate) fn navigate_to_hover_links(
12642 &mut self,
12643 kind: Option<GotoDefinitionKind>,
12644 mut definitions: Vec<HoverLink>,
12645 split: bool,
12646 window: &mut Window,
12647 cx: &mut Context<Editor>,
12648 ) -> Task<Result<Navigated>> {
12649 // If there is one definition, just open it directly
12650 if definitions.len() == 1 {
12651 let definition = definitions.pop().unwrap();
12652
12653 enum TargetTaskResult {
12654 Location(Option<Location>),
12655 AlreadyNavigated,
12656 }
12657
12658 let target_task = match definition {
12659 HoverLink::Text(link) => {
12660 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
12661 }
12662 HoverLink::InlayHint(lsp_location, server_id) => {
12663 let computation =
12664 self.compute_target_location(lsp_location, server_id, window, cx);
12665 cx.background_spawn(async move {
12666 let location = computation.await?;
12667 Ok(TargetTaskResult::Location(location))
12668 })
12669 }
12670 HoverLink::Url(url) => {
12671 cx.open_url(&url);
12672 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
12673 }
12674 HoverLink::File(path) => {
12675 if let Some(workspace) = self.workspace() {
12676 cx.spawn_in(window, async move |_, cx| {
12677 workspace
12678 .update_in(cx, |workspace, window, cx| {
12679 workspace.open_resolved_path(path, window, cx)
12680 })?
12681 .await
12682 .map(|_| TargetTaskResult::AlreadyNavigated)
12683 })
12684 } else {
12685 Task::ready(Ok(TargetTaskResult::Location(None)))
12686 }
12687 }
12688 };
12689 cx.spawn_in(window, async move |editor, cx| {
12690 let target = match target_task.await.context("target resolution task")? {
12691 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
12692 TargetTaskResult::Location(None) => return Ok(Navigated::No),
12693 TargetTaskResult::Location(Some(target)) => target,
12694 };
12695
12696 editor.update_in(cx, |editor, window, cx| {
12697 let Some(workspace) = editor.workspace() else {
12698 return Navigated::No;
12699 };
12700 let pane = workspace.read(cx).active_pane().clone();
12701
12702 let range = target.range.to_point(target.buffer.read(cx));
12703 let range = editor.range_for_match(&range);
12704 let range = collapse_multiline_range(range);
12705
12706 if !split
12707 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
12708 {
12709 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
12710 } else {
12711 window.defer(cx, move |window, cx| {
12712 let target_editor: Entity<Self> =
12713 workspace.update(cx, |workspace, cx| {
12714 let pane = if split {
12715 workspace.adjacent_pane(window, cx)
12716 } else {
12717 workspace.active_pane().clone()
12718 };
12719
12720 workspace.open_project_item(
12721 pane,
12722 target.buffer.clone(),
12723 true,
12724 true,
12725 window,
12726 cx,
12727 )
12728 });
12729 target_editor.update(cx, |target_editor, cx| {
12730 // When selecting a definition in a different buffer, disable the nav history
12731 // to avoid creating a history entry at the previous cursor location.
12732 pane.update(cx, |pane, _| pane.disable_history());
12733 target_editor.go_to_singleton_buffer_range(range, window, cx);
12734 pane.update(cx, |pane, _| pane.enable_history());
12735 });
12736 });
12737 }
12738 Navigated::Yes
12739 })
12740 })
12741 } else if !definitions.is_empty() {
12742 cx.spawn_in(window, async move |editor, cx| {
12743 let (title, location_tasks, workspace) = editor
12744 .update_in(cx, |editor, window, cx| {
12745 let tab_kind = match kind {
12746 Some(GotoDefinitionKind::Implementation) => "Implementations",
12747 _ => "Definitions",
12748 };
12749 let title = definitions
12750 .iter()
12751 .find_map(|definition| match definition {
12752 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
12753 let buffer = origin.buffer.read(cx);
12754 format!(
12755 "{} for {}",
12756 tab_kind,
12757 buffer
12758 .text_for_range(origin.range.clone())
12759 .collect::<String>()
12760 )
12761 }),
12762 HoverLink::InlayHint(_, _) => None,
12763 HoverLink::Url(_) => None,
12764 HoverLink::File(_) => None,
12765 })
12766 .unwrap_or(tab_kind.to_string());
12767 let location_tasks = definitions
12768 .into_iter()
12769 .map(|definition| match definition {
12770 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
12771 HoverLink::InlayHint(lsp_location, server_id) => editor
12772 .compute_target_location(lsp_location, server_id, window, cx),
12773 HoverLink::Url(_) => Task::ready(Ok(None)),
12774 HoverLink::File(_) => Task::ready(Ok(None)),
12775 })
12776 .collect::<Vec<_>>();
12777 (title, location_tasks, editor.workspace().clone())
12778 })
12779 .context("location tasks preparation")?;
12780
12781 let locations = future::join_all(location_tasks)
12782 .await
12783 .into_iter()
12784 .filter_map(|location| location.transpose())
12785 .collect::<Result<_>>()
12786 .context("location tasks")?;
12787
12788 let Some(workspace) = workspace else {
12789 return Ok(Navigated::No);
12790 };
12791 let opened = workspace
12792 .update_in(cx, |workspace, window, cx| {
12793 Self::open_locations_in_multibuffer(
12794 workspace,
12795 locations,
12796 title,
12797 split,
12798 MultibufferSelectionMode::First,
12799 window,
12800 cx,
12801 )
12802 })
12803 .ok();
12804
12805 anyhow::Ok(Navigated::from_bool(opened.is_some()))
12806 })
12807 } else {
12808 Task::ready(Ok(Navigated::No))
12809 }
12810 }
12811
12812 fn compute_target_location(
12813 &self,
12814 lsp_location: lsp::Location,
12815 server_id: LanguageServerId,
12816 window: &mut Window,
12817 cx: &mut Context<Self>,
12818 ) -> Task<anyhow::Result<Option<Location>>> {
12819 let Some(project) = self.project.clone() else {
12820 return Task::ready(Ok(None));
12821 };
12822
12823 cx.spawn_in(window, async move |editor, cx| {
12824 let location_task = editor.update(cx, |_, cx| {
12825 project.update(cx, |project, cx| {
12826 let language_server_name = project
12827 .language_server_statuses(cx)
12828 .find(|(id, _)| server_id == *id)
12829 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
12830 language_server_name.map(|language_server_name| {
12831 project.open_local_buffer_via_lsp(
12832 lsp_location.uri.clone(),
12833 server_id,
12834 language_server_name,
12835 cx,
12836 )
12837 })
12838 })
12839 })?;
12840 let location = match location_task {
12841 Some(task) => Some({
12842 let target_buffer_handle = task.await.context("open local buffer")?;
12843 let range = target_buffer_handle.update(cx, |target_buffer, _| {
12844 let target_start = target_buffer
12845 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
12846 let target_end = target_buffer
12847 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
12848 target_buffer.anchor_after(target_start)
12849 ..target_buffer.anchor_before(target_end)
12850 })?;
12851 Location {
12852 buffer: target_buffer_handle,
12853 range,
12854 }
12855 }),
12856 None => None,
12857 };
12858 Ok(location)
12859 })
12860 }
12861
12862 pub fn find_all_references(
12863 &mut self,
12864 _: &FindAllReferences,
12865 window: &mut Window,
12866 cx: &mut Context<Self>,
12867 ) -> Option<Task<Result<Navigated>>> {
12868 let selection = self.selections.newest::<usize>(cx);
12869 let multi_buffer = self.buffer.read(cx);
12870 let head = selection.head();
12871
12872 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
12873 let head_anchor = multi_buffer_snapshot.anchor_at(
12874 head,
12875 if head < selection.tail() {
12876 Bias::Right
12877 } else {
12878 Bias::Left
12879 },
12880 );
12881
12882 match self
12883 .find_all_references_task_sources
12884 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
12885 {
12886 Ok(_) => {
12887 log::info!(
12888 "Ignoring repeated FindAllReferences invocation with the position of already running task"
12889 );
12890 return None;
12891 }
12892 Err(i) => {
12893 self.find_all_references_task_sources.insert(i, head_anchor);
12894 }
12895 }
12896
12897 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
12898 let workspace = self.workspace()?;
12899 let project = workspace.read(cx).project().clone();
12900 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
12901 Some(cx.spawn_in(window, async move |editor, cx| {
12902 let _cleanup = cx.on_drop(&editor, move |editor, _| {
12903 if let Ok(i) = editor
12904 .find_all_references_task_sources
12905 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
12906 {
12907 editor.find_all_references_task_sources.remove(i);
12908 }
12909 });
12910
12911 let locations = references.await?;
12912 if locations.is_empty() {
12913 return anyhow::Ok(Navigated::No);
12914 }
12915
12916 workspace.update_in(cx, |workspace, window, cx| {
12917 let title = locations
12918 .first()
12919 .as_ref()
12920 .map(|location| {
12921 let buffer = location.buffer.read(cx);
12922 format!(
12923 "References to `{}`",
12924 buffer
12925 .text_for_range(location.range.clone())
12926 .collect::<String>()
12927 )
12928 })
12929 .unwrap();
12930 Self::open_locations_in_multibuffer(
12931 workspace,
12932 locations,
12933 title,
12934 false,
12935 MultibufferSelectionMode::First,
12936 window,
12937 cx,
12938 );
12939 Navigated::Yes
12940 })
12941 }))
12942 }
12943
12944 /// Opens a multibuffer with the given project locations in it
12945 pub fn open_locations_in_multibuffer(
12946 workspace: &mut Workspace,
12947 mut locations: Vec<Location>,
12948 title: String,
12949 split: bool,
12950 multibuffer_selection_mode: MultibufferSelectionMode,
12951 window: &mut Window,
12952 cx: &mut Context<Workspace>,
12953 ) {
12954 // If there are multiple definitions, open them in a multibuffer
12955 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
12956 let mut locations = locations.into_iter().peekable();
12957 let mut ranges = Vec::new();
12958 let capability = workspace.project().read(cx).capability();
12959
12960 let excerpt_buffer = cx.new(|cx| {
12961 let mut multibuffer = MultiBuffer::new(capability);
12962 while let Some(location) = locations.next() {
12963 let buffer = location.buffer.read(cx);
12964 let mut ranges_for_buffer = Vec::new();
12965 let range = location.range.to_offset(buffer);
12966 ranges_for_buffer.push(range.clone());
12967
12968 while let Some(next_location) = locations.peek() {
12969 if next_location.buffer == location.buffer {
12970 ranges_for_buffer.push(next_location.range.to_offset(buffer));
12971 locations.next();
12972 } else {
12973 break;
12974 }
12975 }
12976
12977 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
12978 ranges.extend(multibuffer.push_excerpts_with_context_lines(
12979 location.buffer.clone(),
12980 ranges_for_buffer,
12981 DEFAULT_MULTIBUFFER_CONTEXT,
12982 cx,
12983 ))
12984 }
12985
12986 multibuffer.with_title(title)
12987 });
12988
12989 let editor = cx.new(|cx| {
12990 Editor::for_multibuffer(
12991 excerpt_buffer,
12992 Some(workspace.project().clone()),
12993 window,
12994 cx,
12995 )
12996 });
12997 editor.update(cx, |editor, cx| {
12998 match multibuffer_selection_mode {
12999 MultibufferSelectionMode::First => {
13000 if let Some(first_range) = ranges.first() {
13001 editor.change_selections(None, window, cx, |selections| {
13002 selections.clear_disjoint();
13003 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
13004 });
13005 }
13006 editor.highlight_background::<Self>(
13007 &ranges,
13008 |theme| theme.editor_highlighted_line_background,
13009 cx,
13010 );
13011 }
13012 MultibufferSelectionMode::All => {
13013 editor.change_selections(None, window, cx, |selections| {
13014 selections.clear_disjoint();
13015 selections.select_anchor_ranges(ranges);
13016 });
13017 }
13018 }
13019 editor.register_buffers_with_language_servers(cx);
13020 });
13021
13022 let item = Box::new(editor);
13023 let item_id = item.item_id();
13024
13025 if split {
13026 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
13027 } else {
13028 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
13029 let (preview_item_id, preview_item_idx) =
13030 workspace.active_pane().update(cx, |pane, _| {
13031 (pane.preview_item_id(), pane.preview_item_idx())
13032 });
13033
13034 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
13035
13036 if let Some(preview_item_id) = preview_item_id {
13037 workspace.active_pane().update(cx, |pane, cx| {
13038 pane.remove_item(preview_item_id, false, false, window, cx);
13039 });
13040 }
13041 } else {
13042 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
13043 }
13044 }
13045 workspace.active_pane().update(cx, |pane, cx| {
13046 pane.set_preview_item_id(Some(item_id), cx);
13047 });
13048 }
13049
13050 pub fn rename(
13051 &mut self,
13052 _: &Rename,
13053 window: &mut Window,
13054 cx: &mut Context<Self>,
13055 ) -> Option<Task<Result<()>>> {
13056 use language::ToOffset as _;
13057
13058 let provider = self.semantics_provider.clone()?;
13059 let selection = self.selections.newest_anchor().clone();
13060 let (cursor_buffer, cursor_buffer_position) = self
13061 .buffer
13062 .read(cx)
13063 .text_anchor_for_position(selection.head(), cx)?;
13064 let (tail_buffer, cursor_buffer_position_end) = self
13065 .buffer
13066 .read(cx)
13067 .text_anchor_for_position(selection.tail(), cx)?;
13068 if tail_buffer != cursor_buffer {
13069 return None;
13070 }
13071
13072 let snapshot = cursor_buffer.read(cx).snapshot();
13073 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
13074 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
13075 let prepare_rename = provider
13076 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
13077 .unwrap_or_else(|| Task::ready(Ok(None)));
13078 drop(snapshot);
13079
13080 Some(cx.spawn_in(window, async move |this, cx| {
13081 let rename_range = if let Some(range) = prepare_rename.await? {
13082 Some(range)
13083 } else {
13084 this.update(cx, |this, cx| {
13085 let buffer = this.buffer.read(cx).snapshot(cx);
13086 let mut buffer_highlights = this
13087 .document_highlights_for_position(selection.head(), &buffer)
13088 .filter(|highlight| {
13089 highlight.start.excerpt_id == selection.head().excerpt_id
13090 && highlight.end.excerpt_id == selection.head().excerpt_id
13091 });
13092 buffer_highlights
13093 .next()
13094 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
13095 })?
13096 };
13097 if let Some(rename_range) = rename_range {
13098 this.update_in(cx, |this, window, cx| {
13099 let snapshot = cursor_buffer.read(cx).snapshot();
13100 let rename_buffer_range = rename_range.to_offset(&snapshot);
13101 let cursor_offset_in_rename_range =
13102 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
13103 let cursor_offset_in_rename_range_end =
13104 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
13105
13106 this.take_rename(false, window, cx);
13107 let buffer = this.buffer.read(cx).read(cx);
13108 let cursor_offset = selection.head().to_offset(&buffer);
13109 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
13110 let rename_end = rename_start + rename_buffer_range.len();
13111 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
13112 let mut old_highlight_id = None;
13113 let old_name: Arc<str> = buffer
13114 .chunks(rename_start..rename_end, true)
13115 .map(|chunk| {
13116 if old_highlight_id.is_none() {
13117 old_highlight_id = chunk.syntax_highlight_id;
13118 }
13119 chunk.text
13120 })
13121 .collect::<String>()
13122 .into();
13123
13124 drop(buffer);
13125
13126 // Position the selection in the rename editor so that it matches the current selection.
13127 this.show_local_selections = false;
13128 let rename_editor = cx.new(|cx| {
13129 let mut editor = Editor::single_line(window, cx);
13130 editor.buffer.update(cx, |buffer, cx| {
13131 buffer.edit([(0..0, old_name.clone())], None, cx)
13132 });
13133 let rename_selection_range = match cursor_offset_in_rename_range
13134 .cmp(&cursor_offset_in_rename_range_end)
13135 {
13136 Ordering::Equal => {
13137 editor.select_all(&SelectAll, window, cx);
13138 return editor;
13139 }
13140 Ordering::Less => {
13141 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
13142 }
13143 Ordering::Greater => {
13144 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
13145 }
13146 };
13147 if rename_selection_range.end > old_name.len() {
13148 editor.select_all(&SelectAll, window, cx);
13149 } else {
13150 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13151 s.select_ranges([rename_selection_range]);
13152 });
13153 }
13154 editor
13155 });
13156 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
13157 if e == &EditorEvent::Focused {
13158 cx.emit(EditorEvent::FocusedIn)
13159 }
13160 })
13161 .detach();
13162
13163 let write_highlights =
13164 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
13165 let read_highlights =
13166 this.clear_background_highlights::<DocumentHighlightRead>(cx);
13167 let ranges = write_highlights
13168 .iter()
13169 .flat_map(|(_, ranges)| ranges.iter())
13170 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
13171 .cloned()
13172 .collect();
13173
13174 this.highlight_text::<Rename>(
13175 ranges,
13176 HighlightStyle {
13177 fade_out: Some(0.6),
13178 ..Default::default()
13179 },
13180 cx,
13181 );
13182 let rename_focus_handle = rename_editor.focus_handle(cx);
13183 window.focus(&rename_focus_handle);
13184 let block_id = this.insert_blocks(
13185 [BlockProperties {
13186 style: BlockStyle::Flex,
13187 placement: BlockPlacement::Below(range.start),
13188 height: 1,
13189 render: Arc::new({
13190 let rename_editor = rename_editor.clone();
13191 move |cx: &mut BlockContext| {
13192 let mut text_style = cx.editor_style.text.clone();
13193 if let Some(highlight_style) = old_highlight_id
13194 .and_then(|h| h.style(&cx.editor_style.syntax))
13195 {
13196 text_style = text_style.highlight(highlight_style);
13197 }
13198 div()
13199 .block_mouse_down()
13200 .pl(cx.anchor_x)
13201 .child(EditorElement::new(
13202 &rename_editor,
13203 EditorStyle {
13204 background: cx.theme().system().transparent,
13205 local_player: cx.editor_style.local_player,
13206 text: text_style,
13207 scrollbar_width: cx.editor_style.scrollbar_width,
13208 syntax: cx.editor_style.syntax.clone(),
13209 status: cx.editor_style.status.clone(),
13210 inlay_hints_style: HighlightStyle {
13211 font_weight: Some(FontWeight::BOLD),
13212 ..make_inlay_hints_style(cx.app)
13213 },
13214 inline_completion_styles: make_suggestion_styles(
13215 cx.app,
13216 ),
13217 ..EditorStyle::default()
13218 },
13219 ))
13220 .into_any_element()
13221 }
13222 }),
13223 priority: 0,
13224 }],
13225 Some(Autoscroll::fit()),
13226 cx,
13227 )[0];
13228 this.pending_rename = Some(RenameState {
13229 range,
13230 old_name,
13231 editor: rename_editor,
13232 block_id,
13233 });
13234 })?;
13235 }
13236
13237 Ok(())
13238 }))
13239 }
13240
13241 pub fn confirm_rename(
13242 &mut self,
13243 _: &ConfirmRename,
13244 window: &mut Window,
13245 cx: &mut Context<Self>,
13246 ) -> Option<Task<Result<()>>> {
13247 let rename = self.take_rename(false, window, cx)?;
13248 let workspace = self.workspace()?.downgrade();
13249 let (buffer, start) = self
13250 .buffer
13251 .read(cx)
13252 .text_anchor_for_position(rename.range.start, cx)?;
13253 let (end_buffer, _) = self
13254 .buffer
13255 .read(cx)
13256 .text_anchor_for_position(rename.range.end, cx)?;
13257 if buffer != end_buffer {
13258 return None;
13259 }
13260
13261 let old_name = rename.old_name;
13262 let new_name = rename.editor.read(cx).text(cx);
13263
13264 let rename = self.semantics_provider.as_ref()?.perform_rename(
13265 &buffer,
13266 start,
13267 new_name.clone(),
13268 cx,
13269 )?;
13270
13271 Some(cx.spawn_in(window, async move |editor, cx| {
13272 let project_transaction = rename.await?;
13273 Self::open_project_transaction(
13274 &editor,
13275 workspace,
13276 project_transaction,
13277 format!("Rename: {} → {}", old_name, new_name),
13278 cx,
13279 )
13280 .await?;
13281
13282 editor.update(cx, |editor, cx| {
13283 editor.refresh_document_highlights(cx);
13284 })?;
13285 Ok(())
13286 }))
13287 }
13288
13289 fn take_rename(
13290 &mut self,
13291 moving_cursor: bool,
13292 window: &mut Window,
13293 cx: &mut Context<Self>,
13294 ) -> Option<RenameState> {
13295 let rename = self.pending_rename.take()?;
13296 if rename.editor.focus_handle(cx).is_focused(window) {
13297 window.focus(&self.focus_handle);
13298 }
13299
13300 self.remove_blocks(
13301 [rename.block_id].into_iter().collect(),
13302 Some(Autoscroll::fit()),
13303 cx,
13304 );
13305 self.clear_highlights::<Rename>(cx);
13306 self.show_local_selections = true;
13307
13308 if moving_cursor {
13309 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
13310 editor.selections.newest::<usize>(cx).head()
13311 });
13312
13313 // Update the selection to match the position of the selection inside
13314 // the rename editor.
13315 let snapshot = self.buffer.read(cx).read(cx);
13316 let rename_range = rename.range.to_offset(&snapshot);
13317 let cursor_in_editor = snapshot
13318 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
13319 .min(rename_range.end);
13320 drop(snapshot);
13321
13322 self.change_selections(None, window, cx, |s| {
13323 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
13324 });
13325 } else {
13326 self.refresh_document_highlights(cx);
13327 }
13328
13329 Some(rename)
13330 }
13331
13332 pub fn pending_rename(&self) -> Option<&RenameState> {
13333 self.pending_rename.as_ref()
13334 }
13335
13336 fn format(
13337 &mut self,
13338 _: &Format,
13339 window: &mut Window,
13340 cx: &mut Context<Self>,
13341 ) -> Option<Task<Result<()>>> {
13342 let project = match &self.project {
13343 Some(project) => project.clone(),
13344 None => return None,
13345 };
13346
13347 Some(self.perform_format(
13348 project,
13349 FormatTrigger::Manual,
13350 FormatTarget::Buffers,
13351 window,
13352 cx,
13353 ))
13354 }
13355
13356 fn format_selections(
13357 &mut self,
13358 _: &FormatSelections,
13359 window: &mut Window,
13360 cx: &mut Context<Self>,
13361 ) -> Option<Task<Result<()>>> {
13362 let project = match &self.project {
13363 Some(project) => project.clone(),
13364 None => return None,
13365 };
13366
13367 let ranges = self
13368 .selections
13369 .all_adjusted(cx)
13370 .into_iter()
13371 .map(|selection| selection.range())
13372 .collect_vec();
13373
13374 Some(self.perform_format(
13375 project,
13376 FormatTrigger::Manual,
13377 FormatTarget::Ranges(ranges),
13378 window,
13379 cx,
13380 ))
13381 }
13382
13383 fn perform_format(
13384 &mut self,
13385 project: Entity<Project>,
13386 trigger: FormatTrigger,
13387 target: FormatTarget,
13388 window: &mut Window,
13389 cx: &mut Context<Self>,
13390 ) -> Task<Result<()>> {
13391 let buffer = self.buffer.clone();
13392 let (buffers, target) = match target {
13393 FormatTarget::Buffers => {
13394 let mut buffers = buffer.read(cx).all_buffers();
13395 if trigger == FormatTrigger::Save {
13396 buffers.retain(|buffer| buffer.read(cx).is_dirty());
13397 }
13398 (buffers, LspFormatTarget::Buffers)
13399 }
13400 FormatTarget::Ranges(selection_ranges) => {
13401 let multi_buffer = buffer.read(cx);
13402 let snapshot = multi_buffer.read(cx);
13403 let mut buffers = HashSet::default();
13404 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
13405 BTreeMap::new();
13406 for selection_range in selection_ranges {
13407 for (buffer, buffer_range, _) in
13408 snapshot.range_to_buffer_ranges(selection_range)
13409 {
13410 let buffer_id = buffer.remote_id();
13411 let start = buffer.anchor_before(buffer_range.start);
13412 let end = buffer.anchor_after(buffer_range.end);
13413 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
13414 buffer_id_to_ranges
13415 .entry(buffer_id)
13416 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
13417 .or_insert_with(|| vec![start..end]);
13418 }
13419 }
13420 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
13421 }
13422 };
13423
13424 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
13425 let format = project.update(cx, |project, cx| {
13426 project.format(buffers, target, true, trigger, cx)
13427 });
13428
13429 cx.spawn_in(window, async move |_, cx| {
13430 let transaction = futures::select_biased! {
13431 transaction = format.log_err().fuse() => transaction,
13432 () = timeout => {
13433 log::warn!("timed out waiting for formatting");
13434 None
13435 }
13436 };
13437
13438 buffer
13439 .update(cx, |buffer, cx| {
13440 if let Some(transaction) = transaction {
13441 if !buffer.is_singleton() {
13442 buffer.push_transaction(&transaction.0, cx);
13443 }
13444 }
13445 cx.notify();
13446 })
13447 .ok();
13448
13449 Ok(())
13450 })
13451 }
13452
13453 fn organize_imports(
13454 &mut self,
13455 _: &OrganizeImports,
13456 window: &mut Window,
13457 cx: &mut Context<Self>,
13458 ) -> Option<Task<Result<()>>> {
13459 let project = match &self.project {
13460 Some(project) => project.clone(),
13461 None => return None,
13462 };
13463 Some(self.perform_code_action_kind(
13464 project,
13465 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
13466 window,
13467 cx,
13468 ))
13469 }
13470
13471 fn perform_code_action_kind(
13472 &mut self,
13473 project: Entity<Project>,
13474 kind: CodeActionKind,
13475 window: &mut Window,
13476 cx: &mut Context<Self>,
13477 ) -> Task<Result<()>> {
13478 let buffer = self.buffer.clone();
13479 let buffers = buffer.read(cx).all_buffers();
13480 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
13481 let apply_action = project.update(cx, |project, cx| {
13482 project.apply_code_action_kind(buffers, kind, true, cx)
13483 });
13484 cx.spawn_in(window, async move |_, cx| {
13485 let transaction = futures::select_biased! {
13486 () = timeout => {
13487 log::warn!("timed out waiting for executing code action");
13488 None
13489 }
13490 transaction = apply_action.log_err().fuse() => transaction,
13491 };
13492 buffer
13493 .update(cx, |buffer, cx| {
13494 // check if we need this
13495 if let Some(transaction) = transaction {
13496 if !buffer.is_singleton() {
13497 buffer.push_transaction(&transaction.0, cx);
13498 }
13499 }
13500 cx.notify();
13501 })
13502 .ok();
13503 Ok(())
13504 })
13505 }
13506
13507 fn restart_language_server(
13508 &mut self,
13509 _: &RestartLanguageServer,
13510 _: &mut Window,
13511 cx: &mut Context<Self>,
13512 ) {
13513 if let Some(project) = self.project.clone() {
13514 self.buffer.update(cx, |multi_buffer, cx| {
13515 project.update(cx, |project, cx| {
13516 project.restart_language_servers_for_buffers(
13517 multi_buffer.all_buffers().into_iter().collect(),
13518 cx,
13519 );
13520 });
13521 })
13522 }
13523 }
13524
13525 fn cancel_language_server_work(
13526 workspace: &mut Workspace,
13527 _: &actions::CancelLanguageServerWork,
13528 _: &mut Window,
13529 cx: &mut Context<Workspace>,
13530 ) {
13531 let project = workspace.project();
13532 let buffers = workspace
13533 .active_item(cx)
13534 .and_then(|item| item.act_as::<Editor>(cx))
13535 .map_or(HashSet::default(), |editor| {
13536 editor.read(cx).buffer.read(cx).all_buffers()
13537 });
13538 project.update(cx, |project, cx| {
13539 project.cancel_language_server_work_for_buffers(buffers, cx);
13540 });
13541 }
13542
13543 fn show_character_palette(
13544 &mut self,
13545 _: &ShowCharacterPalette,
13546 window: &mut Window,
13547 _: &mut Context<Self>,
13548 ) {
13549 window.show_character_palette();
13550 }
13551
13552 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
13553 if let Some(active_diagnostics) = self.active_diagnostics.as_mut() {
13554 let buffer = self.buffer.read(cx).snapshot(cx);
13555 let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer);
13556 let primary_range_end = active_diagnostics.primary_range.end.to_offset(&buffer);
13557 let is_valid = buffer
13558 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
13559 .any(|entry| {
13560 entry.diagnostic.is_primary
13561 && !entry.range.is_empty()
13562 && entry.range.start == primary_range_start
13563 && entry.diagnostic.message == active_diagnostics.primary_message
13564 });
13565
13566 if is_valid != active_diagnostics.is_valid {
13567 active_diagnostics.is_valid = is_valid;
13568 if is_valid {
13569 let mut new_styles = HashMap::default();
13570 for (block_id, diagnostic) in &active_diagnostics.blocks {
13571 new_styles.insert(
13572 *block_id,
13573 diagnostic_block_renderer(diagnostic.clone(), None, true),
13574 );
13575 }
13576 self.display_map.update(cx, |display_map, _cx| {
13577 display_map.replace_blocks(new_styles);
13578 });
13579 } else {
13580 self.dismiss_diagnostics(cx);
13581 }
13582 }
13583 }
13584 }
13585
13586 fn activate_diagnostics(
13587 &mut self,
13588 buffer_id: BufferId,
13589 group_id: usize,
13590 window: &mut Window,
13591 cx: &mut Context<Self>,
13592 ) {
13593 self.dismiss_diagnostics(cx);
13594 let snapshot = self.snapshot(window, cx);
13595 self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
13596 let buffer = self.buffer.read(cx).snapshot(cx);
13597
13598 let mut primary_range = None;
13599 let mut primary_message = None;
13600 let diagnostic_group = buffer
13601 .diagnostic_group(buffer_id, group_id)
13602 .filter_map(|entry| {
13603 let start = entry.range.start;
13604 let end = entry.range.end;
13605 if snapshot.is_line_folded(MultiBufferRow(start.row))
13606 && (start.row == end.row
13607 || snapshot.is_line_folded(MultiBufferRow(end.row)))
13608 {
13609 return None;
13610 }
13611 if entry.diagnostic.is_primary {
13612 primary_range = Some(entry.range.clone());
13613 primary_message = Some(entry.diagnostic.message.clone());
13614 }
13615 Some(entry)
13616 })
13617 .collect::<Vec<_>>();
13618 let primary_range = primary_range?;
13619 let primary_message = primary_message?;
13620
13621 let blocks = display_map
13622 .insert_blocks(
13623 diagnostic_group.iter().map(|entry| {
13624 let diagnostic = entry.diagnostic.clone();
13625 let message_height = diagnostic.message.matches('\n').count() as u32 + 1;
13626 BlockProperties {
13627 style: BlockStyle::Fixed,
13628 placement: BlockPlacement::Below(
13629 buffer.anchor_after(entry.range.start),
13630 ),
13631 height: message_height,
13632 render: diagnostic_block_renderer(diagnostic, None, true),
13633 priority: 0,
13634 }
13635 }),
13636 cx,
13637 )
13638 .into_iter()
13639 .zip(diagnostic_group.into_iter().map(|entry| entry.diagnostic))
13640 .collect();
13641
13642 Some(ActiveDiagnosticGroup {
13643 primary_range: buffer.anchor_before(primary_range.start)
13644 ..buffer.anchor_after(primary_range.end),
13645 primary_message,
13646 group_id,
13647 blocks,
13648 is_valid: true,
13649 })
13650 });
13651 }
13652
13653 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
13654 if let Some(active_diagnostic_group) = self.active_diagnostics.take() {
13655 self.display_map.update(cx, |display_map, cx| {
13656 display_map.remove_blocks(active_diagnostic_group.blocks.into_keys().collect(), cx);
13657 });
13658 cx.notify();
13659 }
13660 }
13661
13662 /// Disable inline diagnostics rendering for this editor.
13663 pub fn disable_inline_diagnostics(&mut self) {
13664 self.inline_diagnostics_enabled = false;
13665 self.inline_diagnostics_update = Task::ready(());
13666 self.inline_diagnostics.clear();
13667 }
13668
13669 pub fn inline_diagnostics_enabled(&self) -> bool {
13670 self.inline_diagnostics_enabled
13671 }
13672
13673 pub fn show_inline_diagnostics(&self) -> bool {
13674 self.show_inline_diagnostics
13675 }
13676
13677 pub fn toggle_inline_diagnostics(
13678 &mut self,
13679 _: &ToggleInlineDiagnostics,
13680 window: &mut Window,
13681 cx: &mut Context<'_, Editor>,
13682 ) {
13683 self.show_inline_diagnostics = !self.show_inline_diagnostics;
13684 self.refresh_inline_diagnostics(false, window, cx);
13685 }
13686
13687 fn refresh_inline_diagnostics(
13688 &mut self,
13689 debounce: bool,
13690 window: &mut Window,
13691 cx: &mut Context<Self>,
13692 ) {
13693 if !self.inline_diagnostics_enabled || !self.show_inline_diagnostics {
13694 self.inline_diagnostics_update = Task::ready(());
13695 self.inline_diagnostics.clear();
13696 return;
13697 }
13698
13699 let debounce_ms = ProjectSettings::get_global(cx)
13700 .diagnostics
13701 .inline
13702 .update_debounce_ms;
13703 let debounce = if debounce && debounce_ms > 0 {
13704 Some(Duration::from_millis(debounce_ms))
13705 } else {
13706 None
13707 };
13708 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
13709 if let Some(debounce) = debounce {
13710 cx.background_executor().timer(debounce).await;
13711 }
13712 let Some(snapshot) = editor
13713 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
13714 .ok()
13715 else {
13716 return;
13717 };
13718
13719 let new_inline_diagnostics = cx
13720 .background_spawn(async move {
13721 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
13722 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
13723 let message = diagnostic_entry
13724 .diagnostic
13725 .message
13726 .split_once('\n')
13727 .map(|(line, _)| line)
13728 .map(SharedString::new)
13729 .unwrap_or_else(|| {
13730 SharedString::from(diagnostic_entry.diagnostic.message)
13731 });
13732 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
13733 let (Ok(i) | Err(i)) = inline_diagnostics
13734 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
13735 inline_diagnostics.insert(
13736 i,
13737 (
13738 start_anchor,
13739 InlineDiagnostic {
13740 message,
13741 group_id: diagnostic_entry.diagnostic.group_id,
13742 start: diagnostic_entry.range.start.to_point(&snapshot),
13743 is_primary: diagnostic_entry.diagnostic.is_primary,
13744 severity: diagnostic_entry.diagnostic.severity,
13745 },
13746 ),
13747 );
13748 }
13749 inline_diagnostics
13750 })
13751 .await;
13752
13753 editor
13754 .update(cx, |editor, cx| {
13755 editor.inline_diagnostics = new_inline_diagnostics;
13756 cx.notify();
13757 })
13758 .ok();
13759 });
13760 }
13761
13762 pub fn set_selections_from_remote(
13763 &mut self,
13764 selections: Vec<Selection<Anchor>>,
13765 pending_selection: Option<Selection<Anchor>>,
13766 window: &mut Window,
13767 cx: &mut Context<Self>,
13768 ) {
13769 let old_cursor_position = self.selections.newest_anchor().head();
13770 self.selections.change_with(cx, |s| {
13771 s.select_anchors(selections);
13772 if let Some(pending_selection) = pending_selection {
13773 s.set_pending(pending_selection, SelectMode::Character);
13774 } else {
13775 s.clear_pending();
13776 }
13777 });
13778 self.selections_did_change(false, &old_cursor_position, true, window, cx);
13779 }
13780
13781 fn push_to_selection_history(&mut self) {
13782 self.selection_history.push(SelectionHistoryEntry {
13783 selections: self.selections.disjoint_anchors(),
13784 select_next_state: self.select_next_state.clone(),
13785 select_prev_state: self.select_prev_state.clone(),
13786 add_selections_state: self.add_selections_state.clone(),
13787 });
13788 }
13789
13790 pub fn transact(
13791 &mut self,
13792 window: &mut Window,
13793 cx: &mut Context<Self>,
13794 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
13795 ) -> Option<TransactionId> {
13796 self.start_transaction_at(Instant::now(), window, cx);
13797 update(self, window, cx);
13798 self.end_transaction_at(Instant::now(), cx)
13799 }
13800
13801 pub fn start_transaction_at(
13802 &mut self,
13803 now: Instant,
13804 window: &mut Window,
13805 cx: &mut Context<Self>,
13806 ) {
13807 self.end_selection(window, cx);
13808 if let Some(tx_id) = self
13809 .buffer
13810 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
13811 {
13812 self.selection_history
13813 .insert_transaction(tx_id, self.selections.disjoint_anchors());
13814 cx.emit(EditorEvent::TransactionBegun {
13815 transaction_id: tx_id,
13816 })
13817 }
13818 }
13819
13820 pub fn end_transaction_at(
13821 &mut self,
13822 now: Instant,
13823 cx: &mut Context<Self>,
13824 ) -> Option<TransactionId> {
13825 if let Some(transaction_id) = self
13826 .buffer
13827 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
13828 {
13829 if let Some((_, end_selections)) =
13830 self.selection_history.transaction_mut(transaction_id)
13831 {
13832 *end_selections = Some(self.selections.disjoint_anchors());
13833 } else {
13834 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
13835 }
13836
13837 cx.emit(EditorEvent::Edited { transaction_id });
13838 Some(transaction_id)
13839 } else {
13840 None
13841 }
13842 }
13843
13844 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
13845 if self.selection_mark_mode {
13846 self.change_selections(None, window, cx, |s| {
13847 s.move_with(|_, sel| {
13848 sel.collapse_to(sel.head(), SelectionGoal::None);
13849 });
13850 })
13851 }
13852 self.selection_mark_mode = true;
13853 cx.notify();
13854 }
13855
13856 pub fn swap_selection_ends(
13857 &mut self,
13858 _: &actions::SwapSelectionEnds,
13859 window: &mut Window,
13860 cx: &mut Context<Self>,
13861 ) {
13862 self.change_selections(None, window, cx, |s| {
13863 s.move_with(|_, sel| {
13864 if sel.start != sel.end {
13865 sel.reversed = !sel.reversed
13866 }
13867 });
13868 });
13869 self.request_autoscroll(Autoscroll::newest(), cx);
13870 cx.notify();
13871 }
13872
13873 pub fn toggle_fold(
13874 &mut self,
13875 _: &actions::ToggleFold,
13876 window: &mut Window,
13877 cx: &mut Context<Self>,
13878 ) {
13879 if self.is_singleton(cx) {
13880 let selection = self.selections.newest::<Point>(cx);
13881
13882 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13883 let range = if selection.is_empty() {
13884 let point = selection.head().to_display_point(&display_map);
13885 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
13886 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
13887 .to_point(&display_map);
13888 start..end
13889 } else {
13890 selection.range()
13891 };
13892 if display_map.folds_in_range(range).next().is_some() {
13893 self.unfold_lines(&Default::default(), window, cx)
13894 } else {
13895 self.fold(&Default::default(), window, cx)
13896 }
13897 } else {
13898 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
13899 let buffer_ids: HashSet<_> = self
13900 .selections
13901 .disjoint_anchor_ranges()
13902 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
13903 .collect();
13904
13905 let should_unfold = buffer_ids
13906 .iter()
13907 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
13908
13909 for buffer_id in buffer_ids {
13910 if should_unfold {
13911 self.unfold_buffer(buffer_id, cx);
13912 } else {
13913 self.fold_buffer(buffer_id, cx);
13914 }
13915 }
13916 }
13917 }
13918
13919 pub fn toggle_fold_recursive(
13920 &mut self,
13921 _: &actions::ToggleFoldRecursive,
13922 window: &mut Window,
13923 cx: &mut Context<Self>,
13924 ) {
13925 let selection = self.selections.newest::<Point>(cx);
13926
13927 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13928 let range = if selection.is_empty() {
13929 let point = selection.head().to_display_point(&display_map);
13930 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
13931 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
13932 .to_point(&display_map);
13933 start..end
13934 } else {
13935 selection.range()
13936 };
13937 if display_map.folds_in_range(range).next().is_some() {
13938 self.unfold_recursive(&Default::default(), window, cx)
13939 } else {
13940 self.fold_recursive(&Default::default(), window, cx)
13941 }
13942 }
13943
13944 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
13945 if self.is_singleton(cx) {
13946 let mut to_fold = Vec::new();
13947 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13948 let selections = self.selections.all_adjusted(cx);
13949
13950 for selection in selections {
13951 let range = selection.range().sorted();
13952 let buffer_start_row = range.start.row;
13953
13954 if range.start.row != range.end.row {
13955 let mut found = false;
13956 let mut row = range.start.row;
13957 while row <= range.end.row {
13958 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
13959 {
13960 found = true;
13961 row = crease.range().end.row + 1;
13962 to_fold.push(crease);
13963 } else {
13964 row += 1
13965 }
13966 }
13967 if found {
13968 continue;
13969 }
13970 }
13971
13972 for row in (0..=range.start.row).rev() {
13973 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
13974 if crease.range().end.row >= buffer_start_row {
13975 to_fold.push(crease);
13976 if row <= range.start.row {
13977 break;
13978 }
13979 }
13980 }
13981 }
13982 }
13983
13984 self.fold_creases(to_fold, true, window, cx);
13985 } else {
13986 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
13987 let buffer_ids = self
13988 .selections
13989 .disjoint_anchor_ranges()
13990 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
13991 .collect::<HashSet<_>>();
13992 for buffer_id in buffer_ids {
13993 self.fold_buffer(buffer_id, cx);
13994 }
13995 }
13996 }
13997
13998 fn fold_at_level(
13999 &mut self,
14000 fold_at: &FoldAtLevel,
14001 window: &mut Window,
14002 cx: &mut Context<Self>,
14003 ) {
14004 if !self.buffer.read(cx).is_singleton() {
14005 return;
14006 }
14007
14008 let fold_at_level = fold_at.0;
14009 let snapshot = self.buffer.read(cx).snapshot(cx);
14010 let mut to_fold = Vec::new();
14011 let mut stack = vec![(0, snapshot.max_row().0, 1)];
14012
14013 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
14014 while start_row < end_row {
14015 match self
14016 .snapshot(window, cx)
14017 .crease_for_buffer_row(MultiBufferRow(start_row))
14018 {
14019 Some(crease) => {
14020 let nested_start_row = crease.range().start.row + 1;
14021 let nested_end_row = crease.range().end.row;
14022
14023 if current_level < fold_at_level {
14024 stack.push((nested_start_row, nested_end_row, current_level + 1));
14025 } else if current_level == fold_at_level {
14026 to_fold.push(crease);
14027 }
14028
14029 start_row = nested_end_row + 1;
14030 }
14031 None => start_row += 1,
14032 }
14033 }
14034 }
14035
14036 self.fold_creases(to_fold, true, window, cx);
14037 }
14038
14039 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
14040 if self.buffer.read(cx).is_singleton() {
14041 let mut fold_ranges = Vec::new();
14042 let snapshot = self.buffer.read(cx).snapshot(cx);
14043
14044 for row in 0..snapshot.max_row().0 {
14045 if let Some(foldable_range) = self
14046 .snapshot(window, cx)
14047 .crease_for_buffer_row(MultiBufferRow(row))
14048 {
14049 fold_ranges.push(foldable_range);
14050 }
14051 }
14052
14053 self.fold_creases(fold_ranges, true, window, cx);
14054 } else {
14055 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
14056 editor
14057 .update_in(cx, |editor, _, cx| {
14058 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
14059 editor.fold_buffer(buffer_id, cx);
14060 }
14061 })
14062 .ok();
14063 });
14064 }
14065 }
14066
14067 pub fn fold_function_bodies(
14068 &mut self,
14069 _: &actions::FoldFunctionBodies,
14070 window: &mut Window,
14071 cx: &mut Context<Self>,
14072 ) {
14073 let snapshot = self.buffer.read(cx).snapshot(cx);
14074
14075 let ranges = snapshot
14076 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
14077 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
14078 .collect::<Vec<_>>();
14079
14080 let creases = ranges
14081 .into_iter()
14082 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
14083 .collect();
14084
14085 self.fold_creases(creases, true, window, cx);
14086 }
14087
14088 pub fn fold_recursive(
14089 &mut self,
14090 _: &actions::FoldRecursive,
14091 window: &mut Window,
14092 cx: &mut Context<Self>,
14093 ) {
14094 let mut to_fold = Vec::new();
14095 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14096 let selections = self.selections.all_adjusted(cx);
14097
14098 for selection in selections {
14099 let range = selection.range().sorted();
14100 let buffer_start_row = range.start.row;
14101
14102 if range.start.row != range.end.row {
14103 let mut found = false;
14104 for row in range.start.row..=range.end.row {
14105 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14106 found = true;
14107 to_fold.push(crease);
14108 }
14109 }
14110 if found {
14111 continue;
14112 }
14113 }
14114
14115 for row in (0..=range.start.row).rev() {
14116 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14117 if crease.range().end.row >= buffer_start_row {
14118 to_fold.push(crease);
14119 } else {
14120 break;
14121 }
14122 }
14123 }
14124 }
14125
14126 self.fold_creases(to_fold, true, window, cx);
14127 }
14128
14129 pub fn fold_at(&mut self, fold_at: &FoldAt, window: &mut Window, cx: &mut Context<Self>) {
14130 let buffer_row = fold_at.buffer_row;
14131 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14132
14133 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
14134 let autoscroll = self
14135 .selections
14136 .all::<Point>(cx)
14137 .iter()
14138 .any(|selection| crease.range().overlaps(&selection.range()));
14139
14140 self.fold_creases(vec![crease], autoscroll, window, cx);
14141 }
14142 }
14143
14144 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
14145 if self.is_singleton(cx) {
14146 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14147 let buffer = &display_map.buffer_snapshot;
14148 let selections = self.selections.all::<Point>(cx);
14149 let ranges = selections
14150 .iter()
14151 .map(|s| {
14152 let range = s.display_range(&display_map).sorted();
14153 let mut start = range.start.to_point(&display_map);
14154 let mut end = range.end.to_point(&display_map);
14155 start.column = 0;
14156 end.column = buffer.line_len(MultiBufferRow(end.row));
14157 start..end
14158 })
14159 .collect::<Vec<_>>();
14160
14161 self.unfold_ranges(&ranges, true, true, cx);
14162 } else {
14163 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14164 let buffer_ids = self
14165 .selections
14166 .disjoint_anchor_ranges()
14167 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14168 .collect::<HashSet<_>>();
14169 for buffer_id in buffer_ids {
14170 self.unfold_buffer(buffer_id, cx);
14171 }
14172 }
14173 }
14174
14175 pub fn unfold_recursive(
14176 &mut self,
14177 _: &UnfoldRecursive,
14178 _window: &mut Window,
14179 cx: &mut Context<Self>,
14180 ) {
14181 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14182 let selections = self.selections.all::<Point>(cx);
14183 let ranges = selections
14184 .iter()
14185 .map(|s| {
14186 let mut range = s.display_range(&display_map).sorted();
14187 *range.start.column_mut() = 0;
14188 *range.end.column_mut() = display_map.line_len(range.end.row());
14189 let start = range.start.to_point(&display_map);
14190 let end = range.end.to_point(&display_map);
14191 start..end
14192 })
14193 .collect::<Vec<_>>();
14194
14195 self.unfold_ranges(&ranges, true, true, cx);
14196 }
14197
14198 pub fn unfold_at(
14199 &mut self,
14200 unfold_at: &UnfoldAt,
14201 _window: &mut Window,
14202 cx: &mut Context<Self>,
14203 ) {
14204 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14205
14206 let intersection_range = Point::new(unfold_at.buffer_row.0, 0)
14207 ..Point::new(
14208 unfold_at.buffer_row.0,
14209 display_map.buffer_snapshot.line_len(unfold_at.buffer_row),
14210 );
14211
14212 let autoscroll = self
14213 .selections
14214 .all::<Point>(cx)
14215 .iter()
14216 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
14217
14218 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
14219 }
14220
14221 pub fn unfold_all(
14222 &mut self,
14223 _: &actions::UnfoldAll,
14224 _window: &mut Window,
14225 cx: &mut Context<Self>,
14226 ) {
14227 if self.buffer.read(cx).is_singleton() {
14228 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14229 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
14230 } else {
14231 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
14232 editor
14233 .update(cx, |editor, cx| {
14234 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
14235 editor.unfold_buffer(buffer_id, cx);
14236 }
14237 })
14238 .ok();
14239 });
14240 }
14241 }
14242
14243 pub fn fold_selected_ranges(
14244 &mut self,
14245 _: &FoldSelectedRanges,
14246 window: &mut Window,
14247 cx: &mut Context<Self>,
14248 ) {
14249 let selections = self.selections.all::<Point>(cx);
14250 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14251 let line_mode = self.selections.line_mode;
14252 let ranges = selections
14253 .into_iter()
14254 .map(|s| {
14255 if line_mode {
14256 let start = Point::new(s.start.row, 0);
14257 let end = Point::new(
14258 s.end.row,
14259 display_map
14260 .buffer_snapshot
14261 .line_len(MultiBufferRow(s.end.row)),
14262 );
14263 Crease::simple(start..end, display_map.fold_placeholder.clone())
14264 } else {
14265 Crease::simple(s.start..s.end, display_map.fold_placeholder.clone())
14266 }
14267 })
14268 .collect::<Vec<_>>();
14269 self.fold_creases(ranges, true, window, cx);
14270 }
14271
14272 pub fn fold_ranges<T: ToOffset + Clone>(
14273 &mut self,
14274 ranges: Vec<Range<T>>,
14275 auto_scroll: bool,
14276 window: &mut Window,
14277 cx: &mut Context<Self>,
14278 ) {
14279 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14280 let ranges = ranges
14281 .into_iter()
14282 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
14283 .collect::<Vec<_>>();
14284 self.fold_creases(ranges, auto_scroll, window, cx);
14285 }
14286
14287 pub fn fold_creases<T: ToOffset + Clone>(
14288 &mut self,
14289 creases: Vec<Crease<T>>,
14290 auto_scroll: bool,
14291 window: &mut Window,
14292 cx: &mut Context<Self>,
14293 ) {
14294 if creases.is_empty() {
14295 return;
14296 }
14297
14298 let mut buffers_affected = HashSet::default();
14299 let multi_buffer = self.buffer().read(cx);
14300 for crease in &creases {
14301 if let Some((_, buffer, _)) =
14302 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
14303 {
14304 buffers_affected.insert(buffer.read(cx).remote_id());
14305 };
14306 }
14307
14308 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
14309
14310 if auto_scroll {
14311 self.request_autoscroll(Autoscroll::fit(), cx);
14312 }
14313
14314 cx.notify();
14315
14316 if let Some(active_diagnostics) = self.active_diagnostics.take() {
14317 // Clear diagnostics block when folding a range that contains it.
14318 let snapshot = self.snapshot(window, cx);
14319 if snapshot.intersects_fold(active_diagnostics.primary_range.start) {
14320 drop(snapshot);
14321 self.active_diagnostics = Some(active_diagnostics);
14322 self.dismiss_diagnostics(cx);
14323 } else {
14324 self.active_diagnostics = Some(active_diagnostics);
14325 }
14326 }
14327
14328 self.scrollbar_marker_state.dirty = true;
14329 }
14330
14331 /// Removes any folds whose ranges intersect any of the given ranges.
14332 pub fn unfold_ranges<T: ToOffset + Clone>(
14333 &mut self,
14334 ranges: &[Range<T>],
14335 inclusive: bool,
14336 auto_scroll: bool,
14337 cx: &mut Context<Self>,
14338 ) {
14339 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
14340 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
14341 });
14342 }
14343
14344 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
14345 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
14346 return;
14347 }
14348 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
14349 self.display_map.update(cx, |display_map, cx| {
14350 display_map.fold_buffers([buffer_id], cx)
14351 });
14352 cx.emit(EditorEvent::BufferFoldToggled {
14353 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
14354 folded: true,
14355 });
14356 cx.notify();
14357 }
14358
14359 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
14360 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
14361 return;
14362 }
14363 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
14364 self.display_map.update(cx, |display_map, cx| {
14365 display_map.unfold_buffers([buffer_id], cx);
14366 });
14367 cx.emit(EditorEvent::BufferFoldToggled {
14368 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
14369 folded: false,
14370 });
14371 cx.notify();
14372 }
14373
14374 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
14375 self.display_map.read(cx).is_buffer_folded(buffer)
14376 }
14377
14378 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
14379 self.display_map.read(cx).folded_buffers()
14380 }
14381
14382 /// Removes any folds with the given ranges.
14383 pub fn remove_folds_with_type<T: ToOffset + Clone>(
14384 &mut self,
14385 ranges: &[Range<T>],
14386 type_id: TypeId,
14387 auto_scroll: bool,
14388 cx: &mut Context<Self>,
14389 ) {
14390 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
14391 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
14392 });
14393 }
14394
14395 fn remove_folds_with<T: ToOffset + Clone>(
14396 &mut self,
14397 ranges: &[Range<T>],
14398 auto_scroll: bool,
14399 cx: &mut Context<Self>,
14400 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
14401 ) {
14402 if ranges.is_empty() {
14403 return;
14404 }
14405
14406 let mut buffers_affected = HashSet::default();
14407 let multi_buffer = self.buffer().read(cx);
14408 for range in ranges {
14409 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
14410 buffers_affected.insert(buffer.read(cx).remote_id());
14411 };
14412 }
14413
14414 self.display_map.update(cx, update);
14415
14416 if auto_scroll {
14417 self.request_autoscroll(Autoscroll::fit(), cx);
14418 }
14419
14420 cx.notify();
14421 self.scrollbar_marker_state.dirty = true;
14422 self.active_indent_guides_state.dirty = true;
14423 }
14424
14425 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
14426 self.display_map.read(cx).fold_placeholder.clone()
14427 }
14428
14429 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
14430 self.buffer.update(cx, |buffer, cx| {
14431 buffer.set_all_diff_hunks_expanded(cx);
14432 });
14433 }
14434
14435 pub fn expand_all_diff_hunks(
14436 &mut self,
14437 _: &ExpandAllDiffHunks,
14438 _window: &mut Window,
14439 cx: &mut Context<Self>,
14440 ) {
14441 self.buffer.update(cx, |buffer, cx| {
14442 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
14443 });
14444 }
14445
14446 pub fn toggle_selected_diff_hunks(
14447 &mut self,
14448 _: &ToggleSelectedDiffHunks,
14449 _window: &mut Window,
14450 cx: &mut Context<Self>,
14451 ) {
14452 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
14453 self.toggle_diff_hunks_in_ranges(ranges, cx);
14454 }
14455
14456 pub fn diff_hunks_in_ranges<'a>(
14457 &'a self,
14458 ranges: &'a [Range<Anchor>],
14459 buffer: &'a MultiBufferSnapshot,
14460 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
14461 ranges.iter().flat_map(move |range| {
14462 let end_excerpt_id = range.end.excerpt_id;
14463 let range = range.to_point(buffer);
14464 let mut peek_end = range.end;
14465 if range.end.row < buffer.max_row().0 {
14466 peek_end = Point::new(range.end.row + 1, 0);
14467 }
14468 buffer
14469 .diff_hunks_in_range(range.start..peek_end)
14470 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
14471 })
14472 }
14473
14474 pub fn has_stageable_diff_hunks_in_ranges(
14475 &self,
14476 ranges: &[Range<Anchor>],
14477 snapshot: &MultiBufferSnapshot,
14478 ) -> bool {
14479 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
14480 hunks.any(|hunk| hunk.status().has_secondary_hunk())
14481 }
14482
14483 pub fn toggle_staged_selected_diff_hunks(
14484 &mut self,
14485 _: &::git::ToggleStaged,
14486 _: &mut Window,
14487 cx: &mut Context<Self>,
14488 ) {
14489 let snapshot = self.buffer.read(cx).snapshot(cx);
14490 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
14491 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
14492 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
14493 }
14494
14495 pub fn stage_and_next(
14496 &mut self,
14497 _: &::git::StageAndNext,
14498 window: &mut Window,
14499 cx: &mut Context<Self>,
14500 ) {
14501 self.do_stage_or_unstage_and_next(true, window, cx);
14502 }
14503
14504 pub fn unstage_and_next(
14505 &mut self,
14506 _: &::git::UnstageAndNext,
14507 window: &mut Window,
14508 cx: &mut Context<Self>,
14509 ) {
14510 self.do_stage_or_unstage_and_next(false, window, cx);
14511 }
14512
14513 pub fn stage_or_unstage_diff_hunks(
14514 &mut self,
14515 stage: bool,
14516 ranges: Vec<Range<Anchor>>,
14517 cx: &mut Context<Self>,
14518 ) {
14519 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
14520 cx.spawn(async move |this, cx| {
14521 task.await?;
14522 this.update(cx, |this, cx| {
14523 let snapshot = this.buffer.read(cx).snapshot(cx);
14524 let chunk_by = this
14525 .diff_hunks_in_ranges(&ranges, &snapshot)
14526 .chunk_by(|hunk| hunk.buffer_id);
14527 for (buffer_id, hunks) in &chunk_by {
14528 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
14529 }
14530 })
14531 })
14532 .detach_and_log_err(cx);
14533 }
14534
14535 fn save_buffers_for_ranges_if_needed(
14536 &mut self,
14537 ranges: &[Range<Anchor>],
14538 cx: &mut Context<'_, Editor>,
14539 ) -> Task<Result<()>> {
14540 let multibuffer = self.buffer.read(cx);
14541 let snapshot = multibuffer.read(cx);
14542 let buffer_ids: HashSet<_> = ranges
14543 .iter()
14544 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
14545 .collect();
14546 drop(snapshot);
14547
14548 let mut buffers = HashSet::default();
14549 for buffer_id in buffer_ids {
14550 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
14551 let buffer = buffer_entity.read(cx);
14552 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
14553 {
14554 buffers.insert(buffer_entity);
14555 }
14556 }
14557 }
14558
14559 if let Some(project) = &self.project {
14560 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
14561 } else {
14562 Task::ready(Ok(()))
14563 }
14564 }
14565
14566 fn do_stage_or_unstage_and_next(
14567 &mut self,
14568 stage: bool,
14569 window: &mut Window,
14570 cx: &mut Context<Self>,
14571 ) {
14572 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
14573
14574 if ranges.iter().any(|range| range.start != range.end) {
14575 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
14576 return;
14577 }
14578
14579 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
14580 let snapshot = self.snapshot(window, cx);
14581 let position = self.selections.newest::<Point>(cx).head();
14582 let mut row = snapshot
14583 .buffer_snapshot
14584 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14585 .find(|hunk| hunk.row_range.start.0 > position.row)
14586 .map(|hunk| hunk.row_range.start);
14587
14588 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
14589 // Outside of the project diff editor, wrap around to the beginning.
14590 if !all_diff_hunks_expanded {
14591 row = row.or_else(|| {
14592 snapshot
14593 .buffer_snapshot
14594 .diff_hunks_in_range(Point::zero()..position)
14595 .find(|hunk| hunk.row_range.end.0 < position.row)
14596 .map(|hunk| hunk.row_range.start)
14597 });
14598 }
14599
14600 if let Some(row) = row {
14601 let destination = Point::new(row.0, 0);
14602 let autoscroll = Autoscroll::center();
14603
14604 self.unfold_ranges(&[destination..destination], false, false, cx);
14605 self.change_selections(Some(autoscroll), window, cx, |s| {
14606 s.select_ranges([destination..destination]);
14607 });
14608 }
14609 }
14610
14611 fn do_stage_or_unstage(
14612 &self,
14613 stage: bool,
14614 buffer_id: BufferId,
14615 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
14616 cx: &mut App,
14617 ) -> Option<()> {
14618 let project = self.project.as_ref()?;
14619 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
14620 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
14621 let buffer_snapshot = buffer.read(cx).snapshot();
14622 let file_exists = buffer_snapshot
14623 .file()
14624 .is_some_and(|file| file.disk_state().exists());
14625 diff.update(cx, |diff, cx| {
14626 diff.stage_or_unstage_hunks(
14627 stage,
14628 &hunks
14629 .map(|hunk| buffer_diff::DiffHunk {
14630 buffer_range: hunk.buffer_range,
14631 diff_base_byte_range: hunk.diff_base_byte_range,
14632 secondary_status: hunk.secondary_status,
14633 range: Point::zero()..Point::zero(), // unused
14634 })
14635 .collect::<Vec<_>>(),
14636 &buffer_snapshot,
14637 file_exists,
14638 cx,
14639 )
14640 });
14641 None
14642 }
14643
14644 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
14645 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
14646 self.buffer
14647 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
14648 }
14649
14650 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
14651 self.buffer.update(cx, |buffer, cx| {
14652 let ranges = vec![Anchor::min()..Anchor::max()];
14653 if !buffer.all_diff_hunks_expanded()
14654 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
14655 {
14656 buffer.collapse_diff_hunks(ranges, cx);
14657 true
14658 } else {
14659 false
14660 }
14661 })
14662 }
14663
14664 fn toggle_diff_hunks_in_ranges(
14665 &mut self,
14666 ranges: Vec<Range<Anchor>>,
14667 cx: &mut Context<'_, Editor>,
14668 ) {
14669 self.buffer.update(cx, |buffer, cx| {
14670 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
14671 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
14672 })
14673 }
14674
14675 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
14676 self.buffer.update(cx, |buffer, cx| {
14677 let snapshot = buffer.snapshot(cx);
14678 let excerpt_id = range.end.excerpt_id;
14679 let point_range = range.to_point(&snapshot);
14680 let expand = !buffer.single_hunk_is_expanded(range, cx);
14681 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
14682 })
14683 }
14684
14685 pub(crate) fn apply_all_diff_hunks(
14686 &mut self,
14687 _: &ApplyAllDiffHunks,
14688 window: &mut Window,
14689 cx: &mut Context<Self>,
14690 ) {
14691 let buffers = self.buffer.read(cx).all_buffers();
14692 for branch_buffer in buffers {
14693 branch_buffer.update(cx, |branch_buffer, cx| {
14694 branch_buffer.merge_into_base(Vec::new(), cx);
14695 });
14696 }
14697
14698 if let Some(project) = self.project.clone() {
14699 self.save(true, project, window, cx).detach_and_log_err(cx);
14700 }
14701 }
14702
14703 pub(crate) fn apply_selected_diff_hunks(
14704 &mut self,
14705 _: &ApplyDiffHunk,
14706 window: &mut Window,
14707 cx: &mut Context<Self>,
14708 ) {
14709 let snapshot = self.snapshot(window, cx);
14710 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
14711 let mut ranges_by_buffer = HashMap::default();
14712 self.transact(window, cx, |editor, _window, cx| {
14713 for hunk in hunks {
14714 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
14715 ranges_by_buffer
14716 .entry(buffer.clone())
14717 .or_insert_with(Vec::new)
14718 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
14719 }
14720 }
14721
14722 for (buffer, ranges) in ranges_by_buffer {
14723 buffer.update(cx, |buffer, cx| {
14724 buffer.merge_into_base(ranges, cx);
14725 });
14726 }
14727 });
14728
14729 if let Some(project) = self.project.clone() {
14730 self.save(true, project, window, cx).detach_and_log_err(cx);
14731 }
14732 }
14733
14734 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
14735 if hovered != self.gutter_hovered {
14736 self.gutter_hovered = hovered;
14737 cx.notify();
14738 }
14739 }
14740
14741 pub fn insert_blocks(
14742 &mut self,
14743 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
14744 autoscroll: Option<Autoscroll>,
14745 cx: &mut Context<Self>,
14746 ) -> Vec<CustomBlockId> {
14747 let blocks = self
14748 .display_map
14749 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
14750 if let Some(autoscroll) = autoscroll {
14751 self.request_autoscroll(autoscroll, cx);
14752 }
14753 cx.notify();
14754 blocks
14755 }
14756
14757 pub fn resize_blocks(
14758 &mut self,
14759 heights: HashMap<CustomBlockId, u32>,
14760 autoscroll: Option<Autoscroll>,
14761 cx: &mut Context<Self>,
14762 ) {
14763 self.display_map
14764 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
14765 if let Some(autoscroll) = autoscroll {
14766 self.request_autoscroll(autoscroll, cx);
14767 }
14768 cx.notify();
14769 }
14770
14771 pub fn replace_blocks(
14772 &mut self,
14773 renderers: HashMap<CustomBlockId, RenderBlock>,
14774 autoscroll: Option<Autoscroll>,
14775 cx: &mut Context<Self>,
14776 ) {
14777 self.display_map
14778 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
14779 if let Some(autoscroll) = autoscroll {
14780 self.request_autoscroll(autoscroll, cx);
14781 }
14782 cx.notify();
14783 }
14784
14785 pub fn remove_blocks(
14786 &mut self,
14787 block_ids: HashSet<CustomBlockId>,
14788 autoscroll: Option<Autoscroll>,
14789 cx: &mut Context<Self>,
14790 ) {
14791 self.display_map.update(cx, |display_map, cx| {
14792 display_map.remove_blocks(block_ids, cx)
14793 });
14794 if let Some(autoscroll) = autoscroll {
14795 self.request_autoscroll(autoscroll, cx);
14796 }
14797 cx.notify();
14798 }
14799
14800 pub fn row_for_block(
14801 &self,
14802 block_id: CustomBlockId,
14803 cx: &mut Context<Self>,
14804 ) -> Option<DisplayRow> {
14805 self.display_map
14806 .update(cx, |map, cx| map.row_for_block(block_id, cx))
14807 }
14808
14809 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
14810 self.focused_block = Some(focused_block);
14811 }
14812
14813 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
14814 self.focused_block.take()
14815 }
14816
14817 pub fn insert_creases(
14818 &mut self,
14819 creases: impl IntoIterator<Item = Crease<Anchor>>,
14820 cx: &mut Context<Self>,
14821 ) -> Vec<CreaseId> {
14822 self.display_map
14823 .update(cx, |map, cx| map.insert_creases(creases, cx))
14824 }
14825
14826 pub fn remove_creases(
14827 &mut self,
14828 ids: impl IntoIterator<Item = CreaseId>,
14829 cx: &mut Context<Self>,
14830 ) {
14831 self.display_map
14832 .update(cx, |map, cx| map.remove_creases(ids, cx));
14833 }
14834
14835 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
14836 self.display_map
14837 .update(cx, |map, cx| map.snapshot(cx))
14838 .longest_row()
14839 }
14840
14841 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
14842 self.display_map
14843 .update(cx, |map, cx| map.snapshot(cx))
14844 .max_point()
14845 }
14846
14847 pub fn text(&self, cx: &App) -> String {
14848 self.buffer.read(cx).read(cx).text()
14849 }
14850
14851 pub fn is_empty(&self, cx: &App) -> bool {
14852 self.buffer.read(cx).read(cx).is_empty()
14853 }
14854
14855 pub fn text_option(&self, cx: &App) -> Option<String> {
14856 let text = self.text(cx);
14857 let text = text.trim();
14858
14859 if text.is_empty() {
14860 return None;
14861 }
14862
14863 Some(text.to_string())
14864 }
14865
14866 pub fn set_text(
14867 &mut self,
14868 text: impl Into<Arc<str>>,
14869 window: &mut Window,
14870 cx: &mut Context<Self>,
14871 ) {
14872 self.transact(window, cx, |this, _, cx| {
14873 this.buffer
14874 .read(cx)
14875 .as_singleton()
14876 .expect("you can only call set_text on editors for singleton buffers")
14877 .update(cx, |buffer, cx| buffer.set_text(text, cx));
14878 });
14879 }
14880
14881 pub fn display_text(&self, cx: &mut App) -> String {
14882 self.display_map
14883 .update(cx, |map, cx| map.snapshot(cx))
14884 .text()
14885 }
14886
14887 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
14888 let mut wrap_guides = smallvec::smallvec![];
14889
14890 if self.show_wrap_guides == Some(false) {
14891 return wrap_guides;
14892 }
14893
14894 let settings = self.buffer.read(cx).language_settings(cx);
14895 if settings.show_wrap_guides {
14896 match self.soft_wrap_mode(cx) {
14897 SoftWrap::Column(soft_wrap) => {
14898 wrap_guides.push((soft_wrap as usize, true));
14899 }
14900 SoftWrap::Bounded(soft_wrap) => {
14901 wrap_guides.push((soft_wrap as usize, true));
14902 }
14903 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
14904 }
14905 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
14906 }
14907
14908 wrap_guides
14909 }
14910
14911 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
14912 let settings = self.buffer.read(cx).language_settings(cx);
14913 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
14914 match mode {
14915 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
14916 SoftWrap::None
14917 }
14918 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
14919 language_settings::SoftWrap::PreferredLineLength => {
14920 SoftWrap::Column(settings.preferred_line_length)
14921 }
14922 language_settings::SoftWrap::Bounded => {
14923 SoftWrap::Bounded(settings.preferred_line_length)
14924 }
14925 }
14926 }
14927
14928 pub fn set_soft_wrap_mode(
14929 &mut self,
14930 mode: language_settings::SoftWrap,
14931
14932 cx: &mut Context<Self>,
14933 ) {
14934 self.soft_wrap_mode_override = Some(mode);
14935 cx.notify();
14936 }
14937
14938 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
14939 self.hard_wrap = hard_wrap;
14940 cx.notify();
14941 }
14942
14943 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
14944 self.text_style_refinement = Some(style);
14945 }
14946
14947 /// called by the Element so we know what style we were most recently rendered with.
14948 pub(crate) fn set_style(
14949 &mut self,
14950 style: EditorStyle,
14951 window: &mut Window,
14952 cx: &mut Context<Self>,
14953 ) {
14954 let rem_size = window.rem_size();
14955 self.display_map.update(cx, |map, cx| {
14956 map.set_font(
14957 style.text.font(),
14958 style.text.font_size.to_pixels(rem_size),
14959 cx,
14960 )
14961 });
14962 self.style = Some(style);
14963 }
14964
14965 pub fn style(&self) -> Option<&EditorStyle> {
14966 self.style.as_ref()
14967 }
14968
14969 // Called by the element. This method is not designed to be called outside of the editor
14970 // element's layout code because it does not notify when rewrapping is computed synchronously.
14971 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
14972 self.display_map
14973 .update(cx, |map, cx| map.set_wrap_width(width, cx))
14974 }
14975
14976 pub fn set_soft_wrap(&mut self) {
14977 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
14978 }
14979
14980 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
14981 if self.soft_wrap_mode_override.is_some() {
14982 self.soft_wrap_mode_override.take();
14983 } else {
14984 let soft_wrap = match self.soft_wrap_mode(cx) {
14985 SoftWrap::GitDiff => return,
14986 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
14987 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
14988 language_settings::SoftWrap::None
14989 }
14990 };
14991 self.soft_wrap_mode_override = Some(soft_wrap);
14992 }
14993 cx.notify();
14994 }
14995
14996 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
14997 let Some(workspace) = self.workspace() else {
14998 return;
14999 };
15000 let fs = workspace.read(cx).app_state().fs.clone();
15001 let current_show = TabBarSettings::get_global(cx).show;
15002 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
15003 setting.show = Some(!current_show);
15004 });
15005 }
15006
15007 pub fn toggle_indent_guides(
15008 &mut self,
15009 _: &ToggleIndentGuides,
15010 _: &mut Window,
15011 cx: &mut Context<Self>,
15012 ) {
15013 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
15014 self.buffer
15015 .read(cx)
15016 .language_settings(cx)
15017 .indent_guides
15018 .enabled
15019 });
15020 self.show_indent_guides = Some(!currently_enabled);
15021 cx.notify();
15022 }
15023
15024 fn should_show_indent_guides(&self) -> Option<bool> {
15025 self.show_indent_guides
15026 }
15027
15028 pub fn toggle_line_numbers(
15029 &mut self,
15030 _: &ToggleLineNumbers,
15031 _: &mut Window,
15032 cx: &mut Context<Self>,
15033 ) {
15034 let mut editor_settings = EditorSettings::get_global(cx).clone();
15035 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
15036 EditorSettings::override_global(editor_settings, cx);
15037 }
15038
15039 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
15040 if let Some(show_line_numbers) = self.show_line_numbers {
15041 return show_line_numbers;
15042 }
15043 EditorSettings::get_global(cx).gutter.line_numbers
15044 }
15045
15046 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
15047 self.use_relative_line_numbers
15048 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
15049 }
15050
15051 pub fn toggle_relative_line_numbers(
15052 &mut self,
15053 _: &ToggleRelativeLineNumbers,
15054 _: &mut Window,
15055 cx: &mut Context<Self>,
15056 ) {
15057 let is_relative = self.should_use_relative_line_numbers(cx);
15058 self.set_relative_line_number(Some(!is_relative), cx)
15059 }
15060
15061 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
15062 self.use_relative_line_numbers = is_relative;
15063 cx.notify();
15064 }
15065
15066 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
15067 self.show_gutter = show_gutter;
15068 cx.notify();
15069 }
15070
15071 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
15072 self.show_scrollbars = show_scrollbars;
15073 cx.notify();
15074 }
15075
15076 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
15077 self.show_line_numbers = Some(show_line_numbers);
15078 cx.notify();
15079 }
15080
15081 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
15082 self.show_git_diff_gutter = Some(show_git_diff_gutter);
15083 cx.notify();
15084 }
15085
15086 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
15087 self.show_code_actions = Some(show_code_actions);
15088 cx.notify();
15089 }
15090
15091 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
15092 self.show_runnables = Some(show_runnables);
15093 cx.notify();
15094 }
15095
15096 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
15097 self.show_breakpoints = Some(show_breakpoints);
15098 cx.notify();
15099 }
15100
15101 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
15102 if self.display_map.read(cx).masked != masked {
15103 self.display_map.update(cx, |map, _| map.masked = masked);
15104 }
15105 cx.notify()
15106 }
15107
15108 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
15109 self.show_wrap_guides = Some(show_wrap_guides);
15110 cx.notify();
15111 }
15112
15113 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
15114 self.show_indent_guides = Some(show_indent_guides);
15115 cx.notify();
15116 }
15117
15118 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
15119 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
15120 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
15121 if let Some(dir) = file.abs_path(cx).parent() {
15122 return Some(dir.to_owned());
15123 }
15124 }
15125
15126 if let Some(project_path) = buffer.read(cx).project_path(cx) {
15127 return Some(project_path.path.to_path_buf());
15128 }
15129 }
15130
15131 None
15132 }
15133
15134 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
15135 self.active_excerpt(cx)?
15136 .1
15137 .read(cx)
15138 .file()
15139 .and_then(|f| f.as_local())
15140 }
15141
15142 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15143 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15144 let buffer = buffer.read(cx);
15145 if let Some(project_path) = buffer.project_path(cx) {
15146 let project = self.project.as_ref()?.read(cx);
15147 project.absolute_path(&project_path, cx)
15148 } else {
15149 buffer
15150 .file()
15151 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
15152 }
15153 })
15154 }
15155
15156 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15157 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15158 let project_path = buffer.read(cx).project_path(cx)?;
15159 let project = self.project.as_ref()?.read(cx);
15160 let entry = project.entry_for_path(&project_path, cx)?;
15161 let path = entry.path.to_path_buf();
15162 Some(path)
15163 })
15164 }
15165
15166 pub fn reveal_in_finder(
15167 &mut self,
15168 _: &RevealInFileManager,
15169 _window: &mut Window,
15170 cx: &mut Context<Self>,
15171 ) {
15172 if let Some(target) = self.target_file(cx) {
15173 cx.reveal_path(&target.abs_path(cx));
15174 }
15175 }
15176
15177 pub fn copy_path(
15178 &mut self,
15179 _: &zed_actions::workspace::CopyPath,
15180 _window: &mut Window,
15181 cx: &mut Context<Self>,
15182 ) {
15183 if let Some(path) = self.target_file_abs_path(cx) {
15184 if let Some(path) = path.to_str() {
15185 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15186 }
15187 }
15188 }
15189
15190 pub fn copy_relative_path(
15191 &mut self,
15192 _: &zed_actions::workspace::CopyRelativePath,
15193 _window: &mut Window,
15194 cx: &mut Context<Self>,
15195 ) {
15196 if let Some(path) = self.target_file_path(cx) {
15197 if let Some(path) = path.to_str() {
15198 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15199 }
15200 }
15201 }
15202
15203 pub fn project_path(&self, cx: &mut Context<Self>) -> Option<ProjectPath> {
15204 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
15205 buffer.read_with(cx, |buffer, cx| buffer.project_path(cx))
15206 } else {
15207 None
15208 }
15209 }
15210
15211 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) {
15212 let _ = maybe!({
15213 let breakpoint_store = self.breakpoint_store.as_ref()?;
15214
15215 let Some((_, _, active_position)) =
15216 breakpoint_store.read(cx).active_position().cloned()
15217 else {
15218 self.clear_row_highlights::<DebugCurrentRowHighlight>();
15219 return None;
15220 };
15221
15222 let snapshot = self
15223 .project
15224 .as_ref()?
15225 .read(cx)
15226 .buffer_for_id(active_position.buffer_id?, cx)?
15227 .read(cx)
15228 .snapshot();
15229
15230 for (id, ExcerptRange { context, .. }) in self
15231 .buffer
15232 .read(cx)
15233 .excerpts_for_buffer(active_position.buffer_id?, cx)
15234 {
15235 if context.start.cmp(&active_position, &snapshot).is_ge()
15236 || context.end.cmp(&active_position, &snapshot).is_lt()
15237 {
15238 continue;
15239 }
15240 let snapshot = self.buffer.read(cx).snapshot(cx);
15241 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, active_position)?;
15242
15243 self.clear_row_highlights::<DebugCurrentRowHighlight>();
15244 self.go_to_line::<DebugCurrentRowHighlight>(
15245 multibuffer_anchor,
15246 Some(cx.theme().colors().editor_debugger_active_line_background),
15247 window,
15248 cx,
15249 );
15250
15251 cx.notify();
15252 }
15253
15254 Some(())
15255 });
15256 }
15257
15258 pub fn copy_file_name_without_extension(
15259 &mut self,
15260 _: &CopyFileNameWithoutExtension,
15261 _: &mut Window,
15262 cx: &mut Context<Self>,
15263 ) {
15264 if let Some(file) = self.target_file(cx) {
15265 if let Some(file_stem) = file.path().file_stem() {
15266 if let Some(name) = file_stem.to_str() {
15267 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
15268 }
15269 }
15270 }
15271 }
15272
15273 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
15274 if let Some(file) = self.target_file(cx) {
15275 if let Some(file_name) = file.path().file_name() {
15276 if let Some(name) = file_name.to_str() {
15277 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
15278 }
15279 }
15280 }
15281 }
15282
15283 pub fn toggle_git_blame(
15284 &mut self,
15285 _: &::git::Blame,
15286 window: &mut Window,
15287 cx: &mut Context<Self>,
15288 ) {
15289 self.show_git_blame_gutter = !self.show_git_blame_gutter;
15290
15291 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
15292 self.start_git_blame(true, window, cx);
15293 }
15294
15295 cx.notify();
15296 }
15297
15298 pub fn toggle_git_blame_inline(
15299 &mut self,
15300 _: &ToggleGitBlameInline,
15301 window: &mut Window,
15302 cx: &mut Context<Self>,
15303 ) {
15304 self.toggle_git_blame_inline_internal(true, window, cx);
15305 cx.notify();
15306 }
15307
15308 pub fn git_blame_inline_enabled(&self) -> bool {
15309 self.git_blame_inline_enabled
15310 }
15311
15312 pub fn toggle_selection_menu(
15313 &mut self,
15314 _: &ToggleSelectionMenu,
15315 _: &mut Window,
15316 cx: &mut Context<Self>,
15317 ) {
15318 self.show_selection_menu = self
15319 .show_selection_menu
15320 .map(|show_selections_menu| !show_selections_menu)
15321 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
15322
15323 cx.notify();
15324 }
15325
15326 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
15327 self.show_selection_menu
15328 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
15329 }
15330
15331 fn start_git_blame(
15332 &mut self,
15333 user_triggered: bool,
15334 window: &mut Window,
15335 cx: &mut Context<Self>,
15336 ) {
15337 if let Some(project) = self.project.as_ref() {
15338 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
15339 return;
15340 };
15341
15342 if buffer.read(cx).file().is_none() {
15343 return;
15344 }
15345
15346 let focused = self.focus_handle(cx).contains_focused(window, cx);
15347
15348 let project = project.clone();
15349 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
15350 self.blame_subscription =
15351 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
15352 self.blame = Some(blame);
15353 }
15354 }
15355
15356 fn toggle_git_blame_inline_internal(
15357 &mut self,
15358 user_triggered: bool,
15359 window: &mut Window,
15360 cx: &mut Context<Self>,
15361 ) {
15362 if self.git_blame_inline_enabled {
15363 self.git_blame_inline_enabled = false;
15364 self.show_git_blame_inline = false;
15365 self.show_git_blame_inline_delay_task.take();
15366 } else {
15367 self.git_blame_inline_enabled = true;
15368 self.start_git_blame_inline(user_triggered, window, cx);
15369 }
15370
15371 cx.notify();
15372 }
15373
15374 fn start_git_blame_inline(
15375 &mut self,
15376 user_triggered: bool,
15377 window: &mut Window,
15378 cx: &mut Context<Self>,
15379 ) {
15380 self.start_git_blame(user_triggered, window, cx);
15381
15382 if ProjectSettings::get_global(cx)
15383 .git
15384 .inline_blame_delay()
15385 .is_some()
15386 {
15387 self.start_inline_blame_timer(window, cx);
15388 } else {
15389 self.show_git_blame_inline = true
15390 }
15391 }
15392
15393 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
15394 self.blame.as_ref()
15395 }
15396
15397 pub fn show_git_blame_gutter(&self) -> bool {
15398 self.show_git_blame_gutter
15399 }
15400
15401 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
15402 self.show_git_blame_gutter && self.has_blame_entries(cx)
15403 }
15404
15405 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
15406 self.show_git_blame_inline
15407 && (self.focus_handle.is_focused(window)
15408 || self
15409 .git_blame_inline_tooltip
15410 .as_ref()
15411 .and_then(|t| t.upgrade())
15412 .is_some())
15413 && !self.newest_selection_head_on_empty_line(cx)
15414 && self.has_blame_entries(cx)
15415 }
15416
15417 fn has_blame_entries(&self, cx: &App) -> bool {
15418 self.blame()
15419 .map_or(false, |blame| blame.read(cx).has_generated_entries())
15420 }
15421
15422 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
15423 let cursor_anchor = self.selections.newest_anchor().head();
15424
15425 let snapshot = self.buffer.read(cx).snapshot(cx);
15426 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
15427
15428 snapshot.line_len(buffer_row) == 0
15429 }
15430
15431 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
15432 let buffer_and_selection = maybe!({
15433 let selection = self.selections.newest::<Point>(cx);
15434 let selection_range = selection.range();
15435
15436 let multi_buffer = self.buffer().read(cx);
15437 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15438 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
15439
15440 let (buffer, range, _) = if selection.reversed {
15441 buffer_ranges.first()
15442 } else {
15443 buffer_ranges.last()
15444 }?;
15445
15446 let selection = text::ToPoint::to_point(&range.start, &buffer).row
15447 ..text::ToPoint::to_point(&range.end, &buffer).row;
15448 Some((
15449 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
15450 selection,
15451 ))
15452 });
15453
15454 let Some((buffer, selection)) = buffer_and_selection else {
15455 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
15456 };
15457
15458 let Some(project) = self.project.as_ref() else {
15459 return Task::ready(Err(anyhow!("editor does not have project")));
15460 };
15461
15462 project.update(cx, |project, cx| {
15463 project.get_permalink_to_line(&buffer, selection, cx)
15464 })
15465 }
15466
15467 pub fn copy_permalink_to_line(
15468 &mut self,
15469 _: &CopyPermalinkToLine,
15470 window: &mut Window,
15471 cx: &mut Context<Self>,
15472 ) {
15473 let permalink_task = self.get_permalink_to_line(cx);
15474 let workspace = self.workspace();
15475
15476 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
15477 Ok(permalink) => {
15478 cx.update(|_, cx| {
15479 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
15480 })
15481 .ok();
15482 }
15483 Err(err) => {
15484 let message = format!("Failed to copy permalink: {err}");
15485
15486 Err::<(), anyhow::Error>(err).log_err();
15487
15488 if let Some(workspace) = workspace {
15489 workspace
15490 .update_in(cx, |workspace, _, cx| {
15491 struct CopyPermalinkToLine;
15492
15493 workspace.show_toast(
15494 Toast::new(
15495 NotificationId::unique::<CopyPermalinkToLine>(),
15496 message,
15497 ),
15498 cx,
15499 )
15500 })
15501 .ok();
15502 }
15503 }
15504 })
15505 .detach();
15506 }
15507
15508 pub fn copy_file_location(
15509 &mut self,
15510 _: &CopyFileLocation,
15511 _: &mut Window,
15512 cx: &mut Context<Self>,
15513 ) {
15514 let selection = self.selections.newest::<Point>(cx).start.row + 1;
15515 if let Some(file) = self.target_file(cx) {
15516 if let Some(path) = file.path().to_str() {
15517 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
15518 }
15519 }
15520 }
15521
15522 pub fn open_permalink_to_line(
15523 &mut self,
15524 _: &OpenPermalinkToLine,
15525 window: &mut Window,
15526 cx: &mut Context<Self>,
15527 ) {
15528 let permalink_task = self.get_permalink_to_line(cx);
15529 let workspace = self.workspace();
15530
15531 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
15532 Ok(permalink) => {
15533 cx.update(|_, cx| {
15534 cx.open_url(permalink.as_ref());
15535 })
15536 .ok();
15537 }
15538 Err(err) => {
15539 let message = format!("Failed to open permalink: {err}");
15540
15541 Err::<(), anyhow::Error>(err).log_err();
15542
15543 if let Some(workspace) = workspace {
15544 workspace
15545 .update(cx, |workspace, cx| {
15546 struct OpenPermalinkToLine;
15547
15548 workspace.show_toast(
15549 Toast::new(
15550 NotificationId::unique::<OpenPermalinkToLine>(),
15551 message,
15552 ),
15553 cx,
15554 )
15555 })
15556 .ok();
15557 }
15558 }
15559 })
15560 .detach();
15561 }
15562
15563 pub fn insert_uuid_v4(
15564 &mut self,
15565 _: &InsertUuidV4,
15566 window: &mut Window,
15567 cx: &mut Context<Self>,
15568 ) {
15569 self.insert_uuid(UuidVersion::V4, window, cx);
15570 }
15571
15572 pub fn insert_uuid_v7(
15573 &mut self,
15574 _: &InsertUuidV7,
15575 window: &mut Window,
15576 cx: &mut Context<Self>,
15577 ) {
15578 self.insert_uuid(UuidVersion::V7, window, cx);
15579 }
15580
15581 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
15582 self.transact(window, cx, |this, window, cx| {
15583 let edits = this
15584 .selections
15585 .all::<Point>(cx)
15586 .into_iter()
15587 .map(|selection| {
15588 let uuid = match version {
15589 UuidVersion::V4 => uuid::Uuid::new_v4(),
15590 UuidVersion::V7 => uuid::Uuid::now_v7(),
15591 };
15592
15593 (selection.range(), uuid.to_string())
15594 });
15595 this.edit(edits, cx);
15596 this.refresh_inline_completion(true, false, window, cx);
15597 });
15598 }
15599
15600 pub fn open_selections_in_multibuffer(
15601 &mut self,
15602 _: &OpenSelectionsInMultibuffer,
15603 window: &mut Window,
15604 cx: &mut Context<Self>,
15605 ) {
15606 let multibuffer = self.buffer.read(cx);
15607
15608 let Some(buffer) = multibuffer.as_singleton() else {
15609 return;
15610 };
15611
15612 let Some(workspace) = self.workspace() else {
15613 return;
15614 };
15615
15616 let locations = self
15617 .selections
15618 .disjoint_anchors()
15619 .iter()
15620 .map(|range| Location {
15621 buffer: buffer.clone(),
15622 range: range.start.text_anchor..range.end.text_anchor,
15623 })
15624 .collect::<Vec<_>>();
15625
15626 let title = multibuffer.title(cx).to_string();
15627
15628 cx.spawn_in(window, async move |_, cx| {
15629 workspace.update_in(cx, |workspace, window, cx| {
15630 Self::open_locations_in_multibuffer(
15631 workspace,
15632 locations,
15633 format!("Selections for '{title}'"),
15634 false,
15635 MultibufferSelectionMode::All,
15636 window,
15637 cx,
15638 );
15639 })
15640 })
15641 .detach();
15642 }
15643
15644 /// Adds a row highlight for the given range. If a row has multiple highlights, the
15645 /// last highlight added will be used.
15646 ///
15647 /// If the range ends at the beginning of a line, then that line will not be highlighted.
15648 pub fn highlight_rows<T: 'static>(
15649 &mut self,
15650 range: Range<Anchor>,
15651 color: Hsla,
15652 should_autoscroll: bool,
15653 cx: &mut Context<Self>,
15654 ) {
15655 let snapshot = self.buffer().read(cx).snapshot(cx);
15656 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
15657 let ix = row_highlights.binary_search_by(|highlight| {
15658 Ordering::Equal
15659 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
15660 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
15661 });
15662
15663 if let Err(mut ix) = ix {
15664 let index = post_inc(&mut self.highlight_order);
15665
15666 // If this range intersects with the preceding highlight, then merge it with
15667 // the preceding highlight. Otherwise insert a new highlight.
15668 let mut merged = false;
15669 if ix > 0 {
15670 let prev_highlight = &mut row_highlights[ix - 1];
15671 if prev_highlight
15672 .range
15673 .end
15674 .cmp(&range.start, &snapshot)
15675 .is_ge()
15676 {
15677 ix -= 1;
15678 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
15679 prev_highlight.range.end = range.end;
15680 }
15681 merged = true;
15682 prev_highlight.index = index;
15683 prev_highlight.color = color;
15684 prev_highlight.should_autoscroll = should_autoscroll;
15685 }
15686 }
15687
15688 if !merged {
15689 row_highlights.insert(
15690 ix,
15691 RowHighlight {
15692 range: range.clone(),
15693 index,
15694 color,
15695 should_autoscroll,
15696 },
15697 );
15698 }
15699
15700 // If any of the following highlights intersect with this one, merge them.
15701 while let Some(next_highlight) = row_highlights.get(ix + 1) {
15702 let highlight = &row_highlights[ix];
15703 if next_highlight
15704 .range
15705 .start
15706 .cmp(&highlight.range.end, &snapshot)
15707 .is_le()
15708 {
15709 if next_highlight
15710 .range
15711 .end
15712 .cmp(&highlight.range.end, &snapshot)
15713 .is_gt()
15714 {
15715 row_highlights[ix].range.end = next_highlight.range.end;
15716 }
15717 row_highlights.remove(ix + 1);
15718 } else {
15719 break;
15720 }
15721 }
15722 }
15723 }
15724
15725 /// Remove any highlighted row ranges of the given type that intersect the
15726 /// given ranges.
15727 pub fn remove_highlighted_rows<T: 'static>(
15728 &mut self,
15729 ranges_to_remove: Vec<Range<Anchor>>,
15730 cx: &mut Context<Self>,
15731 ) {
15732 let snapshot = self.buffer().read(cx).snapshot(cx);
15733 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
15734 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
15735 row_highlights.retain(|highlight| {
15736 while let Some(range_to_remove) = ranges_to_remove.peek() {
15737 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
15738 Ordering::Less | Ordering::Equal => {
15739 ranges_to_remove.next();
15740 }
15741 Ordering::Greater => {
15742 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
15743 Ordering::Less | Ordering::Equal => {
15744 return false;
15745 }
15746 Ordering::Greater => break,
15747 }
15748 }
15749 }
15750 }
15751
15752 true
15753 })
15754 }
15755
15756 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
15757 pub fn clear_row_highlights<T: 'static>(&mut self) {
15758 self.highlighted_rows.remove(&TypeId::of::<T>());
15759 }
15760
15761 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
15762 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
15763 self.highlighted_rows
15764 .get(&TypeId::of::<T>())
15765 .map_or(&[] as &[_], |vec| vec.as_slice())
15766 .iter()
15767 .map(|highlight| (highlight.range.clone(), highlight.color))
15768 }
15769
15770 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
15771 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
15772 /// Allows to ignore certain kinds of highlights.
15773 pub fn highlighted_display_rows(
15774 &self,
15775 window: &mut Window,
15776 cx: &mut App,
15777 ) -> BTreeMap<DisplayRow, LineHighlight> {
15778 let snapshot = self.snapshot(window, cx);
15779 let mut used_highlight_orders = HashMap::default();
15780 self.highlighted_rows
15781 .iter()
15782 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
15783 .fold(
15784 BTreeMap::<DisplayRow, LineHighlight>::new(),
15785 |mut unique_rows, highlight| {
15786 let start = highlight.range.start.to_display_point(&snapshot);
15787 let end = highlight.range.end.to_display_point(&snapshot);
15788 let start_row = start.row().0;
15789 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
15790 && end.column() == 0
15791 {
15792 end.row().0.saturating_sub(1)
15793 } else {
15794 end.row().0
15795 };
15796 for row in start_row..=end_row {
15797 let used_index =
15798 used_highlight_orders.entry(row).or_insert(highlight.index);
15799 if highlight.index >= *used_index {
15800 *used_index = highlight.index;
15801 unique_rows.insert(DisplayRow(row), highlight.color.into());
15802 }
15803 }
15804 unique_rows
15805 },
15806 )
15807 }
15808
15809 pub fn highlighted_display_row_for_autoscroll(
15810 &self,
15811 snapshot: &DisplaySnapshot,
15812 ) -> Option<DisplayRow> {
15813 self.highlighted_rows
15814 .values()
15815 .flat_map(|highlighted_rows| highlighted_rows.iter())
15816 .filter_map(|highlight| {
15817 if highlight.should_autoscroll {
15818 Some(highlight.range.start.to_display_point(snapshot).row())
15819 } else {
15820 None
15821 }
15822 })
15823 .min()
15824 }
15825
15826 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
15827 self.highlight_background::<SearchWithinRange>(
15828 ranges,
15829 |colors| colors.editor_document_highlight_read_background,
15830 cx,
15831 )
15832 }
15833
15834 pub fn set_breadcrumb_header(&mut self, new_header: String) {
15835 self.breadcrumb_header = Some(new_header);
15836 }
15837
15838 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
15839 self.clear_background_highlights::<SearchWithinRange>(cx);
15840 }
15841
15842 pub fn highlight_background<T: 'static>(
15843 &mut self,
15844 ranges: &[Range<Anchor>],
15845 color_fetcher: fn(&ThemeColors) -> Hsla,
15846 cx: &mut Context<Self>,
15847 ) {
15848 self.background_highlights
15849 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
15850 self.scrollbar_marker_state.dirty = true;
15851 cx.notify();
15852 }
15853
15854 pub fn clear_background_highlights<T: 'static>(
15855 &mut self,
15856 cx: &mut Context<Self>,
15857 ) -> Option<BackgroundHighlight> {
15858 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
15859 if !text_highlights.1.is_empty() {
15860 self.scrollbar_marker_state.dirty = true;
15861 cx.notify();
15862 }
15863 Some(text_highlights)
15864 }
15865
15866 pub fn highlight_gutter<T: 'static>(
15867 &mut self,
15868 ranges: &[Range<Anchor>],
15869 color_fetcher: fn(&App) -> Hsla,
15870 cx: &mut Context<Self>,
15871 ) {
15872 self.gutter_highlights
15873 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
15874 cx.notify();
15875 }
15876
15877 pub fn clear_gutter_highlights<T: 'static>(
15878 &mut self,
15879 cx: &mut Context<Self>,
15880 ) -> Option<GutterHighlight> {
15881 cx.notify();
15882 self.gutter_highlights.remove(&TypeId::of::<T>())
15883 }
15884
15885 #[cfg(feature = "test-support")]
15886 pub fn all_text_background_highlights(
15887 &self,
15888 window: &mut Window,
15889 cx: &mut Context<Self>,
15890 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
15891 let snapshot = self.snapshot(window, cx);
15892 let buffer = &snapshot.buffer_snapshot;
15893 let start = buffer.anchor_before(0);
15894 let end = buffer.anchor_after(buffer.len());
15895 let theme = cx.theme().colors();
15896 self.background_highlights_in_range(start..end, &snapshot, theme)
15897 }
15898
15899 #[cfg(feature = "test-support")]
15900 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
15901 let snapshot = self.buffer().read(cx).snapshot(cx);
15902
15903 let highlights = self
15904 .background_highlights
15905 .get(&TypeId::of::<items::BufferSearchHighlights>());
15906
15907 if let Some((_color, ranges)) = highlights {
15908 ranges
15909 .iter()
15910 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
15911 .collect_vec()
15912 } else {
15913 vec![]
15914 }
15915 }
15916
15917 fn document_highlights_for_position<'a>(
15918 &'a self,
15919 position: Anchor,
15920 buffer: &'a MultiBufferSnapshot,
15921 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
15922 let read_highlights = self
15923 .background_highlights
15924 .get(&TypeId::of::<DocumentHighlightRead>())
15925 .map(|h| &h.1);
15926 let write_highlights = self
15927 .background_highlights
15928 .get(&TypeId::of::<DocumentHighlightWrite>())
15929 .map(|h| &h.1);
15930 let left_position = position.bias_left(buffer);
15931 let right_position = position.bias_right(buffer);
15932 read_highlights
15933 .into_iter()
15934 .chain(write_highlights)
15935 .flat_map(move |ranges| {
15936 let start_ix = match ranges.binary_search_by(|probe| {
15937 let cmp = probe.end.cmp(&left_position, buffer);
15938 if cmp.is_ge() {
15939 Ordering::Greater
15940 } else {
15941 Ordering::Less
15942 }
15943 }) {
15944 Ok(i) | Err(i) => i,
15945 };
15946
15947 ranges[start_ix..]
15948 .iter()
15949 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
15950 })
15951 }
15952
15953 pub fn has_background_highlights<T: 'static>(&self) -> bool {
15954 self.background_highlights
15955 .get(&TypeId::of::<T>())
15956 .map_or(false, |(_, highlights)| !highlights.is_empty())
15957 }
15958
15959 pub fn background_highlights_in_range(
15960 &self,
15961 search_range: Range<Anchor>,
15962 display_snapshot: &DisplaySnapshot,
15963 theme: &ThemeColors,
15964 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
15965 let mut results = Vec::new();
15966 for (color_fetcher, ranges) in self.background_highlights.values() {
15967 let color = color_fetcher(theme);
15968 let start_ix = match ranges.binary_search_by(|probe| {
15969 let cmp = probe
15970 .end
15971 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
15972 if cmp.is_gt() {
15973 Ordering::Greater
15974 } else {
15975 Ordering::Less
15976 }
15977 }) {
15978 Ok(i) | Err(i) => i,
15979 };
15980 for range in &ranges[start_ix..] {
15981 if range
15982 .start
15983 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
15984 .is_ge()
15985 {
15986 break;
15987 }
15988
15989 let start = range.start.to_display_point(display_snapshot);
15990 let end = range.end.to_display_point(display_snapshot);
15991 results.push((start..end, color))
15992 }
15993 }
15994 results
15995 }
15996
15997 pub fn background_highlight_row_ranges<T: 'static>(
15998 &self,
15999 search_range: Range<Anchor>,
16000 display_snapshot: &DisplaySnapshot,
16001 count: usize,
16002 ) -> Vec<RangeInclusive<DisplayPoint>> {
16003 let mut results = Vec::new();
16004 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
16005 return vec![];
16006 };
16007
16008 let start_ix = match ranges.binary_search_by(|probe| {
16009 let cmp = probe
16010 .end
16011 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16012 if cmp.is_gt() {
16013 Ordering::Greater
16014 } else {
16015 Ordering::Less
16016 }
16017 }) {
16018 Ok(i) | Err(i) => i,
16019 };
16020 let mut push_region = |start: Option<Point>, end: Option<Point>| {
16021 if let (Some(start_display), Some(end_display)) = (start, end) {
16022 results.push(
16023 start_display.to_display_point(display_snapshot)
16024 ..=end_display.to_display_point(display_snapshot),
16025 );
16026 }
16027 };
16028 let mut start_row: Option<Point> = None;
16029 let mut end_row: Option<Point> = None;
16030 if ranges.len() > count {
16031 return Vec::new();
16032 }
16033 for range in &ranges[start_ix..] {
16034 if range
16035 .start
16036 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16037 .is_ge()
16038 {
16039 break;
16040 }
16041 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
16042 if let Some(current_row) = &end_row {
16043 if end.row == current_row.row {
16044 continue;
16045 }
16046 }
16047 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
16048 if start_row.is_none() {
16049 assert_eq!(end_row, None);
16050 start_row = Some(start);
16051 end_row = Some(end);
16052 continue;
16053 }
16054 if let Some(current_end) = end_row.as_mut() {
16055 if start.row > current_end.row + 1 {
16056 push_region(start_row, end_row);
16057 start_row = Some(start);
16058 end_row = Some(end);
16059 } else {
16060 // Merge two hunks.
16061 *current_end = end;
16062 }
16063 } else {
16064 unreachable!();
16065 }
16066 }
16067 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
16068 push_region(start_row, end_row);
16069 results
16070 }
16071
16072 pub fn gutter_highlights_in_range(
16073 &self,
16074 search_range: Range<Anchor>,
16075 display_snapshot: &DisplaySnapshot,
16076 cx: &App,
16077 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16078 let mut results = Vec::new();
16079 for (color_fetcher, ranges) in self.gutter_highlights.values() {
16080 let color = color_fetcher(cx);
16081 let start_ix = match ranges.binary_search_by(|probe| {
16082 let cmp = probe
16083 .end
16084 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16085 if cmp.is_gt() {
16086 Ordering::Greater
16087 } else {
16088 Ordering::Less
16089 }
16090 }) {
16091 Ok(i) | Err(i) => i,
16092 };
16093 for range in &ranges[start_ix..] {
16094 if range
16095 .start
16096 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16097 .is_ge()
16098 {
16099 break;
16100 }
16101
16102 let start = range.start.to_display_point(display_snapshot);
16103 let end = range.end.to_display_point(display_snapshot);
16104 results.push((start..end, color))
16105 }
16106 }
16107 results
16108 }
16109
16110 /// Get the text ranges corresponding to the redaction query
16111 pub fn redacted_ranges(
16112 &self,
16113 search_range: Range<Anchor>,
16114 display_snapshot: &DisplaySnapshot,
16115 cx: &App,
16116 ) -> Vec<Range<DisplayPoint>> {
16117 display_snapshot
16118 .buffer_snapshot
16119 .redacted_ranges(search_range, |file| {
16120 if let Some(file) = file {
16121 file.is_private()
16122 && EditorSettings::get(
16123 Some(SettingsLocation {
16124 worktree_id: file.worktree_id(cx),
16125 path: file.path().as_ref(),
16126 }),
16127 cx,
16128 )
16129 .redact_private_values
16130 } else {
16131 false
16132 }
16133 })
16134 .map(|range| {
16135 range.start.to_display_point(display_snapshot)
16136 ..range.end.to_display_point(display_snapshot)
16137 })
16138 .collect()
16139 }
16140
16141 pub fn highlight_text<T: 'static>(
16142 &mut self,
16143 ranges: Vec<Range<Anchor>>,
16144 style: HighlightStyle,
16145 cx: &mut Context<Self>,
16146 ) {
16147 self.display_map.update(cx, |map, _| {
16148 map.highlight_text(TypeId::of::<T>(), ranges, style)
16149 });
16150 cx.notify();
16151 }
16152
16153 pub(crate) fn highlight_inlays<T: 'static>(
16154 &mut self,
16155 highlights: Vec<InlayHighlight>,
16156 style: HighlightStyle,
16157 cx: &mut Context<Self>,
16158 ) {
16159 self.display_map.update(cx, |map, _| {
16160 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
16161 });
16162 cx.notify();
16163 }
16164
16165 pub fn text_highlights<'a, T: 'static>(
16166 &'a self,
16167 cx: &'a App,
16168 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
16169 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
16170 }
16171
16172 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
16173 let cleared = self
16174 .display_map
16175 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
16176 if cleared {
16177 cx.notify();
16178 }
16179 }
16180
16181 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
16182 (self.read_only(cx) || self.blink_manager.read(cx).visible())
16183 && self.focus_handle.is_focused(window)
16184 }
16185
16186 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
16187 self.show_cursor_when_unfocused = is_enabled;
16188 cx.notify();
16189 }
16190
16191 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
16192 cx.notify();
16193 }
16194
16195 fn on_buffer_event(
16196 &mut self,
16197 multibuffer: &Entity<MultiBuffer>,
16198 event: &multi_buffer::Event,
16199 window: &mut Window,
16200 cx: &mut Context<Self>,
16201 ) {
16202 match event {
16203 multi_buffer::Event::Edited {
16204 singleton_buffer_edited,
16205 edited_buffer: buffer_edited,
16206 } => {
16207 self.scrollbar_marker_state.dirty = true;
16208 self.active_indent_guides_state.dirty = true;
16209 self.refresh_active_diagnostics(cx);
16210 self.refresh_code_actions(window, cx);
16211 if self.has_active_inline_completion() {
16212 self.update_visible_inline_completion(window, cx);
16213 }
16214 if let Some(buffer) = buffer_edited {
16215 let buffer_id = buffer.read(cx).remote_id();
16216 if !self.registered_buffers.contains_key(&buffer_id) {
16217 if let Some(project) = self.project.as_ref() {
16218 project.update(cx, |project, cx| {
16219 self.registered_buffers.insert(
16220 buffer_id,
16221 project.register_buffer_with_language_servers(&buffer, cx),
16222 );
16223 })
16224 }
16225 }
16226 }
16227 cx.emit(EditorEvent::BufferEdited);
16228 cx.emit(SearchEvent::MatchesInvalidated);
16229 if *singleton_buffer_edited {
16230 if let Some(project) = &self.project {
16231 #[allow(clippy::mutable_key_type)]
16232 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
16233 multibuffer
16234 .all_buffers()
16235 .into_iter()
16236 .filter_map(|buffer| {
16237 buffer.update(cx, |buffer, cx| {
16238 let language = buffer.language()?;
16239 let should_discard = project.update(cx, |project, cx| {
16240 project.is_local()
16241 && !project.has_language_servers_for(buffer, cx)
16242 });
16243 should_discard.not().then_some(language.clone())
16244 })
16245 })
16246 .collect::<HashSet<_>>()
16247 });
16248 if !languages_affected.is_empty() {
16249 self.refresh_inlay_hints(
16250 InlayHintRefreshReason::BufferEdited(languages_affected),
16251 cx,
16252 );
16253 }
16254 }
16255 }
16256
16257 let Some(project) = &self.project else { return };
16258 let (telemetry, is_via_ssh) = {
16259 let project = project.read(cx);
16260 let telemetry = project.client().telemetry().clone();
16261 let is_via_ssh = project.is_via_ssh();
16262 (telemetry, is_via_ssh)
16263 };
16264 refresh_linked_ranges(self, window, cx);
16265 telemetry.log_edit_event("editor", is_via_ssh);
16266 }
16267 multi_buffer::Event::ExcerptsAdded {
16268 buffer,
16269 predecessor,
16270 excerpts,
16271 } => {
16272 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16273 let buffer_id = buffer.read(cx).remote_id();
16274 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
16275 if let Some(project) = &self.project {
16276 get_uncommitted_diff_for_buffer(
16277 project,
16278 [buffer.clone()],
16279 self.buffer.clone(),
16280 cx,
16281 )
16282 .detach();
16283 }
16284 }
16285 cx.emit(EditorEvent::ExcerptsAdded {
16286 buffer: buffer.clone(),
16287 predecessor: *predecessor,
16288 excerpts: excerpts.clone(),
16289 });
16290 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
16291 }
16292 multi_buffer::Event::ExcerptsRemoved { ids } => {
16293 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
16294 let buffer = self.buffer.read(cx);
16295 self.registered_buffers
16296 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
16297 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
16298 cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
16299 }
16300 multi_buffer::Event::ExcerptsEdited {
16301 excerpt_ids,
16302 buffer_ids,
16303 } => {
16304 self.display_map.update(cx, |map, cx| {
16305 map.unfold_buffers(buffer_ids.iter().copied(), cx)
16306 });
16307 cx.emit(EditorEvent::ExcerptsEdited {
16308 ids: excerpt_ids.clone(),
16309 })
16310 }
16311 multi_buffer::Event::ExcerptsExpanded { ids } => {
16312 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
16313 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
16314 }
16315 multi_buffer::Event::Reparsed(buffer_id) => {
16316 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16317 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
16318
16319 cx.emit(EditorEvent::Reparsed(*buffer_id));
16320 }
16321 multi_buffer::Event::DiffHunksToggled => {
16322 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16323 }
16324 multi_buffer::Event::LanguageChanged(buffer_id) => {
16325 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
16326 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
16327 cx.emit(EditorEvent::Reparsed(*buffer_id));
16328 cx.notify();
16329 }
16330 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
16331 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
16332 multi_buffer::Event::FileHandleChanged
16333 | multi_buffer::Event::Reloaded
16334 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
16335 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
16336 multi_buffer::Event::DiagnosticsUpdated => {
16337 self.refresh_active_diagnostics(cx);
16338 self.refresh_inline_diagnostics(true, window, cx);
16339 self.scrollbar_marker_state.dirty = true;
16340 cx.notify();
16341 }
16342 _ => {}
16343 };
16344 }
16345
16346 fn on_display_map_changed(
16347 &mut self,
16348 _: Entity<DisplayMap>,
16349 _: &mut Window,
16350 cx: &mut Context<Self>,
16351 ) {
16352 cx.notify();
16353 }
16354
16355 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16356 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16357 self.update_edit_prediction_settings(cx);
16358 self.refresh_inline_completion(true, false, window, cx);
16359 self.refresh_inlay_hints(
16360 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
16361 self.selections.newest_anchor().head(),
16362 &self.buffer.read(cx).snapshot(cx),
16363 cx,
16364 )),
16365 cx,
16366 );
16367
16368 let old_cursor_shape = self.cursor_shape;
16369
16370 {
16371 let editor_settings = EditorSettings::get_global(cx);
16372 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
16373 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
16374 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
16375 self.hide_mouse_while_typing = editor_settings.hide_mouse_while_typing.unwrap_or(true);
16376
16377 if !self.hide_mouse_while_typing {
16378 self.mouse_cursor_hidden = false;
16379 }
16380 }
16381
16382 if old_cursor_shape != self.cursor_shape {
16383 cx.emit(EditorEvent::CursorShapeChanged);
16384 }
16385
16386 let project_settings = ProjectSettings::get_global(cx);
16387 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
16388
16389 if self.mode == EditorMode::Full {
16390 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
16391 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
16392 if self.show_inline_diagnostics != show_inline_diagnostics {
16393 self.show_inline_diagnostics = show_inline_diagnostics;
16394 self.refresh_inline_diagnostics(false, window, cx);
16395 }
16396
16397 if self.git_blame_inline_enabled != inline_blame_enabled {
16398 self.toggle_git_blame_inline_internal(false, window, cx);
16399 }
16400 }
16401
16402 cx.notify();
16403 }
16404
16405 pub fn set_searchable(&mut self, searchable: bool) {
16406 self.searchable = searchable;
16407 }
16408
16409 pub fn searchable(&self) -> bool {
16410 self.searchable
16411 }
16412
16413 fn open_proposed_changes_editor(
16414 &mut self,
16415 _: &OpenProposedChangesEditor,
16416 window: &mut Window,
16417 cx: &mut Context<Self>,
16418 ) {
16419 let Some(workspace) = self.workspace() else {
16420 cx.propagate();
16421 return;
16422 };
16423
16424 let selections = self.selections.all::<usize>(cx);
16425 let multi_buffer = self.buffer.read(cx);
16426 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16427 let mut new_selections_by_buffer = HashMap::default();
16428 for selection in selections {
16429 for (buffer, range, _) in
16430 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
16431 {
16432 let mut range = range.to_point(buffer);
16433 range.start.column = 0;
16434 range.end.column = buffer.line_len(range.end.row);
16435 new_selections_by_buffer
16436 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
16437 .or_insert(Vec::new())
16438 .push(range)
16439 }
16440 }
16441
16442 let proposed_changes_buffers = new_selections_by_buffer
16443 .into_iter()
16444 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
16445 .collect::<Vec<_>>();
16446 let proposed_changes_editor = cx.new(|cx| {
16447 ProposedChangesEditor::new(
16448 "Proposed changes",
16449 proposed_changes_buffers,
16450 self.project.clone(),
16451 window,
16452 cx,
16453 )
16454 });
16455
16456 window.defer(cx, move |window, cx| {
16457 workspace.update(cx, |workspace, cx| {
16458 workspace.active_pane().update(cx, |pane, cx| {
16459 pane.add_item(
16460 Box::new(proposed_changes_editor),
16461 true,
16462 true,
16463 None,
16464 window,
16465 cx,
16466 );
16467 });
16468 });
16469 });
16470 }
16471
16472 pub fn open_excerpts_in_split(
16473 &mut self,
16474 _: &OpenExcerptsSplit,
16475 window: &mut Window,
16476 cx: &mut Context<Self>,
16477 ) {
16478 self.open_excerpts_common(None, true, window, cx)
16479 }
16480
16481 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
16482 self.open_excerpts_common(None, false, window, cx)
16483 }
16484
16485 fn open_excerpts_common(
16486 &mut self,
16487 jump_data: Option<JumpData>,
16488 split: bool,
16489 window: &mut Window,
16490 cx: &mut Context<Self>,
16491 ) {
16492 let Some(workspace) = self.workspace() else {
16493 cx.propagate();
16494 return;
16495 };
16496
16497 if self.buffer.read(cx).is_singleton() {
16498 cx.propagate();
16499 return;
16500 }
16501
16502 let mut new_selections_by_buffer = HashMap::default();
16503 match &jump_data {
16504 Some(JumpData::MultiBufferPoint {
16505 excerpt_id,
16506 position,
16507 anchor,
16508 line_offset_from_top,
16509 }) => {
16510 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16511 if let Some(buffer) = multi_buffer_snapshot
16512 .buffer_id_for_excerpt(*excerpt_id)
16513 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
16514 {
16515 let buffer_snapshot = buffer.read(cx).snapshot();
16516 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
16517 language::ToPoint::to_point(anchor, &buffer_snapshot)
16518 } else {
16519 buffer_snapshot.clip_point(*position, Bias::Left)
16520 };
16521 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
16522 new_selections_by_buffer.insert(
16523 buffer,
16524 (
16525 vec![jump_to_offset..jump_to_offset],
16526 Some(*line_offset_from_top),
16527 ),
16528 );
16529 }
16530 }
16531 Some(JumpData::MultiBufferRow {
16532 row,
16533 line_offset_from_top,
16534 }) => {
16535 let point = MultiBufferPoint::new(row.0, 0);
16536 if let Some((buffer, buffer_point, _)) =
16537 self.buffer.read(cx).point_to_buffer_point(point, cx)
16538 {
16539 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
16540 new_selections_by_buffer
16541 .entry(buffer)
16542 .or_insert((Vec::new(), Some(*line_offset_from_top)))
16543 .0
16544 .push(buffer_offset..buffer_offset)
16545 }
16546 }
16547 None => {
16548 let selections = self.selections.all::<usize>(cx);
16549 let multi_buffer = self.buffer.read(cx);
16550 for selection in selections {
16551 for (snapshot, range, _, anchor) in multi_buffer
16552 .snapshot(cx)
16553 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
16554 {
16555 if let Some(anchor) = anchor {
16556 // selection is in a deleted hunk
16557 let Some(buffer_id) = anchor.buffer_id else {
16558 continue;
16559 };
16560 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
16561 continue;
16562 };
16563 let offset = text::ToOffset::to_offset(
16564 &anchor.text_anchor,
16565 &buffer_handle.read(cx).snapshot(),
16566 );
16567 let range = offset..offset;
16568 new_selections_by_buffer
16569 .entry(buffer_handle)
16570 .or_insert((Vec::new(), None))
16571 .0
16572 .push(range)
16573 } else {
16574 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
16575 else {
16576 continue;
16577 };
16578 new_selections_by_buffer
16579 .entry(buffer_handle)
16580 .or_insert((Vec::new(), None))
16581 .0
16582 .push(range)
16583 }
16584 }
16585 }
16586 }
16587 }
16588
16589 if new_selections_by_buffer.is_empty() {
16590 return;
16591 }
16592
16593 // We defer the pane interaction because we ourselves are a workspace item
16594 // and activating a new item causes the pane to call a method on us reentrantly,
16595 // which panics if we're on the stack.
16596 window.defer(cx, move |window, cx| {
16597 workspace.update(cx, |workspace, cx| {
16598 let pane = if split {
16599 workspace.adjacent_pane(window, cx)
16600 } else {
16601 workspace.active_pane().clone()
16602 };
16603
16604 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
16605 let editor = buffer
16606 .read(cx)
16607 .file()
16608 .is_none()
16609 .then(|| {
16610 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
16611 // so `workspace.open_project_item` will never find them, always opening a new editor.
16612 // Instead, we try to activate the existing editor in the pane first.
16613 let (editor, pane_item_index) =
16614 pane.read(cx).items().enumerate().find_map(|(i, item)| {
16615 let editor = item.downcast::<Editor>()?;
16616 let singleton_buffer =
16617 editor.read(cx).buffer().read(cx).as_singleton()?;
16618 if singleton_buffer == buffer {
16619 Some((editor, i))
16620 } else {
16621 None
16622 }
16623 })?;
16624 pane.update(cx, |pane, cx| {
16625 pane.activate_item(pane_item_index, true, true, window, cx)
16626 });
16627 Some(editor)
16628 })
16629 .flatten()
16630 .unwrap_or_else(|| {
16631 workspace.open_project_item::<Self>(
16632 pane.clone(),
16633 buffer,
16634 true,
16635 true,
16636 window,
16637 cx,
16638 )
16639 });
16640
16641 editor.update(cx, |editor, cx| {
16642 let autoscroll = match scroll_offset {
16643 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
16644 None => Autoscroll::newest(),
16645 };
16646 let nav_history = editor.nav_history.take();
16647 editor.change_selections(Some(autoscroll), window, cx, |s| {
16648 s.select_ranges(ranges);
16649 });
16650 editor.nav_history = nav_history;
16651 });
16652 }
16653 })
16654 });
16655 }
16656
16657 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
16658 let snapshot = self.buffer.read(cx).read(cx);
16659 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
16660 Some(
16661 ranges
16662 .iter()
16663 .map(move |range| {
16664 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
16665 })
16666 .collect(),
16667 )
16668 }
16669
16670 fn selection_replacement_ranges(
16671 &self,
16672 range: Range<OffsetUtf16>,
16673 cx: &mut App,
16674 ) -> Vec<Range<OffsetUtf16>> {
16675 let selections = self.selections.all::<OffsetUtf16>(cx);
16676 let newest_selection = selections
16677 .iter()
16678 .max_by_key(|selection| selection.id)
16679 .unwrap();
16680 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
16681 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
16682 let snapshot = self.buffer.read(cx).read(cx);
16683 selections
16684 .into_iter()
16685 .map(|mut selection| {
16686 selection.start.0 =
16687 (selection.start.0 as isize).saturating_add(start_delta) as usize;
16688 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
16689 snapshot.clip_offset_utf16(selection.start, Bias::Left)
16690 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
16691 })
16692 .collect()
16693 }
16694
16695 fn report_editor_event(
16696 &self,
16697 event_type: &'static str,
16698 file_extension: Option<String>,
16699 cx: &App,
16700 ) {
16701 if cfg!(any(test, feature = "test-support")) {
16702 return;
16703 }
16704
16705 let Some(project) = &self.project else { return };
16706
16707 // If None, we are in a file without an extension
16708 let file = self
16709 .buffer
16710 .read(cx)
16711 .as_singleton()
16712 .and_then(|b| b.read(cx).file());
16713 let file_extension = file_extension.or(file
16714 .as_ref()
16715 .and_then(|file| Path::new(file.file_name(cx)).extension())
16716 .and_then(|e| e.to_str())
16717 .map(|a| a.to_string()));
16718
16719 let vim_mode = cx
16720 .global::<SettingsStore>()
16721 .raw_user_settings()
16722 .get("vim_mode")
16723 == Some(&serde_json::Value::Bool(true));
16724
16725 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
16726 let copilot_enabled = edit_predictions_provider
16727 == language::language_settings::EditPredictionProvider::Copilot;
16728 let copilot_enabled_for_language = self
16729 .buffer
16730 .read(cx)
16731 .language_settings(cx)
16732 .show_edit_predictions;
16733
16734 let project = project.read(cx);
16735 telemetry::event!(
16736 event_type,
16737 file_extension,
16738 vim_mode,
16739 copilot_enabled,
16740 copilot_enabled_for_language,
16741 edit_predictions_provider,
16742 is_via_ssh = project.is_via_ssh(),
16743 );
16744 }
16745
16746 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
16747 /// with each line being an array of {text, highlight} objects.
16748 fn copy_highlight_json(
16749 &mut self,
16750 _: &CopyHighlightJson,
16751 window: &mut Window,
16752 cx: &mut Context<Self>,
16753 ) {
16754 #[derive(Serialize)]
16755 struct Chunk<'a> {
16756 text: String,
16757 highlight: Option<&'a str>,
16758 }
16759
16760 let snapshot = self.buffer.read(cx).snapshot(cx);
16761 let range = self
16762 .selected_text_range(false, window, cx)
16763 .and_then(|selection| {
16764 if selection.range.is_empty() {
16765 None
16766 } else {
16767 Some(selection.range)
16768 }
16769 })
16770 .unwrap_or_else(|| 0..snapshot.len());
16771
16772 let chunks = snapshot.chunks(range, true);
16773 let mut lines = Vec::new();
16774 let mut line: VecDeque<Chunk> = VecDeque::new();
16775
16776 let Some(style) = self.style.as_ref() else {
16777 return;
16778 };
16779
16780 for chunk in chunks {
16781 let highlight = chunk
16782 .syntax_highlight_id
16783 .and_then(|id| id.name(&style.syntax));
16784 let mut chunk_lines = chunk.text.split('\n').peekable();
16785 while let Some(text) = chunk_lines.next() {
16786 let mut merged_with_last_token = false;
16787 if let Some(last_token) = line.back_mut() {
16788 if last_token.highlight == highlight {
16789 last_token.text.push_str(text);
16790 merged_with_last_token = true;
16791 }
16792 }
16793
16794 if !merged_with_last_token {
16795 line.push_back(Chunk {
16796 text: text.into(),
16797 highlight,
16798 });
16799 }
16800
16801 if chunk_lines.peek().is_some() {
16802 if line.len() > 1 && line.front().unwrap().text.is_empty() {
16803 line.pop_front();
16804 }
16805 if line.len() > 1 && line.back().unwrap().text.is_empty() {
16806 line.pop_back();
16807 }
16808
16809 lines.push(mem::take(&mut line));
16810 }
16811 }
16812 }
16813
16814 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
16815 return;
16816 };
16817 cx.write_to_clipboard(ClipboardItem::new_string(lines));
16818 }
16819
16820 pub fn open_context_menu(
16821 &mut self,
16822 _: &OpenContextMenu,
16823 window: &mut Window,
16824 cx: &mut Context<Self>,
16825 ) {
16826 self.request_autoscroll(Autoscroll::newest(), cx);
16827 let position = self.selections.newest_display(cx).start;
16828 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
16829 }
16830
16831 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
16832 &self.inlay_hint_cache
16833 }
16834
16835 pub fn replay_insert_event(
16836 &mut self,
16837 text: &str,
16838 relative_utf16_range: Option<Range<isize>>,
16839 window: &mut Window,
16840 cx: &mut Context<Self>,
16841 ) {
16842 if !self.input_enabled {
16843 cx.emit(EditorEvent::InputIgnored { text: text.into() });
16844 return;
16845 }
16846 if let Some(relative_utf16_range) = relative_utf16_range {
16847 let selections = self.selections.all::<OffsetUtf16>(cx);
16848 self.change_selections(None, window, cx, |s| {
16849 let new_ranges = selections.into_iter().map(|range| {
16850 let start = OffsetUtf16(
16851 range
16852 .head()
16853 .0
16854 .saturating_add_signed(relative_utf16_range.start),
16855 );
16856 let end = OffsetUtf16(
16857 range
16858 .head()
16859 .0
16860 .saturating_add_signed(relative_utf16_range.end),
16861 );
16862 start..end
16863 });
16864 s.select_ranges(new_ranges);
16865 });
16866 }
16867
16868 self.handle_input(text, window, cx);
16869 }
16870
16871 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
16872 let Some(provider) = self.semantics_provider.as_ref() else {
16873 return false;
16874 };
16875
16876 let mut supports = false;
16877 self.buffer().update(cx, |this, cx| {
16878 this.for_each_buffer(|buffer| {
16879 supports |= provider.supports_inlay_hints(buffer, cx);
16880 });
16881 });
16882
16883 supports
16884 }
16885
16886 pub fn is_focused(&self, window: &Window) -> bool {
16887 self.focus_handle.is_focused(window)
16888 }
16889
16890 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16891 cx.emit(EditorEvent::Focused);
16892
16893 if let Some(descendant) = self
16894 .last_focused_descendant
16895 .take()
16896 .and_then(|descendant| descendant.upgrade())
16897 {
16898 window.focus(&descendant);
16899 } else {
16900 if let Some(blame) = self.blame.as_ref() {
16901 blame.update(cx, GitBlame::focus)
16902 }
16903
16904 self.blink_manager.update(cx, BlinkManager::enable);
16905 self.show_cursor_names(window, cx);
16906 self.buffer.update(cx, |buffer, cx| {
16907 buffer.finalize_last_transaction(cx);
16908 if self.leader_peer_id.is_none() {
16909 buffer.set_active_selections(
16910 &self.selections.disjoint_anchors(),
16911 self.selections.line_mode,
16912 self.cursor_shape,
16913 cx,
16914 );
16915 }
16916 });
16917 }
16918 }
16919
16920 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
16921 cx.emit(EditorEvent::FocusedIn)
16922 }
16923
16924 fn handle_focus_out(
16925 &mut self,
16926 event: FocusOutEvent,
16927 _window: &mut Window,
16928 cx: &mut Context<Self>,
16929 ) {
16930 if event.blurred != self.focus_handle {
16931 self.last_focused_descendant = Some(event.blurred);
16932 }
16933 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
16934 }
16935
16936 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16937 self.blink_manager.update(cx, BlinkManager::disable);
16938 self.buffer
16939 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
16940
16941 if let Some(blame) = self.blame.as_ref() {
16942 blame.update(cx, GitBlame::blur)
16943 }
16944 if !self.hover_state.focused(window, cx) {
16945 hide_hover(self, cx);
16946 }
16947 if !self
16948 .context_menu
16949 .borrow()
16950 .as_ref()
16951 .is_some_and(|context_menu| context_menu.focused(window, cx))
16952 {
16953 self.hide_context_menu(window, cx);
16954 }
16955 self.discard_inline_completion(false, cx);
16956 cx.emit(EditorEvent::Blurred);
16957 cx.notify();
16958 }
16959
16960 pub fn register_action<A: Action>(
16961 &mut self,
16962 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
16963 ) -> Subscription {
16964 let id = self.next_editor_action_id.post_inc();
16965 let listener = Arc::new(listener);
16966 self.editor_actions.borrow_mut().insert(
16967 id,
16968 Box::new(move |window, _| {
16969 let listener = listener.clone();
16970 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
16971 let action = action.downcast_ref().unwrap();
16972 if phase == DispatchPhase::Bubble {
16973 listener(action, window, cx)
16974 }
16975 })
16976 }),
16977 );
16978
16979 let editor_actions = self.editor_actions.clone();
16980 Subscription::new(move || {
16981 editor_actions.borrow_mut().remove(&id);
16982 })
16983 }
16984
16985 pub fn file_header_size(&self) -> u32 {
16986 FILE_HEADER_HEIGHT
16987 }
16988
16989 pub fn restore(
16990 &mut self,
16991 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
16992 window: &mut Window,
16993 cx: &mut Context<Self>,
16994 ) {
16995 let workspace = self.workspace();
16996 let project = self.project.as_ref();
16997 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
16998 let mut tasks = Vec::new();
16999 for (buffer_id, changes) in revert_changes {
17000 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
17001 buffer.update(cx, |buffer, cx| {
17002 buffer.edit(
17003 changes
17004 .into_iter()
17005 .map(|(range, text)| (range, text.to_string())),
17006 None,
17007 cx,
17008 );
17009 });
17010
17011 if let Some(project) =
17012 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
17013 {
17014 project.update(cx, |project, cx| {
17015 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
17016 })
17017 }
17018 }
17019 }
17020 tasks
17021 });
17022 cx.spawn_in(window, async move |_, cx| {
17023 for (buffer, task) in save_tasks {
17024 let result = task.await;
17025 if result.is_err() {
17026 let Some(path) = buffer
17027 .read_with(cx, |buffer, cx| buffer.project_path(cx))
17028 .ok()
17029 else {
17030 continue;
17031 };
17032 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
17033 let Some(task) = cx
17034 .update_window_entity(&workspace, |workspace, window, cx| {
17035 workspace
17036 .open_path_preview(path, None, false, false, false, window, cx)
17037 })
17038 .ok()
17039 else {
17040 continue;
17041 };
17042 task.await.log_err();
17043 }
17044 }
17045 }
17046 })
17047 .detach();
17048 self.change_selections(None, window, cx, |selections| selections.refresh());
17049 }
17050
17051 pub fn to_pixel_point(
17052 &self,
17053 source: multi_buffer::Anchor,
17054 editor_snapshot: &EditorSnapshot,
17055 window: &mut Window,
17056 ) -> Option<gpui::Point<Pixels>> {
17057 let source_point = source.to_display_point(editor_snapshot);
17058 self.display_to_pixel_point(source_point, editor_snapshot, window)
17059 }
17060
17061 pub fn display_to_pixel_point(
17062 &self,
17063 source: DisplayPoint,
17064 editor_snapshot: &EditorSnapshot,
17065 window: &mut Window,
17066 ) -> Option<gpui::Point<Pixels>> {
17067 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
17068 let text_layout_details = self.text_layout_details(window);
17069 let scroll_top = text_layout_details
17070 .scroll_anchor
17071 .scroll_position(editor_snapshot)
17072 .y;
17073
17074 if source.row().as_f32() < scroll_top.floor() {
17075 return None;
17076 }
17077 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
17078 let source_y = line_height * (source.row().as_f32() - scroll_top);
17079 Some(gpui::Point::new(source_x, source_y))
17080 }
17081
17082 pub fn has_visible_completions_menu(&self) -> bool {
17083 !self.edit_prediction_preview_is_active()
17084 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
17085 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
17086 })
17087 }
17088
17089 pub fn register_addon<T: Addon>(&mut self, instance: T) {
17090 self.addons
17091 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
17092 }
17093
17094 pub fn unregister_addon<T: Addon>(&mut self) {
17095 self.addons.remove(&std::any::TypeId::of::<T>());
17096 }
17097
17098 pub fn addon<T: Addon>(&self) -> Option<&T> {
17099 let type_id = std::any::TypeId::of::<T>();
17100 self.addons
17101 .get(&type_id)
17102 .and_then(|item| item.to_any().downcast_ref::<T>())
17103 }
17104
17105 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
17106 let text_layout_details = self.text_layout_details(window);
17107 let style = &text_layout_details.editor_style;
17108 let font_id = window.text_system().resolve_font(&style.text.font());
17109 let font_size = style.text.font_size.to_pixels(window.rem_size());
17110 let line_height = style.text.line_height_in_pixels(window.rem_size());
17111 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
17112
17113 gpui::Size::new(em_width, line_height)
17114 }
17115
17116 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
17117 self.load_diff_task.clone()
17118 }
17119
17120 fn read_selections_from_db(
17121 &mut self,
17122 item_id: u64,
17123 workspace_id: WorkspaceId,
17124 window: &mut Window,
17125 cx: &mut Context<Editor>,
17126 ) {
17127 if !self.is_singleton(cx)
17128 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
17129 {
17130 return;
17131 }
17132 let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() else {
17133 return;
17134 };
17135 if selections.is_empty() {
17136 return;
17137 }
17138
17139 let snapshot = self.buffer.read(cx).snapshot(cx);
17140 self.change_selections(None, window, cx, |s| {
17141 s.select_ranges(selections.into_iter().map(|(start, end)| {
17142 snapshot.clip_offset(start, Bias::Left)..snapshot.clip_offset(end, Bias::Right)
17143 }));
17144 });
17145 }
17146}
17147
17148fn insert_extra_newline_brackets(
17149 buffer: &MultiBufferSnapshot,
17150 range: Range<usize>,
17151 language: &language::LanguageScope,
17152) -> bool {
17153 let leading_whitespace_len = buffer
17154 .reversed_chars_at(range.start)
17155 .take_while(|c| c.is_whitespace() && *c != '\n')
17156 .map(|c| c.len_utf8())
17157 .sum::<usize>();
17158 let trailing_whitespace_len = buffer
17159 .chars_at(range.end)
17160 .take_while(|c| c.is_whitespace() && *c != '\n')
17161 .map(|c| c.len_utf8())
17162 .sum::<usize>();
17163 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
17164
17165 language.brackets().any(|(pair, enabled)| {
17166 let pair_start = pair.start.trim_end();
17167 let pair_end = pair.end.trim_start();
17168
17169 enabled
17170 && pair.newline
17171 && buffer.contains_str_at(range.end, pair_end)
17172 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
17173 })
17174}
17175
17176fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
17177 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
17178 [(buffer, range, _)] => (*buffer, range.clone()),
17179 _ => return false,
17180 };
17181 let pair = {
17182 let mut result: Option<BracketMatch> = None;
17183
17184 for pair in buffer
17185 .all_bracket_ranges(range.clone())
17186 .filter(move |pair| {
17187 pair.open_range.start <= range.start && pair.close_range.end >= range.end
17188 })
17189 {
17190 let len = pair.close_range.end - pair.open_range.start;
17191
17192 if let Some(existing) = &result {
17193 let existing_len = existing.close_range.end - existing.open_range.start;
17194 if len > existing_len {
17195 continue;
17196 }
17197 }
17198
17199 result = Some(pair);
17200 }
17201
17202 result
17203 };
17204 let Some(pair) = pair else {
17205 return false;
17206 };
17207 pair.newline_only
17208 && buffer
17209 .chars_for_range(pair.open_range.end..range.start)
17210 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
17211 .all(|c| c.is_whitespace() && c != '\n')
17212}
17213
17214fn get_uncommitted_diff_for_buffer(
17215 project: &Entity<Project>,
17216 buffers: impl IntoIterator<Item = Entity<Buffer>>,
17217 buffer: Entity<MultiBuffer>,
17218 cx: &mut App,
17219) -> Task<()> {
17220 let mut tasks = Vec::new();
17221 project.update(cx, |project, cx| {
17222 for buffer in buffers {
17223 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
17224 }
17225 });
17226 cx.spawn(async move |cx| {
17227 let diffs = future::join_all(tasks).await;
17228 buffer
17229 .update(cx, |buffer, cx| {
17230 for diff in diffs.into_iter().flatten() {
17231 buffer.add_diff(diff, cx);
17232 }
17233 })
17234 .ok();
17235 })
17236}
17237
17238fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
17239 let tab_size = tab_size.get() as usize;
17240 let mut width = offset;
17241
17242 for ch in text.chars() {
17243 width += if ch == '\t' {
17244 tab_size - (width % tab_size)
17245 } else {
17246 1
17247 };
17248 }
17249
17250 width - offset
17251}
17252
17253#[cfg(test)]
17254mod tests {
17255 use super::*;
17256
17257 #[test]
17258 fn test_string_size_with_expanded_tabs() {
17259 let nz = |val| NonZeroU32::new(val).unwrap();
17260 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
17261 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
17262 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
17263 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
17264 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
17265 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
17266 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
17267 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
17268 }
17269}
17270
17271/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
17272struct WordBreakingTokenizer<'a> {
17273 input: &'a str,
17274}
17275
17276impl<'a> WordBreakingTokenizer<'a> {
17277 fn new(input: &'a str) -> Self {
17278 Self { input }
17279 }
17280}
17281
17282fn is_char_ideographic(ch: char) -> bool {
17283 use unicode_script::Script::*;
17284 use unicode_script::UnicodeScript;
17285 matches!(ch.script(), Han | Tangut | Yi)
17286}
17287
17288fn is_grapheme_ideographic(text: &str) -> bool {
17289 text.chars().any(is_char_ideographic)
17290}
17291
17292fn is_grapheme_whitespace(text: &str) -> bool {
17293 text.chars().any(|x| x.is_whitespace())
17294}
17295
17296fn should_stay_with_preceding_ideograph(text: &str) -> bool {
17297 text.chars().next().map_or(false, |ch| {
17298 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
17299 })
17300}
17301
17302#[derive(PartialEq, Eq, Debug, Clone, Copy)]
17303enum WordBreakToken<'a> {
17304 Word { token: &'a str, grapheme_len: usize },
17305 InlineWhitespace { token: &'a str, grapheme_len: usize },
17306 Newline,
17307}
17308
17309impl<'a> Iterator for WordBreakingTokenizer<'a> {
17310 /// Yields a span, the count of graphemes in the token, and whether it was
17311 /// whitespace. Note that it also breaks at word boundaries.
17312 type Item = WordBreakToken<'a>;
17313
17314 fn next(&mut self) -> Option<Self::Item> {
17315 use unicode_segmentation::UnicodeSegmentation;
17316 if self.input.is_empty() {
17317 return None;
17318 }
17319
17320 let mut iter = self.input.graphemes(true).peekable();
17321 let mut offset = 0;
17322 let mut grapheme_len = 0;
17323 if let Some(first_grapheme) = iter.next() {
17324 let is_newline = first_grapheme == "\n";
17325 let is_whitespace = is_grapheme_whitespace(first_grapheme);
17326 offset += first_grapheme.len();
17327 grapheme_len += 1;
17328 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
17329 if let Some(grapheme) = iter.peek().copied() {
17330 if should_stay_with_preceding_ideograph(grapheme) {
17331 offset += grapheme.len();
17332 grapheme_len += 1;
17333 }
17334 }
17335 } else {
17336 let mut words = self.input[offset..].split_word_bound_indices().peekable();
17337 let mut next_word_bound = words.peek().copied();
17338 if next_word_bound.map_or(false, |(i, _)| i == 0) {
17339 next_word_bound = words.next();
17340 }
17341 while let Some(grapheme) = iter.peek().copied() {
17342 if next_word_bound.map_or(false, |(i, _)| i == offset) {
17343 break;
17344 };
17345 if is_grapheme_whitespace(grapheme) != is_whitespace
17346 || (grapheme == "\n") != is_newline
17347 {
17348 break;
17349 };
17350 offset += grapheme.len();
17351 grapheme_len += 1;
17352 iter.next();
17353 }
17354 }
17355 let token = &self.input[..offset];
17356 self.input = &self.input[offset..];
17357 if token == "\n" {
17358 Some(WordBreakToken::Newline)
17359 } else if is_whitespace {
17360 Some(WordBreakToken::InlineWhitespace {
17361 token,
17362 grapheme_len,
17363 })
17364 } else {
17365 Some(WordBreakToken::Word {
17366 token,
17367 grapheme_len,
17368 })
17369 }
17370 } else {
17371 None
17372 }
17373 }
17374}
17375
17376#[test]
17377fn test_word_breaking_tokenizer() {
17378 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
17379 ("", &[]),
17380 (" ", &[whitespace(" ", 2)]),
17381 ("Ʒ", &[word("Ʒ", 1)]),
17382 ("Ǽ", &[word("Ǽ", 1)]),
17383 ("⋑", &[word("⋑", 1)]),
17384 ("⋑⋑", &[word("⋑⋑", 2)]),
17385 (
17386 "原理,进而",
17387 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
17388 ),
17389 (
17390 "hello world",
17391 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
17392 ),
17393 (
17394 "hello, world",
17395 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
17396 ),
17397 (
17398 " hello world",
17399 &[
17400 whitespace(" ", 2),
17401 word("hello", 5),
17402 whitespace(" ", 1),
17403 word("world", 5),
17404 ],
17405 ),
17406 (
17407 "这是什么 \n 钢笔",
17408 &[
17409 word("这", 1),
17410 word("是", 1),
17411 word("什", 1),
17412 word("么", 1),
17413 whitespace(" ", 1),
17414 newline(),
17415 whitespace(" ", 1),
17416 word("钢", 1),
17417 word("笔", 1),
17418 ],
17419 ),
17420 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
17421 ];
17422
17423 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
17424 WordBreakToken::Word {
17425 token,
17426 grapheme_len,
17427 }
17428 }
17429
17430 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
17431 WordBreakToken::InlineWhitespace {
17432 token,
17433 grapheme_len,
17434 }
17435 }
17436
17437 fn newline() -> WordBreakToken<'static> {
17438 WordBreakToken::Newline
17439 }
17440
17441 for (input, result) in tests {
17442 assert_eq!(
17443 WordBreakingTokenizer::new(input)
17444 .collect::<Vec<_>>()
17445 .as_slice(),
17446 *result,
17447 );
17448 }
17449}
17450
17451fn wrap_with_prefix(
17452 line_prefix: String,
17453 unwrapped_text: String,
17454 wrap_column: usize,
17455 tab_size: NonZeroU32,
17456 preserve_existing_whitespace: bool,
17457) -> String {
17458 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
17459 let mut wrapped_text = String::new();
17460 let mut current_line = line_prefix.clone();
17461
17462 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
17463 let mut current_line_len = line_prefix_len;
17464 let mut in_whitespace = false;
17465 for token in tokenizer {
17466 let have_preceding_whitespace = in_whitespace;
17467 match token {
17468 WordBreakToken::Word {
17469 token,
17470 grapheme_len,
17471 } => {
17472 in_whitespace = false;
17473 if current_line_len + grapheme_len > wrap_column
17474 && current_line_len != line_prefix_len
17475 {
17476 wrapped_text.push_str(current_line.trim_end());
17477 wrapped_text.push('\n');
17478 current_line.truncate(line_prefix.len());
17479 current_line_len = line_prefix_len;
17480 }
17481 current_line.push_str(token);
17482 current_line_len += grapheme_len;
17483 }
17484 WordBreakToken::InlineWhitespace {
17485 mut token,
17486 mut grapheme_len,
17487 } => {
17488 in_whitespace = true;
17489 if have_preceding_whitespace && !preserve_existing_whitespace {
17490 continue;
17491 }
17492 if !preserve_existing_whitespace {
17493 token = " ";
17494 grapheme_len = 1;
17495 }
17496 if current_line_len + grapheme_len > wrap_column {
17497 wrapped_text.push_str(current_line.trim_end());
17498 wrapped_text.push('\n');
17499 current_line.truncate(line_prefix.len());
17500 current_line_len = line_prefix_len;
17501 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
17502 current_line.push_str(token);
17503 current_line_len += grapheme_len;
17504 }
17505 }
17506 WordBreakToken::Newline => {
17507 in_whitespace = true;
17508 if preserve_existing_whitespace {
17509 wrapped_text.push_str(current_line.trim_end());
17510 wrapped_text.push('\n');
17511 current_line.truncate(line_prefix.len());
17512 current_line_len = line_prefix_len;
17513 } else if have_preceding_whitespace {
17514 continue;
17515 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
17516 {
17517 wrapped_text.push_str(current_line.trim_end());
17518 wrapped_text.push('\n');
17519 current_line.truncate(line_prefix.len());
17520 current_line_len = line_prefix_len;
17521 } else if current_line_len != line_prefix_len {
17522 current_line.push(' ');
17523 current_line_len += 1;
17524 }
17525 }
17526 }
17527 }
17528
17529 if !current_line.is_empty() {
17530 wrapped_text.push_str(¤t_line);
17531 }
17532 wrapped_text
17533}
17534
17535#[test]
17536fn test_wrap_with_prefix() {
17537 assert_eq!(
17538 wrap_with_prefix(
17539 "# ".to_string(),
17540 "abcdefg".to_string(),
17541 4,
17542 NonZeroU32::new(4).unwrap(),
17543 false,
17544 ),
17545 "# abcdefg"
17546 );
17547 assert_eq!(
17548 wrap_with_prefix(
17549 "".to_string(),
17550 "\thello world".to_string(),
17551 8,
17552 NonZeroU32::new(4).unwrap(),
17553 false,
17554 ),
17555 "hello\nworld"
17556 );
17557 assert_eq!(
17558 wrap_with_prefix(
17559 "// ".to_string(),
17560 "xx \nyy zz aa bb cc".to_string(),
17561 12,
17562 NonZeroU32::new(4).unwrap(),
17563 false,
17564 ),
17565 "// xx yy zz\n// aa bb cc"
17566 );
17567 assert_eq!(
17568 wrap_with_prefix(
17569 String::new(),
17570 "这是什么 \n 钢笔".to_string(),
17571 3,
17572 NonZeroU32::new(4).unwrap(),
17573 false,
17574 ),
17575 "这是什\n么 钢\n笔"
17576 );
17577}
17578
17579pub trait CollaborationHub {
17580 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
17581 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
17582 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
17583}
17584
17585impl CollaborationHub for Entity<Project> {
17586 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
17587 self.read(cx).collaborators()
17588 }
17589
17590 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
17591 self.read(cx).user_store().read(cx).participant_indices()
17592 }
17593
17594 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
17595 let this = self.read(cx);
17596 let user_ids = this.collaborators().values().map(|c| c.user_id);
17597 this.user_store().read_with(cx, |user_store, cx| {
17598 user_store.participant_names(user_ids, cx)
17599 })
17600 }
17601}
17602
17603pub trait SemanticsProvider {
17604 fn hover(
17605 &self,
17606 buffer: &Entity<Buffer>,
17607 position: text::Anchor,
17608 cx: &mut App,
17609 ) -> Option<Task<Vec<project::Hover>>>;
17610
17611 fn inlay_hints(
17612 &self,
17613 buffer_handle: Entity<Buffer>,
17614 range: Range<text::Anchor>,
17615 cx: &mut App,
17616 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
17617
17618 fn resolve_inlay_hint(
17619 &self,
17620 hint: InlayHint,
17621 buffer_handle: Entity<Buffer>,
17622 server_id: LanguageServerId,
17623 cx: &mut App,
17624 ) -> Option<Task<anyhow::Result<InlayHint>>>;
17625
17626 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
17627
17628 fn document_highlights(
17629 &self,
17630 buffer: &Entity<Buffer>,
17631 position: text::Anchor,
17632 cx: &mut App,
17633 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
17634
17635 fn definitions(
17636 &self,
17637 buffer: &Entity<Buffer>,
17638 position: text::Anchor,
17639 kind: GotoDefinitionKind,
17640 cx: &mut App,
17641 ) -> Option<Task<Result<Vec<LocationLink>>>>;
17642
17643 fn range_for_rename(
17644 &self,
17645 buffer: &Entity<Buffer>,
17646 position: text::Anchor,
17647 cx: &mut App,
17648 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
17649
17650 fn perform_rename(
17651 &self,
17652 buffer: &Entity<Buffer>,
17653 position: text::Anchor,
17654 new_name: String,
17655 cx: &mut App,
17656 ) -> Option<Task<Result<ProjectTransaction>>>;
17657}
17658
17659pub trait CompletionProvider {
17660 fn completions(
17661 &self,
17662 buffer: &Entity<Buffer>,
17663 buffer_position: text::Anchor,
17664 trigger: CompletionContext,
17665 window: &mut Window,
17666 cx: &mut Context<Editor>,
17667 ) -> Task<Result<Option<Vec<Completion>>>>;
17668
17669 fn resolve_completions(
17670 &self,
17671 buffer: Entity<Buffer>,
17672 completion_indices: Vec<usize>,
17673 completions: Rc<RefCell<Box<[Completion]>>>,
17674 cx: &mut Context<Editor>,
17675 ) -> Task<Result<bool>>;
17676
17677 fn apply_additional_edits_for_completion(
17678 &self,
17679 _buffer: Entity<Buffer>,
17680 _completions: Rc<RefCell<Box<[Completion]>>>,
17681 _completion_index: usize,
17682 _push_to_history: bool,
17683 _cx: &mut Context<Editor>,
17684 ) -> Task<Result<Option<language::Transaction>>> {
17685 Task::ready(Ok(None))
17686 }
17687
17688 fn is_completion_trigger(
17689 &self,
17690 buffer: &Entity<Buffer>,
17691 position: language::Anchor,
17692 text: &str,
17693 trigger_in_words: bool,
17694 cx: &mut Context<Editor>,
17695 ) -> bool;
17696
17697 fn sort_completions(&self) -> bool {
17698 true
17699 }
17700}
17701
17702pub trait CodeActionProvider {
17703 fn id(&self) -> Arc<str>;
17704
17705 fn code_actions(
17706 &self,
17707 buffer: &Entity<Buffer>,
17708 range: Range<text::Anchor>,
17709 window: &mut Window,
17710 cx: &mut App,
17711 ) -> Task<Result<Vec<CodeAction>>>;
17712
17713 fn apply_code_action(
17714 &self,
17715 buffer_handle: Entity<Buffer>,
17716 action: CodeAction,
17717 excerpt_id: ExcerptId,
17718 push_to_history: bool,
17719 window: &mut Window,
17720 cx: &mut App,
17721 ) -> Task<Result<ProjectTransaction>>;
17722}
17723
17724impl CodeActionProvider for Entity<Project> {
17725 fn id(&self) -> Arc<str> {
17726 "project".into()
17727 }
17728
17729 fn code_actions(
17730 &self,
17731 buffer: &Entity<Buffer>,
17732 range: Range<text::Anchor>,
17733 _window: &mut Window,
17734 cx: &mut App,
17735 ) -> Task<Result<Vec<CodeAction>>> {
17736 self.update(cx, |project, cx| {
17737 let code_lens = project.code_lens(buffer, range.clone(), cx);
17738 let code_actions = project.code_actions(buffer, range, None, cx);
17739 cx.background_spawn(async move {
17740 let (code_lens, code_actions) = join(code_lens, code_actions).await;
17741 Ok(code_lens
17742 .context("code lens fetch")?
17743 .into_iter()
17744 .chain(code_actions.context("code action fetch")?)
17745 .collect())
17746 })
17747 })
17748 }
17749
17750 fn apply_code_action(
17751 &self,
17752 buffer_handle: Entity<Buffer>,
17753 action: CodeAction,
17754 _excerpt_id: ExcerptId,
17755 push_to_history: bool,
17756 _window: &mut Window,
17757 cx: &mut App,
17758 ) -> Task<Result<ProjectTransaction>> {
17759 self.update(cx, |project, cx| {
17760 project.apply_code_action(buffer_handle, action, push_to_history, cx)
17761 })
17762 }
17763}
17764
17765fn snippet_completions(
17766 project: &Project,
17767 buffer: &Entity<Buffer>,
17768 buffer_position: text::Anchor,
17769 cx: &mut App,
17770) -> Task<Result<Vec<Completion>>> {
17771 let language = buffer.read(cx).language_at(buffer_position);
17772 let language_name = language.as_ref().map(|language| language.lsp_id());
17773 let snippet_store = project.snippets().read(cx);
17774 let snippets = snippet_store.snippets_for(language_name, cx);
17775
17776 if snippets.is_empty() {
17777 return Task::ready(Ok(vec![]));
17778 }
17779 let snapshot = buffer.read(cx).text_snapshot();
17780 let chars: String = snapshot
17781 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
17782 .collect();
17783
17784 let scope = language.map(|language| language.default_scope());
17785 let executor = cx.background_executor().clone();
17786
17787 cx.background_spawn(async move {
17788 let classifier = CharClassifier::new(scope).for_completion(true);
17789 let mut last_word = chars
17790 .chars()
17791 .take_while(|c| classifier.is_word(*c))
17792 .collect::<String>();
17793 last_word = last_word.chars().rev().collect();
17794
17795 if last_word.is_empty() {
17796 return Ok(vec![]);
17797 }
17798
17799 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
17800 let to_lsp = |point: &text::Anchor| {
17801 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
17802 point_to_lsp(end)
17803 };
17804 let lsp_end = to_lsp(&buffer_position);
17805
17806 let candidates = snippets
17807 .iter()
17808 .enumerate()
17809 .flat_map(|(ix, snippet)| {
17810 snippet
17811 .prefix
17812 .iter()
17813 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
17814 })
17815 .collect::<Vec<StringMatchCandidate>>();
17816
17817 let mut matches = fuzzy::match_strings(
17818 &candidates,
17819 &last_word,
17820 last_word.chars().any(|c| c.is_uppercase()),
17821 100,
17822 &Default::default(),
17823 executor,
17824 )
17825 .await;
17826
17827 // Remove all candidates where the query's start does not match the start of any word in the candidate
17828 if let Some(query_start) = last_word.chars().next() {
17829 matches.retain(|string_match| {
17830 split_words(&string_match.string).any(|word| {
17831 // Check that the first codepoint of the word as lowercase matches the first
17832 // codepoint of the query as lowercase
17833 word.chars()
17834 .flat_map(|codepoint| codepoint.to_lowercase())
17835 .zip(query_start.to_lowercase())
17836 .all(|(word_cp, query_cp)| word_cp == query_cp)
17837 })
17838 });
17839 }
17840
17841 let matched_strings = matches
17842 .into_iter()
17843 .map(|m| m.string)
17844 .collect::<HashSet<_>>();
17845
17846 let result: Vec<Completion> = snippets
17847 .into_iter()
17848 .filter_map(|snippet| {
17849 let matching_prefix = snippet
17850 .prefix
17851 .iter()
17852 .find(|prefix| matched_strings.contains(*prefix))?;
17853 let start = as_offset - last_word.len();
17854 let start = snapshot.anchor_before(start);
17855 let range = start..buffer_position;
17856 let lsp_start = to_lsp(&start);
17857 let lsp_range = lsp::Range {
17858 start: lsp_start,
17859 end: lsp_end,
17860 };
17861 Some(Completion {
17862 old_range: range,
17863 new_text: snippet.body.clone(),
17864 source: CompletionSource::Lsp {
17865 server_id: LanguageServerId(usize::MAX),
17866 resolved: true,
17867 lsp_completion: Box::new(lsp::CompletionItem {
17868 label: snippet.prefix.first().unwrap().clone(),
17869 kind: Some(CompletionItemKind::SNIPPET),
17870 label_details: snippet.description.as_ref().map(|description| {
17871 lsp::CompletionItemLabelDetails {
17872 detail: Some(description.clone()),
17873 description: None,
17874 }
17875 }),
17876 insert_text_format: Some(InsertTextFormat::SNIPPET),
17877 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17878 lsp::InsertReplaceEdit {
17879 new_text: snippet.body.clone(),
17880 insert: lsp_range,
17881 replace: lsp_range,
17882 },
17883 )),
17884 filter_text: Some(snippet.body.clone()),
17885 sort_text: Some(char::MAX.to_string()),
17886 ..lsp::CompletionItem::default()
17887 }),
17888 lsp_defaults: None,
17889 },
17890 label: CodeLabel {
17891 text: matching_prefix.clone(),
17892 runs: Vec::new(),
17893 filter_range: 0..matching_prefix.len(),
17894 },
17895 documentation: snippet
17896 .description
17897 .clone()
17898 .map(|description| CompletionDocumentation::SingleLine(description.into())),
17899 confirm: None,
17900 })
17901 })
17902 .collect();
17903
17904 Ok(result)
17905 })
17906}
17907
17908impl CompletionProvider for Entity<Project> {
17909 fn completions(
17910 &self,
17911 buffer: &Entity<Buffer>,
17912 buffer_position: text::Anchor,
17913 options: CompletionContext,
17914 _window: &mut Window,
17915 cx: &mut Context<Editor>,
17916 ) -> Task<Result<Option<Vec<Completion>>>> {
17917 self.update(cx, |project, cx| {
17918 let snippets = snippet_completions(project, buffer, buffer_position, cx);
17919 let project_completions = project.completions(buffer, buffer_position, options, cx);
17920 cx.background_spawn(async move {
17921 let snippets_completions = snippets.await?;
17922 match project_completions.await? {
17923 Some(mut completions) => {
17924 completions.extend(snippets_completions);
17925 Ok(Some(completions))
17926 }
17927 None => {
17928 if snippets_completions.is_empty() {
17929 Ok(None)
17930 } else {
17931 Ok(Some(snippets_completions))
17932 }
17933 }
17934 }
17935 })
17936 })
17937 }
17938
17939 fn resolve_completions(
17940 &self,
17941 buffer: Entity<Buffer>,
17942 completion_indices: Vec<usize>,
17943 completions: Rc<RefCell<Box<[Completion]>>>,
17944 cx: &mut Context<Editor>,
17945 ) -> Task<Result<bool>> {
17946 self.update(cx, |project, cx| {
17947 project.lsp_store().update(cx, |lsp_store, cx| {
17948 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
17949 })
17950 })
17951 }
17952
17953 fn apply_additional_edits_for_completion(
17954 &self,
17955 buffer: Entity<Buffer>,
17956 completions: Rc<RefCell<Box<[Completion]>>>,
17957 completion_index: usize,
17958 push_to_history: bool,
17959 cx: &mut Context<Editor>,
17960 ) -> Task<Result<Option<language::Transaction>>> {
17961 self.update(cx, |project, cx| {
17962 project.lsp_store().update(cx, |lsp_store, cx| {
17963 lsp_store.apply_additional_edits_for_completion(
17964 buffer,
17965 completions,
17966 completion_index,
17967 push_to_history,
17968 cx,
17969 )
17970 })
17971 })
17972 }
17973
17974 fn is_completion_trigger(
17975 &self,
17976 buffer: &Entity<Buffer>,
17977 position: language::Anchor,
17978 text: &str,
17979 trigger_in_words: bool,
17980 cx: &mut Context<Editor>,
17981 ) -> bool {
17982 let mut chars = text.chars();
17983 let char = if let Some(char) = chars.next() {
17984 char
17985 } else {
17986 return false;
17987 };
17988 if chars.next().is_some() {
17989 return false;
17990 }
17991
17992 let buffer = buffer.read(cx);
17993 let snapshot = buffer.snapshot();
17994 if !snapshot.settings_at(position, cx).show_completions_on_input {
17995 return false;
17996 }
17997 let classifier = snapshot.char_classifier_at(position).for_completion(true);
17998 if trigger_in_words && classifier.is_word(char) {
17999 return true;
18000 }
18001
18002 buffer.completion_triggers().contains(text)
18003 }
18004}
18005
18006impl SemanticsProvider for Entity<Project> {
18007 fn hover(
18008 &self,
18009 buffer: &Entity<Buffer>,
18010 position: text::Anchor,
18011 cx: &mut App,
18012 ) -> Option<Task<Vec<project::Hover>>> {
18013 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
18014 }
18015
18016 fn document_highlights(
18017 &self,
18018 buffer: &Entity<Buffer>,
18019 position: text::Anchor,
18020 cx: &mut App,
18021 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
18022 Some(self.update(cx, |project, cx| {
18023 project.document_highlights(buffer, position, cx)
18024 }))
18025 }
18026
18027 fn definitions(
18028 &self,
18029 buffer: &Entity<Buffer>,
18030 position: text::Anchor,
18031 kind: GotoDefinitionKind,
18032 cx: &mut App,
18033 ) -> Option<Task<Result<Vec<LocationLink>>>> {
18034 Some(self.update(cx, |project, cx| match kind {
18035 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
18036 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
18037 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
18038 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
18039 }))
18040 }
18041
18042 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
18043 // TODO: make this work for remote projects
18044 self.update(cx, |this, cx| {
18045 buffer.update(cx, |buffer, cx| {
18046 this.any_language_server_supports_inlay_hints(buffer, cx)
18047 })
18048 })
18049 }
18050
18051 fn inlay_hints(
18052 &self,
18053 buffer_handle: Entity<Buffer>,
18054 range: Range<text::Anchor>,
18055 cx: &mut App,
18056 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
18057 Some(self.update(cx, |project, cx| {
18058 project.inlay_hints(buffer_handle, range, cx)
18059 }))
18060 }
18061
18062 fn resolve_inlay_hint(
18063 &self,
18064 hint: InlayHint,
18065 buffer_handle: Entity<Buffer>,
18066 server_id: LanguageServerId,
18067 cx: &mut App,
18068 ) -> Option<Task<anyhow::Result<InlayHint>>> {
18069 Some(self.update(cx, |project, cx| {
18070 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
18071 }))
18072 }
18073
18074 fn range_for_rename(
18075 &self,
18076 buffer: &Entity<Buffer>,
18077 position: text::Anchor,
18078 cx: &mut App,
18079 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
18080 Some(self.update(cx, |project, cx| {
18081 let buffer = buffer.clone();
18082 let task = project.prepare_rename(buffer.clone(), position, cx);
18083 cx.spawn(async move |_, cx| {
18084 Ok(match task.await? {
18085 PrepareRenameResponse::Success(range) => Some(range),
18086 PrepareRenameResponse::InvalidPosition => None,
18087 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
18088 // Fallback on using TreeSitter info to determine identifier range
18089 buffer.update(cx, |buffer, _| {
18090 let snapshot = buffer.snapshot();
18091 let (range, kind) = snapshot.surrounding_word(position);
18092 if kind != Some(CharKind::Word) {
18093 return None;
18094 }
18095 Some(
18096 snapshot.anchor_before(range.start)
18097 ..snapshot.anchor_after(range.end),
18098 )
18099 })?
18100 }
18101 })
18102 })
18103 }))
18104 }
18105
18106 fn perform_rename(
18107 &self,
18108 buffer: &Entity<Buffer>,
18109 position: text::Anchor,
18110 new_name: String,
18111 cx: &mut App,
18112 ) -> Option<Task<Result<ProjectTransaction>>> {
18113 Some(self.update(cx, |project, cx| {
18114 project.perform_rename(buffer.clone(), position, new_name, cx)
18115 }))
18116 }
18117}
18118
18119fn inlay_hint_settings(
18120 location: Anchor,
18121 snapshot: &MultiBufferSnapshot,
18122 cx: &mut Context<Editor>,
18123) -> InlayHintSettings {
18124 let file = snapshot.file_at(location);
18125 let language = snapshot.language_at(location).map(|l| l.name());
18126 language_settings(language, file, cx).inlay_hints
18127}
18128
18129fn consume_contiguous_rows(
18130 contiguous_row_selections: &mut Vec<Selection<Point>>,
18131 selection: &Selection<Point>,
18132 display_map: &DisplaySnapshot,
18133 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
18134) -> (MultiBufferRow, MultiBufferRow) {
18135 contiguous_row_selections.push(selection.clone());
18136 let start_row = MultiBufferRow(selection.start.row);
18137 let mut end_row = ending_row(selection, display_map);
18138
18139 while let Some(next_selection) = selections.peek() {
18140 if next_selection.start.row <= end_row.0 {
18141 end_row = ending_row(next_selection, display_map);
18142 contiguous_row_selections.push(selections.next().unwrap().clone());
18143 } else {
18144 break;
18145 }
18146 }
18147 (start_row, end_row)
18148}
18149
18150fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
18151 if next_selection.end.column > 0 || next_selection.is_empty() {
18152 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
18153 } else {
18154 MultiBufferRow(next_selection.end.row)
18155 }
18156}
18157
18158impl EditorSnapshot {
18159 pub fn remote_selections_in_range<'a>(
18160 &'a self,
18161 range: &'a Range<Anchor>,
18162 collaboration_hub: &dyn CollaborationHub,
18163 cx: &'a App,
18164 ) -> impl 'a + Iterator<Item = RemoteSelection> {
18165 let participant_names = collaboration_hub.user_names(cx);
18166 let participant_indices = collaboration_hub.user_participant_indices(cx);
18167 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
18168 let collaborators_by_replica_id = collaborators_by_peer_id
18169 .iter()
18170 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
18171 .collect::<HashMap<_, _>>();
18172 self.buffer_snapshot
18173 .selections_in_range(range, false)
18174 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
18175 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
18176 let participant_index = participant_indices.get(&collaborator.user_id).copied();
18177 let user_name = participant_names.get(&collaborator.user_id).cloned();
18178 Some(RemoteSelection {
18179 replica_id,
18180 selection,
18181 cursor_shape,
18182 line_mode,
18183 participant_index,
18184 peer_id: collaborator.peer_id,
18185 user_name,
18186 })
18187 })
18188 }
18189
18190 pub fn hunks_for_ranges(
18191 &self,
18192 ranges: impl IntoIterator<Item = Range<Point>>,
18193 ) -> Vec<MultiBufferDiffHunk> {
18194 let mut hunks = Vec::new();
18195 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
18196 HashMap::default();
18197 for query_range in ranges {
18198 let query_rows =
18199 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
18200 for hunk in self.buffer_snapshot.diff_hunks_in_range(
18201 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
18202 ) {
18203 // Include deleted hunks that are adjacent to the query range, because
18204 // otherwise they would be missed.
18205 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
18206 if hunk.status().is_deleted() {
18207 intersects_range |= hunk.row_range.start == query_rows.end;
18208 intersects_range |= hunk.row_range.end == query_rows.start;
18209 }
18210 if intersects_range {
18211 if !processed_buffer_rows
18212 .entry(hunk.buffer_id)
18213 .or_default()
18214 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
18215 {
18216 continue;
18217 }
18218 hunks.push(hunk);
18219 }
18220 }
18221 }
18222
18223 hunks
18224 }
18225
18226 fn display_diff_hunks_for_rows<'a>(
18227 &'a self,
18228 display_rows: Range<DisplayRow>,
18229 folded_buffers: &'a HashSet<BufferId>,
18230 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
18231 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
18232 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
18233
18234 self.buffer_snapshot
18235 .diff_hunks_in_range(buffer_start..buffer_end)
18236 .filter_map(|hunk| {
18237 if folded_buffers.contains(&hunk.buffer_id) {
18238 return None;
18239 }
18240
18241 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
18242 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
18243
18244 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
18245 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
18246
18247 let display_hunk = if hunk_display_start.column() != 0 {
18248 DisplayDiffHunk::Folded {
18249 display_row: hunk_display_start.row(),
18250 }
18251 } else {
18252 let mut end_row = hunk_display_end.row();
18253 if hunk_display_end.column() > 0 {
18254 end_row.0 += 1;
18255 }
18256 let is_created_file = hunk.is_created_file();
18257 DisplayDiffHunk::Unfolded {
18258 status: hunk.status(),
18259 diff_base_byte_range: hunk.diff_base_byte_range,
18260 display_row_range: hunk_display_start.row()..end_row,
18261 multi_buffer_range: Anchor::range_in_buffer(
18262 hunk.excerpt_id,
18263 hunk.buffer_id,
18264 hunk.buffer_range,
18265 ),
18266 is_created_file,
18267 }
18268 };
18269
18270 Some(display_hunk)
18271 })
18272 }
18273
18274 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
18275 self.display_snapshot.buffer_snapshot.language_at(position)
18276 }
18277
18278 pub fn is_focused(&self) -> bool {
18279 self.is_focused
18280 }
18281
18282 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
18283 self.placeholder_text.as_ref()
18284 }
18285
18286 pub fn scroll_position(&self) -> gpui::Point<f32> {
18287 self.scroll_anchor.scroll_position(&self.display_snapshot)
18288 }
18289
18290 fn gutter_dimensions(
18291 &self,
18292 font_id: FontId,
18293 font_size: Pixels,
18294 max_line_number_width: Pixels,
18295 cx: &App,
18296 ) -> Option<GutterDimensions> {
18297 if !self.show_gutter {
18298 return None;
18299 }
18300
18301 let descent = cx.text_system().descent(font_id, font_size);
18302 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
18303 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
18304
18305 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
18306 matches!(
18307 ProjectSettings::get_global(cx).git.git_gutter,
18308 Some(GitGutterSetting::TrackedFiles)
18309 )
18310 });
18311 let gutter_settings = EditorSettings::get_global(cx).gutter;
18312 let show_line_numbers = self
18313 .show_line_numbers
18314 .unwrap_or(gutter_settings.line_numbers);
18315 let line_gutter_width = if show_line_numbers {
18316 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
18317 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
18318 max_line_number_width.max(min_width_for_number_on_gutter)
18319 } else {
18320 0.0.into()
18321 };
18322
18323 let show_code_actions = self
18324 .show_code_actions
18325 .unwrap_or(gutter_settings.code_actions);
18326
18327 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
18328 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
18329
18330 let git_blame_entries_width =
18331 self.git_blame_gutter_max_author_length
18332 .map(|max_author_length| {
18333 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
18334
18335 /// The number of characters to dedicate to gaps and margins.
18336 const SPACING_WIDTH: usize = 4;
18337
18338 let max_char_count = max_author_length
18339 .min(GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED)
18340 + ::git::SHORT_SHA_LENGTH
18341 + MAX_RELATIVE_TIMESTAMP.len()
18342 + SPACING_WIDTH;
18343
18344 em_advance * max_char_count
18345 });
18346
18347 let is_singleton = self.buffer_snapshot.is_singleton();
18348
18349 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
18350 left_padding += if !is_singleton {
18351 em_width * 4.0
18352 } else if show_code_actions || show_runnables || show_breakpoints {
18353 em_width * 3.0
18354 } else if show_git_gutter && show_line_numbers {
18355 em_width * 2.0
18356 } else if show_git_gutter || show_line_numbers {
18357 em_width
18358 } else {
18359 px(0.)
18360 };
18361
18362 let shows_folds = is_singleton && gutter_settings.folds;
18363
18364 let right_padding = if shows_folds && show_line_numbers {
18365 em_width * 4.0
18366 } else if shows_folds || (!is_singleton && show_line_numbers) {
18367 em_width * 3.0
18368 } else if show_line_numbers {
18369 em_width
18370 } else {
18371 px(0.)
18372 };
18373
18374 Some(GutterDimensions {
18375 left_padding,
18376 right_padding,
18377 width: line_gutter_width + left_padding + right_padding,
18378 margin: -descent,
18379 git_blame_entries_width,
18380 })
18381 }
18382
18383 pub fn render_crease_toggle(
18384 &self,
18385 buffer_row: MultiBufferRow,
18386 row_contains_cursor: bool,
18387 editor: Entity<Editor>,
18388 window: &mut Window,
18389 cx: &mut App,
18390 ) -> Option<AnyElement> {
18391 let folded = self.is_line_folded(buffer_row);
18392 let mut is_foldable = false;
18393
18394 if let Some(crease) = self
18395 .crease_snapshot
18396 .query_row(buffer_row, &self.buffer_snapshot)
18397 {
18398 is_foldable = true;
18399 match crease {
18400 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
18401 if let Some(render_toggle) = render_toggle {
18402 let toggle_callback =
18403 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
18404 if folded {
18405 editor.update(cx, |editor, cx| {
18406 editor.fold_at(&crate::FoldAt { buffer_row }, window, cx)
18407 });
18408 } else {
18409 editor.update(cx, |editor, cx| {
18410 editor.unfold_at(
18411 &crate::UnfoldAt { buffer_row },
18412 window,
18413 cx,
18414 )
18415 });
18416 }
18417 });
18418 return Some((render_toggle)(
18419 buffer_row,
18420 folded,
18421 toggle_callback,
18422 window,
18423 cx,
18424 ));
18425 }
18426 }
18427 }
18428 }
18429
18430 is_foldable |= self.starts_indent(buffer_row);
18431
18432 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
18433 Some(
18434 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
18435 .toggle_state(folded)
18436 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
18437 if folded {
18438 this.unfold_at(&UnfoldAt { buffer_row }, window, cx);
18439 } else {
18440 this.fold_at(&FoldAt { buffer_row }, window, cx);
18441 }
18442 }))
18443 .into_any_element(),
18444 )
18445 } else {
18446 None
18447 }
18448 }
18449
18450 pub fn render_crease_trailer(
18451 &self,
18452 buffer_row: MultiBufferRow,
18453 window: &mut Window,
18454 cx: &mut App,
18455 ) -> Option<AnyElement> {
18456 let folded = self.is_line_folded(buffer_row);
18457 if let Crease::Inline { render_trailer, .. } = self
18458 .crease_snapshot
18459 .query_row(buffer_row, &self.buffer_snapshot)?
18460 {
18461 let render_trailer = render_trailer.as_ref()?;
18462 Some(render_trailer(buffer_row, folded, window, cx))
18463 } else {
18464 None
18465 }
18466 }
18467}
18468
18469impl Deref for EditorSnapshot {
18470 type Target = DisplaySnapshot;
18471
18472 fn deref(&self) -> &Self::Target {
18473 &self.display_snapshot
18474 }
18475}
18476
18477#[derive(Clone, Debug, PartialEq, Eq)]
18478pub enum EditorEvent {
18479 InputIgnored {
18480 text: Arc<str>,
18481 },
18482 InputHandled {
18483 utf16_range_to_replace: Option<Range<isize>>,
18484 text: Arc<str>,
18485 },
18486 ExcerptsAdded {
18487 buffer: Entity<Buffer>,
18488 predecessor: ExcerptId,
18489 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
18490 },
18491 ExcerptsRemoved {
18492 ids: Vec<ExcerptId>,
18493 },
18494 BufferFoldToggled {
18495 ids: Vec<ExcerptId>,
18496 folded: bool,
18497 },
18498 ExcerptsEdited {
18499 ids: Vec<ExcerptId>,
18500 },
18501 ExcerptsExpanded {
18502 ids: Vec<ExcerptId>,
18503 },
18504 BufferEdited,
18505 Edited {
18506 transaction_id: clock::Lamport,
18507 },
18508 Reparsed(BufferId),
18509 Focused,
18510 FocusedIn,
18511 Blurred,
18512 DirtyChanged,
18513 Saved,
18514 TitleChanged,
18515 DiffBaseChanged,
18516 SelectionsChanged {
18517 local: bool,
18518 },
18519 ScrollPositionChanged {
18520 local: bool,
18521 autoscroll: bool,
18522 },
18523 Closed,
18524 TransactionUndone {
18525 transaction_id: clock::Lamport,
18526 },
18527 TransactionBegun {
18528 transaction_id: clock::Lamport,
18529 },
18530 Reloaded,
18531 CursorShapeChanged,
18532}
18533
18534impl EventEmitter<EditorEvent> for Editor {}
18535
18536impl Focusable for Editor {
18537 fn focus_handle(&self, _cx: &App) -> FocusHandle {
18538 self.focus_handle.clone()
18539 }
18540}
18541
18542impl Render for Editor {
18543 fn render(&mut self, _: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
18544 let settings = ThemeSettings::get_global(cx);
18545
18546 let mut text_style = match self.mode {
18547 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
18548 color: cx.theme().colors().editor_foreground,
18549 font_family: settings.ui_font.family.clone(),
18550 font_features: settings.ui_font.features.clone(),
18551 font_fallbacks: settings.ui_font.fallbacks.clone(),
18552 font_size: rems(0.875).into(),
18553 font_weight: settings.ui_font.weight,
18554 line_height: relative(settings.buffer_line_height.value()),
18555 ..Default::default()
18556 },
18557 EditorMode::Full => TextStyle {
18558 color: cx.theme().colors().editor_foreground,
18559 font_family: settings.buffer_font.family.clone(),
18560 font_features: settings.buffer_font.features.clone(),
18561 font_fallbacks: settings.buffer_font.fallbacks.clone(),
18562 font_size: settings.buffer_font_size(cx).into(),
18563 font_weight: settings.buffer_font.weight,
18564 line_height: relative(settings.buffer_line_height.value()),
18565 ..Default::default()
18566 },
18567 };
18568 if let Some(text_style_refinement) = &self.text_style_refinement {
18569 text_style.refine(text_style_refinement)
18570 }
18571
18572 let background = match self.mode {
18573 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
18574 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
18575 EditorMode::Full => cx.theme().colors().editor_background,
18576 };
18577
18578 EditorElement::new(
18579 &cx.entity(),
18580 EditorStyle {
18581 background,
18582 local_player: cx.theme().players().local(),
18583 text: text_style,
18584 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
18585 syntax: cx.theme().syntax().clone(),
18586 status: cx.theme().status().clone(),
18587 inlay_hints_style: make_inlay_hints_style(cx),
18588 inline_completion_styles: make_suggestion_styles(cx),
18589 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
18590 },
18591 )
18592 }
18593}
18594
18595impl EntityInputHandler for Editor {
18596 fn text_for_range(
18597 &mut self,
18598 range_utf16: Range<usize>,
18599 adjusted_range: &mut Option<Range<usize>>,
18600 _: &mut Window,
18601 cx: &mut Context<Self>,
18602 ) -> Option<String> {
18603 let snapshot = self.buffer.read(cx).read(cx);
18604 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
18605 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
18606 if (start.0..end.0) != range_utf16 {
18607 adjusted_range.replace(start.0..end.0);
18608 }
18609 Some(snapshot.text_for_range(start..end).collect())
18610 }
18611
18612 fn selected_text_range(
18613 &mut self,
18614 ignore_disabled_input: bool,
18615 _: &mut Window,
18616 cx: &mut Context<Self>,
18617 ) -> Option<UTF16Selection> {
18618 // Prevent the IME menu from appearing when holding down an alphabetic key
18619 // while input is disabled.
18620 if !ignore_disabled_input && !self.input_enabled {
18621 return None;
18622 }
18623
18624 let selection = self.selections.newest::<OffsetUtf16>(cx);
18625 let range = selection.range();
18626
18627 Some(UTF16Selection {
18628 range: range.start.0..range.end.0,
18629 reversed: selection.reversed,
18630 })
18631 }
18632
18633 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
18634 let snapshot = self.buffer.read(cx).read(cx);
18635 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
18636 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
18637 }
18638
18639 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18640 self.clear_highlights::<InputComposition>(cx);
18641 self.ime_transaction.take();
18642 }
18643
18644 fn replace_text_in_range(
18645 &mut self,
18646 range_utf16: Option<Range<usize>>,
18647 text: &str,
18648 window: &mut Window,
18649 cx: &mut Context<Self>,
18650 ) {
18651 if !self.input_enabled {
18652 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18653 return;
18654 }
18655
18656 self.transact(window, cx, |this, window, cx| {
18657 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
18658 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
18659 Some(this.selection_replacement_ranges(range_utf16, cx))
18660 } else {
18661 this.marked_text_ranges(cx)
18662 };
18663
18664 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
18665 let newest_selection_id = this.selections.newest_anchor().id;
18666 this.selections
18667 .all::<OffsetUtf16>(cx)
18668 .iter()
18669 .zip(ranges_to_replace.iter())
18670 .find_map(|(selection, range)| {
18671 if selection.id == newest_selection_id {
18672 Some(
18673 (range.start.0 as isize - selection.head().0 as isize)
18674 ..(range.end.0 as isize - selection.head().0 as isize),
18675 )
18676 } else {
18677 None
18678 }
18679 })
18680 });
18681
18682 cx.emit(EditorEvent::InputHandled {
18683 utf16_range_to_replace: range_to_replace,
18684 text: text.into(),
18685 });
18686
18687 if let Some(new_selected_ranges) = new_selected_ranges {
18688 this.change_selections(None, window, cx, |selections| {
18689 selections.select_ranges(new_selected_ranges)
18690 });
18691 this.backspace(&Default::default(), window, cx);
18692 }
18693
18694 this.handle_input(text, window, cx);
18695 });
18696
18697 if let Some(transaction) = self.ime_transaction {
18698 self.buffer.update(cx, |buffer, cx| {
18699 buffer.group_until_transaction(transaction, cx);
18700 });
18701 }
18702
18703 self.unmark_text(window, cx);
18704 }
18705
18706 fn replace_and_mark_text_in_range(
18707 &mut self,
18708 range_utf16: Option<Range<usize>>,
18709 text: &str,
18710 new_selected_range_utf16: Option<Range<usize>>,
18711 window: &mut Window,
18712 cx: &mut Context<Self>,
18713 ) {
18714 if !self.input_enabled {
18715 return;
18716 }
18717
18718 let transaction = self.transact(window, cx, |this, window, cx| {
18719 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
18720 let snapshot = this.buffer.read(cx).read(cx);
18721 if let Some(relative_range_utf16) = range_utf16.as_ref() {
18722 for marked_range in &mut marked_ranges {
18723 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
18724 marked_range.start.0 += relative_range_utf16.start;
18725 marked_range.start =
18726 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
18727 marked_range.end =
18728 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
18729 }
18730 }
18731 Some(marked_ranges)
18732 } else if let Some(range_utf16) = range_utf16 {
18733 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
18734 Some(this.selection_replacement_ranges(range_utf16, cx))
18735 } else {
18736 None
18737 };
18738
18739 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
18740 let newest_selection_id = this.selections.newest_anchor().id;
18741 this.selections
18742 .all::<OffsetUtf16>(cx)
18743 .iter()
18744 .zip(ranges_to_replace.iter())
18745 .find_map(|(selection, range)| {
18746 if selection.id == newest_selection_id {
18747 Some(
18748 (range.start.0 as isize - selection.head().0 as isize)
18749 ..(range.end.0 as isize - selection.head().0 as isize),
18750 )
18751 } else {
18752 None
18753 }
18754 })
18755 });
18756
18757 cx.emit(EditorEvent::InputHandled {
18758 utf16_range_to_replace: range_to_replace,
18759 text: text.into(),
18760 });
18761
18762 if let Some(ranges) = ranges_to_replace {
18763 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
18764 }
18765
18766 let marked_ranges = {
18767 let snapshot = this.buffer.read(cx).read(cx);
18768 this.selections
18769 .disjoint_anchors()
18770 .iter()
18771 .map(|selection| {
18772 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
18773 })
18774 .collect::<Vec<_>>()
18775 };
18776
18777 if text.is_empty() {
18778 this.unmark_text(window, cx);
18779 } else {
18780 this.highlight_text::<InputComposition>(
18781 marked_ranges.clone(),
18782 HighlightStyle {
18783 underline: Some(UnderlineStyle {
18784 thickness: px(1.),
18785 color: None,
18786 wavy: false,
18787 }),
18788 ..Default::default()
18789 },
18790 cx,
18791 );
18792 }
18793
18794 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
18795 let use_autoclose = this.use_autoclose;
18796 let use_auto_surround = this.use_auto_surround;
18797 this.set_use_autoclose(false);
18798 this.set_use_auto_surround(false);
18799 this.handle_input(text, window, cx);
18800 this.set_use_autoclose(use_autoclose);
18801 this.set_use_auto_surround(use_auto_surround);
18802
18803 if let Some(new_selected_range) = new_selected_range_utf16 {
18804 let snapshot = this.buffer.read(cx).read(cx);
18805 let new_selected_ranges = marked_ranges
18806 .into_iter()
18807 .map(|marked_range| {
18808 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
18809 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
18810 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
18811 snapshot.clip_offset_utf16(new_start, Bias::Left)
18812 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
18813 })
18814 .collect::<Vec<_>>();
18815
18816 drop(snapshot);
18817 this.change_selections(None, window, cx, |selections| {
18818 selections.select_ranges(new_selected_ranges)
18819 });
18820 }
18821 });
18822
18823 self.ime_transaction = self.ime_transaction.or(transaction);
18824 if let Some(transaction) = self.ime_transaction {
18825 self.buffer.update(cx, |buffer, cx| {
18826 buffer.group_until_transaction(transaction, cx);
18827 });
18828 }
18829
18830 if self.text_highlights::<InputComposition>(cx).is_none() {
18831 self.ime_transaction.take();
18832 }
18833 }
18834
18835 fn bounds_for_range(
18836 &mut self,
18837 range_utf16: Range<usize>,
18838 element_bounds: gpui::Bounds<Pixels>,
18839 window: &mut Window,
18840 cx: &mut Context<Self>,
18841 ) -> Option<gpui::Bounds<Pixels>> {
18842 let text_layout_details = self.text_layout_details(window);
18843 let gpui::Size {
18844 width: em_width,
18845 height: line_height,
18846 } = self.character_size(window);
18847
18848 let snapshot = self.snapshot(window, cx);
18849 let scroll_position = snapshot.scroll_position();
18850 let scroll_left = scroll_position.x * em_width;
18851
18852 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
18853 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
18854 + self.gutter_dimensions.width
18855 + self.gutter_dimensions.margin;
18856 let y = line_height * (start.row().as_f32() - scroll_position.y);
18857
18858 Some(Bounds {
18859 origin: element_bounds.origin + point(x, y),
18860 size: size(em_width, line_height),
18861 })
18862 }
18863
18864 fn character_index_for_point(
18865 &mut self,
18866 point: gpui::Point<Pixels>,
18867 _window: &mut Window,
18868 _cx: &mut Context<Self>,
18869 ) -> Option<usize> {
18870 let position_map = self.last_position_map.as_ref()?;
18871 if !position_map.text_hitbox.contains(&point) {
18872 return None;
18873 }
18874 let display_point = position_map.point_for_position(point).previous_valid;
18875 let anchor = position_map
18876 .snapshot
18877 .display_point_to_anchor(display_point, Bias::Left);
18878 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
18879 Some(utf16_offset.0)
18880 }
18881}
18882
18883trait SelectionExt {
18884 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
18885 fn spanned_rows(
18886 &self,
18887 include_end_if_at_line_start: bool,
18888 map: &DisplaySnapshot,
18889 ) -> Range<MultiBufferRow>;
18890}
18891
18892impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
18893 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
18894 let start = self
18895 .start
18896 .to_point(&map.buffer_snapshot)
18897 .to_display_point(map);
18898 let end = self
18899 .end
18900 .to_point(&map.buffer_snapshot)
18901 .to_display_point(map);
18902 if self.reversed {
18903 end..start
18904 } else {
18905 start..end
18906 }
18907 }
18908
18909 fn spanned_rows(
18910 &self,
18911 include_end_if_at_line_start: bool,
18912 map: &DisplaySnapshot,
18913 ) -> Range<MultiBufferRow> {
18914 let start = self.start.to_point(&map.buffer_snapshot);
18915 let mut end = self.end.to_point(&map.buffer_snapshot);
18916 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
18917 end.row -= 1;
18918 }
18919
18920 let buffer_start = map.prev_line_boundary(start).0;
18921 let buffer_end = map.next_line_boundary(end).0;
18922 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
18923 }
18924}
18925
18926impl<T: InvalidationRegion> InvalidationStack<T> {
18927 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
18928 where
18929 S: Clone + ToOffset,
18930 {
18931 while let Some(region) = self.last() {
18932 let all_selections_inside_invalidation_ranges =
18933 if selections.len() == region.ranges().len() {
18934 selections
18935 .iter()
18936 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
18937 .all(|(selection, invalidation_range)| {
18938 let head = selection.head().to_offset(buffer);
18939 invalidation_range.start <= head && invalidation_range.end >= head
18940 })
18941 } else {
18942 false
18943 };
18944
18945 if all_selections_inside_invalidation_ranges {
18946 break;
18947 } else {
18948 self.pop();
18949 }
18950 }
18951 }
18952}
18953
18954impl<T> Default for InvalidationStack<T> {
18955 fn default() -> Self {
18956 Self(Default::default())
18957 }
18958}
18959
18960impl<T> Deref for InvalidationStack<T> {
18961 type Target = Vec<T>;
18962
18963 fn deref(&self) -> &Self::Target {
18964 &self.0
18965 }
18966}
18967
18968impl<T> DerefMut for InvalidationStack<T> {
18969 fn deref_mut(&mut self) -> &mut Self::Target {
18970 &mut self.0
18971 }
18972}
18973
18974impl InvalidationRegion for SnippetState {
18975 fn ranges(&self) -> &[Range<Anchor>] {
18976 &self.ranges[self.active_index]
18977 }
18978}
18979
18980pub fn diagnostic_block_renderer(
18981 diagnostic: Diagnostic,
18982 max_message_rows: Option<u8>,
18983 allow_closing: bool,
18984) -> RenderBlock {
18985 let (text_without_backticks, code_ranges) =
18986 highlight_diagnostic_message(&diagnostic, max_message_rows);
18987
18988 Arc::new(move |cx: &mut BlockContext| {
18989 let group_id: SharedString = cx.block_id.to_string().into();
18990
18991 let mut text_style = cx.window.text_style().clone();
18992 text_style.color = diagnostic_style(diagnostic.severity, cx.theme().status());
18993 let theme_settings = ThemeSettings::get_global(cx);
18994 text_style.font_family = theme_settings.buffer_font.family.clone();
18995 text_style.font_style = theme_settings.buffer_font.style;
18996 text_style.font_features = theme_settings.buffer_font.features.clone();
18997 text_style.font_weight = theme_settings.buffer_font.weight;
18998
18999 let multi_line_diagnostic = diagnostic.message.contains('\n');
19000
19001 let buttons = |diagnostic: &Diagnostic| {
19002 if multi_line_diagnostic {
19003 v_flex()
19004 } else {
19005 h_flex()
19006 }
19007 .when(allow_closing, |div| {
19008 div.children(diagnostic.is_primary.then(|| {
19009 IconButton::new("close-block", IconName::XCircle)
19010 .icon_color(Color::Muted)
19011 .size(ButtonSize::Compact)
19012 .style(ButtonStyle::Transparent)
19013 .visible_on_hover(group_id.clone())
19014 .on_click(move |_click, window, cx| {
19015 window.dispatch_action(Box::new(Cancel), cx)
19016 })
19017 .tooltip(|window, cx| {
19018 Tooltip::for_action("Close Diagnostics", &Cancel, window, cx)
19019 })
19020 }))
19021 })
19022 .child(
19023 IconButton::new("copy-block", IconName::Copy)
19024 .icon_color(Color::Muted)
19025 .size(ButtonSize::Compact)
19026 .style(ButtonStyle::Transparent)
19027 .visible_on_hover(group_id.clone())
19028 .on_click({
19029 let message = diagnostic.message.clone();
19030 move |_click, _, cx| {
19031 cx.write_to_clipboard(ClipboardItem::new_string(message.clone()))
19032 }
19033 })
19034 .tooltip(Tooltip::text("Copy diagnostic message")),
19035 )
19036 };
19037
19038 let icon_size = buttons(&diagnostic).into_any_element().layout_as_root(
19039 AvailableSpace::min_size(),
19040 cx.window,
19041 cx.app,
19042 );
19043
19044 h_flex()
19045 .id(cx.block_id)
19046 .group(group_id.clone())
19047 .relative()
19048 .size_full()
19049 .block_mouse_down()
19050 .pl(cx.gutter_dimensions.width)
19051 .w(cx.max_width - cx.gutter_dimensions.full_width())
19052 .child(
19053 div()
19054 .flex()
19055 .w(cx.anchor_x - cx.gutter_dimensions.width - icon_size.width)
19056 .flex_shrink(),
19057 )
19058 .child(buttons(&diagnostic))
19059 .child(div().flex().flex_shrink_0().child(
19060 StyledText::new(text_without_backticks.clone()).with_default_highlights(
19061 &text_style,
19062 code_ranges.iter().map(|range| {
19063 (
19064 range.clone(),
19065 HighlightStyle {
19066 font_weight: Some(FontWeight::BOLD),
19067 ..Default::default()
19068 },
19069 )
19070 }),
19071 ),
19072 ))
19073 .into_any_element()
19074 })
19075}
19076
19077fn inline_completion_edit_text(
19078 current_snapshot: &BufferSnapshot,
19079 edits: &[(Range<Anchor>, String)],
19080 edit_preview: &EditPreview,
19081 include_deletions: bool,
19082 cx: &App,
19083) -> HighlightedText {
19084 let edits = edits
19085 .iter()
19086 .map(|(anchor, text)| {
19087 (
19088 anchor.start.text_anchor..anchor.end.text_anchor,
19089 text.clone(),
19090 )
19091 })
19092 .collect::<Vec<_>>();
19093
19094 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
19095}
19096
19097pub fn highlight_diagnostic_message(
19098 diagnostic: &Diagnostic,
19099 mut max_message_rows: Option<u8>,
19100) -> (SharedString, Vec<Range<usize>>) {
19101 let mut text_without_backticks = String::new();
19102 let mut code_ranges = Vec::new();
19103
19104 if let Some(source) = &diagnostic.source {
19105 text_without_backticks.push_str(source);
19106 code_ranges.push(0..source.len());
19107 text_without_backticks.push_str(": ");
19108 }
19109
19110 let mut prev_offset = 0;
19111 let mut in_code_block = false;
19112 let has_row_limit = max_message_rows.is_some();
19113 let mut newline_indices = diagnostic
19114 .message
19115 .match_indices('\n')
19116 .filter(|_| has_row_limit)
19117 .map(|(ix, _)| ix)
19118 .fuse()
19119 .peekable();
19120
19121 for (quote_ix, _) in diagnostic
19122 .message
19123 .match_indices('`')
19124 .chain([(diagnostic.message.len(), "")])
19125 {
19126 let mut first_newline_ix = None;
19127 let mut last_newline_ix = None;
19128 while let Some(newline_ix) = newline_indices.peek() {
19129 if *newline_ix < quote_ix {
19130 if first_newline_ix.is_none() {
19131 first_newline_ix = Some(*newline_ix);
19132 }
19133 last_newline_ix = Some(*newline_ix);
19134
19135 if let Some(rows_left) = &mut max_message_rows {
19136 if *rows_left == 0 {
19137 break;
19138 } else {
19139 *rows_left -= 1;
19140 }
19141 }
19142 let _ = newline_indices.next();
19143 } else {
19144 break;
19145 }
19146 }
19147 let prev_len = text_without_backticks.len();
19148 let new_text = &diagnostic.message[prev_offset..first_newline_ix.unwrap_or(quote_ix)];
19149 text_without_backticks.push_str(new_text);
19150 if in_code_block {
19151 code_ranges.push(prev_len..text_without_backticks.len());
19152 }
19153 prev_offset = last_newline_ix.unwrap_or(quote_ix) + 1;
19154 in_code_block = !in_code_block;
19155 if first_newline_ix.map_or(false, |newline_ix| newline_ix < quote_ix) {
19156 text_without_backticks.push_str("...");
19157 break;
19158 }
19159 }
19160
19161 (text_without_backticks.into(), code_ranges)
19162}
19163
19164fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
19165 match severity {
19166 DiagnosticSeverity::ERROR => colors.error,
19167 DiagnosticSeverity::WARNING => colors.warning,
19168 DiagnosticSeverity::INFORMATION => colors.info,
19169 DiagnosticSeverity::HINT => colors.info,
19170 _ => colors.ignored,
19171 }
19172}
19173
19174pub fn styled_runs_for_code_label<'a>(
19175 label: &'a CodeLabel,
19176 syntax_theme: &'a theme::SyntaxTheme,
19177) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
19178 let fade_out = HighlightStyle {
19179 fade_out: Some(0.35),
19180 ..Default::default()
19181 };
19182
19183 let mut prev_end = label.filter_range.end;
19184 label
19185 .runs
19186 .iter()
19187 .enumerate()
19188 .flat_map(move |(ix, (range, highlight_id))| {
19189 let style = if let Some(style) = highlight_id.style(syntax_theme) {
19190 style
19191 } else {
19192 return Default::default();
19193 };
19194 let mut muted_style = style;
19195 muted_style.highlight(fade_out);
19196
19197 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
19198 if range.start >= label.filter_range.end {
19199 if range.start > prev_end {
19200 runs.push((prev_end..range.start, fade_out));
19201 }
19202 runs.push((range.clone(), muted_style));
19203 } else if range.end <= label.filter_range.end {
19204 runs.push((range.clone(), style));
19205 } else {
19206 runs.push((range.start..label.filter_range.end, style));
19207 runs.push((label.filter_range.end..range.end, muted_style));
19208 }
19209 prev_end = cmp::max(prev_end, range.end);
19210
19211 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
19212 runs.push((prev_end..label.text.len(), fade_out));
19213 }
19214
19215 runs
19216 })
19217}
19218
19219pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
19220 let mut prev_index = 0;
19221 let mut prev_codepoint: Option<char> = None;
19222 text.char_indices()
19223 .chain([(text.len(), '\0')])
19224 .filter_map(move |(index, codepoint)| {
19225 let prev_codepoint = prev_codepoint.replace(codepoint)?;
19226 let is_boundary = index == text.len()
19227 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
19228 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
19229 if is_boundary {
19230 let chunk = &text[prev_index..index];
19231 prev_index = index;
19232 Some(chunk)
19233 } else {
19234 None
19235 }
19236 })
19237}
19238
19239pub trait RangeToAnchorExt: Sized {
19240 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
19241
19242 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
19243 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
19244 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
19245 }
19246}
19247
19248impl<T: ToOffset> RangeToAnchorExt for Range<T> {
19249 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
19250 let start_offset = self.start.to_offset(snapshot);
19251 let end_offset = self.end.to_offset(snapshot);
19252 if start_offset == end_offset {
19253 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
19254 } else {
19255 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
19256 }
19257 }
19258}
19259
19260pub trait RowExt {
19261 fn as_f32(&self) -> f32;
19262
19263 fn next_row(&self) -> Self;
19264
19265 fn previous_row(&self) -> Self;
19266
19267 fn minus(&self, other: Self) -> u32;
19268}
19269
19270impl RowExt for DisplayRow {
19271 fn as_f32(&self) -> f32 {
19272 self.0 as f32
19273 }
19274
19275 fn next_row(&self) -> Self {
19276 Self(self.0 + 1)
19277 }
19278
19279 fn previous_row(&self) -> Self {
19280 Self(self.0.saturating_sub(1))
19281 }
19282
19283 fn minus(&self, other: Self) -> u32 {
19284 self.0 - other.0
19285 }
19286}
19287
19288impl RowExt for MultiBufferRow {
19289 fn as_f32(&self) -> f32 {
19290 self.0 as f32
19291 }
19292
19293 fn next_row(&self) -> Self {
19294 Self(self.0 + 1)
19295 }
19296
19297 fn previous_row(&self) -> Self {
19298 Self(self.0.saturating_sub(1))
19299 }
19300
19301 fn minus(&self, other: Self) -> u32 {
19302 self.0 - other.0
19303 }
19304}
19305
19306trait RowRangeExt {
19307 type Row;
19308
19309 fn len(&self) -> usize;
19310
19311 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
19312}
19313
19314impl RowRangeExt for Range<MultiBufferRow> {
19315 type Row = MultiBufferRow;
19316
19317 fn len(&self) -> usize {
19318 (self.end.0 - self.start.0) as usize
19319 }
19320
19321 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
19322 (self.start.0..self.end.0).map(MultiBufferRow)
19323 }
19324}
19325
19326impl RowRangeExt for Range<DisplayRow> {
19327 type Row = DisplayRow;
19328
19329 fn len(&self) -> usize {
19330 (self.end.0 - self.start.0) as usize
19331 }
19332
19333 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
19334 (self.start.0..self.end.0).map(DisplayRow)
19335 }
19336}
19337
19338/// If select range has more than one line, we
19339/// just point the cursor to range.start.
19340fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
19341 if range.start.row == range.end.row {
19342 range
19343 } else {
19344 range.start..range.start
19345 }
19346}
19347pub struct KillRing(ClipboardItem);
19348impl Global for KillRing {}
19349
19350const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
19351
19352struct BreakpointPromptEditor {
19353 pub(crate) prompt: Entity<Editor>,
19354 editor: WeakEntity<Editor>,
19355 breakpoint_anchor: Anchor,
19356 kind: BreakpointKind,
19357 block_ids: HashSet<CustomBlockId>,
19358 gutter_dimensions: Arc<Mutex<GutterDimensions>>,
19359 _subscriptions: Vec<Subscription>,
19360}
19361
19362impl BreakpointPromptEditor {
19363 const MAX_LINES: u8 = 4;
19364
19365 fn new(
19366 editor: WeakEntity<Editor>,
19367 breakpoint_anchor: Anchor,
19368 kind: BreakpointKind,
19369 window: &mut Window,
19370 cx: &mut Context<Self>,
19371 ) -> Self {
19372 let buffer = cx.new(|cx| {
19373 Buffer::local(
19374 kind.log_message()
19375 .map(|msg| msg.to_string())
19376 .unwrap_or_default(),
19377 cx,
19378 )
19379 });
19380 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19381
19382 let prompt = cx.new(|cx| {
19383 let mut prompt = Editor::new(
19384 EditorMode::AutoHeight {
19385 max_lines: Self::MAX_LINES as usize,
19386 },
19387 buffer,
19388 None,
19389 window,
19390 cx,
19391 );
19392 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
19393 prompt.set_show_cursor_when_unfocused(false, cx);
19394 prompt.set_placeholder_text(
19395 "Message to log when breakpoint is hit. Expressions within {} are interpolated.",
19396 cx,
19397 );
19398
19399 prompt
19400 });
19401
19402 Self {
19403 prompt,
19404 editor,
19405 breakpoint_anchor,
19406 kind,
19407 gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
19408 block_ids: Default::default(),
19409 _subscriptions: vec![],
19410 }
19411 }
19412
19413 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
19414 self.block_ids.extend(block_ids)
19415 }
19416
19417 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
19418 if let Some(editor) = self.editor.upgrade() {
19419 let log_message = self
19420 .prompt
19421 .read(cx)
19422 .buffer
19423 .read(cx)
19424 .as_singleton()
19425 .expect("A multi buffer in breakpoint prompt isn't possible")
19426 .read(cx)
19427 .as_rope()
19428 .to_string();
19429
19430 editor.update(cx, |editor, cx| {
19431 editor.edit_breakpoint_at_anchor(
19432 self.breakpoint_anchor,
19433 self.kind.clone(),
19434 BreakpointEditAction::EditLogMessage(log_message.into()),
19435 cx,
19436 );
19437
19438 editor.remove_blocks(self.block_ids.clone(), None, cx);
19439 cx.focus_self(window);
19440 });
19441 }
19442 }
19443
19444 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
19445 self.editor
19446 .update(cx, |editor, cx| {
19447 editor.remove_blocks(self.block_ids.clone(), None, cx);
19448 window.focus(&editor.focus_handle);
19449 })
19450 .log_err();
19451 }
19452
19453 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
19454 let settings = ThemeSettings::get_global(cx);
19455 let text_style = TextStyle {
19456 color: if self.prompt.read(cx).read_only(cx) {
19457 cx.theme().colors().text_disabled
19458 } else {
19459 cx.theme().colors().text
19460 },
19461 font_family: settings.buffer_font.family.clone(),
19462 font_fallbacks: settings.buffer_font.fallbacks.clone(),
19463 font_size: settings.buffer_font_size(cx).into(),
19464 font_weight: settings.buffer_font.weight,
19465 line_height: relative(settings.buffer_line_height.value()),
19466 ..Default::default()
19467 };
19468 EditorElement::new(
19469 &self.prompt,
19470 EditorStyle {
19471 background: cx.theme().colors().editor_background,
19472 local_player: cx.theme().players().local(),
19473 text: text_style,
19474 ..Default::default()
19475 },
19476 )
19477 }
19478}
19479
19480impl Render for BreakpointPromptEditor {
19481 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
19482 let gutter_dimensions = *self.gutter_dimensions.lock();
19483 h_flex()
19484 .key_context("Editor")
19485 .bg(cx.theme().colors().editor_background)
19486 .border_y_1()
19487 .border_color(cx.theme().status().info_border)
19488 .size_full()
19489 .py(window.line_height() / 2.5)
19490 .on_action(cx.listener(Self::confirm))
19491 .on_action(cx.listener(Self::cancel))
19492 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
19493 .child(div().flex_1().child(self.render_prompt_editor(cx)))
19494 }
19495}
19496
19497impl Focusable for BreakpointPromptEditor {
19498 fn focus_handle(&self, cx: &App) -> FocusHandle {
19499 self.prompt.focus_handle(cx)
19500 }
19501}
19502
19503fn all_edits_insertions_or_deletions(
19504 edits: &Vec<(Range<Anchor>, String)>,
19505 snapshot: &MultiBufferSnapshot,
19506) -> bool {
19507 let mut all_insertions = true;
19508 let mut all_deletions = true;
19509
19510 for (range, new_text) in edits.iter() {
19511 let range_is_empty = range.to_offset(&snapshot).is_empty();
19512 let text_is_empty = new_text.is_empty();
19513
19514 if range_is_empty != text_is_empty {
19515 if range_is_empty {
19516 all_deletions = false;
19517 } else {
19518 all_insertions = false;
19519 }
19520 } else {
19521 return false;
19522 }
19523
19524 if !all_insertions && !all_deletions {
19525 return false;
19526 }
19527 }
19528 all_insertions || all_deletions
19529}
19530
19531struct MissingEditPredictionKeybindingTooltip;
19532
19533impl Render for MissingEditPredictionKeybindingTooltip {
19534 fn render(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
19535 ui::tooltip_container(window, cx, |container, _, cx| {
19536 container
19537 .flex_shrink_0()
19538 .max_w_80()
19539 .min_h(rems_from_px(124.))
19540 .justify_between()
19541 .child(
19542 v_flex()
19543 .flex_1()
19544 .text_ui_sm(cx)
19545 .child(Label::new("Conflict with Accept Keybinding"))
19546 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
19547 )
19548 .child(
19549 h_flex()
19550 .pb_1()
19551 .gap_1()
19552 .items_end()
19553 .w_full()
19554 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
19555 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
19556 }))
19557 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
19558 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
19559 })),
19560 )
19561 })
19562 }
19563}
19564
19565#[derive(Debug, Clone, Copy, PartialEq)]
19566pub struct LineHighlight {
19567 pub background: Background,
19568 pub border: Option<gpui::Hsla>,
19569}
19570
19571impl From<Hsla> for LineHighlight {
19572 fn from(hsla: Hsla) -> Self {
19573 Self {
19574 background: hsla.into(),
19575 border: None,
19576 }
19577 }
19578}
19579
19580impl From<Background> for LineHighlight {
19581 fn from(background: Background) -> Self {
19582 Self {
19583 background,
19584 border: None,
19585 }
19586 }
19587}