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, IconButtonShape, IconName,
186 IconSize, Key, 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
224pub type RenderDiffHunkControlsFn = Arc<
225 dyn Fn(
226 u32,
227 &DiffHunkStatus,
228 Range<Anchor>,
229 bool,
230 Pixels,
231 &Entity<Editor>,
232 &mut App,
233 ) -> AnyElement,
234>;
235
236const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
237 alt: true,
238 shift: true,
239 control: false,
240 platform: false,
241 function: false,
242};
243
244#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
245pub enum InlayId {
246 InlineCompletion(usize),
247 Hint(usize),
248}
249
250impl InlayId {
251 fn id(&self) -> usize {
252 match self {
253 Self::InlineCompletion(id) => *id,
254 Self::Hint(id) => *id,
255 }
256 }
257}
258
259pub enum DebugCurrentRowHighlight {}
260enum DocumentHighlightRead {}
261enum DocumentHighlightWrite {}
262enum InputComposition {}
263enum SelectedTextHighlight {}
264
265#[derive(Debug, Copy, Clone, PartialEq, Eq)]
266pub enum Navigated {
267 Yes,
268 No,
269}
270
271impl Navigated {
272 pub fn from_bool(yes: bool) -> Navigated {
273 if yes {
274 Navigated::Yes
275 } else {
276 Navigated::No
277 }
278 }
279}
280
281#[derive(Debug, Clone, PartialEq, Eq)]
282enum DisplayDiffHunk {
283 Folded {
284 display_row: DisplayRow,
285 },
286 Unfolded {
287 is_created_file: bool,
288 diff_base_byte_range: Range<usize>,
289 display_row_range: Range<DisplayRow>,
290 multi_buffer_range: Range<Anchor>,
291 status: DiffHunkStatus,
292 },
293}
294
295pub fn init_settings(cx: &mut App) {
296 EditorSettings::register(cx);
297}
298
299pub fn init(cx: &mut App) {
300 init_settings(cx);
301
302 workspace::register_project_item::<Editor>(cx);
303 workspace::FollowableViewRegistry::register::<Editor>(cx);
304 workspace::register_serializable_item::<Editor>(cx);
305
306 cx.observe_new(
307 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
308 workspace.register_action(Editor::new_file);
309 workspace.register_action(Editor::new_file_vertical);
310 workspace.register_action(Editor::new_file_horizontal);
311 workspace.register_action(Editor::cancel_language_server_work);
312 },
313 )
314 .detach();
315
316 cx.on_action(move |_: &workspace::NewFile, cx| {
317 let app_state = workspace::AppState::global(cx);
318 if let Some(app_state) = app_state.upgrade() {
319 workspace::open_new(
320 Default::default(),
321 app_state,
322 cx,
323 |workspace, window, cx| {
324 Editor::new_file(workspace, &Default::default(), window, cx)
325 },
326 )
327 .detach();
328 }
329 });
330 cx.on_action(move |_: &workspace::NewWindow, cx| {
331 let app_state = workspace::AppState::global(cx);
332 if let Some(app_state) = app_state.upgrade() {
333 workspace::open_new(
334 Default::default(),
335 app_state,
336 cx,
337 |workspace, window, cx| {
338 cx.activate(true);
339 Editor::new_file(workspace, &Default::default(), window, cx)
340 },
341 )
342 .detach();
343 }
344 });
345}
346
347pub struct SearchWithinRange;
348
349trait InvalidationRegion {
350 fn ranges(&self) -> &[Range<Anchor>];
351}
352
353#[derive(Clone, Debug, PartialEq)]
354pub enum SelectPhase {
355 Begin {
356 position: DisplayPoint,
357 add: bool,
358 click_count: usize,
359 },
360 BeginColumnar {
361 position: DisplayPoint,
362 reset: bool,
363 goal_column: u32,
364 },
365 Extend {
366 position: DisplayPoint,
367 click_count: usize,
368 },
369 Update {
370 position: DisplayPoint,
371 goal_column: u32,
372 scroll_delta: gpui::Point<f32>,
373 },
374 End,
375}
376
377#[derive(Clone, Debug)]
378pub enum SelectMode {
379 Character,
380 Word(Range<Anchor>),
381 Line(Range<Anchor>),
382 All,
383}
384
385#[derive(Copy, Clone, PartialEq, Eq, Debug)]
386pub enum EditorMode {
387 SingleLine { auto_width: bool },
388 AutoHeight { max_lines: usize },
389 Full,
390}
391
392#[derive(Copy, Clone, Debug)]
393pub enum SoftWrap {
394 /// Prefer not to wrap at all.
395 ///
396 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
397 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
398 GitDiff,
399 /// Prefer a single line generally, unless an overly long line is encountered.
400 None,
401 /// Soft wrap lines that exceed the editor width.
402 EditorWidth,
403 /// Soft wrap lines at the preferred line length.
404 Column(u32),
405 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
406 Bounded(u32),
407}
408
409#[derive(Clone)]
410pub struct EditorStyle {
411 pub background: Hsla,
412 pub local_player: PlayerColor,
413 pub text: TextStyle,
414 pub scrollbar_width: Pixels,
415 pub syntax: Arc<SyntaxTheme>,
416 pub status: StatusColors,
417 pub inlay_hints_style: HighlightStyle,
418 pub inline_completion_styles: InlineCompletionStyles,
419 pub unnecessary_code_fade: f32,
420}
421
422impl Default for EditorStyle {
423 fn default() -> Self {
424 Self {
425 background: Hsla::default(),
426 local_player: PlayerColor::default(),
427 text: TextStyle::default(),
428 scrollbar_width: Pixels::default(),
429 syntax: Default::default(),
430 // HACK: Status colors don't have a real default.
431 // We should look into removing the status colors from the editor
432 // style and retrieve them directly from the theme.
433 status: StatusColors::dark(),
434 inlay_hints_style: HighlightStyle::default(),
435 inline_completion_styles: InlineCompletionStyles {
436 insertion: HighlightStyle::default(),
437 whitespace: HighlightStyle::default(),
438 },
439 unnecessary_code_fade: Default::default(),
440 }
441 }
442}
443
444pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
445 let show_background = language_settings::language_settings(None, None, cx)
446 .inlay_hints
447 .show_background;
448
449 HighlightStyle {
450 color: Some(cx.theme().status().hint),
451 background_color: show_background.then(|| cx.theme().status().hint_background),
452 ..HighlightStyle::default()
453 }
454}
455
456pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
457 InlineCompletionStyles {
458 insertion: HighlightStyle {
459 color: Some(cx.theme().status().predictive),
460 ..HighlightStyle::default()
461 },
462 whitespace: HighlightStyle {
463 background_color: Some(cx.theme().status().created_background),
464 ..HighlightStyle::default()
465 },
466 }
467}
468
469type CompletionId = usize;
470
471pub(crate) enum EditDisplayMode {
472 TabAccept,
473 DiffPopover,
474 Inline,
475}
476
477enum InlineCompletion {
478 Edit {
479 edits: Vec<(Range<Anchor>, String)>,
480 edit_preview: Option<EditPreview>,
481 display_mode: EditDisplayMode,
482 snapshot: BufferSnapshot,
483 },
484 Move {
485 target: Anchor,
486 snapshot: BufferSnapshot,
487 },
488}
489
490struct InlineCompletionState {
491 inlay_ids: Vec<InlayId>,
492 completion: InlineCompletion,
493 completion_id: Option<SharedString>,
494 invalidation_range: Range<Anchor>,
495}
496
497enum EditPredictionSettings {
498 Disabled,
499 Enabled {
500 show_in_menu: bool,
501 preview_requires_modifier: bool,
502 },
503}
504
505enum InlineCompletionHighlight {}
506
507#[derive(Debug, Clone)]
508struct InlineDiagnostic {
509 message: SharedString,
510 group_id: usize,
511 is_primary: bool,
512 start: Point,
513 severity: DiagnosticSeverity,
514}
515
516pub enum MenuInlineCompletionsPolicy {
517 Never,
518 ByProvider,
519}
520
521pub enum EditPredictionPreview {
522 /// Modifier is not pressed
523 Inactive { released_too_fast: bool },
524 /// Modifier pressed
525 Active {
526 since: Instant,
527 previous_scroll_position: Option<ScrollAnchor>,
528 },
529}
530
531impl EditPredictionPreview {
532 pub fn released_too_fast(&self) -> bool {
533 match self {
534 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
535 EditPredictionPreview::Active { .. } => false,
536 }
537 }
538
539 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
540 if let EditPredictionPreview::Active {
541 previous_scroll_position,
542 ..
543 } = self
544 {
545 *previous_scroll_position = scroll_position;
546 }
547 }
548}
549
550#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
551struct EditorActionId(usize);
552
553impl EditorActionId {
554 pub fn post_inc(&mut self) -> Self {
555 let answer = self.0;
556
557 *self = Self(answer + 1);
558
559 Self(answer)
560 }
561}
562
563// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
564// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
565
566type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
567type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
568
569#[derive(Default)]
570struct ScrollbarMarkerState {
571 scrollbar_size: Size<Pixels>,
572 dirty: bool,
573 markers: Arc<[PaintQuad]>,
574 pending_refresh: Option<Task<Result<()>>>,
575}
576
577impl ScrollbarMarkerState {
578 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
579 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
580 }
581}
582
583#[derive(Clone, Debug)]
584struct RunnableTasks {
585 templates: Vec<(TaskSourceKind, TaskTemplate)>,
586 offset: multi_buffer::Anchor,
587 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
588 column: u32,
589 // Values of all named captures, including those starting with '_'
590 extra_variables: HashMap<String, String>,
591 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
592 context_range: Range<BufferOffset>,
593}
594
595impl RunnableTasks {
596 fn resolve<'a>(
597 &'a self,
598 cx: &'a task::TaskContext,
599 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
600 self.templates.iter().filter_map(|(kind, template)| {
601 template
602 .resolve_task(&kind.to_id_base(), cx)
603 .map(|task| (kind.clone(), task))
604 })
605 }
606}
607
608#[derive(Clone)]
609struct ResolvedTasks {
610 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
611 position: Anchor,
612}
613
614#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
615struct BufferOffset(usize);
616
617// Addons allow storing per-editor state in other crates (e.g. Vim)
618pub trait Addon: 'static {
619 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
620
621 fn render_buffer_header_controls(
622 &self,
623 _: &ExcerptInfo,
624 _: &Window,
625 _: &App,
626 ) -> Option<AnyElement> {
627 None
628 }
629
630 fn to_any(&self) -> &dyn std::any::Any;
631}
632
633/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
634///
635/// See the [module level documentation](self) for more information.
636pub struct Editor {
637 focus_handle: FocusHandle,
638 last_focused_descendant: Option<WeakFocusHandle>,
639 /// The text buffer being edited
640 buffer: Entity<MultiBuffer>,
641 /// Map of how text in the buffer should be displayed.
642 /// Handles soft wraps, folds, fake inlay text insertions, etc.
643 pub display_map: Entity<DisplayMap>,
644 pub selections: SelectionsCollection,
645 pub scroll_manager: ScrollManager,
646 /// When inline assist editors are linked, they all render cursors because
647 /// typing enters text into each of them, even the ones that aren't focused.
648 pub(crate) show_cursor_when_unfocused: bool,
649 columnar_selection_tail: Option<Anchor>,
650 add_selections_state: Option<AddSelectionsState>,
651 select_next_state: Option<SelectNextState>,
652 select_prev_state: Option<SelectNextState>,
653 selection_history: SelectionHistory,
654 autoclose_regions: Vec<AutocloseRegion>,
655 snippet_stack: InvalidationStack<SnippetState>,
656 select_larger_syntax_node_stack: Vec<Box<[Selection<usize>]>>,
657 ime_transaction: Option<TransactionId>,
658 active_diagnostics: Option<ActiveDiagnosticGroup>,
659 show_inline_diagnostics: bool,
660 inline_diagnostics_update: Task<()>,
661 inline_diagnostics_enabled: bool,
662 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
663 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
664 hard_wrap: Option<usize>,
665
666 // TODO: make this a access method
667 pub project: Option<Entity<Project>>,
668 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
669 completion_provider: Option<Box<dyn CompletionProvider>>,
670 collaboration_hub: Option<Box<dyn CollaborationHub>>,
671 blink_manager: Entity<BlinkManager>,
672 show_cursor_names: bool,
673 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
674 pub show_local_selections: bool,
675 mode: EditorMode,
676 show_breadcrumbs: bool,
677 show_gutter: bool,
678 show_scrollbars: bool,
679 show_line_numbers: Option<bool>,
680 use_relative_line_numbers: Option<bool>,
681 show_git_diff_gutter: Option<bool>,
682 show_code_actions: Option<bool>,
683 show_runnables: Option<bool>,
684 show_breakpoints: Option<bool>,
685 show_wrap_guides: Option<bool>,
686 show_indent_guides: Option<bool>,
687 placeholder_text: Option<Arc<str>>,
688 highlight_order: usize,
689 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
690 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
691 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
692 scrollbar_marker_state: ScrollbarMarkerState,
693 active_indent_guides_state: ActiveIndentGuidesState,
694 nav_history: Option<ItemNavHistory>,
695 context_menu: RefCell<Option<CodeContextMenu>>,
696 mouse_context_menu: Option<MouseContextMenu>,
697 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
698 signature_help_state: SignatureHelpState,
699 auto_signature_help: Option<bool>,
700 find_all_references_task_sources: Vec<Anchor>,
701 next_completion_id: CompletionId,
702 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
703 code_actions_task: Option<Task<Result<()>>>,
704 selection_highlight_task: Option<Task<()>>,
705 document_highlights_task: Option<Task<()>>,
706 linked_editing_range_task: Option<Task<Option<()>>>,
707 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
708 pending_rename: Option<RenameState>,
709 searchable: bool,
710 cursor_shape: CursorShape,
711 current_line_highlight: Option<CurrentLineHighlight>,
712 collapse_matches: bool,
713 autoindent_mode: Option<AutoindentMode>,
714 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
715 input_enabled: bool,
716 use_modal_editing: bool,
717 read_only: bool,
718 leader_peer_id: Option<PeerId>,
719 remote_id: Option<ViewId>,
720 hover_state: HoverState,
721 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
722 gutter_hovered: bool,
723 hovered_link_state: Option<HoveredLinkState>,
724 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
725 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
726 active_inline_completion: Option<InlineCompletionState>,
727 /// Used to prevent flickering as the user types while the menu is open
728 stale_inline_completion_in_menu: Option<InlineCompletionState>,
729 edit_prediction_settings: EditPredictionSettings,
730 inline_completions_hidden_for_vim_mode: bool,
731 show_inline_completions_override: Option<bool>,
732 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
733 edit_prediction_preview: EditPredictionPreview,
734 edit_prediction_indent_conflict: bool,
735 edit_prediction_requires_modifier_in_indent_conflict: bool,
736 inlay_hint_cache: InlayHintCache,
737 next_inlay_id: usize,
738 _subscriptions: Vec<Subscription>,
739 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
740 gutter_dimensions: GutterDimensions,
741 style: Option<EditorStyle>,
742 text_style_refinement: Option<TextStyleRefinement>,
743 next_editor_action_id: EditorActionId,
744 editor_actions:
745 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
746 use_autoclose: bool,
747 use_auto_surround: bool,
748 auto_replace_emoji_shortcode: bool,
749 jsx_tag_auto_close_enabled_in_any_buffer: bool,
750 show_git_blame_gutter: bool,
751 show_git_blame_inline: bool,
752 show_git_blame_inline_delay_task: Option<Task<()>>,
753 git_blame_inline_tooltip: Option<WeakEntity<crate::commit_tooltip::CommitTooltip>>,
754 git_blame_inline_enabled: bool,
755 render_diff_hunk_controls: RenderDiffHunkControlsFn,
756 serialize_dirty_buffers: bool,
757 show_selection_menu: Option<bool>,
758 blame: Option<Entity<GitBlame>>,
759 blame_subscription: Option<Subscription>,
760 custom_context_menu: Option<
761 Box<
762 dyn 'static
763 + Fn(
764 &mut Self,
765 DisplayPoint,
766 &mut Window,
767 &mut Context<Self>,
768 ) -> Option<Entity<ui::ContextMenu>>,
769 >,
770 >,
771 last_bounds: Option<Bounds<Pixels>>,
772 last_position_map: Option<Rc<PositionMap>>,
773 expect_bounds_change: Option<Bounds<Pixels>>,
774 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
775 tasks_update_task: Option<Task<()>>,
776 pub breakpoint_store: Option<Entity<BreakpointStore>>,
777 /// Allow's a user to create a breakpoint by selecting this indicator
778 /// It should be None while a user is not hovering over the gutter
779 /// Otherwise it represents the point that the breakpoint will be shown
780 pub gutter_breakpoint_indicator: Option<DisplayPoint>,
781 in_project_search: bool,
782 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
783 breadcrumb_header: Option<String>,
784 focused_block: Option<FocusedBlock>,
785 next_scroll_position: NextScrollCursorCenterTopBottom,
786 addons: HashMap<TypeId, Box<dyn Addon>>,
787 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
788 load_diff_task: Option<Shared<Task<()>>>,
789 selection_mark_mode: bool,
790 toggle_fold_multiple_buffers: Task<()>,
791 _scroll_cursor_center_top_bottom_task: Task<()>,
792 serialize_selections: Task<()>,
793}
794
795#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
796enum NextScrollCursorCenterTopBottom {
797 #[default]
798 Center,
799 Top,
800 Bottom,
801}
802
803impl NextScrollCursorCenterTopBottom {
804 fn next(&self) -> Self {
805 match self {
806 Self::Center => Self::Top,
807 Self::Top => Self::Bottom,
808 Self::Bottom => Self::Center,
809 }
810 }
811}
812
813#[derive(Clone)]
814pub struct EditorSnapshot {
815 pub mode: EditorMode,
816 show_gutter: bool,
817 show_line_numbers: Option<bool>,
818 show_git_diff_gutter: Option<bool>,
819 show_code_actions: Option<bool>,
820 show_runnables: Option<bool>,
821 show_breakpoints: Option<bool>,
822 git_blame_gutter_max_author_length: Option<usize>,
823 pub display_snapshot: DisplaySnapshot,
824 pub placeholder_text: Option<Arc<str>>,
825 is_focused: bool,
826 scroll_anchor: ScrollAnchor,
827 ongoing_scroll: OngoingScroll,
828 current_line_highlight: CurrentLineHighlight,
829 gutter_hovered: bool,
830}
831
832const GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED: usize = 20;
833
834#[derive(Default, Debug, Clone, Copy)]
835pub struct GutterDimensions {
836 pub left_padding: Pixels,
837 pub right_padding: Pixels,
838 pub width: Pixels,
839 pub margin: Pixels,
840 pub git_blame_entries_width: Option<Pixels>,
841}
842
843impl GutterDimensions {
844 /// The full width of the space taken up by the gutter.
845 pub fn full_width(&self) -> Pixels {
846 self.margin + self.width
847 }
848
849 /// The width of the space reserved for the fold indicators,
850 /// use alongside 'justify_end' and `gutter_width` to
851 /// right align content with the line numbers
852 pub fn fold_area_width(&self) -> Pixels {
853 self.margin + self.right_padding
854 }
855}
856
857#[derive(Debug)]
858pub struct RemoteSelection {
859 pub replica_id: ReplicaId,
860 pub selection: Selection<Anchor>,
861 pub cursor_shape: CursorShape,
862 pub peer_id: PeerId,
863 pub line_mode: bool,
864 pub participant_index: Option<ParticipantIndex>,
865 pub user_name: Option<SharedString>,
866}
867
868#[derive(Clone, Debug)]
869struct SelectionHistoryEntry {
870 selections: Arc<[Selection<Anchor>]>,
871 select_next_state: Option<SelectNextState>,
872 select_prev_state: Option<SelectNextState>,
873 add_selections_state: Option<AddSelectionsState>,
874}
875
876enum SelectionHistoryMode {
877 Normal,
878 Undoing,
879 Redoing,
880}
881
882#[derive(Clone, PartialEq, Eq, Hash)]
883struct HoveredCursor {
884 replica_id: u16,
885 selection_id: usize,
886}
887
888impl Default for SelectionHistoryMode {
889 fn default() -> Self {
890 Self::Normal
891 }
892}
893
894#[derive(Default)]
895struct SelectionHistory {
896 #[allow(clippy::type_complexity)]
897 selections_by_transaction:
898 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
899 mode: SelectionHistoryMode,
900 undo_stack: VecDeque<SelectionHistoryEntry>,
901 redo_stack: VecDeque<SelectionHistoryEntry>,
902}
903
904impl SelectionHistory {
905 fn insert_transaction(
906 &mut self,
907 transaction_id: TransactionId,
908 selections: Arc<[Selection<Anchor>]>,
909 ) {
910 self.selections_by_transaction
911 .insert(transaction_id, (selections, None));
912 }
913
914 #[allow(clippy::type_complexity)]
915 fn transaction(
916 &self,
917 transaction_id: TransactionId,
918 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
919 self.selections_by_transaction.get(&transaction_id)
920 }
921
922 #[allow(clippy::type_complexity)]
923 fn transaction_mut(
924 &mut self,
925 transaction_id: TransactionId,
926 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
927 self.selections_by_transaction.get_mut(&transaction_id)
928 }
929
930 fn push(&mut self, entry: SelectionHistoryEntry) {
931 if !entry.selections.is_empty() {
932 match self.mode {
933 SelectionHistoryMode::Normal => {
934 self.push_undo(entry);
935 self.redo_stack.clear();
936 }
937 SelectionHistoryMode::Undoing => self.push_redo(entry),
938 SelectionHistoryMode::Redoing => self.push_undo(entry),
939 }
940 }
941 }
942
943 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
944 if self
945 .undo_stack
946 .back()
947 .map_or(true, |e| e.selections != entry.selections)
948 {
949 self.undo_stack.push_back(entry);
950 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
951 self.undo_stack.pop_front();
952 }
953 }
954 }
955
956 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
957 if self
958 .redo_stack
959 .back()
960 .map_or(true, |e| e.selections != entry.selections)
961 {
962 self.redo_stack.push_back(entry);
963 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
964 self.redo_stack.pop_front();
965 }
966 }
967 }
968}
969
970struct RowHighlight {
971 index: usize,
972 range: Range<Anchor>,
973 color: Hsla,
974 should_autoscroll: bool,
975}
976
977#[derive(Clone, Debug)]
978struct AddSelectionsState {
979 above: bool,
980 stack: Vec<usize>,
981}
982
983#[derive(Clone)]
984struct SelectNextState {
985 query: AhoCorasick,
986 wordwise: bool,
987 done: bool,
988}
989
990impl std::fmt::Debug for SelectNextState {
991 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
992 f.debug_struct(std::any::type_name::<Self>())
993 .field("wordwise", &self.wordwise)
994 .field("done", &self.done)
995 .finish()
996 }
997}
998
999#[derive(Debug)]
1000struct AutocloseRegion {
1001 selection_id: usize,
1002 range: Range<Anchor>,
1003 pair: BracketPair,
1004}
1005
1006#[derive(Debug)]
1007struct SnippetState {
1008 ranges: Vec<Vec<Range<Anchor>>>,
1009 active_index: usize,
1010 choices: Vec<Option<Vec<String>>>,
1011}
1012
1013#[doc(hidden)]
1014pub struct RenameState {
1015 pub range: Range<Anchor>,
1016 pub old_name: Arc<str>,
1017 pub editor: Entity<Editor>,
1018 block_id: CustomBlockId,
1019}
1020
1021struct InvalidationStack<T>(Vec<T>);
1022
1023struct RegisteredInlineCompletionProvider {
1024 provider: Arc<dyn InlineCompletionProviderHandle>,
1025 _subscription: Subscription,
1026}
1027
1028#[derive(Debug, PartialEq, Eq)]
1029struct ActiveDiagnosticGroup {
1030 primary_range: Range<Anchor>,
1031 primary_message: String,
1032 group_id: usize,
1033 blocks: HashMap<CustomBlockId, Diagnostic>,
1034 is_valid: bool,
1035}
1036
1037#[derive(Serialize, Deserialize, Clone, Debug)]
1038pub struct ClipboardSelection {
1039 /// The number of bytes in this selection.
1040 pub len: usize,
1041 /// Whether this was a full-line selection.
1042 pub is_entire_line: bool,
1043 /// The indentation of the first line when this content was originally copied.
1044 pub first_line_indent: u32,
1045}
1046
1047#[derive(Debug)]
1048pub(crate) struct NavigationData {
1049 cursor_anchor: Anchor,
1050 cursor_position: Point,
1051 scroll_anchor: ScrollAnchor,
1052 scroll_top_row: u32,
1053}
1054
1055#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1056pub enum GotoDefinitionKind {
1057 Symbol,
1058 Declaration,
1059 Type,
1060 Implementation,
1061}
1062
1063#[derive(Debug, Clone)]
1064enum InlayHintRefreshReason {
1065 ModifiersChanged(bool),
1066 Toggle(bool),
1067 SettingsChange(InlayHintSettings),
1068 NewLinesShown,
1069 BufferEdited(HashSet<Arc<Language>>),
1070 RefreshRequested,
1071 ExcerptsRemoved(Vec<ExcerptId>),
1072}
1073
1074impl InlayHintRefreshReason {
1075 fn description(&self) -> &'static str {
1076 match self {
1077 Self::ModifiersChanged(_) => "modifiers changed",
1078 Self::Toggle(_) => "toggle",
1079 Self::SettingsChange(_) => "settings change",
1080 Self::NewLinesShown => "new lines shown",
1081 Self::BufferEdited(_) => "buffer edited",
1082 Self::RefreshRequested => "refresh requested",
1083 Self::ExcerptsRemoved(_) => "excerpts removed",
1084 }
1085 }
1086}
1087
1088pub enum FormatTarget {
1089 Buffers,
1090 Ranges(Vec<Range<MultiBufferPoint>>),
1091}
1092
1093pub(crate) struct FocusedBlock {
1094 id: BlockId,
1095 focus_handle: WeakFocusHandle,
1096}
1097
1098#[derive(Clone)]
1099enum JumpData {
1100 MultiBufferRow {
1101 row: MultiBufferRow,
1102 line_offset_from_top: u32,
1103 },
1104 MultiBufferPoint {
1105 excerpt_id: ExcerptId,
1106 position: Point,
1107 anchor: text::Anchor,
1108 line_offset_from_top: u32,
1109 },
1110}
1111
1112pub enum MultibufferSelectionMode {
1113 First,
1114 All,
1115}
1116
1117#[derive(Clone, Copy, Debug, Default)]
1118pub struct RewrapOptions {
1119 pub override_language_settings: bool,
1120 pub preserve_existing_whitespace: bool,
1121}
1122
1123impl Editor {
1124 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1125 let buffer = cx.new(|cx| Buffer::local("", cx));
1126 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1127 Self::new(
1128 EditorMode::SingleLine { auto_width: false },
1129 buffer,
1130 None,
1131 window,
1132 cx,
1133 )
1134 }
1135
1136 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1137 let buffer = cx.new(|cx| Buffer::local("", cx));
1138 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1139 Self::new(EditorMode::Full, buffer, None, window, cx)
1140 }
1141
1142 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1143 let buffer = cx.new(|cx| Buffer::local("", cx));
1144 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1145 Self::new(
1146 EditorMode::SingleLine { auto_width: true },
1147 buffer,
1148 None,
1149 window,
1150 cx,
1151 )
1152 }
1153
1154 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1155 let buffer = cx.new(|cx| Buffer::local("", cx));
1156 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1157 Self::new(
1158 EditorMode::AutoHeight { max_lines },
1159 buffer,
1160 None,
1161 window,
1162 cx,
1163 )
1164 }
1165
1166 pub fn for_buffer(
1167 buffer: Entity<Buffer>,
1168 project: Option<Entity<Project>>,
1169 window: &mut Window,
1170 cx: &mut Context<Self>,
1171 ) -> Self {
1172 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1173 Self::new(EditorMode::Full, buffer, project, window, cx)
1174 }
1175
1176 pub fn for_multibuffer(
1177 buffer: Entity<MultiBuffer>,
1178 project: Option<Entity<Project>>,
1179 window: &mut Window,
1180 cx: &mut Context<Self>,
1181 ) -> Self {
1182 Self::new(EditorMode::Full, buffer, project, window, cx)
1183 }
1184
1185 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1186 let mut clone = Self::new(
1187 self.mode,
1188 self.buffer.clone(),
1189 self.project.clone(),
1190 window,
1191 cx,
1192 );
1193 self.display_map.update(cx, |display_map, cx| {
1194 let snapshot = display_map.snapshot(cx);
1195 clone.display_map.update(cx, |display_map, cx| {
1196 display_map.set_state(&snapshot, cx);
1197 });
1198 });
1199 clone.selections.clone_state(&self.selections);
1200 clone.scroll_manager.clone_state(&self.scroll_manager);
1201 clone.searchable = self.searchable;
1202 clone
1203 }
1204
1205 pub fn new(
1206 mode: EditorMode,
1207 buffer: Entity<MultiBuffer>,
1208 project: Option<Entity<Project>>,
1209 window: &mut Window,
1210 cx: &mut Context<Self>,
1211 ) -> Self {
1212 let style = window.text_style();
1213 let font_size = style.font_size.to_pixels(window.rem_size());
1214 let editor = cx.entity().downgrade();
1215 let fold_placeholder = FoldPlaceholder {
1216 constrain_width: true,
1217 render: Arc::new(move |fold_id, fold_range, cx| {
1218 let editor = editor.clone();
1219 div()
1220 .id(fold_id)
1221 .bg(cx.theme().colors().ghost_element_background)
1222 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1223 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1224 .rounded_xs()
1225 .size_full()
1226 .cursor_pointer()
1227 .child("⋯")
1228 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1229 .on_click(move |_, _window, cx| {
1230 editor
1231 .update(cx, |editor, cx| {
1232 editor.unfold_ranges(
1233 &[fold_range.start..fold_range.end],
1234 true,
1235 false,
1236 cx,
1237 );
1238 cx.stop_propagation();
1239 })
1240 .ok();
1241 })
1242 .into_any()
1243 }),
1244 merge_adjacent: true,
1245 ..Default::default()
1246 };
1247 let display_map = cx.new(|cx| {
1248 DisplayMap::new(
1249 buffer.clone(),
1250 style.font(),
1251 font_size,
1252 None,
1253 FILE_HEADER_HEIGHT,
1254 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1255 fold_placeholder,
1256 cx,
1257 )
1258 });
1259
1260 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1261
1262 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1263
1264 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1265 .then(|| language_settings::SoftWrap::None);
1266
1267 let mut project_subscriptions = Vec::new();
1268 if mode == EditorMode::Full {
1269 if let Some(project) = project.as_ref() {
1270 project_subscriptions.push(cx.subscribe_in(
1271 project,
1272 window,
1273 |editor, _, event, window, cx| match event {
1274 project::Event::RefreshCodeLens => {
1275 // we always query lens with actions, without storing them, always refreshing them
1276 }
1277 project::Event::RefreshInlayHints => {
1278 editor
1279 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1280 }
1281 project::Event::SnippetEdit(id, snippet_edits) => {
1282 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1283 let focus_handle = editor.focus_handle(cx);
1284 if focus_handle.is_focused(window) {
1285 let snapshot = buffer.read(cx).snapshot();
1286 for (range, snippet) in snippet_edits {
1287 let editor_range =
1288 language::range_from_lsp(*range).to_offset(&snapshot);
1289 editor
1290 .insert_snippet(
1291 &[editor_range],
1292 snippet.clone(),
1293 window,
1294 cx,
1295 )
1296 .ok();
1297 }
1298 }
1299 }
1300 }
1301 _ => {}
1302 },
1303 ));
1304 if let Some(task_inventory) = project
1305 .read(cx)
1306 .task_store()
1307 .read(cx)
1308 .task_inventory()
1309 .cloned()
1310 {
1311 project_subscriptions.push(cx.observe_in(
1312 &task_inventory,
1313 window,
1314 |editor, _, window, cx| {
1315 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1316 },
1317 ));
1318 };
1319
1320 project_subscriptions.push(cx.subscribe_in(
1321 &project.read(cx).breakpoint_store(),
1322 window,
1323 |editor, _, event, window, cx| match event {
1324 BreakpointStoreEvent::ActiveDebugLineChanged => {
1325 editor.go_to_active_debug_line(window, cx);
1326 }
1327 _ => {}
1328 },
1329 ));
1330 }
1331 }
1332
1333 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1334
1335 let inlay_hint_settings =
1336 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1337 let focus_handle = cx.focus_handle();
1338 cx.on_focus(&focus_handle, window, Self::handle_focus)
1339 .detach();
1340 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1341 .detach();
1342 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1343 .detach();
1344 cx.on_blur(&focus_handle, window, Self::handle_blur)
1345 .detach();
1346
1347 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1348 Some(false)
1349 } else {
1350 None
1351 };
1352
1353 let breakpoint_store = match (mode, project.as_ref()) {
1354 (EditorMode::Full, Some(project)) => Some(project.read(cx).breakpoint_store()),
1355 _ => None,
1356 };
1357
1358 let mut code_action_providers = Vec::new();
1359 let mut load_uncommitted_diff = None;
1360 if let Some(project) = project.clone() {
1361 load_uncommitted_diff = Some(
1362 get_uncommitted_diff_for_buffer(
1363 &project,
1364 buffer.read(cx).all_buffers(),
1365 buffer.clone(),
1366 cx,
1367 )
1368 .shared(),
1369 );
1370 code_action_providers.push(Rc::new(project) as Rc<_>);
1371 }
1372
1373 let mut this = Self {
1374 focus_handle,
1375 show_cursor_when_unfocused: false,
1376 last_focused_descendant: None,
1377 buffer: buffer.clone(),
1378 display_map: display_map.clone(),
1379 selections,
1380 scroll_manager: ScrollManager::new(cx),
1381 columnar_selection_tail: None,
1382 add_selections_state: None,
1383 select_next_state: None,
1384 select_prev_state: None,
1385 selection_history: Default::default(),
1386 autoclose_regions: Default::default(),
1387 snippet_stack: Default::default(),
1388 select_larger_syntax_node_stack: Vec::new(),
1389 ime_transaction: Default::default(),
1390 active_diagnostics: None,
1391 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1392 inline_diagnostics_update: Task::ready(()),
1393 inline_diagnostics: Vec::new(),
1394 soft_wrap_mode_override,
1395 hard_wrap: None,
1396 completion_provider: project.clone().map(|project| Box::new(project) as _),
1397 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1398 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1399 project,
1400 blink_manager: blink_manager.clone(),
1401 show_local_selections: true,
1402 show_scrollbars: true,
1403 mode,
1404 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1405 show_gutter: mode == EditorMode::Full,
1406 show_line_numbers: None,
1407 use_relative_line_numbers: None,
1408 show_git_diff_gutter: None,
1409 show_code_actions: None,
1410 show_runnables: None,
1411 show_breakpoints: None,
1412 show_wrap_guides: None,
1413 show_indent_guides,
1414 placeholder_text: None,
1415 highlight_order: 0,
1416 highlighted_rows: HashMap::default(),
1417 background_highlights: Default::default(),
1418 gutter_highlights: TreeMap::default(),
1419 scrollbar_marker_state: ScrollbarMarkerState::default(),
1420 active_indent_guides_state: ActiveIndentGuidesState::default(),
1421 nav_history: None,
1422 context_menu: RefCell::new(None),
1423 mouse_context_menu: None,
1424 completion_tasks: Default::default(),
1425 signature_help_state: SignatureHelpState::default(),
1426 auto_signature_help: None,
1427 find_all_references_task_sources: Vec::new(),
1428 next_completion_id: 0,
1429 next_inlay_id: 0,
1430 code_action_providers,
1431 available_code_actions: Default::default(),
1432 code_actions_task: Default::default(),
1433 selection_highlight_task: Default::default(),
1434 document_highlights_task: Default::default(),
1435 linked_editing_range_task: Default::default(),
1436 pending_rename: Default::default(),
1437 searchable: true,
1438 cursor_shape: EditorSettings::get_global(cx)
1439 .cursor_shape
1440 .unwrap_or_default(),
1441 current_line_highlight: None,
1442 autoindent_mode: Some(AutoindentMode::EachLine),
1443 collapse_matches: false,
1444 workspace: None,
1445 input_enabled: true,
1446 use_modal_editing: mode == EditorMode::Full,
1447 read_only: false,
1448 use_autoclose: true,
1449 use_auto_surround: true,
1450 auto_replace_emoji_shortcode: false,
1451 jsx_tag_auto_close_enabled_in_any_buffer: false,
1452 leader_peer_id: None,
1453 remote_id: None,
1454 hover_state: Default::default(),
1455 pending_mouse_down: None,
1456 hovered_link_state: Default::default(),
1457 edit_prediction_provider: None,
1458 active_inline_completion: None,
1459 stale_inline_completion_in_menu: None,
1460 edit_prediction_preview: EditPredictionPreview::Inactive {
1461 released_too_fast: false,
1462 },
1463 inline_diagnostics_enabled: mode == EditorMode::Full,
1464 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1465
1466 gutter_hovered: false,
1467 pixel_position_of_newest_cursor: None,
1468 last_bounds: None,
1469 last_position_map: None,
1470 expect_bounds_change: None,
1471 gutter_dimensions: GutterDimensions::default(),
1472 style: None,
1473 show_cursor_names: false,
1474 hovered_cursors: Default::default(),
1475 next_editor_action_id: EditorActionId::default(),
1476 editor_actions: Rc::default(),
1477 inline_completions_hidden_for_vim_mode: false,
1478 show_inline_completions_override: None,
1479 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1480 edit_prediction_settings: EditPredictionSettings::Disabled,
1481 edit_prediction_indent_conflict: false,
1482 edit_prediction_requires_modifier_in_indent_conflict: true,
1483 custom_context_menu: None,
1484 show_git_blame_gutter: false,
1485 show_git_blame_inline: false,
1486 show_selection_menu: None,
1487 show_git_blame_inline_delay_task: None,
1488 git_blame_inline_tooltip: None,
1489 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1490 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1491 serialize_dirty_buffers: ProjectSettings::get_global(cx)
1492 .session
1493 .restore_unsaved_buffers,
1494 blame: None,
1495 blame_subscription: None,
1496 tasks: Default::default(),
1497
1498 breakpoint_store,
1499 gutter_breakpoint_indicator: None,
1500 _subscriptions: vec![
1501 cx.observe(&buffer, Self::on_buffer_changed),
1502 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1503 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1504 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1505 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1506 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1507 cx.observe_window_activation(window, |editor, window, cx| {
1508 let active = window.is_window_active();
1509 editor.blink_manager.update(cx, |blink_manager, cx| {
1510 if active {
1511 blink_manager.enable(cx);
1512 } else {
1513 blink_manager.disable(cx);
1514 }
1515 });
1516 }),
1517 ],
1518 tasks_update_task: None,
1519 linked_edit_ranges: Default::default(),
1520 in_project_search: false,
1521 previous_search_ranges: None,
1522 breadcrumb_header: None,
1523 focused_block: None,
1524 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1525 addons: HashMap::default(),
1526 registered_buffers: HashMap::default(),
1527 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1528 selection_mark_mode: false,
1529 toggle_fold_multiple_buffers: Task::ready(()),
1530 serialize_selections: Task::ready(()),
1531 text_style_refinement: None,
1532 load_diff_task: load_uncommitted_diff,
1533 };
1534 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1535 this._subscriptions
1536 .push(cx.observe(breakpoints, |_, _, cx| {
1537 cx.notify();
1538 }));
1539 }
1540 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1541 this._subscriptions.extend(project_subscriptions);
1542
1543 this.end_selection(window, cx);
1544 this.scroll_manager.show_scrollbar(window, cx);
1545 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1546
1547 if mode == EditorMode::Full {
1548 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1549 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1550
1551 if this.git_blame_inline_enabled {
1552 this.git_blame_inline_enabled = true;
1553 this.start_git_blame_inline(false, window, cx);
1554 }
1555
1556 this.go_to_active_debug_line(window, cx);
1557
1558 if let Some(buffer) = buffer.read(cx).as_singleton() {
1559 if let Some(project) = this.project.as_ref() {
1560 let handle = project.update(cx, |project, cx| {
1561 project.register_buffer_with_language_servers(&buffer, cx)
1562 });
1563 this.registered_buffers
1564 .insert(buffer.read(cx).remote_id(), handle);
1565 }
1566 }
1567 }
1568
1569 this.report_editor_event("Editor Opened", None, cx);
1570 this
1571 }
1572
1573 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
1574 self.mouse_context_menu
1575 .as_ref()
1576 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
1577 }
1578
1579 fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
1580 self.key_context_internal(self.has_active_inline_completion(), window, cx)
1581 }
1582
1583 fn key_context_internal(
1584 &self,
1585 has_active_edit_prediction: bool,
1586 window: &Window,
1587 cx: &App,
1588 ) -> KeyContext {
1589 let mut key_context = KeyContext::new_with_defaults();
1590 key_context.add("Editor");
1591 let mode = match self.mode {
1592 EditorMode::SingleLine { .. } => "single_line",
1593 EditorMode::AutoHeight { .. } => "auto_height",
1594 EditorMode::Full => "full",
1595 };
1596
1597 if EditorSettings::jupyter_enabled(cx) {
1598 key_context.add("jupyter");
1599 }
1600
1601 key_context.set("mode", mode);
1602 if self.pending_rename.is_some() {
1603 key_context.add("renaming");
1604 }
1605
1606 match self.context_menu.borrow().as_ref() {
1607 Some(CodeContextMenu::Completions(_)) => {
1608 key_context.add("menu");
1609 key_context.add("showing_completions");
1610 }
1611 Some(CodeContextMenu::CodeActions(_)) => {
1612 key_context.add("menu");
1613 key_context.add("showing_code_actions")
1614 }
1615 None => {}
1616 }
1617
1618 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
1619 if !self.focus_handle(cx).contains_focused(window, cx)
1620 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
1621 {
1622 for addon in self.addons.values() {
1623 addon.extend_key_context(&mut key_context, cx)
1624 }
1625 }
1626
1627 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
1628 if let Some(extension) = singleton_buffer
1629 .read(cx)
1630 .file()
1631 .and_then(|file| file.path().extension()?.to_str())
1632 {
1633 key_context.set("extension", extension.to_string());
1634 }
1635 } else {
1636 key_context.add("multibuffer");
1637 }
1638
1639 if has_active_edit_prediction {
1640 if self.edit_prediction_in_conflict() {
1641 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
1642 } else {
1643 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
1644 key_context.add("copilot_suggestion");
1645 }
1646 }
1647
1648 if self.selection_mark_mode {
1649 key_context.add("selection_mode");
1650 }
1651
1652 key_context
1653 }
1654
1655 pub fn edit_prediction_in_conflict(&self) -> bool {
1656 if !self.show_edit_predictions_in_menu() {
1657 return false;
1658 }
1659
1660 let showing_completions = self
1661 .context_menu
1662 .borrow()
1663 .as_ref()
1664 .map_or(false, |context| {
1665 matches!(context, CodeContextMenu::Completions(_))
1666 });
1667
1668 showing_completions
1669 || self.edit_prediction_requires_modifier()
1670 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
1671 // bindings to insert tab characters.
1672 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
1673 }
1674
1675 pub fn accept_edit_prediction_keybind(
1676 &self,
1677 window: &Window,
1678 cx: &App,
1679 ) -> AcceptEditPredictionBinding {
1680 let key_context = self.key_context_internal(true, window, cx);
1681 let in_conflict = self.edit_prediction_in_conflict();
1682
1683 AcceptEditPredictionBinding(
1684 window
1685 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
1686 .into_iter()
1687 .filter(|binding| {
1688 !in_conflict
1689 || binding
1690 .keystrokes()
1691 .first()
1692 .map_or(false, |keystroke| keystroke.modifiers.modified())
1693 })
1694 .rev()
1695 .min_by_key(|binding| {
1696 binding
1697 .keystrokes()
1698 .first()
1699 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
1700 }),
1701 )
1702 }
1703
1704 pub fn new_file(
1705 workspace: &mut Workspace,
1706 _: &workspace::NewFile,
1707 window: &mut Window,
1708 cx: &mut Context<Workspace>,
1709 ) {
1710 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
1711 "Failed to create buffer",
1712 window,
1713 cx,
1714 |e, _, _| match e.error_code() {
1715 ErrorCode::RemoteUpgradeRequired => Some(format!(
1716 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1717 e.error_tag("required").unwrap_or("the latest version")
1718 )),
1719 _ => None,
1720 },
1721 );
1722 }
1723
1724 pub fn new_in_workspace(
1725 workspace: &mut Workspace,
1726 window: &mut Window,
1727 cx: &mut Context<Workspace>,
1728 ) -> Task<Result<Entity<Editor>>> {
1729 let project = workspace.project().clone();
1730 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1731
1732 cx.spawn_in(window, async move |workspace, cx| {
1733 let buffer = create.await?;
1734 workspace.update_in(cx, |workspace, window, cx| {
1735 let editor =
1736 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
1737 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
1738 editor
1739 })
1740 })
1741 }
1742
1743 fn new_file_vertical(
1744 workspace: &mut Workspace,
1745 _: &workspace::NewFileSplitVertical,
1746 window: &mut Window,
1747 cx: &mut Context<Workspace>,
1748 ) {
1749 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
1750 }
1751
1752 fn new_file_horizontal(
1753 workspace: &mut Workspace,
1754 _: &workspace::NewFileSplitHorizontal,
1755 window: &mut Window,
1756 cx: &mut Context<Workspace>,
1757 ) {
1758 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
1759 }
1760
1761 fn new_file_in_direction(
1762 workspace: &mut Workspace,
1763 direction: SplitDirection,
1764 window: &mut Window,
1765 cx: &mut Context<Workspace>,
1766 ) {
1767 let project = workspace.project().clone();
1768 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1769
1770 cx.spawn_in(window, async move |workspace, cx| {
1771 let buffer = create.await?;
1772 workspace.update_in(cx, move |workspace, window, cx| {
1773 workspace.split_item(
1774 direction,
1775 Box::new(
1776 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
1777 ),
1778 window,
1779 cx,
1780 )
1781 })?;
1782 anyhow::Ok(())
1783 })
1784 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
1785 match e.error_code() {
1786 ErrorCode::RemoteUpgradeRequired => Some(format!(
1787 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1788 e.error_tag("required").unwrap_or("the latest version")
1789 )),
1790 _ => None,
1791 }
1792 });
1793 }
1794
1795 pub fn leader_peer_id(&self) -> Option<PeerId> {
1796 self.leader_peer_id
1797 }
1798
1799 pub fn buffer(&self) -> &Entity<MultiBuffer> {
1800 &self.buffer
1801 }
1802
1803 pub fn workspace(&self) -> Option<Entity<Workspace>> {
1804 self.workspace.as_ref()?.0.upgrade()
1805 }
1806
1807 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
1808 self.buffer().read(cx).title(cx)
1809 }
1810
1811 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
1812 let git_blame_gutter_max_author_length = self
1813 .render_git_blame_gutter(cx)
1814 .then(|| {
1815 if let Some(blame) = self.blame.as_ref() {
1816 let max_author_length =
1817 blame.update(cx, |blame, cx| blame.max_author_length(cx));
1818 Some(max_author_length)
1819 } else {
1820 None
1821 }
1822 })
1823 .flatten();
1824
1825 EditorSnapshot {
1826 mode: self.mode,
1827 show_gutter: self.show_gutter,
1828 show_line_numbers: self.show_line_numbers,
1829 show_git_diff_gutter: self.show_git_diff_gutter,
1830 show_code_actions: self.show_code_actions,
1831 show_runnables: self.show_runnables,
1832 show_breakpoints: self.show_breakpoints,
1833 git_blame_gutter_max_author_length,
1834 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
1835 scroll_anchor: self.scroll_manager.anchor(),
1836 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
1837 placeholder_text: self.placeholder_text.clone(),
1838 is_focused: self.focus_handle.is_focused(window),
1839 current_line_highlight: self
1840 .current_line_highlight
1841 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
1842 gutter_hovered: self.gutter_hovered,
1843 }
1844 }
1845
1846 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
1847 self.buffer.read(cx).language_at(point, cx)
1848 }
1849
1850 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
1851 self.buffer.read(cx).read(cx).file_at(point).cloned()
1852 }
1853
1854 pub fn active_excerpt(
1855 &self,
1856 cx: &App,
1857 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
1858 self.buffer
1859 .read(cx)
1860 .excerpt_containing(self.selections.newest_anchor().head(), cx)
1861 }
1862
1863 pub fn mode(&self) -> EditorMode {
1864 self.mode
1865 }
1866
1867 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
1868 self.collaboration_hub.as_deref()
1869 }
1870
1871 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
1872 self.collaboration_hub = Some(hub);
1873 }
1874
1875 pub fn set_in_project_search(&mut self, in_project_search: bool) {
1876 self.in_project_search = in_project_search;
1877 }
1878
1879 pub fn set_custom_context_menu(
1880 &mut self,
1881 f: impl 'static
1882 + Fn(
1883 &mut Self,
1884 DisplayPoint,
1885 &mut Window,
1886 &mut Context<Self>,
1887 ) -> Option<Entity<ui::ContextMenu>>,
1888 ) {
1889 self.custom_context_menu = Some(Box::new(f))
1890 }
1891
1892 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
1893 self.completion_provider = provider;
1894 }
1895
1896 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
1897 self.semantics_provider.clone()
1898 }
1899
1900 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
1901 self.semantics_provider = provider;
1902 }
1903
1904 pub fn set_edit_prediction_provider<T>(
1905 &mut self,
1906 provider: Option<Entity<T>>,
1907 window: &mut Window,
1908 cx: &mut Context<Self>,
1909 ) where
1910 T: EditPredictionProvider,
1911 {
1912 self.edit_prediction_provider =
1913 provider.map(|provider| RegisteredInlineCompletionProvider {
1914 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
1915 if this.focus_handle.is_focused(window) {
1916 this.update_visible_inline_completion(window, cx);
1917 }
1918 }),
1919 provider: Arc::new(provider),
1920 });
1921 self.update_edit_prediction_settings(cx);
1922 self.refresh_inline_completion(false, false, window, cx);
1923 }
1924
1925 pub fn placeholder_text(&self) -> Option<&str> {
1926 self.placeholder_text.as_deref()
1927 }
1928
1929 pub fn set_placeholder_text(
1930 &mut self,
1931 placeholder_text: impl Into<Arc<str>>,
1932 cx: &mut Context<Self>,
1933 ) {
1934 let placeholder_text = Some(placeholder_text.into());
1935 if self.placeholder_text != placeholder_text {
1936 self.placeholder_text = placeholder_text;
1937 cx.notify();
1938 }
1939 }
1940
1941 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
1942 self.cursor_shape = cursor_shape;
1943
1944 // Disrupt blink for immediate user feedback that the cursor shape has changed
1945 self.blink_manager.update(cx, BlinkManager::show_cursor);
1946
1947 cx.notify();
1948 }
1949
1950 pub fn set_current_line_highlight(
1951 &mut self,
1952 current_line_highlight: Option<CurrentLineHighlight>,
1953 ) {
1954 self.current_line_highlight = current_line_highlight;
1955 }
1956
1957 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
1958 self.collapse_matches = collapse_matches;
1959 }
1960
1961 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
1962 let buffers = self.buffer.read(cx).all_buffers();
1963 let Some(project) = self.project.as_ref() else {
1964 return;
1965 };
1966 project.update(cx, |project, cx| {
1967 for buffer in buffers {
1968 self.registered_buffers
1969 .entry(buffer.read(cx).remote_id())
1970 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
1971 }
1972 })
1973 }
1974
1975 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
1976 if self.collapse_matches {
1977 return range.start..range.start;
1978 }
1979 range.clone()
1980 }
1981
1982 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
1983 if self.display_map.read(cx).clip_at_line_ends != clip {
1984 self.display_map
1985 .update(cx, |map, _| map.clip_at_line_ends = clip);
1986 }
1987 }
1988
1989 pub fn set_input_enabled(&mut self, input_enabled: bool) {
1990 self.input_enabled = input_enabled;
1991 }
1992
1993 pub fn set_inline_completions_hidden_for_vim_mode(
1994 &mut self,
1995 hidden: bool,
1996 window: &mut Window,
1997 cx: &mut Context<Self>,
1998 ) {
1999 if hidden != self.inline_completions_hidden_for_vim_mode {
2000 self.inline_completions_hidden_for_vim_mode = hidden;
2001 if hidden {
2002 self.update_visible_inline_completion(window, cx);
2003 } else {
2004 self.refresh_inline_completion(true, false, window, cx);
2005 }
2006 }
2007 }
2008
2009 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2010 self.menu_inline_completions_policy = value;
2011 }
2012
2013 pub fn set_autoindent(&mut self, autoindent: bool) {
2014 if autoindent {
2015 self.autoindent_mode = Some(AutoindentMode::EachLine);
2016 } else {
2017 self.autoindent_mode = None;
2018 }
2019 }
2020
2021 pub fn read_only(&self, cx: &App) -> bool {
2022 self.read_only || self.buffer.read(cx).read_only()
2023 }
2024
2025 pub fn set_read_only(&mut self, read_only: bool) {
2026 self.read_only = read_only;
2027 }
2028
2029 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2030 self.use_autoclose = autoclose;
2031 }
2032
2033 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2034 self.use_auto_surround = auto_surround;
2035 }
2036
2037 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2038 self.auto_replace_emoji_shortcode = auto_replace;
2039 }
2040
2041 pub fn toggle_edit_predictions(
2042 &mut self,
2043 _: &ToggleEditPrediction,
2044 window: &mut Window,
2045 cx: &mut Context<Self>,
2046 ) {
2047 if self.show_inline_completions_override.is_some() {
2048 self.set_show_edit_predictions(None, window, cx);
2049 } else {
2050 let show_edit_predictions = !self.edit_predictions_enabled();
2051 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2052 }
2053 }
2054
2055 pub fn set_show_edit_predictions(
2056 &mut self,
2057 show_edit_predictions: Option<bool>,
2058 window: &mut Window,
2059 cx: &mut Context<Self>,
2060 ) {
2061 self.show_inline_completions_override = show_edit_predictions;
2062 self.update_edit_prediction_settings(cx);
2063
2064 if let Some(false) = show_edit_predictions {
2065 self.discard_inline_completion(false, cx);
2066 } else {
2067 self.refresh_inline_completion(false, true, window, cx);
2068 }
2069 }
2070
2071 fn inline_completions_disabled_in_scope(
2072 &self,
2073 buffer: &Entity<Buffer>,
2074 buffer_position: language::Anchor,
2075 cx: &App,
2076 ) -> bool {
2077 let snapshot = buffer.read(cx).snapshot();
2078 let settings = snapshot.settings_at(buffer_position, cx);
2079
2080 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2081 return false;
2082 };
2083
2084 scope.override_name().map_or(false, |scope_name| {
2085 settings
2086 .edit_predictions_disabled_in
2087 .iter()
2088 .any(|s| s == scope_name)
2089 })
2090 }
2091
2092 pub fn set_use_modal_editing(&mut self, to: bool) {
2093 self.use_modal_editing = to;
2094 }
2095
2096 pub fn use_modal_editing(&self) -> bool {
2097 self.use_modal_editing
2098 }
2099
2100 fn selections_did_change(
2101 &mut self,
2102 local: bool,
2103 old_cursor_position: &Anchor,
2104 show_completions: bool,
2105 window: &mut Window,
2106 cx: &mut Context<Self>,
2107 ) {
2108 window.invalidate_character_coordinates();
2109
2110 // Copy selections to primary selection buffer
2111 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2112 if local {
2113 let selections = self.selections.all::<usize>(cx);
2114 let buffer_handle = self.buffer.read(cx).read(cx);
2115
2116 let mut text = String::new();
2117 for (index, selection) in selections.iter().enumerate() {
2118 let text_for_selection = buffer_handle
2119 .text_for_range(selection.start..selection.end)
2120 .collect::<String>();
2121
2122 text.push_str(&text_for_selection);
2123 if index != selections.len() - 1 {
2124 text.push('\n');
2125 }
2126 }
2127
2128 if !text.is_empty() {
2129 cx.write_to_primary(ClipboardItem::new_string(text));
2130 }
2131 }
2132
2133 if self.focus_handle.is_focused(window) && self.leader_peer_id.is_none() {
2134 self.buffer.update(cx, |buffer, cx| {
2135 buffer.set_active_selections(
2136 &self.selections.disjoint_anchors(),
2137 self.selections.line_mode,
2138 self.cursor_shape,
2139 cx,
2140 )
2141 });
2142 }
2143 let display_map = self
2144 .display_map
2145 .update(cx, |display_map, cx| display_map.snapshot(cx));
2146 let buffer = &display_map.buffer_snapshot;
2147 self.add_selections_state = None;
2148 self.select_next_state = None;
2149 self.select_prev_state = None;
2150 self.select_larger_syntax_node_stack.clear();
2151 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2152 self.snippet_stack
2153 .invalidate(&self.selections.disjoint_anchors(), buffer);
2154 self.take_rename(false, window, cx);
2155
2156 let new_cursor_position = self.selections.newest_anchor().head();
2157
2158 self.push_to_nav_history(
2159 *old_cursor_position,
2160 Some(new_cursor_position.to_point(buffer)),
2161 cx,
2162 );
2163
2164 if local {
2165 let new_cursor_position = self.selections.newest_anchor().head();
2166 let mut context_menu = self.context_menu.borrow_mut();
2167 let completion_menu = match context_menu.as_ref() {
2168 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2169 _ => {
2170 *context_menu = None;
2171 None
2172 }
2173 };
2174 if let Some(buffer_id) = new_cursor_position.buffer_id {
2175 if !self.registered_buffers.contains_key(&buffer_id) {
2176 if let Some(project) = self.project.as_ref() {
2177 project.update(cx, |project, cx| {
2178 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2179 return;
2180 };
2181 self.registered_buffers.insert(
2182 buffer_id,
2183 project.register_buffer_with_language_servers(&buffer, cx),
2184 );
2185 })
2186 }
2187 }
2188 }
2189
2190 if let Some(completion_menu) = completion_menu {
2191 let cursor_position = new_cursor_position.to_offset(buffer);
2192 let (word_range, kind) =
2193 buffer.surrounding_word(completion_menu.initial_position, true);
2194 if kind == Some(CharKind::Word)
2195 && word_range.to_inclusive().contains(&cursor_position)
2196 {
2197 let mut completion_menu = completion_menu.clone();
2198 drop(context_menu);
2199
2200 let query = Self::completion_query(buffer, cursor_position);
2201 cx.spawn(async move |this, cx| {
2202 completion_menu
2203 .filter(query.as_deref(), cx.background_executor().clone())
2204 .await;
2205
2206 this.update(cx, |this, cx| {
2207 let mut context_menu = this.context_menu.borrow_mut();
2208 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2209 else {
2210 return;
2211 };
2212
2213 if menu.id > completion_menu.id {
2214 return;
2215 }
2216
2217 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2218 drop(context_menu);
2219 cx.notify();
2220 })
2221 })
2222 .detach();
2223
2224 if show_completions {
2225 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2226 }
2227 } else {
2228 drop(context_menu);
2229 self.hide_context_menu(window, cx);
2230 }
2231 } else {
2232 drop(context_menu);
2233 }
2234
2235 hide_hover(self, cx);
2236
2237 if old_cursor_position.to_display_point(&display_map).row()
2238 != new_cursor_position.to_display_point(&display_map).row()
2239 {
2240 self.available_code_actions.take();
2241 }
2242 self.refresh_code_actions(window, cx);
2243 self.refresh_document_highlights(cx);
2244 self.refresh_selected_text_highlights(window, cx);
2245 refresh_matching_bracket_highlights(self, window, cx);
2246 self.update_visible_inline_completion(window, cx);
2247 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2248 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2249 if self.git_blame_inline_enabled {
2250 self.start_inline_blame_timer(window, cx);
2251 }
2252 }
2253
2254 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2255 cx.emit(EditorEvent::SelectionsChanged { local });
2256
2257 let selections = &self.selections.disjoint;
2258 if selections.len() == 1 {
2259 cx.emit(SearchEvent::ActiveMatchChanged)
2260 }
2261 if local
2262 && self.is_singleton(cx)
2263 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
2264 {
2265 if let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) {
2266 let background_executor = cx.background_executor().clone();
2267 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2268 let snapshot = self.buffer().read(cx).snapshot(cx);
2269 let selections = selections.clone();
2270 self.serialize_selections = cx.background_spawn(async move {
2271 background_executor.timer(Duration::from_millis(100)).await;
2272 let selections = selections
2273 .iter()
2274 .map(|selection| {
2275 (
2276 selection.start.to_offset(&snapshot),
2277 selection.end.to_offset(&snapshot),
2278 )
2279 })
2280 .collect();
2281 DB.save_editor_selections(editor_id, workspace_id, selections)
2282 .await
2283 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2284 .log_err();
2285 });
2286 }
2287 }
2288
2289 cx.notify();
2290 }
2291
2292 pub fn sync_selections(
2293 &mut self,
2294 other: Entity<Editor>,
2295 cx: &mut Context<Self>,
2296 ) -> gpui::Subscription {
2297 let other_selections = other.read(cx).selections.disjoint.to_vec();
2298 self.selections.change_with(cx, |selections| {
2299 selections.select_anchors(other_selections);
2300 });
2301
2302 let other_subscription =
2303 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2304 EditorEvent::SelectionsChanged { local: true } => {
2305 let other_selections = other.read(cx).selections.disjoint.to_vec();
2306 if other_selections.is_empty() {
2307 return;
2308 }
2309 this.selections.change_with(cx, |selections| {
2310 selections.select_anchors(other_selections);
2311 });
2312 }
2313 _ => {}
2314 });
2315
2316 let this_subscription =
2317 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2318 EditorEvent::SelectionsChanged { local: true } => {
2319 let these_selections = this.selections.disjoint.to_vec();
2320 if these_selections.is_empty() {
2321 return;
2322 }
2323 other.update(cx, |other_editor, cx| {
2324 other_editor.selections.change_with(cx, |selections| {
2325 selections.select_anchors(these_selections);
2326 })
2327 });
2328 }
2329 _ => {}
2330 });
2331
2332 Subscription::join(other_subscription, this_subscription)
2333 }
2334
2335 pub fn change_selections<R>(
2336 &mut self,
2337 autoscroll: Option<Autoscroll>,
2338 window: &mut Window,
2339 cx: &mut Context<Self>,
2340 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2341 ) -> R {
2342 self.change_selections_inner(autoscroll, true, window, cx, change)
2343 }
2344
2345 fn change_selections_inner<R>(
2346 &mut self,
2347 autoscroll: Option<Autoscroll>,
2348 request_completions: bool,
2349 window: &mut Window,
2350 cx: &mut Context<Self>,
2351 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2352 ) -> R {
2353 let old_cursor_position = self.selections.newest_anchor().head();
2354 self.push_to_selection_history();
2355
2356 let (changed, result) = self.selections.change_with(cx, change);
2357
2358 if changed {
2359 if let Some(autoscroll) = autoscroll {
2360 self.request_autoscroll(autoscroll, cx);
2361 }
2362 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2363
2364 if self.should_open_signature_help_automatically(
2365 &old_cursor_position,
2366 self.signature_help_state.backspace_pressed(),
2367 cx,
2368 ) {
2369 self.show_signature_help(&ShowSignatureHelp, window, cx);
2370 }
2371 self.signature_help_state.set_backspace_pressed(false);
2372 }
2373
2374 result
2375 }
2376
2377 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2378 where
2379 I: IntoIterator<Item = (Range<S>, T)>,
2380 S: ToOffset,
2381 T: Into<Arc<str>>,
2382 {
2383 if self.read_only(cx) {
2384 return;
2385 }
2386
2387 self.buffer
2388 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2389 }
2390
2391 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2392 where
2393 I: IntoIterator<Item = (Range<S>, T)>,
2394 S: ToOffset,
2395 T: Into<Arc<str>>,
2396 {
2397 if self.read_only(cx) {
2398 return;
2399 }
2400
2401 self.buffer.update(cx, |buffer, cx| {
2402 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2403 });
2404 }
2405
2406 pub fn edit_with_block_indent<I, S, T>(
2407 &mut self,
2408 edits: I,
2409 original_indent_columns: Vec<Option<u32>>,
2410 cx: &mut Context<Self>,
2411 ) where
2412 I: IntoIterator<Item = (Range<S>, T)>,
2413 S: ToOffset,
2414 T: Into<Arc<str>>,
2415 {
2416 if self.read_only(cx) {
2417 return;
2418 }
2419
2420 self.buffer.update(cx, |buffer, cx| {
2421 buffer.edit(
2422 edits,
2423 Some(AutoindentMode::Block {
2424 original_indent_columns,
2425 }),
2426 cx,
2427 )
2428 });
2429 }
2430
2431 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2432 self.hide_context_menu(window, cx);
2433
2434 match phase {
2435 SelectPhase::Begin {
2436 position,
2437 add,
2438 click_count,
2439 } => self.begin_selection(position, add, click_count, window, cx),
2440 SelectPhase::BeginColumnar {
2441 position,
2442 goal_column,
2443 reset,
2444 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
2445 SelectPhase::Extend {
2446 position,
2447 click_count,
2448 } => self.extend_selection(position, click_count, window, cx),
2449 SelectPhase::Update {
2450 position,
2451 goal_column,
2452 scroll_delta,
2453 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
2454 SelectPhase::End => self.end_selection(window, cx),
2455 }
2456 }
2457
2458 fn extend_selection(
2459 &mut self,
2460 position: DisplayPoint,
2461 click_count: usize,
2462 window: &mut Window,
2463 cx: &mut Context<Self>,
2464 ) {
2465 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2466 let tail = self.selections.newest::<usize>(cx).tail();
2467 self.begin_selection(position, false, click_count, window, cx);
2468
2469 let position = position.to_offset(&display_map, Bias::Left);
2470 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
2471
2472 let mut pending_selection = self
2473 .selections
2474 .pending_anchor()
2475 .expect("extend_selection not called with pending selection");
2476 if position >= tail {
2477 pending_selection.start = tail_anchor;
2478 } else {
2479 pending_selection.end = tail_anchor;
2480 pending_selection.reversed = true;
2481 }
2482
2483 let mut pending_mode = self.selections.pending_mode().unwrap();
2484 match &mut pending_mode {
2485 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
2486 _ => {}
2487 }
2488
2489 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
2490 s.set_pending(pending_selection, pending_mode)
2491 });
2492 }
2493
2494 fn begin_selection(
2495 &mut self,
2496 position: DisplayPoint,
2497 add: bool,
2498 click_count: usize,
2499 window: &mut Window,
2500 cx: &mut Context<Self>,
2501 ) {
2502 if !self.focus_handle.is_focused(window) {
2503 self.last_focused_descendant = None;
2504 window.focus(&self.focus_handle);
2505 }
2506
2507 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2508 let buffer = &display_map.buffer_snapshot;
2509 let newest_selection = self.selections.newest_anchor().clone();
2510 let position = display_map.clip_point(position, Bias::Left);
2511
2512 let start;
2513 let end;
2514 let mode;
2515 let mut auto_scroll;
2516 match click_count {
2517 1 => {
2518 start = buffer.anchor_before(position.to_point(&display_map));
2519 end = start;
2520 mode = SelectMode::Character;
2521 auto_scroll = true;
2522 }
2523 2 => {
2524 let range = movement::surrounding_word(&display_map, position);
2525 start = buffer.anchor_before(range.start.to_point(&display_map));
2526 end = buffer.anchor_before(range.end.to_point(&display_map));
2527 mode = SelectMode::Word(start..end);
2528 auto_scroll = true;
2529 }
2530 3 => {
2531 let position = display_map
2532 .clip_point(position, Bias::Left)
2533 .to_point(&display_map);
2534 let line_start = display_map.prev_line_boundary(position).0;
2535 let next_line_start = buffer.clip_point(
2536 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2537 Bias::Left,
2538 );
2539 start = buffer.anchor_before(line_start);
2540 end = buffer.anchor_before(next_line_start);
2541 mode = SelectMode::Line(start..end);
2542 auto_scroll = true;
2543 }
2544 _ => {
2545 start = buffer.anchor_before(0);
2546 end = buffer.anchor_before(buffer.len());
2547 mode = SelectMode::All;
2548 auto_scroll = false;
2549 }
2550 }
2551 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
2552
2553 let point_to_delete: Option<usize> = {
2554 let selected_points: Vec<Selection<Point>> =
2555 self.selections.disjoint_in_range(start..end, cx);
2556
2557 if !add || click_count > 1 {
2558 None
2559 } else if !selected_points.is_empty() {
2560 Some(selected_points[0].id)
2561 } else {
2562 let clicked_point_already_selected =
2563 self.selections.disjoint.iter().find(|selection| {
2564 selection.start.to_point(buffer) == start.to_point(buffer)
2565 || selection.end.to_point(buffer) == end.to_point(buffer)
2566 });
2567
2568 clicked_point_already_selected.map(|selection| selection.id)
2569 }
2570 };
2571
2572 let selections_count = self.selections.count();
2573
2574 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
2575 if let Some(point_to_delete) = point_to_delete {
2576 s.delete(point_to_delete);
2577
2578 if selections_count == 1 {
2579 s.set_pending_anchor_range(start..end, mode);
2580 }
2581 } else {
2582 if !add {
2583 s.clear_disjoint();
2584 } else if click_count > 1 {
2585 s.delete(newest_selection.id)
2586 }
2587
2588 s.set_pending_anchor_range(start..end, mode);
2589 }
2590 });
2591 }
2592
2593 fn begin_columnar_selection(
2594 &mut self,
2595 position: DisplayPoint,
2596 goal_column: u32,
2597 reset: bool,
2598 window: &mut Window,
2599 cx: &mut Context<Self>,
2600 ) {
2601 if !self.focus_handle.is_focused(window) {
2602 self.last_focused_descendant = None;
2603 window.focus(&self.focus_handle);
2604 }
2605
2606 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2607
2608 if reset {
2609 let pointer_position = display_map
2610 .buffer_snapshot
2611 .anchor_before(position.to_point(&display_map));
2612
2613 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
2614 s.clear_disjoint();
2615 s.set_pending_anchor_range(
2616 pointer_position..pointer_position,
2617 SelectMode::Character,
2618 );
2619 });
2620 }
2621
2622 let tail = self.selections.newest::<Point>(cx).tail();
2623 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
2624
2625 if !reset {
2626 self.select_columns(
2627 tail.to_display_point(&display_map),
2628 position,
2629 goal_column,
2630 &display_map,
2631 window,
2632 cx,
2633 );
2634 }
2635 }
2636
2637 fn update_selection(
2638 &mut self,
2639 position: DisplayPoint,
2640 goal_column: u32,
2641 scroll_delta: gpui::Point<f32>,
2642 window: &mut Window,
2643 cx: &mut Context<Self>,
2644 ) {
2645 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2646
2647 if let Some(tail) = self.columnar_selection_tail.as_ref() {
2648 let tail = tail.to_display_point(&display_map);
2649 self.select_columns(tail, position, goal_column, &display_map, window, cx);
2650 } else if let Some(mut pending) = self.selections.pending_anchor() {
2651 let buffer = self.buffer.read(cx).snapshot(cx);
2652 let head;
2653 let tail;
2654 let mode = self.selections.pending_mode().unwrap();
2655 match &mode {
2656 SelectMode::Character => {
2657 head = position.to_point(&display_map);
2658 tail = pending.tail().to_point(&buffer);
2659 }
2660 SelectMode::Word(original_range) => {
2661 let original_display_range = original_range.start.to_display_point(&display_map)
2662 ..original_range.end.to_display_point(&display_map);
2663 let original_buffer_range = original_display_range.start.to_point(&display_map)
2664 ..original_display_range.end.to_point(&display_map);
2665 if movement::is_inside_word(&display_map, position)
2666 || original_display_range.contains(&position)
2667 {
2668 let word_range = movement::surrounding_word(&display_map, position);
2669 if word_range.start < original_display_range.start {
2670 head = word_range.start.to_point(&display_map);
2671 } else {
2672 head = word_range.end.to_point(&display_map);
2673 }
2674 } else {
2675 head = position.to_point(&display_map);
2676 }
2677
2678 if head <= original_buffer_range.start {
2679 tail = original_buffer_range.end;
2680 } else {
2681 tail = original_buffer_range.start;
2682 }
2683 }
2684 SelectMode::Line(original_range) => {
2685 let original_range = original_range.to_point(&display_map.buffer_snapshot);
2686
2687 let position = display_map
2688 .clip_point(position, Bias::Left)
2689 .to_point(&display_map);
2690 let line_start = display_map.prev_line_boundary(position).0;
2691 let next_line_start = buffer.clip_point(
2692 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2693 Bias::Left,
2694 );
2695
2696 if line_start < original_range.start {
2697 head = line_start
2698 } else {
2699 head = next_line_start
2700 }
2701
2702 if head <= original_range.start {
2703 tail = original_range.end;
2704 } else {
2705 tail = original_range.start;
2706 }
2707 }
2708 SelectMode::All => {
2709 return;
2710 }
2711 };
2712
2713 if head < tail {
2714 pending.start = buffer.anchor_before(head);
2715 pending.end = buffer.anchor_before(tail);
2716 pending.reversed = true;
2717 } else {
2718 pending.start = buffer.anchor_before(tail);
2719 pending.end = buffer.anchor_before(head);
2720 pending.reversed = false;
2721 }
2722
2723 self.change_selections(None, window, cx, |s| {
2724 s.set_pending(pending, mode);
2725 });
2726 } else {
2727 log::error!("update_selection dispatched with no pending selection");
2728 return;
2729 }
2730
2731 self.apply_scroll_delta(scroll_delta, window, cx);
2732 cx.notify();
2733 }
2734
2735 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2736 self.columnar_selection_tail.take();
2737 if self.selections.pending_anchor().is_some() {
2738 let selections = self.selections.all::<usize>(cx);
2739 self.change_selections(None, window, cx, |s| {
2740 s.select(selections);
2741 s.clear_pending();
2742 });
2743 }
2744 }
2745
2746 fn select_columns(
2747 &mut self,
2748 tail: DisplayPoint,
2749 head: DisplayPoint,
2750 goal_column: u32,
2751 display_map: &DisplaySnapshot,
2752 window: &mut Window,
2753 cx: &mut Context<Self>,
2754 ) {
2755 let start_row = cmp::min(tail.row(), head.row());
2756 let end_row = cmp::max(tail.row(), head.row());
2757 let start_column = cmp::min(tail.column(), goal_column);
2758 let end_column = cmp::max(tail.column(), goal_column);
2759 let reversed = start_column < tail.column();
2760
2761 let selection_ranges = (start_row.0..=end_row.0)
2762 .map(DisplayRow)
2763 .filter_map(|row| {
2764 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
2765 let start = display_map
2766 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
2767 .to_point(display_map);
2768 let end = display_map
2769 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
2770 .to_point(display_map);
2771 if reversed {
2772 Some(end..start)
2773 } else {
2774 Some(start..end)
2775 }
2776 } else {
2777 None
2778 }
2779 })
2780 .collect::<Vec<_>>();
2781
2782 self.change_selections(None, window, cx, |s| {
2783 s.select_ranges(selection_ranges);
2784 });
2785 cx.notify();
2786 }
2787
2788 pub fn has_pending_nonempty_selection(&self) -> bool {
2789 let pending_nonempty_selection = match self.selections.pending_anchor() {
2790 Some(Selection { start, end, .. }) => start != end,
2791 None => false,
2792 };
2793
2794 pending_nonempty_selection
2795 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
2796 }
2797
2798 pub fn has_pending_selection(&self) -> bool {
2799 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
2800 }
2801
2802 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
2803 self.selection_mark_mode = false;
2804
2805 if self.clear_expanded_diff_hunks(cx) {
2806 cx.notify();
2807 return;
2808 }
2809 if self.dismiss_menus_and_popups(true, window, cx) {
2810 return;
2811 }
2812
2813 if self.mode == EditorMode::Full
2814 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
2815 {
2816 return;
2817 }
2818
2819 cx.propagate();
2820 }
2821
2822 pub fn dismiss_menus_and_popups(
2823 &mut self,
2824 is_user_requested: bool,
2825 window: &mut Window,
2826 cx: &mut Context<Self>,
2827 ) -> bool {
2828 if self.take_rename(false, window, cx).is_some() {
2829 return true;
2830 }
2831
2832 if hide_hover(self, cx) {
2833 return true;
2834 }
2835
2836 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
2837 return true;
2838 }
2839
2840 if self.hide_context_menu(window, cx).is_some() {
2841 return true;
2842 }
2843
2844 if self.mouse_context_menu.take().is_some() {
2845 return true;
2846 }
2847
2848 if is_user_requested && self.discard_inline_completion(true, cx) {
2849 return true;
2850 }
2851
2852 if self.snippet_stack.pop().is_some() {
2853 return true;
2854 }
2855
2856 if self.mode == EditorMode::Full && self.active_diagnostics.is_some() {
2857 self.dismiss_diagnostics(cx);
2858 return true;
2859 }
2860
2861 false
2862 }
2863
2864 fn linked_editing_ranges_for(
2865 &self,
2866 selection: Range<text::Anchor>,
2867 cx: &App,
2868 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
2869 if self.linked_edit_ranges.is_empty() {
2870 return None;
2871 }
2872 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
2873 selection.end.buffer_id.and_then(|end_buffer_id| {
2874 if selection.start.buffer_id != Some(end_buffer_id) {
2875 return None;
2876 }
2877 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
2878 let snapshot = buffer.read(cx).snapshot();
2879 self.linked_edit_ranges
2880 .get(end_buffer_id, selection.start..selection.end, &snapshot)
2881 .map(|ranges| (ranges, snapshot, buffer))
2882 })?;
2883 use text::ToOffset as TO;
2884 // find offset from the start of current range to current cursor position
2885 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
2886
2887 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
2888 let start_difference = start_offset - start_byte_offset;
2889 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
2890 let end_difference = end_offset - start_byte_offset;
2891 // Current range has associated linked ranges.
2892 let mut linked_edits = HashMap::<_, Vec<_>>::default();
2893 for range in linked_ranges.iter() {
2894 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
2895 let end_offset = start_offset + end_difference;
2896 let start_offset = start_offset + start_difference;
2897 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
2898 continue;
2899 }
2900 if self.selections.disjoint_anchor_ranges().any(|s| {
2901 if s.start.buffer_id != selection.start.buffer_id
2902 || s.end.buffer_id != selection.end.buffer_id
2903 {
2904 return false;
2905 }
2906 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
2907 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
2908 }) {
2909 continue;
2910 }
2911 let start = buffer_snapshot.anchor_after(start_offset);
2912 let end = buffer_snapshot.anchor_after(end_offset);
2913 linked_edits
2914 .entry(buffer.clone())
2915 .or_default()
2916 .push(start..end);
2917 }
2918 Some(linked_edits)
2919 }
2920
2921 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
2922 let text: Arc<str> = text.into();
2923
2924 if self.read_only(cx) {
2925 return;
2926 }
2927
2928 let selections = self.selections.all_adjusted(cx);
2929 let mut bracket_inserted = false;
2930 let mut edits = Vec::new();
2931 let mut linked_edits = HashMap::<_, Vec<_>>::default();
2932 let mut new_selections = Vec::with_capacity(selections.len());
2933 let mut new_autoclose_regions = Vec::new();
2934 let snapshot = self.buffer.read(cx).read(cx);
2935
2936 for (selection, autoclose_region) in
2937 self.selections_with_autoclose_regions(selections, &snapshot)
2938 {
2939 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
2940 // Determine if the inserted text matches the opening or closing
2941 // bracket of any of this language's bracket pairs.
2942 let mut bracket_pair = None;
2943 let mut is_bracket_pair_start = false;
2944 let mut is_bracket_pair_end = false;
2945 if !text.is_empty() {
2946 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
2947 // and they are removing the character that triggered IME popup.
2948 for (pair, enabled) in scope.brackets() {
2949 if !pair.close && !pair.surround {
2950 continue;
2951 }
2952
2953 if enabled && pair.start.ends_with(text.as_ref()) {
2954 let prefix_len = pair.start.len() - text.len();
2955 let preceding_text_matches_prefix = prefix_len == 0
2956 || (selection.start.column >= (prefix_len as u32)
2957 && snapshot.contains_str_at(
2958 Point::new(
2959 selection.start.row,
2960 selection.start.column - (prefix_len as u32),
2961 ),
2962 &pair.start[..prefix_len],
2963 ));
2964 if preceding_text_matches_prefix {
2965 bracket_pair = Some(pair.clone());
2966 is_bracket_pair_start = true;
2967 break;
2968 }
2969 }
2970 if pair.end.as_str() == text.as_ref() {
2971 bracket_pair = Some(pair.clone());
2972 is_bracket_pair_end = true;
2973 break;
2974 }
2975 }
2976 }
2977
2978 if let Some(bracket_pair) = bracket_pair {
2979 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
2980 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
2981 let auto_surround =
2982 self.use_auto_surround && snapshot_settings.use_auto_surround;
2983 if selection.is_empty() {
2984 if is_bracket_pair_start {
2985 // If the inserted text is a suffix of an opening bracket and the
2986 // selection is preceded by the rest of the opening bracket, then
2987 // insert the closing bracket.
2988 let following_text_allows_autoclose = snapshot
2989 .chars_at(selection.start)
2990 .next()
2991 .map_or(true, |c| scope.should_autoclose_before(c));
2992
2993 let preceding_text_allows_autoclose = selection.start.column == 0
2994 || snapshot.reversed_chars_at(selection.start).next().map_or(
2995 true,
2996 |c| {
2997 bracket_pair.start != bracket_pair.end
2998 || !snapshot
2999 .char_classifier_at(selection.start)
3000 .is_word(c)
3001 },
3002 );
3003
3004 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3005 && bracket_pair.start.len() == 1
3006 {
3007 let target = bracket_pair.start.chars().next().unwrap();
3008 let current_line_count = snapshot
3009 .reversed_chars_at(selection.start)
3010 .take_while(|&c| c != '\n')
3011 .filter(|&c| c == target)
3012 .count();
3013 current_line_count % 2 == 1
3014 } else {
3015 false
3016 };
3017
3018 if autoclose
3019 && bracket_pair.close
3020 && following_text_allows_autoclose
3021 && preceding_text_allows_autoclose
3022 && !is_closing_quote
3023 {
3024 let anchor = snapshot.anchor_before(selection.end);
3025 new_selections.push((selection.map(|_| anchor), text.len()));
3026 new_autoclose_regions.push((
3027 anchor,
3028 text.len(),
3029 selection.id,
3030 bracket_pair.clone(),
3031 ));
3032 edits.push((
3033 selection.range(),
3034 format!("{}{}", text, bracket_pair.end).into(),
3035 ));
3036 bracket_inserted = true;
3037 continue;
3038 }
3039 }
3040
3041 if let Some(region) = autoclose_region {
3042 // If the selection is followed by an auto-inserted closing bracket,
3043 // then don't insert that closing bracket again; just move the selection
3044 // past the closing bracket.
3045 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3046 && text.as_ref() == region.pair.end.as_str();
3047 if should_skip {
3048 let anchor = snapshot.anchor_after(selection.end);
3049 new_selections
3050 .push((selection.map(|_| anchor), region.pair.end.len()));
3051 continue;
3052 }
3053 }
3054
3055 let always_treat_brackets_as_autoclosed = snapshot
3056 .language_settings_at(selection.start, cx)
3057 .always_treat_brackets_as_autoclosed;
3058 if always_treat_brackets_as_autoclosed
3059 && is_bracket_pair_end
3060 && snapshot.contains_str_at(selection.end, text.as_ref())
3061 {
3062 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3063 // and the inserted text is a closing bracket and the selection is followed
3064 // by the closing bracket then move the selection past the closing bracket.
3065 let anchor = snapshot.anchor_after(selection.end);
3066 new_selections.push((selection.map(|_| anchor), text.len()));
3067 continue;
3068 }
3069 }
3070 // If an opening bracket is 1 character long and is typed while
3071 // text is selected, then surround that text with the bracket pair.
3072 else if auto_surround
3073 && bracket_pair.surround
3074 && is_bracket_pair_start
3075 && bracket_pair.start.chars().count() == 1
3076 {
3077 edits.push((selection.start..selection.start, text.clone()));
3078 edits.push((
3079 selection.end..selection.end,
3080 bracket_pair.end.as_str().into(),
3081 ));
3082 bracket_inserted = true;
3083 new_selections.push((
3084 Selection {
3085 id: selection.id,
3086 start: snapshot.anchor_after(selection.start),
3087 end: snapshot.anchor_before(selection.end),
3088 reversed: selection.reversed,
3089 goal: selection.goal,
3090 },
3091 0,
3092 ));
3093 continue;
3094 }
3095 }
3096 }
3097
3098 if self.auto_replace_emoji_shortcode
3099 && selection.is_empty()
3100 && text.as_ref().ends_with(':')
3101 {
3102 if let Some(possible_emoji_short_code) =
3103 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3104 {
3105 if !possible_emoji_short_code.is_empty() {
3106 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3107 let emoji_shortcode_start = Point::new(
3108 selection.start.row,
3109 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3110 );
3111
3112 // Remove shortcode from buffer
3113 edits.push((
3114 emoji_shortcode_start..selection.start,
3115 "".to_string().into(),
3116 ));
3117 new_selections.push((
3118 Selection {
3119 id: selection.id,
3120 start: snapshot.anchor_after(emoji_shortcode_start),
3121 end: snapshot.anchor_before(selection.start),
3122 reversed: selection.reversed,
3123 goal: selection.goal,
3124 },
3125 0,
3126 ));
3127
3128 // Insert emoji
3129 let selection_start_anchor = snapshot.anchor_after(selection.start);
3130 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3131 edits.push((selection.start..selection.end, emoji.to_string().into()));
3132
3133 continue;
3134 }
3135 }
3136 }
3137 }
3138
3139 // If not handling any auto-close operation, then just replace the selected
3140 // text with the given input and move the selection to the end of the
3141 // newly inserted text.
3142 let anchor = snapshot.anchor_after(selection.end);
3143 if !self.linked_edit_ranges.is_empty() {
3144 let start_anchor = snapshot.anchor_before(selection.start);
3145
3146 let is_word_char = text.chars().next().map_or(true, |char| {
3147 let classifier = snapshot.char_classifier_at(start_anchor.to_offset(&snapshot));
3148 classifier.is_word(char)
3149 });
3150
3151 if is_word_char {
3152 if let Some(ranges) = self
3153 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3154 {
3155 for (buffer, edits) in ranges {
3156 linked_edits
3157 .entry(buffer.clone())
3158 .or_default()
3159 .extend(edits.into_iter().map(|range| (range, text.clone())));
3160 }
3161 }
3162 }
3163 }
3164
3165 new_selections.push((selection.map(|_| anchor), 0));
3166 edits.push((selection.start..selection.end, text.clone()));
3167 }
3168
3169 drop(snapshot);
3170
3171 self.transact(window, cx, |this, window, cx| {
3172 let initial_buffer_versions =
3173 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3174
3175 this.buffer.update(cx, |buffer, cx| {
3176 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3177 });
3178 for (buffer, edits) in linked_edits {
3179 buffer.update(cx, |buffer, cx| {
3180 let snapshot = buffer.snapshot();
3181 let edits = edits
3182 .into_iter()
3183 .map(|(range, text)| {
3184 use text::ToPoint as TP;
3185 let end_point = TP::to_point(&range.end, &snapshot);
3186 let start_point = TP::to_point(&range.start, &snapshot);
3187 (start_point..end_point, text)
3188 })
3189 .sorted_by_key(|(range, _)| range.start)
3190 .collect::<Vec<_>>();
3191 buffer.edit(edits, None, cx);
3192 })
3193 }
3194 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3195 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3196 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3197 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3198 .zip(new_selection_deltas)
3199 .map(|(selection, delta)| Selection {
3200 id: selection.id,
3201 start: selection.start + delta,
3202 end: selection.end + delta,
3203 reversed: selection.reversed,
3204 goal: SelectionGoal::None,
3205 })
3206 .collect::<Vec<_>>();
3207
3208 let mut i = 0;
3209 for (position, delta, selection_id, pair) in new_autoclose_regions {
3210 let position = position.to_offset(&map.buffer_snapshot) + delta;
3211 let start = map.buffer_snapshot.anchor_before(position);
3212 let end = map.buffer_snapshot.anchor_after(position);
3213 while let Some(existing_state) = this.autoclose_regions.get(i) {
3214 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3215 Ordering::Less => i += 1,
3216 Ordering::Greater => break,
3217 Ordering::Equal => {
3218 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3219 Ordering::Less => i += 1,
3220 Ordering::Equal => break,
3221 Ordering::Greater => break,
3222 }
3223 }
3224 }
3225 }
3226 this.autoclose_regions.insert(
3227 i,
3228 AutocloseRegion {
3229 selection_id,
3230 range: start..end,
3231 pair,
3232 },
3233 );
3234 }
3235
3236 let had_active_inline_completion = this.has_active_inline_completion();
3237 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3238 s.select(new_selections)
3239 });
3240
3241 if !bracket_inserted {
3242 if let Some(on_type_format_task) =
3243 this.trigger_on_type_formatting(text.to_string(), window, cx)
3244 {
3245 on_type_format_task.detach_and_log_err(cx);
3246 }
3247 }
3248
3249 let editor_settings = EditorSettings::get_global(cx);
3250 if bracket_inserted
3251 && (editor_settings.auto_signature_help
3252 || editor_settings.show_signature_help_after_edits)
3253 {
3254 this.show_signature_help(&ShowSignatureHelp, window, cx);
3255 }
3256
3257 let trigger_in_words =
3258 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3259 if this.hard_wrap.is_some() {
3260 let latest: Range<Point> = this.selections.newest(cx).range();
3261 if latest.is_empty()
3262 && this
3263 .buffer()
3264 .read(cx)
3265 .snapshot(cx)
3266 .line_len(MultiBufferRow(latest.start.row))
3267 == latest.start.column
3268 {
3269 this.rewrap_impl(
3270 RewrapOptions {
3271 override_language_settings: true,
3272 preserve_existing_whitespace: true,
3273 },
3274 cx,
3275 )
3276 }
3277 }
3278 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3279 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3280 this.refresh_inline_completion(true, false, window, cx);
3281 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3282 });
3283 }
3284
3285 fn find_possible_emoji_shortcode_at_position(
3286 snapshot: &MultiBufferSnapshot,
3287 position: Point,
3288 ) -> Option<String> {
3289 let mut chars = Vec::new();
3290 let mut found_colon = false;
3291 for char in snapshot.reversed_chars_at(position).take(100) {
3292 // Found a possible emoji shortcode in the middle of the buffer
3293 if found_colon {
3294 if char.is_whitespace() {
3295 chars.reverse();
3296 return Some(chars.iter().collect());
3297 }
3298 // If the previous character is not a whitespace, we are in the middle of a word
3299 // and we only want to complete the shortcode if the word is made up of other emojis
3300 let mut containing_word = String::new();
3301 for ch in snapshot
3302 .reversed_chars_at(position)
3303 .skip(chars.len() + 1)
3304 .take(100)
3305 {
3306 if ch.is_whitespace() {
3307 break;
3308 }
3309 containing_word.push(ch);
3310 }
3311 let containing_word = containing_word.chars().rev().collect::<String>();
3312 if util::word_consists_of_emojis(containing_word.as_str()) {
3313 chars.reverse();
3314 return Some(chars.iter().collect());
3315 }
3316 }
3317
3318 if char.is_whitespace() || !char.is_ascii() {
3319 return None;
3320 }
3321 if char == ':' {
3322 found_colon = true;
3323 } else {
3324 chars.push(char);
3325 }
3326 }
3327 // Found a possible emoji shortcode at the beginning of the buffer
3328 chars.reverse();
3329 Some(chars.iter().collect())
3330 }
3331
3332 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3333 self.transact(window, cx, |this, window, cx| {
3334 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
3335 let selections = this.selections.all::<usize>(cx);
3336 let multi_buffer = this.buffer.read(cx);
3337 let buffer = multi_buffer.snapshot(cx);
3338 selections
3339 .iter()
3340 .map(|selection| {
3341 let start_point = selection.start.to_point(&buffer);
3342 let mut indent =
3343 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3344 indent.len = cmp::min(indent.len, start_point.column);
3345 let start = selection.start;
3346 let end = selection.end;
3347 let selection_is_empty = start == end;
3348 let language_scope = buffer.language_scope_at(start);
3349 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
3350 &language_scope
3351 {
3352 let insert_extra_newline =
3353 insert_extra_newline_brackets(&buffer, start..end, language)
3354 || insert_extra_newline_tree_sitter(&buffer, start..end);
3355
3356 // Comment extension on newline is allowed only for cursor selections
3357 let comment_delimiter = maybe!({
3358 if !selection_is_empty {
3359 return None;
3360 }
3361
3362 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3363 return None;
3364 }
3365
3366 let delimiters = language.line_comment_prefixes();
3367 let max_len_of_delimiter =
3368 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3369 let (snapshot, range) =
3370 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3371
3372 let mut index_of_first_non_whitespace = 0;
3373 let comment_candidate = snapshot
3374 .chars_for_range(range)
3375 .skip_while(|c| {
3376 let should_skip = c.is_whitespace();
3377 if should_skip {
3378 index_of_first_non_whitespace += 1;
3379 }
3380 should_skip
3381 })
3382 .take(max_len_of_delimiter)
3383 .collect::<String>();
3384 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3385 comment_candidate.starts_with(comment_prefix.as_ref())
3386 })?;
3387 let cursor_is_placed_after_comment_marker =
3388 index_of_first_non_whitespace + comment_prefix.len()
3389 <= start_point.column as usize;
3390 if cursor_is_placed_after_comment_marker {
3391 Some(comment_prefix.clone())
3392 } else {
3393 None
3394 }
3395 });
3396 (comment_delimiter, insert_extra_newline)
3397 } else {
3398 (None, false)
3399 };
3400
3401 let capacity_for_delimiter = comment_delimiter
3402 .as_deref()
3403 .map(str::len)
3404 .unwrap_or_default();
3405 let mut new_text =
3406 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3407 new_text.push('\n');
3408 new_text.extend(indent.chars());
3409 if let Some(delimiter) = &comment_delimiter {
3410 new_text.push_str(delimiter);
3411 }
3412 if insert_extra_newline {
3413 new_text = new_text.repeat(2);
3414 }
3415
3416 let anchor = buffer.anchor_after(end);
3417 let new_selection = selection.map(|_| anchor);
3418 (
3419 (start..end, new_text),
3420 (insert_extra_newline, new_selection),
3421 )
3422 })
3423 .unzip()
3424 };
3425
3426 this.edit_with_autoindent(edits, cx);
3427 let buffer = this.buffer.read(cx).snapshot(cx);
3428 let new_selections = selection_fixup_info
3429 .into_iter()
3430 .map(|(extra_newline_inserted, new_selection)| {
3431 let mut cursor = new_selection.end.to_point(&buffer);
3432 if extra_newline_inserted {
3433 cursor.row -= 1;
3434 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
3435 }
3436 new_selection.map(|_| cursor)
3437 })
3438 .collect();
3439
3440 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3441 s.select(new_selections)
3442 });
3443 this.refresh_inline_completion(true, false, window, cx);
3444 });
3445 }
3446
3447 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
3448 let buffer = self.buffer.read(cx);
3449 let snapshot = buffer.snapshot(cx);
3450
3451 let mut edits = Vec::new();
3452 let mut rows = Vec::new();
3453
3454 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
3455 let cursor = selection.head();
3456 let row = cursor.row;
3457
3458 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
3459
3460 let newline = "\n".to_string();
3461 edits.push((start_of_line..start_of_line, newline));
3462
3463 rows.push(row + rows_inserted as u32);
3464 }
3465
3466 self.transact(window, cx, |editor, window, cx| {
3467 editor.edit(edits, cx);
3468
3469 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3470 let mut index = 0;
3471 s.move_cursors_with(|map, _, _| {
3472 let row = rows[index];
3473 index += 1;
3474
3475 let point = Point::new(row, 0);
3476 let boundary = map.next_line_boundary(point).1;
3477 let clipped = map.clip_point(boundary, Bias::Left);
3478
3479 (clipped, SelectionGoal::None)
3480 });
3481 });
3482
3483 let mut indent_edits = Vec::new();
3484 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3485 for row in rows {
3486 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3487 for (row, indent) in indents {
3488 if indent.len == 0 {
3489 continue;
3490 }
3491
3492 let text = match indent.kind {
3493 IndentKind::Space => " ".repeat(indent.len as usize),
3494 IndentKind::Tab => "\t".repeat(indent.len as usize),
3495 };
3496 let point = Point::new(row.0, 0);
3497 indent_edits.push((point..point, text));
3498 }
3499 }
3500 editor.edit(indent_edits, cx);
3501 });
3502 }
3503
3504 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
3505 let buffer = self.buffer.read(cx);
3506 let snapshot = buffer.snapshot(cx);
3507
3508 let mut edits = Vec::new();
3509 let mut rows = Vec::new();
3510 let mut rows_inserted = 0;
3511
3512 for selection in self.selections.all_adjusted(cx) {
3513 let cursor = selection.head();
3514 let row = cursor.row;
3515
3516 let point = Point::new(row + 1, 0);
3517 let start_of_line = snapshot.clip_point(point, Bias::Left);
3518
3519 let newline = "\n".to_string();
3520 edits.push((start_of_line..start_of_line, newline));
3521
3522 rows_inserted += 1;
3523 rows.push(row + rows_inserted);
3524 }
3525
3526 self.transact(window, cx, |editor, window, cx| {
3527 editor.edit(edits, cx);
3528
3529 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3530 let mut index = 0;
3531 s.move_cursors_with(|map, _, _| {
3532 let row = rows[index];
3533 index += 1;
3534
3535 let point = Point::new(row, 0);
3536 let boundary = map.next_line_boundary(point).1;
3537 let clipped = map.clip_point(boundary, Bias::Left);
3538
3539 (clipped, SelectionGoal::None)
3540 });
3541 });
3542
3543 let mut indent_edits = Vec::new();
3544 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3545 for row in rows {
3546 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3547 for (row, indent) in indents {
3548 if indent.len == 0 {
3549 continue;
3550 }
3551
3552 let text = match indent.kind {
3553 IndentKind::Space => " ".repeat(indent.len as usize),
3554 IndentKind::Tab => "\t".repeat(indent.len as usize),
3555 };
3556 let point = Point::new(row.0, 0);
3557 indent_edits.push((point..point, text));
3558 }
3559 }
3560 editor.edit(indent_edits, cx);
3561 });
3562 }
3563
3564 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3565 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
3566 original_indent_columns: Vec::new(),
3567 });
3568 self.insert_with_autoindent_mode(text, autoindent, window, cx);
3569 }
3570
3571 fn insert_with_autoindent_mode(
3572 &mut self,
3573 text: &str,
3574 autoindent_mode: Option<AutoindentMode>,
3575 window: &mut Window,
3576 cx: &mut Context<Self>,
3577 ) {
3578 if self.read_only(cx) {
3579 return;
3580 }
3581
3582 let text: Arc<str> = text.into();
3583 self.transact(window, cx, |this, window, cx| {
3584 let old_selections = this.selections.all_adjusted(cx);
3585 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
3586 let anchors = {
3587 let snapshot = buffer.read(cx);
3588 old_selections
3589 .iter()
3590 .map(|s| {
3591 let anchor = snapshot.anchor_after(s.head());
3592 s.map(|_| anchor)
3593 })
3594 .collect::<Vec<_>>()
3595 };
3596 buffer.edit(
3597 old_selections
3598 .iter()
3599 .map(|s| (s.start..s.end, text.clone())),
3600 autoindent_mode,
3601 cx,
3602 );
3603 anchors
3604 });
3605
3606 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3607 s.select_anchors(selection_anchors);
3608 });
3609
3610 cx.notify();
3611 });
3612 }
3613
3614 fn trigger_completion_on_input(
3615 &mut self,
3616 text: &str,
3617 trigger_in_words: bool,
3618 window: &mut Window,
3619 cx: &mut Context<Self>,
3620 ) {
3621 let ignore_completion_provider = self
3622 .context_menu
3623 .borrow()
3624 .as_ref()
3625 .map(|menu| match menu {
3626 CodeContextMenu::Completions(completions_menu) => {
3627 completions_menu.ignore_completion_provider
3628 }
3629 CodeContextMenu::CodeActions(_) => false,
3630 })
3631 .unwrap_or(false);
3632
3633 if ignore_completion_provider {
3634 self.show_word_completions(&ShowWordCompletions, window, cx);
3635 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
3636 self.show_completions(
3637 &ShowCompletions {
3638 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
3639 },
3640 window,
3641 cx,
3642 );
3643 } else {
3644 self.hide_context_menu(window, cx);
3645 }
3646 }
3647
3648 fn is_completion_trigger(
3649 &self,
3650 text: &str,
3651 trigger_in_words: bool,
3652 cx: &mut Context<Self>,
3653 ) -> bool {
3654 let position = self.selections.newest_anchor().head();
3655 let multibuffer = self.buffer.read(cx);
3656 let Some(buffer) = position
3657 .buffer_id
3658 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
3659 else {
3660 return false;
3661 };
3662
3663 if let Some(completion_provider) = &self.completion_provider {
3664 completion_provider.is_completion_trigger(
3665 &buffer,
3666 position.text_anchor,
3667 text,
3668 trigger_in_words,
3669 cx,
3670 )
3671 } else {
3672 false
3673 }
3674 }
3675
3676 /// If any empty selections is touching the start of its innermost containing autoclose
3677 /// region, expand it to select the brackets.
3678 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3679 let selections = self.selections.all::<usize>(cx);
3680 let buffer = self.buffer.read(cx).read(cx);
3681 let new_selections = self
3682 .selections_with_autoclose_regions(selections, &buffer)
3683 .map(|(mut selection, region)| {
3684 if !selection.is_empty() {
3685 return selection;
3686 }
3687
3688 if let Some(region) = region {
3689 let mut range = region.range.to_offset(&buffer);
3690 if selection.start == range.start && range.start >= region.pair.start.len() {
3691 range.start -= region.pair.start.len();
3692 if buffer.contains_str_at(range.start, ®ion.pair.start)
3693 && buffer.contains_str_at(range.end, ®ion.pair.end)
3694 {
3695 range.end += region.pair.end.len();
3696 selection.start = range.start;
3697 selection.end = range.end;
3698
3699 return selection;
3700 }
3701 }
3702 }
3703
3704 let always_treat_brackets_as_autoclosed = buffer
3705 .language_settings_at(selection.start, cx)
3706 .always_treat_brackets_as_autoclosed;
3707
3708 if !always_treat_brackets_as_autoclosed {
3709 return selection;
3710 }
3711
3712 if let Some(scope) = buffer.language_scope_at(selection.start) {
3713 for (pair, enabled) in scope.brackets() {
3714 if !enabled || !pair.close {
3715 continue;
3716 }
3717
3718 if buffer.contains_str_at(selection.start, &pair.end) {
3719 let pair_start_len = pair.start.len();
3720 if buffer.contains_str_at(
3721 selection.start.saturating_sub(pair_start_len),
3722 &pair.start,
3723 ) {
3724 selection.start -= pair_start_len;
3725 selection.end += pair.end.len();
3726
3727 return selection;
3728 }
3729 }
3730 }
3731 }
3732
3733 selection
3734 })
3735 .collect();
3736
3737 drop(buffer);
3738 self.change_selections(None, window, cx, |selections| {
3739 selections.select(new_selections)
3740 });
3741 }
3742
3743 /// Iterate the given selections, and for each one, find the smallest surrounding
3744 /// autoclose region. This uses the ordering of the selections and the autoclose
3745 /// regions to avoid repeated comparisons.
3746 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
3747 &'a self,
3748 selections: impl IntoIterator<Item = Selection<D>>,
3749 buffer: &'a MultiBufferSnapshot,
3750 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
3751 let mut i = 0;
3752 let mut regions = self.autoclose_regions.as_slice();
3753 selections.into_iter().map(move |selection| {
3754 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
3755
3756 let mut enclosing = None;
3757 while let Some(pair_state) = regions.get(i) {
3758 if pair_state.range.end.to_offset(buffer) < range.start {
3759 regions = ®ions[i + 1..];
3760 i = 0;
3761 } else if pair_state.range.start.to_offset(buffer) > range.end {
3762 break;
3763 } else {
3764 if pair_state.selection_id == selection.id {
3765 enclosing = Some(pair_state);
3766 }
3767 i += 1;
3768 }
3769 }
3770
3771 (selection, enclosing)
3772 })
3773 }
3774
3775 /// Remove any autoclose regions that no longer contain their selection.
3776 fn invalidate_autoclose_regions(
3777 &mut self,
3778 mut selections: &[Selection<Anchor>],
3779 buffer: &MultiBufferSnapshot,
3780 ) {
3781 self.autoclose_regions.retain(|state| {
3782 let mut i = 0;
3783 while let Some(selection) = selections.get(i) {
3784 if selection.end.cmp(&state.range.start, buffer).is_lt() {
3785 selections = &selections[1..];
3786 continue;
3787 }
3788 if selection.start.cmp(&state.range.end, buffer).is_gt() {
3789 break;
3790 }
3791 if selection.id == state.selection_id {
3792 return true;
3793 } else {
3794 i += 1;
3795 }
3796 }
3797 false
3798 });
3799 }
3800
3801 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
3802 let offset = position.to_offset(buffer);
3803 let (word_range, kind) = buffer.surrounding_word(offset, true);
3804 if offset > word_range.start && kind == Some(CharKind::Word) {
3805 Some(
3806 buffer
3807 .text_for_range(word_range.start..offset)
3808 .collect::<String>(),
3809 )
3810 } else {
3811 None
3812 }
3813 }
3814
3815 pub fn toggle_inlay_hints(
3816 &mut self,
3817 _: &ToggleInlayHints,
3818 _: &mut Window,
3819 cx: &mut Context<Self>,
3820 ) {
3821 self.refresh_inlay_hints(
3822 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
3823 cx,
3824 );
3825 }
3826
3827 pub fn inlay_hints_enabled(&self) -> bool {
3828 self.inlay_hint_cache.enabled
3829 }
3830
3831 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
3832 if self.semantics_provider.is_none() || self.mode != EditorMode::Full {
3833 return;
3834 }
3835
3836 let reason_description = reason.description();
3837 let ignore_debounce = matches!(
3838 reason,
3839 InlayHintRefreshReason::SettingsChange(_)
3840 | InlayHintRefreshReason::Toggle(_)
3841 | InlayHintRefreshReason::ExcerptsRemoved(_)
3842 | InlayHintRefreshReason::ModifiersChanged(_)
3843 );
3844 let (invalidate_cache, required_languages) = match reason {
3845 InlayHintRefreshReason::ModifiersChanged(enabled) => {
3846 match self.inlay_hint_cache.modifiers_override(enabled) {
3847 Some(enabled) => {
3848 if enabled {
3849 (InvalidationStrategy::RefreshRequested, None)
3850 } else {
3851 self.splice_inlays(
3852 &self
3853 .visible_inlay_hints(cx)
3854 .iter()
3855 .map(|inlay| inlay.id)
3856 .collect::<Vec<InlayId>>(),
3857 Vec::new(),
3858 cx,
3859 );
3860 return;
3861 }
3862 }
3863 None => return,
3864 }
3865 }
3866 InlayHintRefreshReason::Toggle(enabled) => {
3867 if self.inlay_hint_cache.toggle(enabled) {
3868 if enabled {
3869 (InvalidationStrategy::RefreshRequested, None)
3870 } else {
3871 self.splice_inlays(
3872 &self
3873 .visible_inlay_hints(cx)
3874 .iter()
3875 .map(|inlay| inlay.id)
3876 .collect::<Vec<InlayId>>(),
3877 Vec::new(),
3878 cx,
3879 );
3880 return;
3881 }
3882 } else {
3883 return;
3884 }
3885 }
3886 InlayHintRefreshReason::SettingsChange(new_settings) => {
3887 match self.inlay_hint_cache.update_settings(
3888 &self.buffer,
3889 new_settings,
3890 self.visible_inlay_hints(cx),
3891 cx,
3892 ) {
3893 ControlFlow::Break(Some(InlaySplice {
3894 to_remove,
3895 to_insert,
3896 })) => {
3897 self.splice_inlays(&to_remove, to_insert, cx);
3898 return;
3899 }
3900 ControlFlow::Break(None) => return,
3901 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
3902 }
3903 }
3904 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
3905 if let Some(InlaySplice {
3906 to_remove,
3907 to_insert,
3908 }) = self.inlay_hint_cache.remove_excerpts(excerpts_removed)
3909 {
3910 self.splice_inlays(&to_remove, to_insert, cx);
3911 }
3912 return;
3913 }
3914 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
3915 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
3916 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
3917 }
3918 InlayHintRefreshReason::RefreshRequested => {
3919 (InvalidationStrategy::RefreshRequested, None)
3920 }
3921 };
3922
3923 if let Some(InlaySplice {
3924 to_remove,
3925 to_insert,
3926 }) = self.inlay_hint_cache.spawn_hint_refresh(
3927 reason_description,
3928 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
3929 invalidate_cache,
3930 ignore_debounce,
3931 cx,
3932 ) {
3933 self.splice_inlays(&to_remove, to_insert, cx);
3934 }
3935 }
3936
3937 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
3938 self.display_map
3939 .read(cx)
3940 .current_inlays()
3941 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
3942 .cloned()
3943 .collect()
3944 }
3945
3946 pub fn excerpts_for_inlay_hints_query(
3947 &self,
3948 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
3949 cx: &mut Context<Editor>,
3950 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
3951 let Some(project) = self.project.as_ref() else {
3952 return HashMap::default();
3953 };
3954 let project = project.read(cx);
3955 let multi_buffer = self.buffer().read(cx);
3956 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
3957 let multi_buffer_visible_start = self
3958 .scroll_manager
3959 .anchor()
3960 .anchor
3961 .to_point(&multi_buffer_snapshot);
3962 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
3963 multi_buffer_visible_start
3964 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
3965 Bias::Left,
3966 );
3967 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
3968 multi_buffer_snapshot
3969 .range_to_buffer_ranges(multi_buffer_visible_range)
3970 .into_iter()
3971 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
3972 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
3973 let buffer_file = project::File::from_dyn(buffer.file())?;
3974 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
3975 let worktree_entry = buffer_worktree
3976 .read(cx)
3977 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
3978 if worktree_entry.is_ignored {
3979 return None;
3980 }
3981
3982 let language = buffer.language()?;
3983 if let Some(restrict_to_languages) = restrict_to_languages {
3984 if !restrict_to_languages.contains(language) {
3985 return None;
3986 }
3987 }
3988 Some((
3989 excerpt_id,
3990 (
3991 multi_buffer.buffer(buffer.remote_id()).unwrap(),
3992 buffer.version().clone(),
3993 excerpt_visible_range,
3994 ),
3995 ))
3996 })
3997 .collect()
3998 }
3999
4000 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4001 TextLayoutDetails {
4002 text_system: window.text_system().clone(),
4003 editor_style: self.style.clone().unwrap(),
4004 rem_size: window.rem_size(),
4005 scroll_anchor: self.scroll_manager.anchor(),
4006 visible_rows: self.visible_line_count(),
4007 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4008 }
4009 }
4010
4011 pub fn splice_inlays(
4012 &self,
4013 to_remove: &[InlayId],
4014 to_insert: Vec<Inlay>,
4015 cx: &mut Context<Self>,
4016 ) {
4017 self.display_map.update(cx, |display_map, cx| {
4018 display_map.splice_inlays(to_remove, to_insert, cx)
4019 });
4020 cx.notify();
4021 }
4022
4023 fn trigger_on_type_formatting(
4024 &self,
4025 input: String,
4026 window: &mut Window,
4027 cx: &mut Context<Self>,
4028 ) -> Option<Task<Result<()>>> {
4029 if input.len() != 1 {
4030 return None;
4031 }
4032
4033 let project = self.project.as_ref()?;
4034 let position = self.selections.newest_anchor().head();
4035 let (buffer, buffer_position) = self
4036 .buffer
4037 .read(cx)
4038 .text_anchor_for_position(position, cx)?;
4039
4040 let settings = language_settings::language_settings(
4041 buffer
4042 .read(cx)
4043 .language_at(buffer_position)
4044 .map(|l| l.name()),
4045 buffer.read(cx).file(),
4046 cx,
4047 );
4048 if !settings.use_on_type_format {
4049 return None;
4050 }
4051
4052 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4053 // hence we do LSP request & edit on host side only — add formats to host's history.
4054 let push_to_lsp_host_history = true;
4055 // If this is not the host, append its history with new edits.
4056 let push_to_client_history = project.read(cx).is_via_collab();
4057
4058 let on_type_formatting = project.update(cx, |project, cx| {
4059 project.on_type_format(
4060 buffer.clone(),
4061 buffer_position,
4062 input,
4063 push_to_lsp_host_history,
4064 cx,
4065 )
4066 });
4067 Some(cx.spawn_in(window, async move |editor, cx| {
4068 if let Some(transaction) = on_type_formatting.await? {
4069 if push_to_client_history {
4070 buffer
4071 .update(cx, |buffer, _| {
4072 buffer.push_transaction(transaction, Instant::now());
4073 })
4074 .ok();
4075 }
4076 editor.update(cx, |editor, cx| {
4077 editor.refresh_document_highlights(cx);
4078 })?;
4079 }
4080 Ok(())
4081 }))
4082 }
4083
4084 pub fn show_word_completions(
4085 &mut self,
4086 _: &ShowWordCompletions,
4087 window: &mut Window,
4088 cx: &mut Context<Self>,
4089 ) {
4090 self.open_completions_menu(true, None, window, cx);
4091 }
4092
4093 pub fn show_completions(
4094 &mut self,
4095 options: &ShowCompletions,
4096 window: &mut Window,
4097 cx: &mut Context<Self>,
4098 ) {
4099 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4100 }
4101
4102 fn open_completions_menu(
4103 &mut self,
4104 ignore_completion_provider: bool,
4105 trigger: Option<&str>,
4106 window: &mut Window,
4107 cx: &mut Context<Self>,
4108 ) {
4109 if self.pending_rename.is_some() {
4110 return;
4111 }
4112 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4113 return;
4114 }
4115
4116 let position = self.selections.newest_anchor().head();
4117 if position.diff_base_anchor.is_some() {
4118 return;
4119 }
4120 let (buffer, buffer_position) =
4121 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4122 output
4123 } else {
4124 return;
4125 };
4126 let buffer_snapshot = buffer.read(cx).snapshot();
4127 let show_completion_documentation = buffer_snapshot
4128 .settings_at(buffer_position, cx)
4129 .show_completion_documentation;
4130
4131 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4132
4133 let trigger_kind = match trigger {
4134 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4135 CompletionTriggerKind::TRIGGER_CHARACTER
4136 }
4137 _ => CompletionTriggerKind::INVOKED,
4138 };
4139 let completion_context = CompletionContext {
4140 trigger_character: trigger.and_then(|trigger| {
4141 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4142 Some(String::from(trigger))
4143 } else {
4144 None
4145 }
4146 }),
4147 trigger_kind,
4148 };
4149
4150 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4151 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4152 let word_to_exclude = buffer_snapshot
4153 .text_for_range(old_range.clone())
4154 .collect::<String>();
4155 (
4156 buffer_snapshot.anchor_before(old_range.start)
4157 ..buffer_snapshot.anchor_after(old_range.end),
4158 Some(word_to_exclude),
4159 )
4160 } else {
4161 (buffer_position..buffer_position, None)
4162 };
4163
4164 let completion_settings = language_settings(
4165 buffer_snapshot
4166 .language_at(buffer_position)
4167 .map(|language| language.name()),
4168 buffer_snapshot.file(),
4169 cx,
4170 )
4171 .completions;
4172
4173 // The document can be large, so stay in reasonable bounds when searching for words,
4174 // otherwise completion pop-up might be slow to appear.
4175 const WORD_LOOKUP_ROWS: u32 = 5_000;
4176 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4177 let min_word_search = buffer_snapshot.clip_point(
4178 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4179 Bias::Left,
4180 );
4181 let max_word_search = buffer_snapshot.clip_point(
4182 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4183 Bias::Right,
4184 );
4185 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4186 ..buffer_snapshot.point_to_offset(max_word_search);
4187
4188 let provider = self
4189 .completion_provider
4190 .as_ref()
4191 .filter(|_| !ignore_completion_provider);
4192 let skip_digits = query
4193 .as_ref()
4194 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4195
4196 let (mut words, provided_completions) = match provider {
4197 Some(provider) => {
4198 let completions =
4199 provider.completions(&buffer, buffer_position, completion_context, window, cx);
4200
4201 let words = match completion_settings.words {
4202 WordsCompletionMode::Disabled => Task::ready(HashMap::default()),
4203 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4204 .background_spawn(async move {
4205 buffer_snapshot.words_in_range(WordsQuery {
4206 fuzzy_contents: None,
4207 range: word_search_range,
4208 skip_digits,
4209 })
4210 }),
4211 };
4212
4213 (words, completions)
4214 }
4215 None => (
4216 cx.background_spawn(async move {
4217 buffer_snapshot.words_in_range(WordsQuery {
4218 fuzzy_contents: None,
4219 range: word_search_range,
4220 skip_digits,
4221 })
4222 }),
4223 Task::ready(Ok(None)),
4224 ),
4225 };
4226
4227 let sort_completions = provider
4228 .as_ref()
4229 .map_or(true, |provider| provider.sort_completions());
4230
4231 let id = post_inc(&mut self.next_completion_id);
4232 let task = cx.spawn_in(window, async move |editor, cx| {
4233 async move {
4234 editor.update(cx, |this, _| {
4235 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4236 })?;
4237
4238 let mut completions = Vec::new();
4239 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
4240 completions.extend(provided_completions);
4241 if completion_settings.words == WordsCompletionMode::Fallback {
4242 words = Task::ready(HashMap::default());
4243 }
4244 }
4245
4246 let mut words = words.await;
4247 if let Some(word_to_exclude) = &word_to_exclude {
4248 words.remove(word_to_exclude);
4249 }
4250 for lsp_completion in &completions {
4251 words.remove(&lsp_completion.new_text);
4252 }
4253 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
4254 old_range: old_range.clone(),
4255 new_text: word.clone(),
4256 label: CodeLabel::plain(word, None),
4257 documentation: None,
4258 source: CompletionSource::BufferWord {
4259 word_range,
4260 resolved: false,
4261 },
4262 confirm: None,
4263 }));
4264
4265 let menu = if completions.is_empty() {
4266 None
4267 } else {
4268 let mut menu = CompletionsMenu::new(
4269 id,
4270 sort_completions,
4271 show_completion_documentation,
4272 ignore_completion_provider,
4273 position,
4274 buffer.clone(),
4275 completions.into(),
4276 );
4277
4278 menu.filter(query.as_deref(), cx.background_executor().clone())
4279 .await;
4280
4281 menu.visible().then_some(menu)
4282 };
4283
4284 editor.update_in(cx, |editor, window, cx| {
4285 match editor.context_menu.borrow().as_ref() {
4286 None => {}
4287 Some(CodeContextMenu::Completions(prev_menu)) => {
4288 if prev_menu.id > id {
4289 return;
4290 }
4291 }
4292 _ => return,
4293 }
4294
4295 if editor.focus_handle.is_focused(window) && menu.is_some() {
4296 let mut menu = menu.unwrap();
4297 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
4298
4299 *editor.context_menu.borrow_mut() =
4300 Some(CodeContextMenu::Completions(menu));
4301
4302 if editor.show_edit_predictions_in_menu() {
4303 editor.update_visible_inline_completion(window, cx);
4304 } else {
4305 editor.discard_inline_completion(false, cx);
4306 }
4307
4308 cx.notify();
4309 } else if editor.completion_tasks.len() <= 1 {
4310 // If there are no more completion tasks and the last menu was
4311 // empty, we should hide it.
4312 let was_hidden = editor.hide_context_menu(window, cx).is_none();
4313 // If it was already hidden and we don't show inline
4314 // completions in the menu, we should also show the
4315 // inline-completion when available.
4316 if was_hidden && editor.show_edit_predictions_in_menu() {
4317 editor.update_visible_inline_completion(window, cx);
4318 }
4319 }
4320 })?;
4321
4322 anyhow::Ok(())
4323 }
4324 .log_err()
4325 .await
4326 });
4327
4328 self.completion_tasks.push((id, task));
4329 }
4330
4331 pub fn confirm_completion(
4332 &mut self,
4333 action: &ConfirmCompletion,
4334 window: &mut Window,
4335 cx: &mut Context<Self>,
4336 ) -> Option<Task<Result<()>>> {
4337 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
4338 }
4339
4340 pub fn compose_completion(
4341 &mut self,
4342 action: &ComposeCompletion,
4343 window: &mut Window,
4344 cx: &mut Context<Self>,
4345 ) -> Option<Task<Result<()>>> {
4346 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
4347 }
4348
4349 fn do_completion(
4350 &mut self,
4351 item_ix: Option<usize>,
4352 intent: CompletionIntent,
4353 window: &mut Window,
4354 cx: &mut Context<Editor>,
4355 ) -> Option<Task<std::result::Result<(), anyhow::Error>>> {
4356 use language::ToOffset as _;
4357
4358 let completions_menu =
4359 if let CodeContextMenu::Completions(menu) = self.hide_context_menu(window, cx)? {
4360 menu
4361 } else {
4362 return None;
4363 };
4364
4365 let entries = completions_menu.entries.borrow();
4366 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
4367 if self.show_edit_predictions_in_menu() {
4368 self.discard_inline_completion(true, cx);
4369 }
4370 let candidate_id = mat.candidate_id;
4371 drop(entries);
4372
4373 let buffer_handle = completions_menu.buffer;
4374 let completion = completions_menu
4375 .completions
4376 .borrow()
4377 .get(candidate_id)?
4378 .clone();
4379 cx.stop_propagation();
4380
4381 let snippet;
4382 let text;
4383
4384 if completion.is_snippet() {
4385 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
4386 text = snippet.as_ref().unwrap().text.clone();
4387 } else {
4388 snippet = None;
4389 text = completion.new_text.clone();
4390 };
4391 let selections = self.selections.all::<usize>(cx);
4392 let buffer = buffer_handle.read(cx);
4393 let old_range = completion.old_range.to_offset(buffer);
4394 let old_text = buffer.text_for_range(old_range.clone()).collect::<String>();
4395
4396 let newest_selection = self.selections.newest_anchor();
4397 if newest_selection.start.buffer_id != Some(buffer_handle.read(cx).remote_id()) {
4398 return None;
4399 }
4400
4401 let lookbehind = newest_selection
4402 .start
4403 .text_anchor
4404 .to_offset(buffer)
4405 .saturating_sub(old_range.start);
4406 let lookahead = old_range
4407 .end
4408 .saturating_sub(newest_selection.end.text_anchor.to_offset(buffer));
4409 let mut common_prefix_len = old_text
4410 .bytes()
4411 .zip(text.bytes())
4412 .take_while(|(a, b)| a == b)
4413 .count();
4414
4415 let snapshot = self.buffer.read(cx).snapshot(cx);
4416 let mut range_to_replace: Option<Range<isize>> = None;
4417 let mut ranges = Vec::new();
4418 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4419 for selection in &selections {
4420 if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) {
4421 let start = selection.start.saturating_sub(lookbehind);
4422 let end = selection.end + lookahead;
4423 if selection.id == newest_selection.id {
4424 range_to_replace = Some(
4425 ((start + common_prefix_len) as isize - selection.start as isize)
4426 ..(end as isize - selection.start as isize),
4427 );
4428 }
4429 ranges.push(start + common_prefix_len..end);
4430 } else {
4431 common_prefix_len = 0;
4432 ranges.clear();
4433 ranges.extend(selections.iter().map(|s| {
4434 if s.id == newest_selection.id {
4435 range_to_replace = Some(
4436 old_range.start.to_offset_utf16(&snapshot).0 as isize
4437 - selection.start as isize
4438 ..old_range.end.to_offset_utf16(&snapshot).0 as isize
4439 - selection.start as isize,
4440 );
4441 old_range.clone()
4442 } else {
4443 s.start..s.end
4444 }
4445 }));
4446 break;
4447 }
4448 if !self.linked_edit_ranges.is_empty() {
4449 let start_anchor = snapshot.anchor_before(selection.head());
4450 let end_anchor = snapshot.anchor_after(selection.tail());
4451 if let Some(ranges) = self
4452 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
4453 {
4454 for (buffer, edits) in ranges {
4455 linked_edits.entry(buffer.clone()).or_default().extend(
4456 edits
4457 .into_iter()
4458 .map(|range| (range, text[common_prefix_len..].to_owned())),
4459 );
4460 }
4461 }
4462 }
4463 }
4464 let text = &text[common_prefix_len..];
4465
4466 cx.emit(EditorEvent::InputHandled {
4467 utf16_range_to_replace: range_to_replace,
4468 text: text.into(),
4469 });
4470
4471 self.transact(window, cx, |this, window, cx| {
4472 if let Some(mut snippet) = snippet {
4473 snippet.text = text.to_string();
4474 for tabstop in snippet
4475 .tabstops
4476 .iter_mut()
4477 .flat_map(|tabstop| tabstop.ranges.iter_mut())
4478 {
4479 tabstop.start -= common_prefix_len as isize;
4480 tabstop.end -= common_prefix_len as isize;
4481 }
4482
4483 this.insert_snippet(&ranges, snippet, window, cx).log_err();
4484 } else {
4485 this.buffer.update(cx, |buffer, cx| {
4486 buffer.edit(
4487 ranges.iter().map(|range| (range.clone(), text)),
4488 this.autoindent_mode.clone(),
4489 cx,
4490 );
4491 });
4492 }
4493 for (buffer, edits) in linked_edits {
4494 buffer.update(cx, |buffer, cx| {
4495 let snapshot = buffer.snapshot();
4496 let edits = edits
4497 .into_iter()
4498 .map(|(range, text)| {
4499 use text::ToPoint as TP;
4500 let end_point = TP::to_point(&range.end, &snapshot);
4501 let start_point = TP::to_point(&range.start, &snapshot);
4502 (start_point..end_point, text)
4503 })
4504 .sorted_by_key(|(range, _)| range.start)
4505 .collect::<Vec<_>>();
4506 buffer.edit(edits, None, cx);
4507 })
4508 }
4509
4510 this.refresh_inline_completion(true, false, window, cx);
4511 });
4512
4513 let show_new_completions_on_confirm = completion
4514 .confirm
4515 .as_ref()
4516 .map_or(false, |confirm| confirm(intent, window, cx));
4517 if show_new_completions_on_confirm {
4518 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
4519 }
4520
4521 let provider = self.completion_provider.as_ref()?;
4522 drop(completion);
4523 let apply_edits = provider.apply_additional_edits_for_completion(
4524 buffer_handle,
4525 completions_menu.completions.clone(),
4526 candidate_id,
4527 true,
4528 cx,
4529 );
4530
4531 let editor_settings = EditorSettings::get_global(cx);
4532 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
4533 // After the code completion is finished, users often want to know what signatures are needed.
4534 // so we should automatically call signature_help
4535 self.show_signature_help(&ShowSignatureHelp, window, cx);
4536 }
4537
4538 Some(cx.foreground_executor().spawn(async move {
4539 apply_edits.await?;
4540 Ok(())
4541 }))
4542 }
4543
4544 pub fn toggle_code_actions(
4545 &mut self,
4546 action: &ToggleCodeActions,
4547 window: &mut Window,
4548 cx: &mut Context<Self>,
4549 ) {
4550 let mut context_menu = self.context_menu.borrow_mut();
4551 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
4552 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
4553 // Toggle if we're selecting the same one
4554 *context_menu = None;
4555 cx.notify();
4556 return;
4557 } else {
4558 // Otherwise, clear it and start a new one
4559 *context_menu = None;
4560 cx.notify();
4561 }
4562 }
4563 drop(context_menu);
4564 let snapshot = self.snapshot(window, cx);
4565 let deployed_from_indicator = action.deployed_from_indicator;
4566 let mut task = self.code_actions_task.take();
4567 let action = action.clone();
4568 cx.spawn_in(window, async move |editor, cx| {
4569 while let Some(prev_task) = task {
4570 prev_task.await.log_err();
4571 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
4572 }
4573
4574 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
4575 if editor.focus_handle.is_focused(window) {
4576 let multibuffer_point = action
4577 .deployed_from_indicator
4578 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
4579 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
4580 let (buffer, buffer_row) = snapshot
4581 .buffer_snapshot
4582 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
4583 .and_then(|(buffer_snapshot, range)| {
4584 editor
4585 .buffer
4586 .read(cx)
4587 .buffer(buffer_snapshot.remote_id())
4588 .map(|buffer| (buffer, range.start.row))
4589 })?;
4590 let (_, code_actions) = editor
4591 .available_code_actions
4592 .clone()
4593 .and_then(|(location, code_actions)| {
4594 let snapshot = location.buffer.read(cx).snapshot();
4595 let point_range = location.range.to_point(&snapshot);
4596 let point_range = point_range.start.row..=point_range.end.row;
4597 if point_range.contains(&buffer_row) {
4598 Some((location, code_actions))
4599 } else {
4600 None
4601 }
4602 })
4603 .unzip();
4604 let buffer_id = buffer.read(cx).remote_id();
4605 let tasks = editor
4606 .tasks
4607 .get(&(buffer_id, buffer_row))
4608 .map(|t| Arc::new(t.to_owned()));
4609 if tasks.is_none() && code_actions.is_none() {
4610 return None;
4611 }
4612
4613 editor.completion_tasks.clear();
4614 editor.discard_inline_completion(false, cx);
4615 let task_context =
4616 tasks
4617 .as_ref()
4618 .zip(editor.project.clone())
4619 .map(|(tasks, project)| {
4620 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
4621 });
4622
4623 Some(cx.spawn_in(window, async move |editor, cx| {
4624 let task_context = match task_context {
4625 Some(task_context) => task_context.await,
4626 None => None,
4627 };
4628 let resolved_tasks =
4629 tasks.zip(task_context).map(|(tasks, task_context)| {
4630 Rc::new(ResolvedTasks {
4631 templates: tasks.resolve(&task_context).collect(),
4632 position: snapshot.buffer_snapshot.anchor_before(Point::new(
4633 multibuffer_point.row,
4634 tasks.column,
4635 )),
4636 })
4637 });
4638 let spawn_straight_away = resolved_tasks
4639 .as_ref()
4640 .map_or(false, |tasks| tasks.templates.len() == 1)
4641 && code_actions
4642 .as_ref()
4643 .map_or(true, |actions| actions.is_empty());
4644 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
4645 *editor.context_menu.borrow_mut() =
4646 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
4647 buffer,
4648 actions: CodeActionContents {
4649 tasks: resolved_tasks,
4650 actions: code_actions,
4651 },
4652 selected_item: Default::default(),
4653 scroll_handle: UniformListScrollHandle::default(),
4654 deployed_from_indicator,
4655 }));
4656 if spawn_straight_away {
4657 if let Some(task) = editor.confirm_code_action(
4658 &ConfirmCodeAction { item_ix: Some(0) },
4659 window,
4660 cx,
4661 ) {
4662 cx.notify();
4663 return task;
4664 }
4665 }
4666 cx.notify();
4667 Task::ready(Ok(()))
4668 }) {
4669 task.await
4670 } else {
4671 Ok(())
4672 }
4673 }))
4674 } else {
4675 Some(Task::ready(Ok(())))
4676 }
4677 })?;
4678 if let Some(task) = spawned_test_task {
4679 task.await?;
4680 }
4681
4682 Ok::<_, anyhow::Error>(())
4683 })
4684 .detach_and_log_err(cx);
4685 }
4686
4687 pub fn confirm_code_action(
4688 &mut self,
4689 action: &ConfirmCodeAction,
4690 window: &mut Window,
4691 cx: &mut Context<Self>,
4692 ) -> Option<Task<Result<()>>> {
4693 let actions_menu =
4694 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
4695 menu
4696 } else {
4697 return None;
4698 };
4699 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
4700 let action = actions_menu.actions.get(action_ix)?;
4701 let title = action.label();
4702 let buffer = actions_menu.buffer;
4703 let workspace = self.workspace()?;
4704
4705 match action {
4706 CodeActionsItem::Task(task_source_kind, resolved_task) => {
4707 workspace.update(cx, |workspace, cx| {
4708 workspace::tasks::schedule_resolved_task(
4709 workspace,
4710 task_source_kind,
4711 resolved_task,
4712 false,
4713 cx,
4714 );
4715
4716 Some(Task::ready(Ok(())))
4717 })
4718 }
4719 CodeActionsItem::CodeAction {
4720 excerpt_id,
4721 action,
4722 provider,
4723 } => {
4724 let apply_code_action =
4725 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
4726 let workspace = workspace.downgrade();
4727 Some(cx.spawn_in(window, async move |editor, cx| {
4728 let project_transaction = apply_code_action.await?;
4729 Self::open_project_transaction(
4730 &editor,
4731 workspace,
4732 project_transaction,
4733 title,
4734 cx,
4735 )
4736 .await
4737 }))
4738 }
4739 }
4740 }
4741
4742 pub async fn open_project_transaction(
4743 this: &WeakEntity<Editor>,
4744 workspace: WeakEntity<Workspace>,
4745 transaction: ProjectTransaction,
4746 title: String,
4747 cx: &mut AsyncWindowContext,
4748 ) -> Result<()> {
4749 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
4750 cx.update(|_, cx| {
4751 entries.sort_unstable_by_key(|(buffer, _)| {
4752 buffer.read(cx).file().map(|f| f.path().clone())
4753 });
4754 })?;
4755
4756 // If the project transaction's edits are all contained within this editor, then
4757 // avoid opening a new editor to display them.
4758
4759 if let Some((buffer, transaction)) = entries.first() {
4760 if entries.len() == 1 {
4761 let excerpt = this.update(cx, |editor, cx| {
4762 editor
4763 .buffer()
4764 .read(cx)
4765 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
4766 })?;
4767 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
4768 if excerpted_buffer == *buffer {
4769 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
4770 let excerpt_range = excerpt_range.to_offset(buffer);
4771 buffer
4772 .edited_ranges_for_transaction::<usize>(transaction)
4773 .all(|range| {
4774 excerpt_range.start <= range.start
4775 && excerpt_range.end >= range.end
4776 })
4777 })?;
4778
4779 if all_edits_within_excerpt {
4780 return Ok(());
4781 }
4782 }
4783 }
4784 }
4785 } else {
4786 return Ok(());
4787 }
4788
4789 let mut ranges_to_highlight = Vec::new();
4790 let excerpt_buffer = cx.new(|cx| {
4791 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
4792 for (buffer_handle, transaction) in &entries {
4793 let buffer = buffer_handle.read(cx);
4794 ranges_to_highlight.extend(
4795 multibuffer.push_excerpts_with_context_lines(
4796 buffer_handle.clone(),
4797 buffer
4798 .edited_ranges_for_transaction::<usize>(transaction)
4799 .collect(),
4800 DEFAULT_MULTIBUFFER_CONTEXT,
4801 cx,
4802 ),
4803 );
4804 }
4805 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
4806 multibuffer
4807 })?;
4808
4809 workspace.update_in(cx, |workspace, window, cx| {
4810 let project = workspace.project().clone();
4811 let editor =
4812 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
4813 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
4814 editor.update(cx, |editor, cx| {
4815 editor.highlight_background::<Self>(
4816 &ranges_to_highlight,
4817 |theme| theme.editor_highlighted_line_background,
4818 cx,
4819 );
4820 });
4821 })?;
4822
4823 Ok(())
4824 }
4825
4826 pub fn clear_code_action_providers(&mut self) {
4827 self.code_action_providers.clear();
4828 self.available_code_actions.take();
4829 }
4830
4831 pub fn add_code_action_provider(
4832 &mut self,
4833 provider: Rc<dyn CodeActionProvider>,
4834 window: &mut Window,
4835 cx: &mut Context<Self>,
4836 ) {
4837 if self
4838 .code_action_providers
4839 .iter()
4840 .any(|existing_provider| existing_provider.id() == provider.id())
4841 {
4842 return;
4843 }
4844
4845 self.code_action_providers.push(provider);
4846 self.refresh_code_actions(window, cx);
4847 }
4848
4849 pub fn remove_code_action_provider(
4850 &mut self,
4851 id: Arc<str>,
4852 window: &mut Window,
4853 cx: &mut Context<Self>,
4854 ) {
4855 self.code_action_providers
4856 .retain(|provider| provider.id() != id);
4857 self.refresh_code_actions(window, cx);
4858 }
4859
4860 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
4861 let buffer = self.buffer.read(cx);
4862 let newest_selection = self.selections.newest_anchor().clone();
4863 if newest_selection.head().diff_base_anchor.is_some() {
4864 return None;
4865 }
4866 let (start_buffer, start) = buffer.text_anchor_for_position(newest_selection.start, cx)?;
4867 let (end_buffer, end) = buffer.text_anchor_for_position(newest_selection.end, cx)?;
4868 if start_buffer != end_buffer {
4869 return None;
4870 }
4871
4872 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
4873 cx.background_executor()
4874 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
4875 .await;
4876
4877 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
4878 let providers = this.code_action_providers.clone();
4879 let tasks = this
4880 .code_action_providers
4881 .iter()
4882 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
4883 .collect::<Vec<_>>();
4884 (providers, tasks)
4885 })?;
4886
4887 let mut actions = Vec::new();
4888 for (provider, provider_actions) in
4889 providers.into_iter().zip(future::join_all(tasks).await)
4890 {
4891 if let Some(provider_actions) = provider_actions.log_err() {
4892 actions.extend(provider_actions.into_iter().map(|action| {
4893 AvailableCodeAction {
4894 excerpt_id: newest_selection.start.excerpt_id,
4895 action,
4896 provider: provider.clone(),
4897 }
4898 }));
4899 }
4900 }
4901
4902 this.update(cx, |this, cx| {
4903 this.available_code_actions = if actions.is_empty() {
4904 None
4905 } else {
4906 Some((
4907 Location {
4908 buffer: start_buffer,
4909 range: start..end,
4910 },
4911 actions.into(),
4912 ))
4913 };
4914 cx.notify();
4915 })
4916 }));
4917 None
4918 }
4919
4920 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4921 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
4922 self.show_git_blame_inline = false;
4923
4924 self.show_git_blame_inline_delay_task =
4925 Some(cx.spawn_in(window, async move |this, cx| {
4926 cx.background_executor().timer(delay).await;
4927
4928 this.update(cx, |this, cx| {
4929 this.show_git_blame_inline = true;
4930 cx.notify();
4931 })
4932 .log_err();
4933 }));
4934 }
4935 }
4936
4937 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
4938 if self.pending_rename.is_some() {
4939 return None;
4940 }
4941
4942 let provider = self.semantics_provider.clone()?;
4943 let buffer = self.buffer.read(cx);
4944 let newest_selection = self.selections.newest_anchor().clone();
4945 let cursor_position = newest_selection.head();
4946 let (cursor_buffer, cursor_buffer_position) =
4947 buffer.text_anchor_for_position(cursor_position, cx)?;
4948 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
4949 if cursor_buffer != tail_buffer {
4950 return None;
4951 }
4952 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
4953 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
4954 cx.background_executor()
4955 .timer(Duration::from_millis(debounce))
4956 .await;
4957
4958 let highlights = if let Some(highlights) = cx
4959 .update(|cx| {
4960 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
4961 })
4962 .ok()
4963 .flatten()
4964 {
4965 highlights.await.log_err()
4966 } else {
4967 None
4968 };
4969
4970 if let Some(highlights) = highlights {
4971 this.update(cx, |this, cx| {
4972 if this.pending_rename.is_some() {
4973 return;
4974 }
4975
4976 let buffer_id = cursor_position.buffer_id;
4977 let buffer = this.buffer.read(cx);
4978 if !buffer
4979 .text_anchor_for_position(cursor_position, cx)
4980 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
4981 {
4982 return;
4983 }
4984
4985 let cursor_buffer_snapshot = cursor_buffer.read(cx);
4986 let mut write_ranges = Vec::new();
4987 let mut read_ranges = Vec::new();
4988 for highlight in highlights {
4989 for (excerpt_id, excerpt_range) in
4990 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
4991 {
4992 let start = highlight
4993 .range
4994 .start
4995 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
4996 let end = highlight
4997 .range
4998 .end
4999 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5000 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5001 continue;
5002 }
5003
5004 let range = Anchor {
5005 buffer_id,
5006 excerpt_id,
5007 text_anchor: start,
5008 diff_base_anchor: None,
5009 }..Anchor {
5010 buffer_id,
5011 excerpt_id,
5012 text_anchor: end,
5013 diff_base_anchor: None,
5014 };
5015 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5016 write_ranges.push(range);
5017 } else {
5018 read_ranges.push(range);
5019 }
5020 }
5021 }
5022
5023 this.highlight_background::<DocumentHighlightRead>(
5024 &read_ranges,
5025 |theme| theme.editor_document_highlight_read_background,
5026 cx,
5027 );
5028 this.highlight_background::<DocumentHighlightWrite>(
5029 &write_ranges,
5030 |theme| theme.editor_document_highlight_write_background,
5031 cx,
5032 );
5033 cx.notify();
5034 })
5035 .log_err();
5036 }
5037 }));
5038 None
5039 }
5040
5041 pub fn refresh_selected_text_highlights(
5042 &mut self,
5043 window: &mut Window,
5044 cx: &mut Context<Editor>,
5045 ) {
5046 if matches!(self.mode, EditorMode::SingleLine { .. }) {
5047 return;
5048 }
5049 self.selection_highlight_task.take();
5050 if !EditorSettings::get_global(cx).selection_highlight {
5051 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5052 return;
5053 }
5054 if self.selections.count() != 1 || self.selections.line_mode {
5055 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5056 return;
5057 }
5058 let selection = self.selections.newest::<Point>(cx);
5059 if selection.is_empty() || selection.start.row != selection.end.row {
5060 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5061 return;
5062 }
5063 let debounce = EditorSettings::get_global(cx).selection_highlight_debounce;
5064 self.selection_highlight_task = Some(cx.spawn_in(window, async move |editor, cx| {
5065 cx.background_executor()
5066 .timer(Duration::from_millis(debounce))
5067 .await;
5068 let Some(Some(matches_task)) = editor
5069 .update_in(cx, |editor, _, cx| {
5070 if editor.selections.count() != 1 || editor.selections.line_mode {
5071 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5072 return None;
5073 }
5074 let selection = editor.selections.newest::<Point>(cx);
5075 if selection.is_empty() || selection.start.row != selection.end.row {
5076 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5077 return None;
5078 }
5079 let buffer = editor.buffer().read(cx).snapshot(cx);
5080 let query = buffer.text_for_range(selection.range()).collect::<String>();
5081 if query.trim().is_empty() {
5082 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5083 return None;
5084 }
5085 Some(cx.background_spawn(async move {
5086 let mut ranges = Vec::new();
5087 let selection_anchors = selection.range().to_anchors(&buffer);
5088 for range in [buffer.anchor_before(0)..buffer.anchor_after(buffer.len())] {
5089 for (search_buffer, search_range, excerpt_id) in
5090 buffer.range_to_buffer_ranges(range)
5091 {
5092 ranges.extend(
5093 project::search::SearchQuery::text(
5094 query.clone(),
5095 false,
5096 false,
5097 false,
5098 Default::default(),
5099 Default::default(),
5100 None,
5101 )
5102 .unwrap()
5103 .search(search_buffer, Some(search_range.clone()))
5104 .await
5105 .into_iter()
5106 .filter_map(
5107 |match_range| {
5108 let start = search_buffer.anchor_after(
5109 search_range.start + match_range.start,
5110 );
5111 let end = search_buffer.anchor_before(
5112 search_range.start + match_range.end,
5113 );
5114 let range = Anchor::range_in_buffer(
5115 excerpt_id,
5116 search_buffer.remote_id(),
5117 start..end,
5118 );
5119 (range != selection_anchors).then_some(range)
5120 },
5121 ),
5122 );
5123 }
5124 }
5125 ranges
5126 }))
5127 })
5128 .log_err()
5129 else {
5130 return;
5131 };
5132 let matches = matches_task.await;
5133 editor
5134 .update_in(cx, |editor, _, cx| {
5135 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5136 if !matches.is_empty() {
5137 editor.highlight_background::<SelectedTextHighlight>(
5138 &matches,
5139 |theme| theme.editor_document_highlight_bracket_background,
5140 cx,
5141 )
5142 }
5143 })
5144 .log_err();
5145 }));
5146 }
5147
5148 pub fn refresh_inline_completion(
5149 &mut self,
5150 debounce: bool,
5151 user_requested: bool,
5152 window: &mut Window,
5153 cx: &mut Context<Self>,
5154 ) -> Option<()> {
5155 let provider = self.edit_prediction_provider()?;
5156 let cursor = self.selections.newest_anchor().head();
5157 let (buffer, cursor_buffer_position) =
5158 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5159
5160 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
5161 self.discard_inline_completion(false, cx);
5162 return None;
5163 }
5164
5165 if !user_requested
5166 && (!self.should_show_edit_predictions()
5167 || !self.is_focused(window)
5168 || buffer.read(cx).is_empty())
5169 {
5170 self.discard_inline_completion(false, cx);
5171 return None;
5172 }
5173
5174 self.update_visible_inline_completion(window, cx);
5175 provider.refresh(
5176 self.project.clone(),
5177 buffer,
5178 cursor_buffer_position,
5179 debounce,
5180 cx,
5181 );
5182 Some(())
5183 }
5184
5185 fn show_edit_predictions_in_menu(&self) -> bool {
5186 match self.edit_prediction_settings {
5187 EditPredictionSettings::Disabled => false,
5188 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
5189 }
5190 }
5191
5192 pub fn edit_predictions_enabled(&self) -> bool {
5193 match self.edit_prediction_settings {
5194 EditPredictionSettings::Disabled => false,
5195 EditPredictionSettings::Enabled { .. } => true,
5196 }
5197 }
5198
5199 fn edit_prediction_requires_modifier(&self) -> bool {
5200 match self.edit_prediction_settings {
5201 EditPredictionSettings::Disabled => false,
5202 EditPredictionSettings::Enabled {
5203 preview_requires_modifier,
5204 ..
5205 } => preview_requires_modifier,
5206 }
5207 }
5208
5209 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
5210 if self.edit_prediction_provider.is_none() {
5211 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5212 } else {
5213 let selection = self.selections.newest_anchor();
5214 let cursor = selection.head();
5215
5216 if let Some((buffer, cursor_buffer_position)) =
5217 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5218 {
5219 self.edit_prediction_settings =
5220 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5221 }
5222 }
5223 }
5224
5225 fn edit_prediction_settings_at_position(
5226 &self,
5227 buffer: &Entity<Buffer>,
5228 buffer_position: language::Anchor,
5229 cx: &App,
5230 ) -> EditPredictionSettings {
5231 if self.mode != EditorMode::Full
5232 || !self.show_inline_completions_override.unwrap_or(true)
5233 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
5234 {
5235 return EditPredictionSettings::Disabled;
5236 }
5237
5238 let buffer = buffer.read(cx);
5239
5240 let file = buffer.file();
5241
5242 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
5243 return EditPredictionSettings::Disabled;
5244 };
5245
5246 let by_provider = matches!(
5247 self.menu_inline_completions_policy,
5248 MenuInlineCompletionsPolicy::ByProvider
5249 );
5250
5251 let show_in_menu = by_provider
5252 && self
5253 .edit_prediction_provider
5254 .as_ref()
5255 .map_or(false, |provider| {
5256 provider.provider.show_completions_in_menu()
5257 });
5258
5259 let preview_requires_modifier =
5260 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
5261
5262 EditPredictionSettings::Enabled {
5263 show_in_menu,
5264 preview_requires_modifier,
5265 }
5266 }
5267
5268 fn should_show_edit_predictions(&self) -> bool {
5269 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
5270 }
5271
5272 pub fn edit_prediction_preview_is_active(&self) -> bool {
5273 matches!(
5274 self.edit_prediction_preview,
5275 EditPredictionPreview::Active { .. }
5276 )
5277 }
5278
5279 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
5280 let cursor = self.selections.newest_anchor().head();
5281 if let Some((buffer, cursor_position)) =
5282 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5283 {
5284 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
5285 } else {
5286 false
5287 }
5288 }
5289
5290 fn edit_predictions_enabled_in_buffer(
5291 &self,
5292 buffer: &Entity<Buffer>,
5293 buffer_position: language::Anchor,
5294 cx: &App,
5295 ) -> bool {
5296 maybe!({
5297 if self.read_only(cx) {
5298 return Some(false);
5299 }
5300 let provider = self.edit_prediction_provider()?;
5301 if !provider.is_enabled(&buffer, buffer_position, cx) {
5302 return Some(false);
5303 }
5304 let buffer = buffer.read(cx);
5305 let Some(file) = buffer.file() else {
5306 return Some(true);
5307 };
5308 let settings = all_language_settings(Some(file), cx);
5309 Some(settings.edit_predictions_enabled_for_file(file, cx))
5310 })
5311 .unwrap_or(false)
5312 }
5313
5314 fn cycle_inline_completion(
5315 &mut self,
5316 direction: Direction,
5317 window: &mut Window,
5318 cx: &mut Context<Self>,
5319 ) -> Option<()> {
5320 let provider = self.edit_prediction_provider()?;
5321 let cursor = self.selections.newest_anchor().head();
5322 let (buffer, cursor_buffer_position) =
5323 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5324 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
5325 return None;
5326 }
5327
5328 provider.cycle(buffer, cursor_buffer_position, direction, cx);
5329 self.update_visible_inline_completion(window, cx);
5330
5331 Some(())
5332 }
5333
5334 pub fn show_inline_completion(
5335 &mut self,
5336 _: &ShowEditPrediction,
5337 window: &mut Window,
5338 cx: &mut Context<Self>,
5339 ) {
5340 if !self.has_active_inline_completion() {
5341 self.refresh_inline_completion(false, true, window, cx);
5342 return;
5343 }
5344
5345 self.update_visible_inline_completion(window, cx);
5346 }
5347
5348 pub fn display_cursor_names(
5349 &mut self,
5350 _: &DisplayCursorNames,
5351 window: &mut Window,
5352 cx: &mut Context<Self>,
5353 ) {
5354 self.show_cursor_names(window, cx);
5355 }
5356
5357 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5358 self.show_cursor_names = true;
5359 cx.notify();
5360 cx.spawn_in(window, async move |this, cx| {
5361 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
5362 this.update(cx, |this, cx| {
5363 this.show_cursor_names = false;
5364 cx.notify()
5365 })
5366 .ok()
5367 })
5368 .detach();
5369 }
5370
5371 pub fn next_edit_prediction(
5372 &mut self,
5373 _: &NextEditPrediction,
5374 window: &mut Window,
5375 cx: &mut Context<Self>,
5376 ) {
5377 if self.has_active_inline_completion() {
5378 self.cycle_inline_completion(Direction::Next, window, cx);
5379 } else {
5380 let is_copilot_disabled = self
5381 .refresh_inline_completion(false, true, window, cx)
5382 .is_none();
5383 if is_copilot_disabled {
5384 cx.propagate();
5385 }
5386 }
5387 }
5388
5389 pub fn previous_edit_prediction(
5390 &mut self,
5391 _: &PreviousEditPrediction,
5392 window: &mut Window,
5393 cx: &mut Context<Self>,
5394 ) {
5395 if self.has_active_inline_completion() {
5396 self.cycle_inline_completion(Direction::Prev, window, cx);
5397 } else {
5398 let is_copilot_disabled = self
5399 .refresh_inline_completion(false, true, window, cx)
5400 .is_none();
5401 if is_copilot_disabled {
5402 cx.propagate();
5403 }
5404 }
5405 }
5406
5407 pub fn accept_edit_prediction(
5408 &mut self,
5409 _: &AcceptEditPrediction,
5410 window: &mut Window,
5411 cx: &mut Context<Self>,
5412 ) {
5413 if self.show_edit_predictions_in_menu() {
5414 self.hide_context_menu(window, cx);
5415 }
5416
5417 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5418 return;
5419 };
5420
5421 self.report_inline_completion_event(
5422 active_inline_completion.completion_id.clone(),
5423 true,
5424 cx,
5425 );
5426
5427 match &active_inline_completion.completion {
5428 InlineCompletion::Move { target, .. } => {
5429 let target = *target;
5430
5431 if let Some(position_map) = &self.last_position_map {
5432 if position_map
5433 .visible_row_range
5434 .contains(&target.to_display_point(&position_map.snapshot).row())
5435 || !self.edit_prediction_requires_modifier()
5436 {
5437 self.unfold_ranges(&[target..target], true, false, cx);
5438 // Note that this is also done in vim's handler of the Tab action.
5439 self.change_selections(
5440 Some(Autoscroll::newest()),
5441 window,
5442 cx,
5443 |selections| {
5444 selections.select_anchor_ranges([target..target]);
5445 },
5446 );
5447 self.clear_row_highlights::<EditPredictionPreview>();
5448
5449 self.edit_prediction_preview
5450 .set_previous_scroll_position(None);
5451 } else {
5452 self.edit_prediction_preview
5453 .set_previous_scroll_position(Some(
5454 position_map.snapshot.scroll_anchor,
5455 ));
5456
5457 self.highlight_rows::<EditPredictionPreview>(
5458 target..target,
5459 cx.theme().colors().editor_highlighted_line_background,
5460 true,
5461 cx,
5462 );
5463 self.request_autoscroll(Autoscroll::fit(), cx);
5464 }
5465 }
5466 }
5467 InlineCompletion::Edit { edits, .. } => {
5468 if let Some(provider) = self.edit_prediction_provider() {
5469 provider.accept(cx);
5470 }
5471
5472 let snapshot = self.buffer.read(cx).snapshot(cx);
5473 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
5474
5475 self.buffer.update(cx, |buffer, cx| {
5476 buffer.edit(edits.iter().cloned(), None, cx)
5477 });
5478
5479 self.change_selections(None, window, cx, |s| {
5480 s.select_anchor_ranges([last_edit_end..last_edit_end])
5481 });
5482
5483 self.update_visible_inline_completion(window, cx);
5484 if self.active_inline_completion.is_none() {
5485 self.refresh_inline_completion(true, true, window, cx);
5486 }
5487
5488 cx.notify();
5489 }
5490 }
5491
5492 self.edit_prediction_requires_modifier_in_indent_conflict = false;
5493 }
5494
5495 pub fn accept_partial_inline_completion(
5496 &mut self,
5497 _: &AcceptPartialEditPrediction,
5498 window: &mut Window,
5499 cx: &mut Context<Self>,
5500 ) {
5501 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5502 return;
5503 };
5504 if self.selections.count() != 1 {
5505 return;
5506 }
5507
5508 self.report_inline_completion_event(
5509 active_inline_completion.completion_id.clone(),
5510 true,
5511 cx,
5512 );
5513
5514 match &active_inline_completion.completion {
5515 InlineCompletion::Move { target, .. } => {
5516 let target = *target;
5517 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
5518 selections.select_anchor_ranges([target..target]);
5519 });
5520 }
5521 InlineCompletion::Edit { edits, .. } => {
5522 // Find an insertion that starts at the cursor position.
5523 let snapshot = self.buffer.read(cx).snapshot(cx);
5524 let cursor_offset = self.selections.newest::<usize>(cx).head();
5525 let insertion = edits.iter().find_map(|(range, text)| {
5526 let range = range.to_offset(&snapshot);
5527 if range.is_empty() && range.start == cursor_offset {
5528 Some(text)
5529 } else {
5530 None
5531 }
5532 });
5533
5534 if let Some(text) = insertion {
5535 let mut partial_completion = text
5536 .chars()
5537 .by_ref()
5538 .take_while(|c| c.is_alphabetic())
5539 .collect::<String>();
5540 if partial_completion.is_empty() {
5541 partial_completion = text
5542 .chars()
5543 .by_ref()
5544 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
5545 .collect::<String>();
5546 }
5547
5548 cx.emit(EditorEvent::InputHandled {
5549 utf16_range_to_replace: None,
5550 text: partial_completion.clone().into(),
5551 });
5552
5553 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
5554
5555 self.refresh_inline_completion(true, true, window, cx);
5556 cx.notify();
5557 } else {
5558 self.accept_edit_prediction(&Default::default(), window, cx);
5559 }
5560 }
5561 }
5562 }
5563
5564 fn discard_inline_completion(
5565 &mut self,
5566 should_report_inline_completion_event: bool,
5567 cx: &mut Context<Self>,
5568 ) -> bool {
5569 if should_report_inline_completion_event {
5570 let completion_id = self
5571 .active_inline_completion
5572 .as_ref()
5573 .and_then(|active_completion| active_completion.completion_id.clone());
5574
5575 self.report_inline_completion_event(completion_id, false, cx);
5576 }
5577
5578 if let Some(provider) = self.edit_prediction_provider() {
5579 provider.discard(cx);
5580 }
5581
5582 self.take_active_inline_completion(cx)
5583 }
5584
5585 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
5586 let Some(provider) = self.edit_prediction_provider() else {
5587 return;
5588 };
5589
5590 let Some((_, buffer, _)) = self
5591 .buffer
5592 .read(cx)
5593 .excerpt_containing(self.selections.newest_anchor().head(), cx)
5594 else {
5595 return;
5596 };
5597
5598 let extension = buffer
5599 .read(cx)
5600 .file()
5601 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
5602
5603 let event_type = match accepted {
5604 true => "Edit Prediction Accepted",
5605 false => "Edit Prediction Discarded",
5606 };
5607 telemetry::event!(
5608 event_type,
5609 provider = provider.name(),
5610 prediction_id = id,
5611 suggestion_accepted = accepted,
5612 file_extension = extension,
5613 );
5614 }
5615
5616 pub fn has_active_inline_completion(&self) -> bool {
5617 self.active_inline_completion.is_some()
5618 }
5619
5620 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
5621 let Some(active_inline_completion) = self.active_inline_completion.take() else {
5622 return false;
5623 };
5624
5625 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
5626 self.clear_highlights::<InlineCompletionHighlight>(cx);
5627 self.stale_inline_completion_in_menu = Some(active_inline_completion);
5628 true
5629 }
5630
5631 /// Returns true when we're displaying the edit prediction popover below the cursor
5632 /// like we are not previewing and the LSP autocomplete menu is visible
5633 /// or we are in `when_holding_modifier` mode.
5634 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
5635 if self.edit_prediction_preview_is_active()
5636 || !self.show_edit_predictions_in_menu()
5637 || !self.edit_predictions_enabled()
5638 {
5639 return false;
5640 }
5641
5642 if self.has_visible_completions_menu() {
5643 return true;
5644 }
5645
5646 has_completion && self.edit_prediction_requires_modifier()
5647 }
5648
5649 fn handle_modifiers_changed(
5650 &mut self,
5651 modifiers: Modifiers,
5652 position_map: &PositionMap,
5653 window: &mut Window,
5654 cx: &mut Context<Self>,
5655 ) {
5656 if self.show_edit_predictions_in_menu() {
5657 self.update_edit_prediction_preview(&modifiers, window, cx);
5658 }
5659
5660 self.update_selection_mode(&modifiers, position_map, window, cx);
5661
5662 let mouse_position = window.mouse_position();
5663 if !position_map.text_hitbox.is_hovered(window) {
5664 return;
5665 }
5666
5667 self.update_hovered_link(
5668 position_map.point_for_position(mouse_position),
5669 &position_map.snapshot,
5670 modifiers,
5671 window,
5672 cx,
5673 )
5674 }
5675
5676 fn update_selection_mode(
5677 &mut self,
5678 modifiers: &Modifiers,
5679 position_map: &PositionMap,
5680 window: &mut Window,
5681 cx: &mut Context<Self>,
5682 ) {
5683 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
5684 return;
5685 }
5686
5687 let mouse_position = window.mouse_position();
5688 let point_for_position = position_map.point_for_position(mouse_position);
5689 let position = point_for_position.previous_valid;
5690
5691 self.select(
5692 SelectPhase::BeginColumnar {
5693 position,
5694 reset: false,
5695 goal_column: point_for_position.exact_unclipped.column(),
5696 },
5697 window,
5698 cx,
5699 );
5700 }
5701
5702 fn update_edit_prediction_preview(
5703 &mut self,
5704 modifiers: &Modifiers,
5705 window: &mut Window,
5706 cx: &mut Context<Self>,
5707 ) {
5708 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
5709 let Some(accept_keystroke) = accept_keybind.keystroke() else {
5710 return;
5711 };
5712
5713 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
5714 if matches!(
5715 self.edit_prediction_preview,
5716 EditPredictionPreview::Inactive { .. }
5717 ) {
5718 self.edit_prediction_preview = EditPredictionPreview::Active {
5719 previous_scroll_position: None,
5720 since: Instant::now(),
5721 };
5722
5723 self.update_visible_inline_completion(window, cx);
5724 cx.notify();
5725 }
5726 } else if let EditPredictionPreview::Active {
5727 previous_scroll_position,
5728 since,
5729 } = self.edit_prediction_preview
5730 {
5731 if let (Some(previous_scroll_position), Some(position_map)) =
5732 (previous_scroll_position, self.last_position_map.as_ref())
5733 {
5734 self.set_scroll_position(
5735 previous_scroll_position
5736 .scroll_position(&position_map.snapshot.display_snapshot),
5737 window,
5738 cx,
5739 );
5740 }
5741
5742 self.edit_prediction_preview = EditPredictionPreview::Inactive {
5743 released_too_fast: since.elapsed() < Duration::from_millis(200),
5744 };
5745 self.clear_row_highlights::<EditPredictionPreview>();
5746 self.update_visible_inline_completion(window, cx);
5747 cx.notify();
5748 }
5749 }
5750
5751 fn update_visible_inline_completion(
5752 &mut self,
5753 _window: &mut Window,
5754 cx: &mut Context<Self>,
5755 ) -> Option<()> {
5756 let selection = self.selections.newest_anchor();
5757 let cursor = selection.head();
5758 let multibuffer = self.buffer.read(cx).snapshot(cx);
5759 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
5760 let excerpt_id = cursor.excerpt_id;
5761
5762 let show_in_menu = self.show_edit_predictions_in_menu();
5763 let completions_menu_has_precedence = !show_in_menu
5764 && (self.context_menu.borrow().is_some()
5765 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
5766
5767 if completions_menu_has_precedence
5768 || !offset_selection.is_empty()
5769 || self
5770 .active_inline_completion
5771 .as_ref()
5772 .map_or(false, |completion| {
5773 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
5774 let invalidation_range = invalidation_range.start..=invalidation_range.end;
5775 !invalidation_range.contains(&offset_selection.head())
5776 })
5777 {
5778 self.discard_inline_completion(false, cx);
5779 return None;
5780 }
5781
5782 self.take_active_inline_completion(cx);
5783 let Some(provider) = self.edit_prediction_provider() else {
5784 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5785 return None;
5786 };
5787
5788 let (buffer, cursor_buffer_position) =
5789 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5790
5791 self.edit_prediction_settings =
5792 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5793
5794 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
5795
5796 if self.edit_prediction_indent_conflict {
5797 let cursor_point = cursor.to_point(&multibuffer);
5798
5799 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
5800
5801 if let Some((_, indent)) = indents.iter().next() {
5802 if indent.len == cursor_point.column {
5803 self.edit_prediction_indent_conflict = false;
5804 }
5805 }
5806 }
5807
5808 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
5809 let edits = inline_completion
5810 .edits
5811 .into_iter()
5812 .flat_map(|(range, new_text)| {
5813 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
5814 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
5815 Some((start..end, new_text))
5816 })
5817 .collect::<Vec<_>>();
5818 if edits.is_empty() {
5819 return None;
5820 }
5821
5822 let first_edit_start = edits.first().unwrap().0.start;
5823 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
5824 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
5825
5826 let last_edit_end = edits.last().unwrap().0.end;
5827 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
5828 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
5829
5830 let cursor_row = cursor.to_point(&multibuffer).row;
5831
5832 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
5833
5834 let mut inlay_ids = Vec::new();
5835 let invalidation_row_range;
5836 let move_invalidation_row_range = if cursor_row < edit_start_row {
5837 Some(cursor_row..edit_end_row)
5838 } else if cursor_row > edit_end_row {
5839 Some(edit_start_row..cursor_row)
5840 } else {
5841 None
5842 };
5843 let is_move =
5844 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
5845 let completion = if is_move {
5846 invalidation_row_range =
5847 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
5848 let target = first_edit_start;
5849 InlineCompletion::Move { target, snapshot }
5850 } else {
5851 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
5852 && !self.inline_completions_hidden_for_vim_mode;
5853
5854 if show_completions_in_buffer {
5855 if edits
5856 .iter()
5857 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
5858 {
5859 let mut inlays = Vec::new();
5860 for (range, new_text) in &edits {
5861 let inlay = Inlay::inline_completion(
5862 post_inc(&mut self.next_inlay_id),
5863 range.start,
5864 new_text.as_str(),
5865 );
5866 inlay_ids.push(inlay.id);
5867 inlays.push(inlay);
5868 }
5869
5870 self.splice_inlays(&[], inlays, cx);
5871 } else {
5872 let background_color = cx.theme().status().deleted_background;
5873 self.highlight_text::<InlineCompletionHighlight>(
5874 edits.iter().map(|(range, _)| range.clone()).collect(),
5875 HighlightStyle {
5876 background_color: Some(background_color),
5877 ..Default::default()
5878 },
5879 cx,
5880 );
5881 }
5882 }
5883
5884 invalidation_row_range = edit_start_row..edit_end_row;
5885
5886 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
5887 if provider.show_tab_accept_marker() {
5888 EditDisplayMode::TabAccept
5889 } else {
5890 EditDisplayMode::Inline
5891 }
5892 } else {
5893 EditDisplayMode::DiffPopover
5894 };
5895
5896 InlineCompletion::Edit {
5897 edits,
5898 edit_preview: inline_completion.edit_preview,
5899 display_mode,
5900 snapshot,
5901 }
5902 };
5903
5904 let invalidation_range = multibuffer
5905 .anchor_before(Point::new(invalidation_row_range.start, 0))
5906 ..multibuffer.anchor_after(Point::new(
5907 invalidation_row_range.end,
5908 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
5909 ));
5910
5911 self.stale_inline_completion_in_menu = None;
5912 self.active_inline_completion = Some(InlineCompletionState {
5913 inlay_ids,
5914 completion,
5915 completion_id: inline_completion.id,
5916 invalidation_range,
5917 });
5918
5919 cx.notify();
5920
5921 Some(())
5922 }
5923
5924 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
5925 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
5926 }
5927
5928 fn render_code_actions_indicator(
5929 &self,
5930 _style: &EditorStyle,
5931 row: DisplayRow,
5932 is_active: bool,
5933 breakpoint: Option<&(Anchor, Breakpoint)>,
5934 cx: &mut Context<Self>,
5935 ) -> Option<IconButton> {
5936 let color = Color::Muted;
5937
5938 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
5939 let bp_kind = Arc::new(
5940 breakpoint
5941 .map(|(_, bp)| bp.kind.clone())
5942 .unwrap_or(BreakpointKind::Standard),
5943 );
5944
5945 if self.available_code_actions.is_some() {
5946 Some(
5947 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
5948 .shape(ui::IconButtonShape::Square)
5949 .icon_size(IconSize::XSmall)
5950 .icon_color(color)
5951 .toggle_state(is_active)
5952 .tooltip({
5953 let focus_handle = self.focus_handle.clone();
5954 move |window, cx| {
5955 Tooltip::for_action_in(
5956 "Toggle Code Actions",
5957 &ToggleCodeActions {
5958 deployed_from_indicator: None,
5959 },
5960 &focus_handle,
5961 window,
5962 cx,
5963 )
5964 }
5965 })
5966 .on_click(cx.listener(move |editor, _e, window, cx| {
5967 window.focus(&editor.focus_handle(cx));
5968 editor.toggle_code_actions(
5969 &ToggleCodeActions {
5970 deployed_from_indicator: Some(row),
5971 },
5972 window,
5973 cx,
5974 );
5975 }))
5976 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
5977 editor.set_breakpoint_context_menu(
5978 row,
5979 position,
5980 bp_kind.clone(),
5981 event.down.position,
5982 window,
5983 cx,
5984 );
5985 })),
5986 )
5987 } else {
5988 None
5989 }
5990 }
5991
5992 fn clear_tasks(&mut self) {
5993 self.tasks.clear()
5994 }
5995
5996 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
5997 if self.tasks.insert(key, value).is_some() {
5998 // This case should hopefully be rare, but just in case...
5999 log::error!("multiple different run targets found on a single line, only the last target will be rendered")
6000 }
6001 }
6002
6003 /// Get all display points of breakpoints that will be rendered within editor
6004 ///
6005 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6006 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6007 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6008 fn active_breakpoints(
6009 &mut self,
6010 range: Range<DisplayRow>,
6011 window: &mut Window,
6012 cx: &mut Context<Self>,
6013 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
6014 let mut breakpoint_display_points = HashMap::default();
6015
6016 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
6017 return breakpoint_display_points;
6018 };
6019
6020 let snapshot = self.snapshot(window, cx);
6021
6022 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
6023 let Some(project) = self.project.as_ref() else {
6024 return breakpoint_display_points;
6025 };
6026
6027 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
6028 let buffer_snapshot = buffer.read(cx).snapshot();
6029
6030 for breakpoint in
6031 breakpoint_store
6032 .read(cx)
6033 .breakpoints(&buffer, None, buffer_snapshot.clone(), cx)
6034 {
6035 let point = buffer_snapshot.summary_for_anchor::<Point>(&breakpoint.0);
6036 let anchor = multi_buffer_snapshot.anchor_before(point);
6037 breakpoint_display_points.insert(
6038 snapshot
6039 .point_to_display_point(
6040 MultiBufferPoint {
6041 row: point.row,
6042 column: point.column,
6043 },
6044 Bias::Left,
6045 )
6046 .row(),
6047 (anchor, breakpoint.1.clone()),
6048 );
6049 }
6050
6051 return breakpoint_display_points;
6052 }
6053
6054 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
6055 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
6056 for excerpt_boundary in multi_buffer_snapshot.excerpt_boundaries_in_range(range) {
6057 let info = excerpt_boundary.next;
6058
6059 let Some(excerpt_ranges) = multi_buffer_snapshot.range_for_excerpt(info.id) else {
6060 continue;
6061 };
6062
6063 let Some(buffer) =
6064 project.read_with(cx, |this, cx| this.buffer_for_id(info.buffer_id, cx))
6065 else {
6066 continue;
6067 };
6068
6069 let breakpoints = breakpoint_store.read(cx).breakpoints(
6070 &buffer,
6071 Some(info.range.context.start..info.range.context.end),
6072 info.buffer.clone(),
6073 cx,
6074 );
6075
6076 // To translate a breakpoint's position within a singular buffer to a multi buffer
6077 // position we need to know it's excerpt starting location, it's position within
6078 // the singular buffer, and if that position is within the excerpt's range.
6079 let excerpt_head = excerpt_ranges
6080 .start
6081 .to_display_point(&snapshot.display_snapshot);
6082
6083 let buffer_start = info
6084 .buffer
6085 .summary_for_anchor::<Point>(&info.range.context.start);
6086
6087 for (anchor, breakpoint) in breakpoints {
6088 let as_row = info.buffer.summary_for_anchor::<Point>(&anchor).row;
6089 let delta = as_row - buffer_start.row;
6090
6091 let position = excerpt_head + DisplayPoint::new(DisplayRow(delta), 0);
6092
6093 let anchor = snapshot.display_point_to_anchor(position, Bias::Left);
6094
6095 breakpoint_display_points.insert(position.row(), (anchor, breakpoint.clone()));
6096 }
6097 }
6098
6099 breakpoint_display_points
6100 }
6101
6102 fn breakpoint_context_menu(
6103 &self,
6104 anchor: Anchor,
6105 kind: Arc<BreakpointKind>,
6106 window: &mut Window,
6107 cx: &mut Context<Self>,
6108 ) -> Entity<ui::ContextMenu> {
6109 let weak_editor = cx.weak_entity();
6110 let focus_handle = self.focus_handle(cx);
6111
6112 let second_entry_msg = if kind.log_message().is_some() {
6113 "Edit Log Breakpoint"
6114 } else {
6115 "Add Log Breakpoint"
6116 };
6117
6118 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6119 menu.on_blur_subscription(Subscription::new(|| {}))
6120 .context(focus_handle)
6121 .entry("Toggle Breakpoint", None, {
6122 let weak_editor = weak_editor.clone();
6123 move |_window, cx| {
6124 weak_editor
6125 .update(cx, |this, cx| {
6126 this.edit_breakpoint_at_anchor(
6127 anchor,
6128 BreakpointKind::Standard,
6129 BreakpointEditAction::Toggle,
6130 cx,
6131 );
6132 })
6133 .log_err();
6134 }
6135 })
6136 .entry(second_entry_msg, None, move |window, cx| {
6137 weak_editor
6138 .update(cx, |this, cx| {
6139 this.add_edit_breakpoint_block(anchor, kind.as_ref(), window, cx);
6140 })
6141 .log_err();
6142 })
6143 })
6144 }
6145
6146 fn render_breakpoint(
6147 &self,
6148 position: Anchor,
6149 row: DisplayRow,
6150 kind: &BreakpointKind,
6151 cx: &mut Context<Self>,
6152 ) -> IconButton {
6153 let color = if self
6154 .gutter_breakpoint_indicator
6155 .is_some_and(|gutter_bp| gutter_bp.row() == row)
6156 {
6157 Color::Hint
6158 } else {
6159 Color::Debugger
6160 };
6161
6162 let icon = match &kind {
6163 BreakpointKind::Standard => ui::IconName::DebugBreakpoint,
6164 BreakpointKind::Log(_) => ui::IconName::DebugLogBreakpoint,
6165 };
6166 let arc_kind = Arc::new(kind.clone());
6167 let arc_kind2 = arc_kind.clone();
6168
6169 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
6170 .icon_size(IconSize::XSmall)
6171 .size(ui::ButtonSize::None)
6172 .icon_color(color)
6173 .style(ButtonStyle::Transparent)
6174 .on_click(cx.listener(move |editor, _e, window, cx| {
6175 window.focus(&editor.focus_handle(cx));
6176 editor.edit_breakpoint_at_anchor(
6177 position,
6178 arc_kind.as_ref().clone(),
6179 BreakpointEditAction::Toggle,
6180 cx,
6181 );
6182 }))
6183 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6184 editor.set_breakpoint_context_menu(
6185 row,
6186 Some(position),
6187 arc_kind2.clone(),
6188 event.down.position,
6189 window,
6190 cx,
6191 );
6192 }))
6193 }
6194
6195 fn build_tasks_context(
6196 project: &Entity<Project>,
6197 buffer: &Entity<Buffer>,
6198 buffer_row: u32,
6199 tasks: &Arc<RunnableTasks>,
6200 cx: &mut Context<Self>,
6201 ) -> Task<Option<task::TaskContext>> {
6202 let position = Point::new(buffer_row, tasks.column);
6203 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
6204 let location = Location {
6205 buffer: buffer.clone(),
6206 range: range_start..range_start,
6207 };
6208 // Fill in the environmental variables from the tree-sitter captures
6209 let mut captured_task_variables = TaskVariables::default();
6210 for (capture_name, value) in tasks.extra_variables.clone() {
6211 captured_task_variables.insert(
6212 task::VariableName::Custom(capture_name.into()),
6213 value.clone(),
6214 );
6215 }
6216 project.update(cx, |project, cx| {
6217 project.task_store().update(cx, |task_store, cx| {
6218 task_store.task_context_for_location(captured_task_variables, location, cx)
6219 })
6220 })
6221 }
6222
6223 pub fn spawn_nearest_task(
6224 &mut self,
6225 action: &SpawnNearestTask,
6226 window: &mut Window,
6227 cx: &mut Context<Self>,
6228 ) {
6229 let Some((workspace, _)) = self.workspace.clone() else {
6230 return;
6231 };
6232 let Some(project) = self.project.clone() else {
6233 return;
6234 };
6235
6236 // Try to find a closest, enclosing node using tree-sitter that has a
6237 // task
6238 let Some((buffer, buffer_row, tasks)) = self
6239 .find_enclosing_node_task(cx)
6240 // Or find the task that's closest in row-distance.
6241 .or_else(|| self.find_closest_task(cx))
6242 else {
6243 return;
6244 };
6245
6246 let reveal_strategy = action.reveal;
6247 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6248 cx.spawn_in(window, async move |_, cx| {
6249 let context = task_context.await?;
6250 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
6251
6252 let resolved = resolved_task.resolved.as_mut()?;
6253 resolved.reveal = reveal_strategy;
6254
6255 workspace
6256 .update(cx, |workspace, cx| {
6257 workspace::tasks::schedule_resolved_task(
6258 workspace,
6259 task_source_kind,
6260 resolved_task,
6261 false,
6262 cx,
6263 );
6264 })
6265 .ok()
6266 })
6267 .detach();
6268 }
6269
6270 fn find_closest_task(
6271 &mut self,
6272 cx: &mut Context<Self>,
6273 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6274 let cursor_row = self.selections.newest_adjusted(cx).head().row;
6275
6276 let ((buffer_id, row), tasks) = self
6277 .tasks
6278 .iter()
6279 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
6280
6281 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
6282 let tasks = Arc::new(tasks.to_owned());
6283 Some((buffer, *row, tasks))
6284 }
6285
6286 fn find_enclosing_node_task(
6287 &mut self,
6288 cx: &mut Context<Self>,
6289 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6290 let snapshot = self.buffer.read(cx).snapshot(cx);
6291 let offset = self.selections.newest::<usize>(cx).head();
6292 let excerpt = snapshot.excerpt_containing(offset..offset)?;
6293 let buffer_id = excerpt.buffer().remote_id();
6294
6295 let layer = excerpt.buffer().syntax_layer_at(offset)?;
6296 let mut cursor = layer.node().walk();
6297
6298 while cursor.goto_first_child_for_byte(offset).is_some() {
6299 if cursor.node().end_byte() == offset {
6300 cursor.goto_next_sibling();
6301 }
6302 }
6303
6304 // Ascend to the smallest ancestor that contains the range and has a task.
6305 loop {
6306 let node = cursor.node();
6307 let node_range = node.byte_range();
6308 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
6309
6310 // Check if this node contains our offset
6311 if node_range.start <= offset && node_range.end >= offset {
6312 // If it contains offset, check for task
6313 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
6314 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
6315 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
6316 }
6317 }
6318
6319 if !cursor.goto_parent() {
6320 break;
6321 }
6322 }
6323 None
6324 }
6325
6326 fn render_run_indicator(
6327 &self,
6328 _style: &EditorStyle,
6329 is_active: bool,
6330 row: DisplayRow,
6331 breakpoint: Option<(Anchor, Breakpoint)>,
6332 cx: &mut Context<Self>,
6333 ) -> IconButton {
6334 let color = Color::Muted;
6335
6336 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6337 let bp_kind = Arc::new(
6338 breakpoint
6339 .map(|(_, bp)| bp.kind)
6340 .unwrap_or(BreakpointKind::Standard),
6341 );
6342
6343 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
6344 .shape(ui::IconButtonShape::Square)
6345 .icon_size(IconSize::XSmall)
6346 .icon_color(color)
6347 .toggle_state(is_active)
6348 .on_click(cx.listener(move |editor, _e, window, cx| {
6349 window.focus(&editor.focus_handle(cx));
6350 editor.toggle_code_actions(
6351 &ToggleCodeActions {
6352 deployed_from_indicator: Some(row),
6353 },
6354 window,
6355 cx,
6356 );
6357 }))
6358 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6359 editor.set_breakpoint_context_menu(
6360 row,
6361 position,
6362 bp_kind.clone(),
6363 event.down.position,
6364 window,
6365 cx,
6366 );
6367 }))
6368 }
6369
6370 pub fn context_menu_visible(&self) -> bool {
6371 !self.edit_prediction_preview_is_active()
6372 && self
6373 .context_menu
6374 .borrow()
6375 .as_ref()
6376 .map_or(false, |menu| menu.visible())
6377 }
6378
6379 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
6380 self.context_menu
6381 .borrow()
6382 .as_ref()
6383 .map(|menu| menu.origin())
6384 }
6385
6386 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
6387 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
6388
6389 fn render_edit_prediction_popover(
6390 &mut self,
6391 text_bounds: &Bounds<Pixels>,
6392 content_origin: gpui::Point<Pixels>,
6393 editor_snapshot: &EditorSnapshot,
6394 visible_row_range: Range<DisplayRow>,
6395 scroll_top: f32,
6396 scroll_bottom: f32,
6397 line_layouts: &[LineWithInvisibles],
6398 line_height: Pixels,
6399 scroll_pixel_position: gpui::Point<Pixels>,
6400 newest_selection_head: Option<DisplayPoint>,
6401 editor_width: Pixels,
6402 style: &EditorStyle,
6403 window: &mut Window,
6404 cx: &mut App,
6405 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6406 let active_inline_completion = self.active_inline_completion.as_ref()?;
6407
6408 if self.edit_prediction_visible_in_cursor_popover(true) {
6409 return None;
6410 }
6411
6412 match &active_inline_completion.completion {
6413 InlineCompletion::Move { target, .. } => {
6414 let target_display_point = target.to_display_point(editor_snapshot);
6415
6416 if self.edit_prediction_requires_modifier() {
6417 if !self.edit_prediction_preview_is_active() {
6418 return None;
6419 }
6420
6421 self.render_edit_prediction_modifier_jump_popover(
6422 text_bounds,
6423 content_origin,
6424 visible_row_range,
6425 line_layouts,
6426 line_height,
6427 scroll_pixel_position,
6428 newest_selection_head,
6429 target_display_point,
6430 window,
6431 cx,
6432 )
6433 } else {
6434 self.render_edit_prediction_eager_jump_popover(
6435 text_bounds,
6436 content_origin,
6437 editor_snapshot,
6438 visible_row_range,
6439 scroll_top,
6440 scroll_bottom,
6441 line_height,
6442 scroll_pixel_position,
6443 target_display_point,
6444 editor_width,
6445 window,
6446 cx,
6447 )
6448 }
6449 }
6450 InlineCompletion::Edit {
6451 display_mode: EditDisplayMode::Inline,
6452 ..
6453 } => None,
6454 InlineCompletion::Edit {
6455 display_mode: EditDisplayMode::TabAccept,
6456 edits,
6457 ..
6458 } => {
6459 let range = &edits.first()?.0;
6460 let target_display_point = range.end.to_display_point(editor_snapshot);
6461
6462 self.render_edit_prediction_end_of_line_popover(
6463 "Accept",
6464 editor_snapshot,
6465 visible_row_range,
6466 target_display_point,
6467 line_height,
6468 scroll_pixel_position,
6469 content_origin,
6470 editor_width,
6471 window,
6472 cx,
6473 )
6474 }
6475 InlineCompletion::Edit {
6476 edits,
6477 edit_preview,
6478 display_mode: EditDisplayMode::DiffPopover,
6479 snapshot,
6480 } => self.render_edit_prediction_diff_popover(
6481 text_bounds,
6482 content_origin,
6483 editor_snapshot,
6484 visible_row_range,
6485 line_layouts,
6486 line_height,
6487 scroll_pixel_position,
6488 newest_selection_head,
6489 editor_width,
6490 style,
6491 edits,
6492 edit_preview,
6493 snapshot,
6494 window,
6495 cx,
6496 ),
6497 }
6498 }
6499
6500 fn render_edit_prediction_modifier_jump_popover(
6501 &mut self,
6502 text_bounds: &Bounds<Pixels>,
6503 content_origin: gpui::Point<Pixels>,
6504 visible_row_range: Range<DisplayRow>,
6505 line_layouts: &[LineWithInvisibles],
6506 line_height: Pixels,
6507 scroll_pixel_position: gpui::Point<Pixels>,
6508 newest_selection_head: Option<DisplayPoint>,
6509 target_display_point: DisplayPoint,
6510 window: &mut Window,
6511 cx: &mut App,
6512 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6513 let scrolled_content_origin =
6514 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
6515
6516 const SCROLL_PADDING_Y: Pixels = px(12.);
6517
6518 if target_display_point.row() < visible_row_range.start {
6519 return self.render_edit_prediction_scroll_popover(
6520 |_| SCROLL_PADDING_Y,
6521 IconName::ArrowUp,
6522 visible_row_range,
6523 line_layouts,
6524 newest_selection_head,
6525 scrolled_content_origin,
6526 window,
6527 cx,
6528 );
6529 } else if target_display_point.row() >= visible_row_range.end {
6530 return self.render_edit_prediction_scroll_popover(
6531 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
6532 IconName::ArrowDown,
6533 visible_row_range,
6534 line_layouts,
6535 newest_selection_head,
6536 scrolled_content_origin,
6537 window,
6538 cx,
6539 );
6540 }
6541
6542 const POLE_WIDTH: Pixels = px(2.);
6543
6544 let line_layout =
6545 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
6546 let target_column = target_display_point.column() as usize;
6547
6548 let target_x = line_layout.x_for_index(target_column);
6549 let target_y =
6550 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
6551
6552 let flag_on_right = target_x < text_bounds.size.width / 2.;
6553
6554 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
6555 border_color.l += 0.001;
6556
6557 let mut element = v_flex()
6558 .items_end()
6559 .when(flag_on_right, |el| el.items_start())
6560 .child(if flag_on_right {
6561 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
6562 .rounded_bl(px(0.))
6563 .rounded_tl(px(0.))
6564 .border_l_2()
6565 .border_color(border_color)
6566 } else {
6567 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
6568 .rounded_br(px(0.))
6569 .rounded_tr(px(0.))
6570 .border_r_2()
6571 .border_color(border_color)
6572 })
6573 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
6574 .into_any();
6575
6576 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6577
6578 let mut origin = scrolled_content_origin + point(target_x, target_y)
6579 - point(
6580 if flag_on_right {
6581 POLE_WIDTH
6582 } else {
6583 size.width - POLE_WIDTH
6584 },
6585 size.height - line_height,
6586 );
6587
6588 origin.x = origin.x.max(content_origin.x);
6589
6590 element.prepaint_at(origin, window, cx);
6591
6592 Some((element, origin))
6593 }
6594
6595 fn render_edit_prediction_scroll_popover(
6596 &mut self,
6597 to_y: impl Fn(Size<Pixels>) -> Pixels,
6598 scroll_icon: IconName,
6599 visible_row_range: Range<DisplayRow>,
6600 line_layouts: &[LineWithInvisibles],
6601 newest_selection_head: Option<DisplayPoint>,
6602 scrolled_content_origin: gpui::Point<Pixels>,
6603 window: &mut Window,
6604 cx: &mut App,
6605 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6606 let mut element = self
6607 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
6608 .into_any();
6609
6610 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6611
6612 let cursor = newest_selection_head?;
6613 let cursor_row_layout =
6614 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
6615 let cursor_column = cursor.column() as usize;
6616
6617 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
6618
6619 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
6620
6621 element.prepaint_at(origin, window, cx);
6622 Some((element, origin))
6623 }
6624
6625 fn render_edit_prediction_eager_jump_popover(
6626 &mut self,
6627 text_bounds: &Bounds<Pixels>,
6628 content_origin: gpui::Point<Pixels>,
6629 editor_snapshot: &EditorSnapshot,
6630 visible_row_range: Range<DisplayRow>,
6631 scroll_top: f32,
6632 scroll_bottom: f32,
6633 line_height: Pixels,
6634 scroll_pixel_position: gpui::Point<Pixels>,
6635 target_display_point: DisplayPoint,
6636 editor_width: Pixels,
6637 window: &mut Window,
6638 cx: &mut App,
6639 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6640 if target_display_point.row().as_f32() < scroll_top {
6641 let mut element = self
6642 .render_edit_prediction_line_popover(
6643 "Jump to Edit",
6644 Some(IconName::ArrowUp),
6645 window,
6646 cx,
6647 )?
6648 .into_any();
6649
6650 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6651 let offset = point(
6652 (text_bounds.size.width - size.width) / 2.,
6653 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
6654 );
6655
6656 let origin = text_bounds.origin + offset;
6657 element.prepaint_at(origin, window, cx);
6658 Some((element, origin))
6659 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
6660 let mut element = self
6661 .render_edit_prediction_line_popover(
6662 "Jump to Edit",
6663 Some(IconName::ArrowDown),
6664 window,
6665 cx,
6666 )?
6667 .into_any();
6668
6669 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6670 let offset = point(
6671 (text_bounds.size.width - size.width) / 2.,
6672 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
6673 );
6674
6675 let origin = text_bounds.origin + offset;
6676 element.prepaint_at(origin, window, cx);
6677 Some((element, origin))
6678 } else {
6679 self.render_edit_prediction_end_of_line_popover(
6680 "Jump to Edit",
6681 editor_snapshot,
6682 visible_row_range,
6683 target_display_point,
6684 line_height,
6685 scroll_pixel_position,
6686 content_origin,
6687 editor_width,
6688 window,
6689 cx,
6690 )
6691 }
6692 }
6693
6694 fn render_edit_prediction_end_of_line_popover(
6695 self: &mut Editor,
6696 label: &'static str,
6697 editor_snapshot: &EditorSnapshot,
6698 visible_row_range: Range<DisplayRow>,
6699 target_display_point: DisplayPoint,
6700 line_height: Pixels,
6701 scroll_pixel_position: gpui::Point<Pixels>,
6702 content_origin: gpui::Point<Pixels>,
6703 editor_width: Pixels,
6704 window: &mut Window,
6705 cx: &mut App,
6706 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6707 let target_line_end = DisplayPoint::new(
6708 target_display_point.row(),
6709 editor_snapshot.line_len(target_display_point.row()),
6710 );
6711
6712 let mut element = self
6713 .render_edit_prediction_line_popover(label, None, window, cx)?
6714 .into_any();
6715
6716 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6717
6718 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
6719
6720 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
6721 let mut origin = start_point
6722 + line_origin
6723 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
6724 origin.x = origin.x.max(content_origin.x);
6725
6726 let max_x = content_origin.x + editor_width - size.width;
6727
6728 if origin.x > max_x {
6729 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
6730
6731 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
6732 origin.y += offset;
6733 IconName::ArrowUp
6734 } else {
6735 origin.y -= offset;
6736 IconName::ArrowDown
6737 };
6738
6739 element = self
6740 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
6741 .into_any();
6742
6743 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6744
6745 origin.x = content_origin.x + editor_width - size.width - px(2.);
6746 }
6747
6748 element.prepaint_at(origin, window, cx);
6749 Some((element, origin))
6750 }
6751
6752 fn render_edit_prediction_diff_popover(
6753 self: &Editor,
6754 text_bounds: &Bounds<Pixels>,
6755 content_origin: gpui::Point<Pixels>,
6756 editor_snapshot: &EditorSnapshot,
6757 visible_row_range: Range<DisplayRow>,
6758 line_layouts: &[LineWithInvisibles],
6759 line_height: Pixels,
6760 scroll_pixel_position: gpui::Point<Pixels>,
6761 newest_selection_head: Option<DisplayPoint>,
6762 editor_width: Pixels,
6763 style: &EditorStyle,
6764 edits: &Vec<(Range<Anchor>, String)>,
6765 edit_preview: &Option<language::EditPreview>,
6766 snapshot: &language::BufferSnapshot,
6767 window: &mut Window,
6768 cx: &mut App,
6769 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6770 let edit_start = edits
6771 .first()
6772 .unwrap()
6773 .0
6774 .start
6775 .to_display_point(editor_snapshot);
6776 let edit_end = edits
6777 .last()
6778 .unwrap()
6779 .0
6780 .end
6781 .to_display_point(editor_snapshot);
6782
6783 let is_visible = visible_row_range.contains(&edit_start.row())
6784 || visible_row_range.contains(&edit_end.row());
6785 if !is_visible {
6786 return None;
6787 }
6788
6789 let highlighted_edits =
6790 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
6791
6792 let styled_text = highlighted_edits.to_styled_text(&style.text);
6793 let line_count = highlighted_edits.text.lines().count();
6794
6795 const BORDER_WIDTH: Pixels = px(1.);
6796
6797 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
6798 let has_keybind = keybind.is_some();
6799
6800 let mut element = h_flex()
6801 .items_start()
6802 .child(
6803 h_flex()
6804 .bg(cx.theme().colors().editor_background)
6805 .border(BORDER_WIDTH)
6806 .shadow_sm()
6807 .border_color(cx.theme().colors().border)
6808 .rounded_l_lg()
6809 .when(line_count > 1, |el| el.rounded_br_lg())
6810 .pr_1()
6811 .child(styled_text),
6812 )
6813 .child(
6814 h_flex()
6815 .h(line_height + BORDER_WIDTH * px(2.))
6816 .px_1p5()
6817 .gap_1()
6818 // Workaround: For some reason, there's a gap if we don't do this
6819 .ml(-BORDER_WIDTH)
6820 .shadow(smallvec![gpui::BoxShadow {
6821 color: gpui::black().opacity(0.05),
6822 offset: point(px(1.), px(1.)),
6823 blur_radius: px(2.),
6824 spread_radius: px(0.),
6825 }])
6826 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
6827 .border(BORDER_WIDTH)
6828 .border_color(cx.theme().colors().border)
6829 .rounded_r_lg()
6830 .id("edit_prediction_diff_popover_keybind")
6831 .when(!has_keybind, |el| {
6832 let status_colors = cx.theme().status();
6833
6834 el.bg(status_colors.error_background)
6835 .border_color(status_colors.error.opacity(0.6))
6836 .child(Icon::new(IconName::Info).color(Color::Error))
6837 .cursor_default()
6838 .hoverable_tooltip(move |_window, cx| {
6839 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
6840 })
6841 })
6842 .children(keybind),
6843 )
6844 .into_any();
6845
6846 let longest_row =
6847 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
6848 let longest_line_width = if visible_row_range.contains(&longest_row) {
6849 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
6850 } else {
6851 layout_line(
6852 longest_row,
6853 editor_snapshot,
6854 style,
6855 editor_width,
6856 |_| false,
6857 window,
6858 cx,
6859 )
6860 .width
6861 };
6862
6863 let viewport_bounds =
6864 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
6865 right: -EditorElement::SCROLLBAR_WIDTH,
6866 ..Default::default()
6867 });
6868
6869 let x_after_longest =
6870 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
6871 - scroll_pixel_position.x;
6872
6873 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6874
6875 // Fully visible if it can be displayed within the window (allow overlapping other
6876 // panes). However, this is only allowed if the popover starts within text_bounds.
6877 let can_position_to_the_right = x_after_longest < text_bounds.right()
6878 && x_after_longest + element_bounds.width < viewport_bounds.right();
6879
6880 let mut origin = if can_position_to_the_right {
6881 point(
6882 x_after_longest,
6883 text_bounds.origin.y + edit_start.row().as_f32() * line_height
6884 - scroll_pixel_position.y,
6885 )
6886 } else {
6887 let cursor_row = newest_selection_head.map(|head| head.row());
6888 let above_edit = edit_start
6889 .row()
6890 .0
6891 .checked_sub(line_count as u32)
6892 .map(DisplayRow);
6893 let below_edit = Some(edit_end.row() + 1);
6894 let above_cursor =
6895 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
6896 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
6897
6898 // Place the edit popover adjacent to the edit if there is a location
6899 // available that is onscreen and does not obscure the cursor. Otherwise,
6900 // place it adjacent to the cursor.
6901 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
6902 .into_iter()
6903 .flatten()
6904 .find(|&start_row| {
6905 let end_row = start_row + line_count as u32;
6906 visible_row_range.contains(&start_row)
6907 && visible_row_range.contains(&end_row)
6908 && cursor_row.map_or(true, |cursor_row| {
6909 !((start_row..end_row).contains(&cursor_row))
6910 })
6911 })?;
6912
6913 content_origin
6914 + point(
6915 -scroll_pixel_position.x,
6916 row_target.as_f32() * line_height - scroll_pixel_position.y,
6917 )
6918 };
6919
6920 origin.x -= BORDER_WIDTH;
6921
6922 window.defer_draw(element, origin, 1);
6923
6924 // Do not return an element, since it will already be drawn due to defer_draw.
6925 None
6926 }
6927
6928 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
6929 px(30.)
6930 }
6931
6932 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
6933 if self.read_only(cx) {
6934 cx.theme().players().read_only()
6935 } else {
6936 self.style.as_ref().unwrap().local_player
6937 }
6938 }
6939
6940 fn render_edit_prediction_accept_keybind(
6941 &self,
6942 window: &mut Window,
6943 cx: &App,
6944 ) -> Option<AnyElement> {
6945 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
6946 let accept_keystroke = accept_binding.keystroke()?;
6947
6948 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
6949
6950 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
6951 Color::Accent
6952 } else {
6953 Color::Muted
6954 };
6955
6956 h_flex()
6957 .px_0p5()
6958 .when(is_platform_style_mac, |parent| parent.gap_0p5())
6959 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
6960 .text_size(TextSize::XSmall.rems(cx))
6961 .child(h_flex().children(ui::render_modifiers(
6962 &accept_keystroke.modifiers,
6963 PlatformStyle::platform(),
6964 Some(modifiers_color),
6965 Some(IconSize::XSmall.rems().into()),
6966 true,
6967 )))
6968 .when(is_platform_style_mac, |parent| {
6969 parent.child(accept_keystroke.key.clone())
6970 })
6971 .when(!is_platform_style_mac, |parent| {
6972 parent.child(
6973 Key::new(
6974 util::capitalize(&accept_keystroke.key),
6975 Some(Color::Default),
6976 )
6977 .size(Some(IconSize::XSmall.rems().into())),
6978 )
6979 })
6980 .into_any()
6981 .into()
6982 }
6983
6984 fn render_edit_prediction_line_popover(
6985 &self,
6986 label: impl Into<SharedString>,
6987 icon: Option<IconName>,
6988 window: &mut Window,
6989 cx: &App,
6990 ) -> Option<Stateful<Div>> {
6991 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
6992
6993 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
6994 let has_keybind = keybind.is_some();
6995
6996 let result = h_flex()
6997 .id("ep-line-popover")
6998 .py_0p5()
6999 .pl_1()
7000 .pr(padding_right)
7001 .gap_1()
7002 .rounded_md()
7003 .border_1()
7004 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7005 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7006 .shadow_sm()
7007 .when(!has_keybind, |el| {
7008 let status_colors = cx.theme().status();
7009
7010 el.bg(status_colors.error_background)
7011 .border_color(status_colors.error.opacity(0.6))
7012 .pl_2()
7013 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7014 .cursor_default()
7015 .hoverable_tooltip(move |_window, cx| {
7016 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7017 })
7018 })
7019 .children(keybind)
7020 .child(
7021 Label::new(label)
7022 .size(LabelSize::Small)
7023 .when(!has_keybind, |el| {
7024 el.color(cx.theme().status().error.into()).strikethrough()
7025 }),
7026 )
7027 .when(!has_keybind, |el| {
7028 el.child(
7029 h_flex().ml_1().child(
7030 Icon::new(IconName::Info)
7031 .size(IconSize::Small)
7032 .color(cx.theme().status().error.into()),
7033 ),
7034 )
7035 })
7036 .when_some(icon, |element, icon| {
7037 element.child(
7038 div()
7039 .mt(px(1.5))
7040 .child(Icon::new(icon).size(IconSize::Small)),
7041 )
7042 });
7043
7044 Some(result)
7045 }
7046
7047 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
7048 let accent_color = cx.theme().colors().text_accent;
7049 let editor_bg_color = cx.theme().colors().editor_background;
7050 editor_bg_color.blend(accent_color.opacity(0.1))
7051 }
7052
7053 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
7054 let accent_color = cx.theme().colors().text_accent;
7055 let editor_bg_color = cx.theme().colors().editor_background;
7056 editor_bg_color.blend(accent_color.opacity(0.6))
7057 }
7058
7059 fn render_edit_prediction_cursor_popover(
7060 &self,
7061 min_width: Pixels,
7062 max_width: Pixels,
7063 cursor_point: Point,
7064 style: &EditorStyle,
7065 accept_keystroke: Option<&gpui::Keystroke>,
7066 _window: &Window,
7067 cx: &mut Context<Editor>,
7068 ) -> Option<AnyElement> {
7069 let provider = self.edit_prediction_provider.as_ref()?;
7070
7071 if provider.provider.needs_terms_acceptance(cx) {
7072 return Some(
7073 h_flex()
7074 .min_w(min_width)
7075 .flex_1()
7076 .px_2()
7077 .py_1()
7078 .gap_3()
7079 .elevation_2(cx)
7080 .hover(|style| style.bg(cx.theme().colors().element_hover))
7081 .id("accept-terms")
7082 .cursor_pointer()
7083 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
7084 .on_click(cx.listener(|this, _event, window, cx| {
7085 cx.stop_propagation();
7086 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
7087 window.dispatch_action(
7088 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
7089 cx,
7090 );
7091 }))
7092 .child(
7093 h_flex()
7094 .flex_1()
7095 .gap_2()
7096 .child(Icon::new(IconName::ZedPredict))
7097 .child(Label::new("Accept Terms of Service"))
7098 .child(div().w_full())
7099 .child(
7100 Icon::new(IconName::ArrowUpRight)
7101 .color(Color::Muted)
7102 .size(IconSize::Small),
7103 )
7104 .into_any_element(),
7105 )
7106 .into_any(),
7107 );
7108 }
7109
7110 let is_refreshing = provider.provider.is_refreshing(cx);
7111
7112 fn pending_completion_container() -> Div {
7113 h_flex()
7114 .h_full()
7115 .flex_1()
7116 .gap_2()
7117 .child(Icon::new(IconName::ZedPredict))
7118 }
7119
7120 let completion = match &self.active_inline_completion {
7121 Some(prediction) => {
7122 if !self.has_visible_completions_menu() {
7123 const RADIUS: Pixels = px(6.);
7124 const BORDER_WIDTH: Pixels = px(1.);
7125
7126 return Some(
7127 h_flex()
7128 .elevation_2(cx)
7129 .border(BORDER_WIDTH)
7130 .border_color(cx.theme().colors().border)
7131 .when(accept_keystroke.is_none(), |el| {
7132 el.border_color(cx.theme().status().error)
7133 })
7134 .rounded(RADIUS)
7135 .rounded_tl(px(0.))
7136 .overflow_hidden()
7137 .child(div().px_1p5().child(match &prediction.completion {
7138 InlineCompletion::Move { target, snapshot } => {
7139 use text::ToPoint as _;
7140 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
7141 {
7142 Icon::new(IconName::ZedPredictDown)
7143 } else {
7144 Icon::new(IconName::ZedPredictUp)
7145 }
7146 }
7147 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
7148 }))
7149 .child(
7150 h_flex()
7151 .gap_1()
7152 .py_1()
7153 .px_2()
7154 .rounded_r(RADIUS - BORDER_WIDTH)
7155 .border_l_1()
7156 .border_color(cx.theme().colors().border)
7157 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7158 .when(self.edit_prediction_preview.released_too_fast(), |el| {
7159 el.child(
7160 Label::new("Hold")
7161 .size(LabelSize::Small)
7162 .when(accept_keystroke.is_none(), |el| {
7163 el.strikethrough()
7164 })
7165 .line_height_style(LineHeightStyle::UiLabel),
7166 )
7167 })
7168 .id("edit_prediction_cursor_popover_keybind")
7169 .when(accept_keystroke.is_none(), |el| {
7170 let status_colors = cx.theme().status();
7171
7172 el.bg(status_colors.error_background)
7173 .border_color(status_colors.error.opacity(0.6))
7174 .child(Icon::new(IconName::Info).color(Color::Error))
7175 .cursor_default()
7176 .hoverable_tooltip(move |_window, cx| {
7177 cx.new(|_| MissingEditPredictionKeybindingTooltip)
7178 .into()
7179 })
7180 })
7181 .when_some(
7182 accept_keystroke.as_ref(),
7183 |el, accept_keystroke| {
7184 el.child(h_flex().children(ui::render_modifiers(
7185 &accept_keystroke.modifiers,
7186 PlatformStyle::platform(),
7187 Some(Color::Default),
7188 Some(IconSize::XSmall.rems().into()),
7189 false,
7190 )))
7191 },
7192 ),
7193 )
7194 .into_any(),
7195 );
7196 }
7197
7198 self.render_edit_prediction_cursor_popover_preview(
7199 prediction,
7200 cursor_point,
7201 style,
7202 cx,
7203 )?
7204 }
7205
7206 None if is_refreshing => match &self.stale_inline_completion_in_menu {
7207 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
7208 stale_completion,
7209 cursor_point,
7210 style,
7211 cx,
7212 )?,
7213
7214 None => {
7215 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
7216 }
7217 },
7218
7219 None => pending_completion_container().child(Label::new("No Prediction")),
7220 };
7221
7222 let completion = if is_refreshing {
7223 completion
7224 .with_animation(
7225 "loading-completion",
7226 Animation::new(Duration::from_secs(2))
7227 .repeat()
7228 .with_easing(pulsating_between(0.4, 0.8)),
7229 |label, delta| label.opacity(delta),
7230 )
7231 .into_any_element()
7232 } else {
7233 completion.into_any_element()
7234 };
7235
7236 let has_completion = self.active_inline_completion.is_some();
7237
7238 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7239 Some(
7240 h_flex()
7241 .min_w(min_width)
7242 .max_w(max_width)
7243 .flex_1()
7244 .elevation_2(cx)
7245 .border_color(cx.theme().colors().border)
7246 .child(
7247 div()
7248 .flex_1()
7249 .py_1()
7250 .px_2()
7251 .overflow_hidden()
7252 .child(completion),
7253 )
7254 .when_some(accept_keystroke, |el, accept_keystroke| {
7255 if !accept_keystroke.modifiers.modified() {
7256 return el;
7257 }
7258
7259 el.child(
7260 h_flex()
7261 .h_full()
7262 .border_l_1()
7263 .rounded_r_lg()
7264 .border_color(cx.theme().colors().border)
7265 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7266 .gap_1()
7267 .py_1()
7268 .px_2()
7269 .child(
7270 h_flex()
7271 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7272 .when(is_platform_style_mac, |parent| parent.gap_1())
7273 .child(h_flex().children(ui::render_modifiers(
7274 &accept_keystroke.modifiers,
7275 PlatformStyle::platform(),
7276 Some(if !has_completion {
7277 Color::Muted
7278 } else {
7279 Color::Default
7280 }),
7281 None,
7282 false,
7283 ))),
7284 )
7285 .child(Label::new("Preview").into_any_element())
7286 .opacity(if has_completion { 1.0 } else { 0.4 }),
7287 )
7288 })
7289 .into_any(),
7290 )
7291 }
7292
7293 fn render_edit_prediction_cursor_popover_preview(
7294 &self,
7295 completion: &InlineCompletionState,
7296 cursor_point: Point,
7297 style: &EditorStyle,
7298 cx: &mut Context<Editor>,
7299 ) -> Option<Div> {
7300 use text::ToPoint as _;
7301
7302 fn render_relative_row_jump(
7303 prefix: impl Into<String>,
7304 current_row: u32,
7305 target_row: u32,
7306 ) -> Div {
7307 let (row_diff, arrow) = if target_row < current_row {
7308 (current_row - target_row, IconName::ArrowUp)
7309 } else {
7310 (target_row - current_row, IconName::ArrowDown)
7311 };
7312
7313 h_flex()
7314 .child(
7315 Label::new(format!("{}{}", prefix.into(), row_diff))
7316 .color(Color::Muted)
7317 .size(LabelSize::Small),
7318 )
7319 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
7320 }
7321
7322 match &completion.completion {
7323 InlineCompletion::Move {
7324 target, snapshot, ..
7325 } => Some(
7326 h_flex()
7327 .px_2()
7328 .gap_2()
7329 .flex_1()
7330 .child(
7331 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
7332 Icon::new(IconName::ZedPredictDown)
7333 } else {
7334 Icon::new(IconName::ZedPredictUp)
7335 },
7336 )
7337 .child(Label::new("Jump to Edit")),
7338 ),
7339
7340 InlineCompletion::Edit {
7341 edits,
7342 edit_preview,
7343 snapshot,
7344 display_mode: _,
7345 } => {
7346 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
7347
7348 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
7349 &snapshot,
7350 &edits,
7351 edit_preview.as_ref()?,
7352 true,
7353 cx,
7354 )
7355 .first_line_preview();
7356
7357 let styled_text = gpui::StyledText::new(highlighted_edits.text)
7358 .with_default_highlights(&style.text, highlighted_edits.highlights);
7359
7360 let preview = h_flex()
7361 .gap_1()
7362 .min_w_16()
7363 .child(styled_text)
7364 .when(has_more_lines, |parent| parent.child("…"));
7365
7366 let left = if first_edit_row != cursor_point.row {
7367 render_relative_row_jump("", cursor_point.row, first_edit_row)
7368 .into_any_element()
7369 } else {
7370 Icon::new(IconName::ZedPredict).into_any_element()
7371 };
7372
7373 Some(
7374 h_flex()
7375 .h_full()
7376 .flex_1()
7377 .gap_2()
7378 .pr_1()
7379 .overflow_x_hidden()
7380 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7381 .child(left)
7382 .child(preview),
7383 )
7384 }
7385 }
7386 }
7387
7388 fn render_context_menu(
7389 &self,
7390 style: &EditorStyle,
7391 max_height_in_lines: u32,
7392 y_flipped: bool,
7393 window: &mut Window,
7394 cx: &mut Context<Editor>,
7395 ) -> Option<AnyElement> {
7396 let menu = self.context_menu.borrow();
7397 let menu = menu.as_ref()?;
7398 if !menu.visible() {
7399 return None;
7400 };
7401 Some(menu.render(style, max_height_in_lines, y_flipped, window, cx))
7402 }
7403
7404 fn render_context_menu_aside(
7405 &mut self,
7406 max_size: Size<Pixels>,
7407 window: &mut Window,
7408 cx: &mut Context<Editor>,
7409 ) -> Option<AnyElement> {
7410 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
7411 if menu.visible() {
7412 menu.render_aside(self, max_size, window, cx)
7413 } else {
7414 None
7415 }
7416 })
7417 }
7418
7419 fn hide_context_menu(
7420 &mut self,
7421 window: &mut Window,
7422 cx: &mut Context<Self>,
7423 ) -> Option<CodeContextMenu> {
7424 cx.notify();
7425 self.completion_tasks.clear();
7426 let context_menu = self.context_menu.borrow_mut().take();
7427 self.stale_inline_completion_in_menu.take();
7428 self.update_visible_inline_completion(window, cx);
7429 context_menu
7430 }
7431
7432 fn show_snippet_choices(
7433 &mut self,
7434 choices: &Vec<String>,
7435 selection: Range<Anchor>,
7436 cx: &mut Context<Self>,
7437 ) {
7438 if selection.start.buffer_id.is_none() {
7439 return;
7440 }
7441 let buffer_id = selection.start.buffer_id.unwrap();
7442 let buffer = self.buffer().read(cx).buffer(buffer_id);
7443 let id = post_inc(&mut self.next_completion_id);
7444
7445 if let Some(buffer) = buffer {
7446 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
7447 CompletionsMenu::new_snippet_choices(id, true, choices, selection, buffer),
7448 ));
7449 }
7450 }
7451
7452 pub fn insert_snippet(
7453 &mut self,
7454 insertion_ranges: &[Range<usize>],
7455 snippet: Snippet,
7456 window: &mut Window,
7457 cx: &mut Context<Self>,
7458 ) -> Result<()> {
7459 struct Tabstop<T> {
7460 is_end_tabstop: bool,
7461 ranges: Vec<Range<T>>,
7462 choices: Option<Vec<String>>,
7463 }
7464
7465 let tabstops = self.buffer.update(cx, |buffer, cx| {
7466 let snippet_text: Arc<str> = snippet.text.clone().into();
7467 buffer.edit(
7468 insertion_ranges
7469 .iter()
7470 .cloned()
7471 .map(|range| (range, snippet_text.clone())),
7472 Some(AutoindentMode::EachLine),
7473 cx,
7474 );
7475
7476 let snapshot = &*buffer.read(cx);
7477 let snippet = &snippet;
7478 snippet
7479 .tabstops
7480 .iter()
7481 .map(|tabstop| {
7482 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
7483 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
7484 });
7485 let mut tabstop_ranges = tabstop
7486 .ranges
7487 .iter()
7488 .flat_map(|tabstop_range| {
7489 let mut delta = 0_isize;
7490 insertion_ranges.iter().map(move |insertion_range| {
7491 let insertion_start = insertion_range.start as isize + delta;
7492 delta +=
7493 snippet.text.len() as isize - insertion_range.len() as isize;
7494
7495 let start = ((insertion_start + tabstop_range.start) as usize)
7496 .min(snapshot.len());
7497 let end = ((insertion_start + tabstop_range.end) as usize)
7498 .min(snapshot.len());
7499 snapshot.anchor_before(start)..snapshot.anchor_after(end)
7500 })
7501 })
7502 .collect::<Vec<_>>();
7503 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
7504
7505 Tabstop {
7506 is_end_tabstop,
7507 ranges: tabstop_ranges,
7508 choices: tabstop.choices.clone(),
7509 }
7510 })
7511 .collect::<Vec<_>>()
7512 });
7513 if let Some(tabstop) = tabstops.first() {
7514 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7515 s.select_ranges(tabstop.ranges.iter().cloned());
7516 });
7517
7518 if let Some(choices) = &tabstop.choices {
7519 if let Some(selection) = tabstop.ranges.first() {
7520 self.show_snippet_choices(choices, selection.clone(), cx)
7521 }
7522 }
7523
7524 // If we're already at the last tabstop and it's at the end of the snippet,
7525 // we're done, we don't need to keep the state around.
7526 if !tabstop.is_end_tabstop {
7527 let choices = tabstops
7528 .iter()
7529 .map(|tabstop| tabstop.choices.clone())
7530 .collect();
7531
7532 let ranges = tabstops
7533 .into_iter()
7534 .map(|tabstop| tabstop.ranges)
7535 .collect::<Vec<_>>();
7536
7537 self.snippet_stack.push(SnippetState {
7538 active_index: 0,
7539 ranges,
7540 choices,
7541 });
7542 }
7543
7544 // Check whether the just-entered snippet ends with an auto-closable bracket.
7545 if self.autoclose_regions.is_empty() {
7546 let snapshot = self.buffer.read(cx).snapshot(cx);
7547 for selection in &mut self.selections.all::<Point>(cx) {
7548 let selection_head = selection.head();
7549 let Some(scope) = snapshot.language_scope_at(selection_head) else {
7550 continue;
7551 };
7552
7553 let mut bracket_pair = None;
7554 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
7555 let prev_chars = snapshot
7556 .reversed_chars_at(selection_head)
7557 .collect::<String>();
7558 for (pair, enabled) in scope.brackets() {
7559 if enabled
7560 && pair.close
7561 && prev_chars.starts_with(pair.start.as_str())
7562 && next_chars.starts_with(pair.end.as_str())
7563 {
7564 bracket_pair = Some(pair.clone());
7565 break;
7566 }
7567 }
7568 if let Some(pair) = bracket_pair {
7569 let start = snapshot.anchor_after(selection_head);
7570 let end = snapshot.anchor_after(selection_head);
7571 self.autoclose_regions.push(AutocloseRegion {
7572 selection_id: selection.id,
7573 range: start..end,
7574 pair,
7575 });
7576 }
7577 }
7578 }
7579 }
7580 Ok(())
7581 }
7582
7583 pub fn move_to_next_snippet_tabstop(
7584 &mut self,
7585 window: &mut Window,
7586 cx: &mut Context<Self>,
7587 ) -> bool {
7588 self.move_to_snippet_tabstop(Bias::Right, window, cx)
7589 }
7590
7591 pub fn move_to_prev_snippet_tabstop(
7592 &mut self,
7593 window: &mut Window,
7594 cx: &mut Context<Self>,
7595 ) -> bool {
7596 self.move_to_snippet_tabstop(Bias::Left, window, cx)
7597 }
7598
7599 pub fn move_to_snippet_tabstop(
7600 &mut self,
7601 bias: Bias,
7602 window: &mut Window,
7603 cx: &mut Context<Self>,
7604 ) -> bool {
7605 if let Some(mut snippet) = self.snippet_stack.pop() {
7606 match bias {
7607 Bias::Left => {
7608 if snippet.active_index > 0 {
7609 snippet.active_index -= 1;
7610 } else {
7611 self.snippet_stack.push(snippet);
7612 return false;
7613 }
7614 }
7615 Bias::Right => {
7616 if snippet.active_index + 1 < snippet.ranges.len() {
7617 snippet.active_index += 1;
7618 } else {
7619 self.snippet_stack.push(snippet);
7620 return false;
7621 }
7622 }
7623 }
7624 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
7625 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7626 s.select_anchor_ranges(current_ranges.iter().cloned())
7627 });
7628
7629 if let Some(choices) = &snippet.choices[snippet.active_index] {
7630 if let Some(selection) = current_ranges.first() {
7631 self.show_snippet_choices(&choices, selection.clone(), cx);
7632 }
7633 }
7634
7635 // If snippet state is not at the last tabstop, push it back on the stack
7636 if snippet.active_index + 1 < snippet.ranges.len() {
7637 self.snippet_stack.push(snippet);
7638 }
7639 return true;
7640 }
7641 }
7642
7643 false
7644 }
7645
7646 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7647 self.transact(window, cx, |this, window, cx| {
7648 this.select_all(&SelectAll, window, cx);
7649 this.insert("", window, cx);
7650 });
7651 }
7652
7653 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
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.transact(window, cx, |this, window, cx| {
7753 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7754 let line_mode = s.line_mode;
7755 s.move_with(|map, selection| {
7756 if selection.is_empty() && !line_mode {
7757 let cursor = movement::right(map, selection.head());
7758 selection.end = cursor;
7759 selection.reversed = true;
7760 selection.goal = SelectionGoal::None;
7761 }
7762 })
7763 });
7764 this.insert("", window, cx);
7765 this.refresh_inline_completion(true, false, window, cx);
7766 });
7767 }
7768
7769 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
7770 if self.move_to_prev_snippet_tabstop(window, cx) {
7771 return;
7772 }
7773
7774 self.outdent(&Outdent, window, cx);
7775 }
7776
7777 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
7778 if self.move_to_next_snippet_tabstop(window, cx) || self.read_only(cx) {
7779 return;
7780 }
7781
7782 let mut selections = self.selections.all_adjusted(cx);
7783 let buffer = self.buffer.read(cx);
7784 let snapshot = buffer.snapshot(cx);
7785 let rows_iter = selections.iter().map(|s| s.head().row);
7786 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
7787
7788 let mut edits = Vec::new();
7789 let mut prev_edited_row = 0;
7790 let mut row_delta = 0;
7791 for selection in &mut selections {
7792 if selection.start.row != prev_edited_row {
7793 row_delta = 0;
7794 }
7795 prev_edited_row = selection.end.row;
7796
7797 // If the selection is non-empty, then increase the indentation of the selected lines.
7798 if !selection.is_empty() {
7799 row_delta =
7800 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
7801 continue;
7802 }
7803
7804 // If the selection is empty and the cursor is in the leading whitespace before the
7805 // suggested indentation, then auto-indent the line.
7806 let cursor = selection.head();
7807 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
7808 if let Some(suggested_indent) =
7809 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
7810 {
7811 if cursor.column < suggested_indent.len
7812 && cursor.column <= current_indent.len
7813 && current_indent.len <= suggested_indent.len
7814 {
7815 selection.start = Point::new(cursor.row, suggested_indent.len);
7816 selection.end = selection.start;
7817 if row_delta == 0 {
7818 edits.extend(Buffer::edit_for_indent_size_adjustment(
7819 cursor.row,
7820 current_indent,
7821 suggested_indent,
7822 ));
7823 row_delta = suggested_indent.len - current_indent.len;
7824 }
7825 continue;
7826 }
7827 }
7828
7829 // Otherwise, insert a hard or soft tab.
7830 let settings = buffer.language_settings_at(cursor, cx);
7831 let tab_size = if settings.hard_tabs {
7832 IndentSize::tab()
7833 } else {
7834 let tab_size = settings.tab_size.get();
7835 let char_column = snapshot
7836 .text_for_range(Point::new(cursor.row, 0)..cursor)
7837 .flat_map(str::chars)
7838 .count()
7839 + row_delta as usize;
7840 let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size);
7841 IndentSize::spaces(chars_to_next_tab_stop)
7842 };
7843 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
7844 selection.end = selection.start;
7845 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
7846 row_delta += tab_size.len;
7847 }
7848
7849 self.transact(window, cx, |this, window, cx| {
7850 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
7851 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7852 s.select(selections)
7853 });
7854 this.refresh_inline_completion(true, false, window, cx);
7855 });
7856 }
7857
7858 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
7859 if self.read_only(cx) {
7860 return;
7861 }
7862 let mut selections = self.selections.all::<Point>(cx);
7863 let mut prev_edited_row = 0;
7864 let mut row_delta = 0;
7865 let mut edits = Vec::new();
7866 let buffer = self.buffer.read(cx);
7867 let snapshot = buffer.snapshot(cx);
7868 for selection in &mut selections {
7869 if selection.start.row != prev_edited_row {
7870 row_delta = 0;
7871 }
7872 prev_edited_row = selection.end.row;
7873
7874 row_delta =
7875 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
7876 }
7877
7878 self.transact(window, cx, |this, window, cx| {
7879 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
7880 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7881 s.select(selections)
7882 });
7883 });
7884 }
7885
7886 fn indent_selection(
7887 buffer: &MultiBuffer,
7888 snapshot: &MultiBufferSnapshot,
7889 selection: &mut Selection<Point>,
7890 edits: &mut Vec<(Range<Point>, String)>,
7891 delta_for_start_row: u32,
7892 cx: &App,
7893 ) -> u32 {
7894 let settings = buffer.language_settings_at(selection.start, cx);
7895 let tab_size = settings.tab_size.get();
7896 let indent_kind = if settings.hard_tabs {
7897 IndentKind::Tab
7898 } else {
7899 IndentKind::Space
7900 };
7901 let mut start_row = selection.start.row;
7902 let mut end_row = selection.end.row + 1;
7903
7904 // If a selection ends at the beginning of a line, don't indent
7905 // that last line.
7906 if selection.end.column == 0 && selection.end.row > selection.start.row {
7907 end_row -= 1;
7908 }
7909
7910 // Avoid re-indenting a row that has already been indented by a
7911 // previous selection, but still update this selection's column
7912 // to reflect that indentation.
7913 if delta_for_start_row > 0 {
7914 start_row += 1;
7915 selection.start.column += delta_for_start_row;
7916 if selection.end.row == selection.start.row {
7917 selection.end.column += delta_for_start_row;
7918 }
7919 }
7920
7921 let mut delta_for_end_row = 0;
7922 let has_multiple_rows = start_row + 1 != end_row;
7923 for row in start_row..end_row {
7924 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
7925 let indent_delta = match (current_indent.kind, indent_kind) {
7926 (IndentKind::Space, IndentKind::Space) => {
7927 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
7928 IndentSize::spaces(columns_to_next_tab_stop)
7929 }
7930 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
7931 (_, IndentKind::Tab) => IndentSize::tab(),
7932 };
7933
7934 let start = if has_multiple_rows || current_indent.len < selection.start.column {
7935 0
7936 } else {
7937 selection.start.column
7938 };
7939 let row_start = Point::new(row, start);
7940 edits.push((
7941 row_start..row_start,
7942 indent_delta.chars().collect::<String>(),
7943 ));
7944
7945 // Update this selection's endpoints to reflect the indentation.
7946 if row == selection.start.row {
7947 selection.start.column += indent_delta.len;
7948 }
7949 if row == selection.end.row {
7950 selection.end.column += indent_delta.len;
7951 delta_for_end_row = indent_delta.len;
7952 }
7953 }
7954
7955 if selection.start.row == selection.end.row {
7956 delta_for_start_row + delta_for_end_row
7957 } else {
7958 delta_for_end_row
7959 }
7960 }
7961
7962 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
7963 if self.read_only(cx) {
7964 return;
7965 }
7966 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7967 let selections = self.selections.all::<Point>(cx);
7968 let mut deletion_ranges = Vec::new();
7969 let mut last_outdent = None;
7970 {
7971 let buffer = self.buffer.read(cx);
7972 let snapshot = buffer.snapshot(cx);
7973 for selection in &selections {
7974 let settings = buffer.language_settings_at(selection.start, cx);
7975 let tab_size = settings.tab_size.get();
7976 let mut rows = selection.spanned_rows(false, &display_map);
7977
7978 // Avoid re-outdenting a row that has already been outdented by a
7979 // previous selection.
7980 if let Some(last_row) = last_outdent {
7981 if last_row == rows.start {
7982 rows.start = rows.start.next_row();
7983 }
7984 }
7985 let has_multiple_rows = rows.len() > 1;
7986 for row in rows.iter_rows() {
7987 let indent_size = snapshot.indent_size_for_line(row);
7988 if indent_size.len > 0 {
7989 let deletion_len = match indent_size.kind {
7990 IndentKind::Space => {
7991 let columns_to_prev_tab_stop = indent_size.len % tab_size;
7992 if columns_to_prev_tab_stop == 0 {
7993 tab_size
7994 } else {
7995 columns_to_prev_tab_stop
7996 }
7997 }
7998 IndentKind::Tab => 1,
7999 };
8000 let start = if has_multiple_rows
8001 || deletion_len > selection.start.column
8002 || indent_size.len < selection.start.column
8003 {
8004 0
8005 } else {
8006 selection.start.column - deletion_len
8007 };
8008 deletion_ranges.push(
8009 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
8010 );
8011 last_outdent = Some(row);
8012 }
8013 }
8014 }
8015 }
8016
8017 self.transact(window, cx, |this, window, cx| {
8018 this.buffer.update(cx, |buffer, cx| {
8019 let empty_str: Arc<str> = Arc::default();
8020 buffer.edit(
8021 deletion_ranges
8022 .into_iter()
8023 .map(|range| (range, empty_str.clone())),
8024 None,
8025 cx,
8026 );
8027 });
8028 let selections = this.selections.all::<usize>(cx);
8029 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8030 s.select(selections)
8031 });
8032 });
8033 }
8034
8035 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
8036 if self.read_only(cx) {
8037 return;
8038 }
8039 let selections = self
8040 .selections
8041 .all::<usize>(cx)
8042 .into_iter()
8043 .map(|s| s.range());
8044
8045 self.transact(window, cx, |this, window, cx| {
8046 this.buffer.update(cx, |buffer, cx| {
8047 buffer.autoindent_ranges(selections, cx);
8048 });
8049 let selections = this.selections.all::<usize>(cx);
8050 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8051 s.select(selections)
8052 });
8053 });
8054 }
8055
8056 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
8057 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8058 let selections = self.selections.all::<Point>(cx);
8059
8060 let mut new_cursors = Vec::new();
8061 let mut edit_ranges = Vec::new();
8062 let mut selections = selections.iter().peekable();
8063 while let Some(selection) = selections.next() {
8064 let mut rows = selection.spanned_rows(false, &display_map);
8065 let goal_display_column = selection.head().to_display_point(&display_map).column();
8066
8067 // Accumulate contiguous regions of rows that we want to delete.
8068 while let Some(next_selection) = selections.peek() {
8069 let next_rows = next_selection.spanned_rows(false, &display_map);
8070 if next_rows.start <= rows.end {
8071 rows.end = next_rows.end;
8072 selections.next().unwrap();
8073 } else {
8074 break;
8075 }
8076 }
8077
8078 let buffer = &display_map.buffer_snapshot;
8079 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
8080 let edit_end;
8081 let cursor_buffer_row;
8082 if buffer.max_point().row >= rows.end.0 {
8083 // If there's a line after the range, delete the \n from the end of the row range
8084 // and position the cursor on the next line.
8085 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
8086 cursor_buffer_row = rows.end;
8087 } else {
8088 // If there isn't a line after the range, delete the \n from the line before the
8089 // start of the row range and position the cursor there.
8090 edit_start = edit_start.saturating_sub(1);
8091 edit_end = buffer.len();
8092 cursor_buffer_row = rows.start.previous_row();
8093 }
8094
8095 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
8096 *cursor.column_mut() =
8097 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
8098
8099 new_cursors.push((
8100 selection.id,
8101 buffer.anchor_after(cursor.to_point(&display_map)),
8102 ));
8103 edit_ranges.push(edit_start..edit_end);
8104 }
8105
8106 self.transact(window, cx, |this, window, cx| {
8107 let buffer = this.buffer.update(cx, |buffer, cx| {
8108 let empty_str: Arc<str> = Arc::default();
8109 buffer.edit(
8110 edit_ranges
8111 .into_iter()
8112 .map(|range| (range, empty_str.clone())),
8113 None,
8114 cx,
8115 );
8116 buffer.snapshot(cx)
8117 });
8118 let new_selections = new_cursors
8119 .into_iter()
8120 .map(|(id, cursor)| {
8121 let cursor = cursor.to_point(&buffer);
8122 Selection {
8123 id,
8124 start: cursor,
8125 end: cursor,
8126 reversed: false,
8127 goal: SelectionGoal::None,
8128 }
8129 })
8130 .collect();
8131
8132 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8133 s.select(new_selections);
8134 });
8135 });
8136 }
8137
8138 pub fn join_lines_impl(
8139 &mut self,
8140 insert_whitespace: bool,
8141 window: &mut Window,
8142 cx: &mut Context<Self>,
8143 ) {
8144 if self.read_only(cx) {
8145 return;
8146 }
8147 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
8148 for selection in self.selections.all::<Point>(cx) {
8149 let start = MultiBufferRow(selection.start.row);
8150 // Treat single line selections as if they include the next line. Otherwise this action
8151 // would do nothing for single line selections individual cursors.
8152 let end = if selection.start.row == selection.end.row {
8153 MultiBufferRow(selection.start.row + 1)
8154 } else {
8155 MultiBufferRow(selection.end.row)
8156 };
8157
8158 if let Some(last_row_range) = row_ranges.last_mut() {
8159 if start <= last_row_range.end {
8160 last_row_range.end = end;
8161 continue;
8162 }
8163 }
8164 row_ranges.push(start..end);
8165 }
8166
8167 let snapshot = self.buffer.read(cx).snapshot(cx);
8168 let mut cursor_positions = Vec::new();
8169 for row_range in &row_ranges {
8170 let anchor = snapshot.anchor_before(Point::new(
8171 row_range.end.previous_row().0,
8172 snapshot.line_len(row_range.end.previous_row()),
8173 ));
8174 cursor_positions.push(anchor..anchor);
8175 }
8176
8177 self.transact(window, cx, |this, window, cx| {
8178 for row_range in row_ranges.into_iter().rev() {
8179 for row in row_range.iter_rows().rev() {
8180 let end_of_line = Point::new(row.0, snapshot.line_len(row));
8181 let next_line_row = row.next_row();
8182 let indent = snapshot.indent_size_for_line(next_line_row);
8183 let start_of_next_line = Point::new(next_line_row.0, indent.len);
8184
8185 let replace =
8186 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
8187 " "
8188 } else {
8189 ""
8190 };
8191
8192 this.buffer.update(cx, |buffer, cx| {
8193 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
8194 });
8195 }
8196 }
8197
8198 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8199 s.select_anchor_ranges(cursor_positions)
8200 });
8201 });
8202 }
8203
8204 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
8205 self.join_lines_impl(true, window, cx);
8206 }
8207
8208 pub fn sort_lines_case_sensitive(
8209 &mut self,
8210 _: &SortLinesCaseSensitive,
8211 window: &mut Window,
8212 cx: &mut Context<Self>,
8213 ) {
8214 self.manipulate_lines(window, cx, |lines| lines.sort())
8215 }
8216
8217 pub fn sort_lines_case_insensitive(
8218 &mut self,
8219 _: &SortLinesCaseInsensitive,
8220 window: &mut Window,
8221 cx: &mut Context<Self>,
8222 ) {
8223 self.manipulate_lines(window, cx, |lines| {
8224 lines.sort_by_key(|line| line.to_lowercase())
8225 })
8226 }
8227
8228 pub fn unique_lines_case_insensitive(
8229 &mut self,
8230 _: &UniqueLinesCaseInsensitive,
8231 window: &mut Window,
8232 cx: &mut Context<Self>,
8233 ) {
8234 self.manipulate_lines(window, cx, |lines| {
8235 let mut seen = HashSet::default();
8236 lines.retain(|line| seen.insert(line.to_lowercase()));
8237 })
8238 }
8239
8240 pub fn unique_lines_case_sensitive(
8241 &mut self,
8242 _: &UniqueLinesCaseSensitive,
8243 window: &mut Window,
8244 cx: &mut Context<Self>,
8245 ) {
8246 self.manipulate_lines(window, cx, |lines| {
8247 let mut seen = HashSet::default();
8248 lines.retain(|line| seen.insert(*line));
8249 })
8250 }
8251
8252 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
8253 let Some(project) = self.project.clone() else {
8254 return;
8255 };
8256 self.reload(project, window, cx)
8257 .detach_and_notify_err(window, cx);
8258 }
8259
8260 pub fn restore_file(
8261 &mut self,
8262 _: &::git::RestoreFile,
8263 window: &mut Window,
8264 cx: &mut Context<Self>,
8265 ) {
8266 let mut buffer_ids = HashSet::default();
8267 let snapshot = self.buffer().read(cx).snapshot(cx);
8268 for selection in self.selections.all::<usize>(cx) {
8269 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
8270 }
8271
8272 let buffer = self.buffer().read(cx);
8273 let ranges = buffer_ids
8274 .into_iter()
8275 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
8276 .collect::<Vec<_>>();
8277
8278 self.restore_hunks_in_ranges(ranges, window, cx);
8279 }
8280
8281 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
8282 let selections = self
8283 .selections
8284 .all(cx)
8285 .into_iter()
8286 .map(|s| s.range())
8287 .collect();
8288 self.restore_hunks_in_ranges(selections, window, cx);
8289 }
8290
8291 fn restore_hunks_in_ranges(
8292 &mut self,
8293 ranges: Vec<Range<Point>>,
8294 window: &mut Window,
8295 cx: &mut Context<Editor>,
8296 ) {
8297 let mut revert_changes = HashMap::default();
8298 let chunk_by = self
8299 .snapshot(window, cx)
8300 .hunks_for_ranges(ranges)
8301 .into_iter()
8302 .chunk_by(|hunk| hunk.buffer_id);
8303 for (buffer_id, hunks) in &chunk_by {
8304 let hunks = hunks.collect::<Vec<_>>();
8305 for hunk in &hunks {
8306 self.prepare_restore_change(&mut revert_changes, hunk, cx);
8307 }
8308 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
8309 }
8310 drop(chunk_by);
8311 if !revert_changes.is_empty() {
8312 self.transact(window, cx, |editor, window, cx| {
8313 editor.restore(revert_changes, window, cx);
8314 });
8315 }
8316 }
8317
8318 pub fn open_active_item_in_terminal(
8319 &mut self,
8320 _: &OpenInTerminal,
8321 window: &mut Window,
8322 cx: &mut Context<Self>,
8323 ) {
8324 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
8325 let project_path = buffer.read(cx).project_path(cx)?;
8326 let project = self.project.as_ref()?.read(cx);
8327 let entry = project.entry_for_path(&project_path, cx)?;
8328 let parent = match &entry.canonical_path {
8329 Some(canonical_path) => canonical_path.to_path_buf(),
8330 None => project.absolute_path(&project_path, cx)?,
8331 }
8332 .parent()?
8333 .to_path_buf();
8334 Some(parent)
8335 }) {
8336 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
8337 }
8338 }
8339
8340 fn set_breakpoint_context_menu(
8341 &mut self,
8342 row: DisplayRow,
8343 position: Option<Anchor>,
8344 kind: Arc<BreakpointKind>,
8345 clicked_point: gpui::Point<Pixels>,
8346 window: &mut Window,
8347 cx: &mut Context<Self>,
8348 ) {
8349 if !cx.has_flag::<Debugger>() {
8350 return;
8351 }
8352 let source = self
8353 .buffer
8354 .read(cx)
8355 .snapshot(cx)
8356 .anchor_before(Point::new(row.0, 0u32));
8357
8358 let context_menu =
8359 self.breakpoint_context_menu(position.unwrap_or(source), kind, window, cx);
8360
8361 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
8362 self,
8363 source,
8364 clicked_point,
8365 context_menu,
8366 window,
8367 cx,
8368 );
8369 }
8370
8371 fn add_edit_breakpoint_block(
8372 &mut self,
8373 anchor: Anchor,
8374 kind: &BreakpointKind,
8375 window: &mut Window,
8376 cx: &mut Context<Self>,
8377 ) {
8378 let weak_editor = cx.weak_entity();
8379 let bp_prompt =
8380 cx.new(|cx| BreakpointPromptEditor::new(weak_editor, anchor, kind.clone(), window, cx));
8381
8382 let height = bp_prompt.update(cx, |this, cx| {
8383 this.prompt
8384 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
8385 });
8386 let cloned_prompt = bp_prompt.clone();
8387 let blocks = vec![BlockProperties {
8388 style: BlockStyle::Sticky,
8389 placement: BlockPlacement::Above(anchor),
8390 height,
8391 render: Arc::new(move |cx| {
8392 *cloned_prompt.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
8393 cloned_prompt.clone().into_any_element()
8394 }),
8395 priority: 0,
8396 }];
8397
8398 let focus_handle = bp_prompt.focus_handle(cx);
8399 window.focus(&focus_handle);
8400
8401 let block_ids = self.insert_blocks(blocks, None, cx);
8402 bp_prompt.update(cx, |prompt, _| {
8403 prompt.add_block_ids(block_ids);
8404 });
8405 }
8406
8407 pub(crate) fn breakpoint_at_cursor_head(
8408 &self,
8409 window: &mut Window,
8410 cx: &mut Context<Self>,
8411 ) -> Option<(Anchor, Breakpoint)> {
8412 let cursor_position: Point = self.selections.newest(cx).head();
8413 let snapshot = self.snapshot(window, cx);
8414 // We Set the column position to zero so this function interacts correctly
8415 // between calls by clicking on the gutter & using an action to toggle a
8416 // breakpoint. Otherwise, toggling a breakpoint through an action wouldn't
8417 // untoggle a breakpoint that was added through clicking on the gutter
8418 let cursor_position = snapshot
8419 .display_snapshot
8420 .buffer_snapshot
8421 .anchor_before(Point::new(cursor_position.row, 0));
8422
8423 let project = self.project.clone();
8424
8425 let buffer_id = cursor_position.text_anchor.buffer_id?;
8426 let enclosing_excerpt = snapshot
8427 .buffer_snapshot
8428 .excerpt_ids_for_range(cursor_position..cursor_position)
8429 .next()?;
8430 let buffer = project?.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
8431 let buffer_snapshot = buffer.read(cx).snapshot();
8432
8433 let row = buffer_snapshot
8434 .summary_for_anchor::<text::PointUtf16>(&cursor_position.text_anchor)
8435 .row;
8436
8437 let bp = self
8438 .breakpoint_store
8439 .as_ref()?
8440 .read_with(cx, |breakpoint_store, cx| {
8441 breakpoint_store
8442 .breakpoints(
8443 &buffer,
8444 Some(cursor_position.text_anchor..(text::Anchor::MAX)),
8445 buffer_snapshot.clone(),
8446 cx,
8447 )
8448 .next()
8449 .and_then(move |(anchor, bp)| {
8450 let breakpoint_row = buffer_snapshot
8451 .summary_for_anchor::<text::PointUtf16>(anchor)
8452 .row;
8453
8454 if breakpoint_row == row {
8455 snapshot
8456 .buffer_snapshot
8457 .anchor_in_excerpt(enclosing_excerpt, *anchor)
8458 .map(|anchor| (anchor, bp.clone()))
8459 } else {
8460 None
8461 }
8462 })
8463 });
8464 bp
8465 }
8466
8467 pub fn edit_log_breakpoint(
8468 &mut self,
8469 _: &EditLogBreakpoint,
8470 window: &mut Window,
8471 cx: &mut Context<Self>,
8472 ) {
8473 let (anchor, bp) = self
8474 .breakpoint_at_cursor_head(window, cx)
8475 .unwrap_or_else(|| {
8476 let cursor_position: Point = self.selections.newest(cx).head();
8477
8478 let breakpoint_position = self
8479 .snapshot(window, cx)
8480 .display_snapshot
8481 .buffer_snapshot
8482 .anchor_before(Point::new(cursor_position.row, 0));
8483
8484 (
8485 breakpoint_position,
8486 Breakpoint {
8487 kind: BreakpointKind::Standard,
8488 },
8489 )
8490 });
8491
8492 self.add_edit_breakpoint_block(anchor, &bp.kind, window, cx);
8493 }
8494
8495 pub fn toggle_breakpoint(
8496 &mut self,
8497 _: &crate::actions::ToggleBreakpoint,
8498 window: &mut Window,
8499 cx: &mut Context<Self>,
8500 ) {
8501 let edit_action = BreakpointEditAction::Toggle;
8502
8503 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8504 self.edit_breakpoint_at_anchor(anchor, breakpoint.kind, edit_action, cx);
8505 } else {
8506 let cursor_position: Point = self.selections.newest(cx).head();
8507
8508 let breakpoint_position = self
8509 .snapshot(window, cx)
8510 .display_snapshot
8511 .buffer_snapshot
8512 .anchor_before(Point::new(cursor_position.row, 0));
8513
8514 self.edit_breakpoint_at_anchor(
8515 breakpoint_position,
8516 BreakpointKind::Standard,
8517 edit_action,
8518 cx,
8519 );
8520 }
8521 }
8522
8523 pub fn edit_breakpoint_at_anchor(
8524 &mut self,
8525 breakpoint_position: Anchor,
8526 kind: BreakpointKind,
8527 edit_action: BreakpointEditAction,
8528 cx: &mut Context<Self>,
8529 ) {
8530 let Some(breakpoint_store) = &self.breakpoint_store else {
8531 return;
8532 };
8533
8534 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
8535 if breakpoint_position == Anchor::min() {
8536 self.buffer()
8537 .read(cx)
8538 .excerpt_buffer_ids()
8539 .into_iter()
8540 .next()
8541 } else {
8542 None
8543 }
8544 }) else {
8545 return;
8546 };
8547
8548 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
8549 return;
8550 };
8551
8552 breakpoint_store.update(cx, |breakpoint_store, cx| {
8553 breakpoint_store.toggle_breakpoint(
8554 buffer,
8555 (breakpoint_position.text_anchor, Breakpoint { kind }),
8556 edit_action,
8557 cx,
8558 );
8559 });
8560
8561 cx.notify();
8562 }
8563
8564 #[cfg(any(test, feature = "test-support"))]
8565 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
8566 self.breakpoint_store.clone()
8567 }
8568
8569 pub fn prepare_restore_change(
8570 &self,
8571 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
8572 hunk: &MultiBufferDiffHunk,
8573 cx: &mut App,
8574 ) -> Option<()> {
8575 if hunk.is_created_file() {
8576 return None;
8577 }
8578 let buffer = self.buffer.read(cx);
8579 let diff = buffer.diff_for(hunk.buffer_id)?;
8580 let buffer = buffer.buffer(hunk.buffer_id)?;
8581 let buffer = buffer.read(cx);
8582 let original_text = diff
8583 .read(cx)
8584 .base_text()
8585 .as_rope()
8586 .slice(hunk.diff_base_byte_range.clone());
8587 let buffer_snapshot = buffer.snapshot();
8588 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
8589 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
8590 probe
8591 .0
8592 .start
8593 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
8594 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
8595 }) {
8596 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
8597 Some(())
8598 } else {
8599 None
8600 }
8601 }
8602
8603 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
8604 self.manipulate_lines(window, cx, |lines| lines.reverse())
8605 }
8606
8607 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
8608 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
8609 }
8610
8611 fn manipulate_lines<Fn>(
8612 &mut self,
8613 window: &mut Window,
8614 cx: &mut Context<Self>,
8615 mut callback: Fn,
8616 ) where
8617 Fn: FnMut(&mut Vec<&str>),
8618 {
8619 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8620 let buffer = self.buffer.read(cx).snapshot(cx);
8621
8622 let mut edits = Vec::new();
8623
8624 let selections = self.selections.all::<Point>(cx);
8625 let mut selections = selections.iter().peekable();
8626 let mut contiguous_row_selections = Vec::new();
8627 let mut new_selections = Vec::new();
8628 let mut added_lines = 0;
8629 let mut removed_lines = 0;
8630
8631 while let Some(selection) = selections.next() {
8632 let (start_row, end_row) = consume_contiguous_rows(
8633 &mut contiguous_row_selections,
8634 selection,
8635 &display_map,
8636 &mut selections,
8637 );
8638
8639 let start_point = Point::new(start_row.0, 0);
8640 let end_point = Point::new(
8641 end_row.previous_row().0,
8642 buffer.line_len(end_row.previous_row()),
8643 );
8644 let text = buffer
8645 .text_for_range(start_point..end_point)
8646 .collect::<String>();
8647
8648 let mut lines = text.split('\n').collect_vec();
8649
8650 let lines_before = lines.len();
8651 callback(&mut lines);
8652 let lines_after = lines.len();
8653
8654 edits.push((start_point..end_point, lines.join("\n")));
8655
8656 // Selections must change based on added and removed line count
8657 let start_row =
8658 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
8659 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
8660 new_selections.push(Selection {
8661 id: selection.id,
8662 start: start_row,
8663 end: end_row,
8664 goal: SelectionGoal::None,
8665 reversed: selection.reversed,
8666 });
8667
8668 if lines_after > lines_before {
8669 added_lines += lines_after - lines_before;
8670 } else if lines_before > lines_after {
8671 removed_lines += lines_before - lines_after;
8672 }
8673 }
8674
8675 self.transact(window, cx, |this, window, cx| {
8676 let buffer = this.buffer.update(cx, |buffer, cx| {
8677 buffer.edit(edits, None, cx);
8678 buffer.snapshot(cx)
8679 });
8680
8681 // Recalculate offsets on newly edited buffer
8682 let new_selections = new_selections
8683 .iter()
8684 .map(|s| {
8685 let start_point = Point::new(s.start.0, 0);
8686 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
8687 Selection {
8688 id: s.id,
8689 start: buffer.point_to_offset(start_point),
8690 end: buffer.point_to_offset(end_point),
8691 goal: s.goal,
8692 reversed: s.reversed,
8693 }
8694 })
8695 .collect();
8696
8697 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8698 s.select(new_selections);
8699 });
8700
8701 this.request_autoscroll(Autoscroll::fit(), cx);
8702 });
8703 }
8704
8705 pub fn convert_to_upper_case(
8706 &mut self,
8707 _: &ConvertToUpperCase,
8708 window: &mut Window,
8709 cx: &mut Context<Self>,
8710 ) {
8711 self.manipulate_text(window, cx, |text| text.to_uppercase())
8712 }
8713
8714 pub fn convert_to_lower_case(
8715 &mut self,
8716 _: &ConvertToLowerCase,
8717 window: &mut Window,
8718 cx: &mut Context<Self>,
8719 ) {
8720 self.manipulate_text(window, cx, |text| text.to_lowercase())
8721 }
8722
8723 pub fn convert_to_title_case(
8724 &mut self,
8725 _: &ConvertToTitleCase,
8726 window: &mut Window,
8727 cx: &mut Context<Self>,
8728 ) {
8729 self.manipulate_text(window, cx, |text| {
8730 text.split('\n')
8731 .map(|line| line.to_case(Case::Title))
8732 .join("\n")
8733 })
8734 }
8735
8736 pub fn convert_to_snake_case(
8737 &mut self,
8738 _: &ConvertToSnakeCase,
8739 window: &mut Window,
8740 cx: &mut Context<Self>,
8741 ) {
8742 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
8743 }
8744
8745 pub fn convert_to_kebab_case(
8746 &mut self,
8747 _: &ConvertToKebabCase,
8748 window: &mut Window,
8749 cx: &mut Context<Self>,
8750 ) {
8751 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
8752 }
8753
8754 pub fn convert_to_upper_camel_case(
8755 &mut self,
8756 _: &ConvertToUpperCamelCase,
8757 window: &mut Window,
8758 cx: &mut Context<Self>,
8759 ) {
8760 self.manipulate_text(window, cx, |text| {
8761 text.split('\n')
8762 .map(|line| line.to_case(Case::UpperCamel))
8763 .join("\n")
8764 })
8765 }
8766
8767 pub fn convert_to_lower_camel_case(
8768 &mut self,
8769 _: &ConvertToLowerCamelCase,
8770 window: &mut Window,
8771 cx: &mut Context<Self>,
8772 ) {
8773 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
8774 }
8775
8776 pub fn convert_to_opposite_case(
8777 &mut self,
8778 _: &ConvertToOppositeCase,
8779 window: &mut Window,
8780 cx: &mut Context<Self>,
8781 ) {
8782 self.manipulate_text(window, cx, |text| {
8783 text.chars()
8784 .fold(String::with_capacity(text.len()), |mut t, c| {
8785 if c.is_uppercase() {
8786 t.extend(c.to_lowercase());
8787 } else {
8788 t.extend(c.to_uppercase());
8789 }
8790 t
8791 })
8792 })
8793 }
8794
8795 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
8796 where
8797 Fn: FnMut(&str) -> String,
8798 {
8799 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8800 let buffer = self.buffer.read(cx).snapshot(cx);
8801
8802 let mut new_selections = Vec::new();
8803 let mut edits = Vec::new();
8804 let mut selection_adjustment = 0i32;
8805
8806 for selection in self.selections.all::<usize>(cx) {
8807 let selection_is_empty = selection.is_empty();
8808
8809 let (start, end) = if selection_is_empty {
8810 let word_range = movement::surrounding_word(
8811 &display_map,
8812 selection.start.to_display_point(&display_map),
8813 );
8814 let start = word_range.start.to_offset(&display_map, Bias::Left);
8815 let end = word_range.end.to_offset(&display_map, Bias::Left);
8816 (start, end)
8817 } else {
8818 (selection.start, selection.end)
8819 };
8820
8821 let text = buffer.text_for_range(start..end).collect::<String>();
8822 let old_length = text.len() as i32;
8823 let text = callback(&text);
8824
8825 new_selections.push(Selection {
8826 start: (start as i32 - selection_adjustment) as usize,
8827 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
8828 goal: SelectionGoal::None,
8829 ..selection
8830 });
8831
8832 selection_adjustment += old_length - text.len() as i32;
8833
8834 edits.push((start..end, text));
8835 }
8836
8837 self.transact(window, cx, |this, window, cx| {
8838 this.buffer.update(cx, |buffer, cx| {
8839 buffer.edit(edits, None, cx);
8840 });
8841
8842 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8843 s.select(new_selections);
8844 });
8845
8846 this.request_autoscroll(Autoscroll::fit(), cx);
8847 });
8848 }
8849
8850 pub fn duplicate(
8851 &mut self,
8852 upwards: bool,
8853 whole_lines: bool,
8854 window: &mut Window,
8855 cx: &mut Context<Self>,
8856 ) {
8857 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8858 let buffer = &display_map.buffer_snapshot;
8859 let selections = self.selections.all::<Point>(cx);
8860
8861 let mut edits = Vec::new();
8862 let mut selections_iter = selections.iter().peekable();
8863 while let Some(selection) = selections_iter.next() {
8864 let mut rows = selection.spanned_rows(false, &display_map);
8865 // duplicate line-wise
8866 if whole_lines || selection.start == selection.end {
8867 // Avoid duplicating the same lines twice.
8868 while let Some(next_selection) = selections_iter.peek() {
8869 let next_rows = next_selection.spanned_rows(false, &display_map);
8870 if next_rows.start < rows.end {
8871 rows.end = next_rows.end;
8872 selections_iter.next().unwrap();
8873 } else {
8874 break;
8875 }
8876 }
8877
8878 // Copy the text from the selected row region and splice it either at the start
8879 // or end of the region.
8880 let start = Point::new(rows.start.0, 0);
8881 let end = Point::new(
8882 rows.end.previous_row().0,
8883 buffer.line_len(rows.end.previous_row()),
8884 );
8885 let text = buffer
8886 .text_for_range(start..end)
8887 .chain(Some("\n"))
8888 .collect::<String>();
8889 let insert_location = if upwards {
8890 Point::new(rows.end.0, 0)
8891 } else {
8892 start
8893 };
8894 edits.push((insert_location..insert_location, text));
8895 } else {
8896 // duplicate character-wise
8897 let start = selection.start;
8898 let end = selection.end;
8899 let text = buffer.text_for_range(start..end).collect::<String>();
8900 edits.push((selection.end..selection.end, text));
8901 }
8902 }
8903
8904 self.transact(window, cx, |this, _, cx| {
8905 this.buffer.update(cx, |buffer, cx| {
8906 buffer.edit(edits, None, cx);
8907 });
8908
8909 this.request_autoscroll(Autoscroll::fit(), cx);
8910 });
8911 }
8912
8913 pub fn duplicate_line_up(
8914 &mut self,
8915 _: &DuplicateLineUp,
8916 window: &mut Window,
8917 cx: &mut Context<Self>,
8918 ) {
8919 self.duplicate(true, true, window, cx);
8920 }
8921
8922 pub fn duplicate_line_down(
8923 &mut self,
8924 _: &DuplicateLineDown,
8925 window: &mut Window,
8926 cx: &mut Context<Self>,
8927 ) {
8928 self.duplicate(false, true, window, cx);
8929 }
8930
8931 pub fn duplicate_selection(
8932 &mut self,
8933 _: &DuplicateSelection,
8934 window: &mut Window,
8935 cx: &mut Context<Self>,
8936 ) {
8937 self.duplicate(false, false, window, cx);
8938 }
8939
8940 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
8941 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8942 let buffer = self.buffer.read(cx).snapshot(cx);
8943
8944 let mut edits = Vec::new();
8945 let mut unfold_ranges = Vec::new();
8946 let mut refold_creases = Vec::new();
8947
8948 let selections = self.selections.all::<Point>(cx);
8949 let mut selections = selections.iter().peekable();
8950 let mut contiguous_row_selections = Vec::new();
8951 let mut new_selections = Vec::new();
8952
8953 while let Some(selection) = selections.next() {
8954 // Find all the selections that span a contiguous row range
8955 let (start_row, end_row) = consume_contiguous_rows(
8956 &mut contiguous_row_selections,
8957 selection,
8958 &display_map,
8959 &mut selections,
8960 );
8961
8962 // Move the text spanned by the row range to be before the line preceding the row range
8963 if start_row.0 > 0 {
8964 let range_to_move = Point::new(
8965 start_row.previous_row().0,
8966 buffer.line_len(start_row.previous_row()),
8967 )
8968 ..Point::new(
8969 end_row.previous_row().0,
8970 buffer.line_len(end_row.previous_row()),
8971 );
8972 let insertion_point = display_map
8973 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
8974 .0;
8975
8976 // Don't move lines across excerpts
8977 if buffer
8978 .excerpt_containing(insertion_point..range_to_move.end)
8979 .is_some()
8980 {
8981 let text = buffer
8982 .text_for_range(range_to_move.clone())
8983 .flat_map(|s| s.chars())
8984 .skip(1)
8985 .chain(['\n'])
8986 .collect::<String>();
8987
8988 edits.push((
8989 buffer.anchor_after(range_to_move.start)
8990 ..buffer.anchor_before(range_to_move.end),
8991 String::new(),
8992 ));
8993 let insertion_anchor = buffer.anchor_after(insertion_point);
8994 edits.push((insertion_anchor..insertion_anchor, text));
8995
8996 let row_delta = range_to_move.start.row - insertion_point.row + 1;
8997
8998 // Move selections up
8999 new_selections.extend(contiguous_row_selections.drain(..).map(
9000 |mut selection| {
9001 selection.start.row -= row_delta;
9002 selection.end.row -= row_delta;
9003 selection
9004 },
9005 ));
9006
9007 // Move folds up
9008 unfold_ranges.push(range_to_move.clone());
9009 for fold in display_map.folds_in_range(
9010 buffer.anchor_before(range_to_move.start)
9011 ..buffer.anchor_after(range_to_move.end),
9012 ) {
9013 let mut start = fold.range.start.to_point(&buffer);
9014 let mut end = fold.range.end.to_point(&buffer);
9015 start.row -= row_delta;
9016 end.row -= row_delta;
9017 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9018 }
9019 }
9020 }
9021
9022 // If we didn't move line(s), preserve the existing selections
9023 new_selections.append(&mut contiguous_row_selections);
9024 }
9025
9026 self.transact(window, cx, |this, window, cx| {
9027 this.unfold_ranges(&unfold_ranges, true, true, cx);
9028 this.buffer.update(cx, |buffer, cx| {
9029 for (range, text) in edits {
9030 buffer.edit([(range, text)], None, cx);
9031 }
9032 });
9033 this.fold_creases(refold_creases, true, window, cx);
9034 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9035 s.select(new_selections);
9036 })
9037 });
9038 }
9039
9040 pub fn move_line_down(
9041 &mut self,
9042 _: &MoveLineDown,
9043 window: &mut Window,
9044 cx: &mut Context<Self>,
9045 ) {
9046 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9047 let buffer = self.buffer.read(cx).snapshot(cx);
9048
9049 let mut edits = Vec::new();
9050 let mut unfold_ranges = Vec::new();
9051 let mut refold_creases = Vec::new();
9052
9053 let selections = self.selections.all::<Point>(cx);
9054 let mut selections = selections.iter().peekable();
9055 let mut contiguous_row_selections = Vec::new();
9056 let mut new_selections = Vec::new();
9057
9058 while let Some(selection) = selections.next() {
9059 // Find all the selections that span a contiguous row range
9060 let (start_row, end_row) = consume_contiguous_rows(
9061 &mut contiguous_row_selections,
9062 selection,
9063 &display_map,
9064 &mut selections,
9065 );
9066
9067 // Move the text spanned by the row range to be after the last line of the row range
9068 if end_row.0 <= buffer.max_point().row {
9069 let range_to_move =
9070 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
9071 let insertion_point = display_map
9072 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
9073 .0;
9074
9075 // Don't move lines across excerpt boundaries
9076 if buffer
9077 .excerpt_containing(range_to_move.start..insertion_point)
9078 .is_some()
9079 {
9080 let mut text = String::from("\n");
9081 text.extend(buffer.text_for_range(range_to_move.clone()));
9082 text.pop(); // Drop trailing newline
9083 edits.push((
9084 buffer.anchor_after(range_to_move.start)
9085 ..buffer.anchor_before(range_to_move.end),
9086 String::new(),
9087 ));
9088 let insertion_anchor = buffer.anchor_after(insertion_point);
9089 edits.push((insertion_anchor..insertion_anchor, text));
9090
9091 let row_delta = insertion_point.row - range_to_move.end.row + 1;
9092
9093 // Move selections down
9094 new_selections.extend(contiguous_row_selections.drain(..).map(
9095 |mut selection| {
9096 selection.start.row += row_delta;
9097 selection.end.row += row_delta;
9098 selection
9099 },
9100 ));
9101
9102 // Move folds down
9103 unfold_ranges.push(range_to_move.clone());
9104 for fold in display_map.folds_in_range(
9105 buffer.anchor_before(range_to_move.start)
9106 ..buffer.anchor_after(range_to_move.end),
9107 ) {
9108 let mut start = fold.range.start.to_point(&buffer);
9109 let mut end = fold.range.end.to_point(&buffer);
9110 start.row += row_delta;
9111 end.row += row_delta;
9112 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9113 }
9114 }
9115 }
9116
9117 // If we didn't move line(s), preserve the existing selections
9118 new_selections.append(&mut contiguous_row_selections);
9119 }
9120
9121 self.transact(window, cx, |this, window, cx| {
9122 this.unfold_ranges(&unfold_ranges, true, true, cx);
9123 this.buffer.update(cx, |buffer, cx| {
9124 for (range, text) in edits {
9125 buffer.edit([(range, text)], None, cx);
9126 }
9127 });
9128 this.fold_creases(refold_creases, true, window, cx);
9129 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9130 s.select(new_selections)
9131 });
9132 });
9133 }
9134
9135 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
9136 let text_layout_details = &self.text_layout_details(window);
9137 self.transact(window, cx, |this, window, cx| {
9138 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9139 let mut edits: Vec<(Range<usize>, String)> = Default::default();
9140 let line_mode = s.line_mode;
9141 s.move_with(|display_map, selection| {
9142 if !selection.is_empty() || line_mode {
9143 return;
9144 }
9145
9146 let mut head = selection.head();
9147 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
9148 if head.column() == display_map.line_len(head.row()) {
9149 transpose_offset = display_map
9150 .buffer_snapshot
9151 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9152 }
9153
9154 if transpose_offset == 0 {
9155 return;
9156 }
9157
9158 *head.column_mut() += 1;
9159 head = display_map.clip_point(head, Bias::Right);
9160 let goal = SelectionGoal::HorizontalPosition(
9161 display_map
9162 .x_for_display_point(head, text_layout_details)
9163 .into(),
9164 );
9165 selection.collapse_to(head, goal);
9166
9167 let transpose_start = display_map
9168 .buffer_snapshot
9169 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9170 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
9171 let transpose_end = display_map
9172 .buffer_snapshot
9173 .clip_offset(transpose_offset + 1, Bias::Right);
9174 if let Some(ch) =
9175 display_map.buffer_snapshot.chars_at(transpose_start).next()
9176 {
9177 edits.push((transpose_start..transpose_offset, String::new()));
9178 edits.push((transpose_end..transpose_end, ch.to_string()));
9179 }
9180 }
9181 });
9182 edits
9183 });
9184 this.buffer
9185 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9186 let selections = this.selections.all::<usize>(cx);
9187 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9188 s.select(selections);
9189 });
9190 });
9191 }
9192
9193 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
9194 self.rewrap_impl(RewrapOptions::default(), cx)
9195 }
9196
9197 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
9198 let buffer = self.buffer.read(cx).snapshot(cx);
9199 let selections = self.selections.all::<Point>(cx);
9200 let mut selections = selections.iter().peekable();
9201
9202 let mut edits = Vec::new();
9203 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
9204
9205 while let Some(selection) = selections.next() {
9206 let mut start_row = selection.start.row;
9207 let mut end_row = selection.end.row;
9208
9209 // Skip selections that overlap with a range that has already been rewrapped.
9210 let selection_range = start_row..end_row;
9211 if rewrapped_row_ranges
9212 .iter()
9213 .any(|range| range.overlaps(&selection_range))
9214 {
9215 continue;
9216 }
9217
9218 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
9219
9220 // Since not all lines in the selection may be at the same indent
9221 // level, choose the indent size that is the most common between all
9222 // of the lines.
9223 //
9224 // If there is a tie, we use the deepest indent.
9225 let (indent_size, indent_end) = {
9226 let mut indent_size_occurrences = HashMap::default();
9227 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
9228
9229 for row in start_row..=end_row {
9230 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
9231 rows_by_indent_size.entry(indent).or_default().push(row);
9232 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
9233 }
9234
9235 let indent_size = indent_size_occurrences
9236 .into_iter()
9237 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
9238 .map(|(indent, _)| indent)
9239 .unwrap_or_default();
9240 let row = rows_by_indent_size[&indent_size][0];
9241 let indent_end = Point::new(row, indent_size.len);
9242
9243 (indent_size, indent_end)
9244 };
9245
9246 let mut line_prefix = indent_size.chars().collect::<String>();
9247
9248 let mut inside_comment = false;
9249 if let Some(comment_prefix) =
9250 buffer
9251 .language_scope_at(selection.head())
9252 .and_then(|language| {
9253 language
9254 .line_comment_prefixes()
9255 .iter()
9256 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
9257 .cloned()
9258 })
9259 {
9260 line_prefix.push_str(&comment_prefix);
9261 inside_comment = true;
9262 }
9263
9264 let language_settings = buffer.language_settings_at(selection.head(), cx);
9265 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
9266 RewrapBehavior::InComments => inside_comment,
9267 RewrapBehavior::InSelections => !selection.is_empty(),
9268 RewrapBehavior::Anywhere => true,
9269 };
9270
9271 let should_rewrap = options.override_language_settings
9272 || allow_rewrap_based_on_language
9273 || self.hard_wrap.is_some();
9274 if !should_rewrap {
9275 continue;
9276 }
9277
9278 if selection.is_empty() {
9279 'expand_upwards: while start_row > 0 {
9280 let prev_row = start_row - 1;
9281 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
9282 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
9283 {
9284 start_row = prev_row;
9285 } else {
9286 break 'expand_upwards;
9287 }
9288 }
9289
9290 'expand_downwards: while end_row < buffer.max_point().row {
9291 let next_row = end_row + 1;
9292 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
9293 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
9294 {
9295 end_row = next_row;
9296 } else {
9297 break 'expand_downwards;
9298 }
9299 }
9300 }
9301
9302 let start = Point::new(start_row, 0);
9303 let start_offset = start.to_offset(&buffer);
9304 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
9305 let selection_text = buffer.text_for_range(start..end).collect::<String>();
9306 let Some(lines_without_prefixes) = selection_text
9307 .lines()
9308 .map(|line| {
9309 line.strip_prefix(&line_prefix)
9310 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
9311 .ok_or_else(|| {
9312 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
9313 })
9314 })
9315 .collect::<Result<Vec<_>, _>>()
9316 .log_err()
9317 else {
9318 continue;
9319 };
9320
9321 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
9322 buffer
9323 .language_settings_at(Point::new(start_row, 0), cx)
9324 .preferred_line_length as usize
9325 });
9326 let wrapped_text = wrap_with_prefix(
9327 line_prefix,
9328 lines_without_prefixes.join("\n"),
9329 wrap_column,
9330 tab_size,
9331 options.preserve_existing_whitespace,
9332 );
9333
9334 // TODO: should always use char-based diff while still supporting cursor behavior that
9335 // matches vim.
9336 let mut diff_options = DiffOptions::default();
9337 if options.override_language_settings {
9338 diff_options.max_word_diff_len = 0;
9339 diff_options.max_word_diff_line_count = 0;
9340 } else {
9341 diff_options.max_word_diff_len = usize::MAX;
9342 diff_options.max_word_diff_line_count = usize::MAX;
9343 }
9344
9345 for (old_range, new_text) in
9346 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
9347 {
9348 let edit_start = buffer.anchor_after(start_offset + old_range.start);
9349 let edit_end = buffer.anchor_after(start_offset + old_range.end);
9350 edits.push((edit_start..edit_end, new_text));
9351 }
9352
9353 rewrapped_row_ranges.push(start_row..=end_row);
9354 }
9355
9356 self.buffer
9357 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9358 }
9359
9360 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
9361 let mut text = String::new();
9362 let buffer = self.buffer.read(cx).snapshot(cx);
9363 let mut selections = self.selections.all::<Point>(cx);
9364 let mut clipboard_selections = Vec::with_capacity(selections.len());
9365 {
9366 let max_point = buffer.max_point();
9367 let mut is_first = true;
9368 for selection in &mut selections {
9369 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9370 if is_entire_line {
9371 selection.start = Point::new(selection.start.row, 0);
9372 if !selection.is_empty() && selection.end.column == 0 {
9373 selection.end = cmp::min(max_point, selection.end);
9374 } else {
9375 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
9376 }
9377 selection.goal = SelectionGoal::None;
9378 }
9379 if is_first {
9380 is_first = false;
9381 } else {
9382 text += "\n";
9383 }
9384 let mut len = 0;
9385 for chunk in buffer.text_for_range(selection.start..selection.end) {
9386 text.push_str(chunk);
9387 len += chunk.len();
9388 }
9389 clipboard_selections.push(ClipboardSelection {
9390 len,
9391 is_entire_line,
9392 first_line_indent: buffer
9393 .indent_size_for_line(MultiBufferRow(selection.start.row))
9394 .len,
9395 });
9396 }
9397 }
9398
9399 self.transact(window, cx, |this, window, cx| {
9400 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9401 s.select(selections);
9402 });
9403 this.insert("", window, cx);
9404 });
9405 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
9406 }
9407
9408 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
9409 let item = self.cut_common(window, cx);
9410 cx.write_to_clipboard(item);
9411 }
9412
9413 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
9414 self.change_selections(None, window, cx, |s| {
9415 s.move_with(|snapshot, sel| {
9416 if sel.is_empty() {
9417 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
9418 }
9419 });
9420 });
9421 let item = self.cut_common(window, cx);
9422 cx.set_global(KillRing(item))
9423 }
9424
9425 pub fn kill_ring_yank(
9426 &mut self,
9427 _: &KillRingYank,
9428 window: &mut Window,
9429 cx: &mut Context<Self>,
9430 ) {
9431 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
9432 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
9433 (kill_ring.text().to_string(), kill_ring.metadata_json())
9434 } else {
9435 return;
9436 }
9437 } else {
9438 return;
9439 };
9440 self.do_paste(&text, metadata, false, window, cx);
9441 }
9442
9443 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
9444 let selections = self.selections.all::<Point>(cx);
9445 let buffer = self.buffer.read(cx).read(cx);
9446 let mut text = String::new();
9447
9448 let mut clipboard_selections = Vec::with_capacity(selections.len());
9449 {
9450 let max_point = buffer.max_point();
9451 let mut is_first = true;
9452 for selection in selections.iter() {
9453 let mut start = selection.start;
9454 let mut end = selection.end;
9455 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9456 if is_entire_line {
9457 start = Point::new(start.row, 0);
9458 end = cmp::min(max_point, Point::new(end.row + 1, 0));
9459 }
9460 if is_first {
9461 is_first = false;
9462 } else {
9463 text += "\n";
9464 }
9465 let mut len = 0;
9466 for chunk in buffer.text_for_range(start..end) {
9467 text.push_str(chunk);
9468 len += chunk.len();
9469 }
9470 clipboard_selections.push(ClipboardSelection {
9471 len,
9472 is_entire_line,
9473 first_line_indent: buffer.indent_size_for_line(MultiBufferRow(start.row)).len,
9474 });
9475 }
9476 }
9477
9478 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
9479 text,
9480 clipboard_selections,
9481 ));
9482 }
9483
9484 pub fn do_paste(
9485 &mut self,
9486 text: &String,
9487 clipboard_selections: Option<Vec<ClipboardSelection>>,
9488 handle_entire_lines: bool,
9489 window: &mut Window,
9490 cx: &mut Context<Self>,
9491 ) {
9492 if self.read_only(cx) {
9493 return;
9494 }
9495
9496 let clipboard_text = Cow::Borrowed(text);
9497
9498 self.transact(window, cx, |this, window, cx| {
9499 if let Some(mut clipboard_selections) = clipboard_selections {
9500 let old_selections = this.selections.all::<usize>(cx);
9501 let all_selections_were_entire_line =
9502 clipboard_selections.iter().all(|s| s.is_entire_line);
9503 let first_selection_indent_column =
9504 clipboard_selections.first().map(|s| s.first_line_indent);
9505 if clipboard_selections.len() != old_selections.len() {
9506 clipboard_selections.drain(..);
9507 }
9508 let cursor_offset = this.selections.last::<usize>(cx).head();
9509 let mut auto_indent_on_paste = true;
9510
9511 this.buffer.update(cx, |buffer, cx| {
9512 let snapshot = buffer.read(cx);
9513 auto_indent_on_paste = snapshot
9514 .language_settings_at(cursor_offset, cx)
9515 .auto_indent_on_paste;
9516
9517 let mut start_offset = 0;
9518 let mut edits = Vec::new();
9519 let mut original_indent_columns = Vec::new();
9520 for (ix, selection) in old_selections.iter().enumerate() {
9521 let to_insert;
9522 let entire_line;
9523 let original_indent_column;
9524 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
9525 let end_offset = start_offset + clipboard_selection.len;
9526 to_insert = &clipboard_text[start_offset..end_offset];
9527 entire_line = clipboard_selection.is_entire_line;
9528 start_offset = end_offset + 1;
9529 original_indent_column = Some(clipboard_selection.first_line_indent);
9530 } else {
9531 to_insert = clipboard_text.as_str();
9532 entire_line = all_selections_were_entire_line;
9533 original_indent_column = first_selection_indent_column
9534 }
9535
9536 // If the corresponding selection was empty when this slice of the
9537 // clipboard text was written, then the entire line containing the
9538 // selection was copied. If this selection is also currently empty,
9539 // then paste the line before the current line of the buffer.
9540 let range = if selection.is_empty() && handle_entire_lines && entire_line {
9541 let column = selection.start.to_point(&snapshot).column as usize;
9542 let line_start = selection.start - column;
9543 line_start..line_start
9544 } else {
9545 selection.range()
9546 };
9547
9548 edits.push((range, to_insert));
9549 original_indent_columns.push(original_indent_column);
9550 }
9551 drop(snapshot);
9552
9553 buffer.edit(
9554 edits,
9555 if auto_indent_on_paste {
9556 Some(AutoindentMode::Block {
9557 original_indent_columns,
9558 })
9559 } else {
9560 None
9561 },
9562 cx,
9563 );
9564 });
9565
9566 let selections = this.selections.all::<usize>(cx);
9567 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9568 s.select(selections)
9569 });
9570 } else {
9571 this.insert(&clipboard_text, window, cx);
9572 }
9573 });
9574 }
9575
9576 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
9577 if let Some(item) = cx.read_from_clipboard() {
9578 let entries = item.entries();
9579
9580 match entries.first() {
9581 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
9582 // of all the pasted entries.
9583 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
9584 .do_paste(
9585 clipboard_string.text(),
9586 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
9587 true,
9588 window,
9589 cx,
9590 ),
9591 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
9592 }
9593 }
9594 }
9595
9596 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
9597 if self.read_only(cx) {
9598 return;
9599 }
9600
9601 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
9602 if let Some((selections, _)) =
9603 self.selection_history.transaction(transaction_id).cloned()
9604 {
9605 self.change_selections(None, window, cx, |s| {
9606 s.select_anchors(selections.to_vec());
9607 });
9608 } else {
9609 log::error!(
9610 "No entry in selection_history found for undo. \
9611 This may correspond to a bug where undo does not update the selection. \
9612 If this is occurring, please add details to \
9613 https://github.com/zed-industries/zed/issues/22692"
9614 );
9615 }
9616 self.request_autoscroll(Autoscroll::fit(), cx);
9617 self.unmark_text(window, cx);
9618 self.refresh_inline_completion(true, false, window, cx);
9619 cx.emit(EditorEvent::Edited { transaction_id });
9620 cx.emit(EditorEvent::TransactionUndone { transaction_id });
9621 }
9622 }
9623
9624 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
9625 if self.read_only(cx) {
9626 return;
9627 }
9628
9629 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
9630 if let Some((_, Some(selections))) =
9631 self.selection_history.transaction(transaction_id).cloned()
9632 {
9633 self.change_selections(None, window, cx, |s| {
9634 s.select_anchors(selections.to_vec());
9635 });
9636 } else {
9637 log::error!(
9638 "No entry in selection_history found for redo. \
9639 This may correspond to a bug where undo does not update the selection. \
9640 If this is occurring, please add details to \
9641 https://github.com/zed-industries/zed/issues/22692"
9642 );
9643 }
9644 self.request_autoscroll(Autoscroll::fit(), cx);
9645 self.unmark_text(window, cx);
9646 self.refresh_inline_completion(true, false, window, cx);
9647 cx.emit(EditorEvent::Edited { transaction_id });
9648 }
9649 }
9650
9651 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
9652 self.buffer
9653 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
9654 }
9655
9656 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
9657 self.buffer
9658 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
9659 }
9660
9661 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
9662 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9663 let line_mode = s.line_mode;
9664 s.move_with(|map, selection| {
9665 let cursor = if selection.is_empty() && !line_mode {
9666 movement::left(map, selection.start)
9667 } else {
9668 selection.start
9669 };
9670 selection.collapse_to(cursor, SelectionGoal::None);
9671 });
9672 })
9673 }
9674
9675 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
9676 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9677 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
9678 })
9679 }
9680
9681 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
9682 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9683 let line_mode = s.line_mode;
9684 s.move_with(|map, selection| {
9685 let cursor = if selection.is_empty() && !line_mode {
9686 movement::right(map, selection.end)
9687 } else {
9688 selection.end
9689 };
9690 selection.collapse_to(cursor, SelectionGoal::None)
9691 });
9692 })
9693 }
9694
9695 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
9696 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9697 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
9698 })
9699 }
9700
9701 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
9702 if self.take_rename(true, window, cx).is_some() {
9703 return;
9704 }
9705
9706 if matches!(self.mode, EditorMode::SingleLine { .. }) {
9707 cx.propagate();
9708 return;
9709 }
9710
9711 let text_layout_details = &self.text_layout_details(window);
9712 let selection_count = self.selections.count();
9713 let first_selection = self.selections.first_anchor();
9714
9715 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9716 let line_mode = s.line_mode;
9717 s.move_with(|map, selection| {
9718 if !selection.is_empty() && !line_mode {
9719 selection.goal = SelectionGoal::None;
9720 }
9721 let (cursor, goal) = movement::up(
9722 map,
9723 selection.start,
9724 selection.goal,
9725 false,
9726 text_layout_details,
9727 );
9728 selection.collapse_to(cursor, goal);
9729 });
9730 });
9731
9732 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
9733 {
9734 cx.propagate();
9735 }
9736 }
9737
9738 pub fn move_up_by_lines(
9739 &mut self,
9740 action: &MoveUpByLines,
9741 window: &mut Window,
9742 cx: &mut Context<Self>,
9743 ) {
9744 if self.take_rename(true, window, cx).is_some() {
9745 return;
9746 }
9747
9748 if matches!(self.mode, EditorMode::SingleLine { .. }) {
9749 cx.propagate();
9750 return;
9751 }
9752
9753 let text_layout_details = &self.text_layout_details(window);
9754
9755 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9756 let line_mode = s.line_mode;
9757 s.move_with(|map, selection| {
9758 if !selection.is_empty() && !line_mode {
9759 selection.goal = SelectionGoal::None;
9760 }
9761 let (cursor, goal) = movement::up_by_rows(
9762 map,
9763 selection.start,
9764 action.lines,
9765 selection.goal,
9766 false,
9767 text_layout_details,
9768 );
9769 selection.collapse_to(cursor, goal);
9770 });
9771 })
9772 }
9773
9774 pub fn move_down_by_lines(
9775 &mut self,
9776 action: &MoveDownByLines,
9777 window: &mut Window,
9778 cx: &mut Context<Self>,
9779 ) {
9780 if self.take_rename(true, window, cx).is_some() {
9781 return;
9782 }
9783
9784 if matches!(self.mode, EditorMode::SingleLine { .. }) {
9785 cx.propagate();
9786 return;
9787 }
9788
9789 let text_layout_details = &self.text_layout_details(window);
9790
9791 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9792 let line_mode = s.line_mode;
9793 s.move_with(|map, selection| {
9794 if !selection.is_empty() && !line_mode {
9795 selection.goal = SelectionGoal::None;
9796 }
9797 let (cursor, goal) = movement::down_by_rows(
9798 map,
9799 selection.start,
9800 action.lines,
9801 selection.goal,
9802 false,
9803 text_layout_details,
9804 );
9805 selection.collapse_to(cursor, goal);
9806 });
9807 })
9808 }
9809
9810 pub fn select_down_by_lines(
9811 &mut self,
9812 action: &SelectDownByLines,
9813 window: &mut Window,
9814 cx: &mut Context<Self>,
9815 ) {
9816 let text_layout_details = &self.text_layout_details(window);
9817 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9818 s.move_heads_with(|map, head, goal| {
9819 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
9820 })
9821 })
9822 }
9823
9824 pub fn select_up_by_lines(
9825 &mut self,
9826 action: &SelectUpByLines,
9827 window: &mut Window,
9828 cx: &mut Context<Self>,
9829 ) {
9830 let text_layout_details = &self.text_layout_details(window);
9831 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9832 s.move_heads_with(|map, head, goal| {
9833 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
9834 })
9835 })
9836 }
9837
9838 pub fn select_page_up(
9839 &mut self,
9840 _: &SelectPageUp,
9841 window: &mut Window,
9842 cx: &mut Context<Self>,
9843 ) {
9844 let Some(row_count) = self.visible_row_count() else {
9845 return;
9846 };
9847
9848 let text_layout_details = &self.text_layout_details(window);
9849
9850 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9851 s.move_heads_with(|map, head, goal| {
9852 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
9853 })
9854 })
9855 }
9856
9857 pub fn move_page_up(
9858 &mut self,
9859 action: &MovePageUp,
9860 window: &mut Window,
9861 cx: &mut Context<Self>,
9862 ) {
9863 if self.take_rename(true, window, cx).is_some() {
9864 return;
9865 }
9866
9867 if self
9868 .context_menu
9869 .borrow_mut()
9870 .as_mut()
9871 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
9872 .unwrap_or(false)
9873 {
9874 return;
9875 }
9876
9877 if matches!(self.mode, EditorMode::SingleLine { .. }) {
9878 cx.propagate();
9879 return;
9880 }
9881
9882 let Some(row_count) = self.visible_row_count() else {
9883 return;
9884 };
9885
9886 let autoscroll = if action.center_cursor {
9887 Autoscroll::center()
9888 } else {
9889 Autoscroll::fit()
9890 };
9891
9892 let text_layout_details = &self.text_layout_details(window);
9893
9894 self.change_selections(Some(autoscroll), window, cx, |s| {
9895 let line_mode = s.line_mode;
9896 s.move_with(|map, selection| {
9897 if !selection.is_empty() && !line_mode {
9898 selection.goal = SelectionGoal::None;
9899 }
9900 let (cursor, goal) = movement::up_by_rows(
9901 map,
9902 selection.end,
9903 row_count,
9904 selection.goal,
9905 false,
9906 text_layout_details,
9907 );
9908 selection.collapse_to(cursor, goal);
9909 });
9910 });
9911 }
9912
9913 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
9914 let text_layout_details = &self.text_layout_details(window);
9915 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9916 s.move_heads_with(|map, head, goal| {
9917 movement::up(map, head, goal, false, text_layout_details)
9918 })
9919 })
9920 }
9921
9922 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
9923 self.take_rename(true, window, cx);
9924
9925 if matches!(self.mode, EditorMode::SingleLine { .. }) {
9926 cx.propagate();
9927 return;
9928 }
9929
9930 let text_layout_details = &self.text_layout_details(window);
9931 let selection_count = self.selections.count();
9932 let first_selection = self.selections.first_anchor();
9933
9934 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9935 let line_mode = s.line_mode;
9936 s.move_with(|map, selection| {
9937 if !selection.is_empty() && !line_mode {
9938 selection.goal = SelectionGoal::None;
9939 }
9940 let (cursor, goal) = movement::down(
9941 map,
9942 selection.end,
9943 selection.goal,
9944 false,
9945 text_layout_details,
9946 );
9947 selection.collapse_to(cursor, goal);
9948 });
9949 });
9950
9951 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
9952 {
9953 cx.propagate();
9954 }
9955 }
9956
9957 pub fn select_page_down(
9958 &mut self,
9959 _: &SelectPageDown,
9960 window: &mut Window,
9961 cx: &mut Context<Self>,
9962 ) {
9963 let Some(row_count) = self.visible_row_count() else {
9964 return;
9965 };
9966
9967 let text_layout_details = &self.text_layout_details(window);
9968
9969 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9970 s.move_heads_with(|map, head, goal| {
9971 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
9972 })
9973 })
9974 }
9975
9976 pub fn move_page_down(
9977 &mut self,
9978 action: &MovePageDown,
9979 window: &mut Window,
9980 cx: &mut Context<Self>,
9981 ) {
9982 if self.take_rename(true, window, cx).is_some() {
9983 return;
9984 }
9985
9986 if self
9987 .context_menu
9988 .borrow_mut()
9989 .as_mut()
9990 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
9991 .unwrap_or(false)
9992 {
9993 return;
9994 }
9995
9996 if matches!(self.mode, EditorMode::SingleLine { .. }) {
9997 cx.propagate();
9998 return;
9999 }
10000
10001 let Some(row_count) = self.visible_row_count() else {
10002 return;
10003 };
10004
10005 let autoscroll = if action.center_cursor {
10006 Autoscroll::center()
10007 } else {
10008 Autoscroll::fit()
10009 };
10010
10011 let text_layout_details = &self.text_layout_details(window);
10012 self.change_selections(Some(autoscroll), window, cx, |s| {
10013 let line_mode = s.line_mode;
10014 s.move_with(|map, selection| {
10015 if !selection.is_empty() && !line_mode {
10016 selection.goal = SelectionGoal::None;
10017 }
10018 let (cursor, goal) = movement::down_by_rows(
10019 map,
10020 selection.end,
10021 row_count,
10022 selection.goal,
10023 false,
10024 text_layout_details,
10025 );
10026 selection.collapse_to(cursor, goal);
10027 });
10028 });
10029 }
10030
10031 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
10032 let text_layout_details = &self.text_layout_details(window);
10033 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10034 s.move_heads_with(|map, head, goal| {
10035 movement::down(map, head, goal, false, text_layout_details)
10036 })
10037 });
10038 }
10039
10040 pub fn context_menu_first(
10041 &mut self,
10042 _: &ContextMenuFirst,
10043 _window: &mut Window,
10044 cx: &mut Context<Self>,
10045 ) {
10046 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10047 context_menu.select_first(self.completion_provider.as_deref(), cx);
10048 }
10049 }
10050
10051 pub fn context_menu_prev(
10052 &mut self,
10053 _: &ContextMenuPrevious,
10054 _window: &mut Window,
10055 cx: &mut Context<Self>,
10056 ) {
10057 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10058 context_menu.select_prev(self.completion_provider.as_deref(), cx);
10059 }
10060 }
10061
10062 pub fn context_menu_next(
10063 &mut self,
10064 _: &ContextMenuNext,
10065 _window: &mut Window,
10066 cx: &mut Context<Self>,
10067 ) {
10068 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10069 context_menu.select_next(self.completion_provider.as_deref(), cx);
10070 }
10071 }
10072
10073 pub fn context_menu_last(
10074 &mut self,
10075 _: &ContextMenuLast,
10076 _window: &mut Window,
10077 cx: &mut Context<Self>,
10078 ) {
10079 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10080 context_menu.select_last(self.completion_provider.as_deref(), cx);
10081 }
10082 }
10083
10084 pub fn move_to_previous_word_start(
10085 &mut self,
10086 _: &MoveToPreviousWordStart,
10087 window: &mut Window,
10088 cx: &mut Context<Self>,
10089 ) {
10090 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10091 s.move_cursors_with(|map, head, _| {
10092 (
10093 movement::previous_word_start(map, head),
10094 SelectionGoal::None,
10095 )
10096 });
10097 })
10098 }
10099
10100 pub fn move_to_previous_subword_start(
10101 &mut self,
10102 _: &MoveToPreviousSubwordStart,
10103 window: &mut Window,
10104 cx: &mut Context<Self>,
10105 ) {
10106 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10107 s.move_cursors_with(|map, head, _| {
10108 (
10109 movement::previous_subword_start(map, head),
10110 SelectionGoal::None,
10111 )
10112 });
10113 })
10114 }
10115
10116 pub fn select_to_previous_word_start(
10117 &mut self,
10118 _: &SelectToPreviousWordStart,
10119 window: &mut Window,
10120 cx: &mut Context<Self>,
10121 ) {
10122 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10123 s.move_heads_with(|map, head, _| {
10124 (
10125 movement::previous_word_start(map, head),
10126 SelectionGoal::None,
10127 )
10128 });
10129 })
10130 }
10131
10132 pub fn select_to_previous_subword_start(
10133 &mut self,
10134 _: &SelectToPreviousSubwordStart,
10135 window: &mut Window,
10136 cx: &mut Context<Self>,
10137 ) {
10138 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10139 s.move_heads_with(|map, head, _| {
10140 (
10141 movement::previous_subword_start(map, head),
10142 SelectionGoal::None,
10143 )
10144 });
10145 })
10146 }
10147
10148 pub fn delete_to_previous_word_start(
10149 &mut self,
10150 action: &DeleteToPreviousWordStart,
10151 window: &mut Window,
10152 cx: &mut Context<Self>,
10153 ) {
10154 self.transact(window, cx, |this, window, cx| {
10155 this.select_autoclose_pair(window, cx);
10156 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10157 let line_mode = s.line_mode;
10158 s.move_with(|map, selection| {
10159 if selection.is_empty() && !line_mode {
10160 let cursor = if action.ignore_newlines {
10161 movement::previous_word_start(map, selection.head())
10162 } else {
10163 movement::previous_word_start_or_newline(map, selection.head())
10164 };
10165 selection.set_head(cursor, SelectionGoal::None);
10166 }
10167 });
10168 });
10169 this.insert("", window, cx);
10170 });
10171 }
10172
10173 pub fn delete_to_previous_subword_start(
10174 &mut self,
10175 _: &DeleteToPreviousSubwordStart,
10176 window: &mut Window,
10177 cx: &mut Context<Self>,
10178 ) {
10179 self.transact(window, cx, |this, window, cx| {
10180 this.select_autoclose_pair(window, cx);
10181 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10182 let line_mode = s.line_mode;
10183 s.move_with(|map, selection| {
10184 if selection.is_empty() && !line_mode {
10185 let cursor = movement::previous_subword_start(map, selection.head());
10186 selection.set_head(cursor, SelectionGoal::None);
10187 }
10188 });
10189 });
10190 this.insert("", window, cx);
10191 });
10192 }
10193
10194 pub fn move_to_next_word_end(
10195 &mut self,
10196 _: &MoveToNextWordEnd,
10197 window: &mut Window,
10198 cx: &mut Context<Self>,
10199 ) {
10200 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10201 s.move_cursors_with(|map, head, _| {
10202 (movement::next_word_end(map, head), SelectionGoal::None)
10203 });
10204 })
10205 }
10206
10207 pub fn move_to_next_subword_end(
10208 &mut self,
10209 _: &MoveToNextSubwordEnd,
10210 window: &mut Window,
10211 cx: &mut Context<Self>,
10212 ) {
10213 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10214 s.move_cursors_with(|map, head, _| {
10215 (movement::next_subword_end(map, head), SelectionGoal::None)
10216 });
10217 })
10218 }
10219
10220 pub fn select_to_next_word_end(
10221 &mut self,
10222 _: &SelectToNextWordEnd,
10223 window: &mut Window,
10224 cx: &mut Context<Self>,
10225 ) {
10226 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10227 s.move_heads_with(|map, head, _| {
10228 (movement::next_word_end(map, head), SelectionGoal::None)
10229 });
10230 })
10231 }
10232
10233 pub fn select_to_next_subword_end(
10234 &mut self,
10235 _: &SelectToNextSubwordEnd,
10236 window: &mut Window,
10237 cx: &mut Context<Self>,
10238 ) {
10239 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10240 s.move_heads_with(|map, head, _| {
10241 (movement::next_subword_end(map, head), SelectionGoal::None)
10242 });
10243 })
10244 }
10245
10246 pub fn delete_to_next_word_end(
10247 &mut self,
10248 action: &DeleteToNextWordEnd,
10249 window: &mut Window,
10250 cx: &mut Context<Self>,
10251 ) {
10252 self.transact(window, cx, |this, window, cx| {
10253 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10254 let line_mode = s.line_mode;
10255 s.move_with(|map, selection| {
10256 if selection.is_empty() && !line_mode {
10257 let cursor = if action.ignore_newlines {
10258 movement::next_word_end(map, selection.head())
10259 } else {
10260 movement::next_word_end_or_newline(map, selection.head())
10261 };
10262 selection.set_head(cursor, SelectionGoal::None);
10263 }
10264 });
10265 });
10266 this.insert("", window, cx);
10267 });
10268 }
10269
10270 pub fn delete_to_next_subword_end(
10271 &mut self,
10272 _: &DeleteToNextSubwordEnd,
10273 window: &mut Window,
10274 cx: &mut Context<Self>,
10275 ) {
10276 self.transact(window, cx, |this, window, cx| {
10277 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10278 s.move_with(|map, selection| {
10279 if selection.is_empty() {
10280 let cursor = movement::next_subword_end(map, selection.head());
10281 selection.set_head(cursor, SelectionGoal::None);
10282 }
10283 });
10284 });
10285 this.insert("", window, cx);
10286 });
10287 }
10288
10289 pub fn move_to_beginning_of_line(
10290 &mut self,
10291 action: &MoveToBeginningOfLine,
10292 window: &mut Window,
10293 cx: &mut Context<Self>,
10294 ) {
10295 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10296 s.move_cursors_with(|map, head, _| {
10297 (
10298 movement::indented_line_beginning(
10299 map,
10300 head,
10301 action.stop_at_soft_wraps,
10302 action.stop_at_indent,
10303 ),
10304 SelectionGoal::None,
10305 )
10306 });
10307 })
10308 }
10309
10310 pub fn select_to_beginning_of_line(
10311 &mut self,
10312 action: &SelectToBeginningOfLine,
10313 window: &mut Window,
10314 cx: &mut Context<Self>,
10315 ) {
10316 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10317 s.move_heads_with(|map, head, _| {
10318 (
10319 movement::indented_line_beginning(
10320 map,
10321 head,
10322 action.stop_at_soft_wraps,
10323 action.stop_at_indent,
10324 ),
10325 SelectionGoal::None,
10326 )
10327 });
10328 });
10329 }
10330
10331 pub fn delete_to_beginning_of_line(
10332 &mut self,
10333 action: &DeleteToBeginningOfLine,
10334 window: &mut Window,
10335 cx: &mut Context<Self>,
10336 ) {
10337 self.transact(window, cx, |this, window, cx| {
10338 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10339 s.move_with(|_, selection| {
10340 selection.reversed = true;
10341 });
10342 });
10343
10344 this.select_to_beginning_of_line(
10345 &SelectToBeginningOfLine {
10346 stop_at_soft_wraps: false,
10347 stop_at_indent: action.stop_at_indent,
10348 },
10349 window,
10350 cx,
10351 );
10352 this.backspace(&Backspace, window, cx);
10353 });
10354 }
10355
10356 pub fn move_to_end_of_line(
10357 &mut self,
10358 action: &MoveToEndOfLine,
10359 window: &mut Window,
10360 cx: &mut Context<Self>,
10361 ) {
10362 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10363 s.move_cursors_with(|map, head, _| {
10364 (
10365 movement::line_end(map, head, action.stop_at_soft_wraps),
10366 SelectionGoal::None,
10367 )
10368 });
10369 })
10370 }
10371
10372 pub fn select_to_end_of_line(
10373 &mut self,
10374 action: &SelectToEndOfLine,
10375 window: &mut Window,
10376 cx: &mut Context<Self>,
10377 ) {
10378 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10379 s.move_heads_with(|map, head, _| {
10380 (
10381 movement::line_end(map, head, action.stop_at_soft_wraps),
10382 SelectionGoal::None,
10383 )
10384 });
10385 })
10386 }
10387
10388 pub fn delete_to_end_of_line(
10389 &mut self,
10390 _: &DeleteToEndOfLine,
10391 window: &mut Window,
10392 cx: &mut Context<Self>,
10393 ) {
10394 self.transact(window, cx, |this, window, cx| {
10395 this.select_to_end_of_line(
10396 &SelectToEndOfLine {
10397 stop_at_soft_wraps: false,
10398 },
10399 window,
10400 cx,
10401 );
10402 this.delete(&Delete, window, cx);
10403 });
10404 }
10405
10406 pub fn cut_to_end_of_line(
10407 &mut self,
10408 _: &CutToEndOfLine,
10409 window: &mut Window,
10410 cx: &mut Context<Self>,
10411 ) {
10412 self.transact(window, cx, |this, window, cx| {
10413 this.select_to_end_of_line(
10414 &SelectToEndOfLine {
10415 stop_at_soft_wraps: false,
10416 },
10417 window,
10418 cx,
10419 );
10420 this.cut(&Cut, window, cx);
10421 });
10422 }
10423
10424 pub fn move_to_start_of_paragraph(
10425 &mut self,
10426 _: &MoveToStartOfParagraph,
10427 window: &mut Window,
10428 cx: &mut Context<Self>,
10429 ) {
10430 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10431 cx.propagate();
10432 return;
10433 }
10434
10435 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10436 s.move_with(|map, selection| {
10437 selection.collapse_to(
10438 movement::start_of_paragraph(map, selection.head(), 1),
10439 SelectionGoal::None,
10440 )
10441 });
10442 })
10443 }
10444
10445 pub fn move_to_end_of_paragraph(
10446 &mut self,
10447 _: &MoveToEndOfParagraph,
10448 window: &mut Window,
10449 cx: &mut Context<Self>,
10450 ) {
10451 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10452 cx.propagate();
10453 return;
10454 }
10455
10456 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10457 s.move_with(|map, selection| {
10458 selection.collapse_to(
10459 movement::end_of_paragraph(map, selection.head(), 1),
10460 SelectionGoal::None,
10461 )
10462 });
10463 })
10464 }
10465
10466 pub fn select_to_start_of_paragraph(
10467 &mut self,
10468 _: &SelectToStartOfParagraph,
10469 window: &mut Window,
10470 cx: &mut Context<Self>,
10471 ) {
10472 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10473 cx.propagate();
10474 return;
10475 }
10476
10477 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10478 s.move_heads_with(|map, head, _| {
10479 (
10480 movement::start_of_paragraph(map, head, 1),
10481 SelectionGoal::None,
10482 )
10483 });
10484 })
10485 }
10486
10487 pub fn select_to_end_of_paragraph(
10488 &mut self,
10489 _: &SelectToEndOfParagraph,
10490 window: &mut Window,
10491 cx: &mut Context<Self>,
10492 ) {
10493 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10494 cx.propagate();
10495 return;
10496 }
10497
10498 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10499 s.move_heads_with(|map, head, _| {
10500 (
10501 movement::end_of_paragraph(map, head, 1),
10502 SelectionGoal::None,
10503 )
10504 });
10505 })
10506 }
10507
10508 pub fn move_to_start_of_excerpt(
10509 &mut self,
10510 _: &MoveToStartOfExcerpt,
10511 window: &mut Window,
10512 cx: &mut Context<Self>,
10513 ) {
10514 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10515 cx.propagate();
10516 return;
10517 }
10518
10519 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10520 s.move_with(|map, selection| {
10521 selection.collapse_to(
10522 movement::start_of_excerpt(
10523 map,
10524 selection.head(),
10525 workspace::searchable::Direction::Prev,
10526 ),
10527 SelectionGoal::None,
10528 )
10529 });
10530 })
10531 }
10532
10533 pub fn move_to_start_of_next_excerpt(
10534 &mut self,
10535 _: &MoveToStartOfNextExcerpt,
10536 window: &mut Window,
10537 cx: &mut Context<Self>,
10538 ) {
10539 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10540 cx.propagate();
10541 return;
10542 }
10543
10544 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10545 s.move_with(|map, selection| {
10546 selection.collapse_to(
10547 movement::start_of_excerpt(
10548 map,
10549 selection.head(),
10550 workspace::searchable::Direction::Next,
10551 ),
10552 SelectionGoal::None,
10553 )
10554 });
10555 })
10556 }
10557
10558 pub fn move_to_end_of_excerpt(
10559 &mut self,
10560 _: &MoveToEndOfExcerpt,
10561 window: &mut Window,
10562 cx: &mut Context<Self>,
10563 ) {
10564 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10565 cx.propagate();
10566 return;
10567 }
10568
10569 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10570 s.move_with(|map, selection| {
10571 selection.collapse_to(
10572 movement::end_of_excerpt(
10573 map,
10574 selection.head(),
10575 workspace::searchable::Direction::Next,
10576 ),
10577 SelectionGoal::None,
10578 )
10579 });
10580 })
10581 }
10582
10583 pub fn move_to_end_of_previous_excerpt(
10584 &mut self,
10585 _: &MoveToEndOfPreviousExcerpt,
10586 window: &mut Window,
10587 cx: &mut Context<Self>,
10588 ) {
10589 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10590 cx.propagate();
10591 return;
10592 }
10593
10594 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10595 s.move_with(|map, selection| {
10596 selection.collapse_to(
10597 movement::end_of_excerpt(
10598 map,
10599 selection.head(),
10600 workspace::searchable::Direction::Prev,
10601 ),
10602 SelectionGoal::None,
10603 )
10604 });
10605 })
10606 }
10607
10608 pub fn select_to_start_of_excerpt(
10609 &mut self,
10610 _: &SelectToStartOfExcerpt,
10611 window: &mut Window,
10612 cx: &mut Context<Self>,
10613 ) {
10614 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10615 cx.propagate();
10616 return;
10617 }
10618
10619 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10620 s.move_heads_with(|map, head, _| {
10621 (
10622 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
10623 SelectionGoal::None,
10624 )
10625 });
10626 })
10627 }
10628
10629 pub fn select_to_start_of_next_excerpt(
10630 &mut self,
10631 _: &SelectToStartOfNextExcerpt,
10632 window: &mut Window,
10633 cx: &mut Context<Self>,
10634 ) {
10635 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10636 cx.propagate();
10637 return;
10638 }
10639
10640 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10641 s.move_heads_with(|map, head, _| {
10642 (
10643 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
10644 SelectionGoal::None,
10645 )
10646 });
10647 })
10648 }
10649
10650 pub fn select_to_end_of_excerpt(
10651 &mut self,
10652 _: &SelectToEndOfExcerpt,
10653 window: &mut Window,
10654 cx: &mut Context<Self>,
10655 ) {
10656 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10657 cx.propagate();
10658 return;
10659 }
10660
10661 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10662 s.move_heads_with(|map, head, _| {
10663 (
10664 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
10665 SelectionGoal::None,
10666 )
10667 });
10668 })
10669 }
10670
10671 pub fn select_to_end_of_previous_excerpt(
10672 &mut self,
10673 _: &SelectToEndOfPreviousExcerpt,
10674 window: &mut Window,
10675 cx: &mut Context<Self>,
10676 ) {
10677 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10678 cx.propagate();
10679 return;
10680 }
10681
10682 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10683 s.move_heads_with(|map, head, _| {
10684 (
10685 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
10686 SelectionGoal::None,
10687 )
10688 });
10689 })
10690 }
10691
10692 pub fn move_to_beginning(
10693 &mut self,
10694 _: &MoveToBeginning,
10695 window: &mut Window,
10696 cx: &mut Context<Self>,
10697 ) {
10698 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10699 cx.propagate();
10700 return;
10701 }
10702
10703 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10704 s.select_ranges(vec![0..0]);
10705 });
10706 }
10707
10708 pub fn select_to_beginning(
10709 &mut self,
10710 _: &SelectToBeginning,
10711 window: &mut Window,
10712 cx: &mut Context<Self>,
10713 ) {
10714 let mut selection = self.selections.last::<Point>(cx);
10715 selection.set_head(Point::zero(), SelectionGoal::None);
10716
10717 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10718 s.select(vec![selection]);
10719 });
10720 }
10721
10722 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
10723 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10724 cx.propagate();
10725 return;
10726 }
10727
10728 let cursor = self.buffer.read(cx).read(cx).len();
10729 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10730 s.select_ranges(vec![cursor..cursor])
10731 });
10732 }
10733
10734 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
10735 self.nav_history = nav_history;
10736 }
10737
10738 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
10739 self.nav_history.as_ref()
10740 }
10741
10742 fn push_to_nav_history(
10743 &mut self,
10744 cursor_anchor: Anchor,
10745 new_position: Option<Point>,
10746 cx: &mut Context<Self>,
10747 ) {
10748 if let Some(nav_history) = self.nav_history.as_mut() {
10749 let buffer = self.buffer.read(cx).read(cx);
10750 let cursor_position = cursor_anchor.to_point(&buffer);
10751 let scroll_state = self.scroll_manager.anchor();
10752 let scroll_top_row = scroll_state.top_row(&buffer);
10753 drop(buffer);
10754
10755 if let Some(new_position) = new_position {
10756 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
10757 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
10758 return;
10759 }
10760 }
10761
10762 nav_history.push(
10763 Some(NavigationData {
10764 cursor_anchor,
10765 cursor_position,
10766 scroll_anchor: scroll_state,
10767 scroll_top_row,
10768 }),
10769 cx,
10770 );
10771 }
10772 }
10773
10774 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
10775 let buffer = self.buffer.read(cx).snapshot(cx);
10776 let mut selection = self.selections.first::<usize>(cx);
10777 selection.set_head(buffer.len(), SelectionGoal::None);
10778 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10779 s.select(vec![selection]);
10780 });
10781 }
10782
10783 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
10784 let end = self.buffer.read(cx).read(cx).len();
10785 self.change_selections(None, window, cx, |s| {
10786 s.select_ranges(vec![0..end]);
10787 });
10788 }
10789
10790 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
10791 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10792 let mut selections = self.selections.all::<Point>(cx);
10793 let max_point = display_map.buffer_snapshot.max_point();
10794 for selection in &mut selections {
10795 let rows = selection.spanned_rows(true, &display_map);
10796 selection.start = Point::new(rows.start.0, 0);
10797 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
10798 selection.reversed = false;
10799 }
10800 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10801 s.select(selections);
10802 });
10803 }
10804
10805 pub fn split_selection_into_lines(
10806 &mut self,
10807 _: &SplitSelectionIntoLines,
10808 window: &mut Window,
10809 cx: &mut Context<Self>,
10810 ) {
10811 let selections = self
10812 .selections
10813 .all::<Point>(cx)
10814 .into_iter()
10815 .map(|selection| selection.start..selection.end)
10816 .collect::<Vec<_>>();
10817 self.unfold_ranges(&selections, true, true, cx);
10818
10819 let mut new_selection_ranges = Vec::new();
10820 {
10821 let buffer = self.buffer.read(cx).read(cx);
10822 for selection in selections {
10823 for row in selection.start.row..selection.end.row {
10824 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
10825 new_selection_ranges.push(cursor..cursor);
10826 }
10827
10828 let is_multiline_selection = selection.start.row != selection.end.row;
10829 // Don't insert last one if it's a multi-line selection ending at the start of a line,
10830 // so this action feels more ergonomic when paired with other selection operations
10831 let should_skip_last = is_multiline_selection && selection.end.column == 0;
10832 if !should_skip_last {
10833 new_selection_ranges.push(selection.end..selection.end);
10834 }
10835 }
10836 }
10837 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10838 s.select_ranges(new_selection_ranges);
10839 });
10840 }
10841
10842 pub fn add_selection_above(
10843 &mut self,
10844 _: &AddSelectionAbove,
10845 window: &mut Window,
10846 cx: &mut Context<Self>,
10847 ) {
10848 self.add_selection(true, window, cx);
10849 }
10850
10851 pub fn add_selection_below(
10852 &mut self,
10853 _: &AddSelectionBelow,
10854 window: &mut Window,
10855 cx: &mut Context<Self>,
10856 ) {
10857 self.add_selection(false, window, cx);
10858 }
10859
10860 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
10861 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10862 let mut selections = self.selections.all::<Point>(cx);
10863 let text_layout_details = self.text_layout_details(window);
10864 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
10865 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
10866 let range = oldest_selection.display_range(&display_map).sorted();
10867
10868 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
10869 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
10870 let positions = start_x.min(end_x)..start_x.max(end_x);
10871
10872 selections.clear();
10873 let mut stack = Vec::new();
10874 for row in range.start.row().0..=range.end.row().0 {
10875 if let Some(selection) = self.selections.build_columnar_selection(
10876 &display_map,
10877 DisplayRow(row),
10878 &positions,
10879 oldest_selection.reversed,
10880 &text_layout_details,
10881 ) {
10882 stack.push(selection.id);
10883 selections.push(selection);
10884 }
10885 }
10886
10887 if above {
10888 stack.reverse();
10889 }
10890
10891 AddSelectionsState { above, stack }
10892 });
10893
10894 let last_added_selection = *state.stack.last().unwrap();
10895 let mut new_selections = Vec::new();
10896 if above == state.above {
10897 let end_row = if above {
10898 DisplayRow(0)
10899 } else {
10900 display_map.max_point().row()
10901 };
10902
10903 'outer: for selection in selections {
10904 if selection.id == last_added_selection {
10905 let range = selection.display_range(&display_map).sorted();
10906 debug_assert_eq!(range.start.row(), range.end.row());
10907 let mut row = range.start.row();
10908 let positions =
10909 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
10910 px(start)..px(end)
10911 } else {
10912 let start_x =
10913 display_map.x_for_display_point(range.start, &text_layout_details);
10914 let end_x =
10915 display_map.x_for_display_point(range.end, &text_layout_details);
10916 start_x.min(end_x)..start_x.max(end_x)
10917 };
10918
10919 while row != end_row {
10920 if above {
10921 row.0 -= 1;
10922 } else {
10923 row.0 += 1;
10924 }
10925
10926 if let Some(new_selection) = self.selections.build_columnar_selection(
10927 &display_map,
10928 row,
10929 &positions,
10930 selection.reversed,
10931 &text_layout_details,
10932 ) {
10933 state.stack.push(new_selection.id);
10934 if above {
10935 new_selections.push(new_selection);
10936 new_selections.push(selection);
10937 } else {
10938 new_selections.push(selection);
10939 new_selections.push(new_selection);
10940 }
10941
10942 continue 'outer;
10943 }
10944 }
10945 }
10946
10947 new_selections.push(selection);
10948 }
10949 } else {
10950 new_selections = selections;
10951 new_selections.retain(|s| s.id != last_added_selection);
10952 state.stack.pop();
10953 }
10954
10955 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10956 s.select(new_selections);
10957 });
10958 if state.stack.len() > 1 {
10959 self.add_selections_state = Some(state);
10960 }
10961 }
10962
10963 pub fn select_next_match_internal(
10964 &mut self,
10965 display_map: &DisplaySnapshot,
10966 replace_newest: bool,
10967 autoscroll: Option<Autoscroll>,
10968 window: &mut Window,
10969 cx: &mut Context<Self>,
10970 ) -> Result<()> {
10971 fn select_next_match_ranges(
10972 this: &mut Editor,
10973 range: Range<usize>,
10974 replace_newest: bool,
10975 auto_scroll: Option<Autoscroll>,
10976 window: &mut Window,
10977 cx: &mut Context<Editor>,
10978 ) {
10979 this.unfold_ranges(&[range.clone()], false, true, cx);
10980 this.change_selections(auto_scroll, window, cx, |s| {
10981 if replace_newest {
10982 s.delete(s.newest_anchor().id);
10983 }
10984 s.insert_range(range.clone());
10985 });
10986 }
10987
10988 let buffer = &display_map.buffer_snapshot;
10989 let mut selections = self.selections.all::<usize>(cx);
10990 if let Some(mut select_next_state) = self.select_next_state.take() {
10991 let query = &select_next_state.query;
10992 if !select_next_state.done {
10993 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
10994 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
10995 let mut next_selected_range = None;
10996
10997 let bytes_after_last_selection =
10998 buffer.bytes_in_range(last_selection.end..buffer.len());
10999 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
11000 let query_matches = query
11001 .stream_find_iter(bytes_after_last_selection)
11002 .map(|result| (last_selection.end, result))
11003 .chain(
11004 query
11005 .stream_find_iter(bytes_before_first_selection)
11006 .map(|result| (0, result)),
11007 );
11008
11009 for (start_offset, query_match) in query_matches {
11010 let query_match = query_match.unwrap(); // can only fail due to I/O
11011 let offset_range =
11012 start_offset + query_match.start()..start_offset + query_match.end();
11013 let display_range = offset_range.start.to_display_point(display_map)
11014 ..offset_range.end.to_display_point(display_map);
11015
11016 if !select_next_state.wordwise
11017 || (!movement::is_inside_word(display_map, display_range.start)
11018 && !movement::is_inside_word(display_map, display_range.end))
11019 {
11020 // TODO: This is n^2, because we might check all the selections
11021 if !selections
11022 .iter()
11023 .any(|selection| selection.range().overlaps(&offset_range))
11024 {
11025 next_selected_range = Some(offset_range);
11026 break;
11027 }
11028 }
11029 }
11030
11031 if let Some(next_selected_range) = next_selected_range {
11032 select_next_match_ranges(
11033 self,
11034 next_selected_range,
11035 replace_newest,
11036 autoscroll,
11037 window,
11038 cx,
11039 );
11040 } else {
11041 select_next_state.done = true;
11042 }
11043 }
11044
11045 self.select_next_state = Some(select_next_state);
11046 } else {
11047 let mut only_carets = true;
11048 let mut same_text_selected = true;
11049 let mut selected_text = None;
11050
11051 let mut selections_iter = selections.iter().peekable();
11052 while let Some(selection) = selections_iter.next() {
11053 if selection.start != selection.end {
11054 only_carets = false;
11055 }
11056
11057 if same_text_selected {
11058 if selected_text.is_none() {
11059 selected_text =
11060 Some(buffer.text_for_range(selection.range()).collect::<String>());
11061 }
11062
11063 if let Some(next_selection) = selections_iter.peek() {
11064 if next_selection.range().len() == selection.range().len() {
11065 let next_selected_text = buffer
11066 .text_for_range(next_selection.range())
11067 .collect::<String>();
11068 if Some(next_selected_text) != selected_text {
11069 same_text_selected = false;
11070 selected_text = None;
11071 }
11072 } else {
11073 same_text_selected = false;
11074 selected_text = None;
11075 }
11076 }
11077 }
11078 }
11079
11080 if only_carets {
11081 for selection in &mut selections {
11082 let word_range = movement::surrounding_word(
11083 display_map,
11084 selection.start.to_display_point(display_map),
11085 );
11086 selection.start = word_range.start.to_offset(display_map, Bias::Left);
11087 selection.end = word_range.end.to_offset(display_map, Bias::Left);
11088 selection.goal = SelectionGoal::None;
11089 selection.reversed = false;
11090 select_next_match_ranges(
11091 self,
11092 selection.start..selection.end,
11093 replace_newest,
11094 autoscroll,
11095 window,
11096 cx,
11097 );
11098 }
11099
11100 if selections.len() == 1 {
11101 let selection = selections
11102 .last()
11103 .expect("ensured that there's only one selection");
11104 let query = buffer
11105 .text_for_range(selection.start..selection.end)
11106 .collect::<String>();
11107 let is_empty = query.is_empty();
11108 let select_state = SelectNextState {
11109 query: AhoCorasick::new(&[query])?,
11110 wordwise: true,
11111 done: is_empty,
11112 };
11113 self.select_next_state = Some(select_state);
11114 } else {
11115 self.select_next_state = None;
11116 }
11117 } else if let Some(selected_text) = selected_text {
11118 self.select_next_state = Some(SelectNextState {
11119 query: AhoCorasick::new(&[selected_text])?,
11120 wordwise: false,
11121 done: false,
11122 });
11123 self.select_next_match_internal(
11124 display_map,
11125 replace_newest,
11126 autoscroll,
11127 window,
11128 cx,
11129 )?;
11130 }
11131 }
11132 Ok(())
11133 }
11134
11135 pub fn select_all_matches(
11136 &mut self,
11137 _action: &SelectAllMatches,
11138 window: &mut Window,
11139 cx: &mut Context<Self>,
11140 ) -> Result<()> {
11141 self.push_to_selection_history();
11142 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11143
11144 self.select_next_match_internal(&display_map, false, None, window, cx)?;
11145 let Some(select_next_state) = self.select_next_state.as_mut() else {
11146 return Ok(());
11147 };
11148 if select_next_state.done {
11149 return Ok(());
11150 }
11151
11152 let mut new_selections = self.selections.all::<usize>(cx);
11153
11154 let buffer = &display_map.buffer_snapshot;
11155 let query_matches = select_next_state
11156 .query
11157 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
11158
11159 for query_match in query_matches {
11160 let query_match = query_match.unwrap(); // can only fail due to I/O
11161 let offset_range = query_match.start()..query_match.end();
11162 let display_range = offset_range.start.to_display_point(&display_map)
11163 ..offset_range.end.to_display_point(&display_map);
11164
11165 if !select_next_state.wordwise
11166 || (!movement::is_inside_word(&display_map, display_range.start)
11167 && !movement::is_inside_word(&display_map, display_range.end))
11168 {
11169 self.selections.change_with(cx, |selections| {
11170 new_selections.push(Selection {
11171 id: selections.new_selection_id(),
11172 start: offset_range.start,
11173 end: offset_range.end,
11174 reversed: false,
11175 goal: SelectionGoal::None,
11176 });
11177 });
11178 }
11179 }
11180
11181 new_selections.sort_by_key(|selection| selection.start);
11182 let mut ix = 0;
11183 while ix + 1 < new_selections.len() {
11184 let current_selection = &new_selections[ix];
11185 let next_selection = &new_selections[ix + 1];
11186 if current_selection.range().overlaps(&next_selection.range()) {
11187 if current_selection.id < next_selection.id {
11188 new_selections.remove(ix + 1);
11189 } else {
11190 new_selections.remove(ix);
11191 }
11192 } else {
11193 ix += 1;
11194 }
11195 }
11196
11197 let reversed = self.selections.oldest::<usize>(cx).reversed;
11198
11199 for selection in new_selections.iter_mut() {
11200 selection.reversed = reversed;
11201 }
11202
11203 select_next_state.done = true;
11204 self.unfold_ranges(
11205 &new_selections
11206 .iter()
11207 .map(|selection| selection.range())
11208 .collect::<Vec<_>>(),
11209 false,
11210 false,
11211 cx,
11212 );
11213 self.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
11214 selections.select(new_selections)
11215 });
11216
11217 Ok(())
11218 }
11219
11220 pub fn select_next(
11221 &mut self,
11222 action: &SelectNext,
11223 window: &mut Window,
11224 cx: &mut Context<Self>,
11225 ) -> Result<()> {
11226 self.push_to_selection_history();
11227 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11228 self.select_next_match_internal(
11229 &display_map,
11230 action.replace_newest,
11231 Some(Autoscroll::newest()),
11232 window,
11233 cx,
11234 )?;
11235 Ok(())
11236 }
11237
11238 pub fn select_previous(
11239 &mut self,
11240 action: &SelectPrevious,
11241 window: &mut Window,
11242 cx: &mut Context<Self>,
11243 ) -> Result<()> {
11244 self.push_to_selection_history();
11245 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11246 let buffer = &display_map.buffer_snapshot;
11247 let mut selections = self.selections.all::<usize>(cx);
11248 if let Some(mut select_prev_state) = self.select_prev_state.take() {
11249 let query = &select_prev_state.query;
11250 if !select_prev_state.done {
11251 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11252 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11253 let mut next_selected_range = None;
11254 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
11255 let bytes_before_last_selection =
11256 buffer.reversed_bytes_in_range(0..last_selection.start);
11257 let bytes_after_first_selection =
11258 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
11259 let query_matches = query
11260 .stream_find_iter(bytes_before_last_selection)
11261 .map(|result| (last_selection.start, result))
11262 .chain(
11263 query
11264 .stream_find_iter(bytes_after_first_selection)
11265 .map(|result| (buffer.len(), result)),
11266 );
11267 for (end_offset, query_match) in query_matches {
11268 let query_match = query_match.unwrap(); // can only fail due to I/O
11269 let offset_range =
11270 end_offset - query_match.end()..end_offset - query_match.start();
11271 let display_range = offset_range.start.to_display_point(&display_map)
11272 ..offset_range.end.to_display_point(&display_map);
11273
11274 if !select_prev_state.wordwise
11275 || (!movement::is_inside_word(&display_map, display_range.start)
11276 && !movement::is_inside_word(&display_map, display_range.end))
11277 {
11278 next_selected_range = Some(offset_range);
11279 break;
11280 }
11281 }
11282
11283 if let Some(next_selected_range) = next_selected_range {
11284 self.unfold_ranges(&[next_selected_range.clone()], false, true, cx);
11285 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
11286 if action.replace_newest {
11287 s.delete(s.newest_anchor().id);
11288 }
11289 s.insert_range(next_selected_range);
11290 });
11291 } else {
11292 select_prev_state.done = true;
11293 }
11294 }
11295
11296 self.select_prev_state = Some(select_prev_state);
11297 } else {
11298 let mut only_carets = true;
11299 let mut same_text_selected = true;
11300 let mut selected_text = None;
11301
11302 let mut selections_iter = selections.iter().peekable();
11303 while let Some(selection) = selections_iter.next() {
11304 if selection.start != selection.end {
11305 only_carets = false;
11306 }
11307
11308 if same_text_selected {
11309 if selected_text.is_none() {
11310 selected_text =
11311 Some(buffer.text_for_range(selection.range()).collect::<String>());
11312 }
11313
11314 if let Some(next_selection) = selections_iter.peek() {
11315 if next_selection.range().len() == selection.range().len() {
11316 let next_selected_text = buffer
11317 .text_for_range(next_selection.range())
11318 .collect::<String>();
11319 if Some(next_selected_text) != selected_text {
11320 same_text_selected = false;
11321 selected_text = None;
11322 }
11323 } else {
11324 same_text_selected = false;
11325 selected_text = None;
11326 }
11327 }
11328 }
11329 }
11330
11331 if only_carets {
11332 for selection in &mut selections {
11333 let word_range = movement::surrounding_word(
11334 &display_map,
11335 selection.start.to_display_point(&display_map),
11336 );
11337 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
11338 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
11339 selection.goal = SelectionGoal::None;
11340 selection.reversed = false;
11341 }
11342 if selections.len() == 1 {
11343 let selection = selections
11344 .last()
11345 .expect("ensured that there's only one selection");
11346 let query = buffer
11347 .text_for_range(selection.start..selection.end)
11348 .collect::<String>();
11349 let is_empty = query.is_empty();
11350 let select_state = SelectNextState {
11351 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
11352 wordwise: true,
11353 done: is_empty,
11354 };
11355 self.select_prev_state = Some(select_state);
11356 } else {
11357 self.select_prev_state = None;
11358 }
11359
11360 self.unfold_ranges(
11361 &selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
11362 false,
11363 true,
11364 cx,
11365 );
11366 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
11367 s.select(selections);
11368 });
11369 } else if let Some(selected_text) = selected_text {
11370 self.select_prev_state = Some(SelectNextState {
11371 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
11372 wordwise: false,
11373 done: false,
11374 });
11375 self.select_previous(action, window, cx)?;
11376 }
11377 }
11378 Ok(())
11379 }
11380
11381 pub fn toggle_comments(
11382 &mut self,
11383 action: &ToggleComments,
11384 window: &mut Window,
11385 cx: &mut Context<Self>,
11386 ) {
11387 if self.read_only(cx) {
11388 return;
11389 }
11390 let text_layout_details = &self.text_layout_details(window);
11391 self.transact(window, cx, |this, window, cx| {
11392 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
11393 let mut edits = Vec::new();
11394 let mut selection_edit_ranges = Vec::new();
11395 let mut last_toggled_row = None;
11396 let snapshot = this.buffer.read(cx).read(cx);
11397 let empty_str: Arc<str> = Arc::default();
11398 let mut suffixes_inserted = Vec::new();
11399 let ignore_indent = action.ignore_indent;
11400
11401 fn comment_prefix_range(
11402 snapshot: &MultiBufferSnapshot,
11403 row: MultiBufferRow,
11404 comment_prefix: &str,
11405 comment_prefix_whitespace: &str,
11406 ignore_indent: bool,
11407 ) -> Range<Point> {
11408 let indent_size = if ignore_indent {
11409 0
11410 } else {
11411 snapshot.indent_size_for_line(row).len
11412 };
11413
11414 let start = Point::new(row.0, indent_size);
11415
11416 let mut line_bytes = snapshot
11417 .bytes_in_range(start..snapshot.max_point())
11418 .flatten()
11419 .copied();
11420
11421 // If this line currently begins with the line comment prefix, then record
11422 // the range containing the prefix.
11423 if line_bytes
11424 .by_ref()
11425 .take(comment_prefix.len())
11426 .eq(comment_prefix.bytes())
11427 {
11428 // Include any whitespace that matches the comment prefix.
11429 let matching_whitespace_len = line_bytes
11430 .zip(comment_prefix_whitespace.bytes())
11431 .take_while(|(a, b)| a == b)
11432 .count() as u32;
11433 let end = Point::new(
11434 start.row,
11435 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
11436 );
11437 start..end
11438 } else {
11439 start..start
11440 }
11441 }
11442
11443 fn comment_suffix_range(
11444 snapshot: &MultiBufferSnapshot,
11445 row: MultiBufferRow,
11446 comment_suffix: &str,
11447 comment_suffix_has_leading_space: bool,
11448 ) -> Range<Point> {
11449 let end = Point::new(row.0, snapshot.line_len(row));
11450 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
11451
11452 let mut line_end_bytes = snapshot
11453 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
11454 .flatten()
11455 .copied();
11456
11457 let leading_space_len = if suffix_start_column > 0
11458 && line_end_bytes.next() == Some(b' ')
11459 && comment_suffix_has_leading_space
11460 {
11461 1
11462 } else {
11463 0
11464 };
11465
11466 // If this line currently begins with the line comment prefix, then record
11467 // the range containing the prefix.
11468 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
11469 let start = Point::new(end.row, suffix_start_column - leading_space_len);
11470 start..end
11471 } else {
11472 end..end
11473 }
11474 }
11475
11476 // TODO: Handle selections that cross excerpts
11477 for selection in &mut selections {
11478 let start_column = snapshot
11479 .indent_size_for_line(MultiBufferRow(selection.start.row))
11480 .len;
11481 let language = if let Some(language) =
11482 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
11483 {
11484 language
11485 } else {
11486 continue;
11487 };
11488
11489 selection_edit_ranges.clear();
11490
11491 // If multiple selections contain a given row, avoid processing that
11492 // row more than once.
11493 let mut start_row = MultiBufferRow(selection.start.row);
11494 if last_toggled_row == Some(start_row) {
11495 start_row = start_row.next_row();
11496 }
11497 let end_row =
11498 if selection.end.row > selection.start.row && selection.end.column == 0 {
11499 MultiBufferRow(selection.end.row - 1)
11500 } else {
11501 MultiBufferRow(selection.end.row)
11502 };
11503 last_toggled_row = Some(end_row);
11504
11505 if start_row > end_row {
11506 continue;
11507 }
11508
11509 // If the language has line comments, toggle those.
11510 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
11511
11512 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
11513 if ignore_indent {
11514 full_comment_prefixes = full_comment_prefixes
11515 .into_iter()
11516 .map(|s| Arc::from(s.trim_end()))
11517 .collect();
11518 }
11519
11520 if !full_comment_prefixes.is_empty() {
11521 let first_prefix = full_comment_prefixes
11522 .first()
11523 .expect("prefixes is non-empty");
11524 let prefix_trimmed_lengths = full_comment_prefixes
11525 .iter()
11526 .map(|p| p.trim_end_matches(' ').len())
11527 .collect::<SmallVec<[usize; 4]>>();
11528
11529 let mut all_selection_lines_are_comments = true;
11530
11531 for row in start_row.0..=end_row.0 {
11532 let row = MultiBufferRow(row);
11533 if start_row < end_row && snapshot.is_line_blank(row) {
11534 continue;
11535 }
11536
11537 let prefix_range = full_comment_prefixes
11538 .iter()
11539 .zip(prefix_trimmed_lengths.iter().copied())
11540 .map(|(prefix, trimmed_prefix_len)| {
11541 comment_prefix_range(
11542 snapshot.deref(),
11543 row,
11544 &prefix[..trimmed_prefix_len],
11545 &prefix[trimmed_prefix_len..],
11546 ignore_indent,
11547 )
11548 })
11549 .max_by_key(|range| range.end.column - range.start.column)
11550 .expect("prefixes is non-empty");
11551
11552 if prefix_range.is_empty() {
11553 all_selection_lines_are_comments = false;
11554 }
11555
11556 selection_edit_ranges.push(prefix_range);
11557 }
11558
11559 if all_selection_lines_are_comments {
11560 edits.extend(
11561 selection_edit_ranges
11562 .iter()
11563 .cloned()
11564 .map(|range| (range, empty_str.clone())),
11565 );
11566 } else {
11567 let min_column = selection_edit_ranges
11568 .iter()
11569 .map(|range| range.start.column)
11570 .min()
11571 .unwrap_or(0);
11572 edits.extend(selection_edit_ranges.iter().map(|range| {
11573 let position = Point::new(range.start.row, min_column);
11574 (position..position, first_prefix.clone())
11575 }));
11576 }
11577 } else if let Some((full_comment_prefix, comment_suffix)) =
11578 language.block_comment_delimiters()
11579 {
11580 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
11581 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
11582 let prefix_range = comment_prefix_range(
11583 snapshot.deref(),
11584 start_row,
11585 comment_prefix,
11586 comment_prefix_whitespace,
11587 ignore_indent,
11588 );
11589 let suffix_range = comment_suffix_range(
11590 snapshot.deref(),
11591 end_row,
11592 comment_suffix.trim_start_matches(' '),
11593 comment_suffix.starts_with(' '),
11594 );
11595
11596 if prefix_range.is_empty() || suffix_range.is_empty() {
11597 edits.push((
11598 prefix_range.start..prefix_range.start,
11599 full_comment_prefix.clone(),
11600 ));
11601 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
11602 suffixes_inserted.push((end_row, comment_suffix.len()));
11603 } else {
11604 edits.push((prefix_range, empty_str.clone()));
11605 edits.push((suffix_range, empty_str.clone()));
11606 }
11607 } else {
11608 continue;
11609 }
11610 }
11611
11612 drop(snapshot);
11613 this.buffer.update(cx, |buffer, cx| {
11614 buffer.edit(edits, None, cx);
11615 });
11616
11617 // Adjust selections so that they end before any comment suffixes that
11618 // were inserted.
11619 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
11620 let mut selections = this.selections.all::<Point>(cx);
11621 let snapshot = this.buffer.read(cx).read(cx);
11622 for selection in &mut selections {
11623 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
11624 match row.cmp(&MultiBufferRow(selection.end.row)) {
11625 Ordering::Less => {
11626 suffixes_inserted.next();
11627 continue;
11628 }
11629 Ordering::Greater => break,
11630 Ordering::Equal => {
11631 if selection.end.column == snapshot.line_len(row) {
11632 if selection.is_empty() {
11633 selection.start.column -= suffix_len as u32;
11634 }
11635 selection.end.column -= suffix_len as u32;
11636 }
11637 break;
11638 }
11639 }
11640 }
11641 }
11642
11643 drop(snapshot);
11644 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11645 s.select(selections)
11646 });
11647
11648 let selections = this.selections.all::<Point>(cx);
11649 let selections_on_single_row = selections.windows(2).all(|selections| {
11650 selections[0].start.row == selections[1].start.row
11651 && selections[0].end.row == selections[1].end.row
11652 && selections[0].start.row == selections[0].end.row
11653 });
11654 let selections_selecting = selections
11655 .iter()
11656 .any(|selection| selection.start != selection.end);
11657 let advance_downwards = action.advance_downwards
11658 && selections_on_single_row
11659 && !selections_selecting
11660 && !matches!(this.mode, EditorMode::SingleLine { .. });
11661
11662 if advance_downwards {
11663 let snapshot = this.buffer.read(cx).snapshot(cx);
11664
11665 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11666 s.move_cursors_with(|display_snapshot, display_point, _| {
11667 let mut point = display_point.to_point(display_snapshot);
11668 point.row += 1;
11669 point = snapshot.clip_point(point, Bias::Left);
11670 let display_point = point.to_display_point(display_snapshot);
11671 let goal = SelectionGoal::HorizontalPosition(
11672 display_snapshot
11673 .x_for_display_point(display_point, text_layout_details)
11674 .into(),
11675 );
11676 (display_point, goal)
11677 })
11678 });
11679 }
11680 });
11681 }
11682
11683 pub fn select_enclosing_symbol(
11684 &mut self,
11685 _: &SelectEnclosingSymbol,
11686 window: &mut Window,
11687 cx: &mut Context<Self>,
11688 ) {
11689 let buffer = self.buffer.read(cx).snapshot(cx);
11690 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
11691
11692 fn update_selection(
11693 selection: &Selection<usize>,
11694 buffer_snap: &MultiBufferSnapshot,
11695 ) -> Option<Selection<usize>> {
11696 let cursor = selection.head();
11697 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
11698 for symbol in symbols.iter().rev() {
11699 let start = symbol.range.start.to_offset(buffer_snap);
11700 let end = symbol.range.end.to_offset(buffer_snap);
11701 let new_range = start..end;
11702 if start < selection.start || end > selection.end {
11703 return Some(Selection {
11704 id: selection.id,
11705 start: new_range.start,
11706 end: new_range.end,
11707 goal: SelectionGoal::None,
11708 reversed: selection.reversed,
11709 });
11710 }
11711 }
11712 None
11713 }
11714
11715 let mut selected_larger_symbol = false;
11716 let new_selections = old_selections
11717 .iter()
11718 .map(|selection| match update_selection(selection, &buffer) {
11719 Some(new_selection) => {
11720 if new_selection.range() != selection.range() {
11721 selected_larger_symbol = true;
11722 }
11723 new_selection
11724 }
11725 None => selection.clone(),
11726 })
11727 .collect::<Vec<_>>();
11728
11729 if selected_larger_symbol {
11730 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11731 s.select(new_selections);
11732 });
11733 }
11734 }
11735
11736 pub fn select_larger_syntax_node(
11737 &mut self,
11738 _: &SelectLargerSyntaxNode,
11739 window: &mut Window,
11740 cx: &mut Context<Self>,
11741 ) {
11742 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11743 let buffer = self.buffer.read(cx).snapshot(cx);
11744 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
11745
11746 let mut stack = mem::take(&mut self.select_larger_syntax_node_stack);
11747 let mut selected_larger_node = false;
11748 let new_selections = old_selections
11749 .iter()
11750 .map(|selection| {
11751 let old_range = selection.start..selection.end;
11752 let mut new_range = old_range.clone();
11753 let mut new_node = None;
11754 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
11755 {
11756 new_node = Some(node);
11757 new_range = match containing_range {
11758 MultiOrSingleBufferOffsetRange::Single(_) => break,
11759 MultiOrSingleBufferOffsetRange::Multi(range) => range,
11760 };
11761 if !display_map.intersects_fold(new_range.start)
11762 && !display_map.intersects_fold(new_range.end)
11763 {
11764 break;
11765 }
11766 }
11767
11768 if let Some(node) = new_node {
11769 // Log the ancestor, to support using this action as a way to explore TreeSitter
11770 // nodes. Parent and grandparent are also logged because this operation will not
11771 // visit nodes that have the same range as their parent.
11772 log::info!("Node: {node:?}");
11773 let parent = node.parent();
11774 log::info!("Parent: {parent:?}");
11775 let grandparent = parent.and_then(|x| x.parent());
11776 log::info!("Grandparent: {grandparent:?}");
11777 }
11778
11779 selected_larger_node |= new_range != old_range;
11780 Selection {
11781 id: selection.id,
11782 start: new_range.start,
11783 end: new_range.end,
11784 goal: SelectionGoal::None,
11785 reversed: selection.reversed,
11786 }
11787 })
11788 .collect::<Vec<_>>();
11789
11790 if selected_larger_node {
11791 stack.push(old_selections);
11792 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11793 s.select(new_selections);
11794 });
11795 }
11796 self.select_larger_syntax_node_stack = stack;
11797 }
11798
11799 pub fn select_smaller_syntax_node(
11800 &mut self,
11801 _: &SelectSmallerSyntaxNode,
11802 window: &mut Window,
11803 cx: &mut Context<Self>,
11804 ) {
11805 let mut stack = mem::take(&mut self.select_larger_syntax_node_stack);
11806 if let Some(selections) = stack.pop() {
11807 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11808 s.select(selections.to_vec());
11809 });
11810 }
11811 self.select_larger_syntax_node_stack = stack;
11812 }
11813
11814 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
11815 if !EditorSettings::get_global(cx).gutter.runnables {
11816 self.clear_tasks();
11817 return Task::ready(());
11818 }
11819 let project = self.project.as_ref().map(Entity::downgrade);
11820 cx.spawn_in(window, async move |this, cx| {
11821 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
11822 let Some(project) = project.and_then(|p| p.upgrade()) else {
11823 return;
11824 };
11825 let Ok(display_snapshot) = this.update(cx, |this, cx| {
11826 this.display_map.update(cx, |map, cx| map.snapshot(cx))
11827 }) else {
11828 return;
11829 };
11830
11831 let hide_runnables = project
11832 .update(cx, |project, cx| {
11833 // Do not display any test indicators in non-dev server remote projects.
11834 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
11835 })
11836 .unwrap_or(true);
11837 if hide_runnables {
11838 return;
11839 }
11840 let new_rows =
11841 cx.background_spawn({
11842 let snapshot = display_snapshot.clone();
11843 async move {
11844 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
11845 }
11846 })
11847 .await;
11848
11849 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
11850 this.update(cx, |this, _| {
11851 this.clear_tasks();
11852 for (key, value) in rows {
11853 this.insert_tasks(key, value);
11854 }
11855 })
11856 .ok();
11857 })
11858 }
11859 fn fetch_runnable_ranges(
11860 snapshot: &DisplaySnapshot,
11861 range: Range<Anchor>,
11862 ) -> Vec<language::RunnableRange> {
11863 snapshot.buffer_snapshot.runnable_ranges(range).collect()
11864 }
11865
11866 fn runnable_rows(
11867 project: Entity<Project>,
11868 snapshot: DisplaySnapshot,
11869 runnable_ranges: Vec<RunnableRange>,
11870 mut cx: AsyncWindowContext,
11871 ) -> Vec<((BufferId, u32), RunnableTasks)> {
11872 runnable_ranges
11873 .into_iter()
11874 .filter_map(|mut runnable| {
11875 let tasks = cx
11876 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
11877 .ok()?;
11878 if tasks.is_empty() {
11879 return None;
11880 }
11881
11882 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
11883
11884 let row = snapshot
11885 .buffer_snapshot
11886 .buffer_line_for_row(MultiBufferRow(point.row))?
11887 .1
11888 .start
11889 .row;
11890
11891 let context_range =
11892 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
11893 Some((
11894 (runnable.buffer_id, row),
11895 RunnableTasks {
11896 templates: tasks,
11897 offset: snapshot
11898 .buffer_snapshot
11899 .anchor_before(runnable.run_range.start),
11900 context_range,
11901 column: point.column,
11902 extra_variables: runnable.extra_captures,
11903 },
11904 ))
11905 })
11906 .collect()
11907 }
11908
11909 fn templates_with_tags(
11910 project: &Entity<Project>,
11911 runnable: &mut Runnable,
11912 cx: &mut App,
11913 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
11914 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
11915 let (worktree_id, file) = project
11916 .buffer_for_id(runnable.buffer, cx)
11917 .and_then(|buffer| buffer.read(cx).file())
11918 .map(|file| (file.worktree_id(cx), file.clone()))
11919 .unzip();
11920
11921 (
11922 project.task_store().read(cx).task_inventory().cloned(),
11923 worktree_id,
11924 file,
11925 )
11926 });
11927
11928 let tags = mem::take(&mut runnable.tags);
11929 let mut tags: Vec<_> = tags
11930 .into_iter()
11931 .flat_map(|tag| {
11932 let tag = tag.0.clone();
11933 inventory
11934 .as_ref()
11935 .into_iter()
11936 .flat_map(|inventory| {
11937 inventory.read(cx).list_tasks(
11938 file.clone(),
11939 Some(runnable.language.clone()),
11940 worktree_id,
11941 cx,
11942 )
11943 })
11944 .filter(move |(_, template)| {
11945 template.tags.iter().any(|source_tag| source_tag == &tag)
11946 })
11947 })
11948 .sorted_by_key(|(kind, _)| kind.to_owned())
11949 .collect();
11950 if let Some((leading_tag_source, _)) = tags.first() {
11951 // Strongest source wins; if we have worktree tag binding, prefer that to
11952 // global and language bindings;
11953 // if we have a global binding, prefer that to language binding.
11954 let first_mismatch = tags
11955 .iter()
11956 .position(|(tag_source, _)| tag_source != leading_tag_source);
11957 if let Some(index) = first_mismatch {
11958 tags.truncate(index);
11959 }
11960 }
11961
11962 tags
11963 }
11964
11965 pub fn move_to_enclosing_bracket(
11966 &mut self,
11967 _: &MoveToEnclosingBracket,
11968 window: &mut Window,
11969 cx: &mut Context<Self>,
11970 ) {
11971 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11972 s.move_offsets_with(|snapshot, selection| {
11973 let Some(enclosing_bracket_ranges) =
11974 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
11975 else {
11976 return;
11977 };
11978
11979 let mut best_length = usize::MAX;
11980 let mut best_inside = false;
11981 let mut best_in_bracket_range = false;
11982 let mut best_destination = None;
11983 for (open, close) in enclosing_bracket_ranges {
11984 let close = close.to_inclusive();
11985 let length = close.end() - open.start;
11986 let inside = selection.start >= open.end && selection.end <= *close.start();
11987 let in_bracket_range = open.to_inclusive().contains(&selection.head())
11988 || close.contains(&selection.head());
11989
11990 // If best is next to a bracket and current isn't, skip
11991 if !in_bracket_range && best_in_bracket_range {
11992 continue;
11993 }
11994
11995 // Prefer smaller lengths unless best is inside and current isn't
11996 if length > best_length && (best_inside || !inside) {
11997 continue;
11998 }
11999
12000 best_length = length;
12001 best_inside = inside;
12002 best_in_bracket_range = in_bracket_range;
12003 best_destination = Some(
12004 if close.contains(&selection.start) && close.contains(&selection.end) {
12005 if inside {
12006 open.end
12007 } else {
12008 open.start
12009 }
12010 } else if inside {
12011 *close.start()
12012 } else {
12013 *close.end()
12014 },
12015 );
12016 }
12017
12018 if let Some(destination) = best_destination {
12019 selection.collapse_to(destination, SelectionGoal::None);
12020 }
12021 })
12022 });
12023 }
12024
12025 pub fn undo_selection(
12026 &mut self,
12027 _: &UndoSelection,
12028 window: &mut Window,
12029 cx: &mut Context<Self>,
12030 ) {
12031 self.end_selection(window, cx);
12032 self.selection_history.mode = SelectionHistoryMode::Undoing;
12033 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
12034 self.change_selections(None, window, cx, |s| {
12035 s.select_anchors(entry.selections.to_vec())
12036 });
12037 self.select_next_state = entry.select_next_state;
12038 self.select_prev_state = entry.select_prev_state;
12039 self.add_selections_state = entry.add_selections_state;
12040 self.request_autoscroll(Autoscroll::newest(), cx);
12041 }
12042 self.selection_history.mode = SelectionHistoryMode::Normal;
12043 }
12044
12045 pub fn redo_selection(
12046 &mut self,
12047 _: &RedoSelection,
12048 window: &mut Window,
12049 cx: &mut Context<Self>,
12050 ) {
12051 self.end_selection(window, cx);
12052 self.selection_history.mode = SelectionHistoryMode::Redoing;
12053 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
12054 self.change_selections(None, window, cx, |s| {
12055 s.select_anchors(entry.selections.to_vec())
12056 });
12057 self.select_next_state = entry.select_next_state;
12058 self.select_prev_state = entry.select_prev_state;
12059 self.add_selections_state = entry.add_selections_state;
12060 self.request_autoscroll(Autoscroll::newest(), cx);
12061 }
12062 self.selection_history.mode = SelectionHistoryMode::Normal;
12063 }
12064
12065 pub fn expand_excerpts(
12066 &mut self,
12067 action: &ExpandExcerpts,
12068 _: &mut Window,
12069 cx: &mut Context<Self>,
12070 ) {
12071 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
12072 }
12073
12074 pub fn expand_excerpts_down(
12075 &mut self,
12076 action: &ExpandExcerptsDown,
12077 _: &mut Window,
12078 cx: &mut Context<Self>,
12079 ) {
12080 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
12081 }
12082
12083 pub fn expand_excerpts_up(
12084 &mut self,
12085 action: &ExpandExcerptsUp,
12086 _: &mut Window,
12087 cx: &mut Context<Self>,
12088 ) {
12089 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
12090 }
12091
12092 pub fn expand_excerpts_for_direction(
12093 &mut self,
12094 lines: u32,
12095 direction: ExpandExcerptDirection,
12096
12097 cx: &mut Context<Self>,
12098 ) {
12099 let selections = self.selections.disjoint_anchors();
12100
12101 let lines = if lines == 0 {
12102 EditorSettings::get_global(cx).expand_excerpt_lines
12103 } else {
12104 lines
12105 };
12106
12107 self.buffer.update(cx, |buffer, cx| {
12108 let snapshot = buffer.snapshot(cx);
12109 let mut excerpt_ids = selections
12110 .iter()
12111 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
12112 .collect::<Vec<_>>();
12113 excerpt_ids.sort();
12114 excerpt_ids.dedup();
12115 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
12116 })
12117 }
12118
12119 pub fn expand_excerpt(
12120 &mut self,
12121 excerpt: ExcerptId,
12122 direction: ExpandExcerptDirection,
12123 window: &mut Window,
12124 cx: &mut Context<Self>,
12125 ) {
12126 let current_scroll_position = self.scroll_position(cx);
12127 let lines = EditorSettings::get_global(cx).expand_excerpt_lines;
12128 self.buffer.update(cx, |buffer, cx| {
12129 buffer.expand_excerpts([excerpt], lines, direction, cx)
12130 });
12131 if direction == ExpandExcerptDirection::Down {
12132 let new_scroll_position = current_scroll_position + gpui::Point::new(0.0, lines as f32);
12133 self.set_scroll_position(new_scroll_position, window, cx);
12134 }
12135 }
12136
12137 pub fn go_to_singleton_buffer_point(
12138 &mut self,
12139 point: Point,
12140 window: &mut Window,
12141 cx: &mut Context<Self>,
12142 ) {
12143 self.go_to_singleton_buffer_range(point..point, window, cx);
12144 }
12145
12146 pub fn go_to_singleton_buffer_range(
12147 &mut self,
12148 range: Range<Point>,
12149 window: &mut Window,
12150 cx: &mut Context<Self>,
12151 ) {
12152 let multibuffer = self.buffer().read(cx);
12153 let Some(buffer) = multibuffer.as_singleton() else {
12154 return;
12155 };
12156 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
12157 return;
12158 };
12159 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
12160 return;
12161 };
12162 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
12163 s.select_anchor_ranges([start..end])
12164 });
12165 }
12166
12167 fn go_to_diagnostic(
12168 &mut self,
12169 _: &GoToDiagnostic,
12170 window: &mut Window,
12171 cx: &mut Context<Self>,
12172 ) {
12173 self.go_to_diagnostic_impl(Direction::Next, window, cx)
12174 }
12175
12176 fn go_to_prev_diagnostic(
12177 &mut self,
12178 _: &GoToPreviousDiagnostic,
12179 window: &mut Window,
12180 cx: &mut Context<Self>,
12181 ) {
12182 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
12183 }
12184
12185 pub fn go_to_diagnostic_impl(
12186 &mut self,
12187 direction: Direction,
12188 window: &mut Window,
12189 cx: &mut Context<Self>,
12190 ) {
12191 let buffer = self.buffer.read(cx).snapshot(cx);
12192 let selection = self.selections.newest::<usize>(cx);
12193
12194 // If there is an active Diagnostic Popover jump to its diagnostic instead.
12195 if direction == Direction::Next {
12196 if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() {
12197 let Some(buffer_id) = popover.local_diagnostic.range.start.buffer_id else {
12198 return;
12199 };
12200 self.activate_diagnostics(
12201 buffer_id,
12202 popover.local_diagnostic.diagnostic.group_id,
12203 window,
12204 cx,
12205 );
12206 if let Some(active_diagnostics) = self.active_diagnostics.as_ref() {
12207 let primary_range_start = active_diagnostics.primary_range.start;
12208 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12209 let mut new_selection = s.newest_anchor().clone();
12210 new_selection.collapse_to(primary_range_start, SelectionGoal::None);
12211 s.select_anchors(vec![new_selection.clone()]);
12212 });
12213 self.refresh_inline_completion(false, true, window, cx);
12214 }
12215 return;
12216 }
12217 }
12218
12219 let active_group_id = self
12220 .active_diagnostics
12221 .as_ref()
12222 .map(|active_group| active_group.group_id);
12223 let active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| {
12224 active_diagnostics
12225 .primary_range
12226 .to_offset(&buffer)
12227 .to_inclusive()
12228 });
12229 let search_start = if let Some(active_primary_range) = active_primary_range.as_ref() {
12230 if active_primary_range.contains(&selection.head()) {
12231 *active_primary_range.start()
12232 } else {
12233 selection.head()
12234 }
12235 } else {
12236 selection.head()
12237 };
12238
12239 let snapshot = self.snapshot(window, cx);
12240 let primary_diagnostics_before = buffer
12241 .diagnostics_in_range::<usize>(0..search_start)
12242 .filter(|entry| entry.diagnostic.is_primary)
12243 .filter(|entry| entry.range.start != entry.range.end)
12244 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
12245 .filter(|entry| !snapshot.intersects_fold(entry.range.start))
12246 .collect::<Vec<_>>();
12247 let last_same_group_diagnostic_before = active_group_id.and_then(|active_group_id| {
12248 primary_diagnostics_before
12249 .iter()
12250 .position(|entry| entry.diagnostic.group_id == active_group_id)
12251 });
12252
12253 let primary_diagnostics_after = buffer
12254 .diagnostics_in_range::<usize>(search_start..buffer.len())
12255 .filter(|entry| entry.diagnostic.is_primary)
12256 .filter(|entry| entry.range.start != entry.range.end)
12257 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
12258 .filter(|diagnostic| !snapshot.intersects_fold(diagnostic.range.start))
12259 .collect::<Vec<_>>();
12260 let last_same_group_diagnostic_after = active_group_id.and_then(|active_group_id| {
12261 primary_diagnostics_after
12262 .iter()
12263 .enumerate()
12264 .rev()
12265 .find_map(|(i, entry)| {
12266 if entry.diagnostic.group_id == active_group_id {
12267 Some(i)
12268 } else {
12269 None
12270 }
12271 })
12272 });
12273
12274 let next_primary_diagnostic = match direction {
12275 Direction::Prev => primary_diagnostics_before
12276 .iter()
12277 .take(last_same_group_diagnostic_before.unwrap_or(usize::MAX))
12278 .rev()
12279 .next(),
12280 Direction::Next => primary_diagnostics_after
12281 .iter()
12282 .skip(
12283 last_same_group_diagnostic_after
12284 .map(|index| index + 1)
12285 .unwrap_or(0),
12286 )
12287 .next(),
12288 };
12289
12290 // Cycle around to the start of the buffer, potentially moving back to the start of
12291 // the currently active diagnostic.
12292 let cycle_around = || match direction {
12293 Direction::Prev => primary_diagnostics_after
12294 .iter()
12295 .rev()
12296 .chain(primary_diagnostics_before.iter().rev())
12297 .next(),
12298 Direction::Next => primary_diagnostics_before
12299 .iter()
12300 .chain(primary_diagnostics_after.iter())
12301 .next(),
12302 };
12303
12304 if let Some((primary_range, group_id)) = next_primary_diagnostic
12305 .or_else(cycle_around)
12306 .map(|entry| (&entry.range, entry.diagnostic.group_id))
12307 {
12308 let Some(buffer_id) = buffer.anchor_after(primary_range.start).buffer_id else {
12309 return;
12310 };
12311 self.activate_diagnostics(buffer_id, group_id, window, cx);
12312 if self.active_diagnostics.is_some() {
12313 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12314 s.select(vec![Selection {
12315 id: selection.id,
12316 start: primary_range.start,
12317 end: primary_range.start,
12318 reversed: false,
12319 goal: SelectionGoal::None,
12320 }]);
12321 });
12322 self.refresh_inline_completion(false, true, window, cx);
12323 }
12324 }
12325 }
12326
12327 fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
12328 let snapshot = self.snapshot(window, cx);
12329 let selection = self.selections.newest::<Point>(cx);
12330 self.go_to_hunk_before_or_after_position(
12331 &snapshot,
12332 selection.head(),
12333 Direction::Next,
12334 window,
12335 cx,
12336 );
12337 }
12338
12339 fn go_to_hunk_before_or_after_position(
12340 &mut self,
12341 snapshot: &EditorSnapshot,
12342 position: Point,
12343 direction: Direction,
12344 window: &mut Window,
12345 cx: &mut Context<Editor>,
12346 ) {
12347 let row = if direction == Direction::Next {
12348 self.hunk_after_position(snapshot, position)
12349 .map(|hunk| hunk.row_range.start)
12350 } else {
12351 self.hunk_before_position(snapshot, position)
12352 };
12353
12354 if let Some(row) = row {
12355 let destination = Point::new(row.0, 0);
12356 let autoscroll = Autoscroll::center();
12357
12358 self.unfold_ranges(&[destination..destination], false, false, cx);
12359 self.change_selections(Some(autoscroll), window, cx, |s| {
12360 s.select_ranges([destination..destination]);
12361 });
12362 }
12363 }
12364
12365 fn hunk_after_position(
12366 &mut self,
12367 snapshot: &EditorSnapshot,
12368 position: Point,
12369 ) -> Option<MultiBufferDiffHunk> {
12370 snapshot
12371 .buffer_snapshot
12372 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
12373 .find(|hunk| hunk.row_range.start.0 > position.row)
12374 .or_else(|| {
12375 snapshot
12376 .buffer_snapshot
12377 .diff_hunks_in_range(Point::zero()..position)
12378 .find(|hunk| hunk.row_range.end.0 < position.row)
12379 })
12380 }
12381
12382 fn go_to_prev_hunk(
12383 &mut self,
12384 _: &GoToPreviousHunk,
12385 window: &mut Window,
12386 cx: &mut Context<Self>,
12387 ) {
12388 let snapshot = self.snapshot(window, cx);
12389 let selection = self.selections.newest::<Point>(cx);
12390 self.go_to_hunk_before_or_after_position(
12391 &snapshot,
12392 selection.head(),
12393 Direction::Prev,
12394 window,
12395 cx,
12396 );
12397 }
12398
12399 fn hunk_before_position(
12400 &mut self,
12401 snapshot: &EditorSnapshot,
12402 position: Point,
12403 ) -> Option<MultiBufferRow> {
12404 snapshot
12405 .buffer_snapshot
12406 .diff_hunk_before(position)
12407 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
12408 }
12409
12410 fn go_to_line<T: 'static>(
12411 &mut self,
12412 position: Anchor,
12413 highlight_color: Option<Hsla>,
12414 window: &mut Window,
12415 cx: &mut Context<Self>,
12416 ) {
12417 let snapshot = self.snapshot(window, cx).display_snapshot;
12418 let position = position.to_point(&snapshot.buffer_snapshot);
12419 let start = snapshot
12420 .buffer_snapshot
12421 .clip_point(Point::new(position.row, 0), Bias::Left);
12422 let end = start + Point::new(1, 0);
12423 let start = snapshot.buffer_snapshot.anchor_before(start);
12424 let end = snapshot.buffer_snapshot.anchor_before(end);
12425
12426 self.clear_row_highlights::<T>();
12427 self.highlight_rows::<T>(
12428 start..end,
12429 highlight_color
12430 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
12431 true,
12432 cx,
12433 );
12434 self.request_autoscroll(Autoscroll::center(), cx);
12435 }
12436
12437 pub fn go_to_definition(
12438 &mut self,
12439 _: &GoToDefinition,
12440 window: &mut Window,
12441 cx: &mut Context<Self>,
12442 ) -> Task<Result<Navigated>> {
12443 let definition =
12444 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
12445 cx.spawn_in(window, async move |editor, cx| {
12446 if definition.await? == Navigated::Yes {
12447 return Ok(Navigated::Yes);
12448 }
12449 match editor.update_in(cx, |editor, window, cx| {
12450 editor.find_all_references(&FindAllReferences, window, cx)
12451 })? {
12452 Some(references) => references.await,
12453 None => Ok(Navigated::No),
12454 }
12455 })
12456 }
12457
12458 pub fn go_to_declaration(
12459 &mut self,
12460 _: &GoToDeclaration,
12461 window: &mut Window,
12462 cx: &mut Context<Self>,
12463 ) -> Task<Result<Navigated>> {
12464 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
12465 }
12466
12467 pub fn go_to_declaration_split(
12468 &mut self,
12469 _: &GoToDeclaration,
12470 window: &mut Window,
12471 cx: &mut Context<Self>,
12472 ) -> Task<Result<Navigated>> {
12473 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
12474 }
12475
12476 pub fn go_to_implementation(
12477 &mut self,
12478 _: &GoToImplementation,
12479 window: &mut Window,
12480 cx: &mut Context<Self>,
12481 ) -> Task<Result<Navigated>> {
12482 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
12483 }
12484
12485 pub fn go_to_implementation_split(
12486 &mut self,
12487 _: &GoToImplementationSplit,
12488 window: &mut Window,
12489 cx: &mut Context<Self>,
12490 ) -> Task<Result<Navigated>> {
12491 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
12492 }
12493
12494 pub fn go_to_type_definition(
12495 &mut self,
12496 _: &GoToTypeDefinition,
12497 window: &mut Window,
12498 cx: &mut Context<Self>,
12499 ) -> Task<Result<Navigated>> {
12500 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
12501 }
12502
12503 pub fn go_to_definition_split(
12504 &mut self,
12505 _: &GoToDefinitionSplit,
12506 window: &mut Window,
12507 cx: &mut Context<Self>,
12508 ) -> Task<Result<Navigated>> {
12509 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
12510 }
12511
12512 pub fn go_to_type_definition_split(
12513 &mut self,
12514 _: &GoToTypeDefinitionSplit,
12515 window: &mut Window,
12516 cx: &mut Context<Self>,
12517 ) -> Task<Result<Navigated>> {
12518 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
12519 }
12520
12521 fn go_to_definition_of_kind(
12522 &mut self,
12523 kind: GotoDefinitionKind,
12524 split: bool,
12525 window: &mut Window,
12526 cx: &mut Context<Self>,
12527 ) -> Task<Result<Navigated>> {
12528 let Some(provider) = self.semantics_provider.clone() else {
12529 return Task::ready(Ok(Navigated::No));
12530 };
12531 let head = self.selections.newest::<usize>(cx).head();
12532 let buffer = self.buffer.read(cx);
12533 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
12534 text_anchor
12535 } else {
12536 return Task::ready(Ok(Navigated::No));
12537 };
12538
12539 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
12540 return Task::ready(Ok(Navigated::No));
12541 };
12542
12543 cx.spawn_in(window, async move |editor, cx| {
12544 let definitions = definitions.await?;
12545 let navigated = editor
12546 .update_in(cx, |editor, window, cx| {
12547 editor.navigate_to_hover_links(
12548 Some(kind),
12549 definitions
12550 .into_iter()
12551 .filter(|location| {
12552 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
12553 })
12554 .map(HoverLink::Text)
12555 .collect::<Vec<_>>(),
12556 split,
12557 window,
12558 cx,
12559 )
12560 })?
12561 .await?;
12562 anyhow::Ok(navigated)
12563 })
12564 }
12565
12566 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
12567 let selection = self.selections.newest_anchor();
12568 let head = selection.head();
12569 let tail = selection.tail();
12570
12571 let Some((buffer, start_position)) =
12572 self.buffer.read(cx).text_anchor_for_position(head, cx)
12573 else {
12574 return;
12575 };
12576
12577 let end_position = if head != tail {
12578 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
12579 return;
12580 };
12581 Some(pos)
12582 } else {
12583 None
12584 };
12585
12586 let url_finder = cx.spawn_in(window, async move |editor, cx| {
12587 let url = if let Some(end_pos) = end_position {
12588 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
12589 } else {
12590 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
12591 };
12592
12593 if let Some(url) = url {
12594 editor.update(cx, |_, cx| {
12595 cx.open_url(&url);
12596 })
12597 } else {
12598 Ok(())
12599 }
12600 });
12601
12602 url_finder.detach();
12603 }
12604
12605 pub fn open_selected_filename(
12606 &mut self,
12607 _: &OpenSelectedFilename,
12608 window: &mut Window,
12609 cx: &mut Context<Self>,
12610 ) {
12611 let Some(workspace) = self.workspace() else {
12612 return;
12613 };
12614
12615 let position = self.selections.newest_anchor().head();
12616
12617 let Some((buffer, buffer_position)) =
12618 self.buffer.read(cx).text_anchor_for_position(position, cx)
12619 else {
12620 return;
12621 };
12622
12623 let project = self.project.clone();
12624
12625 cx.spawn_in(window, async move |_, cx| {
12626 let result = find_file(&buffer, project, buffer_position, cx).await;
12627
12628 if let Some((_, path)) = result {
12629 workspace
12630 .update_in(cx, |workspace, window, cx| {
12631 workspace.open_resolved_path(path, window, cx)
12632 })?
12633 .await?;
12634 }
12635 anyhow::Ok(())
12636 })
12637 .detach();
12638 }
12639
12640 pub(crate) fn navigate_to_hover_links(
12641 &mut self,
12642 kind: Option<GotoDefinitionKind>,
12643 mut definitions: Vec<HoverLink>,
12644 split: bool,
12645 window: &mut Window,
12646 cx: &mut Context<Editor>,
12647 ) -> Task<Result<Navigated>> {
12648 // If there is one definition, just open it directly
12649 if definitions.len() == 1 {
12650 let definition = definitions.pop().unwrap();
12651
12652 enum TargetTaskResult {
12653 Location(Option<Location>),
12654 AlreadyNavigated,
12655 }
12656
12657 let target_task = match definition {
12658 HoverLink::Text(link) => {
12659 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
12660 }
12661 HoverLink::InlayHint(lsp_location, server_id) => {
12662 let computation =
12663 self.compute_target_location(lsp_location, server_id, window, cx);
12664 cx.background_spawn(async move {
12665 let location = computation.await?;
12666 Ok(TargetTaskResult::Location(location))
12667 })
12668 }
12669 HoverLink::Url(url) => {
12670 cx.open_url(&url);
12671 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
12672 }
12673 HoverLink::File(path) => {
12674 if let Some(workspace) = self.workspace() {
12675 cx.spawn_in(window, async move |_, cx| {
12676 workspace
12677 .update_in(cx, |workspace, window, cx| {
12678 workspace.open_resolved_path(path, window, cx)
12679 })?
12680 .await
12681 .map(|_| TargetTaskResult::AlreadyNavigated)
12682 })
12683 } else {
12684 Task::ready(Ok(TargetTaskResult::Location(None)))
12685 }
12686 }
12687 };
12688 cx.spawn_in(window, async move |editor, cx| {
12689 let target = match target_task.await.context("target resolution task")? {
12690 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
12691 TargetTaskResult::Location(None) => return Ok(Navigated::No),
12692 TargetTaskResult::Location(Some(target)) => target,
12693 };
12694
12695 editor.update_in(cx, |editor, window, cx| {
12696 let Some(workspace) = editor.workspace() else {
12697 return Navigated::No;
12698 };
12699 let pane = workspace.read(cx).active_pane().clone();
12700
12701 let range = target.range.to_point(target.buffer.read(cx));
12702 let range = editor.range_for_match(&range);
12703 let range = collapse_multiline_range(range);
12704
12705 if !split
12706 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
12707 {
12708 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
12709 } else {
12710 window.defer(cx, move |window, cx| {
12711 let target_editor: Entity<Self> =
12712 workspace.update(cx, |workspace, cx| {
12713 let pane = if split {
12714 workspace.adjacent_pane(window, cx)
12715 } else {
12716 workspace.active_pane().clone()
12717 };
12718
12719 workspace.open_project_item(
12720 pane,
12721 target.buffer.clone(),
12722 true,
12723 true,
12724 window,
12725 cx,
12726 )
12727 });
12728 target_editor.update(cx, |target_editor, cx| {
12729 // When selecting a definition in a different buffer, disable the nav history
12730 // to avoid creating a history entry at the previous cursor location.
12731 pane.update(cx, |pane, _| pane.disable_history());
12732 target_editor.go_to_singleton_buffer_range(range, window, cx);
12733 pane.update(cx, |pane, _| pane.enable_history());
12734 });
12735 });
12736 }
12737 Navigated::Yes
12738 })
12739 })
12740 } else if !definitions.is_empty() {
12741 cx.spawn_in(window, async move |editor, cx| {
12742 let (title, location_tasks, workspace) = editor
12743 .update_in(cx, |editor, window, cx| {
12744 let tab_kind = match kind {
12745 Some(GotoDefinitionKind::Implementation) => "Implementations",
12746 _ => "Definitions",
12747 };
12748 let title = definitions
12749 .iter()
12750 .find_map(|definition| match definition {
12751 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
12752 let buffer = origin.buffer.read(cx);
12753 format!(
12754 "{} for {}",
12755 tab_kind,
12756 buffer
12757 .text_for_range(origin.range.clone())
12758 .collect::<String>()
12759 )
12760 }),
12761 HoverLink::InlayHint(_, _) => None,
12762 HoverLink::Url(_) => None,
12763 HoverLink::File(_) => None,
12764 })
12765 .unwrap_or(tab_kind.to_string());
12766 let location_tasks = definitions
12767 .into_iter()
12768 .map(|definition| match definition {
12769 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
12770 HoverLink::InlayHint(lsp_location, server_id) => editor
12771 .compute_target_location(lsp_location, server_id, window, cx),
12772 HoverLink::Url(_) => Task::ready(Ok(None)),
12773 HoverLink::File(_) => Task::ready(Ok(None)),
12774 })
12775 .collect::<Vec<_>>();
12776 (title, location_tasks, editor.workspace().clone())
12777 })
12778 .context("location tasks preparation")?;
12779
12780 let locations = future::join_all(location_tasks)
12781 .await
12782 .into_iter()
12783 .filter_map(|location| location.transpose())
12784 .collect::<Result<_>>()
12785 .context("location tasks")?;
12786
12787 let Some(workspace) = workspace else {
12788 return Ok(Navigated::No);
12789 };
12790 let opened = workspace
12791 .update_in(cx, |workspace, window, cx| {
12792 Self::open_locations_in_multibuffer(
12793 workspace,
12794 locations,
12795 title,
12796 split,
12797 MultibufferSelectionMode::First,
12798 window,
12799 cx,
12800 )
12801 })
12802 .ok();
12803
12804 anyhow::Ok(Navigated::from_bool(opened.is_some()))
12805 })
12806 } else {
12807 Task::ready(Ok(Navigated::No))
12808 }
12809 }
12810
12811 fn compute_target_location(
12812 &self,
12813 lsp_location: lsp::Location,
12814 server_id: LanguageServerId,
12815 window: &mut Window,
12816 cx: &mut Context<Self>,
12817 ) -> Task<anyhow::Result<Option<Location>>> {
12818 let Some(project) = self.project.clone() else {
12819 return Task::ready(Ok(None));
12820 };
12821
12822 cx.spawn_in(window, async move |editor, cx| {
12823 let location_task = editor.update(cx, |_, cx| {
12824 project.update(cx, |project, cx| {
12825 let language_server_name = project
12826 .language_server_statuses(cx)
12827 .find(|(id, _)| server_id == *id)
12828 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
12829 language_server_name.map(|language_server_name| {
12830 project.open_local_buffer_via_lsp(
12831 lsp_location.uri.clone(),
12832 server_id,
12833 language_server_name,
12834 cx,
12835 )
12836 })
12837 })
12838 })?;
12839 let location = match location_task {
12840 Some(task) => Some({
12841 let target_buffer_handle = task.await.context("open local buffer")?;
12842 let range = target_buffer_handle.update(cx, |target_buffer, _| {
12843 let target_start = target_buffer
12844 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
12845 let target_end = target_buffer
12846 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
12847 target_buffer.anchor_after(target_start)
12848 ..target_buffer.anchor_before(target_end)
12849 })?;
12850 Location {
12851 buffer: target_buffer_handle,
12852 range,
12853 }
12854 }),
12855 None => None,
12856 };
12857 Ok(location)
12858 })
12859 }
12860
12861 pub fn find_all_references(
12862 &mut self,
12863 _: &FindAllReferences,
12864 window: &mut Window,
12865 cx: &mut Context<Self>,
12866 ) -> Option<Task<Result<Navigated>>> {
12867 let selection = self.selections.newest::<usize>(cx);
12868 let multi_buffer = self.buffer.read(cx);
12869 let head = selection.head();
12870
12871 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
12872 let head_anchor = multi_buffer_snapshot.anchor_at(
12873 head,
12874 if head < selection.tail() {
12875 Bias::Right
12876 } else {
12877 Bias::Left
12878 },
12879 );
12880
12881 match self
12882 .find_all_references_task_sources
12883 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
12884 {
12885 Ok(_) => {
12886 log::info!(
12887 "Ignoring repeated FindAllReferences invocation with the position of already running task"
12888 );
12889 return None;
12890 }
12891 Err(i) => {
12892 self.find_all_references_task_sources.insert(i, head_anchor);
12893 }
12894 }
12895
12896 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
12897 let workspace = self.workspace()?;
12898 let project = workspace.read(cx).project().clone();
12899 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
12900 Some(cx.spawn_in(window, async move |editor, cx| {
12901 let _cleanup = cx.on_drop(&editor, move |editor, _| {
12902 if let Ok(i) = editor
12903 .find_all_references_task_sources
12904 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
12905 {
12906 editor.find_all_references_task_sources.remove(i);
12907 }
12908 });
12909
12910 let locations = references.await?;
12911 if locations.is_empty() {
12912 return anyhow::Ok(Navigated::No);
12913 }
12914
12915 workspace.update_in(cx, |workspace, window, cx| {
12916 let title = locations
12917 .first()
12918 .as_ref()
12919 .map(|location| {
12920 let buffer = location.buffer.read(cx);
12921 format!(
12922 "References to `{}`",
12923 buffer
12924 .text_for_range(location.range.clone())
12925 .collect::<String>()
12926 )
12927 })
12928 .unwrap();
12929 Self::open_locations_in_multibuffer(
12930 workspace,
12931 locations,
12932 title,
12933 false,
12934 MultibufferSelectionMode::First,
12935 window,
12936 cx,
12937 );
12938 Navigated::Yes
12939 })
12940 }))
12941 }
12942
12943 /// Opens a multibuffer with the given project locations in it
12944 pub fn open_locations_in_multibuffer(
12945 workspace: &mut Workspace,
12946 mut locations: Vec<Location>,
12947 title: String,
12948 split: bool,
12949 multibuffer_selection_mode: MultibufferSelectionMode,
12950 window: &mut Window,
12951 cx: &mut Context<Workspace>,
12952 ) {
12953 // If there are multiple definitions, open them in a multibuffer
12954 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
12955 let mut locations = locations.into_iter().peekable();
12956 let mut ranges = Vec::new();
12957 let capability = workspace.project().read(cx).capability();
12958
12959 let excerpt_buffer = cx.new(|cx| {
12960 let mut multibuffer = MultiBuffer::new(capability);
12961 while let Some(location) = locations.next() {
12962 let buffer = location.buffer.read(cx);
12963 let mut ranges_for_buffer = Vec::new();
12964 let range = location.range.to_offset(buffer);
12965 ranges_for_buffer.push(range.clone());
12966
12967 while let Some(next_location) = locations.peek() {
12968 if next_location.buffer == location.buffer {
12969 ranges_for_buffer.push(next_location.range.to_offset(buffer));
12970 locations.next();
12971 } else {
12972 break;
12973 }
12974 }
12975
12976 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
12977 ranges.extend(multibuffer.push_excerpts_with_context_lines(
12978 location.buffer.clone(),
12979 ranges_for_buffer,
12980 DEFAULT_MULTIBUFFER_CONTEXT,
12981 cx,
12982 ))
12983 }
12984
12985 multibuffer.with_title(title)
12986 });
12987
12988 let editor = cx.new(|cx| {
12989 Editor::for_multibuffer(
12990 excerpt_buffer,
12991 Some(workspace.project().clone()),
12992 window,
12993 cx,
12994 )
12995 });
12996 editor.update(cx, |editor, cx| {
12997 match multibuffer_selection_mode {
12998 MultibufferSelectionMode::First => {
12999 if let Some(first_range) = ranges.first() {
13000 editor.change_selections(None, window, cx, |selections| {
13001 selections.clear_disjoint();
13002 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
13003 });
13004 }
13005 editor.highlight_background::<Self>(
13006 &ranges,
13007 |theme| theme.editor_highlighted_line_background,
13008 cx,
13009 );
13010 }
13011 MultibufferSelectionMode::All => {
13012 editor.change_selections(None, window, cx, |selections| {
13013 selections.clear_disjoint();
13014 selections.select_anchor_ranges(ranges);
13015 });
13016 }
13017 }
13018 editor.register_buffers_with_language_servers(cx);
13019 });
13020
13021 let item = Box::new(editor);
13022 let item_id = item.item_id();
13023
13024 if split {
13025 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
13026 } else {
13027 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
13028 let (preview_item_id, preview_item_idx) =
13029 workspace.active_pane().update(cx, |pane, _| {
13030 (pane.preview_item_id(), pane.preview_item_idx())
13031 });
13032
13033 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
13034
13035 if let Some(preview_item_id) = preview_item_id {
13036 workspace.active_pane().update(cx, |pane, cx| {
13037 pane.remove_item(preview_item_id, false, false, window, cx);
13038 });
13039 }
13040 } else {
13041 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
13042 }
13043 }
13044 workspace.active_pane().update(cx, |pane, cx| {
13045 pane.set_preview_item_id(Some(item_id), cx);
13046 });
13047 }
13048
13049 pub fn rename(
13050 &mut self,
13051 _: &Rename,
13052 window: &mut Window,
13053 cx: &mut Context<Self>,
13054 ) -> Option<Task<Result<()>>> {
13055 use language::ToOffset as _;
13056
13057 let provider = self.semantics_provider.clone()?;
13058 let selection = self.selections.newest_anchor().clone();
13059 let (cursor_buffer, cursor_buffer_position) = self
13060 .buffer
13061 .read(cx)
13062 .text_anchor_for_position(selection.head(), cx)?;
13063 let (tail_buffer, cursor_buffer_position_end) = self
13064 .buffer
13065 .read(cx)
13066 .text_anchor_for_position(selection.tail(), cx)?;
13067 if tail_buffer != cursor_buffer {
13068 return None;
13069 }
13070
13071 let snapshot = cursor_buffer.read(cx).snapshot();
13072 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
13073 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
13074 let prepare_rename = provider
13075 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
13076 .unwrap_or_else(|| Task::ready(Ok(None)));
13077 drop(snapshot);
13078
13079 Some(cx.spawn_in(window, async move |this, cx| {
13080 let rename_range = if let Some(range) = prepare_rename.await? {
13081 Some(range)
13082 } else {
13083 this.update(cx, |this, cx| {
13084 let buffer = this.buffer.read(cx).snapshot(cx);
13085 let mut buffer_highlights = this
13086 .document_highlights_for_position(selection.head(), &buffer)
13087 .filter(|highlight| {
13088 highlight.start.excerpt_id == selection.head().excerpt_id
13089 && highlight.end.excerpt_id == selection.head().excerpt_id
13090 });
13091 buffer_highlights
13092 .next()
13093 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
13094 })?
13095 };
13096 if let Some(rename_range) = rename_range {
13097 this.update_in(cx, |this, window, cx| {
13098 let snapshot = cursor_buffer.read(cx).snapshot();
13099 let rename_buffer_range = rename_range.to_offset(&snapshot);
13100 let cursor_offset_in_rename_range =
13101 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
13102 let cursor_offset_in_rename_range_end =
13103 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
13104
13105 this.take_rename(false, window, cx);
13106 let buffer = this.buffer.read(cx).read(cx);
13107 let cursor_offset = selection.head().to_offset(&buffer);
13108 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
13109 let rename_end = rename_start + rename_buffer_range.len();
13110 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
13111 let mut old_highlight_id = None;
13112 let old_name: Arc<str> = buffer
13113 .chunks(rename_start..rename_end, true)
13114 .map(|chunk| {
13115 if old_highlight_id.is_none() {
13116 old_highlight_id = chunk.syntax_highlight_id;
13117 }
13118 chunk.text
13119 })
13120 .collect::<String>()
13121 .into();
13122
13123 drop(buffer);
13124
13125 // Position the selection in the rename editor so that it matches the current selection.
13126 this.show_local_selections = false;
13127 let rename_editor = cx.new(|cx| {
13128 let mut editor = Editor::single_line(window, cx);
13129 editor.buffer.update(cx, |buffer, cx| {
13130 buffer.edit([(0..0, old_name.clone())], None, cx)
13131 });
13132 let rename_selection_range = match cursor_offset_in_rename_range
13133 .cmp(&cursor_offset_in_rename_range_end)
13134 {
13135 Ordering::Equal => {
13136 editor.select_all(&SelectAll, window, cx);
13137 return editor;
13138 }
13139 Ordering::Less => {
13140 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
13141 }
13142 Ordering::Greater => {
13143 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
13144 }
13145 };
13146 if rename_selection_range.end > old_name.len() {
13147 editor.select_all(&SelectAll, window, cx);
13148 } else {
13149 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13150 s.select_ranges([rename_selection_range]);
13151 });
13152 }
13153 editor
13154 });
13155 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
13156 if e == &EditorEvent::Focused {
13157 cx.emit(EditorEvent::FocusedIn)
13158 }
13159 })
13160 .detach();
13161
13162 let write_highlights =
13163 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
13164 let read_highlights =
13165 this.clear_background_highlights::<DocumentHighlightRead>(cx);
13166 let ranges = write_highlights
13167 .iter()
13168 .flat_map(|(_, ranges)| ranges.iter())
13169 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
13170 .cloned()
13171 .collect();
13172
13173 this.highlight_text::<Rename>(
13174 ranges,
13175 HighlightStyle {
13176 fade_out: Some(0.6),
13177 ..Default::default()
13178 },
13179 cx,
13180 );
13181 let rename_focus_handle = rename_editor.focus_handle(cx);
13182 window.focus(&rename_focus_handle);
13183 let block_id = this.insert_blocks(
13184 [BlockProperties {
13185 style: BlockStyle::Flex,
13186 placement: BlockPlacement::Below(range.start),
13187 height: 1,
13188 render: Arc::new({
13189 let rename_editor = rename_editor.clone();
13190 move |cx: &mut BlockContext| {
13191 let mut text_style = cx.editor_style.text.clone();
13192 if let Some(highlight_style) = old_highlight_id
13193 .and_then(|h| h.style(&cx.editor_style.syntax))
13194 {
13195 text_style = text_style.highlight(highlight_style);
13196 }
13197 div()
13198 .block_mouse_down()
13199 .pl(cx.anchor_x)
13200 .child(EditorElement::new(
13201 &rename_editor,
13202 EditorStyle {
13203 background: cx.theme().system().transparent,
13204 local_player: cx.editor_style.local_player,
13205 text: text_style,
13206 scrollbar_width: cx.editor_style.scrollbar_width,
13207 syntax: cx.editor_style.syntax.clone(),
13208 status: cx.editor_style.status.clone(),
13209 inlay_hints_style: HighlightStyle {
13210 font_weight: Some(FontWeight::BOLD),
13211 ..make_inlay_hints_style(cx.app)
13212 },
13213 inline_completion_styles: make_suggestion_styles(
13214 cx.app,
13215 ),
13216 ..EditorStyle::default()
13217 },
13218 ))
13219 .into_any_element()
13220 }
13221 }),
13222 priority: 0,
13223 }],
13224 Some(Autoscroll::fit()),
13225 cx,
13226 )[0];
13227 this.pending_rename = Some(RenameState {
13228 range,
13229 old_name,
13230 editor: rename_editor,
13231 block_id,
13232 });
13233 })?;
13234 }
13235
13236 Ok(())
13237 }))
13238 }
13239
13240 pub fn confirm_rename(
13241 &mut self,
13242 _: &ConfirmRename,
13243 window: &mut Window,
13244 cx: &mut Context<Self>,
13245 ) -> Option<Task<Result<()>>> {
13246 let rename = self.take_rename(false, window, cx)?;
13247 let workspace = self.workspace()?.downgrade();
13248 let (buffer, start) = self
13249 .buffer
13250 .read(cx)
13251 .text_anchor_for_position(rename.range.start, cx)?;
13252 let (end_buffer, _) = self
13253 .buffer
13254 .read(cx)
13255 .text_anchor_for_position(rename.range.end, cx)?;
13256 if buffer != end_buffer {
13257 return None;
13258 }
13259
13260 let old_name = rename.old_name;
13261 let new_name = rename.editor.read(cx).text(cx);
13262
13263 let rename = self.semantics_provider.as_ref()?.perform_rename(
13264 &buffer,
13265 start,
13266 new_name.clone(),
13267 cx,
13268 )?;
13269
13270 Some(cx.spawn_in(window, async move |editor, cx| {
13271 let project_transaction = rename.await?;
13272 Self::open_project_transaction(
13273 &editor,
13274 workspace,
13275 project_transaction,
13276 format!("Rename: {} → {}", old_name, new_name),
13277 cx,
13278 )
13279 .await?;
13280
13281 editor.update(cx, |editor, cx| {
13282 editor.refresh_document_highlights(cx);
13283 })?;
13284 Ok(())
13285 }))
13286 }
13287
13288 fn take_rename(
13289 &mut self,
13290 moving_cursor: bool,
13291 window: &mut Window,
13292 cx: &mut Context<Self>,
13293 ) -> Option<RenameState> {
13294 let rename = self.pending_rename.take()?;
13295 if rename.editor.focus_handle(cx).is_focused(window) {
13296 window.focus(&self.focus_handle);
13297 }
13298
13299 self.remove_blocks(
13300 [rename.block_id].into_iter().collect(),
13301 Some(Autoscroll::fit()),
13302 cx,
13303 );
13304 self.clear_highlights::<Rename>(cx);
13305 self.show_local_selections = true;
13306
13307 if moving_cursor {
13308 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
13309 editor.selections.newest::<usize>(cx).head()
13310 });
13311
13312 // Update the selection to match the position of the selection inside
13313 // the rename editor.
13314 let snapshot = self.buffer.read(cx).read(cx);
13315 let rename_range = rename.range.to_offset(&snapshot);
13316 let cursor_in_editor = snapshot
13317 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
13318 .min(rename_range.end);
13319 drop(snapshot);
13320
13321 self.change_selections(None, window, cx, |s| {
13322 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
13323 });
13324 } else {
13325 self.refresh_document_highlights(cx);
13326 }
13327
13328 Some(rename)
13329 }
13330
13331 pub fn pending_rename(&self) -> Option<&RenameState> {
13332 self.pending_rename.as_ref()
13333 }
13334
13335 fn format(
13336 &mut self,
13337 _: &Format,
13338 window: &mut Window,
13339 cx: &mut Context<Self>,
13340 ) -> Option<Task<Result<()>>> {
13341 let project = match &self.project {
13342 Some(project) => project.clone(),
13343 None => return None,
13344 };
13345
13346 Some(self.perform_format(
13347 project,
13348 FormatTrigger::Manual,
13349 FormatTarget::Buffers,
13350 window,
13351 cx,
13352 ))
13353 }
13354
13355 fn format_selections(
13356 &mut self,
13357 _: &FormatSelections,
13358 window: &mut Window,
13359 cx: &mut Context<Self>,
13360 ) -> Option<Task<Result<()>>> {
13361 let project = match &self.project {
13362 Some(project) => project.clone(),
13363 None => return None,
13364 };
13365
13366 let ranges = self
13367 .selections
13368 .all_adjusted(cx)
13369 .into_iter()
13370 .map(|selection| selection.range())
13371 .collect_vec();
13372
13373 Some(self.perform_format(
13374 project,
13375 FormatTrigger::Manual,
13376 FormatTarget::Ranges(ranges),
13377 window,
13378 cx,
13379 ))
13380 }
13381
13382 fn perform_format(
13383 &mut self,
13384 project: Entity<Project>,
13385 trigger: FormatTrigger,
13386 target: FormatTarget,
13387 window: &mut Window,
13388 cx: &mut Context<Self>,
13389 ) -> Task<Result<()>> {
13390 let buffer = self.buffer.clone();
13391 let (buffers, target) = match target {
13392 FormatTarget::Buffers => {
13393 let mut buffers = buffer.read(cx).all_buffers();
13394 if trigger == FormatTrigger::Save {
13395 buffers.retain(|buffer| buffer.read(cx).is_dirty());
13396 }
13397 (buffers, LspFormatTarget::Buffers)
13398 }
13399 FormatTarget::Ranges(selection_ranges) => {
13400 let multi_buffer = buffer.read(cx);
13401 let snapshot = multi_buffer.read(cx);
13402 let mut buffers = HashSet::default();
13403 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
13404 BTreeMap::new();
13405 for selection_range in selection_ranges {
13406 for (buffer, buffer_range, _) in
13407 snapshot.range_to_buffer_ranges(selection_range)
13408 {
13409 let buffer_id = buffer.remote_id();
13410 let start = buffer.anchor_before(buffer_range.start);
13411 let end = buffer.anchor_after(buffer_range.end);
13412 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
13413 buffer_id_to_ranges
13414 .entry(buffer_id)
13415 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
13416 .or_insert_with(|| vec![start..end]);
13417 }
13418 }
13419 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
13420 }
13421 };
13422
13423 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
13424 let format = project.update(cx, |project, cx| {
13425 project.format(buffers, target, true, trigger, cx)
13426 });
13427
13428 cx.spawn_in(window, async move |_, cx| {
13429 let transaction = futures::select_biased! {
13430 transaction = format.log_err().fuse() => transaction,
13431 () = timeout => {
13432 log::warn!("timed out waiting for formatting");
13433 None
13434 }
13435 };
13436
13437 buffer
13438 .update(cx, |buffer, cx| {
13439 if let Some(transaction) = transaction {
13440 if !buffer.is_singleton() {
13441 buffer.push_transaction(&transaction.0, cx);
13442 }
13443 }
13444 cx.notify();
13445 })
13446 .ok();
13447
13448 Ok(())
13449 })
13450 }
13451
13452 fn organize_imports(
13453 &mut self,
13454 _: &OrganizeImports,
13455 window: &mut Window,
13456 cx: &mut Context<Self>,
13457 ) -> Option<Task<Result<()>>> {
13458 let project = match &self.project {
13459 Some(project) => project.clone(),
13460 None => return None,
13461 };
13462 Some(self.perform_code_action_kind(
13463 project,
13464 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
13465 window,
13466 cx,
13467 ))
13468 }
13469
13470 fn perform_code_action_kind(
13471 &mut self,
13472 project: Entity<Project>,
13473 kind: CodeActionKind,
13474 window: &mut Window,
13475 cx: &mut Context<Self>,
13476 ) -> Task<Result<()>> {
13477 let buffer = self.buffer.clone();
13478 let buffers = buffer.read(cx).all_buffers();
13479 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
13480 let apply_action = project.update(cx, |project, cx| {
13481 project.apply_code_action_kind(buffers, kind, true, cx)
13482 });
13483 cx.spawn_in(window, async move |_, cx| {
13484 let transaction = futures::select_biased! {
13485 () = timeout => {
13486 log::warn!("timed out waiting for executing code action");
13487 None
13488 }
13489 transaction = apply_action.log_err().fuse() => transaction,
13490 };
13491 buffer
13492 .update(cx, |buffer, cx| {
13493 // check if we need this
13494 if let Some(transaction) = transaction {
13495 if !buffer.is_singleton() {
13496 buffer.push_transaction(&transaction.0, cx);
13497 }
13498 }
13499 cx.notify();
13500 })
13501 .ok();
13502 Ok(())
13503 })
13504 }
13505
13506 fn restart_language_server(
13507 &mut self,
13508 _: &RestartLanguageServer,
13509 _: &mut Window,
13510 cx: &mut Context<Self>,
13511 ) {
13512 if let Some(project) = self.project.clone() {
13513 self.buffer.update(cx, |multi_buffer, cx| {
13514 project.update(cx, |project, cx| {
13515 project.restart_language_servers_for_buffers(
13516 multi_buffer.all_buffers().into_iter().collect(),
13517 cx,
13518 );
13519 });
13520 })
13521 }
13522 }
13523
13524 fn cancel_language_server_work(
13525 workspace: &mut Workspace,
13526 _: &actions::CancelLanguageServerWork,
13527 _: &mut Window,
13528 cx: &mut Context<Workspace>,
13529 ) {
13530 let project = workspace.project();
13531 let buffers = workspace
13532 .active_item(cx)
13533 .and_then(|item| item.act_as::<Editor>(cx))
13534 .map_or(HashSet::default(), |editor| {
13535 editor.read(cx).buffer.read(cx).all_buffers()
13536 });
13537 project.update(cx, |project, cx| {
13538 project.cancel_language_server_work_for_buffers(buffers, cx);
13539 });
13540 }
13541
13542 fn show_character_palette(
13543 &mut self,
13544 _: &ShowCharacterPalette,
13545 window: &mut Window,
13546 _: &mut Context<Self>,
13547 ) {
13548 window.show_character_palette();
13549 }
13550
13551 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
13552 if let Some(active_diagnostics) = self.active_diagnostics.as_mut() {
13553 let buffer = self.buffer.read(cx).snapshot(cx);
13554 let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer);
13555 let primary_range_end = active_diagnostics.primary_range.end.to_offset(&buffer);
13556 let is_valid = buffer
13557 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
13558 .any(|entry| {
13559 entry.diagnostic.is_primary
13560 && !entry.range.is_empty()
13561 && entry.range.start == primary_range_start
13562 && entry.diagnostic.message == active_diagnostics.primary_message
13563 });
13564
13565 if is_valid != active_diagnostics.is_valid {
13566 active_diagnostics.is_valid = is_valid;
13567 if is_valid {
13568 let mut new_styles = HashMap::default();
13569 for (block_id, diagnostic) in &active_diagnostics.blocks {
13570 new_styles.insert(
13571 *block_id,
13572 diagnostic_block_renderer(diagnostic.clone(), None, true),
13573 );
13574 }
13575 self.display_map.update(cx, |display_map, _cx| {
13576 display_map.replace_blocks(new_styles);
13577 });
13578 } else {
13579 self.dismiss_diagnostics(cx);
13580 }
13581 }
13582 }
13583 }
13584
13585 fn activate_diagnostics(
13586 &mut self,
13587 buffer_id: BufferId,
13588 group_id: usize,
13589 window: &mut Window,
13590 cx: &mut Context<Self>,
13591 ) {
13592 self.dismiss_diagnostics(cx);
13593 let snapshot = self.snapshot(window, cx);
13594 self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
13595 let buffer = self.buffer.read(cx).snapshot(cx);
13596
13597 let mut primary_range = None;
13598 let mut primary_message = None;
13599 let diagnostic_group = buffer
13600 .diagnostic_group(buffer_id, group_id)
13601 .filter_map(|entry| {
13602 let start = entry.range.start;
13603 let end = entry.range.end;
13604 if snapshot.is_line_folded(MultiBufferRow(start.row))
13605 && (start.row == end.row
13606 || snapshot.is_line_folded(MultiBufferRow(end.row)))
13607 {
13608 return None;
13609 }
13610 if entry.diagnostic.is_primary {
13611 primary_range = Some(entry.range.clone());
13612 primary_message = Some(entry.diagnostic.message.clone());
13613 }
13614 Some(entry)
13615 })
13616 .collect::<Vec<_>>();
13617 let primary_range = primary_range?;
13618 let primary_message = primary_message?;
13619
13620 let blocks = display_map
13621 .insert_blocks(
13622 diagnostic_group.iter().map(|entry| {
13623 let diagnostic = entry.diagnostic.clone();
13624 let message_height = diagnostic.message.matches('\n').count() as u32 + 1;
13625 BlockProperties {
13626 style: BlockStyle::Fixed,
13627 placement: BlockPlacement::Below(
13628 buffer.anchor_after(entry.range.start),
13629 ),
13630 height: message_height,
13631 render: diagnostic_block_renderer(diagnostic, None, true),
13632 priority: 0,
13633 }
13634 }),
13635 cx,
13636 )
13637 .into_iter()
13638 .zip(diagnostic_group.into_iter().map(|entry| entry.diagnostic))
13639 .collect();
13640
13641 Some(ActiveDiagnosticGroup {
13642 primary_range: buffer.anchor_before(primary_range.start)
13643 ..buffer.anchor_after(primary_range.end),
13644 primary_message,
13645 group_id,
13646 blocks,
13647 is_valid: true,
13648 })
13649 });
13650 }
13651
13652 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
13653 if let Some(active_diagnostic_group) = self.active_diagnostics.take() {
13654 self.display_map.update(cx, |display_map, cx| {
13655 display_map.remove_blocks(active_diagnostic_group.blocks.into_keys().collect(), cx);
13656 });
13657 cx.notify();
13658 }
13659 }
13660
13661 /// Disable inline diagnostics rendering for this editor.
13662 pub fn disable_inline_diagnostics(&mut self) {
13663 self.inline_diagnostics_enabled = false;
13664 self.inline_diagnostics_update = Task::ready(());
13665 self.inline_diagnostics.clear();
13666 }
13667
13668 pub fn inline_diagnostics_enabled(&self) -> bool {
13669 self.inline_diagnostics_enabled
13670 }
13671
13672 pub fn show_inline_diagnostics(&self) -> bool {
13673 self.show_inline_diagnostics
13674 }
13675
13676 pub fn toggle_inline_diagnostics(
13677 &mut self,
13678 _: &ToggleInlineDiagnostics,
13679 window: &mut Window,
13680 cx: &mut Context<'_, Editor>,
13681 ) {
13682 self.show_inline_diagnostics = !self.show_inline_diagnostics;
13683 self.refresh_inline_diagnostics(false, window, cx);
13684 }
13685
13686 fn refresh_inline_diagnostics(
13687 &mut self,
13688 debounce: bool,
13689 window: &mut Window,
13690 cx: &mut Context<Self>,
13691 ) {
13692 if !self.inline_diagnostics_enabled || !self.show_inline_diagnostics {
13693 self.inline_diagnostics_update = Task::ready(());
13694 self.inline_diagnostics.clear();
13695 return;
13696 }
13697
13698 let debounce_ms = ProjectSettings::get_global(cx)
13699 .diagnostics
13700 .inline
13701 .update_debounce_ms;
13702 let debounce = if debounce && debounce_ms > 0 {
13703 Some(Duration::from_millis(debounce_ms))
13704 } else {
13705 None
13706 };
13707 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
13708 if let Some(debounce) = debounce {
13709 cx.background_executor().timer(debounce).await;
13710 }
13711 let Some(snapshot) = editor
13712 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
13713 .ok()
13714 else {
13715 return;
13716 };
13717
13718 let new_inline_diagnostics = cx
13719 .background_spawn(async move {
13720 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
13721 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
13722 let message = diagnostic_entry
13723 .diagnostic
13724 .message
13725 .split_once('\n')
13726 .map(|(line, _)| line)
13727 .map(SharedString::new)
13728 .unwrap_or_else(|| {
13729 SharedString::from(diagnostic_entry.diagnostic.message)
13730 });
13731 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
13732 let (Ok(i) | Err(i)) = inline_diagnostics
13733 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
13734 inline_diagnostics.insert(
13735 i,
13736 (
13737 start_anchor,
13738 InlineDiagnostic {
13739 message,
13740 group_id: diagnostic_entry.diagnostic.group_id,
13741 start: diagnostic_entry.range.start.to_point(&snapshot),
13742 is_primary: diagnostic_entry.diagnostic.is_primary,
13743 severity: diagnostic_entry.diagnostic.severity,
13744 },
13745 ),
13746 );
13747 }
13748 inline_diagnostics
13749 })
13750 .await;
13751
13752 editor
13753 .update(cx, |editor, cx| {
13754 editor.inline_diagnostics = new_inline_diagnostics;
13755 cx.notify();
13756 })
13757 .ok();
13758 });
13759 }
13760
13761 pub fn set_selections_from_remote(
13762 &mut self,
13763 selections: Vec<Selection<Anchor>>,
13764 pending_selection: Option<Selection<Anchor>>,
13765 window: &mut Window,
13766 cx: &mut Context<Self>,
13767 ) {
13768 let old_cursor_position = self.selections.newest_anchor().head();
13769 self.selections.change_with(cx, |s| {
13770 s.select_anchors(selections);
13771 if let Some(pending_selection) = pending_selection {
13772 s.set_pending(pending_selection, SelectMode::Character);
13773 } else {
13774 s.clear_pending();
13775 }
13776 });
13777 self.selections_did_change(false, &old_cursor_position, true, window, cx);
13778 }
13779
13780 fn push_to_selection_history(&mut self) {
13781 self.selection_history.push(SelectionHistoryEntry {
13782 selections: self.selections.disjoint_anchors(),
13783 select_next_state: self.select_next_state.clone(),
13784 select_prev_state: self.select_prev_state.clone(),
13785 add_selections_state: self.add_selections_state.clone(),
13786 });
13787 }
13788
13789 pub fn transact(
13790 &mut self,
13791 window: &mut Window,
13792 cx: &mut Context<Self>,
13793 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
13794 ) -> Option<TransactionId> {
13795 self.start_transaction_at(Instant::now(), window, cx);
13796 update(self, window, cx);
13797 self.end_transaction_at(Instant::now(), cx)
13798 }
13799
13800 pub fn start_transaction_at(
13801 &mut self,
13802 now: Instant,
13803 window: &mut Window,
13804 cx: &mut Context<Self>,
13805 ) {
13806 self.end_selection(window, cx);
13807 if let Some(tx_id) = self
13808 .buffer
13809 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
13810 {
13811 self.selection_history
13812 .insert_transaction(tx_id, self.selections.disjoint_anchors());
13813 cx.emit(EditorEvent::TransactionBegun {
13814 transaction_id: tx_id,
13815 })
13816 }
13817 }
13818
13819 pub fn end_transaction_at(
13820 &mut self,
13821 now: Instant,
13822 cx: &mut Context<Self>,
13823 ) -> Option<TransactionId> {
13824 if let Some(transaction_id) = self
13825 .buffer
13826 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
13827 {
13828 if let Some((_, end_selections)) =
13829 self.selection_history.transaction_mut(transaction_id)
13830 {
13831 *end_selections = Some(self.selections.disjoint_anchors());
13832 } else {
13833 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
13834 }
13835
13836 cx.emit(EditorEvent::Edited { transaction_id });
13837 Some(transaction_id)
13838 } else {
13839 None
13840 }
13841 }
13842
13843 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
13844 if self.selection_mark_mode {
13845 self.change_selections(None, window, cx, |s| {
13846 s.move_with(|_, sel| {
13847 sel.collapse_to(sel.head(), SelectionGoal::None);
13848 });
13849 })
13850 }
13851 self.selection_mark_mode = true;
13852 cx.notify();
13853 }
13854
13855 pub fn swap_selection_ends(
13856 &mut self,
13857 _: &actions::SwapSelectionEnds,
13858 window: &mut Window,
13859 cx: &mut Context<Self>,
13860 ) {
13861 self.change_selections(None, window, cx, |s| {
13862 s.move_with(|_, sel| {
13863 if sel.start != sel.end {
13864 sel.reversed = !sel.reversed
13865 }
13866 });
13867 });
13868 self.request_autoscroll(Autoscroll::newest(), cx);
13869 cx.notify();
13870 }
13871
13872 pub fn toggle_fold(
13873 &mut self,
13874 _: &actions::ToggleFold,
13875 window: &mut Window,
13876 cx: &mut Context<Self>,
13877 ) {
13878 if self.is_singleton(cx) {
13879 let selection = self.selections.newest::<Point>(cx);
13880
13881 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13882 let range = if selection.is_empty() {
13883 let point = selection.head().to_display_point(&display_map);
13884 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
13885 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
13886 .to_point(&display_map);
13887 start..end
13888 } else {
13889 selection.range()
13890 };
13891 if display_map.folds_in_range(range).next().is_some() {
13892 self.unfold_lines(&Default::default(), window, cx)
13893 } else {
13894 self.fold(&Default::default(), window, cx)
13895 }
13896 } else {
13897 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
13898 let buffer_ids: HashSet<_> = self
13899 .selections
13900 .disjoint_anchor_ranges()
13901 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
13902 .collect();
13903
13904 let should_unfold = buffer_ids
13905 .iter()
13906 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
13907
13908 for buffer_id in buffer_ids {
13909 if should_unfold {
13910 self.unfold_buffer(buffer_id, cx);
13911 } else {
13912 self.fold_buffer(buffer_id, cx);
13913 }
13914 }
13915 }
13916 }
13917
13918 pub fn toggle_fold_recursive(
13919 &mut self,
13920 _: &actions::ToggleFoldRecursive,
13921 window: &mut Window,
13922 cx: &mut Context<Self>,
13923 ) {
13924 let selection = self.selections.newest::<Point>(cx);
13925
13926 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13927 let range = if selection.is_empty() {
13928 let point = selection.head().to_display_point(&display_map);
13929 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
13930 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
13931 .to_point(&display_map);
13932 start..end
13933 } else {
13934 selection.range()
13935 };
13936 if display_map.folds_in_range(range).next().is_some() {
13937 self.unfold_recursive(&Default::default(), window, cx)
13938 } else {
13939 self.fold_recursive(&Default::default(), window, cx)
13940 }
13941 }
13942
13943 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
13944 if self.is_singleton(cx) {
13945 let mut to_fold = Vec::new();
13946 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13947 let selections = self.selections.all_adjusted(cx);
13948
13949 for selection in selections {
13950 let range = selection.range().sorted();
13951 let buffer_start_row = range.start.row;
13952
13953 if range.start.row != range.end.row {
13954 let mut found = false;
13955 let mut row = range.start.row;
13956 while row <= range.end.row {
13957 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
13958 {
13959 found = true;
13960 row = crease.range().end.row + 1;
13961 to_fold.push(crease);
13962 } else {
13963 row += 1
13964 }
13965 }
13966 if found {
13967 continue;
13968 }
13969 }
13970
13971 for row in (0..=range.start.row).rev() {
13972 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
13973 if crease.range().end.row >= buffer_start_row {
13974 to_fold.push(crease);
13975 if row <= range.start.row {
13976 break;
13977 }
13978 }
13979 }
13980 }
13981 }
13982
13983 self.fold_creases(to_fold, true, window, cx);
13984 } else {
13985 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
13986 let buffer_ids = self
13987 .selections
13988 .disjoint_anchor_ranges()
13989 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
13990 .collect::<HashSet<_>>();
13991 for buffer_id in buffer_ids {
13992 self.fold_buffer(buffer_id, cx);
13993 }
13994 }
13995 }
13996
13997 fn fold_at_level(
13998 &mut self,
13999 fold_at: &FoldAtLevel,
14000 window: &mut Window,
14001 cx: &mut Context<Self>,
14002 ) {
14003 if !self.buffer.read(cx).is_singleton() {
14004 return;
14005 }
14006
14007 let fold_at_level = fold_at.0;
14008 let snapshot = self.buffer.read(cx).snapshot(cx);
14009 let mut to_fold = Vec::new();
14010 let mut stack = vec![(0, snapshot.max_row().0, 1)];
14011
14012 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
14013 while start_row < end_row {
14014 match self
14015 .snapshot(window, cx)
14016 .crease_for_buffer_row(MultiBufferRow(start_row))
14017 {
14018 Some(crease) => {
14019 let nested_start_row = crease.range().start.row + 1;
14020 let nested_end_row = crease.range().end.row;
14021
14022 if current_level < fold_at_level {
14023 stack.push((nested_start_row, nested_end_row, current_level + 1));
14024 } else if current_level == fold_at_level {
14025 to_fold.push(crease);
14026 }
14027
14028 start_row = nested_end_row + 1;
14029 }
14030 None => start_row += 1,
14031 }
14032 }
14033 }
14034
14035 self.fold_creases(to_fold, true, window, cx);
14036 }
14037
14038 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
14039 if self.buffer.read(cx).is_singleton() {
14040 let mut fold_ranges = Vec::new();
14041 let snapshot = self.buffer.read(cx).snapshot(cx);
14042
14043 for row in 0..snapshot.max_row().0 {
14044 if let Some(foldable_range) = self
14045 .snapshot(window, cx)
14046 .crease_for_buffer_row(MultiBufferRow(row))
14047 {
14048 fold_ranges.push(foldable_range);
14049 }
14050 }
14051
14052 self.fold_creases(fold_ranges, true, window, cx);
14053 } else {
14054 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
14055 editor
14056 .update_in(cx, |editor, _, cx| {
14057 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
14058 editor.fold_buffer(buffer_id, cx);
14059 }
14060 })
14061 .ok();
14062 });
14063 }
14064 }
14065
14066 pub fn fold_function_bodies(
14067 &mut self,
14068 _: &actions::FoldFunctionBodies,
14069 window: &mut Window,
14070 cx: &mut Context<Self>,
14071 ) {
14072 let snapshot = self.buffer.read(cx).snapshot(cx);
14073
14074 let ranges = snapshot
14075 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
14076 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
14077 .collect::<Vec<_>>();
14078
14079 let creases = ranges
14080 .into_iter()
14081 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
14082 .collect();
14083
14084 self.fold_creases(creases, true, window, cx);
14085 }
14086
14087 pub fn fold_recursive(
14088 &mut self,
14089 _: &actions::FoldRecursive,
14090 window: &mut Window,
14091 cx: &mut Context<Self>,
14092 ) {
14093 let mut to_fold = Vec::new();
14094 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14095 let selections = self.selections.all_adjusted(cx);
14096
14097 for selection in selections {
14098 let range = selection.range().sorted();
14099 let buffer_start_row = range.start.row;
14100
14101 if range.start.row != range.end.row {
14102 let mut found = false;
14103 for row in range.start.row..=range.end.row {
14104 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14105 found = true;
14106 to_fold.push(crease);
14107 }
14108 }
14109 if found {
14110 continue;
14111 }
14112 }
14113
14114 for row in (0..=range.start.row).rev() {
14115 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14116 if crease.range().end.row >= buffer_start_row {
14117 to_fold.push(crease);
14118 } else {
14119 break;
14120 }
14121 }
14122 }
14123 }
14124
14125 self.fold_creases(to_fold, true, window, cx);
14126 }
14127
14128 pub fn fold_at(&mut self, fold_at: &FoldAt, window: &mut Window, cx: &mut Context<Self>) {
14129 let buffer_row = fold_at.buffer_row;
14130 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14131
14132 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
14133 let autoscroll = self
14134 .selections
14135 .all::<Point>(cx)
14136 .iter()
14137 .any(|selection| crease.range().overlaps(&selection.range()));
14138
14139 self.fold_creases(vec![crease], autoscroll, window, cx);
14140 }
14141 }
14142
14143 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
14144 if self.is_singleton(cx) {
14145 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14146 let buffer = &display_map.buffer_snapshot;
14147 let selections = self.selections.all::<Point>(cx);
14148 let ranges = selections
14149 .iter()
14150 .map(|s| {
14151 let range = s.display_range(&display_map).sorted();
14152 let mut start = range.start.to_point(&display_map);
14153 let mut end = range.end.to_point(&display_map);
14154 start.column = 0;
14155 end.column = buffer.line_len(MultiBufferRow(end.row));
14156 start..end
14157 })
14158 .collect::<Vec<_>>();
14159
14160 self.unfold_ranges(&ranges, true, true, cx);
14161 } else {
14162 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14163 let buffer_ids = self
14164 .selections
14165 .disjoint_anchor_ranges()
14166 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14167 .collect::<HashSet<_>>();
14168 for buffer_id in buffer_ids {
14169 self.unfold_buffer(buffer_id, cx);
14170 }
14171 }
14172 }
14173
14174 pub fn unfold_recursive(
14175 &mut self,
14176 _: &UnfoldRecursive,
14177 _window: &mut Window,
14178 cx: &mut Context<Self>,
14179 ) {
14180 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14181 let selections = self.selections.all::<Point>(cx);
14182 let ranges = selections
14183 .iter()
14184 .map(|s| {
14185 let mut range = s.display_range(&display_map).sorted();
14186 *range.start.column_mut() = 0;
14187 *range.end.column_mut() = display_map.line_len(range.end.row());
14188 let start = range.start.to_point(&display_map);
14189 let end = range.end.to_point(&display_map);
14190 start..end
14191 })
14192 .collect::<Vec<_>>();
14193
14194 self.unfold_ranges(&ranges, true, true, cx);
14195 }
14196
14197 pub fn unfold_at(
14198 &mut self,
14199 unfold_at: &UnfoldAt,
14200 _window: &mut Window,
14201 cx: &mut Context<Self>,
14202 ) {
14203 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14204
14205 let intersection_range = Point::new(unfold_at.buffer_row.0, 0)
14206 ..Point::new(
14207 unfold_at.buffer_row.0,
14208 display_map.buffer_snapshot.line_len(unfold_at.buffer_row),
14209 );
14210
14211 let autoscroll = self
14212 .selections
14213 .all::<Point>(cx)
14214 .iter()
14215 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
14216
14217 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
14218 }
14219
14220 pub fn unfold_all(
14221 &mut self,
14222 _: &actions::UnfoldAll,
14223 _window: &mut Window,
14224 cx: &mut Context<Self>,
14225 ) {
14226 if self.buffer.read(cx).is_singleton() {
14227 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14228 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
14229 } else {
14230 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
14231 editor
14232 .update(cx, |editor, cx| {
14233 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
14234 editor.unfold_buffer(buffer_id, cx);
14235 }
14236 })
14237 .ok();
14238 });
14239 }
14240 }
14241
14242 pub fn fold_selected_ranges(
14243 &mut self,
14244 _: &FoldSelectedRanges,
14245 window: &mut Window,
14246 cx: &mut Context<Self>,
14247 ) {
14248 let selections = self.selections.all::<Point>(cx);
14249 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14250 let line_mode = self.selections.line_mode;
14251 let ranges = selections
14252 .into_iter()
14253 .map(|s| {
14254 if line_mode {
14255 let start = Point::new(s.start.row, 0);
14256 let end = Point::new(
14257 s.end.row,
14258 display_map
14259 .buffer_snapshot
14260 .line_len(MultiBufferRow(s.end.row)),
14261 );
14262 Crease::simple(start..end, display_map.fold_placeholder.clone())
14263 } else {
14264 Crease::simple(s.start..s.end, display_map.fold_placeholder.clone())
14265 }
14266 })
14267 .collect::<Vec<_>>();
14268 self.fold_creases(ranges, true, window, cx);
14269 }
14270
14271 pub fn fold_ranges<T: ToOffset + Clone>(
14272 &mut self,
14273 ranges: Vec<Range<T>>,
14274 auto_scroll: bool,
14275 window: &mut Window,
14276 cx: &mut Context<Self>,
14277 ) {
14278 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14279 let ranges = ranges
14280 .into_iter()
14281 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
14282 .collect::<Vec<_>>();
14283 self.fold_creases(ranges, auto_scroll, window, cx);
14284 }
14285
14286 pub fn fold_creases<T: ToOffset + Clone>(
14287 &mut self,
14288 creases: Vec<Crease<T>>,
14289 auto_scroll: bool,
14290 window: &mut Window,
14291 cx: &mut Context<Self>,
14292 ) {
14293 if creases.is_empty() {
14294 return;
14295 }
14296
14297 let mut buffers_affected = HashSet::default();
14298 let multi_buffer = self.buffer().read(cx);
14299 for crease in &creases {
14300 if let Some((_, buffer, _)) =
14301 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
14302 {
14303 buffers_affected.insert(buffer.read(cx).remote_id());
14304 };
14305 }
14306
14307 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
14308
14309 if auto_scroll {
14310 self.request_autoscroll(Autoscroll::fit(), cx);
14311 }
14312
14313 cx.notify();
14314
14315 if let Some(active_diagnostics) = self.active_diagnostics.take() {
14316 // Clear diagnostics block when folding a range that contains it.
14317 let snapshot = self.snapshot(window, cx);
14318 if snapshot.intersects_fold(active_diagnostics.primary_range.start) {
14319 drop(snapshot);
14320 self.active_diagnostics = Some(active_diagnostics);
14321 self.dismiss_diagnostics(cx);
14322 } else {
14323 self.active_diagnostics = Some(active_diagnostics);
14324 }
14325 }
14326
14327 self.scrollbar_marker_state.dirty = true;
14328 }
14329
14330 /// Removes any folds whose ranges intersect any of the given ranges.
14331 pub fn unfold_ranges<T: ToOffset + Clone>(
14332 &mut self,
14333 ranges: &[Range<T>],
14334 inclusive: bool,
14335 auto_scroll: bool,
14336 cx: &mut Context<Self>,
14337 ) {
14338 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
14339 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
14340 });
14341 }
14342
14343 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
14344 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
14345 return;
14346 }
14347 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
14348 self.display_map.update(cx, |display_map, cx| {
14349 display_map.fold_buffers([buffer_id], cx)
14350 });
14351 cx.emit(EditorEvent::BufferFoldToggled {
14352 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
14353 folded: true,
14354 });
14355 cx.notify();
14356 }
14357
14358 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
14359 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
14360 return;
14361 }
14362 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
14363 self.display_map.update(cx, |display_map, cx| {
14364 display_map.unfold_buffers([buffer_id], cx);
14365 });
14366 cx.emit(EditorEvent::BufferFoldToggled {
14367 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
14368 folded: false,
14369 });
14370 cx.notify();
14371 }
14372
14373 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
14374 self.display_map.read(cx).is_buffer_folded(buffer)
14375 }
14376
14377 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
14378 self.display_map.read(cx).folded_buffers()
14379 }
14380
14381 /// Removes any folds with the given ranges.
14382 pub fn remove_folds_with_type<T: ToOffset + Clone>(
14383 &mut self,
14384 ranges: &[Range<T>],
14385 type_id: TypeId,
14386 auto_scroll: bool,
14387 cx: &mut Context<Self>,
14388 ) {
14389 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
14390 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
14391 });
14392 }
14393
14394 fn remove_folds_with<T: ToOffset + Clone>(
14395 &mut self,
14396 ranges: &[Range<T>],
14397 auto_scroll: bool,
14398 cx: &mut Context<Self>,
14399 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
14400 ) {
14401 if ranges.is_empty() {
14402 return;
14403 }
14404
14405 let mut buffers_affected = HashSet::default();
14406 let multi_buffer = self.buffer().read(cx);
14407 for range in ranges {
14408 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
14409 buffers_affected.insert(buffer.read(cx).remote_id());
14410 };
14411 }
14412
14413 self.display_map.update(cx, update);
14414
14415 if auto_scroll {
14416 self.request_autoscroll(Autoscroll::fit(), cx);
14417 }
14418
14419 cx.notify();
14420 self.scrollbar_marker_state.dirty = true;
14421 self.active_indent_guides_state.dirty = true;
14422 }
14423
14424 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
14425 self.display_map.read(cx).fold_placeholder.clone()
14426 }
14427
14428 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
14429 self.buffer.update(cx, |buffer, cx| {
14430 buffer.set_all_diff_hunks_expanded(cx);
14431 });
14432 }
14433
14434 pub fn expand_all_diff_hunks(
14435 &mut self,
14436 _: &ExpandAllDiffHunks,
14437 _window: &mut Window,
14438 cx: &mut Context<Self>,
14439 ) {
14440 self.buffer.update(cx, |buffer, cx| {
14441 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
14442 });
14443 }
14444
14445 pub fn toggle_selected_diff_hunks(
14446 &mut self,
14447 _: &ToggleSelectedDiffHunks,
14448 _window: &mut Window,
14449 cx: &mut Context<Self>,
14450 ) {
14451 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
14452 self.toggle_diff_hunks_in_ranges(ranges, cx);
14453 }
14454
14455 pub fn diff_hunks_in_ranges<'a>(
14456 &'a self,
14457 ranges: &'a [Range<Anchor>],
14458 buffer: &'a MultiBufferSnapshot,
14459 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
14460 ranges.iter().flat_map(move |range| {
14461 let end_excerpt_id = range.end.excerpt_id;
14462 let range = range.to_point(buffer);
14463 let mut peek_end = range.end;
14464 if range.end.row < buffer.max_row().0 {
14465 peek_end = Point::new(range.end.row + 1, 0);
14466 }
14467 buffer
14468 .diff_hunks_in_range(range.start..peek_end)
14469 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
14470 })
14471 }
14472
14473 pub fn has_stageable_diff_hunks_in_ranges(
14474 &self,
14475 ranges: &[Range<Anchor>],
14476 snapshot: &MultiBufferSnapshot,
14477 ) -> bool {
14478 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
14479 hunks.any(|hunk| hunk.status().has_secondary_hunk())
14480 }
14481
14482 pub fn toggle_staged_selected_diff_hunks(
14483 &mut self,
14484 _: &::git::ToggleStaged,
14485 _: &mut Window,
14486 cx: &mut Context<Self>,
14487 ) {
14488 let snapshot = self.buffer.read(cx).snapshot(cx);
14489 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
14490 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
14491 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
14492 }
14493
14494 pub fn set_render_diff_hunk_controls(
14495 &mut self,
14496 render_diff_hunk_controls: RenderDiffHunkControlsFn,
14497 cx: &mut Context<Self>,
14498 ) {
14499 self.render_diff_hunk_controls = render_diff_hunk_controls;
14500 cx.notify();
14501 }
14502
14503 pub fn stage_and_next(
14504 &mut self,
14505 _: &::git::StageAndNext,
14506 window: &mut Window,
14507 cx: &mut Context<Self>,
14508 ) {
14509 self.do_stage_or_unstage_and_next(true, window, cx);
14510 }
14511
14512 pub fn unstage_and_next(
14513 &mut self,
14514 _: &::git::UnstageAndNext,
14515 window: &mut Window,
14516 cx: &mut Context<Self>,
14517 ) {
14518 self.do_stage_or_unstage_and_next(false, window, cx);
14519 }
14520
14521 pub fn stage_or_unstage_diff_hunks(
14522 &mut self,
14523 stage: bool,
14524 ranges: Vec<Range<Anchor>>,
14525 cx: &mut Context<Self>,
14526 ) {
14527 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
14528 cx.spawn(async move |this, cx| {
14529 task.await?;
14530 this.update(cx, |this, cx| {
14531 let snapshot = this.buffer.read(cx).snapshot(cx);
14532 let chunk_by = this
14533 .diff_hunks_in_ranges(&ranges, &snapshot)
14534 .chunk_by(|hunk| hunk.buffer_id);
14535 for (buffer_id, hunks) in &chunk_by {
14536 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
14537 }
14538 })
14539 })
14540 .detach_and_log_err(cx);
14541 }
14542
14543 fn save_buffers_for_ranges_if_needed(
14544 &mut self,
14545 ranges: &[Range<Anchor>],
14546 cx: &mut Context<'_, Editor>,
14547 ) -> Task<Result<()>> {
14548 let multibuffer = self.buffer.read(cx);
14549 let snapshot = multibuffer.read(cx);
14550 let buffer_ids: HashSet<_> = ranges
14551 .iter()
14552 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
14553 .collect();
14554 drop(snapshot);
14555
14556 let mut buffers = HashSet::default();
14557 for buffer_id in buffer_ids {
14558 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
14559 let buffer = buffer_entity.read(cx);
14560 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
14561 {
14562 buffers.insert(buffer_entity);
14563 }
14564 }
14565 }
14566
14567 if let Some(project) = &self.project {
14568 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
14569 } else {
14570 Task::ready(Ok(()))
14571 }
14572 }
14573
14574 fn do_stage_or_unstage_and_next(
14575 &mut self,
14576 stage: bool,
14577 window: &mut Window,
14578 cx: &mut Context<Self>,
14579 ) {
14580 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
14581
14582 if ranges.iter().any(|range| range.start != range.end) {
14583 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
14584 return;
14585 }
14586
14587 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
14588 let snapshot = self.snapshot(window, cx);
14589 let position = self.selections.newest::<Point>(cx).head();
14590 let mut row = snapshot
14591 .buffer_snapshot
14592 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14593 .find(|hunk| hunk.row_range.start.0 > position.row)
14594 .map(|hunk| hunk.row_range.start);
14595
14596 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
14597 // Outside of the project diff editor, wrap around to the beginning.
14598 if !all_diff_hunks_expanded {
14599 row = row.or_else(|| {
14600 snapshot
14601 .buffer_snapshot
14602 .diff_hunks_in_range(Point::zero()..position)
14603 .find(|hunk| hunk.row_range.end.0 < position.row)
14604 .map(|hunk| hunk.row_range.start)
14605 });
14606 }
14607
14608 if let Some(row) = row {
14609 let destination = Point::new(row.0, 0);
14610 let autoscroll = Autoscroll::center();
14611
14612 self.unfold_ranges(&[destination..destination], false, false, cx);
14613 self.change_selections(Some(autoscroll), window, cx, |s| {
14614 s.select_ranges([destination..destination]);
14615 });
14616 }
14617 }
14618
14619 fn do_stage_or_unstage(
14620 &self,
14621 stage: bool,
14622 buffer_id: BufferId,
14623 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
14624 cx: &mut App,
14625 ) -> Option<()> {
14626 let project = self.project.as_ref()?;
14627 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
14628 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
14629 let buffer_snapshot = buffer.read(cx).snapshot();
14630 let file_exists = buffer_snapshot
14631 .file()
14632 .is_some_and(|file| file.disk_state().exists());
14633 diff.update(cx, |diff, cx| {
14634 diff.stage_or_unstage_hunks(
14635 stage,
14636 &hunks
14637 .map(|hunk| buffer_diff::DiffHunk {
14638 buffer_range: hunk.buffer_range,
14639 diff_base_byte_range: hunk.diff_base_byte_range,
14640 secondary_status: hunk.secondary_status,
14641 range: Point::zero()..Point::zero(), // unused
14642 })
14643 .collect::<Vec<_>>(),
14644 &buffer_snapshot,
14645 file_exists,
14646 cx,
14647 )
14648 });
14649 None
14650 }
14651
14652 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
14653 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
14654 self.buffer
14655 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
14656 }
14657
14658 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
14659 self.buffer.update(cx, |buffer, cx| {
14660 let ranges = vec![Anchor::min()..Anchor::max()];
14661 if !buffer.all_diff_hunks_expanded()
14662 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
14663 {
14664 buffer.collapse_diff_hunks(ranges, cx);
14665 true
14666 } else {
14667 false
14668 }
14669 })
14670 }
14671
14672 fn toggle_diff_hunks_in_ranges(
14673 &mut self,
14674 ranges: Vec<Range<Anchor>>,
14675 cx: &mut Context<'_, Editor>,
14676 ) {
14677 self.buffer.update(cx, |buffer, cx| {
14678 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
14679 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
14680 })
14681 }
14682
14683 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
14684 self.buffer.update(cx, |buffer, cx| {
14685 let snapshot = buffer.snapshot(cx);
14686 let excerpt_id = range.end.excerpt_id;
14687 let point_range = range.to_point(&snapshot);
14688 let expand = !buffer.single_hunk_is_expanded(range, cx);
14689 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
14690 })
14691 }
14692
14693 pub(crate) fn apply_all_diff_hunks(
14694 &mut self,
14695 _: &ApplyAllDiffHunks,
14696 window: &mut Window,
14697 cx: &mut Context<Self>,
14698 ) {
14699 let buffers = self.buffer.read(cx).all_buffers();
14700 for branch_buffer in buffers {
14701 branch_buffer.update(cx, |branch_buffer, cx| {
14702 branch_buffer.merge_into_base(Vec::new(), cx);
14703 });
14704 }
14705
14706 if let Some(project) = self.project.clone() {
14707 self.save(true, project, window, cx).detach_and_log_err(cx);
14708 }
14709 }
14710
14711 pub(crate) fn apply_selected_diff_hunks(
14712 &mut self,
14713 _: &ApplyDiffHunk,
14714 window: &mut Window,
14715 cx: &mut Context<Self>,
14716 ) {
14717 let snapshot = self.snapshot(window, cx);
14718 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
14719 let mut ranges_by_buffer = HashMap::default();
14720 self.transact(window, cx, |editor, _window, cx| {
14721 for hunk in hunks {
14722 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
14723 ranges_by_buffer
14724 .entry(buffer.clone())
14725 .or_insert_with(Vec::new)
14726 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
14727 }
14728 }
14729
14730 for (buffer, ranges) in ranges_by_buffer {
14731 buffer.update(cx, |buffer, cx| {
14732 buffer.merge_into_base(ranges, cx);
14733 });
14734 }
14735 });
14736
14737 if let Some(project) = self.project.clone() {
14738 self.save(true, project, window, cx).detach_and_log_err(cx);
14739 }
14740 }
14741
14742 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
14743 if hovered != self.gutter_hovered {
14744 self.gutter_hovered = hovered;
14745 cx.notify();
14746 }
14747 }
14748
14749 pub fn insert_blocks(
14750 &mut self,
14751 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
14752 autoscroll: Option<Autoscroll>,
14753 cx: &mut Context<Self>,
14754 ) -> Vec<CustomBlockId> {
14755 let blocks = self
14756 .display_map
14757 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
14758 if let Some(autoscroll) = autoscroll {
14759 self.request_autoscroll(autoscroll, cx);
14760 }
14761 cx.notify();
14762 blocks
14763 }
14764
14765 pub fn resize_blocks(
14766 &mut self,
14767 heights: HashMap<CustomBlockId, u32>,
14768 autoscroll: Option<Autoscroll>,
14769 cx: &mut Context<Self>,
14770 ) {
14771 self.display_map
14772 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
14773 if let Some(autoscroll) = autoscroll {
14774 self.request_autoscroll(autoscroll, cx);
14775 }
14776 cx.notify();
14777 }
14778
14779 pub fn replace_blocks(
14780 &mut self,
14781 renderers: HashMap<CustomBlockId, RenderBlock>,
14782 autoscroll: Option<Autoscroll>,
14783 cx: &mut Context<Self>,
14784 ) {
14785 self.display_map
14786 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
14787 if let Some(autoscroll) = autoscroll {
14788 self.request_autoscroll(autoscroll, cx);
14789 }
14790 cx.notify();
14791 }
14792
14793 pub fn remove_blocks(
14794 &mut self,
14795 block_ids: HashSet<CustomBlockId>,
14796 autoscroll: Option<Autoscroll>,
14797 cx: &mut Context<Self>,
14798 ) {
14799 self.display_map.update(cx, |display_map, cx| {
14800 display_map.remove_blocks(block_ids, cx)
14801 });
14802 if let Some(autoscroll) = autoscroll {
14803 self.request_autoscroll(autoscroll, cx);
14804 }
14805 cx.notify();
14806 }
14807
14808 pub fn row_for_block(
14809 &self,
14810 block_id: CustomBlockId,
14811 cx: &mut Context<Self>,
14812 ) -> Option<DisplayRow> {
14813 self.display_map
14814 .update(cx, |map, cx| map.row_for_block(block_id, cx))
14815 }
14816
14817 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
14818 self.focused_block = Some(focused_block);
14819 }
14820
14821 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
14822 self.focused_block.take()
14823 }
14824
14825 pub fn insert_creases(
14826 &mut self,
14827 creases: impl IntoIterator<Item = Crease<Anchor>>,
14828 cx: &mut Context<Self>,
14829 ) -> Vec<CreaseId> {
14830 self.display_map
14831 .update(cx, |map, cx| map.insert_creases(creases, cx))
14832 }
14833
14834 pub fn remove_creases(
14835 &mut self,
14836 ids: impl IntoIterator<Item = CreaseId>,
14837 cx: &mut Context<Self>,
14838 ) {
14839 self.display_map
14840 .update(cx, |map, cx| map.remove_creases(ids, cx));
14841 }
14842
14843 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
14844 self.display_map
14845 .update(cx, |map, cx| map.snapshot(cx))
14846 .longest_row()
14847 }
14848
14849 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
14850 self.display_map
14851 .update(cx, |map, cx| map.snapshot(cx))
14852 .max_point()
14853 }
14854
14855 pub fn text(&self, cx: &App) -> String {
14856 self.buffer.read(cx).read(cx).text()
14857 }
14858
14859 pub fn is_empty(&self, cx: &App) -> bool {
14860 self.buffer.read(cx).read(cx).is_empty()
14861 }
14862
14863 pub fn text_option(&self, cx: &App) -> Option<String> {
14864 let text = self.text(cx);
14865 let text = text.trim();
14866
14867 if text.is_empty() {
14868 return None;
14869 }
14870
14871 Some(text.to_string())
14872 }
14873
14874 pub fn set_text(
14875 &mut self,
14876 text: impl Into<Arc<str>>,
14877 window: &mut Window,
14878 cx: &mut Context<Self>,
14879 ) {
14880 self.transact(window, cx, |this, _, cx| {
14881 this.buffer
14882 .read(cx)
14883 .as_singleton()
14884 .expect("you can only call set_text on editors for singleton buffers")
14885 .update(cx, |buffer, cx| buffer.set_text(text, cx));
14886 });
14887 }
14888
14889 pub fn display_text(&self, cx: &mut App) -> String {
14890 self.display_map
14891 .update(cx, |map, cx| map.snapshot(cx))
14892 .text()
14893 }
14894
14895 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
14896 let mut wrap_guides = smallvec::smallvec![];
14897
14898 if self.show_wrap_guides == Some(false) {
14899 return wrap_guides;
14900 }
14901
14902 let settings = self.buffer.read(cx).language_settings(cx);
14903 if settings.show_wrap_guides {
14904 match self.soft_wrap_mode(cx) {
14905 SoftWrap::Column(soft_wrap) => {
14906 wrap_guides.push((soft_wrap as usize, true));
14907 }
14908 SoftWrap::Bounded(soft_wrap) => {
14909 wrap_guides.push((soft_wrap as usize, true));
14910 }
14911 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
14912 }
14913 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
14914 }
14915
14916 wrap_guides
14917 }
14918
14919 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
14920 let settings = self.buffer.read(cx).language_settings(cx);
14921 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
14922 match mode {
14923 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
14924 SoftWrap::None
14925 }
14926 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
14927 language_settings::SoftWrap::PreferredLineLength => {
14928 SoftWrap::Column(settings.preferred_line_length)
14929 }
14930 language_settings::SoftWrap::Bounded => {
14931 SoftWrap::Bounded(settings.preferred_line_length)
14932 }
14933 }
14934 }
14935
14936 pub fn set_soft_wrap_mode(
14937 &mut self,
14938 mode: language_settings::SoftWrap,
14939
14940 cx: &mut Context<Self>,
14941 ) {
14942 self.soft_wrap_mode_override = Some(mode);
14943 cx.notify();
14944 }
14945
14946 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
14947 self.hard_wrap = hard_wrap;
14948 cx.notify();
14949 }
14950
14951 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
14952 self.text_style_refinement = Some(style);
14953 }
14954
14955 /// called by the Element so we know what style we were most recently rendered with.
14956 pub(crate) fn set_style(
14957 &mut self,
14958 style: EditorStyle,
14959 window: &mut Window,
14960 cx: &mut Context<Self>,
14961 ) {
14962 let rem_size = window.rem_size();
14963 self.display_map.update(cx, |map, cx| {
14964 map.set_font(
14965 style.text.font(),
14966 style.text.font_size.to_pixels(rem_size),
14967 cx,
14968 )
14969 });
14970 self.style = Some(style);
14971 }
14972
14973 pub fn style(&self) -> Option<&EditorStyle> {
14974 self.style.as_ref()
14975 }
14976
14977 // Called by the element. This method is not designed to be called outside of the editor
14978 // element's layout code because it does not notify when rewrapping is computed synchronously.
14979 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
14980 self.display_map
14981 .update(cx, |map, cx| map.set_wrap_width(width, cx))
14982 }
14983
14984 pub fn set_soft_wrap(&mut self) {
14985 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
14986 }
14987
14988 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
14989 if self.soft_wrap_mode_override.is_some() {
14990 self.soft_wrap_mode_override.take();
14991 } else {
14992 let soft_wrap = match self.soft_wrap_mode(cx) {
14993 SoftWrap::GitDiff => return,
14994 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
14995 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
14996 language_settings::SoftWrap::None
14997 }
14998 };
14999 self.soft_wrap_mode_override = Some(soft_wrap);
15000 }
15001 cx.notify();
15002 }
15003
15004 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
15005 let Some(workspace) = self.workspace() else {
15006 return;
15007 };
15008 let fs = workspace.read(cx).app_state().fs.clone();
15009 let current_show = TabBarSettings::get_global(cx).show;
15010 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
15011 setting.show = Some(!current_show);
15012 });
15013 }
15014
15015 pub fn toggle_indent_guides(
15016 &mut self,
15017 _: &ToggleIndentGuides,
15018 _: &mut Window,
15019 cx: &mut Context<Self>,
15020 ) {
15021 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
15022 self.buffer
15023 .read(cx)
15024 .language_settings(cx)
15025 .indent_guides
15026 .enabled
15027 });
15028 self.show_indent_guides = Some(!currently_enabled);
15029 cx.notify();
15030 }
15031
15032 fn should_show_indent_guides(&self) -> Option<bool> {
15033 self.show_indent_guides
15034 }
15035
15036 pub fn toggle_line_numbers(
15037 &mut self,
15038 _: &ToggleLineNumbers,
15039 _: &mut Window,
15040 cx: &mut Context<Self>,
15041 ) {
15042 let mut editor_settings = EditorSettings::get_global(cx).clone();
15043 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
15044 EditorSettings::override_global(editor_settings, cx);
15045 }
15046
15047 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
15048 if let Some(show_line_numbers) = self.show_line_numbers {
15049 return show_line_numbers;
15050 }
15051 EditorSettings::get_global(cx).gutter.line_numbers
15052 }
15053
15054 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
15055 self.use_relative_line_numbers
15056 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
15057 }
15058
15059 pub fn toggle_relative_line_numbers(
15060 &mut self,
15061 _: &ToggleRelativeLineNumbers,
15062 _: &mut Window,
15063 cx: &mut Context<Self>,
15064 ) {
15065 let is_relative = self.should_use_relative_line_numbers(cx);
15066 self.set_relative_line_number(Some(!is_relative), cx)
15067 }
15068
15069 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
15070 self.use_relative_line_numbers = is_relative;
15071 cx.notify();
15072 }
15073
15074 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
15075 self.show_gutter = show_gutter;
15076 cx.notify();
15077 }
15078
15079 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
15080 self.show_scrollbars = show_scrollbars;
15081 cx.notify();
15082 }
15083
15084 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
15085 self.show_line_numbers = Some(show_line_numbers);
15086 cx.notify();
15087 }
15088
15089 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
15090 self.show_git_diff_gutter = Some(show_git_diff_gutter);
15091 cx.notify();
15092 }
15093
15094 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
15095 self.show_code_actions = Some(show_code_actions);
15096 cx.notify();
15097 }
15098
15099 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
15100 self.show_runnables = Some(show_runnables);
15101 cx.notify();
15102 }
15103
15104 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
15105 self.show_breakpoints = Some(show_breakpoints);
15106 cx.notify();
15107 }
15108
15109 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
15110 if self.display_map.read(cx).masked != masked {
15111 self.display_map.update(cx, |map, _| map.masked = masked);
15112 }
15113 cx.notify()
15114 }
15115
15116 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
15117 self.show_wrap_guides = Some(show_wrap_guides);
15118 cx.notify();
15119 }
15120
15121 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
15122 self.show_indent_guides = Some(show_indent_guides);
15123 cx.notify();
15124 }
15125
15126 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
15127 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
15128 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
15129 if let Some(dir) = file.abs_path(cx).parent() {
15130 return Some(dir.to_owned());
15131 }
15132 }
15133
15134 if let Some(project_path) = buffer.read(cx).project_path(cx) {
15135 return Some(project_path.path.to_path_buf());
15136 }
15137 }
15138
15139 None
15140 }
15141
15142 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
15143 self.active_excerpt(cx)?
15144 .1
15145 .read(cx)
15146 .file()
15147 .and_then(|f| f.as_local())
15148 }
15149
15150 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15151 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15152 let buffer = buffer.read(cx);
15153 if let Some(project_path) = buffer.project_path(cx) {
15154 let project = self.project.as_ref()?.read(cx);
15155 project.absolute_path(&project_path, cx)
15156 } else {
15157 buffer
15158 .file()
15159 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
15160 }
15161 })
15162 }
15163
15164 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15165 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15166 let project_path = buffer.read(cx).project_path(cx)?;
15167 let project = self.project.as_ref()?.read(cx);
15168 let entry = project.entry_for_path(&project_path, cx)?;
15169 let path = entry.path.to_path_buf();
15170 Some(path)
15171 })
15172 }
15173
15174 pub fn reveal_in_finder(
15175 &mut self,
15176 _: &RevealInFileManager,
15177 _window: &mut Window,
15178 cx: &mut Context<Self>,
15179 ) {
15180 if let Some(target) = self.target_file(cx) {
15181 cx.reveal_path(&target.abs_path(cx));
15182 }
15183 }
15184
15185 pub fn copy_path(
15186 &mut self,
15187 _: &zed_actions::workspace::CopyPath,
15188 _window: &mut Window,
15189 cx: &mut Context<Self>,
15190 ) {
15191 if let Some(path) = self.target_file_abs_path(cx) {
15192 if let Some(path) = path.to_str() {
15193 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15194 }
15195 }
15196 }
15197
15198 pub fn copy_relative_path(
15199 &mut self,
15200 _: &zed_actions::workspace::CopyRelativePath,
15201 _window: &mut Window,
15202 cx: &mut Context<Self>,
15203 ) {
15204 if let Some(path) = self.target_file_path(cx) {
15205 if let Some(path) = path.to_str() {
15206 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15207 }
15208 }
15209 }
15210
15211 pub fn project_path(&self, cx: &mut Context<Self>) -> Option<ProjectPath> {
15212 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
15213 buffer.read_with(cx, |buffer, cx| buffer.project_path(cx))
15214 } else {
15215 None
15216 }
15217 }
15218
15219 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) {
15220 let _ = maybe!({
15221 let breakpoint_store = self.breakpoint_store.as_ref()?;
15222
15223 let Some((_, _, active_position)) =
15224 breakpoint_store.read(cx).active_position().cloned()
15225 else {
15226 self.clear_row_highlights::<DebugCurrentRowHighlight>();
15227 return None;
15228 };
15229
15230 let snapshot = self
15231 .project
15232 .as_ref()?
15233 .read(cx)
15234 .buffer_for_id(active_position.buffer_id?, cx)?
15235 .read(cx)
15236 .snapshot();
15237
15238 for (id, ExcerptRange { context, .. }) in self
15239 .buffer
15240 .read(cx)
15241 .excerpts_for_buffer(active_position.buffer_id?, cx)
15242 {
15243 if context.start.cmp(&active_position, &snapshot).is_ge()
15244 || context.end.cmp(&active_position, &snapshot).is_lt()
15245 {
15246 continue;
15247 }
15248 let snapshot = self.buffer.read(cx).snapshot(cx);
15249 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, active_position)?;
15250
15251 self.clear_row_highlights::<DebugCurrentRowHighlight>();
15252 self.go_to_line::<DebugCurrentRowHighlight>(
15253 multibuffer_anchor,
15254 Some(cx.theme().colors().editor_debugger_active_line_background),
15255 window,
15256 cx,
15257 );
15258
15259 cx.notify();
15260 }
15261
15262 Some(())
15263 });
15264 }
15265
15266 pub fn copy_file_name_without_extension(
15267 &mut self,
15268 _: &CopyFileNameWithoutExtension,
15269 _: &mut Window,
15270 cx: &mut Context<Self>,
15271 ) {
15272 if let Some(file) = self.target_file(cx) {
15273 if let Some(file_stem) = file.path().file_stem() {
15274 if let Some(name) = file_stem.to_str() {
15275 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
15276 }
15277 }
15278 }
15279 }
15280
15281 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
15282 if let Some(file) = self.target_file(cx) {
15283 if let Some(file_name) = file.path().file_name() {
15284 if let Some(name) = file_name.to_str() {
15285 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
15286 }
15287 }
15288 }
15289 }
15290
15291 pub fn toggle_git_blame(
15292 &mut self,
15293 _: &::git::Blame,
15294 window: &mut Window,
15295 cx: &mut Context<Self>,
15296 ) {
15297 self.show_git_blame_gutter = !self.show_git_blame_gutter;
15298
15299 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
15300 self.start_git_blame(true, window, cx);
15301 }
15302
15303 cx.notify();
15304 }
15305
15306 pub fn toggle_git_blame_inline(
15307 &mut self,
15308 _: &ToggleGitBlameInline,
15309 window: &mut Window,
15310 cx: &mut Context<Self>,
15311 ) {
15312 self.toggle_git_blame_inline_internal(true, window, cx);
15313 cx.notify();
15314 }
15315
15316 pub fn git_blame_inline_enabled(&self) -> bool {
15317 self.git_blame_inline_enabled
15318 }
15319
15320 pub fn toggle_selection_menu(
15321 &mut self,
15322 _: &ToggleSelectionMenu,
15323 _: &mut Window,
15324 cx: &mut Context<Self>,
15325 ) {
15326 self.show_selection_menu = self
15327 .show_selection_menu
15328 .map(|show_selections_menu| !show_selections_menu)
15329 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
15330
15331 cx.notify();
15332 }
15333
15334 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
15335 self.show_selection_menu
15336 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
15337 }
15338
15339 fn start_git_blame(
15340 &mut self,
15341 user_triggered: bool,
15342 window: &mut Window,
15343 cx: &mut Context<Self>,
15344 ) {
15345 if let Some(project) = self.project.as_ref() {
15346 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
15347 return;
15348 };
15349
15350 if buffer.read(cx).file().is_none() {
15351 return;
15352 }
15353
15354 let focused = self.focus_handle(cx).contains_focused(window, cx);
15355
15356 let project = project.clone();
15357 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
15358 self.blame_subscription =
15359 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
15360 self.blame = Some(blame);
15361 }
15362 }
15363
15364 fn toggle_git_blame_inline_internal(
15365 &mut self,
15366 user_triggered: bool,
15367 window: &mut Window,
15368 cx: &mut Context<Self>,
15369 ) {
15370 if self.git_blame_inline_enabled {
15371 self.git_blame_inline_enabled = false;
15372 self.show_git_blame_inline = false;
15373 self.show_git_blame_inline_delay_task.take();
15374 } else {
15375 self.git_blame_inline_enabled = true;
15376 self.start_git_blame_inline(user_triggered, window, cx);
15377 }
15378
15379 cx.notify();
15380 }
15381
15382 fn start_git_blame_inline(
15383 &mut self,
15384 user_triggered: bool,
15385 window: &mut Window,
15386 cx: &mut Context<Self>,
15387 ) {
15388 self.start_git_blame(user_triggered, window, cx);
15389
15390 if ProjectSettings::get_global(cx)
15391 .git
15392 .inline_blame_delay()
15393 .is_some()
15394 {
15395 self.start_inline_blame_timer(window, cx);
15396 } else {
15397 self.show_git_blame_inline = true
15398 }
15399 }
15400
15401 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
15402 self.blame.as_ref()
15403 }
15404
15405 pub fn show_git_blame_gutter(&self) -> bool {
15406 self.show_git_blame_gutter
15407 }
15408
15409 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
15410 self.show_git_blame_gutter && self.has_blame_entries(cx)
15411 }
15412
15413 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
15414 self.show_git_blame_inline
15415 && (self.focus_handle.is_focused(window)
15416 || self
15417 .git_blame_inline_tooltip
15418 .as_ref()
15419 .and_then(|t| t.upgrade())
15420 .is_some())
15421 && !self.newest_selection_head_on_empty_line(cx)
15422 && self.has_blame_entries(cx)
15423 }
15424
15425 fn has_blame_entries(&self, cx: &App) -> bool {
15426 self.blame()
15427 .map_or(false, |blame| blame.read(cx).has_generated_entries())
15428 }
15429
15430 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
15431 let cursor_anchor = self.selections.newest_anchor().head();
15432
15433 let snapshot = self.buffer.read(cx).snapshot(cx);
15434 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
15435
15436 snapshot.line_len(buffer_row) == 0
15437 }
15438
15439 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
15440 let buffer_and_selection = maybe!({
15441 let selection = self.selections.newest::<Point>(cx);
15442 let selection_range = selection.range();
15443
15444 let multi_buffer = self.buffer().read(cx);
15445 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15446 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
15447
15448 let (buffer, range, _) = if selection.reversed {
15449 buffer_ranges.first()
15450 } else {
15451 buffer_ranges.last()
15452 }?;
15453
15454 let selection = text::ToPoint::to_point(&range.start, &buffer).row
15455 ..text::ToPoint::to_point(&range.end, &buffer).row;
15456 Some((
15457 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
15458 selection,
15459 ))
15460 });
15461
15462 let Some((buffer, selection)) = buffer_and_selection else {
15463 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
15464 };
15465
15466 let Some(project) = self.project.as_ref() else {
15467 return Task::ready(Err(anyhow!("editor does not have project")));
15468 };
15469
15470 project.update(cx, |project, cx| {
15471 project.get_permalink_to_line(&buffer, selection, cx)
15472 })
15473 }
15474
15475 pub fn copy_permalink_to_line(
15476 &mut self,
15477 _: &CopyPermalinkToLine,
15478 window: &mut Window,
15479 cx: &mut Context<Self>,
15480 ) {
15481 let permalink_task = self.get_permalink_to_line(cx);
15482 let workspace = self.workspace();
15483
15484 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
15485 Ok(permalink) => {
15486 cx.update(|_, cx| {
15487 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
15488 })
15489 .ok();
15490 }
15491 Err(err) => {
15492 let message = format!("Failed to copy permalink: {err}");
15493
15494 Err::<(), anyhow::Error>(err).log_err();
15495
15496 if let Some(workspace) = workspace {
15497 workspace
15498 .update_in(cx, |workspace, _, cx| {
15499 struct CopyPermalinkToLine;
15500
15501 workspace.show_toast(
15502 Toast::new(
15503 NotificationId::unique::<CopyPermalinkToLine>(),
15504 message,
15505 ),
15506 cx,
15507 )
15508 })
15509 .ok();
15510 }
15511 }
15512 })
15513 .detach();
15514 }
15515
15516 pub fn copy_file_location(
15517 &mut self,
15518 _: &CopyFileLocation,
15519 _: &mut Window,
15520 cx: &mut Context<Self>,
15521 ) {
15522 let selection = self.selections.newest::<Point>(cx).start.row + 1;
15523 if let Some(file) = self.target_file(cx) {
15524 if let Some(path) = file.path().to_str() {
15525 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
15526 }
15527 }
15528 }
15529
15530 pub fn open_permalink_to_line(
15531 &mut self,
15532 _: &OpenPermalinkToLine,
15533 window: &mut Window,
15534 cx: &mut Context<Self>,
15535 ) {
15536 let permalink_task = self.get_permalink_to_line(cx);
15537 let workspace = self.workspace();
15538
15539 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
15540 Ok(permalink) => {
15541 cx.update(|_, cx| {
15542 cx.open_url(permalink.as_ref());
15543 })
15544 .ok();
15545 }
15546 Err(err) => {
15547 let message = format!("Failed to open permalink: {err}");
15548
15549 Err::<(), anyhow::Error>(err).log_err();
15550
15551 if let Some(workspace) = workspace {
15552 workspace
15553 .update(cx, |workspace, cx| {
15554 struct OpenPermalinkToLine;
15555
15556 workspace.show_toast(
15557 Toast::new(
15558 NotificationId::unique::<OpenPermalinkToLine>(),
15559 message,
15560 ),
15561 cx,
15562 )
15563 })
15564 .ok();
15565 }
15566 }
15567 })
15568 .detach();
15569 }
15570
15571 pub fn insert_uuid_v4(
15572 &mut self,
15573 _: &InsertUuidV4,
15574 window: &mut Window,
15575 cx: &mut Context<Self>,
15576 ) {
15577 self.insert_uuid(UuidVersion::V4, window, cx);
15578 }
15579
15580 pub fn insert_uuid_v7(
15581 &mut self,
15582 _: &InsertUuidV7,
15583 window: &mut Window,
15584 cx: &mut Context<Self>,
15585 ) {
15586 self.insert_uuid(UuidVersion::V7, window, cx);
15587 }
15588
15589 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
15590 self.transact(window, cx, |this, window, cx| {
15591 let edits = this
15592 .selections
15593 .all::<Point>(cx)
15594 .into_iter()
15595 .map(|selection| {
15596 let uuid = match version {
15597 UuidVersion::V4 => uuid::Uuid::new_v4(),
15598 UuidVersion::V7 => uuid::Uuid::now_v7(),
15599 };
15600
15601 (selection.range(), uuid.to_string())
15602 });
15603 this.edit(edits, cx);
15604 this.refresh_inline_completion(true, false, window, cx);
15605 });
15606 }
15607
15608 pub fn open_selections_in_multibuffer(
15609 &mut self,
15610 _: &OpenSelectionsInMultibuffer,
15611 window: &mut Window,
15612 cx: &mut Context<Self>,
15613 ) {
15614 let multibuffer = self.buffer.read(cx);
15615
15616 let Some(buffer) = multibuffer.as_singleton() else {
15617 return;
15618 };
15619
15620 let Some(workspace) = self.workspace() else {
15621 return;
15622 };
15623
15624 let locations = self
15625 .selections
15626 .disjoint_anchors()
15627 .iter()
15628 .map(|range| Location {
15629 buffer: buffer.clone(),
15630 range: range.start.text_anchor..range.end.text_anchor,
15631 })
15632 .collect::<Vec<_>>();
15633
15634 let title = multibuffer.title(cx).to_string();
15635
15636 cx.spawn_in(window, async move |_, cx| {
15637 workspace.update_in(cx, |workspace, window, cx| {
15638 Self::open_locations_in_multibuffer(
15639 workspace,
15640 locations,
15641 format!("Selections for '{title}'"),
15642 false,
15643 MultibufferSelectionMode::All,
15644 window,
15645 cx,
15646 );
15647 })
15648 })
15649 .detach();
15650 }
15651
15652 /// Adds a row highlight for the given range. If a row has multiple highlights, the
15653 /// last highlight added will be used.
15654 ///
15655 /// If the range ends at the beginning of a line, then that line will not be highlighted.
15656 pub fn highlight_rows<T: 'static>(
15657 &mut self,
15658 range: Range<Anchor>,
15659 color: Hsla,
15660 should_autoscroll: bool,
15661 cx: &mut Context<Self>,
15662 ) {
15663 let snapshot = self.buffer().read(cx).snapshot(cx);
15664 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
15665 let ix = row_highlights.binary_search_by(|highlight| {
15666 Ordering::Equal
15667 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
15668 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
15669 });
15670
15671 if let Err(mut ix) = ix {
15672 let index = post_inc(&mut self.highlight_order);
15673
15674 // If this range intersects with the preceding highlight, then merge it with
15675 // the preceding highlight. Otherwise insert a new highlight.
15676 let mut merged = false;
15677 if ix > 0 {
15678 let prev_highlight = &mut row_highlights[ix - 1];
15679 if prev_highlight
15680 .range
15681 .end
15682 .cmp(&range.start, &snapshot)
15683 .is_ge()
15684 {
15685 ix -= 1;
15686 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
15687 prev_highlight.range.end = range.end;
15688 }
15689 merged = true;
15690 prev_highlight.index = index;
15691 prev_highlight.color = color;
15692 prev_highlight.should_autoscroll = should_autoscroll;
15693 }
15694 }
15695
15696 if !merged {
15697 row_highlights.insert(
15698 ix,
15699 RowHighlight {
15700 range: range.clone(),
15701 index,
15702 color,
15703 should_autoscroll,
15704 },
15705 );
15706 }
15707
15708 // If any of the following highlights intersect with this one, merge them.
15709 while let Some(next_highlight) = row_highlights.get(ix + 1) {
15710 let highlight = &row_highlights[ix];
15711 if next_highlight
15712 .range
15713 .start
15714 .cmp(&highlight.range.end, &snapshot)
15715 .is_le()
15716 {
15717 if next_highlight
15718 .range
15719 .end
15720 .cmp(&highlight.range.end, &snapshot)
15721 .is_gt()
15722 {
15723 row_highlights[ix].range.end = next_highlight.range.end;
15724 }
15725 row_highlights.remove(ix + 1);
15726 } else {
15727 break;
15728 }
15729 }
15730 }
15731 }
15732
15733 /// Remove any highlighted row ranges of the given type that intersect the
15734 /// given ranges.
15735 pub fn remove_highlighted_rows<T: 'static>(
15736 &mut self,
15737 ranges_to_remove: Vec<Range<Anchor>>,
15738 cx: &mut Context<Self>,
15739 ) {
15740 let snapshot = self.buffer().read(cx).snapshot(cx);
15741 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
15742 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
15743 row_highlights.retain(|highlight| {
15744 while let Some(range_to_remove) = ranges_to_remove.peek() {
15745 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
15746 Ordering::Less | Ordering::Equal => {
15747 ranges_to_remove.next();
15748 }
15749 Ordering::Greater => {
15750 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
15751 Ordering::Less | Ordering::Equal => {
15752 return false;
15753 }
15754 Ordering::Greater => break,
15755 }
15756 }
15757 }
15758 }
15759
15760 true
15761 })
15762 }
15763
15764 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
15765 pub fn clear_row_highlights<T: 'static>(&mut self) {
15766 self.highlighted_rows.remove(&TypeId::of::<T>());
15767 }
15768
15769 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
15770 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
15771 self.highlighted_rows
15772 .get(&TypeId::of::<T>())
15773 .map_or(&[] as &[_], |vec| vec.as_slice())
15774 .iter()
15775 .map(|highlight| (highlight.range.clone(), highlight.color))
15776 }
15777
15778 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
15779 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
15780 /// Allows to ignore certain kinds of highlights.
15781 pub fn highlighted_display_rows(
15782 &self,
15783 window: &mut Window,
15784 cx: &mut App,
15785 ) -> BTreeMap<DisplayRow, LineHighlight> {
15786 let snapshot = self.snapshot(window, cx);
15787 let mut used_highlight_orders = HashMap::default();
15788 self.highlighted_rows
15789 .iter()
15790 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
15791 .fold(
15792 BTreeMap::<DisplayRow, LineHighlight>::new(),
15793 |mut unique_rows, highlight| {
15794 let start = highlight.range.start.to_display_point(&snapshot);
15795 let end = highlight.range.end.to_display_point(&snapshot);
15796 let start_row = start.row().0;
15797 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
15798 && end.column() == 0
15799 {
15800 end.row().0.saturating_sub(1)
15801 } else {
15802 end.row().0
15803 };
15804 for row in start_row..=end_row {
15805 let used_index =
15806 used_highlight_orders.entry(row).or_insert(highlight.index);
15807 if highlight.index >= *used_index {
15808 *used_index = highlight.index;
15809 unique_rows.insert(DisplayRow(row), highlight.color.into());
15810 }
15811 }
15812 unique_rows
15813 },
15814 )
15815 }
15816
15817 pub fn highlighted_display_row_for_autoscroll(
15818 &self,
15819 snapshot: &DisplaySnapshot,
15820 ) -> Option<DisplayRow> {
15821 self.highlighted_rows
15822 .values()
15823 .flat_map(|highlighted_rows| highlighted_rows.iter())
15824 .filter_map(|highlight| {
15825 if highlight.should_autoscroll {
15826 Some(highlight.range.start.to_display_point(snapshot).row())
15827 } else {
15828 None
15829 }
15830 })
15831 .min()
15832 }
15833
15834 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
15835 self.highlight_background::<SearchWithinRange>(
15836 ranges,
15837 |colors| colors.editor_document_highlight_read_background,
15838 cx,
15839 )
15840 }
15841
15842 pub fn set_breadcrumb_header(&mut self, new_header: String) {
15843 self.breadcrumb_header = Some(new_header);
15844 }
15845
15846 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
15847 self.clear_background_highlights::<SearchWithinRange>(cx);
15848 }
15849
15850 pub fn highlight_background<T: 'static>(
15851 &mut self,
15852 ranges: &[Range<Anchor>],
15853 color_fetcher: fn(&ThemeColors) -> Hsla,
15854 cx: &mut Context<Self>,
15855 ) {
15856 self.background_highlights
15857 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
15858 self.scrollbar_marker_state.dirty = true;
15859 cx.notify();
15860 }
15861
15862 pub fn clear_background_highlights<T: 'static>(
15863 &mut self,
15864 cx: &mut Context<Self>,
15865 ) -> Option<BackgroundHighlight> {
15866 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
15867 if !text_highlights.1.is_empty() {
15868 self.scrollbar_marker_state.dirty = true;
15869 cx.notify();
15870 }
15871 Some(text_highlights)
15872 }
15873
15874 pub fn highlight_gutter<T: 'static>(
15875 &mut self,
15876 ranges: &[Range<Anchor>],
15877 color_fetcher: fn(&App) -> Hsla,
15878 cx: &mut Context<Self>,
15879 ) {
15880 self.gutter_highlights
15881 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
15882 cx.notify();
15883 }
15884
15885 pub fn clear_gutter_highlights<T: 'static>(
15886 &mut self,
15887 cx: &mut Context<Self>,
15888 ) -> Option<GutterHighlight> {
15889 cx.notify();
15890 self.gutter_highlights.remove(&TypeId::of::<T>())
15891 }
15892
15893 #[cfg(feature = "test-support")]
15894 pub fn all_text_background_highlights(
15895 &self,
15896 window: &mut Window,
15897 cx: &mut Context<Self>,
15898 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
15899 let snapshot = self.snapshot(window, cx);
15900 let buffer = &snapshot.buffer_snapshot;
15901 let start = buffer.anchor_before(0);
15902 let end = buffer.anchor_after(buffer.len());
15903 let theme = cx.theme().colors();
15904 self.background_highlights_in_range(start..end, &snapshot, theme)
15905 }
15906
15907 #[cfg(feature = "test-support")]
15908 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
15909 let snapshot = self.buffer().read(cx).snapshot(cx);
15910
15911 let highlights = self
15912 .background_highlights
15913 .get(&TypeId::of::<items::BufferSearchHighlights>());
15914
15915 if let Some((_color, ranges)) = highlights {
15916 ranges
15917 .iter()
15918 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
15919 .collect_vec()
15920 } else {
15921 vec![]
15922 }
15923 }
15924
15925 fn document_highlights_for_position<'a>(
15926 &'a self,
15927 position: Anchor,
15928 buffer: &'a MultiBufferSnapshot,
15929 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
15930 let read_highlights = self
15931 .background_highlights
15932 .get(&TypeId::of::<DocumentHighlightRead>())
15933 .map(|h| &h.1);
15934 let write_highlights = self
15935 .background_highlights
15936 .get(&TypeId::of::<DocumentHighlightWrite>())
15937 .map(|h| &h.1);
15938 let left_position = position.bias_left(buffer);
15939 let right_position = position.bias_right(buffer);
15940 read_highlights
15941 .into_iter()
15942 .chain(write_highlights)
15943 .flat_map(move |ranges| {
15944 let start_ix = match ranges.binary_search_by(|probe| {
15945 let cmp = probe.end.cmp(&left_position, buffer);
15946 if cmp.is_ge() {
15947 Ordering::Greater
15948 } else {
15949 Ordering::Less
15950 }
15951 }) {
15952 Ok(i) | Err(i) => i,
15953 };
15954
15955 ranges[start_ix..]
15956 .iter()
15957 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
15958 })
15959 }
15960
15961 pub fn has_background_highlights<T: 'static>(&self) -> bool {
15962 self.background_highlights
15963 .get(&TypeId::of::<T>())
15964 .map_or(false, |(_, highlights)| !highlights.is_empty())
15965 }
15966
15967 pub fn background_highlights_in_range(
15968 &self,
15969 search_range: Range<Anchor>,
15970 display_snapshot: &DisplaySnapshot,
15971 theme: &ThemeColors,
15972 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
15973 let mut results = Vec::new();
15974 for (color_fetcher, ranges) in self.background_highlights.values() {
15975 let color = color_fetcher(theme);
15976 let start_ix = match ranges.binary_search_by(|probe| {
15977 let cmp = probe
15978 .end
15979 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
15980 if cmp.is_gt() {
15981 Ordering::Greater
15982 } else {
15983 Ordering::Less
15984 }
15985 }) {
15986 Ok(i) | Err(i) => i,
15987 };
15988 for range in &ranges[start_ix..] {
15989 if range
15990 .start
15991 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
15992 .is_ge()
15993 {
15994 break;
15995 }
15996
15997 let start = range.start.to_display_point(display_snapshot);
15998 let end = range.end.to_display_point(display_snapshot);
15999 results.push((start..end, color))
16000 }
16001 }
16002 results
16003 }
16004
16005 pub fn background_highlight_row_ranges<T: 'static>(
16006 &self,
16007 search_range: Range<Anchor>,
16008 display_snapshot: &DisplaySnapshot,
16009 count: usize,
16010 ) -> Vec<RangeInclusive<DisplayPoint>> {
16011 let mut results = Vec::new();
16012 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
16013 return vec![];
16014 };
16015
16016 let start_ix = match ranges.binary_search_by(|probe| {
16017 let cmp = probe
16018 .end
16019 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16020 if cmp.is_gt() {
16021 Ordering::Greater
16022 } else {
16023 Ordering::Less
16024 }
16025 }) {
16026 Ok(i) | Err(i) => i,
16027 };
16028 let mut push_region = |start: Option<Point>, end: Option<Point>| {
16029 if let (Some(start_display), Some(end_display)) = (start, end) {
16030 results.push(
16031 start_display.to_display_point(display_snapshot)
16032 ..=end_display.to_display_point(display_snapshot),
16033 );
16034 }
16035 };
16036 let mut start_row: Option<Point> = None;
16037 let mut end_row: Option<Point> = None;
16038 if ranges.len() > count {
16039 return Vec::new();
16040 }
16041 for range in &ranges[start_ix..] {
16042 if range
16043 .start
16044 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16045 .is_ge()
16046 {
16047 break;
16048 }
16049 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
16050 if let Some(current_row) = &end_row {
16051 if end.row == current_row.row {
16052 continue;
16053 }
16054 }
16055 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
16056 if start_row.is_none() {
16057 assert_eq!(end_row, None);
16058 start_row = Some(start);
16059 end_row = Some(end);
16060 continue;
16061 }
16062 if let Some(current_end) = end_row.as_mut() {
16063 if start.row > current_end.row + 1 {
16064 push_region(start_row, end_row);
16065 start_row = Some(start);
16066 end_row = Some(end);
16067 } else {
16068 // Merge two hunks.
16069 *current_end = end;
16070 }
16071 } else {
16072 unreachable!();
16073 }
16074 }
16075 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
16076 push_region(start_row, end_row);
16077 results
16078 }
16079
16080 pub fn gutter_highlights_in_range(
16081 &self,
16082 search_range: Range<Anchor>,
16083 display_snapshot: &DisplaySnapshot,
16084 cx: &App,
16085 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16086 let mut results = Vec::new();
16087 for (color_fetcher, ranges) in self.gutter_highlights.values() {
16088 let color = color_fetcher(cx);
16089 let start_ix = match ranges.binary_search_by(|probe| {
16090 let cmp = probe
16091 .end
16092 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16093 if cmp.is_gt() {
16094 Ordering::Greater
16095 } else {
16096 Ordering::Less
16097 }
16098 }) {
16099 Ok(i) | Err(i) => i,
16100 };
16101 for range in &ranges[start_ix..] {
16102 if range
16103 .start
16104 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16105 .is_ge()
16106 {
16107 break;
16108 }
16109
16110 let start = range.start.to_display_point(display_snapshot);
16111 let end = range.end.to_display_point(display_snapshot);
16112 results.push((start..end, color))
16113 }
16114 }
16115 results
16116 }
16117
16118 /// Get the text ranges corresponding to the redaction query
16119 pub fn redacted_ranges(
16120 &self,
16121 search_range: Range<Anchor>,
16122 display_snapshot: &DisplaySnapshot,
16123 cx: &App,
16124 ) -> Vec<Range<DisplayPoint>> {
16125 display_snapshot
16126 .buffer_snapshot
16127 .redacted_ranges(search_range, |file| {
16128 if let Some(file) = file {
16129 file.is_private()
16130 && EditorSettings::get(
16131 Some(SettingsLocation {
16132 worktree_id: file.worktree_id(cx),
16133 path: file.path().as_ref(),
16134 }),
16135 cx,
16136 )
16137 .redact_private_values
16138 } else {
16139 false
16140 }
16141 })
16142 .map(|range| {
16143 range.start.to_display_point(display_snapshot)
16144 ..range.end.to_display_point(display_snapshot)
16145 })
16146 .collect()
16147 }
16148
16149 pub fn highlight_text<T: 'static>(
16150 &mut self,
16151 ranges: Vec<Range<Anchor>>,
16152 style: HighlightStyle,
16153 cx: &mut Context<Self>,
16154 ) {
16155 self.display_map.update(cx, |map, _| {
16156 map.highlight_text(TypeId::of::<T>(), ranges, style)
16157 });
16158 cx.notify();
16159 }
16160
16161 pub(crate) fn highlight_inlays<T: 'static>(
16162 &mut self,
16163 highlights: Vec<InlayHighlight>,
16164 style: HighlightStyle,
16165 cx: &mut Context<Self>,
16166 ) {
16167 self.display_map.update(cx, |map, _| {
16168 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
16169 });
16170 cx.notify();
16171 }
16172
16173 pub fn text_highlights<'a, T: 'static>(
16174 &'a self,
16175 cx: &'a App,
16176 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
16177 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
16178 }
16179
16180 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
16181 let cleared = self
16182 .display_map
16183 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
16184 if cleared {
16185 cx.notify();
16186 }
16187 }
16188
16189 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
16190 (self.read_only(cx) || self.blink_manager.read(cx).visible())
16191 && self.focus_handle.is_focused(window)
16192 }
16193
16194 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
16195 self.show_cursor_when_unfocused = is_enabled;
16196 cx.notify();
16197 }
16198
16199 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
16200 cx.notify();
16201 }
16202
16203 fn on_buffer_event(
16204 &mut self,
16205 multibuffer: &Entity<MultiBuffer>,
16206 event: &multi_buffer::Event,
16207 window: &mut Window,
16208 cx: &mut Context<Self>,
16209 ) {
16210 match event {
16211 multi_buffer::Event::Edited {
16212 singleton_buffer_edited,
16213 edited_buffer: buffer_edited,
16214 } => {
16215 self.scrollbar_marker_state.dirty = true;
16216 self.active_indent_guides_state.dirty = true;
16217 self.refresh_active_diagnostics(cx);
16218 self.refresh_code_actions(window, cx);
16219 if self.has_active_inline_completion() {
16220 self.update_visible_inline_completion(window, cx);
16221 }
16222 if let Some(buffer) = buffer_edited {
16223 let buffer_id = buffer.read(cx).remote_id();
16224 if !self.registered_buffers.contains_key(&buffer_id) {
16225 if let Some(project) = self.project.as_ref() {
16226 project.update(cx, |project, cx| {
16227 self.registered_buffers.insert(
16228 buffer_id,
16229 project.register_buffer_with_language_servers(&buffer, cx),
16230 );
16231 })
16232 }
16233 }
16234 }
16235 cx.emit(EditorEvent::BufferEdited);
16236 cx.emit(SearchEvent::MatchesInvalidated);
16237 if *singleton_buffer_edited {
16238 if let Some(project) = &self.project {
16239 #[allow(clippy::mutable_key_type)]
16240 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
16241 multibuffer
16242 .all_buffers()
16243 .into_iter()
16244 .filter_map(|buffer| {
16245 buffer.update(cx, |buffer, cx| {
16246 let language = buffer.language()?;
16247 let should_discard = project.update(cx, |project, cx| {
16248 project.is_local()
16249 && !project.has_language_servers_for(buffer, cx)
16250 });
16251 should_discard.not().then_some(language.clone())
16252 })
16253 })
16254 .collect::<HashSet<_>>()
16255 });
16256 if !languages_affected.is_empty() {
16257 self.refresh_inlay_hints(
16258 InlayHintRefreshReason::BufferEdited(languages_affected),
16259 cx,
16260 );
16261 }
16262 }
16263 }
16264
16265 let Some(project) = &self.project else { return };
16266 let (telemetry, is_via_ssh) = {
16267 let project = project.read(cx);
16268 let telemetry = project.client().telemetry().clone();
16269 let is_via_ssh = project.is_via_ssh();
16270 (telemetry, is_via_ssh)
16271 };
16272 refresh_linked_ranges(self, window, cx);
16273 telemetry.log_edit_event("editor", is_via_ssh);
16274 }
16275 multi_buffer::Event::ExcerptsAdded {
16276 buffer,
16277 predecessor,
16278 excerpts,
16279 } => {
16280 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16281 let buffer_id = buffer.read(cx).remote_id();
16282 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
16283 if let Some(project) = &self.project {
16284 get_uncommitted_diff_for_buffer(
16285 project,
16286 [buffer.clone()],
16287 self.buffer.clone(),
16288 cx,
16289 )
16290 .detach();
16291 }
16292 }
16293 cx.emit(EditorEvent::ExcerptsAdded {
16294 buffer: buffer.clone(),
16295 predecessor: *predecessor,
16296 excerpts: excerpts.clone(),
16297 });
16298 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
16299 }
16300 multi_buffer::Event::ExcerptsRemoved { ids } => {
16301 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
16302 let buffer = self.buffer.read(cx);
16303 self.registered_buffers
16304 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
16305 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
16306 cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
16307 }
16308 multi_buffer::Event::ExcerptsEdited {
16309 excerpt_ids,
16310 buffer_ids,
16311 } => {
16312 self.display_map.update(cx, |map, cx| {
16313 map.unfold_buffers(buffer_ids.iter().copied(), cx)
16314 });
16315 cx.emit(EditorEvent::ExcerptsEdited {
16316 ids: excerpt_ids.clone(),
16317 })
16318 }
16319 multi_buffer::Event::ExcerptsExpanded { ids } => {
16320 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
16321 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
16322 }
16323 multi_buffer::Event::Reparsed(buffer_id) => {
16324 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16325 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
16326
16327 cx.emit(EditorEvent::Reparsed(*buffer_id));
16328 }
16329 multi_buffer::Event::DiffHunksToggled => {
16330 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16331 }
16332 multi_buffer::Event::LanguageChanged(buffer_id) => {
16333 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
16334 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
16335 cx.emit(EditorEvent::Reparsed(*buffer_id));
16336 cx.notify();
16337 }
16338 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
16339 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
16340 multi_buffer::Event::FileHandleChanged
16341 | multi_buffer::Event::Reloaded
16342 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
16343 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
16344 multi_buffer::Event::DiagnosticsUpdated => {
16345 self.refresh_active_diagnostics(cx);
16346 self.refresh_inline_diagnostics(true, window, cx);
16347 self.scrollbar_marker_state.dirty = true;
16348 cx.notify();
16349 }
16350 _ => {}
16351 };
16352 }
16353
16354 fn on_display_map_changed(
16355 &mut self,
16356 _: Entity<DisplayMap>,
16357 _: &mut Window,
16358 cx: &mut Context<Self>,
16359 ) {
16360 cx.notify();
16361 }
16362
16363 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16364 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16365 self.update_edit_prediction_settings(cx);
16366 self.refresh_inline_completion(true, false, window, cx);
16367 self.refresh_inlay_hints(
16368 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
16369 self.selections.newest_anchor().head(),
16370 &self.buffer.read(cx).snapshot(cx),
16371 cx,
16372 )),
16373 cx,
16374 );
16375
16376 let old_cursor_shape = self.cursor_shape;
16377
16378 {
16379 let editor_settings = EditorSettings::get_global(cx);
16380 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
16381 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
16382 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
16383 }
16384
16385 if old_cursor_shape != self.cursor_shape {
16386 cx.emit(EditorEvent::CursorShapeChanged);
16387 }
16388
16389 let project_settings = ProjectSettings::get_global(cx);
16390 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
16391
16392 if self.mode == EditorMode::Full {
16393 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
16394 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
16395 if self.show_inline_diagnostics != show_inline_diagnostics {
16396 self.show_inline_diagnostics = show_inline_diagnostics;
16397 self.refresh_inline_diagnostics(false, window, cx);
16398 }
16399
16400 if self.git_blame_inline_enabled != inline_blame_enabled {
16401 self.toggle_git_blame_inline_internal(false, window, cx);
16402 }
16403 }
16404
16405 cx.notify();
16406 }
16407
16408 pub fn set_searchable(&mut self, searchable: bool) {
16409 self.searchable = searchable;
16410 }
16411
16412 pub fn searchable(&self) -> bool {
16413 self.searchable
16414 }
16415
16416 fn open_proposed_changes_editor(
16417 &mut self,
16418 _: &OpenProposedChangesEditor,
16419 window: &mut Window,
16420 cx: &mut Context<Self>,
16421 ) {
16422 let Some(workspace) = self.workspace() else {
16423 cx.propagate();
16424 return;
16425 };
16426
16427 let selections = self.selections.all::<usize>(cx);
16428 let multi_buffer = self.buffer.read(cx);
16429 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16430 let mut new_selections_by_buffer = HashMap::default();
16431 for selection in selections {
16432 for (buffer, range, _) in
16433 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
16434 {
16435 let mut range = range.to_point(buffer);
16436 range.start.column = 0;
16437 range.end.column = buffer.line_len(range.end.row);
16438 new_selections_by_buffer
16439 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
16440 .or_insert(Vec::new())
16441 .push(range)
16442 }
16443 }
16444
16445 let proposed_changes_buffers = new_selections_by_buffer
16446 .into_iter()
16447 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
16448 .collect::<Vec<_>>();
16449 let proposed_changes_editor = cx.new(|cx| {
16450 ProposedChangesEditor::new(
16451 "Proposed changes",
16452 proposed_changes_buffers,
16453 self.project.clone(),
16454 window,
16455 cx,
16456 )
16457 });
16458
16459 window.defer(cx, move |window, cx| {
16460 workspace.update(cx, |workspace, cx| {
16461 workspace.active_pane().update(cx, |pane, cx| {
16462 pane.add_item(
16463 Box::new(proposed_changes_editor),
16464 true,
16465 true,
16466 None,
16467 window,
16468 cx,
16469 );
16470 });
16471 });
16472 });
16473 }
16474
16475 pub fn open_excerpts_in_split(
16476 &mut self,
16477 _: &OpenExcerptsSplit,
16478 window: &mut Window,
16479 cx: &mut Context<Self>,
16480 ) {
16481 self.open_excerpts_common(None, true, window, cx)
16482 }
16483
16484 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
16485 self.open_excerpts_common(None, false, window, cx)
16486 }
16487
16488 fn open_excerpts_common(
16489 &mut self,
16490 jump_data: Option<JumpData>,
16491 split: bool,
16492 window: &mut Window,
16493 cx: &mut Context<Self>,
16494 ) {
16495 let Some(workspace) = self.workspace() else {
16496 cx.propagate();
16497 return;
16498 };
16499
16500 if self.buffer.read(cx).is_singleton() {
16501 cx.propagate();
16502 return;
16503 }
16504
16505 let mut new_selections_by_buffer = HashMap::default();
16506 match &jump_data {
16507 Some(JumpData::MultiBufferPoint {
16508 excerpt_id,
16509 position,
16510 anchor,
16511 line_offset_from_top,
16512 }) => {
16513 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16514 if let Some(buffer) = multi_buffer_snapshot
16515 .buffer_id_for_excerpt(*excerpt_id)
16516 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
16517 {
16518 let buffer_snapshot = buffer.read(cx).snapshot();
16519 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
16520 language::ToPoint::to_point(anchor, &buffer_snapshot)
16521 } else {
16522 buffer_snapshot.clip_point(*position, Bias::Left)
16523 };
16524 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
16525 new_selections_by_buffer.insert(
16526 buffer,
16527 (
16528 vec![jump_to_offset..jump_to_offset],
16529 Some(*line_offset_from_top),
16530 ),
16531 );
16532 }
16533 }
16534 Some(JumpData::MultiBufferRow {
16535 row,
16536 line_offset_from_top,
16537 }) => {
16538 let point = MultiBufferPoint::new(row.0, 0);
16539 if let Some((buffer, buffer_point, _)) =
16540 self.buffer.read(cx).point_to_buffer_point(point, cx)
16541 {
16542 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
16543 new_selections_by_buffer
16544 .entry(buffer)
16545 .or_insert((Vec::new(), Some(*line_offset_from_top)))
16546 .0
16547 .push(buffer_offset..buffer_offset)
16548 }
16549 }
16550 None => {
16551 let selections = self.selections.all::<usize>(cx);
16552 let multi_buffer = self.buffer.read(cx);
16553 for selection in selections {
16554 for (snapshot, range, _, anchor) in multi_buffer
16555 .snapshot(cx)
16556 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
16557 {
16558 if let Some(anchor) = anchor {
16559 // selection is in a deleted hunk
16560 let Some(buffer_id) = anchor.buffer_id else {
16561 continue;
16562 };
16563 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
16564 continue;
16565 };
16566 let offset = text::ToOffset::to_offset(
16567 &anchor.text_anchor,
16568 &buffer_handle.read(cx).snapshot(),
16569 );
16570 let range = offset..offset;
16571 new_selections_by_buffer
16572 .entry(buffer_handle)
16573 .or_insert((Vec::new(), None))
16574 .0
16575 .push(range)
16576 } else {
16577 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
16578 else {
16579 continue;
16580 };
16581 new_selections_by_buffer
16582 .entry(buffer_handle)
16583 .or_insert((Vec::new(), None))
16584 .0
16585 .push(range)
16586 }
16587 }
16588 }
16589 }
16590 }
16591
16592 if new_selections_by_buffer.is_empty() {
16593 return;
16594 }
16595
16596 // We defer the pane interaction because we ourselves are a workspace item
16597 // and activating a new item causes the pane to call a method on us reentrantly,
16598 // which panics if we're on the stack.
16599 window.defer(cx, move |window, cx| {
16600 workspace.update(cx, |workspace, cx| {
16601 let pane = if split {
16602 workspace.adjacent_pane(window, cx)
16603 } else {
16604 workspace.active_pane().clone()
16605 };
16606
16607 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
16608 let editor = buffer
16609 .read(cx)
16610 .file()
16611 .is_none()
16612 .then(|| {
16613 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
16614 // so `workspace.open_project_item` will never find them, always opening a new editor.
16615 // Instead, we try to activate the existing editor in the pane first.
16616 let (editor, pane_item_index) =
16617 pane.read(cx).items().enumerate().find_map(|(i, item)| {
16618 let editor = item.downcast::<Editor>()?;
16619 let singleton_buffer =
16620 editor.read(cx).buffer().read(cx).as_singleton()?;
16621 if singleton_buffer == buffer {
16622 Some((editor, i))
16623 } else {
16624 None
16625 }
16626 })?;
16627 pane.update(cx, |pane, cx| {
16628 pane.activate_item(pane_item_index, true, true, window, cx)
16629 });
16630 Some(editor)
16631 })
16632 .flatten()
16633 .unwrap_or_else(|| {
16634 workspace.open_project_item::<Self>(
16635 pane.clone(),
16636 buffer,
16637 true,
16638 true,
16639 window,
16640 cx,
16641 )
16642 });
16643
16644 editor.update(cx, |editor, cx| {
16645 let autoscroll = match scroll_offset {
16646 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
16647 None => Autoscroll::newest(),
16648 };
16649 let nav_history = editor.nav_history.take();
16650 editor.change_selections(Some(autoscroll), window, cx, |s| {
16651 s.select_ranges(ranges);
16652 });
16653 editor.nav_history = nav_history;
16654 });
16655 }
16656 })
16657 });
16658 }
16659
16660 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
16661 let snapshot = self.buffer.read(cx).read(cx);
16662 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
16663 Some(
16664 ranges
16665 .iter()
16666 .map(move |range| {
16667 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
16668 })
16669 .collect(),
16670 )
16671 }
16672
16673 fn selection_replacement_ranges(
16674 &self,
16675 range: Range<OffsetUtf16>,
16676 cx: &mut App,
16677 ) -> Vec<Range<OffsetUtf16>> {
16678 let selections = self.selections.all::<OffsetUtf16>(cx);
16679 let newest_selection = selections
16680 .iter()
16681 .max_by_key(|selection| selection.id)
16682 .unwrap();
16683 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
16684 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
16685 let snapshot = self.buffer.read(cx).read(cx);
16686 selections
16687 .into_iter()
16688 .map(|mut selection| {
16689 selection.start.0 =
16690 (selection.start.0 as isize).saturating_add(start_delta) as usize;
16691 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
16692 snapshot.clip_offset_utf16(selection.start, Bias::Left)
16693 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
16694 })
16695 .collect()
16696 }
16697
16698 fn report_editor_event(
16699 &self,
16700 event_type: &'static str,
16701 file_extension: Option<String>,
16702 cx: &App,
16703 ) {
16704 if cfg!(any(test, feature = "test-support")) {
16705 return;
16706 }
16707
16708 let Some(project) = &self.project else { return };
16709
16710 // If None, we are in a file without an extension
16711 let file = self
16712 .buffer
16713 .read(cx)
16714 .as_singleton()
16715 .and_then(|b| b.read(cx).file());
16716 let file_extension = file_extension.or(file
16717 .as_ref()
16718 .and_then(|file| Path::new(file.file_name(cx)).extension())
16719 .and_then(|e| e.to_str())
16720 .map(|a| a.to_string()));
16721
16722 let vim_mode = cx
16723 .global::<SettingsStore>()
16724 .raw_user_settings()
16725 .get("vim_mode")
16726 == Some(&serde_json::Value::Bool(true));
16727
16728 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
16729 let copilot_enabled = edit_predictions_provider
16730 == language::language_settings::EditPredictionProvider::Copilot;
16731 let copilot_enabled_for_language = self
16732 .buffer
16733 .read(cx)
16734 .language_settings(cx)
16735 .show_edit_predictions;
16736
16737 let project = project.read(cx);
16738 telemetry::event!(
16739 event_type,
16740 file_extension,
16741 vim_mode,
16742 copilot_enabled,
16743 copilot_enabled_for_language,
16744 edit_predictions_provider,
16745 is_via_ssh = project.is_via_ssh(),
16746 );
16747 }
16748
16749 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
16750 /// with each line being an array of {text, highlight} objects.
16751 fn copy_highlight_json(
16752 &mut self,
16753 _: &CopyHighlightJson,
16754 window: &mut Window,
16755 cx: &mut Context<Self>,
16756 ) {
16757 #[derive(Serialize)]
16758 struct Chunk<'a> {
16759 text: String,
16760 highlight: Option<&'a str>,
16761 }
16762
16763 let snapshot = self.buffer.read(cx).snapshot(cx);
16764 let range = self
16765 .selected_text_range(false, window, cx)
16766 .and_then(|selection| {
16767 if selection.range.is_empty() {
16768 None
16769 } else {
16770 Some(selection.range)
16771 }
16772 })
16773 .unwrap_or_else(|| 0..snapshot.len());
16774
16775 let chunks = snapshot.chunks(range, true);
16776 let mut lines = Vec::new();
16777 let mut line: VecDeque<Chunk> = VecDeque::new();
16778
16779 let Some(style) = self.style.as_ref() else {
16780 return;
16781 };
16782
16783 for chunk in chunks {
16784 let highlight = chunk
16785 .syntax_highlight_id
16786 .and_then(|id| id.name(&style.syntax));
16787 let mut chunk_lines = chunk.text.split('\n').peekable();
16788 while let Some(text) = chunk_lines.next() {
16789 let mut merged_with_last_token = false;
16790 if let Some(last_token) = line.back_mut() {
16791 if last_token.highlight == highlight {
16792 last_token.text.push_str(text);
16793 merged_with_last_token = true;
16794 }
16795 }
16796
16797 if !merged_with_last_token {
16798 line.push_back(Chunk {
16799 text: text.into(),
16800 highlight,
16801 });
16802 }
16803
16804 if chunk_lines.peek().is_some() {
16805 if line.len() > 1 && line.front().unwrap().text.is_empty() {
16806 line.pop_front();
16807 }
16808 if line.len() > 1 && line.back().unwrap().text.is_empty() {
16809 line.pop_back();
16810 }
16811
16812 lines.push(mem::take(&mut line));
16813 }
16814 }
16815 }
16816
16817 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
16818 return;
16819 };
16820 cx.write_to_clipboard(ClipboardItem::new_string(lines));
16821 }
16822
16823 pub fn open_context_menu(
16824 &mut self,
16825 _: &OpenContextMenu,
16826 window: &mut Window,
16827 cx: &mut Context<Self>,
16828 ) {
16829 self.request_autoscroll(Autoscroll::newest(), cx);
16830 let position = self.selections.newest_display(cx).start;
16831 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
16832 }
16833
16834 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
16835 &self.inlay_hint_cache
16836 }
16837
16838 pub fn replay_insert_event(
16839 &mut self,
16840 text: &str,
16841 relative_utf16_range: Option<Range<isize>>,
16842 window: &mut Window,
16843 cx: &mut Context<Self>,
16844 ) {
16845 if !self.input_enabled {
16846 cx.emit(EditorEvent::InputIgnored { text: text.into() });
16847 return;
16848 }
16849 if let Some(relative_utf16_range) = relative_utf16_range {
16850 let selections = self.selections.all::<OffsetUtf16>(cx);
16851 self.change_selections(None, window, cx, |s| {
16852 let new_ranges = selections.into_iter().map(|range| {
16853 let start = OffsetUtf16(
16854 range
16855 .head()
16856 .0
16857 .saturating_add_signed(relative_utf16_range.start),
16858 );
16859 let end = OffsetUtf16(
16860 range
16861 .head()
16862 .0
16863 .saturating_add_signed(relative_utf16_range.end),
16864 );
16865 start..end
16866 });
16867 s.select_ranges(new_ranges);
16868 });
16869 }
16870
16871 self.handle_input(text, window, cx);
16872 }
16873
16874 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
16875 let Some(provider) = self.semantics_provider.as_ref() else {
16876 return false;
16877 };
16878
16879 let mut supports = false;
16880 self.buffer().update(cx, |this, cx| {
16881 this.for_each_buffer(|buffer| {
16882 supports |= provider.supports_inlay_hints(buffer, cx);
16883 });
16884 });
16885
16886 supports
16887 }
16888
16889 pub fn is_focused(&self, window: &Window) -> bool {
16890 self.focus_handle.is_focused(window)
16891 }
16892
16893 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16894 cx.emit(EditorEvent::Focused);
16895
16896 if let Some(descendant) = self
16897 .last_focused_descendant
16898 .take()
16899 .and_then(|descendant| descendant.upgrade())
16900 {
16901 window.focus(&descendant);
16902 } else {
16903 if let Some(blame) = self.blame.as_ref() {
16904 blame.update(cx, GitBlame::focus)
16905 }
16906
16907 self.blink_manager.update(cx, BlinkManager::enable);
16908 self.show_cursor_names(window, cx);
16909 self.buffer.update(cx, |buffer, cx| {
16910 buffer.finalize_last_transaction(cx);
16911 if self.leader_peer_id.is_none() {
16912 buffer.set_active_selections(
16913 &self.selections.disjoint_anchors(),
16914 self.selections.line_mode,
16915 self.cursor_shape,
16916 cx,
16917 );
16918 }
16919 });
16920 }
16921 }
16922
16923 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
16924 cx.emit(EditorEvent::FocusedIn)
16925 }
16926
16927 fn handle_focus_out(
16928 &mut self,
16929 event: FocusOutEvent,
16930 _window: &mut Window,
16931 cx: &mut Context<Self>,
16932 ) {
16933 if event.blurred != self.focus_handle {
16934 self.last_focused_descendant = Some(event.blurred);
16935 }
16936 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
16937 }
16938
16939 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16940 self.blink_manager.update(cx, BlinkManager::disable);
16941 self.buffer
16942 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
16943
16944 if let Some(blame) = self.blame.as_ref() {
16945 blame.update(cx, GitBlame::blur)
16946 }
16947 if !self.hover_state.focused(window, cx) {
16948 hide_hover(self, cx);
16949 }
16950 if !self
16951 .context_menu
16952 .borrow()
16953 .as_ref()
16954 .is_some_and(|context_menu| context_menu.focused(window, cx))
16955 {
16956 self.hide_context_menu(window, cx);
16957 }
16958 self.discard_inline_completion(false, cx);
16959 cx.emit(EditorEvent::Blurred);
16960 cx.notify();
16961 }
16962
16963 pub fn register_action<A: Action>(
16964 &mut self,
16965 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
16966 ) -> Subscription {
16967 let id = self.next_editor_action_id.post_inc();
16968 let listener = Arc::new(listener);
16969 self.editor_actions.borrow_mut().insert(
16970 id,
16971 Box::new(move |window, _| {
16972 let listener = listener.clone();
16973 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
16974 let action = action.downcast_ref().unwrap();
16975 if phase == DispatchPhase::Bubble {
16976 listener(action, window, cx)
16977 }
16978 })
16979 }),
16980 );
16981
16982 let editor_actions = self.editor_actions.clone();
16983 Subscription::new(move || {
16984 editor_actions.borrow_mut().remove(&id);
16985 })
16986 }
16987
16988 pub fn file_header_size(&self) -> u32 {
16989 FILE_HEADER_HEIGHT
16990 }
16991
16992 pub fn restore(
16993 &mut self,
16994 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
16995 window: &mut Window,
16996 cx: &mut Context<Self>,
16997 ) {
16998 let workspace = self.workspace();
16999 let project = self.project.as_ref();
17000 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
17001 let mut tasks = Vec::new();
17002 for (buffer_id, changes) in revert_changes {
17003 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
17004 buffer.update(cx, |buffer, cx| {
17005 buffer.edit(
17006 changes
17007 .into_iter()
17008 .map(|(range, text)| (range, text.to_string())),
17009 None,
17010 cx,
17011 );
17012 });
17013
17014 if let Some(project) =
17015 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
17016 {
17017 project.update(cx, |project, cx| {
17018 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
17019 })
17020 }
17021 }
17022 }
17023 tasks
17024 });
17025 cx.spawn_in(window, async move |_, cx| {
17026 for (buffer, task) in save_tasks {
17027 let result = task.await;
17028 if result.is_err() {
17029 let Some(path) = buffer
17030 .read_with(cx, |buffer, cx| buffer.project_path(cx))
17031 .ok()
17032 else {
17033 continue;
17034 };
17035 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
17036 let Some(task) = cx
17037 .update_window_entity(&workspace, |workspace, window, cx| {
17038 workspace
17039 .open_path_preview(path, None, false, false, false, window, cx)
17040 })
17041 .ok()
17042 else {
17043 continue;
17044 };
17045 task.await.log_err();
17046 }
17047 }
17048 }
17049 })
17050 .detach();
17051 self.change_selections(None, window, cx, |selections| selections.refresh());
17052 }
17053
17054 pub fn to_pixel_point(
17055 &self,
17056 source: multi_buffer::Anchor,
17057 editor_snapshot: &EditorSnapshot,
17058 window: &mut Window,
17059 ) -> Option<gpui::Point<Pixels>> {
17060 let source_point = source.to_display_point(editor_snapshot);
17061 self.display_to_pixel_point(source_point, editor_snapshot, window)
17062 }
17063
17064 pub fn display_to_pixel_point(
17065 &self,
17066 source: DisplayPoint,
17067 editor_snapshot: &EditorSnapshot,
17068 window: &mut Window,
17069 ) -> Option<gpui::Point<Pixels>> {
17070 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
17071 let text_layout_details = self.text_layout_details(window);
17072 let scroll_top = text_layout_details
17073 .scroll_anchor
17074 .scroll_position(editor_snapshot)
17075 .y;
17076
17077 if source.row().as_f32() < scroll_top.floor() {
17078 return None;
17079 }
17080 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
17081 let source_y = line_height * (source.row().as_f32() - scroll_top);
17082 Some(gpui::Point::new(source_x, source_y))
17083 }
17084
17085 pub fn has_visible_completions_menu(&self) -> bool {
17086 !self.edit_prediction_preview_is_active()
17087 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
17088 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
17089 })
17090 }
17091
17092 pub fn register_addon<T: Addon>(&mut self, instance: T) {
17093 self.addons
17094 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
17095 }
17096
17097 pub fn unregister_addon<T: Addon>(&mut self) {
17098 self.addons.remove(&std::any::TypeId::of::<T>());
17099 }
17100
17101 pub fn addon<T: Addon>(&self) -> Option<&T> {
17102 let type_id = std::any::TypeId::of::<T>();
17103 self.addons
17104 .get(&type_id)
17105 .and_then(|item| item.to_any().downcast_ref::<T>())
17106 }
17107
17108 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
17109 let text_layout_details = self.text_layout_details(window);
17110 let style = &text_layout_details.editor_style;
17111 let font_id = window.text_system().resolve_font(&style.text.font());
17112 let font_size = style.text.font_size.to_pixels(window.rem_size());
17113 let line_height = style.text.line_height_in_pixels(window.rem_size());
17114 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
17115
17116 gpui::Size::new(em_width, line_height)
17117 }
17118
17119 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
17120 self.load_diff_task.clone()
17121 }
17122
17123 fn read_selections_from_db(
17124 &mut self,
17125 item_id: u64,
17126 workspace_id: WorkspaceId,
17127 window: &mut Window,
17128 cx: &mut Context<Editor>,
17129 ) {
17130 if !self.is_singleton(cx)
17131 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
17132 {
17133 return;
17134 }
17135 let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() else {
17136 return;
17137 };
17138 if selections.is_empty() {
17139 return;
17140 }
17141
17142 let snapshot = self.buffer.read(cx).snapshot(cx);
17143 self.change_selections(None, window, cx, |s| {
17144 s.select_ranges(selections.into_iter().map(|(start, end)| {
17145 snapshot.clip_offset(start, Bias::Left)..snapshot.clip_offset(end, Bias::Right)
17146 }));
17147 });
17148 }
17149}
17150
17151fn insert_extra_newline_brackets(
17152 buffer: &MultiBufferSnapshot,
17153 range: Range<usize>,
17154 language: &language::LanguageScope,
17155) -> bool {
17156 let leading_whitespace_len = buffer
17157 .reversed_chars_at(range.start)
17158 .take_while(|c| c.is_whitespace() && *c != '\n')
17159 .map(|c| c.len_utf8())
17160 .sum::<usize>();
17161 let trailing_whitespace_len = buffer
17162 .chars_at(range.end)
17163 .take_while(|c| c.is_whitespace() && *c != '\n')
17164 .map(|c| c.len_utf8())
17165 .sum::<usize>();
17166 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
17167
17168 language.brackets().any(|(pair, enabled)| {
17169 let pair_start = pair.start.trim_end();
17170 let pair_end = pair.end.trim_start();
17171
17172 enabled
17173 && pair.newline
17174 && buffer.contains_str_at(range.end, pair_end)
17175 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
17176 })
17177}
17178
17179fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
17180 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
17181 [(buffer, range, _)] => (*buffer, range.clone()),
17182 _ => return false,
17183 };
17184 let pair = {
17185 let mut result: Option<BracketMatch> = None;
17186
17187 for pair in buffer
17188 .all_bracket_ranges(range.clone())
17189 .filter(move |pair| {
17190 pair.open_range.start <= range.start && pair.close_range.end >= range.end
17191 })
17192 {
17193 let len = pair.close_range.end - pair.open_range.start;
17194
17195 if let Some(existing) = &result {
17196 let existing_len = existing.close_range.end - existing.open_range.start;
17197 if len > existing_len {
17198 continue;
17199 }
17200 }
17201
17202 result = Some(pair);
17203 }
17204
17205 result
17206 };
17207 let Some(pair) = pair else {
17208 return false;
17209 };
17210 pair.newline_only
17211 && buffer
17212 .chars_for_range(pair.open_range.end..range.start)
17213 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
17214 .all(|c| c.is_whitespace() && c != '\n')
17215}
17216
17217fn get_uncommitted_diff_for_buffer(
17218 project: &Entity<Project>,
17219 buffers: impl IntoIterator<Item = Entity<Buffer>>,
17220 buffer: Entity<MultiBuffer>,
17221 cx: &mut App,
17222) -> Task<()> {
17223 let mut tasks = Vec::new();
17224 project.update(cx, |project, cx| {
17225 for buffer in buffers {
17226 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
17227 }
17228 });
17229 cx.spawn(async move |cx| {
17230 let diffs = future::join_all(tasks).await;
17231 buffer
17232 .update(cx, |buffer, cx| {
17233 for diff in diffs.into_iter().flatten() {
17234 buffer.add_diff(diff, cx);
17235 }
17236 })
17237 .ok();
17238 })
17239}
17240
17241fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
17242 let tab_size = tab_size.get() as usize;
17243 let mut width = offset;
17244
17245 for ch in text.chars() {
17246 width += if ch == '\t' {
17247 tab_size - (width % tab_size)
17248 } else {
17249 1
17250 };
17251 }
17252
17253 width - offset
17254}
17255
17256#[cfg(test)]
17257mod tests {
17258 use super::*;
17259
17260 #[test]
17261 fn test_string_size_with_expanded_tabs() {
17262 let nz = |val| NonZeroU32::new(val).unwrap();
17263 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
17264 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
17265 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
17266 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
17267 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
17268 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
17269 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
17270 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
17271 }
17272}
17273
17274/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
17275struct WordBreakingTokenizer<'a> {
17276 input: &'a str,
17277}
17278
17279impl<'a> WordBreakingTokenizer<'a> {
17280 fn new(input: &'a str) -> Self {
17281 Self { input }
17282 }
17283}
17284
17285fn is_char_ideographic(ch: char) -> bool {
17286 use unicode_script::Script::*;
17287 use unicode_script::UnicodeScript;
17288 matches!(ch.script(), Han | Tangut | Yi)
17289}
17290
17291fn is_grapheme_ideographic(text: &str) -> bool {
17292 text.chars().any(is_char_ideographic)
17293}
17294
17295fn is_grapheme_whitespace(text: &str) -> bool {
17296 text.chars().any(|x| x.is_whitespace())
17297}
17298
17299fn should_stay_with_preceding_ideograph(text: &str) -> bool {
17300 text.chars().next().map_or(false, |ch| {
17301 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
17302 })
17303}
17304
17305#[derive(PartialEq, Eq, Debug, Clone, Copy)]
17306enum WordBreakToken<'a> {
17307 Word { token: &'a str, grapheme_len: usize },
17308 InlineWhitespace { token: &'a str, grapheme_len: usize },
17309 Newline,
17310}
17311
17312impl<'a> Iterator for WordBreakingTokenizer<'a> {
17313 /// Yields a span, the count of graphemes in the token, and whether it was
17314 /// whitespace. Note that it also breaks at word boundaries.
17315 type Item = WordBreakToken<'a>;
17316
17317 fn next(&mut self) -> Option<Self::Item> {
17318 use unicode_segmentation::UnicodeSegmentation;
17319 if self.input.is_empty() {
17320 return None;
17321 }
17322
17323 let mut iter = self.input.graphemes(true).peekable();
17324 let mut offset = 0;
17325 let mut grapheme_len = 0;
17326 if let Some(first_grapheme) = iter.next() {
17327 let is_newline = first_grapheme == "\n";
17328 let is_whitespace = is_grapheme_whitespace(first_grapheme);
17329 offset += first_grapheme.len();
17330 grapheme_len += 1;
17331 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
17332 if let Some(grapheme) = iter.peek().copied() {
17333 if should_stay_with_preceding_ideograph(grapheme) {
17334 offset += grapheme.len();
17335 grapheme_len += 1;
17336 }
17337 }
17338 } else {
17339 let mut words = self.input[offset..].split_word_bound_indices().peekable();
17340 let mut next_word_bound = words.peek().copied();
17341 if next_word_bound.map_or(false, |(i, _)| i == 0) {
17342 next_word_bound = words.next();
17343 }
17344 while let Some(grapheme) = iter.peek().copied() {
17345 if next_word_bound.map_or(false, |(i, _)| i == offset) {
17346 break;
17347 };
17348 if is_grapheme_whitespace(grapheme) != is_whitespace
17349 || (grapheme == "\n") != is_newline
17350 {
17351 break;
17352 };
17353 offset += grapheme.len();
17354 grapheme_len += 1;
17355 iter.next();
17356 }
17357 }
17358 let token = &self.input[..offset];
17359 self.input = &self.input[offset..];
17360 if token == "\n" {
17361 Some(WordBreakToken::Newline)
17362 } else if is_whitespace {
17363 Some(WordBreakToken::InlineWhitespace {
17364 token,
17365 grapheme_len,
17366 })
17367 } else {
17368 Some(WordBreakToken::Word {
17369 token,
17370 grapheme_len,
17371 })
17372 }
17373 } else {
17374 None
17375 }
17376 }
17377}
17378
17379#[test]
17380fn test_word_breaking_tokenizer() {
17381 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
17382 ("", &[]),
17383 (" ", &[whitespace(" ", 2)]),
17384 ("Ʒ", &[word("Ʒ", 1)]),
17385 ("Ǽ", &[word("Ǽ", 1)]),
17386 ("⋑", &[word("⋑", 1)]),
17387 ("⋑⋑", &[word("⋑⋑", 2)]),
17388 (
17389 "原理,进而",
17390 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
17391 ),
17392 (
17393 "hello world",
17394 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
17395 ),
17396 (
17397 "hello, world",
17398 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
17399 ),
17400 (
17401 " hello world",
17402 &[
17403 whitespace(" ", 2),
17404 word("hello", 5),
17405 whitespace(" ", 1),
17406 word("world", 5),
17407 ],
17408 ),
17409 (
17410 "这是什么 \n 钢笔",
17411 &[
17412 word("这", 1),
17413 word("是", 1),
17414 word("什", 1),
17415 word("么", 1),
17416 whitespace(" ", 1),
17417 newline(),
17418 whitespace(" ", 1),
17419 word("钢", 1),
17420 word("笔", 1),
17421 ],
17422 ),
17423 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
17424 ];
17425
17426 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
17427 WordBreakToken::Word {
17428 token,
17429 grapheme_len,
17430 }
17431 }
17432
17433 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
17434 WordBreakToken::InlineWhitespace {
17435 token,
17436 grapheme_len,
17437 }
17438 }
17439
17440 fn newline() -> WordBreakToken<'static> {
17441 WordBreakToken::Newline
17442 }
17443
17444 for (input, result) in tests {
17445 assert_eq!(
17446 WordBreakingTokenizer::new(input)
17447 .collect::<Vec<_>>()
17448 .as_slice(),
17449 *result,
17450 );
17451 }
17452}
17453
17454fn wrap_with_prefix(
17455 line_prefix: String,
17456 unwrapped_text: String,
17457 wrap_column: usize,
17458 tab_size: NonZeroU32,
17459 preserve_existing_whitespace: bool,
17460) -> String {
17461 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
17462 let mut wrapped_text = String::new();
17463 let mut current_line = line_prefix.clone();
17464
17465 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
17466 let mut current_line_len = line_prefix_len;
17467 let mut in_whitespace = false;
17468 for token in tokenizer {
17469 let have_preceding_whitespace = in_whitespace;
17470 match token {
17471 WordBreakToken::Word {
17472 token,
17473 grapheme_len,
17474 } => {
17475 in_whitespace = false;
17476 if current_line_len + grapheme_len > wrap_column
17477 && current_line_len != line_prefix_len
17478 {
17479 wrapped_text.push_str(current_line.trim_end());
17480 wrapped_text.push('\n');
17481 current_line.truncate(line_prefix.len());
17482 current_line_len = line_prefix_len;
17483 }
17484 current_line.push_str(token);
17485 current_line_len += grapheme_len;
17486 }
17487 WordBreakToken::InlineWhitespace {
17488 mut token,
17489 mut grapheme_len,
17490 } => {
17491 in_whitespace = true;
17492 if have_preceding_whitespace && !preserve_existing_whitespace {
17493 continue;
17494 }
17495 if !preserve_existing_whitespace {
17496 token = " ";
17497 grapheme_len = 1;
17498 }
17499 if current_line_len + grapheme_len > wrap_column {
17500 wrapped_text.push_str(current_line.trim_end());
17501 wrapped_text.push('\n');
17502 current_line.truncate(line_prefix.len());
17503 current_line_len = line_prefix_len;
17504 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
17505 current_line.push_str(token);
17506 current_line_len += grapheme_len;
17507 }
17508 }
17509 WordBreakToken::Newline => {
17510 in_whitespace = true;
17511 if preserve_existing_whitespace {
17512 wrapped_text.push_str(current_line.trim_end());
17513 wrapped_text.push('\n');
17514 current_line.truncate(line_prefix.len());
17515 current_line_len = line_prefix_len;
17516 } else if have_preceding_whitespace {
17517 continue;
17518 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
17519 {
17520 wrapped_text.push_str(current_line.trim_end());
17521 wrapped_text.push('\n');
17522 current_line.truncate(line_prefix.len());
17523 current_line_len = line_prefix_len;
17524 } else if current_line_len != line_prefix_len {
17525 current_line.push(' ');
17526 current_line_len += 1;
17527 }
17528 }
17529 }
17530 }
17531
17532 if !current_line.is_empty() {
17533 wrapped_text.push_str(¤t_line);
17534 }
17535 wrapped_text
17536}
17537
17538#[test]
17539fn test_wrap_with_prefix() {
17540 assert_eq!(
17541 wrap_with_prefix(
17542 "# ".to_string(),
17543 "abcdefg".to_string(),
17544 4,
17545 NonZeroU32::new(4).unwrap(),
17546 false,
17547 ),
17548 "# abcdefg"
17549 );
17550 assert_eq!(
17551 wrap_with_prefix(
17552 "".to_string(),
17553 "\thello world".to_string(),
17554 8,
17555 NonZeroU32::new(4).unwrap(),
17556 false,
17557 ),
17558 "hello\nworld"
17559 );
17560 assert_eq!(
17561 wrap_with_prefix(
17562 "// ".to_string(),
17563 "xx \nyy zz aa bb cc".to_string(),
17564 12,
17565 NonZeroU32::new(4).unwrap(),
17566 false,
17567 ),
17568 "// xx yy zz\n// aa bb cc"
17569 );
17570 assert_eq!(
17571 wrap_with_prefix(
17572 String::new(),
17573 "这是什么 \n 钢笔".to_string(),
17574 3,
17575 NonZeroU32::new(4).unwrap(),
17576 false,
17577 ),
17578 "这是什\n么 钢\n笔"
17579 );
17580}
17581
17582pub trait CollaborationHub {
17583 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
17584 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
17585 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
17586}
17587
17588impl CollaborationHub for Entity<Project> {
17589 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
17590 self.read(cx).collaborators()
17591 }
17592
17593 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
17594 self.read(cx).user_store().read(cx).participant_indices()
17595 }
17596
17597 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
17598 let this = self.read(cx);
17599 let user_ids = this.collaborators().values().map(|c| c.user_id);
17600 this.user_store().read_with(cx, |user_store, cx| {
17601 user_store.participant_names(user_ids, cx)
17602 })
17603 }
17604}
17605
17606pub trait SemanticsProvider {
17607 fn hover(
17608 &self,
17609 buffer: &Entity<Buffer>,
17610 position: text::Anchor,
17611 cx: &mut App,
17612 ) -> Option<Task<Vec<project::Hover>>>;
17613
17614 fn inlay_hints(
17615 &self,
17616 buffer_handle: Entity<Buffer>,
17617 range: Range<text::Anchor>,
17618 cx: &mut App,
17619 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
17620
17621 fn resolve_inlay_hint(
17622 &self,
17623 hint: InlayHint,
17624 buffer_handle: Entity<Buffer>,
17625 server_id: LanguageServerId,
17626 cx: &mut App,
17627 ) -> Option<Task<anyhow::Result<InlayHint>>>;
17628
17629 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
17630
17631 fn document_highlights(
17632 &self,
17633 buffer: &Entity<Buffer>,
17634 position: text::Anchor,
17635 cx: &mut App,
17636 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
17637
17638 fn definitions(
17639 &self,
17640 buffer: &Entity<Buffer>,
17641 position: text::Anchor,
17642 kind: GotoDefinitionKind,
17643 cx: &mut App,
17644 ) -> Option<Task<Result<Vec<LocationLink>>>>;
17645
17646 fn range_for_rename(
17647 &self,
17648 buffer: &Entity<Buffer>,
17649 position: text::Anchor,
17650 cx: &mut App,
17651 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
17652
17653 fn perform_rename(
17654 &self,
17655 buffer: &Entity<Buffer>,
17656 position: text::Anchor,
17657 new_name: String,
17658 cx: &mut App,
17659 ) -> Option<Task<Result<ProjectTransaction>>>;
17660}
17661
17662pub trait CompletionProvider {
17663 fn completions(
17664 &self,
17665 buffer: &Entity<Buffer>,
17666 buffer_position: text::Anchor,
17667 trigger: CompletionContext,
17668 window: &mut Window,
17669 cx: &mut Context<Editor>,
17670 ) -> Task<Result<Option<Vec<Completion>>>>;
17671
17672 fn resolve_completions(
17673 &self,
17674 buffer: Entity<Buffer>,
17675 completion_indices: Vec<usize>,
17676 completions: Rc<RefCell<Box<[Completion]>>>,
17677 cx: &mut Context<Editor>,
17678 ) -> Task<Result<bool>>;
17679
17680 fn apply_additional_edits_for_completion(
17681 &self,
17682 _buffer: Entity<Buffer>,
17683 _completions: Rc<RefCell<Box<[Completion]>>>,
17684 _completion_index: usize,
17685 _push_to_history: bool,
17686 _cx: &mut Context<Editor>,
17687 ) -> Task<Result<Option<language::Transaction>>> {
17688 Task::ready(Ok(None))
17689 }
17690
17691 fn is_completion_trigger(
17692 &self,
17693 buffer: &Entity<Buffer>,
17694 position: language::Anchor,
17695 text: &str,
17696 trigger_in_words: bool,
17697 cx: &mut Context<Editor>,
17698 ) -> bool;
17699
17700 fn sort_completions(&self) -> bool {
17701 true
17702 }
17703}
17704
17705pub trait CodeActionProvider {
17706 fn id(&self) -> Arc<str>;
17707
17708 fn code_actions(
17709 &self,
17710 buffer: &Entity<Buffer>,
17711 range: Range<text::Anchor>,
17712 window: &mut Window,
17713 cx: &mut App,
17714 ) -> Task<Result<Vec<CodeAction>>>;
17715
17716 fn apply_code_action(
17717 &self,
17718 buffer_handle: Entity<Buffer>,
17719 action: CodeAction,
17720 excerpt_id: ExcerptId,
17721 push_to_history: bool,
17722 window: &mut Window,
17723 cx: &mut App,
17724 ) -> Task<Result<ProjectTransaction>>;
17725}
17726
17727impl CodeActionProvider for Entity<Project> {
17728 fn id(&self) -> Arc<str> {
17729 "project".into()
17730 }
17731
17732 fn code_actions(
17733 &self,
17734 buffer: &Entity<Buffer>,
17735 range: Range<text::Anchor>,
17736 _window: &mut Window,
17737 cx: &mut App,
17738 ) -> Task<Result<Vec<CodeAction>>> {
17739 self.update(cx, |project, cx| {
17740 let code_lens = project.code_lens(buffer, range.clone(), cx);
17741 let code_actions = project.code_actions(buffer, range, None, cx);
17742 cx.background_spawn(async move {
17743 let (code_lens, code_actions) = join(code_lens, code_actions).await;
17744 Ok(code_lens
17745 .context("code lens fetch")?
17746 .into_iter()
17747 .chain(code_actions.context("code action fetch")?)
17748 .collect())
17749 })
17750 })
17751 }
17752
17753 fn apply_code_action(
17754 &self,
17755 buffer_handle: Entity<Buffer>,
17756 action: CodeAction,
17757 _excerpt_id: ExcerptId,
17758 push_to_history: bool,
17759 _window: &mut Window,
17760 cx: &mut App,
17761 ) -> Task<Result<ProjectTransaction>> {
17762 self.update(cx, |project, cx| {
17763 project.apply_code_action(buffer_handle, action, push_to_history, cx)
17764 })
17765 }
17766}
17767
17768fn snippet_completions(
17769 project: &Project,
17770 buffer: &Entity<Buffer>,
17771 buffer_position: text::Anchor,
17772 cx: &mut App,
17773) -> Task<Result<Vec<Completion>>> {
17774 let language = buffer.read(cx).language_at(buffer_position);
17775 let language_name = language.as_ref().map(|language| language.lsp_id());
17776 let snippet_store = project.snippets().read(cx);
17777 let snippets = snippet_store.snippets_for(language_name, cx);
17778
17779 if snippets.is_empty() {
17780 return Task::ready(Ok(vec![]));
17781 }
17782 let snapshot = buffer.read(cx).text_snapshot();
17783 let chars: String = snapshot
17784 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
17785 .collect();
17786
17787 let scope = language.map(|language| language.default_scope());
17788 let executor = cx.background_executor().clone();
17789
17790 cx.background_spawn(async move {
17791 let classifier = CharClassifier::new(scope).for_completion(true);
17792 let mut last_word = chars
17793 .chars()
17794 .take_while(|c| classifier.is_word(*c))
17795 .collect::<String>();
17796 last_word = last_word.chars().rev().collect();
17797
17798 if last_word.is_empty() {
17799 return Ok(vec![]);
17800 }
17801
17802 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
17803 let to_lsp = |point: &text::Anchor| {
17804 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
17805 point_to_lsp(end)
17806 };
17807 let lsp_end = to_lsp(&buffer_position);
17808
17809 let candidates = snippets
17810 .iter()
17811 .enumerate()
17812 .flat_map(|(ix, snippet)| {
17813 snippet
17814 .prefix
17815 .iter()
17816 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
17817 })
17818 .collect::<Vec<StringMatchCandidate>>();
17819
17820 let mut matches = fuzzy::match_strings(
17821 &candidates,
17822 &last_word,
17823 last_word.chars().any(|c| c.is_uppercase()),
17824 100,
17825 &Default::default(),
17826 executor,
17827 )
17828 .await;
17829
17830 // Remove all candidates where the query's start does not match the start of any word in the candidate
17831 if let Some(query_start) = last_word.chars().next() {
17832 matches.retain(|string_match| {
17833 split_words(&string_match.string).any(|word| {
17834 // Check that the first codepoint of the word as lowercase matches the first
17835 // codepoint of the query as lowercase
17836 word.chars()
17837 .flat_map(|codepoint| codepoint.to_lowercase())
17838 .zip(query_start.to_lowercase())
17839 .all(|(word_cp, query_cp)| word_cp == query_cp)
17840 })
17841 });
17842 }
17843
17844 let matched_strings = matches
17845 .into_iter()
17846 .map(|m| m.string)
17847 .collect::<HashSet<_>>();
17848
17849 let result: Vec<Completion> = snippets
17850 .into_iter()
17851 .filter_map(|snippet| {
17852 let matching_prefix = snippet
17853 .prefix
17854 .iter()
17855 .find(|prefix| matched_strings.contains(*prefix))?;
17856 let start = as_offset - last_word.len();
17857 let start = snapshot.anchor_before(start);
17858 let range = start..buffer_position;
17859 let lsp_start = to_lsp(&start);
17860 let lsp_range = lsp::Range {
17861 start: lsp_start,
17862 end: lsp_end,
17863 };
17864 Some(Completion {
17865 old_range: range,
17866 new_text: snippet.body.clone(),
17867 source: CompletionSource::Lsp {
17868 server_id: LanguageServerId(usize::MAX),
17869 resolved: true,
17870 lsp_completion: Box::new(lsp::CompletionItem {
17871 label: snippet.prefix.first().unwrap().clone(),
17872 kind: Some(CompletionItemKind::SNIPPET),
17873 label_details: snippet.description.as_ref().map(|description| {
17874 lsp::CompletionItemLabelDetails {
17875 detail: Some(description.clone()),
17876 description: None,
17877 }
17878 }),
17879 insert_text_format: Some(InsertTextFormat::SNIPPET),
17880 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17881 lsp::InsertReplaceEdit {
17882 new_text: snippet.body.clone(),
17883 insert: lsp_range,
17884 replace: lsp_range,
17885 },
17886 )),
17887 filter_text: Some(snippet.body.clone()),
17888 sort_text: Some(char::MAX.to_string()),
17889 ..lsp::CompletionItem::default()
17890 }),
17891 lsp_defaults: None,
17892 },
17893 label: CodeLabel {
17894 text: matching_prefix.clone(),
17895 runs: Vec::new(),
17896 filter_range: 0..matching_prefix.len(),
17897 },
17898 documentation: snippet
17899 .description
17900 .clone()
17901 .map(|description| CompletionDocumentation::SingleLine(description.into())),
17902 confirm: None,
17903 })
17904 })
17905 .collect();
17906
17907 Ok(result)
17908 })
17909}
17910
17911impl CompletionProvider for Entity<Project> {
17912 fn completions(
17913 &self,
17914 buffer: &Entity<Buffer>,
17915 buffer_position: text::Anchor,
17916 options: CompletionContext,
17917 _window: &mut Window,
17918 cx: &mut Context<Editor>,
17919 ) -> Task<Result<Option<Vec<Completion>>>> {
17920 self.update(cx, |project, cx| {
17921 let snippets = snippet_completions(project, buffer, buffer_position, cx);
17922 let project_completions = project.completions(buffer, buffer_position, options, cx);
17923 cx.background_spawn(async move {
17924 let snippets_completions = snippets.await?;
17925 match project_completions.await? {
17926 Some(mut completions) => {
17927 completions.extend(snippets_completions);
17928 Ok(Some(completions))
17929 }
17930 None => {
17931 if snippets_completions.is_empty() {
17932 Ok(None)
17933 } else {
17934 Ok(Some(snippets_completions))
17935 }
17936 }
17937 }
17938 })
17939 })
17940 }
17941
17942 fn resolve_completions(
17943 &self,
17944 buffer: Entity<Buffer>,
17945 completion_indices: Vec<usize>,
17946 completions: Rc<RefCell<Box<[Completion]>>>,
17947 cx: &mut Context<Editor>,
17948 ) -> Task<Result<bool>> {
17949 self.update(cx, |project, cx| {
17950 project.lsp_store().update(cx, |lsp_store, cx| {
17951 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
17952 })
17953 })
17954 }
17955
17956 fn apply_additional_edits_for_completion(
17957 &self,
17958 buffer: Entity<Buffer>,
17959 completions: Rc<RefCell<Box<[Completion]>>>,
17960 completion_index: usize,
17961 push_to_history: bool,
17962 cx: &mut Context<Editor>,
17963 ) -> Task<Result<Option<language::Transaction>>> {
17964 self.update(cx, |project, cx| {
17965 project.lsp_store().update(cx, |lsp_store, cx| {
17966 lsp_store.apply_additional_edits_for_completion(
17967 buffer,
17968 completions,
17969 completion_index,
17970 push_to_history,
17971 cx,
17972 )
17973 })
17974 })
17975 }
17976
17977 fn is_completion_trigger(
17978 &self,
17979 buffer: &Entity<Buffer>,
17980 position: language::Anchor,
17981 text: &str,
17982 trigger_in_words: bool,
17983 cx: &mut Context<Editor>,
17984 ) -> bool {
17985 let mut chars = text.chars();
17986 let char = if let Some(char) = chars.next() {
17987 char
17988 } else {
17989 return false;
17990 };
17991 if chars.next().is_some() {
17992 return false;
17993 }
17994
17995 let buffer = buffer.read(cx);
17996 let snapshot = buffer.snapshot();
17997 if !snapshot.settings_at(position, cx).show_completions_on_input {
17998 return false;
17999 }
18000 let classifier = snapshot.char_classifier_at(position).for_completion(true);
18001 if trigger_in_words && classifier.is_word(char) {
18002 return true;
18003 }
18004
18005 buffer.completion_triggers().contains(text)
18006 }
18007}
18008
18009impl SemanticsProvider for Entity<Project> {
18010 fn hover(
18011 &self,
18012 buffer: &Entity<Buffer>,
18013 position: text::Anchor,
18014 cx: &mut App,
18015 ) -> Option<Task<Vec<project::Hover>>> {
18016 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
18017 }
18018
18019 fn document_highlights(
18020 &self,
18021 buffer: &Entity<Buffer>,
18022 position: text::Anchor,
18023 cx: &mut App,
18024 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
18025 Some(self.update(cx, |project, cx| {
18026 project.document_highlights(buffer, position, cx)
18027 }))
18028 }
18029
18030 fn definitions(
18031 &self,
18032 buffer: &Entity<Buffer>,
18033 position: text::Anchor,
18034 kind: GotoDefinitionKind,
18035 cx: &mut App,
18036 ) -> Option<Task<Result<Vec<LocationLink>>>> {
18037 Some(self.update(cx, |project, cx| match kind {
18038 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
18039 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
18040 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
18041 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
18042 }))
18043 }
18044
18045 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
18046 // TODO: make this work for remote projects
18047 self.update(cx, |this, cx| {
18048 buffer.update(cx, |buffer, cx| {
18049 this.any_language_server_supports_inlay_hints(buffer, cx)
18050 })
18051 })
18052 }
18053
18054 fn inlay_hints(
18055 &self,
18056 buffer_handle: Entity<Buffer>,
18057 range: Range<text::Anchor>,
18058 cx: &mut App,
18059 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
18060 Some(self.update(cx, |project, cx| {
18061 project.inlay_hints(buffer_handle, range, cx)
18062 }))
18063 }
18064
18065 fn resolve_inlay_hint(
18066 &self,
18067 hint: InlayHint,
18068 buffer_handle: Entity<Buffer>,
18069 server_id: LanguageServerId,
18070 cx: &mut App,
18071 ) -> Option<Task<anyhow::Result<InlayHint>>> {
18072 Some(self.update(cx, |project, cx| {
18073 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
18074 }))
18075 }
18076
18077 fn range_for_rename(
18078 &self,
18079 buffer: &Entity<Buffer>,
18080 position: text::Anchor,
18081 cx: &mut App,
18082 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
18083 Some(self.update(cx, |project, cx| {
18084 let buffer = buffer.clone();
18085 let task = project.prepare_rename(buffer.clone(), position, cx);
18086 cx.spawn(async move |_, cx| {
18087 Ok(match task.await? {
18088 PrepareRenameResponse::Success(range) => Some(range),
18089 PrepareRenameResponse::InvalidPosition => None,
18090 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
18091 // Fallback on using TreeSitter info to determine identifier range
18092 buffer.update(cx, |buffer, _| {
18093 let snapshot = buffer.snapshot();
18094 let (range, kind) = snapshot.surrounding_word(position);
18095 if kind != Some(CharKind::Word) {
18096 return None;
18097 }
18098 Some(
18099 snapshot.anchor_before(range.start)
18100 ..snapshot.anchor_after(range.end),
18101 )
18102 })?
18103 }
18104 })
18105 })
18106 }))
18107 }
18108
18109 fn perform_rename(
18110 &self,
18111 buffer: &Entity<Buffer>,
18112 position: text::Anchor,
18113 new_name: String,
18114 cx: &mut App,
18115 ) -> Option<Task<Result<ProjectTransaction>>> {
18116 Some(self.update(cx, |project, cx| {
18117 project.perform_rename(buffer.clone(), position, new_name, cx)
18118 }))
18119 }
18120}
18121
18122fn inlay_hint_settings(
18123 location: Anchor,
18124 snapshot: &MultiBufferSnapshot,
18125 cx: &mut Context<Editor>,
18126) -> InlayHintSettings {
18127 let file = snapshot.file_at(location);
18128 let language = snapshot.language_at(location).map(|l| l.name());
18129 language_settings(language, file, cx).inlay_hints
18130}
18131
18132fn consume_contiguous_rows(
18133 contiguous_row_selections: &mut Vec<Selection<Point>>,
18134 selection: &Selection<Point>,
18135 display_map: &DisplaySnapshot,
18136 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
18137) -> (MultiBufferRow, MultiBufferRow) {
18138 contiguous_row_selections.push(selection.clone());
18139 let start_row = MultiBufferRow(selection.start.row);
18140 let mut end_row = ending_row(selection, display_map);
18141
18142 while let Some(next_selection) = selections.peek() {
18143 if next_selection.start.row <= end_row.0 {
18144 end_row = ending_row(next_selection, display_map);
18145 contiguous_row_selections.push(selections.next().unwrap().clone());
18146 } else {
18147 break;
18148 }
18149 }
18150 (start_row, end_row)
18151}
18152
18153fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
18154 if next_selection.end.column > 0 || next_selection.is_empty() {
18155 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
18156 } else {
18157 MultiBufferRow(next_selection.end.row)
18158 }
18159}
18160
18161impl EditorSnapshot {
18162 pub fn remote_selections_in_range<'a>(
18163 &'a self,
18164 range: &'a Range<Anchor>,
18165 collaboration_hub: &dyn CollaborationHub,
18166 cx: &'a App,
18167 ) -> impl 'a + Iterator<Item = RemoteSelection> {
18168 let participant_names = collaboration_hub.user_names(cx);
18169 let participant_indices = collaboration_hub.user_participant_indices(cx);
18170 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
18171 let collaborators_by_replica_id = collaborators_by_peer_id
18172 .iter()
18173 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
18174 .collect::<HashMap<_, _>>();
18175 self.buffer_snapshot
18176 .selections_in_range(range, false)
18177 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
18178 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
18179 let participant_index = participant_indices.get(&collaborator.user_id).copied();
18180 let user_name = participant_names.get(&collaborator.user_id).cloned();
18181 Some(RemoteSelection {
18182 replica_id,
18183 selection,
18184 cursor_shape,
18185 line_mode,
18186 participant_index,
18187 peer_id: collaborator.peer_id,
18188 user_name,
18189 })
18190 })
18191 }
18192
18193 pub fn hunks_for_ranges(
18194 &self,
18195 ranges: impl IntoIterator<Item = Range<Point>>,
18196 ) -> Vec<MultiBufferDiffHunk> {
18197 let mut hunks = Vec::new();
18198 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
18199 HashMap::default();
18200 for query_range in ranges {
18201 let query_rows =
18202 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
18203 for hunk in self.buffer_snapshot.diff_hunks_in_range(
18204 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
18205 ) {
18206 // Include deleted hunks that are adjacent to the query range, because
18207 // otherwise they would be missed.
18208 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
18209 if hunk.status().is_deleted() {
18210 intersects_range |= hunk.row_range.start == query_rows.end;
18211 intersects_range |= hunk.row_range.end == query_rows.start;
18212 }
18213 if intersects_range {
18214 if !processed_buffer_rows
18215 .entry(hunk.buffer_id)
18216 .or_default()
18217 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
18218 {
18219 continue;
18220 }
18221 hunks.push(hunk);
18222 }
18223 }
18224 }
18225
18226 hunks
18227 }
18228
18229 fn display_diff_hunks_for_rows<'a>(
18230 &'a self,
18231 display_rows: Range<DisplayRow>,
18232 folded_buffers: &'a HashSet<BufferId>,
18233 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
18234 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
18235 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
18236
18237 self.buffer_snapshot
18238 .diff_hunks_in_range(buffer_start..buffer_end)
18239 .filter_map(|hunk| {
18240 if folded_buffers.contains(&hunk.buffer_id) {
18241 return None;
18242 }
18243
18244 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
18245 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
18246
18247 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
18248 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
18249
18250 let display_hunk = if hunk_display_start.column() != 0 {
18251 DisplayDiffHunk::Folded {
18252 display_row: hunk_display_start.row(),
18253 }
18254 } else {
18255 let mut end_row = hunk_display_end.row();
18256 if hunk_display_end.column() > 0 {
18257 end_row.0 += 1;
18258 }
18259 let is_created_file = hunk.is_created_file();
18260 DisplayDiffHunk::Unfolded {
18261 status: hunk.status(),
18262 diff_base_byte_range: hunk.diff_base_byte_range,
18263 display_row_range: hunk_display_start.row()..end_row,
18264 multi_buffer_range: Anchor::range_in_buffer(
18265 hunk.excerpt_id,
18266 hunk.buffer_id,
18267 hunk.buffer_range,
18268 ),
18269 is_created_file,
18270 }
18271 };
18272
18273 Some(display_hunk)
18274 })
18275 }
18276
18277 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
18278 self.display_snapshot.buffer_snapshot.language_at(position)
18279 }
18280
18281 pub fn is_focused(&self) -> bool {
18282 self.is_focused
18283 }
18284
18285 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
18286 self.placeholder_text.as_ref()
18287 }
18288
18289 pub fn scroll_position(&self) -> gpui::Point<f32> {
18290 self.scroll_anchor.scroll_position(&self.display_snapshot)
18291 }
18292
18293 fn gutter_dimensions(
18294 &self,
18295 font_id: FontId,
18296 font_size: Pixels,
18297 max_line_number_width: Pixels,
18298 cx: &App,
18299 ) -> Option<GutterDimensions> {
18300 if !self.show_gutter {
18301 return None;
18302 }
18303
18304 let descent = cx.text_system().descent(font_id, font_size);
18305 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
18306 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
18307
18308 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
18309 matches!(
18310 ProjectSettings::get_global(cx).git.git_gutter,
18311 Some(GitGutterSetting::TrackedFiles)
18312 )
18313 });
18314 let gutter_settings = EditorSettings::get_global(cx).gutter;
18315 let show_line_numbers = self
18316 .show_line_numbers
18317 .unwrap_or(gutter_settings.line_numbers);
18318 let line_gutter_width = if show_line_numbers {
18319 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
18320 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
18321 max_line_number_width.max(min_width_for_number_on_gutter)
18322 } else {
18323 0.0.into()
18324 };
18325
18326 let show_code_actions = self
18327 .show_code_actions
18328 .unwrap_or(gutter_settings.code_actions);
18329
18330 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
18331 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
18332
18333 let git_blame_entries_width =
18334 self.git_blame_gutter_max_author_length
18335 .map(|max_author_length| {
18336 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
18337
18338 /// The number of characters to dedicate to gaps and margins.
18339 const SPACING_WIDTH: usize = 4;
18340
18341 let max_char_count = max_author_length
18342 .min(GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED)
18343 + ::git::SHORT_SHA_LENGTH
18344 + MAX_RELATIVE_TIMESTAMP.len()
18345 + SPACING_WIDTH;
18346
18347 em_advance * max_char_count
18348 });
18349
18350 let is_singleton = self.buffer_snapshot.is_singleton();
18351
18352 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
18353 left_padding += if !is_singleton {
18354 em_width * 4.0
18355 } else if show_code_actions || show_runnables || show_breakpoints {
18356 em_width * 3.0
18357 } else if show_git_gutter && show_line_numbers {
18358 em_width * 2.0
18359 } else if show_git_gutter || show_line_numbers {
18360 em_width
18361 } else {
18362 px(0.)
18363 };
18364
18365 let shows_folds = is_singleton && gutter_settings.folds;
18366
18367 let right_padding = if shows_folds && show_line_numbers {
18368 em_width * 4.0
18369 } else if shows_folds || (!is_singleton && show_line_numbers) {
18370 em_width * 3.0
18371 } else if show_line_numbers {
18372 em_width
18373 } else {
18374 px(0.)
18375 };
18376
18377 Some(GutterDimensions {
18378 left_padding,
18379 right_padding,
18380 width: line_gutter_width + left_padding + right_padding,
18381 margin: -descent,
18382 git_blame_entries_width,
18383 })
18384 }
18385
18386 pub fn render_crease_toggle(
18387 &self,
18388 buffer_row: MultiBufferRow,
18389 row_contains_cursor: bool,
18390 editor: Entity<Editor>,
18391 window: &mut Window,
18392 cx: &mut App,
18393 ) -> Option<AnyElement> {
18394 let folded = self.is_line_folded(buffer_row);
18395 let mut is_foldable = false;
18396
18397 if let Some(crease) = self
18398 .crease_snapshot
18399 .query_row(buffer_row, &self.buffer_snapshot)
18400 {
18401 is_foldable = true;
18402 match crease {
18403 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
18404 if let Some(render_toggle) = render_toggle {
18405 let toggle_callback =
18406 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
18407 if folded {
18408 editor.update(cx, |editor, cx| {
18409 editor.fold_at(&crate::FoldAt { buffer_row }, window, cx)
18410 });
18411 } else {
18412 editor.update(cx, |editor, cx| {
18413 editor.unfold_at(
18414 &crate::UnfoldAt { buffer_row },
18415 window,
18416 cx,
18417 )
18418 });
18419 }
18420 });
18421 return Some((render_toggle)(
18422 buffer_row,
18423 folded,
18424 toggle_callback,
18425 window,
18426 cx,
18427 ));
18428 }
18429 }
18430 }
18431 }
18432
18433 is_foldable |= self.starts_indent(buffer_row);
18434
18435 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
18436 Some(
18437 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
18438 .toggle_state(folded)
18439 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
18440 if folded {
18441 this.unfold_at(&UnfoldAt { buffer_row }, window, cx);
18442 } else {
18443 this.fold_at(&FoldAt { buffer_row }, window, cx);
18444 }
18445 }))
18446 .into_any_element(),
18447 )
18448 } else {
18449 None
18450 }
18451 }
18452
18453 pub fn render_crease_trailer(
18454 &self,
18455 buffer_row: MultiBufferRow,
18456 window: &mut Window,
18457 cx: &mut App,
18458 ) -> Option<AnyElement> {
18459 let folded = self.is_line_folded(buffer_row);
18460 if let Crease::Inline { render_trailer, .. } = self
18461 .crease_snapshot
18462 .query_row(buffer_row, &self.buffer_snapshot)?
18463 {
18464 let render_trailer = render_trailer.as_ref()?;
18465 Some(render_trailer(buffer_row, folded, window, cx))
18466 } else {
18467 None
18468 }
18469 }
18470}
18471
18472impl Deref for EditorSnapshot {
18473 type Target = DisplaySnapshot;
18474
18475 fn deref(&self) -> &Self::Target {
18476 &self.display_snapshot
18477 }
18478}
18479
18480#[derive(Clone, Debug, PartialEq, Eq)]
18481pub enum EditorEvent {
18482 InputIgnored {
18483 text: Arc<str>,
18484 },
18485 InputHandled {
18486 utf16_range_to_replace: Option<Range<isize>>,
18487 text: Arc<str>,
18488 },
18489 ExcerptsAdded {
18490 buffer: Entity<Buffer>,
18491 predecessor: ExcerptId,
18492 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
18493 },
18494 ExcerptsRemoved {
18495 ids: Vec<ExcerptId>,
18496 },
18497 BufferFoldToggled {
18498 ids: Vec<ExcerptId>,
18499 folded: bool,
18500 },
18501 ExcerptsEdited {
18502 ids: Vec<ExcerptId>,
18503 },
18504 ExcerptsExpanded {
18505 ids: Vec<ExcerptId>,
18506 },
18507 BufferEdited,
18508 Edited {
18509 transaction_id: clock::Lamport,
18510 },
18511 Reparsed(BufferId),
18512 Focused,
18513 FocusedIn,
18514 Blurred,
18515 DirtyChanged,
18516 Saved,
18517 TitleChanged,
18518 DiffBaseChanged,
18519 SelectionsChanged {
18520 local: bool,
18521 },
18522 ScrollPositionChanged {
18523 local: bool,
18524 autoscroll: bool,
18525 },
18526 Closed,
18527 TransactionUndone {
18528 transaction_id: clock::Lamport,
18529 },
18530 TransactionBegun {
18531 transaction_id: clock::Lamport,
18532 },
18533 Reloaded,
18534 CursorShapeChanged,
18535}
18536
18537impl EventEmitter<EditorEvent> for Editor {}
18538
18539impl Focusable for Editor {
18540 fn focus_handle(&self, _cx: &App) -> FocusHandle {
18541 self.focus_handle.clone()
18542 }
18543}
18544
18545impl Render for Editor {
18546 fn render(&mut self, _: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
18547 let settings = ThemeSettings::get_global(cx);
18548
18549 let mut text_style = match self.mode {
18550 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
18551 color: cx.theme().colors().editor_foreground,
18552 font_family: settings.ui_font.family.clone(),
18553 font_features: settings.ui_font.features.clone(),
18554 font_fallbacks: settings.ui_font.fallbacks.clone(),
18555 font_size: rems(0.875).into(),
18556 font_weight: settings.ui_font.weight,
18557 line_height: relative(settings.buffer_line_height.value()),
18558 ..Default::default()
18559 },
18560 EditorMode::Full => TextStyle {
18561 color: cx.theme().colors().editor_foreground,
18562 font_family: settings.buffer_font.family.clone(),
18563 font_features: settings.buffer_font.features.clone(),
18564 font_fallbacks: settings.buffer_font.fallbacks.clone(),
18565 font_size: settings.buffer_font_size(cx).into(),
18566 font_weight: settings.buffer_font.weight,
18567 line_height: relative(settings.buffer_line_height.value()),
18568 ..Default::default()
18569 },
18570 };
18571 if let Some(text_style_refinement) = &self.text_style_refinement {
18572 text_style.refine(text_style_refinement)
18573 }
18574
18575 let background = match self.mode {
18576 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
18577 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
18578 EditorMode::Full => cx.theme().colors().editor_background,
18579 };
18580
18581 EditorElement::new(
18582 &cx.entity(),
18583 EditorStyle {
18584 background,
18585 local_player: cx.theme().players().local(),
18586 text: text_style,
18587 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
18588 syntax: cx.theme().syntax().clone(),
18589 status: cx.theme().status().clone(),
18590 inlay_hints_style: make_inlay_hints_style(cx),
18591 inline_completion_styles: make_suggestion_styles(cx),
18592 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
18593 },
18594 )
18595 }
18596}
18597
18598impl EntityInputHandler for Editor {
18599 fn text_for_range(
18600 &mut self,
18601 range_utf16: Range<usize>,
18602 adjusted_range: &mut Option<Range<usize>>,
18603 _: &mut Window,
18604 cx: &mut Context<Self>,
18605 ) -> Option<String> {
18606 let snapshot = self.buffer.read(cx).read(cx);
18607 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
18608 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
18609 if (start.0..end.0) != range_utf16 {
18610 adjusted_range.replace(start.0..end.0);
18611 }
18612 Some(snapshot.text_for_range(start..end).collect())
18613 }
18614
18615 fn selected_text_range(
18616 &mut self,
18617 ignore_disabled_input: bool,
18618 _: &mut Window,
18619 cx: &mut Context<Self>,
18620 ) -> Option<UTF16Selection> {
18621 // Prevent the IME menu from appearing when holding down an alphabetic key
18622 // while input is disabled.
18623 if !ignore_disabled_input && !self.input_enabled {
18624 return None;
18625 }
18626
18627 let selection = self.selections.newest::<OffsetUtf16>(cx);
18628 let range = selection.range();
18629
18630 Some(UTF16Selection {
18631 range: range.start.0..range.end.0,
18632 reversed: selection.reversed,
18633 })
18634 }
18635
18636 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
18637 let snapshot = self.buffer.read(cx).read(cx);
18638 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
18639 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
18640 }
18641
18642 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18643 self.clear_highlights::<InputComposition>(cx);
18644 self.ime_transaction.take();
18645 }
18646
18647 fn replace_text_in_range(
18648 &mut self,
18649 range_utf16: Option<Range<usize>>,
18650 text: &str,
18651 window: &mut Window,
18652 cx: &mut Context<Self>,
18653 ) {
18654 if !self.input_enabled {
18655 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18656 return;
18657 }
18658
18659 self.transact(window, cx, |this, window, cx| {
18660 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
18661 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
18662 Some(this.selection_replacement_ranges(range_utf16, cx))
18663 } else {
18664 this.marked_text_ranges(cx)
18665 };
18666
18667 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
18668 let newest_selection_id = this.selections.newest_anchor().id;
18669 this.selections
18670 .all::<OffsetUtf16>(cx)
18671 .iter()
18672 .zip(ranges_to_replace.iter())
18673 .find_map(|(selection, range)| {
18674 if selection.id == newest_selection_id {
18675 Some(
18676 (range.start.0 as isize - selection.head().0 as isize)
18677 ..(range.end.0 as isize - selection.head().0 as isize),
18678 )
18679 } else {
18680 None
18681 }
18682 })
18683 });
18684
18685 cx.emit(EditorEvent::InputHandled {
18686 utf16_range_to_replace: range_to_replace,
18687 text: text.into(),
18688 });
18689
18690 if let Some(new_selected_ranges) = new_selected_ranges {
18691 this.change_selections(None, window, cx, |selections| {
18692 selections.select_ranges(new_selected_ranges)
18693 });
18694 this.backspace(&Default::default(), window, cx);
18695 }
18696
18697 this.handle_input(text, window, cx);
18698 });
18699
18700 if let Some(transaction) = self.ime_transaction {
18701 self.buffer.update(cx, |buffer, cx| {
18702 buffer.group_until_transaction(transaction, cx);
18703 });
18704 }
18705
18706 self.unmark_text(window, cx);
18707 }
18708
18709 fn replace_and_mark_text_in_range(
18710 &mut self,
18711 range_utf16: Option<Range<usize>>,
18712 text: &str,
18713 new_selected_range_utf16: Option<Range<usize>>,
18714 window: &mut Window,
18715 cx: &mut Context<Self>,
18716 ) {
18717 if !self.input_enabled {
18718 return;
18719 }
18720
18721 let transaction = self.transact(window, cx, |this, window, cx| {
18722 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
18723 let snapshot = this.buffer.read(cx).read(cx);
18724 if let Some(relative_range_utf16) = range_utf16.as_ref() {
18725 for marked_range in &mut marked_ranges {
18726 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
18727 marked_range.start.0 += relative_range_utf16.start;
18728 marked_range.start =
18729 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
18730 marked_range.end =
18731 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
18732 }
18733 }
18734 Some(marked_ranges)
18735 } else if let Some(range_utf16) = range_utf16 {
18736 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
18737 Some(this.selection_replacement_ranges(range_utf16, cx))
18738 } else {
18739 None
18740 };
18741
18742 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
18743 let newest_selection_id = this.selections.newest_anchor().id;
18744 this.selections
18745 .all::<OffsetUtf16>(cx)
18746 .iter()
18747 .zip(ranges_to_replace.iter())
18748 .find_map(|(selection, range)| {
18749 if selection.id == newest_selection_id {
18750 Some(
18751 (range.start.0 as isize - selection.head().0 as isize)
18752 ..(range.end.0 as isize - selection.head().0 as isize),
18753 )
18754 } else {
18755 None
18756 }
18757 })
18758 });
18759
18760 cx.emit(EditorEvent::InputHandled {
18761 utf16_range_to_replace: range_to_replace,
18762 text: text.into(),
18763 });
18764
18765 if let Some(ranges) = ranges_to_replace {
18766 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
18767 }
18768
18769 let marked_ranges = {
18770 let snapshot = this.buffer.read(cx).read(cx);
18771 this.selections
18772 .disjoint_anchors()
18773 .iter()
18774 .map(|selection| {
18775 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
18776 })
18777 .collect::<Vec<_>>()
18778 };
18779
18780 if text.is_empty() {
18781 this.unmark_text(window, cx);
18782 } else {
18783 this.highlight_text::<InputComposition>(
18784 marked_ranges.clone(),
18785 HighlightStyle {
18786 underline: Some(UnderlineStyle {
18787 thickness: px(1.),
18788 color: None,
18789 wavy: false,
18790 }),
18791 ..Default::default()
18792 },
18793 cx,
18794 );
18795 }
18796
18797 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
18798 let use_autoclose = this.use_autoclose;
18799 let use_auto_surround = this.use_auto_surround;
18800 this.set_use_autoclose(false);
18801 this.set_use_auto_surround(false);
18802 this.handle_input(text, window, cx);
18803 this.set_use_autoclose(use_autoclose);
18804 this.set_use_auto_surround(use_auto_surround);
18805
18806 if let Some(new_selected_range) = new_selected_range_utf16 {
18807 let snapshot = this.buffer.read(cx).read(cx);
18808 let new_selected_ranges = marked_ranges
18809 .into_iter()
18810 .map(|marked_range| {
18811 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
18812 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
18813 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
18814 snapshot.clip_offset_utf16(new_start, Bias::Left)
18815 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
18816 })
18817 .collect::<Vec<_>>();
18818
18819 drop(snapshot);
18820 this.change_selections(None, window, cx, |selections| {
18821 selections.select_ranges(new_selected_ranges)
18822 });
18823 }
18824 });
18825
18826 self.ime_transaction = self.ime_transaction.or(transaction);
18827 if let Some(transaction) = self.ime_transaction {
18828 self.buffer.update(cx, |buffer, cx| {
18829 buffer.group_until_transaction(transaction, cx);
18830 });
18831 }
18832
18833 if self.text_highlights::<InputComposition>(cx).is_none() {
18834 self.ime_transaction.take();
18835 }
18836 }
18837
18838 fn bounds_for_range(
18839 &mut self,
18840 range_utf16: Range<usize>,
18841 element_bounds: gpui::Bounds<Pixels>,
18842 window: &mut Window,
18843 cx: &mut Context<Self>,
18844 ) -> Option<gpui::Bounds<Pixels>> {
18845 let text_layout_details = self.text_layout_details(window);
18846 let gpui::Size {
18847 width: em_width,
18848 height: line_height,
18849 } = self.character_size(window);
18850
18851 let snapshot = self.snapshot(window, cx);
18852 let scroll_position = snapshot.scroll_position();
18853 let scroll_left = scroll_position.x * em_width;
18854
18855 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
18856 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
18857 + self.gutter_dimensions.width
18858 + self.gutter_dimensions.margin;
18859 let y = line_height * (start.row().as_f32() - scroll_position.y);
18860
18861 Some(Bounds {
18862 origin: element_bounds.origin + point(x, y),
18863 size: size(em_width, line_height),
18864 })
18865 }
18866
18867 fn character_index_for_point(
18868 &mut self,
18869 point: gpui::Point<Pixels>,
18870 _window: &mut Window,
18871 _cx: &mut Context<Self>,
18872 ) -> Option<usize> {
18873 let position_map = self.last_position_map.as_ref()?;
18874 if !position_map.text_hitbox.contains(&point) {
18875 return None;
18876 }
18877 let display_point = position_map.point_for_position(point).previous_valid;
18878 let anchor = position_map
18879 .snapshot
18880 .display_point_to_anchor(display_point, Bias::Left);
18881 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
18882 Some(utf16_offset.0)
18883 }
18884}
18885
18886trait SelectionExt {
18887 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
18888 fn spanned_rows(
18889 &self,
18890 include_end_if_at_line_start: bool,
18891 map: &DisplaySnapshot,
18892 ) -> Range<MultiBufferRow>;
18893}
18894
18895impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
18896 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
18897 let start = self
18898 .start
18899 .to_point(&map.buffer_snapshot)
18900 .to_display_point(map);
18901 let end = self
18902 .end
18903 .to_point(&map.buffer_snapshot)
18904 .to_display_point(map);
18905 if self.reversed {
18906 end..start
18907 } else {
18908 start..end
18909 }
18910 }
18911
18912 fn spanned_rows(
18913 &self,
18914 include_end_if_at_line_start: bool,
18915 map: &DisplaySnapshot,
18916 ) -> Range<MultiBufferRow> {
18917 let start = self.start.to_point(&map.buffer_snapshot);
18918 let mut end = self.end.to_point(&map.buffer_snapshot);
18919 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
18920 end.row -= 1;
18921 }
18922
18923 let buffer_start = map.prev_line_boundary(start).0;
18924 let buffer_end = map.next_line_boundary(end).0;
18925 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
18926 }
18927}
18928
18929impl<T: InvalidationRegion> InvalidationStack<T> {
18930 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
18931 where
18932 S: Clone + ToOffset,
18933 {
18934 while let Some(region) = self.last() {
18935 let all_selections_inside_invalidation_ranges =
18936 if selections.len() == region.ranges().len() {
18937 selections
18938 .iter()
18939 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
18940 .all(|(selection, invalidation_range)| {
18941 let head = selection.head().to_offset(buffer);
18942 invalidation_range.start <= head && invalidation_range.end >= head
18943 })
18944 } else {
18945 false
18946 };
18947
18948 if all_selections_inside_invalidation_ranges {
18949 break;
18950 } else {
18951 self.pop();
18952 }
18953 }
18954 }
18955}
18956
18957impl<T> Default for InvalidationStack<T> {
18958 fn default() -> Self {
18959 Self(Default::default())
18960 }
18961}
18962
18963impl<T> Deref for InvalidationStack<T> {
18964 type Target = Vec<T>;
18965
18966 fn deref(&self) -> &Self::Target {
18967 &self.0
18968 }
18969}
18970
18971impl<T> DerefMut for InvalidationStack<T> {
18972 fn deref_mut(&mut self) -> &mut Self::Target {
18973 &mut self.0
18974 }
18975}
18976
18977impl InvalidationRegion for SnippetState {
18978 fn ranges(&self) -> &[Range<Anchor>] {
18979 &self.ranges[self.active_index]
18980 }
18981}
18982
18983pub fn diagnostic_block_renderer(
18984 diagnostic: Diagnostic,
18985 max_message_rows: Option<u8>,
18986 allow_closing: bool,
18987) -> RenderBlock {
18988 let (text_without_backticks, code_ranges) =
18989 highlight_diagnostic_message(&diagnostic, max_message_rows);
18990
18991 Arc::new(move |cx: &mut BlockContext| {
18992 let group_id: SharedString = cx.block_id.to_string().into();
18993
18994 let mut text_style = cx.window.text_style().clone();
18995 text_style.color = diagnostic_style(diagnostic.severity, cx.theme().status());
18996 let theme_settings = ThemeSettings::get_global(cx);
18997 text_style.font_family = theme_settings.buffer_font.family.clone();
18998 text_style.font_style = theme_settings.buffer_font.style;
18999 text_style.font_features = theme_settings.buffer_font.features.clone();
19000 text_style.font_weight = theme_settings.buffer_font.weight;
19001
19002 let multi_line_diagnostic = diagnostic.message.contains('\n');
19003
19004 let buttons = |diagnostic: &Diagnostic| {
19005 if multi_line_diagnostic {
19006 v_flex()
19007 } else {
19008 h_flex()
19009 }
19010 .when(allow_closing, |div| {
19011 div.children(diagnostic.is_primary.then(|| {
19012 IconButton::new("close-block", IconName::XCircle)
19013 .icon_color(Color::Muted)
19014 .size(ButtonSize::Compact)
19015 .style(ButtonStyle::Transparent)
19016 .visible_on_hover(group_id.clone())
19017 .on_click(move |_click, window, cx| {
19018 window.dispatch_action(Box::new(Cancel), cx)
19019 })
19020 .tooltip(|window, cx| {
19021 Tooltip::for_action("Close Diagnostics", &Cancel, window, cx)
19022 })
19023 }))
19024 })
19025 .child(
19026 IconButton::new("copy-block", IconName::Copy)
19027 .icon_color(Color::Muted)
19028 .size(ButtonSize::Compact)
19029 .style(ButtonStyle::Transparent)
19030 .visible_on_hover(group_id.clone())
19031 .on_click({
19032 let message = diagnostic.message.clone();
19033 move |_click, _, cx| {
19034 cx.write_to_clipboard(ClipboardItem::new_string(message.clone()))
19035 }
19036 })
19037 .tooltip(Tooltip::text("Copy diagnostic message")),
19038 )
19039 };
19040
19041 let icon_size = buttons(&diagnostic).into_any_element().layout_as_root(
19042 AvailableSpace::min_size(),
19043 cx.window,
19044 cx.app,
19045 );
19046
19047 h_flex()
19048 .id(cx.block_id)
19049 .group(group_id.clone())
19050 .relative()
19051 .size_full()
19052 .block_mouse_down()
19053 .pl(cx.gutter_dimensions.width)
19054 .w(cx.max_width - cx.gutter_dimensions.full_width())
19055 .child(
19056 div()
19057 .flex()
19058 .w(cx.anchor_x - cx.gutter_dimensions.width - icon_size.width)
19059 .flex_shrink(),
19060 )
19061 .child(buttons(&diagnostic))
19062 .child(div().flex().flex_shrink_0().child(
19063 StyledText::new(text_without_backticks.clone()).with_default_highlights(
19064 &text_style,
19065 code_ranges.iter().map(|range| {
19066 (
19067 range.clone(),
19068 HighlightStyle {
19069 font_weight: Some(FontWeight::BOLD),
19070 ..Default::default()
19071 },
19072 )
19073 }),
19074 ),
19075 ))
19076 .into_any_element()
19077 })
19078}
19079
19080fn inline_completion_edit_text(
19081 current_snapshot: &BufferSnapshot,
19082 edits: &[(Range<Anchor>, String)],
19083 edit_preview: &EditPreview,
19084 include_deletions: bool,
19085 cx: &App,
19086) -> HighlightedText {
19087 let edits = edits
19088 .iter()
19089 .map(|(anchor, text)| {
19090 (
19091 anchor.start.text_anchor..anchor.end.text_anchor,
19092 text.clone(),
19093 )
19094 })
19095 .collect::<Vec<_>>();
19096
19097 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
19098}
19099
19100pub fn highlight_diagnostic_message(
19101 diagnostic: &Diagnostic,
19102 mut max_message_rows: Option<u8>,
19103) -> (SharedString, Vec<Range<usize>>) {
19104 let mut text_without_backticks = String::new();
19105 let mut code_ranges = Vec::new();
19106
19107 if let Some(source) = &diagnostic.source {
19108 text_without_backticks.push_str(source);
19109 code_ranges.push(0..source.len());
19110 text_without_backticks.push_str(": ");
19111 }
19112
19113 let mut prev_offset = 0;
19114 let mut in_code_block = false;
19115 let has_row_limit = max_message_rows.is_some();
19116 let mut newline_indices = diagnostic
19117 .message
19118 .match_indices('\n')
19119 .filter(|_| has_row_limit)
19120 .map(|(ix, _)| ix)
19121 .fuse()
19122 .peekable();
19123
19124 for (quote_ix, _) in diagnostic
19125 .message
19126 .match_indices('`')
19127 .chain([(diagnostic.message.len(), "")])
19128 {
19129 let mut first_newline_ix = None;
19130 let mut last_newline_ix = None;
19131 while let Some(newline_ix) = newline_indices.peek() {
19132 if *newline_ix < quote_ix {
19133 if first_newline_ix.is_none() {
19134 first_newline_ix = Some(*newline_ix);
19135 }
19136 last_newline_ix = Some(*newline_ix);
19137
19138 if let Some(rows_left) = &mut max_message_rows {
19139 if *rows_left == 0 {
19140 break;
19141 } else {
19142 *rows_left -= 1;
19143 }
19144 }
19145 let _ = newline_indices.next();
19146 } else {
19147 break;
19148 }
19149 }
19150 let prev_len = text_without_backticks.len();
19151 let new_text = &diagnostic.message[prev_offset..first_newline_ix.unwrap_or(quote_ix)];
19152 text_without_backticks.push_str(new_text);
19153 if in_code_block {
19154 code_ranges.push(prev_len..text_without_backticks.len());
19155 }
19156 prev_offset = last_newline_ix.unwrap_or(quote_ix) + 1;
19157 in_code_block = !in_code_block;
19158 if first_newline_ix.map_or(false, |newline_ix| newline_ix < quote_ix) {
19159 text_without_backticks.push_str("...");
19160 break;
19161 }
19162 }
19163
19164 (text_without_backticks.into(), code_ranges)
19165}
19166
19167fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
19168 match severity {
19169 DiagnosticSeverity::ERROR => colors.error,
19170 DiagnosticSeverity::WARNING => colors.warning,
19171 DiagnosticSeverity::INFORMATION => colors.info,
19172 DiagnosticSeverity::HINT => colors.info,
19173 _ => colors.ignored,
19174 }
19175}
19176
19177pub fn styled_runs_for_code_label<'a>(
19178 label: &'a CodeLabel,
19179 syntax_theme: &'a theme::SyntaxTheme,
19180) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
19181 let fade_out = HighlightStyle {
19182 fade_out: Some(0.35),
19183 ..Default::default()
19184 };
19185
19186 let mut prev_end = label.filter_range.end;
19187 label
19188 .runs
19189 .iter()
19190 .enumerate()
19191 .flat_map(move |(ix, (range, highlight_id))| {
19192 let style = if let Some(style) = highlight_id.style(syntax_theme) {
19193 style
19194 } else {
19195 return Default::default();
19196 };
19197 let mut muted_style = style;
19198 muted_style.highlight(fade_out);
19199
19200 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
19201 if range.start >= label.filter_range.end {
19202 if range.start > prev_end {
19203 runs.push((prev_end..range.start, fade_out));
19204 }
19205 runs.push((range.clone(), muted_style));
19206 } else if range.end <= label.filter_range.end {
19207 runs.push((range.clone(), style));
19208 } else {
19209 runs.push((range.start..label.filter_range.end, style));
19210 runs.push((label.filter_range.end..range.end, muted_style));
19211 }
19212 prev_end = cmp::max(prev_end, range.end);
19213
19214 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
19215 runs.push((prev_end..label.text.len(), fade_out));
19216 }
19217
19218 runs
19219 })
19220}
19221
19222pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
19223 let mut prev_index = 0;
19224 let mut prev_codepoint: Option<char> = None;
19225 text.char_indices()
19226 .chain([(text.len(), '\0')])
19227 .filter_map(move |(index, codepoint)| {
19228 let prev_codepoint = prev_codepoint.replace(codepoint)?;
19229 let is_boundary = index == text.len()
19230 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
19231 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
19232 if is_boundary {
19233 let chunk = &text[prev_index..index];
19234 prev_index = index;
19235 Some(chunk)
19236 } else {
19237 None
19238 }
19239 })
19240}
19241
19242pub trait RangeToAnchorExt: Sized {
19243 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
19244
19245 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
19246 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
19247 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
19248 }
19249}
19250
19251impl<T: ToOffset> RangeToAnchorExt for Range<T> {
19252 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
19253 let start_offset = self.start.to_offset(snapshot);
19254 let end_offset = self.end.to_offset(snapshot);
19255 if start_offset == end_offset {
19256 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
19257 } else {
19258 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
19259 }
19260 }
19261}
19262
19263pub trait RowExt {
19264 fn as_f32(&self) -> f32;
19265
19266 fn next_row(&self) -> Self;
19267
19268 fn previous_row(&self) -> Self;
19269
19270 fn minus(&self, other: Self) -> u32;
19271}
19272
19273impl RowExt for DisplayRow {
19274 fn as_f32(&self) -> f32 {
19275 self.0 as f32
19276 }
19277
19278 fn next_row(&self) -> Self {
19279 Self(self.0 + 1)
19280 }
19281
19282 fn previous_row(&self) -> Self {
19283 Self(self.0.saturating_sub(1))
19284 }
19285
19286 fn minus(&self, other: Self) -> u32 {
19287 self.0 - other.0
19288 }
19289}
19290
19291impl RowExt for MultiBufferRow {
19292 fn as_f32(&self) -> f32 {
19293 self.0 as f32
19294 }
19295
19296 fn next_row(&self) -> Self {
19297 Self(self.0 + 1)
19298 }
19299
19300 fn previous_row(&self) -> Self {
19301 Self(self.0.saturating_sub(1))
19302 }
19303
19304 fn minus(&self, other: Self) -> u32 {
19305 self.0 - other.0
19306 }
19307}
19308
19309trait RowRangeExt {
19310 type Row;
19311
19312 fn len(&self) -> usize;
19313
19314 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
19315}
19316
19317impl RowRangeExt for Range<MultiBufferRow> {
19318 type Row = MultiBufferRow;
19319
19320 fn len(&self) -> usize {
19321 (self.end.0 - self.start.0) as usize
19322 }
19323
19324 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
19325 (self.start.0..self.end.0).map(MultiBufferRow)
19326 }
19327}
19328
19329impl RowRangeExt for Range<DisplayRow> {
19330 type Row = DisplayRow;
19331
19332 fn len(&self) -> usize {
19333 (self.end.0 - self.start.0) as usize
19334 }
19335
19336 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
19337 (self.start.0..self.end.0).map(DisplayRow)
19338 }
19339}
19340
19341/// If select range has more than one line, we
19342/// just point the cursor to range.start.
19343fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
19344 if range.start.row == range.end.row {
19345 range
19346 } else {
19347 range.start..range.start
19348 }
19349}
19350pub struct KillRing(ClipboardItem);
19351impl Global for KillRing {}
19352
19353const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
19354
19355struct BreakpointPromptEditor {
19356 pub(crate) prompt: Entity<Editor>,
19357 editor: WeakEntity<Editor>,
19358 breakpoint_anchor: Anchor,
19359 kind: BreakpointKind,
19360 block_ids: HashSet<CustomBlockId>,
19361 gutter_dimensions: Arc<Mutex<GutterDimensions>>,
19362 _subscriptions: Vec<Subscription>,
19363}
19364
19365impl BreakpointPromptEditor {
19366 const MAX_LINES: u8 = 4;
19367
19368 fn new(
19369 editor: WeakEntity<Editor>,
19370 breakpoint_anchor: Anchor,
19371 kind: BreakpointKind,
19372 window: &mut Window,
19373 cx: &mut Context<Self>,
19374 ) -> Self {
19375 let buffer = cx.new(|cx| {
19376 Buffer::local(
19377 kind.log_message()
19378 .map(|msg| msg.to_string())
19379 .unwrap_or_default(),
19380 cx,
19381 )
19382 });
19383 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19384
19385 let prompt = cx.new(|cx| {
19386 let mut prompt = Editor::new(
19387 EditorMode::AutoHeight {
19388 max_lines: Self::MAX_LINES as usize,
19389 },
19390 buffer,
19391 None,
19392 window,
19393 cx,
19394 );
19395 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
19396 prompt.set_show_cursor_when_unfocused(false, cx);
19397 prompt.set_placeholder_text(
19398 "Message to log when breakpoint is hit. Expressions within {} are interpolated.",
19399 cx,
19400 );
19401
19402 prompt
19403 });
19404
19405 Self {
19406 prompt,
19407 editor,
19408 breakpoint_anchor,
19409 kind,
19410 gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
19411 block_ids: Default::default(),
19412 _subscriptions: vec![],
19413 }
19414 }
19415
19416 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
19417 self.block_ids.extend(block_ids)
19418 }
19419
19420 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
19421 if let Some(editor) = self.editor.upgrade() {
19422 let log_message = self
19423 .prompt
19424 .read(cx)
19425 .buffer
19426 .read(cx)
19427 .as_singleton()
19428 .expect("A multi buffer in breakpoint prompt isn't possible")
19429 .read(cx)
19430 .as_rope()
19431 .to_string();
19432
19433 editor.update(cx, |editor, cx| {
19434 editor.edit_breakpoint_at_anchor(
19435 self.breakpoint_anchor,
19436 self.kind.clone(),
19437 BreakpointEditAction::EditLogMessage(log_message.into()),
19438 cx,
19439 );
19440
19441 editor.remove_blocks(self.block_ids.clone(), None, cx);
19442 cx.focus_self(window);
19443 });
19444 }
19445 }
19446
19447 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
19448 self.editor
19449 .update(cx, |editor, cx| {
19450 editor.remove_blocks(self.block_ids.clone(), None, cx);
19451 window.focus(&editor.focus_handle);
19452 })
19453 .log_err();
19454 }
19455
19456 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
19457 let settings = ThemeSettings::get_global(cx);
19458 let text_style = TextStyle {
19459 color: if self.prompt.read(cx).read_only(cx) {
19460 cx.theme().colors().text_disabled
19461 } else {
19462 cx.theme().colors().text
19463 },
19464 font_family: settings.buffer_font.family.clone(),
19465 font_fallbacks: settings.buffer_font.fallbacks.clone(),
19466 font_size: settings.buffer_font_size(cx).into(),
19467 font_weight: settings.buffer_font.weight,
19468 line_height: relative(settings.buffer_line_height.value()),
19469 ..Default::default()
19470 };
19471 EditorElement::new(
19472 &self.prompt,
19473 EditorStyle {
19474 background: cx.theme().colors().editor_background,
19475 local_player: cx.theme().players().local(),
19476 text: text_style,
19477 ..Default::default()
19478 },
19479 )
19480 }
19481}
19482
19483impl Render for BreakpointPromptEditor {
19484 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
19485 let gutter_dimensions = *self.gutter_dimensions.lock();
19486 h_flex()
19487 .key_context("Editor")
19488 .bg(cx.theme().colors().editor_background)
19489 .border_y_1()
19490 .border_color(cx.theme().status().info_border)
19491 .size_full()
19492 .py(window.line_height() / 2.5)
19493 .on_action(cx.listener(Self::confirm))
19494 .on_action(cx.listener(Self::cancel))
19495 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
19496 .child(div().flex_1().child(self.render_prompt_editor(cx)))
19497 }
19498}
19499
19500impl Focusable for BreakpointPromptEditor {
19501 fn focus_handle(&self, cx: &App) -> FocusHandle {
19502 self.prompt.focus_handle(cx)
19503 }
19504}
19505
19506fn all_edits_insertions_or_deletions(
19507 edits: &Vec<(Range<Anchor>, String)>,
19508 snapshot: &MultiBufferSnapshot,
19509) -> bool {
19510 let mut all_insertions = true;
19511 let mut all_deletions = true;
19512
19513 for (range, new_text) in edits.iter() {
19514 let range_is_empty = range.to_offset(&snapshot).is_empty();
19515 let text_is_empty = new_text.is_empty();
19516
19517 if range_is_empty != text_is_empty {
19518 if range_is_empty {
19519 all_deletions = false;
19520 } else {
19521 all_insertions = false;
19522 }
19523 } else {
19524 return false;
19525 }
19526
19527 if !all_insertions && !all_deletions {
19528 return false;
19529 }
19530 }
19531 all_insertions || all_deletions
19532}
19533
19534struct MissingEditPredictionKeybindingTooltip;
19535
19536impl Render for MissingEditPredictionKeybindingTooltip {
19537 fn render(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
19538 ui::tooltip_container(window, cx, |container, _, cx| {
19539 container
19540 .flex_shrink_0()
19541 .max_w_80()
19542 .min_h(rems_from_px(124.))
19543 .justify_between()
19544 .child(
19545 v_flex()
19546 .flex_1()
19547 .text_ui_sm(cx)
19548 .child(Label::new("Conflict with Accept Keybinding"))
19549 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
19550 )
19551 .child(
19552 h_flex()
19553 .pb_1()
19554 .gap_1()
19555 .items_end()
19556 .w_full()
19557 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
19558 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
19559 }))
19560 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
19561 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
19562 })),
19563 )
19564 })
19565 }
19566}
19567
19568#[derive(Debug, Clone, Copy, PartialEq)]
19569pub struct LineHighlight {
19570 pub background: Background,
19571 pub border: Option<gpui::Hsla>,
19572}
19573
19574impl From<Hsla> for LineHighlight {
19575 fn from(hsla: Hsla) -> Self {
19576 Self {
19577 background: hsla.into(),
19578 border: None,
19579 }
19580 }
19581}
19582
19583impl From<Background> for LineHighlight {
19584 fn from(background: Background) -> Self {
19585 Self {
19586 background,
19587 border: None,
19588 }
19589 }
19590}
19591
19592fn render_diff_hunk_controls(
19593 row: u32,
19594 status: &DiffHunkStatus,
19595 hunk_range: Range<Anchor>,
19596 is_created_file: bool,
19597 line_height: Pixels,
19598 editor: &Entity<Editor>,
19599 cx: &mut App,
19600) -> AnyElement {
19601 h_flex()
19602 .h(line_height)
19603 .mr_1()
19604 .gap_1()
19605 .px_0p5()
19606 .pb_1()
19607 .border_x_1()
19608 .border_b_1()
19609 .border_color(cx.theme().colors().border_variant)
19610 .rounded_b_lg()
19611 .bg(cx.theme().colors().editor_background)
19612 .gap_1()
19613 .occlude()
19614 .shadow_md()
19615 .child(if status.has_secondary_hunk() {
19616 Button::new(("stage", row as u64), "Stage")
19617 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
19618 .tooltip({
19619 let focus_handle = editor.focus_handle(cx);
19620 move |window, cx| {
19621 Tooltip::for_action_in(
19622 "Stage Hunk",
19623 &::git::ToggleStaged,
19624 &focus_handle,
19625 window,
19626 cx,
19627 )
19628 }
19629 })
19630 .on_click({
19631 let editor = editor.clone();
19632 move |_event, _window, cx| {
19633 editor.update(cx, |editor, cx| {
19634 editor.stage_or_unstage_diff_hunks(
19635 true,
19636 vec![hunk_range.start..hunk_range.start],
19637 cx,
19638 );
19639 });
19640 }
19641 })
19642 } else {
19643 Button::new(("unstage", row as u64), "Unstage")
19644 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
19645 .tooltip({
19646 let focus_handle = editor.focus_handle(cx);
19647 move |window, cx| {
19648 Tooltip::for_action_in(
19649 "Unstage Hunk",
19650 &::git::ToggleStaged,
19651 &focus_handle,
19652 window,
19653 cx,
19654 )
19655 }
19656 })
19657 .on_click({
19658 let editor = editor.clone();
19659 move |_event, _window, cx| {
19660 editor.update(cx, |editor, cx| {
19661 editor.stage_or_unstage_diff_hunks(
19662 false,
19663 vec![hunk_range.start..hunk_range.start],
19664 cx,
19665 );
19666 });
19667 }
19668 })
19669 })
19670 .child(
19671 Button::new("restore", "Restore")
19672 .tooltip({
19673 let focus_handle = editor.focus_handle(cx);
19674 move |window, cx| {
19675 Tooltip::for_action_in(
19676 "Restore Hunk",
19677 &::git::Restore,
19678 &focus_handle,
19679 window,
19680 cx,
19681 )
19682 }
19683 })
19684 .on_click({
19685 let editor = editor.clone();
19686 move |_event, window, cx| {
19687 editor.update(cx, |editor, cx| {
19688 let snapshot = editor.snapshot(window, cx);
19689 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
19690 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
19691 });
19692 }
19693 })
19694 .disabled(is_created_file),
19695 )
19696 .when(
19697 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
19698 |el| {
19699 el.child(
19700 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
19701 .shape(IconButtonShape::Square)
19702 .icon_size(IconSize::Small)
19703 // .disabled(!has_multiple_hunks)
19704 .tooltip({
19705 let focus_handle = editor.focus_handle(cx);
19706 move |window, cx| {
19707 Tooltip::for_action_in(
19708 "Next Hunk",
19709 &GoToHunk,
19710 &focus_handle,
19711 window,
19712 cx,
19713 )
19714 }
19715 })
19716 .on_click({
19717 let editor = editor.clone();
19718 move |_event, window, cx| {
19719 editor.update(cx, |editor, cx| {
19720 let snapshot = editor.snapshot(window, cx);
19721 let position =
19722 hunk_range.end.to_point(&snapshot.buffer_snapshot);
19723 editor.go_to_hunk_before_or_after_position(
19724 &snapshot,
19725 position,
19726 Direction::Next,
19727 window,
19728 cx,
19729 );
19730 editor.expand_selected_diff_hunks(cx);
19731 });
19732 }
19733 }),
19734 )
19735 .child(
19736 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
19737 .shape(IconButtonShape::Square)
19738 .icon_size(IconSize::Small)
19739 // .disabled(!has_multiple_hunks)
19740 .tooltip({
19741 let focus_handle = editor.focus_handle(cx);
19742 move |window, cx| {
19743 Tooltip::for_action_in(
19744 "Previous Hunk",
19745 &GoToPreviousHunk,
19746 &focus_handle,
19747 window,
19748 cx,
19749 )
19750 }
19751 })
19752 .on_click({
19753 let editor = editor.clone();
19754 move |_event, window, cx| {
19755 editor.update(cx, |editor, cx| {
19756 let snapshot = editor.snapshot(window, cx);
19757 let point =
19758 hunk_range.start.to_point(&snapshot.buffer_snapshot);
19759 editor.go_to_hunk_before_or_after_position(
19760 &snapshot,
19761 point,
19762 Direction::Prev,
19763 window,
19764 cx,
19765 );
19766 editor.expand_selected_diff_hunks(cx);
19767 });
19768 }
19769 }),
19770 )
19771 },
19772 )
19773 .into_any_element()
19774}