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 cx: &mut Context<Self>,
12124 ) {
12125 let lines = EditorSettings::get_global(cx).expand_excerpt_lines;
12126 self.buffer.update(cx, |buffer, cx| {
12127 buffer.expand_excerpts([excerpt], lines, direction, cx)
12128 })
12129 }
12130
12131 pub fn go_to_singleton_buffer_point(
12132 &mut self,
12133 point: Point,
12134 window: &mut Window,
12135 cx: &mut Context<Self>,
12136 ) {
12137 self.go_to_singleton_buffer_range(point..point, window, cx);
12138 }
12139
12140 pub fn go_to_singleton_buffer_range(
12141 &mut self,
12142 range: Range<Point>,
12143 window: &mut Window,
12144 cx: &mut Context<Self>,
12145 ) {
12146 let multibuffer = self.buffer().read(cx);
12147 let Some(buffer) = multibuffer.as_singleton() else {
12148 return;
12149 };
12150 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
12151 return;
12152 };
12153 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
12154 return;
12155 };
12156 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
12157 s.select_anchor_ranges([start..end])
12158 });
12159 }
12160
12161 fn go_to_diagnostic(
12162 &mut self,
12163 _: &GoToDiagnostic,
12164 window: &mut Window,
12165 cx: &mut Context<Self>,
12166 ) {
12167 self.go_to_diagnostic_impl(Direction::Next, window, cx)
12168 }
12169
12170 fn go_to_prev_diagnostic(
12171 &mut self,
12172 _: &GoToPreviousDiagnostic,
12173 window: &mut Window,
12174 cx: &mut Context<Self>,
12175 ) {
12176 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
12177 }
12178
12179 pub fn go_to_diagnostic_impl(
12180 &mut self,
12181 direction: Direction,
12182 window: &mut Window,
12183 cx: &mut Context<Self>,
12184 ) {
12185 let buffer = self.buffer.read(cx).snapshot(cx);
12186 let selection = self.selections.newest::<usize>(cx);
12187
12188 // If there is an active Diagnostic Popover jump to its diagnostic instead.
12189 if direction == Direction::Next {
12190 if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() {
12191 let Some(buffer_id) = popover.local_diagnostic.range.start.buffer_id else {
12192 return;
12193 };
12194 self.activate_diagnostics(
12195 buffer_id,
12196 popover.local_diagnostic.diagnostic.group_id,
12197 window,
12198 cx,
12199 );
12200 if let Some(active_diagnostics) = self.active_diagnostics.as_ref() {
12201 let primary_range_start = active_diagnostics.primary_range.start;
12202 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12203 let mut new_selection = s.newest_anchor().clone();
12204 new_selection.collapse_to(primary_range_start, SelectionGoal::None);
12205 s.select_anchors(vec![new_selection.clone()]);
12206 });
12207 self.refresh_inline_completion(false, true, window, cx);
12208 }
12209 return;
12210 }
12211 }
12212
12213 let active_group_id = self
12214 .active_diagnostics
12215 .as_ref()
12216 .map(|active_group| active_group.group_id);
12217 let active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| {
12218 active_diagnostics
12219 .primary_range
12220 .to_offset(&buffer)
12221 .to_inclusive()
12222 });
12223 let search_start = if let Some(active_primary_range) = active_primary_range.as_ref() {
12224 if active_primary_range.contains(&selection.head()) {
12225 *active_primary_range.start()
12226 } else {
12227 selection.head()
12228 }
12229 } else {
12230 selection.head()
12231 };
12232
12233 let snapshot = self.snapshot(window, cx);
12234 let primary_diagnostics_before = buffer
12235 .diagnostics_in_range::<usize>(0..search_start)
12236 .filter(|entry| entry.diagnostic.is_primary)
12237 .filter(|entry| entry.range.start != entry.range.end)
12238 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
12239 .filter(|entry| !snapshot.intersects_fold(entry.range.start))
12240 .collect::<Vec<_>>();
12241 let last_same_group_diagnostic_before = active_group_id.and_then(|active_group_id| {
12242 primary_diagnostics_before
12243 .iter()
12244 .position(|entry| entry.diagnostic.group_id == active_group_id)
12245 });
12246
12247 let primary_diagnostics_after = buffer
12248 .diagnostics_in_range::<usize>(search_start..buffer.len())
12249 .filter(|entry| entry.diagnostic.is_primary)
12250 .filter(|entry| entry.range.start != entry.range.end)
12251 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
12252 .filter(|diagnostic| !snapshot.intersects_fold(diagnostic.range.start))
12253 .collect::<Vec<_>>();
12254 let last_same_group_diagnostic_after = active_group_id.and_then(|active_group_id| {
12255 primary_diagnostics_after
12256 .iter()
12257 .enumerate()
12258 .rev()
12259 .find_map(|(i, entry)| {
12260 if entry.diagnostic.group_id == active_group_id {
12261 Some(i)
12262 } else {
12263 None
12264 }
12265 })
12266 });
12267
12268 let next_primary_diagnostic = match direction {
12269 Direction::Prev => primary_diagnostics_before
12270 .iter()
12271 .take(last_same_group_diagnostic_before.unwrap_or(usize::MAX))
12272 .rev()
12273 .next(),
12274 Direction::Next => primary_diagnostics_after
12275 .iter()
12276 .skip(
12277 last_same_group_diagnostic_after
12278 .map(|index| index + 1)
12279 .unwrap_or(0),
12280 )
12281 .next(),
12282 };
12283
12284 // Cycle around to the start of the buffer, potentially moving back to the start of
12285 // the currently active diagnostic.
12286 let cycle_around = || match direction {
12287 Direction::Prev => primary_diagnostics_after
12288 .iter()
12289 .rev()
12290 .chain(primary_diagnostics_before.iter().rev())
12291 .next(),
12292 Direction::Next => primary_diagnostics_before
12293 .iter()
12294 .chain(primary_diagnostics_after.iter())
12295 .next(),
12296 };
12297
12298 if let Some((primary_range, group_id)) = next_primary_diagnostic
12299 .or_else(cycle_around)
12300 .map(|entry| (&entry.range, entry.diagnostic.group_id))
12301 {
12302 let Some(buffer_id) = buffer.anchor_after(primary_range.start).buffer_id else {
12303 return;
12304 };
12305 self.activate_diagnostics(buffer_id, group_id, window, cx);
12306 if self.active_diagnostics.is_some() {
12307 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12308 s.select(vec![Selection {
12309 id: selection.id,
12310 start: primary_range.start,
12311 end: primary_range.start,
12312 reversed: false,
12313 goal: SelectionGoal::None,
12314 }]);
12315 });
12316 self.refresh_inline_completion(false, true, window, cx);
12317 }
12318 }
12319 }
12320
12321 fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
12322 let snapshot = self.snapshot(window, cx);
12323 let selection = self.selections.newest::<Point>(cx);
12324 self.go_to_hunk_before_or_after_position(
12325 &snapshot,
12326 selection.head(),
12327 Direction::Next,
12328 window,
12329 cx,
12330 );
12331 }
12332
12333 fn go_to_hunk_before_or_after_position(
12334 &mut self,
12335 snapshot: &EditorSnapshot,
12336 position: Point,
12337 direction: Direction,
12338 window: &mut Window,
12339 cx: &mut Context<Editor>,
12340 ) {
12341 let row = if direction == Direction::Next {
12342 self.hunk_after_position(snapshot, position)
12343 .map(|hunk| hunk.row_range.start)
12344 } else {
12345 self.hunk_before_position(snapshot, position)
12346 };
12347
12348 if let Some(row) = row {
12349 let destination = Point::new(row.0, 0);
12350 let autoscroll = Autoscroll::center();
12351
12352 self.unfold_ranges(&[destination..destination], false, false, cx);
12353 self.change_selections(Some(autoscroll), window, cx, |s| {
12354 s.select_ranges([destination..destination]);
12355 });
12356 }
12357 }
12358
12359 fn hunk_after_position(
12360 &mut self,
12361 snapshot: &EditorSnapshot,
12362 position: Point,
12363 ) -> Option<MultiBufferDiffHunk> {
12364 snapshot
12365 .buffer_snapshot
12366 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
12367 .find(|hunk| hunk.row_range.start.0 > position.row)
12368 .or_else(|| {
12369 snapshot
12370 .buffer_snapshot
12371 .diff_hunks_in_range(Point::zero()..position)
12372 .find(|hunk| hunk.row_range.end.0 < position.row)
12373 })
12374 }
12375
12376 fn go_to_prev_hunk(
12377 &mut self,
12378 _: &GoToPreviousHunk,
12379 window: &mut Window,
12380 cx: &mut Context<Self>,
12381 ) {
12382 let snapshot = self.snapshot(window, cx);
12383 let selection = self.selections.newest::<Point>(cx);
12384 self.go_to_hunk_before_or_after_position(
12385 &snapshot,
12386 selection.head(),
12387 Direction::Prev,
12388 window,
12389 cx,
12390 );
12391 }
12392
12393 fn hunk_before_position(
12394 &mut self,
12395 snapshot: &EditorSnapshot,
12396 position: Point,
12397 ) -> Option<MultiBufferRow> {
12398 snapshot
12399 .buffer_snapshot
12400 .diff_hunk_before(position)
12401 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
12402 }
12403
12404 fn go_to_line<T: 'static>(
12405 &mut self,
12406 position: Anchor,
12407 highlight_color: Option<Hsla>,
12408 window: &mut Window,
12409 cx: &mut Context<Self>,
12410 ) {
12411 let snapshot = self.snapshot(window, cx).display_snapshot;
12412 let position = position.to_point(&snapshot.buffer_snapshot);
12413 let start = snapshot
12414 .buffer_snapshot
12415 .clip_point(Point::new(position.row, 0), Bias::Left);
12416 let end = start + Point::new(1, 0);
12417 let start = snapshot.buffer_snapshot.anchor_before(start);
12418 let end = snapshot.buffer_snapshot.anchor_before(end);
12419
12420 self.clear_row_highlights::<T>();
12421 self.highlight_rows::<T>(
12422 start..end,
12423 highlight_color
12424 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
12425 true,
12426 cx,
12427 );
12428 self.request_autoscroll(Autoscroll::center(), cx);
12429 }
12430
12431 pub fn go_to_definition(
12432 &mut self,
12433 _: &GoToDefinition,
12434 window: &mut Window,
12435 cx: &mut Context<Self>,
12436 ) -> Task<Result<Navigated>> {
12437 let definition =
12438 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
12439 cx.spawn_in(window, async move |editor, cx| {
12440 if definition.await? == Navigated::Yes {
12441 return Ok(Navigated::Yes);
12442 }
12443 match editor.update_in(cx, |editor, window, cx| {
12444 editor.find_all_references(&FindAllReferences, window, cx)
12445 })? {
12446 Some(references) => references.await,
12447 None => Ok(Navigated::No),
12448 }
12449 })
12450 }
12451
12452 pub fn go_to_declaration(
12453 &mut self,
12454 _: &GoToDeclaration,
12455 window: &mut Window,
12456 cx: &mut Context<Self>,
12457 ) -> Task<Result<Navigated>> {
12458 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
12459 }
12460
12461 pub fn go_to_declaration_split(
12462 &mut self,
12463 _: &GoToDeclaration,
12464 window: &mut Window,
12465 cx: &mut Context<Self>,
12466 ) -> Task<Result<Navigated>> {
12467 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
12468 }
12469
12470 pub fn go_to_implementation(
12471 &mut self,
12472 _: &GoToImplementation,
12473 window: &mut Window,
12474 cx: &mut Context<Self>,
12475 ) -> Task<Result<Navigated>> {
12476 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
12477 }
12478
12479 pub fn go_to_implementation_split(
12480 &mut self,
12481 _: &GoToImplementationSplit,
12482 window: &mut Window,
12483 cx: &mut Context<Self>,
12484 ) -> Task<Result<Navigated>> {
12485 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
12486 }
12487
12488 pub fn go_to_type_definition(
12489 &mut self,
12490 _: &GoToTypeDefinition,
12491 window: &mut Window,
12492 cx: &mut Context<Self>,
12493 ) -> Task<Result<Navigated>> {
12494 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
12495 }
12496
12497 pub fn go_to_definition_split(
12498 &mut self,
12499 _: &GoToDefinitionSplit,
12500 window: &mut Window,
12501 cx: &mut Context<Self>,
12502 ) -> Task<Result<Navigated>> {
12503 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
12504 }
12505
12506 pub fn go_to_type_definition_split(
12507 &mut self,
12508 _: &GoToTypeDefinitionSplit,
12509 window: &mut Window,
12510 cx: &mut Context<Self>,
12511 ) -> Task<Result<Navigated>> {
12512 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
12513 }
12514
12515 fn go_to_definition_of_kind(
12516 &mut self,
12517 kind: GotoDefinitionKind,
12518 split: bool,
12519 window: &mut Window,
12520 cx: &mut Context<Self>,
12521 ) -> Task<Result<Navigated>> {
12522 let Some(provider) = self.semantics_provider.clone() else {
12523 return Task::ready(Ok(Navigated::No));
12524 };
12525 let head = self.selections.newest::<usize>(cx).head();
12526 let buffer = self.buffer.read(cx);
12527 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
12528 text_anchor
12529 } else {
12530 return Task::ready(Ok(Navigated::No));
12531 };
12532
12533 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
12534 return Task::ready(Ok(Navigated::No));
12535 };
12536
12537 cx.spawn_in(window, async move |editor, cx| {
12538 let definitions = definitions.await?;
12539 let navigated = editor
12540 .update_in(cx, |editor, window, cx| {
12541 editor.navigate_to_hover_links(
12542 Some(kind),
12543 definitions
12544 .into_iter()
12545 .filter(|location| {
12546 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
12547 })
12548 .map(HoverLink::Text)
12549 .collect::<Vec<_>>(),
12550 split,
12551 window,
12552 cx,
12553 )
12554 })?
12555 .await?;
12556 anyhow::Ok(navigated)
12557 })
12558 }
12559
12560 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
12561 let selection = self.selections.newest_anchor();
12562 let head = selection.head();
12563 let tail = selection.tail();
12564
12565 let Some((buffer, start_position)) =
12566 self.buffer.read(cx).text_anchor_for_position(head, cx)
12567 else {
12568 return;
12569 };
12570
12571 let end_position = if head != tail {
12572 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
12573 return;
12574 };
12575 Some(pos)
12576 } else {
12577 None
12578 };
12579
12580 let url_finder = cx.spawn_in(window, async move |editor, cx| {
12581 let url = if let Some(end_pos) = end_position {
12582 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
12583 } else {
12584 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
12585 };
12586
12587 if let Some(url) = url {
12588 editor.update(cx, |_, cx| {
12589 cx.open_url(&url);
12590 })
12591 } else {
12592 Ok(())
12593 }
12594 });
12595
12596 url_finder.detach();
12597 }
12598
12599 pub fn open_selected_filename(
12600 &mut self,
12601 _: &OpenSelectedFilename,
12602 window: &mut Window,
12603 cx: &mut Context<Self>,
12604 ) {
12605 let Some(workspace) = self.workspace() else {
12606 return;
12607 };
12608
12609 let position = self.selections.newest_anchor().head();
12610
12611 let Some((buffer, buffer_position)) =
12612 self.buffer.read(cx).text_anchor_for_position(position, cx)
12613 else {
12614 return;
12615 };
12616
12617 let project = self.project.clone();
12618
12619 cx.spawn_in(window, async move |_, cx| {
12620 let result = find_file(&buffer, project, buffer_position, cx).await;
12621
12622 if let Some((_, path)) = result {
12623 workspace
12624 .update_in(cx, |workspace, window, cx| {
12625 workspace.open_resolved_path(path, window, cx)
12626 })?
12627 .await?;
12628 }
12629 anyhow::Ok(())
12630 })
12631 .detach();
12632 }
12633
12634 pub(crate) fn navigate_to_hover_links(
12635 &mut self,
12636 kind: Option<GotoDefinitionKind>,
12637 mut definitions: Vec<HoverLink>,
12638 split: bool,
12639 window: &mut Window,
12640 cx: &mut Context<Editor>,
12641 ) -> Task<Result<Navigated>> {
12642 // If there is one definition, just open it directly
12643 if definitions.len() == 1 {
12644 let definition = definitions.pop().unwrap();
12645
12646 enum TargetTaskResult {
12647 Location(Option<Location>),
12648 AlreadyNavigated,
12649 }
12650
12651 let target_task = match definition {
12652 HoverLink::Text(link) => {
12653 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
12654 }
12655 HoverLink::InlayHint(lsp_location, server_id) => {
12656 let computation =
12657 self.compute_target_location(lsp_location, server_id, window, cx);
12658 cx.background_spawn(async move {
12659 let location = computation.await?;
12660 Ok(TargetTaskResult::Location(location))
12661 })
12662 }
12663 HoverLink::Url(url) => {
12664 cx.open_url(&url);
12665 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
12666 }
12667 HoverLink::File(path) => {
12668 if let Some(workspace) = self.workspace() {
12669 cx.spawn_in(window, async move |_, cx| {
12670 workspace
12671 .update_in(cx, |workspace, window, cx| {
12672 workspace.open_resolved_path(path, window, cx)
12673 })?
12674 .await
12675 .map(|_| TargetTaskResult::AlreadyNavigated)
12676 })
12677 } else {
12678 Task::ready(Ok(TargetTaskResult::Location(None)))
12679 }
12680 }
12681 };
12682 cx.spawn_in(window, async move |editor, cx| {
12683 let target = match target_task.await.context("target resolution task")? {
12684 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
12685 TargetTaskResult::Location(None) => return Ok(Navigated::No),
12686 TargetTaskResult::Location(Some(target)) => target,
12687 };
12688
12689 editor.update_in(cx, |editor, window, cx| {
12690 let Some(workspace) = editor.workspace() else {
12691 return Navigated::No;
12692 };
12693 let pane = workspace.read(cx).active_pane().clone();
12694
12695 let range = target.range.to_point(target.buffer.read(cx));
12696 let range = editor.range_for_match(&range);
12697 let range = collapse_multiline_range(range);
12698
12699 if !split
12700 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
12701 {
12702 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
12703 } else {
12704 window.defer(cx, move |window, cx| {
12705 let target_editor: Entity<Self> =
12706 workspace.update(cx, |workspace, cx| {
12707 let pane = if split {
12708 workspace.adjacent_pane(window, cx)
12709 } else {
12710 workspace.active_pane().clone()
12711 };
12712
12713 workspace.open_project_item(
12714 pane,
12715 target.buffer.clone(),
12716 true,
12717 true,
12718 window,
12719 cx,
12720 )
12721 });
12722 target_editor.update(cx, |target_editor, cx| {
12723 // When selecting a definition in a different buffer, disable the nav history
12724 // to avoid creating a history entry at the previous cursor location.
12725 pane.update(cx, |pane, _| pane.disable_history());
12726 target_editor.go_to_singleton_buffer_range(range, window, cx);
12727 pane.update(cx, |pane, _| pane.enable_history());
12728 });
12729 });
12730 }
12731 Navigated::Yes
12732 })
12733 })
12734 } else if !definitions.is_empty() {
12735 cx.spawn_in(window, async move |editor, cx| {
12736 let (title, location_tasks, workspace) = editor
12737 .update_in(cx, |editor, window, cx| {
12738 let tab_kind = match kind {
12739 Some(GotoDefinitionKind::Implementation) => "Implementations",
12740 _ => "Definitions",
12741 };
12742 let title = definitions
12743 .iter()
12744 .find_map(|definition| match definition {
12745 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
12746 let buffer = origin.buffer.read(cx);
12747 format!(
12748 "{} for {}",
12749 tab_kind,
12750 buffer
12751 .text_for_range(origin.range.clone())
12752 .collect::<String>()
12753 )
12754 }),
12755 HoverLink::InlayHint(_, _) => None,
12756 HoverLink::Url(_) => None,
12757 HoverLink::File(_) => None,
12758 })
12759 .unwrap_or(tab_kind.to_string());
12760 let location_tasks = definitions
12761 .into_iter()
12762 .map(|definition| match definition {
12763 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
12764 HoverLink::InlayHint(lsp_location, server_id) => editor
12765 .compute_target_location(lsp_location, server_id, window, cx),
12766 HoverLink::Url(_) => Task::ready(Ok(None)),
12767 HoverLink::File(_) => Task::ready(Ok(None)),
12768 })
12769 .collect::<Vec<_>>();
12770 (title, location_tasks, editor.workspace().clone())
12771 })
12772 .context("location tasks preparation")?;
12773
12774 let locations = future::join_all(location_tasks)
12775 .await
12776 .into_iter()
12777 .filter_map(|location| location.transpose())
12778 .collect::<Result<_>>()
12779 .context("location tasks")?;
12780
12781 let Some(workspace) = workspace else {
12782 return Ok(Navigated::No);
12783 };
12784 let opened = workspace
12785 .update_in(cx, |workspace, window, cx| {
12786 Self::open_locations_in_multibuffer(
12787 workspace,
12788 locations,
12789 title,
12790 split,
12791 MultibufferSelectionMode::First,
12792 window,
12793 cx,
12794 )
12795 })
12796 .ok();
12797
12798 anyhow::Ok(Navigated::from_bool(opened.is_some()))
12799 })
12800 } else {
12801 Task::ready(Ok(Navigated::No))
12802 }
12803 }
12804
12805 fn compute_target_location(
12806 &self,
12807 lsp_location: lsp::Location,
12808 server_id: LanguageServerId,
12809 window: &mut Window,
12810 cx: &mut Context<Self>,
12811 ) -> Task<anyhow::Result<Option<Location>>> {
12812 let Some(project) = self.project.clone() else {
12813 return Task::ready(Ok(None));
12814 };
12815
12816 cx.spawn_in(window, async move |editor, cx| {
12817 let location_task = editor.update(cx, |_, cx| {
12818 project.update(cx, |project, cx| {
12819 let language_server_name = project
12820 .language_server_statuses(cx)
12821 .find(|(id, _)| server_id == *id)
12822 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
12823 language_server_name.map(|language_server_name| {
12824 project.open_local_buffer_via_lsp(
12825 lsp_location.uri.clone(),
12826 server_id,
12827 language_server_name,
12828 cx,
12829 )
12830 })
12831 })
12832 })?;
12833 let location = match location_task {
12834 Some(task) => Some({
12835 let target_buffer_handle = task.await.context("open local buffer")?;
12836 let range = target_buffer_handle.update(cx, |target_buffer, _| {
12837 let target_start = target_buffer
12838 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
12839 let target_end = target_buffer
12840 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
12841 target_buffer.anchor_after(target_start)
12842 ..target_buffer.anchor_before(target_end)
12843 })?;
12844 Location {
12845 buffer: target_buffer_handle,
12846 range,
12847 }
12848 }),
12849 None => None,
12850 };
12851 Ok(location)
12852 })
12853 }
12854
12855 pub fn find_all_references(
12856 &mut self,
12857 _: &FindAllReferences,
12858 window: &mut Window,
12859 cx: &mut Context<Self>,
12860 ) -> Option<Task<Result<Navigated>>> {
12861 let selection = self.selections.newest::<usize>(cx);
12862 let multi_buffer = self.buffer.read(cx);
12863 let head = selection.head();
12864
12865 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
12866 let head_anchor = multi_buffer_snapshot.anchor_at(
12867 head,
12868 if head < selection.tail() {
12869 Bias::Right
12870 } else {
12871 Bias::Left
12872 },
12873 );
12874
12875 match self
12876 .find_all_references_task_sources
12877 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
12878 {
12879 Ok(_) => {
12880 log::info!(
12881 "Ignoring repeated FindAllReferences invocation with the position of already running task"
12882 );
12883 return None;
12884 }
12885 Err(i) => {
12886 self.find_all_references_task_sources.insert(i, head_anchor);
12887 }
12888 }
12889
12890 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
12891 let workspace = self.workspace()?;
12892 let project = workspace.read(cx).project().clone();
12893 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
12894 Some(cx.spawn_in(window, async move |editor, cx| {
12895 let _cleanup = cx.on_drop(&editor, move |editor, _| {
12896 if let Ok(i) = editor
12897 .find_all_references_task_sources
12898 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
12899 {
12900 editor.find_all_references_task_sources.remove(i);
12901 }
12902 });
12903
12904 let locations = references.await?;
12905 if locations.is_empty() {
12906 return anyhow::Ok(Navigated::No);
12907 }
12908
12909 workspace.update_in(cx, |workspace, window, cx| {
12910 let title = locations
12911 .first()
12912 .as_ref()
12913 .map(|location| {
12914 let buffer = location.buffer.read(cx);
12915 format!(
12916 "References to `{}`",
12917 buffer
12918 .text_for_range(location.range.clone())
12919 .collect::<String>()
12920 )
12921 })
12922 .unwrap();
12923 Self::open_locations_in_multibuffer(
12924 workspace,
12925 locations,
12926 title,
12927 false,
12928 MultibufferSelectionMode::First,
12929 window,
12930 cx,
12931 );
12932 Navigated::Yes
12933 })
12934 }))
12935 }
12936
12937 /// Opens a multibuffer with the given project locations in it
12938 pub fn open_locations_in_multibuffer(
12939 workspace: &mut Workspace,
12940 mut locations: Vec<Location>,
12941 title: String,
12942 split: bool,
12943 multibuffer_selection_mode: MultibufferSelectionMode,
12944 window: &mut Window,
12945 cx: &mut Context<Workspace>,
12946 ) {
12947 // If there are multiple definitions, open them in a multibuffer
12948 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
12949 let mut locations = locations.into_iter().peekable();
12950 let mut ranges = Vec::new();
12951 let capability = workspace.project().read(cx).capability();
12952
12953 let excerpt_buffer = cx.new(|cx| {
12954 let mut multibuffer = MultiBuffer::new(capability);
12955 while let Some(location) = locations.next() {
12956 let buffer = location.buffer.read(cx);
12957 let mut ranges_for_buffer = Vec::new();
12958 let range = location.range.to_offset(buffer);
12959 ranges_for_buffer.push(range.clone());
12960
12961 while let Some(next_location) = locations.peek() {
12962 if next_location.buffer == location.buffer {
12963 ranges_for_buffer.push(next_location.range.to_offset(buffer));
12964 locations.next();
12965 } else {
12966 break;
12967 }
12968 }
12969
12970 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
12971 ranges.extend(multibuffer.push_excerpts_with_context_lines(
12972 location.buffer.clone(),
12973 ranges_for_buffer,
12974 DEFAULT_MULTIBUFFER_CONTEXT,
12975 cx,
12976 ))
12977 }
12978
12979 multibuffer.with_title(title)
12980 });
12981
12982 let editor = cx.new(|cx| {
12983 Editor::for_multibuffer(
12984 excerpt_buffer,
12985 Some(workspace.project().clone()),
12986 window,
12987 cx,
12988 )
12989 });
12990 editor.update(cx, |editor, cx| {
12991 match multibuffer_selection_mode {
12992 MultibufferSelectionMode::First => {
12993 if let Some(first_range) = ranges.first() {
12994 editor.change_selections(None, window, cx, |selections| {
12995 selections.clear_disjoint();
12996 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
12997 });
12998 }
12999 editor.highlight_background::<Self>(
13000 &ranges,
13001 |theme| theme.editor_highlighted_line_background,
13002 cx,
13003 );
13004 }
13005 MultibufferSelectionMode::All => {
13006 editor.change_selections(None, window, cx, |selections| {
13007 selections.clear_disjoint();
13008 selections.select_anchor_ranges(ranges);
13009 });
13010 }
13011 }
13012 editor.register_buffers_with_language_servers(cx);
13013 });
13014
13015 let item = Box::new(editor);
13016 let item_id = item.item_id();
13017
13018 if split {
13019 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
13020 } else {
13021 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
13022 let (preview_item_id, preview_item_idx) =
13023 workspace.active_pane().update(cx, |pane, _| {
13024 (pane.preview_item_id(), pane.preview_item_idx())
13025 });
13026
13027 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
13028
13029 if let Some(preview_item_id) = preview_item_id {
13030 workspace.active_pane().update(cx, |pane, cx| {
13031 pane.remove_item(preview_item_id, false, false, window, cx);
13032 });
13033 }
13034 } else {
13035 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
13036 }
13037 }
13038 workspace.active_pane().update(cx, |pane, cx| {
13039 pane.set_preview_item_id(Some(item_id), cx);
13040 });
13041 }
13042
13043 pub fn rename(
13044 &mut self,
13045 _: &Rename,
13046 window: &mut Window,
13047 cx: &mut Context<Self>,
13048 ) -> Option<Task<Result<()>>> {
13049 use language::ToOffset as _;
13050
13051 let provider = self.semantics_provider.clone()?;
13052 let selection = self.selections.newest_anchor().clone();
13053 let (cursor_buffer, cursor_buffer_position) = self
13054 .buffer
13055 .read(cx)
13056 .text_anchor_for_position(selection.head(), cx)?;
13057 let (tail_buffer, cursor_buffer_position_end) = self
13058 .buffer
13059 .read(cx)
13060 .text_anchor_for_position(selection.tail(), cx)?;
13061 if tail_buffer != cursor_buffer {
13062 return None;
13063 }
13064
13065 let snapshot = cursor_buffer.read(cx).snapshot();
13066 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
13067 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
13068 let prepare_rename = provider
13069 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
13070 .unwrap_or_else(|| Task::ready(Ok(None)));
13071 drop(snapshot);
13072
13073 Some(cx.spawn_in(window, async move |this, cx| {
13074 let rename_range = if let Some(range) = prepare_rename.await? {
13075 Some(range)
13076 } else {
13077 this.update(cx, |this, cx| {
13078 let buffer = this.buffer.read(cx).snapshot(cx);
13079 let mut buffer_highlights = this
13080 .document_highlights_for_position(selection.head(), &buffer)
13081 .filter(|highlight| {
13082 highlight.start.excerpt_id == selection.head().excerpt_id
13083 && highlight.end.excerpt_id == selection.head().excerpt_id
13084 });
13085 buffer_highlights
13086 .next()
13087 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
13088 })?
13089 };
13090 if let Some(rename_range) = rename_range {
13091 this.update_in(cx, |this, window, cx| {
13092 let snapshot = cursor_buffer.read(cx).snapshot();
13093 let rename_buffer_range = rename_range.to_offset(&snapshot);
13094 let cursor_offset_in_rename_range =
13095 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
13096 let cursor_offset_in_rename_range_end =
13097 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
13098
13099 this.take_rename(false, window, cx);
13100 let buffer = this.buffer.read(cx).read(cx);
13101 let cursor_offset = selection.head().to_offset(&buffer);
13102 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
13103 let rename_end = rename_start + rename_buffer_range.len();
13104 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
13105 let mut old_highlight_id = None;
13106 let old_name: Arc<str> = buffer
13107 .chunks(rename_start..rename_end, true)
13108 .map(|chunk| {
13109 if old_highlight_id.is_none() {
13110 old_highlight_id = chunk.syntax_highlight_id;
13111 }
13112 chunk.text
13113 })
13114 .collect::<String>()
13115 .into();
13116
13117 drop(buffer);
13118
13119 // Position the selection in the rename editor so that it matches the current selection.
13120 this.show_local_selections = false;
13121 let rename_editor = cx.new(|cx| {
13122 let mut editor = Editor::single_line(window, cx);
13123 editor.buffer.update(cx, |buffer, cx| {
13124 buffer.edit([(0..0, old_name.clone())], None, cx)
13125 });
13126 let rename_selection_range = match cursor_offset_in_rename_range
13127 .cmp(&cursor_offset_in_rename_range_end)
13128 {
13129 Ordering::Equal => {
13130 editor.select_all(&SelectAll, window, cx);
13131 return editor;
13132 }
13133 Ordering::Less => {
13134 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
13135 }
13136 Ordering::Greater => {
13137 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
13138 }
13139 };
13140 if rename_selection_range.end > old_name.len() {
13141 editor.select_all(&SelectAll, window, cx);
13142 } else {
13143 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13144 s.select_ranges([rename_selection_range]);
13145 });
13146 }
13147 editor
13148 });
13149 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
13150 if e == &EditorEvent::Focused {
13151 cx.emit(EditorEvent::FocusedIn)
13152 }
13153 })
13154 .detach();
13155
13156 let write_highlights =
13157 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
13158 let read_highlights =
13159 this.clear_background_highlights::<DocumentHighlightRead>(cx);
13160 let ranges = write_highlights
13161 .iter()
13162 .flat_map(|(_, ranges)| ranges.iter())
13163 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
13164 .cloned()
13165 .collect();
13166
13167 this.highlight_text::<Rename>(
13168 ranges,
13169 HighlightStyle {
13170 fade_out: Some(0.6),
13171 ..Default::default()
13172 },
13173 cx,
13174 );
13175 let rename_focus_handle = rename_editor.focus_handle(cx);
13176 window.focus(&rename_focus_handle);
13177 let block_id = this.insert_blocks(
13178 [BlockProperties {
13179 style: BlockStyle::Flex,
13180 placement: BlockPlacement::Below(range.start),
13181 height: 1,
13182 render: Arc::new({
13183 let rename_editor = rename_editor.clone();
13184 move |cx: &mut BlockContext| {
13185 let mut text_style = cx.editor_style.text.clone();
13186 if let Some(highlight_style) = old_highlight_id
13187 .and_then(|h| h.style(&cx.editor_style.syntax))
13188 {
13189 text_style = text_style.highlight(highlight_style);
13190 }
13191 div()
13192 .block_mouse_down()
13193 .pl(cx.anchor_x)
13194 .child(EditorElement::new(
13195 &rename_editor,
13196 EditorStyle {
13197 background: cx.theme().system().transparent,
13198 local_player: cx.editor_style.local_player,
13199 text: text_style,
13200 scrollbar_width: cx.editor_style.scrollbar_width,
13201 syntax: cx.editor_style.syntax.clone(),
13202 status: cx.editor_style.status.clone(),
13203 inlay_hints_style: HighlightStyle {
13204 font_weight: Some(FontWeight::BOLD),
13205 ..make_inlay_hints_style(cx.app)
13206 },
13207 inline_completion_styles: make_suggestion_styles(
13208 cx.app,
13209 ),
13210 ..EditorStyle::default()
13211 },
13212 ))
13213 .into_any_element()
13214 }
13215 }),
13216 priority: 0,
13217 }],
13218 Some(Autoscroll::fit()),
13219 cx,
13220 )[0];
13221 this.pending_rename = Some(RenameState {
13222 range,
13223 old_name,
13224 editor: rename_editor,
13225 block_id,
13226 });
13227 })?;
13228 }
13229
13230 Ok(())
13231 }))
13232 }
13233
13234 pub fn confirm_rename(
13235 &mut self,
13236 _: &ConfirmRename,
13237 window: &mut Window,
13238 cx: &mut Context<Self>,
13239 ) -> Option<Task<Result<()>>> {
13240 let rename = self.take_rename(false, window, cx)?;
13241 let workspace = self.workspace()?.downgrade();
13242 let (buffer, start) = self
13243 .buffer
13244 .read(cx)
13245 .text_anchor_for_position(rename.range.start, cx)?;
13246 let (end_buffer, _) = self
13247 .buffer
13248 .read(cx)
13249 .text_anchor_for_position(rename.range.end, cx)?;
13250 if buffer != end_buffer {
13251 return None;
13252 }
13253
13254 let old_name = rename.old_name;
13255 let new_name = rename.editor.read(cx).text(cx);
13256
13257 let rename = self.semantics_provider.as_ref()?.perform_rename(
13258 &buffer,
13259 start,
13260 new_name.clone(),
13261 cx,
13262 )?;
13263
13264 Some(cx.spawn_in(window, async move |editor, cx| {
13265 let project_transaction = rename.await?;
13266 Self::open_project_transaction(
13267 &editor,
13268 workspace,
13269 project_transaction,
13270 format!("Rename: {} → {}", old_name, new_name),
13271 cx,
13272 )
13273 .await?;
13274
13275 editor.update(cx, |editor, cx| {
13276 editor.refresh_document_highlights(cx);
13277 })?;
13278 Ok(())
13279 }))
13280 }
13281
13282 fn take_rename(
13283 &mut self,
13284 moving_cursor: bool,
13285 window: &mut Window,
13286 cx: &mut Context<Self>,
13287 ) -> Option<RenameState> {
13288 let rename = self.pending_rename.take()?;
13289 if rename.editor.focus_handle(cx).is_focused(window) {
13290 window.focus(&self.focus_handle);
13291 }
13292
13293 self.remove_blocks(
13294 [rename.block_id].into_iter().collect(),
13295 Some(Autoscroll::fit()),
13296 cx,
13297 );
13298 self.clear_highlights::<Rename>(cx);
13299 self.show_local_selections = true;
13300
13301 if moving_cursor {
13302 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
13303 editor.selections.newest::<usize>(cx).head()
13304 });
13305
13306 // Update the selection to match the position of the selection inside
13307 // the rename editor.
13308 let snapshot = self.buffer.read(cx).read(cx);
13309 let rename_range = rename.range.to_offset(&snapshot);
13310 let cursor_in_editor = snapshot
13311 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
13312 .min(rename_range.end);
13313 drop(snapshot);
13314
13315 self.change_selections(None, window, cx, |s| {
13316 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
13317 });
13318 } else {
13319 self.refresh_document_highlights(cx);
13320 }
13321
13322 Some(rename)
13323 }
13324
13325 pub fn pending_rename(&self) -> Option<&RenameState> {
13326 self.pending_rename.as_ref()
13327 }
13328
13329 fn format(
13330 &mut self,
13331 _: &Format,
13332 window: &mut Window,
13333 cx: &mut Context<Self>,
13334 ) -> Option<Task<Result<()>>> {
13335 let project = match &self.project {
13336 Some(project) => project.clone(),
13337 None => return None,
13338 };
13339
13340 Some(self.perform_format(
13341 project,
13342 FormatTrigger::Manual,
13343 FormatTarget::Buffers,
13344 window,
13345 cx,
13346 ))
13347 }
13348
13349 fn format_selections(
13350 &mut self,
13351 _: &FormatSelections,
13352 window: &mut Window,
13353 cx: &mut Context<Self>,
13354 ) -> Option<Task<Result<()>>> {
13355 let project = match &self.project {
13356 Some(project) => project.clone(),
13357 None => return None,
13358 };
13359
13360 let ranges = self
13361 .selections
13362 .all_adjusted(cx)
13363 .into_iter()
13364 .map(|selection| selection.range())
13365 .collect_vec();
13366
13367 Some(self.perform_format(
13368 project,
13369 FormatTrigger::Manual,
13370 FormatTarget::Ranges(ranges),
13371 window,
13372 cx,
13373 ))
13374 }
13375
13376 fn perform_format(
13377 &mut self,
13378 project: Entity<Project>,
13379 trigger: FormatTrigger,
13380 target: FormatTarget,
13381 window: &mut Window,
13382 cx: &mut Context<Self>,
13383 ) -> Task<Result<()>> {
13384 let buffer = self.buffer.clone();
13385 let (buffers, target) = match target {
13386 FormatTarget::Buffers => {
13387 let mut buffers = buffer.read(cx).all_buffers();
13388 if trigger == FormatTrigger::Save {
13389 buffers.retain(|buffer| buffer.read(cx).is_dirty());
13390 }
13391 (buffers, LspFormatTarget::Buffers)
13392 }
13393 FormatTarget::Ranges(selection_ranges) => {
13394 let multi_buffer = buffer.read(cx);
13395 let snapshot = multi_buffer.read(cx);
13396 let mut buffers = HashSet::default();
13397 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
13398 BTreeMap::new();
13399 for selection_range in selection_ranges {
13400 for (buffer, buffer_range, _) in
13401 snapshot.range_to_buffer_ranges(selection_range)
13402 {
13403 let buffer_id = buffer.remote_id();
13404 let start = buffer.anchor_before(buffer_range.start);
13405 let end = buffer.anchor_after(buffer_range.end);
13406 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
13407 buffer_id_to_ranges
13408 .entry(buffer_id)
13409 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
13410 .or_insert_with(|| vec![start..end]);
13411 }
13412 }
13413 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
13414 }
13415 };
13416
13417 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
13418 let format = project.update(cx, |project, cx| {
13419 project.format(buffers, target, true, trigger, cx)
13420 });
13421
13422 cx.spawn_in(window, async move |_, cx| {
13423 let transaction = futures::select_biased! {
13424 transaction = format.log_err().fuse() => transaction,
13425 () = timeout => {
13426 log::warn!("timed out waiting for formatting");
13427 None
13428 }
13429 };
13430
13431 buffer
13432 .update(cx, |buffer, cx| {
13433 if let Some(transaction) = transaction {
13434 if !buffer.is_singleton() {
13435 buffer.push_transaction(&transaction.0, cx);
13436 }
13437 }
13438 cx.notify();
13439 })
13440 .ok();
13441
13442 Ok(())
13443 })
13444 }
13445
13446 fn organize_imports(
13447 &mut self,
13448 _: &OrganizeImports,
13449 window: &mut Window,
13450 cx: &mut Context<Self>,
13451 ) -> Option<Task<Result<()>>> {
13452 let project = match &self.project {
13453 Some(project) => project.clone(),
13454 None => return None,
13455 };
13456 Some(self.perform_code_action_kind(
13457 project,
13458 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
13459 window,
13460 cx,
13461 ))
13462 }
13463
13464 fn perform_code_action_kind(
13465 &mut self,
13466 project: Entity<Project>,
13467 kind: CodeActionKind,
13468 window: &mut Window,
13469 cx: &mut Context<Self>,
13470 ) -> Task<Result<()>> {
13471 let buffer = self.buffer.clone();
13472 let buffers = buffer.read(cx).all_buffers();
13473 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
13474 let apply_action = project.update(cx, |project, cx| {
13475 project.apply_code_action_kind(buffers, kind, true, cx)
13476 });
13477 cx.spawn_in(window, async move |_, cx| {
13478 let transaction = futures::select_biased! {
13479 () = timeout => {
13480 log::warn!("timed out waiting for executing code action");
13481 None
13482 }
13483 transaction = apply_action.log_err().fuse() => transaction,
13484 };
13485 buffer
13486 .update(cx, |buffer, cx| {
13487 // check if we need this
13488 if let Some(transaction) = transaction {
13489 if !buffer.is_singleton() {
13490 buffer.push_transaction(&transaction.0, cx);
13491 }
13492 }
13493 cx.notify();
13494 })
13495 .ok();
13496 Ok(())
13497 })
13498 }
13499
13500 fn restart_language_server(
13501 &mut self,
13502 _: &RestartLanguageServer,
13503 _: &mut Window,
13504 cx: &mut Context<Self>,
13505 ) {
13506 if let Some(project) = self.project.clone() {
13507 self.buffer.update(cx, |multi_buffer, cx| {
13508 project.update(cx, |project, cx| {
13509 project.restart_language_servers_for_buffers(
13510 multi_buffer.all_buffers().into_iter().collect(),
13511 cx,
13512 );
13513 });
13514 })
13515 }
13516 }
13517
13518 fn cancel_language_server_work(
13519 workspace: &mut Workspace,
13520 _: &actions::CancelLanguageServerWork,
13521 _: &mut Window,
13522 cx: &mut Context<Workspace>,
13523 ) {
13524 let project = workspace.project();
13525 let buffers = workspace
13526 .active_item(cx)
13527 .and_then(|item| item.act_as::<Editor>(cx))
13528 .map_or(HashSet::default(), |editor| {
13529 editor.read(cx).buffer.read(cx).all_buffers()
13530 });
13531 project.update(cx, |project, cx| {
13532 project.cancel_language_server_work_for_buffers(buffers, cx);
13533 });
13534 }
13535
13536 fn show_character_palette(
13537 &mut self,
13538 _: &ShowCharacterPalette,
13539 window: &mut Window,
13540 _: &mut Context<Self>,
13541 ) {
13542 window.show_character_palette();
13543 }
13544
13545 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
13546 if let Some(active_diagnostics) = self.active_diagnostics.as_mut() {
13547 let buffer = self.buffer.read(cx).snapshot(cx);
13548 let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer);
13549 let primary_range_end = active_diagnostics.primary_range.end.to_offset(&buffer);
13550 let is_valid = buffer
13551 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
13552 .any(|entry| {
13553 entry.diagnostic.is_primary
13554 && !entry.range.is_empty()
13555 && entry.range.start == primary_range_start
13556 && entry.diagnostic.message == active_diagnostics.primary_message
13557 });
13558
13559 if is_valid != active_diagnostics.is_valid {
13560 active_diagnostics.is_valid = is_valid;
13561 if is_valid {
13562 let mut new_styles = HashMap::default();
13563 for (block_id, diagnostic) in &active_diagnostics.blocks {
13564 new_styles.insert(
13565 *block_id,
13566 diagnostic_block_renderer(diagnostic.clone(), None, true),
13567 );
13568 }
13569 self.display_map.update(cx, |display_map, _cx| {
13570 display_map.replace_blocks(new_styles);
13571 });
13572 } else {
13573 self.dismiss_diagnostics(cx);
13574 }
13575 }
13576 }
13577 }
13578
13579 fn activate_diagnostics(
13580 &mut self,
13581 buffer_id: BufferId,
13582 group_id: usize,
13583 window: &mut Window,
13584 cx: &mut Context<Self>,
13585 ) {
13586 self.dismiss_diagnostics(cx);
13587 let snapshot = self.snapshot(window, cx);
13588 self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
13589 let buffer = self.buffer.read(cx).snapshot(cx);
13590
13591 let mut primary_range = None;
13592 let mut primary_message = None;
13593 let diagnostic_group = buffer
13594 .diagnostic_group(buffer_id, group_id)
13595 .filter_map(|entry| {
13596 let start = entry.range.start;
13597 let end = entry.range.end;
13598 if snapshot.is_line_folded(MultiBufferRow(start.row))
13599 && (start.row == end.row
13600 || snapshot.is_line_folded(MultiBufferRow(end.row)))
13601 {
13602 return None;
13603 }
13604 if entry.diagnostic.is_primary {
13605 primary_range = Some(entry.range.clone());
13606 primary_message = Some(entry.diagnostic.message.clone());
13607 }
13608 Some(entry)
13609 })
13610 .collect::<Vec<_>>();
13611 let primary_range = primary_range?;
13612 let primary_message = primary_message?;
13613
13614 let blocks = display_map
13615 .insert_blocks(
13616 diagnostic_group.iter().map(|entry| {
13617 let diagnostic = entry.diagnostic.clone();
13618 let message_height = diagnostic.message.matches('\n').count() as u32 + 1;
13619 BlockProperties {
13620 style: BlockStyle::Fixed,
13621 placement: BlockPlacement::Below(
13622 buffer.anchor_after(entry.range.start),
13623 ),
13624 height: message_height,
13625 render: diagnostic_block_renderer(diagnostic, None, true),
13626 priority: 0,
13627 }
13628 }),
13629 cx,
13630 )
13631 .into_iter()
13632 .zip(diagnostic_group.into_iter().map(|entry| entry.diagnostic))
13633 .collect();
13634
13635 Some(ActiveDiagnosticGroup {
13636 primary_range: buffer.anchor_before(primary_range.start)
13637 ..buffer.anchor_after(primary_range.end),
13638 primary_message,
13639 group_id,
13640 blocks,
13641 is_valid: true,
13642 })
13643 });
13644 }
13645
13646 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
13647 if let Some(active_diagnostic_group) = self.active_diagnostics.take() {
13648 self.display_map.update(cx, |display_map, cx| {
13649 display_map.remove_blocks(active_diagnostic_group.blocks.into_keys().collect(), cx);
13650 });
13651 cx.notify();
13652 }
13653 }
13654
13655 /// Disable inline diagnostics rendering for this editor.
13656 pub fn disable_inline_diagnostics(&mut self) {
13657 self.inline_diagnostics_enabled = false;
13658 self.inline_diagnostics_update = Task::ready(());
13659 self.inline_diagnostics.clear();
13660 }
13661
13662 pub fn inline_diagnostics_enabled(&self) -> bool {
13663 self.inline_diagnostics_enabled
13664 }
13665
13666 pub fn show_inline_diagnostics(&self) -> bool {
13667 self.show_inline_diagnostics
13668 }
13669
13670 pub fn toggle_inline_diagnostics(
13671 &mut self,
13672 _: &ToggleInlineDiagnostics,
13673 window: &mut Window,
13674 cx: &mut Context<'_, Editor>,
13675 ) {
13676 self.show_inline_diagnostics = !self.show_inline_diagnostics;
13677 self.refresh_inline_diagnostics(false, window, cx);
13678 }
13679
13680 fn refresh_inline_diagnostics(
13681 &mut self,
13682 debounce: bool,
13683 window: &mut Window,
13684 cx: &mut Context<Self>,
13685 ) {
13686 if !self.inline_diagnostics_enabled || !self.show_inline_diagnostics {
13687 self.inline_diagnostics_update = Task::ready(());
13688 self.inline_diagnostics.clear();
13689 return;
13690 }
13691
13692 let debounce_ms = ProjectSettings::get_global(cx)
13693 .diagnostics
13694 .inline
13695 .update_debounce_ms;
13696 let debounce = if debounce && debounce_ms > 0 {
13697 Some(Duration::from_millis(debounce_ms))
13698 } else {
13699 None
13700 };
13701 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
13702 if let Some(debounce) = debounce {
13703 cx.background_executor().timer(debounce).await;
13704 }
13705 let Some(snapshot) = editor
13706 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
13707 .ok()
13708 else {
13709 return;
13710 };
13711
13712 let new_inline_diagnostics = cx
13713 .background_spawn(async move {
13714 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
13715 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
13716 let message = diagnostic_entry
13717 .diagnostic
13718 .message
13719 .split_once('\n')
13720 .map(|(line, _)| line)
13721 .map(SharedString::new)
13722 .unwrap_or_else(|| {
13723 SharedString::from(diagnostic_entry.diagnostic.message)
13724 });
13725 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
13726 let (Ok(i) | Err(i)) = inline_diagnostics
13727 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
13728 inline_diagnostics.insert(
13729 i,
13730 (
13731 start_anchor,
13732 InlineDiagnostic {
13733 message,
13734 group_id: diagnostic_entry.diagnostic.group_id,
13735 start: diagnostic_entry.range.start.to_point(&snapshot),
13736 is_primary: diagnostic_entry.diagnostic.is_primary,
13737 severity: diagnostic_entry.diagnostic.severity,
13738 },
13739 ),
13740 );
13741 }
13742 inline_diagnostics
13743 })
13744 .await;
13745
13746 editor
13747 .update(cx, |editor, cx| {
13748 editor.inline_diagnostics = new_inline_diagnostics;
13749 cx.notify();
13750 })
13751 .ok();
13752 });
13753 }
13754
13755 pub fn set_selections_from_remote(
13756 &mut self,
13757 selections: Vec<Selection<Anchor>>,
13758 pending_selection: Option<Selection<Anchor>>,
13759 window: &mut Window,
13760 cx: &mut Context<Self>,
13761 ) {
13762 let old_cursor_position = self.selections.newest_anchor().head();
13763 self.selections.change_with(cx, |s| {
13764 s.select_anchors(selections);
13765 if let Some(pending_selection) = pending_selection {
13766 s.set_pending(pending_selection, SelectMode::Character);
13767 } else {
13768 s.clear_pending();
13769 }
13770 });
13771 self.selections_did_change(false, &old_cursor_position, true, window, cx);
13772 }
13773
13774 fn push_to_selection_history(&mut self) {
13775 self.selection_history.push(SelectionHistoryEntry {
13776 selections: self.selections.disjoint_anchors(),
13777 select_next_state: self.select_next_state.clone(),
13778 select_prev_state: self.select_prev_state.clone(),
13779 add_selections_state: self.add_selections_state.clone(),
13780 });
13781 }
13782
13783 pub fn transact(
13784 &mut self,
13785 window: &mut Window,
13786 cx: &mut Context<Self>,
13787 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
13788 ) -> Option<TransactionId> {
13789 self.start_transaction_at(Instant::now(), window, cx);
13790 update(self, window, cx);
13791 self.end_transaction_at(Instant::now(), cx)
13792 }
13793
13794 pub fn start_transaction_at(
13795 &mut self,
13796 now: Instant,
13797 window: &mut Window,
13798 cx: &mut Context<Self>,
13799 ) {
13800 self.end_selection(window, cx);
13801 if let Some(tx_id) = self
13802 .buffer
13803 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
13804 {
13805 self.selection_history
13806 .insert_transaction(tx_id, self.selections.disjoint_anchors());
13807 cx.emit(EditorEvent::TransactionBegun {
13808 transaction_id: tx_id,
13809 })
13810 }
13811 }
13812
13813 pub fn end_transaction_at(
13814 &mut self,
13815 now: Instant,
13816 cx: &mut Context<Self>,
13817 ) -> Option<TransactionId> {
13818 if let Some(transaction_id) = self
13819 .buffer
13820 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
13821 {
13822 if let Some((_, end_selections)) =
13823 self.selection_history.transaction_mut(transaction_id)
13824 {
13825 *end_selections = Some(self.selections.disjoint_anchors());
13826 } else {
13827 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
13828 }
13829
13830 cx.emit(EditorEvent::Edited { transaction_id });
13831 Some(transaction_id)
13832 } else {
13833 None
13834 }
13835 }
13836
13837 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
13838 if self.selection_mark_mode {
13839 self.change_selections(None, window, cx, |s| {
13840 s.move_with(|_, sel| {
13841 sel.collapse_to(sel.head(), SelectionGoal::None);
13842 });
13843 })
13844 }
13845 self.selection_mark_mode = true;
13846 cx.notify();
13847 }
13848
13849 pub fn swap_selection_ends(
13850 &mut self,
13851 _: &actions::SwapSelectionEnds,
13852 window: &mut Window,
13853 cx: &mut Context<Self>,
13854 ) {
13855 self.change_selections(None, window, cx, |s| {
13856 s.move_with(|_, sel| {
13857 if sel.start != sel.end {
13858 sel.reversed = !sel.reversed
13859 }
13860 });
13861 });
13862 self.request_autoscroll(Autoscroll::newest(), cx);
13863 cx.notify();
13864 }
13865
13866 pub fn toggle_fold(
13867 &mut self,
13868 _: &actions::ToggleFold,
13869 window: &mut Window,
13870 cx: &mut Context<Self>,
13871 ) {
13872 if self.is_singleton(cx) {
13873 let selection = self.selections.newest::<Point>(cx);
13874
13875 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13876 let range = if selection.is_empty() {
13877 let point = selection.head().to_display_point(&display_map);
13878 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
13879 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
13880 .to_point(&display_map);
13881 start..end
13882 } else {
13883 selection.range()
13884 };
13885 if display_map.folds_in_range(range).next().is_some() {
13886 self.unfold_lines(&Default::default(), window, cx)
13887 } else {
13888 self.fold(&Default::default(), window, cx)
13889 }
13890 } else {
13891 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
13892 let buffer_ids: HashSet<_> = self
13893 .selections
13894 .disjoint_anchor_ranges()
13895 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
13896 .collect();
13897
13898 let should_unfold = buffer_ids
13899 .iter()
13900 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
13901
13902 for buffer_id in buffer_ids {
13903 if should_unfold {
13904 self.unfold_buffer(buffer_id, cx);
13905 } else {
13906 self.fold_buffer(buffer_id, cx);
13907 }
13908 }
13909 }
13910 }
13911
13912 pub fn toggle_fold_recursive(
13913 &mut self,
13914 _: &actions::ToggleFoldRecursive,
13915 window: &mut Window,
13916 cx: &mut Context<Self>,
13917 ) {
13918 let selection = self.selections.newest::<Point>(cx);
13919
13920 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13921 let range = if selection.is_empty() {
13922 let point = selection.head().to_display_point(&display_map);
13923 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
13924 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
13925 .to_point(&display_map);
13926 start..end
13927 } else {
13928 selection.range()
13929 };
13930 if display_map.folds_in_range(range).next().is_some() {
13931 self.unfold_recursive(&Default::default(), window, cx)
13932 } else {
13933 self.fold_recursive(&Default::default(), window, cx)
13934 }
13935 }
13936
13937 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
13938 if self.is_singleton(cx) {
13939 let mut to_fold = Vec::new();
13940 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13941 let selections = self.selections.all_adjusted(cx);
13942
13943 for selection in selections {
13944 let range = selection.range().sorted();
13945 let buffer_start_row = range.start.row;
13946
13947 if range.start.row != range.end.row {
13948 let mut found = false;
13949 let mut row = range.start.row;
13950 while row <= range.end.row {
13951 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
13952 {
13953 found = true;
13954 row = crease.range().end.row + 1;
13955 to_fold.push(crease);
13956 } else {
13957 row += 1
13958 }
13959 }
13960 if found {
13961 continue;
13962 }
13963 }
13964
13965 for row in (0..=range.start.row).rev() {
13966 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
13967 if crease.range().end.row >= buffer_start_row {
13968 to_fold.push(crease);
13969 if row <= range.start.row {
13970 break;
13971 }
13972 }
13973 }
13974 }
13975 }
13976
13977 self.fold_creases(to_fold, true, window, cx);
13978 } else {
13979 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
13980 let buffer_ids = self
13981 .selections
13982 .disjoint_anchor_ranges()
13983 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
13984 .collect::<HashSet<_>>();
13985 for buffer_id in buffer_ids {
13986 self.fold_buffer(buffer_id, cx);
13987 }
13988 }
13989 }
13990
13991 fn fold_at_level(
13992 &mut self,
13993 fold_at: &FoldAtLevel,
13994 window: &mut Window,
13995 cx: &mut Context<Self>,
13996 ) {
13997 if !self.buffer.read(cx).is_singleton() {
13998 return;
13999 }
14000
14001 let fold_at_level = fold_at.0;
14002 let snapshot = self.buffer.read(cx).snapshot(cx);
14003 let mut to_fold = Vec::new();
14004 let mut stack = vec![(0, snapshot.max_row().0, 1)];
14005
14006 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
14007 while start_row < end_row {
14008 match self
14009 .snapshot(window, cx)
14010 .crease_for_buffer_row(MultiBufferRow(start_row))
14011 {
14012 Some(crease) => {
14013 let nested_start_row = crease.range().start.row + 1;
14014 let nested_end_row = crease.range().end.row;
14015
14016 if current_level < fold_at_level {
14017 stack.push((nested_start_row, nested_end_row, current_level + 1));
14018 } else if current_level == fold_at_level {
14019 to_fold.push(crease);
14020 }
14021
14022 start_row = nested_end_row + 1;
14023 }
14024 None => start_row += 1,
14025 }
14026 }
14027 }
14028
14029 self.fold_creases(to_fold, true, window, cx);
14030 }
14031
14032 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
14033 if self.buffer.read(cx).is_singleton() {
14034 let mut fold_ranges = Vec::new();
14035 let snapshot = self.buffer.read(cx).snapshot(cx);
14036
14037 for row in 0..snapshot.max_row().0 {
14038 if let Some(foldable_range) = self
14039 .snapshot(window, cx)
14040 .crease_for_buffer_row(MultiBufferRow(row))
14041 {
14042 fold_ranges.push(foldable_range);
14043 }
14044 }
14045
14046 self.fold_creases(fold_ranges, true, window, cx);
14047 } else {
14048 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
14049 editor
14050 .update_in(cx, |editor, _, cx| {
14051 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
14052 editor.fold_buffer(buffer_id, cx);
14053 }
14054 })
14055 .ok();
14056 });
14057 }
14058 }
14059
14060 pub fn fold_function_bodies(
14061 &mut self,
14062 _: &actions::FoldFunctionBodies,
14063 window: &mut Window,
14064 cx: &mut Context<Self>,
14065 ) {
14066 let snapshot = self.buffer.read(cx).snapshot(cx);
14067
14068 let ranges = snapshot
14069 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
14070 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
14071 .collect::<Vec<_>>();
14072
14073 let creases = ranges
14074 .into_iter()
14075 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
14076 .collect();
14077
14078 self.fold_creases(creases, true, window, cx);
14079 }
14080
14081 pub fn fold_recursive(
14082 &mut self,
14083 _: &actions::FoldRecursive,
14084 window: &mut Window,
14085 cx: &mut Context<Self>,
14086 ) {
14087 let mut to_fold = Vec::new();
14088 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14089 let selections = self.selections.all_adjusted(cx);
14090
14091 for selection in selections {
14092 let range = selection.range().sorted();
14093 let buffer_start_row = range.start.row;
14094
14095 if range.start.row != range.end.row {
14096 let mut found = false;
14097 for row in range.start.row..=range.end.row {
14098 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14099 found = true;
14100 to_fold.push(crease);
14101 }
14102 }
14103 if found {
14104 continue;
14105 }
14106 }
14107
14108 for row in (0..=range.start.row).rev() {
14109 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14110 if crease.range().end.row >= buffer_start_row {
14111 to_fold.push(crease);
14112 } else {
14113 break;
14114 }
14115 }
14116 }
14117 }
14118
14119 self.fold_creases(to_fold, true, window, cx);
14120 }
14121
14122 pub fn fold_at(&mut self, fold_at: &FoldAt, window: &mut Window, cx: &mut Context<Self>) {
14123 let buffer_row = fold_at.buffer_row;
14124 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14125
14126 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
14127 let autoscroll = self
14128 .selections
14129 .all::<Point>(cx)
14130 .iter()
14131 .any(|selection| crease.range().overlaps(&selection.range()));
14132
14133 self.fold_creases(vec![crease], autoscroll, window, cx);
14134 }
14135 }
14136
14137 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
14138 if self.is_singleton(cx) {
14139 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14140 let buffer = &display_map.buffer_snapshot;
14141 let selections = self.selections.all::<Point>(cx);
14142 let ranges = selections
14143 .iter()
14144 .map(|s| {
14145 let range = s.display_range(&display_map).sorted();
14146 let mut start = range.start.to_point(&display_map);
14147 let mut end = range.end.to_point(&display_map);
14148 start.column = 0;
14149 end.column = buffer.line_len(MultiBufferRow(end.row));
14150 start..end
14151 })
14152 .collect::<Vec<_>>();
14153
14154 self.unfold_ranges(&ranges, true, true, cx);
14155 } else {
14156 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14157 let buffer_ids = self
14158 .selections
14159 .disjoint_anchor_ranges()
14160 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14161 .collect::<HashSet<_>>();
14162 for buffer_id in buffer_ids {
14163 self.unfold_buffer(buffer_id, cx);
14164 }
14165 }
14166 }
14167
14168 pub fn unfold_recursive(
14169 &mut self,
14170 _: &UnfoldRecursive,
14171 _window: &mut Window,
14172 cx: &mut Context<Self>,
14173 ) {
14174 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14175 let selections = self.selections.all::<Point>(cx);
14176 let ranges = selections
14177 .iter()
14178 .map(|s| {
14179 let mut range = s.display_range(&display_map).sorted();
14180 *range.start.column_mut() = 0;
14181 *range.end.column_mut() = display_map.line_len(range.end.row());
14182 let start = range.start.to_point(&display_map);
14183 let end = range.end.to_point(&display_map);
14184 start..end
14185 })
14186 .collect::<Vec<_>>();
14187
14188 self.unfold_ranges(&ranges, true, true, cx);
14189 }
14190
14191 pub fn unfold_at(
14192 &mut self,
14193 unfold_at: &UnfoldAt,
14194 _window: &mut Window,
14195 cx: &mut Context<Self>,
14196 ) {
14197 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14198
14199 let intersection_range = Point::new(unfold_at.buffer_row.0, 0)
14200 ..Point::new(
14201 unfold_at.buffer_row.0,
14202 display_map.buffer_snapshot.line_len(unfold_at.buffer_row),
14203 );
14204
14205 let autoscroll = self
14206 .selections
14207 .all::<Point>(cx)
14208 .iter()
14209 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
14210
14211 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
14212 }
14213
14214 pub fn unfold_all(
14215 &mut self,
14216 _: &actions::UnfoldAll,
14217 _window: &mut Window,
14218 cx: &mut Context<Self>,
14219 ) {
14220 if self.buffer.read(cx).is_singleton() {
14221 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14222 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
14223 } else {
14224 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
14225 editor
14226 .update(cx, |editor, cx| {
14227 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
14228 editor.unfold_buffer(buffer_id, cx);
14229 }
14230 })
14231 .ok();
14232 });
14233 }
14234 }
14235
14236 pub fn fold_selected_ranges(
14237 &mut self,
14238 _: &FoldSelectedRanges,
14239 window: &mut Window,
14240 cx: &mut Context<Self>,
14241 ) {
14242 let selections = self.selections.all::<Point>(cx);
14243 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14244 let line_mode = self.selections.line_mode;
14245 let ranges = selections
14246 .into_iter()
14247 .map(|s| {
14248 if line_mode {
14249 let start = Point::new(s.start.row, 0);
14250 let end = Point::new(
14251 s.end.row,
14252 display_map
14253 .buffer_snapshot
14254 .line_len(MultiBufferRow(s.end.row)),
14255 );
14256 Crease::simple(start..end, display_map.fold_placeholder.clone())
14257 } else {
14258 Crease::simple(s.start..s.end, display_map.fold_placeholder.clone())
14259 }
14260 })
14261 .collect::<Vec<_>>();
14262 self.fold_creases(ranges, true, window, cx);
14263 }
14264
14265 pub fn fold_ranges<T: ToOffset + Clone>(
14266 &mut self,
14267 ranges: Vec<Range<T>>,
14268 auto_scroll: bool,
14269 window: &mut Window,
14270 cx: &mut Context<Self>,
14271 ) {
14272 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14273 let ranges = ranges
14274 .into_iter()
14275 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
14276 .collect::<Vec<_>>();
14277 self.fold_creases(ranges, auto_scroll, window, cx);
14278 }
14279
14280 pub fn fold_creases<T: ToOffset + Clone>(
14281 &mut self,
14282 creases: Vec<Crease<T>>,
14283 auto_scroll: bool,
14284 window: &mut Window,
14285 cx: &mut Context<Self>,
14286 ) {
14287 if creases.is_empty() {
14288 return;
14289 }
14290
14291 let mut buffers_affected = HashSet::default();
14292 let multi_buffer = self.buffer().read(cx);
14293 for crease in &creases {
14294 if let Some((_, buffer, _)) =
14295 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
14296 {
14297 buffers_affected.insert(buffer.read(cx).remote_id());
14298 };
14299 }
14300
14301 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
14302
14303 if auto_scroll {
14304 self.request_autoscroll(Autoscroll::fit(), cx);
14305 }
14306
14307 cx.notify();
14308
14309 if let Some(active_diagnostics) = self.active_diagnostics.take() {
14310 // Clear diagnostics block when folding a range that contains it.
14311 let snapshot = self.snapshot(window, cx);
14312 if snapshot.intersects_fold(active_diagnostics.primary_range.start) {
14313 drop(snapshot);
14314 self.active_diagnostics = Some(active_diagnostics);
14315 self.dismiss_diagnostics(cx);
14316 } else {
14317 self.active_diagnostics = Some(active_diagnostics);
14318 }
14319 }
14320
14321 self.scrollbar_marker_state.dirty = true;
14322 }
14323
14324 /// Removes any folds whose ranges intersect any of the given ranges.
14325 pub fn unfold_ranges<T: ToOffset + Clone>(
14326 &mut self,
14327 ranges: &[Range<T>],
14328 inclusive: bool,
14329 auto_scroll: bool,
14330 cx: &mut Context<Self>,
14331 ) {
14332 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
14333 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
14334 });
14335 }
14336
14337 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
14338 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
14339 return;
14340 }
14341 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
14342 self.display_map.update(cx, |display_map, cx| {
14343 display_map.fold_buffers([buffer_id], cx)
14344 });
14345 cx.emit(EditorEvent::BufferFoldToggled {
14346 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
14347 folded: true,
14348 });
14349 cx.notify();
14350 }
14351
14352 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
14353 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
14354 return;
14355 }
14356 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
14357 self.display_map.update(cx, |display_map, cx| {
14358 display_map.unfold_buffers([buffer_id], cx);
14359 });
14360 cx.emit(EditorEvent::BufferFoldToggled {
14361 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
14362 folded: false,
14363 });
14364 cx.notify();
14365 }
14366
14367 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
14368 self.display_map.read(cx).is_buffer_folded(buffer)
14369 }
14370
14371 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
14372 self.display_map.read(cx).folded_buffers()
14373 }
14374
14375 /// Removes any folds with the given ranges.
14376 pub fn remove_folds_with_type<T: ToOffset + Clone>(
14377 &mut self,
14378 ranges: &[Range<T>],
14379 type_id: TypeId,
14380 auto_scroll: bool,
14381 cx: &mut Context<Self>,
14382 ) {
14383 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
14384 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
14385 });
14386 }
14387
14388 fn remove_folds_with<T: ToOffset + Clone>(
14389 &mut self,
14390 ranges: &[Range<T>],
14391 auto_scroll: bool,
14392 cx: &mut Context<Self>,
14393 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
14394 ) {
14395 if ranges.is_empty() {
14396 return;
14397 }
14398
14399 let mut buffers_affected = HashSet::default();
14400 let multi_buffer = self.buffer().read(cx);
14401 for range in ranges {
14402 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
14403 buffers_affected.insert(buffer.read(cx).remote_id());
14404 };
14405 }
14406
14407 self.display_map.update(cx, update);
14408
14409 if auto_scroll {
14410 self.request_autoscroll(Autoscroll::fit(), cx);
14411 }
14412
14413 cx.notify();
14414 self.scrollbar_marker_state.dirty = true;
14415 self.active_indent_guides_state.dirty = true;
14416 }
14417
14418 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
14419 self.display_map.read(cx).fold_placeholder.clone()
14420 }
14421
14422 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
14423 self.buffer.update(cx, |buffer, cx| {
14424 buffer.set_all_diff_hunks_expanded(cx);
14425 });
14426 }
14427
14428 pub fn expand_all_diff_hunks(
14429 &mut self,
14430 _: &ExpandAllDiffHunks,
14431 _window: &mut Window,
14432 cx: &mut Context<Self>,
14433 ) {
14434 self.buffer.update(cx, |buffer, cx| {
14435 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
14436 });
14437 }
14438
14439 pub fn toggle_selected_diff_hunks(
14440 &mut self,
14441 _: &ToggleSelectedDiffHunks,
14442 _window: &mut Window,
14443 cx: &mut Context<Self>,
14444 ) {
14445 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
14446 self.toggle_diff_hunks_in_ranges(ranges, cx);
14447 }
14448
14449 pub fn diff_hunks_in_ranges<'a>(
14450 &'a self,
14451 ranges: &'a [Range<Anchor>],
14452 buffer: &'a MultiBufferSnapshot,
14453 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
14454 ranges.iter().flat_map(move |range| {
14455 let end_excerpt_id = range.end.excerpt_id;
14456 let range = range.to_point(buffer);
14457 let mut peek_end = range.end;
14458 if range.end.row < buffer.max_row().0 {
14459 peek_end = Point::new(range.end.row + 1, 0);
14460 }
14461 buffer
14462 .diff_hunks_in_range(range.start..peek_end)
14463 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
14464 })
14465 }
14466
14467 pub fn has_stageable_diff_hunks_in_ranges(
14468 &self,
14469 ranges: &[Range<Anchor>],
14470 snapshot: &MultiBufferSnapshot,
14471 ) -> bool {
14472 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
14473 hunks.any(|hunk| hunk.status().has_secondary_hunk())
14474 }
14475
14476 pub fn toggle_staged_selected_diff_hunks(
14477 &mut self,
14478 _: &::git::ToggleStaged,
14479 _: &mut Window,
14480 cx: &mut Context<Self>,
14481 ) {
14482 let snapshot = self.buffer.read(cx).snapshot(cx);
14483 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
14484 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
14485 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
14486 }
14487
14488 pub fn set_render_diff_hunk_controls(
14489 &mut self,
14490 render_diff_hunk_controls: RenderDiffHunkControlsFn,
14491 cx: &mut Context<Self>,
14492 ) {
14493 self.render_diff_hunk_controls = render_diff_hunk_controls;
14494 cx.notify();
14495 }
14496
14497 pub fn stage_and_next(
14498 &mut self,
14499 _: &::git::StageAndNext,
14500 window: &mut Window,
14501 cx: &mut Context<Self>,
14502 ) {
14503 self.do_stage_or_unstage_and_next(true, window, cx);
14504 }
14505
14506 pub fn unstage_and_next(
14507 &mut self,
14508 _: &::git::UnstageAndNext,
14509 window: &mut Window,
14510 cx: &mut Context<Self>,
14511 ) {
14512 self.do_stage_or_unstage_and_next(false, window, cx);
14513 }
14514
14515 pub fn stage_or_unstage_diff_hunks(
14516 &mut self,
14517 stage: bool,
14518 ranges: Vec<Range<Anchor>>,
14519 cx: &mut Context<Self>,
14520 ) {
14521 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
14522 cx.spawn(async move |this, cx| {
14523 task.await?;
14524 this.update(cx, |this, cx| {
14525 let snapshot = this.buffer.read(cx).snapshot(cx);
14526 let chunk_by = this
14527 .diff_hunks_in_ranges(&ranges, &snapshot)
14528 .chunk_by(|hunk| hunk.buffer_id);
14529 for (buffer_id, hunks) in &chunk_by {
14530 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
14531 }
14532 })
14533 })
14534 .detach_and_log_err(cx);
14535 }
14536
14537 fn save_buffers_for_ranges_if_needed(
14538 &mut self,
14539 ranges: &[Range<Anchor>],
14540 cx: &mut Context<'_, Editor>,
14541 ) -> Task<Result<()>> {
14542 let multibuffer = self.buffer.read(cx);
14543 let snapshot = multibuffer.read(cx);
14544 let buffer_ids: HashSet<_> = ranges
14545 .iter()
14546 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
14547 .collect();
14548 drop(snapshot);
14549
14550 let mut buffers = HashSet::default();
14551 for buffer_id in buffer_ids {
14552 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
14553 let buffer = buffer_entity.read(cx);
14554 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
14555 {
14556 buffers.insert(buffer_entity);
14557 }
14558 }
14559 }
14560
14561 if let Some(project) = &self.project {
14562 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
14563 } else {
14564 Task::ready(Ok(()))
14565 }
14566 }
14567
14568 fn do_stage_or_unstage_and_next(
14569 &mut self,
14570 stage: bool,
14571 window: &mut Window,
14572 cx: &mut Context<Self>,
14573 ) {
14574 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
14575
14576 if ranges.iter().any(|range| range.start != range.end) {
14577 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
14578 return;
14579 }
14580
14581 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
14582 let snapshot = self.snapshot(window, cx);
14583 let position = self.selections.newest::<Point>(cx).head();
14584 let mut row = snapshot
14585 .buffer_snapshot
14586 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14587 .find(|hunk| hunk.row_range.start.0 > position.row)
14588 .map(|hunk| hunk.row_range.start);
14589
14590 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
14591 // Outside of the project diff editor, wrap around to the beginning.
14592 if !all_diff_hunks_expanded {
14593 row = row.or_else(|| {
14594 snapshot
14595 .buffer_snapshot
14596 .diff_hunks_in_range(Point::zero()..position)
14597 .find(|hunk| hunk.row_range.end.0 < position.row)
14598 .map(|hunk| hunk.row_range.start)
14599 });
14600 }
14601
14602 if let Some(row) = row {
14603 let destination = Point::new(row.0, 0);
14604 let autoscroll = Autoscroll::center();
14605
14606 self.unfold_ranges(&[destination..destination], false, false, cx);
14607 self.change_selections(Some(autoscroll), window, cx, |s| {
14608 s.select_ranges([destination..destination]);
14609 });
14610 }
14611 }
14612
14613 fn do_stage_or_unstage(
14614 &self,
14615 stage: bool,
14616 buffer_id: BufferId,
14617 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
14618 cx: &mut App,
14619 ) -> Option<()> {
14620 let project = self.project.as_ref()?;
14621 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
14622 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
14623 let buffer_snapshot = buffer.read(cx).snapshot();
14624 let file_exists = buffer_snapshot
14625 .file()
14626 .is_some_and(|file| file.disk_state().exists());
14627 diff.update(cx, |diff, cx| {
14628 diff.stage_or_unstage_hunks(
14629 stage,
14630 &hunks
14631 .map(|hunk| buffer_diff::DiffHunk {
14632 buffer_range: hunk.buffer_range,
14633 diff_base_byte_range: hunk.diff_base_byte_range,
14634 secondary_status: hunk.secondary_status,
14635 range: Point::zero()..Point::zero(), // unused
14636 })
14637 .collect::<Vec<_>>(),
14638 &buffer_snapshot,
14639 file_exists,
14640 cx,
14641 )
14642 });
14643 None
14644 }
14645
14646 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
14647 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
14648 self.buffer
14649 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
14650 }
14651
14652 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
14653 self.buffer.update(cx, |buffer, cx| {
14654 let ranges = vec![Anchor::min()..Anchor::max()];
14655 if !buffer.all_diff_hunks_expanded()
14656 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
14657 {
14658 buffer.collapse_diff_hunks(ranges, cx);
14659 true
14660 } else {
14661 false
14662 }
14663 })
14664 }
14665
14666 fn toggle_diff_hunks_in_ranges(
14667 &mut self,
14668 ranges: Vec<Range<Anchor>>,
14669 cx: &mut Context<'_, Editor>,
14670 ) {
14671 self.buffer.update(cx, |buffer, cx| {
14672 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
14673 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
14674 })
14675 }
14676
14677 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
14678 self.buffer.update(cx, |buffer, cx| {
14679 let snapshot = buffer.snapshot(cx);
14680 let excerpt_id = range.end.excerpt_id;
14681 let point_range = range.to_point(&snapshot);
14682 let expand = !buffer.single_hunk_is_expanded(range, cx);
14683 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
14684 })
14685 }
14686
14687 pub(crate) fn apply_all_diff_hunks(
14688 &mut self,
14689 _: &ApplyAllDiffHunks,
14690 window: &mut Window,
14691 cx: &mut Context<Self>,
14692 ) {
14693 let buffers = self.buffer.read(cx).all_buffers();
14694 for branch_buffer in buffers {
14695 branch_buffer.update(cx, |branch_buffer, cx| {
14696 branch_buffer.merge_into_base(Vec::new(), cx);
14697 });
14698 }
14699
14700 if let Some(project) = self.project.clone() {
14701 self.save(true, project, window, cx).detach_and_log_err(cx);
14702 }
14703 }
14704
14705 pub(crate) fn apply_selected_diff_hunks(
14706 &mut self,
14707 _: &ApplyDiffHunk,
14708 window: &mut Window,
14709 cx: &mut Context<Self>,
14710 ) {
14711 let snapshot = self.snapshot(window, cx);
14712 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
14713 let mut ranges_by_buffer = HashMap::default();
14714 self.transact(window, cx, |editor, _window, cx| {
14715 for hunk in hunks {
14716 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
14717 ranges_by_buffer
14718 .entry(buffer.clone())
14719 .or_insert_with(Vec::new)
14720 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
14721 }
14722 }
14723
14724 for (buffer, ranges) in ranges_by_buffer {
14725 buffer.update(cx, |buffer, cx| {
14726 buffer.merge_into_base(ranges, cx);
14727 });
14728 }
14729 });
14730
14731 if let Some(project) = self.project.clone() {
14732 self.save(true, project, window, cx).detach_and_log_err(cx);
14733 }
14734 }
14735
14736 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
14737 if hovered != self.gutter_hovered {
14738 self.gutter_hovered = hovered;
14739 cx.notify();
14740 }
14741 }
14742
14743 pub fn insert_blocks(
14744 &mut self,
14745 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
14746 autoscroll: Option<Autoscroll>,
14747 cx: &mut Context<Self>,
14748 ) -> Vec<CustomBlockId> {
14749 let blocks = self
14750 .display_map
14751 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
14752 if let Some(autoscroll) = autoscroll {
14753 self.request_autoscroll(autoscroll, cx);
14754 }
14755 cx.notify();
14756 blocks
14757 }
14758
14759 pub fn resize_blocks(
14760 &mut self,
14761 heights: HashMap<CustomBlockId, u32>,
14762 autoscroll: Option<Autoscroll>,
14763 cx: &mut Context<Self>,
14764 ) {
14765 self.display_map
14766 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
14767 if let Some(autoscroll) = autoscroll {
14768 self.request_autoscroll(autoscroll, cx);
14769 }
14770 cx.notify();
14771 }
14772
14773 pub fn replace_blocks(
14774 &mut self,
14775 renderers: HashMap<CustomBlockId, RenderBlock>,
14776 autoscroll: Option<Autoscroll>,
14777 cx: &mut Context<Self>,
14778 ) {
14779 self.display_map
14780 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
14781 if let Some(autoscroll) = autoscroll {
14782 self.request_autoscroll(autoscroll, cx);
14783 }
14784 cx.notify();
14785 }
14786
14787 pub fn remove_blocks(
14788 &mut self,
14789 block_ids: HashSet<CustomBlockId>,
14790 autoscroll: Option<Autoscroll>,
14791 cx: &mut Context<Self>,
14792 ) {
14793 self.display_map.update(cx, |display_map, cx| {
14794 display_map.remove_blocks(block_ids, cx)
14795 });
14796 if let Some(autoscroll) = autoscroll {
14797 self.request_autoscroll(autoscroll, cx);
14798 }
14799 cx.notify();
14800 }
14801
14802 pub fn row_for_block(
14803 &self,
14804 block_id: CustomBlockId,
14805 cx: &mut Context<Self>,
14806 ) -> Option<DisplayRow> {
14807 self.display_map
14808 .update(cx, |map, cx| map.row_for_block(block_id, cx))
14809 }
14810
14811 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
14812 self.focused_block = Some(focused_block);
14813 }
14814
14815 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
14816 self.focused_block.take()
14817 }
14818
14819 pub fn insert_creases(
14820 &mut self,
14821 creases: impl IntoIterator<Item = Crease<Anchor>>,
14822 cx: &mut Context<Self>,
14823 ) -> Vec<CreaseId> {
14824 self.display_map
14825 .update(cx, |map, cx| map.insert_creases(creases, cx))
14826 }
14827
14828 pub fn remove_creases(
14829 &mut self,
14830 ids: impl IntoIterator<Item = CreaseId>,
14831 cx: &mut Context<Self>,
14832 ) {
14833 self.display_map
14834 .update(cx, |map, cx| map.remove_creases(ids, cx));
14835 }
14836
14837 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
14838 self.display_map
14839 .update(cx, |map, cx| map.snapshot(cx))
14840 .longest_row()
14841 }
14842
14843 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
14844 self.display_map
14845 .update(cx, |map, cx| map.snapshot(cx))
14846 .max_point()
14847 }
14848
14849 pub fn text(&self, cx: &App) -> String {
14850 self.buffer.read(cx).read(cx).text()
14851 }
14852
14853 pub fn is_empty(&self, cx: &App) -> bool {
14854 self.buffer.read(cx).read(cx).is_empty()
14855 }
14856
14857 pub fn text_option(&self, cx: &App) -> Option<String> {
14858 let text = self.text(cx);
14859 let text = text.trim();
14860
14861 if text.is_empty() {
14862 return None;
14863 }
14864
14865 Some(text.to_string())
14866 }
14867
14868 pub fn set_text(
14869 &mut self,
14870 text: impl Into<Arc<str>>,
14871 window: &mut Window,
14872 cx: &mut Context<Self>,
14873 ) {
14874 self.transact(window, cx, |this, _, cx| {
14875 this.buffer
14876 .read(cx)
14877 .as_singleton()
14878 .expect("you can only call set_text on editors for singleton buffers")
14879 .update(cx, |buffer, cx| buffer.set_text(text, cx));
14880 });
14881 }
14882
14883 pub fn display_text(&self, cx: &mut App) -> String {
14884 self.display_map
14885 .update(cx, |map, cx| map.snapshot(cx))
14886 .text()
14887 }
14888
14889 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
14890 let mut wrap_guides = smallvec::smallvec![];
14891
14892 if self.show_wrap_guides == Some(false) {
14893 return wrap_guides;
14894 }
14895
14896 let settings = self.buffer.read(cx).language_settings(cx);
14897 if settings.show_wrap_guides {
14898 match self.soft_wrap_mode(cx) {
14899 SoftWrap::Column(soft_wrap) => {
14900 wrap_guides.push((soft_wrap as usize, true));
14901 }
14902 SoftWrap::Bounded(soft_wrap) => {
14903 wrap_guides.push((soft_wrap as usize, true));
14904 }
14905 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
14906 }
14907 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
14908 }
14909
14910 wrap_guides
14911 }
14912
14913 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
14914 let settings = self.buffer.read(cx).language_settings(cx);
14915 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
14916 match mode {
14917 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
14918 SoftWrap::None
14919 }
14920 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
14921 language_settings::SoftWrap::PreferredLineLength => {
14922 SoftWrap::Column(settings.preferred_line_length)
14923 }
14924 language_settings::SoftWrap::Bounded => {
14925 SoftWrap::Bounded(settings.preferred_line_length)
14926 }
14927 }
14928 }
14929
14930 pub fn set_soft_wrap_mode(
14931 &mut self,
14932 mode: language_settings::SoftWrap,
14933
14934 cx: &mut Context<Self>,
14935 ) {
14936 self.soft_wrap_mode_override = Some(mode);
14937 cx.notify();
14938 }
14939
14940 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
14941 self.hard_wrap = hard_wrap;
14942 cx.notify();
14943 }
14944
14945 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
14946 self.text_style_refinement = Some(style);
14947 }
14948
14949 /// called by the Element so we know what style we were most recently rendered with.
14950 pub(crate) fn set_style(
14951 &mut self,
14952 style: EditorStyle,
14953 window: &mut Window,
14954 cx: &mut Context<Self>,
14955 ) {
14956 let rem_size = window.rem_size();
14957 self.display_map.update(cx, |map, cx| {
14958 map.set_font(
14959 style.text.font(),
14960 style.text.font_size.to_pixels(rem_size),
14961 cx,
14962 )
14963 });
14964 self.style = Some(style);
14965 }
14966
14967 pub fn style(&self) -> Option<&EditorStyle> {
14968 self.style.as_ref()
14969 }
14970
14971 // Called by the element. This method is not designed to be called outside of the editor
14972 // element's layout code because it does not notify when rewrapping is computed synchronously.
14973 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
14974 self.display_map
14975 .update(cx, |map, cx| map.set_wrap_width(width, cx))
14976 }
14977
14978 pub fn set_soft_wrap(&mut self) {
14979 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
14980 }
14981
14982 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
14983 if self.soft_wrap_mode_override.is_some() {
14984 self.soft_wrap_mode_override.take();
14985 } else {
14986 let soft_wrap = match self.soft_wrap_mode(cx) {
14987 SoftWrap::GitDiff => return,
14988 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
14989 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
14990 language_settings::SoftWrap::None
14991 }
14992 };
14993 self.soft_wrap_mode_override = Some(soft_wrap);
14994 }
14995 cx.notify();
14996 }
14997
14998 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
14999 let Some(workspace) = self.workspace() else {
15000 return;
15001 };
15002 let fs = workspace.read(cx).app_state().fs.clone();
15003 let current_show = TabBarSettings::get_global(cx).show;
15004 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
15005 setting.show = Some(!current_show);
15006 });
15007 }
15008
15009 pub fn toggle_indent_guides(
15010 &mut self,
15011 _: &ToggleIndentGuides,
15012 _: &mut Window,
15013 cx: &mut Context<Self>,
15014 ) {
15015 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
15016 self.buffer
15017 .read(cx)
15018 .language_settings(cx)
15019 .indent_guides
15020 .enabled
15021 });
15022 self.show_indent_guides = Some(!currently_enabled);
15023 cx.notify();
15024 }
15025
15026 fn should_show_indent_guides(&self) -> Option<bool> {
15027 self.show_indent_guides
15028 }
15029
15030 pub fn toggle_line_numbers(
15031 &mut self,
15032 _: &ToggleLineNumbers,
15033 _: &mut Window,
15034 cx: &mut Context<Self>,
15035 ) {
15036 let mut editor_settings = EditorSettings::get_global(cx).clone();
15037 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
15038 EditorSettings::override_global(editor_settings, cx);
15039 }
15040
15041 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
15042 if let Some(show_line_numbers) = self.show_line_numbers {
15043 return show_line_numbers;
15044 }
15045 EditorSettings::get_global(cx).gutter.line_numbers
15046 }
15047
15048 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
15049 self.use_relative_line_numbers
15050 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
15051 }
15052
15053 pub fn toggle_relative_line_numbers(
15054 &mut self,
15055 _: &ToggleRelativeLineNumbers,
15056 _: &mut Window,
15057 cx: &mut Context<Self>,
15058 ) {
15059 let is_relative = self.should_use_relative_line_numbers(cx);
15060 self.set_relative_line_number(Some(!is_relative), cx)
15061 }
15062
15063 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
15064 self.use_relative_line_numbers = is_relative;
15065 cx.notify();
15066 }
15067
15068 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
15069 self.show_gutter = show_gutter;
15070 cx.notify();
15071 }
15072
15073 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
15074 self.show_scrollbars = show_scrollbars;
15075 cx.notify();
15076 }
15077
15078 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
15079 self.show_line_numbers = Some(show_line_numbers);
15080 cx.notify();
15081 }
15082
15083 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
15084 self.show_git_diff_gutter = Some(show_git_diff_gutter);
15085 cx.notify();
15086 }
15087
15088 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
15089 self.show_code_actions = Some(show_code_actions);
15090 cx.notify();
15091 }
15092
15093 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
15094 self.show_runnables = Some(show_runnables);
15095 cx.notify();
15096 }
15097
15098 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
15099 self.show_breakpoints = Some(show_breakpoints);
15100 cx.notify();
15101 }
15102
15103 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
15104 if self.display_map.read(cx).masked != masked {
15105 self.display_map.update(cx, |map, _| map.masked = masked);
15106 }
15107 cx.notify()
15108 }
15109
15110 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
15111 self.show_wrap_guides = Some(show_wrap_guides);
15112 cx.notify();
15113 }
15114
15115 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
15116 self.show_indent_guides = Some(show_indent_guides);
15117 cx.notify();
15118 }
15119
15120 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
15121 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
15122 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
15123 if let Some(dir) = file.abs_path(cx).parent() {
15124 return Some(dir.to_owned());
15125 }
15126 }
15127
15128 if let Some(project_path) = buffer.read(cx).project_path(cx) {
15129 return Some(project_path.path.to_path_buf());
15130 }
15131 }
15132
15133 None
15134 }
15135
15136 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
15137 self.active_excerpt(cx)?
15138 .1
15139 .read(cx)
15140 .file()
15141 .and_then(|f| f.as_local())
15142 }
15143
15144 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15145 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15146 let buffer = buffer.read(cx);
15147 if let Some(project_path) = buffer.project_path(cx) {
15148 let project = self.project.as_ref()?.read(cx);
15149 project.absolute_path(&project_path, cx)
15150 } else {
15151 buffer
15152 .file()
15153 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
15154 }
15155 })
15156 }
15157
15158 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15159 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15160 let project_path = buffer.read(cx).project_path(cx)?;
15161 let project = self.project.as_ref()?.read(cx);
15162 let entry = project.entry_for_path(&project_path, cx)?;
15163 let path = entry.path.to_path_buf();
15164 Some(path)
15165 })
15166 }
15167
15168 pub fn reveal_in_finder(
15169 &mut self,
15170 _: &RevealInFileManager,
15171 _window: &mut Window,
15172 cx: &mut Context<Self>,
15173 ) {
15174 if let Some(target) = self.target_file(cx) {
15175 cx.reveal_path(&target.abs_path(cx));
15176 }
15177 }
15178
15179 pub fn copy_path(
15180 &mut self,
15181 _: &zed_actions::workspace::CopyPath,
15182 _window: &mut Window,
15183 cx: &mut Context<Self>,
15184 ) {
15185 if let Some(path) = self.target_file_abs_path(cx) {
15186 if let Some(path) = path.to_str() {
15187 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15188 }
15189 }
15190 }
15191
15192 pub fn copy_relative_path(
15193 &mut self,
15194 _: &zed_actions::workspace::CopyRelativePath,
15195 _window: &mut Window,
15196 cx: &mut Context<Self>,
15197 ) {
15198 if let Some(path) = self.target_file_path(cx) {
15199 if let Some(path) = path.to_str() {
15200 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15201 }
15202 }
15203 }
15204
15205 pub fn project_path(&self, cx: &mut Context<Self>) -> Option<ProjectPath> {
15206 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
15207 buffer.read_with(cx, |buffer, cx| buffer.project_path(cx))
15208 } else {
15209 None
15210 }
15211 }
15212
15213 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) {
15214 let _ = maybe!({
15215 let breakpoint_store = self.breakpoint_store.as_ref()?;
15216
15217 let Some((_, _, active_position)) =
15218 breakpoint_store.read(cx).active_position().cloned()
15219 else {
15220 self.clear_row_highlights::<DebugCurrentRowHighlight>();
15221 return None;
15222 };
15223
15224 let snapshot = self
15225 .project
15226 .as_ref()?
15227 .read(cx)
15228 .buffer_for_id(active_position.buffer_id?, cx)?
15229 .read(cx)
15230 .snapshot();
15231
15232 for (id, ExcerptRange { context, .. }) in self
15233 .buffer
15234 .read(cx)
15235 .excerpts_for_buffer(active_position.buffer_id?, cx)
15236 {
15237 if context.start.cmp(&active_position, &snapshot).is_ge()
15238 || context.end.cmp(&active_position, &snapshot).is_lt()
15239 {
15240 continue;
15241 }
15242 let snapshot = self.buffer.read(cx).snapshot(cx);
15243 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, active_position)?;
15244
15245 self.clear_row_highlights::<DebugCurrentRowHighlight>();
15246 self.go_to_line::<DebugCurrentRowHighlight>(
15247 multibuffer_anchor,
15248 Some(cx.theme().colors().editor_debugger_active_line_background),
15249 window,
15250 cx,
15251 );
15252
15253 cx.notify();
15254 }
15255
15256 Some(())
15257 });
15258 }
15259
15260 pub fn copy_file_name_without_extension(
15261 &mut self,
15262 _: &CopyFileNameWithoutExtension,
15263 _: &mut Window,
15264 cx: &mut Context<Self>,
15265 ) {
15266 if let Some(file) = self.target_file(cx) {
15267 if let Some(file_stem) = file.path().file_stem() {
15268 if let Some(name) = file_stem.to_str() {
15269 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
15270 }
15271 }
15272 }
15273 }
15274
15275 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
15276 if let Some(file) = self.target_file(cx) {
15277 if let Some(file_name) = file.path().file_name() {
15278 if let Some(name) = file_name.to_str() {
15279 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
15280 }
15281 }
15282 }
15283 }
15284
15285 pub fn toggle_git_blame(
15286 &mut self,
15287 _: &::git::Blame,
15288 window: &mut Window,
15289 cx: &mut Context<Self>,
15290 ) {
15291 self.show_git_blame_gutter = !self.show_git_blame_gutter;
15292
15293 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
15294 self.start_git_blame(true, window, cx);
15295 }
15296
15297 cx.notify();
15298 }
15299
15300 pub fn toggle_git_blame_inline(
15301 &mut self,
15302 _: &ToggleGitBlameInline,
15303 window: &mut Window,
15304 cx: &mut Context<Self>,
15305 ) {
15306 self.toggle_git_blame_inline_internal(true, window, cx);
15307 cx.notify();
15308 }
15309
15310 pub fn git_blame_inline_enabled(&self) -> bool {
15311 self.git_blame_inline_enabled
15312 }
15313
15314 pub fn toggle_selection_menu(
15315 &mut self,
15316 _: &ToggleSelectionMenu,
15317 _: &mut Window,
15318 cx: &mut Context<Self>,
15319 ) {
15320 self.show_selection_menu = self
15321 .show_selection_menu
15322 .map(|show_selections_menu| !show_selections_menu)
15323 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
15324
15325 cx.notify();
15326 }
15327
15328 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
15329 self.show_selection_menu
15330 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
15331 }
15332
15333 fn start_git_blame(
15334 &mut self,
15335 user_triggered: bool,
15336 window: &mut Window,
15337 cx: &mut Context<Self>,
15338 ) {
15339 if let Some(project) = self.project.as_ref() {
15340 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
15341 return;
15342 };
15343
15344 if buffer.read(cx).file().is_none() {
15345 return;
15346 }
15347
15348 let focused = self.focus_handle(cx).contains_focused(window, cx);
15349
15350 let project = project.clone();
15351 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
15352 self.blame_subscription =
15353 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
15354 self.blame = Some(blame);
15355 }
15356 }
15357
15358 fn toggle_git_blame_inline_internal(
15359 &mut self,
15360 user_triggered: bool,
15361 window: &mut Window,
15362 cx: &mut Context<Self>,
15363 ) {
15364 if self.git_blame_inline_enabled {
15365 self.git_blame_inline_enabled = false;
15366 self.show_git_blame_inline = false;
15367 self.show_git_blame_inline_delay_task.take();
15368 } else {
15369 self.git_blame_inline_enabled = true;
15370 self.start_git_blame_inline(user_triggered, window, cx);
15371 }
15372
15373 cx.notify();
15374 }
15375
15376 fn start_git_blame_inline(
15377 &mut self,
15378 user_triggered: bool,
15379 window: &mut Window,
15380 cx: &mut Context<Self>,
15381 ) {
15382 self.start_git_blame(user_triggered, window, cx);
15383
15384 if ProjectSettings::get_global(cx)
15385 .git
15386 .inline_blame_delay()
15387 .is_some()
15388 {
15389 self.start_inline_blame_timer(window, cx);
15390 } else {
15391 self.show_git_blame_inline = true
15392 }
15393 }
15394
15395 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
15396 self.blame.as_ref()
15397 }
15398
15399 pub fn show_git_blame_gutter(&self) -> bool {
15400 self.show_git_blame_gutter
15401 }
15402
15403 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
15404 self.show_git_blame_gutter && self.has_blame_entries(cx)
15405 }
15406
15407 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
15408 self.show_git_blame_inline
15409 && (self.focus_handle.is_focused(window)
15410 || self
15411 .git_blame_inline_tooltip
15412 .as_ref()
15413 .and_then(|t| t.upgrade())
15414 .is_some())
15415 && !self.newest_selection_head_on_empty_line(cx)
15416 && self.has_blame_entries(cx)
15417 }
15418
15419 fn has_blame_entries(&self, cx: &App) -> bool {
15420 self.blame()
15421 .map_or(false, |blame| blame.read(cx).has_generated_entries())
15422 }
15423
15424 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
15425 let cursor_anchor = self.selections.newest_anchor().head();
15426
15427 let snapshot = self.buffer.read(cx).snapshot(cx);
15428 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
15429
15430 snapshot.line_len(buffer_row) == 0
15431 }
15432
15433 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
15434 let buffer_and_selection = maybe!({
15435 let selection = self.selections.newest::<Point>(cx);
15436 let selection_range = selection.range();
15437
15438 let multi_buffer = self.buffer().read(cx);
15439 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15440 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
15441
15442 let (buffer, range, _) = if selection.reversed {
15443 buffer_ranges.first()
15444 } else {
15445 buffer_ranges.last()
15446 }?;
15447
15448 let selection = text::ToPoint::to_point(&range.start, &buffer).row
15449 ..text::ToPoint::to_point(&range.end, &buffer).row;
15450 Some((
15451 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
15452 selection,
15453 ))
15454 });
15455
15456 let Some((buffer, selection)) = buffer_and_selection else {
15457 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
15458 };
15459
15460 let Some(project) = self.project.as_ref() else {
15461 return Task::ready(Err(anyhow!("editor does not have project")));
15462 };
15463
15464 project.update(cx, |project, cx| {
15465 project.get_permalink_to_line(&buffer, selection, cx)
15466 })
15467 }
15468
15469 pub fn copy_permalink_to_line(
15470 &mut self,
15471 _: &CopyPermalinkToLine,
15472 window: &mut Window,
15473 cx: &mut Context<Self>,
15474 ) {
15475 let permalink_task = self.get_permalink_to_line(cx);
15476 let workspace = self.workspace();
15477
15478 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
15479 Ok(permalink) => {
15480 cx.update(|_, cx| {
15481 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
15482 })
15483 .ok();
15484 }
15485 Err(err) => {
15486 let message = format!("Failed to copy permalink: {err}");
15487
15488 Err::<(), anyhow::Error>(err).log_err();
15489
15490 if let Some(workspace) = workspace {
15491 workspace
15492 .update_in(cx, |workspace, _, cx| {
15493 struct CopyPermalinkToLine;
15494
15495 workspace.show_toast(
15496 Toast::new(
15497 NotificationId::unique::<CopyPermalinkToLine>(),
15498 message,
15499 ),
15500 cx,
15501 )
15502 })
15503 .ok();
15504 }
15505 }
15506 })
15507 .detach();
15508 }
15509
15510 pub fn copy_file_location(
15511 &mut self,
15512 _: &CopyFileLocation,
15513 _: &mut Window,
15514 cx: &mut Context<Self>,
15515 ) {
15516 let selection = self.selections.newest::<Point>(cx).start.row + 1;
15517 if let Some(file) = self.target_file(cx) {
15518 if let Some(path) = file.path().to_str() {
15519 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
15520 }
15521 }
15522 }
15523
15524 pub fn open_permalink_to_line(
15525 &mut self,
15526 _: &OpenPermalinkToLine,
15527 window: &mut Window,
15528 cx: &mut Context<Self>,
15529 ) {
15530 let permalink_task = self.get_permalink_to_line(cx);
15531 let workspace = self.workspace();
15532
15533 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
15534 Ok(permalink) => {
15535 cx.update(|_, cx| {
15536 cx.open_url(permalink.as_ref());
15537 })
15538 .ok();
15539 }
15540 Err(err) => {
15541 let message = format!("Failed to open permalink: {err}");
15542
15543 Err::<(), anyhow::Error>(err).log_err();
15544
15545 if let Some(workspace) = workspace {
15546 workspace
15547 .update(cx, |workspace, cx| {
15548 struct OpenPermalinkToLine;
15549
15550 workspace.show_toast(
15551 Toast::new(
15552 NotificationId::unique::<OpenPermalinkToLine>(),
15553 message,
15554 ),
15555 cx,
15556 )
15557 })
15558 .ok();
15559 }
15560 }
15561 })
15562 .detach();
15563 }
15564
15565 pub fn insert_uuid_v4(
15566 &mut self,
15567 _: &InsertUuidV4,
15568 window: &mut Window,
15569 cx: &mut Context<Self>,
15570 ) {
15571 self.insert_uuid(UuidVersion::V4, window, cx);
15572 }
15573
15574 pub fn insert_uuid_v7(
15575 &mut self,
15576 _: &InsertUuidV7,
15577 window: &mut Window,
15578 cx: &mut Context<Self>,
15579 ) {
15580 self.insert_uuid(UuidVersion::V7, window, cx);
15581 }
15582
15583 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
15584 self.transact(window, cx, |this, window, cx| {
15585 let edits = this
15586 .selections
15587 .all::<Point>(cx)
15588 .into_iter()
15589 .map(|selection| {
15590 let uuid = match version {
15591 UuidVersion::V4 => uuid::Uuid::new_v4(),
15592 UuidVersion::V7 => uuid::Uuid::now_v7(),
15593 };
15594
15595 (selection.range(), uuid.to_string())
15596 });
15597 this.edit(edits, cx);
15598 this.refresh_inline_completion(true, false, window, cx);
15599 });
15600 }
15601
15602 pub fn open_selections_in_multibuffer(
15603 &mut self,
15604 _: &OpenSelectionsInMultibuffer,
15605 window: &mut Window,
15606 cx: &mut Context<Self>,
15607 ) {
15608 let multibuffer = self.buffer.read(cx);
15609
15610 let Some(buffer) = multibuffer.as_singleton() else {
15611 return;
15612 };
15613
15614 let Some(workspace) = self.workspace() else {
15615 return;
15616 };
15617
15618 let locations = self
15619 .selections
15620 .disjoint_anchors()
15621 .iter()
15622 .map(|range| Location {
15623 buffer: buffer.clone(),
15624 range: range.start.text_anchor..range.end.text_anchor,
15625 })
15626 .collect::<Vec<_>>();
15627
15628 let title = multibuffer.title(cx).to_string();
15629
15630 cx.spawn_in(window, async move |_, cx| {
15631 workspace.update_in(cx, |workspace, window, cx| {
15632 Self::open_locations_in_multibuffer(
15633 workspace,
15634 locations,
15635 format!("Selections for '{title}'"),
15636 false,
15637 MultibufferSelectionMode::All,
15638 window,
15639 cx,
15640 );
15641 })
15642 })
15643 .detach();
15644 }
15645
15646 /// Adds a row highlight for the given range. If a row has multiple highlights, the
15647 /// last highlight added will be used.
15648 ///
15649 /// If the range ends at the beginning of a line, then that line will not be highlighted.
15650 pub fn highlight_rows<T: 'static>(
15651 &mut self,
15652 range: Range<Anchor>,
15653 color: Hsla,
15654 should_autoscroll: bool,
15655 cx: &mut Context<Self>,
15656 ) {
15657 let snapshot = self.buffer().read(cx).snapshot(cx);
15658 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
15659 let ix = row_highlights.binary_search_by(|highlight| {
15660 Ordering::Equal
15661 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
15662 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
15663 });
15664
15665 if let Err(mut ix) = ix {
15666 let index = post_inc(&mut self.highlight_order);
15667
15668 // If this range intersects with the preceding highlight, then merge it with
15669 // the preceding highlight. Otherwise insert a new highlight.
15670 let mut merged = false;
15671 if ix > 0 {
15672 let prev_highlight = &mut row_highlights[ix - 1];
15673 if prev_highlight
15674 .range
15675 .end
15676 .cmp(&range.start, &snapshot)
15677 .is_ge()
15678 {
15679 ix -= 1;
15680 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
15681 prev_highlight.range.end = range.end;
15682 }
15683 merged = true;
15684 prev_highlight.index = index;
15685 prev_highlight.color = color;
15686 prev_highlight.should_autoscroll = should_autoscroll;
15687 }
15688 }
15689
15690 if !merged {
15691 row_highlights.insert(
15692 ix,
15693 RowHighlight {
15694 range: range.clone(),
15695 index,
15696 color,
15697 should_autoscroll,
15698 },
15699 );
15700 }
15701
15702 // If any of the following highlights intersect with this one, merge them.
15703 while let Some(next_highlight) = row_highlights.get(ix + 1) {
15704 let highlight = &row_highlights[ix];
15705 if next_highlight
15706 .range
15707 .start
15708 .cmp(&highlight.range.end, &snapshot)
15709 .is_le()
15710 {
15711 if next_highlight
15712 .range
15713 .end
15714 .cmp(&highlight.range.end, &snapshot)
15715 .is_gt()
15716 {
15717 row_highlights[ix].range.end = next_highlight.range.end;
15718 }
15719 row_highlights.remove(ix + 1);
15720 } else {
15721 break;
15722 }
15723 }
15724 }
15725 }
15726
15727 /// Remove any highlighted row ranges of the given type that intersect the
15728 /// given ranges.
15729 pub fn remove_highlighted_rows<T: 'static>(
15730 &mut self,
15731 ranges_to_remove: Vec<Range<Anchor>>,
15732 cx: &mut Context<Self>,
15733 ) {
15734 let snapshot = self.buffer().read(cx).snapshot(cx);
15735 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
15736 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
15737 row_highlights.retain(|highlight| {
15738 while let Some(range_to_remove) = ranges_to_remove.peek() {
15739 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
15740 Ordering::Less | Ordering::Equal => {
15741 ranges_to_remove.next();
15742 }
15743 Ordering::Greater => {
15744 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
15745 Ordering::Less | Ordering::Equal => {
15746 return false;
15747 }
15748 Ordering::Greater => break,
15749 }
15750 }
15751 }
15752 }
15753
15754 true
15755 })
15756 }
15757
15758 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
15759 pub fn clear_row_highlights<T: 'static>(&mut self) {
15760 self.highlighted_rows.remove(&TypeId::of::<T>());
15761 }
15762
15763 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
15764 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
15765 self.highlighted_rows
15766 .get(&TypeId::of::<T>())
15767 .map_or(&[] as &[_], |vec| vec.as_slice())
15768 .iter()
15769 .map(|highlight| (highlight.range.clone(), highlight.color))
15770 }
15771
15772 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
15773 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
15774 /// Allows to ignore certain kinds of highlights.
15775 pub fn highlighted_display_rows(
15776 &self,
15777 window: &mut Window,
15778 cx: &mut App,
15779 ) -> BTreeMap<DisplayRow, LineHighlight> {
15780 let snapshot = self.snapshot(window, cx);
15781 let mut used_highlight_orders = HashMap::default();
15782 self.highlighted_rows
15783 .iter()
15784 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
15785 .fold(
15786 BTreeMap::<DisplayRow, LineHighlight>::new(),
15787 |mut unique_rows, highlight| {
15788 let start = highlight.range.start.to_display_point(&snapshot);
15789 let end = highlight.range.end.to_display_point(&snapshot);
15790 let start_row = start.row().0;
15791 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
15792 && end.column() == 0
15793 {
15794 end.row().0.saturating_sub(1)
15795 } else {
15796 end.row().0
15797 };
15798 for row in start_row..=end_row {
15799 let used_index =
15800 used_highlight_orders.entry(row).or_insert(highlight.index);
15801 if highlight.index >= *used_index {
15802 *used_index = highlight.index;
15803 unique_rows.insert(DisplayRow(row), highlight.color.into());
15804 }
15805 }
15806 unique_rows
15807 },
15808 )
15809 }
15810
15811 pub fn highlighted_display_row_for_autoscroll(
15812 &self,
15813 snapshot: &DisplaySnapshot,
15814 ) -> Option<DisplayRow> {
15815 self.highlighted_rows
15816 .values()
15817 .flat_map(|highlighted_rows| highlighted_rows.iter())
15818 .filter_map(|highlight| {
15819 if highlight.should_autoscroll {
15820 Some(highlight.range.start.to_display_point(snapshot).row())
15821 } else {
15822 None
15823 }
15824 })
15825 .min()
15826 }
15827
15828 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
15829 self.highlight_background::<SearchWithinRange>(
15830 ranges,
15831 |colors| colors.editor_document_highlight_read_background,
15832 cx,
15833 )
15834 }
15835
15836 pub fn set_breadcrumb_header(&mut self, new_header: String) {
15837 self.breadcrumb_header = Some(new_header);
15838 }
15839
15840 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
15841 self.clear_background_highlights::<SearchWithinRange>(cx);
15842 }
15843
15844 pub fn highlight_background<T: 'static>(
15845 &mut self,
15846 ranges: &[Range<Anchor>],
15847 color_fetcher: fn(&ThemeColors) -> Hsla,
15848 cx: &mut Context<Self>,
15849 ) {
15850 self.background_highlights
15851 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
15852 self.scrollbar_marker_state.dirty = true;
15853 cx.notify();
15854 }
15855
15856 pub fn clear_background_highlights<T: 'static>(
15857 &mut self,
15858 cx: &mut Context<Self>,
15859 ) -> Option<BackgroundHighlight> {
15860 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
15861 if !text_highlights.1.is_empty() {
15862 self.scrollbar_marker_state.dirty = true;
15863 cx.notify();
15864 }
15865 Some(text_highlights)
15866 }
15867
15868 pub fn highlight_gutter<T: 'static>(
15869 &mut self,
15870 ranges: &[Range<Anchor>],
15871 color_fetcher: fn(&App) -> Hsla,
15872 cx: &mut Context<Self>,
15873 ) {
15874 self.gutter_highlights
15875 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
15876 cx.notify();
15877 }
15878
15879 pub fn clear_gutter_highlights<T: 'static>(
15880 &mut self,
15881 cx: &mut Context<Self>,
15882 ) -> Option<GutterHighlight> {
15883 cx.notify();
15884 self.gutter_highlights.remove(&TypeId::of::<T>())
15885 }
15886
15887 #[cfg(feature = "test-support")]
15888 pub fn all_text_background_highlights(
15889 &self,
15890 window: &mut Window,
15891 cx: &mut Context<Self>,
15892 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
15893 let snapshot = self.snapshot(window, cx);
15894 let buffer = &snapshot.buffer_snapshot;
15895 let start = buffer.anchor_before(0);
15896 let end = buffer.anchor_after(buffer.len());
15897 let theme = cx.theme().colors();
15898 self.background_highlights_in_range(start..end, &snapshot, theme)
15899 }
15900
15901 #[cfg(feature = "test-support")]
15902 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
15903 let snapshot = self.buffer().read(cx).snapshot(cx);
15904
15905 let highlights = self
15906 .background_highlights
15907 .get(&TypeId::of::<items::BufferSearchHighlights>());
15908
15909 if let Some((_color, ranges)) = highlights {
15910 ranges
15911 .iter()
15912 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
15913 .collect_vec()
15914 } else {
15915 vec![]
15916 }
15917 }
15918
15919 fn document_highlights_for_position<'a>(
15920 &'a self,
15921 position: Anchor,
15922 buffer: &'a MultiBufferSnapshot,
15923 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
15924 let read_highlights = self
15925 .background_highlights
15926 .get(&TypeId::of::<DocumentHighlightRead>())
15927 .map(|h| &h.1);
15928 let write_highlights = self
15929 .background_highlights
15930 .get(&TypeId::of::<DocumentHighlightWrite>())
15931 .map(|h| &h.1);
15932 let left_position = position.bias_left(buffer);
15933 let right_position = position.bias_right(buffer);
15934 read_highlights
15935 .into_iter()
15936 .chain(write_highlights)
15937 .flat_map(move |ranges| {
15938 let start_ix = match ranges.binary_search_by(|probe| {
15939 let cmp = probe.end.cmp(&left_position, buffer);
15940 if cmp.is_ge() {
15941 Ordering::Greater
15942 } else {
15943 Ordering::Less
15944 }
15945 }) {
15946 Ok(i) | Err(i) => i,
15947 };
15948
15949 ranges[start_ix..]
15950 .iter()
15951 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
15952 })
15953 }
15954
15955 pub fn has_background_highlights<T: 'static>(&self) -> bool {
15956 self.background_highlights
15957 .get(&TypeId::of::<T>())
15958 .map_or(false, |(_, highlights)| !highlights.is_empty())
15959 }
15960
15961 pub fn background_highlights_in_range(
15962 &self,
15963 search_range: Range<Anchor>,
15964 display_snapshot: &DisplaySnapshot,
15965 theme: &ThemeColors,
15966 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
15967 let mut results = Vec::new();
15968 for (color_fetcher, ranges) in self.background_highlights.values() {
15969 let color = color_fetcher(theme);
15970 let start_ix = match ranges.binary_search_by(|probe| {
15971 let cmp = probe
15972 .end
15973 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
15974 if cmp.is_gt() {
15975 Ordering::Greater
15976 } else {
15977 Ordering::Less
15978 }
15979 }) {
15980 Ok(i) | Err(i) => i,
15981 };
15982 for range in &ranges[start_ix..] {
15983 if range
15984 .start
15985 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
15986 .is_ge()
15987 {
15988 break;
15989 }
15990
15991 let start = range.start.to_display_point(display_snapshot);
15992 let end = range.end.to_display_point(display_snapshot);
15993 results.push((start..end, color))
15994 }
15995 }
15996 results
15997 }
15998
15999 pub fn background_highlight_row_ranges<T: 'static>(
16000 &self,
16001 search_range: Range<Anchor>,
16002 display_snapshot: &DisplaySnapshot,
16003 count: usize,
16004 ) -> Vec<RangeInclusive<DisplayPoint>> {
16005 let mut results = Vec::new();
16006 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
16007 return vec![];
16008 };
16009
16010 let start_ix = match ranges.binary_search_by(|probe| {
16011 let cmp = probe
16012 .end
16013 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16014 if cmp.is_gt() {
16015 Ordering::Greater
16016 } else {
16017 Ordering::Less
16018 }
16019 }) {
16020 Ok(i) | Err(i) => i,
16021 };
16022 let mut push_region = |start: Option<Point>, end: Option<Point>| {
16023 if let (Some(start_display), Some(end_display)) = (start, end) {
16024 results.push(
16025 start_display.to_display_point(display_snapshot)
16026 ..=end_display.to_display_point(display_snapshot),
16027 );
16028 }
16029 };
16030 let mut start_row: Option<Point> = None;
16031 let mut end_row: Option<Point> = None;
16032 if ranges.len() > count {
16033 return Vec::new();
16034 }
16035 for range in &ranges[start_ix..] {
16036 if range
16037 .start
16038 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16039 .is_ge()
16040 {
16041 break;
16042 }
16043 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
16044 if let Some(current_row) = &end_row {
16045 if end.row == current_row.row {
16046 continue;
16047 }
16048 }
16049 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
16050 if start_row.is_none() {
16051 assert_eq!(end_row, None);
16052 start_row = Some(start);
16053 end_row = Some(end);
16054 continue;
16055 }
16056 if let Some(current_end) = end_row.as_mut() {
16057 if start.row > current_end.row + 1 {
16058 push_region(start_row, end_row);
16059 start_row = Some(start);
16060 end_row = Some(end);
16061 } else {
16062 // Merge two hunks.
16063 *current_end = end;
16064 }
16065 } else {
16066 unreachable!();
16067 }
16068 }
16069 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
16070 push_region(start_row, end_row);
16071 results
16072 }
16073
16074 pub fn gutter_highlights_in_range(
16075 &self,
16076 search_range: Range<Anchor>,
16077 display_snapshot: &DisplaySnapshot,
16078 cx: &App,
16079 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16080 let mut results = Vec::new();
16081 for (color_fetcher, ranges) in self.gutter_highlights.values() {
16082 let color = color_fetcher(cx);
16083 let start_ix = match ranges.binary_search_by(|probe| {
16084 let cmp = probe
16085 .end
16086 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16087 if cmp.is_gt() {
16088 Ordering::Greater
16089 } else {
16090 Ordering::Less
16091 }
16092 }) {
16093 Ok(i) | Err(i) => i,
16094 };
16095 for range in &ranges[start_ix..] {
16096 if range
16097 .start
16098 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16099 .is_ge()
16100 {
16101 break;
16102 }
16103
16104 let start = range.start.to_display_point(display_snapshot);
16105 let end = range.end.to_display_point(display_snapshot);
16106 results.push((start..end, color))
16107 }
16108 }
16109 results
16110 }
16111
16112 /// Get the text ranges corresponding to the redaction query
16113 pub fn redacted_ranges(
16114 &self,
16115 search_range: Range<Anchor>,
16116 display_snapshot: &DisplaySnapshot,
16117 cx: &App,
16118 ) -> Vec<Range<DisplayPoint>> {
16119 display_snapshot
16120 .buffer_snapshot
16121 .redacted_ranges(search_range, |file| {
16122 if let Some(file) = file {
16123 file.is_private()
16124 && EditorSettings::get(
16125 Some(SettingsLocation {
16126 worktree_id: file.worktree_id(cx),
16127 path: file.path().as_ref(),
16128 }),
16129 cx,
16130 )
16131 .redact_private_values
16132 } else {
16133 false
16134 }
16135 })
16136 .map(|range| {
16137 range.start.to_display_point(display_snapshot)
16138 ..range.end.to_display_point(display_snapshot)
16139 })
16140 .collect()
16141 }
16142
16143 pub fn highlight_text<T: 'static>(
16144 &mut self,
16145 ranges: Vec<Range<Anchor>>,
16146 style: HighlightStyle,
16147 cx: &mut Context<Self>,
16148 ) {
16149 self.display_map.update(cx, |map, _| {
16150 map.highlight_text(TypeId::of::<T>(), ranges, style)
16151 });
16152 cx.notify();
16153 }
16154
16155 pub(crate) fn highlight_inlays<T: 'static>(
16156 &mut self,
16157 highlights: Vec<InlayHighlight>,
16158 style: HighlightStyle,
16159 cx: &mut Context<Self>,
16160 ) {
16161 self.display_map.update(cx, |map, _| {
16162 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
16163 });
16164 cx.notify();
16165 }
16166
16167 pub fn text_highlights<'a, T: 'static>(
16168 &'a self,
16169 cx: &'a App,
16170 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
16171 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
16172 }
16173
16174 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
16175 let cleared = self
16176 .display_map
16177 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
16178 if cleared {
16179 cx.notify();
16180 }
16181 }
16182
16183 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
16184 (self.read_only(cx) || self.blink_manager.read(cx).visible())
16185 && self.focus_handle.is_focused(window)
16186 }
16187
16188 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
16189 self.show_cursor_when_unfocused = is_enabled;
16190 cx.notify();
16191 }
16192
16193 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
16194 cx.notify();
16195 }
16196
16197 fn on_buffer_event(
16198 &mut self,
16199 multibuffer: &Entity<MultiBuffer>,
16200 event: &multi_buffer::Event,
16201 window: &mut Window,
16202 cx: &mut Context<Self>,
16203 ) {
16204 match event {
16205 multi_buffer::Event::Edited {
16206 singleton_buffer_edited,
16207 edited_buffer: buffer_edited,
16208 } => {
16209 self.scrollbar_marker_state.dirty = true;
16210 self.active_indent_guides_state.dirty = true;
16211 self.refresh_active_diagnostics(cx);
16212 self.refresh_code_actions(window, cx);
16213 if self.has_active_inline_completion() {
16214 self.update_visible_inline_completion(window, cx);
16215 }
16216 if let Some(buffer) = buffer_edited {
16217 let buffer_id = buffer.read(cx).remote_id();
16218 if !self.registered_buffers.contains_key(&buffer_id) {
16219 if let Some(project) = self.project.as_ref() {
16220 project.update(cx, |project, cx| {
16221 self.registered_buffers.insert(
16222 buffer_id,
16223 project.register_buffer_with_language_servers(&buffer, cx),
16224 );
16225 })
16226 }
16227 }
16228 }
16229 cx.emit(EditorEvent::BufferEdited);
16230 cx.emit(SearchEvent::MatchesInvalidated);
16231 if *singleton_buffer_edited {
16232 if let Some(project) = &self.project {
16233 #[allow(clippy::mutable_key_type)]
16234 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
16235 multibuffer
16236 .all_buffers()
16237 .into_iter()
16238 .filter_map(|buffer| {
16239 buffer.update(cx, |buffer, cx| {
16240 let language = buffer.language()?;
16241 let should_discard = project.update(cx, |project, cx| {
16242 project.is_local()
16243 && !project.has_language_servers_for(buffer, cx)
16244 });
16245 should_discard.not().then_some(language.clone())
16246 })
16247 })
16248 .collect::<HashSet<_>>()
16249 });
16250 if !languages_affected.is_empty() {
16251 self.refresh_inlay_hints(
16252 InlayHintRefreshReason::BufferEdited(languages_affected),
16253 cx,
16254 );
16255 }
16256 }
16257 }
16258
16259 let Some(project) = &self.project else { return };
16260 let (telemetry, is_via_ssh) = {
16261 let project = project.read(cx);
16262 let telemetry = project.client().telemetry().clone();
16263 let is_via_ssh = project.is_via_ssh();
16264 (telemetry, is_via_ssh)
16265 };
16266 refresh_linked_ranges(self, window, cx);
16267 telemetry.log_edit_event("editor", is_via_ssh);
16268 }
16269 multi_buffer::Event::ExcerptsAdded {
16270 buffer,
16271 predecessor,
16272 excerpts,
16273 } => {
16274 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16275 let buffer_id = buffer.read(cx).remote_id();
16276 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
16277 if let Some(project) = &self.project {
16278 get_uncommitted_diff_for_buffer(
16279 project,
16280 [buffer.clone()],
16281 self.buffer.clone(),
16282 cx,
16283 )
16284 .detach();
16285 }
16286 }
16287 cx.emit(EditorEvent::ExcerptsAdded {
16288 buffer: buffer.clone(),
16289 predecessor: *predecessor,
16290 excerpts: excerpts.clone(),
16291 });
16292 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
16293 }
16294 multi_buffer::Event::ExcerptsRemoved { ids } => {
16295 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
16296 let buffer = self.buffer.read(cx);
16297 self.registered_buffers
16298 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
16299 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
16300 cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
16301 }
16302 multi_buffer::Event::ExcerptsEdited {
16303 excerpt_ids,
16304 buffer_ids,
16305 } => {
16306 self.display_map.update(cx, |map, cx| {
16307 map.unfold_buffers(buffer_ids.iter().copied(), cx)
16308 });
16309 cx.emit(EditorEvent::ExcerptsEdited {
16310 ids: excerpt_ids.clone(),
16311 })
16312 }
16313 multi_buffer::Event::ExcerptsExpanded { ids } => {
16314 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
16315 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
16316 }
16317 multi_buffer::Event::Reparsed(buffer_id) => {
16318 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16319 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
16320
16321 cx.emit(EditorEvent::Reparsed(*buffer_id));
16322 }
16323 multi_buffer::Event::DiffHunksToggled => {
16324 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16325 }
16326 multi_buffer::Event::LanguageChanged(buffer_id) => {
16327 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
16328 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
16329 cx.emit(EditorEvent::Reparsed(*buffer_id));
16330 cx.notify();
16331 }
16332 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
16333 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
16334 multi_buffer::Event::FileHandleChanged
16335 | multi_buffer::Event::Reloaded
16336 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
16337 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
16338 multi_buffer::Event::DiagnosticsUpdated => {
16339 self.refresh_active_diagnostics(cx);
16340 self.refresh_inline_diagnostics(true, window, cx);
16341 self.scrollbar_marker_state.dirty = true;
16342 cx.notify();
16343 }
16344 _ => {}
16345 };
16346 }
16347
16348 fn on_display_map_changed(
16349 &mut self,
16350 _: Entity<DisplayMap>,
16351 _: &mut Window,
16352 cx: &mut Context<Self>,
16353 ) {
16354 cx.notify();
16355 }
16356
16357 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16358 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16359 self.update_edit_prediction_settings(cx);
16360 self.refresh_inline_completion(true, false, window, cx);
16361 self.refresh_inlay_hints(
16362 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
16363 self.selections.newest_anchor().head(),
16364 &self.buffer.read(cx).snapshot(cx),
16365 cx,
16366 )),
16367 cx,
16368 );
16369
16370 let old_cursor_shape = self.cursor_shape;
16371
16372 {
16373 let editor_settings = EditorSettings::get_global(cx);
16374 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
16375 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
16376 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
16377 }
16378
16379 if old_cursor_shape != self.cursor_shape {
16380 cx.emit(EditorEvent::CursorShapeChanged);
16381 }
16382
16383 let project_settings = ProjectSettings::get_global(cx);
16384 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
16385
16386 if self.mode == EditorMode::Full {
16387 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
16388 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
16389 if self.show_inline_diagnostics != show_inline_diagnostics {
16390 self.show_inline_diagnostics = show_inline_diagnostics;
16391 self.refresh_inline_diagnostics(false, window, cx);
16392 }
16393
16394 if self.git_blame_inline_enabled != inline_blame_enabled {
16395 self.toggle_git_blame_inline_internal(false, window, cx);
16396 }
16397 }
16398
16399 cx.notify();
16400 }
16401
16402 pub fn set_searchable(&mut self, searchable: bool) {
16403 self.searchable = searchable;
16404 }
16405
16406 pub fn searchable(&self) -> bool {
16407 self.searchable
16408 }
16409
16410 fn open_proposed_changes_editor(
16411 &mut self,
16412 _: &OpenProposedChangesEditor,
16413 window: &mut Window,
16414 cx: &mut Context<Self>,
16415 ) {
16416 let Some(workspace) = self.workspace() else {
16417 cx.propagate();
16418 return;
16419 };
16420
16421 let selections = self.selections.all::<usize>(cx);
16422 let multi_buffer = self.buffer.read(cx);
16423 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16424 let mut new_selections_by_buffer = HashMap::default();
16425 for selection in selections {
16426 for (buffer, range, _) in
16427 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
16428 {
16429 let mut range = range.to_point(buffer);
16430 range.start.column = 0;
16431 range.end.column = buffer.line_len(range.end.row);
16432 new_selections_by_buffer
16433 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
16434 .or_insert(Vec::new())
16435 .push(range)
16436 }
16437 }
16438
16439 let proposed_changes_buffers = new_selections_by_buffer
16440 .into_iter()
16441 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
16442 .collect::<Vec<_>>();
16443 let proposed_changes_editor = cx.new(|cx| {
16444 ProposedChangesEditor::new(
16445 "Proposed changes",
16446 proposed_changes_buffers,
16447 self.project.clone(),
16448 window,
16449 cx,
16450 )
16451 });
16452
16453 window.defer(cx, move |window, cx| {
16454 workspace.update(cx, |workspace, cx| {
16455 workspace.active_pane().update(cx, |pane, cx| {
16456 pane.add_item(
16457 Box::new(proposed_changes_editor),
16458 true,
16459 true,
16460 None,
16461 window,
16462 cx,
16463 );
16464 });
16465 });
16466 });
16467 }
16468
16469 pub fn open_excerpts_in_split(
16470 &mut self,
16471 _: &OpenExcerptsSplit,
16472 window: &mut Window,
16473 cx: &mut Context<Self>,
16474 ) {
16475 self.open_excerpts_common(None, true, window, cx)
16476 }
16477
16478 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
16479 self.open_excerpts_common(None, false, window, cx)
16480 }
16481
16482 fn open_excerpts_common(
16483 &mut self,
16484 jump_data: Option<JumpData>,
16485 split: bool,
16486 window: &mut Window,
16487 cx: &mut Context<Self>,
16488 ) {
16489 let Some(workspace) = self.workspace() else {
16490 cx.propagate();
16491 return;
16492 };
16493
16494 if self.buffer.read(cx).is_singleton() {
16495 cx.propagate();
16496 return;
16497 }
16498
16499 let mut new_selections_by_buffer = HashMap::default();
16500 match &jump_data {
16501 Some(JumpData::MultiBufferPoint {
16502 excerpt_id,
16503 position,
16504 anchor,
16505 line_offset_from_top,
16506 }) => {
16507 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16508 if let Some(buffer) = multi_buffer_snapshot
16509 .buffer_id_for_excerpt(*excerpt_id)
16510 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
16511 {
16512 let buffer_snapshot = buffer.read(cx).snapshot();
16513 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
16514 language::ToPoint::to_point(anchor, &buffer_snapshot)
16515 } else {
16516 buffer_snapshot.clip_point(*position, Bias::Left)
16517 };
16518 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
16519 new_selections_by_buffer.insert(
16520 buffer,
16521 (
16522 vec![jump_to_offset..jump_to_offset],
16523 Some(*line_offset_from_top),
16524 ),
16525 );
16526 }
16527 }
16528 Some(JumpData::MultiBufferRow {
16529 row,
16530 line_offset_from_top,
16531 }) => {
16532 let point = MultiBufferPoint::new(row.0, 0);
16533 if let Some((buffer, buffer_point, _)) =
16534 self.buffer.read(cx).point_to_buffer_point(point, cx)
16535 {
16536 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
16537 new_selections_by_buffer
16538 .entry(buffer)
16539 .or_insert((Vec::new(), Some(*line_offset_from_top)))
16540 .0
16541 .push(buffer_offset..buffer_offset)
16542 }
16543 }
16544 None => {
16545 let selections = self.selections.all::<usize>(cx);
16546 let multi_buffer = self.buffer.read(cx);
16547 for selection in selections {
16548 for (snapshot, range, _, anchor) in multi_buffer
16549 .snapshot(cx)
16550 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
16551 {
16552 if let Some(anchor) = anchor {
16553 // selection is in a deleted hunk
16554 let Some(buffer_id) = anchor.buffer_id else {
16555 continue;
16556 };
16557 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
16558 continue;
16559 };
16560 let offset = text::ToOffset::to_offset(
16561 &anchor.text_anchor,
16562 &buffer_handle.read(cx).snapshot(),
16563 );
16564 let range = offset..offset;
16565 new_selections_by_buffer
16566 .entry(buffer_handle)
16567 .or_insert((Vec::new(), None))
16568 .0
16569 .push(range)
16570 } else {
16571 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
16572 else {
16573 continue;
16574 };
16575 new_selections_by_buffer
16576 .entry(buffer_handle)
16577 .or_insert((Vec::new(), None))
16578 .0
16579 .push(range)
16580 }
16581 }
16582 }
16583 }
16584 }
16585
16586 if new_selections_by_buffer.is_empty() {
16587 return;
16588 }
16589
16590 // We defer the pane interaction because we ourselves are a workspace item
16591 // and activating a new item causes the pane to call a method on us reentrantly,
16592 // which panics if we're on the stack.
16593 window.defer(cx, move |window, cx| {
16594 workspace.update(cx, |workspace, cx| {
16595 let pane = if split {
16596 workspace.adjacent_pane(window, cx)
16597 } else {
16598 workspace.active_pane().clone()
16599 };
16600
16601 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
16602 let editor = buffer
16603 .read(cx)
16604 .file()
16605 .is_none()
16606 .then(|| {
16607 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
16608 // so `workspace.open_project_item` will never find them, always opening a new editor.
16609 // Instead, we try to activate the existing editor in the pane first.
16610 let (editor, pane_item_index) =
16611 pane.read(cx).items().enumerate().find_map(|(i, item)| {
16612 let editor = item.downcast::<Editor>()?;
16613 let singleton_buffer =
16614 editor.read(cx).buffer().read(cx).as_singleton()?;
16615 if singleton_buffer == buffer {
16616 Some((editor, i))
16617 } else {
16618 None
16619 }
16620 })?;
16621 pane.update(cx, |pane, cx| {
16622 pane.activate_item(pane_item_index, true, true, window, cx)
16623 });
16624 Some(editor)
16625 })
16626 .flatten()
16627 .unwrap_or_else(|| {
16628 workspace.open_project_item::<Self>(
16629 pane.clone(),
16630 buffer,
16631 true,
16632 true,
16633 window,
16634 cx,
16635 )
16636 });
16637
16638 editor.update(cx, |editor, cx| {
16639 let autoscroll = match scroll_offset {
16640 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
16641 None => Autoscroll::newest(),
16642 };
16643 let nav_history = editor.nav_history.take();
16644 editor.change_selections(Some(autoscroll), window, cx, |s| {
16645 s.select_ranges(ranges);
16646 });
16647 editor.nav_history = nav_history;
16648 });
16649 }
16650 })
16651 });
16652 }
16653
16654 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
16655 let snapshot = self.buffer.read(cx).read(cx);
16656 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
16657 Some(
16658 ranges
16659 .iter()
16660 .map(move |range| {
16661 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
16662 })
16663 .collect(),
16664 )
16665 }
16666
16667 fn selection_replacement_ranges(
16668 &self,
16669 range: Range<OffsetUtf16>,
16670 cx: &mut App,
16671 ) -> Vec<Range<OffsetUtf16>> {
16672 let selections = self.selections.all::<OffsetUtf16>(cx);
16673 let newest_selection = selections
16674 .iter()
16675 .max_by_key(|selection| selection.id)
16676 .unwrap();
16677 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
16678 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
16679 let snapshot = self.buffer.read(cx).read(cx);
16680 selections
16681 .into_iter()
16682 .map(|mut selection| {
16683 selection.start.0 =
16684 (selection.start.0 as isize).saturating_add(start_delta) as usize;
16685 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
16686 snapshot.clip_offset_utf16(selection.start, Bias::Left)
16687 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
16688 })
16689 .collect()
16690 }
16691
16692 fn report_editor_event(
16693 &self,
16694 event_type: &'static str,
16695 file_extension: Option<String>,
16696 cx: &App,
16697 ) {
16698 if cfg!(any(test, feature = "test-support")) {
16699 return;
16700 }
16701
16702 let Some(project) = &self.project else { return };
16703
16704 // If None, we are in a file without an extension
16705 let file = self
16706 .buffer
16707 .read(cx)
16708 .as_singleton()
16709 .and_then(|b| b.read(cx).file());
16710 let file_extension = file_extension.or(file
16711 .as_ref()
16712 .and_then(|file| Path::new(file.file_name(cx)).extension())
16713 .and_then(|e| e.to_str())
16714 .map(|a| a.to_string()));
16715
16716 let vim_mode = cx
16717 .global::<SettingsStore>()
16718 .raw_user_settings()
16719 .get("vim_mode")
16720 == Some(&serde_json::Value::Bool(true));
16721
16722 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
16723 let copilot_enabled = edit_predictions_provider
16724 == language::language_settings::EditPredictionProvider::Copilot;
16725 let copilot_enabled_for_language = self
16726 .buffer
16727 .read(cx)
16728 .language_settings(cx)
16729 .show_edit_predictions;
16730
16731 let project = project.read(cx);
16732 telemetry::event!(
16733 event_type,
16734 file_extension,
16735 vim_mode,
16736 copilot_enabled,
16737 copilot_enabled_for_language,
16738 edit_predictions_provider,
16739 is_via_ssh = project.is_via_ssh(),
16740 );
16741 }
16742
16743 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
16744 /// with each line being an array of {text, highlight} objects.
16745 fn copy_highlight_json(
16746 &mut self,
16747 _: &CopyHighlightJson,
16748 window: &mut Window,
16749 cx: &mut Context<Self>,
16750 ) {
16751 #[derive(Serialize)]
16752 struct Chunk<'a> {
16753 text: String,
16754 highlight: Option<&'a str>,
16755 }
16756
16757 let snapshot = self.buffer.read(cx).snapshot(cx);
16758 let range = self
16759 .selected_text_range(false, window, cx)
16760 .and_then(|selection| {
16761 if selection.range.is_empty() {
16762 None
16763 } else {
16764 Some(selection.range)
16765 }
16766 })
16767 .unwrap_or_else(|| 0..snapshot.len());
16768
16769 let chunks = snapshot.chunks(range, true);
16770 let mut lines = Vec::new();
16771 let mut line: VecDeque<Chunk> = VecDeque::new();
16772
16773 let Some(style) = self.style.as_ref() else {
16774 return;
16775 };
16776
16777 for chunk in chunks {
16778 let highlight = chunk
16779 .syntax_highlight_id
16780 .and_then(|id| id.name(&style.syntax));
16781 let mut chunk_lines = chunk.text.split('\n').peekable();
16782 while let Some(text) = chunk_lines.next() {
16783 let mut merged_with_last_token = false;
16784 if let Some(last_token) = line.back_mut() {
16785 if last_token.highlight == highlight {
16786 last_token.text.push_str(text);
16787 merged_with_last_token = true;
16788 }
16789 }
16790
16791 if !merged_with_last_token {
16792 line.push_back(Chunk {
16793 text: text.into(),
16794 highlight,
16795 });
16796 }
16797
16798 if chunk_lines.peek().is_some() {
16799 if line.len() > 1 && line.front().unwrap().text.is_empty() {
16800 line.pop_front();
16801 }
16802 if line.len() > 1 && line.back().unwrap().text.is_empty() {
16803 line.pop_back();
16804 }
16805
16806 lines.push(mem::take(&mut line));
16807 }
16808 }
16809 }
16810
16811 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
16812 return;
16813 };
16814 cx.write_to_clipboard(ClipboardItem::new_string(lines));
16815 }
16816
16817 pub fn open_context_menu(
16818 &mut self,
16819 _: &OpenContextMenu,
16820 window: &mut Window,
16821 cx: &mut Context<Self>,
16822 ) {
16823 self.request_autoscroll(Autoscroll::newest(), cx);
16824 let position = self.selections.newest_display(cx).start;
16825 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
16826 }
16827
16828 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
16829 &self.inlay_hint_cache
16830 }
16831
16832 pub fn replay_insert_event(
16833 &mut self,
16834 text: &str,
16835 relative_utf16_range: Option<Range<isize>>,
16836 window: &mut Window,
16837 cx: &mut Context<Self>,
16838 ) {
16839 if !self.input_enabled {
16840 cx.emit(EditorEvent::InputIgnored { text: text.into() });
16841 return;
16842 }
16843 if let Some(relative_utf16_range) = relative_utf16_range {
16844 let selections = self.selections.all::<OffsetUtf16>(cx);
16845 self.change_selections(None, window, cx, |s| {
16846 let new_ranges = selections.into_iter().map(|range| {
16847 let start = OffsetUtf16(
16848 range
16849 .head()
16850 .0
16851 .saturating_add_signed(relative_utf16_range.start),
16852 );
16853 let end = OffsetUtf16(
16854 range
16855 .head()
16856 .0
16857 .saturating_add_signed(relative_utf16_range.end),
16858 );
16859 start..end
16860 });
16861 s.select_ranges(new_ranges);
16862 });
16863 }
16864
16865 self.handle_input(text, window, cx);
16866 }
16867
16868 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
16869 let Some(provider) = self.semantics_provider.as_ref() else {
16870 return false;
16871 };
16872
16873 let mut supports = false;
16874 self.buffer().update(cx, |this, cx| {
16875 this.for_each_buffer(|buffer| {
16876 supports |= provider.supports_inlay_hints(buffer, cx);
16877 });
16878 });
16879
16880 supports
16881 }
16882
16883 pub fn is_focused(&self, window: &Window) -> bool {
16884 self.focus_handle.is_focused(window)
16885 }
16886
16887 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16888 cx.emit(EditorEvent::Focused);
16889
16890 if let Some(descendant) = self
16891 .last_focused_descendant
16892 .take()
16893 .and_then(|descendant| descendant.upgrade())
16894 {
16895 window.focus(&descendant);
16896 } else {
16897 if let Some(blame) = self.blame.as_ref() {
16898 blame.update(cx, GitBlame::focus)
16899 }
16900
16901 self.blink_manager.update(cx, BlinkManager::enable);
16902 self.show_cursor_names(window, cx);
16903 self.buffer.update(cx, |buffer, cx| {
16904 buffer.finalize_last_transaction(cx);
16905 if self.leader_peer_id.is_none() {
16906 buffer.set_active_selections(
16907 &self.selections.disjoint_anchors(),
16908 self.selections.line_mode,
16909 self.cursor_shape,
16910 cx,
16911 );
16912 }
16913 });
16914 }
16915 }
16916
16917 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
16918 cx.emit(EditorEvent::FocusedIn)
16919 }
16920
16921 fn handle_focus_out(
16922 &mut self,
16923 event: FocusOutEvent,
16924 _window: &mut Window,
16925 cx: &mut Context<Self>,
16926 ) {
16927 if event.blurred != self.focus_handle {
16928 self.last_focused_descendant = Some(event.blurred);
16929 }
16930 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
16931 }
16932
16933 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16934 self.blink_manager.update(cx, BlinkManager::disable);
16935 self.buffer
16936 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
16937
16938 if let Some(blame) = self.blame.as_ref() {
16939 blame.update(cx, GitBlame::blur)
16940 }
16941 if !self.hover_state.focused(window, cx) {
16942 hide_hover(self, cx);
16943 }
16944 if !self
16945 .context_menu
16946 .borrow()
16947 .as_ref()
16948 .is_some_and(|context_menu| context_menu.focused(window, cx))
16949 {
16950 self.hide_context_menu(window, cx);
16951 }
16952 self.discard_inline_completion(false, cx);
16953 cx.emit(EditorEvent::Blurred);
16954 cx.notify();
16955 }
16956
16957 pub fn register_action<A: Action>(
16958 &mut self,
16959 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
16960 ) -> Subscription {
16961 let id = self.next_editor_action_id.post_inc();
16962 let listener = Arc::new(listener);
16963 self.editor_actions.borrow_mut().insert(
16964 id,
16965 Box::new(move |window, _| {
16966 let listener = listener.clone();
16967 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
16968 let action = action.downcast_ref().unwrap();
16969 if phase == DispatchPhase::Bubble {
16970 listener(action, window, cx)
16971 }
16972 })
16973 }),
16974 );
16975
16976 let editor_actions = self.editor_actions.clone();
16977 Subscription::new(move || {
16978 editor_actions.borrow_mut().remove(&id);
16979 })
16980 }
16981
16982 pub fn file_header_size(&self) -> u32 {
16983 FILE_HEADER_HEIGHT
16984 }
16985
16986 pub fn restore(
16987 &mut self,
16988 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
16989 window: &mut Window,
16990 cx: &mut Context<Self>,
16991 ) {
16992 let workspace = self.workspace();
16993 let project = self.project.as_ref();
16994 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
16995 let mut tasks = Vec::new();
16996 for (buffer_id, changes) in revert_changes {
16997 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
16998 buffer.update(cx, |buffer, cx| {
16999 buffer.edit(
17000 changes
17001 .into_iter()
17002 .map(|(range, text)| (range, text.to_string())),
17003 None,
17004 cx,
17005 );
17006 });
17007
17008 if let Some(project) =
17009 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
17010 {
17011 project.update(cx, |project, cx| {
17012 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
17013 })
17014 }
17015 }
17016 }
17017 tasks
17018 });
17019 cx.spawn_in(window, async move |_, cx| {
17020 for (buffer, task) in save_tasks {
17021 let result = task.await;
17022 if result.is_err() {
17023 let Some(path) = buffer
17024 .read_with(cx, |buffer, cx| buffer.project_path(cx))
17025 .ok()
17026 else {
17027 continue;
17028 };
17029 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
17030 let Some(task) = cx
17031 .update_window_entity(&workspace, |workspace, window, cx| {
17032 workspace
17033 .open_path_preview(path, None, false, false, false, window, cx)
17034 })
17035 .ok()
17036 else {
17037 continue;
17038 };
17039 task.await.log_err();
17040 }
17041 }
17042 }
17043 })
17044 .detach();
17045 self.change_selections(None, window, cx, |selections| selections.refresh());
17046 }
17047
17048 pub fn to_pixel_point(
17049 &self,
17050 source: multi_buffer::Anchor,
17051 editor_snapshot: &EditorSnapshot,
17052 window: &mut Window,
17053 ) -> Option<gpui::Point<Pixels>> {
17054 let source_point = source.to_display_point(editor_snapshot);
17055 self.display_to_pixel_point(source_point, editor_snapshot, window)
17056 }
17057
17058 pub fn display_to_pixel_point(
17059 &self,
17060 source: DisplayPoint,
17061 editor_snapshot: &EditorSnapshot,
17062 window: &mut Window,
17063 ) -> Option<gpui::Point<Pixels>> {
17064 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
17065 let text_layout_details = self.text_layout_details(window);
17066 let scroll_top = text_layout_details
17067 .scroll_anchor
17068 .scroll_position(editor_snapshot)
17069 .y;
17070
17071 if source.row().as_f32() < scroll_top.floor() {
17072 return None;
17073 }
17074 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
17075 let source_y = line_height * (source.row().as_f32() - scroll_top);
17076 Some(gpui::Point::new(source_x, source_y))
17077 }
17078
17079 pub fn has_visible_completions_menu(&self) -> bool {
17080 !self.edit_prediction_preview_is_active()
17081 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
17082 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
17083 })
17084 }
17085
17086 pub fn register_addon<T: Addon>(&mut self, instance: T) {
17087 self.addons
17088 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
17089 }
17090
17091 pub fn unregister_addon<T: Addon>(&mut self) {
17092 self.addons.remove(&std::any::TypeId::of::<T>());
17093 }
17094
17095 pub fn addon<T: Addon>(&self) -> Option<&T> {
17096 let type_id = std::any::TypeId::of::<T>();
17097 self.addons
17098 .get(&type_id)
17099 .and_then(|item| item.to_any().downcast_ref::<T>())
17100 }
17101
17102 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
17103 let text_layout_details = self.text_layout_details(window);
17104 let style = &text_layout_details.editor_style;
17105 let font_id = window.text_system().resolve_font(&style.text.font());
17106 let font_size = style.text.font_size.to_pixels(window.rem_size());
17107 let line_height = style.text.line_height_in_pixels(window.rem_size());
17108 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
17109
17110 gpui::Size::new(em_width, line_height)
17111 }
17112
17113 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
17114 self.load_diff_task.clone()
17115 }
17116
17117 fn read_selections_from_db(
17118 &mut self,
17119 item_id: u64,
17120 workspace_id: WorkspaceId,
17121 window: &mut Window,
17122 cx: &mut Context<Editor>,
17123 ) {
17124 if !self.is_singleton(cx)
17125 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
17126 {
17127 return;
17128 }
17129 let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() else {
17130 return;
17131 };
17132 if selections.is_empty() {
17133 return;
17134 }
17135
17136 let snapshot = self.buffer.read(cx).snapshot(cx);
17137 self.change_selections(None, window, cx, |s| {
17138 s.select_ranges(selections.into_iter().map(|(start, end)| {
17139 snapshot.clip_offset(start, Bias::Left)..snapshot.clip_offset(end, Bias::Right)
17140 }));
17141 });
17142 }
17143}
17144
17145fn insert_extra_newline_brackets(
17146 buffer: &MultiBufferSnapshot,
17147 range: Range<usize>,
17148 language: &language::LanguageScope,
17149) -> bool {
17150 let leading_whitespace_len = buffer
17151 .reversed_chars_at(range.start)
17152 .take_while(|c| c.is_whitespace() && *c != '\n')
17153 .map(|c| c.len_utf8())
17154 .sum::<usize>();
17155 let trailing_whitespace_len = buffer
17156 .chars_at(range.end)
17157 .take_while(|c| c.is_whitespace() && *c != '\n')
17158 .map(|c| c.len_utf8())
17159 .sum::<usize>();
17160 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
17161
17162 language.brackets().any(|(pair, enabled)| {
17163 let pair_start = pair.start.trim_end();
17164 let pair_end = pair.end.trim_start();
17165
17166 enabled
17167 && pair.newline
17168 && buffer.contains_str_at(range.end, pair_end)
17169 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
17170 })
17171}
17172
17173fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
17174 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
17175 [(buffer, range, _)] => (*buffer, range.clone()),
17176 _ => return false,
17177 };
17178 let pair = {
17179 let mut result: Option<BracketMatch> = None;
17180
17181 for pair in buffer
17182 .all_bracket_ranges(range.clone())
17183 .filter(move |pair| {
17184 pair.open_range.start <= range.start && pair.close_range.end >= range.end
17185 })
17186 {
17187 let len = pair.close_range.end - pair.open_range.start;
17188
17189 if let Some(existing) = &result {
17190 let existing_len = existing.close_range.end - existing.open_range.start;
17191 if len > existing_len {
17192 continue;
17193 }
17194 }
17195
17196 result = Some(pair);
17197 }
17198
17199 result
17200 };
17201 let Some(pair) = pair else {
17202 return false;
17203 };
17204 pair.newline_only
17205 && buffer
17206 .chars_for_range(pair.open_range.end..range.start)
17207 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
17208 .all(|c| c.is_whitespace() && c != '\n')
17209}
17210
17211fn get_uncommitted_diff_for_buffer(
17212 project: &Entity<Project>,
17213 buffers: impl IntoIterator<Item = Entity<Buffer>>,
17214 buffer: Entity<MultiBuffer>,
17215 cx: &mut App,
17216) -> Task<()> {
17217 let mut tasks = Vec::new();
17218 project.update(cx, |project, cx| {
17219 for buffer in buffers {
17220 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
17221 }
17222 });
17223 cx.spawn(async move |cx| {
17224 let diffs = future::join_all(tasks).await;
17225 buffer
17226 .update(cx, |buffer, cx| {
17227 for diff in diffs.into_iter().flatten() {
17228 buffer.add_diff(diff, cx);
17229 }
17230 })
17231 .ok();
17232 })
17233}
17234
17235fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
17236 let tab_size = tab_size.get() as usize;
17237 let mut width = offset;
17238
17239 for ch in text.chars() {
17240 width += if ch == '\t' {
17241 tab_size - (width % tab_size)
17242 } else {
17243 1
17244 };
17245 }
17246
17247 width - offset
17248}
17249
17250#[cfg(test)]
17251mod tests {
17252 use super::*;
17253
17254 #[test]
17255 fn test_string_size_with_expanded_tabs() {
17256 let nz = |val| NonZeroU32::new(val).unwrap();
17257 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
17258 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
17259 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
17260 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
17261 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
17262 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
17263 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
17264 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
17265 }
17266}
17267
17268/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
17269struct WordBreakingTokenizer<'a> {
17270 input: &'a str,
17271}
17272
17273impl<'a> WordBreakingTokenizer<'a> {
17274 fn new(input: &'a str) -> Self {
17275 Self { input }
17276 }
17277}
17278
17279fn is_char_ideographic(ch: char) -> bool {
17280 use unicode_script::Script::*;
17281 use unicode_script::UnicodeScript;
17282 matches!(ch.script(), Han | Tangut | Yi)
17283}
17284
17285fn is_grapheme_ideographic(text: &str) -> bool {
17286 text.chars().any(is_char_ideographic)
17287}
17288
17289fn is_grapheme_whitespace(text: &str) -> bool {
17290 text.chars().any(|x| x.is_whitespace())
17291}
17292
17293fn should_stay_with_preceding_ideograph(text: &str) -> bool {
17294 text.chars().next().map_or(false, |ch| {
17295 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
17296 })
17297}
17298
17299#[derive(PartialEq, Eq, Debug, Clone, Copy)]
17300enum WordBreakToken<'a> {
17301 Word { token: &'a str, grapheme_len: usize },
17302 InlineWhitespace { token: &'a str, grapheme_len: usize },
17303 Newline,
17304}
17305
17306impl<'a> Iterator for WordBreakingTokenizer<'a> {
17307 /// Yields a span, the count of graphemes in the token, and whether it was
17308 /// whitespace. Note that it also breaks at word boundaries.
17309 type Item = WordBreakToken<'a>;
17310
17311 fn next(&mut self) -> Option<Self::Item> {
17312 use unicode_segmentation::UnicodeSegmentation;
17313 if self.input.is_empty() {
17314 return None;
17315 }
17316
17317 let mut iter = self.input.graphemes(true).peekable();
17318 let mut offset = 0;
17319 let mut grapheme_len = 0;
17320 if let Some(first_grapheme) = iter.next() {
17321 let is_newline = first_grapheme == "\n";
17322 let is_whitespace = is_grapheme_whitespace(first_grapheme);
17323 offset += first_grapheme.len();
17324 grapheme_len += 1;
17325 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
17326 if let Some(grapheme) = iter.peek().copied() {
17327 if should_stay_with_preceding_ideograph(grapheme) {
17328 offset += grapheme.len();
17329 grapheme_len += 1;
17330 }
17331 }
17332 } else {
17333 let mut words = self.input[offset..].split_word_bound_indices().peekable();
17334 let mut next_word_bound = words.peek().copied();
17335 if next_word_bound.map_or(false, |(i, _)| i == 0) {
17336 next_word_bound = words.next();
17337 }
17338 while let Some(grapheme) = iter.peek().copied() {
17339 if next_word_bound.map_or(false, |(i, _)| i == offset) {
17340 break;
17341 };
17342 if is_grapheme_whitespace(grapheme) != is_whitespace
17343 || (grapheme == "\n") != is_newline
17344 {
17345 break;
17346 };
17347 offset += grapheme.len();
17348 grapheme_len += 1;
17349 iter.next();
17350 }
17351 }
17352 let token = &self.input[..offset];
17353 self.input = &self.input[offset..];
17354 if token == "\n" {
17355 Some(WordBreakToken::Newline)
17356 } else if is_whitespace {
17357 Some(WordBreakToken::InlineWhitespace {
17358 token,
17359 grapheme_len,
17360 })
17361 } else {
17362 Some(WordBreakToken::Word {
17363 token,
17364 grapheme_len,
17365 })
17366 }
17367 } else {
17368 None
17369 }
17370 }
17371}
17372
17373#[test]
17374fn test_word_breaking_tokenizer() {
17375 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
17376 ("", &[]),
17377 (" ", &[whitespace(" ", 2)]),
17378 ("Ʒ", &[word("Ʒ", 1)]),
17379 ("Ǽ", &[word("Ǽ", 1)]),
17380 ("⋑", &[word("⋑", 1)]),
17381 ("⋑⋑", &[word("⋑⋑", 2)]),
17382 (
17383 "原理,进而",
17384 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
17385 ),
17386 (
17387 "hello world",
17388 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
17389 ),
17390 (
17391 "hello, world",
17392 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
17393 ),
17394 (
17395 " hello world",
17396 &[
17397 whitespace(" ", 2),
17398 word("hello", 5),
17399 whitespace(" ", 1),
17400 word("world", 5),
17401 ],
17402 ),
17403 (
17404 "这是什么 \n 钢笔",
17405 &[
17406 word("这", 1),
17407 word("是", 1),
17408 word("什", 1),
17409 word("么", 1),
17410 whitespace(" ", 1),
17411 newline(),
17412 whitespace(" ", 1),
17413 word("钢", 1),
17414 word("笔", 1),
17415 ],
17416 ),
17417 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
17418 ];
17419
17420 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
17421 WordBreakToken::Word {
17422 token,
17423 grapheme_len,
17424 }
17425 }
17426
17427 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
17428 WordBreakToken::InlineWhitespace {
17429 token,
17430 grapheme_len,
17431 }
17432 }
17433
17434 fn newline() -> WordBreakToken<'static> {
17435 WordBreakToken::Newline
17436 }
17437
17438 for (input, result) in tests {
17439 assert_eq!(
17440 WordBreakingTokenizer::new(input)
17441 .collect::<Vec<_>>()
17442 .as_slice(),
17443 *result,
17444 );
17445 }
17446}
17447
17448fn wrap_with_prefix(
17449 line_prefix: String,
17450 unwrapped_text: String,
17451 wrap_column: usize,
17452 tab_size: NonZeroU32,
17453 preserve_existing_whitespace: bool,
17454) -> String {
17455 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
17456 let mut wrapped_text = String::new();
17457 let mut current_line = line_prefix.clone();
17458
17459 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
17460 let mut current_line_len = line_prefix_len;
17461 let mut in_whitespace = false;
17462 for token in tokenizer {
17463 let have_preceding_whitespace = in_whitespace;
17464 match token {
17465 WordBreakToken::Word {
17466 token,
17467 grapheme_len,
17468 } => {
17469 in_whitespace = false;
17470 if current_line_len + grapheme_len > wrap_column
17471 && current_line_len != line_prefix_len
17472 {
17473 wrapped_text.push_str(current_line.trim_end());
17474 wrapped_text.push('\n');
17475 current_line.truncate(line_prefix.len());
17476 current_line_len = line_prefix_len;
17477 }
17478 current_line.push_str(token);
17479 current_line_len += grapheme_len;
17480 }
17481 WordBreakToken::InlineWhitespace {
17482 mut token,
17483 mut grapheme_len,
17484 } => {
17485 in_whitespace = true;
17486 if have_preceding_whitespace && !preserve_existing_whitespace {
17487 continue;
17488 }
17489 if !preserve_existing_whitespace {
17490 token = " ";
17491 grapheme_len = 1;
17492 }
17493 if current_line_len + grapheme_len > wrap_column {
17494 wrapped_text.push_str(current_line.trim_end());
17495 wrapped_text.push('\n');
17496 current_line.truncate(line_prefix.len());
17497 current_line_len = line_prefix_len;
17498 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
17499 current_line.push_str(token);
17500 current_line_len += grapheme_len;
17501 }
17502 }
17503 WordBreakToken::Newline => {
17504 in_whitespace = true;
17505 if preserve_existing_whitespace {
17506 wrapped_text.push_str(current_line.trim_end());
17507 wrapped_text.push('\n');
17508 current_line.truncate(line_prefix.len());
17509 current_line_len = line_prefix_len;
17510 } else if have_preceding_whitespace {
17511 continue;
17512 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
17513 {
17514 wrapped_text.push_str(current_line.trim_end());
17515 wrapped_text.push('\n');
17516 current_line.truncate(line_prefix.len());
17517 current_line_len = line_prefix_len;
17518 } else if current_line_len != line_prefix_len {
17519 current_line.push(' ');
17520 current_line_len += 1;
17521 }
17522 }
17523 }
17524 }
17525
17526 if !current_line.is_empty() {
17527 wrapped_text.push_str(¤t_line);
17528 }
17529 wrapped_text
17530}
17531
17532#[test]
17533fn test_wrap_with_prefix() {
17534 assert_eq!(
17535 wrap_with_prefix(
17536 "# ".to_string(),
17537 "abcdefg".to_string(),
17538 4,
17539 NonZeroU32::new(4).unwrap(),
17540 false,
17541 ),
17542 "# abcdefg"
17543 );
17544 assert_eq!(
17545 wrap_with_prefix(
17546 "".to_string(),
17547 "\thello world".to_string(),
17548 8,
17549 NonZeroU32::new(4).unwrap(),
17550 false,
17551 ),
17552 "hello\nworld"
17553 );
17554 assert_eq!(
17555 wrap_with_prefix(
17556 "// ".to_string(),
17557 "xx \nyy zz aa bb cc".to_string(),
17558 12,
17559 NonZeroU32::new(4).unwrap(),
17560 false,
17561 ),
17562 "// xx yy zz\n// aa bb cc"
17563 );
17564 assert_eq!(
17565 wrap_with_prefix(
17566 String::new(),
17567 "这是什么 \n 钢笔".to_string(),
17568 3,
17569 NonZeroU32::new(4).unwrap(),
17570 false,
17571 ),
17572 "这是什\n么 钢\n笔"
17573 );
17574}
17575
17576pub trait CollaborationHub {
17577 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
17578 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
17579 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
17580}
17581
17582impl CollaborationHub for Entity<Project> {
17583 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
17584 self.read(cx).collaborators()
17585 }
17586
17587 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
17588 self.read(cx).user_store().read(cx).participant_indices()
17589 }
17590
17591 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
17592 let this = self.read(cx);
17593 let user_ids = this.collaborators().values().map(|c| c.user_id);
17594 this.user_store().read_with(cx, |user_store, cx| {
17595 user_store.participant_names(user_ids, cx)
17596 })
17597 }
17598}
17599
17600pub trait SemanticsProvider {
17601 fn hover(
17602 &self,
17603 buffer: &Entity<Buffer>,
17604 position: text::Anchor,
17605 cx: &mut App,
17606 ) -> Option<Task<Vec<project::Hover>>>;
17607
17608 fn inlay_hints(
17609 &self,
17610 buffer_handle: Entity<Buffer>,
17611 range: Range<text::Anchor>,
17612 cx: &mut App,
17613 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
17614
17615 fn resolve_inlay_hint(
17616 &self,
17617 hint: InlayHint,
17618 buffer_handle: Entity<Buffer>,
17619 server_id: LanguageServerId,
17620 cx: &mut App,
17621 ) -> Option<Task<anyhow::Result<InlayHint>>>;
17622
17623 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
17624
17625 fn document_highlights(
17626 &self,
17627 buffer: &Entity<Buffer>,
17628 position: text::Anchor,
17629 cx: &mut App,
17630 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
17631
17632 fn definitions(
17633 &self,
17634 buffer: &Entity<Buffer>,
17635 position: text::Anchor,
17636 kind: GotoDefinitionKind,
17637 cx: &mut App,
17638 ) -> Option<Task<Result<Vec<LocationLink>>>>;
17639
17640 fn range_for_rename(
17641 &self,
17642 buffer: &Entity<Buffer>,
17643 position: text::Anchor,
17644 cx: &mut App,
17645 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
17646
17647 fn perform_rename(
17648 &self,
17649 buffer: &Entity<Buffer>,
17650 position: text::Anchor,
17651 new_name: String,
17652 cx: &mut App,
17653 ) -> Option<Task<Result<ProjectTransaction>>>;
17654}
17655
17656pub trait CompletionProvider {
17657 fn completions(
17658 &self,
17659 buffer: &Entity<Buffer>,
17660 buffer_position: text::Anchor,
17661 trigger: CompletionContext,
17662 window: &mut Window,
17663 cx: &mut Context<Editor>,
17664 ) -> Task<Result<Option<Vec<Completion>>>>;
17665
17666 fn resolve_completions(
17667 &self,
17668 buffer: Entity<Buffer>,
17669 completion_indices: Vec<usize>,
17670 completions: Rc<RefCell<Box<[Completion]>>>,
17671 cx: &mut Context<Editor>,
17672 ) -> Task<Result<bool>>;
17673
17674 fn apply_additional_edits_for_completion(
17675 &self,
17676 _buffer: Entity<Buffer>,
17677 _completions: Rc<RefCell<Box<[Completion]>>>,
17678 _completion_index: usize,
17679 _push_to_history: bool,
17680 _cx: &mut Context<Editor>,
17681 ) -> Task<Result<Option<language::Transaction>>> {
17682 Task::ready(Ok(None))
17683 }
17684
17685 fn is_completion_trigger(
17686 &self,
17687 buffer: &Entity<Buffer>,
17688 position: language::Anchor,
17689 text: &str,
17690 trigger_in_words: bool,
17691 cx: &mut Context<Editor>,
17692 ) -> bool;
17693
17694 fn sort_completions(&self) -> bool {
17695 true
17696 }
17697}
17698
17699pub trait CodeActionProvider {
17700 fn id(&self) -> Arc<str>;
17701
17702 fn code_actions(
17703 &self,
17704 buffer: &Entity<Buffer>,
17705 range: Range<text::Anchor>,
17706 window: &mut Window,
17707 cx: &mut App,
17708 ) -> Task<Result<Vec<CodeAction>>>;
17709
17710 fn apply_code_action(
17711 &self,
17712 buffer_handle: Entity<Buffer>,
17713 action: CodeAction,
17714 excerpt_id: ExcerptId,
17715 push_to_history: bool,
17716 window: &mut Window,
17717 cx: &mut App,
17718 ) -> Task<Result<ProjectTransaction>>;
17719}
17720
17721impl CodeActionProvider for Entity<Project> {
17722 fn id(&self) -> Arc<str> {
17723 "project".into()
17724 }
17725
17726 fn code_actions(
17727 &self,
17728 buffer: &Entity<Buffer>,
17729 range: Range<text::Anchor>,
17730 _window: &mut Window,
17731 cx: &mut App,
17732 ) -> Task<Result<Vec<CodeAction>>> {
17733 self.update(cx, |project, cx| {
17734 let code_lens = project.code_lens(buffer, range.clone(), cx);
17735 let code_actions = project.code_actions(buffer, range, None, cx);
17736 cx.background_spawn(async move {
17737 let (code_lens, code_actions) = join(code_lens, code_actions).await;
17738 Ok(code_lens
17739 .context("code lens fetch")?
17740 .into_iter()
17741 .chain(code_actions.context("code action fetch")?)
17742 .collect())
17743 })
17744 })
17745 }
17746
17747 fn apply_code_action(
17748 &self,
17749 buffer_handle: Entity<Buffer>,
17750 action: CodeAction,
17751 _excerpt_id: ExcerptId,
17752 push_to_history: bool,
17753 _window: &mut Window,
17754 cx: &mut App,
17755 ) -> Task<Result<ProjectTransaction>> {
17756 self.update(cx, |project, cx| {
17757 project.apply_code_action(buffer_handle, action, push_to_history, cx)
17758 })
17759 }
17760}
17761
17762fn snippet_completions(
17763 project: &Project,
17764 buffer: &Entity<Buffer>,
17765 buffer_position: text::Anchor,
17766 cx: &mut App,
17767) -> Task<Result<Vec<Completion>>> {
17768 let language = buffer.read(cx).language_at(buffer_position);
17769 let language_name = language.as_ref().map(|language| language.lsp_id());
17770 let snippet_store = project.snippets().read(cx);
17771 let snippets = snippet_store.snippets_for(language_name, cx);
17772
17773 if snippets.is_empty() {
17774 return Task::ready(Ok(vec![]));
17775 }
17776 let snapshot = buffer.read(cx).text_snapshot();
17777 let chars: String = snapshot
17778 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
17779 .collect();
17780
17781 let scope = language.map(|language| language.default_scope());
17782 let executor = cx.background_executor().clone();
17783
17784 cx.background_spawn(async move {
17785 let classifier = CharClassifier::new(scope).for_completion(true);
17786 let mut last_word = chars
17787 .chars()
17788 .take_while(|c| classifier.is_word(*c))
17789 .collect::<String>();
17790 last_word = last_word.chars().rev().collect();
17791
17792 if last_word.is_empty() {
17793 return Ok(vec![]);
17794 }
17795
17796 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
17797 let to_lsp = |point: &text::Anchor| {
17798 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
17799 point_to_lsp(end)
17800 };
17801 let lsp_end = to_lsp(&buffer_position);
17802
17803 let candidates = snippets
17804 .iter()
17805 .enumerate()
17806 .flat_map(|(ix, snippet)| {
17807 snippet
17808 .prefix
17809 .iter()
17810 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
17811 })
17812 .collect::<Vec<StringMatchCandidate>>();
17813
17814 let mut matches = fuzzy::match_strings(
17815 &candidates,
17816 &last_word,
17817 last_word.chars().any(|c| c.is_uppercase()),
17818 100,
17819 &Default::default(),
17820 executor,
17821 )
17822 .await;
17823
17824 // Remove all candidates where the query's start does not match the start of any word in the candidate
17825 if let Some(query_start) = last_word.chars().next() {
17826 matches.retain(|string_match| {
17827 split_words(&string_match.string).any(|word| {
17828 // Check that the first codepoint of the word as lowercase matches the first
17829 // codepoint of the query as lowercase
17830 word.chars()
17831 .flat_map(|codepoint| codepoint.to_lowercase())
17832 .zip(query_start.to_lowercase())
17833 .all(|(word_cp, query_cp)| word_cp == query_cp)
17834 })
17835 });
17836 }
17837
17838 let matched_strings = matches
17839 .into_iter()
17840 .map(|m| m.string)
17841 .collect::<HashSet<_>>();
17842
17843 let result: Vec<Completion> = snippets
17844 .into_iter()
17845 .filter_map(|snippet| {
17846 let matching_prefix = snippet
17847 .prefix
17848 .iter()
17849 .find(|prefix| matched_strings.contains(*prefix))?;
17850 let start = as_offset - last_word.len();
17851 let start = snapshot.anchor_before(start);
17852 let range = start..buffer_position;
17853 let lsp_start = to_lsp(&start);
17854 let lsp_range = lsp::Range {
17855 start: lsp_start,
17856 end: lsp_end,
17857 };
17858 Some(Completion {
17859 old_range: range,
17860 new_text: snippet.body.clone(),
17861 source: CompletionSource::Lsp {
17862 server_id: LanguageServerId(usize::MAX),
17863 resolved: true,
17864 lsp_completion: Box::new(lsp::CompletionItem {
17865 label: snippet.prefix.first().unwrap().clone(),
17866 kind: Some(CompletionItemKind::SNIPPET),
17867 label_details: snippet.description.as_ref().map(|description| {
17868 lsp::CompletionItemLabelDetails {
17869 detail: Some(description.clone()),
17870 description: None,
17871 }
17872 }),
17873 insert_text_format: Some(InsertTextFormat::SNIPPET),
17874 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17875 lsp::InsertReplaceEdit {
17876 new_text: snippet.body.clone(),
17877 insert: lsp_range,
17878 replace: lsp_range,
17879 },
17880 )),
17881 filter_text: Some(snippet.body.clone()),
17882 sort_text: Some(char::MAX.to_string()),
17883 ..lsp::CompletionItem::default()
17884 }),
17885 lsp_defaults: None,
17886 },
17887 label: CodeLabel {
17888 text: matching_prefix.clone(),
17889 runs: Vec::new(),
17890 filter_range: 0..matching_prefix.len(),
17891 },
17892 documentation: snippet
17893 .description
17894 .clone()
17895 .map(|description| CompletionDocumentation::SingleLine(description.into())),
17896 confirm: None,
17897 })
17898 })
17899 .collect();
17900
17901 Ok(result)
17902 })
17903}
17904
17905impl CompletionProvider for Entity<Project> {
17906 fn completions(
17907 &self,
17908 buffer: &Entity<Buffer>,
17909 buffer_position: text::Anchor,
17910 options: CompletionContext,
17911 _window: &mut Window,
17912 cx: &mut Context<Editor>,
17913 ) -> Task<Result<Option<Vec<Completion>>>> {
17914 self.update(cx, |project, cx| {
17915 let snippets = snippet_completions(project, buffer, buffer_position, cx);
17916 let project_completions = project.completions(buffer, buffer_position, options, cx);
17917 cx.background_spawn(async move {
17918 let snippets_completions = snippets.await?;
17919 match project_completions.await? {
17920 Some(mut completions) => {
17921 completions.extend(snippets_completions);
17922 Ok(Some(completions))
17923 }
17924 None => {
17925 if snippets_completions.is_empty() {
17926 Ok(None)
17927 } else {
17928 Ok(Some(snippets_completions))
17929 }
17930 }
17931 }
17932 })
17933 })
17934 }
17935
17936 fn resolve_completions(
17937 &self,
17938 buffer: Entity<Buffer>,
17939 completion_indices: Vec<usize>,
17940 completions: Rc<RefCell<Box<[Completion]>>>,
17941 cx: &mut Context<Editor>,
17942 ) -> Task<Result<bool>> {
17943 self.update(cx, |project, cx| {
17944 project.lsp_store().update(cx, |lsp_store, cx| {
17945 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
17946 })
17947 })
17948 }
17949
17950 fn apply_additional_edits_for_completion(
17951 &self,
17952 buffer: Entity<Buffer>,
17953 completions: Rc<RefCell<Box<[Completion]>>>,
17954 completion_index: usize,
17955 push_to_history: bool,
17956 cx: &mut Context<Editor>,
17957 ) -> Task<Result<Option<language::Transaction>>> {
17958 self.update(cx, |project, cx| {
17959 project.lsp_store().update(cx, |lsp_store, cx| {
17960 lsp_store.apply_additional_edits_for_completion(
17961 buffer,
17962 completions,
17963 completion_index,
17964 push_to_history,
17965 cx,
17966 )
17967 })
17968 })
17969 }
17970
17971 fn is_completion_trigger(
17972 &self,
17973 buffer: &Entity<Buffer>,
17974 position: language::Anchor,
17975 text: &str,
17976 trigger_in_words: bool,
17977 cx: &mut Context<Editor>,
17978 ) -> bool {
17979 let mut chars = text.chars();
17980 let char = if let Some(char) = chars.next() {
17981 char
17982 } else {
17983 return false;
17984 };
17985 if chars.next().is_some() {
17986 return false;
17987 }
17988
17989 let buffer = buffer.read(cx);
17990 let snapshot = buffer.snapshot();
17991 if !snapshot.settings_at(position, cx).show_completions_on_input {
17992 return false;
17993 }
17994 let classifier = snapshot.char_classifier_at(position).for_completion(true);
17995 if trigger_in_words && classifier.is_word(char) {
17996 return true;
17997 }
17998
17999 buffer.completion_triggers().contains(text)
18000 }
18001}
18002
18003impl SemanticsProvider for Entity<Project> {
18004 fn hover(
18005 &self,
18006 buffer: &Entity<Buffer>,
18007 position: text::Anchor,
18008 cx: &mut App,
18009 ) -> Option<Task<Vec<project::Hover>>> {
18010 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
18011 }
18012
18013 fn document_highlights(
18014 &self,
18015 buffer: &Entity<Buffer>,
18016 position: text::Anchor,
18017 cx: &mut App,
18018 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
18019 Some(self.update(cx, |project, cx| {
18020 project.document_highlights(buffer, position, cx)
18021 }))
18022 }
18023
18024 fn definitions(
18025 &self,
18026 buffer: &Entity<Buffer>,
18027 position: text::Anchor,
18028 kind: GotoDefinitionKind,
18029 cx: &mut App,
18030 ) -> Option<Task<Result<Vec<LocationLink>>>> {
18031 Some(self.update(cx, |project, cx| match kind {
18032 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
18033 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
18034 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
18035 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
18036 }))
18037 }
18038
18039 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
18040 // TODO: make this work for remote projects
18041 self.update(cx, |this, cx| {
18042 buffer.update(cx, |buffer, cx| {
18043 this.any_language_server_supports_inlay_hints(buffer, cx)
18044 })
18045 })
18046 }
18047
18048 fn inlay_hints(
18049 &self,
18050 buffer_handle: Entity<Buffer>,
18051 range: Range<text::Anchor>,
18052 cx: &mut App,
18053 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
18054 Some(self.update(cx, |project, cx| {
18055 project.inlay_hints(buffer_handle, range, cx)
18056 }))
18057 }
18058
18059 fn resolve_inlay_hint(
18060 &self,
18061 hint: InlayHint,
18062 buffer_handle: Entity<Buffer>,
18063 server_id: LanguageServerId,
18064 cx: &mut App,
18065 ) -> Option<Task<anyhow::Result<InlayHint>>> {
18066 Some(self.update(cx, |project, cx| {
18067 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
18068 }))
18069 }
18070
18071 fn range_for_rename(
18072 &self,
18073 buffer: &Entity<Buffer>,
18074 position: text::Anchor,
18075 cx: &mut App,
18076 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
18077 Some(self.update(cx, |project, cx| {
18078 let buffer = buffer.clone();
18079 let task = project.prepare_rename(buffer.clone(), position, cx);
18080 cx.spawn(async move |_, cx| {
18081 Ok(match task.await? {
18082 PrepareRenameResponse::Success(range) => Some(range),
18083 PrepareRenameResponse::InvalidPosition => None,
18084 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
18085 // Fallback on using TreeSitter info to determine identifier range
18086 buffer.update(cx, |buffer, _| {
18087 let snapshot = buffer.snapshot();
18088 let (range, kind) = snapshot.surrounding_word(position);
18089 if kind != Some(CharKind::Word) {
18090 return None;
18091 }
18092 Some(
18093 snapshot.anchor_before(range.start)
18094 ..snapshot.anchor_after(range.end),
18095 )
18096 })?
18097 }
18098 })
18099 })
18100 }))
18101 }
18102
18103 fn perform_rename(
18104 &self,
18105 buffer: &Entity<Buffer>,
18106 position: text::Anchor,
18107 new_name: String,
18108 cx: &mut App,
18109 ) -> Option<Task<Result<ProjectTransaction>>> {
18110 Some(self.update(cx, |project, cx| {
18111 project.perform_rename(buffer.clone(), position, new_name, cx)
18112 }))
18113 }
18114}
18115
18116fn inlay_hint_settings(
18117 location: Anchor,
18118 snapshot: &MultiBufferSnapshot,
18119 cx: &mut Context<Editor>,
18120) -> InlayHintSettings {
18121 let file = snapshot.file_at(location);
18122 let language = snapshot.language_at(location).map(|l| l.name());
18123 language_settings(language, file, cx).inlay_hints
18124}
18125
18126fn consume_contiguous_rows(
18127 contiguous_row_selections: &mut Vec<Selection<Point>>,
18128 selection: &Selection<Point>,
18129 display_map: &DisplaySnapshot,
18130 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
18131) -> (MultiBufferRow, MultiBufferRow) {
18132 contiguous_row_selections.push(selection.clone());
18133 let start_row = MultiBufferRow(selection.start.row);
18134 let mut end_row = ending_row(selection, display_map);
18135
18136 while let Some(next_selection) = selections.peek() {
18137 if next_selection.start.row <= end_row.0 {
18138 end_row = ending_row(next_selection, display_map);
18139 contiguous_row_selections.push(selections.next().unwrap().clone());
18140 } else {
18141 break;
18142 }
18143 }
18144 (start_row, end_row)
18145}
18146
18147fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
18148 if next_selection.end.column > 0 || next_selection.is_empty() {
18149 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
18150 } else {
18151 MultiBufferRow(next_selection.end.row)
18152 }
18153}
18154
18155impl EditorSnapshot {
18156 pub fn remote_selections_in_range<'a>(
18157 &'a self,
18158 range: &'a Range<Anchor>,
18159 collaboration_hub: &dyn CollaborationHub,
18160 cx: &'a App,
18161 ) -> impl 'a + Iterator<Item = RemoteSelection> {
18162 let participant_names = collaboration_hub.user_names(cx);
18163 let participant_indices = collaboration_hub.user_participant_indices(cx);
18164 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
18165 let collaborators_by_replica_id = collaborators_by_peer_id
18166 .iter()
18167 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
18168 .collect::<HashMap<_, _>>();
18169 self.buffer_snapshot
18170 .selections_in_range(range, false)
18171 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
18172 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
18173 let participant_index = participant_indices.get(&collaborator.user_id).copied();
18174 let user_name = participant_names.get(&collaborator.user_id).cloned();
18175 Some(RemoteSelection {
18176 replica_id,
18177 selection,
18178 cursor_shape,
18179 line_mode,
18180 participant_index,
18181 peer_id: collaborator.peer_id,
18182 user_name,
18183 })
18184 })
18185 }
18186
18187 pub fn hunks_for_ranges(
18188 &self,
18189 ranges: impl IntoIterator<Item = Range<Point>>,
18190 ) -> Vec<MultiBufferDiffHunk> {
18191 let mut hunks = Vec::new();
18192 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
18193 HashMap::default();
18194 for query_range in ranges {
18195 let query_rows =
18196 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
18197 for hunk in self.buffer_snapshot.diff_hunks_in_range(
18198 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
18199 ) {
18200 // Include deleted hunks that are adjacent to the query range, because
18201 // otherwise they would be missed.
18202 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
18203 if hunk.status().is_deleted() {
18204 intersects_range |= hunk.row_range.start == query_rows.end;
18205 intersects_range |= hunk.row_range.end == query_rows.start;
18206 }
18207 if intersects_range {
18208 if !processed_buffer_rows
18209 .entry(hunk.buffer_id)
18210 .or_default()
18211 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
18212 {
18213 continue;
18214 }
18215 hunks.push(hunk);
18216 }
18217 }
18218 }
18219
18220 hunks
18221 }
18222
18223 fn display_diff_hunks_for_rows<'a>(
18224 &'a self,
18225 display_rows: Range<DisplayRow>,
18226 folded_buffers: &'a HashSet<BufferId>,
18227 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
18228 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
18229 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
18230
18231 self.buffer_snapshot
18232 .diff_hunks_in_range(buffer_start..buffer_end)
18233 .filter_map(|hunk| {
18234 if folded_buffers.contains(&hunk.buffer_id) {
18235 return None;
18236 }
18237
18238 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
18239 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
18240
18241 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
18242 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
18243
18244 let display_hunk = if hunk_display_start.column() != 0 {
18245 DisplayDiffHunk::Folded {
18246 display_row: hunk_display_start.row(),
18247 }
18248 } else {
18249 let mut end_row = hunk_display_end.row();
18250 if hunk_display_end.column() > 0 {
18251 end_row.0 += 1;
18252 }
18253 let is_created_file = hunk.is_created_file();
18254 DisplayDiffHunk::Unfolded {
18255 status: hunk.status(),
18256 diff_base_byte_range: hunk.diff_base_byte_range,
18257 display_row_range: hunk_display_start.row()..end_row,
18258 multi_buffer_range: Anchor::range_in_buffer(
18259 hunk.excerpt_id,
18260 hunk.buffer_id,
18261 hunk.buffer_range,
18262 ),
18263 is_created_file,
18264 }
18265 };
18266
18267 Some(display_hunk)
18268 })
18269 }
18270
18271 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
18272 self.display_snapshot.buffer_snapshot.language_at(position)
18273 }
18274
18275 pub fn is_focused(&self) -> bool {
18276 self.is_focused
18277 }
18278
18279 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
18280 self.placeholder_text.as_ref()
18281 }
18282
18283 pub fn scroll_position(&self) -> gpui::Point<f32> {
18284 self.scroll_anchor.scroll_position(&self.display_snapshot)
18285 }
18286
18287 fn gutter_dimensions(
18288 &self,
18289 font_id: FontId,
18290 font_size: Pixels,
18291 max_line_number_width: Pixels,
18292 cx: &App,
18293 ) -> Option<GutterDimensions> {
18294 if !self.show_gutter {
18295 return None;
18296 }
18297
18298 let descent = cx.text_system().descent(font_id, font_size);
18299 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
18300 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
18301
18302 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
18303 matches!(
18304 ProjectSettings::get_global(cx).git.git_gutter,
18305 Some(GitGutterSetting::TrackedFiles)
18306 )
18307 });
18308 let gutter_settings = EditorSettings::get_global(cx).gutter;
18309 let show_line_numbers = self
18310 .show_line_numbers
18311 .unwrap_or(gutter_settings.line_numbers);
18312 let line_gutter_width = if show_line_numbers {
18313 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
18314 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
18315 max_line_number_width.max(min_width_for_number_on_gutter)
18316 } else {
18317 0.0.into()
18318 };
18319
18320 let show_code_actions = self
18321 .show_code_actions
18322 .unwrap_or(gutter_settings.code_actions);
18323
18324 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
18325 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
18326
18327 let git_blame_entries_width =
18328 self.git_blame_gutter_max_author_length
18329 .map(|max_author_length| {
18330 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
18331
18332 /// The number of characters to dedicate to gaps and margins.
18333 const SPACING_WIDTH: usize = 4;
18334
18335 let max_char_count = max_author_length
18336 .min(GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED)
18337 + ::git::SHORT_SHA_LENGTH
18338 + MAX_RELATIVE_TIMESTAMP.len()
18339 + SPACING_WIDTH;
18340
18341 em_advance * max_char_count
18342 });
18343
18344 let is_singleton = self.buffer_snapshot.is_singleton();
18345
18346 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
18347 left_padding += if !is_singleton {
18348 em_width * 4.0
18349 } else if show_code_actions || show_runnables || show_breakpoints {
18350 em_width * 3.0
18351 } else if show_git_gutter && show_line_numbers {
18352 em_width * 2.0
18353 } else if show_git_gutter || show_line_numbers {
18354 em_width
18355 } else {
18356 px(0.)
18357 };
18358
18359 let shows_folds = is_singleton && gutter_settings.folds;
18360
18361 let right_padding = if shows_folds && show_line_numbers {
18362 em_width * 4.0
18363 } else if shows_folds || (!is_singleton && show_line_numbers) {
18364 em_width * 3.0
18365 } else if show_line_numbers {
18366 em_width
18367 } else {
18368 px(0.)
18369 };
18370
18371 Some(GutterDimensions {
18372 left_padding,
18373 right_padding,
18374 width: line_gutter_width + left_padding + right_padding,
18375 margin: -descent,
18376 git_blame_entries_width,
18377 })
18378 }
18379
18380 pub fn render_crease_toggle(
18381 &self,
18382 buffer_row: MultiBufferRow,
18383 row_contains_cursor: bool,
18384 editor: Entity<Editor>,
18385 window: &mut Window,
18386 cx: &mut App,
18387 ) -> Option<AnyElement> {
18388 let folded = self.is_line_folded(buffer_row);
18389 let mut is_foldable = false;
18390
18391 if let Some(crease) = self
18392 .crease_snapshot
18393 .query_row(buffer_row, &self.buffer_snapshot)
18394 {
18395 is_foldable = true;
18396 match crease {
18397 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
18398 if let Some(render_toggle) = render_toggle {
18399 let toggle_callback =
18400 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
18401 if folded {
18402 editor.update(cx, |editor, cx| {
18403 editor.fold_at(&crate::FoldAt { buffer_row }, window, cx)
18404 });
18405 } else {
18406 editor.update(cx, |editor, cx| {
18407 editor.unfold_at(
18408 &crate::UnfoldAt { buffer_row },
18409 window,
18410 cx,
18411 )
18412 });
18413 }
18414 });
18415 return Some((render_toggle)(
18416 buffer_row,
18417 folded,
18418 toggle_callback,
18419 window,
18420 cx,
18421 ));
18422 }
18423 }
18424 }
18425 }
18426
18427 is_foldable |= self.starts_indent(buffer_row);
18428
18429 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
18430 Some(
18431 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
18432 .toggle_state(folded)
18433 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
18434 if folded {
18435 this.unfold_at(&UnfoldAt { buffer_row }, window, cx);
18436 } else {
18437 this.fold_at(&FoldAt { buffer_row }, window, cx);
18438 }
18439 }))
18440 .into_any_element(),
18441 )
18442 } else {
18443 None
18444 }
18445 }
18446
18447 pub fn render_crease_trailer(
18448 &self,
18449 buffer_row: MultiBufferRow,
18450 window: &mut Window,
18451 cx: &mut App,
18452 ) -> Option<AnyElement> {
18453 let folded = self.is_line_folded(buffer_row);
18454 if let Crease::Inline { render_trailer, .. } = self
18455 .crease_snapshot
18456 .query_row(buffer_row, &self.buffer_snapshot)?
18457 {
18458 let render_trailer = render_trailer.as_ref()?;
18459 Some(render_trailer(buffer_row, folded, window, cx))
18460 } else {
18461 None
18462 }
18463 }
18464}
18465
18466impl Deref for EditorSnapshot {
18467 type Target = DisplaySnapshot;
18468
18469 fn deref(&self) -> &Self::Target {
18470 &self.display_snapshot
18471 }
18472}
18473
18474#[derive(Clone, Debug, PartialEq, Eq)]
18475pub enum EditorEvent {
18476 InputIgnored {
18477 text: Arc<str>,
18478 },
18479 InputHandled {
18480 utf16_range_to_replace: Option<Range<isize>>,
18481 text: Arc<str>,
18482 },
18483 ExcerptsAdded {
18484 buffer: Entity<Buffer>,
18485 predecessor: ExcerptId,
18486 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
18487 },
18488 ExcerptsRemoved {
18489 ids: Vec<ExcerptId>,
18490 },
18491 BufferFoldToggled {
18492 ids: Vec<ExcerptId>,
18493 folded: bool,
18494 },
18495 ExcerptsEdited {
18496 ids: Vec<ExcerptId>,
18497 },
18498 ExcerptsExpanded {
18499 ids: Vec<ExcerptId>,
18500 },
18501 BufferEdited,
18502 Edited {
18503 transaction_id: clock::Lamport,
18504 },
18505 Reparsed(BufferId),
18506 Focused,
18507 FocusedIn,
18508 Blurred,
18509 DirtyChanged,
18510 Saved,
18511 TitleChanged,
18512 DiffBaseChanged,
18513 SelectionsChanged {
18514 local: bool,
18515 },
18516 ScrollPositionChanged {
18517 local: bool,
18518 autoscroll: bool,
18519 },
18520 Closed,
18521 TransactionUndone {
18522 transaction_id: clock::Lamport,
18523 },
18524 TransactionBegun {
18525 transaction_id: clock::Lamport,
18526 },
18527 Reloaded,
18528 CursorShapeChanged,
18529}
18530
18531impl EventEmitter<EditorEvent> for Editor {}
18532
18533impl Focusable for Editor {
18534 fn focus_handle(&self, _cx: &App) -> FocusHandle {
18535 self.focus_handle.clone()
18536 }
18537}
18538
18539impl Render for Editor {
18540 fn render(&mut self, _: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
18541 let settings = ThemeSettings::get_global(cx);
18542
18543 let mut text_style = match self.mode {
18544 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
18545 color: cx.theme().colors().editor_foreground,
18546 font_family: settings.ui_font.family.clone(),
18547 font_features: settings.ui_font.features.clone(),
18548 font_fallbacks: settings.ui_font.fallbacks.clone(),
18549 font_size: rems(0.875).into(),
18550 font_weight: settings.ui_font.weight,
18551 line_height: relative(settings.buffer_line_height.value()),
18552 ..Default::default()
18553 },
18554 EditorMode::Full => TextStyle {
18555 color: cx.theme().colors().editor_foreground,
18556 font_family: settings.buffer_font.family.clone(),
18557 font_features: settings.buffer_font.features.clone(),
18558 font_fallbacks: settings.buffer_font.fallbacks.clone(),
18559 font_size: settings.buffer_font_size(cx).into(),
18560 font_weight: settings.buffer_font.weight,
18561 line_height: relative(settings.buffer_line_height.value()),
18562 ..Default::default()
18563 },
18564 };
18565 if let Some(text_style_refinement) = &self.text_style_refinement {
18566 text_style.refine(text_style_refinement)
18567 }
18568
18569 let background = match self.mode {
18570 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
18571 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
18572 EditorMode::Full => cx.theme().colors().editor_background,
18573 };
18574
18575 EditorElement::new(
18576 &cx.entity(),
18577 EditorStyle {
18578 background,
18579 local_player: cx.theme().players().local(),
18580 text: text_style,
18581 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
18582 syntax: cx.theme().syntax().clone(),
18583 status: cx.theme().status().clone(),
18584 inlay_hints_style: make_inlay_hints_style(cx),
18585 inline_completion_styles: make_suggestion_styles(cx),
18586 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
18587 },
18588 )
18589 }
18590}
18591
18592impl EntityInputHandler for Editor {
18593 fn text_for_range(
18594 &mut self,
18595 range_utf16: Range<usize>,
18596 adjusted_range: &mut Option<Range<usize>>,
18597 _: &mut Window,
18598 cx: &mut Context<Self>,
18599 ) -> Option<String> {
18600 let snapshot = self.buffer.read(cx).read(cx);
18601 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
18602 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
18603 if (start.0..end.0) != range_utf16 {
18604 adjusted_range.replace(start.0..end.0);
18605 }
18606 Some(snapshot.text_for_range(start..end).collect())
18607 }
18608
18609 fn selected_text_range(
18610 &mut self,
18611 ignore_disabled_input: bool,
18612 _: &mut Window,
18613 cx: &mut Context<Self>,
18614 ) -> Option<UTF16Selection> {
18615 // Prevent the IME menu from appearing when holding down an alphabetic key
18616 // while input is disabled.
18617 if !ignore_disabled_input && !self.input_enabled {
18618 return None;
18619 }
18620
18621 let selection = self.selections.newest::<OffsetUtf16>(cx);
18622 let range = selection.range();
18623
18624 Some(UTF16Selection {
18625 range: range.start.0..range.end.0,
18626 reversed: selection.reversed,
18627 })
18628 }
18629
18630 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
18631 let snapshot = self.buffer.read(cx).read(cx);
18632 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
18633 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
18634 }
18635
18636 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18637 self.clear_highlights::<InputComposition>(cx);
18638 self.ime_transaction.take();
18639 }
18640
18641 fn replace_text_in_range(
18642 &mut self,
18643 range_utf16: Option<Range<usize>>,
18644 text: &str,
18645 window: &mut Window,
18646 cx: &mut Context<Self>,
18647 ) {
18648 if !self.input_enabled {
18649 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18650 return;
18651 }
18652
18653 self.transact(window, cx, |this, window, cx| {
18654 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
18655 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
18656 Some(this.selection_replacement_ranges(range_utf16, cx))
18657 } else {
18658 this.marked_text_ranges(cx)
18659 };
18660
18661 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
18662 let newest_selection_id = this.selections.newest_anchor().id;
18663 this.selections
18664 .all::<OffsetUtf16>(cx)
18665 .iter()
18666 .zip(ranges_to_replace.iter())
18667 .find_map(|(selection, range)| {
18668 if selection.id == newest_selection_id {
18669 Some(
18670 (range.start.0 as isize - selection.head().0 as isize)
18671 ..(range.end.0 as isize - selection.head().0 as isize),
18672 )
18673 } else {
18674 None
18675 }
18676 })
18677 });
18678
18679 cx.emit(EditorEvent::InputHandled {
18680 utf16_range_to_replace: range_to_replace,
18681 text: text.into(),
18682 });
18683
18684 if let Some(new_selected_ranges) = new_selected_ranges {
18685 this.change_selections(None, window, cx, |selections| {
18686 selections.select_ranges(new_selected_ranges)
18687 });
18688 this.backspace(&Default::default(), window, cx);
18689 }
18690
18691 this.handle_input(text, window, cx);
18692 });
18693
18694 if let Some(transaction) = self.ime_transaction {
18695 self.buffer.update(cx, |buffer, cx| {
18696 buffer.group_until_transaction(transaction, cx);
18697 });
18698 }
18699
18700 self.unmark_text(window, cx);
18701 }
18702
18703 fn replace_and_mark_text_in_range(
18704 &mut self,
18705 range_utf16: Option<Range<usize>>,
18706 text: &str,
18707 new_selected_range_utf16: Option<Range<usize>>,
18708 window: &mut Window,
18709 cx: &mut Context<Self>,
18710 ) {
18711 if !self.input_enabled {
18712 return;
18713 }
18714
18715 let transaction = self.transact(window, cx, |this, window, cx| {
18716 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
18717 let snapshot = this.buffer.read(cx).read(cx);
18718 if let Some(relative_range_utf16) = range_utf16.as_ref() {
18719 for marked_range in &mut marked_ranges {
18720 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
18721 marked_range.start.0 += relative_range_utf16.start;
18722 marked_range.start =
18723 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
18724 marked_range.end =
18725 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
18726 }
18727 }
18728 Some(marked_ranges)
18729 } else if let Some(range_utf16) = range_utf16 {
18730 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
18731 Some(this.selection_replacement_ranges(range_utf16, cx))
18732 } else {
18733 None
18734 };
18735
18736 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
18737 let newest_selection_id = this.selections.newest_anchor().id;
18738 this.selections
18739 .all::<OffsetUtf16>(cx)
18740 .iter()
18741 .zip(ranges_to_replace.iter())
18742 .find_map(|(selection, range)| {
18743 if selection.id == newest_selection_id {
18744 Some(
18745 (range.start.0 as isize - selection.head().0 as isize)
18746 ..(range.end.0 as isize - selection.head().0 as isize),
18747 )
18748 } else {
18749 None
18750 }
18751 })
18752 });
18753
18754 cx.emit(EditorEvent::InputHandled {
18755 utf16_range_to_replace: range_to_replace,
18756 text: text.into(),
18757 });
18758
18759 if let Some(ranges) = ranges_to_replace {
18760 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
18761 }
18762
18763 let marked_ranges = {
18764 let snapshot = this.buffer.read(cx).read(cx);
18765 this.selections
18766 .disjoint_anchors()
18767 .iter()
18768 .map(|selection| {
18769 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
18770 })
18771 .collect::<Vec<_>>()
18772 };
18773
18774 if text.is_empty() {
18775 this.unmark_text(window, cx);
18776 } else {
18777 this.highlight_text::<InputComposition>(
18778 marked_ranges.clone(),
18779 HighlightStyle {
18780 underline: Some(UnderlineStyle {
18781 thickness: px(1.),
18782 color: None,
18783 wavy: false,
18784 }),
18785 ..Default::default()
18786 },
18787 cx,
18788 );
18789 }
18790
18791 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
18792 let use_autoclose = this.use_autoclose;
18793 let use_auto_surround = this.use_auto_surround;
18794 this.set_use_autoclose(false);
18795 this.set_use_auto_surround(false);
18796 this.handle_input(text, window, cx);
18797 this.set_use_autoclose(use_autoclose);
18798 this.set_use_auto_surround(use_auto_surround);
18799
18800 if let Some(new_selected_range) = new_selected_range_utf16 {
18801 let snapshot = this.buffer.read(cx).read(cx);
18802 let new_selected_ranges = marked_ranges
18803 .into_iter()
18804 .map(|marked_range| {
18805 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
18806 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
18807 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
18808 snapshot.clip_offset_utf16(new_start, Bias::Left)
18809 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
18810 })
18811 .collect::<Vec<_>>();
18812
18813 drop(snapshot);
18814 this.change_selections(None, window, cx, |selections| {
18815 selections.select_ranges(new_selected_ranges)
18816 });
18817 }
18818 });
18819
18820 self.ime_transaction = self.ime_transaction.or(transaction);
18821 if let Some(transaction) = self.ime_transaction {
18822 self.buffer.update(cx, |buffer, cx| {
18823 buffer.group_until_transaction(transaction, cx);
18824 });
18825 }
18826
18827 if self.text_highlights::<InputComposition>(cx).is_none() {
18828 self.ime_transaction.take();
18829 }
18830 }
18831
18832 fn bounds_for_range(
18833 &mut self,
18834 range_utf16: Range<usize>,
18835 element_bounds: gpui::Bounds<Pixels>,
18836 window: &mut Window,
18837 cx: &mut Context<Self>,
18838 ) -> Option<gpui::Bounds<Pixels>> {
18839 let text_layout_details = self.text_layout_details(window);
18840 let gpui::Size {
18841 width: em_width,
18842 height: line_height,
18843 } = self.character_size(window);
18844
18845 let snapshot = self.snapshot(window, cx);
18846 let scroll_position = snapshot.scroll_position();
18847 let scroll_left = scroll_position.x * em_width;
18848
18849 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
18850 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
18851 + self.gutter_dimensions.width
18852 + self.gutter_dimensions.margin;
18853 let y = line_height * (start.row().as_f32() - scroll_position.y);
18854
18855 Some(Bounds {
18856 origin: element_bounds.origin + point(x, y),
18857 size: size(em_width, line_height),
18858 })
18859 }
18860
18861 fn character_index_for_point(
18862 &mut self,
18863 point: gpui::Point<Pixels>,
18864 _window: &mut Window,
18865 _cx: &mut Context<Self>,
18866 ) -> Option<usize> {
18867 let position_map = self.last_position_map.as_ref()?;
18868 if !position_map.text_hitbox.contains(&point) {
18869 return None;
18870 }
18871 let display_point = position_map.point_for_position(point).previous_valid;
18872 let anchor = position_map
18873 .snapshot
18874 .display_point_to_anchor(display_point, Bias::Left);
18875 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
18876 Some(utf16_offset.0)
18877 }
18878}
18879
18880trait SelectionExt {
18881 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
18882 fn spanned_rows(
18883 &self,
18884 include_end_if_at_line_start: bool,
18885 map: &DisplaySnapshot,
18886 ) -> Range<MultiBufferRow>;
18887}
18888
18889impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
18890 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
18891 let start = self
18892 .start
18893 .to_point(&map.buffer_snapshot)
18894 .to_display_point(map);
18895 let end = self
18896 .end
18897 .to_point(&map.buffer_snapshot)
18898 .to_display_point(map);
18899 if self.reversed {
18900 end..start
18901 } else {
18902 start..end
18903 }
18904 }
18905
18906 fn spanned_rows(
18907 &self,
18908 include_end_if_at_line_start: bool,
18909 map: &DisplaySnapshot,
18910 ) -> Range<MultiBufferRow> {
18911 let start = self.start.to_point(&map.buffer_snapshot);
18912 let mut end = self.end.to_point(&map.buffer_snapshot);
18913 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
18914 end.row -= 1;
18915 }
18916
18917 let buffer_start = map.prev_line_boundary(start).0;
18918 let buffer_end = map.next_line_boundary(end).0;
18919 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
18920 }
18921}
18922
18923impl<T: InvalidationRegion> InvalidationStack<T> {
18924 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
18925 where
18926 S: Clone + ToOffset,
18927 {
18928 while let Some(region) = self.last() {
18929 let all_selections_inside_invalidation_ranges =
18930 if selections.len() == region.ranges().len() {
18931 selections
18932 .iter()
18933 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
18934 .all(|(selection, invalidation_range)| {
18935 let head = selection.head().to_offset(buffer);
18936 invalidation_range.start <= head && invalidation_range.end >= head
18937 })
18938 } else {
18939 false
18940 };
18941
18942 if all_selections_inside_invalidation_ranges {
18943 break;
18944 } else {
18945 self.pop();
18946 }
18947 }
18948 }
18949}
18950
18951impl<T> Default for InvalidationStack<T> {
18952 fn default() -> Self {
18953 Self(Default::default())
18954 }
18955}
18956
18957impl<T> Deref for InvalidationStack<T> {
18958 type Target = Vec<T>;
18959
18960 fn deref(&self) -> &Self::Target {
18961 &self.0
18962 }
18963}
18964
18965impl<T> DerefMut for InvalidationStack<T> {
18966 fn deref_mut(&mut self) -> &mut Self::Target {
18967 &mut self.0
18968 }
18969}
18970
18971impl InvalidationRegion for SnippetState {
18972 fn ranges(&self) -> &[Range<Anchor>] {
18973 &self.ranges[self.active_index]
18974 }
18975}
18976
18977pub fn diagnostic_block_renderer(
18978 diagnostic: Diagnostic,
18979 max_message_rows: Option<u8>,
18980 allow_closing: bool,
18981) -> RenderBlock {
18982 let (text_without_backticks, code_ranges) =
18983 highlight_diagnostic_message(&diagnostic, max_message_rows);
18984
18985 Arc::new(move |cx: &mut BlockContext| {
18986 let group_id: SharedString = cx.block_id.to_string().into();
18987
18988 let mut text_style = cx.window.text_style().clone();
18989 text_style.color = diagnostic_style(diagnostic.severity, cx.theme().status());
18990 let theme_settings = ThemeSettings::get_global(cx);
18991 text_style.font_family = theme_settings.buffer_font.family.clone();
18992 text_style.font_style = theme_settings.buffer_font.style;
18993 text_style.font_features = theme_settings.buffer_font.features.clone();
18994 text_style.font_weight = theme_settings.buffer_font.weight;
18995
18996 let multi_line_diagnostic = diagnostic.message.contains('\n');
18997
18998 let buttons = |diagnostic: &Diagnostic| {
18999 if multi_line_diagnostic {
19000 v_flex()
19001 } else {
19002 h_flex()
19003 }
19004 .when(allow_closing, |div| {
19005 div.children(diagnostic.is_primary.then(|| {
19006 IconButton::new("close-block", IconName::XCircle)
19007 .icon_color(Color::Muted)
19008 .size(ButtonSize::Compact)
19009 .style(ButtonStyle::Transparent)
19010 .visible_on_hover(group_id.clone())
19011 .on_click(move |_click, window, cx| {
19012 window.dispatch_action(Box::new(Cancel), cx)
19013 })
19014 .tooltip(|window, cx| {
19015 Tooltip::for_action("Close Diagnostics", &Cancel, window, cx)
19016 })
19017 }))
19018 })
19019 .child(
19020 IconButton::new("copy-block", IconName::Copy)
19021 .icon_color(Color::Muted)
19022 .size(ButtonSize::Compact)
19023 .style(ButtonStyle::Transparent)
19024 .visible_on_hover(group_id.clone())
19025 .on_click({
19026 let message = diagnostic.message.clone();
19027 move |_click, _, cx| {
19028 cx.write_to_clipboard(ClipboardItem::new_string(message.clone()))
19029 }
19030 })
19031 .tooltip(Tooltip::text("Copy diagnostic message")),
19032 )
19033 };
19034
19035 let icon_size = buttons(&diagnostic).into_any_element().layout_as_root(
19036 AvailableSpace::min_size(),
19037 cx.window,
19038 cx.app,
19039 );
19040
19041 h_flex()
19042 .id(cx.block_id)
19043 .group(group_id.clone())
19044 .relative()
19045 .size_full()
19046 .block_mouse_down()
19047 .pl(cx.gutter_dimensions.width)
19048 .w(cx.max_width - cx.gutter_dimensions.full_width())
19049 .child(
19050 div()
19051 .flex()
19052 .w(cx.anchor_x - cx.gutter_dimensions.width - icon_size.width)
19053 .flex_shrink(),
19054 )
19055 .child(buttons(&diagnostic))
19056 .child(div().flex().flex_shrink_0().child(
19057 StyledText::new(text_without_backticks.clone()).with_default_highlights(
19058 &text_style,
19059 code_ranges.iter().map(|range| {
19060 (
19061 range.clone(),
19062 HighlightStyle {
19063 font_weight: Some(FontWeight::BOLD),
19064 ..Default::default()
19065 },
19066 )
19067 }),
19068 ),
19069 ))
19070 .into_any_element()
19071 })
19072}
19073
19074fn inline_completion_edit_text(
19075 current_snapshot: &BufferSnapshot,
19076 edits: &[(Range<Anchor>, String)],
19077 edit_preview: &EditPreview,
19078 include_deletions: bool,
19079 cx: &App,
19080) -> HighlightedText {
19081 let edits = edits
19082 .iter()
19083 .map(|(anchor, text)| {
19084 (
19085 anchor.start.text_anchor..anchor.end.text_anchor,
19086 text.clone(),
19087 )
19088 })
19089 .collect::<Vec<_>>();
19090
19091 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
19092}
19093
19094pub fn highlight_diagnostic_message(
19095 diagnostic: &Diagnostic,
19096 mut max_message_rows: Option<u8>,
19097) -> (SharedString, Vec<Range<usize>>) {
19098 let mut text_without_backticks = String::new();
19099 let mut code_ranges = Vec::new();
19100
19101 if let Some(source) = &diagnostic.source {
19102 text_without_backticks.push_str(source);
19103 code_ranges.push(0..source.len());
19104 text_without_backticks.push_str(": ");
19105 }
19106
19107 let mut prev_offset = 0;
19108 let mut in_code_block = false;
19109 let has_row_limit = max_message_rows.is_some();
19110 let mut newline_indices = diagnostic
19111 .message
19112 .match_indices('\n')
19113 .filter(|_| has_row_limit)
19114 .map(|(ix, _)| ix)
19115 .fuse()
19116 .peekable();
19117
19118 for (quote_ix, _) in diagnostic
19119 .message
19120 .match_indices('`')
19121 .chain([(diagnostic.message.len(), "")])
19122 {
19123 let mut first_newline_ix = None;
19124 let mut last_newline_ix = None;
19125 while let Some(newline_ix) = newline_indices.peek() {
19126 if *newline_ix < quote_ix {
19127 if first_newline_ix.is_none() {
19128 first_newline_ix = Some(*newline_ix);
19129 }
19130 last_newline_ix = Some(*newline_ix);
19131
19132 if let Some(rows_left) = &mut max_message_rows {
19133 if *rows_left == 0 {
19134 break;
19135 } else {
19136 *rows_left -= 1;
19137 }
19138 }
19139 let _ = newline_indices.next();
19140 } else {
19141 break;
19142 }
19143 }
19144 let prev_len = text_without_backticks.len();
19145 let new_text = &diagnostic.message[prev_offset..first_newline_ix.unwrap_or(quote_ix)];
19146 text_without_backticks.push_str(new_text);
19147 if in_code_block {
19148 code_ranges.push(prev_len..text_without_backticks.len());
19149 }
19150 prev_offset = last_newline_ix.unwrap_or(quote_ix) + 1;
19151 in_code_block = !in_code_block;
19152 if first_newline_ix.map_or(false, |newline_ix| newline_ix < quote_ix) {
19153 text_without_backticks.push_str("...");
19154 break;
19155 }
19156 }
19157
19158 (text_without_backticks.into(), code_ranges)
19159}
19160
19161fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
19162 match severity {
19163 DiagnosticSeverity::ERROR => colors.error,
19164 DiagnosticSeverity::WARNING => colors.warning,
19165 DiagnosticSeverity::INFORMATION => colors.info,
19166 DiagnosticSeverity::HINT => colors.info,
19167 _ => colors.ignored,
19168 }
19169}
19170
19171pub fn styled_runs_for_code_label<'a>(
19172 label: &'a CodeLabel,
19173 syntax_theme: &'a theme::SyntaxTheme,
19174) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
19175 let fade_out = HighlightStyle {
19176 fade_out: Some(0.35),
19177 ..Default::default()
19178 };
19179
19180 let mut prev_end = label.filter_range.end;
19181 label
19182 .runs
19183 .iter()
19184 .enumerate()
19185 .flat_map(move |(ix, (range, highlight_id))| {
19186 let style = if let Some(style) = highlight_id.style(syntax_theme) {
19187 style
19188 } else {
19189 return Default::default();
19190 };
19191 let mut muted_style = style;
19192 muted_style.highlight(fade_out);
19193
19194 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
19195 if range.start >= label.filter_range.end {
19196 if range.start > prev_end {
19197 runs.push((prev_end..range.start, fade_out));
19198 }
19199 runs.push((range.clone(), muted_style));
19200 } else if range.end <= label.filter_range.end {
19201 runs.push((range.clone(), style));
19202 } else {
19203 runs.push((range.start..label.filter_range.end, style));
19204 runs.push((label.filter_range.end..range.end, muted_style));
19205 }
19206 prev_end = cmp::max(prev_end, range.end);
19207
19208 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
19209 runs.push((prev_end..label.text.len(), fade_out));
19210 }
19211
19212 runs
19213 })
19214}
19215
19216pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
19217 let mut prev_index = 0;
19218 let mut prev_codepoint: Option<char> = None;
19219 text.char_indices()
19220 .chain([(text.len(), '\0')])
19221 .filter_map(move |(index, codepoint)| {
19222 let prev_codepoint = prev_codepoint.replace(codepoint)?;
19223 let is_boundary = index == text.len()
19224 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
19225 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
19226 if is_boundary {
19227 let chunk = &text[prev_index..index];
19228 prev_index = index;
19229 Some(chunk)
19230 } else {
19231 None
19232 }
19233 })
19234}
19235
19236pub trait RangeToAnchorExt: Sized {
19237 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
19238
19239 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
19240 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
19241 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
19242 }
19243}
19244
19245impl<T: ToOffset> RangeToAnchorExt for Range<T> {
19246 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
19247 let start_offset = self.start.to_offset(snapshot);
19248 let end_offset = self.end.to_offset(snapshot);
19249 if start_offset == end_offset {
19250 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
19251 } else {
19252 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
19253 }
19254 }
19255}
19256
19257pub trait RowExt {
19258 fn as_f32(&self) -> f32;
19259
19260 fn next_row(&self) -> Self;
19261
19262 fn previous_row(&self) -> Self;
19263
19264 fn minus(&self, other: Self) -> u32;
19265}
19266
19267impl RowExt for DisplayRow {
19268 fn as_f32(&self) -> f32 {
19269 self.0 as f32
19270 }
19271
19272 fn next_row(&self) -> Self {
19273 Self(self.0 + 1)
19274 }
19275
19276 fn previous_row(&self) -> Self {
19277 Self(self.0.saturating_sub(1))
19278 }
19279
19280 fn minus(&self, other: Self) -> u32 {
19281 self.0 - other.0
19282 }
19283}
19284
19285impl RowExt for MultiBufferRow {
19286 fn as_f32(&self) -> f32 {
19287 self.0 as f32
19288 }
19289
19290 fn next_row(&self) -> Self {
19291 Self(self.0 + 1)
19292 }
19293
19294 fn previous_row(&self) -> Self {
19295 Self(self.0.saturating_sub(1))
19296 }
19297
19298 fn minus(&self, other: Self) -> u32 {
19299 self.0 - other.0
19300 }
19301}
19302
19303trait RowRangeExt {
19304 type Row;
19305
19306 fn len(&self) -> usize;
19307
19308 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
19309}
19310
19311impl RowRangeExt for Range<MultiBufferRow> {
19312 type Row = MultiBufferRow;
19313
19314 fn len(&self) -> usize {
19315 (self.end.0 - self.start.0) as usize
19316 }
19317
19318 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
19319 (self.start.0..self.end.0).map(MultiBufferRow)
19320 }
19321}
19322
19323impl RowRangeExt for Range<DisplayRow> {
19324 type Row = DisplayRow;
19325
19326 fn len(&self) -> usize {
19327 (self.end.0 - self.start.0) as usize
19328 }
19329
19330 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
19331 (self.start.0..self.end.0).map(DisplayRow)
19332 }
19333}
19334
19335/// If select range has more than one line, we
19336/// just point the cursor to range.start.
19337fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
19338 if range.start.row == range.end.row {
19339 range
19340 } else {
19341 range.start..range.start
19342 }
19343}
19344pub struct KillRing(ClipboardItem);
19345impl Global for KillRing {}
19346
19347const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
19348
19349struct BreakpointPromptEditor {
19350 pub(crate) prompt: Entity<Editor>,
19351 editor: WeakEntity<Editor>,
19352 breakpoint_anchor: Anchor,
19353 kind: BreakpointKind,
19354 block_ids: HashSet<CustomBlockId>,
19355 gutter_dimensions: Arc<Mutex<GutterDimensions>>,
19356 _subscriptions: Vec<Subscription>,
19357}
19358
19359impl BreakpointPromptEditor {
19360 const MAX_LINES: u8 = 4;
19361
19362 fn new(
19363 editor: WeakEntity<Editor>,
19364 breakpoint_anchor: Anchor,
19365 kind: BreakpointKind,
19366 window: &mut Window,
19367 cx: &mut Context<Self>,
19368 ) -> Self {
19369 let buffer = cx.new(|cx| {
19370 Buffer::local(
19371 kind.log_message()
19372 .map(|msg| msg.to_string())
19373 .unwrap_or_default(),
19374 cx,
19375 )
19376 });
19377 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19378
19379 let prompt = cx.new(|cx| {
19380 let mut prompt = Editor::new(
19381 EditorMode::AutoHeight {
19382 max_lines: Self::MAX_LINES as usize,
19383 },
19384 buffer,
19385 None,
19386 window,
19387 cx,
19388 );
19389 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
19390 prompt.set_show_cursor_when_unfocused(false, cx);
19391 prompt.set_placeholder_text(
19392 "Message to log when breakpoint is hit. Expressions within {} are interpolated.",
19393 cx,
19394 );
19395
19396 prompt
19397 });
19398
19399 Self {
19400 prompt,
19401 editor,
19402 breakpoint_anchor,
19403 kind,
19404 gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
19405 block_ids: Default::default(),
19406 _subscriptions: vec![],
19407 }
19408 }
19409
19410 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
19411 self.block_ids.extend(block_ids)
19412 }
19413
19414 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
19415 if let Some(editor) = self.editor.upgrade() {
19416 let log_message = self
19417 .prompt
19418 .read(cx)
19419 .buffer
19420 .read(cx)
19421 .as_singleton()
19422 .expect("A multi buffer in breakpoint prompt isn't possible")
19423 .read(cx)
19424 .as_rope()
19425 .to_string();
19426
19427 editor.update(cx, |editor, cx| {
19428 editor.edit_breakpoint_at_anchor(
19429 self.breakpoint_anchor,
19430 self.kind.clone(),
19431 BreakpointEditAction::EditLogMessage(log_message.into()),
19432 cx,
19433 );
19434
19435 editor.remove_blocks(self.block_ids.clone(), None, cx);
19436 cx.focus_self(window);
19437 });
19438 }
19439 }
19440
19441 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
19442 self.editor
19443 .update(cx, |editor, cx| {
19444 editor.remove_blocks(self.block_ids.clone(), None, cx);
19445 window.focus(&editor.focus_handle);
19446 })
19447 .log_err();
19448 }
19449
19450 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
19451 let settings = ThemeSettings::get_global(cx);
19452 let text_style = TextStyle {
19453 color: if self.prompt.read(cx).read_only(cx) {
19454 cx.theme().colors().text_disabled
19455 } else {
19456 cx.theme().colors().text
19457 },
19458 font_family: settings.buffer_font.family.clone(),
19459 font_fallbacks: settings.buffer_font.fallbacks.clone(),
19460 font_size: settings.buffer_font_size(cx).into(),
19461 font_weight: settings.buffer_font.weight,
19462 line_height: relative(settings.buffer_line_height.value()),
19463 ..Default::default()
19464 };
19465 EditorElement::new(
19466 &self.prompt,
19467 EditorStyle {
19468 background: cx.theme().colors().editor_background,
19469 local_player: cx.theme().players().local(),
19470 text: text_style,
19471 ..Default::default()
19472 },
19473 )
19474 }
19475}
19476
19477impl Render for BreakpointPromptEditor {
19478 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
19479 let gutter_dimensions = *self.gutter_dimensions.lock();
19480 h_flex()
19481 .key_context("Editor")
19482 .bg(cx.theme().colors().editor_background)
19483 .border_y_1()
19484 .border_color(cx.theme().status().info_border)
19485 .size_full()
19486 .py(window.line_height() / 2.5)
19487 .on_action(cx.listener(Self::confirm))
19488 .on_action(cx.listener(Self::cancel))
19489 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
19490 .child(div().flex_1().child(self.render_prompt_editor(cx)))
19491 }
19492}
19493
19494impl Focusable for BreakpointPromptEditor {
19495 fn focus_handle(&self, cx: &App) -> FocusHandle {
19496 self.prompt.focus_handle(cx)
19497 }
19498}
19499
19500fn all_edits_insertions_or_deletions(
19501 edits: &Vec<(Range<Anchor>, String)>,
19502 snapshot: &MultiBufferSnapshot,
19503) -> bool {
19504 let mut all_insertions = true;
19505 let mut all_deletions = true;
19506
19507 for (range, new_text) in edits.iter() {
19508 let range_is_empty = range.to_offset(&snapshot).is_empty();
19509 let text_is_empty = new_text.is_empty();
19510
19511 if range_is_empty != text_is_empty {
19512 if range_is_empty {
19513 all_deletions = false;
19514 } else {
19515 all_insertions = false;
19516 }
19517 } else {
19518 return false;
19519 }
19520
19521 if !all_insertions && !all_deletions {
19522 return false;
19523 }
19524 }
19525 all_insertions || all_deletions
19526}
19527
19528struct MissingEditPredictionKeybindingTooltip;
19529
19530impl Render for MissingEditPredictionKeybindingTooltip {
19531 fn render(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
19532 ui::tooltip_container(window, cx, |container, _, cx| {
19533 container
19534 .flex_shrink_0()
19535 .max_w_80()
19536 .min_h(rems_from_px(124.))
19537 .justify_between()
19538 .child(
19539 v_flex()
19540 .flex_1()
19541 .text_ui_sm(cx)
19542 .child(Label::new("Conflict with Accept Keybinding"))
19543 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
19544 )
19545 .child(
19546 h_flex()
19547 .pb_1()
19548 .gap_1()
19549 .items_end()
19550 .w_full()
19551 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
19552 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
19553 }))
19554 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
19555 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
19556 })),
19557 )
19558 })
19559 }
19560}
19561
19562#[derive(Debug, Clone, Copy, PartialEq)]
19563pub struct LineHighlight {
19564 pub background: Background,
19565 pub border: Option<gpui::Hsla>,
19566}
19567
19568impl From<Hsla> for LineHighlight {
19569 fn from(hsla: Hsla) -> Self {
19570 Self {
19571 background: hsla.into(),
19572 border: None,
19573 }
19574 }
19575}
19576
19577impl From<Background> for LineHighlight {
19578 fn from(background: Background) -> Self {
19579 Self {
19580 background,
19581 border: None,
19582 }
19583 }
19584}
19585
19586fn render_diff_hunk_controls(
19587 row: u32,
19588 status: &DiffHunkStatus,
19589 hunk_range: Range<Anchor>,
19590 is_created_file: bool,
19591 line_height: Pixels,
19592 editor: &Entity<Editor>,
19593 cx: &mut App,
19594) -> AnyElement {
19595 h_flex()
19596 .h(line_height)
19597 .mr_1()
19598 .gap_1()
19599 .px_0p5()
19600 .pb_1()
19601 .border_x_1()
19602 .border_b_1()
19603 .border_color(cx.theme().colors().border_variant)
19604 .rounded_b_lg()
19605 .bg(cx.theme().colors().editor_background)
19606 .gap_1()
19607 .occlude()
19608 .shadow_md()
19609 .child(if status.has_secondary_hunk() {
19610 Button::new(("stage", row as u64), "Stage")
19611 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
19612 .tooltip({
19613 let focus_handle = editor.focus_handle(cx);
19614 move |window, cx| {
19615 Tooltip::for_action_in(
19616 "Stage Hunk",
19617 &::git::ToggleStaged,
19618 &focus_handle,
19619 window,
19620 cx,
19621 )
19622 }
19623 })
19624 .on_click({
19625 let editor = editor.clone();
19626 move |_event, _window, cx| {
19627 editor.update(cx, |editor, cx| {
19628 editor.stage_or_unstage_diff_hunks(
19629 true,
19630 vec![hunk_range.start..hunk_range.start],
19631 cx,
19632 );
19633 });
19634 }
19635 })
19636 } else {
19637 Button::new(("unstage", row as u64), "Unstage")
19638 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
19639 .tooltip({
19640 let focus_handle = editor.focus_handle(cx);
19641 move |window, cx| {
19642 Tooltip::for_action_in(
19643 "Unstage Hunk",
19644 &::git::ToggleStaged,
19645 &focus_handle,
19646 window,
19647 cx,
19648 )
19649 }
19650 })
19651 .on_click({
19652 let editor = editor.clone();
19653 move |_event, _window, cx| {
19654 editor.update(cx, |editor, cx| {
19655 editor.stage_or_unstage_diff_hunks(
19656 false,
19657 vec![hunk_range.start..hunk_range.start],
19658 cx,
19659 );
19660 });
19661 }
19662 })
19663 })
19664 .child(
19665 Button::new("restore", "Restore")
19666 .tooltip({
19667 let focus_handle = editor.focus_handle(cx);
19668 move |window, cx| {
19669 Tooltip::for_action_in(
19670 "Restore Hunk",
19671 &::git::Restore,
19672 &focus_handle,
19673 window,
19674 cx,
19675 )
19676 }
19677 })
19678 .on_click({
19679 let editor = editor.clone();
19680 move |_event, window, cx| {
19681 editor.update(cx, |editor, cx| {
19682 let snapshot = editor.snapshot(window, cx);
19683 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
19684 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
19685 });
19686 }
19687 })
19688 .disabled(is_created_file),
19689 )
19690 .when(
19691 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
19692 |el| {
19693 el.child(
19694 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
19695 .shape(IconButtonShape::Square)
19696 .icon_size(IconSize::Small)
19697 // .disabled(!has_multiple_hunks)
19698 .tooltip({
19699 let focus_handle = editor.focus_handle(cx);
19700 move |window, cx| {
19701 Tooltip::for_action_in(
19702 "Next Hunk",
19703 &GoToHunk,
19704 &focus_handle,
19705 window,
19706 cx,
19707 )
19708 }
19709 })
19710 .on_click({
19711 let editor = editor.clone();
19712 move |_event, window, cx| {
19713 editor.update(cx, |editor, cx| {
19714 let snapshot = editor.snapshot(window, cx);
19715 let position =
19716 hunk_range.end.to_point(&snapshot.buffer_snapshot);
19717 editor.go_to_hunk_before_or_after_position(
19718 &snapshot,
19719 position,
19720 Direction::Next,
19721 window,
19722 cx,
19723 );
19724 editor.expand_selected_diff_hunks(cx);
19725 });
19726 }
19727 }),
19728 )
19729 .child(
19730 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
19731 .shape(IconButtonShape::Square)
19732 .icon_size(IconSize::Small)
19733 // .disabled(!has_multiple_hunks)
19734 .tooltip({
19735 let focus_handle = editor.focus_handle(cx);
19736 move |window, cx| {
19737 Tooltip::for_action_in(
19738 "Previous Hunk",
19739 &GoToPreviousHunk,
19740 &focus_handle,
19741 window,
19742 cx,
19743 )
19744 }
19745 })
19746 .on_click({
19747 let editor = editor.clone();
19748 move |_event, window, cx| {
19749 editor.update(cx, |editor, cx| {
19750 let snapshot = editor.snapshot(window, cx);
19751 let point =
19752 hunk_range.start.to_point(&snapshot.buffer_snapshot);
19753 editor.go_to_hunk_before_or_after_position(
19754 &snapshot,
19755 point,
19756 Direction::Prev,
19757 window,
19758 cx,
19759 );
19760 editor.expand_selected_diff_hunks(cx);
19761 });
19762 }
19763 }),
19764 )
19765 },
19766 )
19767 .into_any_element()
19768}