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};
63use editor_settings::GoToDefinitionFallback;
64pub use editor_settings::{
65 CurrentLineHighlight, EditorSettings, ScrollBeyondLastLine, SearchSettings, ShowScrollbar,
66};
67pub use editor_settings_controls::*;
68use element::{layout_line, AcceptEditPredictionBinding, LineWithInvisibles, PositionMap};
69pub use element::{
70 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
71};
72use feature_flags::{Debugger, FeatureFlagAppExt};
73use futures::{
74 future::{self, join, Shared},
75 FutureExt,
76};
77use fuzzy::StringMatchCandidate;
78
79use ::git::Restore;
80use code_context_menus::{
81 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
82 CompletionsMenu, ContextMenuOrigin,
83};
84use git::blame::GitBlame;
85use gpui::{
86 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size, Action, Animation,
87 AnimationExt, AnyElement, App, AppContext, AsyncWindowContext, AvailableSpace, Background,
88 Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context, DispatchPhase, Edges, Entity,
89 EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent, Focusable, FontId, FontWeight,
90 Global, HighlightStyle, Hsla, KeyContext, Modifiers, MouseButton, MouseDownEvent, PaintQuad,
91 ParentElement, Pixels, Render, SharedString, Size, Stateful, Styled, StyledText, Subscription,
92 Task, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle,
93 WeakEntity, WeakFocusHandle, Window,
94};
95use highlight_matching_bracket::refresh_matching_bracket_highlights;
96use hover_links::{find_file, HoverLink, HoveredLinkState, InlayHighlight};
97use hover_popover::{hide_hover, HoverState};
98use indent_guides::ActiveIndentGuidesState;
99use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
100pub use inline_completion::Direction;
101use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
102pub use items::MAX_TAB_TITLE_LEN;
103use itertools::Itertools;
104use language::{
105 language_settings::{
106 self, all_language_settings, language_settings, InlayHintSettings, RewrapBehavior,
107 WordsCompletionMode,
108 },
109 point_from_lsp, text_diff_with_options, AutoindentMode, BracketMatch, BracketPair, Buffer,
110 Capability, CharKind, CodeLabel, CursorShape, Diagnostic, DiffOptions, EditPredictionsMode,
111 EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point,
112 Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
113};
114use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange};
115use linked_editing_ranges::refresh_linked_ranges;
116use mouse_context_menu::MouseContextMenu;
117use persistence::DB;
118use project::{
119 debugger::breakpoint_store::{
120 BreakpointEditAction, BreakpointState, BreakpointStore, BreakpointStoreEvent,
121 },
122 ProjectPath,
123};
124
125pub use proposed_changes_editor::{
126 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
127};
128use smallvec::smallvec;
129use std::{cell::OnceCell, iter::Peekable};
130use task::{ResolvedTask, TaskTemplate, TaskVariables};
131
132pub use lsp::CompletionContext;
133use lsp::{
134 CodeActionKind, CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity,
135 InsertTextFormat, LanguageServerId, LanguageServerName,
136};
137
138use language::BufferSnapshot;
139use movement::TextLayoutDetails;
140pub use multi_buffer::{
141 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, RowInfo,
142 ToOffset, ToPoint,
143};
144use multi_buffer::{
145 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
146 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
147};
148use parking_lot::Mutex;
149use project::{
150 debugger::breakpoint_store::{Breakpoint, BreakpointKind},
151 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
152 project_settings::{GitGutterSetting, ProjectSettings},
153 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
154 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
155 TaskSourceKind,
156};
157use rand::prelude::*;
158use rpc::{proto::*, ErrorExt};
159use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
160use selections_collection::{
161 resolve_selections, MutableSelectionsCollection, SelectionsCollection,
162};
163use serde::{Deserialize, Serialize};
164use settings::{update_settings_file, Settings, SettingsLocation, SettingsStore};
165use smallvec::SmallVec;
166use snippet::Snippet;
167use std::sync::Arc;
168use std::{
169 any::TypeId,
170 borrow::Cow,
171 cell::RefCell,
172 cmp::{self, Ordering, Reverse},
173 mem,
174 num::NonZeroU32,
175 ops::{ControlFlow, Deref, DerefMut, Not as _, Range, RangeInclusive},
176 path::{Path, PathBuf},
177 rc::Rc,
178 time::{Duration, Instant},
179};
180pub use sum_tree::Bias;
181use sum_tree::TreeMap;
182use text::{BufferId, OffsetUtf16, Rope};
183use theme::{
184 observe_buffer_font_size_adjustment, ActiveTheme, PlayerColor, StatusColors, SyntaxTheme,
185 ThemeColors, ThemeSettings,
186};
187use ui::{
188 h_flex, prelude::*, ButtonSize, ButtonStyle, Disclosure, IconButton, IconButtonShape, IconName,
189 IconSize, Key, Tooltip,
190};
191use util::{maybe, post_inc, RangeExt, ResultExt, TryFutureExt};
192use workspace::{
193 item::{ItemHandle, PreviewTabsSettings},
194 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
195 searchable::SearchEvent,
196 Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
197 RestoreOnStartupBehavior, SplitDirection, TabBarSettings, Toast, ViewId, Workspace,
198 WorkspaceId, WorkspaceSettings, SERIALIZATION_THROTTLE_TIME,
199};
200
201use crate::hover_links::{find_url, find_url_from_range};
202use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
203
204pub const FILE_HEADER_HEIGHT: u32 = 2;
205pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
206pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
207const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
208const MAX_LINE_LEN: usize = 1024;
209const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
210const MAX_SELECTION_HISTORY_LEN: usize = 1024;
211pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
212#[doc(hidden)]
213pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
214
215pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
216pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
217pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
218
219pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
220pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
221pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
222
223pub type RenderDiffHunkControlsFn = Arc<
224 dyn Fn(
225 u32,
226 &DiffHunkStatus,
227 Range<Anchor>,
228 bool,
229 Pixels,
230 &Entity<Editor>,
231 &mut App,
232 ) -> AnyElement,
233>;
234
235const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
236 alt: true,
237 shift: true,
238 control: false,
239 platform: false,
240 function: false,
241};
242
243#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
244pub enum InlayId {
245 InlineCompletion(usize),
246 Hint(usize),
247}
248
249impl InlayId {
250 fn id(&self) -> usize {
251 match self {
252 Self::InlineCompletion(id) => *id,
253 Self::Hint(id) => *id,
254 }
255 }
256}
257
258pub enum DebugCurrentRowHighlight {}
259enum DocumentHighlightRead {}
260enum DocumentHighlightWrite {}
261enum InputComposition {}
262enum SelectedTextHighlight {}
263
264#[derive(Debug, Copy, Clone, PartialEq, Eq)]
265pub enum Navigated {
266 Yes,
267 No,
268}
269
270impl Navigated {
271 pub fn from_bool(yes: bool) -> Navigated {
272 if yes {
273 Navigated::Yes
274 } else {
275 Navigated::No
276 }
277 }
278}
279
280#[derive(Debug, Clone, PartialEq, Eq)]
281enum DisplayDiffHunk {
282 Folded {
283 display_row: DisplayRow,
284 },
285 Unfolded {
286 is_created_file: bool,
287 diff_base_byte_range: Range<usize>,
288 display_row_range: Range<DisplayRow>,
289 multi_buffer_range: Range<Anchor>,
290 status: DiffHunkStatus,
291 },
292}
293
294pub fn init_settings(cx: &mut App) {
295 EditorSettings::register(cx);
296}
297
298pub fn init(cx: &mut App) {
299 init_settings(cx);
300
301 workspace::register_project_item::<Editor>(cx);
302 workspace::FollowableViewRegistry::register::<Editor>(cx);
303 workspace::register_serializable_item::<Editor>(cx);
304
305 cx.observe_new(
306 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
307 workspace.register_action(Editor::new_file);
308 workspace.register_action(Editor::new_file_vertical);
309 workspace.register_action(Editor::new_file_horizontal);
310 workspace.register_action(Editor::cancel_language_server_work);
311 },
312 )
313 .detach();
314
315 cx.on_action(move |_: &workspace::NewFile, cx| {
316 let app_state = workspace::AppState::global(cx);
317 if let Some(app_state) = app_state.upgrade() {
318 workspace::open_new(
319 Default::default(),
320 app_state,
321 cx,
322 |workspace, window, cx| {
323 Editor::new_file(workspace, &Default::default(), window, cx)
324 },
325 )
326 .detach();
327 }
328 });
329 cx.on_action(move |_: &workspace::NewWindow, cx| {
330 let app_state = workspace::AppState::global(cx);
331 if let Some(app_state) = app_state.upgrade() {
332 workspace::open_new(
333 Default::default(),
334 app_state,
335 cx,
336 |workspace, window, cx| {
337 cx.activate(true);
338 Editor::new_file(workspace, &Default::default(), window, cx)
339 },
340 )
341 .detach();
342 }
343 });
344}
345
346pub struct SearchWithinRange;
347
348trait InvalidationRegion {
349 fn ranges(&self) -> &[Range<Anchor>];
350}
351
352#[derive(Clone, Debug, PartialEq)]
353pub enum SelectPhase {
354 Begin {
355 position: DisplayPoint,
356 add: bool,
357 click_count: usize,
358 },
359 BeginColumnar {
360 position: DisplayPoint,
361 reset: bool,
362 goal_column: u32,
363 },
364 Extend {
365 position: DisplayPoint,
366 click_count: usize,
367 },
368 Update {
369 position: DisplayPoint,
370 goal_column: u32,
371 scroll_delta: gpui::Point<f32>,
372 },
373 End,
374}
375
376#[derive(Clone, Debug)]
377pub enum SelectMode {
378 Character,
379 Word(Range<Anchor>),
380 Line(Range<Anchor>),
381 All,
382}
383
384#[derive(Copy, Clone, PartialEq, Eq, Debug)]
385pub enum EditorMode {
386 SingleLine { auto_width: bool },
387 AutoHeight { max_lines: usize },
388 Full,
389}
390
391#[derive(Copy, Clone, Debug)]
392pub enum SoftWrap {
393 /// Prefer not to wrap at all.
394 ///
395 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
396 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
397 GitDiff,
398 /// Prefer a single line generally, unless an overly long line is encountered.
399 None,
400 /// Soft wrap lines that exceed the editor width.
401 EditorWidth,
402 /// Soft wrap lines at the preferred line length.
403 Column(u32),
404 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
405 Bounded(u32),
406}
407
408#[derive(Clone)]
409pub struct EditorStyle {
410 pub background: Hsla,
411 pub local_player: PlayerColor,
412 pub text: TextStyle,
413 pub scrollbar_width: Pixels,
414 pub syntax: Arc<SyntaxTheme>,
415 pub status: StatusColors,
416 pub inlay_hints_style: HighlightStyle,
417 pub inline_completion_styles: InlineCompletionStyles,
418 pub unnecessary_code_fade: f32,
419}
420
421impl Default for EditorStyle {
422 fn default() -> Self {
423 Self {
424 background: Hsla::default(),
425 local_player: PlayerColor::default(),
426 text: TextStyle::default(),
427 scrollbar_width: Pixels::default(),
428 syntax: Default::default(),
429 // HACK: Status colors don't have a real default.
430 // We should look into removing the status colors from the editor
431 // style and retrieve them directly from the theme.
432 status: StatusColors::dark(),
433 inlay_hints_style: HighlightStyle::default(),
434 inline_completion_styles: InlineCompletionStyles {
435 insertion: HighlightStyle::default(),
436 whitespace: HighlightStyle::default(),
437 },
438 unnecessary_code_fade: Default::default(),
439 }
440 }
441}
442
443pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
444 let show_background = language_settings::language_settings(None, None, cx)
445 .inlay_hints
446 .show_background;
447
448 HighlightStyle {
449 color: Some(cx.theme().status().hint),
450 background_color: show_background.then(|| cx.theme().status().hint_background),
451 ..HighlightStyle::default()
452 }
453}
454
455pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
456 InlineCompletionStyles {
457 insertion: HighlightStyle {
458 color: Some(cx.theme().status().predictive),
459 ..HighlightStyle::default()
460 },
461 whitespace: HighlightStyle {
462 background_color: Some(cx.theme().status().created_background),
463 ..HighlightStyle::default()
464 },
465 }
466}
467
468type CompletionId = usize;
469
470pub(crate) enum EditDisplayMode {
471 TabAccept,
472 DiffPopover,
473 Inline,
474}
475
476enum InlineCompletion {
477 Edit {
478 edits: Vec<(Range<Anchor>, String)>,
479 edit_preview: Option<EditPreview>,
480 display_mode: EditDisplayMode,
481 snapshot: BufferSnapshot,
482 },
483 Move {
484 target: Anchor,
485 snapshot: BufferSnapshot,
486 },
487}
488
489struct InlineCompletionState {
490 inlay_ids: Vec<InlayId>,
491 completion: InlineCompletion,
492 completion_id: Option<SharedString>,
493 invalidation_range: Range<Anchor>,
494}
495
496enum EditPredictionSettings {
497 Disabled,
498 Enabled {
499 show_in_menu: bool,
500 preview_requires_modifier: bool,
501 },
502}
503
504enum InlineCompletionHighlight {}
505
506#[derive(Debug, Clone)]
507struct InlineDiagnostic {
508 message: SharedString,
509 group_id: usize,
510 is_primary: bool,
511 start: Point,
512 severity: DiagnosticSeverity,
513}
514
515pub enum MenuInlineCompletionsPolicy {
516 Never,
517 ByProvider,
518}
519
520pub enum EditPredictionPreview {
521 /// Modifier is not pressed
522 Inactive { released_too_fast: bool },
523 /// Modifier pressed
524 Active {
525 since: Instant,
526 previous_scroll_position: Option<ScrollAnchor>,
527 },
528}
529
530impl EditPredictionPreview {
531 pub fn released_too_fast(&self) -> bool {
532 match self {
533 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
534 EditPredictionPreview::Active { .. } => false,
535 }
536 }
537
538 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
539 if let EditPredictionPreview::Active {
540 previous_scroll_position,
541 ..
542 } = self
543 {
544 *previous_scroll_position = scroll_position;
545 }
546 }
547}
548
549pub struct ContextMenuOptions {
550 pub min_entries_visible: usize,
551 pub max_entries_visible: usize,
552 pub placement: Option<ContextMenuPlacement>,
553}
554
555#[derive(Debug, Clone, PartialEq, Eq)]
556pub enum ContextMenuPlacement {
557 Above,
558 Below,
559}
560
561#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
562struct EditorActionId(usize);
563
564impl EditorActionId {
565 pub fn post_inc(&mut self) -> Self {
566 let answer = self.0;
567
568 *self = Self(answer + 1);
569
570 Self(answer)
571 }
572}
573
574// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
575// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
576
577type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
578type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
579
580#[derive(Default)]
581struct ScrollbarMarkerState {
582 scrollbar_size: Size<Pixels>,
583 dirty: bool,
584 markers: Arc<[PaintQuad]>,
585 pending_refresh: Option<Task<Result<()>>>,
586}
587
588impl ScrollbarMarkerState {
589 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
590 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
591 }
592}
593
594#[derive(Clone, Debug)]
595struct RunnableTasks {
596 templates: Vec<(TaskSourceKind, TaskTemplate)>,
597 offset: multi_buffer::Anchor,
598 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
599 column: u32,
600 // Values of all named captures, including those starting with '_'
601 extra_variables: HashMap<String, String>,
602 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
603 context_range: Range<BufferOffset>,
604}
605
606impl RunnableTasks {
607 fn resolve<'a>(
608 &'a self,
609 cx: &'a task::TaskContext,
610 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
611 self.templates.iter().filter_map(|(kind, template)| {
612 template
613 .resolve_task(&kind.to_id_base(), cx)
614 .map(|task| (kind.clone(), task))
615 })
616 }
617}
618
619#[derive(Clone)]
620struct ResolvedTasks {
621 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
622 position: Anchor,
623}
624
625#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
626struct BufferOffset(usize);
627
628// Addons allow storing per-editor state in other crates (e.g. Vim)
629pub trait Addon: 'static {
630 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
631
632 fn render_buffer_header_controls(
633 &self,
634 _: &ExcerptInfo,
635 _: &Window,
636 _: &App,
637 ) -> Option<AnyElement> {
638 None
639 }
640
641 fn to_any(&self) -> &dyn std::any::Any;
642}
643
644/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
645///
646/// See the [module level documentation](self) for more information.
647pub struct Editor {
648 focus_handle: FocusHandle,
649 last_focused_descendant: Option<WeakFocusHandle>,
650 /// The text buffer being edited
651 buffer: Entity<MultiBuffer>,
652 /// Map of how text in the buffer should be displayed.
653 /// Handles soft wraps, folds, fake inlay text insertions, etc.
654 pub display_map: Entity<DisplayMap>,
655 pub selections: SelectionsCollection,
656 pub scroll_manager: ScrollManager,
657 /// When inline assist editors are linked, they all render cursors because
658 /// typing enters text into each of them, even the ones that aren't focused.
659 pub(crate) show_cursor_when_unfocused: bool,
660 columnar_selection_tail: Option<Anchor>,
661 add_selections_state: Option<AddSelectionsState>,
662 select_next_state: Option<SelectNextState>,
663 select_prev_state: Option<SelectNextState>,
664 selection_history: SelectionHistory,
665 autoclose_regions: Vec<AutocloseRegion>,
666 snippet_stack: InvalidationStack<SnippetState>,
667 select_syntax_node_history: SelectSyntaxNodeHistory,
668 ime_transaction: Option<TransactionId>,
669 active_diagnostics: Option<ActiveDiagnosticGroup>,
670 show_inline_diagnostics: bool,
671 inline_diagnostics_update: Task<()>,
672 inline_diagnostics_enabled: bool,
673 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
674 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
675 hard_wrap: Option<usize>,
676
677 // TODO: make this a access method
678 pub project: Option<Entity<Project>>,
679 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
680 completion_provider: Option<Box<dyn CompletionProvider>>,
681 collaboration_hub: Option<Box<dyn CollaborationHub>>,
682 blink_manager: Entity<BlinkManager>,
683 show_cursor_names: bool,
684 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
685 pub show_local_selections: bool,
686 mode: EditorMode,
687 show_breadcrumbs: bool,
688 show_gutter: bool,
689 show_scrollbars: bool,
690 show_line_numbers: Option<bool>,
691 use_relative_line_numbers: Option<bool>,
692 show_git_diff_gutter: Option<bool>,
693 show_code_actions: Option<bool>,
694 show_runnables: Option<bool>,
695 show_breakpoints: Option<bool>,
696 show_wrap_guides: Option<bool>,
697 show_indent_guides: Option<bool>,
698 placeholder_text: Option<Arc<str>>,
699 highlight_order: usize,
700 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
701 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
702 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
703 scrollbar_marker_state: ScrollbarMarkerState,
704 active_indent_guides_state: ActiveIndentGuidesState,
705 nav_history: Option<ItemNavHistory>,
706 context_menu: RefCell<Option<CodeContextMenu>>,
707 context_menu_options: Option<ContextMenuOptions>,
708 mouse_context_menu: Option<MouseContextMenu>,
709 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
710 signature_help_state: SignatureHelpState,
711 auto_signature_help: Option<bool>,
712 find_all_references_task_sources: Vec<Anchor>,
713 next_completion_id: CompletionId,
714 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
715 code_actions_task: Option<Task<Result<()>>>,
716 selection_highlight_task: Option<Task<()>>,
717 document_highlights_task: Option<Task<()>>,
718 linked_editing_range_task: Option<Task<Option<()>>>,
719 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
720 pending_rename: Option<RenameState>,
721 searchable: bool,
722 cursor_shape: CursorShape,
723 current_line_highlight: Option<CurrentLineHighlight>,
724 collapse_matches: bool,
725 autoindent_mode: Option<AutoindentMode>,
726 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
727 input_enabled: bool,
728 use_modal_editing: bool,
729 read_only: bool,
730 leader_peer_id: Option<PeerId>,
731 remote_id: Option<ViewId>,
732 hover_state: HoverState,
733 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
734 gutter_hovered: bool,
735 hovered_link_state: Option<HoveredLinkState>,
736 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
737 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
738 active_inline_completion: Option<InlineCompletionState>,
739 /// Used to prevent flickering as the user types while the menu is open
740 stale_inline_completion_in_menu: Option<InlineCompletionState>,
741 edit_prediction_settings: EditPredictionSettings,
742 inline_completions_hidden_for_vim_mode: bool,
743 show_inline_completions_override: Option<bool>,
744 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
745 edit_prediction_preview: EditPredictionPreview,
746 edit_prediction_indent_conflict: bool,
747 edit_prediction_requires_modifier_in_indent_conflict: bool,
748 inlay_hint_cache: InlayHintCache,
749 next_inlay_id: usize,
750 _subscriptions: Vec<Subscription>,
751 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
752 gutter_dimensions: GutterDimensions,
753 style: Option<EditorStyle>,
754 text_style_refinement: Option<TextStyleRefinement>,
755 next_editor_action_id: EditorActionId,
756 editor_actions:
757 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
758 use_autoclose: bool,
759 use_auto_surround: bool,
760 auto_replace_emoji_shortcode: bool,
761 jsx_tag_auto_close_enabled_in_any_buffer: bool,
762 show_git_blame_gutter: bool,
763 show_git_blame_inline: bool,
764 show_git_blame_inline_delay_task: Option<Task<()>>,
765 git_blame_inline_tooltip: Option<WeakEntity<crate::commit_tooltip::CommitTooltip>>,
766 git_blame_inline_enabled: bool,
767 render_diff_hunk_controls: RenderDiffHunkControlsFn,
768 serialize_dirty_buffers: bool,
769 show_selection_menu: Option<bool>,
770 blame: Option<Entity<GitBlame>>,
771 blame_subscription: Option<Subscription>,
772 custom_context_menu: Option<
773 Box<
774 dyn 'static
775 + Fn(
776 &mut Self,
777 DisplayPoint,
778 &mut Window,
779 &mut Context<Self>,
780 ) -> Option<Entity<ui::ContextMenu>>,
781 >,
782 >,
783 last_bounds: Option<Bounds<Pixels>>,
784 last_position_map: Option<Rc<PositionMap>>,
785 expect_bounds_change: Option<Bounds<Pixels>>,
786 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
787 tasks_update_task: Option<Task<()>>,
788 pub breakpoint_store: Option<Entity<BreakpointStore>>,
789 /// Allow's a user to create a breakpoint by selecting this indicator
790 /// It should be None while a user is not hovering over the gutter
791 /// Otherwise it represents the point that the breakpoint will be shown
792 pub gutter_breakpoint_indicator: Option<DisplayPoint>,
793 in_project_search: bool,
794 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
795 breadcrumb_header: Option<String>,
796 focused_block: Option<FocusedBlock>,
797 next_scroll_position: NextScrollCursorCenterTopBottom,
798 addons: HashMap<TypeId, Box<dyn Addon>>,
799 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
800 load_diff_task: Option<Shared<Task<()>>>,
801 selection_mark_mode: bool,
802 toggle_fold_multiple_buffers: Task<()>,
803 _scroll_cursor_center_top_bottom_task: Task<()>,
804 serialize_selections: Task<()>,
805 serialize_folds: Task<()>,
806 mouse_cursor_hidden: bool,
807 hide_mouse_while_typing: bool,
808}
809
810#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
811enum NextScrollCursorCenterTopBottom {
812 #[default]
813 Center,
814 Top,
815 Bottom,
816}
817
818impl NextScrollCursorCenterTopBottom {
819 fn next(&self) -> Self {
820 match self {
821 Self::Center => Self::Top,
822 Self::Top => Self::Bottom,
823 Self::Bottom => Self::Center,
824 }
825 }
826}
827
828#[derive(Clone)]
829pub struct EditorSnapshot {
830 pub mode: EditorMode,
831 show_gutter: bool,
832 show_line_numbers: Option<bool>,
833 show_git_diff_gutter: Option<bool>,
834 show_code_actions: Option<bool>,
835 show_runnables: Option<bool>,
836 show_breakpoints: Option<bool>,
837 git_blame_gutter_max_author_length: Option<usize>,
838 pub display_snapshot: DisplaySnapshot,
839 pub placeholder_text: Option<Arc<str>>,
840 is_focused: bool,
841 scroll_anchor: ScrollAnchor,
842 ongoing_scroll: OngoingScroll,
843 current_line_highlight: CurrentLineHighlight,
844 gutter_hovered: bool,
845}
846
847const GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED: usize = 20;
848
849#[derive(Default, Debug, Clone, Copy)]
850pub struct GutterDimensions {
851 pub left_padding: Pixels,
852 pub right_padding: Pixels,
853 pub width: Pixels,
854 pub margin: Pixels,
855 pub git_blame_entries_width: Option<Pixels>,
856}
857
858impl GutterDimensions {
859 /// The full width of the space taken up by the gutter.
860 pub fn full_width(&self) -> Pixels {
861 self.margin + self.width
862 }
863
864 /// The width of the space reserved for the fold indicators,
865 /// use alongside 'justify_end' and `gutter_width` to
866 /// right align content with the line numbers
867 pub fn fold_area_width(&self) -> Pixels {
868 self.margin + self.right_padding
869 }
870}
871
872#[derive(Debug)]
873pub struct RemoteSelection {
874 pub replica_id: ReplicaId,
875 pub selection: Selection<Anchor>,
876 pub cursor_shape: CursorShape,
877 pub peer_id: PeerId,
878 pub line_mode: bool,
879 pub participant_index: Option<ParticipantIndex>,
880 pub user_name: Option<SharedString>,
881}
882
883#[derive(Clone, Debug)]
884struct SelectionHistoryEntry {
885 selections: Arc<[Selection<Anchor>]>,
886 select_next_state: Option<SelectNextState>,
887 select_prev_state: Option<SelectNextState>,
888 add_selections_state: Option<AddSelectionsState>,
889}
890
891enum SelectionHistoryMode {
892 Normal,
893 Undoing,
894 Redoing,
895}
896
897#[derive(Clone, PartialEq, Eq, Hash)]
898struct HoveredCursor {
899 replica_id: u16,
900 selection_id: usize,
901}
902
903impl Default for SelectionHistoryMode {
904 fn default() -> Self {
905 Self::Normal
906 }
907}
908
909#[derive(Default)]
910struct SelectionHistory {
911 #[allow(clippy::type_complexity)]
912 selections_by_transaction:
913 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
914 mode: SelectionHistoryMode,
915 undo_stack: VecDeque<SelectionHistoryEntry>,
916 redo_stack: VecDeque<SelectionHistoryEntry>,
917}
918
919impl SelectionHistory {
920 fn insert_transaction(
921 &mut self,
922 transaction_id: TransactionId,
923 selections: Arc<[Selection<Anchor>]>,
924 ) {
925 self.selections_by_transaction
926 .insert(transaction_id, (selections, None));
927 }
928
929 #[allow(clippy::type_complexity)]
930 fn transaction(
931 &self,
932 transaction_id: TransactionId,
933 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
934 self.selections_by_transaction.get(&transaction_id)
935 }
936
937 #[allow(clippy::type_complexity)]
938 fn transaction_mut(
939 &mut self,
940 transaction_id: TransactionId,
941 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
942 self.selections_by_transaction.get_mut(&transaction_id)
943 }
944
945 fn push(&mut self, entry: SelectionHistoryEntry) {
946 if !entry.selections.is_empty() {
947 match self.mode {
948 SelectionHistoryMode::Normal => {
949 self.push_undo(entry);
950 self.redo_stack.clear();
951 }
952 SelectionHistoryMode::Undoing => self.push_redo(entry),
953 SelectionHistoryMode::Redoing => self.push_undo(entry),
954 }
955 }
956 }
957
958 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
959 if self
960 .undo_stack
961 .back()
962 .map_or(true, |e| e.selections != entry.selections)
963 {
964 self.undo_stack.push_back(entry);
965 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
966 self.undo_stack.pop_front();
967 }
968 }
969 }
970
971 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
972 if self
973 .redo_stack
974 .back()
975 .map_or(true, |e| e.selections != entry.selections)
976 {
977 self.redo_stack.push_back(entry);
978 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
979 self.redo_stack.pop_front();
980 }
981 }
982 }
983}
984
985struct RowHighlight {
986 index: usize,
987 range: Range<Anchor>,
988 color: Hsla,
989 should_autoscroll: bool,
990}
991
992#[derive(Clone, Debug)]
993struct AddSelectionsState {
994 above: bool,
995 stack: Vec<usize>,
996}
997
998#[derive(Clone)]
999struct SelectNextState {
1000 query: AhoCorasick,
1001 wordwise: bool,
1002 done: bool,
1003}
1004
1005impl std::fmt::Debug for SelectNextState {
1006 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1007 f.debug_struct(std::any::type_name::<Self>())
1008 .field("wordwise", &self.wordwise)
1009 .field("done", &self.done)
1010 .finish()
1011 }
1012}
1013
1014#[derive(Debug)]
1015struct AutocloseRegion {
1016 selection_id: usize,
1017 range: Range<Anchor>,
1018 pair: BracketPair,
1019}
1020
1021#[derive(Debug)]
1022struct SnippetState {
1023 ranges: Vec<Vec<Range<Anchor>>>,
1024 active_index: usize,
1025 choices: Vec<Option<Vec<String>>>,
1026}
1027
1028#[doc(hidden)]
1029pub struct RenameState {
1030 pub range: Range<Anchor>,
1031 pub old_name: Arc<str>,
1032 pub editor: Entity<Editor>,
1033 block_id: CustomBlockId,
1034}
1035
1036struct InvalidationStack<T>(Vec<T>);
1037
1038struct RegisteredInlineCompletionProvider {
1039 provider: Arc<dyn InlineCompletionProviderHandle>,
1040 _subscription: Subscription,
1041}
1042
1043#[derive(Debug, PartialEq, Eq)]
1044struct ActiveDiagnosticGroup {
1045 primary_range: Range<Anchor>,
1046 primary_message: String,
1047 group_id: usize,
1048 blocks: HashMap<CustomBlockId, Diagnostic>,
1049 is_valid: bool,
1050}
1051
1052#[derive(Serialize, Deserialize, Clone, Debug)]
1053pub struct ClipboardSelection {
1054 /// The number of bytes in this selection.
1055 pub len: usize,
1056 /// Whether this was a full-line selection.
1057 pub is_entire_line: bool,
1058 /// The indentation of the first line when this content was originally copied.
1059 pub first_line_indent: u32,
1060}
1061
1062// selections, scroll behavior, was newest selection reversed
1063type SelectSyntaxNodeHistoryState = (
1064 Box<[Selection<usize>]>,
1065 SelectSyntaxNodeScrollBehavior,
1066 bool,
1067);
1068
1069#[derive(Default)]
1070struct SelectSyntaxNodeHistory {
1071 stack: Vec<SelectSyntaxNodeHistoryState>,
1072 // disable temporarily to allow changing selections without losing the stack
1073 pub disable_clearing: bool,
1074}
1075
1076impl SelectSyntaxNodeHistory {
1077 pub fn try_clear(&mut self) {
1078 if !self.disable_clearing {
1079 self.stack.clear();
1080 }
1081 }
1082
1083 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1084 self.stack.push(selection);
1085 }
1086
1087 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1088 self.stack.pop()
1089 }
1090}
1091
1092enum SelectSyntaxNodeScrollBehavior {
1093 CursorTop,
1094 CenterSelection,
1095 CursorBottom,
1096}
1097
1098#[derive(Debug)]
1099pub(crate) struct NavigationData {
1100 cursor_anchor: Anchor,
1101 cursor_position: Point,
1102 scroll_anchor: ScrollAnchor,
1103 scroll_top_row: u32,
1104}
1105
1106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1107pub enum GotoDefinitionKind {
1108 Symbol,
1109 Declaration,
1110 Type,
1111 Implementation,
1112}
1113
1114#[derive(Debug, Clone)]
1115enum InlayHintRefreshReason {
1116 ModifiersChanged(bool),
1117 Toggle(bool),
1118 SettingsChange(InlayHintSettings),
1119 NewLinesShown,
1120 BufferEdited(HashSet<Arc<Language>>),
1121 RefreshRequested,
1122 ExcerptsRemoved(Vec<ExcerptId>),
1123}
1124
1125impl InlayHintRefreshReason {
1126 fn description(&self) -> &'static str {
1127 match self {
1128 Self::ModifiersChanged(_) => "modifiers changed",
1129 Self::Toggle(_) => "toggle",
1130 Self::SettingsChange(_) => "settings change",
1131 Self::NewLinesShown => "new lines shown",
1132 Self::BufferEdited(_) => "buffer edited",
1133 Self::RefreshRequested => "refresh requested",
1134 Self::ExcerptsRemoved(_) => "excerpts removed",
1135 }
1136 }
1137}
1138
1139pub enum FormatTarget {
1140 Buffers,
1141 Ranges(Vec<Range<MultiBufferPoint>>),
1142}
1143
1144pub(crate) struct FocusedBlock {
1145 id: BlockId,
1146 focus_handle: WeakFocusHandle,
1147}
1148
1149#[derive(Clone)]
1150enum JumpData {
1151 MultiBufferRow {
1152 row: MultiBufferRow,
1153 line_offset_from_top: u32,
1154 },
1155 MultiBufferPoint {
1156 excerpt_id: ExcerptId,
1157 position: Point,
1158 anchor: text::Anchor,
1159 line_offset_from_top: u32,
1160 },
1161}
1162
1163pub enum MultibufferSelectionMode {
1164 First,
1165 All,
1166}
1167
1168#[derive(Clone, Copy, Debug, Default)]
1169pub struct RewrapOptions {
1170 pub override_language_settings: bool,
1171 pub preserve_existing_whitespace: bool,
1172}
1173
1174impl Editor {
1175 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1176 let buffer = cx.new(|cx| Buffer::local("", cx));
1177 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1178 Self::new(
1179 EditorMode::SingleLine { auto_width: false },
1180 buffer,
1181 None,
1182 window,
1183 cx,
1184 )
1185 }
1186
1187 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1188 let buffer = cx.new(|cx| Buffer::local("", cx));
1189 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1190 Self::new(EditorMode::Full, buffer, None, window, cx)
1191 }
1192
1193 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1194 let buffer = cx.new(|cx| Buffer::local("", cx));
1195 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1196 Self::new(
1197 EditorMode::SingleLine { auto_width: true },
1198 buffer,
1199 None,
1200 window,
1201 cx,
1202 )
1203 }
1204
1205 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1206 let buffer = cx.new(|cx| Buffer::local("", cx));
1207 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1208 Self::new(
1209 EditorMode::AutoHeight { max_lines },
1210 buffer,
1211 None,
1212 window,
1213 cx,
1214 )
1215 }
1216
1217 pub fn for_buffer(
1218 buffer: Entity<Buffer>,
1219 project: Option<Entity<Project>>,
1220 window: &mut Window,
1221 cx: &mut Context<Self>,
1222 ) -> Self {
1223 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1224 Self::new(EditorMode::Full, buffer, project, window, cx)
1225 }
1226
1227 pub fn for_multibuffer(
1228 buffer: Entity<MultiBuffer>,
1229 project: Option<Entity<Project>>,
1230 window: &mut Window,
1231 cx: &mut Context<Self>,
1232 ) -> Self {
1233 Self::new(EditorMode::Full, buffer, project, window, cx)
1234 }
1235
1236 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1237 let mut clone = Self::new(
1238 self.mode,
1239 self.buffer.clone(),
1240 self.project.clone(),
1241 window,
1242 cx,
1243 );
1244 self.display_map.update(cx, |display_map, cx| {
1245 let snapshot = display_map.snapshot(cx);
1246 clone.display_map.update(cx, |display_map, cx| {
1247 display_map.set_state(&snapshot, cx);
1248 });
1249 });
1250 clone.folds_did_change(cx);
1251 clone.selections.clone_state(&self.selections);
1252 clone.scroll_manager.clone_state(&self.scroll_manager);
1253 clone.searchable = self.searchable;
1254 clone
1255 }
1256
1257 pub fn new(
1258 mode: EditorMode,
1259 buffer: Entity<MultiBuffer>,
1260 project: Option<Entity<Project>>,
1261 window: &mut Window,
1262 cx: &mut Context<Self>,
1263 ) -> Self {
1264 let style = window.text_style();
1265 let font_size = style.font_size.to_pixels(window.rem_size());
1266 let editor = cx.entity().downgrade();
1267 let fold_placeholder = FoldPlaceholder {
1268 constrain_width: true,
1269 render: Arc::new(move |fold_id, fold_range, cx| {
1270 let editor = editor.clone();
1271 div()
1272 .id(fold_id)
1273 .bg(cx.theme().colors().ghost_element_background)
1274 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1275 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1276 .rounded_xs()
1277 .size_full()
1278 .cursor_pointer()
1279 .child("⋯")
1280 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1281 .on_click(move |_, _window, cx| {
1282 editor
1283 .update(cx, |editor, cx| {
1284 editor.unfold_ranges(
1285 &[fold_range.start..fold_range.end],
1286 true,
1287 false,
1288 cx,
1289 );
1290 cx.stop_propagation();
1291 })
1292 .ok();
1293 })
1294 .into_any()
1295 }),
1296 merge_adjacent: true,
1297 ..Default::default()
1298 };
1299 let display_map = cx.new(|cx| {
1300 DisplayMap::new(
1301 buffer.clone(),
1302 style.font(),
1303 font_size,
1304 None,
1305 FILE_HEADER_HEIGHT,
1306 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1307 fold_placeholder,
1308 cx,
1309 )
1310 });
1311
1312 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1313
1314 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1315
1316 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1317 .then(|| language_settings::SoftWrap::None);
1318
1319 let mut project_subscriptions = Vec::new();
1320 if mode == EditorMode::Full {
1321 if let Some(project) = project.as_ref() {
1322 project_subscriptions.push(cx.subscribe_in(
1323 project,
1324 window,
1325 |editor, _, event, window, cx| match event {
1326 project::Event::RefreshCodeLens => {
1327 // we always query lens with actions, without storing them, always refreshing them
1328 }
1329 project::Event::RefreshInlayHints => {
1330 editor
1331 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1332 }
1333 project::Event::SnippetEdit(id, snippet_edits) => {
1334 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1335 let focus_handle = editor.focus_handle(cx);
1336 if focus_handle.is_focused(window) {
1337 let snapshot = buffer.read(cx).snapshot();
1338 for (range, snippet) in snippet_edits {
1339 let editor_range =
1340 language::range_from_lsp(*range).to_offset(&snapshot);
1341 editor
1342 .insert_snippet(
1343 &[editor_range],
1344 snippet.clone(),
1345 window,
1346 cx,
1347 )
1348 .ok();
1349 }
1350 }
1351 }
1352 }
1353 _ => {}
1354 },
1355 ));
1356 if let Some(task_inventory) = project
1357 .read(cx)
1358 .task_store()
1359 .read(cx)
1360 .task_inventory()
1361 .cloned()
1362 {
1363 project_subscriptions.push(cx.observe_in(
1364 &task_inventory,
1365 window,
1366 |editor, _, window, cx| {
1367 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1368 },
1369 ));
1370 };
1371
1372 project_subscriptions.push(cx.subscribe_in(
1373 &project.read(cx).breakpoint_store(),
1374 window,
1375 |editor, _, event, window, cx| match event {
1376 BreakpointStoreEvent::ActiveDebugLineChanged => {
1377 editor.go_to_active_debug_line(window, cx);
1378 }
1379 _ => {}
1380 },
1381 ));
1382 }
1383 }
1384
1385 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1386
1387 let inlay_hint_settings =
1388 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1389 let focus_handle = cx.focus_handle();
1390 cx.on_focus(&focus_handle, window, Self::handle_focus)
1391 .detach();
1392 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1393 .detach();
1394 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1395 .detach();
1396 cx.on_blur(&focus_handle, window, Self::handle_blur)
1397 .detach();
1398
1399 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1400 Some(false)
1401 } else {
1402 None
1403 };
1404
1405 let breakpoint_store = match (mode, project.as_ref()) {
1406 (EditorMode::Full, Some(project)) => Some(project.read(cx).breakpoint_store()),
1407 _ => None,
1408 };
1409
1410 let mut code_action_providers = Vec::new();
1411 let mut load_uncommitted_diff = None;
1412 if let Some(project) = project.clone() {
1413 load_uncommitted_diff = Some(
1414 get_uncommitted_diff_for_buffer(
1415 &project,
1416 buffer.read(cx).all_buffers(),
1417 buffer.clone(),
1418 cx,
1419 )
1420 .shared(),
1421 );
1422 code_action_providers.push(Rc::new(project) as Rc<_>);
1423 }
1424
1425 let mut this = Self {
1426 focus_handle,
1427 show_cursor_when_unfocused: false,
1428 last_focused_descendant: None,
1429 buffer: buffer.clone(),
1430 display_map: display_map.clone(),
1431 selections,
1432 scroll_manager: ScrollManager::new(cx),
1433 columnar_selection_tail: None,
1434 add_selections_state: None,
1435 select_next_state: None,
1436 select_prev_state: None,
1437 selection_history: Default::default(),
1438 autoclose_regions: Default::default(),
1439 snippet_stack: Default::default(),
1440 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1441 ime_transaction: Default::default(),
1442 active_diagnostics: None,
1443 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1444 inline_diagnostics_update: Task::ready(()),
1445 inline_diagnostics: Vec::new(),
1446 soft_wrap_mode_override,
1447 hard_wrap: None,
1448 completion_provider: project.clone().map(|project| Box::new(project) as _),
1449 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1450 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1451 project,
1452 blink_manager: blink_manager.clone(),
1453 show_local_selections: true,
1454 show_scrollbars: true,
1455 mode,
1456 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1457 show_gutter: mode == EditorMode::Full,
1458 show_line_numbers: None,
1459 use_relative_line_numbers: None,
1460 show_git_diff_gutter: None,
1461 show_code_actions: None,
1462 show_runnables: None,
1463 show_breakpoints: None,
1464 show_wrap_guides: None,
1465 show_indent_guides,
1466 placeholder_text: None,
1467 highlight_order: 0,
1468 highlighted_rows: HashMap::default(),
1469 background_highlights: Default::default(),
1470 gutter_highlights: TreeMap::default(),
1471 scrollbar_marker_state: ScrollbarMarkerState::default(),
1472 active_indent_guides_state: ActiveIndentGuidesState::default(),
1473 nav_history: None,
1474 context_menu: RefCell::new(None),
1475 context_menu_options: None,
1476 mouse_context_menu: None,
1477 completion_tasks: Default::default(),
1478 signature_help_state: SignatureHelpState::default(),
1479 auto_signature_help: None,
1480 find_all_references_task_sources: Vec::new(),
1481 next_completion_id: 0,
1482 next_inlay_id: 0,
1483 code_action_providers,
1484 available_code_actions: Default::default(),
1485 code_actions_task: Default::default(),
1486 selection_highlight_task: Default::default(),
1487 document_highlights_task: Default::default(),
1488 linked_editing_range_task: Default::default(),
1489 pending_rename: Default::default(),
1490 searchable: true,
1491 cursor_shape: EditorSettings::get_global(cx)
1492 .cursor_shape
1493 .unwrap_or_default(),
1494 current_line_highlight: None,
1495 autoindent_mode: Some(AutoindentMode::EachLine),
1496 collapse_matches: false,
1497 workspace: None,
1498 input_enabled: true,
1499 use_modal_editing: mode == EditorMode::Full,
1500 read_only: false,
1501 use_autoclose: true,
1502 use_auto_surround: true,
1503 auto_replace_emoji_shortcode: false,
1504 jsx_tag_auto_close_enabled_in_any_buffer: false,
1505 leader_peer_id: None,
1506 remote_id: None,
1507 hover_state: Default::default(),
1508 pending_mouse_down: None,
1509 hovered_link_state: Default::default(),
1510 edit_prediction_provider: None,
1511 active_inline_completion: None,
1512 stale_inline_completion_in_menu: None,
1513 edit_prediction_preview: EditPredictionPreview::Inactive {
1514 released_too_fast: false,
1515 },
1516 inline_diagnostics_enabled: mode == EditorMode::Full,
1517 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1518
1519 gutter_hovered: false,
1520 pixel_position_of_newest_cursor: None,
1521 last_bounds: None,
1522 last_position_map: None,
1523 expect_bounds_change: None,
1524 gutter_dimensions: GutterDimensions::default(),
1525 style: None,
1526 show_cursor_names: false,
1527 hovered_cursors: Default::default(),
1528 next_editor_action_id: EditorActionId::default(),
1529 editor_actions: Rc::default(),
1530 inline_completions_hidden_for_vim_mode: false,
1531 show_inline_completions_override: None,
1532 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1533 edit_prediction_settings: EditPredictionSettings::Disabled,
1534 edit_prediction_indent_conflict: false,
1535 edit_prediction_requires_modifier_in_indent_conflict: true,
1536 custom_context_menu: None,
1537 show_git_blame_gutter: false,
1538 show_git_blame_inline: false,
1539 show_selection_menu: None,
1540 show_git_blame_inline_delay_task: None,
1541 git_blame_inline_tooltip: None,
1542 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1543 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1544 serialize_dirty_buffers: ProjectSettings::get_global(cx)
1545 .session
1546 .restore_unsaved_buffers,
1547 blame: None,
1548 blame_subscription: None,
1549 tasks: Default::default(),
1550
1551 breakpoint_store,
1552 gutter_breakpoint_indicator: None,
1553 _subscriptions: vec![
1554 cx.observe(&buffer, Self::on_buffer_changed),
1555 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1556 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1557 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1558 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1559 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1560 cx.observe_window_activation(window, |editor, window, cx| {
1561 let active = window.is_window_active();
1562 editor.blink_manager.update(cx, |blink_manager, cx| {
1563 if active {
1564 blink_manager.enable(cx);
1565 } else {
1566 blink_manager.disable(cx);
1567 }
1568 });
1569 }),
1570 ],
1571 tasks_update_task: None,
1572 linked_edit_ranges: Default::default(),
1573 in_project_search: false,
1574 previous_search_ranges: None,
1575 breadcrumb_header: None,
1576 focused_block: None,
1577 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1578 addons: HashMap::default(),
1579 registered_buffers: HashMap::default(),
1580 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1581 selection_mark_mode: false,
1582 toggle_fold_multiple_buffers: Task::ready(()),
1583 serialize_selections: Task::ready(()),
1584 serialize_folds: Task::ready(()),
1585 text_style_refinement: None,
1586 load_diff_task: load_uncommitted_diff,
1587 mouse_cursor_hidden: false,
1588 hide_mouse_while_typing: EditorSettings::get_global(cx)
1589 .hide_mouse_while_typing
1590 .unwrap_or(true),
1591 };
1592 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1593 this._subscriptions
1594 .push(cx.observe(breakpoints, |_, _, cx| {
1595 cx.notify();
1596 }));
1597 }
1598 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1599 this._subscriptions.extend(project_subscriptions);
1600 this._subscriptions
1601 .push(cx.subscribe_self(|editor, e: &EditorEvent, cx| {
1602 if let EditorEvent::SelectionsChanged { local } = e {
1603 if *local {
1604 let new_anchor = editor.scroll_manager.anchor();
1605 editor.update_restoration_data(cx, move |data| {
1606 data.scroll_anchor = new_anchor;
1607 });
1608 }
1609 }
1610 }));
1611
1612 this.end_selection(window, cx);
1613 this.scroll_manager.show_scrollbars(window, cx);
1614 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1615
1616 if mode == EditorMode::Full {
1617 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1618 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1619
1620 if this.git_blame_inline_enabled {
1621 this.git_blame_inline_enabled = true;
1622 this.start_git_blame_inline(false, window, cx);
1623 }
1624
1625 this.go_to_active_debug_line(window, cx);
1626
1627 if let Some(buffer) = buffer.read(cx).as_singleton() {
1628 if let Some(project) = this.project.as_ref() {
1629 let handle = project.update(cx, |project, cx| {
1630 project.register_buffer_with_language_servers(&buffer, cx)
1631 });
1632 this.registered_buffers
1633 .insert(buffer.read(cx).remote_id(), handle);
1634 }
1635 }
1636 }
1637
1638 this.report_editor_event("Editor Opened", None, cx);
1639 this
1640 }
1641
1642 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
1643 self.mouse_context_menu
1644 .as_ref()
1645 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
1646 }
1647
1648 fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
1649 self.key_context_internal(self.has_active_inline_completion(), window, cx)
1650 }
1651
1652 fn key_context_internal(
1653 &self,
1654 has_active_edit_prediction: bool,
1655 window: &Window,
1656 cx: &App,
1657 ) -> KeyContext {
1658 let mut key_context = KeyContext::new_with_defaults();
1659 key_context.add("Editor");
1660 let mode = match self.mode {
1661 EditorMode::SingleLine { .. } => "single_line",
1662 EditorMode::AutoHeight { .. } => "auto_height",
1663 EditorMode::Full => "full",
1664 };
1665
1666 if EditorSettings::jupyter_enabled(cx) {
1667 key_context.add("jupyter");
1668 }
1669
1670 key_context.set("mode", mode);
1671 if self.pending_rename.is_some() {
1672 key_context.add("renaming");
1673 }
1674
1675 match self.context_menu.borrow().as_ref() {
1676 Some(CodeContextMenu::Completions(_)) => {
1677 key_context.add("menu");
1678 key_context.add("showing_completions");
1679 }
1680 Some(CodeContextMenu::CodeActions(_)) => {
1681 key_context.add("menu");
1682 key_context.add("showing_code_actions")
1683 }
1684 None => {}
1685 }
1686
1687 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
1688 if !self.focus_handle(cx).contains_focused(window, cx)
1689 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
1690 {
1691 for addon in self.addons.values() {
1692 addon.extend_key_context(&mut key_context, cx)
1693 }
1694 }
1695
1696 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
1697 if let Some(extension) = singleton_buffer
1698 .read(cx)
1699 .file()
1700 .and_then(|file| file.path().extension()?.to_str())
1701 {
1702 key_context.set("extension", extension.to_string());
1703 }
1704 } else {
1705 key_context.add("multibuffer");
1706 }
1707
1708 if has_active_edit_prediction {
1709 if self.edit_prediction_in_conflict() {
1710 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
1711 } else {
1712 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
1713 key_context.add("copilot_suggestion");
1714 }
1715 }
1716
1717 if self.selection_mark_mode {
1718 key_context.add("selection_mode");
1719 }
1720
1721 key_context
1722 }
1723
1724 pub fn edit_prediction_in_conflict(&self) -> bool {
1725 if !self.show_edit_predictions_in_menu() {
1726 return false;
1727 }
1728
1729 let showing_completions = self
1730 .context_menu
1731 .borrow()
1732 .as_ref()
1733 .map_or(false, |context| {
1734 matches!(context, CodeContextMenu::Completions(_))
1735 });
1736
1737 showing_completions
1738 || self.edit_prediction_requires_modifier()
1739 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
1740 // bindings to insert tab characters.
1741 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
1742 }
1743
1744 pub fn accept_edit_prediction_keybind(
1745 &self,
1746 window: &Window,
1747 cx: &App,
1748 ) -> AcceptEditPredictionBinding {
1749 let key_context = self.key_context_internal(true, window, cx);
1750 let in_conflict = self.edit_prediction_in_conflict();
1751
1752 AcceptEditPredictionBinding(
1753 window
1754 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
1755 .into_iter()
1756 .filter(|binding| {
1757 !in_conflict
1758 || binding
1759 .keystrokes()
1760 .first()
1761 .map_or(false, |keystroke| keystroke.modifiers.modified())
1762 })
1763 .rev()
1764 .min_by_key(|binding| {
1765 binding
1766 .keystrokes()
1767 .first()
1768 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
1769 }),
1770 )
1771 }
1772
1773 pub fn new_file(
1774 workspace: &mut Workspace,
1775 _: &workspace::NewFile,
1776 window: &mut Window,
1777 cx: &mut Context<Workspace>,
1778 ) {
1779 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
1780 "Failed to create buffer",
1781 window,
1782 cx,
1783 |e, _, _| match e.error_code() {
1784 ErrorCode::RemoteUpgradeRequired => Some(format!(
1785 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1786 e.error_tag("required").unwrap_or("the latest version")
1787 )),
1788 _ => None,
1789 },
1790 );
1791 }
1792
1793 pub fn new_in_workspace(
1794 workspace: &mut Workspace,
1795 window: &mut Window,
1796 cx: &mut Context<Workspace>,
1797 ) -> Task<Result<Entity<Editor>>> {
1798 let project = workspace.project().clone();
1799 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1800
1801 cx.spawn_in(window, async move |workspace, cx| {
1802 let buffer = create.await?;
1803 workspace.update_in(cx, |workspace, window, cx| {
1804 let editor =
1805 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
1806 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
1807 editor
1808 })
1809 })
1810 }
1811
1812 fn new_file_vertical(
1813 workspace: &mut Workspace,
1814 _: &workspace::NewFileSplitVertical,
1815 window: &mut Window,
1816 cx: &mut Context<Workspace>,
1817 ) {
1818 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
1819 }
1820
1821 fn new_file_horizontal(
1822 workspace: &mut Workspace,
1823 _: &workspace::NewFileSplitHorizontal,
1824 window: &mut Window,
1825 cx: &mut Context<Workspace>,
1826 ) {
1827 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
1828 }
1829
1830 fn new_file_in_direction(
1831 workspace: &mut Workspace,
1832 direction: SplitDirection,
1833 window: &mut Window,
1834 cx: &mut Context<Workspace>,
1835 ) {
1836 let project = workspace.project().clone();
1837 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1838
1839 cx.spawn_in(window, async move |workspace, cx| {
1840 let buffer = create.await?;
1841 workspace.update_in(cx, move |workspace, window, cx| {
1842 workspace.split_item(
1843 direction,
1844 Box::new(
1845 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
1846 ),
1847 window,
1848 cx,
1849 )
1850 })?;
1851 anyhow::Ok(())
1852 })
1853 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
1854 match e.error_code() {
1855 ErrorCode::RemoteUpgradeRequired => Some(format!(
1856 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1857 e.error_tag("required").unwrap_or("the latest version")
1858 )),
1859 _ => None,
1860 }
1861 });
1862 }
1863
1864 pub fn leader_peer_id(&self) -> Option<PeerId> {
1865 self.leader_peer_id
1866 }
1867
1868 pub fn buffer(&self) -> &Entity<MultiBuffer> {
1869 &self.buffer
1870 }
1871
1872 pub fn workspace(&self) -> Option<Entity<Workspace>> {
1873 self.workspace.as_ref()?.0.upgrade()
1874 }
1875
1876 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
1877 self.buffer().read(cx).title(cx)
1878 }
1879
1880 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
1881 let git_blame_gutter_max_author_length = self
1882 .render_git_blame_gutter(cx)
1883 .then(|| {
1884 if let Some(blame) = self.blame.as_ref() {
1885 let max_author_length =
1886 blame.update(cx, |blame, cx| blame.max_author_length(cx));
1887 Some(max_author_length)
1888 } else {
1889 None
1890 }
1891 })
1892 .flatten();
1893
1894 EditorSnapshot {
1895 mode: self.mode,
1896 show_gutter: self.show_gutter,
1897 show_line_numbers: self.show_line_numbers,
1898 show_git_diff_gutter: self.show_git_diff_gutter,
1899 show_code_actions: self.show_code_actions,
1900 show_runnables: self.show_runnables,
1901 show_breakpoints: self.show_breakpoints,
1902 git_blame_gutter_max_author_length,
1903 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
1904 scroll_anchor: self.scroll_manager.anchor(),
1905 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
1906 placeholder_text: self.placeholder_text.clone(),
1907 is_focused: self.focus_handle.is_focused(window),
1908 current_line_highlight: self
1909 .current_line_highlight
1910 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
1911 gutter_hovered: self.gutter_hovered,
1912 }
1913 }
1914
1915 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
1916 self.buffer.read(cx).language_at(point, cx)
1917 }
1918
1919 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
1920 self.buffer.read(cx).read(cx).file_at(point).cloned()
1921 }
1922
1923 pub fn active_excerpt(
1924 &self,
1925 cx: &App,
1926 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
1927 self.buffer
1928 .read(cx)
1929 .excerpt_containing(self.selections.newest_anchor().head(), cx)
1930 }
1931
1932 pub fn mode(&self) -> EditorMode {
1933 self.mode
1934 }
1935
1936 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
1937 self.collaboration_hub.as_deref()
1938 }
1939
1940 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
1941 self.collaboration_hub = Some(hub);
1942 }
1943
1944 pub fn set_in_project_search(&mut self, in_project_search: bool) {
1945 self.in_project_search = in_project_search;
1946 }
1947
1948 pub fn set_custom_context_menu(
1949 &mut self,
1950 f: impl 'static
1951 + Fn(
1952 &mut Self,
1953 DisplayPoint,
1954 &mut Window,
1955 &mut Context<Self>,
1956 ) -> Option<Entity<ui::ContextMenu>>,
1957 ) {
1958 self.custom_context_menu = Some(Box::new(f))
1959 }
1960
1961 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
1962 self.completion_provider = provider;
1963 }
1964
1965 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
1966 self.semantics_provider.clone()
1967 }
1968
1969 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
1970 self.semantics_provider = provider;
1971 }
1972
1973 pub fn set_edit_prediction_provider<T>(
1974 &mut self,
1975 provider: Option<Entity<T>>,
1976 window: &mut Window,
1977 cx: &mut Context<Self>,
1978 ) where
1979 T: EditPredictionProvider,
1980 {
1981 self.edit_prediction_provider =
1982 provider.map(|provider| RegisteredInlineCompletionProvider {
1983 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
1984 if this.focus_handle.is_focused(window) {
1985 this.update_visible_inline_completion(window, cx);
1986 }
1987 }),
1988 provider: Arc::new(provider),
1989 });
1990 self.update_edit_prediction_settings(cx);
1991 self.refresh_inline_completion(false, false, window, cx);
1992 }
1993
1994 pub fn placeholder_text(&self) -> Option<&str> {
1995 self.placeholder_text.as_deref()
1996 }
1997
1998 pub fn set_placeholder_text(
1999 &mut self,
2000 placeholder_text: impl Into<Arc<str>>,
2001 cx: &mut Context<Self>,
2002 ) {
2003 let placeholder_text = Some(placeholder_text.into());
2004 if self.placeholder_text != placeholder_text {
2005 self.placeholder_text = placeholder_text;
2006 cx.notify();
2007 }
2008 }
2009
2010 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2011 self.cursor_shape = cursor_shape;
2012
2013 // Disrupt blink for immediate user feedback that the cursor shape has changed
2014 self.blink_manager.update(cx, BlinkManager::show_cursor);
2015
2016 cx.notify();
2017 }
2018
2019 pub fn set_current_line_highlight(
2020 &mut self,
2021 current_line_highlight: Option<CurrentLineHighlight>,
2022 ) {
2023 self.current_line_highlight = current_line_highlight;
2024 }
2025
2026 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2027 self.collapse_matches = collapse_matches;
2028 }
2029
2030 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2031 let buffers = self.buffer.read(cx).all_buffers();
2032 let Some(project) = self.project.as_ref() else {
2033 return;
2034 };
2035 project.update(cx, |project, cx| {
2036 for buffer in buffers {
2037 self.registered_buffers
2038 .entry(buffer.read(cx).remote_id())
2039 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2040 }
2041 })
2042 }
2043
2044 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2045 if self.collapse_matches {
2046 return range.start..range.start;
2047 }
2048 range.clone()
2049 }
2050
2051 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2052 if self.display_map.read(cx).clip_at_line_ends != clip {
2053 self.display_map
2054 .update(cx, |map, _| map.clip_at_line_ends = clip);
2055 }
2056 }
2057
2058 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2059 self.input_enabled = input_enabled;
2060 }
2061
2062 pub fn set_inline_completions_hidden_for_vim_mode(
2063 &mut self,
2064 hidden: bool,
2065 window: &mut Window,
2066 cx: &mut Context<Self>,
2067 ) {
2068 if hidden != self.inline_completions_hidden_for_vim_mode {
2069 self.inline_completions_hidden_for_vim_mode = hidden;
2070 if hidden {
2071 self.update_visible_inline_completion(window, cx);
2072 } else {
2073 self.refresh_inline_completion(true, false, window, cx);
2074 }
2075 }
2076 }
2077
2078 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2079 self.menu_inline_completions_policy = value;
2080 }
2081
2082 pub fn set_autoindent(&mut self, autoindent: bool) {
2083 if autoindent {
2084 self.autoindent_mode = Some(AutoindentMode::EachLine);
2085 } else {
2086 self.autoindent_mode = None;
2087 }
2088 }
2089
2090 pub fn read_only(&self, cx: &App) -> bool {
2091 self.read_only || self.buffer.read(cx).read_only()
2092 }
2093
2094 pub fn set_read_only(&mut self, read_only: bool) {
2095 self.read_only = read_only;
2096 }
2097
2098 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2099 self.use_autoclose = autoclose;
2100 }
2101
2102 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2103 self.use_auto_surround = auto_surround;
2104 }
2105
2106 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2107 self.auto_replace_emoji_shortcode = auto_replace;
2108 }
2109
2110 pub fn toggle_edit_predictions(
2111 &mut self,
2112 _: &ToggleEditPrediction,
2113 window: &mut Window,
2114 cx: &mut Context<Self>,
2115 ) {
2116 if self.show_inline_completions_override.is_some() {
2117 self.set_show_edit_predictions(None, window, cx);
2118 } else {
2119 let show_edit_predictions = !self.edit_predictions_enabled();
2120 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2121 }
2122 }
2123
2124 pub fn set_show_edit_predictions(
2125 &mut self,
2126 show_edit_predictions: Option<bool>,
2127 window: &mut Window,
2128 cx: &mut Context<Self>,
2129 ) {
2130 self.show_inline_completions_override = show_edit_predictions;
2131 self.update_edit_prediction_settings(cx);
2132
2133 if let Some(false) = show_edit_predictions {
2134 self.discard_inline_completion(false, cx);
2135 } else {
2136 self.refresh_inline_completion(false, true, window, cx);
2137 }
2138 }
2139
2140 fn inline_completions_disabled_in_scope(
2141 &self,
2142 buffer: &Entity<Buffer>,
2143 buffer_position: language::Anchor,
2144 cx: &App,
2145 ) -> bool {
2146 let snapshot = buffer.read(cx).snapshot();
2147 let settings = snapshot.settings_at(buffer_position, cx);
2148
2149 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2150 return false;
2151 };
2152
2153 scope.override_name().map_or(false, |scope_name| {
2154 settings
2155 .edit_predictions_disabled_in
2156 .iter()
2157 .any(|s| s == scope_name)
2158 })
2159 }
2160
2161 pub fn set_use_modal_editing(&mut self, to: bool) {
2162 self.use_modal_editing = to;
2163 }
2164
2165 pub fn use_modal_editing(&self) -> bool {
2166 self.use_modal_editing
2167 }
2168
2169 fn selections_did_change(
2170 &mut self,
2171 local: bool,
2172 old_cursor_position: &Anchor,
2173 show_completions: bool,
2174 window: &mut Window,
2175 cx: &mut Context<Self>,
2176 ) {
2177 window.invalidate_character_coordinates();
2178
2179 // Copy selections to primary selection buffer
2180 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2181 if local {
2182 let selections = self.selections.all::<usize>(cx);
2183 let buffer_handle = self.buffer.read(cx).read(cx);
2184
2185 let mut text = String::new();
2186 for (index, selection) in selections.iter().enumerate() {
2187 let text_for_selection = buffer_handle
2188 .text_for_range(selection.start..selection.end)
2189 .collect::<String>();
2190
2191 text.push_str(&text_for_selection);
2192 if index != selections.len() - 1 {
2193 text.push('\n');
2194 }
2195 }
2196
2197 if !text.is_empty() {
2198 cx.write_to_primary(ClipboardItem::new_string(text));
2199 }
2200 }
2201
2202 if self.focus_handle.is_focused(window) && self.leader_peer_id.is_none() {
2203 self.buffer.update(cx, |buffer, cx| {
2204 buffer.set_active_selections(
2205 &self.selections.disjoint_anchors(),
2206 self.selections.line_mode,
2207 self.cursor_shape,
2208 cx,
2209 )
2210 });
2211 }
2212 let display_map = self
2213 .display_map
2214 .update(cx, |display_map, cx| display_map.snapshot(cx));
2215 let buffer = &display_map.buffer_snapshot;
2216 self.add_selections_state = None;
2217 self.select_next_state = None;
2218 self.select_prev_state = None;
2219 self.select_syntax_node_history.try_clear();
2220 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2221 self.snippet_stack
2222 .invalidate(&self.selections.disjoint_anchors(), buffer);
2223 self.take_rename(false, window, cx);
2224
2225 let new_cursor_position = self.selections.newest_anchor().head();
2226
2227 self.push_to_nav_history(
2228 *old_cursor_position,
2229 Some(new_cursor_position.to_point(buffer)),
2230 false,
2231 cx,
2232 );
2233
2234 if local {
2235 let new_cursor_position = self.selections.newest_anchor().head();
2236 let mut context_menu = self.context_menu.borrow_mut();
2237 let completion_menu = match context_menu.as_ref() {
2238 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2239 _ => {
2240 *context_menu = None;
2241 None
2242 }
2243 };
2244 if let Some(buffer_id) = new_cursor_position.buffer_id {
2245 if !self.registered_buffers.contains_key(&buffer_id) {
2246 if let Some(project) = self.project.as_ref() {
2247 project.update(cx, |project, cx| {
2248 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2249 return;
2250 };
2251 self.registered_buffers.insert(
2252 buffer_id,
2253 project.register_buffer_with_language_servers(&buffer, cx),
2254 );
2255 })
2256 }
2257 }
2258 }
2259
2260 if let Some(completion_menu) = completion_menu {
2261 let cursor_position = new_cursor_position.to_offset(buffer);
2262 let (word_range, kind) =
2263 buffer.surrounding_word(completion_menu.initial_position, true);
2264 if kind == Some(CharKind::Word)
2265 && word_range.to_inclusive().contains(&cursor_position)
2266 {
2267 let mut completion_menu = completion_menu.clone();
2268 drop(context_menu);
2269
2270 let query = Self::completion_query(buffer, cursor_position);
2271 cx.spawn(async move |this, cx| {
2272 completion_menu
2273 .filter(query.as_deref(), cx.background_executor().clone())
2274 .await;
2275
2276 this.update(cx, |this, cx| {
2277 let mut context_menu = this.context_menu.borrow_mut();
2278 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2279 else {
2280 return;
2281 };
2282
2283 if menu.id > completion_menu.id {
2284 return;
2285 }
2286
2287 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2288 drop(context_menu);
2289 cx.notify();
2290 })
2291 })
2292 .detach();
2293
2294 if show_completions {
2295 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2296 }
2297 } else {
2298 drop(context_menu);
2299 self.hide_context_menu(window, cx);
2300 }
2301 } else {
2302 drop(context_menu);
2303 }
2304
2305 hide_hover(self, cx);
2306
2307 if old_cursor_position.to_display_point(&display_map).row()
2308 != new_cursor_position.to_display_point(&display_map).row()
2309 {
2310 self.available_code_actions.take();
2311 }
2312 self.refresh_code_actions(window, cx);
2313 self.refresh_document_highlights(cx);
2314 self.refresh_selected_text_highlights(window, cx);
2315 refresh_matching_bracket_highlights(self, window, cx);
2316 self.update_visible_inline_completion(window, cx);
2317 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2318 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2319 if self.git_blame_inline_enabled {
2320 self.start_inline_blame_timer(window, cx);
2321 }
2322 }
2323
2324 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2325 cx.emit(EditorEvent::SelectionsChanged { local });
2326
2327 let selections = &self.selections.disjoint;
2328 if selections.len() == 1 {
2329 cx.emit(SearchEvent::ActiveMatchChanged)
2330 }
2331 if local && self.is_singleton(cx) {
2332 let inmemory_selections = selections.iter().map(|s| s.range()).collect();
2333 self.update_restoration_data(cx, |data| {
2334 data.selections = inmemory_selections;
2335 });
2336
2337 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
2338 {
2339 if let Some(workspace_id) =
2340 self.workspace.as_ref().and_then(|workspace| workspace.1)
2341 {
2342 let snapshot = self.buffer().read(cx).snapshot(cx);
2343 let selections = selections.clone();
2344 let background_executor = cx.background_executor().clone();
2345 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2346 self.serialize_selections = cx.background_spawn(async move {
2347 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2348 let db_selections = selections
2349 .iter()
2350 .map(|selection| {
2351 (
2352 selection.start.to_offset(&snapshot),
2353 selection.end.to_offset(&snapshot),
2354 )
2355 })
2356 .collect();
2357
2358 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2359 .await
2360 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2361 .log_err();
2362 });
2363 }
2364 }
2365 }
2366
2367 cx.notify();
2368 }
2369
2370 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2371 if !self.is_singleton(cx)
2372 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2373 {
2374 return;
2375 }
2376
2377 let snapshot = self.buffer().read(cx).snapshot(cx);
2378 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2379 display_map
2380 .snapshot(cx)
2381 .folds_in_range(0..snapshot.len())
2382 .map(|fold| fold.range.deref().clone())
2383 .collect()
2384 });
2385 self.update_restoration_data(cx, |data| {
2386 data.folds = inmemory_folds;
2387 });
2388
2389 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2390 return;
2391 };
2392 let background_executor = cx.background_executor().clone();
2393 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2394 let db_folds = self.display_map.update(cx, |display_map, cx| {
2395 display_map
2396 .snapshot(cx)
2397 .folds_in_range(0..snapshot.len())
2398 .map(|fold| {
2399 (
2400 fold.range.start.to_offset(&snapshot),
2401 fold.range.end.to_offset(&snapshot),
2402 )
2403 })
2404 .collect()
2405 });
2406 self.serialize_folds = cx.background_spawn(async move {
2407 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2408 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2409 .await
2410 .with_context(|| format!("persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"))
2411 .log_err();
2412 });
2413 }
2414
2415 pub fn sync_selections(
2416 &mut self,
2417 other: Entity<Editor>,
2418 cx: &mut Context<Self>,
2419 ) -> gpui::Subscription {
2420 let other_selections = other.read(cx).selections.disjoint.to_vec();
2421 self.selections.change_with(cx, |selections| {
2422 selections.select_anchors(other_selections);
2423 });
2424
2425 let other_subscription =
2426 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2427 EditorEvent::SelectionsChanged { local: true } => {
2428 let other_selections = other.read(cx).selections.disjoint.to_vec();
2429 if other_selections.is_empty() {
2430 return;
2431 }
2432 this.selections.change_with(cx, |selections| {
2433 selections.select_anchors(other_selections);
2434 });
2435 }
2436 _ => {}
2437 });
2438
2439 let this_subscription =
2440 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2441 EditorEvent::SelectionsChanged { local: true } => {
2442 let these_selections = this.selections.disjoint.to_vec();
2443 if these_selections.is_empty() {
2444 return;
2445 }
2446 other.update(cx, |other_editor, cx| {
2447 other_editor.selections.change_with(cx, |selections| {
2448 selections.select_anchors(these_selections);
2449 })
2450 });
2451 }
2452 _ => {}
2453 });
2454
2455 Subscription::join(other_subscription, this_subscription)
2456 }
2457
2458 pub fn change_selections<R>(
2459 &mut self,
2460 autoscroll: Option<Autoscroll>,
2461 window: &mut Window,
2462 cx: &mut Context<Self>,
2463 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2464 ) -> R {
2465 self.change_selections_inner(autoscroll, true, window, cx, change)
2466 }
2467
2468 fn change_selections_inner<R>(
2469 &mut self,
2470 autoscroll: Option<Autoscroll>,
2471 request_completions: bool,
2472 window: &mut Window,
2473 cx: &mut Context<Self>,
2474 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2475 ) -> R {
2476 let old_cursor_position = self.selections.newest_anchor().head();
2477 self.push_to_selection_history();
2478
2479 let (changed, result) = self.selections.change_with(cx, change);
2480
2481 if changed {
2482 if let Some(autoscroll) = autoscroll {
2483 self.request_autoscroll(autoscroll, cx);
2484 }
2485 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2486
2487 if self.should_open_signature_help_automatically(
2488 &old_cursor_position,
2489 self.signature_help_state.backspace_pressed(),
2490 cx,
2491 ) {
2492 self.show_signature_help(&ShowSignatureHelp, window, cx);
2493 }
2494 self.signature_help_state.set_backspace_pressed(false);
2495 }
2496
2497 result
2498 }
2499
2500 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2501 where
2502 I: IntoIterator<Item = (Range<S>, T)>,
2503 S: ToOffset,
2504 T: Into<Arc<str>>,
2505 {
2506 if self.read_only(cx) {
2507 return;
2508 }
2509
2510 self.buffer
2511 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2512 }
2513
2514 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2515 where
2516 I: IntoIterator<Item = (Range<S>, T)>,
2517 S: ToOffset,
2518 T: Into<Arc<str>>,
2519 {
2520 if self.read_only(cx) {
2521 return;
2522 }
2523
2524 self.buffer.update(cx, |buffer, cx| {
2525 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2526 });
2527 }
2528
2529 pub fn edit_with_block_indent<I, S, T>(
2530 &mut self,
2531 edits: I,
2532 original_indent_columns: Vec<Option<u32>>,
2533 cx: &mut Context<Self>,
2534 ) where
2535 I: IntoIterator<Item = (Range<S>, T)>,
2536 S: ToOffset,
2537 T: Into<Arc<str>>,
2538 {
2539 if self.read_only(cx) {
2540 return;
2541 }
2542
2543 self.buffer.update(cx, |buffer, cx| {
2544 buffer.edit(
2545 edits,
2546 Some(AutoindentMode::Block {
2547 original_indent_columns,
2548 }),
2549 cx,
2550 )
2551 });
2552 }
2553
2554 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2555 self.hide_context_menu(window, cx);
2556
2557 match phase {
2558 SelectPhase::Begin {
2559 position,
2560 add,
2561 click_count,
2562 } => self.begin_selection(position, add, click_count, window, cx),
2563 SelectPhase::BeginColumnar {
2564 position,
2565 goal_column,
2566 reset,
2567 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
2568 SelectPhase::Extend {
2569 position,
2570 click_count,
2571 } => self.extend_selection(position, click_count, window, cx),
2572 SelectPhase::Update {
2573 position,
2574 goal_column,
2575 scroll_delta,
2576 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
2577 SelectPhase::End => self.end_selection(window, cx),
2578 }
2579 }
2580
2581 fn extend_selection(
2582 &mut self,
2583 position: DisplayPoint,
2584 click_count: usize,
2585 window: &mut Window,
2586 cx: &mut Context<Self>,
2587 ) {
2588 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2589 let tail = self.selections.newest::<usize>(cx).tail();
2590 self.begin_selection(position, false, click_count, window, cx);
2591
2592 let position = position.to_offset(&display_map, Bias::Left);
2593 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
2594
2595 let mut pending_selection = self
2596 .selections
2597 .pending_anchor()
2598 .expect("extend_selection not called with pending selection");
2599 if position >= tail {
2600 pending_selection.start = tail_anchor;
2601 } else {
2602 pending_selection.end = tail_anchor;
2603 pending_selection.reversed = true;
2604 }
2605
2606 let mut pending_mode = self.selections.pending_mode().unwrap();
2607 match &mut pending_mode {
2608 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
2609 _ => {}
2610 }
2611
2612 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
2613 s.set_pending(pending_selection, pending_mode)
2614 });
2615 }
2616
2617 fn begin_selection(
2618 &mut self,
2619 position: DisplayPoint,
2620 add: bool,
2621 click_count: usize,
2622 window: &mut Window,
2623 cx: &mut Context<Self>,
2624 ) {
2625 if !self.focus_handle.is_focused(window) {
2626 self.last_focused_descendant = None;
2627 window.focus(&self.focus_handle);
2628 }
2629
2630 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2631 let buffer = &display_map.buffer_snapshot;
2632 let newest_selection = self.selections.newest_anchor().clone();
2633 let position = display_map.clip_point(position, Bias::Left);
2634
2635 let start;
2636 let end;
2637 let mode;
2638 let mut auto_scroll;
2639 match click_count {
2640 1 => {
2641 start = buffer.anchor_before(position.to_point(&display_map));
2642 end = start;
2643 mode = SelectMode::Character;
2644 auto_scroll = true;
2645 }
2646 2 => {
2647 let range = movement::surrounding_word(&display_map, position);
2648 start = buffer.anchor_before(range.start.to_point(&display_map));
2649 end = buffer.anchor_before(range.end.to_point(&display_map));
2650 mode = SelectMode::Word(start..end);
2651 auto_scroll = true;
2652 }
2653 3 => {
2654 let position = display_map
2655 .clip_point(position, Bias::Left)
2656 .to_point(&display_map);
2657 let line_start = display_map.prev_line_boundary(position).0;
2658 let next_line_start = buffer.clip_point(
2659 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2660 Bias::Left,
2661 );
2662 start = buffer.anchor_before(line_start);
2663 end = buffer.anchor_before(next_line_start);
2664 mode = SelectMode::Line(start..end);
2665 auto_scroll = true;
2666 }
2667 _ => {
2668 start = buffer.anchor_before(0);
2669 end = buffer.anchor_before(buffer.len());
2670 mode = SelectMode::All;
2671 auto_scroll = false;
2672 }
2673 }
2674 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
2675
2676 let point_to_delete: Option<usize> = {
2677 let selected_points: Vec<Selection<Point>> =
2678 self.selections.disjoint_in_range(start..end, cx);
2679
2680 if !add || click_count > 1 {
2681 None
2682 } else if !selected_points.is_empty() {
2683 Some(selected_points[0].id)
2684 } else {
2685 let clicked_point_already_selected =
2686 self.selections.disjoint.iter().find(|selection| {
2687 selection.start.to_point(buffer) == start.to_point(buffer)
2688 || selection.end.to_point(buffer) == end.to_point(buffer)
2689 });
2690
2691 clicked_point_already_selected.map(|selection| selection.id)
2692 }
2693 };
2694
2695 let selections_count = self.selections.count();
2696
2697 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
2698 if let Some(point_to_delete) = point_to_delete {
2699 s.delete(point_to_delete);
2700
2701 if selections_count == 1 {
2702 s.set_pending_anchor_range(start..end, mode);
2703 }
2704 } else {
2705 if !add {
2706 s.clear_disjoint();
2707 } else if click_count > 1 {
2708 s.delete(newest_selection.id)
2709 }
2710
2711 s.set_pending_anchor_range(start..end, mode);
2712 }
2713 });
2714 }
2715
2716 fn begin_columnar_selection(
2717 &mut self,
2718 position: DisplayPoint,
2719 goal_column: u32,
2720 reset: bool,
2721 window: &mut Window,
2722 cx: &mut Context<Self>,
2723 ) {
2724 if !self.focus_handle.is_focused(window) {
2725 self.last_focused_descendant = None;
2726 window.focus(&self.focus_handle);
2727 }
2728
2729 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2730
2731 if reset {
2732 let pointer_position = display_map
2733 .buffer_snapshot
2734 .anchor_before(position.to_point(&display_map));
2735
2736 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
2737 s.clear_disjoint();
2738 s.set_pending_anchor_range(
2739 pointer_position..pointer_position,
2740 SelectMode::Character,
2741 );
2742 });
2743 }
2744
2745 let tail = self.selections.newest::<Point>(cx).tail();
2746 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
2747
2748 if !reset {
2749 self.select_columns(
2750 tail.to_display_point(&display_map),
2751 position,
2752 goal_column,
2753 &display_map,
2754 window,
2755 cx,
2756 );
2757 }
2758 }
2759
2760 fn update_selection(
2761 &mut self,
2762 position: DisplayPoint,
2763 goal_column: u32,
2764 scroll_delta: gpui::Point<f32>,
2765 window: &mut Window,
2766 cx: &mut Context<Self>,
2767 ) {
2768 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2769
2770 if let Some(tail) = self.columnar_selection_tail.as_ref() {
2771 let tail = tail.to_display_point(&display_map);
2772 self.select_columns(tail, position, goal_column, &display_map, window, cx);
2773 } else if let Some(mut pending) = self.selections.pending_anchor() {
2774 let buffer = self.buffer.read(cx).snapshot(cx);
2775 let head;
2776 let tail;
2777 let mode = self.selections.pending_mode().unwrap();
2778 match &mode {
2779 SelectMode::Character => {
2780 head = position.to_point(&display_map);
2781 tail = pending.tail().to_point(&buffer);
2782 }
2783 SelectMode::Word(original_range) => {
2784 let original_display_range = original_range.start.to_display_point(&display_map)
2785 ..original_range.end.to_display_point(&display_map);
2786 let original_buffer_range = original_display_range.start.to_point(&display_map)
2787 ..original_display_range.end.to_point(&display_map);
2788 if movement::is_inside_word(&display_map, position)
2789 || original_display_range.contains(&position)
2790 {
2791 let word_range = movement::surrounding_word(&display_map, position);
2792 if word_range.start < original_display_range.start {
2793 head = word_range.start.to_point(&display_map);
2794 } else {
2795 head = word_range.end.to_point(&display_map);
2796 }
2797 } else {
2798 head = position.to_point(&display_map);
2799 }
2800
2801 if head <= original_buffer_range.start {
2802 tail = original_buffer_range.end;
2803 } else {
2804 tail = original_buffer_range.start;
2805 }
2806 }
2807 SelectMode::Line(original_range) => {
2808 let original_range = original_range.to_point(&display_map.buffer_snapshot);
2809
2810 let position = display_map
2811 .clip_point(position, Bias::Left)
2812 .to_point(&display_map);
2813 let line_start = display_map.prev_line_boundary(position).0;
2814 let next_line_start = buffer.clip_point(
2815 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2816 Bias::Left,
2817 );
2818
2819 if line_start < original_range.start {
2820 head = line_start
2821 } else {
2822 head = next_line_start
2823 }
2824
2825 if head <= original_range.start {
2826 tail = original_range.end;
2827 } else {
2828 tail = original_range.start;
2829 }
2830 }
2831 SelectMode::All => {
2832 return;
2833 }
2834 };
2835
2836 if head < tail {
2837 pending.start = buffer.anchor_before(head);
2838 pending.end = buffer.anchor_before(tail);
2839 pending.reversed = true;
2840 } else {
2841 pending.start = buffer.anchor_before(tail);
2842 pending.end = buffer.anchor_before(head);
2843 pending.reversed = false;
2844 }
2845
2846 self.change_selections(None, window, cx, |s| {
2847 s.set_pending(pending, mode);
2848 });
2849 } else {
2850 log::error!("update_selection dispatched with no pending selection");
2851 return;
2852 }
2853
2854 self.apply_scroll_delta(scroll_delta, window, cx);
2855 cx.notify();
2856 }
2857
2858 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2859 self.columnar_selection_tail.take();
2860 if self.selections.pending_anchor().is_some() {
2861 let selections = self.selections.all::<usize>(cx);
2862 self.change_selections(None, window, cx, |s| {
2863 s.select(selections);
2864 s.clear_pending();
2865 });
2866 }
2867 }
2868
2869 fn select_columns(
2870 &mut self,
2871 tail: DisplayPoint,
2872 head: DisplayPoint,
2873 goal_column: u32,
2874 display_map: &DisplaySnapshot,
2875 window: &mut Window,
2876 cx: &mut Context<Self>,
2877 ) {
2878 let start_row = cmp::min(tail.row(), head.row());
2879 let end_row = cmp::max(tail.row(), head.row());
2880 let start_column = cmp::min(tail.column(), goal_column);
2881 let end_column = cmp::max(tail.column(), goal_column);
2882 let reversed = start_column < tail.column();
2883
2884 let selection_ranges = (start_row.0..=end_row.0)
2885 .map(DisplayRow)
2886 .filter_map(|row| {
2887 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
2888 let start = display_map
2889 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
2890 .to_point(display_map);
2891 let end = display_map
2892 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
2893 .to_point(display_map);
2894 if reversed {
2895 Some(end..start)
2896 } else {
2897 Some(start..end)
2898 }
2899 } else {
2900 None
2901 }
2902 })
2903 .collect::<Vec<_>>();
2904
2905 self.change_selections(None, window, cx, |s| {
2906 s.select_ranges(selection_ranges);
2907 });
2908 cx.notify();
2909 }
2910
2911 pub fn has_pending_nonempty_selection(&self) -> bool {
2912 let pending_nonempty_selection = match self.selections.pending_anchor() {
2913 Some(Selection { start, end, .. }) => start != end,
2914 None => false,
2915 };
2916
2917 pending_nonempty_selection
2918 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
2919 }
2920
2921 pub fn has_pending_selection(&self) -> bool {
2922 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
2923 }
2924
2925 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
2926 self.selection_mark_mode = false;
2927
2928 if self.clear_expanded_diff_hunks(cx) {
2929 cx.notify();
2930 return;
2931 }
2932 if self.dismiss_menus_and_popups(true, window, cx) {
2933 return;
2934 }
2935
2936 if self.mode == EditorMode::Full
2937 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
2938 {
2939 return;
2940 }
2941
2942 cx.propagate();
2943 }
2944
2945 pub fn dismiss_menus_and_popups(
2946 &mut self,
2947 is_user_requested: bool,
2948 window: &mut Window,
2949 cx: &mut Context<Self>,
2950 ) -> bool {
2951 if self.take_rename(false, window, cx).is_some() {
2952 return true;
2953 }
2954
2955 if hide_hover(self, cx) {
2956 return true;
2957 }
2958
2959 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
2960 return true;
2961 }
2962
2963 if self.hide_context_menu(window, cx).is_some() {
2964 return true;
2965 }
2966
2967 if self.mouse_context_menu.take().is_some() {
2968 return true;
2969 }
2970
2971 if is_user_requested && self.discard_inline_completion(true, cx) {
2972 return true;
2973 }
2974
2975 if self.snippet_stack.pop().is_some() {
2976 return true;
2977 }
2978
2979 if self.mode == EditorMode::Full && self.active_diagnostics.is_some() {
2980 self.dismiss_diagnostics(cx);
2981 return true;
2982 }
2983
2984 false
2985 }
2986
2987 fn linked_editing_ranges_for(
2988 &self,
2989 selection: Range<text::Anchor>,
2990 cx: &App,
2991 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
2992 if self.linked_edit_ranges.is_empty() {
2993 return None;
2994 }
2995 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
2996 selection.end.buffer_id.and_then(|end_buffer_id| {
2997 if selection.start.buffer_id != Some(end_buffer_id) {
2998 return None;
2999 }
3000 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3001 let snapshot = buffer.read(cx).snapshot();
3002 self.linked_edit_ranges
3003 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3004 .map(|ranges| (ranges, snapshot, buffer))
3005 })?;
3006 use text::ToOffset as TO;
3007 // find offset from the start of current range to current cursor position
3008 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3009
3010 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3011 let start_difference = start_offset - start_byte_offset;
3012 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3013 let end_difference = end_offset - start_byte_offset;
3014 // Current range has associated linked ranges.
3015 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3016 for range in linked_ranges.iter() {
3017 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3018 let end_offset = start_offset + end_difference;
3019 let start_offset = start_offset + start_difference;
3020 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3021 continue;
3022 }
3023 if self.selections.disjoint_anchor_ranges().any(|s| {
3024 if s.start.buffer_id != selection.start.buffer_id
3025 || s.end.buffer_id != selection.end.buffer_id
3026 {
3027 return false;
3028 }
3029 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3030 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3031 }) {
3032 continue;
3033 }
3034 let start = buffer_snapshot.anchor_after(start_offset);
3035 let end = buffer_snapshot.anchor_after(end_offset);
3036 linked_edits
3037 .entry(buffer.clone())
3038 .or_default()
3039 .push(start..end);
3040 }
3041 Some(linked_edits)
3042 }
3043
3044 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3045 let text: Arc<str> = text.into();
3046
3047 if self.read_only(cx) {
3048 return;
3049 }
3050
3051 self.mouse_cursor_hidden = self.hide_mouse_while_typing;
3052
3053 let selections = self.selections.all_adjusted(cx);
3054 let mut bracket_inserted = false;
3055 let mut edits = Vec::new();
3056 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3057 let mut new_selections = Vec::with_capacity(selections.len());
3058 let mut new_autoclose_regions = Vec::new();
3059 let snapshot = self.buffer.read(cx).read(cx);
3060
3061 for (selection, autoclose_region) in
3062 self.selections_with_autoclose_regions(selections, &snapshot)
3063 {
3064 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3065 // Determine if the inserted text matches the opening or closing
3066 // bracket of any of this language's bracket pairs.
3067 let mut bracket_pair = None;
3068 let mut is_bracket_pair_start = false;
3069 let mut is_bracket_pair_end = false;
3070 if !text.is_empty() {
3071 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3072 // and they are removing the character that triggered IME popup.
3073 for (pair, enabled) in scope.brackets() {
3074 if !pair.close && !pair.surround {
3075 continue;
3076 }
3077
3078 if enabled && pair.start.ends_with(text.as_ref()) {
3079 let prefix_len = pair.start.len() - text.len();
3080 let preceding_text_matches_prefix = prefix_len == 0
3081 || (selection.start.column >= (prefix_len as u32)
3082 && snapshot.contains_str_at(
3083 Point::new(
3084 selection.start.row,
3085 selection.start.column - (prefix_len as u32),
3086 ),
3087 &pair.start[..prefix_len],
3088 ));
3089 if preceding_text_matches_prefix {
3090 bracket_pair = Some(pair.clone());
3091 is_bracket_pair_start = true;
3092 break;
3093 }
3094 }
3095 if pair.end.as_str() == text.as_ref() {
3096 bracket_pair = Some(pair.clone());
3097 is_bracket_pair_end = true;
3098 break;
3099 }
3100 }
3101 }
3102
3103 if let Some(bracket_pair) = bracket_pair {
3104 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3105 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3106 let auto_surround =
3107 self.use_auto_surround && snapshot_settings.use_auto_surround;
3108 if selection.is_empty() {
3109 if is_bracket_pair_start {
3110 // If the inserted text is a suffix of an opening bracket and the
3111 // selection is preceded by the rest of the opening bracket, then
3112 // insert the closing bracket.
3113 let following_text_allows_autoclose = snapshot
3114 .chars_at(selection.start)
3115 .next()
3116 .map_or(true, |c| scope.should_autoclose_before(c));
3117
3118 let preceding_text_allows_autoclose = selection.start.column == 0
3119 || snapshot.reversed_chars_at(selection.start).next().map_or(
3120 true,
3121 |c| {
3122 bracket_pair.start != bracket_pair.end
3123 || !snapshot
3124 .char_classifier_at(selection.start)
3125 .is_word(c)
3126 },
3127 );
3128
3129 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3130 && bracket_pair.start.len() == 1
3131 {
3132 let target = bracket_pair.start.chars().next().unwrap();
3133 let current_line_count = snapshot
3134 .reversed_chars_at(selection.start)
3135 .take_while(|&c| c != '\n')
3136 .filter(|&c| c == target)
3137 .count();
3138 current_line_count % 2 == 1
3139 } else {
3140 false
3141 };
3142
3143 if autoclose
3144 && bracket_pair.close
3145 && following_text_allows_autoclose
3146 && preceding_text_allows_autoclose
3147 && !is_closing_quote
3148 {
3149 let anchor = snapshot.anchor_before(selection.end);
3150 new_selections.push((selection.map(|_| anchor), text.len()));
3151 new_autoclose_regions.push((
3152 anchor,
3153 text.len(),
3154 selection.id,
3155 bracket_pair.clone(),
3156 ));
3157 edits.push((
3158 selection.range(),
3159 format!("{}{}", text, bracket_pair.end).into(),
3160 ));
3161 bracket_inserted = true;
3162 continue;
3163 }
3164 }
3165
3166 if let Some(region) = autoclose_region {
3167 // If the selection is followed by an auto-inserted closing bracket,
3168 // then don't insert that closing bracket again; just move the selection
3169 // past the closing bracket.
3170 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3171 && text.as_ref() == region.pair.end.as_str();
3172 if should_skip {
3173 let anchor = snapshot.anchor_after(selection.end);
3174 new_selections
3175 .push((selection.map(|_| anchor), region.pair.end.len()));
3176 continue;
3177 }
3178 }
3179
3180 let always_treat_brackets_as_autoclosed = snapshot
3181 .language_settings_at(selection.start, cx)
3182 .always_treat_brackets_as_autoclosed;
3183 if always_treat_brackets_as_autoclosed
3184 && is_bracket_pair_end
3185 && snapshot.contains_str_at(selection.end, text.as_ref())
3186 {
3187 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3188 // and the inserted text is a closing bracket and the selection is followed
3189 // by the closing bracket then move the selection past the closing bracket.
3190 let anchor = snapshot.anchor_after(selection.end);
3191 new_selections.push((selection.map(|_| anchor), text.len()));
3192 continue;
3193 }
3194 }
3195 // If an opening bracket is 1 character long and is typed while
3196 // text is selected, then surround that text with the bracket pair.
3197 else if auto_surround
3198 && bracket_pair.surround
3199 && is_bracket_pair_start
3200 && bracket_pair.start.chars().count() == 1
3201 {
3202 edits.push((selection.start..selection.start, text.clone()));
3203 edits.push((
3204 selection.end..selection.end,
3205 bracket_pair.end.as_str().into(),
3206 ));
3207 bracket_inserted = true;
3208 new_selections.push((
3209 Selection {
3210 id: selection.id,
3211 start: snapshot.anchor_after(selection.start),
3212 end: snapshot.anchor_before(selection.end),
3213 reversed: selection.reversed,
3214 goal: selection.goal,
3215 },
3216 0,
3217 ));
3218 continue;
3219 }
3220 }
3221 }
3222
3223 if self.auto_replace_emoji_shortcode
3224 && selection.is_empty()
3225 && text.as_ref().ends_with(':')
3226 {
3227 if let Some(possible_emoji_short_code) =
3228 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3229 {
3230 if !possible_emoji_short_code.is_empty() {
3231 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3232 let emoji_shortcode_start = Point::new(
3233 selection.start.row,
3234 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3235 );
3236
3237 // Remove shortcode from buffer
3238 edits.push((
3239 emoji_shortcode_start..selection.start,
3240 "".to_string().into(),
3241 ));
3242 new_selections.push((
3243 Selection {
3244 id: selection.id,
3245 start: snapshot.anchor_after(emoji_shortcode_start),
3246 end: snapshot.anchor_before(selection.start),
3247 reversed: selection.reversed,
3248 goal: selection.goal,
3249 },
3250 0,
3251 ));
3252
3253 // Insert emoji
3254 let selection_start_anchor = snapshot.anchor_after(selection.start);
3255 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3256 edits.push((selection.start..selection.end, emoji.to_string().into()));
3257
3258 continue;
3259 }
3260 }
3261 }
3262 }
3263
3264 // If not handling any auto-close operation, then just replace the selected
3265 // text with the given input and move the selection to the end of the
3266 // newly inserted text.
3267 let anchor = snapshot.anchor_after(selection.end);
3268 if !self.linked_edit_ranges.is_empty() {
3269 let start_anchor = snapshot.anchor_before(selection.start);
3270
3271 let is_word_char = text.chars().next().map_or(true, |char| {
3272 let classifier = snapshot.char_classifier_at(start_anchor.to_offset(&snapshot));
3273 classifier.is_word(char)
3274 });
3275
3276 if is_word_char {
3277 if let Some(ranges) = self
3278 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3279 {
3280 for (buffer, edits) in ranges {
3281 linked_edits
3282 .entry(buffer.clone())
3283 .or_default()
3284 .extend(edits.into_iter().map(|range| (range, text.clone())));
3285 }
3286 }
3287 }
3288 }
3289
3290 new_selections.push((selection.map(|_| anchor), 0));
3291 edits.push((selection.start..selection.end, text.clone()));
3292 }
3293
3294 drop(snapshot);
3295
3296 self.transact(window, cx, |this, window, cx| {
3297 let initial_buffer_versions =
3298 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3299
3300 this.buffer.update(cx, |buffer, cx| {
3301 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3302 });
3303 for (buffer, edits) in linked_edits {
3304 buffer.update(cx, |buffer, cx| {
3305 let snapshot = buffer.snapshot();
3306 let edits = edits
3307 .into_iter()
3308 .map(|(range, text)| {
3309 use text::ToPoint as TP;
3310 let end_point = TP::to_point(&range.end, &snapshot);
3311 let start_point = TP::to_point(&range.start, &snapshot);
3312 (start_point..end_point, text)
3313 })
3314 .sorted_by_key(|(range, _)| range.start);
3315 buffer.edit(edits, None, cx);
3316 })
3317 }
3318 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3319 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3320 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3321 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3322 .zip(new_selection_deltas)
3323 .map(|(selection, delta)| Selection {
3324 id: selection.id,
3325 start: selection.start + delta,
3326 end: selection.end + delta,
3327 reversed: selection.reversed,
3328 goal: SelectionGoal::None,
3329 })
3330 .collect::<Vec<_>>();
3331
3332 let mut i = 0;
3333 for (position, delta, selection_id, pair) in new_autoclose_regions {
3334 let position = position.to_offset(&map.buffer_snapshot) + delta;
3335 let start = map.buffer_snapshot.anchor_before(position);
3336 let end = map.buffer_snapshot.anchor_after(position);
3337 while let Some(existing_state) = this.autoclose_regions.get(i) {
3338 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3339 Ordering::Less => i += 1,
3340 Ordering::Greater => break,
3341 Ordering::Equal => {
3342 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3343 Ordering::Less => i += 1,
3344 Ordering::Equal => break,
3345 Ordering::Greater => break,
3346 }
3347 }
3348 }
3349 }
3350 this.autoclose_regions.insert(
3351 i,
3352 AutocloseRegion {
3353 selection_id,
3354 range: start..end,
3355 pair,
3356 },
3357 );
3358 }
3359
3360 let had_active_inline_completion = this.has_active_inline_completion();
3361 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3362 s.select(new_selections)
3363 });
3364
3365 if !bracket_inserted {
3366 if let Some(on_type_format_task) =
3367 this.trigger_on_type_formatting(text.to_string(), window, cx)
3368 {
3369 on_type_format_task.detach_and_log_err(cx);
3370 }
3371 }
3372
3373 let editor_settings = EditorSettings::get_global(cx);
3374 if bracket_inserted
3375 && (editor_settings.auto_signature_help
3376 || editor_settings.show_signature_help_after_edits)
3377 {
3378 this.show_signature_help(&ShowSignatureHelp, window, cx);
3379 }
3380
3381 let trigger_in_words =
3382 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3383 if this.hard_wrap.is_some() {
3384 let latest: Range<Point> = this.selections.newest(cx).range();
3385 if latest.is_empty()
3386 && this
3387 .buffer()
3388 .read(cx)
3389 .snapshot(cx)
3390 .line_len(MultiBufferRow(latest.start.row))
3391 == latest.start.column
3392 {
3393 this.rewrap_impl(
3394 RewrapOptions {
3395 override_language_settings: true,
3396 preserve_existing_whitespace: true,
3397 },
3398 cx,
3399 )
3400 }
3401 }
3402 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3403 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3404 this.refresh_inline_completion(true, false, window, cx);
3405 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3406 });
3407 }
3408
3409 fn find_possible_emoji_shortcode_at_position(
3410 snapshot: &MultiBufferSnapshot,
3411 position: Point,
3412 ) -> Option<String> {
3413 let mut chars = Vec::new();
3414 let mut found_colon = false;
3415 for char in snapshot.reversed_chars_at(position).take(100) {
3416 // Found a possible emoji shortcode in the middle of the buffer
3417 if found_colon {
3418 if char.is_whitespace() {
3419 chars.reverse();
3420 return Some(chars.iter().collect());
3421 }
3422 // If the previous character is not a whitespace, we are in the middle of a word
3423 // and we only want to complete the shortcode if the word is made up of other emojis
3424 let mut containing_word = String::new();
3425 for ch in snapshot
3426 .reversed_chars_at(position)
3427 .skip(chars.len() + 1)
3428 .take(100)
3429 {
3430 if ch.is_whitespace() {
3431 break;
3432 }
3433 containing_word.push(ch);
3434 }
3435 let containing_word = containing_word.chars().rev().collect::<String>();
3436 if util::word_consists_of_emojis(containing_word.as_str()) {
3437 chars.reverse();
3438 return Some(chars.iter().collect());
3439 }
3440 }
3441
3442 if char.is_whitespace() || !char.is_ascii() {
3443 return None;
3444 }
3445 if char == ':' {
3446 found_colon = true;
3447 } else {
3448 chars.push(char);
3449 }
3450 }
3451 // Found a possible emoji shortcode at the beginning of the buffer
3452 chars.reverse();
3453 Some(chars.iter().collect())
3454 }
3455
3456 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3457 self.mouse_cursor_hidden = self.hide_mouse_while_typing;
3458 self.transact(window, cx, |this, window, cx| {
3459 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
3460 let selections = this.selections.all::<usize>(cx);
3461 let multi_buffer = this.buffer.read(cx);
3462 let buffer = multi_buffer.snapshot(cx);
3463 selections
3464 .iter()
3465 .map(|selection| {
3466 let start_point = selection.start.to_point(&buffer);
3467 let mut indent =
3468 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3469 indent.len = cmp::min(indent.len, start_point.column);
3470 let start = selection.start;
3471 let end = selection.end;
3472 let selection_is_empty = start == end;
3473 let language_scope = buffer.language_scope_at(start);
3474 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
3475 &language_scope
3476 {
3477 let insert_extra_newline =
3478 insert_extra_newline_brackets(&buffer, start..end, language)
3479 || insert_extra_newline_tree_sitter(&buffer, start..end);
3480
3481 // Comment extension on newline is allowed only for cursor selections
3482 let comment_delimiter = maybe!({
3483 if !selection_is_empty {
3484 return None;
3485 }
3486
3487 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3488 return None;
3489 }
3490
3491 let delimiters = language.line_comment_prefixes();
3492 let max_len_of_delimiter =
3493 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3494 let (snapshot, range) =
3495 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3496
3497 let mut index_of_first_non_whitespace = 0;
3498 let comment_candidate = snapshot
3499 .chars_for_range(range)
3500 .skip_while(|c| {
3501 let should_skip = c.is_whitespace();
3502 if should_skip {
3503 index_of_first_non_whitespace += 1;
3504 }
3505 should_skip
3506 })
3507 .take(max_len_of_delimiter)
3508 .collect::<String>();
3509 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3510 comment_candidate.starts_with(comment_prefix.as_ref())
3511 })?;
3512 let cursor_is_placed_after_comment_marker =
3513 index_of_first_non_whitespace + comment_prefix.len()
3514 <= start_point.column as usize;
3515 if cursor_is_placed_after_comment_marker {
3516 Some(comment_prefix.clone())
3517 } else {
3518 None
3519 }
3520 });
3521 (comment_delimiter, insert_extra_newline)
3522 } else {
3523 (None, false)
3524 };
3525
3526 let capacity_for_delimiter = comment_delimiter
3527 .as_deref()
3528 .map(str::len)
3529 .unwrap_or_default();
3530 let mut new_text =
3531 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3532 new_text.push('\n');
3533 new_text.extend(indent.chars());
3534 if let Some(delimiter) = &comment_delimiter {
3535 new_text.push_str(delimiter);
3536 }
3537 if insert_extra_newline {
3538 new_text = new_text.repeat(2);
3539 }
3540
3541 let anchor = buffer.anchor_after(end);
3542 let new_selection = selection.map(|_| anchor);
3543 (
3544 (start..end, new_text),
3545 (insert_extra_newline, new_selection),
3546 )
3547 })
3548 .unzip()
3549 };
3550
3551 this.edit_with_autoindent(edits, cx);
3552 let buffer = this.buffer.read(cx).snapshot(cx);
3553 let new_selections = selection_fixup_info
3554 .into_iter()
3555 .map(|(extra_newline_inserted, new_selection)| {
3556 let mut cursor = new_selection.end.to_point(&buffer);
3557 if extra_newline_inserted {
3558 cursor.row -= 1;
3559 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
3560 }
3561 new_selection.map(|_| cursor)
3562 })
3563 .collect();
3564
3565 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3566 s.select(new_selections)
3567 });
3568 this.refresh_inline_completion(true, false, window, cx);
3569 });
3570 }
3571
3572 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
3573 self.mouse_cursor_hidden = self.hide_mouse_while_typing;
3574
3575 let buffer = self.buffer.read(cx);
3576 let snapshot = buffer.snapshot(cx);
3577
3578 let mut edits = Vec::new();
3579 let mut rows = Vec::new();
3580
3581 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
3582 let cursor = selection.head();
3583 let row = cursor.row;
3584
3585 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
3586
3587 let newline = "\n".to_string();
3588 edits.push((start_of_line..start_of_line, newline));
3589
3590 rows.push(row + rows_inserted as u32);
3591 }
3592
3593 self.transact(window, cx, |editor, window, cx| {
3594 editor.edit(edits, cx);
3595
3596 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3597 let mut index = 0;
3598 s.move_cursors_with(|map, _, _| {
3599 let row = rows[index];
3600 index += 1;
3601
3602 let point = Point::new(row, 0);
3603 let boundary = map.next_line_boundary(point).1;
3604 let clipped = map.clip_point(boundary, Bias::Left);
3605
3606 (clipped, SelectionGoal::None)
3607 });
3608 });
3609
3610 let mut indent_edits = Vec::new();
3611 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3612 for row in rows {
3613 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3614 for (row, indent) in indents {
3615 if indent.len == 0 {
3616 continue;
3617 }
3618
3619 let text = match indent.kind {
3620 IndentKind::Space => " ".repeat(indent.len as usize),
3621 IndentKind::Tab => "\t".repeat(indent.len as usize),
3622 };
3623 let point = Point::new(row.0, 0);
3624 indent_edits.push((point..point, text));
3625 }
3626 }
3627 editor.edit(indent_edits, cx);
3628 });
3629 }
3630
3631 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
3632 self.mouse_cursor_hidden = self.hide_mouse_while_typing;
3633
3634 let buffer = self.buffer.read(cx);
3635 let snapshot = buffer.snapshot(cx);
3636
3637 let mut edits = Vec::new();
3638 let mut rows = Vec::new();
3639 let mut rows_inserted = 0;
3640
3641 for selection in self.selections.all_adjusted(cx) {
3642 let cursor = selection.head();
3643 let row = cursor.row;
3644
3645 let point = Point::new(row + 1, 0);
3646 let start_of_line = snapshot.clip_point(point, Bias::Left);
3647
3648 let newline = "\n".to_string();
3649 edits.push((start_of_line..start_of_line, newline));
3650
3651 rows_inserted += 1;
3652 rows.push(row + rows_inserted);
3653 }
3654
3655 self.transact(window, cx, |editor, window, cx| {
3656 editor.edit(edits, cx);
3657
3658 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3659 let mut index = 0;
3660 s.move_cursors_with(|map, _, _| {
3661 let row = rows[index];
3662 index += 1;
3663
3664 let point = Point::new(row, 0);
3665 let boundary = map.next_line_boundary(point).1;
3666 let clipped = map.clip_point(boundary, Bias::Left);
3667
3668 (clipped, SelectionGoal::None)
3669 });
3670 });
3671
3672 let mut indent_edits = Vec::new();
3673 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3674 for row in rows {
3675 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3676 for (row, indent) in indents {
3677 if indent.len == 0 {
3678 continue;
3679 }
3680
3681 let text = match indent.kind {
3682 IndentKind::Space => " ".repeat(indent.len as usize),
3683 IndentKind::Tab => "\t".repeat(indent.len as usize),
3684 };
3685 let point = Point::new(row.0, 0);
3686 indent_edits.push((point..point, text));
3687 }
3688 }
3689 editor.edit(indent_edits, cx);
3690 });
3691 }
3692
3693 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3694 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
3695 original_indent_columns: Vec::new(),
3696 });
3697 self.insert_with_autoindent_mode(text, autoindent, window, cx);
3698 }
3699
3700 fn insert_with_autoindent_mode(
3701 &mut self,
3702 text: &str,
3703 autoindent_mode: Option<AutoindentMode>,
3704 window: &mut Window,
3705 cx: &mut Context<Self>,
3706 ) {
3707 if self.read_only(cx) {
3708 return;
3709 }
3710
3711 let text: Arc<str> = text.into();
3712 self.transact(window, cx, |this, window, cx| {
3713 let old_selections = this.selections.all_adjusted(cx);
3714 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
3715 let anchors = {
3716 let snapshot = buffer.read(cx);
3717 old_selections
3718 .iter()
3719 .map(|s| {
3720 let anchor = snapshot.anchor_after(s.head());
3721 s.map(|_| anchor)
3722 })
3723 .collect::<Vec<_>>()
3724 };
3725 buffer.edit(
3726 old_selections
3727 .iter()
3728 .map(|s| (s.start..s.end, text.clone())),
3729 autoindent_mode,
3730 cx,
3731 );
3732 anchors
3733 });
3734
3735 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3736 s.select_anchors(selection_anchors);
3737 });
3738
3739 cx.notify();
3740 });
3741 }
3742
3743 fn trigger_completion_on_input(
3744 &mut self,
3745 text: &str,
3746 trigger_in_words: bool,
3747 window: &mut Window,
3748 cx: &mut Context<Self>,
3749 ) {
3750 let ignore_completion_provider = self
3751 .context_menu
3752 .borrow()
3753 .as_ref()
3754 .map(|menu| match menu {
3755 CodeContextMenu::Completions(completions_menu) => {
3756 completions_menu.ignore_completion_provider
3757 }
3758 CodeContextMenu::CodeActions(_) => false,
3759 })
3760 .unwrap_or(false);
3761
3762 if ignore_completion_provider {
3763 self.show_word_completions(&ShowWordCompletions, window, cx);
3764 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
3765 self.show_completions(
3766 &ShowCompletions {
3767 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
3768 },
3769 window,
3770 cx,
3771 );
3772 } else {
3773 self.hide_context_menu(window, cx);
3774 }
3775 }
3776
3777 fn is_completion_trigger(
3778 &self,
3779 text: &str,
3780 trigger_in_words: bool,
3781 cx: &mut Context<Self>,
3782 ) -> bool {
3783 let position = self.selections.newest_anchor().head();
3784 let multibuffer = self.buffer.read(cx);
3785 let Some(buffer) = position
3786 .buffer_id
3787 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
3788 else {
3789 return false;
3790 };
3791
3792 if let Some(completion_provider) = &self.completion_provider {
3793 completion_provider.is_completion_trigger(
3794 &buffer,
3795 position.text_anchor,
3796 text,
3797 trigger_in_words,
3798 cx,
3799 )
3800 } else {
3801 false
3802 }
3803 }
3804
3805 /// If any empty selections is touching the start of its innermost containing autoclose
3806 /// region, expand it to select the brackets.
3807 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3808 let selections = self.selections.all::<usize>(cx);
3809 let buffer = self.buffer.read(cx).read(cx);
3810 let new_selections = self
3811 .selections_with_autoclose_regions(selections, &buffer)
3812 .map(|(mut selection, region)| {
3813 if !selection.is_empty() {
3814 return selection;
3815 }
3816
3817 if let Some(region) = region {
3818 let mut range = region.range.to_offset(&buffer);
3819 if selection.start == range.start && range.start >= region.pair.start.len() {
3820 range.start -= region.pair.start.len();
3821 if buffer.contains_str_at(range.start, ®ion.pair.start)
3822 && buffer.contains_str_at(range.end, ®ion.pair.end)
3823 {
3824 range.end += region.pair.end.len();
3825 selection.start = range.start;
3826 selection.end = range.end;
3827
3828 return selection;
3829 }
3830 }
3831 }
3832
3833 let always_treat_brackets_as_autoclosed = buffer
3834 .language_settings_at(selection.start, cx)
3835 .always_treat_brackets_as_autoclosed;
3836
3837 if !always_treat_brackets_as_autoclosed {
3838 return selection;
3839 }
3840
3841 if let Some(scope) = buffer.language_scope_at(selection.start) {
3842 for (pair, enabled) in scope.brackets() {
3843 if !enabled || !pair.close {
3844 continue;
3845 }
3846
3847 if buffer.contains_str_at(selection.start, &pair.end) {
3848 let pair_start_len = pair.start.len();
3849 if buffer.contains_str_at(
3850 selection.start.saturating_sub(pair_start_len),
3851 &pair.start,
3852 ) {
3853 selection.start -= pair_start_len;
3854 selection.end += pair.end.len();
3855
3856 return selection;
3857 }
3858 }
3859 }
3860 }
3861
3862 selection
3863 })
3864 .collect();
3865
3866 drop(buffer);
3867 self.change_selections(None, window, cx, |selections| {
3868 selections.select(new_selections)
3869 });
3870 }
3871
3872 /// Iterate the given selections, and for each one, find the smallest surrounding
3873 /// autoclose region. This uses the ordering of the selections and the autoclose
3874 /// regions to avoid repeated comparisons.
3875 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
3876 &'a self,
3877 selections: impl IntoIterator<Item = Selection<D>>,
3878 buffer: &'a MultiBufferSnapshot,
3879 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
3880 let mut i = 0;
3881 let mut regions = self.autoclose_regions.as_slice();
3882 selections.into_iter().map(move |selection| {
3883 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
3884
3885 let mut enclosing = None;
3886 while let Some(pair_state) = regions.get(i) {
3887 if pair_state.range.end.to_offset(buffer) < range.start {
3888 regions = ®ions[i + 1..];
3889 i = 0;
3890 } else if pair_state.range.start.to_offset(buffer) > range.end {
3891 break;
3892 } else {
3893 if pair_state.selection_id == selection.id {
3894 enclosing = Some(pair_state);
3895 }
3896 i += 1;
3897 }
3898 }
3899
3900 (selection, enclosing)
3901 })
3902 }
3903
3904 /// Remove any autoclose regions that no longer contain their selection.
3905 fn invalidate_autoclose_regions(
3906 &mut self,
3907 mut selections: &[Selection<Anchor>],
3908 buffer: &MultiBufferSnapshot,
3909 ) {
3910 self.autoclose_regions.retain(|state| {
3911 let mut i = 0;
3912 while let Some(selection) = selections.get(i) {
3913 if selection.end.cmp(&state.range.start, buffer).is_lt() {
3914 selections = &selections[1..];
3915 continue;
3916 }
3917 if selection.start.cmp(&state.range.end, buffer).is_gt() {
3918 break;
3919 }
3920 if selection.id == state.selection_id {
3921 return true;
3922 } else {
3923 i += 1;
3924 }
3925 }
3926 false
3927 });
3928 }
3929
3930 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
3931 let offset = position.to_offset(buffer);
3932 let (word_range, kind) = buffer.surrounding_word(offset, true);
3933 if offset > word_range.start && kind == Some(CharKind::Word) {
3934 Some(
3935 buffer
3936 .text_for_range(word_range.start..offset)
3937 .collect::<String>(),
3938 )
3939 } else {
3940 None
3941 }
3942 }
3943
3944 pub fn toggle_inlay_hints(
3945 &mut self,
3946 _: &ToggleInlayHints,
3947 _: &mut Window,
3948 cx: &mut Context<Self>,
3949 ) {
3950 self.refresh_inlay_hints(
3951 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
3952 cx,
3953 );
3954 }
3955
3956 pub fn inlay_hints_enabled(&self) -> bool {
3957 self.inlay_hint_cache.enabled
3958 }
3959
3960 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
3961 if self.semantics_provider.is_none() || self.mode != EditorMode::Full {
3962 return;
3963 }
3964
3965 let reason_description = reason.description();
3966 let ignore_debounce = matches!(
3967 reason,
3968 InlayHintRefreshReason::SettingsChange(_)
3969 | InlayHintRefreshReason::Toggle(_)
3970 | InlayHintRefreshReason::ExcerptsRemoved(_)
3971 | InlayHintRefreshReason::ModifiersChanged(_)
3972 );
3973 let (invalidate_cache, required_languages) = match reason {
3974 InlayHintRefreshReason::ModifiersChanged(enabled) => {
3975 match self.inlay_hint_cache.modifiers_override(enabled) {
3976 Some(enabled) => {
3977 if enabled {
3978 (InvalidationStrategy::RefreshRequested, None)
3979 } else {
3980 self.splice_inlays(
3981 &self
3982 .visible_inlay_hints(cx)
3983 .iter()
3984 .map(|inlay| inlay.id)
3985 .collect::<Vec<InlayId>>(),
3986 Vec::new(),
3987 cx,
3988 );
3989 return;
3990 }
3991 }
3992 None => return,
3993 }
3994 }
3995 InlayHintRefreshReason::Toggle(enabled) => {
3996 if self.inlay_hint_cache.toggle(enabled) {
3997 if enabled {
3998 (InvalidationStrategy::RefreshRequested, None)
3999 } else {
4000 self.splice_inlays(
4001 &self
4002 .visible_inlay_hints(cx)
4003 .iter()
4004 .map(|inlay| inlay.id)
4005 .collect::<Vec<InlayId>>(),
4006 Vec::new(),
4007 cx,
4008 );
4009 return;
4010 }
4011 } else {
4012 return;
4013 }
4014 }
4015 InlayHintRefreshReason::SettingsChange(new_settings) => {
4016 match self.inlay_hint_cache.update_settings(
4017 &self.buffer,
4018 new_settings,
4019 self.visible_inlay_hints(cx),
4020 cx,
4021 ) {
4022 ControlFlow::Break(Some(InlaySplice {
4023 to_remove,
4024 to_insert,
4025 })) => {
4026 self.splice_inlays(&to_remove, to_insert, cx);
4027 return;
4028 }
4029 ControlFlow::Break(None) => return,
4030 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4031 }
4032 }
4033 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4034 if let Some(InlaySplice {
4035 to_remove,
4036 to_insert,
4037 }) = self.inlay_hint_cache.remove_excerpts(excerpts_removed)
4038 {
4039 self.splice_inlays(&to_remove, to_insert, cx);
4040 }
4041 return;
4042 }
4043 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4044 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4045 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4046 }
4047 InlayHintRefreshReason::RefreshRequested => {
4048 (InvalidationStrategy::RefreshRequested, None)
4049 }
4050 };
4051
4052 if let Some(InlaySplice {
4053 to_remove,
4054 to_insert,
4055 }) = self.inlay_hint_cache.spawn_hint_refresh(
4056 reason_description,
4057 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4058 invalidate_cache,
4059 ignore_debounce,
4060 cx,
4061 ) {
4062 self.splice_inlays(&to_remove, to_insert, cx);
4063 }
4064 }
4065
4066 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4067 self.display_map
4068 .read(cx)
4069 .current_inlays()
4070 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4071 .cloned()
4072 .collect()
4073 }
4074
4075 pub fn excerpts_for_inlay_hints_query(
4076 &self,
4077 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4078 cx: &mut Context<Editor>,
4079 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4080 let Some(project) = self.project.as_ref() else {
4081 return HashMap::default();
4082 };
4083 let project = project.read(cx);
4084 let multi_buffer = self.buffer().read(cx);
4085 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4086 let multi_buffer_visible_start = self
4087 .scroll_manager
4088 .anchor()
4089 .anchor
4090 .to_point(&multi_buffer_snapshot);
4091 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4092 multi_buffer_visible_start
4093 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4094 Bias::Left,
4095 );
4096 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4097 multi_buffer_snapshot
4098 .range_to_buffer_ranges(multi_buffer_visible_range)
4099 .into_iter()
4100 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4101 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4102 let buffer_file = project::File::from_dyn(buffer.file())?;
4103 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4104 let worktree_entry = buffer_worktree
4105 .read(cx)
4106 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4107 if worktree_entry.is_ignored {
4108 return None;
4109 }
4110
4111 let language = buffer.language()?;
4112 if let Some(restrict_to_languages) = restrict_to_languages {
4113 if !restrict_to_languages.contains(language) {
4114 return None;
4115 }
4116 }
4117 Some((
4118 excerpt_id,
4119 (
4120 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4121 buffer.version().clone(),
4122 excerpt_visible_range,
4123 ),
4124 ))
4125 })
4126 .collect()
4127 }
4128
4129 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4130 TextLayoutDetails {
4131 text_system: window.text_system().clone(),
4132 editor_style: self.style.clone().unwrap(),
4133 rem_size: window.rem_size(),
4134 scroll_anchor: self.scroll_manager.anchor(),
4135 visible_rows: self.visible_line_count(),
4136 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4137 }
4138 }
4139
4140 pub fn splice_inlays(
4141 &self,
4142 to_remove: &[InlayId],
4143 to_insert: Vec<Inlay>,
4144 cx: &mut Context<Self>,
4145 ) {
4146 self.display_map.update(cx, |display_map, cx| {
4147 display_map.splice_inlays(to_remove, to_insert, cx)
4148 });
4149 cx.notify();
4150 }
4151
4152 fn trigger_on_type_formatting(
4153 &self,
4154 input: String,
4155 window: &mut Window,
4156 cx: &mut Context<Self>,
4157 ) -> Option<Task<Result<()>>> {
4158 if input.len() != 1 {
4159 return None;
4160 }
4161
4162 let project = self.project.as_ref()?;
4163 let position = self.selections.newest_anchor().head();
4164 let (buffer, buffer_position) = self
4165 .buffer
4166 .read(cx)
4167 .text_anchor_for_position(position, cx)?;
4168
4169 let settings = language_settings::language_settings(
4170 buffer
4171 .read(cx)
4172 .language_at(buffer_position)
4173 .map(|l| l.name()),
4174 buffer.read(cx).file(),
4175 cx,
4176 );
4177 if !settings.use_on_type_format {
4178 return None;
4179 }
4180
4181 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4182 // hence we do LSP request & edit on host side only — add formats to host's history.
4183 let push_to_lsp_host_history = true;
4184 // If this is not the host, append its history with new edits.
4185 let push_to_client_history = project.read(cx).is_via_collab();
4186
4187 let on_type_formatting = project.update(cx, |project, cx| {
4188 project.on_type_format(
4189 buffer.clone(),
4190 buffer_position,
4191 input,
4192 push_to_lsp_host_history,
4193 cx,
4194 )
4195 });
4196 Some(cx.spawn_in(window, async move |editor, cx| {
4197 if let Some(transaction) = on_type_formatting.await? {
4198 if push_to_client_history {
4199 buffer
4200 .update(cx, |buffer, _| {
4201 buffer.push_transaction(transaction, Instant::now());
4202 })
4203 .ok();
4204 }
4205 editor.update(cx, |editor, cx| {
4206 editor.refresh_document_highlights(cx);
4207 })?;
4208 }
4209 Ok(())
4210 }))
4211 }
4212
4213 pub fn show_word_completions(
4214 &mut self,
4215 _: &ShowWordCompletions,
4216 window: &mut Window,
4217 cx: &mut Context<Self>,
4218 ) {
4219 self.open_completions_menu(true, None, window, cx);
4220 }
4221
4222 pub fn show_completions(
4223 &mut self,
4224 options: &ShowCompletions,
4225 window: &mut Window,
4226 cx: &mut Context<Self>,
4227 ) {
4228 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4229 }
4230
4231 fn open_completions_menu(
4232 &mut self,
4233 ignore_completion_provider: bool,
4234 trigger: Option<&str>,
4235 window: &mut Window,
4236 cx: &mut Context<Self>,
4237 ) {
4238 if self.pending_rename.is_some() {
4239 return;
4240 }
4241 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4242 return;
4243 }
4244
4245 let position = self.selections.newest_anchor().head();
4246 if position.diff_base_anchor.is_some() {
4247 return;
4248 }
4249 let (buffer, buffer_position) =
4250 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4251 output
4252 } else {
4253 return;
4254 };
4255 let buffer_snapshot = buffer.read(cx).snapshot();
4256 let show_completion_documentation = buffer_snapshot
4257 .settings_at(buffer_position, cx)
4258 .show_completion_documentation;
4259
4260 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4261
4262 let trigger_kind = match trigger {
4263 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4264 CompletionTriggerKind::TRIGGER_CHARACTER
4265 }
4266 _ => CompletionTriggerKind::INVOKED,
4267 };
4268 let completion_context = CompletionContext {
4269 trigger_character: trigger.and_then(|trigger| {
4270 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4271 Some(String::from(trigger))
4272 } else {
4273 None
4274 }
4275 }),
4276 trigger_kind,
4277 };
4278
4279 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4280 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4281 let word_to_exclude = buffer_snapshot
4282 .text_for_range(old_range.clone())
4283 .collect::<String>();
4284 (
4285 buffer_snapshot.anchor_before(old_range.start)
4286 ..buffer_snapshot.anchor_after(old_range.end),
4287 Some(word_to_exclude),
4288 )
4289 } else {
4290 (buffer_position..buffer_position, None)
4291 };
4292
4293 let completion_settings = language_settings(
4294 buffer_snapshot
4295 .language_at(buffer_position)
4296 .map(|language| language.name()),
4297 buffer_snapshot.file(),
4298 cx,
4299 )
4300 .completions;
4301
4302 // The document can be large, so stay in reasonable bounds when searching for words,
4303 // otherwise completion pop-up might be slow to appear.
4304 const WORD_LOOKUP_ROWS: u32 = 5_000;
4305 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4306 let min_word_search = buffer_snapshot.clip_point(
4307 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4308 Bias::Left,
4309 );
4310 let max_word_search = buffer_snapshot.clip_point(
4311 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4312 Bias::Right,
4313 );
4314 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4315 ..buffer_snapshot.point_to_offset(max_word_search);
4316
4317 let provider = self
4318 .completion_provider
4319 .as_ref()
4320 .filter(|_| !ignore_completion_provider);
4321 let skip_digits = query
4322 .as_ref()
4323 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4324
4325 let (mut words, provided_completions) = match provider {
4326 Some(provider) => {
4327 let completions = provider.completions(
4328 position.excerpt_id,
4329 &buffer,
4330 buffer_position,
4331 completion_context,
4332 window,
4333 cx,
4334 );
4335
4336 let words = match completion_settings.words {
4337 WordsCompletionMode::Disabled => Task::ready(HashMap::default()),
4338 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4339 .background_spawn(async move {
4340 buffer_snapshot.words_in_range(WordsQuery {
4341 fuzzy_contents: None,
4342 range: word_search_range,
4343 skip_digits,
4344 })
4345 }),
4346 };
4347
4348 (words, completions)
4349 }
4350 None => (
4351 cx.background_spawn(async move {
4352 buffer_snapshot.words_in_range(WordsQuery {
4353 fuzzy_contents: None,
4354 range: word_search_range,
4355 skip_digits,
4356 })
4357 }),
4358 Task::ready(Ok(None)),
4359 ),
4360 };
4361
4362 let sort_completions = provider
4363 .as_ref()
4364 .map_or(true, |provider| provider.sort_completions());
4365
4366 let filter_completions = provider
4367 .as_ref()
4368 .map_or(true, |provider| provider.filter_completions());
4369
4370 let id = post_inc(&mut self.next_completion_id);
4371 let task = cx.spawn_in(window, async move |editor, cx| {
4372 async move {
4373 editor.update(cx, |this, _| {
4374 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4375 })?;
4376
4377 let mut completions = Vec::new();
4378 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
4379 completions.extend(provided_completions);
4380 if completion_settings.words == WordsCompletionMode::Fallback {
4381 words = Task::ready(HashMap::default());
4382 }
4383 }
4384
4385 let mut words = words.await;
4386 if let Some(word_to_exclude) = &word_to_exclude {
4387 words.remove(word_to_exclude);
4388 }
4389 for lsp_completion in &completions {
4390 words.remove(&lsp_completion.new_text);
4391 }
4392 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
4393 old_range: old_range.clone(),
4394 new_text: word.clone(),
4395 label: CodeLabel::plain(word, None),
4396 icon_path: None,
4397 documentation: None,
4398 source: CompletionSource::BufferWord {
4399 word_range,
4400 resolved: false,
4401 },
4402 confirm: None,
4403 }));
4404
4405 let menu = if completions.is_empty() {
4406 None
4407 } else {
4408 let mut menu = CompletionsMenu::new(
4409 id,
4410 sort_completions,
4411 show_completion_documentation,
4412 ignore_completion_provider,
4413 position,
4414 buffer.clone(),
4415 completions.into(),
4416 );
4417
4418 menu.filter(
4419 if filter_completions {
4420 query.as_deref()
4421 } else {
4422 None
4423 },
4424 cx.background_executor().clone(),
4425 )
4426 .await;
4427
4428 menu.visible().then_some(menu)
4429 };
4430
4431 editor.update_in(cx, |editor, window, cx| {
4432 match editor.context_menu.borrow().as_ref() {
4433 None => {}
4434 Some(CodeContextMenu::Completions(prev_menu)) => {
4435 if prev_menu.id > id {
4436 return;
4437 }
4438 }
4439 _ => return,
4440 }
4441
4442 if editor.focus_handle.is_focused(window) && menu.is_some() {
4443 let mut menu = menu.unwrap();
4444 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
4445
4446 *editor.context_menu.borrow_mut() =
4447 Some(CodeContextMenu::Completions(menu));
4448
4449 if editor.show_edit_predictions_in_menu() {
4450 editor.update_visible_inline_completion(window, cx);
4451 } else {
4452 editor.discard_inline_completion(false, cx);
4453 }
4454
4455 cx.notify();
4456 } else if editor.completion_tasks.len() <= 1 {
4457 // If there are no more completion tasks and the last menu was
4458 // empty, we should hide it.
4459 let was_hidden = editor.hide_context_menu(window, cx).is_none();
4460 // If it was already hidden and we don't show inline
4461 // completions in the menu, we should also show the
4462 // inline-completion when available.
4463 if was_hidden && editor.show_edit_predictions_in_menu() {
4464 editor.update_visible_inline_completion(window, cx);
4465 }
4466 }
4467 })?;
4468
4469 anyhow::Ok(())
4470 }
4471 .log_err()
4472 .await
4473 });
4474
4475 self.completion_tasks.push((id, task));
4476 }
4477
4478 #[cfg(feature = "test-support")]
4479 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
4480 let menu = self.context_menu.borrow();
4481 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
4482 let completions = menu.completions.borrow();
4483 Some(completions.to_vec())
4484 } else {
4485 None
4486 }
4487 }
4488
4489 pub fn confirm_completion(
4490 &mut self,
4491 action: &ConfirmCompletion,
4492 window: &mut Window,
4493 cx: &mut Context<Self>,
4494 ) -> Option<Task<Result<()>>> {
4495 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
4496 }
4497
4498 pub fn compose_completion(
4499 &mut self,
4500 action: &ComposeCompletion,
4501 window: &mut Window,
4502 cx: &mut Context<Self>,
4503 ) -> Option<Task<Result<()>>> {
4504 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
4505 }
4506
4507 fn do_completion(
4508 &mut self,
4509 item_ix: Option<usize>,
4510 intent: CompletionIntent,
4511 window: &mut Window,
4512 cx: &mut Context<Editor>,
4513 ) -> Option<Task<Result<()>>> {
4514 use language::ToOffset as _;
4515
4516 let completions_menu =
4517 if let CodeContextMenu::Completions(menu) = self.hide_context_menu(window, cx)? {
4518 menu
4519 } else {
4520 return None;
4521 };
4522
4523 let candidate_id = {
4524 let entries = completions_menu.entries.borrow();
4525 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
4526 if self.show_edit_predictions_in_menu() {
4527 self.discard_inline_completion(true, cx);
4528 }
4529 mat.candidate_id
4530 };
4531
4532 let buffer_handle = completions_menu.buffer;
4533 let completion = completions_menu
4534 .completions
4535 .borrow()
4536 .get(candidate_id)?
4537 .clone();
4538 cx.stop_propagation();
4539
4540 let snippet;
4541 let new_text;
4542 if completion.is_snippet() {
4543 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
4544 new_text = snippet.as_ref().unwrap().text.clone();
4545 } else {
4546 snippet = None;
4547 new_text = completion.new_text.clone();
4548 };
4549 let selections = self.selections.all::<usize>(cx);
4550 let buffer = buffer_handle.read(cx);
4551 let old_range = completion.old_range.to_offset(buffer);
4552 let old_text = buffer.text_for_range(old_range.clone()).collect::<String>();
4553
4554 let newest_selection = self.selections.newest_anchor();
4555 if newest_selection.start.buffer_id != Some(buffer_handle.read(cx).remote_id()) {
4556 return None;
4557 }
4558
4559 let lookbehind = newest_selection
4560 .start
4561 .text_anchor
4562 .to_offset(buffer)
4563 .saturating_sub(old_range.start);
4564 let lookahead = old_range
4565 .end
4566 .saturating_sub(newest_selection.end.text_anchor.to_offset(buffer));
4567 let mut common_prefix_len = old_text
4568 .bytes()
4569 .zip(new_text.bytes())
4570 .take_while(|(a, b)| a == b)
4571 .count();
4572
4573 let snapshot = self.buffer.read(cx).snapshot(cx);
4574 let mut range_to_replace: Option<Range<isize>> = None;
4575 let mut ranges = Vec::new();
4576 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4577 for selection in &selections {
4578 if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) {
4579 let start = selection.start.saturating_sub(lookbehind);
4580 let end = selection.end + lookahead;
4581 if selection.id == newest_selection.id {
4582 range_to_replace = Some(
4583 ((start + common_prefix_len) as isize - selection.start as isize)
4584 ..(end as isize - selection.start as isize),
4585 );
4586 }
4587 ranges.push(start + common_prefix_len..end);
4588 } else {
4589 common_prefix_len = 0;
4590 ranges.clear();
4591 ranges.extend(selections.iter().map(|s| {
4592 if s.id == newest_selection.id {
4593 range_to_replace = Some(
4594 old_range.start.to_offset_utf16(&snapshot).0 as isize
4595 - selection.start as isize
4596 ..old_range.end.to_offset_utf16(&snapshot).0 as isize
4597 - selection.start as isize,
4598 );
4599 old_range.clone()
4600 } else {
4601 s.start..s.end
4602 }
4603 }));
4604 break;
4605 }
4606 if !self.linked_edit_ranges.is_empty() {
4607 let start_anchor = snapshot.anchor_before(selection.head());
4608 let end_anchor = snapshot.anchor_after(selection.tail());
4609 if let Some(ranges) = self
4610 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
4611 {
4612 for (buffer, edits) in ranges {
4613 linked_edits.entry(buffer.clone()).or_default().extend(
4614 edits
4615 .into_iter()
4616 .map(|range| (range, new_text[common_prefix_len..].to_owned())),
4617 );
4618 }
4619 }
4620 }
4621 }
4622 let text = &new_text[common_prefix_len..];
4623
4624 cx.emit(EditorEvent::InputHandled {
4625 utf16_range_to_replace: range_to_replace,
4626 text: text.into(),
4627 });
4628
4629 self.transact(window, cx, |this, window, cx| {
4630 if let Some(mut snippet) = snippet {
4631 snippet.text = text.to_string();
4632 for tabstop in snippet
4633 .tabstops
4634 .iter_mut()
4635 .flat_map(|tabstop| tabstop.ranges.iter_mut())
4636 {
4637 tabstop.start -= common_prefix_len as isize;
4638 tabstop.end -= common_prefix_len as isize;
4639 }
4640
4641 this.insert_snippet(&ranges, snippet, window, cx).log_err();
4642 } else {
4643 this.buffer.update(cx, |buffer, cx| {
4644 let edits = ranges.iter().map(|range| (range.clone(), text));
4645 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4646 });
4647 }
4648 for (buffer, edits) in linked_edits {
4649 buffer.update(cx, |buffer, cx| {
4650 let snapshot = buffer.snapshot();
4651 let edits = edits
4652 .into_iter()
4653 .map(|(range, text)| {
4654 use text::ToPoint as TP;
4655 let end_point = TP::to_point(&range.end, &snapshot);
4656 let start_point = TP::to_point(&range.start, &snapshot);
4657 (start_point..end_point, text)
4658 })
4659 .sorted_by_key(|(range, _)| range.start);
4660 buffer.edit(edits, None, cx);
4661 })
4662 }
4663
4664 this.refresh_inline_completion(true, false, window, cx);
4665 });
4666
4667 let show_new_completions_on_confirm = completion
4668 .confirm
4669 .as_ref()
4670 .map_or(false, |confirm| confirm(intent, window, cx));
4671 if show_new_completions_on_confirm {
4672 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
4673 }
4674
4675 let provider = self.completion_provider.as_ref()?;
4676 drop(completion);
4677 let apply_edits = provider.apply_additional_edits_for_completion(
4678 buffer_handle,
4679 completions_menu.completions.clone(),
4680 candidate_id,
4681 true,
4682 cx,
4683 );
4684
4685 let editor_settings = EditorSettings::get_global(cx);
4686 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
4687 // After the code completion is finished, users often want to know what signatures are needed.
4688 // so we should automatically call signature_help
4689 self.show_signature_help(&ShowSignatureHelp, window, cx);
4690 }
4691
4692 Some(cx.foreground_executor().spawn(async move {
4693 apply_edits.await?;
4694 Ok(())
4695 }))
4696 }
4697
4698 pub fn toggle_code_actions(
4699 &mut self,
4700 action: &ToggleCodeActions,
4701 window: &mut Window,
4702 cx: &mut Context<Self>,
4703 ) {
4704 let mut context_menu = self.context_menu.borrow_mut();
4705 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
4706 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
4707 // Toggle if we're selecting the same one
4708 *context_menu = None;
4709 cx.notify();
4710 return;
4711 } else {
4712 // Otherwise, clear it and start a new one
4713 *context_menu = None;
4714 cx.notify();
4715 }
4716 }
4717 drop(context_menu);
4718 let snapshot = self.snapshot(window, cx);
4719 let deployed_from_indicator = action.deployed_from_indicator;
4720 let mut task = self.code_actions_task.take();
4721 let action = action.clone();
4722 cx.spawn_in(window, async move |editor, cx| {
4723 while let Some(prev_task) = task {
4724 prev_task.await.log_err();
4725 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
4726 }
4727
4728 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
4729 if editor.focus_handle.is_focused(window) {
4730 let multibuffer_point = action
4731 .deployed_from_indicator
4732 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
4733 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
4734 let (buffer, buffer_row) = snapshot
4735 .buffer_snapshot
4736 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
4737 .and_then(|(buffer_snapshot, range)| {
4738 editor
4739 .buffer
4740 .read(cx)
4741 .buffer(buffer_snapshot.remote_id())
4742 .map(|buffer| (buffer, range.start.row))
4743 })?;
4744 let (_, code_actions) = editor
4745 .available_code_actions
4746 .clone()
4747 .and_then(|(location, code_actions)| {
4748 let snapshot = location.buffer.read(cx).snapshot();
4749 let point_range = location.range.to_point(&snapshot);
4750 let point_range = point_range.start.row..=point_range.end.row;
4751 if point_range.contains(&buffer_row) {
4752 Some((location, code_actions))
4753 } else {
4754 None
4755 }
4756 })
4757 .unzip();
4758 let buffer_id = buffer.read(cx).remote_id();
4759 let tasks = editor
4760 .tasks
4761 .get(&(buffer_id, buffer_row))
4762 .map(|t| Arc::new(t.to_owned()));
4763 if tasks.is_none() && code_actions.is_none() {
4764 return None;
4765 }
4766
4767 editor.completion_tasks.clear();
4768 editor.discard_inline_completion(false, cx);
4769 let task_context =
4770 tasks
4771 .as_ref()
4772 .zip(editor.project.clone())
4773 .map(|(tasks, project)| {
4774 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
4775 });
4776
4777 Some(cx.spawn_in(window, async move |editor, cx| {
4778 let task_context = match task_context {
4779 Some(task_context) => task_context.await,
4780 None => None,
4781 };
4782 let resolved_tasks =
4783 tasks.zip(task_context).map(|(tasks, task_context)| {
4784 Rc::new(ResolvedTasks {
4785 templates: tasks.resolve(&task_context).collect(),
4786 position: snapshot.buffer_snapshot.anchor_before(Point::new(
4787 multibuffer_point.row,
4788 tasks.column,
4789 )),
4790 })
4791 });
4792 let spawn_straight_away = resolved_tasks
4793 .as_ref()
4794 .map_or(false, |tasks| tasks.templates.len() == 1)
4795 && code_actions
4796 .as_ref()
4797 .map_or(true, |actions| actions.is_empty());
4798 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
4799 *editor.context_menu.borrow_mut() =
4800 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
4801 buffer,
4802 actions: CodeActionContents {
4803 tasks: resolved_tasks,
4804 actions: code_actions,
4805 },
4806 selected_item: Default::default(),
4807 scroll_handle: UniformListScrollHandle::default(),
4808 deployed_from_indicator,
4809 }));
4810 if spawn_straight_away {
4811 if let Some(task) = editor.confirm_code_action(
4812 &ConfirmCodeAction { item_ix: Some(0) },
4813 window,
4814 cx,
4815 ) {
4816 cx.notify();
4817 return task;
4818 }
4819 }
4820 cx.notify();
4821 Task::ready(Ok(()))
4822 }) {
4823 task.await
4824 } else {
4825 Ok(())
4826 }
4827 }))
4828 } else {
4829 Some(Task::ready(Ok(())))
4830 }
4831 })?;
4832 if let Some(task) = spawned_test_task {
4833 task.await?;
4834 }
4835
4836 Ok::<_, anyhow::Error>(())
4837 })
4838 .detach_and_log_err(cx);
4839 }
4840
4841 pub fn confirm_code_action(
4842 &mut self,
4843 action: &ConfirmCodeAction,
4844 window: &mut Window,
4845 cx: &mut Context<Self>,
4846 ) -> Option<Task<Result<()>>> {
4847 let actions_menu =
4848 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
4849 menu
4850 } else {
4851 return None;
4852 };
4853 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
4854 let action = actions_menu.actions.get(action_ix)?;
4855 let title = action.label();
4856 let buffer = actions_menu.buffer;
4857 let workspace = self.workspace()?;
4858
4859 match action {
4860 CodeActionsItem::Task(task_source_kind, resolved_task) => {
4861 workspace.update(cx, |workspace, cx| {
4862 workspace::tasks::schedule_resolved_task(
4863 workspace,
4864 task_source_kind,
4865 resolved_task,
4866 false,
4867 cx,
4868 );
4869
4870 Some(Task::ready(Ok(())))
4871 })
4872 }
4873 CodeActionsItem::CodeAction {
4874 excerpt_id,
4875 action,
4876 provider,
4877 } => {
4878 let apply_code_action =
4879 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
4880 let workspace = workspace.downgrade();
4881 Some(cx.spawn_in(window, async move |editor, cx| {
4882 let project_transaction = apply_code_action.await?;
4883 Self::open_project_transaction(
4884 &editor,
4885 workspace,
4886 project_transaction,
4887 title,
4888 cx,
4889 )
4890 .await
4891 }))
4892 }
4893 }
4894 }
4895
4896 pub async fn open_project_transaction(
4897 this: &WeakEntity<Editor>,
4898 workspace: WeakEntity<Workspace>,
4899 transaction: ProjectTransaction,
4900 title: String,
4901 cx: &mut AsyncWindowContext,
4902 ) -> Result<()> {
4903 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
4904 cx.update(|_, cx| {
4905 entries.sort_unstable_by_key(|(buffer, _)| {
4906 buffer.read(cx).file().map(|f| f.path().clone())
4907 });
4908 })?;
4909
4910 // If the project transaction's edits are all contained within this editor, then
4911 // avoid opening a new editor to display them.
4912
4913 if let Some((buffer, transaction)) = entries.first() {
4914 if entries.len() == 1 {
4915 let excerpt = this.update(cx, |editor, cx| {
4916 editor
4917 .buffer()
4918 .read(cx)
4919 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
4920 })?;
4921 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
4922 if excerpted_buffer == *buffer {
4923 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
4924 let excerpt_range = excerpt_range.to_offset(buffer);
4925 buffer
4926 .edited_ranges_for_transaction::<usize>(transaction)
4927 .all(|range| {
4928 excerpt_range.start <= range.start
4929 && excerpt_range.end >= range.end
4930 })
4931 })?;
4932
4933 if all_edits_within_excerpt {
4934 return Ok(());
4935 }
4936 }
4937 }
4938 }
4939 } else {
4940 return Ok(());
4941 }
4942
4943 let mut ranges_to_highlight = Vec::new();
4944 let excerpt_buffer = cx.new(|cx| {
4945 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
4946 for (buffer_handle, transaction) in &entries {
4947 let buffer = buffer_handle.read(cx);
4948 ranges_to_highlight.extend(
4949 multibuffer.push_excerpts_with_context_lines(
4950 buffer_handle.clone(),
4951 buffer
4952 .edited_ranges_for_transaction::<usize>(transaction)
4953 .collect(),
4954 DEFAULT_MULTIBUFFER_CONTEXT,
4955 cx,
4956 ),
4957 );
4958 }
4959 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
4960 multibuffer
4961 })?;
4962
4963 workspace.update_in(cx, |workspace, window, cx| {
4964 let project = workspace.project().clone();
4965 let editor =
4966 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
4967 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
4968 editor.update(cx, |editor, cx| {
4969 editor.highlight_background::<Self>(
4970 &ranges_to_highlight,
4971 |theme| theme.editor_highlighted_line_background,
4972 cx,
4973 );
4974 });
4975 })?;
4976
4977 Ok(())
4978 }
4979
4980 pub fn clear_code_action_providers(&mut self) {
4981 self.code_action_providers.clear();
4982 self.available_code_actions.take();
4983 }
4984
4985 pub fn add_code_action_provider(
4986 &mut self,
4987 provider: Rc<dyn CodeActionProvider>,
4988 window: &mut Window,
4989 cx: &mut Context<Self>,
4990 ) {
4991 if self
4992 .code_action_providers
4993 .iter()
4994 .any(|existing_provider| existing_provider.id() == provider.id())
4995 {
4996 return;
4997 }
4998
4999 self.code_action_providers.push(provider);
5000 self.refresh_code_actions(window, cx);
5001 }
5002
5003 pub fn remove_code_action_provider(
5004 &mut self,
5005 id: Arc<str>,
5006 window: &mut Window,
5007 cx: &mut Context<Self>,
5008 ) {
5009 self.code_action_providers
5010 .retain(|provider| provider.id() != id);
5011 self.refresh_code_actions(window, cx);
5012 }
5013
5014 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5015 let buffer = self.buffer.read(cx);
5016 let newest_selection = self.selections.newest_anchor().clone();
5017 if newest_selection.head().diff_base_anchor.is_some() {
5018 return None;
5019 }
5020 let (start_buffer, start) = buffer.text_anchor_for_position(newest_selection.start, cx)?;
5021 let (end_buffer, end) = buffer.text_anchor_for_position(newest_selection.end, cx)?;
5022 if start_buffer != end_buffer {
5023 return None;
5024 }
5025
5026 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5027 cx.background_executor()
5028 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5029 .await;
5030
5031 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5032 let providers = this.code_action_providers.clone();
5033 let tasks = this
5034 .code_action_providers
5035 .iter()
5036 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5037 .collect::<Vec<_>>();
5038 (providers, tasks)
5039 })?;
5040
5041 let mut actions = Vec::new();
5042 for (provider, provider_actions) in
5043 providers.into_iter().zip(future::join_all(tasks).await)
5044 {
5045 if let Some(provider_actions) = provider_actions.log_err() {
5046 actions.extend(provider_actions.into_iter().map(|action| {
5047 AvailableCodeAction {
5048 excerpt_id: newest_selection.start.excerpt_id,
5049 action,
5050 provider: provider.clone(),
5051 }
5052 }));
5053 }
5054 }
5055
5056 this.update(cx, |this, cx| {
5057 this.available_code_actions = if actions.is_empty() {
5058 None
5059 } else {
5060 Some((
5061 Location {
5062 buffer: start_buffer,
5063 range: start..end,
5064 },
5065 actions.into(),
5066 ))
5067 };
5068 cx.notify();
5069 })
5070 }));
5071 None
5072 }
5073
5074 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5075 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5076 self.show_git_blame_inline = false;
5077
5078 self.show_git_blame_inline_delay_task =
5079 Some(cx.spawn_in(window, async move |this, cx| {
5080 cx.background_executor().timer(delay).await;
5081
5082 this.update(cx, |this, cx| {
5083 this.show_git_blame_inline = true;
5084 cx.notify();
5085 })
5086 .log_err();
5087 }));
5088 }
5089 }
5090
5091 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5092 if self.pending_rename.is_some() {
5093 return None;
5094 }
5095
5096 let provider = self.semantics_provider.clone()?;
5097 let buffer = self.buffer.read(cx);
5098 let newest_selection = self.selections.newest_anchor().clone();
5099 let cursor_position = newest_selection.head();
5100 let (cursor_buffer, cursor_buffer_position) =
5101 buffer.text_anchor_for_position(cursor_position, cx)?;
5102 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5103 if cursor_buffer != tail_buffer {
5104 return None;
5105 }
5106 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5107 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5108 cx.background_executor()
5109 .timer(Duration::from_millis(debounce))
5110 .await;
5111
5112 let highlights = if let Some(highlights) = cx
5113 .update(|cx| {
5114 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5115 })
5116 .ok()
5117 .flatten()
5118 {
5119 highlights.await.log_err()
5120 } else {
5121 None
5122 };
5123
5124 if let Some(highlights) = highlights {
5125 this.update(cx, |this, cx| {
5126 if this.pending_rename.is_some() {
5127 return;
5128 }
5129
5130 let buffer_id = cursor_position.buffer_id;
5131 let buffer = this.buffer.read(cx);
5132 if !buffer
5133 .text_anchor_for_position(cursor_position, cx)
5134 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5135 {
5136 return;
5137 }
5138
5139 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5140 let mut write_ranges = Vec::new();
5141 let mut read_ranges = Vec::new();
5142 for highlight in highlights {
5143 for (excerpt_id, excerpt_range) in
5144 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5145 {
5146 let start = highlight
5147 .range
5148 .start
5149 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5150 let end = highlight
5151 .range
5152 .end
5153 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5154 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5155 continue;
5156 }
5157
5158 let range = Anchor {
5159 buffer_id,
5160 excerpt_id,
5161 text_anchor: start,
5162 diff_base_anchor: None,
5163 }..Anchor {
5164 buffer_id,
5165 excerpt_id,
5166 text_anchor: end,
5167 diff_base_anchor: None,
5168 };
5169 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5170 write_ranges.push(range);
5171 } else {
5172 read_ranges.push(range);
5173 }
5174 }
5175 }
5176
5177 this.highlight_background::<DocumentHighlightRead>(
5178 &read_ranges,
5179 |theme| theme.editor_document_highlight_read_background,
5180 cx,
5181 );
5182 this.highlight_background::<DocumentHighlightWrite>(
5183 &write_ranges,
5184 |theme| theme.editor_document_highlight_write_background,
5185 cx,
5186 );
5187 cx.notify();
5188 })
5189 .log_err();
5190 }
5191 }));
5192 None
5193 }
5194
5195 pub fn refresh_selected_text_highlights(
5196 &mut self,
5197 window: &mut Window,
5198 cx: &mut Context<Editor>,
5199 ) {
5200 if matches!(self.mode, EditorMode::SingleLine { .. }) {
5201 return;
5202 }
5203 self.selection_highlight_task.take();
5204 if !EditorSettings::get_global(cx).selection_highlight {
5205 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5206 return;
5207 }
5208 if self.selections.count() != 1 || self.selections.line_mode {
5209 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5210 return;
5211 }
5212 let selection = self.selections.newest::<Point>(cx);
5213 if selection.is_empty() || selection.start.row != selection.end.row {
5214 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5215 return;
5216 }
5217 let debounce = EditorSettings::get_global(cx).selection_highlight_debounce;
5218 self.selection_highlight_task = Some(cx.spawn_in(window, async move |editor, cx| {
5219 cx.background_executor()
5220 .timer(Duration::from_millis(debounce))
5221 .await;
5222 let Some(Some(matches_task)) = editor
5223 .update_in(cx, |editor, _, cx| {
5224 if editor.selections.count() != 1 || editor.selections.line_mode {
5225 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5226 return None;
5227 }
5228 let selection = editor.selections.newest::<Point>(cx);
5229 if selection.is_empty() || selection.start.row != selection.end.row {
5230 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5231 return None;
5232 }
5233 let buffer = editor.buffer().read(cx).snapshot(cx);
5234 let query = buffer.text_for_range(selection.range()).collect::<String>();
5235 if query.trim().is_empty() {
5236 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5237 return None;
5238 }
5239 Some(cx.background_spawn(async move {
5240 let mut ranges = Vec::new();
5241 let selection_anchors = selection.range().to_anchors(&buffer);
5242 for range in [buffer.anchor_before(0)..buffer.anchor_after(buffer.len())] {
5243 for (search_buffer, search_range, excerpt_id) in
5244 buffer.range_to_buffer_ranges(range)
5245 {
5246 ranges.extend(
5247 project::search::SearchQuery::text(
5248 query.clone(),
5249 false,
5250 false,
5251 false,
5252 Default::default(),
5253 Default::default(),
5254 None,
5255 )
5256 .unwrap()
5257 .search(search_buffer, Some(search_range.clone()))
5258 .await
5259 .into_iter()
5260 .filter_map(
5261 |match_range| {
5262 let start = search_buffer.anchor_after(
5263 search_range.start + match_range.start,
5264 );
5265 let end = search_buffer.anchor_before(
5266 search_range.start + match_range.end,
5267 );
5268 let range = Anchor::range_in_buffer(
5269 excerpt_id,
5270 search_buffer.remote_id(),
5271 start..end,
5272 );
5273 (range != selection_anchors).then_some(range)
5274 },
5275 ),
5276 );
5277 }
5278 }
5279 ranges
5280 }))
5281 })
5282 .log_err()
5283 else {
5284 return;
5285 };
5286 let matches = matches_task.await;
5287 editor
5288 .update_in(cx, |editor, _, cx| {
5289 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5290 if !matches.is_empty() {
5291 editor.highlight_background::<SelectedTextHighlight>(
5292 &matches,
5293 |theme| theme.editor_document_highlight_bracket_background,
5294 cx,
5295 )
5296 }
5297 })
5298 .log_err();
5299 }));
5300 }
5301
5302 pub fn refresh_inline_completion(
5303 &mut self,
5304 debounce: bool,
5305 user_requested: bool,
5306 window: &mut Window,
5307 cx: &mut Context<Self>,
5308 ) -> Option<()> {
5309 let provider = self.edit_prediction_provider()?;
5310 let cursor = self.selections.newest_anchor().head();
5311 let (buffer, cursor_buffer_position) =
5312 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5313
5314 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
5315 self.discard_inline_completion(false, cx);
5316 return None;
5317 }
5318
5319 if !user_requested
5320 && (!self.should_show_edit_predictions()
5321 || !self.is_focused(window)
5322 || buffer.read(cx).is_empty())
5323 {
5324 self.discard_inline_completion(false, cx);
5325 return None;
5326 }
5327
5328 self.update_visible_inline_completion(window, cx);
5329 provider.refresh(
5330 self.project.clone(),
5331 buffer,
5332 cursor_buffer_position,
5333 debounce,
5334 cx,
5335 );
5336 Some(())
5337 }
5338
5339 fn show_edit_predictions_in_menu(&self) -> bool {
5340 match self.edit_prediction_settings {
5341 EditPredictionSettings::Disabled => false,
5342 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
5343 }
5344 }
5345
5346 pub fn edit_predictions_enabled(&self) -> bool {
5347 match self.edit_prediction_settings {
5348 EditPredictionSettings::Disabled => false,
5349 EditPredictionSettings::Enabled { .. } => true,
5350 }
5351 }
5352
5353 fn edit_prediction_requires_modifier(&self) -> bool {
5354 match self.edit_prediction_settings {
5355 EditPredictionSettings::Disabled => false,
5356 EditPredictionSettings::Enabled {
5357 preview_requires_modifier,
5358 ..
5359 } => preview_requires_modifier,
5360 }
5361 }
5362
5363 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
5364 if self.edit_prediction_provider.is_none() {
5365 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5366 } else {
5367 let selection = self.selections.newest_anchor();
5368 let cursor = selection.head();
5369
5370 if let Some((buffer, cursor_buffer_position)) =
5371 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5372 {
5373 self.edit_prediction_settings =
5374 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5375 }
5376 }
5377 }
5378
5379 fn edit_prediction_settings_at_position(
5380 &self,
5381 buffer: &Entity<Buffer>,
5382 buffer_position: language::Anchor,
5383 cx: &App,
5384 ) -> EditPredictionSettings {
5385 if self.mode != EditorMode::Full
5386 || !self.show_inline_completions_override.unwrap_or(true)
5387 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
5388 {
5389 return EditPredictionSettings::Disabled;
5390 }
5391
5392 let buffer = buffer.read(cx);
5393
5394 let file = buffer.file();
5395
5396 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
5397 return EditPredictionSettings::Disabled;
5398 };
5399
5400 let by_provider = matches!(
5401 self.menu_inline_completions_policy,
5402 MenuInlineCompletionsPolicy::ByProvider
5403 );
5404
5405 let show_in_menu = by_provider
5406 && self
5407 .edit_prediction_provider
5408 .as_ref()
5409 .map_or(false, |provider| {
5410 provider.provider.show_completions_in_menu()
5411 });
5412
5413 let preview_requires_modifier =
5414 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
5415
5416 EditPredictionSettings::Enabled {
5417 show_in_menu,
5418 preview_requires_modifier,
5419 }
5420 }
5421
5422 fn should_show_edit_predictions(&self) -> bool {
5423 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
5424 }
5425
5426 pub fn edit_prediction_preview_is_active(&self) -> bool {
5427 matches!(
5428 self.edit_prediction_preview,
5429 EditPredictionPreview::Active { .. }
5430 )
5431 }
5432
5433 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
5434 let cursor = self.selections.newest_anchor().head();
5435 if let Some((buffer, cursor_position)) =
5436 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5437 {
5438 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
5439 } else {
5440 false
5441 }
5442 }
5443
5444 fn edit_predictions_enabled_in_buffer(
5445 &self,
5446 buffer: &Entity<Buffer>,
5447 buffer_position: language::Anchor,
5448 cx: &App,
5449 ) -> bool {
5450 maybe!({
5451 if self.read_only(cx) {
5452 return Some(false);
5453 }
5454 let provider = self.edit_prediction_provider()?;
5455 if !provider.is_enabled(&buffer, buffer_position, cx) {
5456 return Some(false);
5457 }
5458 let buffer = buffer.read(cx);
5459 let Some(file) = buffer.file() else {
5460 return Some(true);
5461 };
5462 let settings = all_language_settings(Some(file), cx);
5463 Some(settings.edit_predictions_enabled_for_file(file, cx))
5464 })
5465 .unwrap_or(false)
5466 }
5467
5468 fn cycle_inline_completion(
5469 &mut self,
5470 direction: Direction,
5471 window: &mut Window,
5472 cx: &mut Context<Self>,
5473 ) -> Option<()> {
5474 let provider = self.edit_prediction_provider()?;
5475 let cursor = self.selections.newest_anchor().head();
5476 let (buffer, cursor_buffer_position) =
5477 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5478 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
5479 return None;
5480 }
5481
5482 provider.cycle(buffer, cursor_buffer_position, direction, cx);
5483 self.update_visible_inline_completion(window, cx);
5484
5485 Some(())
5486 }
5487
5488 pub fn show_inline_completion(
5489 &mut self,
5490 _: &ShowEditPrediction,
5491 window: &mut Window,
5492 cx: &mut Context<Self>,
5493 ) {
5494 if !self.has_active_inline_completion() {
5495 self.refresh_inline_completion(false, true, window, cx);
5496 return;
5497 }
5498
5499 self.update_visible_inline_completion(window, cx);
5500 }
5501
5502 pub fn display_cursor_names(
5503 &mut self,
5504 _: &DisplayCursorNames,
5505 window: &mut Window,
5506 cx: &mut Context<Self>,
5507 ) {
5508 self.show_cursor_names(window, cx);
5509 }
5510
5511 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5512 self.show_cursor_names = true;
5513 cx.notify();
5514 cx.spawn_in(window, async move |this, cx| {
5515 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
5516 this.update(cx, |this, cx| {
5517 this.show_cursor_names = false;
5518 cx.notify()
5519 })
5520 .ok()
5521 })
5522 .detach();
5523 }
5524
5525 pub fn next_edit_prediction(
5526 &mut self,
5527 _: &NextEditPrediction,
5528 window: &mut Window,
5529 cx: &mut Context<Self>,
5530 ) {
5531 if self.has_active_inline_completion() {
5532 self.cycle_inline_completion(Direction::Next, window, cx);
5533 } else {
5534 let is_copilot_disabled = self
5535 .refresh_inline_completion(false, true, window, cx)
5536 .is_none();
5537 if is_copilot_disabled {
5538 cx.propagate();
5539 }
5540 }
5541 }
5542
5543 pub fn previous_edit_prediction(
5544 &mut self,
5545 _: &PreviousEditPrediction,
5546 window: &mut Window,
5547 cx: &mut Context<Self>,
5548 ) {
5549 if self.has_active_inline_completion() {
5550 self.cycle_inline_completion(Direction::Prev, window, cx);
5551 } else {
5552 let is_copilot_disabled = self
5553 .refresh_inline_completion(false, true, window, cx)
5554 .is_none();
5555 if is_copilot_disabled {
5556 cx.propagate();
5557 }
5558 }
5559 }
5560
5561 pub fn accept_edit_prediction(
5562 &mut self,
5563 _: &AcceptEditPrediction,
5564 window: &mut Window,
5565 cx: &mut Context<Self>,
5566 ) {
5567 if self.show_edit_predictions_in_menu() {
5568 self.hide_context_menu(window, cx);
5569 }
5570
5571 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5572 return;
5573 };
5574
5575 self.report_inline_completion_event(
5576 active_inline_completion.completion_id.clone(),
5577 true,
5578 cx,
5579 );
5580
5581 match &active_inline_completion.completion {
5582 InlineCompletion::Move { target, .. } => {
5583 let target = *target;
5584
5585 if let Some(position_map) = &self.last_position_map {
5586 if position_map
5587 .visible_row_range
5588 .contains(&target.to_display_point(&position_map.snapshot).row())
5589 || !self.edit_prediction_requires_modifier()
5590 {
5591 self.unfold_ranges(&[target..target], true, false, cx);
5592 // Note that this is also done in vim's handler of the Tab action.
5593 self.change_selections(
5594 Some(Autoscroll::newest()),
5595 window,
5596 cx,
5597 |selections| {
5598 selections.select_anchor_ranges([target..target]);
5599 },
5600 );
5601 self.clear_row_highlights::<EditPredictionPreview>();
5602
5603 self.edit_prediction_preview
5604 .set_previous_scroll_position(None);
5605 } else {
5606 self.edit_prediction_preview
5607 .set_previous_scroll_position(Some(
5608 position_map.snapshot.scroll_anchor,
5609 ));
5610
5611 self.highlight_rows::<EditPredictionPreview>(
5612 target..target,
5613 cx.theme().colors().editor_highlighted_line_background,
5614 true,
5615 cx,
5616 );
5617 self.request_autoscroll(Autoscroll::fit(), cx);
5618 }
5619 }
5620 }
5621 InlineCompletion::Edit { edits, .. } => {
5622 if let Some(provider) = self.edit_prediction_provider() {
5623 provider.accept(cx);
5624 }
5625
5626 let snapshot = self.buffer.read(cx).snapshot(cx);
5627 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
5628
5629 self.buffer.update(cx, |buffer, cx| {
5630 buffer.edit(edits.iter().cloned(), None, cx)
5631 });
5632
5633 self.change_selections(None, window, cx, |s| {
5634 s.select_anchor_ranges([last_edit_end..last_edit_end])
5635 });
5636
5637 self.update_visible_inline_completion(window, cx);
5638 if self.active_inline_completion.is_none() {
5639 self.refresh_inline_completion(true, true, window, cx);
5640 }
5641
5642 cx.notify();
5643 }
5644 }
5645
5646 self.edit_prediction_requires_modifier_in_indent_conflict = false;
5647 }
5648
5649 pub fn accept_partial_inline_completion(
5650 &mut self,
5651 _: &AcceptPartialEditPrediction,
5652 window: &mut Window,
5653 cx: &mut Context<Self>,
5654 ) {
5655 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5656 return;
5657 };
5658 if self.selections.count() != 1 {
5659 return;
5660 }
5661
5662 self.report_inline_completion_event(
5663 active_inline_completion.completion_id.clone(),
5664 true,
5665 cx,
5666 );
5667
5668 match &active_inline_completion.completion {
5669 InlineCompletion::Move { target, .. } => {
5670 let target = *target;
5671 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
5672 selections.select_anchor_ranges([target..target]);
5673 });
5674 }
5675 InlineCompletion::Edit { edits, .. } => {
5676 // Find an insertion that starts at the cursor position.
5677 let snapshot = self.buffer.read(cx).snapshot(cx);
5678 let cursor_offset = self.selections.newest::<usize>(cx).head();
5679 let insertion = edits.iter().find_map(|(range, text)| {
5680 let range = range.to_offset(&snapshot);
5681 if range.is_empty() && range.start == cursor_offset {
5682 Some(text)
5683 } else {
5684 None
5685 }
5686 });
5687
5688 if let Some(text) = insertion {
5689 let mut partial_completion = text
5690 .chars()
5691 .by_ref()
5692 .take_while(|c| c.is_alphabetic())
5693 .collect::<String>();
5694 if partial_completion.is_empty() {
5695 partial_completion = text
5696 .chars()
5697 .by_ref()
5698 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
5699 .collect::<String>();
5700 }
5701
5702 cx.emit(EditorEvent::InputHandled {
5703 utf16_range_to_replace: None,
5704 text: partial_completion.clone().into(),
5705 });
5706
5707 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
5708
5709 self.refresh_inline_completion(true, true, window, cx);
5710 cx.notify();
5711 } else {
5712 self.accept_edit_prediction(&Default::default(), window, cx);
5713 }
5714 }
5715 }
5716 }
5717
5718 fn discard_inline_completion(
5719 &mut self,
5720 should_report_inline_completion_event: bool,
5721 cx: &mut Context<Self>,
5722 ) -> bool {
5723 if should_report_inline_completion_event {
5724 let completion_id = self
5725 .active_inline_completion
5726 .as_ref()
5727 .and_then(|active_completion| active_completion.completion_id.clone());
5728
5729 self.report_inline_completion_event(completion_id, false, cx);
5730 }
5731
5732 if let Some(provider) = self.edit_prediction_provider() {
5733 provider.discard(cx);
5734 }
5735
5736 self.take_active_inline_completion(cx)
5737 }
5738
5739 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
5740 let Some(provider) = self.edit_prediction_provider() else {
5741 return;
5742 };
5743
5744 let Some((_, buffer, _)) = self
5745 .buffer
5746 .read(cx)
5747 .excerpt_containing(self.selections.newest_anchor().head(), cx)
5748 else {
5749 return;
5750 };
5751
5752 let extension = buffer
5753 .read(cx)
5754 .file()
5755 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
5756
5757 let event_type = match accepted {
5758 true => "Edit Prediction Accepted",
5759 false => "Edit Prediction Discarded",
5760 };
5761 telemetry::event!(
5762 event_type,
5763 provider = provider.name(),
5764 prediction_id = id,
5765 suggestion_accepted = accepted,
5766 file_extension = extension,
5767 );
5768 }
5769
5770 pub fn has_active_inline_completion(&self) -> bool {
5771 self.active_inline_completion.is_some()
5772 }
5773
5774 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
5775 let Some(active_inline_completion) = self.active_inline_completion.take() else {
5776 return false;
5777 };
5778
5779 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
5780 self.clear_highlights::<InlineCompletionHighlight>(cx);
5781 self.stale_inline_completion_in_menu = Some(active_inline_completion);
5782 true
5783 }
5784
5785 /// Returns true when we're displaying the edit prediction popover below the cursor
5786 /// like we are not previewing and the LSP autocomplete menu is visible
5787 /// or we are in `when_holding_modifier` mode.
5788 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
5789 if self.edit_prediction_preview_is_active()
5790 || !self.show_edit_predictions_in_menu()
5791 || !self.edit_predictions_enabled()
5792 {
5793 return false;
5794 }
5795
5796 if self.has_visible_completions_menu() {
5797 return true;
5798 }
5799
5800 has_completion && self.edit_prediction_requires_modifier()
5801 }
5802
5803 fn handle_modifiers_changed(
5804 &mut self,
5805 modifiers: Modifiers,
5806 position_map: &PositionMap,
5807 window: &mut Window,
5808 cx: &mut Context<Self>,
5809 ) {
5810 if self.show_edit_predictions_in_menu() {
5811 self.update_edit_prediction_preview(&modifiers, window, cx);
5812 }
5813
5814 self.update_selection_mode(&modifiers, position_map, window, cx);
5815
5816 let mouse_position = window.mouse_position();
5817 if !position_map.text_hitbox.is_hovered(window) {
5818 return;
5819 }
5820
5821 self.update_hovered_link(
5822 position_map.point_for_position(mouse_position),
5823 &position_map.snapshot,
5824 modifiers,
5825 window,
5826 cx,
5827 )
5828 }
5829
5830 fn update_selection_mode(
5831 &mut self,
5832 modifiers: &Modifiers,
5833 position_map: &PositionMap,
5834 window: &mut Window,
5835 cx: &mut Context<Self>,
5836 ) {
5837 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
5838 return;
5839 }
5840
5841 let mouse_position = window.mouse_position();
5842 let point_for_position = position_map.point_for_position(mouse_position);
5843 let position = point_for_position.previous_valid;
5844
5845 self.select(
5846 SelectPhase::BeginColumnar {
5847 position,
5848 reset: false,
5849 goal_column: point_for_position.exact_unclipped.column(),
5850 },
5851 window,
5852 cx,
5853 );
5854 }
5855
5856 fn update_edit_prediction_preview(
5857 &mut self,
5858 modifiers: &Modifiers,
5859 window: &mut Window,
5860 cx: &mut Context<Self>,
5861 ) {
5862 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
5863 let Some(accept_keystroke) = accept_keybind.keystroke() else {
5864 return;
5865 };
5866
5867 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
5868 if matches!(
5869 self.edit_prediction_preview,
5870 EditPredictionPreview::Inactive { .. }
5871 ) {
5872 self.edit_prediction_preview = EditPredictionPreview::Active {
5873 previous_scroll_position: None,
5874 since: Instant::now(),
5875 };
5876
5877 self.update_visible_inline_completion(window, cx);
5878 cx.notify();
5879 }
5880 } else if let EditPredictionPreview::Active {
5881 previous_scroll_position,
5882 since,
5883 } = self.edit_prediction_preview
5884 {
5885 if let (Some(previous_scroll_position), Some(position_map)) =
5886 (previous_scroll_position, self.last_position_map.as_ref())
5887 {
5888 self.set_scroll_position(
5889 previous_scroll_position
5890 .scroll_position(&position_map.snapshot.display_snapshot),
5891 window,
5892 cx,
5893 );
5894 }
5895
5896 self.edit_prediction_preview = EditPredictionPreview::Inactive {
5897 released_too_fast: since.elapsed() < Duration::from_millis(200),
5898 };
5899 self.clear_row_highlights::<EditPredictionPreview>();
5900 self.update_visible_inline_completion(window, cx);
5901 cx.notify();
5902 }
5903 }
5904
5905 fn update_visible_inline_completion(
5906 &mut self,
5907 _window: &mut Window,
5908 cx: &mut Context<Self>,
5909 ) -> Option<()> {
5910 let selection = self.selections.newest_anchor();
5911 let cursor = selection.head();
5912 let multibuffer = self.buffer.read(cx).snapshot(cx);
5913 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
5914 let excerpt_id = cursor.excerpt_id;
5915
5916 let show_in_menu = self.show_edit_predictions_in_menu();
5917 let completions_menu_has_precedence = !show_in_menu
5918 && (self.context_menu.borrow().is_some()
5919 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
5920
5921 if completions_menu_has_precedence
5922 || !offset_selection.is_empty()
5923 || self
5924 .active_inline_completion
5925 .as_ref()
5926 .map_or(false, |completion| {
5927 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
5928 let invalidation_range = invalidation_range.start..=invalidation_range.end;
5929 !invalidation_range.contains(&offset_selection.head())
5930 })
5931 {
5932 self.discard_inline_completion(false, cx);
5933 return None;
5934 }
5935
5936 self.take_active_inline_completion(cx);
5937 let Some(provider) = self.edit_prediction_provider() else {
5938 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5939 return None;
5940 };
5941
5942 let (buffer, cursor_buffer_position) =
5943 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5944
5945 self.edit_prediction_settings =
5946 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5947
5948 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
5949
5950 if self.edit_prediction_indent_conflict {
5951 let cursor_point = cursor.to_point(&multibuffer);
5952
5953 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
5954
5955 if let Some((_, indent)) = indents.iter().next() {
5956 if indent.len == cursor_point.column {
5957 self.edit_prediction_indent_conflict = false;
5958 }
5959 }
5960 }
5961
5962 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
5963 let edits = inline_completion
5964 .edits
5965 .into_iter()
5966 .flat_map(|(range, new_text)| {
5967 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
5968 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
5969 Some((start..end, new_text))
5970 })
5971 .collect::<Vec<_>>();
5972 if edits.is_empty() {
5973 return None;
5974 }
5975
5976 let first_edit_start = edits.first().unwrap().0.start;
5977 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
5978 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
5979
5980 let last_edit_end = edits.last().unwrap().0.end;
5981 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
5982 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
5983
5984 let cursor_row = cursor.to_point(&multibuffer).row;
5985
5986 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
5987
5988 let mut inlay_ids = Vec::new();
5989 let invalidation_row_range;
5990 let move_invalidation_row_range = if cursor_row < edit_start_row {
5991 Some(cursor_row..edit_end_row)
5992 } else if cursor_row > edit_end_row {
5993 Some(edit_start_row..cursor_row)
5994 } else {
5995 None
5996 };
5997 let is_move =
5998 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
5999 let completion = if is_move {
6000 invalidation_row_range =
6001 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6002 let target = first_edit_start;
6003 InlineCompletion::Move { target, snapshot }
6004 } else {
6005 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6006 && !self.inline_completions_hidden_for_vim_mode;
6007
6008 if show_completions_in_buffer {
6009 if edits
6010 .iter()
6011 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6012 {
6013 let mut inlays = Vec::new();
6014 for (range, new_text) in &edits {
6015 let inlay = Inlay::inline_completion(
6016 post_inc(&mut self.next_inlay_id),
6017 range.start,
6018 new_text.as_str(),
6019 );
6020 inlay_ids.push(inlay.id);
6021 inlays.push(inlay);
6022 }
6023
6024 self.splice_inlays(&[], inlays, cx);
6025 } else {
6026 let background_color = cx.theme().status().deleted_background;
6027 self.highlight_text::<InlineCompletionHighlight>(
6028 edits.iter().map(|(range, _)| range.clone()).collect(),
6029 HighlightStyle {
6030 background_color: Some(background_color),
6031 ..Default::default()
6032 },
6033 cx,
6034 );
6035 }
6036 }
6037
6038 invalidation_row_range = edit_start_row..edit_end_row;
6039
6040 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6041 if provider.show_tab_accept_marker() {
6042 EditDisplayMode::TabAccept
6043 } else {
6044 EditDisplayMode::Inline
6045 }
6046 } else {
6047 EditDisplayMode::DiffPopover
6048 };
6049
6050 InlineCompletion::Edit {
6051 edits,
6052 edit_preview: inline_completion.edit_preview,
6053 display_mode,
6054 snapshot,
6055 }
6056 };
6057
6058 let invalidation_range = multibuffer
6059 .anchor_before(Point::new(invalidation_row_range.start, 0))
6060 ..multibuffer.anchor_after(Point::new(
6061 invalidation_row_range.end,
6062 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6063 ));
6064
6065 self.stale_inline_completion_in_menu = None;
6066 self.active_inline_completion = Some(InlineCompletionState {
6067 inlay_ids,
6068 completion,
6069 completion_id: inline_completion.id,
6070 invalidation_range,
6071 });
6072
6073 cx.notify();
6074
6075 Some(())
6076 }
6077
6078 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6079 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6080 }
6081
6082 fn render_code_actions_indicator(
6083 &self,
6084 _style: &EditorStyle,
6085 row: DisplayRow,
6086 is_active: bool,
6087 breakpoint: Option<&(Anchor, Breakpoint)>,
6088 cx: &mut Context<Self>,
6089 ) -> Option<IconButton> {
6090 let color = Color::Muted;
6091 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6092
6093 if self.available_code_actions.is_some() {
6094 Some(
6095 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
6096 .shape(ui::IconButtonShape::Square)
6097 .icon_size(IconSize::XSmall)
6098 .icon_color(color)
6099 .toggle_state(is_active)
6100 .tooltip({
6101 let focus_handle = self.focus_handle.clone();
6102 move |window, cx| {
6103 Tooltip::for_action_in(
6104 "Toggle Code Actions",
6105 &ToggleCodeActions {
6106 deployed_from_indicator: None,
6107 },
6108 &focus_handle,
6109 window,
6110 cx,
6111 )
6112 }
6113 })
6114 .on_click(cx.listener(move |editor, _e, window, cx| {
6115 window.focus(&editor.focus_handle(cx));
6116 editor.toggle_code_actions(
6117 &ToggleCodeActions {
6118 deployed_from_indicator: Some(row),
6119 },
6120 window,
6121 cx,
6122 );
6123 }))
6124 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6125 editor.set_breakpoint_context_menu(
6126 row,
6127 position,
6128 event.down.position,
6129 window,
6130 cx,
6131 );
6132 })),
6133 )
6134 } else {
6135 None
6136 }
6137 }
6138
6139 fn clear_tasks(&mut self) {
6140 self.tasks.clear()
6141 }
6142
6143 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6144 if self.tasks.insert(key, value).is_some() {
6145 // This case should hopefully be rare, but just in case...
6146 log::error!("multiple different run targets found on a single line, only the last target will be rendered")
6147 }
6148 }
6149
6150 /// Get all display points of breakpoints that will be rendered within editor
6151 ///
6152 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6153 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6154 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6155 fn active_breakpoints(
6156 &mut self,
6157 range: Range<DisplayRow>,
6158 window: &mut Window,
6159 cx: &mut Context<Self>,
6160 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
6161 let mut breakpoint_display_points = HashMap::default();
6162
6163 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
6164 return breakpoint_display_points;
6165 };
6166
6167 let snapshot = self.snapshot(window, cx);
6168
6169 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
6170 let Some(project) = self.project.as_ref() else {
6171 return breakpoint_display_points;
6172 };
6173
6174 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
6175 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
6176
6177 for (buffer_snapshot, range, excerpt_id) in
6178 multi_buffer_snapshot.range_to_buffer_ranges(range)
6179 {
6180 let Some(buffer) = project.read_with(cx, |this, cx| {
6181 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
6182 }) else {
6183 continue;
6184 };
6185 let breakpoints = breakpoint_store.read(cx).breakpoints(
6186 &buffer,
6187 Some(
6188 buffer_snapshot.anchor_before(range.start)
6189 ..buffer_snapshot.anchor_after(range.end),
6190 ),
6191 buffer_snapshot,
6192 cx,
6193 );
6194 for (anchor, breakpoint) in breakpoints {
6195 let multi_buffer_anchor =
6196 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), *anchor);
6197 let position = multi_buffer_anchor
6198 .to_point(&multi_buffer_snapshot)
6199 .to_display_point(&snapshot);
6200
6201 breakpoint_display_points
6202 .insert(position.row(), (multi_buffer_anchor, breakpoint.clone()));
6203 }
6204 }
6205
6206 breakpoint_display_points
6207 }
6208
6209 fn breakpoint_context_menu(
6210 &self,
6211 anchor: Anchor,
6212 window: &mut Window,
6213 cx: &mut Context<Self>,
6214 ) -> Entity<ui::ContextMenu> {
6215 let weak_editor = cx.weak_entity();
6216 let focus_handle = self.focus_handle(cx);
6217
6218 let row = self
6219 .buffer
6220 .read(cx)
6221 .snapshot(cx)
6222 .summary_for_anchor::<Point>(&anchor)
6223 .row;
6224
6225 let breakpoint = self
6226 .breakpoint_at_row(row, window, cx)
6227 .map(|(_, bp)| Arc::from(bp));
6228
6229 let log_breakpoint_msg = if breakpoint
6230 .as_ref()
6231 .is_some_and(|bp| bp.kind.log_message().is_some())
6232 {
6233 "Edit Log Breakpoint"
6234 } else {
6235 "Set Log Breakpoint"
6236 };
6237
6238 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
6239 "Unset Breakpoint"
6240 } else {
6241 "Set Breakpoint"
6242 };
6243
6244 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.state {
6245 BreakpointState::Enabled => Some("Disable"),
6246 BreakpointState::Disabled => Some("Enable"),
6247 });
6248
6249 let breakpoint = breakpoint.unwrap_or_else(|| {
6250 Arc::new(Breakpoint {
6251 state: BreakpointState::Enabled,
6252 kind: BreakpointKind::Standard,
6253 })
6254 });
6255
6256 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6257 menu.on_blur_subscription(Subscription::new(|| {}))
6258 .context(focus_handle)
6259 .when_some(toggle_state_msg, |this, msg| {
6260 this.entry(msg, None, {
6261 let weak_editor = weak_editor.clone();
6262 let breakpoint = breakpoint.clone();
6263 move |_window, cx| {
6264 weak_editor
6265 .update(cx, |this, cx| {
6266 this.edit_breakpoint_at_anchor(
6267 anchor,
6268 breakpoint.as_ref().clone(),
6269 BreakpointEditAction::InvertState,
6270 cx,
6271 );
6272 })
6273 .log_err();
6274 }
6275 })
6276 })
6277 .entry(set_breakpoint_msg, None, {
6278 let weak_editor = weak_editor.clone();
6279 let breakpoint = breakpoint.clone();
6280 move |_window, cx| {
6281 weak_editor
6282 .update(cx, |this, cx| {
6283 this.edit_breakpoint_at_anchor(
6284 anchor,
6285 breakpoint.as_ref().clone(),
6286 BreakpointEditAction::Toggle,
6287 cx,
6288 );
6289 })
6290 .log_err();
6291 }
6292 })
6293 .entry(log_breakpoint_msg, None, move |window, cx| {
6294 weak_editor
6295 .update(cx, |this, cx| {
6296 this.add_edit_breakpoint_block(anchor, breakpoint.as_ref(), window, cx);
6297 })
6298 .log_err();
6299 })
6300 })
6301 }
6302
6303 fn render_breakpoint(
6304 &self,
6305 position: Anchor,
6306 row: DisplayRow,
6307 breakpoint: &Breakpoint,
6308 cx: &mut Context<Self>,
6309 ) -> IconButton {
6310 let (color, icon) = {
6311 let icon = match (&breakpoint.kind, breakpoint.is_disabled()) {
6312 (BreakpointKind::Standard, false) => ui::IconName::DebugBreakpoint,
6313 (BreakpointKind::Log(_), false) => ui::IconName::DebugLogBreakpoint,
6314 (BreakpointKind::Standard, true) => ui::IconName::DebugDisabledBreakpoint,
6315 (BreakpointKind::Log(_), true) => ui::IconName::DebugDisabledLogBreakpoint,
6316 };
6317
6318 let color = if self
6319 .gutter_breakpoint_indicator
6320 .is_some_and(|point| point.row() == row)
6321 {
6322 Color::Hint
6323 } else {
6324 Color::Debugger
6325 };
6326
6327 (color, icon)
6328 };
6329
6330 let breakpoint = Arc::from(breakpoint.clone());
6331
6332 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
6333 .icon_size(IconSize::XSmall)
6334 .size(ui::ButtonSize::None)
6335 .icon_color(color)
6336 .style(ButtonStyle::Transparent)
6337 .on_click(cx.listener({
6338 let breakpoint = breakpoint.clone();
6339
6340 move |editor, event: &ClickEvent, window, cx| {
6341 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
6342 BreakpointEditAction::InvertState
6343 } else {
6344 BreakpointEditAction::Toggle
6345 };
6346
6347 window.focus(&editor.focus_handle(cx));
6348 editor.edit_breakpoint_at_anchor(
6349 position,
6350 breakpoint.as_ref().clone(),
6351 edit_action,
6352 cx,
6353 );
6354 }
6355 }))
6356 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6357 editor.set_breakpoint_context_menu(
6358 row,
6359 Some(position),
6360 event.down.position,
6361 window,
6362 cx,
6363 );
6364 }))
6365 }
6366
6367 fn build_tasks_context(
6368 project: &Entity<Project>,
6369 buffer: &Entity<Buffer>,
6370 buffer_row: u32,
6371 tasks: &Arc<RunnableTasks>,
6372 cx: &mut Context<Self>,
6373 ) -> Task<Option<task::TaskContext>> {
6374 let position = Point::new(buffer_row, tasks.column);
6375 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
6376 let location = Location {
6377 buffer: buffer.clone(),
6378 range: range_start..range_start,
6379 };
6380 // Fill in the environmental variables from the tree-sitter captures
6381 let mut captured_task_variables = TaskVariables::default();
6382 for (capture_name, value) in tasks.extra_variables.clone() {
6383 captured_task_variables.insert(
6384 task::VariableName::Custom(capture_name.into()),
6385 value.clone(),
6386 );
6387 }
6388 project.update(cx, |project, cx| {
6389 project.task_store().update(cx, |task_store, cx| {
6390 task_store.task_context_for_location(captured_task_variables, location, cx)
6391 })
6392 })
6393 }
6394
6395 pub fn spawn_nearest_task(
6396 &mut self,
6397 action: &SpawnNearestTask,
6398 window: &mut Window,
6399 cx: &mut Context<Self>,
6400 ) {
6401 let Some((workspace, _)) = self.workspace.clone() else {
6402 return;
6403 };
6404 let Some(project) = self.project.clone() else {
6405 return;
6406 };
6407
6408 // Try to find a closest, enclosing node using tree-sitter that has a
6409 // task
6410 let Some((buffer, buffer_row, tasks)) = self
6411 .find_enclosing_node_task(cx)
6412 // Or find the task that's closest in row-distance.
6413 .or_else(|| self.find_closest_task(cx))
6414 else {
6415 return;
6416 };
6417
6418 let reveal_strategy = action.reveal;
6419 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6420 cx.spawn_in(window, async move |_, cx| {
6421 let context = task_context.await?;
6422 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
6423
6424 let resolved = resolved_task.resolved.as_mut()?;
6425 resolved.reveal = reveal_strategy;
6426
6427 workspace
6428 .update(cx, |workspace, cx| {
6429 workspace::tasks::schedule_resolved_task(
6430 workspace,
6431 task_source_kind,
6432 resolved_task,
6433 false,
6434 cx,
6435 );
6436 })
6437 .ok()
6438 })
6439 .detach();
6440 }
6441
6442 fn find_closest_task(
6443 &mut self,
6444 cx: &mut Context<Self>,
6445 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6446 let cursor_row = self.selections.newest_adjusted(cx).head().row;
6447
6448 let ((buffer_id, row), tasks) = self
6449 .tasks
6450 .iter()
6451 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
6452
6453 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
6454 let tasks = Arc::new(tasks.to_owned());
6455 Some((buffer, *row, tasks))
6456 }
6457
6458 fn find_enclosing_node_task(
6459 &mut self,
6460 cx: &mut Context<Self>,
6461 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6462 let snapshot = self.buffer.read(cx).snapshot(cx);
6463 let offset = self.selections.newest::<usize>(cx).head();
6464 let excerpt = snapshot.excerpt_containing(offset..offset)?;
6465 let buffer_id = excerpt.buffer().remote_id();
6466
6467 let layer = excerpt.buffer().syntax_layer_at(offset)?;
6468 let mut cursor = layer.node().walk();
6469
6470 while cursor.goto_first_child_for_byte(offset).is_some() {
6471 if cursor.node().end_byte() == offset {
6472 cursor.goto_next_sibling();
6473 }
6474 }
6475
6476 // Ascend to the smallest ancestor that contains the range and has a task.
6477 loop {
6478 let node = cursor.node();
6479 let node_range = node.byte_range();
6480 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
6481
6482 // Check if this node contains our offset
6483 if node_range.start <= offset && node_range.end >= offset {
6484 // If it contains offset, check for task
6485 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
6486 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
6487 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
6488 }
6489 }
6490
6491 if !cursor.goto_parent() {
6492 break;
6493 }
6494 }
6495 None
6496 }
6497
6498 fn render_run_indicator(
6499 &self,
6500 _style: &EditorStyle,
6501 is_active: bool,
6502 row: DisplayRow,
6503 breakpoint: Option<(Anchor, Breakpoint)>,
6504 cx: &mut Context<Self>,
6505 ) -> IconButton {
6506 let color = Color::Muted;
6507 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6508
6509 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
6510 .shape(ui::IconButtonShape::Square)
6511 .icon_size(IconSize::XSmall)
6512 .icon_color(color)
6513 .toggle_state(is_active)
6514 .on_click(cx.listener(move |editor, _e, window, cx| {
6515 window.focus(&editor.focus_handle(cx));
6516 editor.toggle_code_actions(
6517 &ToggleCodeActions {
6518 deployed_from_indicator: Some(row),
6519 },
6520 window,
6521 cx,
6522 );
6523 }))
6524 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6525 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
6526 }))
6527 }
6528
6529 pub fn context_menu_visible(&self) -> bool {
6530 !self.edit_prediction_preview_is_active()
6531 && self
6532 .context_menu
6533 .borrow()
6534 .as_ref()
6535 .map_or(false, |menu| menu.visible())
6536 }
6537
6538 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
6539 self.context_menu
6540 .borrow()
6541 .as_ref()
6542 .map(|menu| menu.origin())
6543 }
6544
6545 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
6546 self.context_menu_options = Some(options);
6547 }
6548
6549 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
6550 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
6551
6552 fn render_edit_prediction_popover(
6553 &mut self,
6554 text_bounds: &Bounds<Pixels>,
6555 content_origin: gpui::Point<Pixels>,
6556 editor_snapshot: &EditorSnapshot,
6557 visible_row_range: Range<DisplayRow>,
6558 scroll_top: f32,
6559 scroll_bottom: f32,
6560 line_layouts: &[LineWithInvisibles],
6561 line_height: Pixels,
6562 scroll_pixel_position: gpui::Point<Pixels>,
6563 newest_selection_head: Option<DisplayPoint>,
6564 editor_width: Pixels,
6565 style: &EditorStyle,
6566 window: &mut Window,
6567 cx: &mut App,
6568 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6569 let active_inline_completion = self.active_inline_completion.as_ref()?;
6570
6571 if self.edit_prediction_visible_in_cursor_popover(true) {
6572 return None;
6573 }
6574
6575 match &active_inline_completion.completion {
6576 InlineCompletion::Move { target, .. } => {
6577 let target_display_point = target.to_display_point(editor_snapshot);
6578
6579 if self.edit_prediction_requires_modifier() {
6580 if !self.edit_prediction_preview_is_active() {
6581 return None;
6582 }
6583
6584 self.render_edit_prediction_modifier_jump_popover(
6585 text_bounds,
6586 content_origin,
6587 visible_row_range,
6588 line_layouts,
6589 line_height,
6590 scroll_pixel_position,
6591 newest_selection_head,
6592 target_display_point,
6593 window,
6594 cx,
6595 )
6596 } else {
6597 self.render_edit_prediction_eager_jump_popover(
6598 text_bounds,
6599 content_origin,
6600 editor_snapshot,
6601 visible_row_range,
6602 scroll_top,
6603 scroll_bottom,
6604 line_height,
6605 scroll_pixel_position,
6606 target_display_point,
6607 editor_width,
6608 window,
6609 cx,
6610 )
6611 }
6612 }
6613 InlineCompletion::Edit {
6614 display_mode: EditDisplayMode::Inline,
6615 ..
6616 } => None,
6617 InlineCompletion::Edit {
6618 display_mode: EditDisplayMode::TabAccept,
6619 edits,
6620 ..
6621 } => {
6622 let range = &edits.first()?.0;
6623 let target_display_point = range.end.to_display_point(editor_snapshot);
6624
6625 self.render_edit_prediction_end_of_line_popover(
6626 "Accept",
6627 editor_snapshot,
6628 visible_row_range,
6629 target_display_point,
6630 line_height,
6631 scroll_pixel_position,
6632 content_origin,
6633 editor_width,
6634 window,
6635 cx,
6636 )
6637 }
6638 InlineCompletion::Edit {
6639 edits,
6640 edit_preview,
6641 display_mode: EditDisplayMode::DiffPopover,
6642 snapshot,
6643 } => self.render_edit_prediction_diff_popover(
6644 text_bounds,
6645 content_origin,
6646 editor_snapshot,
6647 visible_row_range,
6648 line_layouts,
6649 line_height,
6650 scroll_pixel_position,
6651 newest_selection_head,
6652 editor_width,
6653 style,
6654 edits,
6655 edit_preview,
6656 snapshot,
6657 window,
6658 cx,
6659 ),
6660 }
6661 }
6662
6663 fn render_edit_prediction_modifier_jump_popover(
6664 &mut self,
6665 text_bounds: &Bounds<Pixels>,
6666 content_origin: gpui::Point<Pixels>,
6667 visible_row_range: Range<DisplayRow>,
6668 line_layouts: &[LineWithInvisibles],
6669 line_height: Pixels,
6670 scroll_pixel_position: gpui::Point<Pixels>,
6671 newest_selection_head: Option<DisplayPoint>,
6672 target_display_point: DisplayPoint,
6673 window: &mut Window,
6674 cx: &mut App,
6675 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6676 let scrolled_content_origin =
6677 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
6678
6679 const SCROLL_PADDING_Y: Pixels = px(12.);
6680
6681 if target_display_point.row() < visible_row_range.start {
6682 return self.render_edit_prediction_scroll_popover(
6683 |_| SCROLL_PADDING_Y,
6684 IconName::ArrowUp,
6685 visible_row_range,
6686 line_layouts,
6687 newest_selection_head,
6688 scrolled_content_origin,
6689 window,
6690 cx,
6691 );
6692 } else if target_display_point.row() >= visible_row_range.end {
6693 return self.render_edit_prediction_scroll_popover(
6694 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
6695 IconName::ArrowDown,
6696 visible_row_range,
6697 line_layouts,
6698 newest_selection_head,
6699 scrolled_content_origin,
6700 window,
6701 cx,
6702 );
6703 }
6704
6705 const POLE_WIDTH: Pixels = px(2.);
6706
6707 let line_layout =
6708 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
6709 let target_column = target_display_point.column() as usize;
6710
6711 let target_x = line_layout.x_for_index(target_column);
6712 let target_y =
6713 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
6714
6715 let flag_on_right = target_x < text_bounds.size.width / 2.;
6716
6717 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
6718 border_color.l += 0.001;
6719
6720 let mut element = v_flex()
6721 .items_end()
6722 .when(flag_on_right, |el| el.items_start())
6723 .child(if flag_on_right {
6724 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
6725 .rounded_bl(px(0.))
6726 .rounded_tl(px(0.))
6727 .border_l_2()
6728 .border_color(border_color)
6729 } else {
6730 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
6731 .rounded_br(px(0.))
6732 .rounded_tr(px(0.))
6733 .border_r_2()
6734 .border_color(border_color)
6735 })
6736 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
6737 .into_any();
6738
6739 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6740
6741 let mut origin = scrolled_content_origin + point(target_x, target_y)
6742 - point(
6743 if flag_on_right {
6744 POLE_WIDTH
6745 } else {
6746 size.width - POLE_WIDTH
6747 },
6748 size.height - line_height,
6749 );
6750
6751 origin.x = origin.x.max(content_origin.x);
6752
6753 element.prepaint_at(origin, window, cx);
6754
6755 Some((element, origin))
6756 }
6757
6758 fn render_edit_prediction_scroll_popover(
6759 &mut self,
6760 to_y: impl Fn(Size<Pixels>) -> Pixels,
6761 scroll_icon: IconName,
6762 visible_row_range: Range<DisplayRow>,
6763 line_layouts: &[LineWithInvisibles],
6764 newest_selection_head: Option<DisplayPoint>,
6765 scrolled_content_origin: gpui::Point<Pixels>,
6766 window: &mut Window,
6767 cx: &mut App,
6768 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6769 let mut element = self
6770 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
6771 .into_any();
6772
6773 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6774
6775 let cursor = newest_selection_head?;
6776 let cursor_row_layout =
6777 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
6778 let cursor_column = cursor.column() as usize;
6779
6780 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
6781
6782 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
6783
6784 element.prepaint_at(origin, window, cx);
6785 Some((element, origin))
6786 }
6787
6788 fn render_edit_prediction_eager_jump_popover(
6789 &mut self,
6790 text_bounds: &Bounds<Pixels>,
6791 content_origin: gpui::Point<Pixels>,
6792 editor_snapshot: &EditorSnapshot,
6793 visible_row_range: Range<DisplayRow>,
6794 scroll_top: f32,
6795 scroll_bottom: f32,
6796 line_height: Pixels,
6797 scroll_pixel_position: gpui::Point<Pixels>,
6798 target_display_point: DisplayPoint,
6799 editor_width: Pixels,
6800 window: &mut Window,
6801 cx: &mut App,
6802 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6803 if target_display_point.row().as_f32() < scroll_top {
6804 let mut element = self
6805 .render_edit_prediction_line_popover(
6806 "Jump to Edit",
6807 Some(IconName::ArrowUp),
6808 window,
6809 cx,
6810 )?
6811 .into_any();
6812
6813 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6814 let offset = point(
6815 (text_bounds.size.width - size.width) / 2.,
6816 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
6817 );
6818
6819 let origin = text_bounds.origin + offset;
6820 element.prepaint_at(origin, window, cx);
6821 Some((element, origin))
6822 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
6823 let mut element = self
6824 .render_edit_prediction_line_popover(
6825 "Jump to Edit",
6826 Some(IconName::ArrowDown),
6827 window,
6828 cx,
6829 )?
6830 .into_any();
6831
6832 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6833 let offset = point(
6834 (text_bounds.size.width - size.width) / 2.,
6835 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
6836 );
6837
6838 let origin = text_bounds.origin + offset;
6839 element.prepaint_at(origin, window, cx);
6840 Some((element, origin))
6841 } else {
6842 self.render_edit_prediction_end_of_line_popover(
6843 "Jump to Edit",
6844 editor_snapshot,
6845 visible_row_range,
6846 target_display_point,
6847 line_height,
6848 scroll_pixel_position,
6849 content_origin,
6850 editor_width,
6851 window,
6852 cx,
6853 )
6854 }
6855 }
6856
6857 fn render_edit_prediction_end_of_line_popover(
6858 self: &mut Editor,
6859 label: &'static str,
6860 editor_snapshot: &EditorSnapshot,
6861 visible_row_range: Range<DisplayRow>,
6862 target_display_point: DisplayPoint,
6863 line_height: Pixels,
6864 scroll_pixel_position: gpui::Point<Pixels>,
6865 content_origin: gpui::Point<Pixels>,
6866 editor_width: Pixels,
6867 window: &mut Window,
6868 cx: &mut App,
6869 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6870 let target_line_end = DisplayPoint::new(
6871 target_display_point.row(),
6872 editor_snapshot.line_len(target_display_point.row()),
6873 );
6874
6875 let mut element = self
6876 .render_edit_prediction_line_popover(label, None, window, cx)?
6877 .into_any();
6878
6879 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6880
6881 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
6882
6883 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
6884 let mut origin = start_point
6885 + line_origin
6886 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
6887 origin.x = origin.x.max(content_origin.x);
6888
6889 let max_x = content_origin.x + editor_width - size.width;
6890
6891 if origin.x > max_x {
6892 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
6893
6894 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
6895 origin.y += offset;
6896 IconName::ArrowUp
6897 } else {
6898 origin.y -= offset;
6899 IconName::ArrowDown
6900 };
6901
6902 element = self
6903 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
6904 .into_any();
6905
6906 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6907
6908 origin.x = content_origin.x + editor_width - size.width - px(2.);
6909 }
6910
6911 element.prepaint_at(origin, window, cx);
6912 Some((element, origin))
6913 }
6914
6915 fn render_edit_prediction_diff_popover(
6916 self: &Editor,
6917 text_bounds: &Bounds<Pixels>,
6918 content_origin: gpui::Point<Pixels>,
6919 editor_snapshot: &EditorSnapshot,
6920 visible_row_range: Range<DisplayRow>,
6921 line_layouts: &[LineWithInvisibles],
6922 line_height: Pixels,
6923 scroll_pixel_position: gpui::Point<Pixels>,
6924 newest_selection_head: Option<DisplayPoint>,
6925 editor_width: Pixels,
6926 style: &EditorStyle,
6927 edits: &Vec<(Range<Anchor>, String)>,
6928 edit_preview: &Option<language::EditPreview>,
6929 snapshot: &language::BufferSnapshot,
6930 window: &mut Window,
6931 cx: &mut App,
6932 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6933 let edit_start = edits
6934 .first()
6935 .unwrap()
6936 .0
6937 .start
6938 .to_display_point(editor_snapshot);
6939 let edit_end = edits
6940 .last()
6941 .unwrap()
6942 .0
6943 .end
6944 .to_display_point(editor_snapshot);
6945
6946 let is_visible = visible_row_range.contains(&edit_start.row())
6947 || visible_row_range.contains(&edit_end.row());
6948 if !is_visible {
6949 return None;
6950 }
6951
6952 let highlighted_edits =
6953 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
6954
6955 let styled_text = highlighted_edits.to_styled_text(&style.text);
6956 let line_count = highlighted_edits.text.lines().count();
6957
6958 const BORDER_WIDTH: Pixels = px(1.);
6959
6960 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
6961 let has_keybind = keybind.is_some();
6962
6963 let mut element = h_flex()
6964 .items_start()
6965 .child(
6966 h_flex()
6967 .bg(cx.theme().colors().editor_background)
6968 .border(BORDER_WIDTH)
6969 .shadow_sm()
6970 .border_color(cx.theme().colors().border)
6971 .rounded_l_lg()
6972 .when(line_count > 1, |el| el.rounded_br_lg())
6973 .pr_1()
6974 .child(styled_text),
6975 )
6976 .child(
6977 h_flex()
6978 .h(line_height + BORDER_WIDTH * 2.)
6979 .px_1p5()
6980 .gap_1()
6981 // Workaround: For some reason, there's a gap if we don't do this
6982 .ml(-BORDER_WIDTH)
6983 .shadow(smallvec![gpui::BoxShadow {
6984 color: gpui::black().opacity(0.05),
6985 offset: point(px(1.), px(1.)),
6986 blur_radius: px(2.),
6987 spread_radius: px(0.),
6988 }])
6989 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
6990 .border(BORDER_WIDTH)
6991 .border_color(cx.theme().colors().border)
6992 .rounded_r_lg()
6993 .id("edit_prediction_diff_popover_keybind")
6994 .when(!has_keybind, |el| {
6995 let status_colors = cx.theme().status();
6996
6997 el.bg(status_colors.error_background)
6998 .border_color(status_colors.error.opacity(0.6))
6999 .child(Icon::new(IconName::Info).color(Color::Error))
7000 .cursor_default()
7001 .hoverable_tooltip(move |_window, cx| {
7002 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7003 })
7004 })
7005 .children(keybind),
7006 )
7007 .into_any();
7008
7009 let longest_row =
7010 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7011 let longest_line_width = if visible_row_range.contains(&longest_row) {
7012 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7013 } else {
7014 layout_line(
7015 longest_row,
7016 editor_snapshot,
7017 style,
7018 editor_width,
7019 |_| false,
7020 window,
7021 cx,
7022 )
7023 .width
7024 };
7025
7026 let viewport_bounds =
7027 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
7028 right: -EditorElement::SCROLLBAR_WIDTH,
7029 ..Default::default()
7030 });
7031
7032 let x_after_longest =
7033 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
7034 - scroll_pixel_position.x;
7035
7036 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7037
7038 // Fully visible if it can be displayed within the window (allow overlapping other
7039 // panes). However, this is only allowed if the popover starts within text_bounds.
7040 let can_position_to_the_right = x_after_longest < text_bounds.right()
7041 && x_after_longest + element_bounds.width < viewport_bounds.right();
7042
7043 let mut origin = if can_position_to_the_right {
7044 point(
7045 x_after_longest,
7046 text_bounds.origin.y + edit_start.row().as_f32() * line_height
7047 - scroll_pixel_position.y,
7048 )
7049 } else {
7050 let cursor_row = newest_selection_head.map(|head| head.row());
7051 let above_edit = edit_start
7052 .row()
7053 .0
7054 .checked_sub(line_count as u32)
7055 .map(DisplayRow);
7056 let below_edit = Some(edit_end.row() + 1);
7057 let above_cursor =
7058 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
7059 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
7060
7061 // Place the edit popover adjacent to the edit if there is a location
7062 // available that is onscreen and does not obscure the cursor. Otherwise,
7063 // place it adjacent to the cursor.
7064 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
7065 .into_iter()
7066 .flatten()
7067 .find(|&start_row| {
7068 let end_row = start_row + line_count as u32;
7069 visible_row_range.contains(&start_row)
7070 && visible_row_range.contains(&end_row)
7071 && cursor_row.map_or(true, |cursor_row| {
7072 !((start_row..end_row).contains(&cursor_row))
7073 })
7074 })?;
7075
7076 content_origin
7077 + point(
7078 -scroll_pixel_position.x,
7079 row_target.as_f32() * line_height - scroll_pixel_position.y,
7080 )
7081 };
7082
7083 origin.x -= BORDER_WIDTH;
7084
7085 window.defer_draw(element, origin, 1);
7086
7087 // Do not return an element, since it will already be drawn due to defer_draw.
7088 None
7089 }
7090
7091 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
7092 px(30.)
7093 }
7094
7095 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
7096 if self.read_only(cx) {
7097 cx.theme().players().read_only()
7098 } else {
7099 self.style.as_ref().unwrap().local_player
7100 }
7101 }
7102
7103 fn render_edit_prediction_accept_keybind(
7104 &self,
7105 window: &mut Window,
7106 cx: &App,
7107 ) -> Option<AnyElement> {
7108 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
7109 let accept_keystroke = accept_binding.keystroke()?;
7110
7111 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7112
7113 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
7114 Color::Accent
7115 } else {
7116 Color::Muted
7117 };
7118
7119 h_flex()
7120 .px_0p5()
7121 .when(is_platform_style_mac, |parent| parent.gap_0p5())
7122 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7123 .text_size(TextSize::XSmall.rems(cx))
7124 .child(h_flex().children(ui::render_modifiers(
7125 &accept_keystroke.modifiers,
7126 PlatformStyle::platform(),
7127 Some(modifiers_color),
7128 Some(IconSize::XSmall.rems().into()),
7129 true,
7130 )))
7131 .when(is_platform_style_mac, |parent| {
7132 parent.child(accept_keystroke.key.clone())
7133 })
7134 .when(!is_platform_style_mac, |parent| {
7135 parent.child(
7136 Key::new(
7137 util::capitalize(&accept_keystroke.key),
7138 Some(Color::Default),
7139 )
7140 .size(Some(IconSize::XSmall.rems().into())),
7141 )
7142 })
7143 .into_any()
7144 .into()
7145 }
7146
7147 fn render_edit_prediction_line_popover(
7148 &self,
7149 label: impl Into<SharedString>,
7150 icon: Option<IconName>,
7151 window: &mut Window,
7152 cx: &App,
7153 ) -> Option<Stateful<Div>> {
7154 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
7155
7156 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7157 let has_keybind = keybind.is_some();
7158
7159 let result = h_flex()
7160 .id("ep-line-popover")
7161 .py_0p5()
7162 .pl_1()
7163 .pr(padding_right)
7164 .gap_1()
7165 .rounded_md()
7166 .border_1()
7167 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7168 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7169 .shadow_sm()
7170 .when(!has_keybind, |el| {
7171 let status_colors = cx.theme().status();
7172
7173 el.bg(status_colors.error_background)
7174 .border_color(status_colors.error.opacity(0.6))
7175 .pl_2()
7176 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7177 .cursor_default()
7178 .hoverable_tooltip(move |_window, cx| {
7179 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7180 })
7181 })
7182 .children(keybind)
7183 .child(
7184 Label::new(label)
7185 .size(LabelSize::Small)
7186 .when(!has_keybind, |el| {
7187 el.color(cx.theme().status().error.into()).strikethrough()
7188 }),
7189 )
7190 .when(!has_keybind, |el| {
7191 el.child(
7192 h_flex().ml_1().child(
7193 Icon::new(IconName::Info)
7194 .size(IconSize::Small)
7195 .color(cx.theme().status().error.into()),
7196 ),
7197 )
7198 })
7199 .when_some(icon, |element, icon| {
7200 element.child(
7201 div()
7202 .mt(px(1.5))
7203 .child(Icon::new(icon).size(IconSize::Small)),
7204 )
7205 });
7206
7207 Some(result)
7208 }
7209
7210 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
7211 let accent_color = cx.theme().colors().text_accent;
7212 let editor_bg_color = cx.theme().colors().editor_background;
7213 editor_bg_color.blend(accent_color.opacity(0.1))
7214 }
7215
7216 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
7217 let accent_color = cx.theme().colors().text_accent;
7218 let editor_bg_color = cx.theme().colors().editor_background;
7219 editor_bg_color.blend(accent_color.opacity(0.6))
7220 }
7221
7222 fn render_edit_prediction_cursor_popover(
7223 &self,
7224 min_width: Pixels,
7225 max_width: Pixels,
7226 cursor_point: Point,
7227 style: &EditorStyle,
7228 accept_keystroke: Option<&gpui::Keystroke>,
7229 _window: &Window,
7230 cx: &mut Context<Editor>,
7231 ) -> Option<AnyElement> {
7232 let provider = self.edit_prediction_provider.as_ref()?;
7233
7234 if provider.provider.needs_terms_acceptance(cx) {
7235 return Some(
7236 h_flex()
7237 .min_w(min_width)
7238 .flex_1()
7239 .px_2()
7240 .py_1()
7241 .gap_3()
7242 .elevation_2(cx)
7243 .hover(|style| style.bg(cx.theme().colors().element_hover))
7244 .id("accept-terms")
7245 .cursor_pointer()
7246 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
7247 .on_click(cx.listener(|this, _event, window, cx| {
7248 cx.stop_propagation();
7249 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
7250 window.dispatch_action(
7251 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
7252 cx,
7253 );
7254 }))
7255 .child(
7256 h_flex()
7257 .flex_1()
7258 .gap_2()
7259 .child(Icon::new(IconName::ZedPredict))
7260 .child(Label::new("Accept Terms of Service"))
7261 .child(div().w_full())
7262 .child(
7263 Icon::new(IconName::ArrowUpRight)
7264 .color(Color::Muted)
7265 .size(IconSize::Small),
7266 )
7267 .into_any_element(),
7268 )
7269 .into_any(),
7270 );
7271 }
7272
7273 let is_refreshing = provider.provider.is_refreshing(cx);
7274
7275 fn pending_completion_container() -> Div {
7276 h_flex()
7277 .h_full()
7278 .flex_1()
7279 .gap_2()
7280 .child(Icon::new(IconName::ZedPredict))
7281 }
7282
7283 let completion = match &self.active_inline_completion {
7284 Some(prediction) => {
7285 if !self.has_visible_completions_menu() {
7286 const RADIUS: Pixels = px(6.);
7287 const BORDER_WIDTH: Pixels = px(1.);
7288
7289 return Some(
7290 h_flex()
7291 .elevation_2(cx)
7292 .border(BORDER_WIDTH)
7293 .border_color(cx.theme().colors().border)
7294 .when(accept_keystroke.is_none(), |el| {
7295 el.border_color(cx.theme().status().error)
7296 })
7297 .rounded(RADIUS)
7298 .rounded_tl(px(0.))
7299 .overflow_hidden()
7300 .child(div().px_1p5().child(match &prediction.completion {
7301 InlineCompletion::Move { target, snapshot } => {
7302 use text::ToPoint as _;
7303 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
7304 {
7305 Icon::new(IconName::ZedPredictDown)
7306 } else {
7307 Icon::new(IconName::ZedPredictUp)
7308 }
7309 }
7310 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
7311 }))
7312 .child(
7313 h_flex()
7314 .gap_1()
7315 .py_1()
7316 .px_2()
7317 .rounded_r(RADIUS - BORDER_WIDTH)
7318 .border_l_1()
7319 .border_color(cx.theme().colors().border)
7320 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7321 .when(self.edit_prediction_preview.released_too_fast(), |el| {
7322 el.child(
7323 Label::new("Hold")
7324 .size(LabelSize::Small)
7325 .when(accept_keystroke.is_none(), |el| {
7326 el.strikethrough()
7327 })
7328 .line_height_style(LineHeightStyle::UiLabel),
7329 )
7330 })
7331 .id("edit_prediction_cursor_popover_keybind")
7332 .when(accept_keystroke.is_none(), |el| {
7333 let status_colors = cx.theme().status();
7334
7335 el.bg(status_colors.error_background)
7336 .border_color(status_colors.error.opacity(0.6))
7337 .child(Icon::new(IconName::Info).color(Color::Error))
7338 .cursor_default()
7339 .hoverable_tooltip(move |_window, cx| {
7340 cx.new(|_| MissingEditPredictionKeybindingTooltip)
7341 .into()
7342 })
7343 })
7344 .when_some(
7345 accept_keystroke.as_ref(),
7346 |el, accept_keystroke| {
7347 el.child(h_flex().children(ui::render_modifiers(
7348 &accept_keystroke.modifiers,
7349 PlatformStyle::platform(),
7350 Some(Color::Default),
7351 Some(IconSize::XSmall.rems().into()),
7352 false,
7353 )))
7354 },
7355 ),
7356 )
7357 .into_any(),
7358 );
7359 }
7360
7361 self.render_edit_prediction_cursor_popover_preview(
7362 prediction,
7363 cursor_point,
7364 style,
7365 cx,
7366 )?
7367 }
7368
7369 None if is_refreshing => match &self.stale_inline_completion_in_menu {
7370 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
7371 stale_completion,
7372 cursor_point,
7373 style,
7374 cx,
7375 )?,
7376
7377 None => {
7378 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
7379 }
7380 },
7381
7382 None => pending_completion_container().child(Label::new("No Prediction")),
7383 };
7384
7385 let completion = if is_refreshing {
7386 completion
7387 .with_animation(
7388 "loading-completion",
7389 Animation::new(Duration::from_secs(2))
7390 .repeat()
7391 .with_easing(pulsating_between(0.4, 0.8)),
7392 |label, delta| label.opacity(delta),
7393 )
7394 .into_any_element()
7395 } else {
7396 completion.into_any_element()
7397 };
7398
7399 let has_completion = self.active_inline_completion.is_some();
7400
7401 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7402 Some(
7403 h_flex()
7404 .min_w(min_width)
7405 .max_w(max_width)
7406 .flex_1()
7407 .elevation_2(cx)
7408 .border_color(cx.theme().colors().border)
7409 .child(
7410 div()
7411 .flex_1()
7412 .py_1()
7413 .px_2()
7414 .overflow_hidden()
7415 .child(completion),
7416 )
7417 .when_some(accept_keystroke, |el, accept_keystroke| {
7418 if !accept_keystroke.modifiers.modified() {
7419 return el;
7420 }
7421
7422 el.child(
7423 h_flex()
7424 .h_full()
7425 .border_l_1()
7426 .rounded_r_lg()
7427 .border_color(cx.theme().colors().border)
7428 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7429 .gap_1()
7430 .py_1()
7431 .px_2()
7432 .child(
7433 h_flex()
7434 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7435 .when(is_platform_style_mac, |parent| parent.gap_1())
7436 .child(h_flex().children(ui::render_modifiers(
7437 &accept_keystroke.modifiers,
7438 PlatformStyle::platform(),
7439 Some(if !has_completion {
7440 Color::Muted
7441 } else {
7442 Color::Default
7443 }),
7444 None,
7445 false,
7446 ))),
7447 )
7448 .child(Label::new("Preview").into_any_element())
7449 .opacity(if has_completion { 1.0 } else { 0.4 }),
7450 )
7451 })
7452 .into_any(),
7453 )
7454 }
7455
7456 fn render_edit_prediction_cursor_popover_preview(
7457 &self,
7458 completion: &InlineCompletionState,
7459 cursor_point: Point,
7460 style: &EditorStyle,
7461 cx: &mut Context<Editor>,
7462 ) -> Option<Div> {
7463 use text::ToPoint as _;
7464
7465 fn render_relative_row_jump(
7466 prefix: impl Into<String>,
7467 current_row: u32,
7468 target_row: u32,
7469 ) -> Div {
7470 let (row_diff, arrow) = if target_row < current_row {
7471 (current_row - target_row, IconName::ArrowUp)
7472 } else {
7473 (target_row - current_row, IconName::ArrowDown)
7474 };
7475
7476 h_flex()
7477 .child(
7478 Label::new(format!("{}{}", prefix.into(), row_diff))
7479 .color(Color::Muted)
7480 .size(LabelSize::Small),
7481 )
7482 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
7483 }
7484
7485 match &completion.completion {
7486 InlineCompletion::Move {
7487 target, snapshot, ..
7488 } => Some(
7489 h_flex()
7490 .px_2()
7491 .gap_2()
7492 .flex_1()
7493 .child(
7494 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
7495 Icon::new(IconName::ZedPredictDown)
7496 } else {
7497 Icon::new(IconName::ZedPredictUp)
7498 },
7499 )
7500 .child(Label::new("Jump to Edit")),
7501 ),
7502
7503 InlineCompletion::Edit {
7504 edits,
7505 edit_preview,
7506 snapshot,
7507 display_mode: _,
7508 } => {
7509 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
7510
7511 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
7512 &snapshot,
7513 &edits,
7514 edit_preview.as_ref()?,
7515 true,
7516 cx,
7517 )
7518 .first_line_preview();
7519
7520 let styled_text = gpui::StyledText::new(highlighted_edits.text)
7521 .with_default_highlights(&style.text, highlighted_edits.highlights);
7522
7523 let preview = h_flex()
7524 .gap_1()
7525 .min_w_16()
7526 .child(styled_text)
7527 .when(has_more_lines, |parent| parent.child("…"));
7528
7529 let left = if first_edit_row != cursor_point.row {
7530 render_relative_row_jump("", cursor_point.row, first_edit_row)
7531 .into_any_element()
7532 } else {
7533 Icon::new(IconName::ZedPredict).into_any_element()
7534 };
7535
7536 Some(
7537 h_flex()
7538 .h_full()
7539 .flex_1()
7540 .gap_2()
7541 .pr_1()
7542 .overflow_x_hidden()
7543 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7544 .child(left)
7545 .child(preview),
7546 )
7547 }
7548 }
7549 }
7550
7551 fn render_context_menu(
7552 &self,
7553 style: &EditorStyle,
7554 max_height_in_lines: u32,
7555 y_flipped: bool,
7556 window: &mut Window,
7557 cx: &mut Context<Editor>,
7558 ) -> Option<AnyElement> {
7559 let menu = self.context_menu.borrow();
7560 let menu = menu.as_ref()?;
7561 if !menu.visible() {
7562 return None;
7563 };
7564 Some(menu.render(style, max_height_in_lines, y_flipped, window, cx))
7565 }
7566
7567 fn render_context_menu_aside(
7568 &mut self,
7569 max_size: Size<Pixels>,
7570 window: &mut Window,
7571 cx: &mut Context<Editor>,
7572 ) -> Option<AnyElement> {
7573 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
7574 if menu.visible() {
7575 menu.render_aside(self, max_size, window, cx)
7576 } else {
7577 None
7578 }
7579 })
7580 }
7581
7582 fn hide_context_menu(
7583 &mut self,
7584 window: &mut Window,
7585 cx: &mut Context<Self>,
7586 ) -> Option<CodeContextMenu> {
7587 cx.notify();
7588 self.completion_tasks.clear();
7589 let context_menu = self.context_menu.borrow_mut().take();
7590 self.stale_inline_completion_in_menu.take();
7591 self.update_visible_inline_completion(window, cx);
7592 context_menu
7593 }
7594
7595 fn show_snippet_choices(
7596 &mut self,
7597 choices: &Vec<String>,
7598 selection: Range<Anchor>,
7599 cx: &mut Context<Self>,
7600 ) {
7601 if selection.start.buffer_id.is_none() {
7602 return;
7603 }
7604 let buffer_id = selection.start.buffer_id.unwrap();
7605 let buffer = self.buffer().read(cx).buffer(buffer_id);
7606 let id = post_inc(&mut self.next_completion_id);
7607
7608 if let Some(buffer) = buffer {
7609 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
7610 CompletionsMenu::new_snippet_choices(id, true, choices, selection, buffer),
7611 ));
7612 }
7613 }
7614
7615 pub fn insert_snippet(
7616 &mut self,
7617 insertion_ranges: &[Range<usize>],
7618 snippet: Snippet,
7619 window: &mut Window,
7620 cx: &mut Context<Self>,
7621 ) -> Result<()> {
7622 struct Tabstop<T> {
7623 is_end_tabstop: bool,
7624 ranges: Vec<Range<T>>,
7625 choices: Option<Vec<String>>,
7626 }
7627
7628 let tabstops = self.buffer.update(cx, |buffer, cx| {
7629 let snippet_text: Arc<str> = snippet.text.clone().into();
7630 let edits = insertion_ranges
7631 .iter()
7632 .cloned()
7633 .map(|range| (range, snippet_text.clone()));
7634 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
7635
7636 let snapshot = &*buffer.read(cx);
7637 let snippet = &snippet;
7638 snippet
7639 .tabstops
7640 .iter()
7641 .map(|tabstop| {
7642 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
7643 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
7644 });
7645 let mut tabstop_ranges = tabstop
7646 .ranges
7647 .iter()
7648 .flat_map(|tabstop_range| {
7649 let mut delta = 0_isize;
7650 insertion_ranges.iter().map(move |insertion_range| {
7651 let insertion_start = insertion_range.start as isize + delta;
7652 delta +=
7653 snippet.text.len() as isize - insertion_range.len() as isize;
7654
7655 let start = ((insertion_start + tabstop_range.start) as usize)
7656 .min(snapshot.len());
7657 let end = ((insertion_start + tabstop_range.end) as usize)
7658 .min(snapshot.len());
7659 snapshot.anchor_before(start)..snapshot.anchor_after(end)
7660 })
7661 })
7662 .collect::<Vec<_>>();
7663 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
7664
7665 Tabstop {
7666 is_end_tabstop,
7667 ranges: tabstop_ranges,
7668 choices: tabstop.choices.clone(),
7669 }
7670 })
7671 .collect::<Vec<_>>()
7672 });
7673 if let Some(tabstop) = tabstops.first() {
7674 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7675 s.select_ranges(tabstop.ranges.iter().cloned());
7676 });
7677
7678 if let Some(choices) = &tabstop.choices {
7679 if let Some(selection) = tabstop.ranges.first() {
7680 self.show_snippet_choices(choices, selection.clone(), cx)
7681 }
7682 }
7683
7684 // If we're already at the last tabstop and it's at the end of the snippet,
7685 // we're done, we don't need to keep the state around.
7686 if !tabstop.is_end_tabstop {
7687 let choices = tabstops
7688 .iter()
7689 .map(|tabstop| tabstop.choices.clone())
7690 .collect();
7691
7692 let ranges = tabstops
7693 .into_iter()
7694 .map(|tabstop| tabstop.ranges)
7695 .collect::<Vec<_>>();
7696
7697 self.snippet_stack.push(SnippetState {
7698 active_index: 0,
7699 ranges,
7700 choices,
7701 });
7702 }
7703
7704 // Check whether the just-entered snippet ends with an auto-closable bracket.
7705 if self.autoclose_regions.is_empty() {
7706 let snapshot = self.buffer.read(cx).snapshot(cx);
7707 for selection in &mut self.selections.all::<Point>(cx) {
7708 let selection_head = selection.head();
7709 let Some(scope) = snapshot.language_scope_at(selection_head) else {
7710 continue;
7711 };
7712
7713 let mut bracket_pair = None;
7714 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
7715 let prev_chars = snapshot
7716 .reversed_chars_at(selection_head)
7717 .collect::<String>();
7718 for (pair, enabled) in scope.brackets() {
7719 if enabled
7720 && pair.close
7721 && prev_chars.starts_with(pair.start.as_str())
7722 && next_chars.starts_with(pair.end.as_str())
7723 {
7724 bracket_pair = Some(pair.clone());
7725 break;
7726 }
7727 }
7728 if let Some(pair) = bracket_pair {
7729 let start = snapshot.anchor_after(selection_head);
7730 let end = snapshot.anchor_after(selection_head);
7731 self.autoclose_regions.push(AutocloseRegion {
7732 selection_id: selection.id,
7733 range: start..end,
7734 pair,
7735 });
7736 }
7737 }
7738 }
7739 }
7740 Ok(())
7741 }
7742
7743 pub fn move_to_next_snippet_tabstop(
7744 &mut self,
7745 window: &mut Window,
7746 cx: &mut Context<Self>,
7747 ) -> bool {
7748 self.move_to_snippet_tabstop(Bias::Right, window, cx)
7749 }
7750
7751 pub fn move_to_prev_snippet_tabstop(
7752 &mut self,
7753 window: &mut Window,
7754 cx: &mut Context<Self>,
7755 ) -> bool {
7756 self.move_to_snippet_tabstop(Bias::Left, window, cx)
7757 }
7758
7759 pub fn move_to_snippet_tabstop(
7760 &mut self,
7761 bias: Bias,
7762 window: &mut Window,
7763 cx: &mut Context<Self>,
7764 ) -> bool {
7765 if let Some(mut snippet) = self.snippet_stack.pop() {
7766 match bias {
7767 Bias::Left => {
7768 if snippet.active_index > 0 {
7769 snippet.active_index -= 1;
7770 } else {
7771 self.snippet_stack.push(snippet);
7772 return false;
7773 }
7774 }
7775 Bias::Right => {
7776 if snippet.active_index + 1 < snippet.ranges.len() {
7777 snippet.active_index += 1;
7778 } else {
7779 self.snippet_stack.push(snippet);
7780 return false;
7781 }
7782 }
7783 }
7784 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
7785 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7786 s.select_anchor_ranges(current_ranges.iter().cloned())
7787 });
7788
7789 if let Some(choices) = &snippet.choices[snippet.active_index] {
7790 if let Some(selection) = current_ranges.first() {
7791 self.show_snippet_choices(&choices, selection.clone(), cx);
7792 }
7793 }
7794
7795 // If snippet state is not at the last tabstop, push it back on the stack
7796 if snippet.active_index + 1 < snippet.ranges.len() {
7797 self.snippet_stack.push(snippet);
7798 }
7799 return true;
7800 }
7801 }
7802
7803 false
7804 }
7805
7806 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7807 self.transact(window, cx, |this, window, cx| {
7808 this.select_all(&SelectAll, window, cx);
7809 this.insert("", window, cx);
7810 });
7811 }
7812
7813 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
7814 self.mouse_cursor_hidden = self.hide_mouse_while_typing;
7815 self.transact(window, cx, |this, window, cx| {
7816 this.select_autoclose_pair(window, cx);
7817 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
7818 if !this.linked_edit_ranges.is_empty() {
7819 let selections = this.selections.all::<MultiBufferPoint>(cx);
7820 let snapshot = this.buffer.read(cx).snapshot(cx);
7821
7822 for selection in selections.iter() {
7823 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
7824 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
7825 if selection_start.buffer_id != selection_end.buffer_id {
7826 continue;
7827 }
7828 if let Some(ranges) =
7829 this.linked_editing_ranges_for(selection_start..selection_end, cx)
7830 {
7831 for (buffer, entries) in ranges {
7832 linked_ranges.entry(buffer).or_default().extend(entries);
7833 }
7834 }
7835 }
7836 }
7837
7838 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
7839 if !this.selections.line_mode {
7840 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
7841 for selection in &mut selections {
7842 if selection.is_empty() {
7843 let old_head = selection.head();
7844 let mut new_head =
7845 movement::left(&display_map, old_head.to_display_point(&display_map))
7846 .to_point(&display_map);
7847 if let Some((buffer, line_buffer_range)) = display_map
7848 .buffer_snapshot
7849 .buffer_line_for_row(MultiBufferRow(old_head.row))
7850 {
7851 let indent_size =
7852 buffer.indent_size_for_line(line_buffer_range.start.row);
7853 let indent_len = match indent_size.kind {
7854 IndentKind::Space => {
7855 buffer.settings_at(line_buffer_range.start, cx).tab_size
7856 }
7857 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
7858 };
7859 if old_head.column <= indent_size.len && old_head.column > 0 {
7860 let indent_len = indent_len.get();
7861 new_head = cmp::min(
7862 new_head,
7863 MultiBufferPoint::new(
7864 old_head.row,
7865 ((old_head.column - 1) / indent_len) * indent_len,
7866 ),
7867 );
7868 }
7869 }
7870
7871 selection.set_head(new_head, SelectionGoal::None);
7872 }
7873 }
7874 }
7875
7876 this.signature_help_state.set_backspace_pressed(true);
7877 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7878 s.select(selections)
7879 });
7880 this.insert("", window, cx);
7881 let empty_str: Arc<str> = Arc::from("");
7882 for (buffer, edits) in linked_ranges {
7883 let snapshot = buffer.read(cx).snapshot();
7884 use text::ToPoint as TP;
7885
7886 let edits = edits
7887 .into_iter()
7888 .map(|range| {
7889 let end_point = TP::to_point(&range.end, &snapshot);
7890 let mut start_point = TP::to_point(&range.start, &snapshot);
7891
7892 if end_point == start_point {
7893 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
7894 .saturating_sub(1);
7895 start_point =
7896 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
7897 };
7898
7899 (start_point..end_point, empty_str.clone())
7900 })
7901 .sorted_by_key(|(range, _)| range.start)
7902 .collect::<Vec<_>>();
7903 buffer.update(cx, |this, cx| {
7904 this.edit(edits, None, cx);
7905 })
7906 }
7907 this.refresh_inline_completion(true, false, window, cx);
7908 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
7909 });
7910 }
7911
7912 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
7913 self.mouse_cursor_hidden = self.hide_mouse_while_typing;
7914 self.transact(window, cx, |this, window, cx| {
7915 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7916 let line_mode = s.line_mode;
7917 s.move_with(|map, selection| {
7918 if selection.is_empty() && !line_mode {
7919 let cursor = movement::right(map, selection.head());
7920 selection.end = cursor;
7921 selection.reversed = true;
7922 selection.goal = SelectionGoal::None;
7923 }
7924 })
7925 });
7926 this.insert("", window, cx);
7927 this.refresh_inline_completion(true, false, window, cx);
7928 });
7929 }
7930
7931 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
7932 if self.move_to_prev_snippet_tabstop(window, cx) {
7933 return;
7934 }
7935 self.mouse_cursor_hidden = self.hide_mouse_while_typing;
7936 self.outdent(&Outdent, window, cx);
7937 }
7938
7939 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
7940 if self.move_to_next_snippet_tabstop(window, cx) || self.read_only(cx) {
7941 return;
7942 }
7943 self.mouse_cursor_hidden = self.hide_mouse_while_typing;
7944 let mut selections = self.selections.all_adjusted(cx);
7945 let buffer = self.buffer.read(cx);
7946 let snapshot = buffer.snapshot(cx);
7947 let rows_iter = selections.iter().map(|s| s.head().row);
7948 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
7949
7950 let mut edits = Vec::new();
7951 let mut prev_edited_row = 0;
7952 let mut row_delta = 0;
7953 for selection in &mut selections {
7954 if selection.start.row != prev_edited_row {
7955 row_delta = 0;
7956 }
7957 prev_edited_row = selection.end.row;
7958
7959 // If the selection is non-empty, then increase the indentation of the selected lines.
7960 if !selection.is_empty() {
7961 row_delta =
7962 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
7963 continue;
7964 }
7965
7966 // If the selection is empty and the cursor is in the leading whitespace before the
7967 // suggested indentation, then auto-indent the line.
7968 let cursor = selection.head();
7969 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
7970 if let Some(suggested_indent) =
7971 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
7972 {
7973 if cursor.column < suggested_indent.len
7974 && cursor.column <= current_indent.len
7975 && current_indent.len <= suggested_indent.len
7976 {
7977 selection.start = Point::new(cursor.row, suggested_indent.len);
7978 selection.end = selection.start;
7979 if row_delta == 0 {
7980 edits.extend(Buffer::edit_for_indent_size_adjustment(
7981 cursor.row,
7982 current_indent,
7983 suggested_indent,
7984 ));
7985 row_delta = suggested_indent.len - current_indent.len;
7986 }
7987 continue;
7988 }
7989 }
7990
7991 // Otherwise, insert a hard or soft tab.
7992 let settings = buffer.language_settings_at(cursor, cx);
7993 let tab_size = if settings.hard_tabs {
7994 IndentSize::tab()
7995 } else {
7996 let tab_size = settings.tab_size.get();
7997 let char_column = snapshot
7998 .text_for_range(Point::new(cursor.row, 0)..cursor)
7999 .flat_map(str::chars)
8000 .count()
8001 + row_delta as usize;
8002 let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size);
8003 IndentSize::spaces(chars_to_next_tab_stop)
8004 };
8005 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
8006 selection.end = selection.start;
8007 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
8008 row_delta += tab_size.len;
8009 }
8010
8011 self.transact(window, cx, |this, window, cx| {
8012 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8013 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8014 s.select(selections)
8015 });
8016 this.refresh_inline_completion(true, false, window, cx);
8017 });
8018 }
8019
8020 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
8021 if self.read_only(cx) {
8022 return;
8023 }
8024 let mut selections = self.selections.all::<Point>(cx);
8025 let mut prev_edited_row = 0;
8026 let mut row_delta = 0;
8027 let mut edits = Vec::new();
8028 let buffer = self.buffer.read(cx);
8029 let snapshot = buffer.snapshot(cx);
8030 for selection in &mut selections {
8031 if selection.start.row != prev_edited_row {
8032 row_delta = 0;
8033 }
8034 prev_edited_row = selection.end.row;
8035
8036 row_delta =
8037 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8038 }
8039
8040 self.transact(window, cx, |this, window, cx| {
8041 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8042 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8043 s.select(selections)
8044 });
8045 });
8046 }
8047
8048 fn indent_selection(
8049 buffer: &MultiBuffer,
8050 snapshot: &MultiBufferSnapshot,
8051 selection: &mut Selection<Point>,
8052 edits: &mut Vec<(Range<Point>, String)>,
8053 delta_for_start_row: u32,
8054 cx: &App,
8055 ) -> u32 {
8056 let settings = buffer.language_settings_at(selection.start, cx);
8057 let tab_size = settings.tab_size.get();
8058 let indent_kind = if settings.hard_tabs {
8059 IndentKind::Tab
8060 } else {
8061 IndentKind::Space
8062 };
8063 let mut start_row = selection.start.row;
8064 let mut end_row = selection.end.row + 1;
8065
8066 // If a selection ends at the beginning of a line, don't indent
8067 // that last line.
8068 if selection.end.column == 0 && selection.end.row > selection.start.row {
8069 end_row -= 1;
8070 }
8071
8072 // Avoid re-indenting a row that has already been indented by a
8073 // previous selection, but still update this selection's column
8074 // to reflect that indentation.
8075 if delta_for_start_row > 0 {
8076 start_row += 1;
8077 selection.start.column += delta_for_start_row;
8078 if selection.end.row == selection.start.row {
8079 selection.end.column += delta_for_start_row;
8080 }
8081 }
8082
8083 let mut delta_for_end_row = 0;
8084 let has_multiple_rows = start_row + 1 != end_row;
8085 for row in start_row..end_row {
8086 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
8087 let indent_delta = match (current_indent.kind, indent_kind) {
8088 (IndentKind::Space, IndentKind::Space) => {
8089 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
8090 IndentSize::spaces(columns_to_next_tab_stop)
8091 }
8092 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
8093 (_, IndentKind::Tab) => IndentSize::tab(),
8094 };
8095
8096 let start = if has_multiple_rows || current_indent.len < selection.start.column {
8097 0
8098 } else {
8099 selection.start.column
8100 };
8101 let row_start = Point::new(row, start);
8102 edits.push((
8103 row_start..row_start,
8104 indent_delta.chars().collect::<String>(),
8105 ));
8106
8107 // Update this selection's endpoints to reflect the indentation.
8108 if row == selection.start.row {
8109 selection.start.column += indent_delta.len;
8110 }
8111 if row == selection.end.row {
8112 selection.end.column += indent_delta.len;
8113 delta_for_end_row = indent_delta.len;
8114 }
8115 }
8116
8117 if selection.start.row == selection.end.row {
8118 delta_for_start_row + delta_for_end_row
8119 } else {
8120 delta_for_end_row
8121 }
8122 }
8123
8124 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
8125 if self.read_only(cx) {
8126 return;
8127 }
8128 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8129 let selections = self.selections.all::<Point>(cx);
8130 let mut deletion_ranges = Vec::new();
8131 let mut last_outdent = None;
8132 {
8133 let buffer = self.buffer.read(cx);
8134 let snapshot = buffer.snapshot(cx);
8135 for selection in &selections {
8136 let settings = buffer.language_settings_at(selection.start, cx);
8137 let tab_size = settings.tab_size.get();
8138 let mut rows = selection.spanned_rows(false, &display_map);
8139
8140 // Avoid re-outdenting a row that has already been outdented by a
8141 // previous selection.
8142 if let Some(last_row) = last_outdent {
8143 if last_row == rows.start {
8144 rows.start = rows.start.next_row();
8145 }
8146 }
8147 let has_multiple_rows = rows.len() > 1;
8148 for row in rows.iter_rows() {
8149 let indent_size = snapshot.indent_size_for_line(row);
8150 if indent_size.len > 0 {
8151 let deletion_len = match indent_size.kind {
8152 IndentKind::Space => {
8153 let columns_to_prev_tab_stop = indent_size.len % tab_size;
8154 if columns_to_prev_tab_stop == 0 {
8155 tab_size
8156 } else {
8157 columns_to_prev_tab_stop
8158 }
8159 }
8160 IndentKind::Tab => 1,
8161 };
8162 let start = if has_multiple_rows
8163 || deletion_len > selection.start.column
8164 || indent_size.len < selection.start.column
8165 {
8166 0
8167 } else {
8168 selection.start.column - deletion_len
8169 };
8170 deletion_ranges.push(
8171 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
8172 );
8173 last_outdent = Some(row);
8174 }
8175 }
8176 }
8177 }
8178
8179 self.transact(window, cx, |this, window, cx| {
8180 this.buffer.update(cx, |buffer, cx| {
8181 let empty_str: Arc<str> = Arc::default();
8182 buffer.edit(
8183 deletion_ranges
8184 .into_iter()
8185 .map(|range| (range, empty_str.clone())),
8186 None,
8187 cx,
8188 );
8189 });
8190 let selections = this.selections.all::<usize>(cx);
8191 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8192 s.select(selections)
8193 });
8194 });
8195 }
8196
8197 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
8198 if self.read_only(cx) {
8199 return;
8200 }
8201 let selections = self
8202 .selections
8203 .all::<usize>(cx)
8204 .into_iter()
8205 .map(|s| s.range());
8206
8207 self.transact(window, cx, |this, window, cx| {
8208 this.buffer.update(cx, |buffer, cx| {
8209 buffer.autoindent_ranges(selections, cx);
8210 });
8211 let selections = this.selections.all::<usize>(cx);
8212 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8213 s.select(selections)
8214 });
8215 });
8216 }
8217
8218 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
8219 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8220 let selections = self.selections.all::<Point>(cx);
8221
8222 let mut new_cursors = Vec::new();
8223 let mut edit_ranges = Vec::new();
8224 let mut selections = selections.iter().peekable();
8225 while let Some(selection) = selections.next() {
8226 let mut rows = selection.spanned_rows(false, &display_map);
8227 let goal_display_column = selection.head().to_display_point(&display_map).column();
8228
8229 // Accumulate contiguous regions of rows that we want to delete.
8230 while let Some(next_selection) = selections.peek() {
8231 let next_rows = next_selection.spanned_rows(false, &display_map);
8232 if next_rows.start <= rows.end {
8233 rows.end = next_rows.end;
8234 selections.next().unwrap();
8235 } else {
8236 break;
8237 }
8238 }
8239
8240 let buffer = &display_map.buffer_snapshot;
8241 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
8242 let edit_end;
8243 let cursor_buffer_row;
8244 if buffer.max_point().row >= rows.end.0 {
8245 // If there's a line after the range, delete the \n from the end of the row range
8246 // and position the cursor on the next line.
8247 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
8248 cursor_buffer_row = rows.end;
8249 } else {
8250 // If there isn't a line after the range, delete the \n from the line before the
8251 // start of the row range and position the cursor there.
8252 edit_start = edit_start.saturating_sub(1);
8253 edit_end = buffer.len();
8254 cursor_buffer_row = rows.start.previous_row();
8255 }
8256
8257 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
8258 *cursor.column_mut() =
8259 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
8260
8261 new_cursors.push((
8262 selection.id,
8263 buffer.anchor_after(cursor.to_point(&display_map)),
8264 ));
8265 edit_ranges.push(edit_start..edit_end);
8266 }
8267
8268 self.transact(window, cx, |this, window, cx| {
8269 let buffer = this.buffer.update(cx, |buffer, cx| {
8270 let empty_str: Arc<str> = Arc::default();
8271 buffer.edit(
8272 edit_ranges
8273 .into_iter()
8274 .map(|range| (range, empty_str.clone())),
8275 None,
8276 cx,
8277 );
8278 buffer.snapshot(cx)
8279 });
8280 let new_selections = new_cursors
8281 .into_iter()
8282 .map(|(id, cursor)| {
8283 let cursor = cursor.to_point(&buffer);
8284 Selection {
8285 id,
8286 start: cursor,
8287 end: cursor,
8288 reversed: false,
8289 goal: SelectionGoal::None,
8290 }
8291 })
8292 .collect();
8293
8294 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8295 s.select(new_selections);
8296 });
8297 });
8298 }
8299
8300 pub fn join_lines_impl(
8301 &mut self,
8302 insert_whitespace: bool,
8303 window: &mut Window,
8304 cx: &mut Context<Self>,
8305 ) {
8306 if self.read_only(cx) {
8307 return;
8308 }
8309 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
8310 for selection in self.selections.all::<Point>(cx) {
8311 let start = MultiBufferRow(selection.start.row);
8312 // Treat single line selections as if they include the next line. Otherwise this action
8313 // would do nothing for single line selections individual cursors.
8314 let end = if selection.start.row == selection.end.row {
8315 MultiBufferRow(selection.start.row + 1)
8316 } else {
8317 MultiBufferRow(selection.end.row)
8318 };
8319
8320 if let Some(last_row_range) = row_ranges.last_mut() {
8321 if start <= last_row_range.end {
8322 last_row_range.end = end;
8323 continue;
8324 }
8325 }
8326 row_ranges.push(start..end);
8327 }
8328
8329 let snapshot = self.buffer.read(cx).snapshot(cx);
8330 let mut cursor_positions = Vec::new();
8331 for row_range in &row_ranges {
8332 let anchor = snapshot.anchor_before(Point::new(
8333 row_range.end.previous_row().0,
8334 snapshot.line_len(row_range.end.previous_row()),
8335 ));
8336 cursor_positions.push(anchor..anchor);
8337 }
8338
8339 self.transact(window, cx, |this, window, cx| {
8340 for row_range in row_ranges.into_iter().rev() {
8341 for row in row_range.iter_rows().rev() {
8342 let end_of_line = Point::new(row.0, snapshot.line_len(row));
8343 let next_line_row = row.next_row();
8344 let indent = snapshot.indent_size_for_line(next_line_row);
8345 let start_of_next_line = Point::new(next_line_row.0, indent.len);
8346
8347 let replace =
8348 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
8349 " "
8350 } else {
8351 ""
8352 };
8353
8354 this.buffer.update(cx, |buffer, cx| {
8355 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
8356 });
8357 }
8358 }
8359
8360 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8361 s.select_anchor_ranges(cursor_positions)
8362 });
8363 });
8364 }
8365
8366 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
8367 self.join_lines_impl(true, window, cx);
8368 }
8369
8370 pub fn sort_lines_case_sensitive(
8371 &mut self,
8372 _: &SortLinesCaseSensitive,
8373 window: &mut Window,
8374 cx: &mut Context<Self>,
8375 ) {
8376 self.manipulate_lines(window, cx, |lines| lines.sort())
8377 }
8378
8379 pub fn sort_lines_case_insensitive(
8380 &mut self,
8381 _: &SortLinesCaseInsensitive,
8382 window: &mut Window,
8383 cx: &mut Context<Self>,
8384 ) {
8385 self.manipulate_lines(window, cx, |lines| {
8386 lines.sort_by_key(|line| line.to_lowercase())
8387 })
8388 }
8389
8390 pub fn unique_lines_case_insensitive(
8391 &mut self,
8392 _: &UniqueLinesCaseInsensitive,
8393 window: &mut Window,
8394 cx: &mut Context<Self>,
8395 ) {
8396 self.manipulate_lines(window, cx, |lines| {
8397 let mut seen = HashSet::default();
8398 lines.retain(|line| seen.insert(line.to_lowercase()));
8399 })
8400 }
8401
8402 pub fn unique_lines_case_sensitive(
8403 &mut self,
8404 _: &UniqueLinesCaseSensitive,
8405 window: &mut Window,
8406 cx: &mut Context<Self>,
8407 ) {
8408 self.manipulate_lines(window, cx, |lines| {
8409 let mut seen = HashSet::default();
8410 lines.retain(|line| seen.insert(*line));
8411 })
8412 }
8413
8414 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
8415 let Some(project) = self.project.clone() else {
8416 return;
8417 };
8418 self.reload(project, window, cx)
8419 .detach_and_notify_err(window, cx);
8420 }
8421
8422 pub fn restore_file(
8423 &mut self,
8424 _: &::git::RestoreFile,
8425 window: &mut Window,
8426 cx: &mut Context<Self>,
8427 ) {
8428 let mut buffer_ids = HashSet::default();
8429 let snapshot = self.buffer().read(cx).snapshot(cx);
8430 for selection in self.selections.all::<usize>(cx) {
8431 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
8432 }
8433
8434 let buffer = self.buffer().read(cx);
8435 let ranges = buffer_ids
8436 .into_iter()
8437 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
8438 .collect::<Vec<_>>();
8439
8440 self.restore_hunks_in_ranges(ranges, window, cx);
8441 }
8442
8443 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
8444 let selections = self
8445 .selections
8446 .all(cx)
8447 .into_iter()
8448 .map(|s| s.range())
8449 .collect();
8450 self.restore_hunks_in_ranges(selections, window, cx);
8451 }
8452
8453 pub fn restore_hunks_in_ranges(
8454 &mut self,
8455 ranges: Vec<Range<Point>>,
8456 window: &mut Window,
8457 cx: &mut Context<Editor>,
8458 ) {
8459 let mut revert_changes = HashMap::default();
8460 let chunk_by = self
8461 .snapshot(window, cx)
8462 .hunks_for_ranges(ranges)
8463 .into_iter()
8464 .chunk_by(|hunk| hunk.buffer_id);
8465 for (buffer_id, hunks) in &chunk_by {
8466 let hunks = hunks.collect::<Vec<_>>();
8467 for hunk in &hunks {
8468 self.prepare_restore_change(&mut revert_changes, hunk, cx);
8469 }
8470 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
8471 }
8472 drop(chunk_by);
8473 if !revert_changes.is_empty() {
8474 self.transact(window, cx, |editor, window, cx| {
8475 editor.restore(revert_changes, window, cx);
8476 });
8477 }
8478 }
8479
8480 pub fn open_active_item_in_terminal(
8481 &mut self,
8482 _: &OpenInTerminal,
8483 window: &mut Window,
8484 cx: &mut Context<Self>,
8485 ) {
8486 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
8487 let project_path = buffer.read(cx).project_path(cx)?;
8488 let project = self.project.as_ref()?.read(cx);
8489 let entry = project.entry_for_path(&project_path, cx)?;
8490 let parent = match &entry.canonical_path {
8491 Some(canonical_path) => canonical_path.to_path_buf(),
8492 None => project.absolute_path(&project_path, cx)?,
8493 }
8494 .parent()?
8495 .to_path_buf();
8496 Some(parent)
8497 }) {
8498 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
8499 }
8500 }
8501
8502 fn set_breakpoint_context_menu(
8503 &mut self,
8504 display_row: DisplayRow,
8505 position: Option<Anchor>,
8506 clicked_point: gpui::Point<Pixels>,
8507 window: &mut Window,
8508 cx: &mut Context<Self>,
8509 ) {
8510 if !cx.has_flag::<Debugger>() {
8511 return;
8512 }
8513 let source = self
8514 .buffer
8515 .read(cx)
8516 .snapshot(cx)
8517 .anchor_before(Point::new(display_row.0, 0u32));
8518
8519 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
8520
8521 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
8522 self,
8523 source,
8524 clicked_point,
8525 context_menu,
8526 window,
8527 cx,
8528 );
8529 }
8530
8531 fn add_edit_breakpoint_block(
8532 &mut self,
8533 anchor: Anchor,
8534 breakpoint: &Breakpoint,
8535 window: &mut Window,
8536 cx: &mut Context<Self>,
8537 ) {
8538 let weak_editor = cx.weak_entity();
8539 let bp_prompt = cx.new(|cx| {
8540 BreakpointPromptEditor::new(weak_editor, anchor, breakpoint.clone(), window, cx)
8541 });
8542
8543 let height = bp_prompt.update(cx, |this, cx| {
8544 this.prompt
8545 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
8546 });
8547 let cloned_prompt = bp_prompt.clone();
8548 let blocks = vec![BlockProperties {
8549 style: BlockStyle::Sticky,
8550 placement: BlockPlacement::Above(anchor),
8551 height,
8552 render: Arc::new(move |cx| {
8553 *cloned_prompt.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
8554 cloned_prompt.clone().into_any_element()
8555 }),
8556 priority: 0,
8557 }];
8558
8559 let focus_handle = bp_prompt.focus_handle(cx);
8560 window.focus(&focus_handle);
8561
8562 let block_ids = self.insert_blocks(blocks, None, cx);
8563 bp_prompt.update(cx, |prompt, _| {
8564 prompt.add_block_ids(block_ids);
8565 });
8566 }
8567
8568 fn breakpoint_at_cursor_head(
8569 &self,
8570 window: &mut Window,
8571 cx: &mut Context<Self>,
8572 ) -> Option<(Anchor, Breakpoint)> {
8573 let cursor_position: Point = self.selections.newest(cx).head();
8574 self.breakpoint_at_row(cursor_position.row, window, cx)
8575 }
8576
8577 pub(crate) fn breakpoint_at_row(
8578 &self,
8579 row: u32,
8580 window: &mut Window,
8581 cx: &mut Context<Self>,
8582 ) -> Option<(Anchor, Breakpoint)> {
8583 let snapshot = self.snapshot(window, cx);
8584 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
8585
8586 let project = self.project.clone()?;
8587
8588 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
8589 snapshot
8590 .buffer_snapshot
8591 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
8592 })?;
8593
8594 let enclosing_excerpt = breakpoint_position.excerpt_id;
8595 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
8596 let buffer_snapshot = buffer.read(cx).snapshot();
8597
8598 let row = buffer_snapshot
8599 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
8600 .row;
8601
8602 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
8603 let anchor_end = snapshot
8604 .buffer_snapshot
8605 .anchor_before(Point::new(row, line_len));
8606
8607 let bp = self
8608 .breakpoint_store
8609 .as_ref()?
8610 .read_with(cx, |breakpoint_store, cx| {
8611 breakpoint_store
8612 .breakpoints(
8613 &buffer,
8614 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
8615 &buffer_snapshot,
8616 cx,
8617 )
8618 .next()
8619 .and_then(|(anchor, bp)| {
8620 let breakpoint_row = buffer_snapshot
8621 .summary_for_anchor::<text::PointUtf16>(anchor)
8622 .row;
8623
8624 if breakpoint_row == row {
8625 snapshot
8626 .buffer_snapshot
8627 .anchor_in_excerpt(enclosing_excerpt, *anchor)
8628 .map(|anchor| (anchor, bp.clone()))
8629 } else {
8630 None
8631 }
8632 })
8633 });
8634 bp
8635 }
8636
8637 pub fn edit_log_breakpoint(
8638 &mut self,
8639 _: &EditLogBreakpoint,
8640 window: &mut Window,
8641 cx: &mut Context<Self>,
8642 ) {
8643 let (anchor, bp) = self
8644 .breakpoint_at_cursor_head(window, cx)
8645 .unwrap_or_else(|| {
8646 let cursor_position: Point = self.selections.newest(cx).head();
8647
8648 let breakpoint_position = self
8649 .snapshot(window, cx)
8650 .display_snapshot
8651 .buffer_snapshot
8652 .anchor_after(Point::new(cursor_position.row, 0));
8653
8654 (
8655 breakpoint_position,
8656 Breakpoint {
8657 kind: BreakpointKind::Standard,
8658 state: BreakpointState::Enabled,
8659 },
8660 )
8661 });
8662
8663 self.add_edit_breakpoint_block(anchor, &bp, window, cx);
8664 }
8665
8666 pub fn enable_breakpoint(
8667 &mut self,
8668 _: &crate::actions::EnableBreakpoint,
8669 window: &mut Window,
8670 cx: &mut Context<Self>,
8671 ) {
8672 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8673 if breakpoint.is_disabled() {
8674 self.edit_breakpoint_at_anchor(
8675 anchor,
8676 breakpoint,
8677 BreakpointEditAction::InvertState,
8678 cx,
8679 );
8680 }
8681 }
8682 }
8683
8684 pub fn disable_breakpoint(
8685 &mut self,
8686 _: &crate::actions::DisableBreakpoint,
8687 window: &mut Window,
8688 cx: &mut Context<Self>,
8689 ) {
8690 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8691 if breakpoint.is_enabled() {
8692 self.edit_breakpoint_at_anchor(
8693 anchor,
8694 breakpoint,
8695 BreakpointEditAction::InvertState,
8696 cx,
8697 );
8698 }
8699 }
8700 }
8701
8702 pub fn toggle_breakpoint(
8703 &mut self,
8704 _: &crate::actions::ToggleBreakpoint,
8705 window: &mut Window,
8706 cx: &mut Context<Self>,
8707 ) {
8708 let edit_action = BreakpointEditAction::Toggle;
8709
8710 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8711 self.edit_breakpoint_at_anchor(anchor, breakpoint, edit_action, cx);
8712 } else {
8713 let cursor_position: Point = self.selections.newest(cx).head();
8714
8715 let breakpoint_position = self
8716 .snapshot(window, cx)
8717 .display_snapshot
8718 .buffer_snapshot
8719 .anchor_after(Point::new(cursor_position.row, 0));
8720
8721 self.edit_breakpoint_at_anchor(
8722 breakpoint_position,
8723 Breakpoint::new_standard(),
8724 edit_action,
8725 cx,
8726 );
8727 }
8728 }
8729
8730 pub fn edit_breakpoint_at_anchor(
8731 &mut self,
8732 breakpoint_position: Anchor,
8733 breakpoint: Breakpoint,
8734 edit_action: BreakpointEditAction,
8735 cx: &mut Context<Self>,
8736 ) {
8737 let Some(breakpoint_store) = &self.breakpoint_store else {
8738 return;
8739 };
8740
8741 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
8742 if breakpoint_position == Anchor::min() {
8743 self.buffer()
8744 .read(cx)
8745 .excerpt_buffer_ids()
8746 .into_iter()
8747 .next()
8748 } else {
8749 None
8750 }
8751 }) else {
8752 return;
8753 };
8754
8755 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
8756 return;
8757 };
8758
8759 breakpoint_store.update(cx, |breakpoint_store, cx| {
8760 breakpoint_store.toggle_breakpoint(
8761 buffer,
8762 (breakpoint_position.text_anchor, breakpoint),
8763 edit_action,
8764 cx,
8765 );
8766 });
8767
8768 cx.notify();
8769 }
8770
8771 #[cfg(any(test, feature = "test-support"))]
8772 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
8773 self.breakpoint_store.clone()
8774 }
8775
8776 pub fn prepare_restore_change(
8777 &self,
8778 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
8779 hunk: &MultiBufferDiffHunk,
8780 cx: &mut App,
8781 ) -> Option<()> {
8782 if hunk.is_created_file() {
8783 return None;
8784 }
8785 let buffer = self.buffer.read(cx);
8786 let diff = buffer.diff_for(hunk.buffer_id)?;
8787 let buffer = buffer.buffer(hunk.buffer_id)?;
8788 let buffer = buffer.read(cx);
8789 let original_text = diff
8790 .read(cx)
8791 .base_text()
8792 .as_rope()
8793 .slice(hunk.diff_base_byte_range.clone());
8794 let buffer_snapshot = buffer.snapshot();
8795 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
8796 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
8797 probe
8798 .0
8799 .start
8800 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
8801 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
8802 }) {
8803 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
8804 Some(())
8805 } else {
8806 None
8807 }
8808 }
8809
8810 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
8811 self.manipulate_lines(window, cx, |lines| lines.reverse())
8812 }
8813
8814 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
8815 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
8816 }
8817
8818 fn manipulate_lines<Fn>(
8819 &mut self,
8820 window: &mut Window,
8821 cx: &mut Context<Self>,
8822 mut callback: Fn,
8823 ) where
8824 Fn: FnMut(&mut Vec<&str>),
8825 {
8826 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8827 let buffer = self.buffer.read(cx).snapshot(cx);
8828
8829 let mut edits = Vec::new();
8830
8831 let selections = self.selections.all::<Point>(cx);
8832 let mut selections = selections.iter().peekable();
8833 let mut contiguous_row_selections = Vec::new();
8834 let mut new_selections = Vec::new();
8835 let mut added_lines = 0;
8836 let mut removed_lines = 0;
8837
8838 while let Some(selection) = selections.next() {
8839 let (start_row, end_row) = consume_contiguous_rows(
8840 &mut contiguous_row_selections,
8841 selection,
8842 &display_map,
8843 &mut selections,
8844 );
8845
8846 let start_point = Point::new(start_row.0, 0);
8847 let end_point = Point::new(
8848 end_row.previous_row().0,
8849 buffer.line_len(end_row.previous_row()),
8850 );
8851 let text = buffer
8852 .text_for_range(start_point..end_point)
8853 .collect::<String>();
8854
8855 let mut lines = text.split('\n').collect_vec();
8856
8857 let lines_before = lines.len();
8858 callback(&mut lines);
8859 let lines_after = lines.len();
8860
8861 edits.push((start_point..end_point, lines.join("\n")));
8862
8863 // Selections must change based on added and removed line count
8864 let start_row =
8865 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
8866 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
8867 new_selections.push(Selection {
8868 id: selection.id,
8869 start: start_row,
8870 end: end_row,
8871 goal: SelectionGoal::None,
8872 reversed: selection.reversed,
8873 });
8874
8875 if lines_after > lines_before {
8876 added_lines += lines_after - lines_before;
8877 } else if lines_before > lines_after {
8878 removed_lines += lines_before - lines_after;
8879 }
8880 }
8881
8882 self.transact(window, cx, |this, window, cx| {
8883 let buffer = this.buffer.update(cx, |buffer, cx| {
8884 buffer.edit(edits, None, cx);
8885 buffer.snapshot(cx)
8886 });
8887
8888 // Recalculate offsets on newly edited buffer
8889 let new_selections = new_selections
8890 .iter()
8891 .map(|s| {
8892 let start_point = Point::new(s.start.0, 0);
8893 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
8894 Selection {
8895 id: s.id,
8896 start: buffer.point_to_offset(start_point),
8897 end: buffer.point_to_offset(end_point),
8898 goal: s.goal,
8899 reversed: s.reversed,
8900 }
8901 })
8902 .collect();
8903
8904 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8905 s.select(new_selections);
8906 });
8907
8908 this.request_autoscroll(Autoscroll::fit(), cx);
8909 });
8910 }
8911
8912 pub fn convert_to_upper_case(
8913 &mut self,
8914 _: &ConvertToUpperCase,
8915 window: &mut Window,
8916 cx: &mut Context<Self>,
8917 ) {
8918 self.manipulate_text(window, cx, |text| text.to_uppercase())
8919 }
8920
8921 pub fn convert_to_lower_case(
8922 &mut self,
8923 _: &ConvertToLowerCase,
8924 window: &mut Window,
8925 cx: &mut Context<Self>,
8926 ) {
8927 self.manipulate_text(window, cx, |text| text.to_lowercase())
8928 }
8929
8930 pub fn convert_to_title_case(
8931 &mut self,
8932 _: &ConvertToTitleCase,
8933 window: &mut Window,
8934 cx: &mut Context<Self>,
8935 ) {
8936 self.manipulate_text(window, cx, |text| {
8937 text.split('\n')
8938 .map(|line| line.to_case(Case::Title))
8939 .join("\n")
8940 })
8941 }
8942
8943 pub fn convert_to_snake_case(
8944 &mut self,
8945 _: &ConvertToSnakeCase,
8946 window: &mut Window,
8947 cx: &mut Context<Self>,
8948 ) {
8949 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
8950 }
8951
8952 pub fn convert_to_kebab_case(
8953 &mut self,
8954 _: &ConvertToKebabCase,
8955 window: &mut Window,
8956 cx: &mut Context<Self>,
8957 ) {
8958 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
8959 }
8960
8961 pub fn convert_to_upper_camel_case(
8962 &mut self,
8963 _: &ConvertToUpperCamelCase,
8964 window: &mut Window,
8965 cx: &mut Context<Self>,
8966 ) {
8967 self.manipulate_text(window, cx, |text| {
8968 text.split('\n')
8969 .map(|line| line.to_case(Case::UpperCamel))
8970 .join("\n")
8971 })
8972 }
8973
8974 pub fn convert_to_lower_camel_case(
8975 &mut self,
8976 _: &ConvertToLowerCamelCase,
8977 window: &mut Window,
8978 cx: &mut Context<Self>,
8979 ) {
8980 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
8981 }
8982
8983 pub fn convert_to_opposite_case(
8984 &mut self,
8985 _: &ConvertToOppositeCase,
8986 window: &mut Window,
8987 cx: &mut Context<Self>,
8988 ) {
8989 self.manipulate_text(window, cx, |text| {
8990 text.chars()
8991 .fold(String::with_capacity(text.len()), |mut t, c| {
8992 if c.is_uppercase() {
8993 t.extend(c.to_lowercase());
8994 } else {
8995 t.extend(c.to_uppercase());
8996 }
8997 t
8998 })
8999 })
9000 }
9001
9002 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
9003 where
9004 Fn: FnMut(&str) -> String,
9005 {
9006 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9007 let buffer = self.buffer.read(cx).snapshot(cx);
9008
9009 let mut new_selections = Vec::new();
9010 let mut edits = Vec::new();
9011 let mut selection_adjustment = 0i32;
9012
9013 for selection in self.selections.all::<usize>(cx) {
9014 let selection_is_empty = selection.is_empty();
9015
9016 let (start, end) = if selection_is_empty {
9017 let word_range = movement::surrounding_word(
9018 &display_map,
9019 selection.start.to_display_point(&display_map),
9020 );
9021 let start = word_range.start.to_offset(&display_map, Bias::Left);
9022 let end = word_range.end.to_offset(&display_map, Bias::Left);
9023 (start, end)
9024 } else {
9025 (selection.start, selection.end)
9026 };
9027
9028 let text = buffer.text_for_range(start..end).collect::<String>();
9029 let old_length = text.len() as i32;
9030 let text = callback(&text);
9031
9032 new_selections.push(Selection {
9033 start: (start as i32 - selection_adjustment) as usize,
9034 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
9035 goal: SelectionGoal::None,
9036 ..selection
9037 });
9038
9039 selection_adjustment += old_length - text.len() as i32;
9040
9041 edits.push((start..end, text));
9042 }
9043
9044 self.transact(window, cx, |this, window, cx| {
9045 this.buffer.update(cx, |buffer, cx| {
9046 buffer.edit(edits, None, cx);
9047 });
9048
9049 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9050 s.select(new_selections);
9051 });
9052
9053 this.request_autoscroll(Autoscroll::fit(), cx);
9054 });
9055 }
9056
9057 pub fn duplicate(
9058 &mut self,
9059 upwards: bool,
9060 whole_lines: bool,
9061 window: &mut Window,
9062 cx: &mut Context<Self>,
9063 ) {
9064 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9065 let buffer = &display_map.buffer_snapshot;
9066 let selections = self.selections.all::<Point>(cx);
9067
9068 let mut edits = Vec::new();
9069 let mut selections_iter = selections.iter().peekable();
9070 while let Some(selection) = selections_iter.next() {
9071 let mut rows = selection.spanned_rows(false, &display_map);
9072 // duplicate line-wise
9073 if whole_lines || selection.start == selection.end {
9074 // Avoid duplicating the same lines twice.
9075 while let Some(next_selection) = selections_iter.peek() {
9076 let next_rows = next_selection.spanned_rows(false, &display_map);
9077 if next_rows.start < rows.end {
9078 rows.end = next_rows.end;
9079 selections_iter.next().unwrap();
9080 } else {
9081 break;
9082 }
9083 }
9084
9085 // Copy the text from the selected row region and splice it either at the start
9086 // or end of the region.
9087 let start = Point::new(rows.start.0, 0);
9088 let end = Point::new(
9089 rows.end.previous_row().0,
9090 buffer.line_len(rows.end.previous_row()),
9091 );
9092 let text = buffer
9093 .text_for_range(start..end)
9094 .chain(Some("\n"))
9095 .collect::<String>();
9096 let insert_location = if upwards {
9097 Point::new(rows.end.0, 0)
9098 } else {
9099 start
9100 };
9101 edits.push((insert_location..insert_location, text));
9102 } else {
9103 // duplicate character-wise
9104 let start = selection.start;
9105 let end = selection.end;
9106 let text = buffer.text_for_range(start..end).collect::<String>();
9107 edits.push((selection.end..selection.end, text));
9108 }
9109 }
9110
9111 self.transact(window, cx, |this, _, cx| {
9112 this.buffer.update(cx, |buffer, cx| {
9113 buffer.edit(edits, None, cx);
9114 });
9115
9116 this.request_autoscroll(Autoscroll::fit(), cx);
9117 });
9118 }
9119
9120 pub fn duplicate_line_up(
9121 &mut self,
9122 _: &DuplicateLineUp,
9123 window: &mut Window,
9124 cx: &mut Context<Self>,
9125 ) {
9126 self.duplicate(true, true, window, cx);
9127 }
9128
9129 pub fn duplicate_line_down(
9130 &mut self,
9131 _: &DuplicateLineDown,
9132 window: &mut Window,
9133 cx: &mut Context<Self>,
9134 ) {
9135 self.duplicate(false, true, window, cx);
9136 }
9137
9138 pub fn duplicate_selection(
9139 &mut self,
9140 _: &DuplicateSelection,
9141 window: &mut Window,
9142 cx: &mut Context<Self>,
9143 ) {
9144 self.duplicate(false, false, window, cx);
9145 }
9146
9147 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
9148 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9149 let buffer = self.buffer.read(cx).snapshot(cx);
9150
9151 let mut edits = Vec::new();
9152 let mut unfold_ranges = Vec::new();
9153 let mut refold_creases = Vec::new();
9154
9155 let selections = self.selections.all::<Point>(cx);
9156 let mut selections = selections.iter().peekable();
9157 let mut contiguous_row_selections = Vec::new();
9158 let mut new_selections = Vec::new();
9159
9160 while let Some(selection) = selections.next() {
9161 // Find all the selections that span a contiguous row range
9162 let (start_row, end_row) = consume_contiguous_rows(
9163 &mut contiguous_row_selections,
9164 selection,
9165 &display_map,
9166 &mut selections,
9167 );
9168
9169 // Move the text spanned by the row range to be before the line preceding the row range
9170 if start_row.0 > 0 {
9171 let range_to_move = Point::new(
9172 start_row.previous_row().0,
9173 buffer.line_len(start_row.previous_row()),
9174 )
9175 ..Point::new(
9176 end_row.previous_row().0,
9177 buffer.line_len(end_row.previous_row()),
9178 );
9179 let insertion_point = display_map
9180 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
9181 .0;
9182
9183 // Don't move lines across excerpts
9184 if buffer
9185 .excerpt_containing(insertion_point..range_to_move.end)
9186 .is_some()
9187 {
9188 let text = buffer
9189 .text_for_range(range_to_move.clone())
9190 .flat_map(|s| s.chars())
9191 .skip(1)
9192 .chain(['\n'])
9193 .collect::<String>();
9194
9195 edits.push((
9196 buffer.anchor_after(range_to_move.start)
9197 ..buffer.anchor_before(range_to_move.end),
9198 String::new(),
9199 ));
9200 let insertion_anchor = buffer.anchor_after(insertion_point);
9201 edits.push((insertion_anchor..insertion_anchor, text));
9202
9203 let row_delta = range_to_move.start.row - insertion_point.row + 1;
9204
9205 // Move selections up
9206 new_selections.extend(contiguous_row_selections.drain(..).map(
9207 |mut selection| {
9208 selection.start.row -= row_delta;
9209 selection.end.row -= row_delta;
9210 selection
9211 },
9212 ));
9213
9214 // Move folds up
9215 unfold_ranges.push(range_to_move.clone());
9216 for fold in display_map.folds_in_range(
9217 buffer.anchor_before(range_to_move.start)
9218 ..buffer.anchor_after(range_to_move.end),
9219 ) {
9220 let mut start = fold.range.start.to_point(&buffer);
9221 let mut end = fold.range.end.to_point(&buffer);
9222 start.row -= row_delta;
9223 end.row -= row_delta;
9224 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9225 }
9226 }
9227 }
9228
9229 // If we didn't move line(s), preserve the existing selections
9230 new_selections.append(&mut contiguous_row_selections);
9231 }
9232
9233 self.transact(window, cx, |this, window, cx| {
9234 this.unfold_ranges(&unfold_ranges, true, true, cx);
9235 this.buffer.update(cx, |buffer, cx| {
9236 for (range, text) in edits {
9237 buffer.edit([(range, text)], None, cx);
9238 }
9239 });
9240 this.fold_creases(refold_creases, true, window, cx);
9241 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9242 s.select(new_selections);
9243 })
9244 });
9245 }
9246
9247 pub fn move_line_down(
9248 &mut self,
9249 _: &MoveLineDown,
9250 window: &mut Window,
9251 cx: &mut Context<Self>,
9252 ) {
9253 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9254 let buffer = self.buffer.read(cx).snapshot(cx);
9255
9256 let mut edits = Vec::new();
9257 let mut unfold_ranges = Vec::new();
9258 let mut refold_creases = Vec::new();
9259
9260 let selections = self.selections.all::<Point>(cx);
9261 let mut selections = selections.iter().peekable();
9262 let mut contiguous_row_selections = Vec::new();
9263 let mut new_selections = Vec::new();
9264
9265 while let Some(selection) = selections.next() {
9266 // Find all the selections that span a contiguous row range
9267 let (start_row, end_row) = consume_contiguous_rows(
9268 &mut contiguous_row_selections,
9269 selection,
9270 &display_map,
9271 &mut selections,
9272 );
9273
9274 // Move the text spanned by the row range to be after the last line of the row range
9275 if end_row.0 <= buffer.max_point().row {
9276 let range_to_move =
9277 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
9278 let insertion_point = display_map
9279 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
9280 .0;
9281
9282 // Don't move lines across excerpt boundaries
9283 if buffer
9284 .excerpt_containing(range_to_move.start..insertion_point)
9285 .is_some()
9286 {
9287 let mut text = String::from("\n");
9288 text.extend(buffer.text_for_range(range_to_move.clone()));
9289 text.pop(); // Drop trailing newline
9290 edits.push((
9291 buffer.anchor_after(range_to_move.start)
9292 ..buffer.anchor_before(range_to_move.end),
9293 String::new(),
9294 ));
9295 let insertion_anchor = buffer.anchor_after(insertion_point);
9296 edits.push((insertion_anchor..insertion_anchor, text));
9297
9298 let row_delta = insertion_point.row - range_to_move.end.row + 1;
9299
9300 // Move selections down
9301 new_selections.extend(contiguous_row_selections.drain(..).map(
9302 |mut selection| {
9303 selection.start.row += row_delta;
9304 selection.end.row += row_delta;
9305 selection
9306 },
9307 ));
9308
9309 // Move folds down
9310 unfold_ranges.push(range_to_move.clone());
9311 for fold in display_map.folds_in_range(
9312 buffer.anchor_before(range_to_move.start)
9313 ..buffer.anchor_after(range_to_move.end),
9314 ) {
9315 let mut start = fold.range.start.to_point(&buffer);
9316 let mut end = fold.range.end.to_point(&buffer);
9317 start.row += row_delta;
9318 end.row += row_delta;
9319 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9320 }
9321 }
9322 }
9323
9324 // If we didn't move line(s), preserve the existing selections
9325 new_selections.append(&mut contiguous_row_selections);
9326 }
9327
9328 self.transact(window, cx, |this, window, cx| {
9329 this.unfold_ranges(&unfold_ranges, true, true, cx);
9330 this.buffer.update(cx, |buffer, cx| {
9331 for (range, text) in edits {
9332 buffer.edit([(range, text)], None, cx);
9333 }
9334 });
9335 this.fold_creases(refold_creases, true, window, cx);
9336 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9337 s.select(new_selections)
9338 });
9339 });
9340 }
9341
9342 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
9343 let text_layout_details = &self.text_layout_details(window);
9344 self.transact(window, cx, |this, window, cx| {
9345 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9346 let mut edits: Vec<(Range<usize>, String)> = Default::default();
9347 let line_mode = s.line_mode;
9348 s.move_with(|display_map, selection| {
9349 if !selection.is_empty() || line_mode {
9350 return;
9351 }
9352
9353 let mut head = selection.head();
9354 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
9355 if head.column() == display_map.line_len(head.row()) {
9356 transpose_offset = display_map
9357 .buffer_snapshot
9358 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9359 }
9360
9361 if transpose_offset == 0 {
9362 return;
9363 }
9364
9365 *head.column_mut() += 1;
9366 head = display_map.clip_point(head, Bias::Right);
9367 let goal = SelectionGoal::HorizontalPosition(
9368 display_map
9369 .x_for_display_point(head, text_layout_details)
9370 .into(),
9371 );
9372 selection.collapse_to(head, goal);
9373
9374 let transpose_start = display_map
9375 .buffer_snapshot
9376 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9377 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
9378 let transpose_end = display_map
9379 .buffer_snapshot
9380 .clip_offset(transpose_offset + 1, Bias::Right);
9381 if let Some(ch) =
9382 display_map.buffer_snapshot.chars_at(transpose_start).next()
9383 {
9384 edits.push((transpose_start..transpose_offset, String::new()));
9385 edits.push((transpose_end..transpose_end, ch.to_string()));
9386 }
9387 }
9388 });
9389 edits
9390 });
9391 this.buffer
9392 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9393 let selections = this.selections.all::<usize>(cx);
9394 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9395 s.select(selections);
9396 });
9397 });
9398 }
9399
9400 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
9401 self.rewrap_impl(RewrapOptions::default(), cx)
9402 }
9403
9404 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
9405 let buffer = self.buffer.read(cx).snapshot(cx);
9406 let selections = self.selections.all::<Point>(cx);
9407 let mut selections = selections.iter().peekable();
9408
9409 let mut edits = Vec::new();
9410 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
9411
9412 while let Some(selection) = selections.next() {
9413 let mut start_row = selection.start.row;
9414 let mut end_row = selection.end.row;
9415
9416 // Skip selections that overlap with a range that has already been rewrapped.
9417 let selection_range = start_row..end_row;
9418 if rewrapped_row_ranges
9419 .iter()
9420 .any(|range| range.overlaps(&selection_range))
9421 {
9422 continue;
9423 }
9424
9425 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
9426
9427 // Since not all lines in the selection may be at the same indent
9428 // level, choose the indent size that is the most common between all
9429 // of the lines.
9430 //
9431 // If there is a tie, we use the deepest indent.
9432 let (indent_size, indent_end) = {
9433 let mut indent_size_occurrences = HashMap::default();
9434 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
9435
9436 for row in start_row..=end_row {
9437 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
9438 rows_by_indent_size.entry(indent).or_default().push(row);
9439 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
9440 }
9441
9442 let indent_size = indent_size_occurrences
9443 .into_iter()
9444 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
9445 .map(|(indent, _)| indent)
9446 .unwrap_or_default();
9447 let row = rows_by_indent_size[&indent_size][0];
9448 let indent_end = Point::new(row, indent_size.len);
9449
9450 (indent_size, indent_end)
9451 };
9452
9453 let mut line_prefix = indent_size.chars().collect::<String>();
9454
9455 let mut inside_comment = false;
9456 if let Some(comment_prefix) =
9457 buffer
9458 .language_scope_at(selection.head())
9459 .and_then(|language| {
9460 language
9461 .line_comment_prefixes()
9462 .iter()
9463 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
9464 .cloned()
9465 })
9466 {
9467 line_prefix.push_str(&comment_prefix);
9468 inside_comment = true;
9469 }
9470
9471 let language_settings = buffer.language_settings_at(selection.head(), cx);
9472 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
9473 RewrapBehavior::InComments => inside_comment,
9474 RewrapBehavior::InSelections => !selection.is_empty(),
9475 RewrapBehavior::Anywhere => true,
9476 };
9477
9478 let should_rewrap = options.override_language_settings
9479 || allow_rewrap_based_on_language
9480 || self.hard_wrap.is_some();
9481 if !should_rewrap {
9482 continue;
9483 }
9484
9485 if selection.is_empty() {
9486 'expand_upwards: while start_row > 0 {
9487 let prev_row = start_row - 1;
9488 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
9489 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
9490 {
9491 start_row = prev_row;
9492 } else {
9493 break 'expand_upwards;
9494 }
9495 }
9496
9497 'expand_downwards: while end_row < buffer.max_point().row {
9498 let next_row = end_row + 1;
9499 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
9500 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
9501 {
9502 end_row = next_row;
9503 } else {
9504 break 'expand_downwards;
9505 }
9506 }
9507 }
9508
9509 let start = Point::new(start_row, 0);
9510 let start_offset = start.to_offset(&buffer);
9511 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
9512 let selection_text = buffer.text_for_range(start..end).collect::<String>();
9513 let Some(lines_without_prefixes) = selection_text
9514 .lines()
9515 .map(|line| {
9516 line.strip_prefix(&line_prefix)
9517 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
9518 .ok_or_else(|| {
9519 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
9520 })
9521 })
9522 .collect::<Result<Vec<_>, _>>()
9523 .log_err()
9524 else {
9525 continue;
9526 };
9527
9528 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
9529 buffer
9530 .language_settings_at(Point::new(start_row, 0), cx)
9531 .preferred_line_length as usize
9532 });
9533 let wrapped_text = wrap_with_prefix(
9534 line_prefix,
9535 lines_without_prefixes.join("\n"),
9536 wrap_column,
9537 tab_size,
9538 options.preserve_existing_whitespace,
9539 );
9540
9541 // TODO: should always use char-based diff while still supporting cursor behavior that
9542 // matches vim.
9543 let mut diff_options = DiffOptions::default();
9544 if options.override_language_settings {
9545 diff_options.max_word_diff_len = 0;
9546 diff_options.max_word_diff_line_count = 0;
9547 } else {
9548 diff_options.max_word_diff_len = usize::MAX;
9549 diff_options.max_word_diff_line_count = usize::MAX;
9550 }
9551
9552 for (old_range, new_text) in
9553 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
9554 {
9555 let edit_start = buffer.anchor_after(start_offset + old_range.start);
9556 let edit_end = buffer.anchor_after(start_offset + old_range.end);
9557 edits.push((edit_start..edit_end, new_text));
9558 }
9559
9560 rewrapped_row_ranges.push(start_row..=end_row);
9561 }
9562
9563 self.buffer
9564 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9565 }
9566
9567 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
9568 let mut text = String::new();
9569 let buffer = self.buffer.read(cx).snapshot(cx);
9570 let mut selections = self.selections.all::<Point>(cx);
9571 let mut clipboard_selections = Vec::with_capacity(selections.len());
9572 {
9573 let max_point = buffer.max_point();
9574 let mut is_first = true;
9575 for selection in &mut selections {
9576 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9577 if is_entire_line {
9578 selection.start = Point::new(selection.start.row, 0);
9579 if !selection.is_empty() && selection.end.column == 0 {
9580 selection.end = cmp::min(max_point, selection.end);
9581 } else {
9582 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
9583 }
9584 selection.goal = SelectionGoal::None;
9585 }
9586 if is_first {
9587 is_first = false;
9588 } else {
9589 text += "\n";
9590 }
9591 let mut len = 0;
9592 for chunk in buffer.text_for_range(selection.start..selection.end) {
9593 text.push_str(chunk);
9594 len += chunk.len();
9595 }
9596 clipboard_selections.push(ClipboardSelection {
9597 len,
9598 is_entire_line,
9599 first_line_indent: buffer
9600 .indent_size_for_line(MultiBufferRow(selection.start.row))
9601 .len,
9602 });
9603 }
9604 }
9605
9606 self.transact(window, cx, |this, window, cx| {
9607 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9608 s.select(selections);
9609 });
9610 this.insert("", window, cx);
9611 });
9612 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
9613 }
9614
9615 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
9616 let item = self.cut_common(window, cx);
9617 cx.write_to_clipboard(item);
9618 }
9619
9620 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
9621 self.change_selections(None, window, cx, |s| {
9622 s.move_with(|snapshot, sel| {
9623 if sel.is_empty() {
9624 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
9625 }
9626 });
9627 });
9628 let item = self.cut_common(window, cx);
9629 cx.set_global(KillRing(item))
9630 }
9631
9632 pub fn kill_ring_yank(
9633 &mut self,
9634 _: &KillRingYank,
9635 window: &mut Window,
9636 cx: &mut Context<Self>,
9637 ) {
9638 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
9639 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
9640 (kill_ring.text().to_string(), kill_ring.metadata_json())
9641 } else {
9642 return;
9643 }
9644 } else {
9645 return;
9646 };
9647 self.do_paste(&text, metadata, false, window, cx);
9648 }
9649
9650 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
9651 self.do_copy(true, cx);
9652 }
9653
9654 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
9655 self.do_copy(false, cx);
9656 }
9657
9658 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
9659 let selections = self.selections.all::<Point>(cx);
9660 let buffer = self.buffer.read(cx).read(cx);
9661 let mut text = String::new();
9662
9663 let mut clipboard_selections = Vec::with_capacity(selections.len());
9664 {
9665 let max_point = buffer.max_point();
9666 let mut is_first = true;
9667 for selection in &selections {
9668 let mut start = selection.start;
9669 let mut end = selection.end;
9670 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9671 if is_entire_line {
9672 start = Point::new(start.row, 0);
9673 end = cmp::min(max_point, Point::new(end.row + 1, 0));
9674 }
9675
9676 let mut trimmed_selections = Vec::new();
9677 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
9678 let row = MultiBufferRow(start.row);
9679 let first_indent = buffer.indent_size_for_line(row);
9680 if first_indent.len == 0 || start.column > first_indent.len {
9681 trimmed_selections.push(start..end);
9682 } else {
9683 trimmed_selections.push(
9684 Point::new(row.0, first_indent.len)
9685 ..Point::new(row.0, buffer.line_len(row)),
9686 );
9687 for row in start.row + 1..=end.row {
9688 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
9689 if row_indent_size.len >= first_indent.len {
9690 trimmed_selections.push(
9691 Point::new(row, first_indent.len)
9692 ..Point::new(row, buffer.line_len(MultiBufferRow(row))),
9693 );
9694 } else {
9695 trimmed_selections.clear();
9696 trimmed_selections.push(start..end);
9697 break;
9698 }
9699 }
9700 }
9701 } else {
9702 trimmed_selections.push(start..end);
9703 }
9704
9705 for trimmed_range in trimmed_selections {
9706 if is_first {
9707 is_first = false;
9708 } else {
9709 text += "\n";
9710 }
9711 let mut len = 0;
9712 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
9713 text.push_str(chunk);
9714 len += chunk.len();
9715 }
9716 clipboard_selections.push(ClipboardSelection {
9717 len,
9718 is_entire_line,
9719 first_line_indent: buffer
9720 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
9721 .len,
9722 });
9723 }
9724 }
9725 }
9726
9727 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
9728 text,
9729 clipboard_selections,
9730 ));
9731 }
9732
9733 pub fn do_paste(
9734 &mut self,
9735 text: &String,
9736 clipboard_selections: Option<Vec<ClipboardSelection>>,
9737 handle_entire_lines: bool,
9738 window: &mut Window,
9739 cx: &mut Context<Self>,
9740 ) {
9741 if self.read_only(cx) {
9742 return;
9743 }
9744
9745 let clipboard_text = Cow::Borrowed(text);
9746
9747 self.transact(window, cx, |this, window, cx| {
9748 if let Some(mut clipboard_selections) = clipboard_selections {
9749 let old_selections = this.selections.all::<usize>(cx);
9750 let all_selections_were_entire_line =
9751 clipboard_selections.iter().all(|s| s.is_entire_line);
9752 let first_selection_indent_column =
9753 clipboard_selections.first().map(|s| s.first_line_indent);
9754 if clipboard_selections.len() != old_selections.len() {
9755 clipboard_selections.drain(..);
9756 }
9757 let cursor_offset = this.selections.last::<usize>(cx).head();
9758 let mut auto_indent_on_paste = true;
9759
9760 this.buffer.update(cx, |buffer, cx| {
9761 let snapshot = buffer.read(cx);
9762 auto_indent_on_paste = snapshot
9763 .language_settings_at(cursor_offset, cx)
9764 .auto_indent_on_paste;
9765
9766 let mut start_offset = 0;
9767 let mut edits = Vec::new();
9768 let mut original_indent_columns = Vec::new();
9769 for (ix, selection) in old_selections.iter().enumerate() {
9770 let to_insert;
9771 let entire_line;
9772 let original_indent_column;
9773 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
9774 let end_offset = start_offset + clipboard_selection.len;
9775 to_insert = &clipboard_text[start_offset..end_offset];
9776 entire_line = clipboard_selection.is_entire_line;
9777 start_offset = end_offset + 1;
9778 original_indent_column = Some(clipboard_selection.first_line_indent);
9779 } else {
9780 to_insert = clipboard_text.as_str();
9781 entire_line = all_selections_were_entire_line;
9782 original_indent_column = first_selection_indent_column
9783 }
9784
9785 // If the corresponding selection was empty when this slice of the
9786 // clipboard text was written, then the entire line containing the
9787 // selection was copied. If this selection is also currently empty,
9788 // then paste the line before the current line of the buffer.
9789 let range = if selection.is_empty() && handle_entire_lines && entire_line {
9790 let column = selection.start.to_point(&snapshot).column as usize;
9791 let line_start = selection.start - column;
9792 line_start..line_start
9793 } else {
9794 selection.range()
9795 };
9796
9797 edits.push((range, to_insert));
9798 original_indent_columns.push(original_indent_column);
9799 }
9800 drop(snapshot);
9801
9802 buffer.edit(
9803 edits,
9804 if auto_indent_on_paste {
9805 Some(AutoindentMode::Block {
9806 original_indent_columns,
9807 })
9808 } else {
9809 None
9810 },
9811 cx,
9812 );
9813 });
9814
9815 let selections = this.selections.all::<usize>(cx);
9816 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9817 s.select(selections)
9818 });
9819 } else {
9820 this.insert(&clipboard_text, window, cx);
9821 }
9822 });
9823 }
9824
9825 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
9826 if let Some(item) = cx.read_from_clipboard() {
9827 let entries = item.entries();
9828
9829 match entries.first() {
9830 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
9831 // of all the pasted entries.
9832 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
9833 .do_paste(
9834 clipboard_string.text(),
9835 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
9836 true,
9837 window,
9838 cx,
9839 ),
9840 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
9841 }
9842 }
9843 }
9844
9845 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
9846 if self.read_only(cx) {
9847 return;
9848 }
9849
9850 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
9851 if let Some((selections, _)) =
9852 self.selection_history.transaction(transaction_id).cloned()
9853 {
9854 self.change_selections(None, window, cx, |s| {
9855 s.select_anchors(selections.to_vec());
9856 });
9857 } else {
9858 log::error!(
9859 "No entry in selection_history found for undo. \
9860 This may correspond to a bug where undo does not update the selection. \
9861 If this is occurring, please add details to \
9862 https://github.com/zed-industries/zed/issues/22692"
9863 );
9864 }
9865 self.request_autoscroll(Autoscroll::fit(), cx);
9866 self.unmark_text(window, cx);
9867 self.refresh_inline_completion(true, false, window, cx);
9868 cx.emit(EditorEvent::Edited { transaction_id });
9869 cx.emit(EditorEvent::TransactionUndone { transaction_id });
9870 }
9871 }
9872
9873 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
9874 if self.read_only(cx) {
9875 return;
9876 }
9877
9878 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
9879 if let Some((_, Some(selections))) =
9880 self.selection_history.transaction(transaction_id).cloned()
9881 {
9882 self.change_selections(None, window, cx, |s| {
9883 s.select_anchors(selections.to_vec());
9884 });
9885 } else {
9886 log::error!(
9887 "No entry in selection_history found for redo. \
9888 This may correspond to a bug where undo does not update the selection. \
9889 If this is occurring, please add details to \
9890 https://github.com/zed-industries/zed/issues/22692"
9891 );
9892 }
9893 self.request_autoscroll(Autoscroll::fit(), cx);
9894 self.unmark_text(window, cx);
9895 self.refresh_inline_completion(true, false, window, cx);
9896 cx.emit(EditorEvent::Edited { transaction_id });
9897 }
9898 }
9899
9900 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
9901 self.buffer
9902 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
9903 }
9904
9905 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
9906 self.buffer
9907 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
9908 }
9909
9910 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
9911 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9912 let line_mode = s.line_mode;
9913 s.move_with(|map, selection| {
9914 let cursor = if selection.is_empty() && !line_mode {
9915 movement::left(map, selection.start)
9916 } else {
9917 selection.start
9918 };
9919 selection.collapse_to(cursor, SelectionGoal::None);
9920 });
9921 })
9922 }
9923
9924 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
9925 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9926 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
9927 })
9928 }
9929
9930 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
9931 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9932 let line_mode = s.line_mode;
9933 s.move_with(|map, selection| {
9934 let cursor = if selection.is_empty() && !line_mode {
9935 movement::right(map, selection.end)
9936 } else {
9937 selection.end
9938 };
9939 selection.collapse_to(cursor, SelectionGoal::None)
9940 });
9941 })
9942 }
9943
9944 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
9945 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9946 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
9947 })
9948 }
9949
9950 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
9951 if self.take_rename(true, window, cx).is_some() {
9952 return;
9953 }
9954
9955 if matches!(self.mode, EditorMode::SingleLine { .. }) {
9956 cx.propagate();
9957 return;
9958 }
9959
9960 let text_layout_details = &self.text_layout_details(window);
9961 let selection_count = self.selections.count();
9962 let first_selection = self.selections.first_anchor();
9963
9964 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9965 let line_mode = s.line_mode;
9966 s.move_with(|map, selection| {
9967 if !selection.is_empty() && !line_mode {
9968 selection.goal = SelectionGoal::None;
9969 }
9970 let (cursor, goal) = movement::up(
9971 map,
9972 selection.start,
9973 selection.goal,
9974 false,
9975 text_layout_details,
9976 );
9977 selection.collapse_to(cursor, goal);
9978 });
9979 });
9980
9981 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
9982 {
9983 cx.propagate();
9984 }
9985 }
9986
9987 pub fn move_up_by_lines(
9988 &mut self,
9989 action: &MoveUpByLines,
9990 window: &mut Window,
9991 cx: &mut Context<Self>,
9992 ) {
9993 if self.take_rename(true, window, cx).is_some() {
9994 return;
9995 }
9996
9997 if matches!(self.mode, EditorMode::SingleLine { .. }) {
9998 cx.propagate();
9999 return;
10000 }
10001
10002 let text_layout_details = &self.text_layout_details(window);
10003
10004 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10005 let line_mode = s.line_mode;
10006 s.move_with(|map, selection| {
10007 if !selection.is_empty() && !line_mode {
10008 selection.goal = SelectionGoal::None;
10009 }
10010 let (cursor, goal) = movement::up_by_rows(
10011 map,
10012 selection.start,
10013 action.lines,
10014 selection.goal,
10015 false,
10016 text_layout_details,
10017 );
10018 selection.collapse_to(cursor, goal);
10019 });
10020 })
10021 }
10022
10023 pub fn move_down_by_lines(
10024 &mut self,
10025 action: &MoveDownByLines,
10026 window: &mut Window,
10027 cx: &mut Context<Self>,
10028 ) {
10029 if self.take_rename(true, window, cx).is_some() {
10030 return;
10031 }
10032
10033 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10034 cx.propagate();
10035 return;
10036 }
10037
10038 let text_layout_details = &self.text_layout_details(window);
10039
10040 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10041 let line_mode = s.line_mode;
10042 s.move_with(|map, selection| {
10043 if !selection.is_empty() && !line_mode {
10044 selection.goal = SelectionGoal::None;
10045 }
10046 let (cursor, goal) = movement::down_by_rows(
10047 map,
10048 selection.start,
10049 action.lines,
10050 selection.goal,
10051 false,
10052 text_layout_details,
10053 );
10054 selection.collapse_to(cursor, goal);
10055 });
10056 })
10057 }
10058
10059 pub fn select_down_by_lines(
10060 &mut self,
10061 action: &SelectDownByLines,
10062 window: &mut Window,
10063 cx: &mut Context<Self>,
10064 ) {
10065 let text_layout_details = &self.text_layout_details(window);
10066 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10067 s.move_heads_with(|map, head, goal| {
10068 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
10069 })
10070 })
10071 }
10072
10073 pub fn select_up_by_lines(
10074 &mut self,
10075 action: &SelectUpByLines,
10076 window: &mut Window,
10077 cx: &mut Context<Self>,
10078 ) {
10079 let text_layout_details = &self.text_layout_details(window);
10080 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10081 s.move_heads_with(|map, head, goal| {
10082 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
10083 })
10084 })
10085 }
10086
10087 pub fn select_page_up(
10088 &mut self,
10089 _: &SelectPageUp,
10090 window: &mut Window,
10091 cx: &mut Context<Self>,
10092 ) {
10093 let Some(row_count) = self.visible_row_count() else {
10094 return;
10095 };
10096
10097 let text_layout_details = &self.text_layout_details(window);
10098
10099 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10100 s.move_heads_with(|map, head, goal| {
10101 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
10102 })
10103 })
10104 }
10105
10106 pub fn move_page_up(
10107 &mut self,
10108 action: &MovePageUp,
10109 window: &mut Window,
10110 cx: &mut Context<Self>,
10111 ) {
10112 if self.take_rename(true, window, cx).is_some() {
10113 return;
10114 }
10115
10116 if self
10117 .context_menu
10118 .borrow_mut()
10119 .as_mut()
10120 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
10121 .unwrap_or(false)
10122 {
10123 return;
10124 }
10125
10126 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10127 cx.propagate();
10128 return;
10129 }
10130
10131 let Some(row_count) = self.visible_row_count() else {
10132 return;
10133 };
10134
10135 let autoscroll = if action.center_cursor {
10136 Autoscroll::center()
10137 } else {
10138 Autoscroll::fit()
10139 };
10140
10141 let text_layout_details = &self.text_layout_details(window);
10142
10143 self.change_selections(Some(autoscroll), window, cx, |s| {
10144 let line_mode = s.line_mode;
10145 s.move_with(|map, selection| {
10146 if !selection.is_empty() && !line_mode {
10147 selection.goal = SelectionGoal::None;
10148 }
10149 let (cursor, goal) = movement::up_by_rows(
10150 map,
10151 selection.end,
10152 row_count,
10153 selection.goal,
10154 false,
10155 text_layout_details,
10156 );
10157 selection.collapse_to(cursor, goal);
10158 });
10159 });
10160 }
10161
10162 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
10163 let text_layout_details = &self.text_layout_details(window);
10164 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10165 s.move_heads_with(|map, head, goal| {
10166 movement::up(map, head, goal, false, text_layout_details)
10167 })
10168 })
10169 }
10170
10171 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
10172 self.take_rename(true, window, cx);
10173
10174 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10175 cx.propagate();
10176 return;
10177 }
10178
10179 let text_layout_details = &self.text_layout_details(window);
10180 let selection_count = self.selections.count();
10181 let first_selection = self.selections.first_anchor();
10182
10183 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10184 let line_mode = s.line_mode;
10185 s.move_with(|map, selection| {
10186 if !selection.is_empty() && !line_mode {
10187 selection.goal = SelectionGoal::None;
10188 }
10189 let (cursor, goal) = movement::down(
10190 map,
10191 selection.end,
10192 selection.goal,
10193 false,
10194 text_layout_details,
10195 );
10196 selection.collapse_to(cursor, goal);
10197 });
10198 });
10199
10200 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10201 {
10202 cx.propagate();
10203 }
10204 }
10205
10206 pub fn select_page_down(
10207 &mut self,
10208 _: &SelectPageDown,
10209 window: &mut Window,
10210 cx: &mut Context<Self>,
10211 ) {
10212 let Some(row_count) = self.visible_row_count() else {
10213 return;
10214 };
10215
10216 let text_layout_details = &self.text_layout_details(window);
10217
10218 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10219 s.move_heads_with(|map, head, goal| {
10220 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
10221 })
10222 })
10223 }
10224
10225 pub fn move_page_down(
10226 &mut self,
10227 action: &MovePageDown,
10228 window: &mut Window,
10229 cx: &mut Context<Self>,
10230 ) {
10231 if self.take_rename(true, window, cx).is_some() {
10232 return;
10233 }
10234
10235 if self
10236 .context_menu
10237 .borrow_mut()
10238 .as_mut()
10239 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
10240 .unwrap_or(false)
10241 {
10242 return;
10243 }
10244
10245 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10246 cx.propagate();
10247 return;
10248 }
10249
10250 let Some(row_count) = self.visible_row_count() else {
10251 return;
10252 };
10253
10254 let autoscroll = if action.center_cursor {
10255 Autoscroll::center()
10256 } else {
10257 Autoscroll::fit()
10258 };
10259
10260 let text_layout_details = &self.text_layout_details(window);
10261 self.change_selections(Some(autoscroll), window, cx, |s| {
10262 let line_mode = s.line_mode;
10263 s.move_with(|map, selection| {
10264 if !selection.is_empty() && !line_mode {
10265 selection.goal = SelectionGoal::None;
10266 }
10267 let (cursor, goal) = movement::down_by_rows(
10268 map,
10269 selection.end,
10270 row_count,
10271 selection.goal,
10272 false,
10273 text_layout_details,
10274 );
10275 selection.collapse_to(cursor, goal);
10276 });
10277 });
10278 }
10279
10280 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
10281 let text_layout_details = &self.text_layout_details(window);
10282 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10283 s.move_heads_with(|map, head, goal| {
10284 movement::down(map, head, goal, false, text_layout_details)
10285 })
10286 });
10287 }
10288
10289 pub fn context_menu_first(
10290 &mut self,
10291 _: &ContextMenuFirst,
10292 _window: &mut Window,
10293 cx: &mut Context<Self>,
10294 ) {
10295 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10296 context_menu.select_first(self.completion_provider.as_deref(), cx);
10297 }
10298 }
10299
10300 pub fn context_menu_prev(
10301 &mut self,
10302 _: &ContextMenuPrevious,
10303 _window: &mut Window,
10304 cx: &mut Context<Self>,
10305 ) {
10306 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10307 context_menu.select_prev(self.completion_provider.as_deref(), cx);
10308 }
10309 }
10310
10311 pub fn context_menu_next(
10312 &mut self,
10313 _: &ContextMenuNext,
10314 _window: &mut Window,
10315 cx: &mut Context<Self>,
10316 ) {
10317 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10318 context_menu.select_next(self.completion_provider.as_deref(), cx);
10319 }
10320 }
10321
10322 pub fn context_menu_last(
10323 &mut self,
10324 _: &ContextMenuLast,
10325 _window: &mut Window,
10326 cx: &mut Context<Self>,
10327 ) {
10328 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10329 context_menu.select_last(self.completion_provider.as_deref(), cx);
10330 }
10331 }
10332
10333 pub fn move_to_previous_word_start(
10334 &mut self,
10335 _: &MoveToPreviousWordStart,
10336 window: &mut Window,
10337 cx: &mut Context<Self>,
10338 ) {
10339 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10340 s.move_cursors_with(|map, head, _| {
10341 (
10342 movement::previous_word_start(map, head),
10343 SelectionGoal::None,
10344 )
10345 });
10346 })
10347 }
10348
10349 pub fn move_to_previous_subword_start(
10350 &mut self,
10351 _: &MoveToPreviousSubwordStart,
10352 window: &mut Window,
10353 cx: &mut Context<Self>,
10354 ) {
10355 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10356 s.move_cursors_with(|map, head, _| {
10357 (
10358 movement::previous_subword_start(map, head),
10359 SelectionGoal::None,
10360 )
10361 });
10362 })
10363 }
10364
10365 pub fn select_to_previous_word_start(
10366 &mut self,
10367 _: &SelectToPreviousWordStart,
10368 window: &mut Window,
10369 cx: &mut Context<Self>,
10370 ) {
10371 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10372 s.move_heads_with(|map, head, _| {
10373 (
10374 movement::previous_word_start(map, head),
10375 SelectionGoal::None,
10376 )
10377 });
10378 })
10379 }
10380
10381 pub fn select_to_previous_subword_start(
10382 &mut self,
10383 _: &SelectToPreviousSubwordStart,
10384 window: &mut Window,
10385 cx: &mut Context<Self>,
10386 ) {
10387 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10388 s.move_heads_with(|map, head, _| {
10389 (
10390 movement::previous_subword_start(map, head),
10391 SelectionGoal::None,
10392 )
10393 });
10394 })
10395 }
10396
10397 pub fn delete_to_previous_word_start(
10398 &mut self,
10399 action: &DeleteToPreviousWordStart,
10400 window: &mut Window,
10401 cx: &mut Context<Self>,
10402 ) {
10403 self.transact(window, cx, |this, window, cx| {
10404 this.select_autoclose_pair(window, cx);
10405 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10406 let line_mode = s.line_mode;
10407 s.move_with(|map, selection| {
10408 if selection.is_empty() && !line_mode {
10409 let cursor = if action.ignore_newlines {
10410 movement::previous_word_start(map, selection.head())
10411 } else {
10412 movement::previous_word_start_or_newline(map, selection.head())
10413 };
10414 selection.set_head(cursor, SelectionGoal::None);
10415 }
10416 });
10417 });
10418 this.insert("", window, cx);
10419 });
10420 }
10421
10422 pub fn delete_to_previous_subword_start(
10423 &mut self,
10424 _: &DeleteToPreviousSubwordStart,
10425 window: &mut Window,
10426 cx: &mut Context<Self>,
10427 ) {
10428 self.transact(window, cx, |this, window, cx| {
10429 this.select_autoclose_pair(window, cx);
10430 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10431 let line_mode = s.line_mode;
10432 s.move_with(|map, selection| {
10433 if selection.is_empty() && !line_mode {
10434 let cursor = movement::previous_subword_start(map, selection.head());
10435 selection.set_head(cursor, SelectionGoal::None);
10436 }
10437 });
10438 });
10439 this.insert("", window, cx);
10440 });
10441 }
10442
10443 pub fn move_to_next_word_end(
10444 &mut self,
10445 _: &MoveToNextWordEnd,
10446 window: &mut Window,
10447 cx: &mut Context<Self>,
10448 ) {
10449 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10450 s.move_cursors_with(|map, head, _| {
10451 (movement::next_word_end(map, head), SelectionGoal::None)
10452 });
10453 })
10454 }
10455
10456 pub fn move_to_next_subword_end(
10457 &mut self,
10458 _: &MoveToNextSubwordEnd,
10459 window: &mut Window,
10460 cx: &mut Context<Self>,
10461 ) {
10462 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10463 s.move_cursors_with(|map, head, _| {
10464 (movement::next_subword_end(map, head), SelectionGoal::None)
10465 });
10466 })
10467 }
10468
10469 pub fn select_to_next_word_end(
10470 &mut self,
10471 _: &SelectToNextWordEnd,
10472 window: &mut Window,
10473 cx: &mut Context<Self>,
10474 ) {
10475 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10476 s.move_heads_with(|map, head, _| {
10477 (movement::next_word_end(map, head), SelectionGoal::None)
10478 });
10479 })
10480 }
10481
10482 pub fn select_to_next_subword_end(
10483 &mut self,
10484 _: &SelectToNextSubwordEnd,
10485 window: &mut Window,
10486 cx: &mut Context<Self>,
10487 ) {
10488 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10489 s.move_heads_with(|map, head, _| {
10490 (movement::next_subword_end(map, head), SelectionGoal::None)
10491 });
10492 })
10493 }
10494
10495 pub fn delete_to_next_word_end(
10496 &mut self,
10497 action: &DeleteToNextWordEnd,
10498 window: &mut Window,
10499 cx: &mut Context<Self>,
10500 ) {
10501 self.transact(window, cx, |this, window, cx| {
10502 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10503 let line_mode = s.line_mode;
10504 s.move_with(|map, selection| {
10505 if selection.is_empty() && !line_mode {
10506 let cursor = if action.ignore_newlines {
10507 movement::next_word_end(map, selection.head())
10508 } else {
10509 movement::next_word_end_or_newline(map, selection.head())
10510 };
10511 selection.set_head(cursor, SelectionGoal::None);
10512 }
10513 });
10514 });
10515 this.insert("", window, cx);
10516 });
10517 }
10518
10519 pub fn delete_to_next_subword_end(
10520 &mut self,
10521 _: &DeleteToNextSubwordEnd,
10522 window: &mut Window,
10523 cx: &mut Context<Self>,
10524 ) {
10525 self.transact(window, cx, |this, window, cx| {
10526 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10527 s.move_with(|map, selection| {
10528 if selection.is_empty() {
10529 let cursor = movement::next_subword_end(map, selection.head());
10530 selection.set_head(cursor, SelectionGoal::None);
10531 }
10532 });
10533 });
10534 this.insert("", window, cx);
10535 });
10536 }
10537
10538 pub fn move_to_beginning_of_line(
10539 &mut self,
10540 action: &MoveToBeginningOfLine,
10541 window: &mut Window,
10542 cx: &mut Context<Self>,
10543 ) {
10544 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10545 s.move_cursors_with(|map, head, _| {
10546 (
10547 movement::indented_line_beginning(
10548 map,
10549 head,
10550 action.stop_at_soft_wraps,
10551 action.stop_at_indent,
10552 ),
10553 SelectionGoal::None,
10554 )
10555 });
10556 })
10557 }
10558
10559 pub fn select_to_beginning_of_line(
10560 &mut self,
10561 action: &SelectToBeginningOfLine,
10562 window: &mut Window,
10563 cx: &mut Context<Self>,
10564 ) {
10565 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10566 s.move_heads_with(|map, head, _| {
10567 (
10568 movement::indented_line_beginning(
10569 map,
10570 head,
10571 action.stop_at_soft_wraps,
10572 action.stop_at_indent,
10573 ),
10574 SelectionGoal::None,
10575 )
10576 });
10577 });
10578 }
10579
10580 pub fn delete_to_beginning_of_line(
10581 &mut self,
10582 action: &DeleteToBeginningOfLine,
10583 window: &mut Window,
10584 cx: &mut Context<Self>,
10585 ) {
10586 self.transact(window, cx, |this, window, cx| {
10587 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10588 s.move_with(|_, selection| {
10589 selection.reversed = true;
10590 });
10591 });
10592
10593 this.select_to_beginning_of_line(
10594 &SelectToBeginningOfLine {
10595 stop_at_soft_wraps: false,
10596 stop_at_indent: action.stop_at_indent,
10597 },
10598 window,
10599 cx,
10600 );
10601 this.backspace(&Backspace, window, cx);
10602 });
10603 }
10604
10605 pub fn move_to_end_of_line(
10606 &mut self,
10607 action: &MoveToEndOfLine,
10608 window: &mut Window,
10609 cx: &mut Context<Self>,
10610 ) {
10611 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10612 s.move_cursors_with(|map, head, _| {
10613 (
10614 movement::line_end(map, head, action.stop_at_soft_wraps),
10615 SelectionGoal::None,
10616 )
10617 });
10618 })
10619 }
10620
10621 pub fn select_to_end_of_line(
10622 &mut self,
10623 action: &SelectToEndOfLine,
10624 window: &mut Window,
10625 cx: &mut Context<Self>,
10626 ) {
10627 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10628 s.move_heads_with(|map, head, _| {
10629 (
10630 movement::line_end(map, head, action.stop_at_soft_wraps),
10631 SelectionGoal::None,
10632 )
10633 });
10634 })
10635 }
10636
10637 pub fn delete_to_end_of_line(
10638 &mut self,
10639 _: &DeleteToEndOfLine,
10640 window: &mut Window,
10641 cx: &mut Context<Self>,
10642 ) {
10643 self.transact(window, cx, |this, window, cx| {
10644 this.select_to_end_of_line(
10645 &SelectToEndOfLine {
10646 stop_at_soft_wraps: false,
10647 },
10648 window,
10649 cx,
10650 );
10651 this.delete(&Delete, window, cx);
10652 });
10653 }
10654
10655 pub fn cut_to_end_of_line(
10656 &mut self,
10657 _: &CutToEndOfLine,
10658 window: &mut Window,
10659 cx: &mut Context<Self>,
10660 ) {
10661 self.transact(window, cx, |this, window, cx| {
10662 this.select_to_end_of_line(
10663 &SelectToEndOfLine {
10664 stop_at_soft_wraps: false,
10665 },
10666 window,
10667 cx,
10668 );
10669 this.cut(&Cut, window, cx);
10670 });
10671 }
10672
10673 pub fn move_to_start_of_paragraph(
10674 &mut self,
10675 _: &MoveToStartOfParagraph,
10676 window: &mut Window,
10677 cx: &mut Context<Self>,
10678 ) {
10679 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10680 cx.propagate();
10681 return;
10682 }
10683
10684 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10685 s.move_with(|map, selection| {
10686 selection.collapse_to(
10687 movement::start_of_paragraph(map, selection.head(), 1),
10688 SelectionGoal::None,
10689 )
10690 });
10691 })
10692 }
10693
10694 pub fn move_to_end_of_paragraph(
10695 &mut self,
10696 _: &MoveToEndOfParagraph,
10697 window: &mut Window,
10698 cx: &mut Context<Self>,
10699 ) {
10700 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10701 cx.propagate();
10702 return;
10703 }
10704
10705 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10706 s.move_with(|map, selection| {
10707 selection.collapse_to(
10708 movement::end_of_paragraph(map, selection.head(), 1),
10709 SelectionGoal::None,
10710 )
10711 });
10712 })
10713 }
10714
10715 pub fn select_to_start_of_paragraph(
10716 &mut self,
10717 _: &SelectToStartOfParagraph,
10718 window: &mut Window,
10719 cx: &mut Context<Self>,
10720 ) {
10721 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10722 cx.propagate();
10723 return;
10724 }
10725
10726 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10727 s.move_heads_with(|map, head, _| {
10728 (
10729 movement::start_of_paragraph(map, head, 1),
10730 SelectionGoal::None,
10731 )
10732 });
10733 })
10734 }
10735
10736 pub fn select_to_end_of_paragraph(
10737 &mut self,
10738 _: &SelectToEndOfParagraph,
10739 window: &mut Window,
10740 cx: &mut Context<Self>,
10741 ) {
10742 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10743 cx.propagate();
10744 return;
10745 }
10746
10747 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10748 s.move_heads_with(|map, head, _| {
10749 (
10750 movement::end_of_paragraph(map, head, 1),
10751 SelectionGoal::None,
10752 )
10753 });
10754 })
10755 }
10756
10757 pub fn move_to_start_of_excerpt(
10758 &mut self,
10759 _: &MoveToStartOfExcerpt,
10760 window: &mut Window,
10761 cx: &mut Context<Self>,
10762 ) {
10763 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10764 cx.propagate();
10765 return;
10766 }
10767
10768 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10769 s.move_with(|map, selection| {
10770 selection.collapse_to(
10771 movement::start_of_excerpt(
10772 map,
10773 selection.head(),
10774 workspace::searchable::Direction::Prev,
10775 ),
10776 SelectionGoal::None,
10777 )
10778 });
10779 })
10780 }
10781
10782 pub fn move_to_start_of_next_excerpt(
10783 &mut self,
10784 _: &MoveToStartOfNextExcerpt,
10785 window: &mut Window,
10786 cx: &mut Context<Self>,
10787 ) {
10788 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10789 cx.propagate();
10790 return;
10791 }
10792
10793 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10794 s.move_with(|map, selection| {
10795 selection.collapse_to(
10796 movement::start_of_excerpt(
10797 map,
10798 selection.head(),
10799 workspace::searchable::Direction::Next,
10800 ),
10801 SelectionGoal::None,
10802 )
10803 });
10804 })
10805 }
10806
10807 pub fn move_to_end_of_excerpt(
10808 &mut self,
10809 _: &MoveToEndOfExcerpt,
10810 window: &mut Window,
10811 cx: &mut Context<Self>,
10812 ) {
10813 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10814 cx.propagate();
10815 return;
10816 }
10817
10818 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10819 s.move_with(|map, selection| {
10820 selection.collapse_to(
10821 movement::end_of_excerpt(
10822 map,
10823 selection.head(),
10824 workspace::searchable::Direction::Next,
10825 ),
10826 SelectionGoal::None,
10827 )
10828 });
10829 })
10830 }
10831
10832 pub fn move_to_end_of_previous_excerpt(
10833 &mut self,
10834 _: &MoveToEndOfPreviousExcerpt,
10835 window: &mut Window,
10836 cx: &mut Context<Self>,
10837 ) {
10838 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10839 cx.propagate();
10840 return;
10841 }
10842
10843 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10844 s.move_with(|map, selection| {
10845 selection.collapse_to(
10846 movement::end_of_excerpt(
10847 map,
10848 selection.head(),
10849 workspace::searchable::Direction::Prev,
10850 ),
10851 SelectionGoal::None,
10852 )
10853 });
10854 })
10855 }
10856
10857 pub fn select_to_start_of_excerpt(
10858 &mut self,
10859 _: &SelectToStartOfExcerpt,
10860 window: &mut Window,
10861 cx: &mut Context<Self>,
10862 ) {
10863 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10864 cx.propagate();
10865 return;
10866 }
10867
10868 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10869 s.move_heads_with(|map, head, _| {
10870 (
10871 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
10872 SelectionGoal::None,
10873 )
10874 });
10875 })
10876 }
10877
10878 pub fn select_to_start_of_next_excerpt(
10879 &mut self,
10880 _: &SelectToStartOfNextExcerpt,
10881 window: &mut Window,
10882 cx: &mut Context<Self>,
10883 ) {
10884 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10885 cx.propagate();
10886 return;
10887 }
10888
10889 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10890 s.move_heads_with(|map, head, _| {
10891 (
10892 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
10893 SelectionGoal::None,
10894 )
10895 });
10896 })
10897 }
10898
10899 pub fn select_to_end_of_excerpt(
10900 &mut self,
10901 _: &SelectToEndOfExcerpt,
10902 window: &mut Window,
10903 cx: &mut Context<Self>,
10904 ) {
10905 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10906 cx.propagate();
10907 return;
10908 }
10909
10910 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10911 s.move_heads_with(|map, head, _| {
10912 (
10913 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
10914 SelectionGoal::None,
10915 )
10916 });
10917 })
10918 }
10919
10920 pub fn select_to_end_of_previous_excerpt(
10921 &mut self,
10922 _: &SelectToEndOfPreviousExcerpt,
10923 window: &mut Window,
10924 cx: &mut Context<Self>,
10925 ) {
10926 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10927 cx.propagate();
10928 return;
10929 }
10930
10931 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10932 s.move_heads_with(|map, head, _| {
10933 (
10934 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
10935 SelectionGoal::None,
10936 )
10937 });
10938 })
10939 }
10940
10941 pub fn move_to_beginning(
10942 &mut self,
10943 _: &MoveToBeginning,
10944 window: &mut Window,
10945 cx: &mut Context<Self>,
10946 ) {
10947 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10948 cx.propagate();
10949 return;
10950 }
10951
10952 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10953 s.select_ranges(vec![0..0]);
10954 });
10955 }
10956
10957 pub fn select_to_beginning(
10958 &mut self,
10959 _: &SelectToBeginning,
10960 window: &mut Window,
10961 cx: &mut Context<Self>,
10962 ) {
10963 let mut selection = self.selections.last::<Point>(cx);
10964 selection.set_head(Point::zero(), SelectionGoal::None);
10965
10966 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10967 s.select(vec![selection]);
10968 });
10969 }
10970
10971 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
10972 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10973 cx.propagate();
10974 return;
10975 }
10976
10977 let cursor = self.buffer.read(cx).read(cx).len();
10978 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10979 s.select_ranges(vec![cursor..cursor])
10980 });
10981 }
10982
10983 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
10984 self.nav_history = nav_history;
10985 }
10986
10987 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
10988 self.nav_history.as_ref()
10989 }
10990
10991 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
10992 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
10993 }
10994
10995 fn push_to_nav_history(
10996 &mut self,
10997 cursor_anchor: Anchor,
10998 new_position: Option<Point>,
10999 is_deactivate: bool,
11000 cx: &mut Context<Self>,
11001 ) {
11002 if let Some(nav_history) = self.nav_history.as_mut() {
11003 let buffer = self.buffer.read(cx).read(cx);
11004 let cursor_position = cursor_anchor.to_point(&buffer);
11005 let scroll_state = self.scroll_manager.anchor();
11006 let scroll_top_row = scroll_state.top_row(&buffer);
11007 drop(buffer);
11008
11009 if let Some(new_position) = new_position {
11010 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
11011 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
11012 return;
11013 }
11014 }
11015
11016 nav_history.push(
11017 Some(NavigationData {
11018 cursor_anchor,
11019 cursor_position,
11020 scroll_anchor: scroll_state,
11021 scroll_top_row,
11022 }),
11023 cx,
11024 );
11025 cx.emit(EditorEvent::PushedToNavHistory {
11026 anchor: cursor_anchor,
11027 is_deactivate,
11028 })
11029 }
11030 }
11031
11032 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
11033 let buffer = self.buffer.read(cx).snapshot(cx);
11034 let mut selection = self.selections.first::<usize>(cx);
11035 selection.set_head(buffer.len(), SelectionGoal::None);
11036 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11037 s.select(vec![selection]);
11038 });
11039 }
11040
11041 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
11042 let end = self.buffer.read(cx).read(cx).len();
11043 self.change_selections(None, window, cx, |s| {
11044 s.select_ranges(vec![0..end]);
11045 });
11046 }
11047
11048 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
11049 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11050 let mut selections = self.selections.all::<Point>(cx);
11051 let max_point = display_map.buffer_snapshot.max_point();
11052 for selection in &mut selections {
11053 let rows = selection.spanned_rows(true, &display_map);
11054 selection.start = Point::new(rows.start.0, 0);
11055 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
11056 selection.reversed = false;
11057 }
11058 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11059 s.select(selections);
11060 });
11061 }
11062
11063 pub fn split_selection_into_lines(
11064 &mut self,
11065 _: &SplitSelectionIntoLines,
11066 window: &mut Window,
11067 cx: &mut Context<Self>,
11068 ) {
11069 let selections = self
11070 .selections
11071 .all::<Point>(cx)
11072 .into_iter()
11073 .map(|selection| selection.start..selection.end)
11074 .collect::<Vec<_>>();
11075 self.unfold_ranges(&selections, true, true, cx);
11076
11077 let mut new_selection_ranges = Vec::new();
11078 {
11079 let buffer = self.buffer.read(cx).read(cx);
11080 for selection in selections {
11081 for row in selection.start.row..selection.end.row {
11082 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11083 new_selection_ranges.push(cursor..cursor);
11084 }
11085
11086 let is_multiline_selection = selection.start.row != selection.end.row;
11087 // Don't insert last one if it's a multi-line selection ending at the start of a line,
11088 // so this action feels more ergonomic when paired with other selection operations
11089 let should_skip_last = is_multiline_selection && selection.end.column == 0;
11090 if !should_skip_last {
11091 new_selection_ranges.push(selection.end..selection.end);
11092 }
11093 }
11094 }
11095 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11096 s.select_ranges(new_selection_ranges);
11097 });
11098 }
11099
11100 pub fn add_selection_above(
11101 &mut self,
11102 _: &AddSelectionAbove,
11103 window: &mut Window,
11104 cx: &mut Context<Self>,
11105 ) {
11106 self.add_selection(true, window, cx);
11107 }
11108
11109 pub fn add_selection_below(
11110 &mut self,
11111 _: &AddSelectionBelow,
11112 window: &mut Window,
11113 cx: &mut Context<Self>,
11114 ) {
11115 self.add_selection(false, window, cx);
11116 }
11117
11118 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
11119 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11120 let mut selections = self.selections.all::<Point>(cx);
11121 let text_layout_details = self.text_layout_details(window);
11122 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
11123 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
11124 let range = oldest_selection.display_range(&display_map).sorted();
11125
11126 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
11127 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
11128 let positions = start_x.min(end_x)..start_x.max(end_x);
11129
11130 selections.clear();
11131 let mut stack = Vec::new();
11132 for row in range.start.row().0..=range.end.row().0 {
11133 if let Some(selection) = self.selections.build_columnar_selection(
11134 &display_map,
11135 DisplayRow(row),
11136 &positions,
11137 oldest_selection.reversed,
11138 &text_layout_details,
11139 ) {
11140 stack.push(selection.id);
11141 selections.push(selection);
11142 }
11143 }
11144
11145 if above {
11146 stack.reverse();
11147 }
11148
11149 AddSelectionsState { above, stack }
11150 });
11151
11152 let last_added_selection = *state.stack.last().unwrap();
11153 let mut new_selections = Vec::new();
11154 if above == state.above {
11155 let end_row = if above {
11156 DisplayRow(0)
11157 } else {
11158 display_map.max_point().row()
11159 };
11160
11161 'outer: for selection in selections {
11162 if selection.id == last_added_selection {
11163 let range = selection.display_range(&display_map).sorted();
11164 debug_assert_eq!(range.start.row(), range.end.row());
11165 let mut row = range.start.row();
11166 let positions =
11167 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
11168 px(start)..px(end)
11169 } else {
11170 let start_x =
11171 display_map.x_for_display_point(range.start, &text_layout_details);
11172 let end_x =
11173 display_map.x_for_display_point(range.end, &text_layout_details);
11174 start_x.min(end_x)..start_x.max(end_x)
11175 };
11176
11177 while row != end_row {
11178 if above {
11179 row.0 -= 1;
11180 } else {
11181 row.0 += 1;
11182 }
11183
11184 if let Some(new_selection) = self.selections.build_columnar_selection(
11185 &display_map,
11186 row,
11187 &positions,
11188 selection.reversed,
11189 &text_layout_details,
11190 ) {
11191 state.stack.push(new_selection.id);
11192 if above {
11193 new_selections.push(new_selection);
11194 new_selections.push(selection);
11195 } else {
11196 new_selections.push(selection);
11197 new_selections.push(new_selection);
11198 }
11199
11200 continue 'outer;
11201 }
11202 }
11203 }
11204
11205 new_selections.push(selection);
11206 }
11207 } else {
11208 new_selections = selections;
11209 new_selections.retain(|s| s.id != last_added_selection);
11210 state.stack.pop();
11211 }
11212
11213 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11214 s.select(new_selections);
11215 });
11216 if state.stack.len() > 1 {
11217 self.add_selections_state = Some(state);
11218 }
11219 }
11220
11221 pub fn select_next_match_internal(
11222 &mut self,
11223 display_map: &DisplaySnapshot,
11224 replace_newest: bool,
11225 autoscroll: Option<Autoscroll>,
11226 window: &mut Window,
11227 cx: &mut Context<Self>,
11228 ) -> Result<()> {
11229 fn select_next_match_ranges(
11230 this: &mut Editor,
11231 range: Range<usize>,
11232 replace_newest: bool,
11233 auto_scroll: Option<Autoscroll>,
11234 window: &mut Window,
11235 cx: &mut Context<Editor>,
11236 ) {
11237 this.unfold_ranges(&[range.clone()], false, true, cx);
11238 this.change_selections(auto_scroll, window, cx, |s| {
11239 if replace_newest {
11240 s.delete(s.newest_anchor().id);
11241 }
11242 s.insert_range(range.clone());
11243 });
11244 }
11245
11246 let buffer = &display_map.buffer_snapshot;
11247 let mut selections = self.selections.all::<usize>(cx);
11248 if let Some(mut select_next_state) = self.select_next_state.take() {
11249 let query = &select_next_state.query;
11250 if !select_next_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
11255 let bytes_after_last_selection =
11256 buffer.bytes_in_range(last_selection.end..buffer.len());
11257 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
11258 let query_matches = query
11259 .stream_find_iter(bytes_after_last_selection)
11260 .map(|result| (last_selection.end, result))
11261 .chain(
11262 query
11263 .stream_find_iter(bytes_before_first_selection)
11264 .map(|result| (0, result)),
11265 );
11266
11267 for (start_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 start_offset + query_match.start()..start_offset + query_match.end();
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_next_state.wordwise
11275 || (!movement::is_inside_word(display_map, display_range.start)
11276 && !movement::is_inside_word(display_map, display_range.end))
11277 {
11278 // TODO: This is n^2, because we might check all the selections
11279 if !selections
11280 .iter()
11281 .any(|selection| selection.range().overlaps(&offset_range))
11282 {
11283 next_selected_range = Some(offset_range);
11284 break;
11285 }
11286 }
11287 }
11288
11289 if let Some(next_selected_range) = next_selected_range {
11290 select_next_match_ranges(
11291 self,
11292 next_selected_range,
11293 replace_newest,
11294 autoscroll,
11295 window,
11296 cx,
11297 );
11298 } else {
11299 select_next_state.done = true;
11300 }
11301 }
11302
11303 self.select_next_state = Some(select_next_state);
11304 } else {
11305 let mut only_carets = true;
11306 let mut same_text_selected = true;
11307 let mut selected_text = None;
11308
11309 let mut selections_iter = selections.iter().peekable();
11310 while let Some(selection) = selections_iter.next() {
11311 if selection.start != selection.end {
11312 only_carets = false;
11313 }
11314
11315 if same_text_selected {
11316 if selected_text.is_none() {
11317 selected_text =
11318 Some(buffer.text_for_range(selection.range()).collect::<String>());
11319 }
11320
11321 if let Some(next_selection) = selections_iter.peek() {
11322 if next_selection.range().len() == selection.range().len() {
11323 let next_selected_text = buffer
11324 .text_for_range(next_selection.range())
11325 .collect::<String>();
11326 if Some(next_selected_text) != selected_text {
11327 same_text_selected = false;
11328 selected_text = None;
11329 }
11330 } else {
11331 same_text_selected = false;
11332 selected_text = None;
11333 }
11334 }
11335 }
11336 }
11337
11338 if only_carets {
11339 for selection in &mut selections {
11340 let word_range = movement::surrounding_word(
11341 display_map,
11342 selection.start.to_display_point(display_map),
11343 );
11344 selection.start = word_range.start.to_offset(display_map, Bias::Left);
11345 selection.end = word_range.end.to_offset(display_map, Bias::Left);
11346 selection.goal = SelectionGoal::None;
11347 selection.reversed = false;
11348 select_next_match_ranges(
11349 self,
11350 selection.start..selection.end,
11351 replace_newest,
11352 autoscroll,
11353 window,
11354 cx,
11355 );
11356 }
11357
11358 if selections.len() == 1 {
11359 let selection = selections
11360 .last()
11361 .expect("ensured that there's only one selection");
11362 let query = buffer
11363 .text_for_range(selection.start..selection.end)
11364 .collect::<String>();
11365 let is_empty = query.is_empty();
11366 let select_state = SelectNextState {
11367 query: AhoCorasick::new(&[query])?,
11368 wordwise: true,
11369 done: is_empty,
11370 };
11371 self.select_next_state = Some(select_state);
11372 } else {
11373 self.select_next_state = None;
11374 }
11375 } else if let Some(selected_text) = selected_text {
11376 self.select_next_state = Some(SelectNextState {
11377 query: AhoCorasick::new(&[selected_text])?,
11378 wordwise: false,
11379 done: false,
11380 });
11381 self.select_next_match_internal(
11382 display_map,
11383 replace_newest,
11384 autoscroll,
11385 window,
11386 cx,
11387 )?;
11388 }
11389 }
11390 Ok(())
11391 }
11392
11393 pub fn select_all_matches(
11394 &mut self,
11395 _action: &SelectAllMatches,
11396 window: &mut Window,
11397 cx: &mut Context<Self>,
11398 ) -> Result<()> {
11399 self.push_to_selection_history();
11400 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11401
11402 self.select_next_match_internal(&display_map, false, None, window, cx)?;
11403 let Some(select_next_state) = self.select_next_state.as_mut() else {
11404 return Ok(());
11405 };
11406 if select_next_state.done {
11407 return Ok(());
11408 }
11409
11410 let mut new_selections = self.selections.all::<usize>(cx);
11411
11412 let buffer = &display_map.buffer_snapshot;
11413 let query_matches = select_next_state
11414 .query
11415 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
11416
11417 for query_match in query_matches {
11418 let query_match = query_match.unwrap(); // can only fail due to I/O
11419 let offset_range = query_match.start()..query_match.end();
11420 let display_range = offset_range.start.to_display_point(&display_map)
11421 ..offset_range.end.to_display_point(&display_map);
11422
11423 if !select_next_state.wordwise
11424 || (!movement::is_inside_word(&display_map, display_range.start)
11425 && !movement::is_inside_word(&display_map, display_range.end))
11426 {
11427 self.selections.change_with(cx, |selections| {
11428 new_selections.push(Selection {
11429 id: selections.new_selection_id(),
11430 start: offset_range.start,
11431 end: offset_range.end,
11432 reversed: false,
11433 goal: SelectionGoal::None,
11434 });
11435 });
11436 }
11437 }
11438
11439 new_selections.sort_by_key(|selection| selection.start);
11440 let mut ix = 0;
11441 while ix + 1 < new_selections.len() {
11442 let current_selection = &new_selections[ix];
11443 let next_selection = &new_selections[ix + 1];
11444 if current_selection.range().overlaps(&next_selection.range()) {
11445 if current_selection.id < next_selection.id {
11446 new_selections.remove(ix + 1);
11447 } else {
11448 new_selections.remove(ix);
11449 }
11450 } else {
11451 ix += 1;
11452 }
11453 }
11454
11455 let reversed = self.selections.oldest::<usize>(cx).reversed;
11456
11457 for selection in new_selections.iter_mut() {
11458 selection.reversed = reversed;
11459 }
11460
11461 select_next_state.done = true;
11462 self.unfold_ranges(
11463 &new_selections
11464 .iter()
11465 .map(|selection| selection.range())
11466 .collect::<Vec<_>>(),
11467 false,
11468 false,
11469 cx,
11470 );
11471 self.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
11472 selections.select(new_selections)
11473 });
11474
11475 Ok(())
11476 }
11477
11478 pub fn select_next(
11479 &mut self,
11480 action: &SelectNext,
11481 window: &mut Window,
11482 cx: &mut Context<Self>,
11483 ) -> Result<()> {
11484 self.push_to_selection_history();
11485 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11486 self.select_next_match_internal(
11487 &display_map,
11488 action.replace_newest,
11489 Some(Autoscroll::newest()),
11490 window,
11491 cx,
11492 )?;
11493 Ok(())
11494 }
11495
11496 pub fn select_previous(
11497 &mut self,
11498 action: &SelectPrevious,
11499 window: &mut Window,
11500 cx: &mut Context<Self>,
11501 ) -> Result<()> {
11502 self.push_to_selection_history();
11503 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11504 let buffer = &display_map.buffer_snapshot;
11505 let mut selections = self.selections.all::<usize>(cx);
11506 if let Some(mut select_prev_state) = self.select_prev_state.take() {
11507 let query = &select_prev_state.query;
11508 if !select_prev_state.done {
11509 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11510 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11511 let mut next_selected_range = None;
11512 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
11513 let bytes_before_last_selection =
11514 buffer.reversed_bytes_in_range(0..last_selection.start);
11515 let bytes_after_first_selection =
11516 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
11517 let query_matches = query
11518 .stream_find_iter(bytes_before_last_selection)
11519 .map(|result| (last_selection.start, result))
11520 .chain(
11521 query
11522 .stream_find_iter(bytes_after_first_selection)
11523 .map(|result| (buffer.len(), result)),
11524 );
11525 for (end_offset, query_match) in query_matches {
11526 let query_match = query_match.unwrap(); // can only fail due to I/O
11527 let offset_range =
11528 end_offset - query_match.end()..end_offset - query_match.start();
11529 let display_range = offset_range.start.to_display_point(&display_map)
11530 ..offset_range.end.to_display_point(&display_map);
11531
11532 if !select_prev_state.wordwise
11533 || (!movement::is_inside_word(&display_map, display_range.start)
11534 && !movement::is_inside_word(&display_map, display_range.end))
11535 {
11536 next_selected_range = Some(offset_range);
11537 break;
11538 }
11539 }
11540
11541 if let Some(next_selected_range) = next_selected_range {
11542 self.unfold_ranges(&[next_selected_range.clone()], false, true, cx);
11543 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
11544 if action.replace_newest {
11545 s.delete(s.newest_anchor().id);
11546 }
11547 s.insert_range(next_selected_range);
11548 });
11549 } else {
11550 select_prev_state.done = true;
11551 }
11552 }
11553
11554 self.select_prev_state = Some(select_prev_state);
11555 } else {
11556 let mut only_carets = true;
11557 let mut same_text_selected = true;
11558 let mut selected_text = None;
11559
11560 let mut selections_iter = selections.iter().peekable();
11561 while let Some(selection) = selections_iter.next() {
11562 if selection.start != selection.end {
11563 only_carets = false;
11564 }
11565
11566 if same_text_selected {
11567 if selected_text.is_none() {
11568 selected_text =
11569 Some(buffer.text_for_range(selection.range()).collect::<String>());
11570 }
11571
11572 if let Some(next_selection) = selections_iter.peek() {
11573 if next_selection.range().len() == selection.range().len() {
11574 let next_selected_text = buffer
11575 .text_for_range(next_selection.range())
11576 .collect::<String>();
11577 if Some(next_selected_text) != selected_text {
11578 same_text_selected = false;
11579 selected_text = None;
11580 }
11581 } else {
11582 same_text_selected = false;
11583 selected_text = None;
11584 }
11585 }
11586 }
11587 }
11588
11589 if only_carets {
11590 for selection in &mut selections {
11591 let word_range = movement::surrounding_word(
11592 &display_map,
11593 selection.start.to_display_point(&display_map),
11594 );
11595 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
11596 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
11597 selection.goal = SelectionGoal::None;
11598 selection.reversed = false;
11599 }
11600 if selections.len() == 1 {
11601 let selection = selections
11602 .last()
11603 .expect("ensured that there's only one selection");
11604 let query = buffer
11605 .text_for_range(selection.start..selection.end)
11606 .collect::<String>();
11607 let is_empty = query.is_empty();
11608 let select_state = SelectNextState {
11609 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
11610 wordwise: true,
11611 done: is_empty,
11612 };
11613 self.select_prev_state = Some(select_state);
11614 } else {
11615 self.select_prev_state = None;
11616 }
11617
11618 self.unfold_ranges(
11619 &selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
11620 false,
11621 true,
11622 cx,
11623 );
11624 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
11625 s.select(selections);
11626 });
11627 } else if let Some(selected_text) = selected_text {
11628 self.select_prev_state = Some(SelectNextState {
11629 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
11630 wordwise: false,
11631 done: false,
11632 });
11633 self.select_previous(action, window, cx)?;
11634 }
11635 }
11636 Ok(())
11637 }
11638
11639 pub fn toggle_comments(
11640 &mut self,
11641 action: &ToggleComments,
11642 window: &mut Window,
11643 cx: &mut Context<Self>,
11644 ) {
11645 if self.read_only(cx) {
11646 return;
11647 }
11648 let text_layout_details = &self.text_layout_details(window);
11649 self.transact(window, cx, |this, window, cx| {
11650 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
11651 let mut edits = Vec::new();
11652 let mut selection_edit_ranges = Vec::new();
11653 let mut last_toggled_row = None;
11654 let snapshot = this.buffer.read(cx).read(cx);
11655 let empty_str: Arc<str> = Arc::default();
11656 let mut suffixes_inserted = Vec::new();
11657 let ignore_indent = action.ignore_indent;
11658
11659 fn comment_prefix_range(
11660 snapshot: &MultiBufferSnapshot,
11661 row: MultiBufferRow,
11662 comment_prefix: &str,
11663 comment_prefix_whitespace: &str,
11664 ignore_indent: bool,
11665 ) -> Range<Point> {
11666 let indent_size = if ignore_indent {
11667 0
11668 } else {
11669 snapshot.indent_size_for_line(row).len
11670 };
11671
11672 let start = Point::new(row.0, indent_size);
11673
11674 let mut line_bytes = snapshot
11675 .bytes_in_range(start..snapshot.max_point())
11676 .flatten()
11677 .copied();
11678
11679 // If this line currently begins with the line comment prefix, then record
11680 // the range containing the prefix.
11681 if line_bytes
11682 .by_ref()
11683 .take(comment_prefix.len())
11684 .eq(comment_prefix.bytes())
11685 {
11686 // Include any whitespace that matches the comment prefix.
11687 let matching_whitespace_len = line_bytes
11688 .zip(comment_prefix_whitespace.bytes())
11689 .take_while(|(a, b)| a == b)
11690 .count() as u32;
11691 let end = Point::new(
11692 start.row,
11693 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
11694 );
11695 start..end
11696 } else {
11697 start..start
11698 }
11699 }
11700
11701 fn comment_suffix_range(
11702 snapshot: &MultiBufferSnapshot,
11703 row: MultiBufferRow,
11704 comment_suffix: &str,
11705 comment_suffix_has_leading_space: bool,
11706 ) -> Range<Point> {
11707 let end = Point::new(row.0, snapshot.line_len(row));
11708 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
11709
11710 let mut line_end_bytes = snapshot
11711 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
11712 .flatten()
11713 .copied();
11714
11715 let leading_space_len = if suffix_start_column > 0
11716 && line_end_bytes.next() == Some(b' ')
11717 && comment_suffix_has_leading_space
11718 {
11719 1
11720 } else {
11721 0
11722 };
11723
11724 // If this line currently begins with the line comment prefix, then record
11725 // the range containing the prefix.
11726 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
11727 let start = Point::new(end.row, suffix_start_column - leading_space_len);
11728 start..end
11729 } else {
11730 end..end
11731 }
11732 }
11733
11734 // TODO: Handle selections that cross excerpts
11735 for selection in &mut selections {
11736 let start_column = snapshot
11737 .indent_size_for_line(MultiBufferRow(selection.start.row))
11738 .len;
11739 let language = if let Some(language) =
11740 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
11741 {
11742 language
11743 } else {
11744 continue;
11745 };
11746
11747 selection_edit_ranges.clear();
11748
11749 // If multiple selections contain a given row, avoid processing that
11750 // row more than once.
11751 let mut start_row = MultiBufferRow(selection.start.row);
11752 if last_toggled_row == Some(start_row) {
11753 start_row = start_row.next_row();
11754 }
11755 let end_row =
11756 if selection.end.row > selection.start.row && selection.end.column == 0 {
11757 MultiBufferRow(selection.end.row - 1)
11758 } else {
11759 MultiBufferRow(selection.end.row)
11760 };
11761 last_toggled_row = Some(end_row);
11762
11763 if start_row > end_row {
11764 continue;
11765 }
11766
11767 // If the language has line comments, toggle those.
11768 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
11769
11770 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
11771 if ignore_indent {
11772 full_comment_prefixes = full_comment_prefixes
11773 .into_iter()
11774 .map(|s| Arc::from(s.trim_end()))
11775 .collect();
11776 }
11777
11778 if !full_comment_prefixes.is_empty() {
11779 let first_prefix = full_comment_prefixes
11780 .first()
11781 .expect("prefixes is non-empty");
11782 let prefix_trimmed_lengths = full_comment_prefixes
11783 .iter()
11784 .map(|p| p.trim_end_matches(' ').len())
11785 .collect::<SmallVec<[usize; 4]>>();
11786
11787 let mut all_selection_lines_are_comments = true;
11788
11789 for row in start_row.0..=end_row.0 {
11790 let row = MultiBufferRow(row);
11791 if start_row < end_row && snapshot.is_line_blank(row) {
11792 continue;
11793 }
11794
11795 let prefix_range = full_comment_prefixes
11796 .iter()
11797 .zip(prefix_trimmed_lengths.iter().copied())
11798 .map(|(prefix, trimmed_prefix_len)| {
11799 comment_prefix_range(
11800 snapshot.deref(),
11801 row,
11802 &prefix[..trimmed_prefix_len],
11803 &prefix[trimmed_prefix_len..],
11804 ignore_indent,
11805 )
11806 })
11807 .max_by_key(|range| range.end.column - range.start.column)
11808 .expect("prefixes is non-empty");
11809
11810 if prefix_range.is_empty() {
11811 all_selection_lines_are_comments = false;
11812 }
11813
11814 selection_edit_ranges.push(prefix_range);
11815 }
11816
11817 if all_selection_lines_are_comments {
11818 edits.extend(
11819 selection_edit_ranges
11820 .iter()
11821 .cloned()
11822 .map(|range| (range, empty_str.clone())),
11823 );
11824 } else {
11825 let min_column = selection_edit_ranges
11826 .iter()
11827 .map(|range| range.start.column)
11828 .min()
11829 .unwrap_or(0);
11830 edits.extend(selection_edit_ranges.iter().map(|range| {
11831 let position = Point::new(range.start.row, min_column);
11832 (position..position, first_prefix.clone())
11833 }));
11834 }
11835 } else if let Some((full_comment_prefix, comment_suffix)) =
11836 language.block_comment_delimiters()
11837 {
11838 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
11839 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
11840 let prefix_range = comment_prefix_range(
11841 snapshot.deref(),
11842 start_row,
11843 comment_prefix,
11844 comment_prefix_whitespace,
11845 ignore_indent,
11846 );
11847 let suffix_range = comment_suffix_range(
11848 snapshot.deref(),
11849 end_row,
11850 comment_suffix.trim_start_matches(' '),
11851 comment_suffix.starts_with(' '),
11852 );
11853
11854 if prefix_range.is_empty() || suffix_range.is_empty() {
11855 edits.push((
11856 prefix_range.start..prefix_range.start,
11857 full_comment_prefix.clone(),
11858 ));
11859 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
11860 suffixes_inserted.push((end_row, comment_suffix.len()));
11861 } else {
11862 edits.push((prefix_range, empty_str.clone()));
11863 edits.push((suffix_range, empty_str.clone()));
11864 }
11865 } else {
11866 continue;
11867 }
11868 }
11869
11870 drop(snapshot);
11871 this.buffer.update(cx, |buffer, cx| {
11872 buffer.edit(edits, None, cx);
11873 });
11874
11875 // Adjust selections so that they end before any comment suffixes that
11876 // were inserted.
11877 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
11878 let mut selections = this.selections.all::<Point>(cx);
11879 let snapshot = this.buffer.read(cx).read(cx);
11880 for selection in &mut selections {
11881 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
11882 match row.cmp(&MultiBufferRow(selection.end.row)) {
11883 Ordering::Less => {
11884 suffixes_inserted.next();
11885 continue;
11886 }
11887 Ordering::Greater => break,
11888 Ordering::Equal => {
11889 if selection.end.column == snapshot.line_len(row) {
11890 if selection.is_empty() {
11891 selection.start.column -= suffix_len as u32;
11892 }
11893 selection.end.column -= suffix_len as u32;
11894 }
11895 break;
11896 }
11897 }
11898 }
11899 }
11900
11901 drop(snapshot);
11902 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11903 s.select(selections)
11904 });
11905
11906 let selections = this.selections.all::<Point>(cx);
11907 let selections_on_single_row = selections.windows(2).all(|selections| {
11908 selections[0].start.row == selections[1].start.row
11909 && selections[0].end.row == selections[1].end.row
11910 && selections[0].start.row == selections[0].end.row
11911 });
11912 let selections_selecting = selections
11913 .iter()
11914 .any(|selection| selection.start != selection.end);
11915 let advance_downwards = action.advance_downwards
11916 && selections_on_single_row
11917 && !selections_selecting
11918 && !matches!(this.mode, EditorMode::SingleLine { .. });
11919
11920 if advance_downwards {
11921 let snapshot = this.buffer.read(cx).snapshot(cx);
11922
11923 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11924 s.move_cursors_with(|display_snapshot, display_point, _| {
11925 let mut point = display_point.to_point(display_snapshot);
11926 point.row += 1;
11927 point = snapshot.clip_point(point, Bias::Left);
11928 let display_point = point.to_display_point(display_snapshot);
11929 let goal = SelectionGoal::HorizontalPosition(
11930 display_snapshot
11931 .x_for_display_point(display_point, text_layout_details)
11932 .into(),
11933 );
11934 (display_point, goal)
11935 })
11936 });
11937 }
11938 });
11939 }
11940
11941 pub fn select_enclosing_symbol(
11942 &mut self,
11943 _: &SelectEnclosingSymbol,
11944 window: &mut Window,
11945 cx: &mut Context<Self>,
11946 ) {
11947 let buffer = self.buffer.read(cx).snapshot(cx);
11948 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
11949
11950 fn update_selection(
11951 selection: &Selection<usize>,
11952 buffer_snap: &MultiBufferSnapshot,
11953 ) -> Option<Selection<usize>> {
11954 let cursor = selection.head();
11955 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
11956 for symbol in symbols.iter().rev() {
11957 let start = symbol.range.start.to_offset(buffer_snap);
11958 let end = symbol.range.end.to_offset(buffer_snap);
11959 let new_range = start..end;
11960 if start < selection.start || end > selection.end {
11961 return Some(Selection {
11962 id: selection.id,
11963 start: new_range.start,
11964 end: new_range.end,
11965 goal: SelectionGoal::None,
11966 reversed: selection.reversed,
11967 });
11968 }
11969 }
11970 None
11971 }
11972
11973 let mut selected_larger_symbol = false;
11974 let new_selections = old_selections
11975 .iter()
11976 .map(|selection| match update_selection(selection, &buffer) {
11977 Some(new_selection) => {
11978 if new_selection.range() != selection.range() {
11979 selected_larger_symbol = true;
11980 }
11981 new_selection
11982 }
11983 None => selection.clone(),
11984 })
11985 .collect::<Vec<_>>();
11986
11987 if selected_larger_symbol {
11988 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11989 s.select(new_selections);
11990 });
11991 }
11992 }
11993
11994 pub fn select_larger_syntax_node(
11995 &mut self,
11996 _: &SelectLargerSyntaxNode,
11997 window: &mut Window,
11998 cx: &mut Context<Self>,
11999 ) {
12000 let Some(visible_row_count) = self.visible_row_count() else {
12001 return;
12002 };
12003 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
12004 if old_selections.is_empty() {
12005 return;
12006 }
12007
12008 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12009 let buffer = self.buffer.read(cx).snapshot(cx);
12010
12011 let mut selected_larger_node = false;
12012 let mut new_selections = old_selections
12013 .iter()
12014 .map(|selection| {
12015 let old_range = selection.start..selection.end;
12016 let mut new_range = old_range.clone();
12017 let mut new_node = None;
12018 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
12019 {
12020 new_node = Some(node);
12021 new_range = match containing_range {
12022 MultiOrSingleBufferOffsetRange::Single(_) => break,
12023 MultiOrSingleBufferOffsetRange::Multi(range) => range,
12024 };
12025 if !display_map.intersects_fold(new_range.start)
12026 && !display_map.intersects_fold(new_range.end)
12027 {
12028 break;
12029 }
12030 }
12031
12032 if let Some(node) = new_node {
12033 // Log the ancestor, to support using this action as a way to explore TreeSitter
12034 // nodes. Parent and grandparent are also logged because this operation will not
12035 // visit nodes that have the same range as their parent.
12036 log::info!("Node: {node:?}");
12037 let parent = node.parent();
12038 log::info!("Parent: {parent:?}");
12039 let grandparent = parent.and_then(|x| x.parent());
12040 log::info!("Grandparent: {grandparent:?}");
12041 }
12042
12043 selected_larger_node |= new_range != old_range;
12044 Selection {
12045 id: selection.id,
12046 start: new_range.start,
12047 end: new_range.end,
12048 goal: SelectionGoal::None,
12049 reversed: selection.reversed,
12050 }
12051 })
12052 .collect::<Vec<_>>();
12053
12054 if !selected_larger_node {
12055 return; // don't put this call in the history
12056 }
12057
12058 // scroll based on transformation done to the last selection created by the user
12059 let (last_old, last_new) = old_selections
12060 .last()
12061 .zip(new_selections.last().cloned())
12062 .expect("old_selections isn't empty");
12063
12064 // revert selection
12065 let is_selection_reversed = {
12066 let should_newest_selection_be_reversed = last_old.start != last_new.start;
12067 new_selections.last_mut().expect("checked above").reversed =
12068 should_newest_selection_be_reversed;
12069 should_newest_selection_be_reversed
12070 };
12071
12072 if selected_larger_node {
12073 self.select_syntax_node_history.disable_clearing = true;
12074 self.change_selections(None, window, cx, |s| {
12075 s.select(new_selections.clone());
12076 });
12077 self.select_syntax_node_history.disable_clearing = false;
12078 }
12079
12080 let start_row = last_new.start.to_display_point(&display_map).row().0;
12081 let end_row = last_new.end.to_display_point(&display_map).row().0;
12082 let selection_height = end_row - start_row + 1;
12083 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
12084
12085 // if fits on screen (considering margin), keep it in the middle, else, scroll to selection head
12086 let scroll_behavior = if visible_row_count >= selection_height + scroll_margin_rows * 2 {
12087 let middle_row = (end_row + start_row) / 2;
12088 let selection_center = middle_row.saturating_sub(visible_row_count / 2);
12089 self.set_scroll_top_row(DisplayRow(selection_center), window, cx);
12090 SelectSyntaxNodeScrollBehavior::CenterSelection
12091 } else if is_selection_reversed {
12092 self.scroll_cursor_top(&Default::default(), window, cx);
12093 SelectSyntaxNodeScrollBehavior::CursorTop
12094 } else {
12095 self.scroll_cursor_bottom(&Default::default(), window, cx);
12096 SelectSyntaxNodeScrollBehavior::CursorBottom
12097 };
12098
12099 self.select_syntax_node_history.push((
12100 old_selections,
12101 scroll_behavior,
12102 is_selection_reversed,
12103 ));
12104 }
12105
12106 pub fn select_smaller_syntax_node(
12107 &mut self,
12108 _: &SelectSmallerSyntaxNode,
12109 window: &mut Window,
12110 cx: &mut Context<Self>,
12111 ) {
12112 let Some(visible_row_count) = self.visible_row_count() else {
12113 return;
12114 };
12115
12116 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
12117 self.select_syntax_node_history.pop()
12118 {
12119 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12120
12121 if let Some(selection) = selections.last_mut() {
12122 selection.reversed = is_selection_reversed;
12123 }
12124
12125 self.select_syntax_node_history.disable_clearing = true;
12126 self.change_selections(None, window, cx, |s| {
12127 s.select(selections.to_vec());
12128 });
12129 self.select_syntax_node_history.disable_clearing = false;
12130
12131 let newest = self.selections.newest::<usize>(cx);
12132 let start_row = newest.start.to_display_point(&display_map).row().0;
12133 let end_row = newest.end.to_display_point(&display_map).row().0;
12134
12135 match scroll_behavior {
12136 SelectSyntaxNodeScrollBehavior::CursorTop => {
12137 self.scroll_cursor_top(&Default::default(), window, cx);
12138 }
12139 SelectSyntaxNodeScrollBehavior::CenterSelection => {
12140 let middle_row = (end_row + start_row) / 2;
12141 let selection_center = middle_row.saturating_sub(visible_row_count / 2);
12142 // centralize the selection, not the cursor
12143 self.set_scroll_top_row(DisplayRow(selection_center), window, cx);
12144 }
12145 SelectSyntaxNodeScrollBehavior::CursorBottom => {
12146 self.scroll_cursor_bottom(&Default::default(), window, cx);
12147 }
12148 }
12149 }
12150 }
12151
12152 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
12153 if !EditorSettings::get_global(cx).gutter.runnables {
12154 self.clear_tasks();
12155 return Task::ready(());
12156 }
12157 let project = self.project.as_ref().map(Entity::downgrade);
12158 cx.spawn_in(window, async move |this, cx| {
12159 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
12160 let Some(project) = project.and_then(|p| p.upgrade()) else {
12161 return;
12162 };
12163 let Ok(display_snapshot) = this.update(cx, |this, cx| {
12164 this.display_map.update(cx, |map, cx| map.snapshot(cx))
12165 }) else {
12166 return;
12167 };
12168
12169 let hide_runnables = project
12170 .update(cx, |project, cx| {
12171 // Do not display any test indicators in non-dev server remote projects.
12172 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
12173 })
12174 .unwrap_or(true);
12175 if hide_runnables {
12176 return;
12177 }
12178 let new_rows =
12179 cx.background_spawn({
12180 let snapshot = display_snapshot.clone();
12181 async move {
12182 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
12183 }
12184 })
12185 .await;
12186
12187 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
12188 this.update(cx, |this, _| {
12189 this.clear_tasks();
12190 for (key, value) in rows {
12191 this.insert_tasks(key, value);
12192 }
12193 })
12194 .ok();
12195 })
12196 }
12197 fn fetch_runnable_ranges(
12198 snapshot: &DisplaySnapshot,
12199 range: Range<Anchor>,
12200 ) -> Vec<language::RunnableRange> {
12201 snapshot.buffer_snapshot.runnable_ranges(range).collect()
12202 }
12203
12204 fn runnable_rows(
12205 project: Entity<Project>,
12206 snapshot: DisplaySnapshot,
12207 runnable_ranges: Vec<RunnableRange>,
12208 mut cx: AsyncWindowContext,
12209 ) -> Vec<((BufferId, u32), RunnableTasks)> {
12210 runnable_ranges
12211 .into_iter()
12212 .filter_map(|mut runnable| {
12213 let tasks = cx
12214 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
12215 .ok()?;
12216 if tasks.is_empty() {
12217 return None;
12218 }
12219
12220 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
12221
12222 let row = snapshot
12223 .buffer_snapshot
12224 .buffer_line_for_row(MultiBufferRow(point.row))?
12225 .1
12226 .start
12227 .row;
12228
12229 let context_range =
12230 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
12231 Some((
12232 (runnable.buffer_id, row),
12233 RunnableTasks {
12234 templates: tasks,
12235 offset: snapshot
12236 .buffer_snapshot
12237 .anchor_before(runnable.run_range.start),
12238 context_range,
12239 column: point.column,
12240 extra_variables: runnable.extra_captures,
12241 },
12242 ))
12243 })
12244 .collect()
12245 }
12246
12247 fn templates_with_tags(
12248 project: &Entity<Project>,
12249 runnable: &mut Runnable,
12250 cx: &mut App,
12251 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
12252 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
12253 let (worktree_id, file) = project
12254 .buffer_for_id(runnable.buffer, cx)
12255 .and_then(|buffer| buffer.read(cx).file())
12256 .map(|file| (file.worktree_id(cx), file.clone()))
12257 .unzip();
12258
12259 (
12260 project.task_store().read(cx).task_inventory().cloned(),
12261 worktree_id,
12262 file,
12263 )
12264 });
12265
12266 let tags = mem::take(&mut runnable.tags);
12267 let mut tags: Vec<_> = tags
12268 .into_iter()
12269 .flat_map(|tag| {
12270 let tag = tag.0.clone();
12271 inventory
12272 .as_ref()
12273 .into_iter()
12274 .flat_map(|inventory| {
12275 inventory.read(cx).list_tasks(
12276 file.clone(),
12277 Some(runnable.language.clone()),
12278 worktree_id,
12279 cx,
12280 )
12281 })
12282 .filter(move |(_, template)| {
12283 template.tags.iter().any(|source_tag| source_tag == &tag)
12284 })
12285 })
12286 .sorted_by_key(|(kind, _)| kind.to_owned())
12287 .collect();
12288 if let Some((leading_tag_source, _)) = tags.first() {
12289 // Strongest source wins; if we have worktree tag binding, prefer that to
12290 // global and language bindings;
12291 // if we have a global binding, prefer that to language binding.
12292 let first_mismatch = tags
12293 .iter()
12294 .position(|(tag_source, _)| tag_source != leading_tag_source);
12295 if let Some(index) = first_mismatch {
12296 tags.truncate(index);
12297 }
12298 }
12299
12300 tags
12301 }
12302
12303 pub fn move_to_enclosing_bracket(
12304 &mut self,
12305 _: &MoveToEnclosingBracket,
12306 window: &mut Window,
12307 cx: &mut Context<Self>,
12308 ) {
12309 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12310 s.move_offsets_with(|snapshot, selection| {
12311 let Some(enclosing_bracket_ranges) =
12312 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
12313 else {
12314 return;
12315 };
12316
12317 let mut best_length = usize::MAX;
12318 let mut best_inside = false;
12319 let mut best_in_bracket_range = false;
12320 let mut best_destination = None;
12321 for (open, close) in enclosing_bracket_ranges {
12322 let close = close.to_inclusive();
12323 let length = close.end() - open.start;
12324 let inside = selection.start >= open.end && selection.end <= *close.start();
12325 let in_bracket_range = open.to_inclusive().contains(&selection.head())
12326 || close.contains(&selection.head());
12327
12328 // If best is next to a bracket and current isn't, skip
12329 if !in_bracket_range && best_in_bracket_range {
12330 continue;
12331 }
12332
12333 // Prefer smaller lengths unless best is inside and current isn't
12334 if length > best_length && (best_inside || !inside) {
12335 continue;
12336 }
12337
12338 best_length = length;
12339 best_inside = inside;
12340 best_in_bracket_range = in_bracket_range;
12341 best_destination = Some(
12342 if close.contains(&selection.start) && close.contains(&selection.end) {
12343 if inside {
12344 open.end
12345 } else {
12346 open.start
12347 }
12348 } else if inside {
12349 *close.start()
12350 } else {
12351 *close.end()
12352 },
12353 );
12354 }
12355
12356 if let Some(destination) = best_destination {
12357 selection.collapse_to(destination, SelectionGoal::None);
12358 }
12359 })
12360 });
12361 }
12362
12363 pub fn undo_selection(
12364 &mut self,
12365 _: &UndoSelection,
12366 window: &mut Window,
12367 cx: &mut Context<Self>,
12368 ) {
12369 self.end_selection(window, cx);
12370 self.selection_history.mode = SelectionHistoryMode::Undoing;
12371 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
12372 self.change_selections(None, window, cx, |s| {
12373 s.select_anchors(entry.selections.to_vec())
12374 });
12375 self.select_next_state = entry.select_next_state;
12376 self.select_prev_state = entry.select_prev_state;
12377 self.add_selections_state = entry.add_selections_state;
12378 self.request_autoscroll(Autoscroll::newest(), cx);
12379 }
12380 self.selection_history.mode = SelectionHistoryMode::Normal;
12381 }
12382
12383 pub fn redo_selection(
12384 &mut self,
12385 _: &RedoSelection,
12386 window: &mut Window,
12387 cx: &mut Context<Self>,
12388 ) {
12389 self.end_selection(window, cx);
12390 self.selection_history.mode = SelectionHistoryMode::Redoing;
12391 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
12392 self.change_selections(None, window, cx, |s| {
12393 s.select_anchors(entry.selections.to_vec())
12394 });
12395 self.select_next_state = entry.select_next_state;
12396 self.select_prev_state = entry.select_prev_state;
12397 self.add_selections_state = entry.add_selections_state;
12398 self.request_autoscroll(Autoscroll::newest(), cx);
12399 }
12400 self.selection_history.mode = SelectionHistoryMode::Normal;
12401 }
12402
12403 pub fn expand_excerpts(
12404 &mut self,
12405 action: &ExpandExcerpts,
12406 _: &mut Window,
12407 cx: &mut Context<Self>,
12408 ) {
12409 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
12410 }
12411
12412 pub fn expand_excerpts_down(
12413 &mut self,
12414 action: &ExpandExcerptsDown,
12415 _: &mut Window,
12416 cx: &mut Context<Self>,
12417 ) {
12418 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
12419 }
12420
12421 pub fn expand_excerpts_up(
12422 &mut self,
12423 action: &ExpandExcerptsUp,
12424 _: &mut Window,
12425 cx: &mut Context<Self>,
12426 ) {
12427 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
12428 }
12429
12430 pub fn expand_excerpts_for_direction(
12431 &mut self,
12432 lines: u32,
12433 direction: ExpandExcerptDirection,
12434
12435 cx: &mut Context<Self>,
12436 ) {
12437 let selections = self.selections.disjoint_anchors();
12438
12439 let lines = if lines == 0 {
12440 EditorSettings::get_global(cx).expand_excerpt_lines
12441 } else {
12442 lines
12443 };
12444
12445 self.buffer.update(cx, |buffer, cx| {
12446 let snapshot = buffer.snapshot(cx);
12447 let mut excerpt_ids = selections
12448 .iter()
12449 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
12450 .collect::<Vec<_>>();
12451 excerpt_ids.sort();
12452 excerpt_ids.dedup();
12453 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
12454 })
12455 }
12456
12457 pub fn expand_excerpt(
12458 &mut self,
12459 excerpt: ExcerptId,
12460 direction: ExpandExcerptDirection,
12461 window: &mut Window,
12462 cx: &mut Context<Self>,
12463 ) {
12464 let current_scroll_position = self.scroll_position(cx);
12465 let lines = EditorSettings::get_global(cx).expand_excerpt_lines;
12466 self.buffer.update(cx, |buffer, cx| {
12467 buffer.expand_excerpts([excerpt], lines, direction, cx)
12468 });
12469 if direction == ExpandExcerptDirection::Down {
12470 let new_scroll_position = current_scroll_position + gpui::Point::new(0.0, lines as f32);
12471 self.set_scroll_position(new_scroll_position, window, cx);
12472 }
12473 }
12474
12475 pub fn go_to_singleton_buffer_point(
12476 &mut self,
12477 point: Point,
12478 window: &mut Window,
12479 cx: &mut Context<Self>,
12480 ) {
12481 self.go_to_singleton_buffer_range(point..point, window, cx);
12482 }
12483
12484 pub fn go_to_singleton_buffer_range(
12485 &mut self,
12486 range: Range<Point>,
12487 window: &mut Window,
12488 cx: &mut Context<Self>,
12489 ) {
12490 let multibuffer = self.buffer().read(cx);
12491 let Some(buffer) = multibuffer.as_singleton() else {
12492 return;
12493 };
12494 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
12495 return;
12496 };
12497 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
12498 return;
12499 };
12500 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
12501 s.select_anchor_ranges([start..end])
12502 });
12503 }
12504
12505 fn go_to_diagnostic(
12506 &mut self,
12507 _: &GoToDiagnostic,
12508 window: &mut Window,
12509 cx: &mut Context<Self>,
12510 ) {
12511 self.go_to_diagnostic_impl(Direction::Next, window, cx)
12512 }
12513
12514 fn go_to_prev_diagnostic(
12515 &mut self,
12516 _: &GoToPreviousDiagnostic,
12517 window: &mut Window,
12518 cx: &mut Context<Self>,
12519 ) {
12520 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
12521 }
12522
12523 pub fn go_to_diagnostic_impl(
12524 &mut self,
12525 direction: Direction,
12526 window: &mut Window,
12527 cx: &mut Context<Self>,
12528 ) {
12529 let buffer = self.buffer.read(cx).snapshot(cx);
12530 let selection = self.selections.newest::<usize>(cx);
12531
12532 // If there is an active Diagnostic Popover jump to its diagnostic instead.
12533 if direction == Direction::Next {
12534 if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() {
12535 let Some(buffer_id) = popover.local_diagnostic.range.start.buffer_id else {
12536 return;
12537 };
12538 self.activate_diagnostics(
12539 buffer_id,
12540 popover.local_diagnostic.diagnostic.group_id,
12541 window,
12542 cx,
12543 );
12544 if let Some(active_diagnostics) = self.active_diagnostics.as_ref() {
12545 let primary_range_start = active_diagnostics.primary_range.start;
12546 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12547 let mut new_selection = s.newest_anchor().clone();
12548 new_selection.collapse_to(primary_range_start, SelectionGoal::None);
12549 s.select_anchors(vec![new_selection.clone()]);
12550 });
12551 self.refresh_inline_completion(false, true, window, cx);
12552 }
12553 return;
12554 }
12555 }
12556
12557 let active_group_id = self
12558 .active_diagnostics
12559 .as_ref()
12560 .map(|active_group| active_group.group_id);
12561 let active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| {
12562 active_diagnostics
12563 .primary_range
12564 .to_offset(&buffer)
12565 .to_inclusive()
12566 });
12567 let search_start = if let Some(active_primary_range) = active_primary_range.as_ref() {
12568 if active_primary_range.contains(&selection.head()) {
12569 *active_primary_range.start()
12570 } else {
12571 selection.head()
12572 }
12573 } else {
12574 selection.head()
12575 };
12576
12577 let snapshot = self.snapshot(window, cx);
12578 let primary_diagnostics_before = buffer
12579 .diagnostics_in_range::<usize>(0..search_start)
12580 .filter(|entry| entry.diagnostic.is_primary)
12581 .filter(|entry| entry.range.start != entry.range.end)
12582 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
12583 .filter(|entry| !snapshot.intersects_fold(entry.range.start))
12584 .collect::<Vec<_>>();
12585 let last_same_group_diagnostic_before = active_group_id.and_then(|active_group_id| {
12586 primary_diagnostics_before
12587 .iter()
12588 .position(|entry| entry.diagnostic.group_id == active_group_id)
12589 });
12590
12591 let primary_diagnostics_after = buffer
12592 .diagnostics_in_range::<usize>(search_start..buffer.len())
12593 .filter(|entry| entry.diagnostic.is_primary)
12594 .filter(|entry| entry.range.start != entry.range.end)
12595 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
12596 .filter(|diagnostic| !snapshot.intersects_fold(diagnostic.range.start))
12597 .collect::<Vec<_>>();
12598 let last_same_group_diagnostic_after = active_group_id.and_then(|active_group_id| {
12599 primary_diagnostics_after
12600 .iter()
12601 .enumerate()
12602 .rev()
12603 .find_map(|(i, entry)| {
12604 if entry.diagnostic.group_id == active_group_id {
12605 Some(i)
12606 } else {
12607 None
12608 }
12609 })
12610 });
12611
12612 let next_primary_diagnostic = match direction {
12613 Direction::Prev => primary_diagnostics_before
12614 .iter()
12615 .take(last_same_group_diagnostic_before.unwrap_or(usize::MAX))
12616 .rev()
12617 .next(),
12618 Direction::Next => primary_diagnostics_after
12619 .iter()
12620 .skip(
12621 last_same_group_diagnostic_after
12622 .map(|index| index + 1)
12623 .unwrap_or(0),
12624 )
12625 .next(),
12626 };
12627
12628 // Cycle around to the start of the buffer, potentially moving back to the start of
12629 // the currently active diagnostic.
12630 let cycle_around = || match direction {
12631 Direction::Prev => primary_diagnostics_after
12632 .iter()
12633 .rev()
12634 .chain(primary_diagnostics_before.iter().rev())
12635 .next(),
12636 Direction::Next => primary_diagnostics_before
12637 .iter()
12638 .chain(primary_diagnostics_after.iter())
12639 .next(),
12640 };
12641
12642 if let Some((primary_range, group_id)) = next_primary_diagnostic
12643 .or_else(cycle_around)
12644 .map(|entry| (&entry.range, entry.diagnostic.group_id))
12645 {
12646 let Some(buffer_id) = buffer.anchor_after(primary_range.start).buffer_id else {
12647 return;
12648 };
12649 self.activate_diagnostics(buffer_id, group_id, window, cx);
12650 if self.active_diagnostics.is_some() {
12651 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12652 s.select(vec![Selection {
12653 id: selection.id,
12654 start: primary_range.start,
12655 end: primary_range.start,
12656 reversed: false,
12657 goal: SelectionGoal::None,
12658 }]);
12659 });
12660 self.refresh_inline_completion(false, true, window, cx);
12661 }
12662 }
12663 }
12664
12665 fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
12666 let snapshot = self.snapshot(window, cx);
12667 let selection = self.selections.newest::<Point>(cx);
12668 self.go_to_hunk_before_or_after_position(
12669 &snapshot,
12670 selection.head(),
12671 Direction::Next,
12672 window,
12673 cx,
12674 );
12675 }
12676
12677 pub fn go_to_hunk_before_or_after_position(
12678 &mut self,
12679 snapshot: &EditorSnapshot,
12680 position: Point,
12681 direction: Direction,
12682 window: &mut Window,
12683 cx: &mut Context<Editor>,
12684 ) {
12685 let row = if direction == Direction::Next {
12686 self.hunk_after_position(snapshot, position)
12687 .map(|hunk| hunk.row_range.start)
12688 } else {
12689 self.hunk_before_position(snapshot, position)
12690 };
12691
12692 if let Some(row) = row {
12693 let destination = Point::new(row.0, 0);
12694 let autoscroll = Autoscroll::center();
12695
12696 self.unfold_ranges(&[destination..destination], false, false, cx);
12697 self.change_selections(Some(autoscroll), window, cx, |s| {
12698 s.select_ranges([destination..destination]);
12699 });
12700 }
12701 }
12702
12703 fn hunk_after_position(
12704 &mut self,
12705 snapshot: &EditorSnapshot,
12706 position: Point,
12707 ) -> Option<MultiBufferDiffHunk> {
12708 snapshot
12709 .buffer_snapshot
12710 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
12711 .find(|hunk| hunk.row_range.start.0 > position.row)
12712 .or_else(|| {
12713 snapshot
12714 .buffer_snapshot
12715 .diff_hunks_in_range(Point::zero()..position)
12716 .find(|hunk| hunk.row_range.end.0 < position.row)
12717 })
12718 }
12719
12720 fn go_to_prev_hunk(
12721 &mut self,
12722 _: &GoToPreviousHunk,
12723 window: &mut Window,
12724 cx: &mut Context<Self>,
12725 ) {
12726 let snapshot = self.snapshot(window, cx);
12727 let selection = self.selections.newest::<Point>(cx);
12728 self.go_to_hunk_before_or_after_position(
12729 &snapshot,
12730 selection.head(),
12731 Direction::Prev,
12732 window,
12733 cx,
12734 );
12735 }
12736
12737 fn hunk_before_position(
12738 &mut self,
12739 snapshot: &EditorSnapshot,
12740 position: Point,
12741 ) -> Option<MultiBufferRow> {
12742 snapshot
12743 .buffer_snapshot
12744 .diff_hunk_before(position)
12745 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
12746 }
12747
12748 fn go_to_line<T: 'static>(
12749 &mut self,
12750 position: Anchor,
12751 highlight_color: Option<Hsla>,
12752 window: &mut Window,
12753 cx: &mut Context<Self>,
12754 ) {
12755 let snapshot = self.snapshot(window, cx).display_snapshot;
12756 let position = position.to_point(&snapshot.buffer_snapshot);
12757 let start = snapshot
12758 .buffer_snapshot
12759 .clip_point(Point::new(position.row, 0), Bias::Left);
12760 let end = start + Point::new(1, 0);
12761 let start = snapshot.buffer_snapshot.anchor_before(start);
12762 let end = snapshot.buffer_snapshot.anchor_before(end);
12763
12764 self.highlight_rows::<T>(
12765 start..end,
12766 highlight_color
12767 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
12768 false,
12769 cx,
12770 );
12771 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
12772 }
12773
12774 pub fn go_to_definition(
12775 &mut self,
12776 _: &GoToDefinition,
12777 window: &mut Window,
12778 cx: &mut Context<Self>,
12779 ) -> Task<Result<Navigated>> {
12780 let definition =
12781 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
12782 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
12783 cx.spawn_in(window, async move |editor, cx| {
12784 if definition.await? == Navigated::Yes {
12785 return Ok(Navigated::Yes);
12786 }
12787 match fallback_strategy {
12788 GoToDefinitionFallback::None => Ok(Navigated::No),
12789 GoToDefinitionFallback::FindAllReferences => {
12790 match editor.update_in(cx, |editor, window, cx| {
12791 editor.find_all_references(&FindAllReferences, window, cx)
12792 })? {
12793 Some(references) => references.await,
12794 None => Ok(Navigated::No),
12795 }
12796 }
12797 }
12798 })
12799 }
12800
12801 pub fn go_to_declaration(
12802 &mut self,
12803 _: &GoToDeclaration,
12804 window: &mut Window,
12805 cx: &mut Context<Self>,
12806 ) -> Task<Result<Navigated>> {
12807 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
12808 }
12809
12810 pub fn go_to_declaration_split(
12811 &mut self,
12812 _: &GoToDeclaration,
12813 window: &mut Window,
12814 cx: &mut Context<Self>,
12815 ) -> Task<Result<Navigated>> {
12816 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
12817 }
12818
12819 pub fn go_to_implementation(
12820 &mut self,
12821 _: &GoToImplementation,
12822 window: &mut Window,
12823 cx: &mut Context<Self>,
12824 ) -> Task<Result<Navigated>> {
12825 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
12826 }
12827
12828 pub fn go_to_implementation_split(
12829 &mut self,
12830 _: &GoToImplementationSplit,
12831 window: &mut Window,
12832 cx: &mut Context<Self>,
12833 ) -> Task<Result<Navigated>> {
12834 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
12835 }
12836
12837 pub fn go_to_type_definition(
12838 &mut self,
12839 _: &GoToTypeDefinition,
12840 window: &mut Window,
12841 cx: &mut Context<Self>,
12842 ) -> Task<Result<Navigated>> {
12843 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
12844 }
12845
12846 pub fn go_to_definition_split(
12847 &mut self,
12848 _: &GoToDefinitionSplit,
12849 window: &mut Window,
12850 cx: &mut Context<Self>,
12851 ) -> Task<Result<Navigated>> {
12852 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
12853 }
12854
12855 pub fn go_to_type_definition_split(
12856 &mut self,
12857 _: &GoToTypeDefinitionSplit,
12858 window: &mut Window,
12859 cx: &mut Context<Self>,
12860 ) -> Task<Result<Navigated>> {
12861 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
12862 }
12863
12864 fn go_to_definition_of_kind(
12865 &mut self,
12866 kind: GotoDefinitionKind,
12867 split: bool,
12868 window: &mut Window,
12869 cx: &mut Context<Self>,
12870 ) -> Task<Result<Navigated>> {
12871 let Some(provider) = self.semantics_provider.clone() else {
12872 return Task::ready(Ok(Navigated::No));
12873 };
12874 let head = self.selections.newest::<usize>(cx).head();
12875 let buffer = self.buffer.read(cx);
12876 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
12877 text_anchor
12878 } else {
12879 return Task::ready(Ok(Navigated::No));
12880 };
12881
12882 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
12883 return Task::ready(Ok(Navigated::No));
12884 };
12885
12886 cx.spawn_in(window, async move |editor, cx| {
12887 let definitions = definitions.await?;
12888 let navigated = editor
12889 .update_in(cx, |editor, window, cx| {
12890 editor.navigate_to_hover_links(
12891 Some(kind),
12892 definitions
12893 .into_iter()
12894 .filter(|location| {
12895 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
12896 })
12897 .map(HoverLink::Text)
12898 .collect::<Vec<_>>(),
12899 split,
12900 window,
12901 cx,
12902 )
12903 })?
12904 .await?;
12905 anyhow::Ok(navigated)
12906 })
12907 }
12908
12909 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
12910 let selection = self.selections.newest_anchor();
12911 let head = selection.head();
12912 let tail = selection.tail();
12913
12914 let Some((buffer, start_position)) =
12915 self.buffer.read(cx).text_anchor_for_position(head, cx)
12916 else {
12917 return;
12918 };
12919
12920 let end_position = if head != tail {
12921 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
12922 return;
12923 };
12924 Some(pos)
12925 } else {
12926 None
12927 };
12928
12929 let url_finder = cx.spawn_in(window, async move |editor, cx| {
12930 let url = if let Some(end_pos) = end_position {
12931 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
12932 } else {
12933 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
12934 };
12935
12936 if let Some(url) = url {
12937 editor.update(cx, |_, cx| {
12938 cx.open_url(&url);
12939 })
12940 } else {
12941 Ok(())
12942 }
12943 });
12944
12945 url_finder.detach();
12946 }
12947
12948 pub fn open_selected_filename(
12949 &mut self,
12950 _: &OpenSelectedFilename,
12951 window: &mut Window,
12952 cx: &mut Context<Self>,
12953 ) {
12954 let Some(workspace) = self.workspace() else {
12955 return;
12956 };
12957
12958 let position = self.selections.newest_anchor().head();
12959
12960 let Some((buffer, buffer_position)) =
12961 self.buffer.read(cx).text_anchor_for_position(position, cx)
12962 else {
12963 return;
12964 };
12965
12966 let project = self.project.clone();
12967
12968 cx.spawn_in(window, async move |_, cx| {
12969 let result = find_file(&buffer, project, buffer_position, cx).await;
12970
12971 if let Some((_, path)) = result {
12972 workspace
12973 .update_in(cx, |workspace, window, cx| {
12974 workspace.open_resolved_path(path, window, cx)
12975 })?
12976 .await?;
12977 }
12978 anyhow::Ok(())
12979 })
12980 .detach();
12981 }
12982
12983 pub(crate) fn navigate_to_hover_links(
12984 &mut self,
12985 kind: Option<GotoDefinitionKind>,
12986 mut definitions: Vec<HoverLink>,
12987 split: bool,
12988 window: &mut Window,
12989 cx: &mut Context<Editor>,
12990 ) -> Task<Result<Navigated>> {
12991 // If there is one definition, just open it directly
12992 if definitions.len() == 1 {
12993 let definition = definitions.pop().unwrap();
12994
12995 enum TargetTaskResult {
12996 Location(Option<Location>),
12997 AlreadyNavigated,
12998 }
12999
13000 let target_task = match definition {
13001 HoverLink::Text(link) => {
13002 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
13003 }
13004 HoverLink::InlayHint(lsp_location, server_id) => {
13005 let computation =
13006 self.compute_target_location(lsp_location, server_id, window, cx);
13007 cx.background_spawn(async move {
13008 let location = computation.await?;
13009 Ok(TargetTaskResult::Location(location))
13010 })
13011 }
13012 HoverLink::Url(url) => {
13013 cx.open_url(&url);
13014 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
13015 }
13016 HoverLink::File(path) => {
13017 if let Some(workspace) = self.workspace() {
13018 cx.spawn_in(window, async move |_, cx| {
13019 workspace
13020 .update_in(cx, |workspace, window, cx| {
13021 workspace.open_resolved_path(path, window, cx)
13022 })?
13023 .await
13024 .map(|_| TargetTaskResult::AlreadyNavigated)
13025 })
13026 } else {
13027 Task::ready(Ok(TargetTaskResult::Location(None)))
13028 }
13029 }
13030 };
13031 cx.spawn_in(window, async move |editor, cx| {
13032 let target = match target_task.await.context("target resolution task")? {
13033 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
13034 TargetTaskResult::Location(None) => return Ok(Navigated::No),
13035 TargetTaskResult::Location(Some(target)) => target,
13036 };
13037
13038 editor.update_in(cx, |editor, window, cx| {
13039 let Some(workspace) = editor.workspace() else {
13040 return Navigated::No;
13041 };
13042 let pane = workspace.read(cx).active_pane().clone();
13043
13044 let range = target.range.to_point(target.buffer.read(cx));
13045 let range = editor.range_for_match(&range);
13046 let range = collapse_multiline_range(range);
13047
13048 if !split
13049 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
13050 {
13051 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
13052 } else {
13053 window.defer(cx, move |window, cx| {
13054 let target_editor: Entity<Self> =
13055 workspace.update(cx, |workspace, cx| {
13056 let pane = if split {
13057 workspace.adjacent_pane(window, cx)
13058 } else {
13059 workspace.active_pane().clone()
13060 };
13061
13062 workspace.open_project_item(
13063 pane,
13064 target.buffer.clone(),
13065 true,
13066 true,
13067 window,
13068 cx,
13069 )
13070 });
13071 target_editor.update(cx, |target_editor, cx| {
13072 // When selecting a definition in a different buffer, disable the nav history
13073 // to avoid creating a history entry at the previous cursor location.
13074 pane.update(cx, |pane, _| pane.disable_history());
13075 target_editor.go_to_singleton_buffer_range(range, window, cx);
13076 pane.update(cx, |pane, _| pane.enable_history());
13077 });
13078 });
13079 }
13080 Navigated::Yes
13081 })
13082 })
13083 } else if !definitions.is_empty() {
13084 cx.spawn_in(window, async move |editor, cx| {
13085 let (title, location_tasks, workspace) = editor
13086 .update_in(cx, |editor, window, cx| {
13087 let tab_kind = match kind {
13088 Some(GotoDefinitionKind::Implementation) => "Implementations",
13089 _ => "Definitions",
13090 };
13091 let title = definitions
13092 .iter()
13093 .find_map(|definition| match definition {
13094 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
13095 let buffer = origin.buffer.read(cx);
13096 format!(
13097 "{} for {}",
13098 tab_kind,
13099 buffer
13100 .text_for_range(origin.range.clone())
13101 .collect::<String>()
13102 )
13103 }),
13104 HoverLink::InlayHint(_, _) => None,
13105 HoverLink::Url(_) => None,
13106 HoverLink::File(_) => None,
13107 })
13108 .unwrap_or(tab_kind.to_string());
13109 let location_tasks = definitions
13110 .into_iter()
13111 .map(|definition| match definition {
13112 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
13113 HoverLink::InlayHint(lsp_location, server_id) => editor
13114 .compute_target_location(lsp_location, server_id, window, cx),
13115 HoverLink::Url(_) => Task::ready(Ok(None)),
13116 HoverLink::File(_) => Task::ready(Ok(None)),
13117 })
13118 .collect::<Vec<_>>();
13119 (title, location_tasks, editor.workspace().clone())
13120 })
13121 .context("location tasks preparation")?;
13122
13123 let locations = future::join_all(location_tasks)
13124 .await
13125 .into_iter()
13126 .filter_map(|location| location.transpose())
13127 .collect::<Result<_>>()
13128 .context("location tasks")?;
13129
13130 let Some(workspace) = workspace else {
13131 return Ok(Navigated::No);
13132 };
13133 let opened = workspace
13134 .update_in(cx, |workspace, window, cx| {
13135 Self::open_locations_in_multibuffer(
13136 workspace,
13137 locations,
13138 title,
13139 split,
13140 MultibufferSelectionMode::First,
13141 window,
13142 cx,
13143 )
13144 })
13145 .ok();
13146
13147 anyhow::Ok(Navigated::from_bool(opened.is_some()))
13148 })
13149 } else {
13150 Task::ready(Ok(Navigated::No))
13151 }
13152 }
13153
13154 fn compute_target_location(
13155 &self,
13156 lsp_location: lsp::Location,
13157 server_id: LanguageServerId,
13158 window: &mut Window,
13159 cx: &mut Context<Self>,
13160 ) -> Task<anyhow::Result<Option<Location>>> {
13161 let Some(project) = self.project.clone() else {
13162 return Task::ready(Ok(None));
13163 };
13164
13165 cx.spawn_in(window, async move |editor, cx| {
13166 let location_task = editor.update(cx, |_, cx| {
13167 project.update(cx, |project, cx| {
13168 let language_server_name = project
13169 .language_server_statuses(cx)
13170 .find(|(id, _)| server_id == *id)
13171 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
13172 language_server_name.map(|language_server_name| {
13173 project.open_local_buffer_via_lsp(
13174 lsp_location.uri.clone(),
13175 server_id,
13176 language_server_name,
13177 cx,
13178 )
13179 })
13180 })
13181 })?;
13182 let location = match location_task {
13183 Some(task) => Some({
13184 let target_buffer_handle = task.await.context("open local buffer")?;
13185 let range = target_buffer_handle.update(cx, |target_buffer, _| {
13186 let target_start = target_buffer
13187 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
13188 let target_end = target_buffer
13189 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
13190 target_buffer.anchor_after(target_start)
13191 ..target_buffer.anchor_before(target_end)
13192 })?;
13193 Location {
13194 buffer: target_buffer_handle,
13195 range,
13196 }
13197 }),
13198 None => None,
13199 };
13200 Ok(location)
13201 })
13202 }
13203
13204 pub fn find_all_references(
13205 &mut self,
13206 _: &FindAllReferences,
13207 window: &mut Window,
13208 cx: &mut Context<Self>,
13209 ) -> Option<Task<Result<Navigated>>> {
13210 let selection = self.selections.newest::<usize>(cx);
13211 let multi_buffer = self.buffer.read(cx);
13212 let head = selection.head();
13213
13214 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
13215 let head_anchor = multi_buffer_snapshot.anchor_at(
13216 head,
13217 if head < selection.tail() {
13218 Bias::Right
13219 } else {
13220 Bias::Left
13221 },
13222 );
13223
13224 match self
13225 .find_all_references_task_sources
13226 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13227 {
13228 Ok(_) => {
13229 log::info!(
13230 "Ignoring repeated FindAllReferences invocation with the position of already running task"
13231 );
13232 return None;
13233 }
13234 Err(i) => {
13235 self.find_all_references_task_sources.insert(i, head_anchor);
13236 }
13237 }
13238
13239 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
13240 let workspace = self.workspace()?;
13241 let project = workspace.read(cx).project().clone();
13242 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
13243 Some(cx.spawn_in(window, async move |editor, cx| {
13244 let _cleanup = cx.on_drop(&editor, move |editor, _| {
13245 if let Ok(i) = editor
13246 .find_all_references_task_sources
13247 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13248 {
13249 editor.find_all_references_task_sources.remove(i);
13250 }
13251 });
13252
13253 let locations = references.await?;
13254 if locations.is_empty() {
13255 return anyhow::Ok(Navigated::No);
13256 }
13257
13258 workspace.update_in(cx, |workspace, window, cx| {
13259 let title = locations
13260 .first()
13261 .as_ref()
13262 .map(|location| {
13263 let buffer = location.buffer.read(cx);
13264 format!(
13265 "References to `{}`",
13266 buffer
13267 .text_for_range(location.range.clone())
13268 .collect::<String>()
13269 )
13270 })
13271 .unwrap();
13272 Self::open_locations_in_multibuffer(
13273 workspace,
13274 locations,
13275 title,
13276 false,
13277 MultibufferSelectionMode::First,
13278 window,
13279 cx,
13280 );
13281 Navigated::Yes
13282 })
13283 }))
13284 }
13285
13286 /// Opens a multibuffer with the given project locations in it
13287 pub fn open_locations_in_multibuffer(
13288 workspace: &mut Workspace,
13289 mut locations: Vec<Location>,
13290 title: String,
13291 split: bool,
13292 multibuffer_selection_mode: MultibufferSelectionMode,
13293 window: &mut Window,
13294 cx: &mut Context<Workspace>,
13295 ) {
13296 // If there are multiple definitions, open them in a multibuffer
13297 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
13298 let mut locations = locations.into_iter().peekable();
13299 let mut ranges = Vec::new();
13300 let capability = workspace.project().read(cx).capability();
13301
13302 let excerpt_buffer = cx.new(|cx| {
13303 let mut multibuffer = MultiBuffer::new(capability);
13304 while let Some(location) = locations.next() {
13305 let buffer = location.buffer.read(cx);
13306 let mut ranges_for_buffer = Vec::new();
13307 let range = location.range.to_offset(buffer);
13308 ranges_for_buffer.push(range.clone());
13309
13310 while let Some(next_location) = locations.peek() {
13311 if next_location.buffer == location.buffer {
13312 ranges_for_buffer.push(next_location.range.to_offset(buffer));
13313 locations.next();
13314 } else {
13315 break;
13316 }
13317 }
13318
13319 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
13320 ranges.extend(multibuffer.push_excerpts_with_context_lines(
13321 location.buffer.clone(),
13322 ranges_for_buffer,
13323 DEFAULT_MULTIBUFFER_CONTEXT,
13324 cx,
13325 ))
13326 }
13327
13328 multibuffer.with_title(title)
13329 });
13330
13331 let editor = cx.new(|cx| {
13332 Editor::for_multibuffer(
13333 excerpt_buffer,
13334 Some(workspace.project().clone()),
13335 window,
13336 cx,
13337 )
13338 });
13339 editor.update(cx, |editor, cx| {
13340 match multibuffer_selection_mode {
13341 MultibufferSelectionMode::First => {
13342 if let Some(first_range) = ranges.first() {
13343 editor.change_selections(None, window, cx, |selections| {
13344 selections.clear_disjoint();
13345 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
13346 });
13347 }
13348 editor.highlight_background::<Self>(
13349 &ranges,
13350 |theme| theme.editor_highlighted_line_background,
13351 cx,
13352 );
13353 }
13354 MultibufferSelectionMode::All => {
13355 editor.change_selections(None, window, cx, |selections| {
13356 selections.clear_disjoint();
13357 selections.select_anchor_ranges(ranges);
13358 });
13359 }
13360 }
13361 editor.register_buffers_with_language_servers(cx);
13362 });
13363
13364 let item = Box::new(editor);
13365 let item_id = item.item_id();
13366
13367 if split {
13368 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
13369 } else {
13370 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
13371 let (preview_item_id, preview_item_idx) =
13372 workspace.active_pane().update(cx, |pane, _| {
13373 (pane.preview_item_id(), pane.preview_item_idx())
13374 });
13375
13376 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
13377
13378 if let Some(preview_item_id) = preview_item_id {
13379 workspace.active_pane().update(cx, |pane, cx| {
13380 pane.remove_item(preview_item_id, false, false, window, cx);
13381 });
13382 }
13383 } else {
13384 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
13385 }
13386 }
13387 workspace.active_pane().update(cx, |pane, cx| {
13388 pane.set_preview_item_id(Some(item_id), cx);
13389 });
13390 }
13391
13392 pub fn rename(
13393 &mut self,
13394 _: &Rename,
13395 window: &mut Window,
13396 cx: &mut Context<Self>,
13397 ) -> Option<Task<Result<()>>> {
13398 use language::ToOffset as _;
13399
13400 let provider = self.semantics_provider.clone()?;
13401 let selection = self.selections.newest_anchor().clone();
13402 let (cursor_buffer, cursor_buffer_position) = self
13403 .buffer
13404 .read(cx)
13405 .text_anchor_for_position(selection.head(), cx)?;
13406 let (tail_buffer, cursor_buffer_position_end) = self
13407 .buffer
13408 .read(cx)
13409 .text_anchor_for_position(selection.tail(), cx)?;
13410 if tail_buffer != cursor_buffer {
13411 return None;
13412 }
13413
13414 let snapshot = cursor_buffer.read(cx).snapshot();
13415 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
13416 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
13417 let prepare_rename = provider
13418 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
13419 .unwrap_or_else(|| Task::ready(Ok(None)));
13420 drop(snapshot);
13421
13422 Some(cx.spawn_in(window, async move |this, cx| {
13423 let rename_range = if let Some(range) = prepare_rename.await? {
13424 Some(range)
13425 } else {
13426 this.update(cx, |this, cx| {
13427 let buffer = this.buffer.read(cx).snapshot(cx);
13428 let mut buffer_highlights = this
13429 .document_highlights_for_position(selection.head(), &buffer)
13430 .filter(|highlight| {
13431 highlight.start.excerpt_id == selection.head().excerpt_id
13432 && highlight.end.excerpt_id == selection.head().excerpt_id
13433 });
13434 buffer_highlights
13435 .next()
13436 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
13437 })?
13438 };
13439 if let Some(rename_range) = rename_range {
13440 this.update_in(cx, |this, window, cx| {
13441 let snapshot = cursor_buffer.read(cx).snapshot();
13442 let rename_buffer_range = rename_range.to_offset(&snapshot);
13443 let cursor_offset_in_rename_range =
13444 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
13445 let cursor_offset_in_rename_range_end =
13446 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
13447
13448 this.take_rename(false, window, cx);
13449 let buffer = this.buffer.read(cx).read(cx);
13450 let cursor_offset = selection.head().to_offset(&buffer);
13451 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
13452 let rename_end = rename_start + rename_buffer_range.len();
13453 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
13454 let mut old_highlight_id = None;
13455 let old_name: Arc<str> = buffer
13456 .chunks(rename_start..rename_end, true)
13457 .map(|chunk| {
13458 if old_highlight_id.is_none() {
13459 old_highlight_id = chunk.syntax_highlight_id;
13460 }
13461 chunk.text
13462 })
13463 .collect::<String>()
13464 .into();
13465
13466 drop(buffer);
13467
13468 // Position the selection in the rename editor so that it matches the current selection.
13469 this.show_local_selections = false;
13470 let rename_editor = cx.new(|cx| {
13471 let mut editor = Editor::single_line(window, cx);
13472 editor.buffer.update(cx, |buffer, cx| {
13473 buffer.edit([(0..0, old_name.clone())], None, cx)
13474 });
13475 let rename_selection_range = match cursor_offset_in_rename_range
13476 .cmp(&cursor_offset_in_rename_range_end)
13477 {
13478 Ordering::Equal => {
13479 editor.select_all(&SelectAll, window, cx);
13480 return editor;
13481 }
13482 Ordering::Less => {
13483 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
13484 }
13485 Ordering::Greater => {
13486 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
13487 }
13488 };
13489 if rename_selection_range.end > old_name.len() {
13490 editor.select_all(&SelectAll, window, cx);
13491 } else {
13492 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13493 s.select_ranges([rename_selection_range]);
13494 });
13495 }
13496 editor
13497 });
13498 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
13499 if e == &EditorEvent::Focused {
13500 cx.emit(EditorEvent::FocusedIn)
13501 }
13502 })
13503 .detach();
13504
13505 let write_highlights =
13506 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
13507 let read_highlights =
13508 this.clear_background_highlights::<DocumentHighlightRead>(cx);
13509 let ranges = write_highlights
13510 .iter()
13511 .flat_map(|(_, ranges)| ranges.iter())
13512 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
13513 .cloned()
13514 .collect();
13515
13516 this.highlight_text::<Rename>(
13517 ranges,
13518 HighlightStyle {
13519 fade_out: Some(0.6),
13520 ..Default::default()
13521 },
13522 cx,
13523 );
13524 let rename_focus_handle = rename_editor.focus_handle(cx);
13525 window.focus(&rename_focus_handle);
13526 let block_id = this.insert_blocks(
13527 [BlockProperties {
13528 style: BlockStyle::Flex,
13529 placement: BlockPlacement::Below(range.start),
13530 height: 1,
13531 render: Arc::new({
13532 let rename_editor = rename_editor.clone();
13533 move |cx: &mut BlockContext| {
13534 let mut text_style = cx.editor_style.text.clone();
13535 if let Some(highlight_style) = old_highlight_id
13536 .and_then(|h| h.style(&cx.editor_style.syntax))
13537 {
13538 text_style = text_style.highlight(highlight_style);
13539 }
13540 div()
13541 .block_mouse_down()
13542 .pl(cx.anchor_x)
13543 .child(EditorElement::new(
13544 &rename_editor,
13545 EditorStyle {
13546 background: cx.theme().system().transparent,
13547 local_player: cx.editor_style.local_player,
13548 text: text_style,
13549 scrollbar_width: cx.editor_style.scrollbar_width,
13550 syntax: cx.editor_style.syntax.clone(),
13551 status: cx.editor_style.status.clone(),
13552 inlay_hints_style: HighlightStyle {
13553 font_weight: Some(FontWeight::BOLD),
13554 ..make_inlay_hints_style(cx.app)
13555 },
13556 inline_completion_styles: make_suggestion_styles(
13557 cx.app,
13558 ),
13559 ..EditorStyle::default()
13560 },
13561 ))
13562 .into_any_element()
13563 }
13564 }),
13565 priority: 0,
13566 }],
13567 Some(Autoscroll::fit()),
13568 cx,
13569 )[0];
13570 this.pending_rename = Some(RenameState {
13571 range,
13572 old_name,
13573 editor: rename_editor,
13574 block_id,
13575 });
13576 })?;
13577 }
13578
13579 Ok(())
13580 }))
13581 }
13582
13583 pub fn confirm_rename(
13584 &mut self,
13585 _: &ConfirmRename,
13586 window: &mut Window,
13587 cx: &mut Context<Self>,
13588 ) -> Option<Task<Result<()>>> {
13589 let rename = self.take_rename(false, window, cx)?;
13590 let workspace = self.workspace()?.downgrade();
13591 let (buffer, start) = self
13592 .buffer
13593 .read(cx)
13594 .text_anchor_for_position(rename.range.start, cx)?;
13595 let (end_buffer, _) = self
13596 .buffer
13597 .read(cx)
13598 .text_anchor_for_position(rename.range.end, cx)?;
13599 if buffer != end_buffer {
13600 return None;
13601 }
13602
13603 let old_name = rename.old_name;
13604 let new_name = rename.editor.read(cx).text(cx);
13605
13606 let rename = self.semantics_provider.as_ref()?.perform_rename(
13607 &buffer,
13608 start,
13609 new_name.clone(),
13610 cx,
13611 )?;
13612
13613 Some(cx.spawn_in(window, async move |editor, cx| {
13614 let project_transaction = rename.await?;
13615 Self::open_project_transaction(
13616 &editor,
13617 workspace,
13618 project_transaction,
13619 format!("Rename: {} → {}", old_name, new_name),
13620 cx,
13621 )
13622 .await?;
13623
13624 editor.update(cx, |editor, cx| {
13625 editor.refresh_document_highlights(cx);
13626 })?;
13627 Ok(())
13628 }))
13629 }
13630
13631 fn take_rename(
13632 &mut self,
13633 moving_cursor: bool,
13634 window: &mut Window,
13635 cx: &mut Context<Self>,
13636 ) -> Option<RenameState> {
13637 let rename = self.pending_rename.take()?;
13638 if rename.editor.focus_handle(cx).is_focused(window) {
13639 window.focus(&self.focus_handle);
13640 }
13641
13642 self.remove_blocks(
13643 [rename.block_id].into_iter().collect(),
13644 Some(Autoscroll::fit()),
13645 cx,
13646 );
13647 self.clear_highlights::<Rename>(cx);
13648 self.show_local_selections = true;
13649
13650 if moving_cursor {
13651 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
13652 editor.selections.newest::<usize>(cx).head()
13653 });
13654
13655 // Update the selection to match the position of the selection inside
13656 // the rename editor.
13657 let snapshot = self.buffer.read(cx).read(cx);
13658 let rename_range = rename.range.to_offset(&snapshot);
13659 let cursor_in_editor = snapshot
13660 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
13661 .min(rename_range.end);
13662 drop(snapshot);
13663
13664 self.change_selections(None, window, cx, |s| {
13665 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
13666 });
13667 } else {
13668 self.refresh_document_highlights(cx);
13669 }
13670
13671 Some(rename)
13672 }
13673
13674 pub fn pending_rename(&self) -> Option<&RenameState> {
13675 self.pending_rename.as_ref()
13676 }
13677
13678 fn format(
13679 &mut self,
13680 _: &Format,
13681 window: &mut Window,
13682 cx: &mut Context<Self>,
13683 ) -> Option<Task<Result<()>>> {
13684 let project = match &self.project {
13685 Some(project) => project.clone(),
13686 None => return None,
13687 };
13688
13689 Some(self.perform_format(
13690 project,
13691 FormatTrigger::Manual,
13692 FormatTarget::Buffers,
13693 window,
13694 cx,
13695 ))
13696 }
13697
13698 fn format_selections(
13699 &mut self,
13700 _: &FormatSelections,
13701 window: &mut Window,
13702 cx: &mut Context<Self>,
13703 ) -> Option<Task<Result<()>>> {
13704 let project = match &self.project {
13705 Some(project) => project.clone(),
13706 None => return None,
13707 };
13708
13709 let ranges = self
13710 .selections
13711 .all_adjusted(cx)
13712 .into_iter()
13713 .map(|selection| selection.range())
13714 .collect_vec();
13715
13716 Some(self.perform_format(
13717 project,
13718 FormatTrigger::Manual,
13719 FormatTarget::Ranges(ranges),
13720 window,
13721 cx,
13722 ))
13723 }
13724
13725 fn perform_format(
13726 &mut self,
13727 project: Entity<Project>,
13728 trigger: FormatTrigger,
13729 target: FormatTarget,
13730 window: &mut Window,
13731 cx: &mut Context<Self>,
13732 ) -> Task<Result<()>> {
13733 let buffer = self.buffer.clone();
13734 let (buffers, target) = match target {
13735 FormatTarget::Buffers => {
13736 let mut buffers = buffer.read(cx).all_buffers();
13737 if trigger == FormatTrigger::Save {
13738 buffers.retain(|buffer| buffer.read(cx).is_dirty());
13739 }
13740 (buffers, LspFormatTarget::Buffers)
13741 }
13742 FormatTarget::Ranges(selection_ranges) => {
13743 let multi_buffer = buffer.read(cx);
13744 let snapshot = multi_buffer.read(cx);
13745 let mut buffers = HashSet::default();
13746 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
13747 BTreeMap::new();
13748 for selection_range in selection_ranges {
13749 for (buffer, buffer_range, _) in
13750 snapshot.range_to_buffer_ranges(selection_range)
13751 {
13752 let buffer_id = buffer.remote_id();
13753 let start = buffer.anchor_before(buffer_range.start);
13754 let end = buffer.anchor_after(buffer_range.end);
13755 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
13756 buffer_id_to_ranges
13757 .entry(buffer_id)
13758 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
13759 .or_insert_with(|| vec![start..end]);
13760 }
13761 }
13762 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
13763 }
13764 };
13765
13766 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
13767 let format = project.update(cx, |project, cx| {
13768 project.format(buffers, target, true, trigger, cx)
13769 });
13770
13771 cx.spawn_in(window, async move |_, cx| {
13772 let transaction = futures::select_biased! {
13773 transaction = format.log_err().fuse() => transaction,
13774 () = timeout => {
13775 log::warn!("timed out waiting for formatting");
13776 None
13777 }
13778 };
13779
13780 buffer
13781 .update(cx, |buffer, cx| {
13782 if let Some(transaction) = transaction {
13783 if !buffer.is_singleton() {
13784 buffer.push_transaction(&transaction.0, cx);
13785 }
13786 }
13787 cx.notify();
13788 })
13789 .ok();
13790
13791 Ok(())
13792 })
13793 }
13794
13795 fn organize_imports(
13796 &mut self,
13797 _: &OrganizeImports,
13798 window: &mut Window,
13799 cx: &mut Context<Self>,
13800 ) -> Option<Task<Result<()>>> {
13801 let project = match &self.project {
13802 Some(project) => project.clone(),
13803 None => return None,
13804 };
13805 Some(self.perform_code_action_kind(
13806 project,
13807 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
13808 window,
13809 cx,
13810 ))
13811 }
13812
13813 fn perform_code_action_kind(
13814 &mut self,
13815 project: Entity<Project>,
13816 kind: CodeActionKind,
13817 window: &mut Window,
13818 cx: &mut Context<Self>,
13819 ) -> Task<Result<()>> {
13820 let buffer = self.buffer.clone();
13821 let buffers = buffer.read(cx).all_buffers();
13822 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
13823 let apply_action = project.update(cx, |project, cx| {
13824 project.apply_code_action_kind(buffers, kind, true, cx)
13825 });
13826 cx.spawn_in(window, async move |_, cx| {
13827 let transaction = futures::select_biased! {
13828 () = timeout => {
13829 log::warn!("timed out waiting for executing code action");
13830 None
13831 }
13832 transaction = apply_action.log_err().fuse() => transaction,
13833 };
13834 buffer
13835 .update(cx, |buffer, cx| {
13836 // check if we need this
13837 if let Some(transaction) = transaction {
13838 if !buffer.is_singleton() {
13839 buffer.push_transaction(&transaction.0, cx);
13840 }
13841 }
13842 cx.notify();
13843 })
13844 .ok();
13845 Ok(())
13846 })
13847 }
13848
13849 fn restart_language_server(
13850 &mut self,
13851 _: &RestartLanguageServer,
13852 _: &mut Window,
13853 cx: &mut Context<Self>,
13854 ) {
13855 if let Some(project) = self.project.clone() {
13856 self.buffer.update(cx, |multi_buffer, cx| {
13857 project.update(cx, |project, cx| {
13858 project.restart_language_servers_for_buffers(
13859 multi_buffer.all_buffers().into_iter().collect(),
13860 cx,
13861 );
13862 });
13863 })
13864 }
13865 }
13866
13867 fn cancel_language_server_work(
13868 workspace: &mut Workspace,
13869 _: &actions::CancelLanguageServerWork,
13870 _: &mut Window,
13871 cx: &mut Context<Workspace>,
13872 ) {
13873 let project = workspace.project();
13874 let buffers = workspace
13875 .active_item(cx)
13876 .and_then(|item| item.act_as::<Editor>(cx))
13877 .map_or(HashSet::default(), |editor| {
13878 editor.read(cx).buffer.read(cx).all_buffers()
13879 });
13880 project.update(cx, |project, cx| {
13881 project.cancel_language_server_work_for_buffers(buffers, cx);
13882 });
13883 }
13884
13885 fn show_character_palette(
13886 &mut self,
13887 _: &ShowCharacterPalette,
13888 window: &mut Window,
13889 _: &mut Context<Self>,
13890 ) {
13891 window.show_character_palette();
13892 }
13893
13894 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
13895 if let Some(active_diagnostics) = self.active_diagnostics.as_mut() {
13896 let buffer = self.buffer.read(cx).snapshot(cx);
13897 let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer);
13898 let primary_range_end = active_diagnostics.primary_range.end.to_offset(&buffer);
13899 let is_valid = buffer
13900 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
13901 .any(|entry| {
13902 entry.diagnostic.is_primary
13903 && !entry.range.is_empty()
13904 && entry.range.start == primary_range_start
13905 && entry.diagnostic.message == active_diagnostics.primary_message
13906 });
13907
13908 if is_valid != active_diagnostics.is_valid {
13909 active_diagnostics.is_valid = is_valid;
13910 if is_valid {
13911 let mut new_styles = HashMap::default();
13912 for (block_id, diagnostic) in &active_diagnostics.blocks {
13913 new_styles.insert(
13914 *block_id,
13915 diagnostic_block_renderer(diagnostic.clone(), None, true),
13916 );
13917 }
13918 self.display_map.update(cx, |display_map, _cx| {
13919 display_map.replace_blocks(new_styles);
13920 });
13921 } else {
13922 self.dismiss_diagnostics(cx);
13923 }
13924 }
13925 }
13926 }
13927
13928 fn activate_diagnostics(
13929 &mut self,
13930 buffer_id: BufferId,
13931 group_id: usize,
13932 window: &mut Window,
13933 cx: &mut Context<Self>,
13934 ) {
13935 self.dismiss_diagnostics(cx);
13936 let snapshot = self.snapshot(window, cx);
13937 self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
13938 let buffer = self.buffer.read(cx).snapshot(cx);
13939
13940 let mut primary_range = None;
13941 let mut primary_message = None;
13942 let diagnostic_group = buffer
13943 .diagnostic_group(buffer_id, group_id)
13944 .filter_map(|entry| {
13945 let start = entry.range.start;
13946 let end = entry.range.end;
13947 if snapshot.is_line_folded(MultiBufferRow(start.row))
13948 && (start.row == end.row
13949 || snapshot.is_line_folded(MultiBufferRow(end.row)))
13950 {
13951 return None;
13952 }
13953 if entry.diagnostic.is_primary {
13954 primary_range = Some(entry.range.clone());
13955 primary_message = Some(entry.diagnostic.message.clone());
13956 }
13957 Some(entry)
13958 })
13959 .collect::<Vec<_>>();
13960 let primary_range = primary_range?;
13961 let primary_message = primary_message?;
13962
13963 let blocks = display_map
13964 .insert_blocks(
13965 diagnostic_group.iter().map(|entry| {
13966 let diagnostic = entry.diagnostic.clone();
13967 let message_height = diagnostic.message.matches('\n').count() as u32 + 1;
13968 BlockProperties {
13969 style: BlockStyle::Fixed,
13970 placement: BlockPlacement::Below(
13971 buffer.anchor_after(entry.range.start),
13972 ),
13973 height: message_height,
13974 render: diagnostic_block_renderer(diagnostic, None, true),
13975 priority: 0,
13976 }
13977 }),
13978 cx,
13979 )
13980 .into_iter()
13981 .zip(diagnostic_group.into_iter().map(|entry| entry.diagnostic))
13982 .collect();
13983
13984 Some(ActiveDiagnosticGroup {
13985 primary_range: buffer.anchor_before(primary_range.start)
13986 ..buffer.anchor_after(primary_range.end),
13987 primary_message,
13988 group_id,
13989 blocks,
13990 is_valid: true,
13991 })
13992 });
13993 }
13994
13995 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
13996 if let Some(active_diagnostic_group) = self.active_diagnostics.take() {
13997 self.display_map.update(cx, |display_map, cx| {
13998 display_map.remove_blocks(active_diagnostic_group.blocks.into_keys().collect(), cx);
13999 });
14000 cx.notify();
14001 }
14002 }
14003
14004 /// Disable inline diagnostics rendering for this editor.
14005 pub fn disable_inline_diagnostics(&mut self) {
14006 self.inline_diagnostics_enabled = false;
14007 self.inline_diagnostics_update = Task::ready(());
14008 self.inline_diagnostics.clear();
14009 }
14010
14011 pub fn inline_diagnostics_enabled(&self) -> bool {
14012 self.inline_diagnostics_enabled
14013 }
14014
14015 pub fn show_inline_diagnostics(&self) -> bool {
14016 self.show_inline_diagnostics
14017 }
14018
14019 pub fn toggle_inline_diagnostics(
14020 &mut self,
14021 _: &ToggleInlineDiagnostics,
14022 window: &mut Window,
14023 cx: &mut Context<Editor>,
14024 ) {
14025 self.show_inline_diagnostics = !self.show_inline_diagnostics;
14026 self.refresh_inline_diagnostics(false, window, cx);
14027 }
14028
14029 fn refresh_inline_diagnostics(
14030 &mut self,
14031 debounce: bool,
14032 window: &mut Window,
14033 cx: &mut Context<Self>,
14034 ) {
14035 if !self.inline_diagnostics_enabled || !self.show_inline_diagnostics {
14036 self.inline_diagnostics_update = Task::ready(());
14037 self.inline_diagnostics.clear();
14038 return;
14039 }
14040
14041 let debounce_ms = ProjectSettings::get_global(cx)
14042 .diagnostics
14043 .inline
14044 .update_debounce_ms;
14045 let debounce = if debounce && debounce_ms > 0 {
14046 Some(Duration::from_millis(debounce_ms))
14047 } else {
14048 None
14049 };
14050 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
14051 if let Some(debounce) = debounce {
14052 cx.background_executor().timer(debounce).await;
14053 }
14054 let Some(snapshot) = editor
14055 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
14056 .ok()
14057 else {
14058 return;
14059 };
14060
14061 let new_inline_diagnostics = cx
14062 .background_spawn(async move {
14063 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
14064 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
14065 let message = diagnostic_entry
14066 .diagnostic
14067 .message
14068 .split_once('\n')
14069 .map(|(line, _)| line)
14070 .map(SharedString::new)
14071 .unwrap_or_else(|| {
14072 SharedString::from(diagnostic_entry.diagnostic.message)
14073 });
14074 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
14075 let (Ok(i) | Err(i)) = inline_diagnostics
14076 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
14077 inline_diagnostics.insert(
14078 i,
14079 (
14080 start_anchor,
14081 InlineDiagnostic {
14082 message,
14083 group_id: diagnostic_entry.diagnostic.group_id,
14084 start: diagnostic_entry.range.start.to_point(&snapshot),
14085 is_primary: diagnostic_entry.diagnostic.is_primary,
14086 severity: diagnostic_entry.diagnostic.severity,
14087 },
14088 ),
14089 );
14090 }
14091 inline_diagnostics
14092 })
14093 .await;
14094
14095 editor
14096 .update(cx, |editor, cx| {
14097 editor.inline_diagnostics = new_inline_diagnostics;
14098 cx.notify();
14099 })
14100 .ok();
14101 });
14102 }
14103
14104 pub fn set_selections_from_remote(
14105 &mut self,
14106 selections: Vec<Selection<Anchor>>,
14107 pending_selection: Option<Selection<Anchor>>,
14108 window: &mut Window,
14109 cx: &mut Context<Self>,
14110 ) {
14111 let old_cursor_position = self.selections.newest_anchor().head();
14112 self.selections.change_with(cx, |s| {
14113 s.select_anchors(selections);
14114 if let Some(pending_selection) = pending_selection {
14115 s.set_pending(pending_selection, SelectMode::Character);
14116 } else {
14117 s.clear_pending();
14118 }
14119 });
14120 self.selections_did_change(false, &old_cursor_position, true, window, cx);
14121 }
14122
14123 fn push_to_selection_history(&mut self) {
14124 self.selection_history.push(SelectionHistoryEntry {
14125 selections: self.selections.disjoint_anchors(),
14126 select_next_state: self.select_next_state.clone(),
14127 select_prev_state: self.select_prev_state.clone(),
14128 add_selections_state: self.add_selections_state.clone(),
14129 });
14130 }
14131
14132 pub fn transact(
14133 &mut self,
14134 window: &mut Window,
14135 cx: &mut Context<Self>,
14136 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
14137 ) -> Option<TransactionId> {
14138 self.start_transaction_at(Instant::now(), window, cx);
14139 update(self, window, cx);
14140 self.end_transaction_at(Instant::now(), cx)
14141 }
14142
14143 pub fn start_transaction_at(
14144 &mut self,
14145 now: Instant,
14146 window: &mut Window,
14147 cx: &mut Context<Self>,
14148 ) {
14149 self.end_selection(window, cx);
14150 if let Some(tx_id) = self
14151 .buffer
14152 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
14153 {
14154 self.selection_history
14155 .insert_transaction(tx_id, self.selections.disjoint_anchors());
14156 cx.emit(EditorEvent::TransactionBegun {
14157 transaction_id: tx_id,
14158 })
14159 }
14160 }
14161
14162 pub fn end_transaction_at(
14163 &mut self,
14164 now: Instant,
14165 cx: &mut Context<Self>,
14166 ) -> Option<TransactionId> {
14167 if let Some(transaction_id) = self
14168 .buffer
14169 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
14170 {
14171 if let Some((_, end_selections)) =
14172 self.selection_history.transaction_mut(transaction_id)
14173 {
14174 *end_selections = Some(self.selections.disjoint_anchors());
14175 } else {
14176 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
14177 }
14178
14179 cx.emit(EditorEvent::Edited { transaction_id });
14180 Some(transaction_id)
14181 } else {
14182 None
14183 }
14184 }
14185
14186 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
14187 if self.selection_mark_mode {
14188 self.change_selections(None, window, cx, |s| {
14189 s.move_with(|_, sel| {
14190 sel.collapse_to(sel.head(), SelectionGoal::None);
14191 });
14192 })
14193 }
14194 self.selection_mark_mode = true;
14195 cx.notify();
14196 }
14197
14198 pub fn swap_selection_ends(
14199 &mut self,
14200 _: &actions::SwapSelectionEnds,
14201 window: &mut Window,
14202 cx: &mut Context<Self>,
14203 ) {
14204 self.change_selections(None, window, cx, |s| {
14205 s.move_with(|_, sel| {
14206 if sel.start != sel.end {
14207 sel.reversed = !sel.reversed
14208 }
14209 });
14210 });
14211 self.request_autoscroll(Autoscroll::newest(), cx);
14212 cx.notify();
14213 }
14214
14215 pub fn toggle_fold(
14216 &mut self,
14217 _: &actions::ToggleFold,
14218 window: &mut Window,
14219 cx: &mut Context<Self>,
14220 ) {
14221 if self.is_singleton(cx) {
14222 let selection = self.selections.newest::<Point>(cx);
14223
14224 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14225 let range = if selection.is_empty() {
14226 let point = selection.head().to_display_point(&display_map);
14227 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14228 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14229 .to_point(&display_map);
14230 start..end
14231 } else {
14232 selection.range()
14233 };
14234 if display_map.folds_in_range(range).next().is_some() {
14235 self.unfold_lines(&Default::default(), window, cx)
14236 } else {
14237 self.fold(&Default::default(), window, cx)
14238 }
14239 } else {
14240 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14241 let buffer_ids: HashSet<_> = self
14242 .selections
14243 .disjoint_anchor_ranges()
14244 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14245 .collect();
14246
14247 let should_unfold = buffer_ids
14248 .iter()
14249 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
14250
14251 for buffer_id in buffer_ids {
14252 if should_unfold {
14253 self.unfold_buffer(buffer_id, cx);
14254 } else {
14255 self.fold_buffer(buffer_id, cx);
14256 }
14257 }
14258 }
14259 }
14260
14261 pub fn toggle_fold_recursive(
14262 &mut self,
14263 _: &actions::ToggleFoldRecursive,
14264 window: &mut Window,
14265 cx: &mut Context<Self>,
14266 ) {
14267 let selection = self.selections.newest::<Point>(cx);
14268
14269 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14270 let range = if selection.is_empty() {
14271 let point = selection.head().to_display_point(&display_map);
14272 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14273 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14274 .to_point(&display_map);
14275 start..end
14276 } else {
14277 selection.range()
14278 };
14279 if display_map.folds_in_range(range).next().is_some() {
14280 self.unfold_recursive(&Default::default(), window, cx)
14281 } else {
14282 self.fold_recursive(&Default::default(), window, cx)
14283 }
14284 }
14285
14286 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
14287 if self.is_singleton(cx) {
14288 let mut to_fold = Vec::new();
14289 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14290 let selections = self.selections.all_adjusted(cx);
14291
14292 for selection in selections {
14293 let range = selection.range().sorted();
14294 let buffer_start_row = range.start.row;
14295
14296 if range.start.row != range.end.row {
14297 let mut found = false;
14298 let mut row = range.start.row;
14299 while row <= range.end.row {
14300 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
14301 {
14302 found = true;
14303 row = crease.range().end.row + 1;
14304 to_fold.push(crease);
14305 } else {
14306 row += 1
14307 }
14308 }
14309 if found {
14310 continue;
14311 }
14312 }
14313
14314 for row in (0..=range.start.row).rev() {
14315 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14316 if crease.range().end.row >= buffer_start_row {
14317 to_fold.push(crease);
14318 if row <= range.start.row {
14319 break;
14320 }
14321 }
14322 }
14323 }
14324 }
14325
14326 self.fold_creases(to_fold, true, window, cx);
14327 } else {
14328 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14329 let buffer_ids = self
14330 .selections
14331 .disjoint_anchor_ranges()
14332 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14333 .collect::<HashSet<_>>();
14334 for buffer_id in buffer_ids {
14335 self.fold_buffer(buffer_id, cx);
14336 }
14337 }
14338 }
14339
14340 fn fold_at_level(
14341 &mut self,
14342 fold_at: &FoldAtLevel,
14343 window: &mut Window,
14344 cx: &mut Context<Self>,
14345 ) {
14346 if !self.buffer.read(cx).is_singleton() {
14347 return;
14348 }
14349
14350 let fold_at_level = fold_at.0;
14351 let snapshot = self.buffer.read(cx).snapshot(cx);
14352 let mut to_fold = Vec::new();
14353 let mut stack = vec![(0, snapshot.max_row().0, 1)];
14354
14355 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
14356 while start_row < end_row {
14357 match self
14358 .snapshot(window, cx)
14359 .crease_for_buffer_row(MultiBufferRow(start_row))
14360 {
14361 Some(crease) => {
14362 let nested_start_row = crease.range().start.row + 1;
14363 let nested_end_row = crease.range().end.row;
14364
14365 if current_level < fold_at_level {
14366 stack.push((nested_start_row, nested_end_row, current_level + 1));
14367 } else if current_level == fold_at_level {
14368 to_fold.push(crease);
14369 }
14370
14371 start_row = nested_end_row + 1;
14372 }
14373 None => start_row += 1,
14374 }
14375 }
14376 }
14377
14378 self.fold_creases(to_fold, true, window, cx);
14379 }
14380
14381 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
14382 if self.buffer.read(cx).is_singleton() {
14383 let mut fold_ranges = Vec::new();
14384 let snapshot = self.buffer.read(cx).snapshot(cx);
14385
14386 for row in 0..snapshot.max_row().0 {
14387 if let Some(foldable_range) = self
14388 .snapshot(window, cx)
14389 .crease_for_buffer_row(MultiBufferRow(row))
14390 {
14391 fold_ranges.push(foldable_range);
14392 }
14393 }
14394
14395 self.fold_creases(fold_ranges, true, window, cx);
14396 } else {
14397 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
14398 editor
14399 .update_in(cx, |editor, _, cx| {
14400 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
14401 editor.fold_buffer(buffer_id, cx);
14402 }
14403 })
14404 .ok();
14405 });
14406 }
14407 }
14408
14409 pub fn fold_function_bodies(
14410 &mut self,
14411 _: &actions::FoldFunctionBodies,
14412 window: &mut Window,
14413 cx: &mut Context<Self>,
14414 ) {
14415 let snapshot = self.buffer.read(cx).snapshot(cx);
14416
14417 let ranges = snapshot
14418 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
14419 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
14420 .collect::<Vec<_>>();
14421
14422 let creases = ranges
14423 .into_iter()
14424 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
14425 .collect();
14426
14427 self.fold_creases(creases, true, window, cx);
14428 }
14429
14430 pub fn fold_recursive(
14431 &mut self,
14432 _: &actions::FoldRecursive,
14433 window: &mut Window,
14434 cx: &mut Context<Self>,
14435 ) {
14436 let mut to_fold = Vec::new();
14437 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14438 let selections = self.selections.all_adjusted(cx);
14439
14440 for selection in selections {
14441 let range = selection.range().sorted();
14442 let buffer_start_row = range.start.row;
14443
14444 if range.start.row != range.end.row {
14445 let mut found = false;
14446 for row in range.start.row..=range.end.row {
14447 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14448 found = true;
14449 to_fold.push(crease);
14450 }
14451 }
14452 if found {
14453 continue;
14454 }
14455 }
14456
14457 for row in (0..=range.start.row).rev() {
14458 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14459 if crease.range().end.row >= buffer_start_row {
14460 to_fold.push(crease);
14461 } else {
14462 break;
14463 }
14464 }
14465 }
14466 }
14467
14468 self.fold_creases(to_fold, true, window, cx);
14469 }
14470
14471 pub fn fold_at(&mut self, fold_at: &FoldAt, window: &mut Window, cx: &mut Context<Self>) {
14472 let buffer_row = fold_at.buffer_row;
14473 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14474
14475 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
14476 let autoscroll = self
14477 .selections
14478 .all::<Point>(cx)
14479 .iter()
14480 .any(|selection| crease.range().overlaps(&selection.range()));
14481
14482 self.fold_creases(vec![crease], autoscroll, window, cx);
14483 }
14484 }
14485
14486 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
14487 if self.is_singleton(cx) {
14488 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14489 let buffer = &display_map.buffer_snapshot;
14490 let selections = self.selections.all::<Point>(cx);
14491 let ranges = selections
14492 .iter()
14493 .map(|s| {
14494 let range = s.display_range(&display_map).sorted();
14495 let mut start = range.start.to_point(&display_map);
14496 let mut end = range.end.to_point(&display_map);
14497 start.column = 0;
14498 end.column = buffer.line_len(MultiBufferRow(end.row));
14499 start..end
14500 })
14501 .collect::<Vec<_>>();
14502
14503 self.unfold_ranges(&ranges, true, true, cx);
14504 } else {
14505 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14506 let buffer_ids = self
14507 .selections
14508 .disjoint_anchor_ranges()
14509 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14510 .collect::<HashSet<_>>();
14511 for buffer_id in buffer_ids {
14512 self.unfold_buffer(buffer_id, cx);
14513 }
14514 }
14515 }
14516
14517 pub fn unfold_recursive(
14518 &mut self,
14519 _: &UnfoldRecursive,
14520 _window: &mut Window,
14521 cx: &mut Context<Self>,
14522 ) {
14523 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14524 let selections = self.selections.all::<Point>(cx);
14525 let ranges = selections
14526 .iter()
14527 .map(|s| {
14528 let mut range = s.display_range(&display_map).sorted();
14529 *range.start.column_mut() = 0;
14530 *range.end.column_mut() = display_map.line_len(range.end.row());
14531 let start = range.start.to_point(&display_map);
14532 let end = range.end.to_point(&display_map);
14533 start..end
14534 })
14535 .collect::<Vec<_>>();
14536
14537 self.unfold_ranges(&ranges, true, true, cx);
14538 }
14539
14540 pub fn unfold_at(
14541 &mut self,
14542 unfold_at: &UnfoldAt,
14543 _window: &mut Window,
14544 cx: &mut Context<Self>,
14545 ) {
14546 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14547
14548 let intersection_range = Point::new(unfold_at.buffer_row.0, 0)
14549 ..Point::new(
14550 unfold_at.buffer_row.0,
14551 display_map.buffer_snapshot.line_len(unfold_at.buffer_row),
14552 );
14553
14554 let autoscroll = self
14555 .selections
14556 .all::<Point>(cx)
14557 .iter()
14558 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
14559
14560 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
14561 }
14562
14563 pub fn unfold_all(
14564 &mut self,
14565 _: &actions::UnfoldAll,
14566 _window: &mut Window,
14567 cx: &mut Context<Self>,
14568 ) {
14569 if self.buffer.read(cx).is_singleton() {
14570 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14571 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
14572 } else {
14573 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
14574 editor
14575 .update(cx, |editor, cx| {
14576 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
14577 editor.unfold_buffer(buffer_id, cx);
14578 }
14579 })
14580 .ok();
14581 });
14582 }
14583 }
14584
14585 pub fn fold_selected_ranges(
14586 &mut self,
14587 _: &FoldSelectedRanges,
14588 window: &mut Window,
14589 cx: &mut Context<Self>,
14590 ) {
14591 let selections = self.selections.all::<Point>(cx);
14592 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14593 let line_mode = self.selections.line_mode;
14594 let ranges = selections
14595 .into_iter()
14596 .map(|s| {
14597 if line_mode {
14598 let start = Point::new(s.start.row, 0);
14599 let end = Point::new(
14600 s.end.row,
14601 display_map
14602 .buffer_snapshot
14603 .line_len(MultiBufferRow(s.end.row)),
14604 );
14605 Crease::simple(start..end, display_map.fold_placeholder.clone())
14606 } else {
14607 Crease::simple(s.start..s.end, display_map.fold_placeholder.clone())
14608 }
14609 })
14610 .collect::<Vec<_>>();
14611 self.fold_creases(ranges, true, window, cx);
14612 }
14613
14614 pub fn fold_ranges<T: ToOffset + Clone>(
14615 &mut self,
14616 ranges: Vec<Range<T>>,
14617 auto_scroll: bool,
14618 window: &mut Window,
14619 cx: &mut Context<Self>,
14620 ) {
14621 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14622 let ranges = ranges
14623 .into_iter()
14624 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
14625 .collect::<Vec<_>>();
14626 self.fold_creases(ranges, auto_scroll, window, cx);
14627 }
14628
14629 pub fn fold_creases<T: ToOffset + Clone>(
14630 &mut self,
14631 creases: Vec<Crease<T>>,
14632 auto_scroll: bool,
14633 window: &mut Window,
14634 cx: &mut Context<Self>,
14635 ) {
14636 if creases.is_empty() {
14637 return;
14638 }
14639
14640 let mut buffers_affected = HashSet::default();
14641 let multi_buffer = self.buffer().read(cx);
14642 for crease in &creases {
14643 if let Some((_, buffer, _)) =
14644 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
14645 {
14646 buffers_affected.insert(buffer.read(cx).remote_id());
14647 };
14648 }
14649
14650 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
14651
14652 if auto_scroll {
14653 self.request_autoscroll(Autoscroll::fit(), cx);
14654 }
14655
14656 cx.notify();
14657
14658 if let Some(active_diagnostics) = self.active_diagnostics.take() {
14659 // Clear diagnostics block when folding a range that contains it.
14660 let snapshot = self.snapshot(window, cx);
14661 if snapshot.intersects_fold(active_diagnostics.primary_range.start) {
14662 drop(snapshot);
14663 self.active_diagnostics = Some(active_diagnostics);
14664 self.dismiss_diagnostics(cx);
14665 } else {
14666 self.active_diagnostics = Some(active_diagnostics);
14667 }
14668 }
14669
14670 self.scrollbar_marker_state.dirty = true;
14671 self.folds_did_change(cx);
14672 }
14673
14674 /// Removes any folds whose ranges intersect any of the given ranges.
14675 pub fn unfold_ranges<T: ToOffset + Clone>(
14676 &mut self,
14677 ranges: &[Range<T>],
14678 inclusive: bool,
14679 auto_scroll: bool,
14680 cx: &mut Context<Self>,
14681 ) {
14682 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
14683 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
14684 });
14685 self.folds_did_change(cx);
14686 }
14687
14688 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
14689 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
14690 return;
14691 }
14692 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
14693 self.display_map.update(cx, |display_map, cx| {
14694 display_map.fold_buffers([buffer_id], cx)
14695 });
14696 cx.emit(EditorEvent::BufferFoldToggled {
14697 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
14698 folded: true,
14699 });
14700 cx.notify();
14701 }
14702
14703 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
14704 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
14705 return;
14706 }
14707 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
14708 self.display_map.update(cx, |display_map, cx| {
14709 display_map.unfold_buffers([buffer_id], cx);
14710 });
14711 cx.emit(EditorEvent::BufferFoldToggled {
14712 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
14713 folded: false,
14714 });
14715 cx.notify();
14716 }
14717
14718 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
14719 self.display_map.read(cx).is_buffer_folded(buffer)
14720 }
14721
14722 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
14723 self.display_map.read(cx).folded_buffers()
14724 }
14725
14726 /// Removes any folds with the given ranges.
14727 pub fn remove_folds_with_type<T: ToOffset + Clone>(
14728 &mut self,
14729 ranges: &[Range<T>],
14730 type_id: TypeId,
14731 auto_scroll: bool,
14732 cx: &mut Context<Self>,
14733 ) {
14734 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
14735 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
14736 });
14737 self.folds_did_change(cx);
14738 }
14739
14740 fn remove_folds_with<T: ToOffset + Clone>(
14741 &mut self,
14742 ranges: &[Range<T>],
14743 auto_scroll: bool,
14744 cx: &mut Context<Self>,
14745 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
14746 ) {
14747 if ranges.is_empty() {
14748 return;
14749 }
14750
14751 let mut buffers_affected = HashSet::default();
14752 let multi_buffer = self.buffer().read(cx);
14753 for range in ranges {
14754 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
14755 buffers_affected.insert(buffer.read(cx).remote_id());
14756 };
14757 }
14758
14759 self.display_map.update(cx, update);
14760
14761 if auto_scroll {
14762 self.request_autoscroll(Autoscroll::fit(), cx);
14763 }
14764
14765 cx.notify();
14766 self.scrollbar_marker_state.dirty = true;
14767 self.active_indent_guides_state.dirty = true;
14768 }
14769
14770 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
14771 self.display_map.read(cx).fold_placeholder.clone()
14772 }
14773
14774 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
14775 self.buffer.update(cx, |buffer, cx| {
14776 buffer.set_all_diff_hunks_expanded(cx);
14777 });
14778 }
14779
14780 pub fn expand_all_diff_hunks(
14781 &mut self,
14782 _: &ExpandAllDiffHunks,
14783 _window: &mut Window,
14784 cx: &mut Context<Self>,
14785 ) {
14786 self.buffer.update(cx, |buffer, cx| {
14787 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
14788 });
14789 }
14790
14791 pub fn toggle_selected_diff_hunks(
14792 &mut self,
14793 _: &ToggleSelectedDiffHunks,
14794 _window: &mut Window,
14795 cx: &mut Context<Self>,
14796 ) {
14797 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
14798 self.toggle_diff_hunks_in_ranges(ranges, cx);
14799 }
14800
14801 pub fn diff_hunks_in_ranges<'a>(
14802 &'a self,
14803 ranges: &'a [Range<Anchor>],
14804 buffer: &'a MultiBufferSnapshot,
14805 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
14806 ranges.iter().flat_map(move |range| {
14807 let end_excerpt_id = range.end.excerpt_id;
14808 let range = range.to_point(buffer);
14809 let mut peek_end = range.end;
14810 if range.end.row < buffer.max_row().0 {
14811 peek_end = Point::new(range.end.row + 1, 0);
14812 }
14813 buffer
14814 .diff_hunks_in_range(range.start..peek_end)
14815 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
14816 })
14817 }
14818
14819 pub fn has_stageable_diff_hunks_in_ranges(
14820 &self,
14821 ranges: &[Range<Anchor>],
14822 snapshot: &MultiBufferSnapshot,
14823 ) -> bool {
14824 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
14825 hunks.any(|hunk| hunk.status().has_secondary_hunk())
14826 }
14827
14828 pub fn toggle_staged_selected_diff_hunks(
14829 &mut self,
14830 _: &::git::ToggleStaged,
14831 _: &mut Window,
14832 cx: &mut Context<Self>,
14833 ) {
14834 let snapshot = self.buffer.read(cx).snapshot(cx);
14835 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
14836 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
14837 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
14838 }
14839
14840 pub fn set_render_diff_hunk_controls(
14841 &mut self,
14842 render_diff_hunk_controls: RenderDiffHunkControlsFn,
14843 cx: &mut Context<Self>,
14844 ) {
14845 self.render_diff_hunk_controls = render_diff_hunk_controls;
14846 cx.notify();
14847 }
14848
14849 pub fn stage_and_next(
14850 &mut self,
14851 _: &::git::StageAndNext,
14852 window: &mut Window,
14853 cx: &mut Context<Self>,
14854 ) {
14855 self.do_stage_or_unstage_and_next(true, window, cx);
14856 }
14857
14858 pub fn unstage_and_next(
14859 &mut self,
14860 _: &::git::UnstageAndNext,
14861 window: &mut Window,
14862 cx: &mut Context<Self>,
14863 ) {
14864 self.do_stage_or_unstage_and_next(false, window, cx);
14865 }
14866
14867 pub fn stage_or_unstage_diff_hunks(
14868 &mut self,
14869 stage: bool,
14870 ranges: Vec<Range<Anchor>>,
14871 cx: &mut Context<Self>,
14872 ) {
14873 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
14874 cx.spawn(async move |this, cx| {
14875 task.await?;
14876 this.update(cx, |this, cx| {
14877 let snapshot = this.buffer.read(cx).snapshot(cx);
14878 let chunk_by = this
14879 .diff_hunks_in_ranges(&ranges, &snapshot)
14880 .chunk_by(|hunk| hunk.buffer_id);
14881 for (buffer_id, hunks) in &chunk_by {
14882 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
14883 }
14884 })
14885 })
14886 .detach_and_log_err(cx);
14887 }
14888
14889 fn save_buffers_for_ranges_if_needed(
14890 &mut self,
14891 ranges: &[Range<Anchor>],
14892 cx: &mut Context<Editor>,
14893 ) -> Task<Result<()>> {
14894 let multibuffer = self.buffer.read(cx);
14895 let snapshot = multibuffer.read(cx);
14896 let buffer_ids: HashSet<_> = ranges
14897 .iter()
14898 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
14899 .collect();
14900 drop(snapshot);
14901
14902 let mut buffers = HashSet::default();
14903 for buffer_id in buffer_ids {
14904 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
14905 let buffer = buffer_entity.read(cx);
14906 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
14907 {
14908 buffers.insert(buffer_entity);
14909 }
14910 }
14911 }
14912
14913 if let Some(project) = &self.project {
14914 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
14915 } else {
14916 Task::ready(Ok(()))
14917 }
14918 }
14919
14920 fn do_stage_or_unstage_and_next(
14921 &mut self,
14922 stage: bool,
14923 window: &mut Window,
14924 cx: &mut Context<Self>,
14925 ) {
14926 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
14927
14928 if ranges.iter().any(|range| range.start != range.end) {
14929 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
14930 return;
14931 }
14932
14933 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
14934 let snapshot = self.snapshot(window, cx);
14935 let position = self.selections.newest::<Point>(cx).head();
14936 let mut row = snapshot
14937 .buffer_snapshot
14938 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14939 .find(|hunk| hunk.row_range.start.0 > position.row)
14940 .map(|hunk| hunk.row_range.start);
14941
14942 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
14943 // Outside of the project diff editor, wrap around to the beginning.
14944 if !all_diff_hunks_expanded {
14945 row = row.or_else(|| {
14946 snapshot
14947 .buffer_snapshot
14948 .diff_hunks_in_range(Point::zero()..position)
14949 .find(|hunk| hunk.row_range.end.0 < position.row)
14950 .map(|hunk| hunk.row_range.start)
14951 });
14952 }
14953
14954 if let Some(row) = row {
14955 let destination = Point::new(row.0, 0);
14956 let autoscroll = Autoscroll::center();
14957
14958 self.unfold_ranges(&[destination..destination], false, false, cx);
14959 self.change_selections(Some(autoscroll), window, cx, |s| {
14960 s.select_ranges([destination..destination]);
14961 });
14962 }
14963 }
14964
14965 fn do_stage_or_unstage(
14966 &self,
14967 stage: bool,
14968 buffer_id: BufferId,
14969 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
14970 cx: &mut App,
14971 ) -> Option<()> {
14972 let project = self.project.as_ref()?;
14973 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
14974 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
14975 let buffer_snapshot = buffer.read(cx).snapshot();
14976 let file_exists = buffer_snapshot
14977 .file()
14978 .is_some_and(|file| file.disk_state().exists());
14979 diff.update(cx, |diff, cx| {
14980 diff.stage_or_unstage_hunks(
14981 stage,
14982 &hunks
14983 .map(|hunk| buffer_diff::DiffHunk {
14984 buffer_range: hunk.buffer_range,
14985 diff_base_byte_range: hunk.diff_base_byte_range,
14986 secondary_status: hunk.secondary_status,
14987 range: Point::zero()..Point::zero(), // unused
14988 })
14989 .collect::<Vec<_>>(),
14990 &buffer_snapshot,
14991 file_exists,
14992 cx,
14993 )
14994 });
14995 None
14996 }
14997
14998 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
14999 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15000 self.buffer
15001 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
15002 }
15003
15004 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
15005 self.buffer.update(cx, |buffer, cx| {
15006 let ranges = vec![Anchor::min()..Anchor::max()];
15007 if !buffer.all_diff_hunks_expanded()
15008 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
15009 {
15010 buffer.collapse_diff_hunks(ranges, cx);
15011 true
15012 } else {
15013 false
15014 }
15015 })
15016 }
15017
15018 fn toggle_diff_hunks_in_ranges(
15019 &mut self,
15020 ranges: Vec<Range<Anchor>>,
15021 cx: &mut Context<Editor>,
15022 ) {
15023 self.buffer.update(cx, |buffer, cx| {
15024 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
15025 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
15026 })
15027 }
15028
15029 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
15030 self.buffer.update(cx, |buffer, cx| {
15031 let snapshot = buffer.snapshot(cx);
15032 let excerpt_id = range.end.excerpt_id;
15033 let point_range = range.to_point(&snapshot);
15034 let expand = !buffer.single_hunk_is_expanded(range, cx);
15035 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
15036 })
15037 }
15038
15039 pub(crate) fn apply_all_diff_hunks(
15040 &mut self,
15041 _: &ApplyAllDiffHunks,
15042 window: &mut Window,
15043 cx: &mut Context<Self>,
15044 ) {
15045 let buffers = self.buffer.read(cx).all_buffers();
15046 for branch_buffer in buffers {
15047 branch_buffer.update(cx, |branch_buffer, cx| {
15048 branch_buffer.merge_into_base(Vec::new(), cx);
15049 });
15050 }
15051
15052 if let Some(project) = self.project.clone() {
15053 self.save(true, project, window, cx).detach_and_log_err(cx);
15054 }
15055 }
15056
15057 pub(crate) fn apply_selected_diff_hunks(
15058 &mut self,
15059 _: &ApplyDiffHunk,
15060 window: &mut Window,
15061 cx: &mut Context<Self>,
15062 ) {
15063 let snapshot = self.snapshot(window, cx);
15064 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
15065 let mut ranges_by_buffer = HashMap::default();
15066 self.transact(window, cx, |editor, _window, cx| {
15067 for hunk in hunks {
15068 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
15069 ranges_by_buffer
15070 .entry(buffer.clone())
15071 .or_insert_with(Vec::new)
15072 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
15073 }
15074 }
15075
15076 for (buffer, ranges) in ranges_by_buffer {
15077 buffer.update(cx, |buffer, cx| {
15078 buffer.merge_into_base(ranges, cx);
15079 });
15080 }
15081 });
15082
15083 if let Some(project) = self.project.clone() {
15084 self.save(true, project, window, cx).detach_and_log_err(cx);
15085 }
15086 }
15087
15088 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
15089 if hovered != self.gutter_hovered {
15090 self.gutter_hovered = hovered;
15091 cx.notify();
15092 }
15093 }
15094
15095 pub fn insert_blocks(
15096 &mut self,
15097 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
15098 autoscroll: Option<Autoscroll>,
15099 cx: &mut Context<Self>,
15100 ) -> Vec<CustomBlockId> {
15101 let blocks = self
15102 .display_map
15103 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
15104 if let Some(autoscroll) = autoscroll {
15105 self.request_autoscroll(autoscroll, cx);
15106 }
15107 cx.notify();
15108 blocks
15109 }
15110
15111 pub fn resize_blocks(
15112 &mut self,
15113 heights: HashMap<CustomBlockId, u32>,
15114 autoscroll: Option<Autoscroll>,
15115 cx: &mut Context<Self>,
15116 ) {
15117 self.display_map
15118 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
15119 if let Some(autoscroll) = autoscroll {
15120 self.request_autoscroll(autoscroll, cx);
15121 }
15122 cx.notify();
15123 }
15124
15125 pub fn replace_blocks(
15126 &mut self,
15127 renderers: HashMap<CustomBlockId, RenderBlock>,
15128 autoscroll: Option<Autoscroll>,
15129 cx: &mut Context<Self>,
15130 ) {
15131 self.display_map
15132 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
15133 if let Some(autoscroll) = autoscroll {
15134 self.request_autoscroll(autoscroll, cx);
15135 }
15136 cx.notify();
15137 }
15138
15139 pub fn remove_blocks(
15140 &mut self,
15141 block_ids: HashSet<CustomBlockId>,
15142 autoscroll: Option<Autoscroll>,
15143 cx: &mut Context<Self>,
15144 ) {
15145 self.display_map.update(cx, |display_map, cx| {
15146 display_map.remove_blocks(block_ids, cx)
15147 });
15148 if let Some(autoscroll) = autoscroll {
15149 self.request_autoscroll(autoscroll, cx);
15150 }
15151 cx.notify();
15152 }
15153
15154 pub fn row_for_block(
15155 &self,
15156 block_id: CustomBlockId,
15157 cx: &mut Context<Self>,
15158 ) -> Option<DisplayRow> {
15159 self.display_map
15160 .update(cx, |map, cx| map.row_for_block(block_id, cx))
15161 }
15162
15163 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
15164 self.focused_block = Some(focused_block);
15165 }
15166
15167 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
15168 self.focused_block.take()
15169 }
15170
15171 pub fn insert_creases(
15172 &mut self,
15173 creases: impl IntoIterator<Item = Crease<Anchor>>,
15174 cx: &mut Context<Self>,
15175 ) -> Vec<CreaseId> {
15176 self.display_map
15177 .update(cx, |map, cx| map.insert_creases(creases, cx))
15178 }
15179
15180 pub fn remove_creases(
15181 &mut self,
15182 ids: impl IntoIterator<Item = CreaseId>,
15183 cx: &mut Context<Self>,
15184 ) {
15185 self.display_map
15186 .update(cx, |map, cx| map.remove_creases(ids, cx));
15187 }
15188
15189 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
15190 self.display_map
15191 .update(cx, |map, cx| map.snapshot(cx))
15192 .longest_row()
15193 }
15194
15195 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
15196 self.display_map
15197 .update(cx, |map, cx| map.snapshot(cx))
15198 .max_point()
15199 }
15200
15201 pub fn text(&self, cx: &App) -> String {
15202 self.buffer.read(cx).read(cx).text()
15203 }
15204
15205 pub fn is_empty(&self, cx: &App) -> bool {
15206 self.buffer.read(cx).read(cx).is_empty()
15207 }
15208
15209 pub fn text_option(&self, cx: &App) -> Option<String> {
15210 let text = self.text(cx);
15211 let text = text.trim();
15212
15213 if text.is_empty() {
15214 return None;
15215 }
15216
15217 Some(text.to_string())
15218 }
15219
15220 pub fn set_text(
15221 &mut self,
15222 text: impl Into<Arc<str>>,
15223 window: &mut Window,
15224 cx: &mut Context<Self>,
15225 ) {
15226 self.transact(window, cx, |this, _, cx| {
15227 this.buffer
15228 .read(cx)
15229 .as_singleton()
15230 .expect("you can only call set_text on editors for singleton buffers")
15231 .update(cx, |buffer, cx| buffer.set_text(text, cx));
15232 });
15233 }
15234
15235 pub fn display_text(&self, cx: &mut App) -> String {
15236 self.display_map
15237 .update(cx, |map, cx| map.snapshot(cx))
15238 .text()
15239 }
15240
15241 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
15242 let mut wrap_guides = smallvec::smallvec![];
15243
15244 if self.show_wrap_guides == Some(false) {
15245 return wrap_guides;
15246 }
15247
15248 let settings = self.buffer.read(cx).language_settings(cx);
15249 if settings.show_wrap_guides {
15250 match self.soft_wrap_mode(cx) {
15251 SoftWrap::Column(soft_wrap) => {
15252 wrap_guides.push((soft_wrap as usize, true));
15253 }
15254 SoftWrap::Bounded(soft_wrap) => {
15255 wrap_guides.push((soft_wrap as usize, true));
15256 }
15257 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
15258 }
15259 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
15260 }
15261
15262 wrap_guides
15263 }
15264
15265 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
15266 let settings = self.buffer.read(cx).language_settings(cx);
15267 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
15268 match mode {
15269 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
15270 SoftWrap::None
15271 }
15272 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
15273 language_settings::SoftWrap::PreferredLineLength => {
15274 SoftWrap::Column(settings.preferred_line_length)
15275 }
15276 language_settings::SoftWrap::Bounded => {
15277 SoftWrap::Bounded(settings.preferred_line_length)
15278 }
15279 }
15280 }
15281
15282 pub fn set_soft_wrap_mode(
15283 &mut self,
15284 mode: language_settings::SoftWrap,
15285
15286 cx: &mut Context<Self>,
15287 ) {
15288 self.soft_wrap_mode_override = Some(mode);
15289 cx.notify();
15290 }
15291
15292 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
15293 self.hard_wrap = hard_wrap;
15294 cx.notify();
15295 }
15296
15297 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
15298 self.text_style_refinement = Some(style);
15299 }
15300
15301 /// called by the Element so we know what style we were most recently rendered with.
15302 pub(crate) fn set_style(
15303 &mut self,
15304 style: EditorStyle,
15305 window: &mut Window,
15306 cx: &mut Context<Self>,
15307 ) {
15308 let rem_size = window.rem_size();
15309 self.display_map.update(cx, |map, cx| {
15310 map.set_font(
15311 style.text.font(),
15312 style.text.font_size.to_pixels(rem_size),
15313 cx,
15314 )
15315 });
15316 self.style = Some(style);
15317 }
15318
15319 pub fn style(&self) -> Option<&EditorStyle> {
15320 self.style.as_ref()
15321 }
15322
15323 // Called by the element. This method is not designed to be called outside of the editor
15324 // element's layout code because it does not notify when rewrapping is computed synchronously.
15325 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
15326 self.display_map
15327 .update(cx, |map, cx| map.set_wrap_width(width, cx))
15328 }
15329
15330 pub fn set_soft_wrap(&mut self) {
15331 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
15332 }
15333
15334 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
15335 if self.soft_wrap_mode_override.is_some() {
15336 self.soft_wrap_mode_override.take();
15337 } else {
15338 let soft_wrap = match self.soft_wrap_mode(cx) {
15339 SoftWrap::GitDiff => return,
15340 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
15341 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
15342 language_settings::SoftWrap::None
15343 }
15344 };
15345 self.soft_wrap_mode_override = Some(soft_wrap);
15346 }
15347 cx.notify();
15348 }
15349
15350 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
15351 let Some(workspace) = self.workspace() else {
15352 return;
15353 };
15354 let fs = workspace.read(cx).app_state().fs.clone();
15355 let current_show = TabBarSettings::get_global(cx).show;
15356 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
15357 setting.show = Some(!current_show);
15358 });
15359 }
15360
15361 pub fn toggle_indent_guides(
15362 &mut self,
15363 _: &ToggleIndentGuides,
15364 _: &mut Window,
15365 cx: &mut Context<Self>,
15366 ) {
15367 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
15368 self.buffer
15369 .read(cx)
15370 .language_settings(cx)
15371 .indent_guides
15372 .enabled
15373 });
15374 self.show_indent_guides = Some(!currently_enabled);
15375 cx.notify();
15376 }
15377
15378 fn should_show_indent_guides(&self) -> Option<bool> {
15379 self.show_indent_guides
15380 }
15381
15382 pub fn toggle_line_numbers(
15383 &mut self,
15384 _: &ToggleLineNumbers,
15385 _: &mut Window,
15386 cx: &mut Context<Self>,
15387 ) {
15388 let mut editor_settings = EditorSettings::get_global(cx).clone();
15389 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
15390 EditorSettings::override_global(editor_settings, cx);
15391 }
15392
15393 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
15394 if let Some(show_line_numbers) = self.show_line_numbers {
15395 return show_line_numbers;
15396 }
15397 EditorSettings::get_global(cx).gutter.line_numbers
15398 }
15399
15400 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
15401 self.use_relative_line_numbers
15402 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
15403 }
15404
15405 pub fn toggle_relative_line_numbers(
15406 &mut self,
15407 _: &ToggleRelativeLineNumbers,
15408 _: &mut Window,
15409 cx: &mut Context<Self>,
15410 ) {
15411 let is_relative = self.should_use_relative_line_numbers(cx);
15412 self.set_relative_line_number(Some(!is_relative), cx)
15413 }
15414
15415 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
15416 self.use_relative_line_numbers = is_relative;
15417 cx.notify();
15418 }
15419
15420 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
15421 self.show_gutter = show_gutter;
15422 cx.notify();
15423 }
15424
15425 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
15426 self.show_scrollbars = show_scrollbars;
15427 cx.notify();
15428 }
15429
15430 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
15431 self.show_line_numbers = Some(show_line_numbers);
15432 cx.notify();
15433 }
15434
15435 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
15436 self.show_git_diff_gutter = Some(show_git_diff_gutter);
15437 cx.notify();
15438 }
15439
15440 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
15441 self.show_code_actions = Some(show_code_actions);
15442 cx.notify();
15443 }
15444
15445 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
15446 self.show_runnables = Some(show_runnables);
15447 cx.notify();
15448 }
15449
15450 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
15451 self.show_breakpoints = Some(show_breakpoints);
15452 cx.notify();
15453 }
15454
15455 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
15456 if self.display_map.read(cx).masked != masked {
15457 self.display_map.update(cx, |map, _| map.masked = masked);
15458 }
15459 cx.notify()
15460 }
15461
15462 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
15463 self.show_wrap_guides = Some(show_wrap_guides);
15464 cx.notify();
15465 }
15466
15467 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
15468 self.show_indent_guides = Some(show_indent_guides);
15469 cx.notify();
15470 }
15471
15472 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
15473 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
15474 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
15475 if let Some(dir) = file.abs_path(cx).parent() {
15476 return Some(dir.to_owned());
15477 }
15478 }
15479
15480 if let Some(project_path) = buffer.read(cx).project_path(cx) {
15481 return Some(project_path.path.to_path_buf());
15482 }
15483 }
15484
15485 None
15486 }
15487
15488 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
15489 self.active_excerpt(cx)?
15490 .1
15491 .read(cx)
15492 .file()
15493 .and_then(|f| f.as_local())
15494 }
15495
15496 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15497 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15498 let buffer = buffer.read(cx);
15499 if let Some(project_path) = buffer.project_path(cx) {
15500 let project = self.project.as_ref()?.read(cx);
15501 project.absolute_path(&project_path, cx)
15502 } else {
15503 buffer
15504 .file()
15505 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
15506 }
15507 })
15508 }
15509
15510 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15511 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15512 let project_path = buffer.read(cx).project_path(cx)?;
15513 let project = self.project.as_ref()?.read(cx);
15514 let entry = project.entry_for_path(&project_path, cx)?;
15515 let path = entry.path.to_path_buf();
15516 Some(path)
15517 })
15518 }
15519
15520 pub fn reveal_in_finder(
15521 &mut self,
15522 _: &RevealInFileManager,
15523 _window: &mut Window,
15524 cx: &mut Context<Self>,
15525 ) {
15526 if let Some(target) = self.target_file(cx) {
15527 cx.reveal_path(&target.abs_path(cx));
15528 }
15529 }
15530
15531 pub fn copy_path(
15532 &mut self,
15533 _: &zed_actions::workspace::CopyPath,
15534 _window: &mut Window,
15535 cx: &mut Context<Self>,
15536 ) {
15537 if let Some(path) = self.target_file_abs_path(cx) {
15538 if let Some(path) = path.to_str() {
15539 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15540 }
15541 }
15542 }
15543
15544 pub fn copy_relative_path(
15545 &mut self,
15546 _: &zed_actions::workspace::CopyRelativePath,
15547 _window: &mut Window,
15548 cx: &mut Context<Self>,
15549 ) {
15550 if let Some(path) = self.target_file_path(cx) {
15551 if let Some(path) = path.to_str() {
15552 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15553 }
15554 }
15555 }
15556
15557 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
15558 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
15559 buffer.read(cx).project_path(cx)
15560 } else {
15561 None
15562 }
15563 }
15564
15565 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) {
15566 let _ = maybe!({
15567 let breakpoint_store = self.breakpoint_store.as_ref()?;
15568
15569 let Some((_, _, active_position)) =
15570 breakpoint_store.read(cx).active_position().cloned()
15571 else {
15572 self.clear_row_highlights::<DebugCurrentRowHighlight>();
15573 return None;
15574 };
15575
15576 let snapshot = self
15577 .project
15578 .as_ref()?
15579 .read(cx)
15580 .buffer_for_id(active_position.buffer_id?, cx)?
15581 .read(cx)
15582 .snapshot();
15583
15584 for (id, ExcerptRange { context, .. }) in self
15585 .buffer
15586 .read(cx)
15587 .excerpts_for_buffer(active_position.buffer_id?, cx)
15588 {
15589 if context.start.cmp(&active_position, &snapshot).is_ge()
15590 || context.end.cmp(&active_position, &snapshot).is_lt()
15591 {
15592 continue;
15593 }
15594 let snapshot = self.buffer.read(cx).snapshot(cx);
15595 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, active_position)?;
15596
15597 self.clear_row_highlights::<DebugCurrentRowHighlight>();
15598 self.go_to_line::<DebugCurrentRowHighlight>(
15599 multibuffer_anchor,
15600 Some(cx.theme().colors().editor_debugger_active_line_background),
15601 window,
15602 cx,
15603 );
15604
15605 cx.notify();
15606 }
15607
15608 Some(())
15609 });
15610 }
15611
15612 pub fn copy_file_name_without_extension(
15613 &mut self,
15614 _: &CopyFileNameWithoutExtension,
15615 _: &mut Window,
15616 cx: &mut Context<Self>,
15617 ) {
15618 if let Some(file) = self.target_file(cx) {
15619 if let Some(file_stem) = file.path().file_stem() {
15620 if let Some(name) = file_stem.to_str() {
15621 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
15622 }
15623 }
15624 }
15625 }
15626
15627 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
15628 if let Some(file) = self.target_file(cx) {
15629 if let Some(file_name) = file.path().file_name() {
15630 if let Some(name) = file_name.to_str() {
15631 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
15632 }
15633 }
15634 }
15635 }
15636
15637 pub fn toggle_git_blame(
15638 &mut self,
15639 _: &::git::Blame,
15640 window: &mut Window,
15641 cx: &mut Context<Self>,
15642 ) {
15643 self.show_git_blame_gutter = !self.show_git_blame_gutter;
15644
15645 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
15646 self.start_git_blame(true, window, cx);
15647 }
15648
15649 cx.notify();
15650 }
15651
15652 pub fn toggle_git_blame_inline(
15653 &mut self,
15654 _: &ToggleGitBlameInline,
15655 window: &mut Window,
15656 cx: &mut Context<Self>,
15657 ) {
15658 self.toggle_git_blame_inline_internal(true, window, cx);
15659 cx.notify();
15660 }
15661
15662 pub fn git_blame_inline_enabled(&self) -> bool {
15663 self.git_blame_inline_enabled
15664 }
15665
15666 pub fn toggle_selection_menu(
15667 &mut self,
15668 _: &ToggleSelectionMenu,
15669 _: &mut Window,
15670 cx: &mut Context<Self>,
15671 ) {
15672 self.show_selection_menu = self
15673 .show_selection_menu
15674 .map(|show_selections_menu| !show_selections_menu)
15675 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
15676
15677 cx.notify();
15678 }
15679
15680 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
15681 self.show_selection_menu
15682 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
15683 }
15684
15685 fn start_git_blame(
15686 &mut self,
15687 user_triggered: bool,
15688 window: &mut Window,
15689 cx: &mut Context<Self>,
15690 ) {
15691 if let Some(project) = self.project.as_ref() {
15692 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
15693 return;
15694 };
15695
15696 if buffer.read(cx).file().is_none() {
15697 return;
15698 }
15699
15700 let focused = self.focus_handle(cx).contains_focused(window, cx);
15701
15702 let project = project.clone();
15703 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
15704 self.blame_subscription =
15705 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
15706 self.blame = Some(blame);
15707 }
15708 }
15709
15710 fn toggle_git_blame_inline_internal(
15711 &mut self,
15712 user_triggered: bool,
15713 window: &mut Window,
15714 cx: &mut Context<Self>,
15715 ) {
15716 if self.git_blame_inline_enabled {
15717 self.git_blame_inline_enabled = false;
15718 self.show_git_blame_inline = false;
15719 self.show_git_blame_inline_delay_task.take();
15720 } else {
15721 self.git_blame_inline_enabled = true;
15722 self.start_git_blame_inline(user_triggered, window, cx);
15723 }
15724
15725 cx.notify();
15726 }
15727
15728 fn start_git_blame_inline(
15729 &mut self,
15730 user_triggered: bool,
15731 window: &mut Window,
15732 cx: &mut Context<Self>,
15733 ) {
15734 self.start_git_blame(user_triggered, window, cx);
15735
15736 if ProjectSettings::get_global(cx)
15737 .git
15738 .inline_blame_delay()
15739 .is_some()
15740 {
15741 self.start_inline_blame_timer(window, cx);
15742 } else {
15743 self.show_git_blame_inline = true
15744 }
15745 }
15746
15747 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
15748 self.blame.as_ref()
15749 }
15750
15751 pub fn show_git_blame_gutter(&self) -> bool {
15752 self.show_git_blame_gutter
15753 }
15754
15755 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
15756 self.show_git_blame_gutter && self.has_blame_entries(cx)
15757 }
15758
15759 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
15760 self.show_git_blame_inline
15761 && (self.focus_handle.is_focused(window)
15762 || self
15763 .git_blame_inline_tooltip
15764 .as_ref()
15765 .and_then(|t| t.upgrade())
15766 .is_some())
15767 && !self.newest_selection_head_on_empty_line(cx)
15768 && self.has_blame_entries(cx)
15769 }
15770
15771 fn has_blame_entries(&self, cx: &App) -> bool {
15772 self.blame()
15773 .map_or(false, |blame| blame.read(cx).has_generated_entries())
15774 }
15775
15776 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
15777 let cursor_anchor = self.selections.newest_anchor().head();
15778
15779 let snapshot = self.buffer.read(cx).snapshot(cx);
15780 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
15781
15782 snapshot.line_len(buffer_row) == 0
15783 }
15784
15785 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
15786 let buffer_and_selection = maybe!({
15787 let selection = self.selections.newest::<Point>(cx);
15788 let selection_range = selection.range();
15789
15790 let multi_buffer = self.buffer().read(cx);
15791 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15792 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
15793
15794 let (buffer, range, _) = if selection.reversed {
15795 buffer_ranges.first()
15796 } else {
15797 buffer_ranges.last()
15798 }?;
15799
15800 let selection = text::ToPoint::to_point(&range.start, &buffer).row
15801 ..text::ToPoint::to_point(&range.end, &buffer).row;
15802 Some((
15803 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
15804 selection,
15805 ))
15806 });
15807
15808 let Some((buffer, selection)) = buffer_and_selection else {
15809 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
15810 };
15811
15812 let Some(project) = self.project.as_ref() else {
15813 return Task::ready(Err(anyhow!("editor does not have project")));
15814 };
15815
15816 project.update(cx, |project, cx| {
15817 project.get_permalink_to_line(&buffer, selection, cx)
15818 })
15819 }
15820
15821 pub fn copy_permalink_to_line(
15822 &mut self,
15823 _: &CopyPermalinkToLine,
15824 window: &mut Window,
15825 cx: &mut Context<Self>,
15826 ) {
15827 let permalink_task = self.get_permalink_to_line(cx);
15828 let workspace = self.workspace();
15829
15830 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
15831 Ok(permalink) => {
15832 cx.update(|_, cx| {
15833 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
15834 })
15835 .ok();
15836 }
15837 Err(err) => {
15838 let message = format!("Failed to copy permalink: {err}");
15839
15840 Err::<(), anyhow::Error>(err).log_err();
15841
15842 if let Some(workspace) = workspace {
15843 workspace
15844 .update_in(cx, |workspace, _, cx| {
15845 struct CopyPermalinkToLine;
15846
15847 workspace.show_toast(
15848 Toast::new(
15849 NotificationId::unique::<CopyPermalinkToLine>(),
15850 message,
15851 ),
15852 cx,
15853 )
15854 })
15855 .ok();
15856 }
15857 }
15858 })
15859 .detach();
15860 }
15861
15862 pub fn copy_file_location(
15863 &mut self,
15864 _: &CopyFileLocation,
15865 _: &mut Window,
15866 cx: &mut Context<Self>,
15867 ) {
15868 let selection = self.selections.newest::<Point>(cx).start.row + 1;
15869 if let Some(file) = self.target_file(cx) {
15870 if let Some(path) = file.path().to_str() {
15871 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
15872 }
15873 }
15874 }
15875
15876 pub fn open_permalink_to_line(
15877 &mut self,
15878 _: &OpenPermalinkToLine,
15879 window: &mut Window,
15880 cx: &mut Context<Self>,
15881 ) {
15882 let permalink_task = self.get_permalink_to_line(cx);
15883 let workspace = self.workspace();
15884
15885 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
15886 Ok(permalink) => {
15887 cx.update(|_, cx| {
15888 cx.open_url(permalink.as_ref());
15889 })
15890 .ok();
15891 }
15892 Err(err) => {
15893 let message = format!("Failed to open permalink: {err}");
15894
15895 Err::<(), anyhow::Error>(err).log_err();
15896
15897 if let Some(workspace) = workspace {
15898 workspace
15899 .update(cx, |workspace, cx| {
15900 struct OpenPermalinkToLine;
15901
15902 workspace.show_toast(
15903 Toast::new(
15904 NotificationId::unique::<OpenPermalinkToLine>(),
15905 message,
15906 ),
15907 cx,
15908 )
15909 })
15910 .ok();
15911 }
15912 }
15913 })
15914 .detach();
15915 }
15916
15917 pub fn insert_uuid_v4(
15918 &mut self,
15919 _: &InsertUuidV4,
15920 window: &mut Window,
15921 cx: &mut Context<Self>,
15922 ) {
15923 self.insert_uuid(UuidVersion::V4, window, cx);
15924 }
15925
15926 pub fn insert_uuid_v7(
15927 &mut self,
15928 _: &InsertUuidV7,
15929 window: &mut Window,
15930 cx: &mut Context<Self>,
15931 ) {
15932 self.insert_uuid(UuidVersion::V7, window, cx);
15933 }
15934
15935 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
15936 self.transact(window, cx, |this, window, cx| {
15937 let edits = this
15938 .selections
15939 .all::<Point>(cx)
15940 .into_iter()
15941 .map(|selection| {
15942 let uuid = match version {
15943 UuidVersion::V4 => uuid::Uuid::new_v4(),
15944 UuidVersion::V7 => uuid::Uuid::now_v7(),
15945 };
15946
15947 (selection.range(), uuid.to_string())
15948 });
15949 this.edit(edits, cx);
15950 this.refresh_inline_completion(true, false, window, cx);
15951 });
15952 }
15953
15954 pub fn open_selections_in_multibuffer(
15955 &mut self,
15956 _: &OpenSelectionsInMultibuffer,
15957 window: &mut Window,
15958 cx: &mut Context<Self>,
15959 ) {
15960 let multibuffer = self.buffer.read(cx);
15961
15962 let Some(buffer) = multibuffer.as_singleton() else {
15963 return;
15964 };
15965
15966 let Some(workspace) = self.workspace() else {
15967 return;
15968 };
15969
15970 let locations = self
15971 .selections
15972 .disjoint_anchors()
15973 .iter()
15974 .map(|range| Location {
15975 buffer: buffer.clone(),
15976 range: range.start.text_anchor..range.end.text_anchor,
15977 })
15978 .collect::<Vec<_>>();
15979
15980 let title = multibuffer.title(cx).to_string();
15981
15982 cx.spawn_in(window, async move |_, cx| {
15983 workspace.update_in(cx, |workspace, window, cx| {
15984 Self::open_locations_in_multibuffer(
15985 workspace,
15986 locations,
15987 format!("Selections for '{title}'"),
15988 false,
15989 MultibufferSelectionMode::All,
15990 window,
15991 cx,
15992 );
15993 })
15994 })
15995 .detach();
15996 }
15997
15998 /// Adds a row highlight for the given range. If a row has multiple highlights, the
15999 /// last highlight added will be used.
16000 ///
16001 /// If the range ends at the beginning of a line, then that line will not be highlighted.
16002 pub fn highlight_rows<T: 'static>(
16003 &mut self,
16004 range: Range<Anchor>,
16005 color: Hsla,
16006 should_autoscroll: bool,
16007 cx: &mut Context<Self>,
16008 ) {
16009 let snapshot = self.buffer().read(cx).snapshot(cx);
16010 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16011 let ix = row_highlights.binary_search_by(|highlight| {
16012 Ordering::Equal
16013 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
16014 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
16015 });
16016
16017 if let Err(mut ix) = ix {
16018 let index = post_inc(&mut self.highlight_order);
16019
16020 // If this range intersects with the preceding highlight, then merge it with
16021 // the preceding highlight. Otherwise insert a new highlight.
16022 let mut merged = false;
16023 if ix > 0 {
16024 let prev_highlight = &mut row_highlights[ix - 1];
16025 if prev_highlight
16026 .range
16027 .end
16028 .cmp(&range.start, &snapshot)
16029 .is_ge()
16030 {
16031 ix -= 1;
16032 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
16033 prev_highlight.range.end = range.end;
16034 }
16035 merged = true;
16036 prev_highlight.index = index;
16037 prev_highlight.color = color;
16038 prev_highlight.should_autoscroll = should_autoscroll;
16039 }
16040 }
16041
16042 if !merged {
16043 row_highlights.insert(
16044 ix,
16045 RowHighlight {
16046 range: range.clone(),
16047 index,
16048 color,
16049 should_autoscroll,
16050 },
16051 );
16052 }
16053
16054 // If any of the following highlights intersect with this one, merge them.
16055 while let Some(next_highlight) = row_highlights.get(ix + 1) {
16056 let highlight = &row_highlights[ix];
16057 if next_highlight
16058 .range
16059 .start
16060 .cmp(&highlight.range.end, &snapshot)
16061 .is_le()
16062 {
16063 if next_highlight
16064 .range
16065 .end
16066 .cmp(&highlight.range.end, &snapshot)
16067 .is_gt()
16068 {
16069 row_highlights[ix].range.end = next_highlight.range.end;
16070 }
16071 row_highlights.remove(ix + 1);
16072 } else {
16073 break;
16074 }
16075 }
16076 }
16077 }
16078
16079 /// Remove any highlighted row ranges of the given type that intersect the
16080 /// given ranges.
16081 pub fn remove_highlighted_rows<T: 'static>(
16082 &mut self,
16083 ranges_to_remove: Vec<Range<Anchor>>,
16084 cx: &mut Context<Self>,
16085 ) {
16086 let snapshot = self.buffer().read(cx).snapshot(cx);
16087 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16088 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
16089 row_highlights.retain(|highlight| {
16090 while let Some(range_to_remove) = ranges_to_remove.peek() {
16091 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
16092 Ordering::Less | Ordering::Equal => {
16093 ranges_to_remove.next();
16094 }
16095 Ordering::Greater => {
16096 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
16097 Ordering::Less | Ordering::Equal => {
16098 return false;
16099 }
16100 Ordering::Greater => break,
16101 }
16102 }
16103 }
16104 }
16105
16106 true
16107 })
16108 }
16109
16110 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
16111 pub fn clear_row_highlights<T: 'static>(&mut self) {
16112 self.highlighted_rows.remove(&TypeId::of::<T>());
16113 }
16114
16115 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
16116 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
16117 self.highlighted_rows
16118 .get(&TypeId::of::<T>())
16119 .map_or(&[] as &[_], |vec| vec.as_slice())
16120 .iter()
16121 .map(|highlight| (highlight.range.clone(), highlight.color))
16122 }
16123
16124 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
16125 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
16126 /// Allows to ignore certain kinds of highlights.
16127 pub fn highlighted_display_rows(
16128 &self,
16129 window: &mut Window,
16130 cx: &mut App,
16131 ) -> BTreeMap<DisplayRow, LineHighlight> {
16132 let snapshot = self.snapshot(window, cx);
16133 let mut used_highlight_orders = HashMap::default();
16134 self.highlighted_rows
16135 .iter()
16136 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
16137 .fold(
16138 BTreeMap::<DisplayRow, LineHighlight>::new(),
16139 |mut unique_rows, highlight| {
16140 let start = highlight.range.start.to_display_point(&snapshot);
16141 let end = highlight.range.end.to_display_point(&snapshot);
16142 let start_row = start.row().0;
16143 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
16144 && end.column() == 0
16145 {
16146 end.row().0.saturating_sub(1)
16147 } else {
16148 end.row().0
16149 };
16150 for row in start_row..=end_row {
16151 let used_index =
16152 used_highlight_orders.entry(row).or_insert(highlight.index);
16153 if highlight.index >= *used_index {
16154 *used_index = highlight.index;
16155 unique_rows.insert(DisplayRow(row), highlight.color.into());
16156 }
16157 }
16158 unique_rows
16159 },
16160 )
16161 }
16162
16163 pub fn highlighted_display_row_for_autoscroll(
16164 &self,
16165 snapshot: &DisplaySnapshot,
16166 ) -> Option<DisplayRow> {
16167 self.highlighted_rows
16168 .values()
16169 .flat_map(|highlighted_rows| highlighted_rows.iter())
16170 .filter_map(|highlight| {
16171 if highlight.should_autoscroll {
16172 Some(highlight.range.start.to_display_point(snapshot).row())
16173 } else {
16174 None
16175 }
16176 })
16177 .min()
16178 }
16179
16180 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
16181 self.highlight_background::<SearchWithinRange>(
16182 ranges,
16183 |colors| colors.editor_document_highlight_read_background,
16184 cx,
16185 )
16186 }
16187
16188 pub fn set_breadcrumb_header(&mut self, new_header: String) {
16189 self.breadcrumb_header = Some(new_header);
16190 }
16191
16192 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
16193 self.clear_background_highlights::<SearchWithinRange>(cx);
16194 }
16195
16196 pub fn highlight_background<T: 'static>(
16197 &mut self,
16198 ranges: &[Range<Anchor>],
16199 color_fetcher: fn(&ThemeColors) -> Hsla,
16200 cx: &mut Context<Self>,
16201 ) {
16202 self.background_highlights
16203 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16204 self.scrollbar_marker_state.dirty = true;
16205 cx.notify();
16206 }
16207
16208 pub fn clear_background_highlights<T: 'static>(
16209 &mut self,
16210 cx: &mut Context<Self>,
16211 ) -> Option<BackgroundHighlight> {
16212 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
16213 if !text_highlights.1.is_empty() {
16214 self.scrollbar_marker_state.dirty = true;
16215 cx.notify();
16216 }
16217 Some(text_highlights)
16218 }
16219
16220 pub fn highlight_gutter<T: 'static>(
16221 &mut self,
16222 ranges: &[Range<Anchor>],
16223 color_fetcher: fn(&App) -> Hsla,
16224 cx: &mut Context<Self>,
16225 ) {
16226 self.gutter_highlights
16227 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16228 cx.notify();
16229 }
16230
16231 pub fn clear_gutter_highlights<T: 'static>(
16232 &mut self,
16233 cx: &mut Context<Self>,
16234 ) -> Option<GutterHighlight> {
16235 cx.notify();
16236 self.gutter_highlights.remove(&TypeId::of::<T>())
16237 }
16238
16239 #[cfg(feature = "test-support")]
16240 pub fn all_text_background_highlights(
16241 &self,
16242 window: &mut Window,
16243 cx: &mut Context<Self>,
16244 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16245 let snapshot = self.snapshot(window, cx);
16246 let buffer = &snapshot.buffer_snapshot;
16247 let start = buffer.anchor_before(0);
16248 let end = buffer.anchor_after(buffer.len());
16249 let theme = cx.theme().colors();
16250 self.background_highlights_in_range(start..end, &snapshot, theme)
16251 }
16252
16253 #[cfg(feature = "test-support")]
16254 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
16255 let snapshot = self.buffer().read(cx).snapshot(cx);
16256
16257 let highlights = self
16258 .background_highlights
16259 .get(&TypeId::of::<items::BufferSearchHighlights>());
16260
16261 if let Some((_color, ranges)) = highlights {
16262 ranges
16263 .iter()
16264 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
16265 .collect_vec()
16266 } else {
16267 vec![]
16268 }
16269 }
16270
16271 fn document_highlights_for_position<'a>(
16272 &'a self,
16273 position: Anchor,
16274 buffer: &'a MultiBufferSnapshot,
16275 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
16276 let read_highlights = self
16277 .background_highlights
16278 .get(&TypeId::of::<DocumentHighlightRead>())
16279 .map(|h| &h.1);
16280 let write_highlights = self
16281 .background_highlights
16282 .get(&TypeId::of::<DocumentHighlightWrite>())
16283 .map(|h| &h.1);
16284 let left_position = position.bias_left(buffer);
16285 let right_position = position.bias_right(buffer);
16286 read_highlights
16287 .into_iter()
16288 .chain(write_highlights)
16289 .flat_map(move |ranges| {
16290 let start_ix = match ranges.binary_search_by(|probe| {
16291 let cmp = probe.end.cmp(&left_position, buffer);
16292 if cmp.is_ge() {
16293 Ordering::Greater
16294 } else {
16295 Ordering::Less
16296 }
16297 }) {
16298 Ok(i) | Err(i) => i,
16299 };
16300
16301 ranges[start_ix..]
16302 .iter()
16303 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
16304 })
16305 }
16306
16307 pub fn has_background_highlights<T: 'static>(&self) -> bool {
16308 self.background_highlights
16309 .get(&TypeId::of::<T>())
16310 .map_or(false, |(_, highlights)| !highlights.is_empty())
16311 }
16312
16313 pub fn background_highlights_in_range(
16314 &self,
16315 search_range: Range<Anchor>,
16316 display_snapshot: &DisplaySnapshot,
16317 theme: &ThemeColors,
16318 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16319 let mut results = Vec::new();
16320 for (color_fetcher, ranges) in self.background_highlights.values() {
16321 let color = color_fetcher(theme);
16322 let start_ix = match ranges.binary_search_by(|probe| {
16323 let cmp = probe
16324 .end
16325 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16326 if cmp.is_gt() {
16327 Ordering::Greater
16328 } else {
16329 Ordering::Less
16330 }
16331 }) {
16332 Ok(i) | Err(i) => i,
16333 };
16334 for range in &ranges[start_ix..] {
16335 if range
16336 .start
16337 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16338 .is_ge()
16339 {
16340 break;
16341 }
16342
16343 let start = range.start.to_display_point(display_snapshot);
16344 let end = range.end.to_display_point(display_snapshot);
16345 results.push((start..end, color))
16346 }
16347 }
16348 results
16349 }
16350
16351 pub fn background_highlight_row_ranges<T: 'static>(
16352 &self,
16353 search_range: Range<Anchor>,
16354 display_snapshot: &DisplaySnapshot,
16355 count: usize,
16356 ) -> Vec<RangeInclusive<DisplayPoint>> {
16357 let mut results = Vec::new();
16358 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
16359 return vec![];
16360 };
16361
16362 let start_ix = match ranges.binary_search_by(|probe| {
16363 let cmp = probe
16364 .end
16365 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16366 if cmp.is_gt() {
16367 Ordering::Greater
16368 } else {
16369 Ordering::Less
16370 }
16371 }) {
16372 Ok(i) | Err(i) => i,
16373 };
16374 let mut push_region = |start: Option<Point>, end: Option<Point>| {
16375 if let (Some(start_display), Some(end_display)) = (start, end) {
16376 results.push(
16377 start_display.to_display_point(display_snapshot)
16378 ..=end_display.to_display_point(display_snapshot),
16379 );
16380 }
16381 };
16382 let mut start_row: Option<Point> = None;
16383 let mut end_row: Option<Point> = None;
16384 if ranges.len() > count {
16385 return Vec::new();
16386 }
16387 for range in &ranges[start_ix..] {
16388 if range
16389 .start
16390 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16391 .is_ge()
16392 {
16393 break;
16394 }
16395 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
16396 if let Some(current_row) = &end_row {
16397 if end.row == current_row.row {
16398 continue;
16399 }
16400 }
16401 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
16402 if start_row.is_none() {
16403 assert_eq!(end_row, None);
16404 start_row = Some(start);
16405 end_row = Some(end);
16406 continue;
16407 }
16408 if let Some(current_end) = end_row.as_mut() {
16409 if start.row > current_end.row + 1 {
16410 push_region(start_row, end_row);
16411 start_row = Some(start);
16412 end_row = Some(end);
16413 } else {
16414 // Merge two hunks.
16415 *current_end = end;
16416 }
16417 } else {
16418 unreachable!();
16419 }
16420 }
16421 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
16422 push_region(start_row, end_row);
16423 results
16424 }
16425
16426 pub fn gutter_highlights_in_range(
16427 &self,
16428 search_range: Range<Anchor>,
16429 display_snapshot: &DisplaySnapshot,
16430 cx: &App,
16431 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16432 let mut results = Vec::new();
16433 for (color_fetcher, ranges) in self.gutter_highlights.values() {
16434 let color = color_fetcher(cx);
16435 let start_ix = match ranges.binary_search_by(|probe| {
16436 let cmp = probe
16437 .end
16438 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16439 if cmp.is_gt() {
16440 Ordering::Greater
16441 } else {
16442 Ordering::Less
16443 }
16444 }) {
16445 Ok(i) | Err(i) => i,
16446 };
16447 for range in &ranges[start_ix..] {
16448 if range
16449 .start
16450 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16451 .is_ge()
16452 {
16453 break;
16454 }
16455
16456 let start = range.start.to_display_point(display_snapshot);
16457 let end = range.end.to_display_point(display_snapshot);
16458 results.push((start..end, color))
16459 }
16460 }
16461 results
16462 }
16463
16464 /// Get the text ranges corresponding to the redaction query
16465 pub fn redacted_ranges(
16466 &self,
16467 search_range: Range<Anchor>,
16468 display_snapshot: &DisplaySnapshot,
16469 cx: &App,
16470 ) -> Vec<Range<DisplayPoint>> {
16471 display_snapshot
16472 .buffer_snapshot
16473 .redacted_ranges(search_range, |file| {
16474 if let Some(file) = file {
16475 file.is_private()
16476 && EditorSettings::get(
16477 Some(SettingsLocation {
16478 worktree_id: file.worktree_id(cx),
16479 path: file.path().as_ref(),
16480 }),
16481 cx,
16482 )
16483 .redact_private_values
16484 } else {
16485 false
16486 }
16487 })
16488 .map(|range| {
16489 range.start.to_display_point(display_snapshot)
16490 ..range.end.to_display_point(display_snapshot)
16491 })
16492 .collect()
16493 }
16494
16495 pub fn highlight_text<T: 'static>(
16496 &mut self,
16497 ranges: Vec<Range<Anchor>>,
16498 style: HighlightStyle,
16499 cx: &mut Context<Self>,
16500 ) {
16501 self.display_map.update(cx, |map, _| {
16502 map.highlight_text(TypeId::of::<T>(), ranges, style)
16503 });
16504 cx.notify();
16505 }
16506
16507 pub(crate) fn highlight_inlays<T: 'static>(
16508 &mut self,
16509 highlights: Vec<InlayHighlight>,
16510 style: HighlightStyle,
16511 cx: &mut Context<Self>,
16512 ) {
16513 self.display_map.update(cx, |map, _| {
16514 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
16515 });
16516 cx.notify();
16517 }
16518
16519 pub fn text_highlights<'a, T: 'static>(
16520 &'a self,
16521 cx: &'a App,
16522 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
16523 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
16524 }
16525
16526 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
16527 let cleared = self
16528 .display_map
16529 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
16530 if cleared {
16531 cx.notify();
16532 }
16533 }
16534
16535 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
16536 (self.read_only(cx) || self.blink_manager.read(cx).visible())
16537 && self.focus_handle.is_focused(window)
16538 }
16539
16540 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
16541 self.show_cursor_when_unfocused = is_enabled;
16542 cx.notify();
16543 }
16544
16545 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
16546 cx.notify();
16547 }
16548
16549 fn on_buffer_event(
16550 &mut self,
16551 multibuffer: &Entity<MultiBuffer>,
16552 event: &multi_buffer::Event,
16553 window: &mut Window,
16554 cx: &mut Context<Self>,
16555 ) {
16556 match event {
16557 multi_buffer::Event::Edited {
16558 singleton_buffer_edited,
16559 edited_buffer: buffer_edited,
16560 } => {
16561 self.scrollbar_marker_state.dirty = true;
16562 self.active_indent_guides_state.dirty = true;
16563 self.refresh_active_diagnostics(cx);
16564 self.refresh_code_actions(window, cx);
16565 if self.has_active_inline_completion() {
16566 self.update_visible_inline_completion(window, cx);
16567 }
16568 if let Some(buffer) = buffer_edited {
16569 let buffer_id = buffer.read(cx).remote_id();
16570 if !self.registered_buffers.contains_key(&buffer_id) {
16571 if let Some(project) = self.project.as_ref() {
16572 project.update(cx, |project, cx| {
16573 self.registered_buffers.insert(
16574 buffer_id,
16575 project.register_buffer_with_language_servers(&buffer, cx),
16576 );
16577 })
16578 }
16579 }
16580 }
16581 cx.emit(EditorEvent::BufferEdited);
16582 cx.emit(SearchEvent::MatchesInvalidated);
16583 if *singleton_buffer_edited {
16584 if let Some(project) = &self.project {
16585 #[allow(clippy::mutable_key_type)]
16586 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
16587 multibuffer
16588 .all_buffers()
16589 .into_iter()
16590 .filter_map(|buffer| {
16591 buffer.update(cx, |buffer, cx| {
16592 let language = buffer.language()?;
16593 let should_discard = project.update(cx, |project, cx| {
16594 project.is_local()
16595 && !project.has_language_servers_for(buffer, cx)
16596 });
16597 should_discard.not().then_some(language.clone())
16598 })
16599 })
16600 .collect::<HashSet<_>>()
16601 });
16602 if !languages_affected.is_empty() {
16603 self.refresh_inlay_hints(
16604 InlayHintRefreshReason::BufferEdited(languages_affected),
16605 cx,
16606 );
16607 }
16608 }
16609 }
16610
16611 let Some(project) = &self.project else { return };
16612 let (telemetry, is_via_ssh) = {
16613 let project = project.read(cx);
16614 let telemetry = project.client().telemetry().clone();
16615 let is_via_ssh = project.is_via_ssh();
16616 (telemetry, is_via_ssh)
16617 };
16618 refresh_linked_ranges(self, window, cx);
16619 telemetry.log_edit_event("editor", is_via_ssh);
16620 }
16621 multi_buffer::Event::ExcerptsAdded {
16622 buffer,
16623 predecessor,
16624 excerpts,
16625 } => {
16626 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16627 let buffer_id = buffer.read(cx).remote_id();
16628 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
16629 if let Some(project) = &self.project {
16630 get_uncommitted_diff_for_buffer(
16631 project,
16632 [buffer.clone()],
16633 self.buffer.clone(),
16634 cx,
16635 )
16636 .detach();
16637 }
16638 }
16639 cx.emit(EditorEvent::ExcerptsAdded {
16640 buffer: buffer.clone(),
16641 predecessor: *predecessor,
16642 excerpts: excerpts.clone(),
16643 });
16644 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
16645 }
16646 multi_buffer::Event::ExcerptsRemoved { ids } => {
16647 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
16648 let buffer = self.buffer.read(cx);
16649 self.registered_buffers
16650 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
16651 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
16652 cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
16653 }
16654 multi_buffer::Event::ExcerptsEdited {
16655 excerpt_ids,
16656 buffer_ids,
16657 } => {
16658 self.display_map.update(cx, |map, cx| {
16659 map.unfold_buffers(buffer_ids.iter().copied(), cx)
16660 });
16661 cx.emit(EditorEvent::ExcerptsEdited {
16662 ids: excerpt_ids.clone(),
16663 })
16664 }
16665 multi_buffer::Event::ExcerptsExpanded { ids } => {
16666 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
16667 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
16668 }
16669 multi_buffer::Event::Reparsed(buffer_id) => {
16670 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16671 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
16672
16673 cx.emit(EditorEvent::Reparsed(*buffer_id));
16674 }
16675 multi_buffer::Event::DiffHunksToggled => {
16676 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16677 }
16678 multi_buffer::Event::LanguageChanged(buffer_id) => {
16679 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
16680 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
16681 cx.emit(EditorEvent::Reparsed(*buffer_id));
16682 cx.notify();
16683 }
16684 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
16685 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
16686 multi_buffer::Event::FileHandleChanged
16687 | multi_buffer::Event::Reloaded
16688 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
16689 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
16690 multi_buffer::Event::DiagnosticsUpdated => {
16691 self.refresh_active_diagnostics(cx);
16692 self.refresh_inline_diagnostics(true, window, cx);
16693 self.scrollbar_marker_state.dirty = true;
16694 cx.notify();
16695 }
16696 _ => {}
16697 };
16698 }
16699
16700 fn on_display_map_changed(
16701 &mut self,
16702 _: Entity<DisplayMap>,
16703 _: &mut Window,
16704 cx: &mut Context<Self>,
16705 ) {
16706 cx.notify();
16707 }
16708
16709 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16710 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16711 self.update_edit_prediction_settings(cx);
16712 self.refresh_inline_completion(true, false, window, cx);
16713 self.refresh_inlay_hints(
16714 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
16715 self.selections.newest_anchor().head(),
16716 &self.buffer.read(cx).snapshot(cx),
16717 cx,
16718 )),
16719 cx,
16720 );
16721
16722 let old_cursor_shape = self.cursor_shape;
16723
16724 {
16725 let editor_settings = EditorSettings::get_global(cx);
16726 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
16727 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
16728 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
16729 self.hide_mouse_while_typing = editor_settings.hide_mouse_while_typing.unwrap_or(true);
16730
16731 if !self.hide_mouse_while_typing {
16732 self.mouse_cursor_hidden = false;
16733 }
16734 }
16735
16736 if old_cursor_shape != self.cursor_shape {
16737 cx.emit(EditorEvent::CursorShapeChanged);
16738 }
16739
16740 let project_settings = ProjectSettings::get_global(cx);
16741 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
16742
16743 if self.mode == EditorMode::Full {
16744 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
16745 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
16746 if self.show_inline_diagnostics != show_inline_diagnostics {
16747 self.show_inline_diagnostics = show_inline_diagnostics;
16748 self.refresh_inline_diagnostics(false, window, cx);
16749 }
16750
16751 if self.git_blame_inline_enabled != inline_blame_enabled {
16752 self.toggle_git_blame_inline_internal(false, window, cx);
16753 }
16754 }
16755
16756 cx.notify();
16757 }
16758
16759 pub fn set_searchable(&mut self, searchable: bool) {
16760 self.searchable = searchable;
16761 }
16762
16763 pub fn searchable(&self) -> bool {
16764 self.searchable
16765 }
16766
16767 fn open_proposed_changes_editor(
16768 &mut self,
16769 _: &OpenProposedChangesEditor,
16770 window: &mut Window,
16771 cx: &mut Context<Self>,
16772 ) {
16773 let Some(workspace) = self.workspace() else {
16774 cx.propagate();
16775 return;
16776 };
16777
16778 let selections = self.selections.all::<usize>(cx);
16779 let multi_buffer = self.buffer.read(cx);
16780 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16781 let mut new_selections_by_buffer = HashMap::default();
16782 for selection in selections {
16783 for (buffer, range, _) in
16784 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
16785 {
16786 let mut range = range.to_point(buffer);
16787 range.start.column = 0;
16788 range.end.column = buffer.line_len(range.end.row);
16789 new_selections_by_buffer
16790 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
16791 .or_insert(Vec::new())
16792 .push(range)
16793 }
16794 }
16795
16796 let proposed_changes_buffers = new_selections_by_buffer
16797 .into_iter()
16798 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
16799 .collect::<Vec<_>>();
16800 let proposed_changes_editor = cx.new(|cx| {
16801 ProposedChangesEditor::new(
16802 "Proposed changes",
16803 proposed_changes_buffers,
16804 self.project.clone(),
16805 window,
16806 cx,
16807 )
16808 });
16809
16810 window.defer(cx, move |window, cx| {
16811 workspace.update(cx, |workspace, cx| {
16812 workspace.active_pane().update(cx, |pane, cx| {
16813 pane.add_item(
16814 Box::new(proposed_changes_editor),
16815 true,
16816 true,
16817 None,
16818 window,
16819 cx,
16820 );
16821 });
16822 });
16823 });
16824 }
16825
16826 pub fn open_excerpts_in_split(
16827 &mut self,
16828 _: &OpenExcerptsSplit,
16829 window: &mut Window,
16830 cx: &mut Context<Self>,
16831 ) {
16832 self.open_excerpts_common(None, true, window, cx)
16833 }
16834
16835 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
16836 self.open_excerpts_common(None, false, window, cx)
16837 }
16838
16839 fn open_excerpts_common(
16840 &mut self,
16841 jump_data: Option<JumpData>,
16842 split: bool,
16843 window: &mut Window,
16844 cx: &mut Context<Self>,
16845 ) {
16846 let Some(workspace) = self.workspace() else {
16847 cx.propagate();
16848 return;
16849 };
16850
16851 if self.buffer.read(cx).is_singleton() {
16852 cx.propagate();
16853 return;
16854 }
16855
16856 let mut new_selections_by_buffer = HashMap::default();
16857 match &jump_data {
16858 Some(JumpData::MultiBufferPoint {
16859 excerpt_id,
16860 position,
16861 anchor,
16862 line_offset_from_top,
16863 }) => {
16864 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16865 if let Some(buffer) = multi_buffer_snapshot
16866 .buffer_id_for_excerpt(*excerpt_id)
16867 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
16868 {
16869 let buffer_snapshot = buffer.read(cx).snapshot();
16870 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
16871 language::ToPoint::to_point(anchor, &buffer_snapshot)
16872 } else {
16873 buffer_snapshot.clip_point(*position, Bias::Left)
16874 };
16875 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
16876 new_selections_by_buffer.insert(
16877 buffer,
16878 (
16879 vec![jump_to_offset..jump_to_offset],
16880 Some(*line_offset_from_top),
16881 ),
16882 );
16883 }
16884 }
16885 Some(JumpData::MultiBufferRow {
16886 row,
16887 line_offset_from_top,
16888 }) => {
16889 let point = MultiBufferPoint::new(row.0, 0);
16890 if let Some((buffer, buffer_point, _)) =
16891 self.buffer.read(cx).point_to_buffer_point(point, cx)
16892 {
16893 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
16894 new_selections_by_buffer
16895 .entry(buffer)
16896 .or_insert((Vec::new(), Some(*line_offset_from_top)))
16897 .0
16898 .push(buffer_offset..buffer_offset)
16899 }
16900 }
16901 None => {
16902 let selections = self.selections.all::<usize>(cx);
16903 let multi_buffer = self.buffer.read(cx);
16904 for selection in selections {
16905 for (snapshot, range, _, anchor) in multi_buffer
16906 .snapshot(cx)
16907 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
16908 {
16909 if let Some(anchor) = anchor {
16910 // selection is in a deleted hunk
16911 let Some(buffer_id) = anchor.buffer_id else {
16912 continue;
16913 };
16914 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
16915 continue;
16916 };
16917 let offset = text::ToOffset::to_offset(
16918 &anchor.text_anchor,
16919 &buffer_handle.read(cx).snapshot(),
16920 );
16921 let range = offset..offset;
16922 new_selections_by_buffer
16923 .entry(buffer_handle)
16924 .or_insert((Vec::new(), None))
16925 .0
16926 .push(range)
16927 } else {
16928 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
16929 else {
16930 continue;
16931 };
16932 new_selections_by_buffer
16933 .entry(buffer_handle)
16934 .or_insert((Vec::new(), None))
16935 .0
16936 .push(range)
16937 }
16938 }
16939 }
16940 }
16941 }
16942
16943 if new_selections_by_buffer.is_empty() {
16944 return;
16945 }
16946
16947 // We defer the pane interaction because we ourselves are a workspace item
16948 // and activating a new item causes the pane to call a method on us reentrantly,
16949 // which panics if we're on the stack.
16950 window.defer(cx, move |window, cx| {
16951 workspace.update(cx, |workspace, cx| {
16952 let pane = if split {
16953 workspace.adjacent_pane(window, cx)
16954 } else {
16955 workspace.active_pane().clone()
16956 };
16957
16958 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
16959 let editor = buffer
16960 .read(cx)
16961 .file()
16962 .is_none()
16963 .then(|| {
16964 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
16965 // so `workspace.open_project_item` will never find them, always opening a new editor.
16966 // Instead, we try to activate the existing editor in the pane first.
16967 let (editor, pane_item_index) =
16968 pane.read(cx).items().enumerate().find_map(|(i, item)| {
16969 let editor = item.downcast::<Editor>()?;
16970 let singleton_buffer =
16971 editor.read(cx).buffer().read(cx).as_singleton()?;
16972 if singleton_buffer == buffer {
16973 Some((editor, i))
16974 } else {
16975 None
16976 }
16977 })?;
16978 pane.update(cx, |pane, cx| {
16979 pane.activate_item(pane_item_index, true, true, window, cx)
16980 });
16981 Some(editor)
16982 })
16983 .flatten()
16984 .unwrap_or_else(|| {
16985 workspace.open_project_item::<Self>(
16986 pane.clone(),
16987 buffer,
16988 true,
16989 true,
16990 window,
16991 cx,
16992 )
16993 });
16994
16995 editor.update(cx, |editor, cx| {
16996 let autoscroll = match scroll_offset {
16997 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
16998 None => Autoscroll::newest(),
16999 };
17000 let nav_history = editor.nav_history.take();
17001 editor.change_selections(Some(autoscroll), window, cx, |s| {
17002 s.select_ranges(ranges);
17003 });
17004 editor.nav_history = nav_history;
17005 });
17006 }
17007 })
17008 });
17009 }
17010
17011 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
17012 let snapshot = self.buffer.read(cx).read(cx);
17013 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
17014 Some(
17015 ranges
17016 .iter()
17017 .map(move |range| {
17018 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
17019 })
17020 .collect(),
17021 )
17022 }
17023
17024 fn selection_replacement_ranges(
17025 &self,
17026 range: Range<OffsetUtf16>,
17027 cx: &mut App,
17028 ) -> Vec<Range<OffsetUtf16>> {
17029 let selections = self.selections.all::<OffsetUtf16>(cx);
17030 let newest_selection = selections
17031 .iter()
17032 .max_by_key(|selection| selection.id)
17033 .unwrap();
17034 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
17035 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
17036 let snapshot = self.buffer.read(cx).read(cx);
17037 selections
17038 .into_iter()
17039 .map(|mut selection| {
17040 selection.start.0 =
17041 (selection.start.0 as isize).saturating_add(start_delta) as usize;
17042 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
17043 snapshot.clip_offset_utf16(selection.start, Bias::Left)
17044 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
17045 })
17046 .collect()
17047 }
17048
17049 fn report_editor_event(
17050 &self,
17051 event_type: &'static str,
17052 file_extension: Option<String>,
17053 cx: &App,
17054 ) {
17055 if cfg!(any(test, feature = "test-support")) {
17056 return;
17057 }
17058
17059 let Some(project) = &self.project else { return };
17060
17061 // If None, we are in a file without an extension
17062 let file = self
17063 .buffer
17064 .read(cx)
17065 .as_singleton()
17066 .and_then(|b| b.read(cx).file());
17067 let file_extension = file_extension.or(file
17068 .as_ref()
17069 .and_then(|file| Path::new(file.file_name(cx)).extension())
17070 .and_then(|e| e.to_str())
17071 .map(|a| a.to_string()));
17072
17073 let vim_mode = cx
17074 .global::<SettingsStore>()
17075 .raw_user_settings()
17076 .get("vim_mode")
17077 == Some(&serde_json::Value::Bool(true));
17078
17079 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
17080 let copilot_enabled = edit_predictions_provider
17081 == language::language_settings::EditPredictionProvider::Copilot;
17082 let copilot_enabled_for_language = self
17083 .buffer
17084 .read(cx)
17085 .language_settings(cx)
17086 .show_edit_predictions;
17087
17088 let project = project.read(cx);
17089 telemetry::event!(
17090 event_type,
17091 file_extension,
17092 vim_mode,
17093 copilot_enabled,
17094 copilot_enabled_for_language,
17095 edit_predictions_provider,
17096 is_via_ssh = project.is_via_ssh(),
17097 );
17098 }
17099
17100 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
17101 /// with each line being an array of {text, highlight} objects.
17102 fn copy_highlight_json(
17103 &mut self,
17104 _: &CopyHighlightJson,
17105 window: &mut Window,
17106 cx: &mut Context<Self>,
17107 ) {
17108 #[derive(Serialize)]
17109 struct Chunk<'a> {
17110 text: String,
17111 highlight: Option<&'a str>,
17112 }
17113
17114 let snapshot = self.buffer.read(cx).snapshot(cx);
17115 let range = self
17116 .selected_text_range(false, window, cx)
17117 .and_then(|selection| {
17118 if selection.range.is_empty() {
17119 None
17120 } else {
17121 Some(selection.range)
17122 }
17123 })
17124 .unwrap_or_else(|| 0..snapshot.len());
17125
17126 let chunks = snapshot.chunks(range, true);
17127 let mut lines = Vec::new();
17128 let mut line: VecDeque<Chunk> = VecDeque::new();
17129
17130 let Some(style) = self.style.as_ref() else {
17131 return;
17132 };
17133
17134 for chunk in chunks {
17135 let highlight = chunk
17136 .syntax_highlight_id
17137 .and_then(|id| id.name(&style.syntax));
17138 let mut chunk_lines = chunk.text.split('\n').peekable();
17139 while let Some(text) = chunk_lines.next() {
17140 let mut merged_with_last_token = false;
17141 if let Some(last_token) = line.back_mut() {
17142 if last_token.highlight == highlight {
17143 last_token.text.push_str(text);
17144 merged_with_last_token = true;
17145 }
17146 }
17147
17148 if !merged_with_last_token {
17149 line.push_back(Chunk {
17150 text: text.into(),
17151 highlight,
17152 });
17153 }
17154
17155 if chunk_lines.peek().is_some() {
17156 if line.len() > 1 && line.front().unwrap().text.is_empty() {
17157 line.pop_front();
17158 }
17159 if line.len() > 1 && line.back().unwrap().text.is_empty() {
17160 line.pop_back();
17161 }
17162
17163 lines.push(mem::take(&mut line));
17164 }
17165 }
17166 }
17167
17168 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
17169 return;
17170 };
17171 cx.write_to_clipboard(ClipboardItem::new_string(lines));
17172 }
17173
17174 pub fn open_context_menu(
17175 &mut self,
17176 _: &OpenContextMenu,
17177 window: &mut Window,
17178 cx: &mut Context<Self>,
17179 ) {
17180 self.request_autoscroll(Autoscroll::newest(), cx);
17181 let position = self.selections.newest_display(cx).start;
17182 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
17183 }
17184
17185 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
17186 &self.inlay_hint_cache
17187 }
17188
17189 pub fn replay_insert_event(
17190 &mut self,
17191 text: &str,
17192 relative_utf16_range: Option<Range<isize>>,
17193 window: &mut Window,
17194 cx: &mut Context<Self>,
17195 ) {
17196 if !self.input_enabled {
17197 cx.emit(EditorEvent::InputIgnored { text: text.into() });
17198 return;
17199 }
17200 if let Some(relative_utf16_range) = relative_utf16_range {
17201 let selections = self.selections.all::<OffsetUtf16>(cx);
17202 self.change_selections(None, window, cx, |s| {
17203 let new_ranges = selections.into_iter().map(|range| {
17204 let start = OffsetUtf16(
17205 range
17206 .head()
17207 .0
17208 .saturating_add_signed(relative_utf16_range.start),
17209 );
17210 let end = OffsetUtf16(
17211 range
17212 .head()
17213 .0
17214 .saturating_add_signed(relative_utf16_range.end),
17215 );
17216 start..end
17217 });
17218 s.select_ranges(new_ranges);
17219 });
17220 }
17221
17222 self.handle_input(text, window, cx);
17223 }
17224
17225 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
17226 let Some(provider) = self.semantics_provider.as_ref() else {
17227 return false;
17228 };
17229
17230 let mut supports = false;
17231 self.buffer().update(cx, |this, cx| {
17232 this.for_each_buffer(|buffer| {
17233 supports |= provider.supports_inlay_hints(buffer, cx);
17234 });
17235 });
17236
17237 supports
17238 }
17239
17240 pub fn is_focused(&self, window: &Window) -> bool {
17241 self.focus_handle.is_focused(window)
17242 }
17243
17244 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17245 cx.emit(EditorEvent::Focused);
17246
17247 if let Some(descendant) = self
17248 .last_focused_descendant
17249 .take()
17250 .and_then(|descendant| descendant.upgrade())
17251 {
17252 window.focus(&descendant);
17253 } else {
17254 if let Some(blame) = self.blame.as_ref() {
17255 blame.update(cx, GitBlame::focus)
17256 }
17257
17258 self.blink_manager.update(cx, BlinkManager::enable);
17259 self.show_cursor_names(window, cx);
17260 self.buffer.update(cx, |buffer, cx| {
17261 buffer.finalize_last_transaction(cx);
17262 if self.leader_peer_id.is_none() {
17263 buffer.set_active_selections(
17264 &self.selections.disjoint_anchors(),
17265 self.selections.line_mode,
17266 self.cursor_shape,
17267 cx,
17268 );
17269 }
17270 });
17271 }
17272 }
17273
17274 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
17275 cx.emit(EditorEvent::FocusedIn)
17276 }
17277
17278 fn handle_focus_out(
17279 &mut self,
17280 event: FocusOutEvent,
17281 _window: &mut Window,
17282 cx: &mut Context<Self>,
17283 ) {
17284 if event.blurred != self.focus_handle {
17285 self.last_focused_descendant = Some(event.blurred);
17286 }
17287 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
17288 }
17289
17290 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17291 self.blink_manager.update(cx, BlinkManager::disable);
17292 self.buffer
17293 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
17294
17295 if let Some(blame) = self.blame.as_ref() {
17296 blame.update(cx, GitBlame::blur)
17297 }
17298 if !self.hover_state.focused(window, cx) {
17299 hide_hover(self, cx);
17300 }
17301 if !self
17302 .context_menu
17303 .borrow()
17304 .as_ref()
17305 .is_some_and(|context_menu| context_menu.focused(window, cx))
17306 {
17307 self.hide_context_menu(window, cx);
17308 }
17309 self.discard_inline_completion(false, cx);
17310 cx.emit(EditorEvent::Blurred);
17311 cx.notify();
17312 }
17313
17314 pub fn register_action<A: Action>(
17315 &mut self,
17316 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
17317 ) -> Subscription {
17318 let id = self.next_editor_action_id.post_inc();
17319 let listener = Arc::new(listener);
17320 self.editor_actions.borrow_mut().insert(
17321 id,
17322 Box::new(move |window, _| {
17323 let listener = listener.clone();
17324 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
17325 let action = action.downcast_ref().unwrap();
17326 if phase == DispatchPhase::Bubble {
17327 listener(action, window, cx)
17328 }
17329 })
17330 }),
17331 );
17332
17333 let editor_actions = self.editor_actions.clone();
17334 Subscription::new(move || {
17335 editor_actions.borrow_mut().remove(&id);
17336 })
17337 }
17338
17339 pub fn file_header_size(&self) -> u32 {
17340 FILE_HEADER_HEIGHT
17341 }
17342
17343 pub fn restore(
17344 &mut self,
17345 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
17346 window: &mut Window,
17347 cx: &mut Context<Self>,
17348 ) {
17349 let workspace = self.workspace();
17350 let project = self.project.as_ref();
17351 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
17352 let mut tasks = Vec::new();
17353 for (buffer_id, changes) in revert_changes {
17354 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
17355 buffer.update(cx, |buffer, cx| {
17356 buffer.edit(
17357 changes
17358 .into_iter()
17359 .map(|(range, text)| (range, text.to_string())),
17360 None,
17361 cx,
17362 );
17363 });
17364
17365 if let Some(project) =
17366 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
17367 {
17368 project.update(cx, |project, cx| {
17369 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
17370 })
17371 }
17372 }
17373 }
17374 tasks
17375 });
17376 cx.spawn_in(window, async move |_, cx| {
17377 for (buffer, task) in save_tasks {
17378 let result = task.await;
17379 if result.is_err() {
17380 let Some(path) = buffer
17381 .read_with(cx, |buffer, cx| buffer.project_path(cx))
17382 .ok()
17383 else {
17384 continue;
17385 };
17386 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
17387 let Some(task) = cx
17388 .update_window_entity(&workspace, |workspace, window, cx| {
17389 workspace
17390 .open_path_preview(path, None, false, false, false, window, cx)
17391 })
17392 .ok()
17393 else {
17394 continue;
17395 };
17396 task.await.log_err();
17397 }
17398 }
17399 }
17400 })
17401 .detach();
17402 self.change_selections(None, window, cx, |selections| selections.refresh());
17403 }
17404
17405 pub fn to_pixel_point(
17406 &self,
17407 source: multi_buffer::Anchor,
17408 editor_snapshot: &EditorSnapshot,
17409 window: &mut Window,
17410 ) -> Option<gpui::Point<Pixels>> {
17411 let source_point = source.to_display_point(editor_snapshot);
17412 self.display_to_pixel_point(source_point, editor_snapshot, window)
17413 }
17414
17415 pub fn display_to_pixel_point(
17416 &self,
17417 source: DisplayPoint,
17418 editor_snapshot: &EditorSnapshot,
17419 window: &mut Window,
17420 ) -> Option<gpui::Point<Pixels>> {
17421 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
17422 let text_layout_details = self.text_layout_details(window);
17423 let scroll_top = text_layout_details
17424 .scroll_anchor
17425 .scroll_position(editor_snapshot)
17426 .y;
17427
17428 if source.row().as_f32() < scroll_top.floor() {
17429 return None;
17430 }
17431 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
17432 let source_y = line_height * (source.row().as_f32() - scroll_top);
17433 Some(gpui::Point::new(source_x, source_y))
17434 }
17435
17436 pub fn has_visible_completions_menu(&self) -> bool {
17437 !self.edit_prediction_preview_is_active()
17438 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
17439 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
17440 })
17441 }
17442
17443 pub fn register_addon<T: Addon>(&mut self, instance: T) {
17444 self.addons
17445 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
17446 }
17447
17448 pub fn unregister_addon<T: Addon>(&mut self) {
17449 self.addons.remove(&std::any::TypeId::of::<T>());
17450 }
17451
17452 pub fn addon<T: Addon>(&self) -> Option<&T> {
17453 let type_id = std::any::TypeId::of::<T>();
17454 self.addons
17455 .get(&type_id)
17456 .and_then(|item| item.to_any().downcast_ref::<T>())
17457 }
17458
17459 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
17460 let text_layout_details = self.text_layout_details(window);
17461 let style = &text_layout_details.editor_style;
17462 let font_id = window.text_system().resolve_font(&style.text.font());
17463 let font_size = style.text.font_size.to_pixels(window.rem_size());
17464 let line_height = style.text.line_height_in_pixels(window.rem_size());
17465 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
17466
17467 gpui::Size::new(em_width, line_height)
17468 }
17469
17470 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
17471 self.load_diff_task.clone()
17472 }
17473
17474 fn read_metadata_from_db(
17475 &mut self,
17476 item_id: u64,
17477 workspace_id: WorkspaceId,
17478 window: &mut Window,
17479 cx: &mut Context<Editor>,
17480 ) {
17481 if self.is_singleton(cx)
17482 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
17483 {
17484 let buffer_snapshot = OnceCell::new();
17485
17486 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
17487 if !folds.is_empty() {
17488 let snapshot =
17489 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
17490 self.fold_ranges(
17491 folds
17492 .into_iter()
17493 .map(|(start, end)| {
17494 snapshot.clip_offset(start, Bias::Left)
17495 ..snapshot.clip_offset(end, Bias::Right)
17496 })
17497 .collect(),
17498 false,
17499 window,
17500 cx,
17501 );
17502 }
17503 }
17504
17505 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
17506 if !selections.is_empty() {
17507 let snapshot =
17508 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
17509 self.change_selections(None, window, cx, |s| {
17510 s.select_ranges(selections.into_iter().map(|(start, end)| {
17511 snapshot.clip_offset(start, Bias::Left)
17512 ..snapshot.clip_offset(end, Bias::Right)
17513 }));
17514 });
17515 }
17516 };
17517 }
17518
17519 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
17520 }
17521}
17522
17523fn insert_extra_newline_brackets(
17524 buffer: &MultiBufferSnapshot,
17525 range: Range<usize>,
17526 language: &language::LanguageScope,
17527) -> bool {
17528 let leading_whitespace_len = buffer
17529 .reversed_chars_at(range.start)
17530 .take_while(|c| c.is_whitespace() && *c != '\n')
17531 .map(|c| c.len_utf8())
17532 .sum::<usize>();
17533 let trailing_whitespace_len = buffer
17534 .chars_at(range.end)
17535 .take_while(|c| c.is_whitespace() && *c != '\n')
17536 .map(|c| c.len_utf8())
17537 .sum::<usize>();
17538 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
17539
17540 language.brackets().any(|(pair, enabled)| {
17541 let pair_start = pair.start.trim_end();
17542 let pair_end = pair.end.trim_start();
17543
17544 enabled
17545 && pair.newline
17546 && buffer.contains_str_at(range.end, pair_end)
17547 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
17548 })
17549}
17550
17551fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
17552 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
17553 [(buffer, range, _)] => (*buffer, range.clone()),
17554 _ => return false,
17555 };
17556 let pair = {
17557 let mut result: Option<BracketMatch> = None;
17558
17559 for pair in buffer
17560 .all_bracket_ranges(range.clone())
17561 .filter(move |pair| {
17562 pair.open_range.start <= range.start && pair.close_range.end >= range.end
17563 })
17564 {
17565 let len = pair.close_range.end - pair.open_range.start;
17566
17567 if let Some(existing) = &result {
17568 let existing_len = existing.close_range.end - existing.open_range.start;
17569 if len > existing_len {
17570 continue;
17571 }
17572 }
17573
17574 result = Some(pair);
17575 }
17576
17577 result
17578 };
17579 let Some(pair) = pair else {
17580 return false;
17581 };
17582 pair.newline_only
17583 && buffer
17584 .chars_for_range(pair.open_range.end..range.start)
17585 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
17586 .all(|c| c.is_whitespace() && c != '\n')
17587}
17588
17589fn get_uncommitted_diff_for_buffer(
17590 project: &Entity<Project>,
17591 buffers: impl IntoIterator<Item = Entity<Buffer>>,
17592 buffer: Entity<MultiBuffer>,
17593 cx: &mut App,
17594) -> Task<()> {
17595 let mut tasks = Vec::new();
17596 project.update(cx, |project, cx| {
17597 for buffer in buffers {
17598 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
17599 }
17600 });
17601 cx.spawn(async move |cx| {
17602 let diffs = future::join_all(tasks).await;
17603 buffer
17604 .update(cx, |buffer, cx| {
17605 for diff in diffs.into_iter().flatten() {
17606 buffer.add_diff(diff, cx);
17607 }
17608 })
17609 .ok();
17610 })
17611}
17612
17613fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
17614 let tab_size = tab_size.get() as usize;
17615 let mut width = offset;
17616
17617 for ch in text.chars() {
17618 width += if ch == '\t' {
17619 tab_size - (width % tab_size)
17620 } else {
17621 1
17622 };
17623 }
17624
17625 width - offset
17626}
17627
17628#[cfg(test)]
17629mod tests {
17630 use super::*;
17631
17632 #[test]
17633 fn test_string_size_with_expanded_tabs() {
17634 let nz = |val| NonZeroU32::new(val).unwrap();
17635 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
17636 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
17637 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
17638 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
17639 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
17640 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
17641 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
17642 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
17643 }
17644}
17645
17646/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
17647struct WordBreakingTokenizer<'a> {
17648 input: &'a str,
17649}
17650
17651impl<'a> WordBreakingTokenizer<'a> {
17652 fn new(input: &'a str) -> Self {
17653 Self { input }
17654 }
17655}
17656
17657fn is_char_ideographic(ch: char) -> bool {
17658 use unicode_script::Script::*;
17659 use unicode_script::UnicodeScript;
17660 matches!(ch.script(), Han | Tangut | Yi)
17661}
17662
17663fn is_grapheme_ideographic(text: &str) -> bool {
17664 text.chars().any(is_char_ideographic)
17665}
17666
17667fn is_grapheme_whitespace(text: &str) -> bool {
17668 text.chars().any(|x| x.is_whitespace())
17669}
17670
17671fn should_stay_with_preceding_ideograph(text: &str) -> bool {
17672 text.chars().next().map_or(false, |ch| {
17673 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
17674 })
17675}
17676
17677#[derive(PartialEq, Eq, Debug, Clone, Copy)]
17678enum WordBreakToken<'a> {
17679 Word { token: &'a str, grapheme_len: usize },
17680 InlineWhitespace { token: &'a str, grapheme_len: usize },
17681 Newline,
17682}
17683
17684impl<'a> Iterator for WordBreakingTokenizer<'a> {
17685 /// Yields a span, the count of graphemes in the token, and whether it was
17686 /// whitespace. Note that it also breaks at word boundaries.
17687 type Item = WordBreakToken<'a>;
17688
17689 fn next(&mut self) -> Option<Self::Item> {
17690 use unicode_segmentation::UnicodeSegmentation;
17691 if self.input.is_empty() {
17692 return None;
17693 }
17694
17695 let mut iter = self.input.graphemes(true).peekable();
17696 let mut offset = 0;
17697 let mut grapheme_len = 0;
17698 if let Some(first_grapheme) = iter.next() {
17699 let is_newline = first_grapheme == "\n";
17700 let is_whitespace = is_grapheme_whitespace(first_grapheme);
17701 offset += first_grapheme.len();
17702 grapheme_len += 1;
17703 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
17704 if let Some(grapheme) = iter.peek().copied() {
17705 if should_stay_with_preceding_ideograph(grapheme) {
17706 offset += grapheme.len();
17707 grapheme_len += 1;
17708 }
17709 }
17710 } else {
17711 let mut words = self.input[offset..].split_word_bound_indices().peekable();
17712 let mut next_word_bound = words.peek().copied();
17713 if next_word_bound.map_or(false, |(i, _)| i == 0) {
17714 next_word_bound = words.next();
17715 }
17716 while let Some(grapheme) = iter.peek().copied() {
17717 if next_word_bound.map_or(false, |(i, _)| i == offset) {
17718 break;
17719 };
17720 if is_grapheme_whitespace(grapheme) != is_whitespace
17721 || (grapheme == "\n") != is_newline
17722 {
17723 break;
17724 };
17725 offset += grapheme.len();
17726 grapheme_len += 1;
17727 iter.next();
17728 }
17729 }
17730 let token = &self.input[..offset];
17731 self.input = &self.input[offset..];
17732 if token == "\n" {
17733 Some(WordBreakToken::Newline)
17734 } else if is_whitespace {
17735 Some(WordBreakToken::InlineWhitespace {
17736 token,
17737 grapheme_len,
17738 })
17739 } else {
17740 Some(WordBreakToken::Word {
17741 token,
17742 grapheme_len,
17743 })
17744 }
17745 } else {
17746 None
17747 }
17748 }
17749}
17750
17751#[test]
17752fn test_word_breaking_tokenizer() {
17753 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
17754 ("", &[]),
17755 (" ", &[whitespace(" ", 2)]),
17756 ("Ʒ", &[word("Ʒ", 1)]),
17757 ("Ǽ", &[word("Ǽ", 1)]),
17758 ("⋑", &[word("⋑", 1)]),
17759 ("⋑⋑", &[word("⋑⋑", 2)]),
17760 (
17761 "原理,进而",
17762 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
17763 ),
17764 (
17765 "hello world",
17766 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
17767 ),
17768 (
17769 "hello, world",
17770 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
17771 ),
17772 (
17773 " hello world",
17774 &[
17775 whitespace(" ", 2),
17776 word("hello", 5),
17777 whitespace(" ", 1),
17778 word("world", 5),
17779 ],
17780 ),
17781 (
17782 "这是什么 \n 钢笔",
17783 &[
17784 word("这", 1),
17785 word("是", 1),
17786 word("什", 1),
17787 word("么", 1),
17788 whitespace(" ", 1),
17789 newline(),
17790 whitespace(" ", 1),
17791 word("钢", 1),
17792 word("笔", 1),
17793 ],
17794 ),
17795 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
17796 ];
17797
17798 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
17799 WordBreakToken::Word {
17800 token,
17801 grapheme_len,
17802 }
17803 }
17804
17805 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
17806 WordBreakToken::InlineWhitespace {
17807 token,
17808 grapheme_len,
17809 }
17810 }
17811
17812 fn newline() -> WordBreakToken<'static> {
17813 WordBreakToken::Newline
17814 }
17815
17816 for (input, result) in tests {
17817 assert_eq!(
17818 WordBreakingTokenizer::new(input)
17819 .collect::<Vec<_>>()
17820 .as_slice(),
17821 *result,
17822 );
17823 }
17824}
17825
17826fn wrap_with_prefix(
17827 line_prefix: String,
17828 unwrapped_text: String,
17829 wrap_column: usize,
17830 tab_size: NonZeroU32,
17831 preserve_existing_whitespace: bool,
17832) -> String {
17833 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
17834 let mut wrapped_text = String::new();
17835 let mut current_line = line_prefix.clone();
17836
17837 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
17838 let mut current_line_len = line_prefix_len;
17839 let mut in_whitespace = false;
17840 for token in tokenizer {
17841 let have_preceding_whitespace = in_whitespace;
17842 match token {
17843 WordBreakToken::Word {
17844 token,
17845 grapheme_len,
17846 } => {
17847 in_whitespace = false;
17848 if current_line_len + grapheme_len > wrap_column
17849 && current_line_len != line_prefix_len
17850 {
17851 wrapped_text.push_str(current_line.trim_end());
17852 wrapped_text.push('\n');
17853 current_line.truncate(line_prefix.len());
17854 current_line_len = line_prefix_len;
17855 }
17856 current_line.push_str(token);
17857 current_line_len += grapheme_len;
17858 }
17859 WordBreakToken::InlineWhitespace {
17860 mut token,
17861 mut grapheme_len,
17862 } => {
17863 in_whitespace = true;
17864 if have_preceding_whitespace && !preserve_existing_whitespace {
17865 continue;
17866 }
17867 if !preserve_existing_whitespace {
17868 token = " ";
17869 grapheme_len = 1;
17870 }
17871 if current_line_len + grapheme_len > wrap_column {
17872 wrapped_text.push_str(current_line.trim_end());
17873 wrapped_text.push('\n');
17874 current_line.truncate(line_prefix.len());
17875 current_line_len = line_prefix_len;
17876 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
17877 current_line.push_str(token);
17878 current_line_len += grapheme_len;
17879 }
17880 }
17881 WordBreakToken::Newline => {
17882 in_whitespace = true;
17883 if preserve_existing_whitespace {
17884 wrapped_text.push_str(current_line.trim_end());
17885 wrapped_text.push('\n');
17886 current_line.truncate(line_prefix.len());
17887 current_line_len = line_prefix_len;
17888 } else if have_preceding_whitespace {
17889 continue;
17890 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
17891 {
17892 wrapped_text.push_str(current_line.trim_end());
17893 wrapped_text.push('\n');
17894 current_line.truncate(line_prefix.len());
17895 current_line_len = line_prefix_len;
17896 } else if current_line_len != line_prefix_len {
17897 current_line.push(' ');
17898 current_line_len += 1;
17899 }
17900 }
17901 }
17902 }
17903
17904 if !current_line.is_empty() {
17905 wrapped_text.push_str(¤t_line);
17906 }
17907 wrapped_text
17908}
17909
17910#[test]
17911fn test_wrap_with_prefix() {
17912 assert_eq!(
17913 wrap_with_prefix(
17914 "# ".to_string(),
17915 "abcdefg".to_string(),
17916 4,
17917 NonZeroU32::new(4).unwrap(),
17918 false,
17919 ),
17920 "# abcdefg"
17921 );
17922 assert_eq!(
17923 wrap_with_prefix(
17924 "".to_string(),
17925 "\thello world".to_string(),
17926 8,
17927 NonZeroU32::new(4).unwrap(),
17928 false,
17929 ),
17930 "hello\nworld"
17931 );
17932 assert_eq!(
17933 wrap_with_prefix(
17934 "// ".to_string(),
17935 "xx \nyy zz aa bb cc".to_string(),
17936 12,
17937 NonZeroU32::new(4).unwrap(),
17938 false,
17939 ),
17940 "// xx yy zz\n// aa bb cc"
17941 );
17942 assert_eq!(
17943 wrap_with_prefix(
17944 String::new(),
17945 "这是什么 \n 钢笔".to_string(),
17946 3,
17947 NonZeroU32::new(4).unwrap(),
17948 false,
17949 ),
17950 "这是什\n么 钢\n笔"
17951 );
17952}
17953
17954pub trait CollaborationHub {
17955 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
17956 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
17957 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
17958}
17959
17960impl CollaborationHub for Entity<Project> {
17961 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
17962 self.read(cx).collaborators()
17963 }
17964
17965 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
17966 self.read(cx).user_store().read(cx).participant_indices()
17967 }
17968
17969 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
17970 let this = self.read(cx);
17971 let user_ids = this.collaborators().values().map(|c| c.user_id);
17972 this.user_store().read_with(cx, |user_store, cx| {
17973 user_store.participant_names(user_ids, cx)
17974 })
17975 }
17976}
17977
17978pub trait SemanticsProvider {
17979 fn hover(
17980 &self,
17981 buffer: &Entity<Buffer>,
17982 position: text::Anchor,
17983 cx: &mut App,
17984 ) -> Option<Task<Vec<project::Hover>>>;
17985
17986 fn inlay_hints(
17987 &self,
17988 buffer_handle: Entity<Buffer>,
17989 range: Range<text::Anchor>,
17990 cx: &mut App,
17991 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
17992
17993 fn resolve_inlay_hint(
17994 &self,
17995 hint: InlayHint,
17996 buffer_handle: Entity<Buffer>,
17997 server_id: LanguageServerId,
17998 cx: &mut App,
17999 ) -> Option<Task<anyhow::Result<InlayHint>>>;
18000
18001 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
18002
18003 fn document_highlights(
18004 &self,
18005 buffer: &Entity<Buffer>,
18006 position: text::Anchor,
18007 cx: &mut App,
18008 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
18009
18010 fn definitions(
18011 &self,
18012 buffer: &Entity<Buffer>,
18013 position: text::Anchor,
18014 kind: GotoDefinitionKind,
18015 cx: &mut App,
18016 ) -> Option<Task<Result<Vec<LocationLink>>>>;
18017
18018 fn range_for_rename(
18019 &self,
18020 buffer: &Entity<Buffer>,
18021 position: text::Anchor,
18022 cx: &mut App,
18023 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
18024
18025 fn perform_rename(
18026 &self,
18027 buffer: &Entity<Buffer>,
18028 position: text::Anchor,
18029 new_name: String,
18030 cx: &mut App,
18031 ) -> Option<Task<Result<ProjectTransaction>>>;
18032}
18033
18034pub trait CompletionProvider {
18035 fn completions(
18036 &self,
18037 excerpt_id: ExcerptId,
18038 buffer: &Entity<Buffer>,
18039 buffer_position: text::Anchor,
18040 trigger: CompletionContext,
18041 window: &mut Window,
18042 cx: &mut Context<Editor>,
18043 ) -> Task<Result<Option<Vec<Completion>>>>;
18044
18045 fn resolve_completions(
18046 &self,
18047 buffer: Entity<Buffer>,
18048 completion_indices: Vec<usize>,
18049 completions: Rc<RefCell<Box<[Completion]>>>,
18050 cx: &mut Context<Editor>,
18051 ) -> Task<Result<bool>>;
18052
18053 fn apply_additional_edits_for_completion(
18054 &self,
18055 _buffer: Entity<Buffer>,
18056 _completions: Rc<RefCell<Box<[Completion]>>>,
18057 _completion_index: usize,
18058 _push_to_history: bool,
18059 _cx: &mut Context<Editor>,
18060 ) -> Task<Result<Option<language::Transaction>>> {
18061 Task::ready(Ok(None))
18062 }
18063
18064 fn is_completion_trigger(
18065 &self,
18066 buffer: &Entity<Buffer>,
18067 position: language::Anchor,
18068 text: &str,
18069 trigger_in_words: bool,
18070 cx: &mut Context<Editor>,
18071 ) -> bool;
18072
18073 fn sort_completions(&self) -> bool {
18074 true
18075 }
18076
18077 fn filter_completions(&self) -> bool {
18078 true
18079 }
18080}
18081
18082pub trait CodeActionProvider {
18083 fn id(&self) -> Arc<str>;
18084
18085 fn code_actions(
18086 &self,
18087 buffer: &Entity<Buffer>,
18088 range: Range<text::Anchor>,
18089 window: &mut Window,
18090 cx: &mut App,
18091 ) -> Task<Result<Vec<CodeAction>>>;
18092
18093 fn apply_code_action(
18094 &self,
18095 buffer_handle: Entity<Buffer>,
18096 action: CodeAction,
18097 excerpt_id: ExcerptId,
18098 push_to_history: bool,
18099 window: &mut Window,
18100 cx: &mut App,
18101 ) -> Task<Result<ProjectTransaction>>;
18102}
18103
18104impl CodeActionProvider for Entity<Project> {
18105 fn id(&self) -> Arc<str> {
18106 "project".into()
18107 }
18108
18109 fn code_actions(
18110 &self,
18111 buffer: &Entity<Buffer>,
18112 range: Range<text::Anchor>,
18113 _window: &mut Window,
18114 cx: &mut App,
18115 ) -> Task<Result<Vec<CodeAction>>> {
18116 self.update(cx, |project, cx| {
18117 let code_lens = project.code_lens(buffer, range.clone(), cx);
18118 let code_actions = project.code_actions(buffer, range, None, cx);
18119 cx.background_spawn(async move {
18120 let (code_lens, code_actions) = join(code_lens, code_actions).await;
18121 Ok(code_lens
18122 .context("code lens fetch")?
18123 .into_iter()
18124 .chain(code_actions.context("code action fetch")?)
18125 .collect())
18126 })
18127 })
18128 }
18129
18130 fn apply_code_action(
18131 &self,
18132 buffer_handle: Entity<Buffer>,
18133 action: CodeAction,
18134 _excerpt_id: ExcerptId,
18135 push_to_history: bool,
18136 _window: &mut Window,
18137 cx: &mut App,
18138 ) -> Task<Result<ProjectTransaction>> {
18139 self.update(cx, |project, cx| {
18140 project.apply_code_action(buffer_handle, action, push_to_history, cx)
18141 })
18142 }
18143}
18144
18145fn snippet_completions(
18146 project: &Project,
18147 buffer: &Entity<Buffer>,
18148 buffer_position: text::Anchor,
18149 cx: &mut App,
18150) -> Task<Result<Vec<Completion>>> {
18151 let language = buffer.read(cx).language_at(buffer_position);
18152 let language_name = language.as_ref().map(|language| language.lsp_id());
18153 let snippet_store = project.snippets().read(cx);
18154 let snippets = snippet_store.snippets_for(language_name, cx);
18155
18156 if snippets.is_empty() {
18157 return Task::ready(Ok(vec![]));
18158 }
18159 let snapshot = buffer.read(cx).text_snapshot();
18160 let chars: String = snapshot
18161 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
18162 .collect();
18163
18164 let scope = language.map(|language| language.default_scope());
18165 let executor = cx.background_executor().clone();
18166
18167 cx.background_spawn(async move {
18168 let classifier = CharClassifier::new(scope).for_completion(true);
18169 let mut last_word = chars
18170 .chars()
18171 .take_while(|c| classifier.is_word(*c))
18172 .collect::<String>();
18173 last_word = last_word.chars().rev().collect();
18174
18175 if last_word.is_empty() {
18176 return Ok(vec![]);
18177 }
18178
18179 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
18180 let to_lsp = |point: &text::Anchor| {
18181 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
18182 point_to_lsp(end)
18183 };
18184 let lsp_end = to_lsp(&buffer_position);
18185
18186 let candidates = snippets
18187 .iter()
18188 .enumerate()
18189 .flat_map(|(ix, snippet)| {
18190 snippet
18191 .prefix
18192 .iter()
18193 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
18194 })
18195 .collect::<Vec<StringMatchCandidate>>();
18196
18197 let mut matches = fuzzy::match_strings(
18198 &candidates,
18199 &last_word,
18200 last_word.chars().any(|c| c.is_uppercase()),
18201 100,
18202 &Default::default(),
18203 executor,
18204 )
18205 .await;
18206
18207 // Remove all candidates where the query's start does not match the start of any word in the candidate
18208 if let Some(query_start) = last_word.chars().next() {
18209 matches.retain(|string_match| {
18210 split_words(&string_match.string).any(|word| {
18211 // Check that the first codepoint of the word as lowercase matches the first
18212 // codepoint of the query as lowercase
18213 word.chars()
18214 .flat_map(|codepoint| codepoint.to_lowercase())
18215 .zip(query_start.to_lowercase())
18216 .all(|(word_cp, query_cp)| word_cp == query_cp)
18217 })
18218 });
18219 }
18220
18221 let matched_strings = matches
18222 .into_iter()
18223 .map(|m| m.string)
18224 .collect::<HashSet<_>>();
18225
18226 let result: Vec<Completion> = snippets
18227 .into_iter()
18228 .filter_map(|snippet| {
18229 let matching_prefix = snippet
18230 .prefix
18231 .iter()
18232 .find(|prefix| matched_strings.contains(*prefix))?;
18233 let start = as_offset - last_word.len();
18234 let start = snapshot.anchor_before(start);
18235 let range = start..buffer_position;
18236 let lsp_start = to_lsp(&start);
18237 let lsp_range = lsp::Range {
18238 start: lsp_start,
18239 end: lsp_end,
18240 };
18241 Some(Completion {
18242 old_range: range,
18243 new_text: snippet.body.clone(),
18244 source: CompletionSource::Lsp {
18245 server_id: LanguageServerId(usize::MAX),
18246 resolved: true,
18247 lsp_completion: Box::new(lsp::CompletionItem {
18248 label: snippet.prefix.first().unwrap().clone(),
18249 kind: Some(CompletionItemKind::SNIPPET),
18250 label_details: snippet.description.as_ref().map(|description| {
18251 lsp::CompletionItemLabelDetails {
18252 detail: Some(description.clone()),
18253 description: None,
18254 }
18255 }),
18256 insert_text_format: Some(InsertTextFormat::SNIPPET),
18257 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18258 lsp::InsertReplaceEdit {
18259 new_text: snippet.body.clone(),
18260 insert: lsp_range,
18261 replace: lsp_range,
18262 },
18263 )),
18264 filter_text: Some(snippet.body.clone()),
18265 sort_text: Some(char::MAX.to_string()),
18266 ..lsp::CompletionItem::default()
18267 }),
18268 lsp_defaults: None,
18269 },
18270 label: CodeLabel {
18271 text: matching_prefix.clone(),
18272 runs: Vec::new(),
18273 filter_range: 0..matching_prefix.len(),
18274 },
18275 icon_path: None,
18276 documentation: snippet
18277 .description
18278 .clone()
18279 .map(|description| CompletionDocumentation::SingleLine(description.into())),
18280 confirm: None,
18281 })
18282 })
18283 .collect();
18284
18285 Ok(result)
18286 })
18287}
18288
18289impl CompletionProvider for Entity<Project> {
18290 fn completions(
18291 &self,
18292 _excerpt_id: ExcerptId,
18293 buffer: &Entity<Buffer>,
18294 buffer_position: text::Anchor,
18295 options: CompletionContext,
18296 _window: &mut Window,
18297 cx: &mut Context<Editor>,
18298 ) -> Task<Result<Option<Vec<Completion>>>> {
18299 self.update(cx, |project, cx| {
18300 let snippets = snippet_completions(project, buffer, buffer_position, cx);
18301 let project_completions = project.completions(buffer, buffer_position, options, cx);
18302 cx.background_spawn(async move {
18303 let snippets_completions = snippets.await?;
18304 match project_completions.await? {
18305 Some(mut completions) => {
18306 completions.extend(snippets_completions);
18307 Ok(Some(completions))
18308 }
18309 None => {
18310 if snippets_completions.is_empty() {
18311 Ok(None)
18312 } else {
18313 Ok(Some(snippets_completions))
18314 }
18315 }
18316 }
18317 })
18318 })
18319 }
18320
18321 fn resolve_completions(
18322 &self,
18323 buffer: Entity<Buffer>,
18324 completion_indices: Vec<usize>,
18325 completions: Rc<RefCell<Box<[Completion]>>>,
18326 cx: &mut Context<Editor>,
18327 ) -> Task<Result<bool>> {
18328 self.update(cx, |project, cx| {
18329 project.lsp_store().update(cx, |lsp_store, cx| {
18330 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
18331 })
18332 })
18333 }
18334
18335 fn apply_additional_edits_for_completion(
18336 &self,
18337 buffer: Entity<Buffer>,
18338 completions: Rc<RefCell<Box<[Completion]>>>,
18339 completion_index: usize,
18340 push_to_history: bool,
18341 cx: &mut Context<Editor>,
18342 ) -> Task<Result<Option<language::Transaction>>> {
18343 self.update(cx, |project, cx| {
18344 project.lsp_store().update(cx, |lsp_store, cx| {
18345 lsp_store.apply_additional_edits_for_completion(
18346 buffer,
18347 completions,
18348 completion_index,
18349 push_to_history,
18350 cx,
18351 )
18352 })
18353 })
18354 }
18355
18356 fn is_completion_trigger(
18357 &self,
18358 buffer: &Entity<Buffer>,
18359 position: language::Anchor,
18360 text: &str,
18361 trigger_in_words: bool,
18362 cx: &mut Context<Editor>,
18363 ) -> bool {
18364 let mut chars = text.chars();
18365 let char = if let Some(char) = chars.next() {
18366 char
18367 } else {
18368 return false;
18369 };
18370 if chars.next().is_some() {
18371 return false;
18372 }
18373
18374 let buffer = buffer.read(cx);
18375 let snapshot = buffer.snapshot();
18376 if !snapshot.settings_at(position, cx).show_completions_on_input {
18377 return false;
18378 }
18379 let classifier = snapshot.char_classifier_at(position).for_completion(true);
18380 if trigger_in_words && classifier.is_word(char) {
18381 return true;
18382 }
18383
18384 buffer.completion_triggers().contains(text)
18385 }
18386}
18387
18388impl SemanticsProvider for Entity<Project> {
18389 fn hover(
18390 &self,
18391 buffer: &Entity<Buffer>,
18392 position: text::Anchor,
18393 cx: &mut App,
18394 ) -> Option<Task<Vec<project::Hover>>> {
18395 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
18396 }
18397
18398 fn document_highlights(
18399 &self,
18400 buffer: &Entity<Buffer>,
18401 position: text::Anchor,
18402 cx: &mut App,
18403 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
18404 Some(self.update(cx, |project, cx| {
18405 project.document_highlights(buffer, position, cx)
18406 }))
18407 }
18408
18409 fn definitions(
18410 &self,
18411 buffer: &Entity<Buffer>,
18412 position: text::Anchor,
18413 kind: GotoDefinitionKind,
18414 cx: &mut App,
18415 ) -> Option<Task<Result<Vec<LocationLink>>>> {
18416 Some(self.update(cx, |project, cx| match kind {
18417 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
18418 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
18419 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
18420 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
18421 }))
18422 }
18423
18424 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
18425 // TODO: make this work for remote projects
18426 self.update(cx, |this, cx| {
18427 buffer.update(cx, |buffer, cx| {
18428 this.any_language_server_supports_inlay_hints(buffer, cx)
18429 })
18430 })
18431 }
18432
18433 fn inlay_hints(
18434 &self,
18435 buffer_handle: Entity<Buffer>,
18436 range: Range<text::Anchor>,
18437 cx: &mut App,
18438 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
18439 Some(self.update(cx, |project, cx| {
18440 project.inlay_hints(buffer_handle, range, cx)
18441 }))
18442 }
18443
18444 fn resolve_inlay_hint(
18445 &self,
18446 hint: InlayHint,
18447 buffer_handle: Entity<Buffer>,
18448 server_id: LanguageServerId,
18449 cx: &mut App,
18450 ) -> Option<Task<anyhow::Result<InlayHint>>> {
18451 Some(self.update(cx, |project, cx| {
18452 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
18453 }))
18454 }
18455
18456 fn range_for_rename(
18457 &self,
18458 buffer: &Entity<Buffer>,
18459 position: text::Anchor,
18460 cx: &mut App,
18461 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
18462 Some(self.update(cx, |project, cx| {
18463 let buffer = buffer.clone();
18464 let task = project.prepare_rename(buffer.clone(), position, cx);
18465 cx.spawn(async move |_, cx| {
18466 Ok(match task.await? {
18467 PrepareRenameResponse::Success(range) => Some(range),
18468 PrepareRenameResponse::InvalidPosition => None,
18469 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
18470 // Fallback on using TreeSitter info to determine identifier range
18471 buffer.update(cx, |buffer, _| {
18472 let snapshot = buffer.snapshot();
18473 let (range, kind) = snapshot.surrounding_word(position);
18474 if kind != Some(CharKind::Word) {
18475 return None;
18476 }
18477 Some(
18478 snapshot.anchor_before(range.start)
18479 ..snapshot.anchor_after(range.end),
18480 )
18481 })?
18482 }
18483 })
18484 })
18485 }))
18486 }
18487
18488 fn perform_rename(
18489 &self,
18490 buffer: &Entity<Buffer>,
18491 position: text::Anchor,
18492 new_name: String,
18493 cx: &mut App,
18494 ) -> Option<Task<Result<ProjectTransaction>>> {
18495 Some(self.update(cx, |project, cx| {
18496 project.perform_rename(buffer.clone(), position, new_name, cx)
18497 }))
18498 }
18499}
18500
18501fn inlay_hint_settings(
18502 location: Anchor,
18503 snapshot: &MultiBufferSnapshot,
18504 cx: &mut Context<Editor>,
18505) -> InlayHintSettings {
18506 let file = snapshot.file_at(location);
18507 let language = snapshot.language_at(location).map(|l| l.name());
18508 language_settings(language, file, cx).inlay_hints
18509}
18510
18511fn consume_contiguous_rows(
18512 contiguous_row_selections: &mut Vec<Selection<Point>>,
18513 selection: &Selection<Point>,
18514 display_map: &DisplaySnapshot,
18515 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
18516) -> (MultiBufferRow, MultiBufferRow) {
18517 contiguous_row_selections.push(selection.clone());
18518 let start_row = MultiBufferRow(selection.start.row);
18519 let mut end_row = ending_row(selection, display_map);
18520
18521 while let Some(next_selection) = selections.peek() {
18522 if next_selection.start.row <= end_row.0 {
18523 end_row = ending_row(next_selection, display_map);
18524 contiguous_row_selections.push(selections.next().unwrap().clone());
18525 } else {
18526 break;
18527 }
18528 }
18529 (start_row, end_row)
18530}
18531
18532fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
18533 if next_selection.end.column > 0 || next_selection.is_empty() {
18534 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
18535 } else {
18536 MultiBufferRow(next_selection.end.row)
18537 }
18538}
18539
18540impl EditorSnapshot {
18541 pub fn remote_selections_in_range<'a>(
18542 &'a self,
18543 range: &'a Range<Anchor>,
18544 collaboration_hub: &dyn CollaborationHub,
18545 cx: &'a App,
18546 ) -> impl 'a + Iterator<Item = RemoteSelection> {
18547 let participant_names = collaboration_hub.user_names(cx);
18548 let participant_indices = collaboration_hub.user_participant_indices(cx);
18549 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
18550 let collaborators_by_replica_id = collaborators_by_peer_id
18551 .iter()
18552 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
18553 .collect::<HashMap<_, _>>();
18554 self.buffer_snapshot
18555 .selections_in_range(range, false)
18556 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
18557 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
18558 let participant_index = participant_indices.get(&collaborator.user_id).copied();
18559 let user_name = participant_names.get(&collaborator.user_id).cloned();
18560 Some(RemoteSelection {
18561 replica_id,
18562 selection,
18563 cursor_shape,
18564 line_mode,
18565 participant_index,
18566 peer_id: collaborator.peer_id,
18567 user_name,
18568 })
18569 })
18570 }
18571
18572 pub fn hunks_for_ranges(
18573 &self,
18574 ranges: impl IntoIterator<Item = Range<Point>>,
18575 ) -> Vec<MultiBufferDiffHunk> {
18576 let mut hunks = Vec::new();
18577 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
18578 HashMap::default();
18579 for query_range in ranges {
18580 let query_rows =
18581 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
18582 for hunk in self.buffer_snapshot.diff_hunks_in_range(
18583 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
18584 ) {
18585 // Include deleted hunks that are adjacent to the query range, because
18586 // otherwise they would be missed.
18587 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
18588 if hunk.status().is_deleted() {
18589 intersects_range |= hunk.row_range.start == query_rows.end;
18590 intersects_range |= hunk.row_range.end == query_rows.start;
18591 }
18592 if intersects_range {
18593 if !processed_buffer_rows
18594 .entry(hunk.buffer_id)
18595 .or_default()
18596 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
18597 {
18598 continue;
18599 }
18600 hunks.push(hunk);
18601 }
18602 }
18603 }
18604
18605 hunks
18606 }
18607
18608 fn display_diff_hunks_for_rows<'a>(
18609 &'a self,
18610 display_rows: Range<DisplayRow>,
18611 folded_buffers: &'a HashSet<BufferId>,
18612 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
18613 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
18614 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
18615
18616 self.buffer_snapshot
18617 .diff_hunks_in_range(buffer_start..buffer_end)
18618 .filter_map(|hunk| {
18619 if folded_buffers.contains(&hunk.buffer_id) {
18620 return None;
18621 }
18622
18623 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
18624 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
18625
18626 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
18627 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
18628
18629 let display_hunk = if hunk_display_start.column() != 0 {
18630 DisplayDiffHunk::Folded {
18631 display_row: hunk_display_start.row(),
18632 }
18633 } else {
18634 let mut end_row = hunk_display_end.row();
18635 if hunk_display_end.column() > 0 {
18636 end_row.0 += 1;
18637 }
18638 let is_created_file = hunk.is_created_file();
18639 DisplayDiffHunk::Unfolded {
18640 status: hunk.status(),
18641 diff_base_byte_range: hunk.diff_base_byte_range,
18642 display_row_range: hunk_display_start.row()..end_row,
18643 multi_buffer_range: Anchor::range_in_buffer(
18644 hunk.excerpt_id,
18645 hunk.buffer_id,
18646 hunk.buffer_range,
18647 ),
18648 is_created_file,
18649 }
18650 };
18651
18652 Some(display_hunk)
18653 })
18654 }
18655
18656 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
18657 self.display_snapshot.buffer_snapshot.language_at(position)
18658 }
18659
18660 pub fn is_focused(&self) -> bool {
18661 self.is_focused
18662 }
18663
18664 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
18665 self.placeholder_text.as_ref()
18666 }
18667
18668 pub fn scroll_position(&self) -> gpui::Point<f32> {
18669 self.scroll_anchor.scroll_position(&self.display_snapshot)
18670 }
18671
18672 fn gutter_dimensions(
18673 &self,
18674 font_id: FontId,
18675 font_size: Pixels,
18676 max_line_number_width: Pixels,
18677 cx: &App,
18678 ) -> Option<GutterDimensions> {
18679 if !self.show_gutter {
18680 return None;
18681 }
18682
18683 let descent = cx.text_system().descent(font_id, font_size);
18684 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
18685 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
18686
18687 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
18688 matches!(
18689 ProjectSettings::get_global(cx).git.git_gutter,
18690 Some(GitGutterSetting::TrackedFiles)
18691 )
18692 });
18693 let gutter_settings = EditorSettings::get_global(cx).gutter;
18694 let show_line_numbers = self
18695 .show_line_numbers
18696 .unwrap_or(gutter_settings.line_numbers);
18697 let line_gutter_width = if show_line_numbers {
18698 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
18699 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
18700 max_line_number_width.max(min_width_for_number_on_gutter)
18701 } else {
18702 0.0.into()
18703 };
18704
18705 let show_code_actions = self
18706 .show_code_actions
18707 .unwrap_or(gutter_settings.code_actions);
18708
18709 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
18710 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
18711
18712 let git_blame_entries_width =
18713 self.git_blame_gutter_max_author_length
18714 .map(|max_author_length| {
18715 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
18716
18717 /// The number of characters to dedicate to gaps and margins.
18718 const SPACING_WIDTH: usize = 4;
18719
18720 let max_char_count = max_author_length
18721 .min(GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED)
18722 + ::git::SHORT_SHA_LENGTH
18723 + MAX_RELATIVE_TIMESTAMP.len()
18724 + SPACING_WIDTH;
18725
18726 em_advance * max_char_count
18727 });
18728
18729 let is_singleton = self.buffer_snapshot.is_singleton();
18730
18731 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
18732 left_padding += if !is_singleton {
18733 em_width * 4.0
18734 } else if show_code_actions || show_runnables || show_breakpoints {
18735 em_width * 3.0
18736 } else if show_git_gutter && show_line_numbers {
18737 em_width * 2.0
18738 } else if show_git_gutter || show_line_numbers {
18739 em_width
18740 } else {
18741 px(0.)
18742 };
18743
18744 let shows_folds = is_singleton && gutter_settings.folds;
18745
18746 let right_padding = if shows_folds && show_line_numbers {
18747 em_width * 4.0
18748 } else if shows_folds || (!is_singleton && show_line_numbers) {
18749 em_width * 3.0
18750 } else if show_line_numbers {
18751 em_width
18752 } else {
18753 px(0.)
18754 };
18755
18756 Some(GutterDimensions {
18757 left_padding,
18758 right_padding,
18759 width: line_gutter_width + left_padding + right_padding,
18760 margin: -descent,
18761 git_blame_entries_width,
18762 })
18763 }
18764
18765 pub fn render_crease_toggle(
18766 &self,
18767 buffer_row: MultiBufferRow,
18768 row_contains_cursor: bool,
18769 editor: Entity<Editor>,
18770 window: &mut Window,
18771 cx: &mut App,
18772 ) -> Option<AnyElement> {
18773 let folded = self.is_line_folded(buffer_row);
18774 let mut is_foldable = false;
18775
18776 if let Some(crease) = self
18777 .crease_snapshot
18778 .query_row(buffer_row, &self.buffer_snapshot)
18779 {
18780 is_foldable = true;
18781 match crease {
18782 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
18783 if let Some(render_toggle) = render_toggle {
18784 let toggle_callback =
18785 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
18786 if folded {
18787 editor.update(cx, |editor, cx| {
18788 editor.fold_at(&crate::FoldAt { buffer_row }, window, cx)
18789 });
18790 } else {
18791 editor.update(cx, |editor, cx| {
18792 editor.unfold_at(
18793 &crate::UnfoldAt { buffer_row },
18794 window,
18795 cx,
18796 )
18797 });
18798 }
18799 });
18800 return Some((render_toggle)(
18801 buffer_row,
18802 folded,
18803 toggle_callback,
18804 window,
18805 cx,
18806 ));
18807 }
18808 }
18809 }
18810 }
18811
18812 is_foldable |= self.starts_indent(buffer_row);
18813
18814 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
18815 Some(
18816 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
18817 .toggle_state(folded)
18818 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
18819 if folded {
18820 this.unfold_at(&UnfoldAt { buffer_row }, window, cx);
18821 } else {
18822 this.fold_at(&FoldAt { buffer_row }, window, cx);
18823 }
18824 }))
18825 .into_any_element(),
18826 )
18827 } else {
18828 None
18829 }
18830 }
18831
18832 pub fn render_crease_trailer(
18833 &self,
18834 buffer_row: MultiBufferRow,
18835 window: &mut Window,
18836 cx: &mut App,
18837 ) -> Option<AnyElement> {
18838 let folded = self.is_line_folded(buffer_row);
18839 if let Crease::Inline { render_trailer, .. } = self
18840 .crease_snapshot
18841 .query_row(buffer_row, &self.buffer_snapshot)?
18842 {
18843 let render_trailer = render_trailer.as_ref()?;
18844 Some(render_trailer(buffer_row, folded, window, cx))
18845 } else {
18846 None
18847 }
18848 }
18849}
18850
18851impl Deref for EditorSnapshot {
18852 type Target = DisplaySnapshot;
18853
18854 fn deref(&self) -> &Self::Target {
18855 &self.display_snapshot
18856 }
18857}
18858
18859#[derive(Clone, Debug, PartialEq, Eq)]
18860pub enum EditorEvent {
18861 InputIgnored {
18862 text: Arc<str>,
18863 },
18864 InputHandled {
18865 utf16_range_to_replace: Option<Range<isize>>,
18866 text: Arc<str>,
18867 },
18868 ExcerptsAdded {
18869 buffer: Entity<Buffer>,
18870 predecessor: ExcerptId,
18871 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
18872 },
18873 ExcerptsRemoved {
18874 ids: Vec<ExcerptId>,
18875 },
18876 BufferFoldToggled {
18877 ids: Vec<ExcerptId>,
18878 folded: bool,
18879 },
18880 ExcerptsEdited {
18881 ids: Vec<ExcerptId>,
18882 },
18883 ExcerptsExpanded {
18884 ids: Vec<ExcerptId>,
18885 },
18886 BufferEdited,
18887 Edited {
18888 transaction_id: clock::Lamport,
18889 },
18890 Reparsed(BufferId),
18891 Focused,
18892 FocusedIn,
18893 Blurred,
18894 DirtyChanged,
18895 Saved,
18896 TitleChanged,
18897 DiffBaseChanged,
18898 SelectionsChanged {
18899 local: bool,
18900 },
18901 ScrollPositionChanged {
18902 local: bool,
18903 autoscroll: bool,
18904 },
18905 Closed,
18906 TransactionUndone {
18907 transaction_id: clock::Lamport,
18908 },
18909 TransactionBegun {
18910 transaction_id: clock::Lamport,
18911 },
18912 Reloaded,
18913 CursorShapeChanged,
18914 PushedToNavHistory {
18915 anchor: Anchor,
18916 is_deactivate: bool,
18917 },
18918}
18919
18920impl EventEmitter<EditorEvent> for Editor {}
18921
18922impl Focusable for Editor {
18923 fn focus_handle(&self, _cx: &App) -> FocusHandle {
18924 self.focus_handle.clone()
18925 }
18926}
18927
18928impl Render for Editor {
18929 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
18930 let settings = ThemeSettings::get_global(cx);
18931
18932 let mut text_style = match self.mode {
18933 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
18934 color: cx.theme().colors().editor_foreground,
18935 font_family: settings.ui_font.family.clone(),
18936 font_features: settings.ui_font.features.clone(),
18937 font_fallbacks: settings.ui_font.fallbacks.clone(),
18938 font_size: rems(0.875).into(),
18939 font_weight: settings.ui_font.weight,
18940 line_height: relative(settings.buffer_line_height.value()),
18941 ..Default::default()
18942 },
18943 EditorMode::Full => TextStyle {
18944 color: cx.theme().colors().editor_foreground,
18945 font_family: settings.buffer_font.family.clone(),
18946 font_features: settings.buffer_font.features.clone(),
18947 font_fallbacks: settings.buffer_font.fallbacks.clone(),
18948 font_size: settings.buffer_font_size(cx).into(),
18949 font_weight: settings.buffer_font.weight,
18950 line_height: relative(settings.buffer_line_height.value()),
18951 ..Default::default()
18952 },
18953 };
18954 if let Some(text_style_refinement) = &self.text_style_refinement {
18955 text_style.refine(text_style_refinement)
18956 }
18957
18958 let background = match self.mode {
18959 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
18960 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
18961 EditorMode::Full => cx.theme().colors().editor_background,
18962 };
18963
18964 EditorElement::new(
18965 &cx.entity(),
18966 EditorStyle {
18967 background,
18968 local_player: cx.theme().players().local(),
18969 text: text_style,
18970 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
18971 syntax: cx.theme().syntax().clone(),
18972 status: cx.theme().status().clone(),
18973 inlay_hints_style: make_inlay_hints_style(cx),
18974 inline_completion_styles: make_suggestion_styles(cx),
18975 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
18976 },
18977 )
18978 }
18979}
18980
18981impl EntityInputHandler for Editor {
18982 fn text_for_range(
18983 &mut self,
18984 range_utf16: Range<usize>,
18985 adjusted_range: &mut Option<Range<usize>>,
18986 _: &mut Window,
18987 cx: &mut Context<Self>,
18988 ) -> Option<String> {
18989 let snapshot = self.buffer.read(cx).read(cx);
18990 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
18991 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
18992 if (start.0..end.0) != range_utf16 {
18993 adjusted_range.replace(start.0..end.0);
18994 }
18995 Some(snapshot.text_for_range(start..end).collect())
18996 }
18997
18998 fn selected_text_range(
18999 &mut self,
19000 ignore_disabled_input: bool,
19001 _: &mut Window,
19002 cx: &mut Context<Self>,
19003 ) -> Option<UTF16Selection> {
19004 // Prevent the IME menu from appearing when holding down an alphabetic key
19005 // while input is disabled.
19006 if !ignore_disabled_input && !self.input_enabled {
19007 return None;
19008 }
19009
19010 let selection = self.selections.newest::<OffsetUtf16>(cx);
19011 let range = selection.range();
19012
19013 Some(UTF16Selection {
19014 range: range.start.0..range.end.0,
19015 reversed: selection.reversed,
19016 })
19017 }
19018
19019 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
19020 let snapshot = self.buffer.read(cx).read(cx);
19021 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
19022 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
19023 }
19024
19025 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19026 self.clear_highlights::<InputComposition>(cx);
19027 self.ime_transaction.take();
19028 }
19029
19030 fn replace_text_in_range(
19031 &mut self,
19032 range_utf16: Option<Range<usize>>,
19033 text: &str,
19034 window: &mut Window,
19035 cx: &mut Context<Self>,
19036 ) {
19037 if !self.input_enabled {
19038 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19039 return;
19040 }
19041
19042 self.transact(window, cx, |this, window, cx| {
19043 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
19044 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
19045 Some(this.selection_replacement_ranges(range_utf16, cx))
19046 } else {
19047 this.marked_text_ranges(cx)
19048 };
19049
19050 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
19051 let newest_selection_id = this.selections.newest_anchor().id;
19052 this.selections
19053 .all::<OffsetUtf16>(cx)
19054 .iter()
19055 .zip(ranges_to_replace.iter())
19056 .find_map(|(selection, range)| {
19057 if selection.id == newest_selection_id {
19058 Some(
19059 (range.start.0 as isize - selection.head().0 as isize)
19060 ..(range.end.0 as isize - selection.head().0 as isize),
19061 )
19062 } else {
19063 None
19064 }
19065 })
19066 });
19067
19068 cx.emit(EditorEvent::InputHandled {
19069 utf16_range_to_replace: range_to_replace,
19070 text: text.into(),
19071 });
19072
19073 if let Some(new_selected_ranges) = new_selected_ranges {
19074 this.change_selections(None, window, cx, |selections| {
19075 selections.select_ranges(new_selected_ranges)
19076 });
19077 this.backspace(&Default::default(), window, cx);
19078 }
19079
19080 this.handle_input(text, window, cx);
19081 });
19082
19083 if let Some(transaction) = self.ime_transaction {
19084 self.buffer.update(cx, |buffer, cx| {
19085 buffer.group_until_transaction(transaction, cx);
19086 });
19087 }
19088
19089 self.unmark_text(window, cx);
19090 }
19091
19092 fn replace_and_mark_text_in_range(
19093 &mut self,
19094 range_utf16: Option<Range<usize>>,
19095 text: &str,
19096 new_selected_range_utf16: Option<Range<usize>>,
19097 window: &mut Window,
19098 cx: &mut Context<Self>,
19099 ) {
19100 if !self.input_enabled {
19101 return;
19102 }
19103
19104 let transaction = self.transact(window, cx, |this, window, cx| {
19105 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
19106 let snapshot = this.buffer.read(cx).read(cx);
19107 if let Some(relative_range_utf16) = range_utf16.as_ref() {
19108 for marked_range in &mut marked_ranges {
19109 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
19110 marked_range.start.0 += relative_range_utf16.start;
19111 marked_range.start =
19112 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
19113 marked_range.end =
19114 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
19115 }
19116 }
19117 Some(marked_ranges)
19118 } else if let Some(range_utf16) = range_utf16 {
19119 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
19120 Some(this.selection_replacement_ranges(range_utf16, cx))
19121 } else {
19122 None
19123 };
19124
19125 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
19126 let newest_selection_id = this.selections.newest_anchor().id;
19127 this.selections
19128 .all::<OffsetUtf16>(cx)
19129 .iter()
19130 .zip(ranges_to_replace.iter())
19131 .find_map(|(selection, range)| {
19132 if selection.id == newest_selection_id {
19133 Some(
19134 (range.start.0 as isize - selection.head().0 as isize)
19135 ..(range.end.0 as isize - selection.head().0 as isize),
19136 )
19137 } else {
19138 None
19139 }
19140 })
19141 });
19142
19143 cx.emit(EditorEvent::InputHandled {
19144 utf16_range_to_replace: range_to_replace,
19145 text: text.into(),
19146 });
19147
19148 if let Some(ranges) = ranges_to_replace {
19149 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
19150 }
19151
19152 let marked_ranges = {
19153 let snapshot = this.buffer.read(cx).read(cx);
19154 this.selections
19155 .disjoint_anchors()
19156 .iter()
19157 .map(|selection| {
19158 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
19159 })
19160 .collect::<Vec<_>>()
19161 };
19162
19163 if text.is_empty() {
19164 this.unmark_text(window, cx);
19165 } else {
19166 this.highlight_text::<InputComposition>(
19167 marked_ranges.clone(),
19168 HighlightStyle {
19169 underline: Some(UnderlineStyle {
19170 thickness: px(1.),
19171 color: None,
19172 wavy: false,
19173 }),
19174 ..Default::default()
19175 },
19176 cx,
19177 );
19178 }
19179
19180 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
19181 let use_autoclose = this.use_autoclose;
19182 let use_auto_surround = this.use_auto_surround;
19183 this.set_use_autoclose(false);
19184 this.set_use_auto_surround(false);
19185 this.handle_input(text, window, cx);
19186 this.set_use_autoclose(use_autoclose);
19187 this.set_use_auto_surround(use_auto_surround);
19188
19189 if let Some(new_selected_range) = new_selected_range_utf16 {
19190 let snapshot = this.buffer.read(cx).read(cx);
19191 let new_selected_ranges = marked_ranges
19192 .into_iter()
19193 .map(|marked_range| {
19194 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
19195 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
19196 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
19197 snapshot.clip_offset_utf16(new_start, Bias::Left)
19198 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
19199 })
19200 .collect::<Vec<_>>();
19201
19202 drop(snapshot);
19203 this.change_selections(None, window, cx, |selections| {
19204 selections.select_ranges(new_selected_ranges)
19205 });
19206 }
19207 });
19208
19209 self.ime_transaction = self.ime_transaction.or(transaction);
19210 if let Some(transaction) = self.ime_transaction {
19211 self.buffer.update(cx, |buffer, cx| {
19212 buffer.group_until_transaction(transaction, cx);
19213 });
19214 }
19215
19216 if self.text_highlights::<InputComposition>(cx).is_none() {
19217 self.ime_transaction.take();
19218 }
19219 }
19220
19221 fn bounds_for_range(
19222 &mut self,
19223 range_utf16: Range<usize>,
19224 element_bounds: gpui::Bounds<Pixels>,
19225 window: &mut Window,
19226 cx: &mut Context<Self>,
19227 ) -> Option<gpui::Bounds<Pixels>> {
19228 let text_layout_details = self.text_layout_details(window);
19229 let gpui::Size {
19230 width: em_width,
19231 height: line_height,
19232 } = self.character_size(window);
19233
19234 let snapshot = self.snapshot(window, cx);
19235 let scroll_position = snapshot.scroll_position();
19236 let scroll_left = scroll_position.x * em_width;
19237
19238 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
19239 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
19240 + self.gutter_dimensions.width
19241 + self.gutter_dimensions.margin;
19242 let y = line_height * (start.row().as_f32() - scroll_position.y);
19243
19244 Some(Bounds {
19245 origin: element_bounds.origin + point(x, y),
19246 size: size(em_width, line_height),
19247 })
19248 }
19249
19250 fn character_index_for_point(
19251 &mut self,
19252 point: gpui::Point<Pixels>,
19253 _window: &mut Window,
19254 _cx: &mut Context<Self>,
19255 ) -> Option<usize> {
19256 let position_map = self.last_position_map.as_ref()?;
19257 if !position_map.text_hitbox.contains(&point) {
19258 return None;
19259 }
19260 let display_point = position_map.point_for_position(point).previous_valid;
19261 let anchor = position_map
19262 .snapshot
19263 .display_point_to_anchor(display_point, Bias::Left);
19264 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
19265 Some(utf16_offset.0)
19266 }
19267}
19268
19269trait SelectionExt {
19270 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
19271 fn spanned_rows(
19272 &self,
19273 include_end_if_at_line_start: bool,
19274 map: &DisplaySnapshot,
19275 ) -> Range<MultiBufferRow>;
19276}
19277
19278impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
19279 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
19280 let start = self
19281 .start
19282 .to_point(&map.buffer_snapshot)
19283 .to_display_point(map);
19284 let end = self
19285 .end
19286 .to_point(&map.buffer_snapshot)
19287 .to_display_point(map);
19288 if self.reversed {
19289 end..start
19290 } else {
19291 start..end
19292 }
19293 }
19294
19295 fn spanned_rows(
19296 &self,
19297 include_end_if_at_line_start: bool,
19298 map: &DisplaySnapshot,
19299 ) -> Range<MultiBufferRow> {
19300 let start = self.start.to_point(&map.buffer_snapshot);
19301 let mut end = self.end.to_point(&map.buffer_snapshot);
19302 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
19303 end.row -= 1;
19304 }
19305
19306 let buffer_start = map.prev_line_boundary(start).0;
19307 let buffer_end = map.next_line_boundary(end).0;
19308 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
19309 }
19310}
19311
19312impl<T: InvalidationRegion> InvalidationStack<T> {
19313 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
19314 where
19315 S: Clone + ToOffset,
19316 {
19317 while let Some(region) = self.last() {
19318 let all_selections_inside_invalidation_ranges =
19319 if selections.len() == region.ranges().len() {
19320 selections
19321 .iter()
19322 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
19323 .all(|(selection, invalidation_range)| {
19324 let head = selection.head().to_offset(buffer);
19325 invalidation_range.start <= head && invalidation_range.end >= head
19326 })
19327 } else {
19328 false
19329 };
19330
19331 if all_selections_inside_invalidation_ranges {
19332 break;
19333 } else {
19334 self.pop();
19335 }
19336 }
19337 }
19338}
19339
19340impl<T> Default for InvalidationStack<T> {
19341 fn default() -> Self {
19342 Self(Default::default())
19343 }
19344}
19345
19346impl<T> Deref for InvalidationStack<T> {
19347 type Target = Vec<T>;
19348
19349 fn deref(&self) -> &Self::Target {
19350 &self.0
19351 }
19352}
19353
19354impl<T> DerefMut for InvalidationStack<T> {
19355 fn deref_mut(&mut self) -> &mut Self::Target {
19356 &mut self.0
19357 }
19358}
19359
19360impl InvalidationRegion for SnippetState {
19361 fn ranges(&self) -> &[Range<Anchor>] {
19362 &self.ranges[self.active_index]
19363 }
19364}
19365
19366pub fn diagnostic_block_renderer(
19367 diagnostic: Diagnostic,
19368 max_message_rows: Option<u8>,
19369 allow_closing: bool,
19370) -> RenderBlock {
19371 let (text_without_backticks, code_ranges) =
19372 highlight_diagnostic_message(&diagnostic, max_message_rows);
19373
19374 Arc::new(move |cx: &mut BlockContext| {
19375 let group_id: SharedString = cx.block_id.to_string().into();
19376
19377 let mut text_style = cx.window.text_style().clone();
19378 text_style.color = diagnostic_style(diagnostic.severity, cx.theme().status());
19379 let theme_settings = ThemeSettings::get_global(cx);
19380 text_style.font_family = theme_settings.buffer_font.family.clone();
19381 text_style.font_style = theme_settings.buffer_font.style;
19382 text_style.font_features = theme_settings.buffer_font.features.clone();
19383 text_style.font_weight = theme_settings.buffer_font.weight;
19384
19385 let multi_line_diagnostic = diagnostic.message.contains('\n');
19386
19387 let buttons = |diagnostic: &Diagnostic| {
19388 if multi_line_diagnostic {
19389 v_flex()
19390 } else {
19391 h_flex()
19392 }
19393 .when(allow_closing, |div| {
19394 div.children(diagnostic.is_primary.then(|| {
19395 IconButton::new("close-block", IconName::XCircle)
19396 .icon_color(Color::Muted)
19397 .size(ButtonSize::Compact)
19398 .style(ButtonStyle::Transparent)
19399 .visible_on_hover(group_id.clone())
19400 .on_click(move |_click, window, cx| {
19401 window.dispatch_action(Box::new(Cancel), cx)
19402 })
19403 .tooltip(|window, cx| {
19404 Tooltip::for_action("Close Diagnostics", &Cancel, window, cx)
19405 })
19406 }))
19407 })
19408 .child(
19409 IconButton::new("copy-block", IconName::Copy)
19410 .icon_color(Color::Muted)
19411 .size(ButtonSize::Compact)
19412 .style(ButtonStyle::Transparent)
19413 .visible_on_hover(group_id.clone())
19414 .on_click({
19415 let message = diagnostic.message.clone();
19416 move |_click, _, cx| {
19417 cx.write_to_clipboard(ClipboardItem::new_string(message.clone()))
19418 }
19419 })
19420 .tooltip(Tooltip::text("Copy diagnostic message")),
19421 )
19422 };
19423
19424 let icon_size = buttons(&diagnostic).into_any_element().layout_as_root(
19425 AvailableSpace::min_size(),
19426 cx.window,
19427 cx.app,
19428 );
19429
19430 h_flex()
19431 .id(cx.block_id)
19432 .group(group_id.clone())
19433 .relative()
19434 .size_full()
19435 .block_mouse_down()
19436 .pl(cx.gutter_dimensions.width)
19437 .w(cx.max_width - cx.gutter_dimensions.full_width())
19438 .child(
19439 div()
19440 .flex()
19441 .w(cx.anchor_x - cx.gutter_dimensions.width - icon_size.width)
19442 .flex_shrink(),
19443 )
19444 .child(buttons(&diagnostic))
19445 .child(div().flex().flex_shrink_0().child(
19446 StyledText::new(text_without_backticks.clone()).with_default_highlights(
19447 &text_style,
19448 code_ranges.iter().map(|range| {
19449 (
19450 range.clone(),
19451 HighlightStyle {
19452 font_weight: Some(FontWeight::BOLD),
19453 ..Default::default()
19454 },
19455 )
19456 }),
19457 ),
19458 ))
19459 .into_any_element()
19460 })
19461}
19462
19463fn inline_completion_edit_text(
19464 current_snapshot: &BufferSnapshot,
19465 edits: &[(Range<Anchor>, String)],
19466 edit_preview: &EditPreview,
19467 include_deletions: bool,
19468 cx: &App,
19469) -> HighlightedText {
19470 let edits = edits
19471 .iter()
19472 .map(|(anchor, text)| {
19473 (
19474 anchor.start.text_anchor..anchor.end.text_anchor,
19475 text.clone(),
19476 )
19477 })
19478 .collect::<Vec<_>>();
19479
19480 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
19481}
19482
19483pub fn highlight_diagnostic_message(
19484 diagnostic: &Diagnostic,
19485 mut max_message_rows: Option<u8>,
19486) -> (SharedString, Vec<Range<usize>>) {
19487 let mut text_without_backticks = String::new();
19488 let mut code_ranges = Vec::new();
19489
19490 if let Some(source) = &diagnostic.source {
19491 text_without_backticks.push_str(source);
19492 code_ranges.push(0..source.len());
19493 text_without_backticks.push_str(": ");
19494 }
19495
19496 let mut prev_offset = 0;
19497 let mut in_code_block = false;
19498 let has_row_limit = max_message_rows.is_some();
19499 let mut newline_indices = diagnostic
19500 .message
19501 .match_indices('\n')
19502 .filter(|_| has_row_limit)
19503 .map(|(ix, _)| ix)
19504 .fuse()
19505 .peekable();
19506
19507 for (quote_ix, _) in diagnostic
19508 .message
19509 .match_indices('`')
19510 .chain([(diagnostic.message.len(), "")])
19511 {
19512 let mut first_newline_ix = None;
19513 let mut last_newline_ix = None;
19514 while let Some(newline_ix) = newline_indices.peek() {
19515 if *newline_ix < quote_ix {
19516 if first_newline_ix.is_none() {
19517 first_newline_ix = Some(*newline_ix);
19518 }
19519 last_newline_ix = Some(*newline_ix);
19520
19521 if let Some(rows_left) = &mut max_message_rows {
19522 if *rows_left == 0 {
19523 break;
19524 } else {
19525 *rows_left -= 1;
19526 }
19527 }
19528 let _ = newline_indices.next();
19529 } else {
19530 break;
19531 }
19532 }
19533 let prev_len = text_without_backticks.len();
19534 let new_text = &diagnostic.message[prev_offset..first_newline_ix.unwrap_or(quote_ix)];
19535 text_without_backticks.push_str(new_text);
19536 if in_code_block {
19537 code_ranges.push(prev_len..text_without_backticks.len());
19538 }
19539 prev_offset = last_newline_ix.unwrap_or(quote_ix) + 1;
19540 in_code_block = !in_code_block;
19541 if first_newline_ix.map_or(false, |newline_ix| newline_ix < quote_ix) {
19542 text_without_backticks.push_str("...");
19543 break;
19544 }
19545 }
19546
19547 (text_without_backticks.into(), code_ranges)
19548}
19549
19550fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
19551 match severity {
19552 DiagnosticSeverity::ERROR => colors.error,
19553 DiagnosticSeverity::WARNING => colors.warning,
19554 DiagnosticSeverity::INFORMATION => colors.info,
19555 DiagnosticSeverity::HINT => colors.info,
19556 _ => colors.ignored,
19557 }
19558}
19559
19560pub fn styled_runs_for_code_label<'a>(
19561 label: &'a CodeLabel,
19562 syntax_theme: &'a theme::SyntaxTheme,
19563) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
19564 let fade_out = HighlightStyle {
19565 fade_out: Some(0.35),
19566 ..Default::default()
19567 };
19568
19569 let mut prev_end = label.filter_range.end;
19570 label
19571 .runs
19572 .iter()
19573 .enumerate()
19574 .flat_map(move |(ix, (range, highlight_id))| {
19575 let style = if let Some(style) = highlight_id.style(syntax_theme) {
19576 style
19577 } else {
19578 return Default::default();
19579 };
19580 let mut muted_style = style;
19581 muted_style.highlight(fade_out);
19582
19583 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
19584 if range.start >= label.filter_range.end {
19585 if range.start > prev_end {
19586 runs.push((prev_end..range.start, fade_out));
19587 }
19588 runs.push((range.clone(), muted_style));
19589 } else if range.end <= label.filter_range.end {
19590 runs.push((range.clone(), style));
19591 } else {
19592 runs.push((range.start..label.filter_range.end, style));
19593 runs.push((label.filter_range.end..range.end, muted_style));
19594 }
19595 prev_end = cmp::max(prev_end, range.end);
19596
19597 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
19598 runs.push((prev_end..label.text.len(), fade_out));
19599 }
19600
19601 runs
19602 })
19603}
19604
19605pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
19606 let mut prev_index = 0;
19607 let mut prev_codepoint: Option<char> = None;
19608 text.char_indices()
19609 .chain([(text.len(), '\0')])
19610 .filter_map(move |(index, codepoint)| {
19611 let prev_codepoint = prev_codepoint.replace(codepoint)?;
19612 let is_boundary = index == text.len()
19613 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
19614 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
19615 if is_boundary {
19616 let chunk = &text[prev_index..index];
19617 prev_index = index;
19618 Some(chunk)
19619 } else {
19620 None
19621 }
19622 })
19623}
19624
19625pub trait RangeToAnchorExt: Sized {
19626 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
19627
19628 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
19629 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
19630 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
19631 }
19632}
19633
19634impl<T: ToOffset> RangeToAnchorExt for Range<T> {
19635 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
19636 let start_offset = self.start.to_offset(snapshot);
19637 let end_offset = self.end.to_offset(snapshot);
19638 if start_offset == end_offset {
19639 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
19640 } else {
19641 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
19642 }
19643 }
19644}
19645
19646pub trait RowExt {
19647 fn as_f32(&self) -> f32;
19648
19649 fn next_row(&self) -> Self;
19650
19651 fn previous_row(&self) -> Self;
19652
19653 fn minus(&self, other: Self) -> u32;
19654}
19655
19656impl RowExt for DisplayRow {
19657 fn as_f32(&self) -> f32 {
19658 self.0 as f32
19659 }
19660
19661 fn next_row(&self) -> Self {
19662 Self(self.0 + 1)
19663 }
19664
19665 fn previous_row(&self) -> Self {
19666 Self(self.0.saturating_sub(1))
19667 }
19668
19669 fn minus(&self, other: Self) -> u32 {
19670 self.0 - other.0
19671 }
19672}
19673
19674impl RowExt for MultiBufferRow {
19675 fn as_f32(&self) -> f32 {
19676 self.0 as f32
19677 }
19678
19679 fn next_row(&self) -> Self {
19680 Self(self.0 + 1)
19681 }
19682
19683 fn previous_row(&self) -> Self {
19684 Self(self.0.saturating_sub(1))
19685 }
19686
19687 fn minus(&self, other: Self) -> u32 {
19688 self.0 - other.0
19689 }
19690}
19691
19692trait RowRangeExt {
19693 type Row;
19694
19695 fn len(&self) -> usize;
19696
19697 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
19698}
19699
19700impl RowRangeExt for Range<MultiBufferRow> {
19701 type Row = MultiBufferRow;
19702
19703 fn len(&self) -> usize {
19704 (self.end.0 - self.start.0) as usize
19705 }
19706
19707 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
19708 (self.start.0..self.end.0).map(MultiBufferRow)
19709 }
19710}
19711
19712impl RowRangeExt for Range<DisplayRow> {
19713 type Row = DisplayRow;
19714
19715 fn len(&self) -> usize {
19716 (self.end.0 - self.start.0) as usize
19717 }
19718
19719 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
19720 (self.start.0..self.end.0).map(DisplayRow)
19721 }
19722}
19723
19724/// If select range has more than one line, we
19725/// just point the cursor to range.start.
19726fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
19727 if range.start.row == range.end.row {
19728 range
19729 } else {
19730 range.start..range.start
19731 }
19732}
19733pub struct KillRing(ClipboardItem);
19734impl Global for KillRing {}
19735
19736const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
19737
19738struct BreakpointPromptEditor {
19739 pub(crate) prompt: Entity<Editor>,
19740 editor: WeakEntity<Editor>,
19741 breakpoint_anchor: Anchor,
19742 breakpoint: Breakpoint,
19743 block_ids: HashSet<CustomBlockId>,
19744 gutter_dimensions: Arc<Mutex<GutterDimensions>>,
19745 _subscriptions: Vec<Subscription>,
19746}
19747
19748impl BreakpointPromptEditor {
19749 const MAX_LINES: u8 = 4;
19750
19751 fn new(
19752 editor: WeakEntity<Editor>,
19753 breakpoint_anchor: Anchor,
19754 breakpoint: Breakpoint,
19755 window: &mut Window,
19756 cx: &mut Context<Self>,
19757 ) -> Self {
19758 let buffer = cx.new(|cx| {
19759 Buffer::local(
19760 breakpoint
19761 .kind
19762 .log_message()
19763 .map(|msg| msg.to_string())
19764 .unwrap_or_default(),
19765 cx,
19766 )
19767 });
19768 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19769
19770 let prompt = cx.new(|cx| {
19771 let mut prompt = Editor::new(
19772 EditorMode::AutoHeight {
19773 max_lines: Self::MAX_LINES as usize,
19774 },
19775 buffer,
19776 None,
19777 window,
19778 cx,
19779 );
19780 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
19781 prompt.set_show_cursor_when_unfocused(false, cx);
19782 prompt.set_placeholder_text(
19783 "Message to log when breakpoint is hit. Expressions within {} are interpolated.",
19784 cx,
19785 );
19786
19787 prompt
19788 });
19789
19790 Self {
19791 prompt,
19792 editor,
19793 breakpoint_anchor,
19794 breakpoint,
19795 gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
19796 block_ids: Default::default(),
19797 _subscriptions: vec![],
19798 }
19799 }
19800
19801 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
19802 self.block_ids.extend(block_ids)
19803 }
19804
19805 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
19806 if let Some(editor) = self.editor.upgrade() {
19807 let log_message = self
19808 .prompt
19809 .read(cx)
19810 .buffer
19811 .read(cx)
19812 .as_singleton()
19813 .expect("A multi buffer in breakpoint prompt isn't possible")
19814 .read(cx)
19815 .as_rope()
19816 .to_string();
19817
19818 editor.update(cx, |editor, cx| {
19819 editor.edit_breakpoint_at_anchor(
19820 self.breakpoint_anchor,
19821 self.breakpoint.clone(),
19822 BreakpointEditAction::EditLogMessage(log_message.into()),
19823 cx,
19824 );
19825
19826 editor.remove_blocks(self.block_ids.clone(), None, cx);
19827 cx.focus_self(window);
19828 });
19829 }
19830 }
19831
19832 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
19833 self.editor
19834 .update(cx, |editor, cx| {
19835 editor.remove_blocks(self.block_ids.clone(), None, cx);
19836 window.focus(&editor.focus_handle);
19837 })
19838 .log_err();
19839 }
19840
19841 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
19842 let settings = ThemeSettings::get_global(cx);
19843 let text_style = TextStyle {
19844 color: if self.prompt.read(cx).read_only(cx) {
19845 cx.theme().colors().text_disabled
19846 } else {
19847 cx.theme().colors().text
19848 },
19849 font_family: settings.buffer_font.family.clone(),
19850 font_fallbacks: settings.buffer_font.fallbacks.clone(),
19851 font_size: settings.buffer_font_size(cx).into(),
19852 font_weight: settings.buffer_font.weight,
19853 line_height: relative(settings.buffer_line_height.value()),
19854 ..Default::default()
19855 };
19856 EditorElement::new(
19857 &self.prompt,
19858 EditorStyle {
19859 background: cx.theme().colors().editor_background,
19860 local_player: cx.theme().players().local(),
19861 text: text_style,
19862 ..Default::default()
19863 },
19864 )
19865 }
19866}
19867
19868impl Render for BreakpointPromptEditor {
19869 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
19870 let gutter_dimensions = *self.gutter_dimensions.lock();
19871 h_flex()
19872 .key_context("Editor")
19873 .bg(cx.theme().colors().editor_background)
19874 .border_y_1()
19875 .border_color(cx.theme().status().info_border)
19876 .size_full()
19877 .py(window.line_height() / 2.5)
19878 .on_action(cx.listener(Self::confirm))
19879 .on_action(cx.listener(Self::cancel))
19880 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
19881 .child(div().flex_1().child(self.render_prompt_editor(cx)))
19882 }
19883}
19884
19885impl Focusable for BreakpointPromptEditor {
19886 fn focus_handle(&self, cx: &App) -> FocusHandle {
19887 self.prompt.focus_handle(cx)
19888 }
19889}
19890
19891fn all_edits_insertions_or_deletions(
19892 edits: &Vec<(Range<Anchor>, String)>,
19893 snapshot: &MultiBufferSnapshot,
19894) -> bool {
19895 let mut all_insertions = true;
19896 let mut all_deletions = true;
19897
19898 for (range, new_text) in edits.iter() {
19899 let range_is_empty = range.to_offset(&snapshot).is_empty();
19900 let text_is_empty = new_text.is_empty();
19901
19902 if range_is_empty != text_is_empty {
19903 if range_is_empty {
19904 all_deletions = false;
19905 } else {
19906 all_insertions = false;
19907 }
19908 } else {
19909 return false;
19910 }
19911
19912 if !all_insertions && !all_deletions {
19913 return false;
19914 }
19915 }
19916 all_insertions || all_deletions
19917}
19918
19919struct MissingEditPredictionKeybindingTooltip;
19920
19921impl Render for MissingEditPredictionKeybindingTooltip {
19922 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
19923 ui::tooltip_container(window, cx, |container, _, cx| {
19924 container
19925 .flex_shrink_0()
19926 .max_w_80()
19927 .min_h(rems_from_px(124.))
19928 .justify_between()
19929 .child(
19930 v_flex()
19931 .flex_1()
19932 .text_ui_sm(cx)
19933 .child(Label::new("Conflict with Accept Keybinding"))
19934 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
19935 )
19936 .child(
19937 h_flex()
19938 .pb_1()
19939 .gap_1()
19940 .items_end()
19941 .w_full()
19942 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
19943 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
19944 }))
19945 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
19946 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
19947 })),
19948 )
19949 })
19950 }
19951}
19952
19953#[derive(Debug, Clone, Copy, PartialEq)]
19954pub struct LineHighlight {
19955 pub background: Background,
19956 pub border: Option<gpui::Hsla>,
19957}
19958
19959impl From<Hsla> for LineHighlight {
19960 fn from(hsla: Hsla) -> Self {
19961 Self {
19962 background: hsla.into(),
19963 border: None,
19964 }
19965 }
19966}
19967
19968impl From<Background> for LineHighlight {
19969 fn from(background: Background) -> Self {
19970 Self {
19971 background,
19972 border: None,
19973 }
19974 }
19975}
19976
19977fn render_diff_hunk_controls(
19978 row: u32,
19979 status: &DiffHunkStatus,
19980 hunk_range: Range<Anchor>,
19981 is_created_file: bool,
19982 line_height: Pixels,
19983 editor: &Entity<Editor>,
19984 cx: &mut App,
19985) -> AnyElement {
19986 h_flex()
19987 .h(line_height)
19988 .mr_1()
19989 .gap_1()
19990 .px_0p5()
19991 .pb_1()
19992 .border_x_1()
19993 .border_b_1()
19994 .border_color(cx.theme().colors().border_variant)
19995 .rounded_b_lg()
19996 .bg(cx.theme().colors().editor_background)
19997 .gap_1()
19998 .occlude()
19999 .shadow_md()
20000 .child(if status.has_secondary_hunk() {
20001 Button::new(("stage", row as u64), "Stage")
20002 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20003 .tooltip({
20004 let focus_handle = editor.focus_handle(cx);
20005 move |window, cx| {
20006 Tooltip::for_action_in(
20007 "Stage Hunk",
20008 &::git::ToggleStaged,
20009 &focus_handle,
20010 window,
20011 cx,
20012 )
20013 }
20014 })
20015 .on_click({
20016 let editor = editor.clone();
20017 move |_event, _window, cx| {
20018 editor.update(cx, |editor, cx| {
20019 editor.stage_or_unstage_diff_hunks(
20020 true,
20021 vec![hunk_range.start..hunk_range.start],
20022 cx,
20023 );
20024 });
20025 }
20026 })
20027 } else {
20028 Button::new(("unstage", row as u64), "Unstage")
20029 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20030 .tooltip({
20031 let focus_handle = editor.focus_handle(cx);
20032 move |window, cx| {
20033 Tooltip::for_action_in(
20034 "Unstage Hunk",
20035 &::git::ToggleStaged,
20036 &focus_handle,
20037 window,
20038 cx,
20039 )
20040 }
20041 })
20042 .on_click({
20043 let editor = editor.clone();
20044 move |_event, _window, cx| {
20045 editor.update(cx, |editor, cx| {
20046 editor.stage_or_unstage_diff_hunks(
20047 false,
20048 vec![hunk_range.start..hunk_range.start],
20049 cx,
20050 );
20051 });
20052 }
20053 })
20054 })
20055 .child(
20056 Button::new("restore", "Restore")
20057 .tooltip({
20058 let focus_handle = editor.focus_handle(cx);
20059 move |window, cx| {
20060 Tooltip::for_action_in(
20061 "Restore Hunk",
20062 &::git::Restore,
20063 &focus_handle,
20064 window,
20065 cx,
20066 )
20067 }
20068 })
20069 .on_click({
20070 let editor = editor.clone();
20071 move |_event, window, cx| {
20072 editor.update(cx, |editor, cx| {
20073 let snapshot = editor.snapshot(window, cx);
20074 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
20075 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
20076 });
20077 }
20078 })
20079 .disabled(is_created_file),
20080 )
20081 .when(
20082 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
20083 |el| {
20084 el.child(
20085 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
20086 .shape(IconButtonShape::Square)
20087 .icon_size(IconSize::Small)
20088 // .disabled(!has_multiple_hunks)
20089 .tooltip({
20090 let focus_handle = editor.focus_handle(cx);
20091 move |window, cx| {
20092 Tooltip::for_action_in(
20093 "Next Hunk",
20094 &GoToHunk,
20095 &focus_handle,
20096 window,
20097 cx,
20098 )
20099 }
20100 })
20101 .on_click({
20102 let editor = editor.clone();
20103 move |_event, window, cx| {
20104 editor.update(cx, |editor, cx| {
20105 let snapshot = editor.snapshot(window, cx);
20106 let position =
20107 hunk_range.end.to_point(&snapshot.buffer_snapshot);
20108 editor.go_to_hunk_before_or_after_position(
20109 &snapshot,
20110 position,
20111 Direction::Next,
20112 window,
20113 cx,
20114 );
20115 editor.expand_selected_diff_hunks(cx);
20116 });
20117 }
20118 }),
20119 )
20120 .child(
20121 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
20122 .shape(IconButtonShape::Square)
20123 .icon_size(IconSize::Small)
20124 // .disabled(!has_multiple_hunks)
20125 .tooltip({
20126 let focus_handle = editor.focus_handle(cx);
20127 move |window, cx| {
20128 Tooltip::for_action_in(
20129 "Previous Hunk",
20130 &GoToPreviousHunk,
20131 &focus_handle,
20132 window,
20133 cx,
20134 )
20135 }
20136 })
20137 .on_click({
20138 let editor = editor.clone();
20139 move |_event, window, cx| {
20140 editor.update(cx, |editor, cx| {
20141 let snapshot = editor.snapshot(window, cx);
20142 let point =
20143 hunk_range.start.to_point(&snapshot.buffer_snapshot);
20144 editor.go_to_hunk_before_or_after_position(
20145 &snapshot,
20146 point,
20147 Direction::Prev,
20148 window,
20149 cx,
20150 );
20151 editor.expand_selected_diff_hunks(cx);
20152 });
20153 }
20154 }),
20155 )
20156 },
20157 )
20158 .into_any_element()
20159}