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::{BreakpointEditAction, BreakpointStore, BreakpointStoreEvent},
120 ProjectPath,
121};
122
123pub use proposed_changes_editor::{
124 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
125};
126use smallvec::smallvec;
127use std::{cell::OnceCell, iter::Peekable};
128use task::{ResolvedTask, TaskTemplate, TaskVariables};
129
130pub use lsp::CompletionContext;
131use lsp::{
132 CodeActionKind, CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity,
133 InsertTextFormat, LanguageServerId, LanguageServerName,
134};
135
136use language::BufferSnapshot;
137use movement::TextLayoutDetails;
138pub use multi_buffer::{
139 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, RowInfo,
140 ToOffset, ToPoint,
141};
142use multi_buffer::{
143 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
144 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
145};
146use parking_lot::Mutex;
147use project::{
148 debugger::breakpoint_store::{Breakpoint, BreakpointKind},
149 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
150 project_settings::{GitGutterSetting, ProjectSettings},
151 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
152 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
153 TaskSourceKind,
154};
155use rand::prelude::*;
156use rpc::{proto::*, ErrorExt};
157use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
158use selections_collection::{
159 resolve_selections, MutableSelectionsCollection, SelectionsCollection,
160};
161use serde::{Deserialize, Serialize};
162use settings::{update_settings_file, Settings, SettingsLocation, SettingsStore};
163use smallvec::SmallVec;
164use snippet::Snippet;
165use std::sync::Arc;
166use std::{
167 any::TypeId,
168 borrow::Cow,
169 cell::RefCell,
170 cmp::{self, Ordering, Reverse},
171 mem,
172 num::NonZeroU32,
173 ops::{ControlFlow, Deref, DerefMut, Not as _, Range, RangeInclusive},
174 path::{Path, PathBuf},
175 rc::Rc,
176 time::{Duration, Instant},
177};
178pub use sum_tree::Bias;
179use sum_tree::TreeMap;
180use text::{BufferId, OffsetUtf16, Rope};
181use theme::{
182 observe_buffer_font_size_adjustment, ActiveTheme, PlayerColor, StatusColors, SyntaxTheme,
183 ThemeColors, ThemeSettings,
184};
185use ui::{
186 h_flex, prelude::*, ButtonSize, ButtonStyle, Disclosure, IconButton, IconName, IconSize, Key,
187 Tooltip,
188};
189use util::{maybe, post_inc, RangeExt, ResultExt, TryFutureExt};
190use workspace::{
191 item::{ItemHandle, PreviewTabsSettings},
192 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
193 searchable::SearchEvent,
194 Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
195 RestoreOnStartupBehavior, SplitDirection, TabBarSettings, Toast, ViewId, Workspace,
196 WorkspaceId, WorkspaceSettings, SERIALIZATION_THROTTLE_TIME,
197};
198
199use crate::hover_links::{find_url, find_url_from_range};
200use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
201
202pub const FILE_HEADER_HEIGHT: u32 = 2;
203pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
204pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
205const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
206const MAX_LINE_LEN: usize = 1024;
207const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
208const MAX_SELECTION_HISTORY_LEN: usize = 1024;
209pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
210#[doc(hidden)]
211pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
212
213pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
214pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
215pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
216
217pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
218pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
219pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
220
221const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
222 alt: true,
223 shift: true,
224 control: false,
225 platform: false,
226 function: false,
227};
228
229#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
230pub enum InlayId {
231 InlineCompletion(usize),
232 Hint(usize),
233}
234
235impl InlayId {
236 fn id(&self) -> usize {
237 match self {
238 Self::InlineCompletion(id) => *id,
239 Self::Hint(id) => *id,
240 }
241 }
242}
243
244pub enum DebugCurrentRowHighlight {}
245enum DocumentHighlightRead {}
246enum DocumentHighlightWrite {}
247enum InputComposition {}
248enum SelectedTextHighlight {}
249
250#[derive(Debug, Copy, Clone, PartialEq, Eq)]
251pub enum Navigated {
252 Yes,
253 No,
254}
255
256impl Navigated {
257 pub fn from_bool(yes: bool) -> Navigated {
258 if yes {
259 Navigated::Yes
260 } else {
261 Navigated::No
262 }
263 }
264}
265
266#[derive(Debug, Clone, PartialEq, Eq)]
267enum DisplayDiffHunk {
268 Folded {
269 display_row: DisplayRow,
270 },
271 Unfolded {
272 is_created_file: bool,
273 diff_base_byte_range: Range<usize>,
274 display_row_range: Range<DisplayRow>,
275 multi_buffer_range: Range<Anchor>,
276 status: DiffHunkStatus,
277 },
278}
279
280pub fn init_settings(cx: &mut App) {
281 EditorSettings::register(cx);
282}
283
284pub fn init(cx: &mut App) {
285 init_settings(cx);
286
287 workspace::register_project_item::<Editor>(cx);
288 workspace::FollowableViewRegistry::register::<Editor>(cx);
289 workspace::register_serializable_item::<Editor>(cx);
290
291 cx.observe_new(
292 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
293 workspace.register_action(Editor::new_file);
294 workspace.register_action(Editor::new_file_vertical);
295 workspace.register_action(Editor::new_file_horizontal);
296 workspace.register_action(Editor::cancel_language_server_work);
297 },
298 )
299 .detach();
300
301 cx.on_action(move |_: &workspace::NewFile, cx| {
302 let app_state = workspace::AppState::global(cx);
303 if let Some(app_state) = app_state.upgrade() {
304 workspace::open_new(
305 Default::default(),
306 app_state,
307 cx,
308 |workspace, window, cx| {
309 Editor::new_file(workspace, &Default::default(), window, cx)
310 },
311 )
312 .detach();
313 }
314 });
315 cx.on_action(move |_: &workspace::NewWindow, 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 cx.activate(true);
324 Editor::new_file(workspace, &Default::default(), window, cx)
325 },
326 )
327 .detach();
328 }
329 });
330}
331
332pub struct SearchWithinRange;
333
334trait InvalidationRegion {
335 fn ranges(&self) -> &[Range<Anchor>];
336}
337
338#[derive(Clone, Debug, PartialEq)]
339pub enum SelectPhase {
340 Begin {
341 position: DisplayPoint,
342 add: bool,
343 click_count: usize,
344 },
345 BeginColumnar {
346 position: DisplayPoint,
347 reset: bool,
348 goal_column: u32,
349 },
350 Extend {
351 position: DisplayPoint,
352 click_count: usize,
353 },
354 Update {
355 position: DisplayPoint,
356 goal_column: u32,
357 scroll_delta: gpui::Point<f32>,
358 },
359 End,
360}
361
362#[derive(Clone, Debug)]
363pub enum SelectMode {
364 Character,
365 Word(Range<Anchor>),
366 Line(Range<Anchor>),
367 All,
368}
369
370#[derive(Copy, Clone, PartialEq, Eq, Debug)]
371pub enum EditorMode {
372 SingleLine { auto_width: bool },
373 AutoHeight { max_lines: usize },
374 Full,
375}
376
377#[derive(Copy, Clone, Debug)]
378pub enum SoftWrap {
379 /// Prefer not to wrap at all.
380 ///
381 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
382 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
383 GitDiff,
384 /// Prefer a single line generally, unless an overly long line is encountered.
385 None,
386 /// Soft wrap lines that exceed the editor width.
387 EditorWidth,
388 /// Soft wrap lines at the preferred line length.
389 Column(u32),
390 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
391 Bounded(u32),
392}
393
394#[derive(Clone)]
395pub struct EditorStyle {
396 pub background: Hsla,
397 pub local_player: PlayerColor,
398 pub text: TextStyle,
399 pub scrollbar_width: Pixels,
400 pub syntax: Arc<SyntaxTheme>,
401 pub status: StatusColors,
402 pub inlay_hints_style: HighlightStyle,
403 pub inline_completion_styles: InlineCompletionStyles,
404 pub unnecessary_code_fade: f32,
405}
406
407impl Default for EditorStyle {
408 fn default() -> Self {
409 Self {
410 background: Hsla::default(),
411 local_player: PlayerColor::default(),
412 text: TextStyle::default(),
413 scrollbar_width: Pixels::default(),
414 syntax: Default::default(),
415 // HACK: Status colors don't have a real default.
416 // We should look into removing the status colors from the editor
417 // style and retrieve them directly from the theme.
418 status: StatusColors::dark(),
419 inlay_hints_style: HighlightStyle::default(),
420 inline_completion_styles: InlineCompletionStyles {
421 insertion: HighlightStyle::default(),
422 whitespace: HighlightStyle::default(),
423 },
424 unnecessary_code_fade: Default::default(),
425 }
426 }
427}
428
429pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
430 let show_background = language_settings::language_settings(None, None, cx)
431 .inlay_hints
432 .show_background;
433
434 HighlightStyle {
435 color: Some(cx.theme().status().hint),
436 background_color: show_background.then(|| cx.theme().status().hint_background),
437 ..HighlightStyle::default()
438 }
439}
440
441pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
442 InlineCompletionStyles {
443 insertion: HighlightStyle {
444 color: Some(cx.theme().status().predictive),
445 ..HighlightStyle::default()
446 },
447 whitespace: HighlightStyle {
448 background_color: Some(cx.theme().status().created_background),
449 ..HighlightStyle::default()
450 },
451 }
452}
453
454type CompletionId = usize;
455
456pub(crate) enum EditDisplayMode {
457 TabAccept,
458 DiffPopover,
459 Inline,
460}
461
462enum InlineCompletion {
463 Edit {
464 edits: Vec<(Range<Anchor>, String)>,
465 edit_preview: Option<EditPreview>,
466 display_mode: EditDisplayMode,
467 snapshot: BufferSnapshot,
468 },
469 Move {
470 target: Anchor,
471 snapshot: BufferSnapshot,
472 },
473}
474
475struct InlineCompletionState {
476 inlay_ids: Vec<InlayId>,
477 completion: InlineCompletion,
478 completion_id: Option<SharedString>,
479 invalidation_range: Range<Anchor>,
480}
481
482enum EditPredictionSettings {
483 Disabled,
484 Enabled {
485 show_in_menu: bool,
486 preview_requires_modifier: bool,
487 },
488}
489
490enum InlineCompletionHighlight {}
491
492#[derive(Debug, Clone)]
493struct InlineDiagnostic {
494 message: SharedString,
495 group_id: usize,
496 is_primary: bool,
497 start: Point,
498 severity: DiagnosticSeverity,
499}
500
501pub enum MenuInlineCompletionsPolicy {
502 Never,
503 ByProvider,
504}
505
506pub enum EditPredictionPreview {
507 /// Modifier is not pressed
508 Inactive { released_too_fast: bool },
509 /// Modifier pressed
510 Active {
511 since: Instant,
512 previous_scroll_position: Option<ScrollAnchor>,
513 },
514}
515
516impl EditPredictionPreview {
517 pub fn released_too_fast(&self) -> bool {
518 match self {
519 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
520 EditPredictionPreview::Active { .. } => false,
521 }
522 }
523
524 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
525 if let EditPredictionPreview::Active {
526 previous_scroll_position,
527 ..
528 } = self
529 {
530 *previous_scroll_position = scroll_position;
531 }
532 }
533}
534
535pub struct ContextMenuOptions {
536 pub min_entries_visible: usize,
537 pub max_entries_visible: usize,
538 pub placement: Option<ContextMenuPlacement>,
539}
540
541#[derive(Debug, Clone, PartialEq, Eq)]
542pub enum ContextMenuPlacement {
543 Above,
544 Below,
545}
546
547#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
548struct EditorActionId(usize);
549
550impl EditorActionId {
551 pub fn post_inc(&mut self) -> Self {
552 let answer = self.0;
553
554 *self = Self(answer + 1);
555
556 Self(answer)
557 }
558}
559
560// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
561// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
562
563type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
564type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
565
566#[derive(Default)]
567struct ScrollbarMarkerState {
568 scrollbar_size: Size<Pixels>,
569 dirty: bool,
570 markers: Arc<[PaintQuad]>,
571 pending_refresh: Option<Task<Result<()>>>,
572}
573
574impl ScrollbarMarkerState {
575 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
576 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
577 }
578}
579
580#[derive(Clone, Debug)]
581struct RunnableTasks {
582 templates: Vec<(TaskSourceKind, TaskTemplate)>,
583 offset: multi_buffer::Anchor,
584 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
585 column: u32,
586 // Values of all named captures, including those starting with '_'
587 extra_variables: HashMap<String, String>,
588 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
589 context_range: Range<BufferOffset>,
590}
591
592impl RunnableTasks {
593 fn resolve<'a>(
594 &'a self,
595 cx: &'a task::TaskContext,
596 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
597 self.templates.iter().filter_map(|(kind, template)| {
598 template
599 .resolve_task(&kind.to_id_base(), cx)
600 .map(|task| (kind.clone(), task))
601 })
602 }
603}
604
605#[derive(Clone)]
606struct ResolvedTasks {
607 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
608 position: Anchor,
609}
610
611#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
612struct BufferOffset(usize);
613
614// Addons allow storing per-editor state in other crates (e.g. Vim)
615pub trait Addon: 'static {
616 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
617
618 fn render_buffer_header_controls(
619 &self,
620 _: &ExcerptInfo,
621 _: &Window,
622 _: &App,
623 ) -> Option<AnyElement> {
624 None
625 }
626
627 fn to_any(&self) -> &dyn std::any::Any;
628}
629
630/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
631///
632/// See the [module level documentation](self) for more information.
633pub struct Editor {
634 focus_handle: FocusHandle,
635 last_focused_descendant: Option<WeakFocusHandle>,
636 /// The text buffer being edited
637 buffer: Entity<MultiBuffer>,
638 /// Map of how text in the buffer should be displayed.
639 /// Handles soft wraps, folds, fake inlay text insertions, etc.
640 pub display_map: Entity<DisplayMap>,
641 pub selections: SelectionsCollection,
642 pub scroll_manager: ScrollManager,
643 /// When inline assist editors are linked, they all render cursors because
644 /// typing enters text into each of them, even the ones that aren't focused.
645 pub(crate) show_cursor_when_unfocused: bool,
646 columnar_selection_tail: Option<Anchor>,
647 add_selections_state: Option<AddSelectionsState>,
648 select_next_state: Option<SelectNextState>,
649 select_prev_state: Option<SelectNextState>,
650 selection_history: SelectionHistory,
651 autoclose_regions: Vec<AutocloseRegion>,
652 snippet_stack: InvalidationStack<SnippetState>,
653 select_syntax_node_history: SelectSyntaxNodeHistory,
654 ime_transaction: Option<TransactionId>,
655 active_diagnostics: Option<ActiveDiagnosticGroup>,
656 show_inline_diagnostics: bool,
657 inline_diagnostics_update: Task<()>,
658 inline_diagnostics_enabled: bool,
659 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
660 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
661 hard_wrap: Option<usize>,
662
663 // TODO: make this a access method
664 pub project: Option<Entity<Project>>,
665 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
666 completion_provider: Option<Box<dyn CompletionProvider>>,
667 collaboration_hub: Option<Box<dyn CollaborationHub>>,
668 blink_manager: Entity<BlinkManager>,
669 show_cursor_names: bool,
670 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
671 pub show_local_selections: bool,
672 mode: EditorMode,
673 show_breadcrumbs: bool,
674 show_gutter: bool,
675 show_scrollbars: bool,
676 show_line_numbers: Option<bool>,
677 use_relative_line_numbers: Option<bool>,
678 show_git_diff_gutter: Option<bool>,
679 show_code_actions: Option<bool>,
680 show_runnables: Option<bool>,
681 show_breakpoints: Option<bool>,
682 show_wrap_guides: Option<bool>,
683 show_indent_guides: Option<bool>,
684 placeholder_text: Option<Arc<str>>,
685 highlight_order: usize,
686 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
687 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
688 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
689 scrollbar_marker_state: ScrollbarMarkerState,
690 active_indent_guides_state: ActiveIndentGuidesState,
691 nav_history: Option<ItemNavHistory>,
692 context_menu: RefCell<Option<CodeContextMenu>>,
693 context_menu_options: Option<ContextMenuOptions>,
694 mouse_context_menu: Option<MouseContextMenu>,
695 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
696 signature_help_state: SignatureHelpState,
697 auto_signature_help: Option<bool>,
698 find_all_references_task_sources: Vec<Anchor>,
699 next_completion_id: CompletionId,
700 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
701 code_actions_task: Option<Task<Result<()>>>,
702 selection_highlight_task: Option<Task<()>>,
703 document_highlights_task: Option<Task<()>>,
704 linked_editing_range_task: Option<Task<Option<()>>>,
705 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
706 pending_rename: Option<RenameState>,
707 searchable: bool,
708 cursor_shape: CursorShape,
709 current_line_highlight: Option<CurrentLineHighlight>,
710 collapse_matches: bool,
711 autoindent_mode: Option<AutoindentMode>,
712 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
713 input_enabled: bool,
714 use_modal_editing: bool,
715 read_only: bool,
716 leader_peer_id: Option<PeerId>,
717 remote_id: Option<ViewId>,
718 hover_state: HoverState,
719 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
720 gutter_hovered: bool,
721 hovered_link_state: Option<HoveredLinkState>,
722 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
723 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
724 active_inline_completion: Option<InlineCompletionState>,
725 /// Used to prevent flickering as the user types while the menu is open
726 stale_inline_completion_in_menu: Option<InlineCompletionState>,
727 edit_prediction_settings: EditPredictionSettings,
728 inline_completions_hidden_for_vim_mode: bool,
729 show_inline_completions_override: Option<bool>,
730 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
731 edit_prediction_preview: EditPredictionPreview,
732 edit_prediction_indent_conflict: bool,
733 edit_prediction_requires_modifier_in_indent_conflict: bool,
734 inlay_hint_cache: InlayHintCache,
735 next_inlay_id: usize,
736 _subscriptions: Vec<Subscription>,
737 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
738 gutter_dimensions: GutterDimensions,
739 style: Option<EditorStyle>,
740 text_style_refinement: Option<TextStyleRefinement>,
741 next_editor_action_id: EditorActionId,
742 editor_actions:
743 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
744 use_autoclose: bool,
745 use_auto_surround: bool,
746 auto_replace_emoji_shortcode: bool,
747 jsx_tag_auto_close_enabled_in_any_buffer: bool,
748 show_git_blame_gutter: bool,
749 show_git_blame_inline: bool,
750 show_git_blame_inline_delay_task: Option<Task<()>>,
751 git_blame_inline_tooltip: Option<WeakEntity<crate::commit_tooltip::CommitTooltip>>,
752 git_blame_inline_enabled: bool,
753 serialize_dirty_buffers: bool,
754 show_selection_menu: Option<bool>,
755 blame: Option<Entity<GitBlame>>,
756 blame_subscription: Option<Subscription>,
757 custom_context_menu: Option<
758 Box<
759 dyn 'static
760 + Fn(
761 &mut Self,
762 DisplayPoint,
763 &mut Window,
764 &mut Context<Self>,
765 ) -> Option<Entity<ui::ContextMenu>>,
766 >,
767 >,
768 last_bounds: Option<Bounds<Pixels>>,
769 last_position_map: Option<Rc<PositionMap>>,
770 expect_bounds_change: Option<Bounds<Pixels>>,
771 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
772 tasks_update_task: Option<Task<()>>,
773 pub breakpoint_store: Option<Entity<BreakpointStore>>,
774 /// Allow's a user to create a breakpoint by selecting this indicator
775 /// It should be None while a user is not hovering over the gutter
776 /// Otherwise it represents the point that the breakpoint will be shown
777 pub gutter_breakpoint_indicator: Option<DisplayPoint>,
778 in_project_search: bool,
779 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
780 breadcrumb_header: Option<String>,
781 focused_block: Option<FocusedBlock>,
782 next_scroll_position: NextScrollCursorCenterTopBottom,
783 addons: HashMap<TypeId, Box<dyn Addon>>,
784 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
785 load_diff_task: Option<Shared<Task<()>>>,
786 selection_mark_mode: bool,
787 toggle_fold_multiple_buffers: Task<()>,
788 _scroll_cursor_center_top_bottom_task: Task<()>,
789 serialize_selections: Task<()>,
790 serialize_folds: Task<()>,
791}
792
793#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
794enum NextScrollCursorCenterTopBottom {
795 #[default]
796 Center,
797 Top,
798 Bottom,
799}
800
801impl NextScrollCursorCenterTopBottom {
802 fn next(&self) -> Self {
803 match self {
804 Self::Center => Self::Top,
805 Self::Top => Self::Bottom,
806 Self::Bottom => Self::Center,
807 }
808 }
809}
810
811#[derive(Clone)]
812pub struct EditorSnapshot {
813 pub mode: EditorMode,
814 show_gutter: bool,
815 show_line_numbers: Option<bool>,
816 show_git_diff_gutter: Option<bool>,
817 show_code_actions: Option<bool>,
818 show_runnables: Option<bool>,
819 show_breakpoints: Option<bool>,
820 git_blame_gutter_max_author_length: Option<usize>,
821 pub display_snapshot: DisplaySnapshot,
822 pub placeholder_text: Option<Arc<str>>,
823 is_focused: bool,
824 scroll_anchor: ScrollAnchor,
825 ongoing_scroll: OngoingScroll,
826 current_line_highlight: CurrentLineHighlight,
827 gutter_hovered: bool,
828}
829
830const GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED: usize = 20;
831
832#[derive(Default, Debug, Clone, Copy)]
833pub struct GutterDimensions {
834 pub left_padding: Pixels,
835 pub right_padding: Pixels,
836 pub width: Pixels,
837 pub margin: Pixels,
838 pub git_blame_entries_width: Option<Pixels>,
839}
840
841impl GutterDimensions {
842 /// The full width of the space taken up by the gutter.
843 pub fn full_width(&self) -> Pixels {
844 self.margin + self.width
845 }
846
847 /// The width of the space reserved for the fold indicators,
848 /// use alongside 'justify_end' and `gutter_width` to
849 /// right align content with the line numbers
850 pub fn fold_area_width(&self) -> Pixels {
851 self.margin + self.right_padding
852 }
853}
854
855#[derive(Debug)]
856pub struct RemoteSelection {
857 pub replica_id: ReplicaId,
858 pub selection: Selection<Anchor>,
859 pub cursor_shape: CursorShape,
860 pub peer_id: PeerId,
861 pub line_mode: bool,
862 pub participant_index: Option<ParticipantIndex>,
863 pub user_name: Option<SharedString>,
864}
865
866#[derive(Clone, Debug)]
867struct SelectionHistoryEntry {
868 selections: Arc<[Selection<Anchor>]>,
869 select_next_state: Option<SelectNextState>,
870 select_prev_state: Option<SelectNextState>,
871 add_selections_state: Option<AddSelectionsState>,
872}
873
874enum SelectionHistoryMode {
875 Normal,
876 Undoing,
877 Redoing,
878}
879
880#[derive(Clone, PartialEq, Eq, Hash)]
881struct HoveredCursor {
882 replica_id: u16,
883 selection_id: usize,
884}
885
886impl Default for SelectionHistoryMode {
887 fn default() -> Self {
888 Self::Normal
889 }
890}
891
892#[derive(Default)]
893struct SelectionHistory {
894 #[allow(clippy::type_complexity)]
895 selections_by_transaction:
896 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
897 mode: SelectionHistoryMode,
898 undo_stack: VecDeque<SelectionHistoryEntry>,
899 redo_stack: VecDeque<SelectionHistoryEntry>,
900}
901
902impl SelectionHistory {
903 fn insert_transaction(
904 &mut self,
905 transaction_id: TransactionId,
906 selections: Arc<[Selection<Anchor>]>,
907 ) {
908 self.selections_by_transaction
909 .insert(transaction_id, (selections, None));
910 }
911
912 #[allow(clippy::type_complexity)]
913 fn transaction(
914 &self,
915 transaction_id: TransactionId,
916 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
917 self.selections_by_transaction.get(&transaction_id)
918 }
919
920 #[allow(clippy::type_complexity)]
921 fn transaction_mut(
922 &mut self,
923 transaction_id: TransactionId,
924 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
925 self.selections_by_transaction.get_mut(&transaction_id)
926 }
927
928 fn push(&mut self, entry: SelectionHistoryEntry) {
929 if !entry.selections.is_empty() {
930 match self.mode {
931 SelectionHistoryMode::Normal => {
932 self.push_undo(entry);
933 self.redo_stack.clear();
934 }
935 SelectionHistoryMode::Undoing => self.push_redo(entry),
936 SelectionHistoryMode::Redoing => self.push_undo(entry),
937 }
938 }
939 }
940
941 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
942 if self
943 .undo_stack
944 .back()
945 .map_or(true, |e| e.selections != entry.selections)
946 {
947 self.undo_stack.push_back(entry);
948 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
949 self.undo_stack.pop_front();
950 }
951 }
952 }
953
954 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
955 if self
956 .redo_stack
957 .back()
958 .map_or(true, |e| e.selections != entry.selections)
959 {
960 self.redo_stack.push_back(entry);
961 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
962 self.redo_stack.pop_front();
963 }
964 }
965 }
966}
967
968struct RowHighlight {
969 index: usize,
970 range: Range<Anchor>,
971 color: Hsla,
972 should_autoscroll: bool,
973}
974
975#[derive(Clone, Debug)]
976struct AddSelectionsState {
977 above: bool,
978 stack: Vec<usize>,
979}
980
981#[derive(Clone)]
982struct SelectNextState {
983 query: AhoCorasick,
984 wordwise: bool,
985 done: bool,
986}
987
988impl std::fmt::Debug for SelectNextState {
989 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
990 f.debug_struct(std::any::type_name::<Self>())
991 .field("wordwise", &self.wordwise)
992 .field("done", &self.done)
993 .finish()
994 }
995}
996
997#[derive(Debug)]
998struct AutocloseRegion {
999 selection_id: usize,
1000 range: Range<Anchor>,
1001 pair: BracketPair,
1002}
1003
1004#[derive(Debug)]
1005struct SnippetState {
1006 ranges: Vec<Vec<Range<Anchor>>>,
1007 active_index: usize,
1008 choices: Vec<Option<Vec<String>>>,
1009}
1010
1011#[doc(hidden)]
1012pub struct RenameState {
1013 pub range: Range<Anchor>,
1014 pub old_name: Arc<str>,
1015 pub editor: Entity<Editor>,
1016 block_id: CustomBlockId,
1017}
1018
1019struct InvalidationStack<T>(Vec<T>);
1020
1021struct RegisteredInlineCompletionProvider {
1022 provider: Arc<dyn InlineCompletionProviderHandle>,
1023 _subscription: Subscription,
1024}
1025
1026#[derive(Debug, PartialEq, Eq)]
1027struct ActiveDiagnosticGroup {
1028 primary_range: Range<Anchor>,
1029 primary_message: String,
1030 group_id: usize,
1031 blocks: HashMap<CustomBlockId, Diagnostic>,
1032 is_valid: bool,
1033}
1034
1035#[derive(Serialize, Deserialize, Clone, Debug)]
1036pub struct ClipboardSelection {
1037 /// The number of bytes in this selection.
1038 pub len: usize,
1039 /// Whether this was a full-line selection.
1040 pub is_entire_line: bool,
1041 /// The indentation of the first line when this content was originally copied.
1042 pub first_line_indent: u32,
1043}
1044
1045// selections, scroll behavior, was newest selection reversed
1046type SelectSyntaxNodeHistoryState = (
1047 Box<[Selection<usize>]>,
1048 SelectSyntaxNodeScrollBehavior,
1049 bool,
1050);
1051
1052#[derive(Default)]
1053struct SelectSyntaxNodeHistory {
1054 stack: Vec<SelectSyntaxNodeHistoryState>,
1055 // disable temporarily to allow changing selections without losing the stack
1056 pub disable_clearing: bool,
1057}
1058
1059impl SelectSyntaxNodeHistory {
1060 pub fn try_clear(&mut self) {
1061 if !self.disable_clearing {
1062 self.stack.clear();
1063 }
1064 }
1065
1066 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1067 self.stack.push(selection);
1068 }
1069
1070 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1071 self.stack.pop()
1072 }
1073}
1074
1075enum SelectSyntaxNodeScrollBehavior {
1076 CursorTop,
1077 CenterSelection,
1078 CursorBottom,
1079}
1080
1081#[derive(Debug)]
1082pub(crate) struct NavigationData {
1083 cursor_anchor: Anchor,
1084 cursor_position: Point,
1085 scroll_anchor: ScrollAnchor,
1086 scroll_top_row: u32,
1087}
1088
1089#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1090pub enum GotoDefinitionKind {
1091 Symbol,
1092 Declaration,
1093 Type,
1094 Implementation,
1095}
1096
1097#[derive(Debug, Clone)]
1098enum InlayHintRefreshReason {
1099 ModifiersChanged(bool),
1100 Toggle(bool),
1101 SettingsChange(InlayHintSettings),
1102 NewLinesShown,
1103 BufferEdited(HashSet<Arc<Language>>),
1104 RefreshRequested,
1105 ExcerptsRemoved(Vec<ExcerptId>),
1106}
1107
1108impl InlayHintRefreshReason {
1109 fn description(&self) -> &'static str {
1110 match self {
1111 Self::ModifiersChanged(_) => "modifiers changed",
1112 Self::Toggle(_) => "toggle",
1113 Self::SettingsChange(_) => "settings change",
1114 Self::NewLinesShown => "new lines shown",
1115 Self::BufferEdited(_) => "buffer edited",
1116 Self::RefreshRequested => "refresh requested",
1117 Self::ExcerptsRemoved(_) => "excerpts removed",
1118 }
1119 }
1120}
1121
1122pub enum FormatTarget {
1123 Buffers,
1124 Ranges(Vec<Range<MultiBufferPoint>>),
1125}
1126
1127pub(crate) struct FocusedBlock {
1128 id: BlockId,
1129 focus_handle: WeakFocusHandle,
1130}
1131
1132#[derive(Clone)]
1133enum JumpData {
1134 MultiBufferRow {
1135 row: MultiBufferRow,
1136 line_offset_from_top: u32,
1137 },
1138 MultiBufferPoint {
1139 excerpt_id: ExcerptId,
1140 position: Point,
1141 anchor: text::Anchor,
1142 line_offset_from_top: u32,
1143 },
1144}
1145
1146pub enum MultibufferSelectionMode {
1147 First,
1148 All,
1149}
1150
1151#[derive(Clone, Copy, Debug, Default)]
1152pub struct RewrapOptions {
1153 pub override_language_settings: bool,
1154 pub preserve_existing_whitespace: bool,
1155}
1156
1157impl Editor {
1158 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1159 let buffer = cx.new(|cx| Buffer::local("", cx));
1160 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1161 Self::new(
1162 EditorMode::SingleLine { auto_width: false },
1163 buffer,
1164 None,
1165 window,
1166 cx,
1167 )
1168 }
1169
1170 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1171 let buffer = cx.new(|cx| Buffer::local("", cx));
1172 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1173 Self::new(EditorMode::Full, buffer, None, window, cx)
1174 }
1175
1176 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1177 let buffer = cx.new(|cx| Buffer::local("", cx));
1178 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1179 Self::new(
1180 EditorMode::SingleLine { auto_width: true },
1181 buffer,
1182 None,
1183 window,
1184 cx,
1185 )
1186 }
1187
1188 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1189 let buffer = cx.new(|cx| Buffer::local("", cx));
1190 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1191 Self::new(
1192 EditorMode::AutoHeight { max_lines },
1193 buffer,
1194 None,
1195 window,
1196 cx,
1197 )
1198 }
1199
1200 pub fn for_buffer(
1201 buffer: Entity<Buffer>,
1202 project: Option<Entity<Project>>,
1203 window: &mut Window,
1204 cx: &mut Context<Self>,
1205 ) -> Self {
1206 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1207 Self::new(EditorMode::Full, buffer, project, window, cx)
1208 }
1209
1210 pub fn for_multibuffer(
1211 buffer: Entity<MultiBuffer>,
1212 project: Option<Entity<Project>>,
1213 window: &mut Window,
1214 cx: &mut Context<Self>,
1215 ) -> Self {
1216 Self::new(EditorMode::Full, buffer, project, window, cx)
1217 }
1218
1219 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1220 let mut clone = Self::new(
1221 self.mode,
1222 self.buffer.clone(),
1223 self.project.clone(),
1224 window,
1225 cx,
1226 );
1227 self.display_map.update(cx, |display_map, cx| {
1228 let snapshot = display_map.snapshot(cx);
1229 clone.display_map.update(cx, |display_map, cx| {
1230 display_map.set_state(&snapshot, cx);
1231 });
1232 });
1233 clone.folds_did_change(cx);
1234 clone.selections.clone_state(&self.selections);
1235 clone.scroll_manager.clone_state(&self.scroll_manager);
1236 clone.searchable = self.searchable;
1237 clone
1238 }
1239
1240 pub fn new(
1241 mode: EditorMode,
1242 buffer: Entity<MultiBuffer>,
1243 project: Option<Entity<Project>>,
1244 window: &mut Window,
1245 cx: &mut Context<Self>,
1246 ) -> Self {
1247 let style = window.text_style();
1248 let font_size = style.font_size.to_pixels(window.rem_size());
1249 let editor = cx.entity().downgrade();
1250 let fold_placeholder = FoldPlaceholder {
1251 constrain_width: true,
1252 render: Arc::new(move |fold_id, fold_range, cx| {
1253 let editor = editor.clone();
1254 div()
1255 .id(fold_id)
1256 .bg(cx.theme().colors().ghost_element_background)
1257 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1258 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1259 .rounded_xs()
1260 .size_full()
1261 .cursor_pointer()
1262 .child("⋯")
1263 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1264 .on_click(move |_, _window, cx| {
1265 editor
1266 .update(cx, |editor, cx| {
1267 editor.unfold_ranges(
1268 &[fold_range.start..fold_range.end],
1269 true,
1270 false,
1271 cx,
1272 );
1273 cx.stop_propagation();
1274 })
1275 .ok();
1276 })
1277 .into_any()
1278 }),
1279 merge_adjacent: true,
1280 ..Default::default()
1281 };
1282 let display_map = cx.new(|cx| {
1283 DisplayMap::new(
1284 buffer.clone(),
1285 style.font(),
1286 font_size,
1287 None,
1288 FILE_HEADER_HEIGHT,
1289 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1290 fold_placeholder,
1291 cx,
1292 )
1293 });
1294
1295 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1296
1297 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1298
1299 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1300 .then(|| language_settings::SoftWrap::None);
1301
1302 let mut project_subscriptions = Vec::new();
1303 if mode == EditorMode::Full {
1304 if let Some(project) = project.as_ref() {
1305 project_subscriptions.push(cx.subscribe_in(
1306 project,
1307 window,
1308 |editor, _, event, window, cx| match event {
1309 project::Event::RefreshCodeLens => {
1310 // we always query lens with actions, without storing them, always refreshing them
1311 }
1312 project::Event::RefreshInlayHints => {
1313 editor
1314 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1315 }
1316 project::Event::SnippetEdit(id, snippet_edits) => {
1317 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1318 let focus_handle = editor.focus_handle(cx);
1319 if focus_handle.is_focused(window) {
1320 let snapshot = buffer.read(cx).snapshot();
1321 for (range, snippet) in snippet_edits {
1322 let editor_range =
1323 language::range_from_lsp(*range).to_offset(&snapshot);
1324 editor
1325 .insert_snippet(
1326 &[editor_range],
1327 snippet.clone(),
1328 window,
1329 cx,
1330 )
1331 .ok();
1332 }
1333 }
1334 }
1335 }
1336 _ => {}
1337 },
1338 ));
1339 if let Some(task_inventory) = project
1340 .read(cx)
1341 .task_store()
1342 .read(cx)
1343 .task_inventory()
1344 .cloned()
1345 {
1346 project_subscriptions.push(cx.observe_in(
1347 &task_inventory,
1348 window,
1349 |editor, _, window, cx| {
1350 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1351 },
1352 ));
1353 };
1354
1355 project_subscriptions.push(cx.subscribe_in(
1356 &project.read(cx).breakpoint_store(),
1357 window,
1358 |editor, _, event, window, cx| match event {
1359 BreakpointStoreEvent::ActiveDebugLineChanged => {
1360 editor.go_to_active_debug_line(window, cx);
1361 }
1362 _ => {}
1363 },
1364 ));
1365 }
1366 }
1367
1368 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1369
1370 let inlay_hint_settings =
1371 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1372 let focus_handle = cx.focus_handle();
1373 cx.on_focus(&focus_handle, window, Self::handle_focus)
1374 .detach();
1375 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1376 .detach();
1377 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1378 .detach();
1379 cx.on_blur(&focus_handle, window, Self::handle_blur)
1380 .detach();
1381
1382 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1383 Some(false)
1384 } else {
1385 None
1386 };
1387
1388 let breakpoint_store = match (mode, project.as_ref()) {
1389 (EditorMode::Full, Some(project)) => Some(project.read(cx).breakpoint_store()),
1390 _ => None,
1391 };
1392
1393 let mut code_action_providers = Vec::new();
1394 let mut load_uncommitted_diff = None;
1395 if let Some(project) = project.clone() {
1396 load_uncommitted_diff = Some(
1397 get_uncommitted_diff_for_buffer(
1398 &project,
1399 buffer.read(cx).all_buffers(),
1400 buffer.clone(),
1401 cx,
1402 )
1403 .shared(),
1404 );
1405 code_action_providers.push(Rc::new(project) as Rc<_>);
1406 }
1407
1408 let mut this = Self {
1409 focus_handle,
1410 show_cursor_when_unfocused: false,
1411 last_focused_descendant: None,
1412 buffer: buffer.clone(),
1413 display_map: display_map.clone(),
1414 selections,
1415 scroll_manager: ScrollManager::new(cx),
1416 columnar_selection_tail: None,
1417 add_selections_state: None,
1418 select_next_state: None,
1419 select_prev_state: None,
1420 selection_history: Default::default(),
1421 autoclose_regions: Default::default(),
1422 snippet_stack: Default::default(),
1423 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1424 ime_transaction: Default::default(),
1425 active_diagnostics: None,
1426 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1427 inline_diagnostics_update: Task::ready(()),
1428 inline_diagnostics: Vec::new(),
1429 soft_wrap_mode_override,
1430 hard_wrap: None,
1431 completion_provider: project.clone().map(|project| Box::new(project) as _),
1432 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1433 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1434 project,
1435 blink_manager: blink_manager.clone(),
1436 show_local_selections: true,
1437 show_scrollbars: true,
1438 mode,
1439 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1440 show_gutter: mode == EditorMode::Full,
1441 show_line_numbers: None,
1442 use_relative_line_numbers: None,
1443 show_git_diff_gutter: None,
1444 show_code_actions: None,
1445 show_runnables: None,
1446 show_breakpoints: None,
1447 show_wrap_guides: None,
1448 show_indent_guides,
1449 placeholder_text: None,
1450 highlight_order: 0,
1451 highlighted_rows: HashMap::default(),
1452 background_highlights: Default::default(),
1453 gutter_highlights: TreeMap::default(),
1454 scrollbar_marker_state: ScrollbarMarkerState::default(),
1455 active_indent_guides_state: ActiveIndentGuidesState::default(),
1456 nav_history: None,
1457 context_menu: RefCell::new(None),
1458 context_menu_options: None,
1459 mouse_context_menu: None,
1460 completion_tasks: Default::default(),
1461 signature_help_state: SignatureHelpState::default(),
1462 auto_signature_help: None,
1463 find_all_references_task_sources: Vec::new(),
1464 next_completion_id: 0,
1465 next_inlay_id: 0,
1466 code_action_providers,
1467 available_code_actions: Default::default(),
1468 code_actions_task: Default::default(),
1469 selection_highlight_task: Default::default(),
1470 document_highlights_task: Default::default(),
1471 linked_editing_range_task: Default::default(),
1472 pending_rename: Default::default(),
1473 searchable: true,
1474 cursor_shape: EditorSettings::get_global(cx)
1475 .cursor_shape
1476 .unwrap_or_default(),
1477 current_line_highlight: None,
1478 autoindent_mode: Some(AutoindentMode::EachLine),
1479 collapse_matches: false,
1480 workspace: None,
1481 input_enabled: true,
1482 use_modal_editing: mode == EditorMode::Full,
1483 read_only: false,
1484 use_autoclose: true,
1485 use_auto_surround: true,
1486 auto_replace_emoji_shortcode: false,
1487 jsx_tag_auto_close_enabled_in_any_buffer: false,
1488 leader_peer_id: None,
1489 remote_id: None,
1490 hover_state: Default::default(),
1491 pending_mouse_down: None,
1492 hovered_link_state: Default::default(),
1493 edit_prediction_provider: None,
1494 active_inline_completion: None,
1495 stale_inline_completion_in_menu: None,
1496 edit_prediction_preview: EditPredictionPreview::Inactive {
1497 released_too_fast: false,
1498 },
1499 inline_diagnostics_enabled: mode == EditorMode::Full,
1500 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1501
1502 gutter_hovered: false,
1503 pixel_position_of_newest_cursor: None,
1504 last_bounds: None,
1505 last_position_map: None,
1506 expect_bounds_change: None,
1507 gutter_dimensions: GutterDimensions::default(),
1508 style: None,
1509 show_cursor_names: false,
1510 hovered_cursors: Default::default(),
1511 next_editor_action_id: EditorActionId::default(),
1512 editor_actions: Rc::default(),
1513 inline_completions_hidden_for_vim_mode: false,
1514 show_inline_completions_override: None,
1515 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1516 edit_prediction_settings: EditPredictionSettings::Disabled,
1517 edit_prediction_indent_conflict: false,
1518 edit_prediction_requires_modifier_in_indent_conflict: true,
1519 custom_context_menu: None,
1520 show_git_blame_gutter: false,
1521 show_git_blame_inline: false,
1522 show_selection_menu: None,
1523 show_git_blame_inline_delay_task: None,
1524 git_blame_inline_tooltip: None,
1525 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1526 serialize_dirty_buffers: ProjectSettings::get_global(cx)
1527 .session
1528 .restore_unsaved_buffers,
1529 blame: None,
1530 blame_subscription: None,
1531 tasks: Default::default(),
1532
1533 breakpoint_store,
1534 gutter_breakpoint_indicator: None,
1535 _subscriptions: vec![
1536 cx.observe(&buffer, Self::on_buffer_changed),
1537 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1538 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1539 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1540 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1541 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1542 cx.observe_window_activation(window, |editor, window, cx| {
1543 let active = window.is_window_active();
1544 editor.blink_manager.update(cx, |blink_manager, cx| {
1545 if active {
1546 blink_manager.enable(cx);
1547 } else {
1548 blink_manager.disable(cx);
1549 }
1550 });
1551 }),
1552 ],
1553 tasks_update_task: None,
1554 linked_edit_ranges: Default::default(),
1555 in_project_search: false,
1556 previous_search_ranges: None,
1557 breadcrumb_header: None,
1558 focused_block: None,
1559 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1560 addons: HashMap::default(),
1561 registered_buffers: HashMap::default(),
1562 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1563 selection_mark_mode: false,
1564 toggle_fold_multiple_buffers: Task::ready(()),
1565 serialize_selections: Task::ready(()),
1566 serialize_folds: Task::ready(()),
1567 text_style_refinement: None,
1568 load_diff_task: load_uncommitted_diff,
1569 };
1570 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1571 this._subscriptions
1572 .push(cx.observe(breakpoints, |_, _, cx| {
1573 cx.notify();
1574 }));
1575 }
1576 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1577 this._subscriptions.extend(project_subscriptions);
1578
1579 this.end_selection(window, cx);
1580 this.scroll_manager.show_scrollbars(window, cx);
1581 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1582
1583 if mode == EditorMode::Full {
1584 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1585 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1586
1587 if this.git_blame_inline_enabled {
1588 this.git_blame_inline_enabled = true;
1589 this.start_git_blame_inline(false, window, cx);
1590 }
1591
1592 this.go_to_active_debug_line(window, cx);
1593
1594 if let Some(buffer) = buffer.read(cx).as_singleton() {
1595 if let Some(project) = this.project.as_ref() {
1596 let handle = project.update(cx, |project, cx| {
1597 project.register_buffer_with_language_servers(&buffer, cx)
1598 });
1599 this.registered_buffers
1600 .insert(buffer.read(cx).remote_id(), handle);
1601 }
1602 }
1603 }
1604
1605 this.report_editor_event("Editor Opened", None, cx);
1606 this
1607 }
1608
1609 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
1610 self.mouse_context_menu
1611 .as_ref()
1612 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
1613 }
1614
1615 fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
1616 self.key_context_internal(self.has_active_inline_completion(), window, cx)
1617 }
1618
1619 fn key_context_internal(
1620 &self,
1621 has_active_edit_prediction: bool,
1622 window: &Window,
1623 cx: &App,
1624 ) -> KeyContext {
1625 let mut key_context = KeyContext::new_with_defaults();
1626 key_context.add("Editor");
1627 let mode = match self.mode {
1628 EditorMode::SingleLine { .. } => "single_line",
1629 EditorMode::AutoHeight { .. } => "auto_height",
1630 EditorMode::Full => "full",
1631 };
1632
1633 if EditorSettings::jupyter_enabled(cx) {
1634 key_context.add("jupyter");
1635 }
1636
1637 key_context.set("mode", mode);
1638 if self.pending_rename.is_some() {
1639 key_context.add("renaming");
1640 }
1641
1642 match self.context_menu.borrow().as_ref() {
1643 Some(CodeContextMenu::Completions(_)) => {
1644 key_context.add("menu");
1645 key_context.add("showing_completions");
1646 }
1647 Some(CodeContextMenu::CodeActions(_)) => {
1648 key_context.add("menu");
1649 key_context.add("showing_code_actions")
1650 }
1651 None => {}
1652 }
1653
1654 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
1655 if !self.focus_handle(cx).contains_focused(window, cx)
1656 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
1657 {
1658 for addon in self.addons.values() {
1659 addon.extend_key_context(&mut key_context, cx)
1660 }
1661 }
1662
1663 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
1664 if let Some(extension) = singleton_buffer
1665 .read(cx)
1666 .file()
1667 .and_then(|file| file.path().extension()?.to_str())
1668 {
1669 key_context.set("extension", extension.to_string());
1670 }
1671 } else {
1672 key_context.add("multibuffer");
1673 }
1674
1675 if has_active_edit_prediction {
1676 if self.edit_prediction_in_conflict() {
1677 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
1678 } else {
1679 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
1680 key_context.add("copilot_suggestion");
1681 }
1682 }
1683
1684 if self.selection_mark_mode {
1685 key_context.add("selection_mode");
1686 }
1687
1688 key_context
1689 }
1690
1691 pub fn edit_prediction_in_conflict(&self) -> bool {
1692 if !self.show_edit_predictions_in_menu() {
1693 return false;
1694 }
1695
1696 let showing_completions = self
1697 .context_menu
1698 .borrow()
1699 .as_ref()
1700 .map_or(false, |context| {
1701 matches!(context, CodeContextMenu::Completions(_))
1702 });
1703
1704 showing_completions
1705 || self.edit_prediction_requires_modifier()
1706 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
1707 // bindings to insert tab characters.
1708 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
1709 }
1710
1711 pub fn accept_edit_prediction_keybind(
1712 &self,
1713 window: &Window,
1714 cx: &App,
1715 ) -> AcceptEditPredictionBinding {
1716 let key_context = self.key_context_internal(true, window, cx);
1717 let in_conflict = self.edit_prediction_in_conflict();
1718
1719 AcceptEditPredictionBinding(
1720 window
1721 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
1722 .into_iter()
1723 .filter(|binding| {
1724 !in_conflict
1725 || binding
1726 .keystrokes()
1727 .first()
1728 .map_or(false, |keystroke| keystroke.modifiers.modified())
1729 })
1730 .rev()
1731 .min_by_key(|binding| {
1732 binding
1733 .keystrokes()
1734 .first()
1735 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
1736 }),
1737 )
1738 }
1739
1740 pub fn new_file(
1741 workspace: &mut Workspace,
1742 _: &workspace::NewFile,
1743 window: &mut Window,
1744 cx: &mut Context<Workspace>,
1745 ) {
1746 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
1747 "Failed to create buffer",
1748 window,
1749 cx,
1750 |e, _, _| match e.error_code() {
1751 ErrorCode::RemoteUpgradeRequired => Some(format!(
1752 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1753 e.error_tag("required").unwrap_or("the latest version")
1754 )),
1755 _ => None,
1756 },
1757 );
1758 }
1759
1760 pub fn new_in_workspace(
1761 workspace: &mut Workspace,
1762 window: &mut Window,
1763 cx: &mut Context<Workspace>,
1764 ) -> Task<Result<Entity<Editor>>> {
1765 let project = workspace.project().clone();
1766 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1767
1768 cx.spawn_in(window, async move |workspace, cx| {
1769 let buffer = create.await?;
1770 workspace.update_in(cx, |workspace, window, cx| {
1771 let editor =
1772 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
1773 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
1774 editor
1775 })
1776 })
1777 }
1778
1779 fn new_file_vertical(
1780 workspace: &mut Workspace,
1781 _: &workspace::NewFileSplitVertical,
1782 window: &mut Window,
1783 cx: &mut Context<Workspace>,
1784 ) {
1785 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
1786 }
1787
1788 fn new_file_horizontal(
1789 workspace: &mut Workspace,
1790 _: &workspace::NewFileSplitHorizontal,
1791 window: &mut Window,
1792 cx: &mut Context<Workspace>,
1793 ) {
1794 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
1795 }
1796
1797 fn new_file_in_direction(
1798 workspace: &mut Workspace,
1799 direction: SplitDirection,
1800 window: &mut Window,
1801 cx: &mut Context<Workspace>,
1802 ) {
1803 let project = workspace.project().clone();
1804 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1805
1806 cx.spawn_in(window, async move |workspace, cx| {
1807 let buffer = create.await?;
1808 workspace.update_in(cx, move |workspace, window, cx| {
1809 workspace.split_item(
1810 direction,
1811 Box::new(
1812 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
1813 ),
1814 window,
1815 cx,
1816 )
1817 })?;
1818 anyhow::Ok(())
1819 })
1820 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
1821 match e.error_code() {
1822 ErrorCode::RemoteUpgradeRequired => Some(format!(
1823 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1824 e.error_tag("required").unwrap_or("the latest version")
1825 )),
1826 _ => None,
1827 }
1828 });
1829 }
1830
1831 pub fn leader_peer_id(&self) -> Option<PeerId> {
1832 self.leader_peer_id
1833 }
1834
1835 pub fn buffer(&self) -> &Entity<MultiBuffer> {
1836 &self.buffer
1837 }
1838
1839 pub fn workspace(&self) -> Option<Entity<Workspace>> {
1840 self.workspace.as_ref()?.0.upgrade()
1841 }
1842
1843 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
1844 self.buffer().read(cx).title(cx)
1845 }
1846
1847 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
1848 let git_blame_gutter_max_author_length = self
1849 .render_git_blame_gutter(cx)
1850 .then(|| {
1851 if let Some(blame) = self.blame.as_ref() {
1852 let max_author_length =
1853 blame.update(cx, |blame, cx| blame.max_author_length(cx));
1854 Some(max_author_length)
1855 } else {
1856 None
1857 }
1858 })
1859 .flatten();
1860
1861 EditorSnapshot {
1862 mode: self.mode,
1863 show_gutter: self.show_gutter,
1864 show_line_numbers: self.show_line_numbers,
1865 show_git_diff_gutter: self.show_git_diff_gutter,
1866 show_code_actions: self.show_code_actions,
1867 show_runnables: self.show_runnables,
1868 show_breakpoints: self.show_breakpoints,
1869 git_blame_gutter_max_author_length,
1870 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
1871 scroll_anchor: self.scroll_manager.anchor(),
1872 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
1873 placeholder_text: self.placeholder_text.clone(),
1874 is_focused: self.focus_handle.is_focused(window),
1875 current_line_highlight: self
1876 .current_line_highlight
1877 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
1878 gutter_hovered: self.gutter_hovered,
1879 }
1880 }
1881
1882 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
1883 self.buffer.read(cx).language_at(point, cx)
1884 }
1885
1886 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
1887 self.buffer.read(cx).read(cx).file_at(point).cloned()
1888 }
1889
1890 pub fn active_excerpt(
1891 &self,
1892 cx: &App,
1893 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
1894 self.buffer
1895 .read(cx)
1896 .excerpt_containing(self.selections.newest_anchor().head(), cx)
1897 }
1898
1899 pub fn mode(&self) -> EditorMode {
1900 self.mode
1901 }
1902
1903 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
1904 self.collaboration_hub.as_deref()
1905 }
1906
1907 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
1908 self.collaboration_hub = Some(hub);
1909 }
1910
1911 pub fn set_in_project_search(&mut self, in_project_search: bool) {
1912 self.in_project_search = in_project_search;
1913 }
1914
1915 pub fn set_custom_context_menu(
1916 &mut self,
1917 f: impl 'static
1918 + Fn(
1919 &mut Self,
1920 DisplayPoint,
1921 &mut Window,
1922 &mut Context<Self>,
1923 ) -> Option<Entity<ui::ContextMenu>>,
1924 ) {
1925 self.custom_context_menu = Some(Box::new(f))
1926 }
1927
1928 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
1929 self.completion_provider = provider;
1930 }
1931
1932 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
1933 self.semantics_provider.clone()
1934 }
1935
1936 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
1937 self.semantics_provider = provider;
1938 }
1939
1940 pub fn set_edit_prediction_provider<T>(
1941 &mut self,
1942 provider: Option<Entity<T>>,
1943 window: &mut Window,
1944 cx: &mut Context<Self>,
1945 ) where
1946 T: EditPredictionProvider,
1947 {
1948 self.edit_prediction_provider =
1949 provider.map(|provider| RegisteredInlineCompletionProvider {
1950 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
1951 if this.focus_handle.is_focused(window) {
1952 this.update_visible_inline_completion(window, cx);
1953 }
1954 }),
1955 provider: Arc::new(provider),
1956 });
1957 self.update_edit_prediction_settings(cx);
1958 self.refresh_inline_completion(false, false, window, cx);
1959 }
1960
1961 pub fn placeholder_text(&self) -> Option<&str> {
1962 self.placeholder_text.as_deref()
1963 }
1964
1965 pub fn set_placeholder_text(
1966 &mut self,
1967 placeholder_text: impl Into<Arc<str>>,
1968 cx: &mut Context<Self>,
1969 ) {
1970 let placeholder_text = Some(placeholder_text.into());
1971 if self.placeholder_text != placeholder_text {
1972 self.placeholder_text = placeholder_text;
1973 cx.notify();
1974 }
1975 }
1976
1977 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
1978 self.cursor_shape = cursor_shape;
1979
1980 // Disrupt blink for immediate user feedback that the cursor shape has changed
1981 self.blink_manager.update(cx, BlinkManager::show_cursor);
1982
1983 cx.notify();
1984 }
1985
1986 pub fn set_current_line_highlight(
1987 &mut self,
1988 current_line_highlight: Option<CurrentLineHighlight>,
1989 ) {
1990 self.current_line_highlight = current_line_highlight;
1991 }
1992
1993 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
1994 self.collapse_matches = collapse_matches;
1995 }
1996
1997 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
1998 let buffers = self.buffer.read(cx).all_buffers();
1999 let Some(project) = self.project.as_ref() else {
2000 return;
2001 };
2002 project.update(cx, |project, cx| {
2003 for buffer in buffers {
2004 self.registered_buffers
2005 .entry(buffer.read(cx).remote_id())
2006 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2007 }
2008 })
2009 }
2010
2011 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2012 if self.collapse_matches {
2013 return range.start..range.start;
2014 }
2015 range.clone()
2016 }
2017
2018 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2019 if self.display_map.read(cx).clip_at_line_ends != clip {
2020 self.display_map
2021 .update(cx, |map, _| map.clip_at_line_ends = clip);
2022 }
2023 }
2024
2025 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2026 self.input_enabled = input_enabled;
2027 }
2028
2029 pub fn set_inline_completions_hidden_for_vim_mode(
2030 &mut self,
2031 hidden: bool,
2032 window: &mut Window,
2033 cx: &mut Context<Self>,
2034 ) {
2035 if hidden != self.inline_completions_hidden_for_vim_mode {
2036 self.inline_completions_hidden_for_vim_mode = hidden;
2037 if hidden {
2038 self.update_visible_inline_completion(window, cx);
2039 } else {
2040 self.refresh_inline_completion(true, false, window, cx);
2041 }
2042 }
2043 }
2044
2045 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2046 self.menu_inline_completions_policy = value;
2047 }
2048
2049 pub fn set_autoindent(&mut self, autoindent: bool) {
2050 if autoindent {
2051 self.autoindent_mode = Some(AutoindentMode::EachLine);
2052 } else {
2053 self.autoindent_mode = None;
2054 }
2055 }
2056
2057 pub fn read_only(&self, cx: &App) -> bool {
2058 self.read_only || self.buffer.read(cx).read_only()
2059 }
2060
2061 pub fn set_read_only(&mut self, read_only: bool) {
2062 self.read_only = read_only;
2063 }
2064
2065 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2066 self.use_autoclose = autoclose;
2067 }
2068
2069 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2070 self.use_auto_surround = auto_surround;
2071 }
2072
2073 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2074 self.auto_replace_emoji_shortcode = auto_replace;
2075 }
2076
2077 pub fn toggle_edit_predictions(
2078 &mut self,
2079 _: &ToggleEditPrediction,
2080 window: &mut Window,
2081 cx: &mut Context<Self>,
2082 ) {
2083 if self.show_inline_completions_override.is_some() {
2084 self.set_show_edit_predictions(None, window, cx);
2085 } else {
2086 let show_edit_predictions = !self.edit_predictions_enabled();
2087 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2088 }
2089 }
2090
2091 pub fn set_show_edit_predictions(
2092 &mut self,
2093 show_edit_predictions: Option<bool>,
2094 window: &mut Window,
2095 cx: &mut Context<Self>,
2096 ) {
2097 self.show_inline_completions_override = show_edit_predictions;
2098 self.update_edit_prediction_settings(cx);
2099
2100 if let Some(false) = show_edit_predictions {
2101 self.discard_inline_completion(false, cx);
2102 } else {
2103 self.refresh_inline_completion(false, true, window, cx);
2104 }
2105 }
2106
2107 fn inline_completions_disabled_in_scope(
2108 &self,
2109 buffer: &Entity<Buffer>,
2110 buffer_position: language::Anchor,
2111 cx: &App,
2112 ) -> bool {
2113 let snapshot = buffer.read(cx).snapshot();
2114 let settings = snapshot.settings_at(buffer_position, cx);
2115
2116 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2117 return false;
2118 };
2119
2120 scope.override_name().map_or(false, |scope_name| {
2121 settings
2122 .edit_predictions_disabled_in
2123 .iter()
2124 .any(|s| s == scope_name)
2125 })
2126 }
2127
2128 pub fn set_use_modal_editing(&mut self, to: bool) {
2129 self.use_modal_editing = to;
2130 }
2131
2132 pub fn use_modal_editing(&self) -> bool {
2133 self.use_modal_editing
2134 }
2135
2136 fn selections_did_change(
2137 &mut self,
2138 local: bool,
2139 old_cursor_position: &Anchor,
2140 show_completions: bool,
2141 window: &mut Window,
2142 cx: &mut Context<Self>,
2143 ) {
2144 window.invalidate_character_coordinates();
2145
2146 // Copy selections to primary selection buffer
2147 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2148 if local {
2149 let selections = self.selections.all::<usize>(cx);
2150 let buffer_handle = self.buffer.read(cx).read(cx);
2151
2152 let mut text = String::new();
2153 for (index, selection) in selections.iter().enumerate() {
2154 let text_for_selection = buffer_handle
2155 .text_for_range(selection.start..selection.end)
2156 .collect::<String>();
2157
2158 text.push_str(&text_for_selection);
2159 if index != selections.len() - 1 {
2160 text.push('\n');
2161 }
2162 }
2163
2164 if !text.is_empty() {
2165 cx.write_to_primary(ClipboardItem::new_string(text));
2166 }
2167 }
2168
2169 if self.focus_handle.is_focused(window) && self.leader_peer_id.is_none() {
2170 self.buffer.update(cx, |buffer, cx| {
2171 buffer.set_active_selections(
2172 &self.selections.disjoint_anchors(),
2173 self.selections.line_mode,
2174 self.cursor_shape,
2175 cx,
2176 )
2177 });
2178 }
2179 let display_map = self
2180 .display_map
2181 .update(cx, |display_map, cx| display_map.snapshot(cx));
2182 let buffer = &display_map.buffer_snapshot;
2183 self.add_selections_state = None;
2184 self.select_next_state = None;
2185 self.select_prev_state = None;
2186 self.select_syntax_node_history.try_clear();
2187 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2188 self.snippet_stack
2189 .invalidate(&self.selections.disjoint_anchors(), buffer);
2190 self.take_rename(false, window, cx);
2191
2192 let new_cursor_position = self.selections.newest_anchor().head();
2193
2194 self.push_to_nav_history(
2195 *old_cursor_position,
2196 Some(new_cursor_position.to_point(buffer)),
2197 false,
2198 cx,
2199 );
2200
2201 if local {
2202 let new_cursor_position = self.selections.newest_anchor().head();
2203 let mut context_menu = self.context_menu.borrow_mut();
2204 let completion_menu = match context_menu.as_ref() {
2205 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2206 _ => {
2207 *context_menu = None;
2208 None
2209 }
2210 };
2211 if let Some(buffer_id) = new_cursor_position.buffer_id {
2212 if !self.registered_buffers.contains_key(&buffer_id) {
2213 if let Some(project) = self.project.as_ref() {
2214 project.update(cx, |project, cx| {
2215 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2216 return;
2217 };
2218 self.registered_buffers.insert(
2219 buffer_id,
2220 project.register_buffer_with_language_servers(&buffer, cx),
2221 );
2222 })
2223 }
2224 }
2225 }
2226
2227 if let Some(completion_menu) = completion_menu {
2228 let cursor_position = new_cursor_position.to_offset(buffer);
2229 let (word_range, kind) =
2230 buffer.surrounding_word(completion_menu.initial_position, true);
2231 if kind == Some(CharKind::Word)
2232 && word_range.to_inclusive().contains(&cursor_position)
2233 {
2234 let mut completion_menu = completion_menu.clone();
2235 drop(context_menu);
2236
2237 let query = Self::completion_query(buffer, cursor_position);
2238 cx.spawn(async move |this, cx| {
2239 completion_menu
2240 .filter(query.as_deref(), cx.background_executor().clone())
2241 .await;
2242
2243 this.update(cx, |this, cx| {
2244 let mut context_menu = this.context_menu.borrow_mut();
2245 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2246 else {
2247 return;
2248 };
2249
2250 if menu.id > completion_menu.id {
2251 return;
2252 }
2253
2254 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2255 drop(context_menu);
2256 cx.notify();
2257 })
2258 })
2259 .detach();
2260
2261 if show_completions {
2262 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2263 }
2264 } else {
2265 drop(context_menu);
2266 self.hide_context_menu(window, cx);
2267 }
2268 } else {
2269 drop(context_menu);
2270 }
2271
2272 hide_hover(self, cx);
2273
2274 if old_cursor_position.to_display_point(&display_map).row()
2275 != new_cursor_position.to_display_point(&display_map).row()
2276 {
2277 self.available_code_actions.take();
2278 }
2279 self.refresh_code_actions(window, cx);
2280 self.refresh_document_highlights(cx);
2281 self.refresh_selected_text_highlights(window, cx);
2282 refresh_matching_bracket_highlights(self, window, cx);
2283 self.update_visible_inline_completion(window, cx);
2284 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2285 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2286 if self.git_blame_inline_enabled {
2287 self.start_inline_blame_timer(window, cx);
2288 }
2289 }
2290
2291 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2292 cx.emit(EditorEvent::SelectionsChanged { local });
2293
2294 let selections = &self.selections.disjoint;
2295 if selections.len() == 1 {
2296 cx.emit(SearchEvent::ActiveMatchChanged)
2297 }
2298 if local
2299 && self.is_singleton(cx)
2300 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
2301 {
2302 if let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) {
2303 let background_executor = cx.background_executor().clone();
2304 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2305 let snapshot = self.buffer().read(cx).snapshot(cx);
2306 let selections = selections.clone();
2307 self.serialize_selections = cx.background_spawn(async move {
2308 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2309 let selections = selections
2310 .iter()
2311 .map(|selection| {
2312 (
2313 selection.start.to_offset(&snapshot),
2314 selection.end.to_offset(&snapshot),
2315 )
2316 })
2317 .collect();
2318
2319 DB.save_editor_selections(editor_id, workspace_id, selections)
2320 .await
2321 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2322 .log_err();
2323 });
2324 }
2325 }
2326
2327 cx.notify();
2328 }
2329
2330 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2331 if !self.is_singleton(cx)
2332 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2333 {
2334 return;
2335 }
2336
2337 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2338 return;
2339 };
2340 let background_executor = cx.background_executor().clone();
2341 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2342 let snapshot = self.buffer().read(cx).snapshot(cx);
2343 let folds = self.display_map.update(cx, |display_map, cx| {
2344 display_map
2345 .snapshot(cx)
2346 .folds_in_range(0..snapshot.len())
2347 .map(|fold| {
2348 (
2349 fold.range.start.to_offset(&snapshot),
2350 fold.range.end.to_offset(&snapshot),
2351 )
2352 })
2353 .collect()
2354 });
2355 self.serialize_folds = cx.background_spawn(async move {
2356 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2357 DB.save_editor_folds(editor_id, workspace_id, folds)
2358 .await
2359 .with_context(|| format!("persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"))
2360 .log_err();
2361 });
2362 }
2363
2364 pub fn sync_selections(
2365 &mut self,
2366 other: Entity<Editor>,
2367 cx: &mut Context<Self>,
2368 ) -> gpui::Subscription {
2369 let other_selections = other.read(cx).selections.disjoint.to_vec();
2370 self.selections.change_with(cx, |selections| {
2371 selections.select_anchors(other_selections);
2372 });
2373
2374 let other_subscription =
2375 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2376 EditorEvent::SelectionsChanged { local: true } => {
2377 let other_selections = other.read(cx).selections.disjoint.to_vec();
2378 if other_selections.is_empty() {
2379 return;
2380 }
2381 this.selections.change_with(cx, |selections| {
2382 selections.select_anchors(other_selections);
2383 });
2384 }
2385 _ => {}
2386 });
2387
2388 let this_subscription =
2389 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2390 EditorEvent::SelectionsChanged { local: true } => {
2391 let these_selections = this.selections.disjoint.to_vec();
2392 if these_selections.is_empty() {
2393 return;
2394 }
2395 other.update(cx, |other_editor, cx| {
2396 other_editor.selections.change_with(cx, |selections| {
2397 selections.select_anchors(these_selections);
2398 })
2399 });
2400 }
2401 _ => {}
2402 });
2403
2404 Subscription::join(other_subscription, this_subscription)
2405 }
2406
2407 pub fn change_selections<R>(
2408 &mut self,
2409 autoscroll: Option<Autoscroll>,
2410 window: &mut Window,
2411 cx: &mut Context<Self>,
2412 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2413 ) -> R {
2414 self.change_selections_inner(autoscroll, true, window, cx, change)
2415 }
2416
2417 fn change_selections_inner<R>(
2418 &mut self,
2419 autoscroll: Option<Autoscroll>,
2420 request_completions: bool,
2421 window: &mut Window,
2422 cx: &mut Context<Self>,
2423 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2424 ) -> R {
2425 let old_cursor_position = self.selections.newest_anchor().head();
2426 self.push_to_selection_history();
2427
2428 let (changed, result) = self.selections.change_with(cx, change);
2429
2430 if changed {
2431 if let Some(autoscroll) = autoscroll {
2432 self.request_autoscroll(autoscroll, cx);
2433 }
2434 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2435
2436 if self.should_open_signature_help_automatically(
2437 &old_cursor_position,
2438 self.signature_help_state.backspace_pressed(),
2439 cx,
2440 ) {
2441 self.show_signature_help(&ShowSignatureHelp, window, cx);
2442 }
2443 self.signature_help_state.set_backspace_pressed(false);
2444 }
2445
2446 result
2447 }
2448
2449 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2450 where
2451 I: IntoIterator<Item = (Range<S>, T)>,
2452 S: ToOffset,
2453 T: Into<Arc<str>>,
2454 {
2455 if self.read_only(cx) {
2456 return;
2457 }
2458
2459 self.buffer
2460 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2461 }
2462
2463 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2464 where
2465 I: IntoIterator<Item = (Range<S>, T)>,
2466 S: ToOffset,
2467 T: Into<Arc<str>>,
2468 {
2469 if self.read_only(cx) {
2470 return;
2471 }
2472
2473 self.buffer.update(cx, |buffer, cx| {
2474 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2475 });
2476 }
2477
2478 pub fn edit_with_block_indent<I, S, T>(
2479 &mut self,
2480 edits: I,
2481 original_indent_columns: Vec<Option<u32>>,
2482 cx: &mut Context<Self>,
2483 ) where
2484 I: IntoIterator<Item = (Range<S>, T)>,
2485 S: ToOffset,
2486 T: Into<Arc<str>>,
2487 {
2488 if self.read_only(cx) {
2489 return;
2490 }
2491
2492 self.buffer.update(cx, |buffer, cx| {
2493 buffer.edit(
2494 edits,
2495 Some(AutoindentMode::Block {
2496 original_indent_columns,
2497 }),
2498 cx,
2499 )
2500 });
2501 }
2502
2503 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2504 self.hide_context_menu(window, cx);
2505
2506 match phase {
2507 SelectPhase::Begin {
2508 position,
2509 add,
2510 click_count,
2511 } => self.begin_selection(position, add, click_count, window, cx),
2512 SelectPhase::BeginColumnar {
2513 position,
2514 goal_column,
2515 reset,
2516 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
2517 SelectPhase::Extend {
2518 position,
2519 click_count,
2520 } => self.extend_selection(position, click_count, window, cx),
2521 SelectPhase::Update {
2522 position,
2523 goal_column,
2524 scroll_delta,
2525 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
2526 SelectPhase::End => self.end_selection(window, cx),
2527 }
2528 }
2529
2530 fn extend_selection(
2531 &mut self,
2532 position: DisplayPoint,
2533 click_count: usize,
2534 window: &mut Window,
2535 cx: &mut Context<Self>,
2536 ) {
2537 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2538 let tail = self.selections.newest::<usize>(cx).tail();
2539 self.begin_selection(position, false, click_count, window, cx);
2540
2541 let position = position.to_offset(&display_map, Bias::Left);
2542 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
2543
2544 let mut pending_selection = self
2545 .selections
2546 .pending_anchor()
2547 .expect("extend_selection not called with pending selection");
2548 if position >= tail {
2549 pending_selection.start = tail_anchor;
2550 } else {
2551 pending_selection.end = tail_anchor;
2552 pending_selection.reversed = true;
2553 }
2554
2555 let mut pending_mode = self.selections.pending_mode().unwrap();
2556 match &mut pending_mode {
2557 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
2558 _ => {}
2559 }
2560
2561 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
2562 s.set_pending(pending_selection, pending_mode)
2563 });
2564 }
2565
2566 fn begin_selection(
2567 &mut self,
2568 position: DisplayPoint,
2569 add: bool,
2570 click_count: usize,
2571 window: &mut Window,
2572 cx: &mut Context<Self>,
2573 ) {
2574 if !self.focus_handle.is_focused(window) {
2575 self.last_focused_descendant = None;
2576 window.focus(&self.focus_handle);
2577 }
2578
2579 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2580 let buffer = &display_map.buffer_snapshot;
2581 let newest_selection = self.selections.newest_anchor().clone();
2582 let position = display_map.clip_point(position, Bias::Left);
2583
2584 let start;
2585 let end;
2586 let mode;
2587 let mut auto_scroll;
2588 match click_count {
2589 1 => {
2590 start = buffer.anchor_before(position.to_point(&display_map));
2591 end = start;
2592 mode = SelectMode::Character;
2593 auto_scroll = true;
2594 }
2595 2 => {
2596 let range = movement::surrounding_word(&display_map, position);
2597 start = buffer.anchor_before(range.start.to_point(&display_map));
2598 end = buffer.anchor_before(range.end.to_point(&display_map));
2599 mode = SelectMode::Word(start..end);
2600 auto_scroll = true;
2601 }
2602 3 => {
2603 let position = display_map
2604 .clip_point(position, Bias::Left)
2605 .to_point(&display_map);
2606 let line_start = display_map.prev_line_boundary(position).0;
2607 let next_line_start = buffer.clip_point(
2608 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2609 Bias::Left,
2610 );
2611 start = buffer.anchor_before(line_start);
2612 end = buffer.anchor_before(next_line_start);
2613 mode = SelectMode::Line(start..end);
2614 auto_scroll = true;
2615 }
2616 _ => {
2617 start = buffer.anchor_before(0);
2618 end = buffer.anchor_before(buffer.len());
2619 mode = SelectMode::All;
2620 auto_scroll = false;
2621 }
2622 }
2623 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
2624
2625 let point_to_delete: Option<usize> = {
2626 let selected_points: Vec<Selection<Point>> =
2627 self.selections.disjoint_in_range(start..end, cx);
2628
2629 if !add || click_count > 1 {
2630 None
2631 } else if !selected_points.is_empty() {
2632 Some(selected_points[0].id)
2633 } else {
2634 let clicked_point_already_selected =
2635 self.selections.disjoint.iter().find(|selection| {
2636 selection.start.to_point(buffer) == start.to_point(buffer)
2637 || selection.end.to_point(buffer) == end.to_point(buffer)
2638 });
2639
2640 clicked_point_already_selected.map(|selection| selection.id)
2641 }
2642 };
2643
2644 let selections_count = self.selections.count();
2645
2646 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
2647 if let Some(point_to_delete) = point_to_delete {
2648 s.delete(point_to_delete);
2649
2650 if selections_count == 1 {
2651 s.set_pending_anchor_range(start..end, mode);
2652 }
2653 } else {
2654 if !add {
2655 s.clear_disjoint();
2656 } else if click_count > 1 {
2657 s.delete(newest_selection.id)
2658 }
2659
2660 s.set_pending_anchor_range(start..end, mode);
2661 }
2662 });
2663 }
2664
2665 fn begin_columnar_selection(
2666 &mut self,
2667 position: DisplayPoint,
2668 goal_column: u32,
2669 reset: bool,
2670 window: &mut Window,
2671 cx: &mut Context<Self>,
2672 ) {
2673 if !self.focus_handle.is_focused(window) {
2674 self.last_focused_descendant = None;
2675 window.focus(&self.focus_handle);
2676 }
2677
2678 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2679
2680 if reset {
2681 let pointer_position = display_map
2682 .buffer_snapshot
2683 .anchor_before(position.to_point(&display_map));
2684
2685 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
2686 s.clear_disjoint();
2687 s.set_pending_anchor_range(
2688 pointer_position..pointer_position,
2689 SelectMode::Character,
2690 );
2691 });
2692 }
2693
2694 let tail = self.selections.newest::<Point>(cx).tail();
2695 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
2696
2697 if !reset {
2698 self.select_columns(
2699 tail.to_display_point(&display_map),
2700 position,
2701 goal_column,
2702 &display_map,
2703 window,
2704 cx,
2705 );
2706 }
2707 }
2708
2709 fn update_selection(
2710 &mut self,
2711 position: DisplayPoint,
2712 goal_column: u32,
2713 scroll_delta: gpui::Point<f32>,
2714 window: &mut Window,
2715 cx: &mut Context<Self>,
2716 ) {
2717 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2718
2719 if let Some(tail) = self.columnar_selection_tail.as_ref() {
2720 let tail = tail.to_display_point(&display_map);
2721 self.select_columns(tail, position, goal_column, &display_map, window, cx);
2722 } else if let Some(mut pending) = self.selections.pending_anchor() {
2723 let buffer = self.buffer.read(cx).snapshot(cx);
2724 let head;
2725 let tail;
2726 let mode = self.selections.pending_mode().unwrap();
2727 match &mode {
2728 SelectMode::Character => {
2729 head = position.to_point(&display_map);
2730 tail = pending.tail().to_point(&buffer);
2731 }
2732 SelectMode::Word(original_range) => {
2733 let original_display_range = original_range.start.to_display_point(&display_map)
2734 ..original_range.end.to_display_point(&display_map);
2735 let original_buffer_range = original_display_range.start.to_point(&display_map)
2736 ..original_display_range.end.to_point(&display_map);
2737 if movement::is_inside_word(&display_map, position)
2738 || original_display_range.contains(&position)
2739 {
2740 let word_range = movement::surrounding_word(&display_map, position);
2741 if word_range.start < original_display_range.start {
2742 head = word_range.start.to_point(&display_map);
2743 } else {
2744 head = word_range.end.to_point(&display_map);
2745 }
2746 } else {
2747 head = position.to_point(&display_map);
2748 }
2749
2750 if head <= original_buffer_range.start {
2751 tail = original_buffer_range.end;
2752 } else {
2753 tail = original_buffer_range.start;
2754 }
2755 }
2756 SelectMode::Line(original_range) => {
2757 let original_range = original_range.to_point(&display_map.buffer_snapshot);
2758
2759 let position = display_map
2760 .clip_point(position, Bias::Left)
2761 .to_point(&display_map);
2762 let line_start = display_map.prev_line_boundary(position).0;
2763 let next_line_start = buffer.clip_point(
2764 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2765 Bias::Left,
2766 );
2767
2768 if line_start < original_range.start {
2769 head = line_start
2770 } else {
2771 head = next_line_start
2772 }
2773
2774 if head <= original_range.start {
2775 tail = original_range.end;
2776 } else {
2777 tail = original_range.start;
2778 }
2779 }
2780 SelectMode::All => {
2781 return;
2782 }
2783 };
2784
2785 if head < tail {
2786 pending.start = buffer.anchor_before(head);
2787 pending.end = buffer.anchor_before(tail);
2788 pending.reversed = true;
2789 } else {
2790 pending.start = buffer.anchor_before(tail);
2791 pending.end = buffer.anchor_before(head);
2792 pending.reversed = false;
2793 }
2794
2795 self.change_selections(None, window, cx, |s| {
2796 s.set_pending(pending, mode);
2797 });
2798 } else {
2799 log::error!("update_selection dispatched with no pending selection");
2800 return;
2801 }
2802
2803 self.apply_scroll_delta(scroll_delta, window, cx);
2804 cx.notify();
2805 }
2806
2807 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2808 self.columnar_selection_tail.take();
2809 if self.selections.pending_anchor().is_some() {
2810 let selections = self.selections.all::<usize>(cx);
2811 self.change_selections(None, window, cx, |s| {
2812 s.select(selections);
2813 s.clear_pending();
2814 });
2815 }
2816 }
2817
2818 fn select_columns(
2819 &mut self,
2820 tail: DisplayPoint,
2821 head: DisplayPoint,
2822 goal_column: u32,
2823 display_map: &DisplaySnapshot,
2824 window: &mut Window,
2825 cx: &mut Context<Self>,
2826 ) {
2827 let start_row = cmp::min(tail.row(), head.row());
2828 let end_row = cmp::max(tail.row(), head.row());
2829 let start_column = cmp::min(tail.column(), goal_column);
2830 let end_column = cmp::max(tail.column(), goal_column);
2831 let reversed = start_column < tail.column();
2832
2833 let selection_ranges = (start_row.0..=end_row.0)
2834 .map(DisplayRow)
2835 .filter_map(|row| {
2836 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
2837 let start = display_map
2838 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
2839 .to_point(display_map);
2840 let end = display_map
2841 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
2842 .to_point(display_map);
2843 if reversed {
2844 Some(end..start)
2845 } else {
2846 Some(start..end)
2847 }
2848 } else {
2849 None
2850 }
2851 })
2852 .collect::<Vec<_>>();
2853
2854 self.change_selections(None, window, cx, |s| {
2855 s.select_ranges(selection_ranges);
2856 });
2857 cx.notify();
2858 }
2859
2860 pub fn has_pending_nonempty_selection(&self) -> bool {
2861 let pending_nonempty_selection = match self.selections.pending_anchor() {
2862 Some(Selection { start, end, .. }) => start != end,
2863 None => false,
2864 };
2865
2866 pending_nonempty_selection
2867 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
2868 }
2869
2870 pub fn has_pending_selection(&self) -> bool {
2871 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
2872 }
2873
2874 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
2875 self.selection_mark_mode = false;
2876
2877 if self.clear_expanded_diff_hunks(cx) {
2878 cx.notify();
2879 return;
2880 }
2881 if self.dismiss_menus_and_popups(true, window, cx) {
2882 return;
2883 }
2884
2885 if self.mode == EditorMode::Full
2886 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
2887 {
2888 return;
2889 }
2890
2891 cx.propagate();
2892 }
2893
2894 pub fn dismiss_menus_and_popups(
2895 &mut self,
2896 is_user_requested: bool,
2897 window: &mut Window,
2898 cx: &mut Context<Self>,
2899 ) -> bool {
2900 if self.take_rename(false, window, cx).is_some() {
2901 return true;
2902 }
2903
2904 if hide_hover(self, cx) {
2905 return true;
2906 }
2907
2908 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
2909 return true;
2910 }
2911
2912 if self.hide_context_menu(window, cx).is_some() {
2913 return true;
2914 }
2915
2916 if self.mouse_context_menu.take().is_some() {
2917 return true;
2918 }
2919
2920 if is_user_requested && self.discard_inline_completion(true, cx) {
2921 return true;
2922 }
2923
2924 if self.snippet_stack.pop().is_some() {
2925 return true;
2926 }
2927
2928 if self.mode == EditorMode::Full && self.active_diagnostics.is_some() {
2929 self.dismiss_diagnostics(cx);
2930 return true;
2931 }
2932
2933 false
2934 }
2935
2936 fn linked_editing_ranges_for(
2937 &self,
2938 selection: Range<text::Anchor>,
2939 cx: &App,
2940 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
2941 if self.linked_edit_ranges.is_empty() {
2942 return None;
2943 }
2944 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
2945 selection.end.buffer_id.and_then(|end_buffer_id| {
2946 if selection.start.buffer_id != Some(end_buffer_id) {
2947 return None;
2948 }
2949 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
2950 let snapshot = buffer.read(cx).snapshot();
2951 self.linked_edit_ranges
2952 .get(end_buffer_id, selection.start..selection.end, &snapshot)
2953 .map(|ranges| (ranges, snapshot, buffer))
2954 })?;
2955 use text::ToOffset as TO;
2956 // find offset from the start of current range to current cursor position
2957 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
2958
2959 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
2960 let start_difference = start_offset - start_byte_offset;
2961 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
2962 let end_difference = end_offset - start_byte_offset;
2963 // Current range has associated linked ranges.
2964 let mut linked_edits = HashMap::<_, Vec<_>>::default();
2965 for range in linked_ranges.iter() {
2966 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
2967 let end_offset = start_offset + end_difference;
2968 let start_offset = start_offset + start_difference;
2969 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
2970 continue;
2971 }
2972 if self.selections.disjoint_anchor_ranges().any(|s| {
2973 if s.start.buffer_id != selection.start.buffer_id
2974 || s.end.buffer_id != selection.end.buffer_id
2975 {
2976 return false;
2977 }
2978 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
2979 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
2980 }) {
2981 continue;
2982 }
2983 let start = buffer_snapshot.anchor_after(start_offset);
2984 let end = buffer_snapshot.anchor_after(end_offset);
2985 linked_edits
2986 .entry(buffer.clone())
2987 .or_default()
2988 .push(start..end);
2989 }
2990 Some(linked_edits)
2991 }
2992
2993 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
2994 let text: Arc<str> = text.into();
2995
2996 if self.read_only(cx) {
2997 return;
2998 }
2999
3000 let selections = self.selections.all_adjusted(cx);
3001 let mut bracket_inserted = false;
3002 let mut edits = Vec::new();
3003 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3004 let mut new_selections = Vec::with_capacity(selections.len());
3005 let mut new_autoclose_regions = Vec::new();
3006 let snapshot = self.buffer.read(cx).read(cx);
3007
3008 for (selection, autoclose_region) in
3009 self.selections_with_autoclose_regions(selections, &snapshot)
3010 {
3011 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3012 // Determine if the inserted text matches the opening or closing
3013 // bracket of any of this language's bracket pairs.
3014 let mut bracket_pair = None;
3015 let mut is_bracket_pair_start = false;
3016 let mut is_bracket_pair_end = false;
3017 if !text.is_empty() {
3018 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3019 // and they are removing the character that triggered IME popup.
3020 for (pair, enabled) in scope.brackets() {
3021 if !pair.close && !pair.surround {
3022 continue;
3023 }
3024
3025 if enabled && pair.start.ends_with(text.as_ref()) {
3026 let prefix_len = pair.start.len() - text.len();
3027 let preceding_text_matches_prefix = prefix_len == 0
3028 || (selection.start.column >= (prefix_len as u32)
3029 && snapshot.contains_str_at(
3030 Point::new(
3031 selection.start.row,
3032 selection.start.column - (prefix_len as u32),
3033 ),
3034 &pair.start[..prefix_len],
3035 ));
3036 if preceding_text_matches_prefix {
3037 bracket_pair = Some(pair.clone());
3038 is_bracket_pair_start = true;
3039 break;
3040 }
3041 }
3042 if pair.end.as_str() == text.as_ref() {
3043 bracket_pair = Some(pair.clone());
3044 is_bracket_pair_end = true;
3045 break;
3046 }
3047 }
3048 }
3049
3050 if let Some(bracket_pair) = bracket_pair {
3051 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3052 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3053 let auto_surround =
3054 self.use_auto_surround && snapshot_settings.use_auto_surround;
3055 if selection.is_empty() {
3056 if is_bracket_pair_start {
3057 // If the inserted text is a suffix of an opening bracket and the
3058 // selection is preceded by the rest of the opening bracket, then
3059 // insert the closing bracket.
3060 let following_text_allows_autoclose = snapshot
3061 .chars_at(selection.start)
3062 .next()
3063 .map_or(true, |c| scope.should_autoclose_before(c));
3064
3065 let preceding_text_allows_autoclose = selection.start.column == 0
3066 || snapshot.reversed_chars_at(selection.start).next().map_or(
3067 true,
3068 |c| {
3069 bracket_pair.start != bracket_pair.end
3070 || !snapshot
3071 .char_classifier_at(selection.start)
3072 .is_word(c)
3073 },
3074 );
3075
3076 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3077 && bracket_pair.start.len() == 1
3078 {
3079 let target = bracket_pair.start.chars().next().unwrap();
3080 let current_line_count = snapshot
3081 .reversed_chars_at(selection.start)
3082 .take_while(|&c| c != '\n')
3083 .filter(|&c| c == target)
3084 .count();
3085 current_line_count % 2 == 1
3086 } else {
3087 false
3088 };
3089
3090 if autoclose
3091 && bracket_pair.close
3092 && following_text_allows_autoclose
3093 && preceding_text_allows_autoclose
3094 && !is_closing_quote
3095 {
3096 let anchor = snapshot.anchor_before(selection.end);
3097 new_selections.push((selection.map(|_| anchor), text.len()));
3098 new_autoclose_regions.push((
3099 anchor,
3100 text.len(),
3101 selection.id,
3102 bracket_pair.clone(),
3103 ));
3104 edits.push((
3105 selection.range(),
3106 format!("{}{}", text, bracket_pair.end).into(),
3107 ));
3108 bracket_inserted = true;
3109 continue;
3110 }
3111 }
3112
3113 if let Some(region) = autoclose_region {
3114 // If the selection is followed by an auto-inserted closing bracket,
3115 // then don't insert that closing bracket again; just move the selection
3116 // past the closing bracket.
3117 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3118 && text.as_ref() == region.pair.end.as_str();
3119 if should_skip {
3120 let anchor = snapshot.anchor_after(selection.end);
3121 new_selections
3122 .push((selection.map(|_| anchor), region.pair.end.len()));
3123 continue;
3124 }
3125 }
3126
3127 let always_treat_brackets_as_autoclosed = snapshot
3128 .language_settings_at(selection.start, cx)
3129 .always_treat_brackets_as_autoclosed;
3130 if always_treat_brackets_as_autoclosed
3131 && is_bracket_pair_end
3132 && snapshot.contains_str_at(selection.end, text.as_ref())
3133 {
3134 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3135 // and the inserted text is a closing bracket and the selection is followed
3136 // by the closing bracket then move the selection past the closing bracket.
3137 let anchor = snapshot.anchor_after(selection.end);
3138 new_selections.push((selection.map(|_| anchor), text.len()));
3139 continue;
3140 }
3141 }
3142 // If an opening bracket is 1 character long and is typed while
3143 // text is selected, then surround that text with the bracket pair.
3144 else if auto_surround
3145 && bracket_pair.surround
3146 && is_bracket_pair_start
3147 && bracket_pair.start.chars().count() == 1
3148 {
3149 edits.push((selection.start..selection.start, text.clone()));
3150 edits.push((
3151 selection.end..selection.end,
3152 bracket_pair.end.as_str().into(),
3153 ));
3154 bracket_inserted = true;
3155 new_selections.push((
3156 Selection {
3157 id: selection.id,
3158 start: snapshot.anchor_after(selection.start),
3159 end: snapshot.anchor_before(selection.end),
3160 reversed: selection.reversed,
3161 goal: selection.goal,
3162 },
3163 0,
3164 ));
3165 continue;
3166 }
3167 }
3168 }
3169
3170 if self.auto_replace_emoji_shortcode
3171 && selection.is_empty()
3172 && text.as_ref().ends_with(':')
3173 {
3174 if let Some(possible_emoji_short_code) =
3175 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3176 {
3177 if !possible_emoji_short_code.is_empty() {
3178 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3179 let emoji_shortcode_start = Point::new(
3180 selection.start.row,
3181 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3182 );
3183
3184 // Remove shortcode from buffer
3185 edits.push((
3186 emoji_shortcode_start..selection.start,
3187 "".to_string().into(),
3188 ));
3189 new_selections.push((
3190 Selection {
3191 id: selection.id,
3192 start: snapshot.anchor_after(emoji_shortcode_start),
3193 end: snapshot.anchor_before(selection.start),
3194 reversed: selection.reversed,
3195 goal: selection.goal,
3196 },
3197 0,
3198 ));
3199
3200 // Insert emoji
3201 let selection_start_anchor = snapshot.anchor_after(selection.start);
3202 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3203 edits.push((selection.start..selection.end, emoji.to_string().into()));
3204
3205 continue;
3206 }
3207 }
3208 }
3209 }
3210
3211 // If not handling any auto-close operation, then just replace the selected
3212 // text with the given input and move the selection to the end of the
3213 // newly inserted text.
3214 let anchor = snapshot.anchor_after(selection.end);
3215 if !self.linked_edit_ranges.is_empty() {
3216 let start_anchor = snapshot.anchor_before(selection.start);
3217
3218 let is_word_char = text.chars().next().map_or(true, |char| {
3219 let classifier = snapshot.char_classifier_at(start_anchor.to_offset(&snapshot));
3220 classifier.is_word(char)
3221 });
3222
3223 if is_word_char {
3224 if let Some(ranges) = self
3225 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3226 {
3227 for (buffer, edits) in ranges {
3228 linked_edits
3229 .entry(buffer.clone())
3230 .or_default()
3231 .extend(edits.into_iter().map(|range| (range, text.clone())));
3232 }
3233 }
3234 }
3235 }
3236
3237 new_selections.push((selection.map(|_| anchor), 0));
3238 edits.push((selection.start..selection.end, text.clone()));
3239 }
3240
3241 drop(snapshot);
3242
3243 self.transact(window, cx, |this, window, cx| {
3244 let initial_buffer_versions =
3245 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3246
3247 this.buffer.update(cx, |buffer, cx| {
3248 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3249 });
3250 for (buffer, edits) in linked_edits {
3251 buffer.update(cx, |buffer, cx| {
3252 let snapshot = buffer.snapshot();
3253 let edits = edits
3254 .into_iter()
3255 .map(|(range, text)| {
3256 use text::ToPoint as TP;
3257 let end_point = TP::to_point(&range.end, &snapshot);
3258 let start_point = TP::to_point(&range.start, &snapshot);
3259 (start_point..end_point, text)
3260 })
3261 .sorted_by_key(|(range, _)| range.start);
3262 buffer.edit(edits, None, cx);
3263 })
3264 }
3265 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3266 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3267 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3268 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3269 .zip(new_selection_deltas)
3270 .map(|(selection, delta)| Selection {
3271 id: selection.id,
3272 start: selection.start + delta,
3273 end: selection.end + delta,
3274 reversed: selection.reversed,
3275 goal: SelectionGoal::None,
3276 })
3277 .collect::<Vec<_>>();
3278
3279 let mut i = 0;
3280 for (position, delta, selection_id, pair) in new_autoclose_regions {
3281 let position = position.to_offset(&map.buffer_snapshot) + delta;
3282 let start = map.buffer_snapshot.anchor_before(position);
3283 let end = map.buffer_snapshot.anchor_after(position);
3284 while let Some(existing_state) = this.autoclose_regions.get(i) {
3285 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3286 Ordering::Less => i += 1,
3287 Ordering::Greater => break,
3288 Ordering::Equal => {
3289 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3290 Ordering::Less => i += 1,
3291 Ordering::Equal => break,
3292 Ordering::Greater => break,
3293 }
3294 }
3295 }
3296 }
3297 this.autoclose_regions.insert(
3298 i,
3299 AutocloseRegion {
3300 selection_id,
3301 range: start..end,
3302 pair,
3303 },
3304 );
3305 }
3306
3307 let had_active_inline_completion = this.has_active_inline_completion();
3308 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3309 s.select(new_selections)
3310 });
3311
3312 if !bracket_inserted {
3313 if let Some(on_type_format_task) =
3314 this.trigger_on_type_formatting(text.to_string(), window, cx)
3315 {
3316 on_type_format_task.detach_and_log_err(cx);
3317 }
3318 }
3319
3320 let editor_settings = EditorSettings::get_global(cx);
3321 if bracket_inserted
3322 && (editor_settings.auto_signature_help
3323 || editor_settings.show_signature_help_after_edits)
3324 {
3325 this.show_signature_help(&ShowSignatureHelp, window, cx);
3326 }
3327
3328 let trigger_in_words =
3329 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3330 if this.hard_wrap.is_some() {
3331 let latest: Range<Point> = this.selections.newest(cx).range();
3332 if latest.is_empty()
3333 && this
3334 .buffer()
3335 .read(cx)
3336 .snapshot(cx)
3337 .line_len(MultiBufferRow(latest.start.row))
3338 == latest.start.column
3339 {
3340 this.rewrap_impl(
3341 RewrapOptions {
3342 override_language_settings: true,
3343 preserve_existing_whitespace: true,
3344 },
3345 cx,
3346 )
3347 }
3348 }
3349 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3350 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3351 this.refresh_inline_completion(true, false, window, cx);
3352 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3353 });
3354 }
3355
3356 fn find_possible_emoji_shortcode_at_position(
3357 snapshot: &MultiBufferSnapshot,
3358 position: Point,
3359 ) -> Option<String> {
3360 let mut chars = Vec::new();
3361 let mut found_colon = false;
3362 for char in snapshot.reversed_chars_at(position).take(100) {
3363 // Found a possible emoji shortcode in the middle of the buffer
3364 if found_colon {
3365 if char.is_whitespace() {
3366 chars.reverse();
3367 return Some(chars.iter().collect());
3368 }
3369 // If the previous character is not a whitespace, we are in the middle of a word
3370 // and we only want to complete the shortcode if the word is made up of other emojis
3371 let mut containing_word = String::new();
3372 for ch in snapshot
3373 .reversed_chars_at(position)
3374 .skip(chars.len() + 1)
3375 .take(100)
3376 {
3377 if ch.is_whitespace() {
3378 break;
3379 }
3380 containing_word.push(ch);
3381 }
3382 let containing_word = containing_word.chars().rev().collect::<String>();
3383 if util::word_consists_of_emojis(containing_word.as_str()) {
3384 chars.reverse();
3385 return Some(chars.iter().collect());
3386 }
3387 }
3388
3389 if char.is_whitespace() || !char.is_ascii() {
3390 return None;
3391 }
3392 if char == ':' {
3393 found_colon = true;
3394 } else {
3395 chars.push(char);
3396 }
3397 }
3398 // Found a possible emoji shortcode at the beginning of the buffer
3399 chars.reverse();
3400 Some(chars.iter().collect())
3401 }
3402
3403 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3404 self.transact(window, cx, |this, window, cx| {
3405 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
3406 let selections = this.selections.all::<usize>(cx);
3407 let multi_buffer = this.buffer.read(cx);
3408 let buffer = multi_buffer.snapshot(cx);
3409 selections
3410 .iter()
3411 .map(|selection| {
3412 let start_point = selection.start.to_point(&buffer);
3413 let mut indent =
3414 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3415 indent.len = cmp::min(indent.len, start_point.column);
3416 let start = selection.start;
3417 let end = selection.end;
3418 let selection_is_empty = start == end;
3419 let language_scope = buffer.language_scope_at(start);
3420 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
3421 &language_scope
3422 {
3423 let insert_extra_newline =
3424 insert_extra_newline_brackets(&buffer, start..end, language)
3425 || insert_extra_newline_tree_sitter(&buffer, start..end);
3426
3427 // Comment extension on newline is allowed only for cursor selections
3428 let comment_delimiter = maybe!({
3429 if !selection_is_empty {
3430 return None;
3431 }
3432
3433 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3434 return None;
3435 }
3436
3437 let delimiters = language.line_comment_prefixes();
3438 let max_len_of_delimiter =
3439 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3440 let (snapshot, range) =
3441 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3442
3443 let mut index_of_first_non_whitespace = 0;
3444 let comment_candidate = snapshot
3445 .chars_for_range(range)
3446 .skip_while(|c| {
3447 let should_skip = c.is_whitespace();
3448 if should_skip {
3449 index_of_first_non_whitespace += 1;
3450 }
3451 should_skip
3452 })
3453 .take(max_len_of_delimiter)
3454 .collect::<String>();
3455 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3456 comment_candidate.starts_with(comment_prefix.as_ref())
3457 })?;
3458 let cursor_is_placed_after_comment_marker =
3459 index_of_first_non_whitespace + comment_prefix.len()
3460 <= start_point.column as usize;
3461 if cursor_is_placed_after_comment_marker {
3462 Some(comment_prefix.clone())
3463 } else {
3464 None
3465 }
3466 });
3467 (comment_delimiter, insert_extra_newline)
3468 } else {
3469 (None, false)
3470 };
3471
3472 let capacity_for_delimiter = comment_delimiter
3473 .as_deref()
3474 .map(str::len)
3475 .unwrap_or_default();
3476 let mut new_text =
3477 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3478 new_text.push('\n');
3479 new_text.extend(indent.chars());
3480 if let Some(delimiter) = &comment_delimiter {
3481 new_text.push_str(delimiter);
3482 }
3483 if insert_extra_newline {
3484 new_text = new_text.repeat(2);
3485 }
3486
3487 let anchor = buffer.anchor_after(end);
3488 let new_selection = selection.map(|_| anchor);
3489 (
3490 (start..end, new_text),
3491 (insert_extra_newline, new_selection),
3492 )
3493 })
3494 .unzip()
3495 };
3496
3497 this.edit_with_autoindent(edits, cx);
3498 let buffer = this.buffer.read(cx).snapshot(cx);
3499 let new_selections = selection_fixup_info
3500 .into_iter()
3501 .map(|(extra_newline_inserted, new_selection)| {
3502 let mut cursor = new_selection.end.to_point(&buffer);
3503 if extra_newline_inserted {
3504 cursor.row -= 1;
3505 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
3506 }
3507 new_selection.map(|_| cursor)
3508 })
3509 .collect();
3510
3511 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3512 s.select(new_selections)
3513 });
3514 this.refresh_inline_completion(true, false, window, cx);
3515 });
3516 }
3517
3518 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
3519 let buffer = self.buffer.read(cx);
3520 let snapshot = buffer.snapshot(cx);
3521
3522 let mut edits = Vec::new();
3523 let mut rows = Vec::new();
3524
3525 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
3526 let cursor = selection.head();
3527 let row = cursor.row;
3528
3529 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
3530
3531 let newline = "\n".to_string();
3532 edits.push((start_of_line..start_of_line, newline));
3533
3534 rows.push(row + rows_inserted as u32);
3535 }
3536
3537 self.transact(window, cx, |editor, window, cx| {
3538 editor.edit(edits, cx);
3539
3540 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3541 let mut index = 0;
3542 s.move_cursors_with(|map, _, _| {
3543 let row = rows[index];
3544 index += 1;
3545
3546 let point = Point::new(row, 0);
3547 let boundary = map.next_line_boundary(point).1;
3548 let clipped = map.clip_point(boundary, Bias::Left);
3549
3550 (clipped, SelectionGoal::None)
3551 });
3552 });
3553
3554 let mut indent_edits = Vec::new();
3555 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3556 for row in rows {
3557 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3558 for (row, indent) in indents {
3559 if indent.len == 0 {
3560 continue;
3561 }
3562
3563 let text = match indent.kind {
3564 IndentKind::Space => " ".repeat(indent.len as usize),
3565 IndentKind::Tab => "\t".repeat(indent.len as usize),
3566 };
3567 let point = Point::new(row.0, 0);
3568 indent_edits.push((point..point, text));
3569 }
3570 }
3571 editor.edit(indent_edits, cx);
3572 });
3573 }
3574
3575 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
3576 let buffer = self.buffer.read(cx);
3577 let snapshot = buffer.snapshot(cx);
3578
3579 let mut edits = Vec::new();
3580 let mut rows = Vec::new();
3581 let mut rows_inserted = 0;
3582
3583 for selection in self.selections.all_adjusted(cx) {
3584 let cursor = selection.head();
3585 let row = cursor.row;
3586
3587 let point = Point::new(row + 1, 0);
3588 let start_of_line = snapshot.clip_point(point, Bias::Left);
3589
3590 let newline = "\n".to_string();
3591 edits.push((start_of_line..start_of_line, newline));
3592
3593 rows_inserted += 1;
3594 rows.push(row + rows_inserted);
3595 }
3596
3597 self.transact(window, cx, |editor, window, cx| {
3598 editor.edit(edits, cx);
3599
3600 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3601 let mut index = 0;
3602 s.move_cursors_with(|map, _, _| {
3603 let row = rows[index];
3604 index += 1;
3605
3606 let point = Point::new(row, 0);
3607 let boundary = map.next_line_boundary(point).1;
3608 let clipped = map.clip_point(boundary, Bias::Left);
3609
3610 (clipped, SelectionGoal::None)
3611 });
3612 });
3613
3614 let mut indent_edits = Vec::new();
3615 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3616 for row in rows {
3617 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3618 for (row, indent) in indents {
3619 if indent.len == 0 {
3620 continue;
3621 }
3622
3623 let text = match indent.kind {
3624 IndentKind::Space => " ".repeat(indent.len as usize),
3625 IndentKind::Tab => "\t".repeat(indent.len as usize),
3626 };
3627 let point = Point::new(row.0, 0);
3628 indent_edits.push((point..point, text));
3629 }
3630 }
3631 editor.edit(indent_edits, cx);
3632 });
3633 }
3634
3635 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3636 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
3637 original_indent_columns: Vec::new(),
3638 });
3639 self.insert_with_autoindent_mode(text, autoindent, window, cx);
3640 }
3641
3642 fn insert_with_autoindent_mode(
3643 &mut self,
3644 text: &str,
3645 autoindent_mode: Option<AutoindentMode>,
3646 window: &mut Window,
3647 cx: &mut Context<Self>,
3648 ) {
3649 if self.read_only(cx) {
3650 return;
3651 }
3652
3653 let text: Arc<str> = text.into();
3654 self.transact(window, cx, |this, window, cx| {
3655 let old_selections = this.selections.all_adjusted(cx);
3656 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
3657 let anchors = {
3658 let snapshot = buffer.read(cx);
3659 old_selections
3660 .iter()
3661 .map(|s| {
3662 let anchor = snapshot.anchor_after(s.head());
3663 s.map(|_| anchor)
3664 })
3665 .collect::<Vec<_>>()
3666 };
3667 buffer.edit(
3668 old_selections
3669 .iter()
3670 .map(|s| (s.start..s.end, text.clone())),
3671 autoindent_mode,
3672 cx,
3673 );
3674 anchors
3675 });
3676
3677 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3678 s.select_anchors(selection_anchors);
3679 });
3680
3681 cx.notify();
3682 });
3683 }
3684
3685 fn trigger_completion_on_input(
3686 &mut self,
3687 text: &str,
3688 trigger_in_words: bool,
3689 window: &mut Window,
3690 cx: &mut Context<Self>,
3691 ) {
3692 let ignore_completion_provider = self
3693 .context_menu
3694 .borrow()
3695 .as_ref()
3696 .map(|menu| match menu {
3697 CodeContextMenu::Completions(completions_menu) => {
3698 completions_menu.ignore_completion_provider
3699 }
3700 CodeContextMenu::CodeActions(_) => false,
3701 })
3702 .unwrap_or(false);
3703
3704 if ignore_completion_provider {
3705 self.show_word_completions(&ShowWordCompletions, window, cx);
3706 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
3707 self.show_completions(
3708 &ShowCompletions {
3709 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
3710 },
3711 window,
3712 cx,
3713 );
3714 } else {
3715 self.hide_context_menu(window, cx);
3716 }
3717 }
3718
3719 fn is_completion_trigger(
3720 &self,
3721 text: &str,
3722 trigger_in_words: bool,
3723 cx: &mut Context<Self>,
3724 ) -> bool {
3725 let position = self.selections.newest_anchor().head();
3726 let multibuffer = self.buffer.read(cx);
3727 let Some(buffer) = position
3728 .buffer_id
3729 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
3730 else {
3731 return false;
3732 };
3733
3734 if let Some(completion_provider) = &self.completion_provider {
3735 completion_provider.is_completion_trigger(
3736 &buffer,
3737 position.text_anchor,
3738 text,
3739 trigger_in_words,
3740 cx,
3741 )
3742 } else {
3743 false
3744 }
3745 }
3746
3747 /// If any empty selections is touching the start of its innermost containing autoclose
3748 /// region, expand it to select the brackets.
3749 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3750 let selections = self.selections.all::<usize>(cx);
3751 let buffer = self.buffer.read(cx).read(cx);
3752 let new_selections = self
3753 .selections_with_autoclose_regions(selections, &buffer)
3754 .map(|(mut selection, region)| {
3755 if !selection.is_empty() {
3756 return selection;
3757 }
3758
3759 if let Some(region) = region {
3760 let mut range = region.range.to_offset(&buffer);
3761 if selection.start == range.start && range.start >= region.pair.start.len() {
3762 range.start -= region.pair.start.len();
3763 if buffer.contains_str_at(range.start, ®ion.pair.start)
3764 && buffer.contains_str_at(range.end, ®ion.pair.end)
3765 {
3766 range.end += region.pair.end.len();
3767 selection.start = range.start;
3768 selection.end = range.end;
3769
3770 return selection;
3771 }
3772 }
3773 }
3774
3775 let always_treat_brackets_as_autoclosed = buffer
3776 .language_settings_at(selection.start, cx)
3777 .always_treat_brackets_as_autoclosed;
3778
3779 if !always_treat_brackets_as_autoclosed {
3780 return selection;
3781 }
3782
3783 if let Some(scope) = buffer.language_scope_at(selection.start) {
3784 for (pair, enabled) in scope.brackets() {
3785 if !enabled || !pair.close {
3786 continue;
3787 }
3788
3789 if buffer.contains_str_at(selection.start, &pair.end) {
3790 let pair_start_len = pair.start.len();
3791 if buffer.contains_str_at(
3792 selection.start.saturating_sub(pair_start_len),
3793 &pair.start,
3794 ) {
3795 selection.start -= pair_start_len;
3796 selection.end += pair.end.len();
3797
3798 return selection;
3799 }
3800 }
3801 }
3802 }
3803
3804 selection
3805 })
3806 .collect();
3807
3808 drop(buffer);
3809 self.change_selections(None, window, cx, |selections| {
3810 selections.select(new_selections)
3811 });
3812 }
3813
3814 /// Iterate the given selections, and for each one, find the smallest surrounding
3815 /// autoclose region. This uses the ordering of the selections and the autoclose
3816 /// regions to avoid repeated comparisons.
3817 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
3818 &'a self,
3819 selections: impl IntoIterator<Item = Selection<D>>,
3820 buffer: &'a MultiBufferSnapshot,
3821 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
3822 let mut i = 0;
3823 let mut regions = self.autoclose_regions.as_slice();
3824 selections.into_iter().map(move |selection| {
3825 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
3826
3827 let mut enclosing = None;
3828 while let Some(pair_state) = regions.get(i) {
3829 if pair_state.range.end.to_offset(buffer) < range.start {
3830 regions = ®ions[i + 1..];
3831 i = 0;
3832 } else if pair_state.range.start.to_offset(buffer) > range.end {
3833 break;
3834 } else {
3835 if pair_state.selection_id == selection.id {
3836 enclosing = Some(pair_state);
3837 }
3838 i += 1;
3839 }
3840 }
3841
3842 (selection, enclosing)
3843 })
3844 }
3845
3846 /// Remove any autoclose regions that no longer contain their selection.
3847 fn invalidate_autoclose_regions(
3848 &mut self,
3849 mut selections: &[Selection<Anchor>],
3850 buffer: &MultiBufferSnapshot,
3851 ) {
3852 self.autoclose_regions.retain(|state| {
3853 let mut i = 0;
3854 while let Some(selection) = selections.get(i) {
3855 if selection.end.cmp(&state.range.start, buffer).is_lt() {
3856 selections = &selections[1..];
3857 continue;
3858 }
3859 if selection.start.cmp(&state.range.end, buffer).is_gt() {
3860 break;
3861 }
3862 if selection.id == state.selection_id {
3863 return true;
3864 } else {
3865 i += 1;
3866 }
3867 }
3868 false
3869 });
3870 }
3871
3872 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
3873 let offset = position.to_offset(buffer);
3874 let (word_range, kind) = buffer.surrounding_word(offset, true);
3875 if offset > word_range.start && kind == Some(CharKind::Word) {
3876 Some(
3877 buffer
3878 .text_for_range(word_range.start..offset)
3879 .collect::<String>(),
3880 )
3881 } else {
3882 None
3883 }
3884 }
3885
3886 pub fn toggle_inlay_hints(
3887 &mut self,
3888 _: &ToggleInlayHints,
3889 _: &mut Window,
3890 cx: &mut Context<Self>,
3891 ) {
3892 self.refresh_inlay_hints(
3893 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
3894 cx,
3895 );
3896 }
3897
3898 pub fn inlay_hints_enabled(&self) -> bool {
3899 self.inlay_hint_cache.enabled
3900 }
3901
3902 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
3903 if self.semantics_provider.is_none() || self.mode != EditorMode::Full {
3904 return;
3905 }
3906
3907 let reason_description = reason.description();
3908 let ignore_debounce = matches!(
3909 reason,
3910 InlayHintRefreshReason::SettingsChange(_)
3911 | InlayHintRefreshReason::Toggle(_)
3912 | InlayHintRefreshReason::ExcerptsRemoved(_)
3913 | InlayHintRefreshReason::ModifiersChanged(_)
3914 );
3915 let (invalidate_cache, required_languages) = match reason {
3916 InlayHintRefreshReason::ModifiersChanged(enabled) => {
3917 match self.inlay_hint_cache.modifiers_override(enabled) {
3918 Some(enabled) => {
3919 if enabled {
3920 (InvalidationStrategy::RefreshRequested, None)
3921 } else {
3922 self.splice_inlays(
3923 &self
3924 .visible_inlay_hints(cx)
3925 .iter()
3926 .map(|inlay| inlay.id)
3927 .collect::<Vec<InlayId>>(),
3928 Vec::new(),
3929 cx,
3930 );
3931 return;
3932 }
3933 }
3934 None => return,
3935 }
3936 }
3937 InlayHintRefreshReason::Toggle(enabled) => {
3938 if self.inlay_hint_cache.toggle(enabled) {
3939 if enabled {
3940 (InvalidationStrategy::RefreshRequested, None)
3941 } else {
3942 self.splice_inlays(
3943 &self
3944 .visible_inlay_hints(cx)
3945 .iter()
3946 .map(|inlay| inlay.id)
3947 .collect::<Vec<InlayId>>(),
3948 Vec::new(),
3949 cx,
3950 );
3951 return;
3952 }
3953 } else {
3954 return;
3955 }
3956 }
3957 InlayHintRefreshReason::SettingsChange(new_settings) => {
3958 match self.inlay_hint_cache.update_settings(
3959 &self.buffer,
3960 new_settings,
3961 self.visible_inlay_hints(cx),
3962 cx,
3963 ) {
3964 ControlFlow::Break(Some(InlaySplice {
3965 to_remove,
3966 to_insert,
3967 })) => {
3968 self.splice_inlays(&to_remove, to_insert, cx);
3969 return;
3970 }
3971 ControlFlow::Break(None) => return,
3972 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
3973 }
3974 }
3975 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
3976 if let Some(InlaySplice {
3977 to_remove,
3978 to_insert,
3979 }) = self.inlay_hint_cache.remove_excerpts(excerpts_removed)
3980 {
3981 self.splice_inlays(&to_remove, to_insert, cx);
3982 }
3983 return;
3984 }
3985 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
3986 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
3987 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
3988 }
3989 InlayHintRefreshReason::RefreshRequested => {
3990 (InvalidationStrategy::RefreshRequested, None)
3991 }
3992 };
3993
3994 if let Some(InlaySplice {
3995 to_remove,
3996 to_insert,
3997 }) = self.inlay_hint_cache.spawn_hint_refresh(
3998 reason_description,
3999 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4000 invalidate_cache,
4001 ignore_debounce,
4002 cx,
4003 ) {
4004 self.splice_inlays(&to_remove, to_insert, cx);
4005 }
4006 }
4007
4008 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4009 self.display_map
4010 .read(cx)
4011 .current_inlays()
4012 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4013 .cloned()
4014 .collect()
4015 }
4016
4017 pub fn excerpts_for_inlay_hints_query(
4018 &self,
4019 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4020 cx: &mut Context<Editor>,
4021 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4022 let Some(project) = self.project.as_ref() else {
4023 return HashMap::default();
4024 };
4025 let project = project.read(cx);
4026 let multi_buffer = self.buffer().read(cx);
4027 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4028 let multi_buffer_visible_start = self
4029 .scroll_manager
4030 .anchor()
4031 .anchor
4032 .to_point(&multi_buffer_snapshot);
4033 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4034 multi_buffer_visible_start
4035 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4036 Bias::Left,
4037 );
4038 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4039 multi_buffer_snapshot
4040 .range_to_buffer_ranges(multi_buffer_visible_range)
4041 .into_iter()
4042 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4043 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4044 let buffer_file = project::File::from_dyn(buffer.file())?;
4045 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4046 let worktree_entry = buffer_worktree
4047 .read(cx)
4048 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4049 if worktree_entry.is_ignored {
4050 return None;
4051 }
4052
4053 let language = buffer.language()?;
4054 if let Some(restrict_to_languages) = restrict_to_languages {
4055 if !restrict_to_languages.contains(language) {
4056 return None;
4057 }
4058 }
4059 Some((
4060 excerpt_id,
4061 (
4062 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4063 buffer.version().clone(),
4064 excerpt_visible_range,
4065 ),
4066 ))
4067 })
4068 .collect()
4069 }
4070
4071 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4072 TextLayoutDetails {
4073 text_system: window.text_system().clone(),
4074 editor_style: self.style.clone().unwrap(),
4075 rem_size: window.rem_size(),
4076 scroll_anchor: self.scroll_manager.anchor(),
4077 visible_rows: self.visible_line_count(),
4078 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4079 }
4080 }
4081
4082 pub fn splice_inlays(
4083 &self,
4084 to_remove: &[InlayId],
4085 to_insert: Vec<Inlay>,
4086 cx: &mut Context<Self>,
4087 ) {
4088 self.display_map.update(cx, |display_map, cx| {
4089 display_map.splice_inlays(to_remove, to_insert, cx)
4090 });
4091 cx.notify();
4092 }
4093
4094 fn trigger_on_type_formatting(
4095 &self,
4096 input: String,
4097 window: &mut Window,
4098 cx: &mut Context<Self>,
4099 ) -> Option<Task<Result<()>>> {
4100 if input.len() != 1 {
4101 return None;
4102 }
4103
4104 let project = self.project.as_ref()?;
4105 let position = self.selections.newest_anchor().head();
4106 let (buffer, buffer_position) = self
4107 .buffer
4108 .read(cx)
4109 .text_anchor_for_position(position, cx)?;
4110
4111 let settings = language_settings::language_settings(
4112 buffer
4113 .read(cx)
4114 .language_at(buffer_position)
4115 .map(|l| l.name()),
4116 buffer.read(cx).file(),
4117 cx,
4118 );
4119 if !settings.use_on_type_format {
4120 return None;
4121 }
4122
4123 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4124 // hence we do LSP request & edit on host side only — add formats to host's history.
4125 let push_to_lsp_host_history = true;
4126 // If this is not the host, append its history with new edits.
4127 let push_to_client_history = project.read(cx).is_via_collab();
4128
4129 let on_type_formatting = project.update(cx, |project, cx| {
4130 project.on_type_format(
4131 buffer.clone(),
4132 buffer_position,
4133 input,
4134 push_to_lsp_host_history,
4135 cx,
4136 )
4137 });
4138 Some(cx.spawn_in(window, async move |editor, cx| {
4139 if let Some(transaction) = on_type_formatting.await? {
4140 if push_to_client_history {
4141 buffer
4142 .update(cx, |buffer, _| {
4143 buffer.push_transaction(transaction, Instant::now());
4144 })
4145 .ok();
4146 }
4147 editor.update(cx, |editor, cx| {
4148 editor.refresh_document_highlights(cx);
4149 })?;
4150 }
4151 Ok(())
4152 }))
4153 }
4154
4155 pub fn show_word_completions(
4156 &mut self,
4157 _: &ShowWordCompletions,
4158 window: &mut Window,
4159 cx: &mut Context<Self>,
4160 ) {
4161 self.open_completions_menu(true, None, window, cx);
4162 }
4163
4164 pub fn show_completions(
4165 &mut self,
4166 options: &ShowCompletions,
4167 window: &mut Window,
4168 cx: &mut Context<Self>,
4169 ) {
4170 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4171 }
4172
4173 fn open_completions_menu(
4174 &mut self,
4175 ignore_completion_provider: bool,
4176 trigger: Option<&str>,
4177 window: &mut Window,
4178 cx: &mut Context<Self>,
4179 ) {
4180 if self.pending_rename.is_some() {
4181 return;
4182 }
4183 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4184 return;
4185 }
4186
4187 let position = self.selections.newest_anchor().head();
4188 if position.diff_base_anchor.is_some() {
4189 return;
4190 }
4191 let (buffer, buffer_position) =
4192 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4193 output
4194 } else {
4195 return;
4196 };
4197 let buffer_snapshot = buffer.read(cx).snapshot();
4198 let show_completion_documentation = buffer_snapshot
4199 .settings_at(buffer_position, cx)
4200 .show_completion_documentation;
4201
4202 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4203
4204 let trigger_kind = match trigger {
4205 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4206 CompletionTriggerKind::TRIGGER_CHARACTER
4207 }
4208 _ => CompletionTriggerKind::INVOKED,
4209 };
4210 let completion_context = CompletionContext {
4211 trigger_character: trigger.and_then(|trigger| {
4212 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4213 Some(String::from(trigger))
4214 } else {
4215 None
4216 }
4217 }),
4218 trigger_kind,
4219 };
4220
4221 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4222 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4223 let word_to_exclude = buffer_snapshot
4224 .text_for_range(old_range.clone())
4225 .collect::<String>();
4226 (
4227 buffer_snapshot.anchor_before(old_range.start)
4228 ..buffer_snapshot.anchor_after(old_range.end),
4229 Some(word_to_exclude),
4230 )
4231 } else {
4232 (buffer_position..buffer_position, None)
4233 };
4234
4235 let completion_settings = language_settings(
4236 buffer_snapshot
4237 .language_at(buffer_position)
4238 .map(|language| language.name()),
4239 buffer_snapshot.file(),
4240 cx,
4241 )
4242 .completions;
4243
4244 // The document can be large, so stay in reasonable bounds when searching for words,
4245 // otherwise completion pop-up might be slow to appear.
4246 const WORD_LOOKUP_ROWS: u32 = 5_000;
4247 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4248 let min_word_search = buffer_snapshot.clip_point(
4249 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4250 Bias::Left,
4251 );
4252 let max_word_search = buffer_snapshot.clip_point(
4253 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4254 Bias::Right,
4255 );
4256 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4257 ..buffer_snapshot.point_to_offset(max_word_search);
4258
4259 let provider = self
4260 .completion_provider
4261 .as_ref()
4262 .filter(|_| !ignore_completion_provider);
4263 let skip_digits = query
4264 .as_ref()
4265 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4266
4267 let (mut words, provided_completions) = match provider {
4268 Some(provider) => {
4269 let completions = provider.completions(
4270 position.excerpt_id,
4271 &buffer,
4272 buffer_position,
4273 completion_context,
4274 window,
4275 cx,
4276 );
4277
4278 let words = match completion_settings.words {
4279 WordsCompletionMode::Disabled => Task::ready(HashMap::default()),
4280 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4281 .background_spawn(async move {
4282 buffer_snapshot.words_in_range(WordsQuery {
4283 fuzzy_contents: None,
4284 range: word_search_range,
4285 skip_digits,
4286 })
4287 }),
4288 };
4289
4290 (words, completions)
4291 }
4292 None => (
4293 cx.background_spawn(async move {
4294 buffer_snapshot.words_in_range(WordsQuery {
4295 fuzzy_contents: None,
4296 range: word_search_range,
4297 skip_digits,
4298 })
4299 }),
4300 Task::ready(Ok(None)),
4301 ),
4302 };
4303
4304 let sort_completions = provider
4305 .as_ref()
4306 .map_or(true, |provider| provider.sort_completions());
4307
4308 let id = post_inc(&mut self.next_completion_id);
4309 let task = cx.spawn_in(window, async move |editor, cx| {
4310 async move {
4311 editor.update(cx, |this, _| {
4312 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4313 })?;
4314
4315 let mut completions = Vec::new();
4316 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
4317 completions.extend(provided_completions);
4318 if completion_settings.words == WordsCompletionMode::Fallback {
4319 words = Task::ready(HashMap::default());
4320 }
4321 }
4322
4323 let mut words = words.await;
4324 if let Some(word_to_exclude) = &word_to_exclude {
4325 words.remove(word_to_exclude);
4326 }
4327 for lsp_completion in &completions {
4328 words.remove(&lsp_completion.new_text);
4329 }
4330 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
4331 old_range: old_range.clone(),
4332 new_text: word.clone(),
4333 label: CodeLabel::plain(word, None),
4334 icon_path: None,
4335 documentation: None,
4336 source: CompletionSource::BufferWord {
4337 word_range,
4338 resolved: false,
4339 },
4340 confirm: None,
4341 }));
4342
4343 let menu = if completions.is_empty() {
4344 None
4345 } else {
4346 let mut menu = CompletionsMenu::new(
4347 id,
4348 sort_completions,
4349 show_completion_documentation,
4350 ignore_completion_provider,
4351 position,
4352 buffer.clone(),
4353 completions.into(),
4354 );
4355
4356 menu.filter(query.as_deref(), cx.background_executor().clone())
4357 .await;
4358
4359 menu.visible().then_some(menu)
4360 };
4361
4362 editor.update_in(cx, |editor, window, cx| {
4363 match editor.context_menu.borrow().as_ref() {
4364 None => {}
4365 Some(CodeContextMenu::Completions(prev_menu)) => {
4366 if prev_menu.id > id {
4367 return;
4368 }
4369 }
4370 _ => return,
4371 }
4372
4373 if editor.focus_handle.is_focused(window) && menu.is_some() {
4374 let mut menu = menu.unwrap();
4375 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
4376
4377 *editor.context_menu.borrow_mut() =
4378 Some(CodeContextMenu::Completions(menu));
4379
4380 if editor.show_edit_predictions_in_menu() {
4381 editor.update_visible_inline_completion(window, cx);
4382 } else {
4383 editor.discard_inline_completion(false, cx);
4384 }
4385
4386 cx.notify();
4387 } else if editor.completion_tasks.len() <= 1 {
4388 // If there are no more completion tasks and the last menu was
4389 // empty, we should hide it.
4390 let was_hidden = editor.hide_context_menu(window, cx).is_none();
4391 // If it was already hidden and we don't show inline
4392 // completions in the menu, we should also show the
4393 // inline-completion when available.
4394 if was_hidden && editor.show_edit_predictions_in_menu() {
4395 editor.update_visible_inline_completion(window, cx);
4396 }
4397 }
4398 })?;
4399
4400 anyhow::Ok(())
4401 }
4402 .log_err()
4403 .await
4404 });
4405
4406 self.completion_tasks.push((id, task));
4407 }
4408
4409 #[cfg(feature = "test-support")]
4410 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
4411 let menu = self.context_menu.borrow();
4412 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
4413 let completions = menu.completions.borrow();
4414 Some(completions.to_vec())
4415 } else {
4416 None
4417 }
4418 }
4419
4420 pub fn confirm_completion(
4421 &mut self,
4422 action: &ConfirmCompletion,
4423 window: &mut Window,
4424 cx: &mut Context<Self>,
4425 ) -> Option<Task<Result<()>>> {
4426 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
4427 }
4428
4429 pub fn compose_completion(
4430 &mut self,
4431 action: &ComposeCompletion,
4432 window: &mut Window,
4433 cx: &mut Context<Self>,
4434 ) -> Option<Task<Result<()>>> {
4435 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
4436 }
4437
4438 fn do_completion(
4439 &mut self,
4440 item_ix: Option<usize>,
4441 intent: CompletionIntent,
4442 window: &mut Window,
4443 cx: &mut Context<Editor>,
4444 ) -> Option<Task<Result<()>>> {
4445 use language::ToOffset as _;
4446
4447 let completions_menu =
4448 if let CodeContextMenu::Completions(menu) = self.hide_context_menu(window, cx)? {
4449 menu
4450 } else {
4451 return None;
4452 };
4453
4454 let candidate_id = {
4455 let entries = completions_menu.entries.borrow();
4456 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
4457 if self.show_edit_predictions_in_menu() {
4458 self.discard_inline_completion(true, cx);
4459 }
4460 mat.candidate_id
4461 };
4462
4463 let buffer_handle = completions_menu.buffer;
4464 let completion = completions_menu
4465 .completions
4466 .borrow()
4467 .get(candidate_id)?
4468 .clone();
4469 cx.stop_propagation();
4470
4471 let snippet;
4472 let new_text;
4473 if completion.is_snippet() {
4474 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
4475 new_text = snippet.as_ref().unwrap().text.clone();
4476 } else {
4477 snippet = None;
4478 new_text = completion.new_text.clone();
4479 };
4480 let selections = self.selections.all::<usize>(cx);
4481 let buffer = buffer_handle.read(cx);
4482 let old_range = completion.old_range.to_offset(buffer);
4483 let old_text = buffer.text_for_range(old_range.clone()).collect::<String>();
4484
4485 let newest_selection = self.selections.newest_anchor();
4486 if newest_selection.start.buffer_id != Some(buffer_handle.read(cx).remote_id()) {
4487 return None;
4488 }
4489
4490 let lookbehind = newest_selection
4491 .start
4492 .text_anchor
4493 .to_offset(buffer)
4494 .saturating_sub(old_range.start);
4495 let lookahead = old_range
4496 .end
4497 .saturating_sub(newest_selection.end.text_anchor.to_offset(buffer));
4498 let mut common_prefix_len = old_text
4499 .bytes()
4500 .zip(new_text.bytes())
4501 .take_while(|(a, b)| a == b)
4502 .count();
4503
4504 let snapshot = self.buffer.read(cx).snapshot(cx);
4505 let mut range_to_replace: Option<Range<isize>> = None;
4506 let mut ranges = Vec::new();
4507 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4508 for selection in &selections {
4509 if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) {
4510 let start = selection.start.saturating_sub(lookbehind);
4511 let end = selection.end + lookahead;
4512 if selection.id == newest_selection.id {
4513 range_to_replace = Some(
4514 ((start + common_prefix_len) as isize - selection.start as isize)
4515 ..(end as isize - selection.start as isize),
4516 );
4517 }
4518 ranges.push(start + common_prefix_len..end);
4519 } else {
4520 common_prefix_len = 0;
4521 ranges.clear();
4522 ranges.extend(selections.iter().map(|s| {
4523 if s.id == newest_selection.id {
4524 range_to_replace = Some(
4525 old_range.start.to_offset_utf16(&snapshot).0 as isize
4526 - selection.start as isize
4527 ..old_range.end.to_offset_utf16(&snapshot).0 as isize
4528 - selection.start as isize,
4529 );
4530 old_range.clone()
4531 } else {
4532 s.start..s.end
4533 }
4534 }));
4535 break;
4536 }
4537 if !self.linked_edit_ranges.is_empty() {
4538 let start_anchor = snapshot.anchor_before(selection.head());
4539 let end_anchor = snapshot.anchor_after(selection.tail());
4540 if let Some(ranges) = self
4541 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
4542 {
4543 for (buffer, edits) in ranges {
4544 linked_edits.entry(buffer.clone()).or_default().extend(
4545 edits
4546 .into_iter()
4547 .map(|range| (range, new_text[common_prefix_len..].to_owned())),
4548 );
4549 }
4550 }
4551 }
4552 }
4553 let text = &new_text[common_prefix_len..];
4554
4555 cx.emit(EditorEvent::InputHandled {
4556 utf16_range_to_replace: range_to_replace,
4557 text: text.into(),
4558 });
4559
4560 self.transact(window, cx, |this, window, cx| {
4561 if let Some(mut snippet) = snippet {
4562 snippet.text = text.to_string();
4563 for tabstop in snippet
4564 .tabstops
4565 .iter_mut()
4566 .flat_map(|tabstop| tabstop.ranges.iter_mut())
4567 {
4568 tabstop.start -= common_prefix_len as isize;
4569 tabstop.end -= common_prefix_len as isize;
4570 }
4571
4572 this.insert_snippet(&ranges, snippet, window, cx).log_err();
4573 } else {
4574 this.buffer.update(cx, |buffer, cx| {
4575 let edits = ranges.iter().map(|range| (range.clone(), text));
4576 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4577 });
4578 }
4579 for (buffer, edits) in linked_edits {
4580 buffer.update(cx, |buffer, cx| {
4581 let snapshot = buffer.snapshot();
4582 let edits = edits
4583 .into_iter()
4584 .map(|(range, text)| {
4585 use text::ToPoint as TP;
4586 let end_point = TP::to_point(&range.end, &snapshot);
4587 let start_point = TP::to_point(&range.start, &snapshot);
4588 (start_point..end_point, text)
4589 })
4590 .sorted_by_key(|(range, _)| range.start);
4591 buffer.edit(edits, None, cx);
4592 })
4593 }
4594
4595 this.refresh_inline_completion(true, false, window, cx);
4596 });
4597
4598 let show_new_completions_on_confirm = completion
4599 .confirm
4600 .as_ref()
4601 .map_or(false, |confirm| confirm(intent, window, cx));
4602 if show_new_completions_on_confirm {
4603 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
4604 }
4605
4606 let provider = self.completion_provider.as_ref()?;
4607 drop(completion);
4608 let apply_edits = provider.apply_additional_edits_for_completion(
4609 buffer_handle,
4610 completions_menu.completions.clone(),
4611 candidate_id,
4612 true,
4613 cx,
4614 );
4615
4616 let editor_settings = EditorSettings::get_global(cx);
4617 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
4618 // After the code completion is finished, users often want to know what signatures are needed.
4619 // so we should automatically call signature_help
4620 self.show_signature_help(&ShowSignatureHelp, window, cx);
4621 }
4622
4623 Some(cx.foreground_executor().spawn(async move {
4624 apply_edits.await?;
4625 Ok(())
4626 }))
4627 }
4628
4629 pub fn toggle_code_actions(
4630 &mut self,
4631 action: &ToggleCodeActions,
4632 window: &mut Window,
4633 cx: &mut Context<Self>,
4634 ) {
4635 let mut context_menu = self.context_menu.borrow_mut();
4636 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
4637 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
4638 // Toggle if we're selecting the same one
4639 *context_menu = None;
4640 cx.notify();
4641 return;
4642 } else {
4643 // Otherwise, clear it and start a new one
4644 *context_menu = None;
4645 cx.notify();
4646 }
4647 }
4648 drop(context_menu);
4649 let snapshot = self.snapshot(window, cx);
4650 let deployed_from_indicator = action.deployed_from_indicator;
4651 let mut task = self.code_actions_task.take();
4652 let action = action.clone();
4653 cx.spawn_in(window, async move |editor, cx| {
4654 while let Some(prev_task) = task {
4655 prev_task.await.log_err();
4656 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
4657 }
4658
4659 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
4660 if editor.focus_handle.is_focused(window) {
4661 let multibuffer_point = action
4662 .deployed_from_indicator
4663 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
4664 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
4665 let (buffer, buffer_row) = snapshot
4666 .buffer_snapshot
4667 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
4668 .and_then(|(buffer_snapshot, range)| {
4669 editor
4670 .buffer
4671 .read(cx)
4672 .buffer(buffer_snapshot.remote_id())
4673 .map(|buffer| (buffer, range.start.row))
4674 })?;
4675 let (_, code_actions) = editor
4676 .available_code_actions
4677 .clone()
4678 .and_then(|(location, code_actions)| {
4679 let snapshot = location.buffer.read(cx).snapshot();
4680 let point_range = location.range.to_point(&snapshot);
4681 let point_range = point_range.start.row..=point_range.end.row;
4682 if point_range.contains(&buffer_row) {
4683 Some((location, code_actions))
4684 } else {
4685 None
4686 }
4687 })
4688 .unzip();
4689 let buffer_id = buffer.read(cx).remote_id();
4690 let tasks = editor
4691 .tasks
4692 .get(&(buffer_id, buffer_row))
4693 .map(|t| Arc::new(t.to_owned()));
4694 if tasks.is_none() && code_actions.is_none() {
4695 return None;
4696 }
4697
4698 editor.completion_tasks.clear();
4699 editor.discard_inline_completion(false, cx);
4700 let task_context =
4701 tasks
4702 .as_ref()
4703 .zip(editor.project.clone())
4704 .map(|(tasks, project)| {
4705 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
4706 });
4707
4708 Some(cx.spawn_in(window, async move |editor, cx| {
4709 let task_context = match task_context {
4710 Some(task_context) => task_context.await,
4711 None => None,
4712 };
4713 let resolved_tasks =
4714 tasks.zip(task_context).map(|(tasks, task_context)| {
4715 Rc::new(ResolvedTasks {
4716 templates: tasks.resolve(&task_context).collect(),
4717 position: snapshot.buffer_snapshot.anchor_before(Point::new(
4718 multibuffer_point.row,
4719 tasks.column,
4720 )),
4721 })
4722 });
4723 let spawn_straight_away = resolved_tasks
4724 .as_ref()
4725 .map_or(false, |tasks| tasks.templates.len() == 1)
4726 && code_actions
4727 .as_ref()
4728 .map_or(true, |actions| actions.is_empty());
4729 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
4730 *editor.context_menu.borrow_mut() =
4731 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
4732 buffer,
4733 actions: CodeActionContents {
4734 tasks: resolved_tasks,
4735 actions: code_actions,
4736 },
4737 selected_item: Default::default(),
4738 scroll_handle: UniformListScrollHandle::default(),
4739 deployed_from_indicator,
4740 }));
4741 if spawn_straight_away {
4742 if let Some(task) = editor.confirm_code_action(
4743 &ConfirmCodeAction { item_ix: Some(0) },
4744 window,
4745 cx,
4746 ) {
4747 cx.notify();
4748 return task;
4749 }
4750 }
4751 cx.notify();
4752 Task::ready(Ok(()))
4753 }) {
4754 task.await
4755 } else {
4756 Ok(())
4757 }
4758 }))
4759 } else {
4760 Some(Task::ready(Ok(())))
4761 }
4762 })?;
4763 if let Some(task) = spawned_test_task {
4764 task.await?;
4765 }
4766
4767 Ok::<_, anyhow::Error>(())
4768 })
4769 .detach_and_log_err(cx);
4770 }
4771
4772 pub fn confirm_code_action(
4773 &mut self,
4774 action: &ConfirmCodeAction,
4775 window: &mut Window,
4776 cx: &mut Context<Self>,
4777 ) -> Option<Task<Result<()>>> {
4778 let actions_menu =
4779 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
4780 menu
4781 } else {
4782 return None;
4783 };
4784 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
4785 let action = actions_menu.actions.get(action_ix)?;
4786 let title = action.label();
4787 let buffer = actions_menu.buffer;
4788 let workspace = self.workspace()?;
4789
4790 match action {
4791 CodeActionsItem::Task(task_source_kind, resolved_task) => {
4792 workspace.update(cx, |workspace, cx| {
4793 workspace::tasks::schedule_resolved_task(
4794 workspace,
4795 task_source_kind,
4796 resolved_task,
4797 false,
4798 cx,
4799 );
4800
4801 Some(Task::ready(Ok(())))
4802 })
4803 }
4804 CodeActionsItem::CodeAction {
4805 excerpt_id,
4806 action,
4807 provider,
4808 } => {
4809 let apply_code_action =
4810 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
4811 let workspace = workspace.downgrade();
4812 Some(cx.spawn_in(window, async move |editor, cx| {
4813 let project_transaction = apply_code_action.await?;
4814 Self::open_project_transaction(
4815 &editor,
4816 workspace,
4817 project_transaction,
4818 title,
4819 cx,
4820 )
4821 .await
4822 }))
4823 }
4824 }
4825 }
4826
4827 pub async fn open_project_transaction(
4828 this: &WeakEntity<Editor>,
4829 workspace: WeakEntity<Workspace>,
4830 transaction: ProjectTransaction,
4831 title: String,
4832 cx: &mut AsyncWindowContext,
4833 ) -> Result<()> {
4834 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
4835 cx.update(|_, cx| {
4836 entries.sort_unstable_by_key(|(buffer, _)| {
4837 buffer.read(cx).file().map(|f| f.path().clone())
4838 });
4839 })?;
4840
4841 // If the project transaction's edits are all contained within this editor, then
4842 // avoid opening a new editor to display them.
4843
4844 if let Some((buffer, transaction)) = entries.first() {
4845 if entries.len() == 1 {
4846 let excerpt = this.update(cx, |editor, cx| {
4847 editor
4848 .buffer()
4849 .read(cx)
4850 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
4851 })?;
4852 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
4853 if excerpted_buffer == *buffer {
4854 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
4855 let excerpt_range = excerpt_range.to_offset(buffer);
4856 buffer
4857 .edited_ranges_for_transaction::<usize>(transaction)
4858 .all(|range| {
4859 excerpt_range.start <= range.start
4860 && excerpt_range.end >= range.end
4861 })
4862 })?;
4863
4864 if all_edits_within_excerpt {
4865 return Ok(());
4866 }
4867 }
4868 }
4869 }
4870 } else {
4871 return Ok(());
4872 }
4873
4874 let mut ranges_to_highlight = Vec::new();
4875 let excerpt_buffer = cx.new(|cx| {
4876 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
4877 for (buffer_handle, transaction) in &entries {
4878 let buffer = buffer_handle.read(cx);
4879 ranges_to_highlight.extend(
4880 multibuffer.push_excerpts_with_context_lines(
4881 buffer_handle.clone(),
4882 buffer
4883 .edited_ranges_for_transaction::<usize>(transaction)
4884 .collect(),
4885 DEFAULT_MULTIBUFFER_CONTEXT,
4886 cx,
4887 ),
4888 );
4889 }
4890 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
4891 multibuffer
4892 })?;
4893
4894 workspace.update_in(cx, |workspace, window, cx| {
4895 let project = workspace.project().clone();
4896 let editor =
4897 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
4898 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
4899 editor.update(cx, |editor, cx| {
4900 editor.highlight_background::<Self>(
4901 &ranges_to_highlight,
4902 |theme| theme.editor_highlighted_line_background,
4903 cx,
4904 );
4905 });
4906 })?;
4907
4908 Ok(())
4909 }
4910
4911 pub fn clear_code_action_providers(&mut self) {
4912 self.code_action_providers.clear();
4913 self.available_code_actions.take();
4914 }
4915
4916 pub fn add_code_action_provider(
4917 &mut self,
4918 provider: Rc<dyn CodeActionProvider>,
4919 window: &mut Window,
4920 cx: &mut Context<Self>,
4921 ) {
4922 if self
4923 .code_action_providers
4924 .iter()
4925 .any(|existing_provider| existing_provider.id() == provider.id())
4926 {
4927 return;
4928 }
4929
4930 self.code_action_providers.push(provider);
4931 self.refresh_code_actions(window, cx);
4932 }
4933
4934 pub fn remove_code_action_provider(
4935 &mut self,
4936 id: Arc<str>,
4937 window: &mut Window,
4938 cx: &mut Context<Self>,
4939 ) {
4940 self.code_action_providers
4941 .retain(|provider| provider.id() != id);
4942 self.refresh_code_actions(window, cx);
4943 }
4944
4945 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
4946 let buffer = self.buffer.read(cx);
4947 let newest_selection = self.selections.newest_anchor().clone();
4948 if newest_selection.head().diff_base_anchor.is_some() {
4949 return None;
4950 }
4951 let (start_buffer, start) = buffer.text_anchor_for_position(newest_selection.start, cx)?;
4952 let (end_buffer, end) = buffer.text_anchor_for_position(newest_selection.end, cx)?;
4953 if start_buffer != end_buffer {
4954 return None;
4955 }
4956
4957 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
4958 cx.background_executor()
4959 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
4960 .await;
4961
4962 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
4963 let providers = this.code_action_providers.clone();
4964 let tasks = this
4965 .code_action_providers
4966 .iter()
4967 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
4968 .collect::<Vec<_>>();
4969 (providers, tasks)
4970 })?;
4971
4972 let mut actions = Vec::new();
4973 for (provider, provider_actions) in
4974 providers.into_iter().zip(future::join_all(tasks).await)
4975 {
4976 if let Some(provider_actions) = provider_actions.log_err() {
4977 actions.extend(provider_actions.into_iter().map(|action| {
4978 AvailableCodeAction {
4979 excerpt_id: newest_selection.start.excerpt_id,
4980 action,
4981 provider: provider.clone(),
4982 }
4983 }));
4984 }
4985 }
4986
4987 this.update(cx, |this, cx| {
4988 this.available_code_actions = if actions.is_empty() {
4989 None
4990 } else {
4991 Some((
4992 Location {
4993 buffer: start_buffer,
4994 range: start..end,
4995 },
4996 actions.into(),
4997 ))
4998 };
4999 cx.notify();
5000 })
5001 }));
5002 None
5003 }
5004
5005 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5006 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5007 self.show_git_blame_inline = false;
5008
5009 self.show_git_blame_inline_delay_task =
5010 Some(cx.spawn_in(window, async move |this, cx| {
5011 cx.background_executor().timer(delay).await;
5012
5013 this.update(cx, |this, cx| {
5014 this.show_git_blame_inline = true;
5015 cx.notify();
5016 })
5017 .log_err();
5018 }));
5019 }
5020 }
5021
5022 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5023 if self.pending_rename.is_some() {
5024 return None;
5025 }
5026
5027 let provider = self.semantics_provider.clone()?;
5028 let buffer = self.buffer.read(cx);
5029 let newest_selection = self.selections.newest_anchor().clone();
5030 let cursor_position = newest_selection.head();
5031 let (cursor_buffer, cursor_buffer_position) =
5032 buffer.text_anchor_for_position(cursor_position, cx)?;
5033 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5034 if cursor_buffer != tail_buffer {
5035 return None;
5036 }
5037 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5038 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5039 cx.background_executor()
5040 .timer(Duration::from_millis(debounce))
5041 .await;
5042
5043 let highlights = if let Some(highlights) = cx
5044 .update(|cx| {
5045 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5046 })
5047 .ok()
5048 .flatten()
5049 {
5050 highlights.await.log_err()
5051 } else {
5052 None
5053 };
5054
5055 if let Some(highlights) = highlights {
5056 this.update(cx, |this, cx| {
5057 if this.pending_rename.is_some() {
5058 return;
5059 }
5060
5061 let buffer_id = cursor_position.buffer_id;
5062 let buffer = this.buffer.read(cx);
5063 if !buffer
5064 .text_anchor_for_position(cursor_position, cx)
5065 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5066 {
5067 return;
5068 }
5069
5070 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5071 let mut write_ranges = Vec::new();
5072 let mut read_ranges = Vec::new();
5073 for highlight in highlights {
5074 for (excerpt_id, excerpt_range) in
5075 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5076 {
5077 let start = highlight
5078 .range
5079 .start
5080 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5081 let end = highlight
5082 .range
5083 .end
5084 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5085 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5086 continue;
5087 }
5088
5089 let range = Anchor {
5090 buffer_id,
5091 excerpt_id,
5092 text_anchor: start,
5093 diff_base_anchor: None,
5094 }..Anchor {
5095 buffer_id,
5096 excerpt_id,
5097 text_anchor: end,
5098 diff_base_anchor: None,
5099 };
5100 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5101 write_ranges.push(range);
5102 } else {
5103 read_ranges.push(range);
5104 }
5105 }
5106 }
5107
5108 this.highlight_background::<DocumentHighlightRead>(
5109 &read_ranges,
5110 |theme| theme.editor_document_highlight_read_background,
5111 cx,
5112 );
5113 this.highlight_background::<DocumentHighlightWrite>(
5114 &write_ranges,
5115 |theme| theme.editor_document_highlight_write_background,
5116 cx,
5117 );
5118 cx.notify();
5119 })
5120 .log_err();
5121 }
5122 }));
5123 None
5124 }
5125
5126 pub fn refresh_selected_text_highlights(
5127 &mut self,
5128 window: &mut Window,
5129 cx: &mut Context<Editor>,
5130 ) {
5131 if matches!(self.mode, EditorMode::SingleLine { .. }) {
5132 return;
5133 }
5134 self.selection_highlight_task.take();
5135 if !EditorSettings::get_global(cx).selection_highlight {
5136 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5137 return;
5138 }
5139 if self.selections.count() != 1 || self.selections.line_mode {
5140 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5141 return;
5142 }
5143 let selection = self.selections.newest::<Point>(cx);
5144 if selection.is_empty() || selection.start.row != selection.end.row {
5145 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5146 return;
5147 }
5148 let debounce = EditorSettings::get_global(cx).selection_highlight_debounce;
5149 self.selection_highlight_task = Some(cx.spawn_in(window, async move |editor, cx| {
5150 cx.background_executor()
5151 .timer(Duration::from_millis(debounce))
5152 .await;
5153 let Some(Some(matches_task)) = editor
5154 .update_in(cx, |editor, _, cx| {
5155 if editor.selections.count() != 1 || editor.selections.line_mode {
5156 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5157 return None;
5158 }
5159 let selection = editor.selections.newest::<Point>(cx);
5160 if selection.is_empty() || selection.start.row != selection.end.row {
5161 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5162 return None;
5163 }
5164 let buffer = editor.buffer().read(cx).snapshot(cx);
5165 let query = buffer.text_for_range(selection.range()).collect::<String>();
5166 if query.trim().is_empty() {
5167 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5168 return None;
5169 }
5170 Some(cx.background_spawn(async move {
5171 let mut ranges = Vec::new();
5172 let selection_anchors = selection.range().to_anchors(&buffer);
5173 for range in [buffer.anchor_before(0)..buffer.anchor_after(buffer.len())] {
5174 for (search_buffer, search_range, excerpt_id) in
5175 buffer.range_to_buffer_ranges(range)
5176 {
5177 ranges.extend(
5178 project::search::SearchQuery::text(
5179 query.clone(),
5180 false,
5181 false,
5182 false,
5183 Default::default(),
5184 Default::default(),
5185 None,
5186 )
5187 .unwrap()
5188 .search(search_buffer, Some(search_range.clone()))
5189 .await
5190 .into_iter()
5191 .filter_map(
5192 |match_range| {
5193 let start = search_buffer.anchor_after(
5194 search_range.start + match_range.start,
5195 );
5196 let end = search_buffer.anchor_before(
5197 search_range.start + match_range.end,
5198 );
5199 let range = Anchor::range_in_buffer(
5200 excerpt_id,
5201 search_buffer.remote_id(),
5202 start..end,
5203 );
5204 (range != selection_anchors).then_some(range)
5205 },
5206 ),
5207 );
5208 }
5209 }
5210 ranges
5211 }))
5212 })
5213 .log_err()
5214 else {
5215 return;
5216 };
5217 let matches = matches_task.await;
5218 editor
5219 .update_in(cx, |editor, _, cx| {
5220 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5221 if !matches.is_empty() {
5222 editor.highlight_background::<SelectedTextHighlight>(
5223 &matches,
5224 |theme| theme.editor_document_highlight_bracket_background,
5225 cx,
5226 )
5227 }
5228 })
5229 .log_err();
5230 }));
5231 }
5232
5233 pub fn refresh_inline_completion(
5234 &mut self,
5235 debounce: bool,
5236 user_requested: bool,
5237 window: &mut Window,
5238 cx: &mut Context<Self>,
5239 ) -> Option<()> {
5240 let provider = self.edit_prediction_provider()?;
5241 let cursor = self.selections.newest_anchor().head();
5242 let (buffer, cursor_buffer_position) =
5243 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5244
5245 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
5246 self.discard_inline_completion(false, cx);
5247 return None;
5248 }
5249
5250 if !user_requested
5251 && (!self.should_show_edit_predictions()
5252 || !self.is_focused(window)
5253 || buffer.read(cx).is_empty())
5254 {
5255 self.discard_inline_completion(false, cx);
5256 return None;
5257 }
5258
5259 self.update_visible_inline_completion(window, cx);
5260 provider.refresh(
5261 self.project.clone(),
5262 buffer,
5263 cursor_buffer_position,
5264 debounce,
5265 cx,
5266 );
5267 Some(())
5268 }
5269
5270 fn show_edit_predictions_in_menu(&self) -> bool {
5271 match self.edit_prediction_settings {
5272 EditPredictionSettings::Disabled => false,
5273 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
5274 }
5275 }
5276
5277 pub fn edit_predictions_enabled(&self) -> bool {
5278 match self.edit_prediction_settings {
5279 EditPredictionSettings::Disabled => false,
5280 EditPredictionSettings::Enabled { .. } => true,
5281 }
5282 }
5283
5284 fn edit_prediction_requires_modifier(&self) -> bool {
5285 match self.edit_prediction_settings {
5286 EditPredictionSettings::Disabled => false,
5287 EditPredictionSettings::Enabled {
5288 preview_requires_modifier,
5289 ..
5290 } => preview_requires_modifier,
5291 }
5292 }
5293
5294 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
5295 if self.edit_prediction_provider.is_none() {
5296 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5297 } else {
5298 let selection = self.selections.newest_anchor();
5299 let cursor = selection.head();
5300
5301 if let Some((buffer, cursor_buffer_position)) =
5302 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5303 {
5304 self.edit_prediction_settings =
5305 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5306 }
5307 }
5308 }
5309
5310 fn edit_prediction_settings_at_position(
5311 &self,
5312 buffer: &Entity<Buffer>,
5313 buffer_position: language::Anchor,
5314 cx: &App,
5315 ) -> EditPredictionSettings {
5316 if self.mode != EditorMode::Full
5317 || !self.show_inline_completions_override.unwrap_or(true)
5318 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
5319 {
5320 return EditPredictionSettings::Disabled;
5321 }
5322
5323 let buffer = buffer.read(cx);
5324
5325 let file = buffer.file();
5326
5327 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
5328 return EditPredictionSettings::Disabled;
5329 };
5330
5331 let by_provider = matches!(
5332 self.menu_inline_completions_policy,
5333 MenuInlineCompletionsPolicy::ByProvider
5334 );
5335
5336 let show_in_menu = by_provider
5337 && self
5338 .edit_prediction_provider
5339 .as_ref()
5340 .map_or(false, |provider| {
5341 provider.provider.show_completions_in_menu()
5342 });
5343
5344 let preview_requires_modifier =
5345 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
5346
5347 EditPredictionSettings::Enabled {
5348 show_in_menu,
5349 preview_requires_modifier,
5350 }
5351 }
5352
5353 fn should_show_edit_predictions(&self) -> bool {
5354 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
5355 }
5356
5357 pub fn edit_prediction_preview_is_active(&self) -> bool {
5358 matches!(
5359 self.edit_prediction_preview,
5360 EditPredictionPreview::Active { .. }
5361 )
5362 }
5363
5364 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
5365 let cursor = self.selections.newest_anchor().head();
5366 if let Some((buffer, cursor_position)) =
5367 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5368 {
5369 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
5370 } else {
5371 false
5372 }
5373 }
5374
5375 fn edit_predictions_enabled_in_buffer(
5376 &self,
5377 buffer: &Entity<Buffer>,
5378 buffer_position: language::Anchor,
5379 cx: &App,
5380 ) -> bool {
5381 maybe!({
5382 if self.read_only(cx) {
5383 return Some(false);
5384 }
5385 let provider = self.edit_prediction_provider()?;
5386 if !provider.is_enabled(&buffer, buffer_position, cx) {
5387 return Some(false);
5388 }
5389 let buffer = buffer.read(cx);
5390 let Some(file) = buffer.file() else {
5391 return Some(true);
5392 };
5393 let settings = all_language_settings(Some(file), cx);
5394 Some(settings.edit_predictions_enabled_for_file(file, cx))
5395 })
5396 .unwrap_or(false)
5397 }
5398
5399 fn cycle_inline_completion(
5400 &mut self,
5401 direction: Direction,
5402 window: &mut Window,
5403 cx: &mut Context<Self>,
5404 ) -> Option<()> {
5405 let provider = self.edit_prediction_provider()?;
5406 let cursor = self.selections.newest_anchor().head();
5407 let (buffer, cursor_buffer_position) =
5408 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5409 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
5410 return None;
5411 }
5412
5413 provider.cycle(buffer, cursor_buffer_position, direction, cx);
5414 self.update_visible_inline_completion(window, cx);
5415
5416 Some(())
5417 }
5418
5419 pub fn show_inline_completion(
5420 &mut self,
5421 _: &ShowEditPrediction,
5422 window: &mut Window,
5423 cx: &mut Context<Self>,
5424 ) {
5425 if !self.has_active_inline_completion() {
5426 self.refresh_inline_completion(false, true, window, cx);
5427 return;
5428 }
5429
5430 self.update_visible_inline_completion(window, cx);
5431 }
5432
5433 pub fn display_cursor_names(
5434 &mut self,
5435 _: &DisplayCursorNames,
5436 window: &mut Window,
5437 cx: &mut Context<Self>,
5438 ) {
5439 self.show_cursor_names(window, cx);
5440 }
5441
5442 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5443 self.show_cursor_names = true;
5444 cx.notify();
5445 cx.spawn_in(window, async move |this, cx| {
5446 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
5447 this.update(cx, |this, cx| {
5448 this.show_cursor_names = false;
5449 cx.notify()
5450 })
5451 .ok()
5452 })
5453 .detach();
5454 }
5455
5456 pub fn next_edit_prediction(
5457 &mut self,
5458 _: &NextEditPrediction,
5459 window: &mut Window,
5460 cx: &mut Context<Self>,
5461 ) {
5462 if self.has_active_inline_completion() {
5463 self.cycle_inline_completion(Direction::Next, window, cx);
5464 } else {
5465 let is_copilot_disabled = self
5466 .refresh_inline_completion(false, true, window, cx)
5467 .is_none();
5468 if is_copilot_disabled {
5469 cx.propagate();
5470 }
5471 }
5472 }
5473
5474 pub fn previous_edit_prediction(
5475 &mut self,
5476 _: &PreviousEditPrediction,
5477 window: &mut Window,
5478 cx: &mut Context<Self>,
5479 ) {
5480 if self.has_active_inline_completion() {
5481 self.cycle_inline_completion(Direction::Prev, window, cx);
5482 } else {
5483 let is_copilot_disabled = self
5484 .refresh_inline_completion(false, true, window, cx)
5485 .is_none();
5486 if is_copilot_disabled {
5487 cx.propagate();
5488 }
5489 }
5490 }
5491
5492 pub fn accept_edit_prediction(
5493 &mut self,
5494 _: &AcceptEditPrediction,
5495 window: &mut Window,
5496 cx: &mut Context<Self>,
5497 ) {
5498 if self.show_edit_predictions_in_menu() {
5499 self.hide_context_menu(window, cx);
5500 }
5501
5502 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5503 return;
5504 };
5505
5506 self.report_inline_completion_event(
5507 active_inline_completion.completion_id.clone(),
5508 true,
5509 cx,
5510 );
5511
5512 match &active_inline_completion.completion {
5513 InlineCompletion::Move { target, .. } => {
5514 let target = *target;
5515
5516 if let Some(position_map) = &self.last_position_map {
5517 if position_map
5518 .visible_row_range
5519 .contains(&target.to_display_point(&position_map.snapshot).row())
5520 || !self.edit_prediction_requires_modifier()
5521 {
5522 self.unfold_ranges(&[target..target], true, false, cx);
5523 // Note that this is also done in vim's handler of the Tab action.
5524 self.change_selections(
5525 Some(Autoscroll::newest()),
5526 window,
5527 cx,
5528 |selections| {
5529 selections.select_anchor_ranges([target..target]);
5530 },
5531 );
5532 self.clear_row_highlights::<EditPredictionPreview>();
5533
5534 self.edit_prediction_preview
5535 .set_previous_scroll_position(None);
5536 } else {
5537 self.edit_prediction_preview
5538 .set_previous_scroll_position(Some(
5539 position_map.snapshot.scroll_anchor,
5540 ));
5541
5542 self.highlight_rows::<EditPredictionPreview>(
5543 target..target,
5544 cx.theme().colors().editor_highlighted_line_background,
5545 true,
5546 cx,
5547 );
5548 self.request_autoscroll(Autoscroll::fit(), cx);
5549 }
5550 }
5551 }
5552 InlineCompletion::Edit { edits, .. } => {
5553 if let Some(provider) = self.edit_prediction_provider() {
5554 provider.accept(cx);
5555 }
5556
5557 let snapshot = self.buffer.read(cx).snapshot(cx);
5558 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
5559
5560 self.buffer.update(cx, |buffer, cx| {
5561 buffer.edit(edits.iter().cloned(), None, cx)
5562 });
5563
5564 self.change_selections(None, window, cx, |s| {
5565 s.select_anchor_ranges([last_edit_end..last_edit_end])
5566 });
5567
5568 self.update_visible_inline_completion(window, cx);
5569 if self.active_inline_completion.is_none() {
5570 self.refresh_inline_completion(true, true, window, cx);
5571 }
5572
5573 cx.notify();
5574 }
5575 }
5576
5577 self.edit_prediction_requires_modifier_in_indent_conflict = false;
5578 }
5579
5580 pub fn accept_partial_inline_completion(
5581 &mut self,
5582 _: &AcceptPartialEditPrediction,
5583 window: &mut Window,
5584 cx: &mut Context<Self>,
5585 ) {
5586 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5587 return;
5588 };
5589 if self.selections.count() != 1 {
5590 return;
5591 }
5592
5593 self.report_inline_completion_event(
5594 active_inline_completion.completion_id.clone(),
5595 true,
5596 cx,
5597 );
5598
5599 match &active_inline_completion.completion {
5600 InlineCompletion::Move { target, .. } => {
5601 let target = *target;
5602 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
5603 selections.select_anchor_ranges([target..target]);
5604 });
5605 }
5606 InlineCompletion::Edit { edits, .. } => {
5607 // Find an insertion that starts at the cursor position.
5608 let snapshot = self.buffer.read(cx).snapshot(cx);
5609 let cursor_offset = self.selections.newest::<usize>(cx).head();
5610 let insertion = edits.iter().find_map(|(range, text)| {
5611 let range = range.to_offset(&snapshot);
5612 if range.is_empty() && range.start == cursor_offset {
5613 Some(text)
5614 } else {
5615 None
5616 }
5617 });
5618
5619 if let Some(text) = insertion {
5620 let mut partial_completion = text
5621 .chars()
5622 .by_ref()
5623 .take_while(|c| c.is_alphabetic())
5624 .collect::<String>();
5625 if partial_completion.is_empty() {
5626 partial_completion = text
5627 .chars()
5628 .by_ref()
5629 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
5630 .collect::<String>();
5631 }
5632
5633 cx.emit(EditorEvent::InputHandled {
5634 utf16_range_to_replace: None,
5635 text: partial_completion.clone().into(),
5636 });
5637
5638 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
5639
5640 self.refresh_inline_completion(true, true, window, cx);
5641 cx.notify();
5642 } else {
5643 self.accept_edit_prediction(&Default::default(), window, cx);
5644 }
5645 }
5646 }
5647 }
5648
5649 fn discard_inline_completion(
5650 &mut self,
5651 should_report_inline_completion_event: bool,
5652 cx: &mut Context<Self>,
5653 ) -> bool {
5654 if should_report_inline_completion_event {
5655 let completion_id = self
5656 .active_inline_completion
5657 .as_ref()
5658 .and_then(|active_completion| active_completion.completion_id.clone());
5659
5660 self.report_inline_completion_event(completion_id, false, cx);
5661 }
5662
5663 if let Some(provider) = self.edit_prediction_provider() {
5664 provider.discard(cx);
5665 }
5666
5667 self.take_active_inline_completion(cx)
5668 }
5669
5670 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
5671 let Some(provider) = self.edit_prediction_provider() else {
5672 return;
5673 };
5674
5675 let Some((_, buffer, _)) = self
5676 .buffer
5677 .read(cx)
5678 .excerpt_containing(self.selections.newest_anchor().head(), cx)
5679 else {
5680 return;
5681 };
5682
5683 let extension = buffer
5684 .read(cx)
5685 .file()
5686 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
5687
5688 let event_type = match accepted {
5689 true => "Edit Prediction Accepted",
5690 false => "Edit Prediction Discarded",
5691 };
5692 telemetry::event!(
5693 event_type,
5694 provider = provider.name(),
5695 prediction_id = id,
5696 suggestion_accepted = accepted,
5697 file_extension = extension,
5698 );
5699 }
5700
5701 pub fn has_active_inline_completion(&self) -> bool {
5702 self.active_inline_completion.is_some()
5703 }
5704
5705 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
5706 let Some(active_inline_completion) = self.active_inline_completion.take() else {
5707 return false;
5708 };
5709
5710 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
5711 self.clear_highlights::<InlineCompletionHighlight>(cx);
5712 self.stale_inline_completion_in_menu = Some(active_inline_completion);
5713 true
5714 }
5715
5716 /// Returns true when we're displaying the edit prediction popover below the cursor
5717 /// like we are not previewing and the LSP autocomplete menu is visible
5718 /// or we are in `when_holding_modifier` mode.
5719 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
5720 if self.edit_prediction_preview_is_active()
5721 || !self.show_edit_predictions_in_menu()
5722 || !self.edit_predictions_enabled()
5723 {
5724 return false;
5725 }
5726
5727 if self.has_visible_completions_menu() {
5728 return true;
5729 }
5730
5731 has_completion && self.edit_prediction_requires_modifier()
5732 }
5733
5734 fn handle_modifiers_changed(
5735 &mut self,
5736 modifiers: Modifiers,
5737 position_map: &PositionMap,
5738 window: &mut Window,
5739 cx: &mut Context<Self>,
5740 ) {
5741 if self.show_edit_predictions_in_menu() {
5742 self.update_edit_prediction_preview(&modifiers, window, cx);
5743 }
5744
5745 self.update_selection_mode(&modifiers, position_map, window, cx);
5746
5747 let mouse_position = window.mouse_position();
5748 if !position_map.text_hitbox.is_hovered(window) {
5749 return;
5750 }
5751
5752 self.update_hovered_link(
5753 position_map.point_for_position(mouse_position),
5754 &position_map.snapshot,
5755 modifiers,
5756 window,
5757 cx,
5758 )
5759 }
5760
5761 fn update_selection_mode(
5762 &mut self,
5763 modifiers: &Modifiers,
5764 position_map: &PositionMap,
5765 window: &mut Window,
5766 cx: &mut Context<Self>,
5767 ) {
5768 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
5769 return;
5770 }
5771
5772 let mouse_position = window.mouse_position();
5773 let point_for_position = position_map.point_for_position(mouse_position);
5774 let position = point_for_position.previous_valid;
5775
5776 self.select(
5777 SelectPhase::BeginColumnar {
5778 position,
5779 reset: false,
5780 goal_column: point_for_position.exact_unclipped.column(),
5781 },
5782 window,
5783 cx,
5784 );
5785 }
5786
5787 fn update_edit_prediction_preview(
5788 &mut self,
5789 modifiers: &Modifiers,
5790 window: &mut Window,
5791 cx: &mut Context<Self>,
5792 ) {
5793 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
5794 let Some(accept_keystroke) = accept_keybind.keystroke() else {
5795 return;
5796 };
5797
5798 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
5799 if matches!(
5800 self.edit_prediction_preview,
5801 EditPredictionPreview::Inactive { .. }
5802 ) {
5803 self.edit_prediction_preview = EditPredictionPreview::Active {
5804 previous_scroll_position: None,
5805 since: Instant::now(),
5806 };
5807
5808 self.update_visible_inline_completion(window, cx);
5809 cx.notify();
5810 }
5811 } else if let EditPredictionPreview::Active {
5812 previous_scroll_position,
5813 since,
5814 } = self.edit_prediction_preview
5815 {
5816 if let (Some(previous_scroll_position), Some(position_map)) =
5817 (previous_scroll_position, self.last_position_map.as_ref())
5818 {
5819 self.set_scroll_position(
5820 previous_scroll_position
5821 .scroll_position(&position_map.snapshot.display_snapshot),
5822 window,
5823 cx,
5824 );
5825 }
5826
5827 self.edit_prediction_preview = EditPredictionPreview::Inactive {
5828 released_too_fast: since.elapsed() < Duration::from_millis(200),
5829 };
5830 self.clear_row_highlights::<EditPredictionPreview>();
5831 self.update_visible_inline_completion(window, cx);
5832 cx.notify();
5833 }
5834 }
5835
5836 fn update_visible_inline_completion(
5837 &mut self,
5838 _window: &mut Window,
5839 cx: &mut Context<Self>,
5840 ) -> Option<()> {
5841 let selection = self.selections.newest_anchor();
5842 let cursor = selection.head();
5843 let multibuffer = self.buffer.read(cx).snapshot(cx);
5844 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
5845 let excerpt_id = cursor.excerpt_id;
5846
5847 let show_in_menu = self.show_edit_predictions_in_menu();
5848 let completions_menu_has_precedence = !show_in_menu
5849 && (self.context_menu.borrow().is_some()
5850 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
5851
5852 if completions_menu_has_precedence
5853 || !offset_selection.is_empty()
5854 || self
5855 .active_inline_completion
5856 .as_ref()
5857 .map_or(false, |completion| {
5858 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
5859 let invalidation_range = invalidation_range.start..=invalidation_range.end;
5860 !invalidation_range.contains(&offset_selection.head())
5861 })
5862 {
5863 self.discard_inline_completion(false, cx);
5864 return None;
5865 }
5866
5867 self.take_active_inline_completion(cx);
5868 let Some(provider) = self.edit_prediction_provider() else {
5869 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5870 return None;
5871 };
5872
5873 let (buffer, cursor_buffer_position) =
5874 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5875
5876 self.edit_prediction_settings =
5877 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5878
5879 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
5880
5881 if self.edit_prediction_indent_conflict {
5882 let cursor_point = cursor.to_point(&multibuffer);
5883
5884 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
5885
5886 if let Some((_, indent)) = indents.iter().next() {
5887 if indent.len == cursor_point.column {
5888 self.edit_prediction_indent_conflict = false;
5889 }
5890 }
5891 }
5892
5893 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
5894 let edits = inline_completion
5895 .edits
5896 .into_iter()
5897 .flat_map(|(range, new_text)| {
5898 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
5899 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
5900 Some((start..end, new_text))
5901 })
5902 .collect::<Vec<_>>();
5903 if edits.is_empty() {
5904 return None;
5905 }
5906
5907 let first_edit_start = edits.first().unwrap().0.start;
5908 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
5909 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
5910
5911 let last_edit_end = edits.last().unwrap().0.end;
5912 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
5913 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
5914
5915 let cursor_row = cursor.to_point(&multibuffer).row;
5916
5917 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
5918
5919 let mut inlay_ids = Vec::new();
5920 let invalidation_row_range;
5921 let move_invalidation_row_range = if cursor_row < edit_start_row {
5922 Some(cursor_row..edit_end_row)
5923 } else if cursor_row > edit_end_row {
5924 Some(edit_start_row..cursor_row)
5925 } else {
5926 None
5927 };
5928 let is_move =
5929 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
5930 let completion = if is_move {
5931 invalidation_row_range =
5932 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
5933 let target = first_edit_start;
5934 InlineCompletion::Move { target, snapshot }
5935 } else {
5936 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
5937 && !self.inline_completions_hidden_for_vim_mode;
5938
5939 if show_completions_in_buffer {
5940 if edits
5941 .iter()
5942 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
5943 {
5944 let mut inlays = Vec::new();
5945 for (range, new_text) in &edits {
5946 let inlay = Inlay::inline_completion(
5947 post_inc(&mut self.next_inlay_id),
5948 range.start,
5949 new_text.as_str(),
5950 );
5951 inlay_ids.push(inlay.id);
5952 inlays.push(inlay);
5953 }
5954
5955 self.splice_inlays(&[], inlays, cx);
5956 } else {
5957 let background_color = cx.theme().status().deleted_background;
5958 self.highlight_text::<InlineCompletionHighlight>(
5959 edits.iter().map(|(range, _)| range.clone()).collect(),
5960 HighlightStyle {
5961 background_color: Some(background_color),
5962 ..Default::default()
5963 },
5964 cx,
5965 );
5966 }
5967 }
5968
5969 invalidation_row_range = edit_start_row..edit_end_row;
5970
5971 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
5972 if provider.show_tab_accept_marker() {
5973 EditDisplayMode::TabAccept
5974 } else {
5975 EditDisplayMode::Inline
5976 }
5977 } else {
5978 EditDisplayMode::DiffPopover
5979 };
5980
5981 InlineCompletion::Edit {
5982 edits,
5983 edit_preview: inline_completion.edit_preview,
5984 display_mode,
5985 snapshot,
5986 }
5987 };
5988
5989 let invalidation_range = multibuffer
5990 .anchor_before(Point::new(invalidation_row_range.start, 0))
5991 ..multibuffer.anchor_after(Point::new(
5992 invalidation_row_range.end,
5993 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
5994 ));
5995
5996 self.stale_inline_completion_in_menu = None;
5997 self.active_inline_completion = Some(InlineCompletionState {
5998 inlay_ids,
5999 completion,
6000 completion_id: inline_completion.id,
6001 invalidation_range,
6002 });
6003
6004 cx.notify();
6005
6006 Some(())
6007 }
6008
6009 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6010 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6011 }
6012
6013 fn render_code_actions_indicator(
6014 &self,
6015 _style: &EditorStyle,
6016 row: DisplayRow,
6017 is_active: bool,
6018 breakpoint: Option<&(Anchor, Breakpoint)>,
6019 cx: &mut Context<Self>,
6020 ) -> Option<IconButton> {
6021 let color = Color::Muted;
6022
6023 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6024 let bp_kind = Arc::new(
6025 breakpoint
6026 .map(|(_, bp)| bp.kind.clone())
6027 .unwrap_or(BreakpointKind::Standard),
6028 );
6029
6030 if self.available_code_actions.is_some() {
6031 Some(
6032 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
6033 .shape(ui::IconButtonShape::Square)
6034 .icon_size(IconSize::XSmall)
6035 .icon_color(color)
6036 .toggle_state(is_active)
6037 .tooltip({
6038 let focus_handle = self.focus_handle.clone();
6039 move |window, cx| {
6040 Tooltip::for_action_in(
6041 "Toggle Code Actions",
6042 &ToggleCodeActions {
6043 deployed_from_indicator: None,
6044 },
6045 &focus_handle,
6046 window,
6047 cx,
6048 )
6049 }
6050 })
6051 .on_click(cx.listener(move |editor, _e, window, cx| {
6052 window.focus(&editor.focus_handle(cx));
6053 editor.toggle_code_actions(
6054 &ToggleCodeActions {
6055 deployed_from_indicator: Some(row),
6056 },
6057 window,
6058 cx,
6059 );
6060 }))
6061 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6062 editor.set_breakpoint_context_menu(
6063 row,
6064 position,
6065 bp_kind.clone(),
6066 event.down.position,
6067 window,
6068 cx,
6069 );
6070 })),
6071 )
6072 } else {
6073 None
6074 }
6075 }
6076
6077 fn clear_tasks(&mut self) {
6078 self.tasks.clear()
6079 }
6080
6081 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6082 if self.tasks.insert(key, value).is_some() {
6083 // This case should hopefully be rare, but just in case...
6084 log::error!("multiple different run targets found on a single line, only the last target will be rendered")
6085 }
6086 }
6087
6088 /// Get all display points of breakpoints that will be rendered within editor
6089 ///
6090 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6091 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6092 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6093 fn active_breakpoints(
6094 &mut self,
6095 range: Range<DisplayRow>,
6096 window: &mut Window,
6097 cx: &mut Context<Self>,
6098 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
6099 let mut breakpoint_display_points = HashMap::default();
6100
6101 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
6102 return breakpoint_display_points;
6103 };
6104
6105 let snapshot = self.snapshot(window, cx);
6106
6107 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
6108 let Some(project) = self.project.as_ref() else {
6109 return breakpoint_display_points;
6110 };
6111
6112 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
6113 let buffer_snapshot = buffer.read(cx).snapshot();
6114
6115 for breakpoint in
6116 breakpoint_store
6117 .read(cx)
6118 .breakpoints(&buffer, None, buffer_snapshot.clone(), cx)
6119 {
6120 let point = buffer_snapshot.summary_for_anchor::<Point>(&breakpoint.0);
6121 let mut anchor = multi_buffer_snapshot.anchor_before(point);
6122 anchor.text_anchor = breakpoint.0;
6123
6124 breakpoint_display_points.insert(
6125 snapshot
6126 .point_to_display_point(
6127 MultiBufferPoint {
6128 row: point.row,
6129 column: point.column,
6130 },
6131 Bias::Left,
6132 )
6133 .row(),
6134 (anchor, breakpoint.1.clone()),
6135 );
6136 }
6137
6138 return breakpoint_display_points;
6139 }
6140
6141 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
6142 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
6143 for excerpt_boundary in multi_buffer_snapshot.excerpt_boundaries_in_range(range) {
6144 let info = excerpt_boundary.next;
6145
6146 let Some(excerpt_ranges) = multi_buffer_snapshot.range_for_excerpt(info.id) else {
6147 continue;
6148 };
6149
6150 let Some(buffer) =
6151 project.read_with(cx, |this, cx| this.buffer_for_id(info.buffer_id, cx))
6152 else {
6153 continue;
6154 };
6155
6156 if buffer.read(cx).file().is_none() {
6157 continue;
6158 }
6159 let breakpoints = breakpoint_store.read(cx).breakpoints(
6160 &buffer,
6161 Some(info.range.context.start..info.range.context.end),
6162 info.buffer.clone(),
6163 cx,
6164 );
6165
6166 // To translate a breakpoint's position within a singular buffer to a multi buffer
6167 // position we need to know it's excerpt starting location, it's position within
6168 // the singular buffer, and if that position is within the excerpt's range.
6169 let excerpt_head = excerpt_ranges
6170 .start
6171 .to_display_point(&snapshot.display_snapshot);
6172
6173 let buffer_start = info
6174 .buffer
6175 .summary_for_anchor::<Point>(&info.range.context.start);
6176
6177 for (anchor, breakpoint) in breakpoints {
6178 let as_row = info.buffer.summary_for_anchor::<Point>(&anchor).row;
6179 let delta = as_row - buffer_start.row;
6180
6181 let position = excerpt_head + DisplayPoint::new(DisplayRow(delta), 0);
6182
6183 let anchor = snapshot.display_point_to_anchor(position, Bias::Left);
6184
6185 breakpoint_display_points.insert(position.row(), (anchor, breakpoint.clone()));
6186 }
6187 }
6188
6189 breakpoint_display_points
6190 }
6191
6192 fn breakpoint_context_menu(
6193 &self,
6194 anchor: Anchor,
6195 kind: Arc<BreakpointKind>,
6196 window: &mut Window,
6197 cx: &mut Context<Self>,
6198 ) -> Entity<ui::ContextMenu> {
6199 let weak_editor = cx.weak_entity();
6200 let focus_handle = self.focus_handle(cx);
6201
6202 let second_entry_msg = if kind.log_message().is_some() {
6203 "Edit Log Breakpoint"
6204 } else {
6205 "Add Log Breakpoint"
6206 };
6207
6208 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6209 menu.on_blur_subscription(Subscription::new(|| {}))
6210 .context(focus_handle)
6211 .entry("Toggle Breakpoint", None, {
6212 let weak_editor = weak_editor.clone();
6213 move |_window, cx| {
6214 weak_editor
6215 .update(cx, |this, cx| {
6216 this.edit_breakpoint_at_anchor(
6217 anchor,
6218 BreakpointKind::Standard,
6219 BreakpointEditAction::Toggle,
6220 cx,
6221 );
6222 })
6223 .log_err();
6224 }
6225 })
6226 .entry(second_entry_msg, None, move |window, cx| {
6227 weak_editor
6228 .update(cx, |this, cx| {
6229 this.add_edit_breakpoint_block(anchor, kind.as_ref(), window, cx);
6230 })
6231 .log_err();
6232 })
6233 })
6234 }
6235
6236 fn render_breakpoint(
6237 &self,
6238 position: Anchor,
6239 row: DisplayRow,
6240 kind: &BreakpointKind,
6241 cx: &mut Context<Self>,
6242 ) -> IconButton {
6243 let color = if self
6244 .gutter_breakpoint_indicator
6245 .is_some_and(|gutter_bp| gutter_bp.row() == row)
6246 {
6247 Color::Hint
6248 } else {
6249 Color::Debugger
6250 };
6251
6252 let icon = match &kind {
6253 BreakpointKind::Standard => ui::IconName::DebugBreakpoint,
6254 BreakpointKind::Log(_) => ui::IconName::DebugLogBreakpoint,
6255 };
6256 let arc_kind = Arc::new(kind.clone());
6257 let arc_kind2 = arc_kind.clone();
6258
6259 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
6260 .icon_size(IconSize::XSmall)
6261 .size(ui::ButtonSize::None)
6262 .icon_color(color)
6263 .style(ButtonStyle::Transparent)
6264 .on_click(cx.listener(move |editor, _e, window, cx| {
6265 window.focus(&editor.focus_handle(cx));
6266 editor.edit_breakpoint_at_anchor(
6267 position,
6268 arc_kind.as_ref().clone(),
6269 BreakpointEditAction::Toggle,
6270 cx,
6271 );
6272 }))
6273 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6274 editor.set_breakpoint_context_menu(
6275 row,
6276 Some(position),
6277 arc_kind2.clone(),
6278 event.down.position,
6279 window,
6280 cx,
6281 );
6282 }))
6283 }
6284
6285 fn build_tasks_context(
6286 project: &Entity<Project>,
6287 buffer: &Entity<Buffer>,
6288 buffer_row: u32,
6289 tasks: &Arc<RunnableTasks>,
6290 cx: &mut Context<Self>,
6291 ) -> Task<Option<task::TaskContext>> {
6292 let position = Point::new(buffer_row, tasks.column);
6293 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
6294 let location = Location {
6295 buffer: buffer.clone(),
6296 range: range_start..range_start,
6297 };
6298 // Fill in the environmental variables from the tree-sitter captures
6299 let mut captured_task_variables = TaskVariables::default();
6300 for (capture_name, value) in tasks.extra_variables.clone() {
6301 captured_task_variables.insert(
6302 task::VariableName::Custom(capture_name.into()),
6303 value.clone(),
6304 );
6305 }
6306 project.update(cx, |project, cx| {
6307 project.task_store().update(cx, |task_store, cx| {
6308 task_store.task_context_for_location(captured_task_variables, location, cx)
6309 })
6310 })
6311 }
6312
6313 pub fn spawn_nearest_task(
6314 &mut self,
6315 action: &SpawnNearestTask,
6316 window: &mut Window,
6317 cx: &mut Context<Self>,
6318 ) {
6319 let Some((workspace, _)) = self.workspace.clone() else {
6320 return;
6321 };
6322 let Some(project) = self.project.clone() else {
6323 return;
6324 };
6325
6326 // Try to find a closest, enclosing node using tree-sitter that has a
6327 // task
6328 let Some((buffer, buffer_row, tasks)) = self
6329 .find_enclosing_node_task(cx)
6330 // Or find the task that's closest in row-distance.
6331 .or_else(|| self.find_closest_task(cx))
6332 else {
6333 return;
6334 };
6335
6336 let reveal_strategy = action.reveal;
6337 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6338 cx.spawn_in(window, async move |_, cx| {
6339 let context = task_context.await?;
6340 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
6341
6342 let resolved = resolved_task.resolved.as_mut()?;
6343 resolved.reveal = reveal_strategy;
6344
6345 workspace
6346 .update(cx, |workspace, cx| {
6347 workspace::tasks::schedule_resolved_task(
6348 workspace,
6349 task_source_kind,
6350 resolved_task,
6351 false,
6352 cx,
6353 );
6354 })
6355 .ok()
6356 })
6357 .detach();
6358 }
6359
6360 fn find_closest_task(
6361 &mut self,
6362 cx: &mut Context<Self>,
6363 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6364 let cursor_row = self.selections.newest_adjusted(cx).head().row;
6365
6366 let ((buffer_id, row), tasks) = self
6367 .tasks
6368 .iter()
6369 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
6370
6371 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
6372 let tasks = Arc::new(tasks.to_owned());
6373 Some((buffer, *row, tasks))
6374 }
6375
6376 fn find_enclosing_node_task(
6377 &mut self,
6378 cx: &mut Context<Self>,
6379 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6380 let snapshot = self.buffer.read(cx).snapshot(cx);
6381 let offset = self.selections.newest::<usize>(cx).head();
6382 let excerpt = snapshot.excerpt_containing(offset..offset)?;
6383 let buffer_id = excerpt.buffer().remote_id();
6384
6385 let layer = excerpt.buffer().syntax_layer_at(offset)?;
6386 let mut cursor = layer.node().walk();
6387
6388 while cursor.goto_first_child_for_byte(offset).is_some() {
6389 if cursor.node().end_byte() == offset {
6390 cursor.goto_next_sibling();
6391 }
6392 }
6393
6394 // Ascend to the smallest ancestor that contains the range and has a task.
6395 loop {
6396 let node = cursor.node();
6397 let node_range = node.byte_range();
6398 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
6399
6400 // Check if this node contains our offset
6401 if node_range.start <= offset && node_range.end >= offset {
6402 // If it contains offset, check for task
6403 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
6404 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
6405 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
6406 }
6407 }
6408
6409 if !cursor.goto_parent() {
6410 break;
6411 }
6412 }
6413 None
6414 }
6415
6416 fn render_run_indicator(
6417 &self,
6418 _style: &EditorStyle,
6419 is_active: bool,
6420 row: DisplayRow,
6421 breakpoint: Option<(Anchor, Breakpoint)>,
6422 cx: &mut Context<Self>,
6423 ) -> IconButton {
6424 let color = Color::Muted;
6425
6426 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6427 let bp_kind = Arc::new(
6428 breakpoint
6429 .map(|(_, bp)| bp.kind)
6430 .unwrap_or(BreakpointKind::Standard),
6431 );
6432
6433 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
6434 .shape(ui::IconButtonShape::Square)
6435 .icon_size(IconSize::XSmall)
6436 .icon_color(color)
6437 .toggle_state(is_active)
6438 .on_click(cx.listener(move |editor, _e, window, cx| {
6439 window.focus(&editor.focus_handle(cx));
6440 editor.toggle_code_actions(
6441 &ToggleCodeActions {
6442 deployed_from_indicator: Some(row),
6443 },
6444 window,
6445 cx,
6446 );
6447 }))
6448 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6449 editor.set_breakpoint_context_menu(
6450 row,
6451 position,
6452 bp_kind.clone(),
6453 event.down.position,
6454 window,
6455 cx,
6456 );
6457 }))
6458 }
6459
6460 pub fn context_menu_visible(&self) -> bool {
6461 !self.edit_prediction_preview_is_active()
6462 && self
6463 .context_menu
6464 .borrow()
6465 .as_ref()
6466 .map_or(false, |menu| menu.visible())
6467 }
6468
6469 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
6470 self.context_menu
6471 .borrow()
6472 .as_ref()
6473 .map(|menu| menu.origin())
6474 }
6475
6476 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
6477 self.context_menu_options = Some(options);
6478 }
6479
6480 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
6481 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
6482
6483 fn render_edit_prediction_popover(
6484 &mut self,
6485 text_bounds: &Bounds<Pixels>,
6486 content_origin: gpui::Point<Pixels>,
6487 editor_snapshot: &EditorSnapshot,
6488 visible_row_range: Range<DisplayRow>,
6489 scroll_top: f32,
6490 scroll_bottom: f32,
6491 line_layouts: &[LineWithInvisibles],
6492 line_height: Pixels,
6493 scroll_pixel_position: gpui::Point<Pixels>,
6494 newest_selection_head: Option<DisplayPoint>,
6495 editor_width: Pixels,
6496 style: &EditorStyle,
6497 window: &mut Window,
6498 cx: &mut App,
6499 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6500 let active_inline_completion = self.active_inline_completion.as_ref()?;
6501
6502 if self.edit_prediction_visible_in_cursor_popover(true) {
6503 return None;
6504 }
6505
6506 match &active_inline_completion.completion {
6507 InlineCompletion::Move { target, .. } => {
6508 let target_display_point = target.to_display_point(editor_snapshot);
6509
6510 if self.edit_prediction_requires_modifier() {
6511 if !self.edit_prediction_preview_is_active() {
6512 return None;
6513 }
6514
6515 self.render_edit_prediction_modifier_jump_popover(
6516 text_bounds,
6517 content_origin,
6518 visible_row_range,
6519 line_layouts,
6520 line_height,
6521 scroll_pixel_position,
6522 newest_selection_head,
6523 target_display_point,
6524 window,
6525 cx,
6526 )
6527 } else {
6528 self.render_edit_prediction_eager_jump_popover(
6529 text_bounds,
6530 content_origin,
6531 editor_snapshot,
6532 visible_row_range,
6533 scroll_top,
6534 scroll_bottom,
6535 line_height,
6536 scroll_pixel_position,
6537 target_display_point,
6538 editor_width,
6539 window,
6540 cx,
6541 )
6542 }
6543 }
6544 InlineCompletion::Edit {
6545 display_mode: EditDisplayMode::Inline,
6546 ..
6547 } => None,
6548 InlineCompletion::Edit {
6549 display_mode: EditDisplayMode::TabAccept,
6550 edits,
6551 ..
6552 } => {
6553 let range = &edits.first()?.0;
6554 let target_display_point = range.end.to_display_point(editor_snapshot);
6555
6556 self.render_edit_prediction_end_of_line_popover(
6557 "Accept",
6558 editor_snapshot,
6559 visible_row_range,
6560 target_display_point,
6561 line_height,
6562 scroll_pixel_position,
6563 content_origin,
6564 editor_width,
6565 window,
6566 cx,
6567 )
6568 }
6569 InlineCompletion::Edit {
6570 edits,
6571 edit_preview,
6572 display_mode: EditDisplayMode::DiffPopover,
6573 snapshot,
6574 } => self.render_edit_prediction_diff_popover(
6575 text_bounds,
6576 content_origin,
6577 editor_snapshot,
6578 visible_row_range,
6579 line_layouts,
6580 line_height,
6581 scroll_pixel_position,
6582 newest_selection_head,
6583 editor_width,
6584 style,
6585 edits,
6586 edit_preview,
6587 snapshot,
6588 window,
6589 cx,
6590 ),
6591 }
6592 }
6593
6594 fn render_edit_prediction_modifier_jump_popover(
6595 &mut self,
6596 text_bounds: &Bounds<Pixels>,
6597 content_origin: gpui::Point<Pixels>,
6598 visible_row_range: Range<DisplayRow>,
6599 line_layouts: &[LineWithInvisibles],
6600 line_height: Pixels,
6601 scroll_pixel_position: gpui::Point<Pixels>,
6602 newest_selection_head: Option<DisplayPoint>,
6603 target_display_point: DisplayPoint,
6604 window: &mut Window,
6605 cx: &mut App,
6606 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6607 let scrolled_content_origin =
6608 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
6609
6610 const SCROLL_PADDING_Y: Pixels = px(12.);
6611
6612 if target_display_point.row() < visible_row_range.start {
6613 return self.render_edit_prediction_scroll_popover(
6614 |_| SCROLL_PADDING_Y,
6615 IconName::ArrowUp,
6616 visible_row_range,
6617 line_layouts,
6618 newest_selection_head,
6619 scrolled_content_origin,
6620 window,
6621 cx,
6622 );
6623 } else if target_display_point.row() >= visible_row_range.end {
6624 return self.render_edit_prediction_scroll_popover(
6625 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
6626 IconName::ArrowDown,
6627 visible_row_range,
6628 line_layouts,
6629 newest_selection_head,
6630 scrolled_content_origin,
6631 window,
6632 cx,
6633 );
6634 }
6635
6636 const POLE_WIDTH: Pixels = px(2.);
6637
6638 let line_layout =
6639 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
6640 let target_column = target_display_point.column() as usize;
6641
6642 let target_x = line_layout.x_for_index(target_column);
6643 let target_y =
6644 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
6645
6646 let flag_on_right = target_x < text_bounds.size.width / 2.;
6647
6648 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
6649 border_color.l += 0.001;
6650
6651 let mut element = v_flex()
6652 .items_end()
6653 .when(flag_on_right, |el| el.items_start())
6654 .child(if flag_on_right {
6655 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
6656 .rounded_bl(px(0.))
6657 .rounded_tl(px(0.))
6658 .border_l_2()
6659 .border_color(border_color)
6660 } else {
6661 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
6662 .rounded_br(px(0.))
6663 .rounded_tr(px(0.))
6664 .border_r_2()
6665 .border_color(border_color)
6666 })
6667 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
6668 .into_any();
6669
6670 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6671
6672 let mut origin = scrolled_content_origin + point(target_x, target_y)
6673 - point(
6674 if flag_on_right {
6675 POLE_WIDTH
6676 } else {
6677 size.width - POLE_WIDTH
6678 },
6679 size.height - line_height,
6680 );
6681
6682 origin.x = origin.x.max(content_origin.x);
6683
6684 element.prepaint_at(origin, window, cx);
6685
6686 Some((element, origin))
6687 }
6688
6689 fn render_edit_prediction_scroll_popover(
6690 &mut self,
6691 to_y: impl Fn(Size<Pixels>) -> Pixels,
6692 scroll_icon: IconName,
6693 visible_row_range: Range<DisplayRow>,
6694 line_layouts: &[LineWithInvisibles],
6695 newest_selection_head: Option<DisplayPoint>,
6696 scrolled_content_origin: gpui::Point<Pixels>,
6697 window: &mut Window,
6698 cx: &mut App,
6699 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6700 let mut element = self
6701 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
6702 .into_any();
6703
6704 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6705
6706 let cursor = newest_selection_head?;
6707 let cursor_row_layout =
6708 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
6709 let cursor_column = cursor.column() as usize;
6710
6711 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
6712
6713 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
6714
6715 element.prepaint_at(origin, window, cx);
6716 Some((element, origin))
6717 }
6718
6719 fn render_edit_prediction_eager_jump_popover(
6720 &mut self,
6721 text_bounds: &Bounds<Pixels>,
6722 content_origin: gpui::Point<Pixels>,
6723 editor_snapshot: &EditorSnapshot,
6724 visible_row_range: Range<DisplayRow>,
6725 scroll_top: f32,
6726 scroll_bottom: f32,
6727 line_height: Pixels,
6728 scroll_pixel_position: gpui::Point<Pixels>,
6729 target_display_point: DisplayPoint,
6730 editor_width: Pixels,
6731 window: &mut Window,
6732 cx: &mut App,
6733 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6734 if target_display_point.row().as_f32() < scroll_top {
6735 let mut element = self
6736 .render_edit_prediction_line_popover(
6737 "Jump to Edit",
6738 Some(IconName::ArrowUp),
6739 window,
6740 cx,
6741 )?
6742 .into_any();
6743
6744 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6745 let offset = point(
6746 (text_bounds.size.width - size.width) / 2.,
6747 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
6748 );
6749
6750 let origin = text_bounds.origin + offset;
6751 element.prepaint_at(origin, window, cx);
6752 Some((element, origin))
6753 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
6754 let mut element = self
6755 .render_edit_prediction_line_popover(
6756 "Jump to Edit",
6757 Some(IconName::ArrowDown),
6758 window,
6759 cx,
6760 )?
6761 .into_any();
6762
6763 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6764 let offset = point(
6765 (text_bounds.size.width - size.width) / 2.,
6766 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
6767 );
6768
6769 let origin = text_bounds.origin + offset;
6770 element.prepaint_at(origin, window, cx);
6771 Some((element, origin))
6772 } else {
6773 self.render_edit_prediction_end_of_line_popover(
6774 "Jump to Edit",
6775 editor_snapshot,
6776 visible_row_range,
6777 target_display_point,
6778 line_height,
6779 scroll_pixel_position,
6780 content_origin,
6781 editor_width,
6782 window,
6783 cx,
6784 )
6785 }
6786 }
6787
6788 fn render_edit_prediction_end_of_line_popover(
6789 self: &mut Editor,
6790 label: &'static str,
6791 editor_snapshot: &EditorSnapshot,
6792 visible_row_range: Range<DisplayRow>,
6793 target_display_point: DisplayPoint,
6794 line_height: Pixels,
6795 scroll_pixel_position: gpui::Point<Pixels>,
6796 content_origin: gpui::Point<Pixels>,
6797 editor_width: Pixels,
6798 window: &mut Window,
6799 cx: &mut App,
6800 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6801 let target_line_end = DisplayPoint::new(
6802 target_display_point.row(),
6803 editor_snapshot.line_len(target_display_point.row()),
6804 );
6805
6806 let mut element = self
6807 .render_edit_prediction_line_popover(label, None, window, cx)?
6808 .into_any();
6809
6810 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6811
6812 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
6813
6814 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
6815 let mut origin = start_point
6816 + line_origin
6817 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
6818 origin.x = origin.x.max(content_origin.x);
6819
6820 let max_x = content_origin.x + editor_width - size.width;
6821
6822 if origin.x > max_x {
6823 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
6824
6825 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
6826 origin.y += offset;
6827 IconName::ArrowUp
6828 } else {
6829 origin.y -= offset;
6830 IconName::ArrowDown
6831 };
6832
6833 element = self
6834 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
6835 .into_any();
6836
6837 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6838
6839 origin.x = content_origin.x + editor_width - size.width - px(2.);
6840 }
6841
6842 element.prepaint_at(origin, window, cx);
6843 Some((element, origin))
6844 }
6845
6846 fn render_edit_prediction_diff_popover(
6847 self: &Editor,
6848 text_bounds: &Bounds<Pixels>,
6849 content_origin: gpui::Point<Pixels>,
6850 editor_snapshot: &EditorSnapshot,
6851 visible_row_range: Range<DisplayRow>,
6852 line_layouts: &[LineWithInvisibles],
6853 line_height: Pixels,
6854 scroll_pixel_position: gpui::Point<Pixels>,
6855 newest_selection_head: Option<DisplayPoint>,
6856 editor_width: Pixels,
6857 style: &EditorStyle,
6858 edits: &Vec<(Range<Anchor>, String)>,
6859 edit_preview: &Option<language::EditPreview>,
6860 snapshot: &language::BufferSnapshot,
6861 window: &mut Window,
6862 cx: &mut App,
6863 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6864 let edit_start = edits
6865 .first()
6866 .unwrap()
6867 .0
6868 .start
6869 .to_display_point(editor_snapshot);
6870 let edit_end = edits
6871 .last()
6872 .unwrap()
6873 .0
6874 .end
6875 .to_display_point(editor_snapshot);
6876
6877 let is_visible = visible_row_range.contains(&edit_start.row())
6878 || visible_row_range.contains(&edit_end.row());
6879 if !is_visible {
6880 return None;
6881 }
6882
6883 let highlighted_edits =
6884 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
6885
6886 let styled_text = highlighted_edits.to_styled_text(&style.text);
6887 let line_count = highlighted_edits.text.lines().count();
6888
6889 const BORDER_WIDTH: Pixels = px(1.);
6890
6891 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
6892 let has_keybind = keybind.is_some();
6893
6894 let mut element = h_flex()
6895 .items_start()
6896 .child(
6897 h_flex()
6898 .bg(cx.theme().colors().editor_background)
6899 .border(BORDER_WIDTH)
6900 .shadow_sm()
6901 .border_color(cx.theme().colors().border)
6902 .rounded_l_lg()
6903 .when(line_count > 1, |el| el.rounded_br_lg())
6904 .pr_1()
6905 .child(styled_text),
6906 )
6907 .child(
6908 h_flex()
6909 .h(line_height + BORDER_WIDTH * 2.)
6910 .px_1p5()
6911 .gap_1()
6912 // Workaround: For some reason, there's a gap if we don't do this
6913 .ml(-BORDER_WIDTH)
6914 .shadow(smallvec![gpui::BoxShadow {
6915 color: gpui::black().opacity(0.05),
6916 offset: point(px(1.), px(1.)),
6917 blur_radius: px(2.),
6918 spread_radius: px(0.),
6919 }])
6920 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
6921 .border(BORDER_WIDTH)
6922 .border_color(cx.theme().colors().border)
6923 .rounded_r_lg()
6924 .id("edit_prediction_diff_popover_keybind")
6925 .when(!has_keybind, |el| {
6926 let status_colors = cx.theme().status();
6927
6928 el.bg(status_colors.error_background)
6929 .border_color(status_colors.error.opacity(0.6))
6930 .child(Icon::new(IconName::Info).color(Color::Error))
6931 .cursor_default()
6932 .hoverable_tooltip(move |_window, cx| {
6933 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
6934 })
6935 })
6936 .children(keybind),
6937 )
6938 .into_any();
6939
6940 let longest_row =
6941 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
6942 let longest_line_width = if visible_row_range.contains(&longest_row) {
6943 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
6944 } else {
6945 layout_line(
6946 longest_row,
6947 editor_snapshot,
6948 style,
6949 editor_width,
6950 |_| false,
6951 window,
6952 cx,
6953 )
6954 .width
6955 };
6956
6957 let viewport_bounds =
6958 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
6959 right: -EditorElement::SCROLLBAR_WIDTH,
6960 ..Default::default()
6961 });
6962
6963 let x_after_longest =
6964 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
6965 - scroll_pixel_position.x;
6966
6967 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6968
6969 // Fully visible if it can be displayed within the window (allow overlapping other
6970 // panes). However, this is only allowed if the popover starts within text_bounds.
6971 let can_position_to_the_right = x_after_longest < text_bounds.right()
6972 && x_after_longest + element_bounds.width < viewport_bounds.right();
6973
6974 let mut origin = if can_position_to_the_right {
6975 point(
6976 x_after_longest,
6977 text_bounds.origin.y + edit_start.row().as_f32() * line_height
6978 - scroll_pixel_position.y,
6979 )
6980 } else {
6981 let cursor_row = newest_selection_head.map(|head| head.row());
6982 let above_edit = edit_start
6983 .row()
6984 .0
6985 .checked_sub(line_count as u32)
6986 .map(DisplayRow);
6987 let below_edit = Some(edit_end.row() + 1);
6988 let above_cursor =
6989 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
6990 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
6991
6992 // Place the edit popover adjacent to the edit if there is a location
6993 // available that is onscreen and does not obscure the cursor. Otherwise,
6994 // place it adjacent to the cursor.
6995 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
6996 .into_iter()
6997 .flatten()
6998 .find(|&start_row| {
6999 let end_row = start_row + line_count as u32;
7000 visible_row_range.contains(&start_row)
7001 && visible_row_range.contains(&end_row)
7002 && cursor_row.map_or(true, |cursor_row| {
7003 !((start_row..end_row).contains(&cursor_row))
7004 })
7005 })?;
7006
7007 content_origin
7008 + point(
7009 -scroll_pixel_position.x,
7010 row_target.as_f32() * line_height - scroll_pixel_position.y,
7011 )
7012 };
7013
7014 origin.x -= BORDER_WIDTH;
7015
7016 window.defer_draw(element, origin, 1);
7017
7018 // Do not return an element, since it will already be drawn due to defer_draw.
7019 None
7020 }
7021
7022 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
7023 px(30.)
7024 }
7025
7026 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
7027 if self.read_only(cx) {
7028 cx.theme().players().read_only()
7029 } else {
7030 self.style.as_ref().unwrap().local_player
7031 }
7032 }
7033
7034 fn render_edit_prediction_accept_keybind(
7035 &self,
7036 window: &mut Window,
7037 cx: &App,
7038 ) -> Option<AnyElement> {
7039 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
7040 let accept_keystroke = accept_binding.keystroke()?;
7041
7042 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7043
7044 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
7045 Color::Accent
7046 } else {
7047 Color::Muted
7048 };
7049
7050 h_flex()
7051 .px_0p5()
7052 .when(is_platform_style_mac, |parent| parent.gap_0p5())
7053 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7054 .text_size(TextSize::XSmall.rems(cx))
7055 .child(h_flex().children(ui::render_modifiers(
7056 &accept_keystroke.modifiers,
7057 PlatformStyle::platform(),
7058 Some(modifiers_color),
7059 Some(IconSize::XSmall.rems().into()),
7060 true,
7061 )))
7062 .when(is_platform_style_mac, |parent| {
7063 parent.child(accept_keystroke.key.clone())
7064 })
7065 .when(!is_platform_style_mac, |parent| {
7066 parent.child(
7067 Key::new(
7068 util::capitalize(&accept_keystroke.key),
7069 Some(Color::Default),
7070 )
7071 .size(Some(IconSize::XSmall.rems().into())),
7072 )
7073 })
7074 .into_any()
7075 .into()
7076 }
7077
7078 fn render_edit_prediction_line_popover(
7079 &self,
7080 label: impl Into<SharedString>,
7081 icon: Option<IconName>,
7082 window: &mut Window,
7083 cx: &App,
7084 ) -> Option<Stateful<Div>> {
7085 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
7086
7087 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7088 let has_keybind = keybind.is_some();
7089
7090 let result = h_flex()
7091 .id("ep-line-popover")
7092 .py_0p5()
7093 .pl_1()
7094 .pr(padding_right)
7095 .gap_1()
7096 .rounded_md()
7097 .border_1()
7098 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7099 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7100 .shadow_sm()
7101 .when(!has_keybind, |el| {
7102 let status_colors = cx.theme().status();
7103
7104 el.bg(status_colors.error_background)
7105 .border_color(status_colors.error.opacity(0.6))
7106 .pl_2()
7107 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7108 .cursor_default()
7109 .hoverable_tooltip(move |_window, cx| {
7110 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7111 })
7112 })
7113 .children(keybind)
7114 .child(
7115 Label::new(label)
7116 .size(LabelSize::Small)
7117 .when(!has_keybind, |el| {
7118 el.color(cx.theme().status().error.into()).strikethrough()
7119 }),
7120 )
7121 .when(!has_keybind, |el| {
7122 el.child(
7123 h_flex().ml_1().child(
7124 Icon::new(IconName::Info)
7125 .size(IconSize::Small)
7126 .color(cx.theme().status().error.into()),
7127 ),
7128 )
7129 })
7130 .when_some(icon, |element, icon| {
7131 element.child(
7132 div()
7133 .mt(px(1.5))
7134 .child(Icon::new(icon).size(IconSize::Small)),
7135 )
7136 });
7137
7138 Some(result)
7139 }
7140
7141 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
7142 let accent_color = cx.theme().colors().text_accent;
7143 let editor_bg_color = cx.theme().colors().editor_background;
7144 editor_bg_color.blend(accent_color.opacity(0.1))
7145 }
7146
7147 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
7148 let accent_color = cx.theme().colors().text_accent;
7149 let editor_bg_color = cx.theme().colors().editor_background;
7150 editor_bg_color.blend(accent_color.opacity(0.6))
7151 }
7152
7153 fn render_edit_prediction_cursor_popover(
7154 &self,
7155 min_width: Pixels,
7156 max_width: Pixels,
7157 cursor_point: Point,
7158 style: &EditorStyle,
7159 accept_keystroke: Option<&gpui::Keystroke>,
7160 _window: &Window,
7161 cx: &mut Context<Editor>,
7162 ) -> Option<AnyElement> {
7163 let provider = self.edit_prediction_provider.as_ref()?;
7164
7165 if provider.provider.needs_terms_acceptance(cx) {
7166 return Some(
7167 h_flex()
7168 .min_w(min_width)
7169 .flex_1()
7170 .px_2()
7171 .py_1()
7172 .gap_3()
7173 .elevation_2(cx)
7174 .hover(|style| style.bg(cx.theme().colors().element_hover))
7175 .id("accept-terms")
7176 .cursor_pointer()
7177 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
7178 .on_click(cx.listener(|this, _event, window, cx| {
7179 cx.stop_propagation();
7180 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
7181 window.dispatch_action(
7182 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
7183 cx,
7184 );
7185 }))
7186 .child(
7187 h_flex()
7188 .flex_1()
7189 .gap_2()
7190 .child(Icon::new(IconName::ZedPredict))
7191 .child(Label::new("Accept Terms of Service"))
7192 .child(div().w_full())
7193 .child(
7194 Icon::new(IconName::ArrowUpRight)
7195 .color(Color::Muted)
7196 .size(IconSize::Small),
7197 )
7198 .into_any_element(),
7199 )
7200 .into_any(),
7201 );
7202 }
7203
7204 let is_refreshing = provider.provider.is_refreshing(cx);
7205
7206 fn pending_completion_container() -> Div {
7207 h_flex()
7208 .h_full()
7209 .flex_1()
7210 .gap_2()
7211 .child(Icon::new(IconName::ZedPredict))
7212 }
7213
7214 let completion = match &self.active_inline_completion {
7215 Some(prediction) => {
7216 if !self.has_visible_completions_menu() {
7217 const RADIUS: Pixels = px(6.);
7218 const BORDER_WIDTH: Pixels = px(1.);
7219
7220 return Some(
7221 h_flex()
7222 .elevation_2(cx)
7223 .border(BORDER_WIDTH)
7224 .border_color(cx.theme().colors().border)
7225 .when(accept_keystroke.is_none(), |el| {
7226 el.border_color(cx.theme().status().error)
7227 })
7228 .rounded(RADIUS)
7229 .rounded_tl(px(0.))
7230 .overflow_hidden()
7231 .child(div().px_1p5().child(match &prediction.completion {
7232 InlineCompletion::Move { target, snapshot } => {
7233 use text::ToPoint as _;
7234 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
7235 {
7236 Icon::new(IconName::ZedPredictDown)
7237 } else {
7238 Icon::new(IconName::ZedPredictUp)
7239 }
7240 }
7241 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
7242 }))
7243 .child(
7244 h_flex()
7245 .gap_1()
7246 .py_1()
7247 .px_2()
7248 .rounded_r(RADIUS - BORDER_WIDTH)
7249 .border_l_1()
7250 .border_color(cx.theme().colors().border)
7251 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7252 .when(self.edit_prediction_preview.released_too_fast(), |el| {
7253 el.child(
7254 Label::new("Hold")
7255 .size(LabelSize::Small)
7256 .when(accept_keystroke.is_none(), |el| {
7257 el.strikethrough()
7258 })
7259 .line_height_style(LineHeightStyle::UiLabel),
7260 )
7261 })
7262 .id("edit_prediction_cursor_popover_keybind")
7263 .when(accept_keystroke.is_none(), |el| {
7264 let status_colors = cx.theme().status();
7265
7266 el.bg(status_colors.error_background)
7267 .border_color(status_colors.error.opacity(0.6))
7268 .child(Icon::new(IconName::Info).color(Color::Error))
7269 .cursor_default()
7270 .hoverable_tooltip(move |_window, cx| {
7271 cx.new(|_| MissingEditPredictionKeybindingTooltip)
7272 .into()
7273 })
7274 })
7275 .when_some(
7276 accept_keystroke.as_ref(),
7277 |el, accept_keystroke| {
7278 el.child(h_flex().children(ui::render_modifiers(
7279 &accept_keystroke.modifiers,
7280 PlatformStyle::platform(),
7281 Some(Color::Default),
7282 Some(IconSize::XSmall.rems().into()),
7283 false,
7284 )))
7285 },
7286 ),
7287 )
7288 .into_any(),
7289 );
7290 }
7291
7292 self.render_edit_prediction_cursor_popover_preview(
7293 prediction,
7294 cursor_point,
7295 style,
7296 cx,
7297 )?
7298 }
7299
7300 None if is_refreshing => match &self.stale_inline_completion_in_menu {
7301 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
7302 stale_completion,
7303 cursor_point,
7304 style,
7305 cx,
7306 )?,
7307
7308 None => {
7309 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
7310 }
7311 },
7312
7313 None => pending_completion_container().child(Label::new("No Prediction")),
7314 };
7315
7316 let completion = if is_refreshing {
7317 completion
7318 .with_animation(
7319 "loading-completion",
7320 Animation::new(Duration::from_secs(2))
7321 .repeat()
7322 .with_easing(pulsating_between(0.4, 0.8)),
7323 |label, delta| label.opacity(delta),
7324 )
7325 .into_any_element()
7326 } else {
7327 completion.into_any_element()
7328 };
7329
7330 let has_completion = self.active_inline_completion.is_some();
7331
7332 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7333 Some(
7334 h_flex()
7335 .min_w(min_width)
7336 .max_w(max_width)
7337 .flex_1()
7338 .elevation_2(cx)
7339 .border_color(cx.theme().colors().border)
7340 .child(
7341 div()
7342 .flex_1()
7343 .py_1()
7344 .px_2()
7345 .overflow_hidden()
7346 .child(completion),
7347 )
7348 .when_some(accept_keystroke, |el, accept_keystroke| {
7349 if !accept_keystroke.modifiers.modified() {
7350 return el;
7351 }
7352
7353 el.child(
7354 h_flex()
7355 .h_full()
7356 .border_l_1()
7357 .rounded_r_lg()
7358 .border_color(cx.theme().colors().border)
7359 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7360 .gap_1()
7361 .py_1()
7362 .px_2()
7363 .child(
7364 h_flex()
7365 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7366 .when(is_platform_style_mac, |parent| parent.gap_1())
7367 .child(h_flex().children(ui::render_modifiers(
7368 &accept_keystroke.modifiers,
7369 PlatformStyle::platform(),
7370 Some(if !has_completion {
7371 Color::Muted
7372 } else {
7373 Color::Default
7374 }),
7375 None,
7376 false,
7377 ))),
7378 )
7379 .child(Label::new("Preview").into_any_element())
7380 .opacity(if has_completion { 1.0 } else { 0.4 }),
7381 )
7382 })
7383 .into_any(),
7384 )
7385 }
7386
7387 fn render_edit_prediction_cursor_popover_preview(
7388 &self,
7389 completion: &InlineCompletionState,
7390 cursor_point: Point,
7391 style: &EditorStyle,
7392 cx: &mut Context<Editor>,
7393 ) -> Option<Div> {
7394 use text::ToPoint as _;
7395
7396 fn render_relative_row_jump(
7397 prefix: impl Into<String>,
7398 current_row: u32,
7399 target_row: u32,
7400 ) -> Div {
7401 let (row_diff, arrow) = if target_row < current_row {
7402 (current_row - target_row, IconName::ArrowUp)
7403 } else {
7404 (target_row - current_row, IconName::ArrowDown)
7405 };
7406
7407 h_flex()
7408 .child(
7409 Label::new(format!("{}{}", prefix.into(), row_diff))
7410 .color(Color::Muted)
7411 .size(LabelSize::Small),
7412 )
7413 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
7414 }
7415
7416 match &completion.completion {
7417 InlineCompletion::Move {
7418 target, snapshot, ..
7419 } => Some(
7420 h_flex()
7421 .px_2()
7422 .gap_2()
7423 .flex_1()
7424 .child(
7425 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
7426 Icon::new(IconName::ZedPredictDown)
7427 } else {
7428 Icon::new(IconName::ZedPredictUp)
7429 },
7430 )
7431 .child(Label::new("Jump to Edit")),
7432 ),
7433
7434 InlineCompletion::Edit {
7435 edits,
7436 edit_preview,
7437 snapshot,
7438 display_mode: _,
7439 } => {
7440 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
7441
7442 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
7443 &snapshot,
7444 &edits,
7445 edit_preview.as_ref()?,
7446 true,
7447 cx,
7448 )
7449 .first_line_preview();
7450
7451 let styled_text = gpui::StyledText::new(highlighted_edits.text)
7452 .with_default_highlights(&style.text, highlighted_edits.highlights);
7453
7454 let preview = h_flex()
7455 .gap_1()
7456 .min_w_16()
7457 .child(styled_text)
7458 .when(has_more_lines, |parent| parent.child("…"));
7459
7460 let left = if first_edit_row != cursor_point.row {
7461 render_relative_row_jump("", cursor_point.row, first_edit_row)
7462 .into_any_element()
7463 } else {
7464 Icon::new(IconName::ZedPredict).into_any_element()
7465 };
7466
7467 Some(
7468 h_flex()
7469 .h_full()
7470 .flex_1()
7471 .gap_2()
7472 .pr_1()
7473 .overflow_x_hidden()
7474 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7475 .child(left)
7476 .child(preview),
7477 )
7478 }
7479 }
7480 }
7481
7482 fn render_context_menu(
7483 &self,
7484 style: &EditorStyle,
7485 max_height_in_lines: u32,
7486 y_flipped: bool,
7487 window: &mut Window,
7488 cx: &mut Context<Editor>,
7489 ) -> Option<AnyElement> {
7490 let menu = self.context_menu.borrow();
7491 let menu = menu.as_ref()?;
7492 if !menu.visible() {
7493 return None;
7494 };
7495 Some(menu.render(style, max_height_in_lines, y_flipped, window, cx))
7496 }
7497
7498 fn render_context_menu_aside(
7499 &mut self,
7500 max_size: Size<Pixels>,
7501 window: &mut Window,
7502 cx: &mut Context<Editor>,
7503 ) -> Option<AnyElement> {
7504 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
7505 if menu.visible() {
7506 menu.render_aside(self, max_size, window, cx)
7507 } else {
7508 None
7509 }
7510 })
7511 }
7512
7513 fn hide_context_menu(
7514 &mut self,
7515 window: &mut Window,
7516 cx: &mut Context<Self>,
7517 ) -> Option<CodeContextMenu> {
7518 cx.notify();
7519 self.completion_tasks.clear();
7520 let context_menu = self.context_menu.borrow_mut().take();
7521 self.stale_inline_completion_in_menu.take();
7522 self.update_visible_inline_completion(window, cx);
7523 context_menu
7524 }
7525
7526 fn show_snippet_choices(
7527 &mut self,
7528 choices: &Vec<String>,
7529 selection: Range<Anchor>,
7530 cx: &mut Context<Self>,
7531 ) {
7532 if selection.start.buffer_id.is_none() {
7533 return;
7534 }
7535 let buffer_id = selection.start.buffer_id.unwrap();
7536 let buffer = self.buffer().read(cx).buffer(buffer_id);
7537 let id = post_inc(&mut self.next_completion_id);
7538
7539 if let Some(buffer) = buffer {
7540 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
7541 CompletionsMenu::new_snippet_choices(id, true, choices, selection, buffer),
7542 ));
7543 }
7544 }
7545
7546 pub fn insert_snippet(
7547 &mut self,
7548 insertion_ranges: &[Range<usize>],
7549 snippet: Snippet,
7550 window: &mut Window,
7551 cx: &mut Context<Self>,
7552 ) -> Result<()> {
7553 struct Tabstop<T> {
7554 is_end_tabstop: bool,
7555 ranges: Vec<Range<T>>,
7556 choices: Option<Vec<String>>,
7557 }
7558
7559 let tabstops = self.buffer.update(cx, |buffer, cx| {
7560 let snippet_text: Arc<str> = snippet.text.clone().into();
7561 let edits = insertion_ranges
7562 .iter()
7563 .cloned()
7564 .map(|range| (range, snippet_text.clone()));
7565 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
7566
7567 let snapshot = &*buffer.read(cx);
7568 let snippet = &snippet;
7569 snippet
7570 .tabstops
7571 .iter()
7572 .map(|tabstop| {
7573 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
7574 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
7575 });
7576 let mut tabstop_ranges = tabstop
7577 .ranges
7578 .iter()
7579 .flat_map(|tabstop_range| {
7580 let mut delta = 0_isize;
7581 insertion_ranges.iter().map(move |insertion_range| {
7582 let insertion_start = insertion_range.start as isize + delta;
7583 delta +=
7584 snippet.text.len() as isize - insertion_range.len() as isize;
7585
7586 let start = ((insertion_start + tabstop_range.start) as usize)
7587 .min(snapshot.len());
7588 let end = ((insertion_start + tabstop_range.end) as usize)
7589 .min(snapshot.len());
7590 snapshot.anchor_before(start)..snapshot.anchor_after(end)
7591 })
7592 })
7593 .collect::<Vec<_>>();
7594 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
7595
7596 Tabstop {
7597 is_end_tabstop,
7598 ranges: tabstop_ranges,
7599 choices: tabstop.choices.clone(),
7600 }
7601 })
7602 .collect::<Vec<_>>()
7603 });
7604 if let Some(tabstop) = tabstops.first() {
7605 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7606 s.select_ranges(tabstop.ranges.iter().cloned());
7607 });
7608
7609 if let Some(choices) = &tabstop.choices {
7610 if let Some(selection) = tabstop.ranges.first() {
7611 self.show_snippet_choices(choices, selection.clone(), cx)
7612 }
7613 }
7614
7615 // If we're already at the last tabstop and it's at the end of the snippet,
7616 // we're done, we don't need to keep the state around.
7617 if !tabstop.is_end_tabstop {
7618 let choices = tabstops
7619 .iter()
7620 .map(|tabstop| tabstop.choices.clone())
7621 .collect();
7622
7623 let ranges = tabstops
7624 .into_iter()
7625 .map(|tabstop| tabstop.ranges)
7626 .collect::<Vec<_>>();
7627
7628 self.snippet_stack.push(SnippetState {
7629 active_index: 0,
7630 ranges,
7631 choices,
7632 });
7633 }
7634
7635 // Check whether the just-entered snippet ends with an auto-closable bracket.
7636 if self.autoclose_regions.is_empty() {
7637 let snapshot = self.buffer.read(cx).snapshot(cx);
7638 for selection in &mut self.selections.all::<Point>(cx) {
7639 let selection_head = selection.head();
7640 let Some(scope) = snapshot.language_scope_at(selection_head) else {
7641 continue;
7642 };
7643
7644 let mut bracket_pair = None;
7645 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
7646 let prev_chars = snapshot
7647 .reversed_chars_at(selection_head)
7648 .collect::<String>();
7649 for (pair, enabled) in scope.brackets() {
7650 if enabled
7651 && pair.close
7652 && prev_chars.starts_with(pair.start.as_str())
7653 && next_chars.starts_with(pair.end.as_str())
7654 {
7655 bracket_pair = Some(pair.clone());
7656 break;
7657 }
7658 }
7659 if let Some(pair) = bracket_pair {
7660 let start = snapshot.anchor_after(selection_head);
7661 let end = snapshot.anchor_after(selection_head);
7662 self.autoclose_regions.push(AutocloseRegion {
7663 selection_id: selection.id,
7664 range: start..end,
7665 pair,
7666 });
7667 }
7668 }
7669 }
7670 }
7671 Ok(())
7672 }
7673
7674 pub fn move_to_next_snippet_tabstop(
7675 &mut self,
7676 window: &mut Window,
7677 cx: &mut Context<Self>,
7678 ) -> bool {
7679 self.move_to_snippet_tabstop(Bias::Right, window, cx)
7680 }
7681
7682 pub fn move_to_prev_snippet_tabstop(
7683 &mut self,
7684 window: &mut Window,
7685 cx: &mut Context<Self>,
7686 ) -> bool {
7687 self.move_to_snippet_tabstop(Bias::Left, window, cx)
7688 }
7689
7690 pub fn move_to_snippet_tabstop(
7691 &mut self,
7692 bias: Bias,
7693 window: &mut Window,
7694 cx: &mut Context<Self>,
7695 ) -> bool {
7696 if let Some(mut snippet) = self.snippet_stack.pop() {
7697 match bias {
7698 Bias::Left => {
7699 if snippet.active_index > 0 {
7700 snippet.active_index -= 1;
7701 } else {
7702 self.snippet_stack.push(snippet);
7703 return false;
7704 }
7705 }
7706 Bias::Right => {
7707 if snippet.active_index + 1 < snippet.ranges.len() {
7708 snippet.active_index += 1;
7709 } else {
7710 self.snippet_stack.push(snippet);
7711 return false;
7712 }
7713 }
7714 }
7715 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
7716 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7717 s.select_anchor_ranges(current_ranges.iter().cloned())
7718 });
7719
7720 if let Some(choices) = &snippet.choices[snippet.active_index] {
7721 if let Some(selection) = current_ranges.first() {
7722 self.show_snippet_choices(&choices, selection.clone(), cx);
7723 }
7724 }
7725
7726 // If snippet state is not at the last tabstop, push it back on the stack
7727 if snippet.active_index + 1 < snippet.ranges.len() {
7728 self.snippet_stack.push(snippet);
7729 }
7730 return true;
7731 }
7732 }
7733
7734 false
7735 }
7736
7737 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7738 self.transact(window, cx, |this, window, cx| {
7739 this.select_all(&SelectAll, window, cx);
7740 this.insert("", window, cx);
7741 });
7742 }
7743
7744 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
7745 self.transact(window, cx, |this, window, cx| {
7746 this.select_autoclose_pair(window, cx);
7747 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
7748 if !this.linked_edit_ranges.is_empty() {
7749 let selections = this.selections.all::<MultiBufferPoint>(cx);
7750 let snapshot = this.buffer.read(cx).snapshot(cx);
7751
7752 for selection in selections.iter() {
7753 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
7754 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
7755 if selection_start.buffer_id != selection_end.buffer_id {
7756 continue;
7757 }
7758 if let Some(ranges) =
7759 this.linked_editing_ranges_for(selection_start..selection_end, cx)
7760 {
7761 for (buffer, entries) in ranges {
7762 linked_ranges.entry(buffer).or_default().extend(entries);
7763 }
7764 }
7765 }
7766 }
7767
7768 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
7769 if !this.selections.line_mode {
7770 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
7771 for selection in &mut selections {
7772 if selection.is_empty() {
7773 let old_head = selection.head();
7774 let mut new_head =
7775 movement::left(&display_map, old_head.to_display_point(&display_map))
7776 .to_point(&display_map);
7777 if let Some((buffer, line_buffer_range)) = display_map
7778 .buffer_snapshot
7779 .buffer_line_for_row(MultiBufferRow(old_head.row))
7780 {
7781 let indent_size =
7782 buffer.indent_size_for_line(line_buffer_range.start.row);
7783 let indent_len = match indent_size.kind {
7784 IndentKind::Space => {
7785 buffer.settings_at(line_buffer_range.start, cx).tab_size
7786 }
7787 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
7788 };
7789 if old_head.column <= indent_size.len && old_head.column > 0 {
7790 let indent_len = indent_len.get();
7791 new_head = cmp::min(
7792 new_head,
7793 MultiBufferPoint::new(
7794 old_head.row,
7795 ((old_head.column - 1) / indent_len) * indent_len,
7796 ),
7797 );
7798 }
7799 }
7800
7801 selection.set_head(new_head, SelectionGoal::None);
7802 }
7803 }
7804 }
7805
7806 this.signature_help_state.set_backspace_pressed(true);
7807 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7808 s.select(selections)
7809 });
7810 this.insert("", window, cx);
7811 let empty_str: Arc<str> = Arc::from("");
7812 for (buffer, edits) in linked_ranges {
7813 let snapshot = buffer.read(cx).snapshot();
7814 use text::ToPoint as TP;
7815
7816 let edits = edits
7817 .into_iter()
7818 .map(|range| {
7819 let end_point = TP::to_point(&range.end, &snapshot);
7820 let mut start_point = TP::to_point(&range.start, &snapshot);
7821
7822 if end_point == start_point {
7823 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
7824 .saturating_sub(1);
7825 start_point =
7826 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
7827 };
7828
7829 (start_point..end_point, empty_str.clone())
7830 })
7831 .sorted_by_key(|(range, _)| range.start)
7832 .collect::<Vec<_>>();
7833 buffer.update(cx, |this, cx| {
7834 this.edit(edits, None, cx);
7835 })
7836 }
7837 this.refresh_inline_completion(true, false, window, cx);
7838 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
7839 });
7840 }
7841
7842 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
7843 self.transact(window, cx, |this, window, cx| {
7844 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7845 let line_mode = s.line_mode;
7846 s.move_with(|map, selection| {
7847 if selection.is_empty() && !line_mode {
7848 let cursor = movement::right(map, selection.head());
7849 selection.end = cursor;
7850 selection.reversed = true;
7851 selection.goal = SelectionGoal::None;
7852 }
7853 })
7854 });
7855 this.insert("", window, cx);
7856 this.refresh_inline_completion(true, false, window, cx);
7857 });
7858 }
7859
7860 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
7861 if self.move_to_prev_snippet_tabstop(window, cx) {
7862 return;
7863 }
7864
7865 self.outdent(&Outdent, window, cx);
7866 }
7867
7868 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
7869 if self.move_to_next_snippet_tabstop(window, cx) || self.read_only(cx) {
7870 return;
7871 }
7872
7873 let mut selections = self.selections.all_adjusted(cx);
7874 let buffer = self.buffer.read(cx);
7875 let snapshot = buffer.snapshot(cx);
7876 let rows_iter = selections.iter().map(|s| s.head().row);
7877 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
7878
7879 let mut edits = Vec::new();
7880 let mut prev_edited_row = 0;
7881 let mut row_delta = 0;
7882 for selection in &mut selections {
7883 if selection.start.row != prev_edited_row {
7884 row_delta = 0;
7885 }
7886 prev_edited_row = selection.end.row;
7887
7888 // If the selection is non-empty, then increase the indentation of the selected lines.
7889 if !selection.is_empty() {
7890 row_delta =
7891 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
7892 continue;
7893 }
7894
7895 // If the selection is empty and the cursor is in the leading whitespace before the
7896 // suggested indentation, then auto-indent the line.
7897 let cursor = selection.head();
7898 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
7899 if let Some(suggested_indent) =
7900 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
7901 {
7902 if cursor.column < suggested_indent.len
7903 && cursor.column <= current_indent.len
7904 && current_indent.len <= suggested_indent.len
7905 {
7906 selection.start = Point::new(cursor.row, suggested_indent.len);
7907 selection.end = selection.start;
7908 if row_delta == 0 {
7909 edits.extend(Buffer::edit_for_indent_size_adjustment(
7910 cursor.row,
7911 current_indent,
7912 suggested_indent,
7913 ));
7914 row_delta = suggested_indent.len - current_indent.len;
7915 }
7916 continue;
7917 }
7918 }
7919
7920 // Otherwise, insert a hard or soft tab.
7921 let settings = buffer.language_settings_at(cursor, cx);
7922 let tab_size = if settings.hard_tabs {
7923 IndentSize::tab()
7924 } else {
7925 let tab_size = settings.tab_size.get();
7926 let char_column = snapshot
7927 .text_for_range(Point::new(cursor.row, 0)..cursor)
7928 .flat_map(str::chars)
7929 .count()
7930 + row_delta as usize;
7931 let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size);
7932 IndentSize::spaces(chars_to_next_tab_stop)
7933 };
7934 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
7935 selection.end = selection.start;
7936 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
7937 row_delta += tab_size.len;
7938 }
7939
7940 self.transact(window, cx, |this, window, cx| {
7941 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
7942 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7943 s.select(selections)
7944 });
7945 this.refresh_inline_completion(true, false, window, cx);
7946 });
7947 }
7948
7949 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
7950 if self.read_only(cx) {
7951 return;
7952 }
7953 let mut selections = self.selections.all::<Point>(cx);
7954 let mut prev_edited_row = 0;
7955 let mut row_delta = 0;
7956 let mut edits = Vec::new();
7957 let buffer = self.buffer.read(cx);
7958 let snapshot = buffer.snapshot(cx);
7959 for selection in &mut selections {
7960 if selection.start.row != prev_edited_row {
7961 row_delta = 0;
7962 }
7963 prev_edited_row = selection.end.row;
7964
7965 row_delta =
7966 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
7967 }
7968
7969 self.transact(window, cx, |this, window, cx| {
7970 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
7971 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7972 s.select(selections)
7973 });
7974 });
7975 }
7976
7977 fn indent_selection(
7978 buffer: &MultiBuffer,
7979 snapshot: &MultiBufferSnapshot,
7980 selection: &mut Selection<Point>,
7981 edits: &mut Vec<(Range<Point>, String)>,
7982 delta_for_start_row: u32,
7983 cx: &App,
7984 ) -> u32 {
7985 let settings = buffer.language_settings_at(selection.start, cx);
7986 let tab_size = settings.tab_size.get();
7987 let indent_kind = if settings.hard_tabs {
7988 IndentKind::Tab
7989 } else {
7990 IndentKind::Space
7991 };
7992 let mut start_row = selection.start.row;
7993 let mut end_row = selection.end.row + 1;
7994
7995 // If a selection ends at the beginning of a line, don't indent
7996 // that last line.
7997 if selection.end.column == 0 && selection.end.row > selection.start.row {
7998 end_row -= 1;
7999 }
8000
8001 // Avoid re-indenting a row that has already been indented by a
8002 // previous selection, but still update this selection's column
8003 // to reflect that indentation.
8004 if delta_for_start_row > 0 {
8005 start_row += 1;
8006 selection.start.column += delta_for_start_row;
8007 if selection.end.row == selection.start.row {
8008 selection.end.column += delta_for_start_row;
8009 }
8010 }
8011
8012 let mut delta_for_end_row = 0;
8013 let has_multiple_rows = start_row + 1 != end_row;
8014 for row in start_row..end_row {
8015 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
8016 let indent_delta = match (current_indent.kind, indent_kind) {
8017 (IndentKind::Space, IndentKind::Space) => {
8018 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
8019 IndentSize::spaces(columns_to_next_tab_stop)
8020 }
8021 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
8022 (_, IndentKind::Tab) => IndentSize::tab(),
8023 };
8024
8025 let start = if has_multiple_rows || current_indent.len < selection.start.column {
8026 0
8027 } else {
8028 selection.start.column
8029 };
8030 let row_start = Point::new(row, start);
8031 edits.push((
8032 row_start..row_start,
8033 indent_delta.chars().collect::<String>(),
8034 ));
8035
8036 // Update this selection's endpoints to reflect the indentation.
8037 if row == selection.start.row {
8038 selection.start.column += indent_delta.len;
8039 }
8040 if row == selection.end.row {
8041 selection.end.column += indent_delta.len;
8042 delta_for_end_row = indent_delta.len;
8043 }
8044 }
8045
8046 if selection.start.row == selection.end.row {
8047 delta_for_start_row + delta_for_end_row
8048 } else {
8049 delta_for_end_row
8050 }
8051 }
8052
8053 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
8054 if self.read_only(cx) {
8055 return;
8056 }
8057 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8058 let selections = self.selections.all::<Point>(cx);
8059 let mut deletion_ranges = Vec::new();
8060 let mut last_outdent = None;
8061 {
8062 let buffer = self.buffer.read(cx);
8063 let snapshot = buffer.snapshot(cx);
8064 for selection in &selections {
8065 let settings = buffer.language_settings_at(selection.start, cx);
8066 let tab_size = settings.tab_size.get();
8067 let mut rows = selection.spanned_rows(false, &display_map);
8068
8069 // Avoid re-outdenting a row that has already been outdented by a
8070 // previous selection.
8071 if let Some(last_row) = last_outdent {
8072 if last_row == rows.start {
8073 rows.start = rows.start.next_row();
8074 }
8075 }
8076 let has_multiple_rows = rows.len() > 1;
8077 for row in rows.iter_rows() {
8078 let indent_size = snapshot.indent_size_for_line(row);
8079 if indent_size.len > 0 {
8080 let deletion_len = match indent_size.kind {
8081 IndentKind::Space => {
8082 let columns_to_prev_tab_stop = indent_size.len % tab_size;
8083 if columns_to_prev_tab_stop == 0 {
8084 tab_size
8085 } else {
8086 columns_to_prev_tab_stop
8087 }
8088 }
8089 IndentKind::Tab => 1,
8090 };
8091 let start = if has_multiple_rows
8092 || deletion_len > selection.start.column
8093 || indent_size.len < selection.start.column
8094 {
8095 0
8096 } else {
8097 selection.start.column - deletion_len
8098 };
8099 deletion_ranges.push(
8100 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
8101 );
8102 last_outdent = Some(row);
8103 }
8104 }
8105 }
8106 }
8107
8108 self.transact(window, cx, |this, window, cx| {
8109 this.buffer.update(cx, |buffer, cx| {
8110 let empty_str: Arc<str> = Arc::default();
8111 buffer.edit(
8112 deletion_ranges
8113 .into_iter()
8114 .map(|range| (range, empty_str.clone())),
8115 None,
8116 cx,
8117 );
8118 });
8119 let selections = this.selections.all::<usize>(cx);
8120 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8121 s.select(selections)
8122 });
8123 });
8124 }
8125
8126 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
8127 if self.read_only(cx) {
8128 return;
8129 }
8130 let selections = self
8131 .selections
8132 .all::<usize>(cx)
8133 .into_iter()
8134 .map(|s| s.range());
8135
8136 self.transact(window, cx, |this, window, cx| {
8137 this.buffer.update(cx, |buffer, cx| {
8138 buffer.autoindent_ranges(selections, cx);
8139 });
8140 let selections = this.selections.all::<usize>(cx);
8141 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8142 s.select(selections)
8143 });
8144 });
8145 }
8146
8147 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
8148 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8149 let selections = self.selections.all::<Point>(cx);
8150
8151 let mut new_cursors = Vec::new();
8152 let mut edit_ranges = Vec::new();
8153 let mut selections = selections.iter().peekable();
8154 while let Some(selection) = selections.next() {
8155 let mut rows = selection.spanned_rows(false, &display_map);
8156 let goal_display_column = selection.head().to_display_point(&display_map).column();
8157
8158 // Accumulate contiguous regions of rows that we want to delete.
8159 while let Some(next_selection) = selections.peek() {
8160 let next_rows = next_selection.spanned_rows(false, &display_map);
8161 if next_rows.start <= rows.end {
8162 rows.end = next_rows.end;
8163 selections.next().unwrap();
8164 } else {
8165 break;
8166 }
8167 }
8168
8169 let buffer = &display_map.buffer_snapshot;
8170 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
8171 let edit_end;
8172 let cursor_buffer_row;
8173 if buffer.max_point().row >= rows.end.0 {
8174 // If there's a line after the range, delete the \n from the end of the row range
8175 // and position the cursor on the next line.
8176 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
8177 cursor_buffer_row = rows.end;
8178 } else {
8179 // If there isn't a line after the range, delete the \n from the line before the
8180 // start of the row range and position the cursor there.
8181 edit_start = edit_start.saturating_sub(1);
8182 edit_end = buffer.len();
8183 cursor_buffer_row = rows.start.previous_row();
8184 }
8185
8186 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
8187 *cursor.column_mut() =
8188 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
8189
8190 new_cursors.push((
8191 selection.id,
8192 buffer.anchor_after(cursor.to_point(&display_map)),
8193 ));
8194 edit_ranges.push(edit_start..edit_end);
8195 }
8196
8197 self.transact(window, cx, |this, window, cx| {
8198 let buffer = this.buffer.update(cx, |buffer, cx| {
8199 let empty_str: Arc<str> = Arc::default();
8200 buffer.edit(
8201 edit_ranges
8202 .into_iter()
8203 .map(|range| (range, empty_str.clone())),
8204 None,
8205 cx,
8206 );
8207 buffer.snapshot(cx)
8208 });
8209 let new_selections = new_cursors
8210 .into_iter()
8211 .map(|(id, cursor)| {
8212 let cursor = cursor.to_point(&buffer);
8213 Selection {
8214 id,
8215 start: cursor,
8216 end: cursor,
8217 reversed: false,
8218 goal: SelectionGoal::None,
8219 }
8220 })
8221 .collect();
8222
8223 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8224 s.select(new_selections);
8225 });
8226 });
8227 }
8228
8229 pub fn join_lines_impl(
8230 &mut self,
8231 insert_whitespace: bool,
8232 window: &mut Window,
8233 cx: &mut Context<Self>,
8234 ) {
8235 if self.read_only(cx) {
8236 return;
8237 }
8238 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
8239 for selection in self.selections.all::<Point>(cx) {
8240 let start = MultiBufferRow(selection.start.row);
8241 // Treat single line selections as if they include the next line. Otherwise this action
8242 // would do nothing for single line selections individual cursors.
8243 let end = if selection.start.row == selection.end.row {
8244 MultiBufferRow(selection.start.row + 1)
8245 } else {
8246 MultiBufferRow(selection.end.row)
8247 };
8248
8249 if let Some(last_row_range) = row_ranges.last_mut() {
8250 if start <= last_row_range.end {
8251 last_row_range.end = end;
8252 continue;
8253 }
8254 }
8255 row_ranges.push(start..end);
8256 }
8257
8258 let snapshot = self.buffer.read(cx).snapshot(cx);
8259 let mut cursor_positions = Vec::new();
8260 for row_range in &row_ranges {
8261 let anchor = snapshot.anchor_before(Point::new(
8262 row_range.end.previous_row().0,
8263 snapshot.line_len(row_range.end.previous_row()),
8264 ));
8265 cursor_positions.push(anchor..anchor);
8266 }
8267
8268 self.transact(window, cx, |this, window, cx| {
8269 for row_range in row_ranges.into_iter().rev() {
8270 for row in row_range.iter_rows().rev() {
8271 let end_of_line = Point::new(row.0, snapshot.line_len(row));
8272 let next_line_row = row.next_row();
8273 let indent = snapshot.indent_size_for_line(next_line_row);
8274 let start_of_next_line = Point::new(next_line_row.0, indent.len);
8275
8276 let replace =
8277 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
8278 " "
8279 } else {
8280 ""
8281 };
8282
8283 this.buffer.update(cx, |buffer, cx| {
8284 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
8285 });
8286 }
8287 }
8288
8289 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8290 s.select_anchor_ranges(cursor_positions)
8291 });
8292 });
8293 }
8294
8295 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
8296 self.join_lines_impl(true, window, cx);
8297 }
8298
8299 pub fn sort_lines_case_sensitive(
8300 &mut self,
8301 _: &SortLinesCaseSensitive,
8302 window: &mut Window,
8303 cx: &mut Context<Self>,
8304 ) {
8305 self.manipulate_lines(window, cx, |lines| lines.sort())
8306 }
8307
8308 pub fn sort_lines_case_insensitive(
8309 &mut self,
8310 _: &SortLinesCaseInsensitive,
8311 window: &mut Window,
8312 cx: &mut Context<Self>,
8313 ) {
8314 self.manipulate_lines(window, cx, |lines| {
8315 lines.sort_by_key(|line| line.to_lowercase())
8316 })
8317 }
8318
8319 pub fn unique_lines_case_insensitive(
8320 &mut self,
8321 _: &UniqueLinesCaseInsensitive,
8322 window: &mut Window,
8323 cx: &mut Context<Self>,
8324 ) {
8325 self.manipulate_lines(window, cx, |lines| {
8326 let mut seen = HashSet::default();
8327 lines.retain(|line| seen.insert(line.to_lowercase()));
8328 })
8329 }
8330
8331 pub fn unique_lines_case_sensitive(
8332 &mut self,
8333 _: &UniqueLinesCaseSensitive,
8334 window: &mut Window,
8335 cx: &mut Context<Self>,
8336 ) {
8337 self.manipulate_lines(window, cx, |lines| {
8338 let mut seen = HashSet::default();
8339 lines.retain(|line| seen.insert(*line));
8340 })
8341 }
8342
8343 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
8344 let Some(project) = self.project.clone() else {
8345 return;
8346 };
8347 self.reload(project, window, cx)
8348 .detach_and_notify_err(window, cx);
8349 }
8350
8351 pub fn restore_file(
8352 &mut self,
8353 _: &::git::RestoreFile,
8354 window: &mut Window,
8355 cx: &mut Context<Self>,
8356 ) {
8357 let mut buffer_ids = HashSet::default();
8358 let snapshot = self.buffer().read(cx).snapshot(cx);
8359 for selection in self.selections.all::<usize>(cx) {
8360 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
8361 }
8362
8363 let buffer = self.buffer().read(cx);
8364 let ranges = buffer_ids
8365 .into_iter()
8366 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
8367 .collect::<Vec<_>>();
8368
8369 self.restore_hunks_in_ranges(ranges, window, cx);
8370 }
8371
8372 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
8373 let selections = self
8374 .selections
8375 .all(cx)
8376 .into_iter()
8377 .map(|s| s.range())
8378 .collect();
8379 self.restore_hunks_in_ranges(selections, window, cx);
8380 }
8381
8382 fn restore_hunks_in_ranges(
8383 &mut self,
8384 ranges: Vec<Range<Point>>,
8385 window: &mut Window,
8386 cx: &mut Context<Editor>,
8387 ) {
8388 let mut revert_changes = HashMap::default();
8389 let chunk_by = self
8390 .snapshot(window, cx)
8391 .hunks_for_ranges(ranges)
8392 .into_iter()
8393 .chunk_by(|hunk| hunk.buffer_id);
8394 for (buffer_id, hunks) in &chunk_by {
8395 let hunks = hunks.collect::<Vec<_>>();
8396 for hunk in &hunks {
8397 self.prepare_restore_change(&mut revert_changes, hunk, cx);
8398 }
8399 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
8400 }
8401 drop(chunk_by);
8402 if !revert_changes.is_empty() {
8403 self.transact(window, cx, |editor, window, cx| {
8404 editor.restore(revert_changes, window, cx);
8405 });
8406 }
8407 }
8408
8409 pub fn open_active_item_in_terminal(
8410 &mut self,
8411 _: &OpenInTerminal,
8412 window: &mut Window,
8413 cx: &mut Context<Self>,
8414 ) {
8415 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
8416 let project_path = buffer.read(cx).project_path(cx)?;
8417 let project = self.project.as_ref()?.read(cx);
8418 let entry = project.entry_for_path(&project_path, cx)?;
8419 let parent = match &entry.canonical_path {
8420 Some(canonical_path) => canonical_path.to_path_buf(),
8421 None => project.absolute_path(&project_path, cx)?,
8422 }
8423 .parent()?
8424 .to_path_buf();
8425 Some(parent)
8426 }) {
8427 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
8428 }
8429 }
8430
8431 fn set_breakpoint_context_menu(
8432 &mut self,
8433 row: DisplayRow,
8434 position: Option<Anchor>,
8435 kind: Arc<BreakpointKind>,
8436 clicked_point: gpui::Point<Pixels>,
8437 window: &mut Window,
8438 cx: &mut Context<Self>,
8439 ) {
8440 if !cx.has_flag::<Debugger>() {
8441 return;
8442 }
8443 let source = self
8444 .buffer
8445 .read(cx)
8446 .snapshot(cx)
8447 .anchor_before(Point::new(row.0, 0u32));
8448
8449 let context_menu =
8450 self.breakpoint_context_menu(position.unwrap_or(source), kind, window, cx);
8451
8452 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
8453 self,
8454 source,
8455 clicked_point,
8456 context_menu,
8457 window,
8458 cx,
8459 );
8460 }
8461
8462 fn add_edit_breakpoint_block(
8463 &mut self,
8464 anchor: Anchor,
8465 kind: &BreakpointKind,
8466 window: &mut Window,
8467 cx: &mut Context<Self>,
8468 ) {
8469 let weak_editor = cx.weak_entity();
8470 let bp_prompt =
8471 cx.new(|cx| BreakpointPromptEditor::new(weak_editor, anchor, kind.clone(), window, cx));
8472
8473 let height = bp_prompt.update(cx, |this, cx| {
8474 this.prompt
8475 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
8476 });
8477 let cloned_prompt = bp_prompt.clone();
8478 let blocks = vec![BlockProperties {
8479 style: BlockStyle::Sticky,
8480 placement: BlockPlacement::Above(anchor),
8481 height,
8482 render: Arc::new(move |cx| {
8483 *cloned_prompt.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
8484 cloned_prompt.clone().into_any_element()
8485 }),
8486 priority: 0,
8487 }];
8488
8489 let focus_handle = bp_prompt.focus_handle(cx);
8490 window.focus(&focus_handle);
8491
8492 let block_ids = self.insert_blocks(blocks, None, cx);
8493 bp_prompt.update(cx, |prompt, _| {
8494 prompt.add_block_ids(block_ids);
8495 });
8496 }
8497
8498 pub(crate) fn breakpoint_at_cursor_head(
8499 &self,
8500 window: &mut Window,
8501 cx: &mut Context<Self>,
8502 ) -> Option<(Anchor, Breakpoint)> {
8503 let cursor_position: Point = self.selections.newest(cx).head();
8504 let snapshot = self.snapshot(window, cx);
8505 // We Set the column position to zero so this function interacts correctly
8506 // between calls by clicking on the gutter & using an action to toggle a
8507 // breakpoint. Otherwise, toggling a breakpoint through an action wouldn't
8508 // untoggle a breakpoint that was added through clicking on the gutter
8509 let cursor_position = snapshot
8510 .display_snapshot
8511 .buffer_snapshot
8512 .anchor_before(Point::new(cursor_position.row, 0));
8513
8514 let project = self.project.clone();
8515
8516 let buffer_id = cursor_position.text_anchor.buffer_id?;
8517 let enclosing_excerpt = snapshot
8518 .buffer_snapshot
8519 .excerpt_ids_for_range(cursor_position..cursor_position)
8520 .next()?;
8521 let buffer = project?.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
8522 let buffer_snapshot = buffer.read(cx).snapshot();
8523
8524 let row = buffer_snapshot
8525 .summary_for_anchor::<text::PointUtf16>(&cursor_position.text_anchor)
8526 .row;
8527
8528 let bp = self
8529 .breakpoint_store
8530 .as_ref()?
8531 .read_with(cx, |breakpoint_store, cx| {
8532 breakpoint_store
8533 .breakpoints(
8534 &buffer,
8535 Some(cursor_position.text_anchor..(text::Anchor::MAX)),
8536 buffer_snapshot.clone(),
8537 cx,
8538 )
8539 .next()
8540 .and_then(move |(anchor, bp)| {
8541 let breakpoint_row = buffer_snapshot
8542 .summary_for_anchor::<text::PointUtf16>(anchor)
8543 .row;
8544
8545 if breakpoint_row == row {
8546 snapshot
8547 .buffer_snapshot
8548 .anchor_in_excerpt(enclosing_excerpt, *anchor)
8549 .map(|anchor| (anchor, bp.clone()))
8550 } else {
8551 None
8552 }
8553 })
8554 });
8555 bp
8556 }
8557
8558 pub fn edit_log_breakpoint(
8559 &mut self,
8560 _: &EditLogBreakpoint,
8561 window: &mut Window,
8562 cx: &mut Context<Self>,
8563 ) {
8564 let (anchor, bp) = self
8565 .breakpoint_at_cursor_head(window, cx)
8566 .unwrap_or_else(|| {
8567 let cursor_position: Point = self.selections.newest(cx).head();
8568
8569 let breakpoint_position = self
8570 .snapshot(window, cx)
8571 .display_snapshot
8572 .buffer_snapshot
8573 .anchor_before(Point::new(cursor_position.row, 0));
8574
8575 (
8576 breakpoint_position,
8577 Breakpoint {
8578 kind: BreakpointKind::Standard,
8579 },
8580 )
8581 });
8582
8583 self.add_edit_breakpoint_block(anchor, &bp.kind, window, cx);
8584 }
8585
8586 pub fn toggle_breakpoint(
8587 &mut self,
8588 _: &crate::actions::ToggleBreakpoint,
8589 window: &mut Window,
8590 cx: &mut Context<Self>,
8591 ) {
8592 let edit_action = BreakpointEditAction::Toggle;
8593
8594 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8595 self.edit_breakpoint_at_anchor(anchor, breakpoint.kind, edit_action, cx);
8596 } else {
8597 let cursor_position: Point = self.selections.newest(cx).head();
8598
8599 let breakpoint_position = self
8600 .snapshot(window, cx)
8601 .display_snapshot
8602 .buffer_snapshot
8603 .anchor_before(Point::new(cursor_position.row, 0));
8604
8605 self.edit_breakpoint_at_anchor(
8606 breakpoint_position,
8607 BreakpointKind::Standard,
8608 edit_action,
8609 cx,
8610 );
8611 }
8612 }
8613
8614 pub fn edit_breakpoint_at_anchor(
8615 &mut self,
8616 breakpoint_position: Anchor,
8617 kind: BreakpointKind,
8618 edit_action: BreakpointEditAction,
8619 cx: &mut Context<Self>,
8620 ) {
8621 let Some(breakpoint_store) = &self.breakpoint_store else {
8622 return;
8623 };
8624
8625 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
8626 if breakpoint_position == Anchor::min() {
8627 self.buffer()
8628 .read(cx)
8629 .excerpt_buffer_ids()
8630 .into_iter()
8631 .next()
8632 } else {
8633 None
8634 }
8635 }) else {
8636 return;
8637 };
8638
8639 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
8640 return;
8641 };
8642
8643 breakpoint_store.update(cx, |breakpoint_store, cx| {
8644 breakpoint_store.toggle_breakpoint(
8645 buffer,
8646 (breakpoint_position.text_anchor, Breakpoint { kind }),
8647 edit_action,
8648 cx,
8649 );
8650 });
8651
8652 cx.notify();
8653 }
8654
8655 #[cfg(any(test, feature = "test-support"))]
8656 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
8657 self.breakpoint_store.clone()
8658 }
8659
8660 pub fn prepare_restore_change(
8661 &self,
8662 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
8663 hunk: &MultiBufferDiffHunk,
8664 cx: &mut App,
8665 ) -> Option<()> {
8666 if hunk.is_created_file() {
8667 return None;
8668 }
8669 let buffer = self.buffer.read(cx);
8670 let diff = buffer.diff_for(hunk.buffer_id)?;
8671 let buffer = buffer.buffer(hunk.buffer_id)?;
8672 let buffer = buffer.read(cx);
8673 let original_text = diff
8674 .read(cx)
8675 .base_text()
8676 .as_rope()
8677 .slice(hunk.diff_base_byte_range.clone());
8678 let buffer_snapshot = buffer.snapshot();
8679 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
8680 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
8681 probe
8682 .0
8683 .start
8684 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
8685 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
8686 }) {
8687 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
8688 Some(())
8689 } else {
8690 None
8691 }
8692 }
8693
8694 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
8695 self.manipulate_lines(window, cx, |lines| lines.reverse())
8696 }
8697
8698 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
8699 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
8700 }
8701
8702 fn manipulate_lines<Fn>(
8703 &mut self,
8704 window: &mut Window,
8705 cx: &mut Context<Self>,
8706 mut callback: Fn,
8707 ) where
8708 Fn: FnMut(&mut Vec<&str>),
8709 {
8710 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8711 let buffer = self.buffer.read(cx).snapshot(cx);
8712
8713 let mut edits = Vec::new();
8714
8715 let selections = self.selections.all::<Point>(cx);
8716 let mut selections = selections.iter().peekable();
8717 let mut contiguous_row_selections = Vec::new();
8718 let mut new_selections = Vec::new();
8719 let mut added_lines = 0;
8720 let mut removed_lines = 0;
8721
8722 while let Some(selection) = selections.next() {
8723 let (start_row, end_row) = consume_contiguous_rows(
8724 &mut contiguous_row_selections,
8725 selection,
8726 &display_map,
8727 &mut selections,
8728 );
8729
8730 let start_point = Point::new(start_row.0, 0);
8731 let end_point = Point::new(
8732 end_row.previous_row().0,
8733 buffer.line_len(end_row.previous_row()),
8734 );
8735 let text = buffer
8736 .text_for_range(start_point..end_point)
8737 .collect::<String>();
8738
8739 let mut lines = text.split('\n').collect_vec();
8740
8741 let lines_before = lines.len();
8742 callback(&mut lines);
8743 let lines_after = lines.len();
8744
8745 edits.push((start_point..end_point, lines.join("\n")));
8746
8747 // Selections must change based on added and removed line count
8748 let start_row =
8749 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
8750 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
8751 new_selections.push(Selection {
8752 id: selection.id,
8753 start: start_row,
8754 end: end_row,
8755 goal: SelectionGoal::None,
8756 reversed: selection.reversed,
8757 });
8758
8759 if lines_after > lines_before {
8760 added_lines += lines_after - lines_before;
8761 } else if lines_before > lines_after {
8762 removed_lines += lines_before - lines_after;
8763 }
8764 }
8765
8766 self.transact(window, cx, |this, window, cx| {
8767 let buffer = this.buffer.update(cx, |buffer, cx| {
8768 buffer.edit(edits, None, cx);
8769 buffer.snapshot(cx)
8770 });
8771
8772 // Recalculate offsets on newly edited buffer
8773 let new_selections = new_selections
8774 .iter()
8775 .map(|s| {
8776 let start_point = Point::new(s.start.0, 0);
8777 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
8778 Selection {
8779 id: s.id,
8780 start: buffer.point_to_offset(start_point),
8781 end: buffer.point_to_offset(end_point),
8782 goal: s.goal,
8783 reversed: s.reversed,
8784 }
8785 })
8786 .collect();
8787
8788 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8789 s.select(new_selections);
8790 });
8791
8792 this.request_autoscroll(Autoscroll::fit(), cx);
8793 });
8794 }
8795
8796 pub fn convert_to_upper_case(
8797 &mut self,
8798 _: &ConvertToUpperCase,
8799 window: &mut Window,
8800 cx: &mut Context<Self>,
8801 ) {
8802 self.manipulate_text(window, cx, |text| text.to_uppercase())
8803 }
8804
8805 pub fn convert_to_lower_case(
8806 &mut self,
8807 _: &ConvertToLowerCase,
8808 window: &mut Window,
8809 cx: &mut Context<Self>,
8810 ) {
8811 self.manipulate_text(window, cx, |text| text.to_lowercase())
8812 }
8813
8814 pub fn convert_to_title_case(
8815 &mut self,
8816 _: &ConvertToTitleCase,
8817 window: &mut Window,
8818 cx: &mut Context<Self>,
8819 ) {
8820 self.manipulate_text(window, cx, |text| {
8821 text.split('\n')
8822 .map(|line| line.to_case(Case::Title))
8823 .join("\n")
8824 })
8825 }
8826
8827 pub fn convert_to_snake_case(
8828 &mut self,
8829 _: &ConvertToSnakeCase,
8830 window: &mut Window,
8831 cx: &mut Context<Self>,
8832 ) {
8833 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
8834 }
8835
8836 pub fn convert_to_kebab_case(
8837 &mut self,
8838 _: &ConvertToKebabCase,
8839 window: &mut Window,
8840 cx: &mut Context<Self>,
8841 ) {
8842 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
8843 }
8844
8845 pub fn convert_to_upper_camel_case(
8846 &mut self,
8847 _: &ConvertToUpperCamelCase,
8848 window: &mut Window,
8849 cx: &mut Context<Self>,
8850 ) {
8851 self.manipulate_text(window, cx, |text| {
8852 text.split('\n')
8853 .map(|line| line.to_case(Case::UpperCamel))
8854 .join("\n")
8855 })
8856 }
8857
8858 pub fn convert_to_lower_camel_case(
8859 &mut self,
8860 _: &ConvertToLowerCamelCase,
8861 window: &mut Window,
8862 cx: &mut Context<Self>,
8863 ) {
8864 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
8865 }
8866
8867 pub fn convert_to_opposite_case(
8868 &mut self,
8869 _: &ConvertToOppositeCase,
8870 window: &mut Window,
8871 cx: &mut Context<Self>,
8872 ) {
8873 self.manipulate_text(window, cx, |text| {
8874 text.chars()
8875 .fold(String::with_capacity(text.len()), |mut t, c| {
8876 if c.is_uppercase() {
8877 t.extend(c.to_lowercase());
8878 } else {
8879 t.extend(c.to_uppercase());
8880 }
8881 t
8882 })
8883 })
8884 }
8885
8886 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
8887 where
8888 Fn: FnMut(&str) -> String,
8889 {
8890 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8891 let buffer = self.buffer.read(cx).snapshot(cx);
8892
8893 let mut new_selections = Vec::new();
8894 let mut edits = Vec::new();
8895 let mut selection_adjustment = 0i32;
8896
8897 for selection in self.selections.all::<usize>(cx) {
8898 let selection_is_empty = selection.is_empty();
8899
8900 let (start, end) = if selection_is_empty {
8901 let word_range = movement::surrounding_word(
8902 &display_map,
8903 selection.start.to_display_point(&display_map),
8904 );
8905 let start = word_range.start.to_offset(&display_map, Bias::Left);
8906 let end = word_range.end.to_offset(&display_map, Bias::Left);
8907 (start, end)
8908 } else {
8909 (selection.start, selection.end)
8910 };
8911
8912 let text = buffer.text_for_range(start..end).collect::<String>();
8913 let old_length = text.len() as i32;
8914 let text = callback(&text);
8915
8916 new_selections.push(Selection {
8917 start: (start as i32 - selection_adjustment) as usize,
8918 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
8919 goal: SelectionGoal::None,
8920 ..selection
8921 });
8922
8923 selection_adjustment += old_length - text.len() as i32;
8924
8925 edits.push((start..end, text));
8926 }
8927
8928 self.transact(window, cx, |this, window, cx| {
8929 this.buffer.update(cx, |buffer, cx| {
8930 buffer.edit(edits, None, cx);
8931 });
8932
8933 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8934 s.select(new_selections);
8935 });
8936
8937 this.request_autoscroll(Autoscroll::fit(), cx);
8938 });
8939 }
8940
8941 pub fn duplicate(
8942 &mut self,
8943 upwards: bool,
8944 whole_lines: bool,
8945 window: &mut Window,
8946 cx: &mut Context<Self>,
8947 ) {
8948 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8949 let buffer = &display_map.buffer_snapshot;
8950 let selections = self.selections.all::<Point>(cx);
8951
8952 let mut edits = Vec::new();
8953 let mut selections_iter = selections.iter().peekable();
8954 while let Some(selection) = selections_iter.next() {
8955 let mut rows = selection.spanned_rows(false, &display_map);
8956 // duplicate line-wise
8957 if whole_lines || selection.start == selection.end {
8958 // Avoid duplicating the same lines twice.
8959 while let Some(next_selection) = selections_iter.peek() {
8960 let next_rows = next_selection.spanned_rows(false, &display_map);
8961 if next_rows.start < rows.end {
8962 rows.end = next_rows.end;
8963 selections_iter.next().unwrap();
8964 } else {
8965 break;
8966 }
8967 }
8968
8969 // Copy the text from the selected row region and splice it either at the start
8970 // or end of the region.
8971 let start = Point::new(rows.start.0, 0);
8972 let end = Point::new(
8973 rows.end.previous_row().0,
8974 buffer.line_len(rows.end.previous_row()),
8975 );
8976 let text = buffer
8977 .text_for_range(start..end)
8978 .chain(Some("\n"))
8979 .collect::<String>();
8980 let insert_location = if upwards {
8981 Point::new(rows.end.0, 0)
8982 } else {
8983 start
8984 };
8985 edits.push((insert_location..insert_location, text));
8986 } else {
8987 // duplicate character-wise
8988 let start = selection.start;
8989 let end = selection.end;
8990 let text = buffer.text_for_range(start..end).collect::<String>();
8991 edits.push((selection.end..selection.end, text));
8992 }
8993 }
8994
8995 self.transact(window, cx, |this, _, cx| {
8996 this.buffer.update(cx, |buffer, cx| {
8997 buffer.edit(edits, None, cx);
8998 });
8999
9000 this.request_autoscroll(Autoscroll::fit(), cx);
9001 });
9002 }
9003
9004 pub fn duplicate_line_up(
9005 &mut self,
9006 _: &DuplicateLineUp,
9007 window: &mut Window,
9008 cx: &mut Context<Self>,
9009 ) {
9010 self.duplicate(true, true, window, cx);
9011 }
9012
9013 pub fn duplicate_line_down(
9014 &mut self,
9015 _: &DuplicateLineDown,
9016 window: &mut Window,
9017 cx: &mut Context<Self>,
9018 ) {
9019 self.duplicate(false, true, window, cx);
9020 }
9021
9022 pub fn duplicate_selection(
9023 &mut self,
9024 _: &DuplicateSelection,
9025 window: &mut Window,
9026 cx: &mut Context<Self>,
9027 ) {
9028 self.duplicate(false, false, window, cx);
9029 }
9030
9031 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
9032 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9033 let buffer = self.buffer.read(cx).snapshot(cx);
9034
9035 let mut edits = Vec::new();
9036 let mut unfold_ranges = Vec::new();
9037 let mut refold_creases = Vec::new();
9038
9039 let selections = self.selections.all::<Point>(cx);
9040 let mut selections = selections.iter().peekable();
9041 let mut contiguous_row_selections = Vec::new();
9042 let mut new_selections = Vec::new();
9043
9044 while let Some(selection) = selections.next() {
9045 // Find all the selections that span a contiguous row range
9046 let (start_row, end_row) = consume_contiguous_rows(
9047 &mut contiguous_row_selections,
9048 selection,
9049 &display_map,
9050 &mut selections,
9051 );
9052
9053 // Move the text spanned by the row range to be before the line preceding the row range
9054 if start_row.0 > 0 {
9055 let range_to_move = Point::new(
9056 start_row.previous_row().0,
9057 buffer.line_len(start_row.previous_row()),
9058 )
9059 ..Point::new(
9060 end_row.previous_row().0,
9061 buffer.line_len(end_row.previous_row()),
9062 );
9063 let insertion_point = display_map
9064 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
9065 .0;
9066
9067 // Don't move lines across excerpts
9068 if buffer
9069 .excerpt_containing(insertion_point..range_to_move.end)
9070 .is_some()
9071 {
9072 let text = buffer
9073 .text_for_range(range_to_move.clone())
9074 .flat_map(|s| s.chars())
9075 .skip(1)
9076 .chain(['\n'])
9077 .collect::<String>();
9078
9079 edits.push((
9080 buffer.anchor_after(range_to_move.start)
9081 ..buffer.anchor_before(range_to_move.end),
9082 String::new(),
9083 ));
9084 let insertion_anchor = buffer.anchor_after(insertion_point);
9085 edits.push((insertion_anchor..insertion_anchor, text));
9086
9087 let row_delta = range_to_move.start.row - insertion_point.row + 1;
9088
9089 // Move selections up
9090 new_selections.extend(contiguous_row_selections.drain(..).map(
9091 |mut selection| {
9092 selection.start.row -= row_delta;
9093 selection.end.row -= row_delta;
9094 selection
9095 },
9096 ));
9097
9098 // Move folds up
9099 unfold_ranges.push(range_to_move.clone());
9100 for fold in display_map.folds_in_range(
9101 buffer.anchor_before(range_to_move.start)
9102 ..buffer.anchor_after(range_to_move.end),
9103 ) {
9104 let mut start = fold.range.start.to_point(&buffer);
9105 let mut end = fold.range.end.to_point(&buffer);
9106 start.row -= row_delta;
9107 end.row -= row_delta;
9108 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9109 }
9110 }
9111 }
9112
9113 // If we didn't move line(s), preserve the existing selections
9114 new_selections.append(&mut contiguous_row_selections);
9115 }
9116
9117 self.transact(window, cx, |this, window, cx| {
9118 this.unfold_ranges(&unfold_ranges, true, true, cx);
9119 this.buffer.update(cx, |buffer, cx| {
9120 for (range, text) in edits {
9121 buffer.edit([(range, text)], None, cx);
9122 }
9123 });
9124 this.fold_creases(refold_creases, true, window, cx);
9125 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9126 s.select(new_selections);
9127 })
9128 });
9129 }
9130
9131 pub fn move_line_down(
9132 &mut self,
9133 _: &MoveLineDown,
9134 window: &mut Window,
9135 cx: &mut Context<Self>,
9136 ) {
9137 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9138 let buffer = self.buffer.read(cx).snapshot(cx);
9139
9140 let mut edits = Vec::new();
9141 let mut unfold_ranges = Vec::new();
9142 let mut refold_creases = Vec::new();
9143
9144 let selections = self.selections.all::<Point>(cx);
9145 let mut selections = selections.iter().peekable();
9146 let mut contiguous_row_selections = Vec::new();
9147 let mut new_selections = Vec::new();
9148
9149 while let Some(selection) = selections.next() {
9150 // Find all the selections that span a contiguous row range
9151 let (start_row, end_row) = consume_contiguous_rows(
9152 &mut contiguous_row_selections,
9153 selection,
9154 &display_map,
9155 &mut selections,
9156 );
9157
9158 // Move the text spanned by the row range to be after the last line of the row range
9159 if end_row.0 <= buffer.max_point().row {
9160 let range_to_move =
9161 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
9162 let insertion_point = display_map
9163 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
9164 .0;
9165
9166 // Don't move lines across excerpt boundaries
9167 if buffer
9168 .excerpt_containing(range_to_move.start..insertion_point)
9169 .is_some()
9170 {
9171 let mut text = String::from("\n");
9172 text.extend(buffer.text_for_range(range_to_move.clone()));
9173 text.pop(); // Drop trailing newline
9174 edits.push((
9175 buffer.anchor_after(range_to_move.start)
9176 ..buffer.anchor_before(range_to_move.end),
9177 String::new(),
9178 ));
9179 let insertion_anchor = buffer.anchor_after(insertion_point);
9180 edits.push((insertion_anchor..insertion_anchor, text));
9181
9182 let row_delta = insertion_point.row - range_to_move.end.row + 1;
9183
9184 // Move selections down
9185 new_selections.extend(contiguous_row_selections.drain(..).map(
9186 |mut selection| {
9187 selection.start.row += row_delta;
9188 selection.end.row += row_delta;
9189 selection
9190 },
9191 ));
9192
9193 // Move folds down
9194 unfold_ranges.push(range_to_move.clone());
9195 for fold in display_map.folds_in_range(
9196 buffer.anchor_before(range_to_move.start)
9197 ..buffer.anchor_after(range_to_move.end),
9198 ) {
9199 let mut start = fold.range.start.to_point(&buffer);
9200 let mut end = fold.range.end.to_point(&buffer);
9201 start.row += row_delta;
9202 end.row += row_delta;
9203 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9204 }
9205 }
9206 }
9207
9208 // If we didn't move line(s), preserve the existing selections
9209 new_selections.append(&mut contiguous_row_selections);
9210 }
9211
9212 self.transact(window, cx, |this, window, cx| {
9213 this.unfold_ranges(&unfold_ranges, true, true, cx);
9214 this.buffer.update(cx, |buffer, cx| {
9215 for (range, text) in edits {
9216 buffer.edit([(range, text)], None, cx);
9217 }
9218 });
9219 this.fold_creases(refold_creases, true, window, cx);
9220 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9221 s.select(new_selections)
9222 });
9223 });
9224 }
9225
9226 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
9227 let text_layout_details = &self.text_layout_details(window);
9228 self.transact(window, cx, |this, window, cx| {
9229 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9230 let mut edits: Vec<(Range<usize>, String)> = Default::default();
9231 let line_mode = s.line_mode;
9232 s.move_with(|display_map, selection| {
9233 if !selection.is_empty() || line_mode {
9234 return;
9235 }
9236
9237 let mut head = selection.head();
9238 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
9239 if head.column() == display_map.line_len(head.row()) {
9240 transpose_offset = display_map
9241 .buffer_snapshot
9242 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9243 }
9244
9245 if transpose_offset == 0 {
9246 return;
9247 }
9248
9249 *head.column_mut() += 1;
9250 head = display_map.clip_point(head, Bias::Right);
9251 let goal = SelectionGoal::HorizontalPosition(
9252 display_map
9253 .x_for_display_point(head, text_layout_details)
9254 .into(),
9255 );
9256 selection.collapse_to(head, goal);
9257
9258 let transpose_start = display_map
9259 .buffer_snapshot
9260 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9261 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
9262 let transpose_end = display_map
9263 .buffer_snapshot
9264 .clip_offset(transpose_offset + 1, Bias::Right);
9265 if let Some(ch) =
9266 display_map.buffer_snapshot.chars_at(transpose_start).next()
9267 {
9268 edits.push((transpose_start..transpose_offset, String::new()));
9269 edits.push((transpose_end..transpose_end, ch.to_string()));
9270 }
9271 }
9272 });
9273 edits
9274 });
9275 this.buffer
9276 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9277 let selections = this.selections.all::<usize>(cx);
9278 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9279 s.select(selections);
9280 });
9281 });
9282 }
9283
9284 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
9285 self.rewrap_impl(RewrapOptions::default(), cx)
9286 }
9287
9288 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
9289 let buffer = self.buffer.read(cx).snapshot(cx);
9290 let selections = self.selections.all::<Point>(cx);
9291 let mut selections = selections.iter().peekable();
9292
9293 let mut edits = Vec::new();
9294 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
9295
9296 while let Some(selection) = selections.next() {
9297 let mut start_row = selection.start.row;
9298 let mut end_row = selection.end.row;
9299
9300 // Skip selections that overlap with a range that has already been rewrapped.
9301 let selection_range = start_row..end_row;
9302 if rewrapped_row_ranges
9303 .iter()
9304 .any(|range| range.overlaps(&selection_range))
9305 {
9306 continue;
9307 }
9308
9309 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
9310
9311 // Since not all lines in the selection may be at the same indent
9312 // level, choose the indent size that is the most common between all
9313 // of the lines.
9314 //
9315 // If there is a tie, we use the deepest indent.
9316 let (indent_size, indent_end) = {
9317 let mut indent_size_occurrences = HashMap::default();
9318 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
9319
9320 for row in start_row..=end_row {
9321 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
9322 rows_by_indent_size.entry(indent).or_default().push(row);
9323 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
9324 }
9325
9326 let indent_size = indent_size_occurrences
9327 .into_iter()
9328 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
9329 .map(|(indent, _)| indent)
9330 .unwrap_or_default();
9331 let row = rows_by_indent_size[&indent_size][0];
9332 let indent_end = Point::new(row, indent_size.len);
9333
9334 (indent_size, indent_end)
9335 };
9336
9337 let mut line_prefix = indent_size.chars().collect::<String>();
9338
9339 let mut inside_comment = false;
9340 if let Some(comment_prefix) =
9341 buffer
9342 .language_scope_at(selection.head())
9343 .and_then(|language| {
9344 language
9345 .line_comment_prefixes()
9346 .iter()
9347 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
9348 .cloned()
9349 })
9350 {
9351 line_prefix.push_str(&comment_prefix);
9352 inside_comment = true;
9353 }
9354
9355 let language_settings = buffer.language_settings_at(selection.head(), cx);
9356 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
9357 RewrapBehavior::InComments => inside_comment,
9358 RewrapBehavior::InSelections => !selection.is_empty(),
9359 RewrapBehavior::Anywhere => true,
9360 };
9361
9362 let should_rewrap = options.override_language_settings
9363 || allow_rewrap_based_on_language
9364 || self.hard_wrap.is_some();
9365 if !should_rewrap {
9366 continue;
9367 }
9368
9369 if selection.is_empty() {
9370 'expand_upwards: while start_row > 0 {
9371 let prev_row = start_row - 1;
9372 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
9373 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
9374 {
9375 start_row = prev_row;
9376 } else {
9377 break 'expand_upwards;
9378 }
9379 }
9380
9381 'expand_downwards: while end_row < buffer.max_point().row {
9382 let next_row = end_row + 1;
9383 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
9384 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
9385 {
9386 end_row = next_row;
9387 } else {
9388 break 'expand_downwards;
9389 }
9390 }
9391 }
9392
9393 let start = Point::new(start_row, 0);
9394 let start_offset = start.to_offset(&buffer);
9395 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
9396 let selection_text = buffer.text_for_range(start..end).collect::<String>();
9397 let Some(lines_without_prefixes) = selection_text
9398 .lines()
9399 .map(|line| {
9400 line.strip_prefix(&line_prefix)
9401 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
9402 .ok_or_else(|| {
9403 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
9404 })
9405 })
9406 .collect::<Result<Vec<_>, _>>()
9407 .log_err()
9408 else {
9409 continue;
9410 };
9411
9412 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
9413 buffer
9414 .language_settings_at(Point::new(start_row, 0), cx)
9415 .preferred_line_length as usize
9416 });
9417 let wrapped_text = wrap_with_prefix(
9418 line_prefix,
9419 lines_without_prefixes.join("\n"),
9420 wrap_column,
9421 tab_size,
9422 options.preserve_existing_whitespace,
9423 );
9424
9425 // TODO: should always use char-based diff while still supporting cursor behavior that
9426 // matches vim.
9427 let mut diff_options = DiffOptions::default();
9428 if options.override_language_settings {
9429 diff_options.max_word_diff_len = 0;
9430 diff_options.max_word_diff_line_count = 0;
9431 } else {
9432 diff_options.max_word_diff_len = usize::MAX;
9433 diff_options.max_word_diff_line_count = usize::MAX;
9434 }
9435
9436 for (old_range, new_text) in
9437 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
9438 {
9439 let edit_start = buffer.anchor_after(start_offset + old_range.start);
9440 let edit_end = buffer.anchor_after(start_offset + old_range.end);
9441 edits.push((edit_start..edit_end, new_text));
9442 }
9443
9444 rewrapped_row_ranges.push(start_row..=end_row);
9445 }
9446
9447 self.buffer
9448 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9449 }
9450
9451 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
9452 let mut text = String::new();
9453 let buffer = self.buffer.read(cx).snapshot(cx);
9454 let mut selections = self.selections.all::<Point>(cx);
9455 let mut clipboard_selections = Vec::with_capacity(selections.len());
9456 {
9457 let max_point = buffer.max_point();
9458 let mut is_first = true;
9459 for selection in &mut selections {
9460 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9461 if is_entire_line {
9462 selection.start = Point::new(selection.start.row, 0);
9463 if !selection.is_empty() && selection.end.column == 0 {
9464 selection.end = cmp::min(max_point, selection.end);
9465 } else {
9466 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
9467 }
9468 selection.goal = SelectionGoal::None;
9469 }
9470 if is_first {
9471 is_first = false;
9472 } else {
9473 text += "\n";
9474 }
9475 let mut len = 0;
9476 for chunk in buffer.text_for_range(selection.start..selection.end) {
9477 text.push_str(chunk);
9478 len += chunk.len();
9479 }
9480 clipboard_selections.push(ClipboardSelection {
9481 len,
9482 is_entire_line,
9483 first_line_indent: buffer
9484 .indent_size_for_line(MultiBufferRow(selection.start.row))
9485 .len,
9486 });
9487 }
9488 }
9489
9490 self.transact(window, cx, |this, window, cx| {
9491 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9492 s.select(selections);
9493 });
9494 this.insert("", window, cx);
9495 });
9496 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
9497 }
9498
9499 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
9500 let item = self.cut_common(window, cx);
9501 cx.write_to_clipboard(item);
9502 }
9503
9504 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
9505 self.change_selections(None, window, cx, |s| {
9506 s.move_with(|snapshot, sel| {
9507 if sel.is_empty() {
9508 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
9509 }
9510 });
9511 });
9512 let item = self.cut_common(window, cx);
9513 cx.set_global(KillRing(item))
9514 }
9515
9516 pub fn kill_ring_yank(
9517 &mut self,
9518 _: &KillRingYank,
9519 window: &mut Window,
9520 cx: &mut Context<Self>,
9521 ) {
9522 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
9523 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
9524 (kill_ring.text().to_string(), kill_ring.metadata_json())
9525 } else {
9526 return;
9527 }
9528 } else {
9529 return;
9530 };
9531 self.do_paste(&text, metadata, false, window, cx);
9532 }
9533
9534 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
9535 self.do_copy(true, cx);
9536 }
9537
9538 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
9539 self.do_copy(false, cx);
9540 }
9541
9542 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
9543 let selections = self.selections.all::<Point>(cx);
9544 let buffer = self.buffer.read(cx).read(cx);
9545 let mut text = String::new();
9546
9547 let mut clipboard_selections = Vec::with_capacity(selections.len());
9548 {
9549 let max_point = buffer.max_point();
9550 let mut is_first = true;
9551 for selection in &selections {
9552 let mut start = selection.start;
9553 let mut end = selection.end;
9554 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9555 if is_entire_line {
9556 start = Point::new(start.row, 0);
9557 end = cmp::min(max_point, Point::new(end.row + 1, 0));
9558 }
9559
9560 let mut trimmed_selections = Vec::new();
9561 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
9562 let row = MultiBufferRow(start.row);
9563 let first_indent = buffer.indent_size_for_line(row);
9564 if first_indent.len == 0 || start.column > first_indent.len {
9565 trimmed_selections.push(start..end);
9566 } else {
9567 trimmed_selections.push(
9568 Point::new(row.0, first_indent.len)
9569 ..Point::new(row.0, buffer.line_len(row)),
9570 );
9571 for row in start.row + 1..=end.row {
9572 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
9573 if row_indent_size.len >= first_indent.len {
9574 trimmed_selections.push(
9575 Point::new(row, first_indent.len)
9576 ..Point::new(row, buffer.line_len(MultiBufferRow(row))),
9577 );
9578 } else {
9579 trimmed_selections.clear();
9580 trimmed_selections.push(start..end);
9581 break;
9582 }
9583 }
9584 }
9585 } else {
9586 trimmed_selections.push(start..end);
9587 }
9588
9589 for trimmed_range in trimmed_selections {
9590 if is_first {
9591 is_first = false;
9592 } else {
9593 text += "\n";
9594 }
9595 let mut len = 0;
9596 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
9597 text.push_str(chunk);
9598 len += chunk.len();
9599 }
9600 clipboard_selections.push(ClipboardSelection {
9601 len,
9602 is_entire_line,
9603 first_line_indent: buffer
9604 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
9605 .len,
9606 });
9607 }
9608 }
9609 }
9610
9611 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
9612 text,
9613 clipboard_selections,
9614 ));
9615 }
9616
9617 pub fn do_paste(
9618 &mut self,
9619 text: &String,
9620 clipboard_selections: Option<Vec<ClipboardSelection>>,
9621 handle_entire_lines: bool,
9622 window: &mut Window,
9623 cx: &mut Context<Self>,
9624 ) {
9625 if self.read_only(cx) {
9626 return;
9627 }
9628
9629 let clipboard_text = Cow::Borrowed(text);
9630
9631 self.transact(window, cx, |this, window, cx| {
9632 if let Some(mut clipboard_selections) = clipboard_selections {
9633 let old_selections = this.selections.all::<usize>(cx);
9634 let all_selections_were_entire_line =
9635 clipboard_selections.iter().all(|s| s.is_entire_line);
9636 let first_selection_indent_column =
9637 clipboard_selections.first().map(|s| s.first_line_indent);
9638 if clipboard_selections.len() != old_selections.len() {
9639 clipboard_selections.drain(..);
9640 }
9641 let cursor_offset = this.selections.last::<usize>(cx).head();
9642 let mut auto_indent_on_paste = true;
9643
9644 this.buffer.update(cx, |buffer, cx| {
9645 let snapshot = buffer.read(cx);
9646 auto_indent_on_paste = snapshot
9647 .language_settings_at(cursor_offset, cx)
9648 .auto_indent_on_paste;
9649
9650 let mut start_offset = 0;
9651 let mut edits = Vec::new();
9652 let mut original_indent_columns = Vec::new();
9653 for (ix, selection) in old_selections.iter().enumerate() {
9654 let to_insert;
9655 let entire_line;
9656 let original_indent_column;
9657 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
9658 let end_offset = start_offset + clipboard_selection.len;
9659 to_insert = &clipboard_text[start_offset..end_offset];
9660 entire_line = clipboard_selection.is_entire_line;
9661 start_offset = end_offset + 1;
9662 original_indent_column = Some(clipboard_selection.first_line_indent);
9663 } else {
9664 to_insert = clipboard_text.as_str();
9665 entire_line = all_selections_were_entire_line;
9666 original_indent_column = first_selection_indent_column
9667 }
9668
9669 // If the corresponding selection was empty when this slice of the
9670 // clipboard text was written, then the entire line containing the
9671 // selection was copied. If this selection is also currently empty,
9672 // then paste the line before the current line of the buffer.
9673 let range = if selection.is_empty() && handle_entire_lines && entire_line {
9674 let column = selection.start.to_point(&snapshot).column as usize;
9675 let line_start = selection.start - column;
9676 line_start..line_start
9677 } else {
9678 selection.range()
9679 };
9680
9681 edits.push((range, to_insert));
9682 original_indent_columns.push(original_indent_column);
9683 }
9684 drop(snapshot);
9685
9686 buffer.edit(
9687 edits,
9688 if auto_indent_on_paste {
9689 Some(AutoindentMode::Block {
9690 original_indent_columns,
9691 })
9692 } else {
9693 None
9694 },
9695 cx,
9696 );
9697 });
9698
9699 let selections = this.selections.all::<usize>(cx);
9700 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9701 s.select(selections)
9702 });
9703 } else {
9704 this.insert(&clipboard_text, window, cx);
9705 }
9706 });
9707 }
9708
9709 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
9710 if let Some(item) = cx.read_from_clipboard() {
9711 let entries = item.entries();
9712
9713 match entries.first() {
9714 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
9715 // of all the pasted entries.
9716 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
9717 .do_paste(
9718 clipboard_string.text(),
9719 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
9720 true,
9721 window,
9722 cx,
9723 ),
9724 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
9725 }
9726 }
9727 }
9728
9729 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
9730 if self.read_only(cx) {
9731 return;
9732 }
9733
9734 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
9735 if let Some((selections, _)) =
9736 self.selection_history.transaction(transaction_id).cloned()
9737 {
9738 self.change_selections(None, window, cx, |s| {
9739 s.select_anchors(selections.to_vec());
9740 });
9741 } else {
9742 log::error!(
9743 "No entry in selection_history found for undo. \
9744 This may correspond to a bug where undo does not update the selection. \
9745 If this is occurring, please add details to \
9746 https://github.com/zed-industries/zed/issues/22692"
9747 );
9748 }
9749 self.request_autoscroll(Autoscroll::fit(), cx);
9750 self.unmark_text(window, cx);
9751 self.refresh_inline_completion(true, false, window, cx);
9752 cx.emit(EditorEvent::Edited { transaction_id });
9753 cx.emit(EditorEvent::TransactionUndone { transaction_id });
9754 }
9755 }
9756
9757 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
9758 if self.read_only(cx) {
9759 return;
9760 }
9761
9762 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
9763 if let Some((_, Some(selections))) =
9764 self.selection_history.transaction(transaction_id).cloned()
9765 {
9766 self.change_selections(None, window, cx, |s| {
9767 s.select_anchors(selections.to_vec());
9768 });
9769 } else {
9770 log::error!(
9771 "No entry in selection_history found for redo. \
9772 This may correspond to a bug where undo does not update the selection. \
9773 If this is occurring, please add details to \
9774 https://github.com/zed-industries/zed/issues/22692"
9775 );
9776 }
9777 self.request_autoscroll(Autoscroll::fit(), cx);
9778 self.unmark_text(window, cx);
9779 self.refresh_inline_completion(true, false, window, cx);
9780 cx.emit(EditorEvent::Edited { transaction_id });
9781 }
9782 }
9783
9784 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
9785 self.buffer
9786 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
9787 }
9788
9789 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
9790 self.buffer
9791 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
9792 }
9793
9794 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
9795 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9796 let line_mode = s.line_mode;
9797 s.move_with(|map, selection| {
9798 let cursor = if selection.is_empty() && !line_mode {
9799 movement::left(map, selection.start)
9800 } else {
9801 selection.start
9802 };
9803 selection.collapse_to(cursor, SelectionGoal::None);
9804 });
9805 })
9806 }
9807
9808 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
9809 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9810 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
9811 })
9812 }
9813
9814 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
9815 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9816 let line_mode = s.line_mode;
9817 s.move_with(|map, selection| {
9818 let cursor = if selection.is_empty() && !line_mode {
9819 movement::right(map, selection.end)
9820 } else {
9821 selection.end
9822 };
9823 selection.collapse_to(cursor, SelectionGoal::None)
9824 });
9825 })
9826 }
9827
9828 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
9829 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9830 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
9831 })
9832 }
9833
9834 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
9835 if self.take_rename(true, window, cx).is_some() {
9836 return;
9837 }
9838
9839 if matches!(self.mode, EditorMode::SingleLine { .. }) {
9840 cx.propagate();
9841 return;
9842 }
9843
9844 let text_layout_details = &self.text_layout_details(window);
9845 let selection_count = self.selections.count();
9846 let first_selection = self.selections.first_anchor();
9847
9848 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9849 let line_mode = s.line_mode;
9850 s.move_with(|map, selection| {
9851 if !selection.is_empty() && !line_mode {
9852 selection.goal = SelectionGoal::None;
9853 }
9854 let (cursor, goal) = movement::up(
9855 map,
9856 selection.start,
9857 selection.goal,
9858 false,
9859 text_layout_details,
9860 );
9861 selection.collapse_to(cursor, goal);
9862 });
9863 });
9864
9865 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
9866 {
9867 cx.propagate();
9868 }
9869 }
9870
9871 pub fn move_up_by_lines(
9872 &mut self,
9873 action: &MoveUpByLines,
9874 window: &mut Window,
9875 cx: &mut Context<Self>,
9876 ) {
9877 if self.take_rename(true, window, cx).is_some() {
9878 return;
9879 }
9880
9881 if matches!(self.mode, EditorMode::SingleLine { .. }) {
9882 cx.propagate();
9883 return;
9884 }
9885
9886 let text_layout_details = &self.text_layout_details(window);
9887
9888 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9889 let line_mode = s.line_mode;
9890 s.move_with(|map, selection| {
9891 if !selection.is_empty() && !line_mode {
9892 selection.goal = SelectionGoal::None;
9893 }
9894 let (cursor, goal) = movement::up_by_rows(
9895 map,
9896 selection.start,
9897 action.lines,
9898 selection.goal,
9899 false,
9900 text_layout_details,
9901 );
9902 selection.collapse_to(cursor, goal);
9903 });
9904 })
9905 }
9906
9907 pub fn move_down_by_lines(
9908 &mut self,
9909 action: &MoveDownByLines,
9910 window: &mut Window,
9911 cx: &mut Context<Self>,
9912 ) {
9913 if self.take_rename(true, window, cx).is_some() {
9914 return;
9915 }
9916
9917 if matches!(self.mode, EditorMode::SingleLine { .. }) {
9918 cx.propagate();
9919 return;
9920 }
9921
9922 let text_layout_details = &self.text_layout_details(window);
9923
9924 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9925 let line_mode = s.line_mode;
9926 s.move_with(|map, selection| {
9927 if !selection.is_empty() && !line_mode {
9928 selection.goal = SelectionGoal::None;
9929 }
9930 let (cursor, goal) = movement::down_by_rows(
9931 map,
9932 selection.start,
9933 action.lines,
9934 selection.goal,
9935 false,
9936 text_layout_details,
9937 );
9938 selection.collapse_to(cursor, goal);
9939 });
9940 })
9941 }
9942
9943 pub fn select_down_by_lines(
9944 &mut self,
9945 action: &SelectDownByLines,
9946 window: &mut Window,
9947 cx: &mut Context<Self>,
9948 ) {
9949 let text_layout_details = &self.text_layout_details(window);
9950 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9951 s.move_heads_with(|map, head, goal| {
9952 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
9953 })
9954 })
9955 }
9956
9957 pub fn select_up_by_lines(
9958 &mut self,
9959 action: &SelectUpByLines,
9960 window: &mut Window,
9961 cx: &mut Context<Self>,
9962 ) {
9963 let text_layout_details = &self.text_layout_details(window);
9964 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9965 s.move_heads_with(|map, head, goal| {
9966 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
9967 })
9968 })
9969 }
9970
9971 pub fn select_page_up(
9972 &mut self,
9973 _: &SelectPageUp,
9974 window: &mut Window,
9975 cx: &mut Context<Self>,
9976 ) {
9977 let Some(row_count) = self.visible_row_count() else {
9978 return;
9979 };
9980
9981 let text_layout_details = &self.text_layout_details(window);
9982
9983 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9984 s.move_heads_with(|map, head, goal| {
9985 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
9986 })
9987 })
9988 }
9989
9990 pub fn move_page_up(
9991 &mut self,
9992 action: &MovePageUp,
9993 window: &mut Window,
9994 cx: &mut Context<Self>,
9995 ) {
9996 if self.take_rename(true, window, cx).is_some() {
9997 return;
9998 }
9999
10000 if self
10001 .context_menu
10002 .borrow_mut()
10003 .as_mut()
10004 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
10005 .unwrap_or(false)
10006 {
10007 return;
10008 }
10009
10010 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10011 cx.propagate();
10012 return;
10013 }
10014
10015 let Some(row_count) = self.visible_row_count() else {
10016 return;
10017 };
10018
10019 let autoscroll = if action.center_cursor {
10020 Autoscroll::center()
10021 } else {
10022 Autoscroll::fit()
10023 };
10024
10025 let text_layout_details = &self.text_layout_details(window);
10026
10027 self.change_selections(Some(autoscroll), window, cx, |s| {
10028 let line_mode = s.line_mode;
10029 s.move_with(|map, selection| {
10030 if !selection.is_empty() && !line_mode {
10031 selection.goal = SelectionGoal::None;
10032 }
10033 let (cursor, goal) = movement::up_by_rows(
10034 map,
10035 selection.end,
10036 row_count,
10037 selection.goal,
10038 false,
10039 text_layout_details,
10040 );
10041 selection.collapse_to(cursor, goal);
10042 });
10043 });
10044 }
10045
10046 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
10047 let text_layout_details = &self.text_layout_details(window);
10048 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10049 s.move_heads_with(|map, head, goal| {
10050 movement::up(map, head, goal, false, text_layout_details)
10051 })
10052 })
10053 }
10054
10055 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
10056 self.take_rename(true, window, cx);
10057
10058 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10059 cx.propagate();
10060 return;
10061 }
10062
10063 let text_layout_details = &self.text_layout_details(window);
10064 let selection_count = self.selections.count();
10065 let first_selection = self.selections.first_anchor();
10066
10067 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10068 let line_mode = s.line_mode;
10069 s.move_with(|map, selection| {
10070 if !selection.is_empty() && !line_mode {
10071 selection.goal = SelectionGoal::None;
10072 }
10073 let (cursor, goal) = movement::down(
10074 map,
10075 selection.end,
10076 selection.goal,
10077 false,
10078 text_layout_details,
10079 );
10080 selection.collapse_to(cursor, goal);
10081 });
10082 });
10083
10084 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10085 {
10086 cx.propagate();
10087 }
10088 }
10089
10090 pub fn select_page_down(
10091 &mut self,
10092 _: &SelectPageDown,
10093 window: &mut Window,
10094 cx: &mut Context<Self>,
10095 ) {
10096 let Some(row_count) = self.visible_row_count() else {
10097 return;
10098 };
10099
10100 let text_layout_details = &self.text_layout_details(window);
10101
10102 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10103 s.move_heads_with(|map, head, goal| {
10104 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
10105 })
10106 })
10107 }
10108
10109 pub fn move_page_down(
10110 &mut self,
10111 action: &MovePageDown,
10112 window: &mut Window,
10113 cx: &mut Context<Self>,
10114 ) {
10115 if self.take_rename(true, window, cx).is_some() {
10116 return;
10117 }
10118
10119 if self
10120 .context_menu
10121 .borrow_mut()
10122 .as_mut()
10123 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
10124 .unwrap_or(false)
10125 {
10126 return;
10127 }
10128
10129 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10130 cx.propagate();
10131 return;
10132 }
10133
10134 let Some(row_count) = self.visible_row_count() else {
10135 return;
10136 };
10137
10138 let autoscroll = if action.center_cursor {
10139 Autoscroll::center()
10140 } else {
10141 Autoscroll::fit()
10142 };
10143
10144 let text_layout_details = &self.text_layout_details(window);
10145 self.change_selections(Some(autoscroll), window, cx, |s| {
10146 let line_mode = s.line_mode;
10147 s.move_with(|map, selection| {
10148 if !selection.is_empty() && !line_mode {
10149 selection.goal = SelectionGoal::None;
10150 }
10151 let (cursor, goal) = movement::down_by_rows(
10152 map,
10153 selection.end,
10154 row_count,
10155 selection.goal,
10156 false,
10157 text_layout_details,
10158 );
10159 selection.collapse_to(cursor, goal);
10160 });
10161 });
10162 }
10163
10164 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
10165 let text_layout_details = &self.text_layout_details(window);
10166 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10167 s.move_heads_with(|map, head, goal| {
10168 movement::down(map, head, goal, false, text_layout_details)
10169 })
10170 });
10171 }
10172
10173 pub fn context_menu_first(
10174 &mut self,
10175 _: &ContextMenuFirst,
10176 _window: &mut Window,
10177 cx: &mut Context<Self>,
10178 ) {
10179 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10180 context_menu.select_first(self.completion_provider.as_deref(), cx);
10181 }
10182 }
10183
10184 pub fn context_menu_prev(
10185 &mut self,
10186 _: &ContextMenuPrevious,
10187 _window: &mut Window,
10188 cx: &mut Context<Self>,
10189 ) {
10190 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10191 context_menu.select_prev(self.completion_provider.as_deref(), cx);
10192 }
10193 }
10194
10195 pub fn context_menu_next(
10196 &mut self,
10197 _: &ContextMenuNext,
10198 _window: &mut Window,
10199 cx: &mut Context<Self>,
10200 ) {
10201 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10202 context_menu.select_next(self.completion_provider.as_deref(), cx);
10203 }
10204 }
10205
10206 pub fn context_menu_last(
10207 &mut self,
10208 _: &ContextMenuLast,
10209 _window: &mut Window,
10210 cx: &mut Context<Self>,
10211 ) {
10212 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10213 context_menu.select_last(self.completion_provider.as_deref(), cx);
10214 }
10215 }
10216
10217 pub fn move_to_previous_word_start(
10218 &mut self,
10219 _: &MoveToPreviousWordStart,
10220 window: &mut Window,
10221 cx: &mut Context<Self>,
10222 ) {
10223 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10224 s.move_cursors_with(|map, head, _| {
10225 (
10226 movement::previous_word_start(map, head),
10227 SelectionGoal::None,
10228 )
10229 });
10230 })
10231 }
10232
10233 pub fn move_to_previous_subword_start(
10234 &mut self,
10235 _: &MoveToPreviousSubwordStart,
10236 window: &mut Window,
10237 cx: &mut Context<Self>,
10238 ) {
10239 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10240 s.move_cursors_with(|map, head, _| {
10241 (
10242 movement::previous_subword_start(map, head),
10243 SelectionGoal::None,
10244 )
10245 });
10246 })
10247 }
10248
10249 pub fn select_to_previous_word_start(
10250 &mut self,
10251 _: &SelectToPreviousWordStart,
10252 window: &mut Window,
10253 cx: &mut Context<Self>,
10254 ) {
10255 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10256 s.move_heads_with(|map, head, _| {
10257 (
10258 movement::previous_word_start(map, head),
10259 SelectionGoal::None,
10260 )
10261 });
10262 })
10263 }
10264
10265 pub fn select_to_previous_subword_start(
10266 &mut self,
10267 _: &SelectToPreviousSubwordStart,
10268 window: &mut Window,
10269 cx: &mut Context<Self>,
10270 ) {
10271 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10272 s.move_heads_with(|map, head, _| {
10273 (
10274 movement::previous_subword_start(map, head),
10275 SelectionGoal::None,
10276 )
10277 });
10278 })
10279 }
10280
10281 pub fn delete_to_previous_word_start(
10282 &mut self,
10283 action: &DeleteToPreviousWordStart,
10284 window: &mut Window,
10285 cx: &mut Context<Self>,
10286 ) {
10287 self.transact(window, cx, |this, window, cx| {
10288 this.select_autoclose_pair(window, cx);
10289 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10290 let line_mode = s.line_mode;
10291 s.move_with(|map, selection| {
10292 if selection.is_empty() && !line_mode {
10293 let cursor = if action.ignore_newlines {
10294 movement::previous_word_start(map, selection.head())
10295 } else {
10296 movement::previous_word_start_or_newline(map, selection.head())
10297 };
10298 selection.set_head(cursor, SelectionGoal::None);
10299 }
10300 });
10301 });
10302 this.insert("", window, cx);
10303 });
10304 }
10305
10306 pub fn delete_to_previous_subword_start(
10307 &mut self,
10308 _: &DeleteToPreviousSubwordStart,
10309 window: &mut Window,
10310 cx: &mut Context<Self>,
10311 ) {
10312 self.transact(window, cx, |this, window, cx| {
10313 this.select_autoclose_pair(window, cx);
10314 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10315 let line_mode = s.line_mode;
10316 s.move_with(|map, selection| {
10317 if selection.is_empty() && !line_mode {
10318 let cursor = movement::previous_subword_start(map, selection.head());
10319 selection.set_head(cursor, SelectionGoal::None);
10320 }
10321 });
10322 });
10323 this.insert("", window, cx);
10324 });
10325 }
10326
10327 pub fn move_to_next_word_end(
10328 &mut self,
10329 _: &MoveToNextWordEnd,
10330 window: &mut Window,
10331 cx: &mut Context<Self>,
10332 ) {
10333 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10334 s.move_cursors_with(|map, head, _| {
10335 (movement::next_word_end(map, head), SelectionGoal::None)
10336 });
10337 })
10338 }
10339
10340 pub fn move_to_next_subword_end(
10341 &mut self,
10342 _: &MoveToNextSubwordEnd,
10343 window: &mut Window,
10344 cx: &mut Context<Self>,
10345 ) {
10346 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10347 s.move_cursors_with(|map, head, _| {
10348 (movement::next_subword_end(map, head), SelectionGoal::None)
10349 });
10350 })
10351 }
10352
10353 pub fn select_to_next_word_end(
10354 &mut self,
10355 _: &SelectToNextWordEnd,
10356 window: &mut Window,
10357 cx: &mut Context<Self>,
10358 ) {
10359 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10360 s.move_heads_with(|map, head, _| {
10361 (movement::next_word_end(map, head), SelectionGoal::None)
10362 });
10363 })
10364 }
10365
10366 pub fn select_to_next_subword_end(
10367 &mut self,
10368 _: &SelectToNextSubwordEnd,
10369 window: &mut Window,
10370 cx: &mut Context<Self>,
10371 ) {
10372 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10373 s.move_heads_with(|map, head, _| {
10374 (movement::next_subword_end(map, head), SelectionGoal::None)
10375 });
10376 })
10377 }
10378
10379 pub fn delete_to_next_word_end(
10380 &mut self,
10381 action: &DeleteToNextWordEnd,
10382 window: &mut Window,
10383 cx: &mut Context<Self>,
10384 ) {
10385 self.transact(window, cx, |this, window, cx| {
10386 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10387 let line_mode = s.line_mode;
10388 s.move_with(|map, selection| {
10389 if selection.is_empty() && !line_mode {
10390 let cursor = if action.ignore_newlines {
10391 movement::next_word_end(map, selection.head())
10392 } else {
10393 movement::next_word_end_or_newline(map, selection.head())
10394 };
10395 selection.set_head(cursor, SelectionGoal::None);
10396 }
10397 });
10398 });
10399 this.insert("", window, cx);
10400 });
10401 }
10402
10403 pub fn delete_to_next_subword_end(
10404 &mut self,
10405 _: &DeleteToNextSubwordEnd,
10406 window: &mut Window,
10407 cx: &mut Context<Self>,
10408 ) {
10409 self.transact(window, cx, |this, window, cx| {
10410 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10411 s.move_with(|map, selection| {
10412 if selection.is_empty() {
10413 let cursor = movement::next_subword_end(map, selection.head());
10414 selection.set_head(cursor, SelectionGoal::None);
10415 }
10416 });
10417 });
10418 this.insert("", window, cx);
10419 });
10420 }
10421
10422 pub fn move_to_beginning_of_line(
10423 &mut self,
10424 action: &MoveToBeginningOfLine,
10425 window: &mut Window,
10426 cx: &mut Context<Self>,
10427 ) {
10428 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10429 s.move_cursors_with(|map, head, _| {
10430 (
10431 movement::indented_line_beginning(
10432 map,
10433 head,
10434 action.stop_at_soft_wraps,
10435 action.stop_at_indent,
10436 ),
10437 SelectionGoal::None,
10438 )
10439 });
10440 })
10441 }
10442
10443 pub fn select_to_beginning_of_line(
10444 &mut self,
10445 action: &SelectToBeginningOfLine,
10446 window: &mut Window,
10447 cx: &mut Context<Self>,
10448 ) {
10449 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10450 s.move_heads_with(|map, head, _| {
10451 (
10452 movement::indented_line_beginning(
10453 map,
10454 head,
10455 action.stop_at_soft_wraps,
10456 action.stop_at_indent,
10457 ),
10458 SelectionGoal::None,
10459 )
10460 });
10461 });
10462 }
10463
10464 pub fn delete_to_beginning_of_line(
10465 &mut self,
10466 action: &DeleteToBeginningOfLine,
10467 window: &mut Window,
10468 cx: &mut Context<Self>,
10469 ) {
10470 self.transact(window, cx, |this, window, cx| {
10471 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10472 s.move_with(|_, selection| {
10473 selection.reversed = true;
10474 });
10475 });
10476
10477 this.select_to_beginning_of_line(
10478 &SelectToBeginningOfLine {
10479 stop_at_soft_wraps: false,
10480 stop_at_indent: action.stop_at_indent,
10481 },
10482 window,
10483 cx,
10484 );
10485 this.backspace(&Backspace, window, cx);
10486 });
10487 }
10488
10489 pub fn move_to_end_of_line(
10490 &mut self,
10491 action: &MoveToEndOfLine,
10492 window: &mut Window,
10493 cx: &mut Context<Self>,
10494 ) {
10495 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10496 s.move_cursors_with(|map, head, _| {
10497 (
10498 movement::line_end(map, head, action.stop_at_soft_wraps),
10499 SelectionGoal::None,
10500 )
10501 });
10502 })
10503 }
10504
10505 pub fn select_to_end_of_line(
10506 &mut self,
10507 action: &SelectToEndOfLine,
10508 window: &mut Window,
10509 cx: &mut Context<Self>,
10510 ) {
10511 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10512 s.move_heads_with(|map, head, _| {
10513 (
10514 movement::line_end(map, head, action.stop_at_soft_wraps),
10515 SelectionGoal::None,
10516 )
10517 });
10518 })
10519 }
10520
10521 pub fn delete_to_end_of_line(
10522 &mut self,
10523 _: &DeleteToEndOfLine,
10524 window: &mut Window,
10525 cx: &mut Context<Self>,
10526 ) {
10527 self.transact(window, cx, |this, window, cx| {
10528 this.select_to_end_of_line(
10529 &SelectToEndOfLine {
10530 stop_at_soft_wraps: false,
10531 },
10532 window,
10533 cx,
10534 );
10535 this.delete(&Delete, window, cx);
10536 });
10537 }
10538
10539 pub fn cut_to_end_of_line(
10540 &mut self,
10541 _: &CutToEndOfLine,
10542 window: &mut Window,
10543 cx: &mut Context<Self>,
10544 ) {
10545 self.transact(window, cx, |this, window, cx| {
10546 this.select_to_end_of_line(
10547 &SelectToEndOfLine {
10548 stop_at_soft_wraps: false,
10549 },
10550 window,
10551 cx,
10552 );
10553 this.cut(&Cut, window, cx);
10554 });
10555 }
10556
10557 pub fn move_to_start_of_paragraph(
10558 &mut self,
10559 _: &MoveToStartOfParagraph,
10560 window: &mut Window,
10561 cx: &mut Context<Self>,
10562 ) {
10563 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10564 cx.propagate();
10565 return;
10566 }
10567
10568 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10569 s.move_with(|map, selection| {
10570 selection.collapse_to(
10571 movement::start_of_paragraph(map, selection.head(), 1),
10572 SelectionGoal::None,
10573 )
10574 });
10575 })
10576 }
10577
10578 pub fn move_to_end_of_paragraph(
10579 &mut self,
10580 _: &MoveToEndOfParagraph,
10581 window: &mut Window,
10582 cx: &mut Context<Self>,
10583 ) {
10584 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10585 cx.propagate();
10586 return;
10587 }
10588
10589 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10590 s.move_with(|map, selection| {
10591 selection.collapse_to(
10592 movement::end_of_paragraph(map, selection.head(), 1),
10593 SelectionGoal::None,
10594 )
10595 });
10596 })
10597 }
10598
10599 pub fn select_to_start_of_paragraph(
10600 &mut self,
10601 _: &SelectToStartOfParagraph,
10602 window: &mut Window,
10603 cx: &mut Context<Self>,
10604 ) {
10605 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10606 cx.propagate();
10607 return;
10608 }
10609
10610 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10611 s.move_heads_with(|map, head, _| {
10612 (
10613 movement::start_of_paragraph(map, head, 1),
10614 SelectionGoal::None,
10615 )
10616 });
10617 })
10618 }
10619
10620 pub fn select_to_end_of_paragraph(
10621 &mut self,
10622 _: &SelectToEndOfParagraph,
10623 window: &mut Window,
10624 cx: &mut Context<Self>,
10625 ) {
10626 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10627 cx.propagate();
10628 return;
10629 }
10630
10631 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10632 s.move_heads_with(|map, head, _| {
10633 (
10634 movement::end_of_paragraph(map, head, 1),
10635 SelectionGoal::None,
10636 )
10637 });
10638 })
10639 }
10640
10641 pub fn move_to_start_of_excerpt(
10642 &mut self,
10643 _: &MoveToStartOfExcerpt,
10644 window: &mut Window,
10645 cx: &mut Context<Self>,
10646 ) {
10647 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10648 cx.propagate();
10649 return;
10650 }
10651
10652 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10653 s.move_with(|map, selection| {
10654 selection.collapse_to(
10655 movement::start_of_excerpt(
10656 map,
10657 selection.head(),
10658 workspace::searchable::Direction::Prev,
10659 ),
10660 SelectionGoal::None,
10661 )
10662 });
10663 })
10664 }
10665
10666 pub fn move_to_start_of_next_excerpt(
10667 &mut self,
10668 _: &MoveToStartOfNextExcerpt,
10669 window: &mut Window,
10670 cx: &mut Context<Self>,
10671 ) {
10672 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10673 cx.propagate();
10674 return;
10675 }
10676
10677 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10678 s.move_with(|map, selection| {
10679 selection.collapse_to(
10680 movement::start_of_excerpt(
10681 map,
10682 selection.head(),
10683 workspace::searchable::Direction::Next,
10684 ),
10685 SelectionGoal::None,
10686 )
10687 });
10688 })
10689 }
10690
10691 pub fn move_to_end_of_excerpt(
10692 &mut self,
10693 _: &MoveToEndOfExcerpt,
10694 window: &mut Window,
10695 cx: &mut Context<Self>,
10696 ) {
10697 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10698 cx.propagate();
10699 return;
10700 }
10701
10702 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10703 s.move_with(|map, selection| {
10704 selection.collapse_to(
10705 movement::end_of_excerpt(
10706 map,
10707 selection.head(),
10708 workspace::searchable::Direction::Next,
10709 ),
10710 SelectionGoal::None,
10711 )
10712 });
10713 })
10714 }
10715
10716 pub fn move_to_end_of_previous_excerpt(
10717 &mut self,
10718 _: &MoveToEndOfPreviousExcerpt,
10719 window: &mut Window,
10720 cx: &mut Context<Self>,
10721 ) {
10722 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10723 cx.propagate();
10724 return;
10725 }
10726
10727 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10728 s.move_with(|map, selection| {
10729 selection.collapse_to(
10730 movement::end_of_excerpt(
10731 map,
10732 selection.head(),
10733 workspace::searchable::Direction::Prev,
10734 ),
10735 SelectionGoal::None,
10736 )
10737 });
10738 })
10739 }
10740
10741 pub fn select_to_start_of_excerpt(
10742 &mut self,
10743 _: &SelectToStartOfExcerpt,
10744 window: &mut Window,
10745 cx: &mut Context<Self>,
10746 ) {
10747 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10748 cx.propagate();
10749 return;
10750 }
10751
10752 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10753 s.move_heads_with(|map, head, _| {
10754 (
10755 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
10756 SelectionGoal::None,
10757 )
10758 });
10759 })
10760 }
10761
10762 pub fn select_to_start_of_next_excerpt(
10763 &mut self,
10764 _: &SelectToStartOfNextExcerpt,
10765 window: &mut Window,
10766 cx: &mut Context<Self>,
10767 ) {
10768 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10769 cx.propagate();
10770 return;
10771 }
10772
10773 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10774 s.move_heads_with(|map, head, _| {
10775 (
10776 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
10777 SelectionGoal::None,
10778 )
10779 });
10780 })
10781 }
10782
10783 pub fn select_to_end_of_excerpt(
10784 &mut self,
10785 _: &SelectToEndOfExcerpt,
10786 window: &mut Window,
10787 cx: &mut Context<Self>,
10788 ) {
10789 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10790 cx.propagate();
10791 return;
10792 }
10793
10794 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10795 s.move_heads_with(|map, head, _| {
10796 (
10797 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
10798 SelectionGoal::None,
10799 )
10800 });
10801 })
10802 }
10803
10804 pub fn select_to_end_of_previous_excerpt(
10805 &mut self,
10806 _: &SelectToEndOfPreviousExcerpt,
10807 window: &mut Window,
10808 cx: &mut Context<Self>,
10809 ) {
10810 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10811 cx.propagate();
10812 return;
10813 }
10814
10815 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10816 s.move_heads_with(|map, head, _| {
10817 (
10818 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
10819 SelectionGoal::None,
10820 )
10821 });
10822 })
10823 }
10824
10825 pub fn move_to_beginning(
10826 &mut self,
10827 _: &MoveToBeginning,
10828 window: &mut Window,
10829 cx: &mut Context<Self>,
10830 ) {
10831 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10832 cx.propagate();
10833 return;
10834 }
10835
10836 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10837 s.select_ranges(vec![0..0]);
10838 });
10839 }
10840
10841 pub fn select_to_beginning(
10842 &mut self,
10843 _: &SelectToBeginning,
10844 window: &mut Window,
10845 cx: &mut Context<Self>,
10846 ) {
10847 let mut selection = self.selections.last::<Point>(cx);
10848 selection.set_head(Point::zero(), SelectionGoal::None);
10849
10850 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10851 s.select(vec![selection]);
10852 });
10853 }
10854
10855 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
10856 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10857 cx.propagate();
10858 return;
10859 }
10860
10861 let cursor = self.buffer.read(cx).read(cx).len();
10862 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10863 s.select_ranges(vec![cursor..cursor])
10864 });
10865 }
10866
10867 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
10868 self.nav_history = nav_history;
10869 }
10870
10871 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
10872 self.nav_history.as_ref()
10873 }
10874
10875 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
10876 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
10877 }
10878
10879 fn push_to_nav_history(
10880 &mut self,
10881 cursor_anchor: Anchor,
10882 new_position: Option<Point>,
10883 is_deactivate: bool,
10884 cx: &mut Context<Self>,
10885 ) {
10886 if let Some(nav_history) = self.nav_history.as_mut() {
10887 let buffer = self.buffer.read(cx).read(cx);
10888 let cursor_position = cursor_anchor.to_point(&buffer);
10889 let scroll_state = self.scroll_manager.anchor();
10890 let scroll_top_row = scroll_state.top_row(&buffer);
10891 drop(buffer);
10892
10893 if let Some(new_position) = new_position {
10894 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
10895 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
10896 return;
10897 }
10898 }
10899
10900 nav_history.push(
10901 Some(NavigationData {
10902 cursor_anchor,
10903 cursor_position,
10904 scroll_anchor: scroll_state,
10905 scroll_top_row,
10906 }),
10907 cx,
10908 );
10909 cx.emit(EditorEvent::PushedToNavHistory {
10910 anchor: cursor_anchor,
10911 is_deactivate,
10912 })
10913 }
10914 }
10915
10916 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
10917 let buffer = self.buffer.read(cx).snapshot(cx);
10918 let mut selection = self.selections.first::<usize>(cx);
10919 selection.set_head(buffer.len(), SelectionGoal::None);
10920 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10921 s.select(vec![selection]);
10922 });
10923 }
10924
10925 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
10926 let end = self.buffer.read(cx).read(cx).len();
10927 self.change_selections(None, window, cx, |s| {
10928 s.select_ranges(vec![0..end]);
10929 });
10930 }
10931
10932 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
10933 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10934 let mut selections = self.selections.all::<Point>(cx);
10935 let max_point = display_map.buffer_snapshot.max_point();
10936 for selection in &mut selections {
10937 let rows = selection.spanned_rows(true, &display_map);
10938 selection.start = Point::new(rows.start.0, 0);
10939 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
10940 selection.reversed = false;
10941 }
10942 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10943 s.select(selections);
10944 });
10945 }
10946
10947 pub fn split_selection_into_lines(
10948 &mut self,
10949 _: &SplitSelectionIntoLines,
10950 window: &mut Window,
10951 cx: &mut Context<Self>,
10952 ) {
10953 let selections = self
10954 .selections
10955 .all::<Point>(cx)
10956 .into_iter()
10957 .map(|selection| selection.start..selection.end)
10958 .collect::<Vec<_>>();
10959 self.unfold_ranges(&selections, true, true, cx);
10960
10961 let mut new_selection_ranges = Vec::new();
10962 {
10963 let buffer = self.buffer.read(cx).read(cx);
10964 for selection in selections {
10965 for row in selection.start.row..selection.end.row {
10966 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
10967 new_selection_ranges.push(cursor..cursor);
10968 }
10969
10970 let is_multiline_selection = selection.start.row != selection.end.row;
10971 // Don't insert last one if it's a multi-line selection ending at the start of a line,
10972 // so this action feels more ergonomic when paired with other selection operations
10973 let should_skip_last = is_multiline_selection && selection.end.column == 0;
10974 if !should_skip_last {
10975 new_selection_ranges.push(selection.end..selection.end);
10976 }
10977 }
10978 }
10979 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10980 s.select_ranges(new_selection_ranges);
10981 });
10982 }
10983
10984 pub fn add_selection_above(
10985 &mut self,
10986 _: &AddSelectionAbove,
10987 window: &mut Window,
10988 cx: &mut Context<Self>,
10989 ) {
10990 self.add_selection(true, window, cx);
10991 }
10992
10993 pub fn add_selection_below(
10994 &mut self,
10995 _: &AddSelectionBelow,
10996 window: &mut Window,
10997 cx: &mut Context<Self>,
10998 ) {
10999 self.add_selection(false, window, cx);
11000 }
11001
11002 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
11003 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11004 let mut selections = self.selections.all::<Point>(cx);
11005 let text_layout_details = self.text_layout_details(window);
11006 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
11007 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
11008 let range = oldest_selection.display_range(&display_map).sorted();
11009
11010 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
11011 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
11012 let positions = start_x.min(end_x)..start_x.max(end_x);
11013
11014 selections.clear();
11015 let mut stack = Vec::new();
11016 for row in range.start.row().0..=range.end.row().0 {
11017 if let Some(selection) = self.selections.build_columnar_selection(
11018 &display_map,
11019 DisplayRow(row),
11020 &positions,
11021 oldest_selection.reversed,
11022 &text_layout_details,
11023 ) {
11024 stack.push(selection.id);
11025 selections.push(selection);
11026 }
11027 }
11028
11029 if above {
11030 stack.reverse();
11031 }
11032
11033 AddSelectionsState { above, stack }
11034 });
11035
11036 let last_added_selection = *state.stack.last().unwrap();
11037 let mut new_selections = Vec::new();
11038 if above == state.above {
11039 let end_row = if above {
11040 DisplayRow(0)
11041 } else {
11042 display_map.max_point().row()
11043 };
11044
11045 'outer: for selection in selections {
11046 if selection.id == last_added_selection {
11047 let range = selection.display_range(&display_map).sorted();
11048 debug_assert_eq!(range.start.row(), range.end.row());
11049 let mut row = range.start.row();
11050 let positions =
11051 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
11052 px(start)..px(end)
11053 } else {
11054 let start_x =
11055 display_map.x_for_display_point(range.start, &text_layout_details);
11056 let end_x =
11057 display_map.x_for_display_point(range.end, &text_layout_details);
11058 start_x.min(end_x)..start_x.max(end_x)
11059 };
11060
11061 while row != end_row {
11062 if above {
11063 row.0 -= 1;
11064 } else {
11065 row.0 += 1;
11066 }
11067
11068 if let Some(new_selection) = self.selections.build_columnar_selection(
11069 &display_map,
11070 row,
11071 &positions,
11072 selection.reversed,
11073 &text_layout_details,
11074 ) {
11075 state.stack.push(new_selection.id);
11076 if above {
11077 new_selections.push(new_selection);
11078 new_selections.push(selection);
11079 } else {
11080 new_selections.push(selection);
11081 new_selections.push(new_selection);
11082 }
11083
11084 continue 'outer;
11085 }
11086 }
11087 }
11088
11089 new_selections.push(selection);
11090 }
11091 } else {
11092 new_selections = selections;
11093 new_selections.retain(|s| s.id != last_added_selection);
11094 state.stack.pop();
11095 }
11096
11097 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11098 s.select(new_selections);
11099 });
11100 if state.stack.len() > 1 {
11101 self.add_selections_state = Some(state);
11102 }
11103 }
11104
11105 pub fn select_next_match_internal(
11106 &mut self,
11107 display_map: &DisplaySnapshot,
11108 replace_newest: bool,
11109 autoscroll: Option<Autoscroll>,
11110 window: &mut Window,
11111 cx: &mut Context<Self>,
11112 ) -> Result<()> {
11113 fn select_next_match_ranges(
11114 this: &mut Editor,
11115 range: Range<usize>,
11116 replace_newest: bool,
11117 auto_scroll: Option<Autoscroll>,
11118 window: &mut Window,
11119 cx: &mut Context<Editor>,
11120 ) {
11121 this.unfold_ranges(&[range.clone()], false, true, cx);
11122 this.change_selections(auto_scroll, window, cx, |s| {
11123 if replace_newest {
11124 s.delete(s.newest_anchor().id);
11125 }
11126 s.insert_range(range.clone());
11127 });
11128 }
11129
11130 let buffer = &display_map.buffer_snapshot;
11131 let mut selections = self.selections.all::<usize>(cx);
11132 if let Some(mut select_next_state) = self.select_next_state.take() {
11133 let query = &select_next_state.query;
11134 if !select_next_state.done {
11135 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11136 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11137 let mut next_selected_range = None;
11138
11139 let bytes_after_last_selection =
11140 buffer.bytes_in_range(last_selection.end..buffer.len());
11141 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
11142 let query_matches = query
11143 .stream_find_iter(bytes_after_last_selection)
11144 .map(|result| (last_selection.end, result))
11145 .chain(
11146 query
11147 .stream_find_iter(bytes_before_first_selection)
11148 .map(|result| (0, result)),
11149 );
11150
11151 for (start_offset, query_match) in query_matches {
11152 let query_match = query_match.unwrap(); // can only fail due to I/O
11153 let offset_range =
11154 start_offset + query_match.start()..start_offset + query_match.end();
11155 let display_range = offset_range.start.to_display_point(display_map)
11156 ..offset_range.end.to_display_point(display_map);
11157
11158 if !select_next_state.wordwise
11159 || (!movement::is_inside_word(display_map, display_range.start)
11160 && !movement::is_inside_word(display_map, display_range.end))
11161 {
11162 // TODO: This is n^2, because we might check all the selections
11163 if !selections
11164 .iter()
11165 .any(|selection| selection.range().overlaps(&offset_range))
11166 {
11167 next_selected_range = Some(offset_range);
11168 break;
11169 }
11170 }
11171 }
11172
11173 if let Some(next_selected_range) = next_selected_range {
11174 select_next_match_ranges(
11175 self,
11176 next_selected_range,
11177 replace_newest,
11178 autoscroll,
11179 window,
11180 cx,
11181 );
11182 } else {
11183 select_next_state.done = true;
11184 }
11185 }
11186
11187 self.select_next_state = Some(select_next_state);
11188 } else {
11189 let mut only_carets = true;
11190 let mut same_text_selected = true;
11191 let mut selected_text = None;
11192
11193 let mut selections_iter = selections.iter().peekable();
11194 while let Some(selection) = selections_iter.next() {
11195 if selection.start != selection.end {
11196 only_carets = false;
11197 }
11198
11199 if same_text_selected {
11200 if selected_text.is_none() {
11201 selected_text =
11202 Some(buffer.text_for_range(selection.range()).collect::<String>());
11203 }
11204
11205 if let Some(next_selection) = selections_iter.peek() {
11206 if next_selection.range().len() == selection.range().len() {
11207 let next_selected_text = buffer
11208 .text_for_range(next_selection.range())
11209 .collect::<String>();
11210 if Some(next_selected_text) != selected_text {
11211 same_text_selected = false;
11212 selected_text = None;
11213 }
11214 } else {
11215 same_text_selected = false;
11216 selected_text = None;
11217 }
11218 }
11219 }
11220 }
11221
11222 if only_carets {
11223 for selection in &mut selections {
11224 let word_range = movement::surrounding_word(
11225 display_map,
11226 selection.start.to_display_point(display_map),
11227 );
11228 selection.start = word_range.start.to_offset(display_map, Bias::Left);
11229 selection.end = word_range.end.to_offset(display_map, Bias::Left);
11230 selection.goal = SelectionGoal::None;
11231 selection.reversed = false;
11232 select_next_match_ranges(
11233 self,
11234 selection.start..selection.end,
11235 replace_newest,
11236 autoscroll,
11237 window,
11238 cx,
11239 );
11240 }
11241
11242 if selections.len() == 1 {
11243 let selection = selections
11244 .last()
11245 .expect("ensured that there's only one selection");
11246 let query = buffer
11247 .text_for_range(selection.start..selection.end)
11248 .collect::<String>();
11249 let is_empty = query.is_empty();
11250 let select_state = SelectNextState {
11251 query: AhoCorasick::new(&[query])?,
11252 wordwise: true,
11253 done: is_empty,
11254 };
11255 self.select_next_state = Some(select_state);
11256 } else {
11257 self.select_next_state = None;
11258 }
11259 } else if let Some(selected_text) = selected_text {
11260 self.select_next_state = Some(SelectNextState {
11261 query: AhoCorasick::new(&[selected_text])?,
11262 wordwise: false,
11263 done: false,
11264 });
11265 self.select_next_match_internal(
11266 display_map,
11267 replace_newest,
11268 autoscroll,
11269 window,
11270 cx,
11271 )?;
11272 }
11273 }
11274 Ok(())
11275 }
11276
11277 pub fn select_all_matches(
11278 &mut self,
11279 _action: &SelectAllMatches,
11280 window: &mut Window,
11281 cx: &mut Context<Self>,
11282 ) -> Result<()> {
11283 self.push_to_selection_history();
11284 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11285
11286 self.select_next_match_internal(&display_map, false, None, window, cx)?;
11287 let Some(select_next_state) = self.select_next_state.as_mut() else {
11288 return Ok(());
11289 };
11290 if select_next_state.done {
11291 return Ok(());
11292 }
11293
11294 let mut new_selections = self.selections.all::<usize>(cx);
11295
11296 let buffer = &display_map.buffer_snapshot;
11297 let query_matches = select_next_state
11298 .query
11299 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
11300
11301 for query_match in query_matches {
11302 let query_match = query_match.unwrap(); // can only fail due to I/O
11303 let offset_range = query_match.start()..query_match.end();
11304 let display_range = offset_range.start.to_display_point(&display_map)
11305 ..offset_range.end.to_display_point(&display_map);
11306
11307 if !select_next_state.wordwise
11308 || (!movement::is_inside_word(&display_map, display_range.start)
11309 && !movement::is_inside_word(&display_map, display_range.end))
11310 {
11311 self.selections.change_with(cx, |selections| {
11312 new_selections.push(Selection {
11313 id: selections.new_selection_id(),
11314 start: offset_range.start,
11315 end: offset_range.end,
11316 reversed: false,
11317 goal: SelectionGoal::None,
11318 });
11319 });
11320 }
11321 }
11322
11323 new_selections.sort_by_key(|selection| selection.start);
11324 let mut ix = 0;
11325 while ix + 1 < new_selections.len() {
11326 let current_selection = &new_selections[ix];
11327 let next_selection = &new_selections[ix + 1];
11328 if current_selection.range().overlaps(&next_selection.range()) {
11329 if current_selection.id < next_selection.id {
11330 new_selections.remove(ix + 1);
11331 } else {
11332 new_selections.remove(ix);
11333 }
11334 } else {
11335 ix += 1;
11336 }
11337 }
11338
11339 let reversed = self.selections.oldest::<usize>(cx).reversed;
11340
11341 for selection in new_selections.iter_mut() {
11342 selection.reversed = reversed;
11343 }
11344
11345 select_next_state.done = true;
11346 self.unfold_ranges(
11347 &new_selections
11348 .iter()
11349 .map(|selection| selection.range())
11350 .collect::<Vec<_>>(),
11351 false,
11352 false,
11353 cx,
11354 );
11355 self.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
11356 selections.select(new_selections)
11357 });
11358
11359 Ok(())
11360 }
11361
11362 pub fn select_next(
11363 &mut self,
11364 action: &SelectNext,
11365 window: &mut Window,
11366 cx: &mut Context<Self>,
11367 ) -> Result<()> {
11368 self.push_to_selection_history();
11369 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11370 self.select_next_match_internal(
11371 &display_map,
11372 action.replace_newest,
11373 Some(Autoscroll::newest()),
11374 window,
11375 cx,
11376 )?;
11377 Ok(())
11378 }
11379
11380 pub fn select_previous(
11381 &mut self,
11382 action: &SelectPrevious,
11383 window: &mut Window,
11384 cx: &mut Context<Self>,
11385 ) -> Result<()> {
11386 self.push_to_selection_history();
11387 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11388 let buffer = &display_map.buffer_snapshot;
11389 let mut selections = self.selections.all::<usize>(cx);
11390 if let Some(mut select_prev_state) = self.select_prev_state.take() {
11391 let query = &select_prev_state.query;
11392 if !select_prev_state.done {
11393 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11394 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11395 let mut next_selected_range = None;
11396 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
11397 let bytes_before_last_selection =
11398 buffer.reversed_bytes_in_range(0..last_selection.start);
11399 let bytes_after_first_selection =
11400 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
11401 let query_matches = query
11402 .stream_find_iter(bytes_before_last_selection)
11403 .map(|result| (last_selection.start, result))
11404 .chain(
11405 query
11406 .stream_find_iter(bytes_after_first_selection)
11407 .map(|result| (buffer.len(), result)),
11408 );
11409 for (end_offset, query_match) in query_matches {
11410 let query_match = query_match.unwrap(); // can only fail due to I/O
11411 let offset_range =
11412 end_offset - query_match.end()..end_offset - query_match.start();
11413 let display_range = offset_range.start.to_display_point(&display_map)
11414 ..offset_range.end.to_display_point(&display_map);
11415
11416 if !select_prev_state.wordwise
11417 || (!movement::is_inside_word(&display_map, display_range.start)
11418 && !movement::is_inside_word(&display_map, display_range.end))
11419 {
11420 next_selected_range = Some(offset_range);
11421 break;
11422 }
11423 }
11424
11425 if let Some(next_selected_range) = next_selected_range {
11426 self.unfold_ranges(&[next_selected_range.clone()], false, true, cx);
11427 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
11428 if action.replace_newest {
11429 s.delete(s.newest_anchor().id);
11430 }
11431 s.insert_range(next_selected_range);
11432 });
11433 } else {
11434 select_prev_state.done = true;
11435 }
11436 }
11437
11438 self.select_prev_state = Some(select_prev_state);
11439 } else {
11440 let mut only_carets = true;
11441 let mut same_text_selected = true;
11442 let mut selected_text = None;
11443
11444 let mut selections_iter = selections.iter().peekable();
11445 while let Some(selection) = selections_iter.next() {
11446 if selection.start != selection.end {
11447 only_carets = false;
11448 }
11449
11450 if same_text_selected {
11451 if selected_text.is_none() {
11452 selected_text =
11453 Some(buffer.text_for_range(selection.range()).collect::<String>());
11454 }
11455
11456 if let Some(next_selection) = selections_iter.peek() {
11457 if next_selection.range().len() == selection.range().len() {
11458 let next_selected_text = buffer
11459 .text_for_range(next_selection.range())
11460 .collect::<String>();
11461 if Some(next_selected_text) != selected_text {
11462 same_text_selected = false;
11463 selected_text = None;
11464 }
11465 } else {
11466 same_text_selected = false;
11467 selected_text = None;
11468 }
11469 }
11470 }
11471 }
11472
11473 if only_carets {
11474 for selection in &mut selections {
11475 let word_range = movement::surrounding_word(
11476 &display_map,
11477 selection.start.to_display_point(&display_map),
11478 );
11479 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
11480 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
11481 selection.goal = SelectionGoal::None;
11482 selection.reversed = false;
11483 }
11484 if selections.len() == 1 {
11485 let selection = selections
11486 .last()
11487 .expect("ensured that there's only one selection");
11488 let query = buffer
11489 .text_for_range(selection.start..selection.end)
11490 .collect::<String>();
11491 let is_empty = query.is_empty();
11492 let select_state = SelectNextState {
11493 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
11494 wordwise: true,
11495 done: is_empty,
11496 };
11497 self.select_prev_state = Some(select_state);
11498 } else {
11499 self.select_prev_state = None;
11500 }
11501
11502 self.unfold_ranges(
11503 &selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
11504 false,
11505 true,
11506 cx,
11507 );
11508 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
11509 s.select(selections);
11510 });
11511 } else if let Some(selected_text) = selected_text {
11512 self.select_prev_state = Some(SelectNextState {
11513 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
11514 wordwise: false,
11515 done: false,
11516 });
11517 self.select_previous(action, window, cx)?;
11518 }
11519 }
11520 Ok(())
11521 }
11522
11523 pub fn toggle_comments(
11524 &mut self,
11525 action: &ToggleComments,
11526 window: &mut Window,
11527 cx: &mut Context<Self>,
11528 ) {
11529 if self.read_only(cx) {
11530 return;
11531 }
11532 let text_layout_details = &self.text_layout_details(window);
11533 self.transact(window, cx, |this, window, cx| {
11534 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
11535 let mut edits = Vec::new();
11536 let mut selection_edit_ranges = Vec::new();
11537 let mut last_toggled_row = None;
11538 let snapshot = this.buffer.read(cx).read(cx);
11539 let empty_str: Arc<str> = Arc::default();
11540 let mut suffixes_inserted = Vec::new();
11541 let ignore_indent = action.ignore_indent;
11542
11543 fn comment_prefix_range(
11544 snapshot: &MultiBufferSnapshot,
11545 row: MultiBufferRow,
11546 comment_prefix: &str,
11547 comment_prefix_whitespace: &str,
11548 ignore_indent: bool,
11549 ) -> Range<Point> {
11550 let indent_size = if ignore_indent {
11551 0
11552 } else {
11553 snapshot.indent_size_for_line(row).len
11554 };
11555
11556 let start = Point::new(row.0, indent_size);
11557
11558 let mut line_bytes = snapshot
11559 .bytes_in_range(start..snapshot.max_point())
11560 .flatten()
11561 .copied();
11562
11563 // If this line currently begins with the line comment prefix, then record
11564 // the range containing the prefix.
11565 if line_bytes
11566 .by_ref()
11567 .take(comment_prefix.len())
11568 .eq(comment_prefix.bytes())
11569 {
11570 // Include any whitespace that matches the comment prefix.
11571 let matching_whitespace_len = line_bytes
11572 .zip(comment_prefix_whitespace.bytes())
11573 .take_while(|(a, b)| a == b)
11574 .count() as u32;
11575 let end = Point::new(
11576 start.row,
11577 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
11578 );
11579 start..end
11580 } else {
11581 start..start
11582 }
11583 }
11584
11585 fn comment_suffix_range(
11586 snapshot: &MultiBufferSnapshot,
11587 row: MultiBufferRow,
11588 comment_suffix: &str,
11589 comment_suffix_has_leading_space: bool,
11590 ) -> Range<Point> {
11591 let end = Point::new(row.0, snapshot.line_len(row));
11592 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
11593
11594 let mut line_end_bytes = snapshot
11595 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
11596 .flatten()
11597 .copied();
11598
11599 let leading_space_len = if suffix_start_column > 0
11600 && line_end_bytes.next() == Some(b' ')
11601 && comment_suffix_has_leading_space
11602 {
11603 1
11604 } else {
11605 0
11606 };
11607
11608 // If this line currently begins with the line comment prefix, then record
11609 // the range containing the prefix.
11610 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
11611 let start = Point::new(end.row, suffix_start_column - leading_space_len);
11612 start..end
11613 } else {
11614 end..end
11615 }
11616 }
11617
11618 // TODO: Handle selections that cross excerpts
11619 for selection in &mut selections {
11620 let start_column = snapshot
11621 .indent_size_for_line(MultiBufferRow(selection.start.row))
11622 .len;
11623 let language = if let Some(language) =
11624 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
11625 {
11626 language
11627 } else {
11628 continue;
11629 };
11630
11631 selection_edit_ranges.clear();
11632
11633 // If multiple selections contain a given row, avoid processing that
11634 // row more than once.
11635 let mut start_row = MultiBufferRow(selection.start.row);
11636 if last_toggled_row == Some(start_row) {
11637 start_row = start_row.next_row();
11638 }
11639 let end_row =
11640 if selection.end.row > selection.start.row && selection.end.column == 0 {
11641 MultiBufferRow(selection.end.row - 1)
11642 } else {
11643 MultiBufferRow(selection.end.row)
11644 };
11645 last_toggled_row = Some(end_row);
11646
11647 if start_row > end_row {
11648 continue;
11649 }
11650
11651 // If the language has line comments, toggle those.
11652 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
11653
11654 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
11655 if ignore_indent {
11656 full_comment_prefixes = full_comment_prefixes
11657 .into_iter()
11658 .map(|s| Arc::from(s.trim_end()))
11659 .collect();
11660 }
11661
11662 if !full_comment_prefixes.is_empty() {
11663 let first_prefix = full_comment_prefixes
11664 .first()
11665 .expect("prefixes is non-empty");
11666 let prefix_trimmed_lengths = full_comment_prefixes
11667 .iter()
11668 .map(|p| p.trim_end_matches(' ').len())
11669 .collect::<SmallVec<[usize; 4]>>();
11670
11671 let mut all_selection_lines_are_comments = true;
11672
11673 for row in start_row.0..=end_row.0 {
11674 let row = MultiBufferRow(row);
11675 if start_row < end_row && snapshot.is_line_blank(row) {
11676 continue;
11677 }
11678
11679 let prefix_range = full_comment_prefixes
11680 .iter()
11681 .zip(prefix_trimmed_lengths.iter().copied())
11682 .map(|(prefix, trimmed_prefix_len)| {
11683 comment_prefix_range(
11684 snapshot.deref(),
11685 row,
11686 &prefix[..trimmed_prefix_len],
11687 &prefix[trimmed_prefix_len..],
11688 ignore_indent,
11689 )
11690 })
11691 .max_by_key(|range| range.end.column - range.start.column)
11692 .expect("prefixes is non-empty");
11693
11694 if prefix_range.is_empty() {
11695 all_selection_lines_are_comments = false;
11696 }
11697
11698 selection_edit_ranges.push(prefix_range);
11699 }
11700
11701 if all_selection_lines_are_comments {
11702 edits.extend(
11703 selection_edit_ranges
11704 .iter()
11705 .cloned()
11706 .map(|range| (range, empty_str.clone())),
11707 );
11708 } else {
11709 let min_column = selection_edit_ranges
11710 .iter()
11711 .map(|range| range.start.column)
11712 .min()
11713 .unwrap_or(0);
11714 edits.extend(selection_edit_ranges.iter().map(|range| {
11715 let position = Point::new(range.start.row, min_column);
11716 (position..position, first_prefix.clone())
11717 }));
11718 }
11719 } else if let Some((full_comment_prefix, comment_suffix)) =
11720 language.block_comment_delimiters()
11721 {
11722 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
11723 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
11724 let prefix_range = comment_prefix_range(
11725 snapshot.deref(),
11726 start_row,
11727 comment_prefix,
11728 comment_prefix_whitespace,
11729 ignore_indent,
11730 );
11731 let suffix_range = comment_suffix_range(
11732 snapshot.deref(),
11733 end_row,
11734 comment_suffix.trim_start_matches(' '),
11735 comment_suffix.starts_with(' '),
11736 );
11737
11738 if prefix_range.is_empty() || suffix_range.is_empty() {
11739 edits.push((
11740 prefix_range.start..prefix_range.start,
11741 full_comment_prefix.clone(),
11742 ));
11743 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
11744 suffixes_inserted.push((end_row, comment_suffix.len()));
11745 } else {
11746 edits.push((prefix_range, empty_str.clone()));
11747 edits.push((suffix_range, empty_str.clone()));
11748 }
11749 } else {
11750 continue;
11751 }
11752 }
11753
11754 drop(snapshot);
11755 this.buffer.update(cx, |buffer, cx| {
11756 buffer.edit(edits, None, cx);
11757 });
11758
11759 // Adjust selections so that they end before any comment suffixes that
11760 // were inserted.
11761 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
11762 let mut selections = this.selections.all::<Point>(cx);
11763 let snapshot = this.buffer.read(cx).read(cx);
11764 for selection in &mut selections {
11765 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
11766 match row.cmp(&MultiBufferRow(selection.end.row)) {
11767 Ordering::Less => {
11768 suffixes_inserted.next();
11769 continue;
11770 }
11771 Ordering::Greater => break,
11772 Ordering::Equal => {
11773 if selection.end.column == snapshot.line_len(row) {
11774 if selection.is_empty() {
11775 selection.start.column -= suffix_len as u32;
11776 }
11777 selection.end.column -= suffix_len as u32;
11778 }
11779 break;
11780 }
11781 }
11782 }
11783 }
11784
11785 drop(snapshot);
11786 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11787 s.select(selections)
11788 });
11789
11790 let selections = this.selections.all::<Point>(cx);
11791 let selections_on_single_row = selections.windows(2).all(|selections| {
11792 selections[0].start.row == selections[1].start.row
11793 && selections[0].end.row == selections[1].end.row
11794 && selections[0].start.row == selections[0].end.row
11795 });
11796 let selections_selecting = selections
11797 .iter()
11798 .any(|selection| selection.start != selection.end);
11799 let advance_downwards = action.advance_downwards
11800 && selections_on_single_row
11801 && !selections_selecting
11802 && !matches!(this.mode, EditorMode::SingleLine { .. });
11803
11804 if advance_downwards {
11805 let snapshot = this.buffer.read(cx).snapshot(cx);
11806
11807 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11808 s.move_cursors_with(|display_snapshot, display_point, _| {
11809 let mut point = display_point.to_point(display_snapshot);
11810 point.row += 1;
11811 point = snapshot.clip_point(point, Bias::Left);
11812 let display_point = point.to_display_point(display_snapshot);
11813 let goal = SelectionGoal::HorizontalPosition(
11814 display_snapshot
11815 .x_for_display_point(display_point, text_layout_details)
11816 .into(),
11817 );
11818 (display_point, goal)
11819 })
11820 });
11821 }
11822 });
11823 }
11824
11825 pub fn select_enclosing_symbol(
11826 &mut self,
11827 _: &SelectEnclosingSymbol,
11828 window: &mut Window,
11829 cx: &mut Context<Self>,
11830 ) {
11831 let buffer = self.buffer.read(cx).snapshot(cx);
11832 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
11833
11834 fn update_selection(
11835 selection: &Selection<usize>,
11836 buffer_snap: &MultiBufferSnapshot,
11837 ) -> Option<Selection<usize>> {
11838 let cursor = selection.head();
11839 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
11840 for symbol in symbols.iter().rev() {
11841 let start = symbol.range.start.to_offset(buffer_snap);
11842 let end = symbol.range.end.to_offset(buffer_snap);
11843 let new_range = start..end;
11844 if start < selection.start || end > selection.end {
11845 return Some(Selection {
11846 id: selection.id,
11847 start: new_range.start,
11848 end: new_range.end,
11849 goal: SelectionGoal::None,
11850 reversed: selection.reversed,
11851 });
11852 }
11853 }
11854 None
11855 }
11856
11857 let mut selected_larger_symbol = false;
11858 let new_selections = old_selections
11859 .iter()
11860 .map(|selection| match update_selection(selection, &buffer) {
11861 Some(new_selection) => {
11862 if new_selection.range() != selection.range() {
11863 selected_larger_symbol = true;
11864 }
11865 new_selection
11866 }
11867 None => selection.clone(),
11868 })
11869 .collect::<Vec<_>>();
11870
11871 if selected_larger_symbol {
11872 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11873 s.select(new_selections);
11874 });
11875 }
11876 }
11877
11878 pub fn select_larger_syntax_node(
11879 &mut self,
11880 _: &SelectLargerSyntaxNode,
11881 window: &mut Window,
11882 cx: &mut Context<Self>,
11883 ) {
11884 let Some(visible_row_count) = self.visible_row_count() else {
11885 return;
11886 };
11887 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
11888 if old_selections.is_empty() {
11889 return;
11890 }
11891
11892 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11893 let buffer = self.buffer.read(cx).snapshot(cx);
11894
11895 let mut selected_larger_node = false;
11896 let mut new_selections = old_selections
11897 .iter()
11898 .map(|selection| {
11899 let old_range = selection.start..selection.end;
11900 let mut new_range = old_range.clone();
11901 let mut new_node = None;
11902 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
11903 {
11904 new_node = Some(node);
11905 new_range = match containing_range {
11906 MultiOrSingleBufferOffsetRange::Single(_) => break,
11907 MultiOrSingleBufferOffsetRange::Multi(range) => range,
11908 };
11909 if !display_map.intersects_fold(new_range.start)
11910 && !display_map.intersects_fold(new_range.end)
11911 {
11912 break;
11913 }
11914 }
11915
11916 if let Some(node) = new_node {
11917 // Log the ancestor, to support using this action as a way to explore TreeSitter
11918 // nodes. Parent and grandparent are also logged because this operation will not
11919 // visit nodes that have the same range as their parent.
11920 log::info!("Node: {node:?}");
11921 let parent = node.parent();
11922 log::info!("Parent: {parent:?}");
11923 let grandparent = parent.and_then(|x| x.parent());
11924 log::info!("Grandparent: {grandparent:?}");
11925 }
11926
11927 selected_larger_node |= new_range != old_range;
11928 Selection {
11929 id: selection.id,
11930 start: new_range.start,
11931 end: new_range.end,
11932 goal: SelectionGoal::None,
11933 reversed: selection.reversed,
11934 }
11935 })
11936 .collect::<Vec<_>>();
11937
11938 if !selected_larger_node {
11939 return; // don't put this call in the history
11940 }
11941
11942 // scroll based on transformation done to the last selection created by the user
11943 let (last_old, last_new) = old_selections
11944 .last()
11945 .zip(new_selections.last().cloned())
11946 .expect("old_selections isn't empty");
11947
11948 // revert selection
11949 let is_selection_reversed = {
11950 let should_newest_selection_be_reversed = last_old.start != last_new.start;
11951 new_selections.last_mut().expect("checked above").reversed =
11952 should_newest_selection_be_reversed;
11953 should_newest_selection_be_reversed
11954 };
11955
11956 if selected_larger_node {
11957 self.select_syntax_node_history.disable_clearing = true;
11958 self.change_selections(None, window, cx, |s| {
11959 s.select(new_selections.clone());
11960 });
11961 self.select_syntax_node_history.disable_clearing = false;
11962 }
11963
11964 let start_row = last_new.start.to_display_point(&display_map).row().0;
11965 let end_row = last_new.end.to_display_point(&display_map).row().0;
11966 let selection_height = end_row - start_row + 1;
11967 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
11968
11969 // if fits on screen (considering margin), keep it in the middle, else, scroll to selection head
11970 let scroll_behavior = if visible_row_count >= selection_height + scroll_margin_rows * 2 {
11971 let middle_row = (end_row + start_row) / 2;
11972 let selection_center = middle_row.saturating_sub(visible_row_count / 2);
11973 self.set_scroll_top_row(DisplayRow(selection_center), window, cx);
11974 SelectSyntaxNodeScrollBehavior::CenterSelection
11975 } else if is_selection_reversed {
11976 self.scroll_cursor_top(&Default::default(), window, cx);
11977 SelectSyntaxNodeScrollBehavior::CursorTop
11978 } else {
11979 self.scroll_cursor_bottom(&Default::default(), window, cx);
11980 SelectSyntaxNodeScrollBehavior::CursorBottom
11981 };
11982
11983 self.select_syntax_node_history.push((
11984 old_selections,
11985 scroll_behavior,
11986 is_selection_reversed,
11987 ));
11988 }
11989
11990 pub fn select_smaller_syntax_node(
11991 &mut self,
11992 _: &SelectSmallerSyntaxNode,
11993 window: &mut Window,
11994 cx: &mut Context<Self>,
11995 ) {
11996 let Some(visible_row_count) = self.visible_row_count() else {
11997 return;
11998 };
11999
12000 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
12001 self.select_syntax_node_history.pop()
12002 {
12003 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12004
12005 if let Some(selection) = selections.last_mut() {
12006 selection.reversed = is_selection_reversed;
12007 }
12008
12009 self.select_syntax_node_history.disable_clearing = true;
12010 self.change_selections(None, window, cx, |s| {
12011 s.select(selections.to_vec());
12012 });
12013 self.select_syntax_node_history.disable_clearing = false;
12014
12015 let newest = self.selections.newest::<usize>(cx);
12016 let start_row = newest.start.to_display_point(&display_map).row().0;
12017 let end_row = newest.end.to_display_point(&display_map).row().0;
12018
12019 match scroll_behavior {
12020 SelectSyntaxNodeScrollBehavior::CursorTop => {
12021 self.scroll_cursor_top(&Default::default(), window, cx);
12022 }
12023 SelectSyntaxNodeScrollBehavior::CenterSelection => {
12024 let middle_row = (end_row + start_row) / 2;
12025 let selection_center = middle_row.saturating_sub(visible_row_count / 2);
12026 // centralize the selection, not the cursor
12027 self.set_scroll_top_row(DisplayRow(selection_center), window, cx);
12028 }
12029 SelectSyntaxNodeScrollBehavior::CursorBottom => {
12030 self.scroll_cursor_bottom(&Default::default(), window, cx);
12031 }
12032 }
12033 }
12034 }
12035
12036 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
12037 if !EditorSettings::get_global(cx).gutter.runnables {
12038 self.clear_tasks();
12039 return Task::ready(());
12040 }
12041 let project = self.project.as_ref().map(Entity::downgrade);
12042 cx.spawn_in(window, async move |this, cx| {
12043 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
12044 let Some(project) = project.and_then(|p| p.upgrade()) else {
12045 return;
12046 };
12047 let Ok(display_snapshot) = this.update(cx, |this, cx| {
12048 this.display_map.update(cx, |map, cx| map.snapshot(cx))
12049 }) else {
12050 return;
12051 };
12052
12053 let hide_runnables = project
12054 .update(cx, |project, cx| {
12055 // Do not display any test indicators in non-dev server remote projects.
12056 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
12057 })
12058 .unwrap_or(true);
12059 if hide_runnables {
12060 return;
12061 }
12062 let new_rows =
12063 cx.background_spawn({
12064 let snapshot = display_snapshot.clone();
12065 async move {
12066 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
12067 }
12068 })
12069 .await;
12070
12071 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
12072 this.update(cx, |this, _| {
12073 this.clear_tasks();
12074 for (key, value) in rows {
12075 this.insert_tasks(key, value);
12076 }
12077 })
12078 .ok();
12079 })
12080 }
12081 fn fetch_runnable_ranges(
12082 snapshot: &DisplaySnapshot,
12083 range: Range<Anchor>,
12084 ) -> Vec<language::RunnableRange> {
12085 snapshot.buffer_snapshot.runnable_ranges(range).collect()
12086 }
12087
12088 fn runnable_rows(
12089 project: Entity<Project>,
12090 snapshot: DisplaySnapshot,
12091 runnable_ranges: Vec<RunnableRange>,
12092 mut cx: AsyncWindowContext,
12093 ) -> Vec<((BufferId, u32), RunnableTasks)> {
12094 runnable_ranges
12095 .into_iter()
12096 .filter_map(|mut runnable| {
12097 let tasks = cx
12098 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
12099 .ok()?;
12100 if tasks.is_empty() {
12101 return None;
12102 }
12103
12104 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
12105
12106 let row = snapshot
12107 .buffer_snapshot
12108 .buffer_line_for_row(MultiBufferRow(point.row))?
12109 .1
12110 .start
12111 .row;
12112
12113 let context_range =
12114 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
12115 Some((
12116 (runnable.buffer_id, row),
12117 RunnableTasks {
12118 templates: tasks,
12119 offset: snapshot
12120 .buffer_snapshot
12121 .anchor_before(runnable.run_range.start),
12122 context_range,
12123 column: point.column,
12124 extra_variables: runnable.extra_captures,
12125 },
12126 ))
12127 })
12128 .collect()
12129 }
12130
12131 fn templates_with_tags(
12132 project: &Entity<Project>,
12133 runnable: &mut Runnable,
12134 cx: &mut App,
12135 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
12136 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
12137 let (worktree_id, file) = project
12138 .buffer_for_id(runnable.buffer, cx)
12139 .and_then(|buffer| buffer.read(cx).file())
12140 .map(|file| (file.worktree_id(cx), file.clone()))
12141 .unzip();
12142
12143 (
12144 project.task_store().read(cx).task_inventory().cloned(),
12145 worktree_id,
12146 file,
12147 )
12148 });
12149
12150 let tags = mem::take(&mut runnable.tags);
12151 let mut tags: Vec<_> = tags
12152 .into_iter()
12153 .flat_map(|tag| {
12154 let tag = tag.0.clone();
12155 inventory
12156 .as_ref()
12157 .into_iter()
12158 .flat_map(|inventory| {
12159 inventory.read(cx).list_tasks(
12160 file.clone(),
12161 Some(runnable.language.clone()),
12162 worktree_id,
12163 cx,
12164 )
12165 })
12166 .filter(move |(_, template)| {
12167 template.tags.iter().any(|source_tag| source_tag == &tag)
12168 })
12169 })
12170 .sorted_by_key(|(kind, _)| kind.to_owned())
12171 .collect();
12172 if let Some((leading_tag_source, _)) = tags.first() {
12173 // Strongest source wins; if we have worktree tag binding, prefer that to
12174 // global and language bindings;
12175 // if we have a global binding, prefer that to language binding.
12176 let first_mismatch = tags
12177 .iter()
12178 .position(|(tag_source, _)| tag_source != leading_tag_source);
12179 if let Some(index) = first_mismatch {
12180 tags.truncate(index);
12181 }
12182 }
12183
12184 tags
12185 }
12186
12187 pub fn move_to_enclosing_bracket(
12188 &mut self,
12189 _: &MoveToEnclosingBracket,
12190 window: &mut Window,
12191 cx: &mut Context<Self>,
12192 ) {
12193 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12194 s.move_offsets_with(|snapshot, selection| {
12195 let Some(enclosing_bracket_ranges) =
12196 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
12197 else {
12198 return;
12199 };
12200
12201 let mut best_length = usize::MAX;
12202 let mut best_inside = false;
12203 let mut best_in_bracket_range = false;
12204 let mut best_destination = None;
12205 for (open, close) in enclosing_bracket_ranges {
12206 let close = close.to_inclusive();
12207 let length = close.end() - open.start;
12208 let inside = selection.start >= open.end && selection.end <= *close.start();
12209 let in_bracket_range = open.to_inclusive().contains(&selection.head())
12210 || close.contains(&selection.head());
12211
12212 // If best is next to a bracket and current isn't, skip
12213 if !in_bracket_range && best_in_bracket_range {
12214 continue;
12215 }
12216
12217 // Prefer smaller lengths unless best is inside and current isn't
12218 if length > best_length && (best_inside || !inside) {
12219 continue;
12220 }
12221
12222 best_length = length;
12223 best_inside = inside;
12224 best_in_bracket_range = in_bracket_range;
12225 best_destination = Some(
12226 if close.contains(&selection.start) && close.contains(&selection.end) {
12227 if inside {
12228 open.end
12229 } else {
12230 open.start
12231 }
12232 } else if inside {
12233 *close.start()
12234 } else {
12235 *close.end()
12236 },
12237 );
12238 }
12239
12240 if let Some(destination) = best_destination {
12241 selection.collapse_to(destination, SelectionGoal::None);
12242 }
12243 })
12244 });
12245 }
12246
12247 pub fn undo_selection(
12248 &mut self,
12249 _: &UndoSelection,
12250 window: &mut Window,
12251 cx: &mut Context<Self>,
12252 ) {
12253 self.end_selection(window, cx);
12254 self.selection_history.mode = SelectionHistoryMode::Undoing;
12255 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
12256 self.change_selections(None, window, cx, |s| {
12257 s.select_anchors(entry.selections.to_vec())
12258 });
12259 self.select_next_state = entry.select_next_state;
12260 self.select_prev_state = entry.select_prev_state;
12261 self.add_selections_state = entry.add_selections_state;
12262 self.request_autoscroll(Autoscroll::newest(), cx);
12263 }
12264 self.selection_history.mode = SelectionHistoryMode::Normal;
12265 }
12266
12267 pub fn redo_selection(
12268 &mut self,
12269 _: &RedoSelection,
12270 window: &mut Window,
12271 cx: &mut Context<Self>,
12272 ) {
12273 self.end_selection(window, cx);
12274 self.selection_history.mode = SelectionHistoryMode::Redoing;
12275 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
12276 self.change_selections(None, window, cx, |s| {
12277 s.select_anchors(entry.selections.to_vec())
12278 });
12279 self.select_next_state = entry.select_next_state;
12280 self.select_prev_state = entry.select_prev_state;
12281 self.add_selections_state = entry.add_selections_state;
12282 self.request_autoscroll(Autoscroll::newest(), cx);
12283 }
12284 self.selection_history.mode = SelectionHistoryMode::Normal;
12285 }
12286
12287 pub fn expand_excerpts(
12288 &mut self,
12289 action: &ExpandExcerpts,
12290 _: &mut Window,
12291 cx: &mut Context<Self>,
12292 ) {
12293 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
12294 }
12295
12296 pub fn expand_excerpts_down(
12297 &mut self,
12298 action: &ExpandExcerptsDown,
12299 _: &mut Window,
12300 cx: &mut Context<Self>,
12301 ) {
12302 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
12303 }
12304
12305 pub fn expand_excerpts_up(
12306 &mut self,
12307 action: &ExpandExcerptsUp,
12308 _: &mut Window,
12309 cx: &mut Context<Self>,
12310 ) {
12311 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
12312 }
12313
12314 pub fn expand_excerpts_for_direction(
12315 &mut self,
12316 lines: u32,
12317 direction: ExpandExcerptDirection,
12318
12319 cx: &mut Context<Self>,
12320 ) {
12321 let selections = self.selections.disjoint_anchors();
12322
12323 let lines = if lines == 0 {
12324 EditorSettings::get_global(cx).expand_excerpt_lines
12325 } else {
12326 lines
12327 };
12328
12329 self.buffer.update(cx, |buffer, cx| {
12330 let snapshot = buffer.snapshot(cx);
12331 let mut excerpt_ids = selections
12332 .iter()
12333 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
12334 .collect::<Vec<_>>();
12335 excerpt_ids.sort();
12336 excerpt_ids.dedup();
12337 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
12338 })
12339 }
12340
12341 pub fn expand_excerpt(
12342 &mut self,
12343 excerpt: ExcerptId,
12344 direction: ExpandExcerptDirection,
12345 window: &mut Window,
12346 cx: &mut Context<Self>,
12347 ) {
12348 let current_scroll_position = self.scroll_position(cx);
12349 let lines = EditorSettings::get_global(cx).expand_excerpt_lines;
12350 self.buffer.update(cx, |buffer, cx| {
12351 buffer.expand_excerpts([excerpt], lines, direction, cx)
12352 });
12353 if direction == ExpandExcerptDirection::Down {
12354 let new_scroll_position = current_scroll_position + gpui::Point::new(0.0, lines as f32);
12355 self.set_scroll_position(new_scroll_position, window, cx);
12356 }
12357 }
12358
12359 pub fn go_to_singleton_buffer_point(
12360 &mut self,
12361 point: Point,
12362 window: &mut Window,
12363 cx: &mut Context<Self>,
12364 ) {
12365 self.go_to_singleton_buffer_range(point..point, window, cx);
12366 }
12367
12368 pub fn go_to_singleton_buffer_range(
12369 &mut self,
12370 range: Range<Point>,
12371 window: &mut Window,
12372 cx: &mut Context<Self>,
12373 ) {
12374 let multibuffer = self.buffer().read(cx);
12375 let Some(buffer) = multibuffer.as_singleton() else {
12376 return;
12377 };
12378 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
12379 return;
12380 };
12381 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
12382 return;
12383 };
12384 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
12385 s.select_anchor_ranges([start..end])
12386 });
12387 }
12388
12389 fn go_to_diagnostic(
12390 &mut self,
12391 _: &GoToDiagnostic,
12392 window: &mut Window,
12393 cx: &mut Context<Self>,
12394 ) {
12395 self.go_to_diagnostic_impl(Direction::Next, window, cx)
12396 }
12397
12398 fn go_to_prev_diagnostic(
12399 &mut self,
12400 _: &GoToPreviousDiagnostic,
12401 window: &mut Window,
12402 cx: &mut Context<Self>,
12403 ) {
12404 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
12405 }
12406
12407 pub fn go_to_diagnostic_impl(
12408 &mut self,
12409 direction: Direction,
12410 window: &mut Window,
12411 cx: &mut Context<Self>,
12412 ) {
12413 let buffer = self.buffer.read(cx).snapshot(cx);
12414 let selection = self.selections.newest::<usize>(cx);
12415
12416 // If there is an active Diagnostic Popover jump to its diagnostic instead.
12417 if direction == Direction::Next {
12418 if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() {
12419 let Some(buffer_id) = popover.local_diagnostic.range.start.buffer_id else {
12420 return;
12421 };
12422 self.activate_diagnostics(
12423 buffer_id,
12424 popover.local_diagnostic.diagnostic.group_id,
12425 window,
12426 cx,
12427 );
12428 if let Some(active_diagnostics) = self.active_diagnostics.as_ref() {
12429 let primary_range_start = active_diagnostics.primary_range.start;
12430 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12431 let mut new_selection = s.newest_anchor().clone();
12432 new_selection.collapse_to(primary_range_start, SelectionGoal::None);
12433 s.select_anchors(vec![new_selection.clone()]);
12434 });
12435 self.refresh_inline_completion(false, true, window, cx);
12436 }
12437 return;
12438 }
12439 }
12440
12441 let active_group_id = self
12442 .active_diagnostics
12443 .as_ref()
12444 .map(|active_group| active_group.group_id);
12445 let active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| {
12446 active_diagnostics
12447 .primary_range
12448 .to_offset(&buffer)
12449 .to_inclusive()
12450 });
12451 let search_start = if let Some(active_primary_range) = active_primary_range.as_ref() {
12452 if active_primary_range.contains(&selection.head()) {
12453 *active_primary_range.start()
12454 } else {
12455 selection.head()
12456 }
12457 } else {
12458 selection.head()
12459 };
12460
12461 let snapshot = self.snapshot(window, cx);
12462 let primary_diagnostics_before = buffer
12463 .diagnostics_in_range::<usize>(0..search_start)
12464 .filter(|entry| entry.diagnostic.is_primary)
12465 .filter(|entry| entry.range.start != entry.range.end)
12466 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
12467 .filter(|entry| !snapshot.intersects_fold(entry.range.start))
12468 .collect::<Vec<_>>();
12469 let last_same_group_diagnostic_before = active_group_id.and_then(|active_group_id| {
12470 primary_diagnostics_before
12471 .iter()
12472 .position(|entry| entry.diagnostic.group_id == active_group_id)
12473 });
12474
12475 let primary_diagnostics_after = buffer
12476 .diagnostics_in_range::<usize>(search_start..buffer.len())
12477 .filter(|entry| entry.diagnostic.is_primary)
12478 .filter(|entry| entry.range.start != entry.range.end)
12479 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
12480 .filter(|diagnostic| !snapshot.intersects_fold(diagnostic.range.start))
12481 .collect::<Vec<_>>();
12482 let last_same_group_diagnostic_after = active_group_id.and_then(|active_group_id| {
12483 primary_diagnostics_after
12484 .iter()
12485 .enumerate()
12486 .rev()
12487 .find_map(|(i, entry)| {
12488 if entry.diagnostic.group_id == active_group_id {
12489 Some(i)
12490 } else {
12491 None
12492 }
12493 })
12494 });
12495
12496 let next_primary_diagnostic = match direction {
12497 Direction::Prev => primary_diagnostics_before
12498 .iter()
12499 .take(last_same_group_diagnostic_before.unwrap_or(usize::MAX))
12500 .rev()
12501 .next(),
12502 Direction::Next => primary_diagnostics_after
12503 .iter()
12504 .skip(
12505 last_same_group_diagnostic_after
12506 .map(|index| index + 1)
12507 .unwrap_or(0),
12508 )
12509 .next(),
12510 };
12511
12512 // Cycle around to the start of the buffer, potentially moving back to the start of
12513 // the currently active diagnostic.
12514 let cycle_around = || match direction {
12515 Direction::Prev => primary_diagnostics_after
12516 .iter()
12517 .rev()
12518 .chain(primary_diagnostics_before.iter().rev())
12519 .next(),
12520 Direction::Next => primary_diagnostics_before
12521 .iter()
12522 .chain(primary_diagnostics_after.iter())
12523 .next(),
12524 };
12525
12526 if let Some((primary_range, group_id)) = next_primary_diagnostic
12527 .or_else(cycle_around)
12528 .map(|entry| (&entry.range, entry.diagnostic.group_id))
12529 {
12530 let Some(buffer_id) = buffer.anchor_after(primary_range.start).buffer_id else {
12531 return;
12532 };
12533 self.activate_diagnostics(buffer_id, group_id, window, cx);
12534 if self.active_diagnostics.is_some() {
12535 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12536 s.select(vec![Selection {
12537 id: selection.id,
12538 start: primary_range.start,
12539 end: primary_range.start,
12540 reversed: false,
12541 goal: SelectionGoal::None,
12542 }]);
12543 });
12544 self.refresh_inline_completion(false, true, window, cx);
12545 }
12546 }
12547 }
12548
12549 fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
12550 let snapshot = self.snapshot(window, cx);
12551 let selection = self.selections.newest::<Point>(cx);
12552 self.go_to_hunk_before_or_after_position(
12553 &snapshot,
12554 selection.head(),
12555 Direction::Next,
12556 window,
12557 cx,
12558 );
12559 }
12560
12561 fn go_to_hunk_before_or_after_position(
12562 &mut self,
12563 snapshot: &EditorSnapshot,
12564 position: Point,
12565 direction: Direction,
12566 window: &mut Window,
12567 cx: &mut Context<Editor>,
12568 ) {
12569 let row = if direction == Direction::Next {
12570 self.hunk_after_position(snapshot, position)
12571 .map(|hunk| hunk.row_range.start)
12572 } else {
12573 self.hunk_before_position(snapshot, position)
12574 };
12575
12576 if let Some(row) = row {
12577 let destination = Point::new(row.0, 0);
12578 let autoscroll = Autoscroll::center();
12579
12580 self.unfold_ranges(&[destination..destination], false, false, cx);
12581 self.change_selections(Some(autoscroll), window, cx, |s| {
12582 s.select_ranges([destination..destination]);
12583 });
12584 }
12585 }
12586
12587 fn hunk_after_position(
12588 &mut self,
12589 snapshot: &EditorSnapshot,
12590 position: Point,
12591 ) -> Option<MultiBufferDiffHunk> {
12592 snapshot
12593 .buffer_snapshot
12594 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
12595 .find(|hunk| hunk.row_range.start.0 > position.row)
12596 .or_else(|| {
12597 snapshot
12598 .buffer_snapshot
12599 .diff_hunks_in_range(Point::zero()..position)
12600 .find(|hunk| hunk.row_range.end.0 < position.row)
12601 })
12602 }
12603
12604 fn go_to_prev_hunk(
12605 &mut self,
12606 _: &GoToPreviousHunk,
12607 window: &mut Window,
12608 cx: &mut Context<Self>,
12609 ) {
12610 let snapshot = self.snapshot(window, cx);
12611 let selection = self.selections.newest::<Point>(cx);
12612 self.go_to_hunk_before_or_after_position(
12613 &snapshot,
12614 selection.head(),
12615 Direction::Prev,
12616 window,
12617 cx,
12618 );
12619 }
12620
12621 fn hunk_before_position(
12622 &mut self,
12623 snapshot: &EditorSnapshot,
12624 position: Point,
12625 ) -> Option<MultiBufferRow> {
12626 snapshot
12627 .buffer_snapshot
12628 .diff_hunk_before(position)
12629 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
12630 }
12631
12632 fn go_to_line<T: 'static>(
12633 &mut self,
12634 position: Anchor,
12635 highlight_color: Option<Hsla>,
12636 window: &mut Window,
12637 cx: &mut Context<Self>,
12638 ) {
12639 let snapshot = self.snapshot(window, cx).display_snapshot;
12640 let position = position.to_point(&snapshot.buffer_snapshot);
12641 let start = snapshot
12642 .buffer_snapshot
12643 .clip_point(Point::new(position.row, 0), Bias::Left);
12644 let end = start + Point::new(1, 0);
12645 let start = snapshot.buffer_snapshot.anchor_before(start);
12646 let end = snapshot.buffer_snapshot.anchor_before(end);
12647
12648 self.highlight_rows::<T>(
12649 start..end,
12650 highlight_color
12651 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
12652 false,
12653 cx,
12654 );
12655 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
12656 }
12657
12658 pub fn go_to_definition(
12659 &mut self,
12660 _: &GoToDefinition,
12661 window: &mut Window,
12662 cx: &mut Context<Self>,
12663 ) -> Task<Result<Navigated>> {
12664 let definition =
12665 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
12666 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
12667 cx.spawn_in(window, async move |editor, cx| {
12668 if definition.await? == Navigated::Yes {
12669 return Ok(Navigated::Yes);
12670 }
12671 match fallback_strategy {
12672 GoToDefinitionFallback::None => Ok(Navigated::No),
12673 GoToDefinitionFallback::FindAllReferences => {
12674 match editor.update_in(cx, |editor, window, cx| {
12675 editor.find_all_references(&FindAllReferences, window, cx)
12676 })? {
12677 Some(references) => references.await,
12678 None => Ok(Navigated::No),
12679 }
12680 }
12681 }
12682 })
12683 }
12684
12685 pub fn go_to_declaration(
12686 &mut self,
12687 _: &GoToDeclaration,
12688 window: &mut Window,
12689 cx: &mut Context<Self>,
12690 ) -> Task<Result<Navigated>> {
12691 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
12692 }
12693
12694 pub fn go_to_declaration_split(
12695 &mut self,
12696 _: &GoToDeclaration,
12697 window: &mut Window,
12698 cx: &mut Context<Self>,
12699 ) -> Task<Result<Navigated>> {
12700 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
12701 }
12702
12703 pub fn go_to_implementation(
12704 &mut self,
12705 _: &GoToImplementation,
12706 window: &mut Window,
12707 cx: &mut Context<Self>,
12708 ) -> Task<Result<Navigated>> {
12709 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
12710 }
12711
12712 pub fn go_to_implementation_split(
12713 &mut self,
12714 _: &GoToImplementationSplit,
12715 window: &mut Window,
12716 cx: &mut Context<Self>,
12717 ) -> Task<Result<Navigated>> {
12718 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
12719 }
12720
12721 pub fn go_to_type_definition(
12722 &mut self,
12723 _: &GoToTypeDefinition,
12724 window: &mut Window,
12725 cx: &mut Context<Self>,
12726 ) -> Task<Result<Navigated>> {
12727 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
12728 }
12729
12730 pub fn go_to_definition_split(
12731 &mut self,
12732 _: &GoToDefinitionSplit,
12733 window: &mut Window,
12734 cx: &mut Context<Self>,
12735 ) -> Task<Result<Navigated>> {
12736 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
12737 }
12738
12739 pub fn go_to_type_definition_split(
12740 &mut self,
12741 _: &GoToTypeDefinitionSplit,
12742 window: &mut Window,
12743 cx: &mut Context<Self>,
12744 ) -> Task<Result<Navigated>> {
12745 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
12746 }
12747
12748 fn go_to_definition_of_kind(
12749 &mut self,
12750 kind: GotoDefinitionKind,
12751 split: bool,
12752 window: &mut Window,
12753 cx: &mut Context<Self>,
12754 ) -> Task<Result<Navigated>> {
12755 let Some(provider) = self.semantics_provider.clone() else {
12756 return Task::ready(Ok(Navigated::No));
12757 };
12758 let head = self.selections.newest::<usize>(cx).head();
12759 let buffer = self.buffer.read(cx);
12760 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
12761 text_anchor
12762 } else {
12763 return Task::ready(Ok(Navigated::No));
12764 };
12765
12766 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
12767 return Task::ready(Ok(Navigated::No));
12768 };
12769
12770 cx.spawn_in(window, async move |editor, cx| {
12771 let definitions = definitions.await?;
12772 let navigated = editor
12773 .update_in(cx, |editor, window, cx| {
12774 editor.navigate_to_hover_links(
12775 Some(kind),
12776 definitions
12777 .into_iter()
12778 .filter(|location| {
12779 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
12780 })
12781 .map(HoverLink::Text)
12782 .collect::<Vec<_>>(),
12783 split,
12784 window,
12785 cx,
12786 )
12787 })?
12788 .await?;
12789 anyhow::Ok(navigated)
12790 })
12791 }
12792
12793 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
12794 let selection = self.selections.newest_anchor();
12795 let head = selection.head();
12796 let tail = selection.tail();
12797
12798 let Some((buffer, start_position)) =
12799 self.buffer.read(cx).text_anchor_for_position(head, cx)
12800 else {
12801 return;
12802 };
12803
12804 let end_position = if head != tail {
12805 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
12806 return;
12807 };
12808 Some(pos)
12809 } else {
12810 None
12811 };
12812
12813 let url_finder = cx.spawn_in(window, async move |editor, cx| {
12814 let url = if let Some(end_pos) = end_position {
12815 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
12816 } else {
12817 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
12818 };
12819
12820 if let Some(url) = url {
12821 editor.update(cx, |_, cx| {
12822 cx.open_url(&url);
12823 })
12824 } else {
12825 Ok(())
12826 }
12827 });
12828
12829 url_finder.detach();
12830 }
12831
12832 pub fn open_selected_filename(
12833 &mut self,
12834 _: &OpenSelectedFilename,
12835 window: &mut Window,
12836 cx: &mut Context<Self>,
12837 ) {
12838 let Some(workspace) = self.workspace() else {
12839 return;
12840 };
12841
12842 let position = self.selections.newest_anchor().head();
12843
12844 let Some((buffer, buffer_position)) =
12845 self.buffer.read(cx).text_anchor_for_position(position, cx)
12846 else {
12847 return;
12848 };
12849
12850 let project = self.project.clone();
12851
12852 cx.spawn_in(window, async move |_, cx| {
12853 let result = find_file(&buffer, project, buffer_position, cx).await;
12854
12855 if let Some((_, path)) = result {
12856 workspace
12857 .update_in(cx, |workspace, window, cx| {
12858 workspace.open_resolved_path(path, window, cx)
12859 })?
12860 .await?;
12861 }
12862 anyhow::Ok(())
12863 })
12864 .detach();
12865 }
12866
12867 pub(crate) fn navigate_to_hover_links(
12868 &mut self,
12869 kind: Option<GotoDefinitionKind>,
12870 mut definitions: Vec<HoverLink>,
12871 split: bool,
12872 window: &mut Window,
12873 cx: &mut Context<Editor>,
12874 ) -> Task<Result<Navigated>> {
12875 // If there is one definition, just open it directly
12876 if definitions.len() == 1 {
12877 let definition = definitions.pop().unwrap();
12878
12879 enum TargetTaskResult {
12880 Location(Option<Location>),
12881 AlreadyNavigated,
12882 }
12883
12884 let target_task = match definition {
12885 HoverLink::Text(link) => {
12886 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
12887 }
12888 HoverLink::InlayHint(lsp_location, server_id) => {
12889 let computation =
12890 self.compute_target_location(lsp_location, server_id, window, cx);
12891 cx.background_spawn(async move {
12892 let location = computation.await?;
12893 Ok(TargetTaskResult::Location(location))
12894 })
12895 }
12896 HoverLink::Url(url) => {
12897 cx.open_url(&url);
12898 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
12899 }
12900 HoverLink::File(path) => {
12901 if let Some(workspace) = self.workspace() {
12902 cx.spawn_in(window, async move |_, cx| {
12903 workspace
12904 .update_in(cx, |workspace, window, cx| {
12905 workspace.open_resolved_path(path, window, cx)
12906 })?
12907 .await
12908 .map(|_| TargetTaskResult::AlreadyNavigated)
12909 })
12910 } else {
12911 Task::ready(Ok(TargetTaskResult::Location(None)))
12912 }
12913 }
12914 };
12915 cx.spawn_in(window, async move |editor, cx| {
12916 let target = match target_task.await.context("target resolution task")? {
12917 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
12918 TargetTaskResult::Location(None) => return Ok(Navigated::No),
12919 TargetTaskResult::Location(Some(target)) => target,
12920 };
12921
12922 editor.update_in(cx, |editor, window, cx| {
12923 let Some(workspace) = editor.workspace() else {
12924 return Navigated::No;
12925 };
12926 let pane = workspace.read(cx).active_pane().clone();
12927
12928 let range = target.range.to_point(target.buffer.read(cx));
12929 let range = editor.range_for_match(&range);
12930 let range = collapse_multiline_range(range);
12931
12932 if !split
12933 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
12934 {
12935 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
12936 } else {
12937 window.defer(cx, move |window, cx| {
12938 let target_editor: Entity<Self> =
12939 workspace.update(cx, |workspace, cx| {
12940 let pane = if split {
12941 workspace.adjacent_pane(window, cx)
12942 } else {
12943 workspace.active_pane().clone()
12944 };
12945
12946 workspace.open_project_item(
12947 pane,
12948 target.buffer.clone(),
12949 true,
12950 true,
12951 window,
12952 cx,
12953 )
12954 });
12955 target_editor.update(cx, |target_editor, cx| {
12956 // When selecting a definition in a different buffer, disable the nav history
12957 // to avoid creating a history entry at the previous cursor location.
12958 pane.update(cx, |pane, _| pane.disable_history());
12959 target_editor.go_to_singleton_buffer_range(range, window, cx);
12960 pane.update(cx, |pane, _| pane.enable_history());
12961 });
12962 });
12963 }
12964 Navigated::Yes
12965 })
12966 })
12967 } else if !definitions.is_empty() {
12968 cx.spawn_in(window, async move |editor, cx| {
12969 let (title, location_tasks, workspace) = editor
12970 .update_in(cx, |editor, window, cx| {
12971 let tab_kind = match kind {
12972 Some(GotoDefinitionKind::Implementation) => "Implementations",
12973 _ => "Definitions",
12974 };
12975 let title = definitions
12976 .iter()
12977 .find_map(|definition| match definition {
12978 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
12979 let buffer = origin.buffer.read(cx);
12980 format!(
12981 "{} for {}",
12982 tab_kind,
12983 buffer
12984 .text_for_range(origin.range.clone())
12985 .collect::<String>()
12986 )
12987 }),
12988 HoverLink::InlayHint(_, _) => None,
12989 HoverLink::Url(_) => None,
12990 HoverLink::File(_) => None,
12991 })
12992 .unwrap_or(tab_kind.to_string());
12993 let location_tasks = definitions
12994 .into_iter()
12995 .map(|definition| match definition {
12996 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
12997 HoverLink::InlayHint(lsp_location, server_id) => editor
12998 .compute_target_location(lsp_location, server_id, window, cx),
12999 HoverLink::Url(_) => Task::ready(Ok(None)),
13000 HoverLink::File(_) => Task::ready(Ok(None)),
13001 })
13002 .collect::<Vec<_>>();
13003 (title, location_tasks, editor.workspace().clone())
13004 })
13005 .context("location tasks preparation")?;
13006
13007 let locations = future::join_all(location_tasks)
13008 .await
13009 .into_iter()
13010 .filter_map(|location| location.transpose())
13011 .collect::<Result<_>>()
13012 .context("location tasks")?;
13013
13014 let Some(workspace) = workspace else {
13015 return Ok(Navigated::No);
13016 };
13017 let opened = workspace
13018 .update_in(cx, |workspace, window, cx| {
13019 Self::open_locations_in_multibuffer(
13020 workspace,
13021 locations,
13022 title,
13023 split,
13024 MultibufferSelectionMode::First,
13025 window,
13026 cx,
13027 )
13028 })
13029 .ok();
13030
13031 anyhow::Ok(Navigated::from_bool(opened.is_some()))
13032 })
13033 } else {
13034 Task::ready(Ok(Navigated::No))
13035 }
13036 }
13037
13038 fn compute_target_location(
13039 &self,
13040 lsp_location: lsp::Location,
13041 server_id: LanguageServerId,
13042 window: &mut Window,
13043 cx: &mut Context<Self>,
13044 ) -> Task<anyhow::Result<Option<Location>>> {
13045 let Some(project) = self.project.clone() else {
13046 return Task::ready(Ok(None));
13047 };
13048
13049 cx.spawn_in(window, async move |editor, cx| {
13050 let location_task = editor.update(cx, |_, cx| {
13051 project.update(cx, |project, cx| {
13052 let language_server_name = project
13053 .language_server_statuses(cx)
13054 .find(|(id, _)| server_id == *id)
13055 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
13056 language_server_name.map(|language_server_name| {
13057 project.open_local_buffer_via_lsp(
13058 lsp_location.uri.clone(),
13059 server_id,
13060 language_server_name,
13061 cx,
13062 )
13063 })
13064 })
13065 })?;
13066 let location = match location_task {
13067 Some(task) => Some({
13068 let target_buffer_handle = task.await.context("open local buffer")?;
13069 let range = target_buffer_handle.update(cx, |target_buffer, _| {
13070 let target_start = target_buffer
13071 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
13072 let target_end = target_buffer
13073 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
13074 target_buffer.anchor_after(target_start)
13075 ..target_buffer.anchor_before(target_end)
13076 })?;
13077 Location {
13078 buffer: target_buffer_handle,
13079 range,
13080 }
13081 }),
13082 None => None,
13083 };
13084 Ok(location)
13085 })
13086 }
13087
13088 pub fn find_all_references(
13089 &mut self,
13090 _: &FindAllReferences,
13091 window: &mut Window,
13092 cx: &mut Context<Self>,
13093 ) -> Option<Task<Result<Navigated>>> {
13094 let selection = self.selections.newest::<usize>(cx);
13095 let multi_buffer = self.buffer.read(cx);
13096 let head = selection.head();
13097
13098 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
13099 let head_anchor = multi_buffer_snapshot.anchor_at(
13100 head,
13101 if head < selection.tail() {
13102 Bias::Right
13103 } else {
13104 Bias::Left
13105 },
13106 );
13107
13108 match self
13109 .find_all_references_task_sources
13110 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13111 {
13112 Ok(_) => {
13113 log::info!(
13114 "Ignoring repeated FindAllReferences invocation with the position of already running task"
13115 );
13116 return None;
13117 }
13118 Err(i) => {
13119 self.find_all_references_task_sources.insert(i, head_anchor);
13120 }
13121 }
13122
13123 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
13124 let workspace = self.workspace()?;
13125 let project = workspace.read(cx).project().clone();
13126 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
13127 Some(cx.spawn_in(window, async move |editor, cx| {
13128 let _cleanup = cx.on_drop(&editor, move |editor, _| {
13129 if let Ok(i) = editor
13130 .find_all_references_task_sources
13131 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13132 {
13133 editor.find_all_references_task_sources.remove(i);
13134 }
13135 });
13136
13137 let locations = references.await?;
13138 if locations.is_empty() {
13139 return anyhow::Ok(Navigated::No);
13140 }
13141
13142 workspace.update_in(cx, |workspace, window, cx| {
13143 let title = locations
13144 .first()
13145 .as_ref()
13146 .map(|location| {
13147 let buffer = location.buffer.read(cx);
13148 format!(
13149 "References to `{}`",
13150 buffer
13151 .text_for_range(location.range.clone())
13152 .collect::<String>()
13153 )
13154 })
13155 .unwrap();
13156 Self::open_locations_in_multibuffer(
13157 workspace,
13158 locations,
13159 title,
13160 false,
13161 MultibufferSelectionMode::First,
13162 window,
13163 cx,
13164 );
13165 Navigated::Yes
13166 })
13167 }))
13168 }
13169
13170 /// Opens a multibuffer with the given project locations in it
13171 pub fn open_locations_in_multibuffer(
13172 workspace: &mut Workspace,
13173 mut locations: Vec<Location>,
13174 title: String,
13175 split: bool,
13176 multibuffer_selection_mode: MultibufferSelectionMode,
13177 window: &mut Window,
13178 cx: &mut Context<Workspace>,
13179 ) {
13180 // If there are multiple definitions, open them in a multibuffer
13181 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
13182 let mut locations = locations.into_iter().peekable();
13183 let mut ranges = Vec::new();
13184 let capability = workspace.project().read(cx).capability();
13185
13186 let excerpt_buffer = cx.new(|cx| {
13187 let mut multibuffer = MultiBuffer::new(capability);
13188 while let Some(location) = locations.next() {
13189 let buffer = location.buffer.read(cx);
13190 let mut ranges_for_buffer = Vec::new();
13191 let range = location.range.to_offset(buffer);
13192 ranges_for_buffer.push(range.clone());
13193
13194 while let Some(next_location) = locations.peek() {
13195 if next_location.buffer == location.buffer {
13196 ranges_for_buffer.push(next_location.range.to_offset(buffer));
13197 locations.next();
13198 } else {
13199 break;
13200 }
13201 }
13202
13203 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
13204 ranges.extend(multibuffer.push_excerpts_with_context_lines(
13205 location.buffer.clone(),
13206 ranges_for_buffer,
13207 DEFAULT_MULTIBUFFER_CONTEXT,
13208 cx,
13209 ))
13210 }
13211
13212 multibuffer.with_title(title)
13213 });
13214
13215 let editor = cx.new(|cx| {
13216 Editor::for_multibuffer(
13217 excerpt_buffer,
13218 Some(workspace.project().clone()),
13219 window,
13220 cx,
13221 )
13222 });
13223 editor.update(cx, |editor, cx| {
13224 match multibuffer_selection_mode {
13225 MultibufferSelectionMode::First => {
13226 if let Some(first_range) = ranges.first() {
13227 editor.change_selections(None, window, cx, |selections| {
13228 selections.clear_disjoint();
13229 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
13230 });
13231 }
13232 editor.highlight_background::<Self>(
13233 &ranges,
13234 |theme| theme.editor_highlighted_line_background,
13235 cx,
13236 );
13237 }
13238 MultibufferSelectionMode::All => {
13239 editor.change_selections(None, window, cx, |selections| {
13240 selections.clear_disjoint();
13241 selections.select_anchor_ranges(ranges);
13242 });
13243 }
13244 }
13245 editor.register_buffers_with_language_servers(cx);
13246 });
13247
13248 let item = Box::new(editor);
13249 let item_id = item.item_id();
13250
13251 if split {
13252 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
13253 } else {
13254 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
13255 let (preview_item_id, preview_item_idx) =
13256 workspace.active_pane().update(cx, |pane, _| {
13257 (pane.preview_item_id(), pane.preview_item_idx())
13258 });
13259
13260 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
13261
13262 if let Some(preview_item_id) = preview_item_id {
13263 workspace.active_pane().update(cx, |pane, cx| {
13264 pane.remove_item(preview_item_id, false, false, window, cx);
13265 });
13266 }
13267 } else {
13268 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
13269 }
13270 }
13271 workspace.active_pane().update(cx, |pane, cx| {
13272 pane.set_preview_item_id(Some(item_id), cx);
13273 });
13274 }
13275
13276 pub fn rename(
13277 &mut self,
13278 _: &Rename,
13279 window: &mut Window,
13280 cx: &mut Context<Self>,
13281 ) -> Option<Task<Result<()>>> {
13282 use language::ToOffset as _;
13283
13284 let provider = self.semantics_provider.clone()?;
13285 let selection = self.selections.newest_anchor().clone();
13286 let (cursor_buffer, cursor_buffer_position) = self
13287 .buffer
13288 .read(cx)
13289 .text_anchor_for_position(selection.head(), cx)?;
13290 let (tail_buffer, cursor_buffer_position_end) = self
13291 .buffer
13292 .read(cx)
13293 .text_anchor_for_position(selection.tail(), cx)?;
13294 if tail_buffer != cursor_buffer {
13295 return None;
13296 }
13297
13298 let snapshot = cursor_buffer.read(cx).snapshot();
13299 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
13300 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
13301 let prepare_rename = provider
13302 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
13303 .unwrap_or_else(|| Task::ready(Ok(None)));
13304 drop(snapshot);
13305
13306 Some(cx.spawn_in(window, async move |this, cx| {
13307 let rename_range = if let Some(range) = prepare_rename.await? {
13308 Some(range)
13309 } else {
13310 this.update(cx, |this, cx| {
13311 let buffer = this.buffer.read(cx).snapshot(cx);
13312 let mut buffer_highlights = this
13313 .document_highlights_for_position(selection.head(), &buffer)
13314 .filter(|highlight| {
13315 highlight.start.excerpt_id == selection.head().excerpt_id
13316 && highlight.end.excerpt_id == selection.head().excerpt_id
13317 });
13318 buffer_highlights
13319 .next()
13320 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
13321 })?
13322 };
13323 if let Some(rename_range) = rename_range {
13324 this.update_in(cx, |this, window, cx| {
13325 let snapshot = cursor_buffer.read(cx).snapshot();
13326 let rename_buffer_range = rename_range.to_offset(&snapshot);
13327 let cursor_offset_in_rename_range =
13328 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
13329 let cursor_offset_in_rename_range_end =
13330 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
13331
13332 this.take_rename(false, window, cx);
13333 let buffer = this.buffer.read(cx).read(cx);
13334 let cursor_offset = selection.head().to_offset(&buffer);
13335 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
13336 let rename_end = rename_start + rename_buffer_range.len();
13337 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
13338 let mut old_highlight_id = None;
13339 let old_name: Arc<str> = buffer
13340 .chunks(rename_start..rename_end, true)
13341 .map(|chunk| {
13342 if old_highlight_id.is_none() {
13343 old_highlight_id = chunk.syntax_highlight_id;
13344 }
13345 chunk.text
13346 })
13347 .collect::<String>()
13348 .into();
13349
13350 drop(buffer);
13351
13352 // Position the selection in the rename editor so that it matches the current selection.
13353 this.show_local_selections = false;
13354 let rename_editor = cx.new(|cx| {
13355 let mut editor = Editor::single_line(window, cx);
13356 editor.buffer.update(cx, |buffer, cx| {
13357 buffer.edit([(0..0, old_name.clone())], None, cx)
13358 });
13359 let rename_selection_range = match cursor_offset_in_rename_range
13360 .cmp(&cursor_offset_in_rename_range_end)
13361 {
13362 Ordering::Equal => {
13363 editor.select_all(&SelectAll, window, cx);
13364 return editor;
13365 }
13366 Ordering::Less => {
13367 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
13368 }
13369 Ordering::Greater => {
13370 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
13371 }
13372 };
13373 if rename_selection_range.end > old_name.len() {
13374 editor.select_all(&SelectAll, window, cx);
13375 } else {
13376 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13377 s.select_ranges([rename_selection_range]);
13378 });
13379 }
13380 editor
13381 });
13382 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
13383 if e == &EditorEvent::Focused {
13384 cx.emit(EditorEvent::FocusedIn)
13385 }
13386 })
13387 .detach();
13388
13389 let write_highlights =
13390 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
13391 let read_highlights =
13392 this.clear_background_highlights::<DocumentHighlightRead>(cx);
13393 let ranges = write_highlights
13394 .iter()
13395 .flat_map(|(_, ranges)| ranges.iter())
13396 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
13397 .cloned()
13398 .collect();
13399
13400 this.highlight_text::<Rename>(
13401 ranges,
13402 HighlightStyle {
13403 fade_out: Some(0.6),
13404 ..Default::default()
13405 },
13406 cx,
13407 );
13408 let rename_focus_handle = rename_editor.focus_handle(cx);
13409 window.focus(&rename_focus_handle);
13410 let block_id = this.insert_blocks(
13411 [BlockProperties {
13412 style: BlockStyle::Flex,
13413 placement: BlockPlacement::Below(range.start),
13414 height: 1,
13415 render: Arc::new({
13416 let rename_editor = rename_editor.clone();
13417 move |cx: &mut BlockContext| {
13418 let mut text_style = cx.editor_style.text.clone();
13419 if let Some(highlight_style) = old_highlight_id
13420 .and_then(|h| h.style(&cx.editor_style.syntax))
13421 {
13422 text_style = text_style.highlight(highlight_style);
13423 }
13424 div()
13425 .block_mouse_down()
13426 .pl(cx.anchor_x)
13427 .child(EditorElement::new(
13428 &rename_editor,
13429 EditorStyle {
13430 background: cx.theme().system().transparent,
13431 local_player: cx.editor_style.local_player,
13432 text: text_style,
13433 scrollbar_width: cx.editor_style.scrollbar_width,
13434 syntax: cx.editor_style.syntax.clone(),
13435 status: cx.editor_style.status.clone(),
13436 inlay_hints_style: HighlightStyle {
13437 font_weight: Some(FontWeight::BOLD),
13438 ..make_inlay_hints_style(cx.app)
13439 },
13440 inline_completion_styles: make_suggestion_styles(
13441 cx.app,
13442 ),
13443 ..EditorStyle::default()
13444 },
13445 ))
13446 .into_any_element()
13447 }
13448 }),
13449 priority: 0,
13450 }],
13451 Some(Autoscroll::fit()),
13452 cx,
13453 )[0];
13454 this.pending_rename = Some(RenameState {
13455 range,
13456 old_name,
13457 editor: rename_editor,
13458 block_id,
13459 });
13460 })?;
13461 }
13462
13463 Ok(())
13464 }))
13465 }
13466
13467 pub fn confirm_rename(
13468 &mut self,
13469 _: &ConfirmRename,
13470 window: &mut Window,
13471 cx: &mut Context<Self>,
13472 ) -> Option<Task<Result<()>>> {
13473 let rename = self.take_rename(false, window, cx)?;
13474 let workspace = self.workspace()?.downgrade();
13475 let (buffer, start) = self
13476 .buffer
13477 .read(cx)
13478 .text_anchor_for_position(rename.range.start, cx)?;
13479 let (end_buffer, _) = self
13480 .buffer
13481 .read(cx)
13482 .text_anchor_for_position(rename.range.end, cx)?;
13483 if buffer != end_buffer {
13484 return None;
13485 }
13486
13487 let old_name = rename.old_name;
13488 let new_name = rename.editor.read(cx).text(cx);
13489
13490 let rename = self.semantics_provider.as_ref()?.perform_rename(
13491 &buffer,
13492 start,
13493 new_name.clone(),
13494 cx,
13495 )?;
13496
13497 Some(cx.spawn_in(window, async move |editor, cx| {
13498 let project_transaction = rename.await?;
13499 Self::open_project_transaction(
13500 &editor,
13501 workspace,
13502 project_transaction,
13503 format!("Rename: {} → {}", old_name, new_name),
13504 cx,
13505 )
13506 .await?;
13507
13508 editor.update(cx, |editor, cx| {
13509 editor.refresh_document_highlights(cx);
13510 })?;
13511 Ok(())
13512 }))
13513 }
13514
13515 fn take_rename(
13516 &mut self,
13517 moving_cursor: bool,
13518 window: &mut Window,
13519 cx: &mut Context<Self>,
13520 ) -> Option<RenameState> {
13521 let rename = self.pending_rename.take()?;
13522 if rename.editor.focus_handle(cx).is_focused(window) {
13523 window.focus(&self.focus_handle);
13524 }
13525
13526 self.remove_blocks(
13527 [rename.block_id].into_iter().collect(),
13528 Some(Autoscroll::fit()),
13529 cx,
13530 );
13531 self.clear_highlights::<Rename>(cx);
13532 self.show_local_selections = true;
13533
13534 if moving_cursor {
13535 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
13536 editor.selections.newest::<usize>(cx).head()
13537 });
13538
13539 // Update the selection to match the position of the selection inside
13540 // the rename editor.
13541 let snapshot = self.buffer.read(cx).read(cx);
13542 let rename_range = rename.range.to_offset(&snapshot);
13543 let cursor_in_editor = snapshot
13544 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
13545 .min(rename_range.end);
13546 drop(snapshot);
13547
13548 self.change_selections(None, window, cx, |s| {
13549 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
13550 });
13551 } else {
13552 self.refresh_document_highlights(cx);
13553 }
13554
13555 Some(rename)
13556 }
13557
13558 pub fn pending_rename(&self) -> Option<&RenameState> {
13559 self.pending_rename.as_ref()
13560 }
13561
13562 fn format(
13563 &mut self,
13564 _: &Format,
13565 window: &mut Window,
13566 cx: &mut Context<Self>,
13567 ) -> Option<Task<Result<()>>> {
13568 let project = match &self.project {
13569 Some(project) => project.clone(),
13570 None => return None,
13571 };
13572
13573 Some(self.perform_format(
13574 project,
13575 FormatTrigger::Manual,
13576 FormatTarget::Buffers,
13577 window,
13578 cx,
13579 ))
13580 }
13581
13582 fn format_selections(
13583 &mut self,
13584 _: &FormatSelections,
13585 window: &mut Window,
13586 cx: &mut Context<Self>,
13587 ) -> Option<Task<Result<()>>> {
13588 let project = match &self.project {
13589 Some(project) => project.clone(),
13590 None => return None,
13591 };
13592
13593 let ranges = self
13594 .selections
13595 .all_adjusted(cx)
13596 .into_iter()
13597 .map(|selection| selection.range())
13598 .collect_vec();
13599
13600 Some(self.perform_format(
13601 project,
13602 FormatTrigger::Manual,
13603 FormatTarget::Ranges(ranges),
13604 window,
13605 cx,
13606 ))
13607 }
13608
13609 fn perform_format(
13610 &mut self,
13611 project: Entity<Project>,
13612 trigger: FormatTrigger,
13613 target: FormatTarget,
13614 window: &mut Window,
13615 cx: &mut Context<Self>,
13616 ) -> Task<Result<()>> {
13617 let buffer = self.buffer.clone();
13618 let (buffers, target) = match target {
13619 FormatTarget::Buffers => {
13620 let mut buffers = buffer.read(cx).all_buffers();
13621 if trigger == FormatTrigger::Save {
13622 buffers.retain(|buffer| buffer.read(cx).is_dirty());
13623 }
13624 (buffers, LspFormatTarget::Buffers)
13625 }
13626 FormatTarget::Ranges(selection_ranges) => {
13627 let multi_buffer = buffer.read(cx);
13628 let snapshot = multi_buffer.read(cx);
13629 let mut buffers = HashSet::default();
13630 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
13631 BTreeMap::new();
13632 for selection_range in selection_ranges {
13633 for (buffer, buffer_range, _) in
13634 snapshot.range_to_buffer_ranges(selection_range)
13635 {
13636 let buffer_id = buffer.remote_id();
13637 let start = buffer.anchor_before(buffer_range.start);
13638 let end = buffer.anchor_after(buffer_range.end);
13639 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
13640 buffer_id_to_ranges
13641 .entry(buffer_id)
13642 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
13643 .or_insert_with(|| vec![start..end]);
13644 }
13645 }
13646 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
13647 }
13648 };
13649
13650 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
13651 let format = project.update(cx, |project, cx| {
13652 project.format(buffers, target, true, trigger, cx)
13653 });
13654
13655 cx.spawn_in(window, async move |_, cx| {
13656 let transaction = futures::select_biased! {
13657 transaction = format.log_err().fuse() => transaction,
13658 () = timeout => {
13659 log::warn!("timed out waiting for formatting");
13660 None
13661 }
13662 };
13663
13664 buffer
13665 .update(cx, |buffer, cx| {
13666 if let Some(transaction) = transaction {
13667 if !buffer.is_singleton() {
13668 buffer.push_transaction(&transaction.0, cx);
13669 }
13670 }
13671 cx.notify();
13672 })
13673 .ok();
13674
13675 Ok(())
13676 })
13677 }
13678
13679 fn organize_imports(
13680 &mut self,
13681 _: &OrganizeImports,
13682 window: &mut Window,
13683 cx: &mut Context<Self>,
13684 ) -> Option<Task<Result<()>>> {
13685 let project = match &self.project {
13686 Some(project) => project.clone(),
13687 None => return None,
13688 };
13689 Some(self.perform_code_action_kind(
13690 project,
13691 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
13692 window,
13693 cx,
13694 ))
13695 }
13696
13697 fn perform_code_action_kind(
13698 &mut self,
13699 project: Entity<Project>,
13700 kind: CodeActionKind,
13701 window: &mut Window,
13702 cx: &mut Context<Self>,
13703 ) -> Task<Result<()>> {
13704 let buffer = self.buffer.clone();
13705 let buffers = buffer.read(cx).all_buffers();
13706 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
13707 let apply_action = project.update(cx, |project, cx| {
13708 project.apply_code_action_kind(buffers, kind, true, cx)
13709 });
13710 cx.spawn_in(window, async move |_, cx| {
13711 let transaction = futures::select_biased! {
13712 () = timeout => {
13713 log::warn!("timed out waiting for executing code action");
13714 None
13715 }
13716 transaction = apply_action.log_err().fuse() => transaction,
13717 };
13718 buffer
13719 .update(cx, |buffer, cx| {
13720 // check if we need this
13721 if let Some(transaction) = transaction {
13722 if !buffer.is_singleton() {
13723 buffer.push_transaction(&transaction.0, cx);
13724 }
13725 }
13726 cx.notify();
13727 })
13728 .ok();
13729 Ok(())
13730 })
13731 }
13732
13733 fn restart_language_server(
13734 &mut self,
13735 _: &RestartLanguageServer,
13736 _: &mut Window,
13737 cx: &mut Context<Self>,
13738 ) {
13739 if let Some(project) = self.project.clone() {
13740 self.buffer.update(cx, |multi_buffer, cx| {
13741 project.update(cx, |project, cx| {
13742 project.restart_language_servers_for_buffers(
13743 multi_buffer.all_buffers().into_iter().collect(),
13744 cx,
13745 );
13746 });
13747 })
13748 }
13749 }
13750
13751 fn cancel_language_server_work(
13752 workspace: &mut Workspace,
13753 _: &actions::CancelLanguageServerWork,
13754 _: &mut Window,
13755 cx: &mut Context<Workspace>,
13756 ) {
13757 let project = workspace.project();
13758 let buffers = workspace
13759 .active_item(cx)
13760 .and_then(|item| item.act_as::<Editor>(cx))
13761 .map_or(HashSet::default(), |editor| {
13762 editor.read(cx).buffer.read(cx).all_buffers()
13763 });
13764 project.update(cx, |project, cx| {
13765 project.cancel_language_server_work_for_buffers(buffers, cx);
13766 });
13767 }
13768
13769 fn show_character_palette(
13770 &mut self,
13771 _: &ShowCharacterPalette,
13772 window: &mut Window,
13773 _: &mut Context<Self>,
13774 ) {
13775 window.show_character_palette();
13776 }
13777
13778 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
13779 if let Some(active_diagnostics) = self.active_diagnostics.as_mut() {
13780 let buffer = self.buffer.read(cx).snapshot(cx);
13781 let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer);
13782 let primary_range_end = active_diagnostics.primary_range.end.to_offset(&buffer);
13783 let is_valid = buffer
13784 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
13785 .any(|entry| {
13786 entry.diagnostic.is_primary
13787 && !entry.range.is_empty()
13788 && entry.range.start == primary_range_start
13789 && entry.diagnostic.message == active_diagnostics.primary_message
13790 });
13791
13792 if is_valid != active_diagnostics.is_valid {
13793 active_diagnostics.is_valid = is_valid;
13794 if is_valid {
13795 let mut new_styles = HashMap::default();
13796 for (block_id, diagnostic) in &active_diagnostics.blocks {
13797 new_styles.insert(
13798 *block_id,
13799 diagnostic_block_renderer(diagnostic.clone(), None, true),
13800 );
13801 }
13802 self.display_map.update(cx, |display_map, _cx| {
13803 display_map.replace_blocks(new_styles);
13804 });
13805 } else {
13806 self.dismiss_diagnostics(cx);
13807 }
13808 }
13809 }
13810 }
13811
13812 fn activate_diagnostics(
13813 &mut self,
13814 buffer_id: BufferId,
13815 group_id: usize,
13816 window: &mut Window,
13817 cx: &mut Context<Self>,
13818 ) {
13819 self.dismiss_diagnostics(cx);
13820 let snapshot = self.snapshot(window, cx);
13821 self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
13822 let buffer = self.buffer.read(cx).snapshot(cx);
13823
13824 let mut primary_range = None;
13825 let mut primary_message = None;
13826 let diagnostic_group = buffer
13827 .diagnostic_group(buffer_id, group_id)
13828 .filter_map(|entry| {
13829 let start = entry.range.start;
13830 let end = entry.range.end;
13831 if snapshot.is_line_folded(MultiBufferRow(start.row))
13832 && (start.row == end.row
13833 || snapshot.is_line_folded(MultiBufferRow(end.row)))
13834 {
13835 return None;
13836 }
13837 if entry.diagnostic.is_primary {
13838 primary_range = Some(entry.range.clone());
13839 primary_message = Some(entry.diagnostic.message.clone());
13840 }
13841 Some(entry)
13842 })
13843 .collect::<Vec<_>>();
13844 let primary_range = primary_range?;
13845 let primary_message = primary_message?;
13846
13847 let blocks = display_map
13848 .insert_blocks(
13849 diagnostic_group.iter().map(|entry| {
13850 let diagnostic = entry.diagnostic.clone();
13851 let message_height = diagnostic.message.matches('\n').count() as u32 + 1;
13852 BlockProperties {
13853 style: BlockStyle::Fixed,
13854 placement: BlockPlacement::Below(
13855 buffer.anchor_after(entry.range.start),
13856 ),
13857 height: message_height,
13858 render: diagnostic_block_renderer(diagnostic, None, true),
13859 priority: 0,
13860 }
13861 }),
13862 cx,
13863 )
13864 .into_iter()
13865 .zip(diagnostic_group.into_iter().map(|entry| entry.diagnostic))
13866 .collect();
13867
13868 Some(ActiveDiagnosticGroup {
13869 primary_range: buffer.anchor_before(primary_range.start)
13870 ..buffer.anchor_after(primary_range.end),
13871 primary_message,
13872 group_id,
13873 blocks,
13874 is_valid: true,
13875 })
13876 });
13877 }
13878
13879 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
13880 if let Some(active_diagnostic_group) = self.active_diagnostics.take() {
13881 self.display_map.update(cx, |display_map, cx| {
13882 display_map.remove_blocks(active_diagnostic_group.blocks.into_keys().collect(), cx);
13883 });
13884 cx.notify();
13885 }
13886 }
13887
13888 /// Disable inline diagnostics rendering for this editor.
13889 pub fn disable_inline_diagnostics(&mut self) {
13890 self.inline_diagnostics_enabled = false;
13891 self.inline_diagnostics_update = Task::ready(());
13892 self.inline_diagnostics.clear();
13893 }
13894
13895 pub fn inline_diagnostics_enabled(&self) -> bool {
13896 self.inline_diagnostics_enabled
13897 }
13898
13899 pub fn show_inline_diagnostics(&self) -> bool {
13900 self.show_inline_diagnostics
13901 }
13902
13903 pub fn toggle_inline_diagnostics(
13904 &mut self,
13905 _: &ToggleInlineDiagnostics,
13906 window: &mut Window,
13907 cx: &mut Context<'_, Editor>,
13908 ) {
13909 self.show_inline_diagnostics = !self.show_inline_diagnostics;
13910 self.refresh_inline_diagnostics(false, window, cx);
13911 }
13912
13913 fn refresh_inline_diagnostics(
13914 &mut self,
13915 debounce: bool,
13916 window: &mut Window,
13917 cx: &mut Context<Self>,
13918 ) {
13919 if !self.inline_diagnostics_enabled || !self.show_inline_diagnostics {
13920 self.inline_diagnostics_update = Task::ready(());
13921 self.inline_diagnostics.clear();
13922 return;
13923 }
13924
13925 let debounce_ms = ProjectSettings::get_global(cx)
13926 .diagnostics
13927 .inline
13928 .update_debounce_ms;
13929 let debounce = if debounce && debounce_ms > 0 {
13930 Some(Duration::from_millis(debounce_ms))
13931 } else {
13932 None
13933 };
13934 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
13935 if let Some(debounce) = debounce {
13936 cx.background_executor().timer(debounce).await;
13937 }
13938 let Some(snapshot) = editor
13939 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
13940 .ok()
13941 else {
13942 return;
13943 };
13944
13945 let new_inline_diagnostics = cx
13946 .background_spawn(async move {
13947 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
13948 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
13949 let message = diagnostic_entry
13950 .diagnostic
13951 .message
13952 .split_once('\n')
13953 .map(|(line, _)| line)
13954 .map(SharedString::new)
13955 .unwrap_or_else(|| {
13956 SharedString::from(diagnostic_entry.diagnostic.message)
13957 });
13958 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
13959 let (Ok(i) | Err(i)) = inline_diagnostics
13960 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
13961 inline_diagnostics.insert(
13962 i,
13963 (
13964 start_anchor,
13965 InlineDiagnostic {
13966 message,
13967 group_id: diagnostic_entry.diagnostic.group_id,
13968 start: diagnostic_entry.range.start.to_point(&snapshot),
13969 is_primary: diagnostic_entry.diagnostic.is_primary,
13970 severity: diagnostic_entry.diagnostic.severity,
13971 },
13972 ),
13973 );
13974 }
13975 inline_diagnostics
13976 })
13977 .await;
13978
13979 editor
13980 .update(cx, |editor, cx| {
13981 editor.inline_diagnostics = new_inline_diagnostics;
13982 cx.notify();
13983 })
13984 .ok();
13985 });
13986 }
13987
13988 pub fn set_selections_from_remote(
13989 &mut self,
13990 selections: Vec<Selection<Anchor>>,
13991 pending_selection: Option<Selection<Anchor>>,
13992 window: &mut Window,
13993 cx: &mut Context<Self>,
13994 ) {
13995 let old_cursor_position = self.selections.newest_anchor().head();
13996 self.selections.change_with(cx, |s| {
13997 s.select_anchors(selections);
13998 if let Some(pending_selection) = pending_selection {
13999 s.set_pending(pending_selection, SelectMode::Character);
14000 } else {
14001 s.clear_pending();
14002 }
14003 });
14004 self.selections_did_change(false, &old_cursor_position, true, window, cx);
14005 }
14006
14007 fn push_to_selection_history(&mut self) {
14008 self.selection_history.push(SelectionHistoryEntry {
14009 selections: self.selections.disjoint_anchors(),
14010 select_next_state: self.select_next_state.clone(),
14011 select_prev_state: self.select_prev_state.clone(),
14012 add_selections_state: self.add_selections_state.clone(),
14013 });
14014 }
14015
14016 pub fn transact(
14017 &mut self,
14018 window: &mut Window,
14019 cx: &mut Context<Self>,
14020 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
14021 ) -> Option<TransactionId> {
14022 self.start_transaction_at(Instant::now(), window, cx);
14023 update(self, window, cx);
14024 self.end_transaction_at(Instant::now(), cx)
14025 }
14026
14027 pub fn start_transaction_at(
14028 &mut self,
14029 now: Instant,
14030 window: &mut Window,
14031 cx: &mut Context<Self>,
14032 ) {
14033 self.end_selection(window, cx);
14034 if let Some(tx_id) = self
14035 .buffer
14036 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
14037 {
14038 self.selection_history
14039 .insert_transaction(tx_id, self.selections.disjoint_anchors());
14040 cx.emit(EditorEvent::TransactionBegun {
14041 transaction_id: tx_id,
14042 })
14043 }
14044 }
14045
14046 pub fn end_transaction_at(
14047 &mut self,
14048 now: Instant,
14049 cx: &mut Context<Self>,
14050 ) -> Option<TransactionId> {
14051 if let Some(transaction_id) = self
14052 .buffer
14053 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
14054 {
14055 if let Some((_, end_selections)) =
14056 self.selection_history.transaction_mut(transaction_id)
14057 {
14058 *end_selections = Some(self.selections.disjoint_anchors());
14059 } else {
14060 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
14061 }
14062
14063 cx.emit(EditorEvent::Edited { transaction_id });
14064 Some(transaction_id)
14065 } else {
14066 None
14067 }
14068 }
14069
14070 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
14071 if self.selection_mark_mode {
14072 self.change_selections(None, window, cx, |s| {
14073 s.move_with(|_, sel| {
14074 sel.collapse_to(sel.head(), SelectionGoal::None);
14075 });
14076 })
14077 }
14078 self.selection_mark_mode = true;
14079 cx.notify();
14080 }
14081
14082 pub fn swap_selection_ends(
14083 &mut self,
14084 _: &actions::SwapSelectionEnds,
14085 window: &mut Window,
14086 cx: &mut Context<Self>,
14087 ) {
14088 self.change_selections(None, window, cx, |s| {
14089 s.move_with(|_, sel| {
14090 if sel.start != sel.end {
14091 sel.reversed = !sel.reversed
14092 }
14093 });
14094 });
14095 self.request_autoscroll(Autoscroll::newest(), cx);
14096 cx.notify();
14097 }
14098
14099 pub fn toggle_fold(
14100 &mut self,
14101 _: &actions::ToggleFold,
14102 window: &mut Window,
14103 cx: &mut Context<Self>,
14104 ) {
14105 if self.is_singleton(cx) {
14106 let selection = self.selections.newest::<Point>(cx);
14107
14108 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14109 let range = if selection.is_empty() {
14110 let point = selection.head().to_display_point(&display_map);
14111 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14112 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14113 .to_point(&display_map);
14114 start..end
14115 } else {
14116 selection.range()
14117 };
14118 if display_map.folds_in_range(range).next().is_some() {
14119 self.unfold_lines(&Default::default(), window, cx)
14120 } else {
14121 self.fold(&Default::default(), window, cx)
14122 }
14123 } else {
14124 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14125 let buffer_ids: HashSet<_> = self
14126 .selections
14127 .disjoint_anchor_ranges()
14128 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14129 .collect();
14130
14131 let should_unfold = buffer_ids
14132 .iter()
14133 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
14134
14135 for buffer_id in buffer_ids {
14136 if should_unfold {
14137 self.unfold_buffer(buffer_id, cx);
14138 } else {
14139 self.fold_buffer(buffer_id, cx);
14140 }
14141 }
14142 }
14143 }
14144
14145 pub fn toggle_fold_recursive(
14146 &mut self,
14147 _: &actions::ToggleFoldRecursive,
14148 window: &mut Window,
14149 cx: &mut Context<Self>,
14150 ) {
14151 let selection = self.selections.newest::<Point>(cx);
14152
14153 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14154 let range = if selection.is_empty() {
14155 let point = selection.head().to_display_point(&display_map);
14156 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14157 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14158 .to_point(&display_map);
14159 start..end
14160 } else {
14161 selection.range()
14162 };
14163 if display_map.folds_in_range(range).next().is_some() {
14164 self.unfold_recursive(&Default::default(), window, cx)
14165 } else {
14166 self.fold_recursive(&Default::default(), window, cx)
14167 }
14168 }
14169
14170 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
14171 if self.is_singleton(cx) {
14172 let mut to_fold = Vec::new();
14173 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14174 let selections = self.selections.all_adjusted(cx);
14175
14176 for selection in selections {
14177 let range = selection.range().sorted();
14178 let buffer_start_row = range.start.row;
14179
14180 if range.start.row != range.end.row {
14181 let mut found = false;
14182 let mut row = range.start.row;
14183 while row <= range.end.row {
14184 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
14185 {
14186 found = true;
14187 row = crease.range().end.row + 1;
14188 to_fold.push(crease);
14189 } else {
14190 row += 1
14191 }
14192 }
14193 if found {
14194 continue;
14195 }
14196 }
14197
14198 for row in (0..=range.start.row).rev() {
14199 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14200 if crease.range().end.row >= buffer_start_row {
14201 to_fold.push(crease);
14202 if row <= range.start.row {
14203 break;
14204 }
14205 }
14206 }
14207 }
14208 }
14209
14210 self.fold_creases(to_fold, true, window, cx);
14211 } else {
14212 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14213 let buffer_ids = self
14214 .selections
14215 .disjoint_anchor_ranges()
14216 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14217 .collect::<HashSet<_>>();
14218 for buffer_id in buffer_ids {
14219 self.fold_buffer(buffer_id, cx);
14220 }
14221 }
14222 }
14223
14224 fn fold_at_level(
14225 &mut self,
14226 fold_at: &FoldAtLevel,
14227 window: &mut Window,
14228 cx: &mut Context<Self>,
14229 ) {
14230 if !self.buffer.read(cx).is_singleton() {
14231 return;
14232 }
14233
14234 let fold_at_level = fold_at.0;
14235 let snapshot = self.buffer.read(cx).snapshot(cx);
14236 let mut to_fold = Vec::new();
14237 let mut stack = vec![(0, snapshot.max_row().0, 1)];
14238
14239 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
14240 while start_row < end_row {
14241 match self
14242 .snapshot(window, cx)
14243 .crease_for_buffer_row(MultiBufferRow(start_row))
14244 {
14245 Some(crease) => {
14246 let nested_start_row = crease.range().start.row + 1;
14247 let nested_end_row = crease.range().end.row;
14248
14249 if current_level < fold_at_level {
14250 stack.push((nested_start_row, nested_end_row, current_level + 1));
14251 } else if current_level == fold_at_level {
14252 to_fold.push(crease);
14253 }
14254
14255 start_row = nested_end_row + 1;
14256 }
14257 None => start_row += 1,
14258 }
14259 }
14260 }
14261
14262 self.fold_creases(to_fold, true, window, cx);
14263 }
14264
14265 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
14266 if self.buffer.read(cx).is_singleton() {
14267 let mut fold_ranges = Vec::new();
14268 let snapshot = self.buffer.read(cx).snapshot(cx);
14269
14270 for row in 0..snapshot.max_row().0 {
14271 if let Some(foldable_range) = self
14272 .snapshot(window, cx)
14273 .crease_for_buffer_row(MultiBufferRow(row))
14274 {
14275 fold_ranges.push(foldable_range);
14276 }
14277 }
14278
14279 self.fold_creases(fold_ranges, true, window, cx);
14280 } else {
14281 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
14282 editor
14283 .update_in(cx, |editor, _, cx| {
14284 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
14285 editor.fold_buffer(buffer_id, cx);
14286 }
14287 })
14288 .ok();
14289 });
14290 }
14291 }
14292
14293 pub fn fold_function_bodies(
14294 &mut self,
14295 _: &actions::FoldFunctionBodies,
14296 window: &mut Window,
14297 cx: &mut Context<Self>,
14298 ) {
14299 let snapshot = self.buffer.read(cx).snapshot(cx);
14300
14301 let ranges = snapshot
14302 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
14303 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
14304 .collect::<Vec<_>>();
14305
14306 let creases = ranges
14307 .into_iter()
14308 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
14309 .collect();
14310
14311 self.fold_creases(creases, true, window, cx);
14312 }
14313
14314 pub fn fold_recursive(
14315 &mut self,
14316 _: &actions::FoldRecursive,
14317 window: &mut Window,
14318 cx: &mut Context<Self>,
14319 ) {
14320 let mut to_fold = Vec::new();
14321 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14322 let selections = self.selections.all_adjusted(cx);
14323
14324 for selection in selections {
14325 let range = selection.range().sorted();
14326 let buffer_start_row = range.start.row;
14327
14328 if range.start.row != range.end.row {
14329 let mut found = false;
14330 for row in range.start.row..=range.end.row {
14331 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14332 found = true;
14333 to_fold.push(crease);
14334 }
14335 }
14336 if found {
14337 continue;
14338 }
14339 }
14340
14341 for row in (0..=range.start.row).rev() {
14342 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14343 if crease.range().end.row >= buffer_start_row {
14344 to_fold.push(crease);
14345 } else {
14346 break;
14347 }
14348 }
14349 }
14350 }
14351
14352 self.fold_creases(to_fold, true, window, cx);
14353 }
14354
14355 pub fn fold_at(&mut self, fold_at: &FoldAt, window: &mut Window, cx: &mut Context<Self>) {
14356 let buffer_row = fold_at.buffer_row;
14357 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14358
14359 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
14360 let autoscroll = self
14361 .selections
14362 .all::<Point>(cx)
14363 .iter()
14364 .any(|selection| crease.range().overlaps(&selection.range()));
14365
14366 self.fold_creases(vec![crease], autoscroll, window, cx);
14367 }
14368 }
14369
14370 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
14371 if self.is_singleton(cx) {
14372 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14373 let buffer = &display_map.buffer_snapshot;
14374 let selections = self.selections.all::<Point>(cx);
14375 let ranges = selections
14376 .iter()
14377 .map(|s| {
14378 let range = s.display_range(&display_map).sorted();
14379 let mut start = range.start.to_point(&display_map);
14380 let mut end = range.end.to_point(&display_map);
14381 start.column = 0;
14382 end.column = buffer.line_len(MultiBufferRow(end.row));
14383 start..end
14384 })
14385 .collect::<Vec<_>>();
14386
14387 self.unfold_ranges(&ranges, true, true, cx);
14388 } else {
14389 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14390 let buffer_ids = self
14391 .selections
14392 .disjoint_anchor_ranges()
14393 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14394 .collect::<HashSet<_>>();
14395 for buffer_id in buffer_ids {
14396 self.unfold_buffer(buffer_id, cx);
14397 }
14398 }
14399 }
14400
14401 pub fn unfold_recursive(
14402 &mut self,
14403 _: &UnfoldRecursive,
14404 _window: &mut Window,
14405 cx: &mut Context<Self>,
14406 ) {
14407 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14408 let selections = self.selections.all::<Point>(cx);
14409 let ranges = selections
14410 .iter()
14411 .map(|s| {
14412 let mut range = s.display_range(&display_map).sorted();
14413 *range.start.column_mut() = 0;
14414 *range.end.column_mut() = display_map.line_len(range.end.row());
14415 let start = range.start.to_point(&display_map);
14416 let end = range.end.to_point(&display_map);
14417 start..end
14418 })
14419 .collect::<Vec<_>>();
14420
14421 self.unfold_ranges(&ranges, true, true, cx);
14422 }
14423
14424 pub fn unfold_at(
14425 &mut self,
14426 unfold_at: &UnfoldAt,
14427 _window: &mut Window,
14428 cx: &mut Context<Self>,
14429 ) {
14430 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14431
14432 let intersection_range = Point::new(unfold_at.buffer_row.0, 0)
14433 ..Point::new(
14434 unfold_at.buffer_row.0,
14435 display_map.buffer_snapshot.line_len(unfold_at.buffer_row),
14436 );
14437
14438 let autoscroll = self
14439 .selections
14440 .all::<Point>(cx)
14441 .iter()
14442 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
14443
14444 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
14445 }
14446
14447 pub fn unfold_all(
14448 &mut self,
14449 _: &actions::UnfoldAll,
14450 _window: &mut Window,
14451 cx: &mut Context<Self>,
14452 ) {
14453 if self.buffer.read(cx).is_singleton() {
14454 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14455 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
14456 } else {
14457 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
14458 editor
14459 .update(cx, |editor, cx| {
14460 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
14461 editor.unfold_buffer(buffer_id, cx);
14462 }
14463 })
14464 .ok();
14465 });
14466 }
14467 }
14468
14469 pub fn fold_selected_ranges(
14470 &mut self,
14471 _: &FoldSelectedRanges,
14472 window: &mut Window,
14473 cx: &mut Context<Self>,
14474 ) {
14475 let selections = self.selections.all::<Point>(cx);
14476 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14477 let line_mode = self.selections.line_mode;
14478 let ranges = selections
14479 .into_iter()
14480 .map(|s| {
14481 if line_mode {
14482 let start = Point::new(s.start.row, 0);
14483 let end = Point::new(
14484 s.end.row,
14485 display_map
14486 .buffer_snapshot
14487 .line_len(MultiBufferRow(s.end.row)),
14488 );
14489 Crease::simple(start..end, display_map.fold_placeholder.clone())
14490 } else {
14491 Crease::simple(s.start..s.end, display_map.fold_placeholder.clone())
14492 }
14493 })
14494 .collect::<Vec<_>>();
14495 self.fold_creases(ranges, true, window, cx);
14496 }
14497
14498 pub fn fold_ranges<T: ToOffset + Clone>(
14499 &mut self,
14500 ranges: Vec<Range<T>>,
14501 auto_scroll: bool,
14502 window: &mut Window,
14503 cx: &mut Context<Self>,
14504 ) {
14505 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14506 let ranges = ranges
14507 .into_iter()
14508 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
14509 .collect::<Vec<_>>();
14510 self.fold_creases(ranges, auto_scroll, window, cx);
14511 }
14512
14513 pub fn fold_creases<T: ToOffset + Clone>(
14514 &mut self,
14515 creases: Vec<Crease<T>>,
14516 auto_scroll: bool,
14517 window: &mut Window,
14518 cx: &mut Context<Self>,
14519 ) {
14520 if creases.is_empty() {
14521 return;
14522 }
14523
14524 let mut buffers_affected = HashSet::default();
14525 let multi_buffer = self.buffer().read(cx);
14526 for crease in &creases {
14527 if let Some((_, buffer, _)) =
14528 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
14529 {
14530 buffers_affected.insert(buffer.read(cx).remote_id());
14531 };
14532 }
14533
14534 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
14535
14536 if auto_scroll {
14537 self.request_autoscroll(Autoscroll::fit(), cx);
14538 }
14539
14540 cx.notify();
14541
14542 if let Some(active_diagnostics) = self.active_diagnostics.take() {
14543 // Clear diagnostics block when folding a range that contains it.
14544 let snapshot = self.snapshot(window, cx);
14545 if snapshot.intersects_fold(active_diagnostics.primary_range.start) {
14546 drop(snapshot);
14547 self.active_diagnostics = Some(active_diagnostics);
14548 self.dismiss_diagnostics(cx);
14549 } else {
14550 self.active_diagnostics = Some(active_diagnostics);
14551 }
14552 }
14553
14554 self.scrollbar_marker_state.dirty = true;
14555 self.folds_did_change(cx);
14556 }
14557
14558 /// Removes any folds whose ranges intersect any of the given ranges.
14559 pub fn unfold_ranges<T: ToOffset + Clone>(
14560 &mut self,
14561 ranges: &[Range<T>],
14562 inclusive: bool,
14563 auto_scroll: bool,
14564 cx: &mut Context<Self>,
14565 ) {
14566 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
14567 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
14568 });
14569 self.folds_did_change(cx);
14570 }
14571
14572 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
14573 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
14574 return;
14575 }
14576 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
14577 self.display_map.update(cx, |display_map, cx| {
14578 display_map.fold_buffers([buffer_id], cx)
14579 });
14580 cx.emit(EditorEvent::BufferFoldToggled {
14581 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
14582 folded: true,
14583 });
14584 cx.notify();
14585 }
14586
14587 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
14588 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
14589 return;
14590 }
14591 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
14592 self.display_map.update(cx, |display_map, cx| {
14593 display_map.unfold_buffers([buffer_id], cx);
14594 });
14595 cx.emit(EditorEvent::BufferFoldToggled {
14596 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
14597 folded: false,
14598 });
14599 cx.notify();
14600 }
14601
14602 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
14603 self.display_map.read(cx).is_buffer_folded(buffer)
14604 }
14605
14606 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
14607 self.display_map.read(cx).folded_buffers()
14608 }
14609
14610 /// Removes any folds with the given ranges.
14611 pub fn remove_folds_with_type<T: ToOffset + Clone>(
14612 &mut self,
14613 ranges: &[Range<T>],
14614 type_id: TypeId,
14615 auto_scroll: bool,
14616 cx: &mut Context<Self>,
14617 ) {
14618 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
14619 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
14620 });
14621 self.folds_did_change(cx);
14622 }
14623
14624 fn remove_folds_with<T: ToOffset + Clone>(
14625 &mut self,
14626 ranges: &[Range<T>],
14627 auto_scroll: bool,
14628 cx: &mut Context<Self>,
14629 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
14630 ) {
14631 if ranges.is_empty() {
14632 return;
14633 }
14634
14635 let mut buffers_affected = HashSet::default();
14636 let multi_buffer = self.buffer().read(cx);
14637 for range in ranges {
14638 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
14639 buffers_affected.insert(buffer.read(cx).remote_id());
14640 };
14641 }
14642
14643 self.display_map.update(cx, update);
14644
14645 if auto_scroll {
14646 self.request_autoscroll(Autoscroll::fit(), cx);
14647 }
14648
14649 cx.notify();
14650 self.scrollbar_marker_state.dirty = true;
14651 self.active_indent_guides_state.dirty = true;
14652 }
14653
14654 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
14655 self.display_map.read(cx).fold_placeholder.clone()
14656 }
14657
14658 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
14659 self.buffer.update(cx, |buffer, cx| {
14660 buffer.set_all_diff_hunks_expanded(cx);
14661 });
14662 }
14663
14664 pub fn expand_all_diff_hunks(
14665 &mut self,
14666 _: &ExpandAllDiffHunks,
14667 _window: &mut Window,
14668 cx: &mut Context<Self>,
14669 ) {
14670 self.buffer.update(cx, |buffer, cx| {
14671 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
14672 });
14673 }
14674
14675 pub fn toggle_selected_diff_hunks(
14676 &mut self,
14677 _: &ToggleSelectedDiffHunks,
14678 _window: &mut Window,
14679 cx: &mut Context<Self>,
14680 ) {
14681 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
14682 self.toggle_diff_hunks_in_ranges(ranges, cx);
14683 }
14684
14685 pub fn diff_hunks_in_ranges<'a>(
14686 &'a self,
14687 ranges: &'a [Range<Anchor>],
14688 buffer: &'a MultiBufferSnapshot,
14689 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
14690 ranges.iter().flat_map(move |range| {
14691 let end_excerpt_id = range.end.excerpt_id;
14692 let range = range.to_point(buffer);
14693 let mut peek_end = range.end;
14694 if range.end.row < buffer.max_row().0 {
14695 peek_end = Point::new(range.end.row + 1, 0);
14696 }
14697 buffer
14698 .diff_hunks_in_range(range.start..peek_end)
14699 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
14700 })
14701 }
14702
14703 pub fn has_stageable_diff_hunks_in_ranges(
14704 &self,
14705 ranges: &[Range<Anchor>],
14706 snapshot: &MultiBufferSnapshot,
14707 ) -> bool {
14708 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
14709 hunks.any(|hunk| hunk.status().has_secondary_hunk())
14710 }
14711
14712 pub fn toggle_staged_selected_diff_hunks(
14713 &mut self,
14714 _: &::git::ToggleStaged,
14715 _: &mut Window,
14716 cx: &mut Context<Self>,
14717 ) {
14718 let snapshot = self.buffer.read(cx).snapshot(cx);
14719 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
14720 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
14721 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
14722 }
14723
14724 pub fn stage_and_next(
14725 &mut self,
14726 _: &::git::StageAndNext,
14727 window: &mut Window,
14728 cx: &mut Context<Self>,
14729 ) {
14730 self.do_stage_or_unstage_and_next(true, window, cx);
14731 }
14732
14733 pub fn unstage_and_next(
14734 &mut self,
14735 _: &::git::UnstageAndNext,
14736 window: &mut Window,
14737 cx: &mut Context<Self>,
14738 ) {
14739 self.do_stage_or_unstage_and_next(false, window, cx);
14740 }
14741
14742 pub fn stage_or_unstage_diff_hunks(
14743 &mut self,
14744 stage: bool,
14745 ranges: Vec<Range<Anchor>>,
14746 cx: &mut Context<Self>,
14747 ) {
14748 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
14749 cx.spawn(async move |this, cx| {
14750 task.await?;
14751 this.update(cx, |this, cx| {
14752 let snapshot = this.buffer.read(cx).snapshot(cx);
14753 let chunk_by = this
14754 .diff_hunks_in_ranges(&ranges, &snapshot)
14755 .chunk_by(|hunk| hunk.buffer_id);
14756 for (buffer_id, hunks) in &chunk_by {
14757 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
14758 }
14759 })
14760 })
14761 .detach_and_log_err(cx);
14762 }
14763
14764 fn save_buffers_for_ranges_if_needed(
14765 &mut self,
14766 ranges: &[Range<Anchor>],
14767 cx: &mut Context<'_, Editor>,
14768 ) -> Task<Result<()>> {
14769 let multibuffer = self.buffer.read(cx);
14770 let snapshot = multibuffer.read(cx);
14771 let buffer_ids: HashSet<_> = ranges
14772 .iter()
14773 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
14774 .collect();
14775 drop(snapshot);
14776
14777 let mut buffers = HashSet::default();
14778 for buffer_id in buffer_ids {
14779 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
14780 let buffer = buffer_entity.read(cx);
14781 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
14782 {
14783 buffers.insert(buffer_entity);
14784 }
14785 }
14786 }
14787
14788 if let Some(project) = &self.project {
14789 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
14790 } else {
14791 Task::ready(Ok(()))
14792 }
14793 }
14794
14795 fn do_stage_or_unstage_and_next(
14796 &mut self,
14797 stage: bool,
14798 window: &mut Window,
14799 cx: &mut Context<Self>,
14800 ) {
14801 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
14802
14803 if ranges.iter().any(|range| range.start != range.end) {
14804 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
14805 return;
14806 }
14807
14808 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
14809 let snapshot = self.snapshot(window, cx);
14810 let position = self.selections.newest::<Point>(cx).head();
14811 let mut row = snapshot
14812 .buffer_snapshot
14813 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14814 .find(|hunk| hunk.row_range.start.0 > position.row)
14815 .map(|hunk| hunk.row_range.start);
14816
14817 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
14818 // Outside of the project diff editor, wrap around to the beginning.
14819 if !all_diff_hunks_expanded {
14820 row = row.or_else(|| {
14821 snapshot
14822 .buffer_snapshot
14823 .diff_hunks_in_range(Point::zero()..position)
14824 .find(|hunk| hunk.row_range.end.0 < position.row)
14825 .map(|hunk| hunk.row_range.start)
14826 });
14827 }
14828
14829 if let Some(row) = row {
14830 let destination = Point::new(row.0, 0);
14831 let autoscroll = Autoscroll::center();
14832
14833 self.unfold_ranges(&[destination..destination], false, false, cx);
14834 self.change_selections(Some(autoscroll), window, cx, |s| {
14835 s.select_ranges([destination..destination]);
14836 });
14837 }
14838 }
14839
14840 fn do_stage_or_unstage(
14841 &self,
14842 stage: bool,
14843 buffer_id: BufferId,
14844 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
14845 cx: &mut App,
14846 ) -> Option<()> {
14847 let project = self.project.as_ref()?;
14848 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
14849 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
14850 let buffer_snapshot = buffer.read(cx).snapshot();
14851 let file_exists = buffer_snapshot
14852 .file()
14853 .is_some_and(|file| file.disk_state().exists());
14854 diff.update(cx, |diff, cx| {
14855 diff.stage_or_unstage_hunks(
14856 stage,
14857 &hunks
14858 .map(|hunk| buffer_diff::DiffHunk {
14859 buffer_range: hunk.buffer_range,
14860 diff_base_byte_range: hunk.diff_base_byte_range,
14861 secondary_status: hunk.secondary_status,
14862 range: Point::zero()..Point::zero(), // unused
14863 })
14864 .collect::<Vec<_>>(),
14865 &buffer_snapshot,
14866 file_exists,
14867 cx,
14868 )
14869 });
14870 None
14871 }
14872
14873 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
14874 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
14875 self.buffer
14876 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
14877 }
14878
14879 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
14880 self.buffer.update(cx, |buffer, cx| {
14881 let ranges = vec![Anchor::min()..Anchor::max()];
14882 if !buffer.all_diff_hunks_expanded()
14883 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
14884 {
14885 buffer.collapse_diff_hunks(ranges, cx);
14886 true
14887 } else {
14888 false
14889 }
14890 })
14891 }
14892
14893 fn toggle_diff_hunks_in_ranges(
14894 &mut self,
14895 ranges: Vec<Range<Anchor>>,
14896 cx: &mut Context<'_, Editor>,
14897 ) {
14898 self.buffer.update(cx, |buffer, cx| {
14899 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
14900 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
14901 })
14902 }
14903
14904 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
14905 self.buffer.update(cx, |buffer, cx| {
14906 let snapshot = buffer.snapshot(cx);
14907 let excerpt_id = range.end.excerpt_id;
14908 let point_range = range.to_point(&snapshot);
14909 let expand = !buffer.single_hunk_is_expanded(range, cx);
14910 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
14911 })
14912 }
14913
14914 pub(crate) fn apply_all_diff_hunks(
14915 &mut self,
14916 _: &ApplyAllDiffHunks,
14917 window: &mut Window,
14918 cx: &mut Context<Self>,
14919 ) {
14920 let buffers = self.buffer.read(cx).all_buffers();
14921 for branch_buffer in buffers {
14922 branch_buffer.update(cx, |branch_buffer, cx| {
14923 branch_buffer.merge_into_base(Vec::new(), cx);
14924 });
14925 }
14926
14927 if let Some(project) = self.project.clone() {
14928 self.save(true, project, window, cx).detach_and_log_err(cx);
14929 }
14930 }
14931
14932 pub(crate) fn apply_selected_diff_hunks(
14933 &mut self,
14934 _: &ApplyDiffHunk,
14935 window: &mut Window,
14936 cx: &mut Context<Self>,
14937 ) {
14938 let snapshot = self.snapshot(window, cx);
14939 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
14940 let mut ranges_by_buffer = HashMap::default();
14941 self.transact(window, cx, |editor, _window, cx| {
14942 for hunk in hunks {
14943 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
14944 ranges_by_buffer
14945 .entry(buffer.clone())
14946 .or_insert_with(Vec::new)
14947 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
14948 }
14949 }
14950
14951 for (buffer, ranges) in ranges_by_buffer {
14952 buffer.update(cx, |buffer, cx| {
14953 buffer.merge_into_base(ranges, cx);
14954 });
14955 }
14956 });
14957
14958 if let Some(project) = self.project.clone() {
14959 self.save(true, project, window, cx).detach_and_log_err(cx);
14960 }
14961 }
14962
14963 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
14964 if hovered != self.gutter_hovered {
14965 self.gutter_hovered = hovered;
14966 cx.notify();
14967 }
14968 }
14969
14970 pub fn insert_blocks(
14971 &mut self,
14972 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
14973 autoscroll: Option<Autoscroll>,
14974 cx: &mut Context<Self>,
14975 ) -> Vec<CustomBlockId> {
14976 let blocks = self
14977 .display_map
14978 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
14979 if let Some(autoscroll) = autoscroll {
14980 self.request_autoscroll(autoscroll, cx);
14981 }
14982 cx.notify();
14983 blocks
14984 }
14985
14986 pub fn resize_blocks(
14987 &mut self,
14988 heights: HashMap<CustomBlockId, u32>,
14989 autoscroll: Option<Autoscroll>,
14990 cx: &mut Context<Self>,
14991 ) {
14992 self.display_map
14993 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
14994 if let Some(autoscroll) = autoscroll {
14995 self.request_autoscroll(autoscroll, cx);
14996 }
14997 cx.notify();
14998 }
14999
15000 pub fn replace_blocks(
15001 &mut self,
15002 renderers: HashMap<CustomBlockId, RenderBlock>,
15003 autoscroll: Option<Autoscroll>,
15004 cx: &mut Context<Self>,
15005 ) {
15006 self.display_map
15007 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
15008 if let Some(autoscroll) = autoscroll {
15009 self.request_autoscroll(autoscroll, cx);
15010 }
15011 cx.notify();
15012 }
15013
15014 pub fn remove_blocks(
15015 &mut self,
15016 block_ids: HashSet<CustomBlockId>,
15017 autoscroll: Option<Autoscroll>,
15018 cx: &mut Context<Self>,
15019 ) {
15020 self.display_map.update(cx, |display_map, cx| {
15021 display_map.remove_blocks(block_ids, cx)
15022 });
15023 if let Some(autoscroll) = autoscroll {
15024 self.request_autoscroll(autoscroll, cx);
15025 }
15026 cx.notify();
15027 }
15028
15029 pub fn row_for_block(
15030 &self,
15031 block_id: CustomBlockId,
15032 cx: &mut Context<Self>,
15033 ) -> Option<DisplayRow> {
15034 self.display_map
15035 .update(cx, |map, cx| map.row_for_block(block_id, cx))
15036 }
15037
15038 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
15039 self.focused_block = Some(focused_block);
15040 }
15041
15042 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
15043 self.focused_block.take()
15044 }
15045
15046 pub fn insert_creases(
15047 &mut self,
15048 creases: impl IntoIterator<Item = Crease<Anchor>>,
15049 cx: &mut Context<Self>,
15050 ) -> Vec<CreaseId> {
15051 self.display_map
15052 .update(cx, |map, cx| map.insert_creases(creases, cx))
15053 }
15054
15055 pub fn remove_creases(
15056 &mut self,
15057 ids: impl IntoIterator<Item = CreaseId>,
15058 cx: &mut Context<Self>,
15059 ) {
15060 self.display_map
15061 .update(cx, |map, cx| map.remove_creases(ids, cx));
15062 }
15063
15064 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
15065 self.display_map
15066 .update(cx, |map, cx| map.snapshot(cx))
15067 .longest_row()
15068 }
15069
15070 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
15071 self.display_map
15072 .update(cx, |map, cx| map.snapshot(cx))
15073 .max_point()
15074 }
15075
15076 pub fn text(&self, cx: &App) -> String {
15077 self.buffer.read(cx).read(cx).text()
15078 }
15079
15080 pub fn is_empty(&self, cx: &App) -> bool {
15081 self.buffer.read(cx).read(cx).is_empty()
15082 }
15083
15084 pub fn text_option(&self, cx: &App) -> Option<String> {
15085 let text = self.text(cx);
15086 let text = text.trim();
15087
15088 if text.is_empty() {
15089 return None;
15090 }
15091
15092 Some(text.to_string())
15093 }
15094
15095 pub fn set_text(
15096 &mut self,
15097 text: impl Into<Arc<str>>,
15098 window: &mut Window,
15099 cx: &mut Context<Self>,
15100 ) {
15101 self.transact(window, cx, |this, _, cx| {
15102 this.buffer
15103 .read(cx)
15104 .as_singleton()
15105 .expect("you can only call set_text on editors for singleton buffers")
15106 .update(cx, |buffer, cx| buffer.set_text(text, cx));
15107 });
15108 }
15109
15110 pub fn display_text(&self, cx: &mut App) -> String {
15111 self.display_map
15112 .update(cx, |map, cx| map.snapshot(cx))
15113 .text()
15114 }
15115
15116 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
15117 let mut wrap_guides = smallvec::smallvec![];
15118
15119 if self.show_wrap_guides == Some(false) {
15120 return wrap_guides;
15121 }
15122
15123 let settings = self.buffer.read(cx).language_settings(cx);
15124 if settings.show_wrap_guides {
15125 match self.soft_wrap_mode(cx) {
15126 SoftWrap::Column(soft_wrap) => {
15127 wrap_guides.push((soft_wrap as usize, true));
15128 }
15129 SoftWrap::Bounded(soft_wrap) => {
15130 wrap_guides.push((soft_wrap as usize, true));
15131 }
15132 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
15133 }
15134 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
15135 }
15136
15137 wrap_guides
15138 }
15139
15140 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
15141 let settings = self.buffer.read(cx).language_settings(cx);
15142 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
15143 match mode {
15144 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
15145 SoftWrap::None
15146 }
15147 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
15148 language_settings::SoftWrap::PreferredLineLength => {
15149 SoftWrap::Column(settings.preferred_line_length)
15150 }
15151 language_settings::SoftWrap::Bounded => {
15152 SoftWrap::Bounded(settings.preferred_line_length)
15153 }
15154 }
15155 }
15156
15157 pub fn set_soft_wrap_mode(
15158 &mut self,
15159 mode: language_settings::SoftWrap,
15160
15161 cx: &mut Context<Self>,
15162 ) {
15163 self.soft_wrap_mode_override = Some(mode);
15164 cx.notify();
15165 }
15166
15167 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
15168 self.hard_wrap = hard_wrap;
15169 cx.notify();
15170 }
15171
15172 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
15173 self.text_style_refinement = Some(style);
15174 }
15175
15176 /// called by the Element so we know what style we were most recently rendered with.
15177 pub(crate) fn set_style(
15178 &mut self,
15179 style: EditorStyle,
15180 window: &mut Window,
15181 cx: &mut Context<Self>,
15182 ) {
15183 let rem_size = window.rem_size();
15184 self.display_map.update(cx, |map, cx| {
15185 map.set_font(
15186 style.text.font(),
15187 style.text.font_size.to_pixels(rem_size),
15188 cx,
15189 )
15190 });
15191 self.style = Some(style);
15192 }
15193
15194 pub fn style(&self) -> Option<&EditorStyle> {
15195 self.style.as_ref()
15196 }
15197
15198 // Called by the element. This method is not designed to be called outside of the editor
15199 // element's layout code because it does not notify when rewrapping is computed synchronously.
15200 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
15201 self.display_map
15202 .update(cx, |map, cx| map.set_wrap_width(width, cx))
15203 }
15204
15205 pub fn set_soft_wrap(&mut self) {
15206 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
15207 }
15208
15209 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
15210 if self.soft_wrap_mode_override.is_some() {
15211 self.soft_wrap_mode_override.take();
15212 } else {
15213 let soft_wrap = match self.soft_wrap_mode(cx) {
15214 SoftWrap::GitDiff => return,
15215 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
15216 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
15217 language_settings::SoftWrap::None
15218 }
15219 };
15220 self.soft_wrap_mode_override = Some(soft_wrap);
15221 }
15222 cx.notify();
15223 }
15224
15225 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
15226 let Some(workspace) = self.workspace() else {
15227 return;
15228 };
15229 let fs = workspace.read(cx).app_state().fs.clone();
15230 let current_show = TabBarSettings::get_global(cx).show;
15231 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
15232 setting.show = Some(!current_show);
15233 });
15234 }
15235
15236 pub fn toggle_indent_guides(
15237 &mut self,
15238 _: &ToggleIndentGuides,
15239 _: &mut Window,
15240 cx: &mut Context<Self>,
15241 ) {
15242 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
15243 self.buffer
15244 .read(cx)
15245 .language_settings(cx)
15246 .indent_guides
15247 .enabled
15248 });
15249 self.show_indent_guides = Some(!currently_enabled);
15250 cx.notify();
15251 }
15252
15253 fn should_show_indent_guides(&self) -> Option<bool> {
15254 self.show_indent_guides
15255 }
15256
15257 pub fn toggle_line_numbers(
15258 &mut self,
15259 _: &ToggleLineNumbers,
15260 _: &mut Window,
15261 cx: &mut Context<Self>,
15262 ) {
15263 let mut editor_settings = EditorSettings::get_global(cx).clone();
15264 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
15265 EditorSettings::override_global(editor_settings, cx);
15266 }
15267
15268 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
15269 if let Some(show_line_numbers) = self.show_line_numbers {
15270 return show_line_numbers;
15271 }
15272 EditorSettings::get_global(cx).gutter.line_numbers
15273 }
15274
15275 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
15276 self.use_relative_line_numbers
15277 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
15278 }
15279
15280 pub fn toggle_relative_line_numbers(
15281 &mut self,
15282 _: &ToggleRelativeLineNumbers,
15283 _: &mut Window,
15284 cx: &mut Context<Self>,
15285 ) {
15286 let is_relative = self.should_use_relative_line_numbers(cx);
15287 self.set_relative_line_number(Some(!is_relative), cx)
15288 }
15289
15290 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
15291 self.use_relative_line_numbers = is_relative;
15292 cx.notify();
15293 }
15294
15295 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
15296 self.show_gutter = show_gutter;
15297 cx.notify();
15298 }
15299
15300 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
15301 self.show_scrollbars = show_scrollbars;
15302 cx.notify();
15303 }
15304
15305 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
15306 self.show_line_numbers = Some(show_line_numbers);
15307 cx.notify();
15308 }
15309
15310 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
15311 self.show_git_diff_gutter = Some(show_git_diff_gutter);
15312 cx.notify();
15313 }
15314
15315 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
15316 self.show_code_actions = Some(show_code_actions);
15317 cx.notify();
15318 }
15319
15320 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
15321 self.show_runnables = Some(show_runnables);
15322 cx.notify();
15323 }
15324
15325 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
15326 self.show_breakpoints = Some(show_breakpoints);
15327 cx.notify();
15328 }
15329
15330 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
15331 if self.display_map.read(cx).masked != masked {
15332 self.display_map.update(cx, |map, _| map.masked = masked);
15333 }
15334 cx.notify()
15335 }
15336
15337 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
15338 self.show_wrap_guides = Some(show_wrap_guides);
15339 cx.notify();
15340 }
15341
15342 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
15343 self.show_indent_guides = Some(show_indent_guides);
15344 cx.notify();
15345 }
15346
15347 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
15348 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
15349 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
15350 if let Some(dir) = file.abs_path(cx).parent() {
15351 return Some(dir.to_owned());
15352 }
15353 }
15354
15355 if let Some(project_path) = buffer.read(cx).project_path(cx) {
15356 return Some(project_path.path.to_path_buf());
15357 }
15358 }
15359
15360 None
15361 }
15362
15363 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
15364 self.active_excerpt(cx)?
15365 .1
15366 .read(cx)
15367 .file()
15368 .and_then(|f| f.as_local())
15369 }
15370
15371 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15372 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15373 let buffer = buffer.read(cx);
15374 if let Some(project_path) = buffer.project_path(cx) {
15375 let project = self.project.as_ref()?.read(cx);
15376 project.absolute_path(&project_path, cx)
15377 } else {
15378 buffer
15379 .file()
15380 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
15381 }
15382 })
15383 }
15384
15385 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15386 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15387 let project_path = buffer.read(cx).project_path(cx)?;
15388 let project = self.project.as_ref()?.read(cx);
15389 let entry = project.entry_for_path(&project_path, cx)?;
15390 let path = entry.path.to_path_buf();
15391 Some(path)
15392 })
15393 }
15394
15395 pub fn reveal_in_finder(
15396 &mut self,
15397 _: &RevealInFileManager,
15398 _window: &mut Window,
15399 cx: &mut Context<Self>,
15400 ) {
15401 if let Some(target) = self.target_file(cx) {
15402 cx.reveal_path(&target.abs_path(cx));
15403 }
15404 }
15405
15406 pub fn copy_path(
15407 &mut self,
15408 _: &zed_actions::workspace::CopyPath,
15409 _window: &mut Window,
15410 cx: &mut Context<Self>,
15411 ) {
15412 if let Some(path) = self.target_file_abs_path(cx) {
15413 if let Some(path) = path.to_str() {
15414 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15415 }
15416 }
15417 }
15418
15419 pub fn copy_relative_path(
15420 &mut self,
15421 _: &zed_actions::workspace::CopyRelativePath,
15422 _window: &mut Window,
15423 cx: &mut Context<Self>,
15424 ) {
15425 if let Some(path) = self.target_file_path(cx) {
15426 if let Some(path) = path.to_str() {
15427 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15428 }
15429 }
15430 }
15431
15432 pub fn project_path(&self, cx: &mut Context<Self>) -> Option<ProjectPath> {
15433 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
15434 buffer.read_with(cx, |buffer, cx| buffer.project_path(cx))
15435 } else {
15436 None
15437 }
15438 }
15439
15440 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) {
15441 let _ = maybe!({
15442 let breakpoint_store = self.breakpoint_store.as_ref()?;
15443
15444 let Some((_, _, active_position)) =
15445 breakpoint_store.read(cx).active_position().cloned()
15446 else {
15447 self.clear_row_highlights::<DebugCurrentRowHighlight>();
15448 return None;
15449 };
15450
15451 let snapshot = self
15452 .project
15453 .as_ref()?
15454 .read(cx)
15455 .buffer_for_id(active_position.buffer_id?, cx)?
15456 .read(cx)
15457 .snapshot();
15458
15459 for (id, ExcerptRange { context, .. }) in self
15460 .buffer
15461 .read(cx)
15462 .excerpts_for_buffer(active_position.buffer_id?, cx)
15463 {
15464 if context.start.cmp(&active_position, &snapshot).is_ge()
15465 || context.end.cmp(&active_position, &snapshot).is_lt()
15466 {
15467 continue;
15468 }
15469 let snapshot = self.buffer.read(cx).snapshot(cx);
15470 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, active_position)?;
15471
15472 self.clear_row_highlights::<DebugCurrentRowHighlight>();
15473 self.go_to_line::<DebugCurrentRowHighlight>(
15474 multibuffer_anchor,
15475 Some(cx.theme().colors().editor_debugger_active_line_background),
15476 window,
15477 cx,
15478 );
15479
15480 cx.notify();
15481 }
15482
15483 Some(())
15484 });
15485 }
15486
15487 pub fn copy_file_name_without_extension(
15488 &mut self,
15489 _: &CopyFileNameWithoutExtension,
15490 _: &mut Window,
15491 cx: &mut Context<Self>,
15492 ) {
15493 if let Some(file) = self.target_file(cx) {
15494 if let Some(file_stem) = file.path().file_stem() {
15495 if let Some(name) = file_stem.to_str() {
15496 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
15497 }
15498 }
15499 }
15500 }
15501
15502 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
15503 if let Some(file) = self.target_file(cx) {
15504 if let Some(file_name) = file.path().file_name() {
15505 if let Some(name) = file_name.to_str() {
15506 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
15507 }
15508 }
15509 }
15510 }
15511
15512 pub fn toggle_git_blame(
15513 &mut self,
15514 _: &::git::Blame,
15515 window: &mut Window,
15516 cx: &mut Context<Self>,
15517 ) {
15518 self.show_git_blame_gutter = !self.show_git_blame_gutter;
15519
15520 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
15521 self.start_git_blame(true, window, cx);
15522 }
15523
15524 cx.notify();
15525 }
15526
15527 pub fn toggle_git_blame_inline(
15528 &mut self,
15529 _: &ToggleGitBlameInline,
15530 window: &mut Window,
15531 cx: &mut Context<Self>,
15532 ) {
15533 self.toggle_git_blame_inline_internal(true, window, cx);
15534 cx.notify();
15535 }
15536
15537 pub fn git_blame_inline_enabled(&self) -> bool {
15538 self.git_blame_inline_enabled
15539 }
15540
15541 pub fn toggle_selection_menu(
15542 &mut self,
15543 _: &ToggleSelectionMenu,
15544 _: &mut Window,
15545 cx: &mut Context<Self>,
15546 ) {
15547 self.show_selection_menu = self
15548 .show_selection_menu
15549 .map(|show_selections_menu| !show_selections_menu)
15550 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
15551
15552 cx.notify();
15553 }
15554
15555 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
15556 self.show_selection_menu
15557 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
15558 }
15559
15560 fn start_git_blame(
15561 &mut self,
15562 user_triggered: bool,
15563 window: &mut Window,
15564 cx: &mut Context<Self>,
15565 ) {
15566 if let Some(project) = self.project.as_ref() {
15567 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
15568 return;
15569 };
15570
15571 if buffer.read(cx).file().is_none() {
15572 return;
15573 }
15574
15575 let focused = self.focus_handle(cx).contains_focused(window, cx);
15576
15577 let project = project.clone();
15578 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
15579 self.blame_subscription =
15580 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
15581 self.blame = Some(blame);
15582 }
15583 }
15584
15585 fn toggle_git_blame_inline_internal(
15586 &mut self,
15587 user_triggered: bool,
15588 window: &mut Window,
15589 cx: &mut Context<Self>,
15590 ) {
15591 if self.git_blame_inline_enabled {
15592 self.git_blame_inline_enabled = false;
15593 self.show_git_blame_inline = false;
15594 self.show_git_blame_inline_delay_task.take();
15595 } else {
15596 self.git_blame_inline_enabled = true;
15597 self.start_git_blame_inline(user_triggered, window, cx);
15598 }
15599
15600 cx.notify();
15601 }
15602
15603 fn start_git_blame_inline(
15604 &mut self,
15605 user_triggered: bool,
15606 window: &mut Window,
15607 cx: &mut Context<Self>,
15608 ) {
15609 self.start_git_blame(user_triggered, window, cx);
15610
15611 if ProjectSettings::get_global(cx)
15612 .git
15613 .inline_blame_delay()
15614 .is_some()
15615 {
15616 self.start_inline_blame_timer(window, cx);
15617 } else {
15618 self.show_git_blame_inline = true
15619 }
15620 }
15621
15622 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
15623 self.blame.as_ref()
15624 }
15625
15626 pub fn show_git_blame_gutter(&self) -> bool {
15627 self.show_git_blame_gutter
15628 }
15629
15630 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
15631 self.show_git_blame_gutter && self.has_blame_entries(cx)
15632 }
15633
15634 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
15635 self.show_git_blame_inline
15636 && (self.focus_handle.is_focused(window)
15637 || self
15638 .git_blame_inline_tooltip
15639 .as_ref()
15640 .and_then(|t| t.upgrade())
15641 .is_some())
15642 && !self.newest_selection_head_on_empty_line(cx)
15643 && self.has_blame_entries(cx)
15644 }
15645
15646 fn has_blame_entries(&self, cx: &App) -> bool {
15647 self.blame()
15648 .map_or(false, |blame| blame.read(cx).has_generated_entries())
15649 }
15650
15651 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
15652 let cursor_anchor = self.selections.newest_anchor().head();
15653
15654 let snapshot = self.buffer.read(cx).snapshot(cx);
15655 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
15656
15657 snapshot.line_len(buffer_row) == 0
15658 }
15659
15660 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
15661 let buffer_and_selection = maybe!({
15662 let selection = self.selections.newest::<Point>(cx);
15663 let selection_range = selection.range();
15664
15665 let multi_buffer = self.buffer().read(cx);
15666 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15667 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
15668
15669 let (buffer, range, _) = if selection.reversed {
15670 buffer_ranges.first()
15671 } else {
15672 buffer_ranges.last()
15673 }?;
15674
15675 let selection = text::ToPoint::to_point(&range.start, &buffer).row
15676 ..text::ToPoint::to_point(&range.end, &buffer).row;
15677 Some((
15678 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
15679 selection,
15680 ))
15681 });
15682
15683 let Some((buffer, selection)) = buffer_and_selection else {
15684 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
15685 };
15686
15687 let Some(project) = self.project.as_ref() else {
15688 return Task::ready(Err(anyhow!("editor does not have project")));
15689 };
15690
15691 project.update(cx, |project, cx| {
15692 project.get_permalink_to_line(&buffer, selection, cx)
15693 })
15694 }
15695
15696 pub fn copy_permalink_to_line(
15697 &mut self,
15698 _: &CopyPermalinkToLine,
15699 window: &mut Window,
15700 cx: &mut Context<Self>,
15701 ) {
15702 let permalink_task = self.get_permalink_to_line(cx);
15703 let workspace = self.workspace();
15704
15705 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
15706 Ok(permalink) => {
15707 cx.update(|_, cx| {
15708 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
15709 })
15710 .ok();
15711 }
15712 Err(err) => {
15713 let message = format!("Failed to copy permalink: {err}");
15714
15715 Err::<(), anyhow::Error>(err).log_err();
15716
15717 if let Some(workspace) = workspace {
15718 workspace
15719 .update_in(cx, |workspace, _, cx| {
15720 struct CopyPermalinkToLine;
15721
15722 workspace.show_toast(
15723 Toast::new(
15724 NotificationId::unique::<CopyPermalinkToLine>(),
15725 message,
15726 ),
15727 cx,
15728 )
15729 })
15730 .ok();
15731 }
15732 }
15733 })
15734 .detach();
15735 }
15736
15737 pub fn copy_file_location(
15738 &mut self,
15739 _: &CopyFileLocation,
15740 _: &mut Window,
15741 cx: &mut Context<Self>,
15742 ) {
15743 let selection = self.selections.newest::<Point>(cx).start.row + 1;
15744 if let Some(file) = self.target_file(cx) {
15745 if let Some(path) = file.path().to_str() {
15746 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
15747 }
15748 }
15749 }
15750
15751 pub fn open_permalink_to_line(
15752 &mut self,
15753 _: &OpenPermalinkToLine,
15754 window: &mut Window,
15755 cx: &mut Context<Self>,
15756 ) {
15757 let permalink_task = self.get_permalink_to_line(cx);
15758 let workspace = self.workspace();
15759
15760 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
15761 Ok(permalink) => {
15762 cx.update(|_, cx| {
15763 cx.open_url(permalink.as_ref());
15764 })
15765 .ok();
15766 }
15767 Err(err) => {
15768 let message = format!("Failed to open permalink: {err}");
15769
15770 Err::<(), anyhow::Error>(err).log_err();
15771
15772 if let Some(workspace) = workspace {
15773 workspace
15774 .update(cx, |workspace, cx| {
15775 struct OpenPermalinkToLine;
15776
15777 workspace.show_toast(
15778 Toast::new(
15779 NotificationId::unique::<OpenPermalinkToLine>(),
15780 message,
15781 ),
15782 cx,
15783 )
15784 })
15785 .ok();
15786 }
15787 }
15788 })
15789 .detach();
15790 }
15791
15792 pub fn insert_uuid_v4(
15793 &mut self,
15794 _: &InsertUuidV4,
15795 window: &mut Window,
15796 cx: &mut Context<Self>,
15797 ) {
15798 self.insert_uuid(UuidVersion::V4, window, cx);
15799 }
15800
15801 pub fn insert_uuid_v7(
15802 &mut self,
15803 _: &InsertUuidV7,
15804 window: &mut Window,
15805 cx: &mut Context<Self>,
15806 ) {
15807 self.insert_uuid(UuidVersion::V7, window, cx);
15808 }
15809
15810 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
15811 self.transact(window, cx, |this, window, cx| {
15812 let edits = this
15813 .selections
15814 .all::<Point>(cx)
15815 .into_iter()
15816 .map(|selection| {
15817 let uuid = match version {
15818 UuidVersion::V4 => uuid::Uuid::new_v4(),
15819 UuidVersion::V7 => uuid::Uuid::now_v7(),
15820 };
15821
15822 (selection.range(), uuid.to_string())
15823 });
15824 this.edit(edits, cx);
15825 this.refresh_inline_completion(true, false, window, cx);
15826 });
15827 }
15828
15829 pub fn open_selections_in_multibuffer(
15830 &mut self,
15831 _: &OpenSelectionsInMultibuffer,
15832 window: &mut Window,
15833 cx: &mut Context<Self>,
15834 ) {
15835 let multibuffer = self.buffer.read(cx);
15836
15837 let Some(buffer) = multibuffer.as_singleton() else {
15838 return;
15839 };
15840
15841 let Some(workspace) = self.workspace() else {
15842 return;
15843 };
15844
15845 let locations = self
15846 .selections
15847 .disjoint_anchors()
15848 .iter()
15849 .map(|range| Location {
15850 buffer: buffer.clone(),
15851 range: range.start.text_anchor..range.end.text_anchor,
15852 })
15853 .collect::<Vec<_>>();
15854
15855 let title = multibuffer.title(cx).to_string();
15856
15857 cx.spawn_in(window, async move |_, cx| {
15858 workspace.update_in(cx, |workspace, window, cx| {
15859 Self::open_locations_in_multibuffer(
15860 workspace,
15861 locations,
15862 format!("Selections for '{title}'"),
15863 false,
15864 MultibufferSelectionMode::All,
15865 window,
15866 cx,
15867 );
15868 })
15869 })
15870 .detach();
15871 }
15872
15873 /// Adds a row highlight for the given range. If a row has multiple highlights, the
15874 /// last highlight added will be used.
15875 ///
15876 /// If the range ends at the beginning of a line, then that line will not be highlighted.
15877 pub fn highlight_rows<T: 'static>(
15878 &mut self,
15879 range: Range<Anchor>,
15880 color: Hsla,
15881 should_autoscroll: bool,
15882 cx: &mut Context<Self>,
15883 ) {
15884 let snapshot = self.buffer().read(cx).snapshot(cx);
15885 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
15886 let ix = row_highlights.binary_search_by(|highlight| {
15887 Ordering::Equal
15888 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
15889 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
15890 });
15891
15892 if let Err(mut ix) = ix {
15893 let index = post_inc(&mut self.highlight_order);
15894
15895 // If this range intersects with the preceding highlight, then merge it with
15896 // the preceding highlight. Otherwise insert a new highlight.
15897 let mut merged = false;
15898 if ix > 0 {
15899 let prev_highlight = &mut row_highlights[ix - 1];
15900 if prev_highlight
15901 .range
15902 .end
15903 .cmp(&range.start, &snapshot)
15904 .is_ge()
15905 {
15906 ix -= 1;
15907 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
15908 prev_highlight.range.end = range.end;
15909 }
15910 merged = true;
15911 prev_highlight.index = index;
15912 prev_highlight.color = color;
15913 prev_highlight.should_autoscroll = should_autoscroll;
15914 }
15915 }
15916
15917 if !merged {
15918 row_highlights.insert(
15919 ix,
15920 RowHighlight {
15921 range: range.clone(),
15922 index,
15923 color,
15924 should_autoscroll,
15925 },
15926 );
15927 }
15928
15929 // If any of the following highlights intersect with this one, merge them.
15930 while let Some(next_highlight) = row_highlights.get(ix + 1) {
15931 let highlight = &row_highlights[ix];
15932 if next_highlight
15933 .range
15934 .start
15935 .cmp(&highlight.range.end, &snapshot)
15936 .is_le()
15937 {
15938 if next_highlight
15939 .range
15940 .end
15941 .cmp(&highlight.range.end, &snapshot)
15942 .is_gt()
15943 {
15944 row_highlights[ix].range.end = next_highlight.range.end;
15945 }
15946 row_highlights.remove(ix + 1);
15947 } else {
15948 break;
15949 }
15950 }
15951 }
15952 }
15953
15954 /// Remove any highlighted row ranges of the given type that intersect the
15955 /// given ranges.
15956 pub fn remove_highlighted_rows<T: 'static>(
15957 &mut self,
15958 ranges_to_remove: Vec<Range<Anchor>>,
15959 cx: &mut Context<Self>,
15960 ) {
15961 let snapshot = self.buffer().read(cx).snapshot(cx);
15962 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
15963 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
15964 row_highlights.retain(|highlight| {
15965 while let Some(range_to_remove) = ranges_to_remove.peek() {
15966 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
15967 Ordering::Less | Ordering::Equal => {
15968 ranges_to_remove.next();
15969 }
15970 Ordering::Greater => {
15971 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
15972 Ordering::Less | Ordering::Equal => {
15973 return false;
15974 }
15975 Ordering::Greater => break,
15976 }
15977 }
15978 }
15979 }
15980
15981 true
15982 })
15983 }
15984
15985 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
15986 pub fn clear_row_highlights<T: 'static>(&mut self) {
15987 self.highlighted_rows.remove(&TypeId::of::<T>());
15988 }
15989
15990 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
15991 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
15992 self.highlighted_rows
15993 .get(&TypeId::of::<T>())
15994 .map_or(&[] as &[_], |vec| vec.as_slice())
15995 .iter()
15996 .map(|highlight| (highlight.range.clone(), highlight.color))
15997 }
15998
15999 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
16000 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
16001 /// Allows to ignore certain kinds of highlights.
16002 pub fn highlighted_display_rows(
16003 &self,
16004 window: &mut Window,
16005 cx: &mut App,
16006 ) -> BTreeMap<DisplayRow, LineHighlight> {
16007 let snapshot = self.snapshot(window, cx);
16008 let mut used_highlight_orders = HashMap::default();
16009 self.highlighted_rows
16010 .iter()
16011 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
16012 .fold(
16013 BTreeMap::<DisplayRow, LineHighlight>::new(),
16014 |mut unique_rows, highlight| {
16015 let start = highlight.range.start.to_display_point(&snapshot);
16016 let end = highlight.range.end.to_display_point(&snapshot);
16017 let start_row = start.row().0;
16018 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
16019 && end.column() == 0
16020 {
16021 end.row().0.saturating_sub(1)
16022 } else {
16023 end.row().0
16024 };
16025 for row in start_row..=end_row {
16026 let used_index =
16027 used_highlight_orders.entry(row).or_insert(highlight.index);
16028 if highlight.index >= *used_index {
16029 *used_index = highlight.index;
16030 unique_rows.insert(DisplayRow(row), highlight.color.into());
16031 }
16032 }
16033 unique_rows
16034 },
16035 )
16036 }
16037
16038 pub fn highlighted_display_row_for_autoscroll(
16039 &self,
16040 snapshot: &DisplaySnapshot,
16041 ) -> Option<DisplayRow> {
16042 self.highlighted_rows
16043 .values()
16044 .flat_map(|highlighted_rows| highlighted_rows.iter())
16045 .filter_map(|highlight| {
16046 if highlight.should_autoscroll {
16047 Some(highlight.range.start.to_display_point(snapshot).row())
16048 } else {
16049 None
16050 }
16051 })
16052 .min()
16053 }
16054
16055 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
16056 self.highlight_background::<SearchWithinRange>(
16057 ranges,
16058 |colors| colors.editor_document_highlight_read_background,
16059 cx,
16060 )
16061 }
16062
16063 pub fn set_breadcrumb_header(&mut self, new_header: String) {
16064 self.breadcrumb_header = Some(new_header);
16065 }
16066
16067 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
16068 self.clear_background_highlights::<SearchWithinRange>(cx);
16069 }
16070
16071 pub fn highlight_background<T: 'static>(
16072 &mut self,
16073 ranges: &[Range<Anchor>],
16074 color_fetcher: fn(&ThemeColors) -> Hsla,
16075 cx: &mut Context<Self>,
16076 ) {
16077 self.background_highlights
16078 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16079 self.scrollbar_marker_state.dirty = true;
16080 cx.notify();
16081 }
16082
16083 pub fn clear_background_highlights<T: 'static>(
16084 &mut self,
16085 cx: &mut Context<Self>,
16086 ) -> Option<BackgroundHighlight> {
16087 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
16088 if !text_highlights.1.is_empty() {
16089 self.scrollbar_marker_state.dirty = true;
16090 cx.notify();
16091 }
16092 Some(text_highlights)
16093 }
16094
16095 pub fn highlight_gutter<T: 'static>(
16096 &mut self,
16097 ranges: &[Range<Anchor>],
16098 color_fetcher: fn(&App) -> Hsla,
16099 cx: &mut Context<Self>,
16100 ) {
16101 self.gutter_highlights
16102 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16103 cx.notify();
16104 }
16105
16106 pub fn clear_gutter_highlights<T: 'static>(
16107 &mut self,
16108 cx: &mut Context<Self>,
16109 ) -> Option<GutterHighlight> {
16110 cx.notify();
16111 self.gutter_highlights.remove(&TypeId::of::<T>())
16112 }
16113
16114 #[cfg(feature = "test-support")]
16115 pub fn all_text_background_highlights(
16116 &self,
16117 window: &mut Window,
16118 cx: &mut Context<Self>,
16119 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16120 let snapshot = self.snapshot(window, cx);
16121 let buffer = &snapshot.buffer_snapshot;
16122 let start = buffer.anchor_before(0);
16123 let end = buffer.anchor_after(buffer.len());
16124 let theme = cx.theme().colors();
16125 self.background_highlights_in_range(start..end, &snapshot, theme)
16126 }
16127
16128 #[cfg(feature = "test-support")]
16129 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
16130 let snapshot = self.buffer().read(cx).snapshot(cx);
16131
16132 let highlights = self
16133 .background_highlights
16134 .get(&TypeId::of::<items::BufferSearchHighlights>());
16135
16136 if let Some((_color, ranges)) = highlights {
16137 ranges
16138 .iter()
16139 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
16140 .collect_vec()
16141 } else {
16142 vec![]
16143 }
16144 }
16145
16146 fn document_highlights_for_position<'a>(
16147 &'a self,
16148 position: Anchor,
16149 buffer: &'a MultiBufferSnapshot,
16150 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
16151 let read_highlights = self
16152 .background_highlights
16153 .get(&TypeId::of::<DocumentHighlightRead>())
16154 .map(|h| &h.1);
16155 let write_highlights = self
16156 .background_highlights
16157 .get(&TypeId::of::<DocumentHighlightWrite>())
16158 .map(|h| &h.1);
16159 let left_position = position.bias_left(buffer);
16160 let right_position = position.bias_right(buffer);
16161 read_highlights
16162 .into_iter()
16163 .chain(write_highlights)
16164 .flat_map(move |ranges| {
16165 let start_ix = match ranges.binary_search_by(|probe| {
16166 let cmp = probe.end.cmp(&left_position, buffer);
16167 if cmp.is_ge() {
16168 Ordering::Greater
16169 } else {
16170 Ordering::Less
16171 }
16172 }) {
16173 Ok(i) | Err(i) => i,
16174 };
16175
16176 ranges[start_ix..]
16177 .iter()
16178 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
16179 })
16180 }
16181
16182 pub fn has_background_highlights<T: 'static>(&self) -> bool {
16183 self.background_highlights
16184 .get(&TypeId::of::<T>())
16185 .map_or(false, |(_, highlights)| !highlights.is_empty())
16186 }
16187
16188 pub fn background_highlights_in_range(
16189 &self,
16190 search_range: Range<Anchor>,
16191 display_snapshot: &DisplaySnapshot,
16192 theme: &ThemeColors,
16193 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16194 let mut results = Vec::new();
16195 for (color_fetcher, ranges) in self.background_highlights.values() {
16196 let color = color_fetcher(theme);
16197 let start_ix = match ranges.binary_search_by(|probe| {
16198 let cmp = probe
16199 .end
16200 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16201 if cmp.is_gt() {
16202 Ordering::Greater
16203 } else {
16204 Ordering::Less
16205 }
16206 }) {
16207 Ok(i) | Err(i) => i,
16208 };
16209 for range in &ranges[start_ix..] {
16210 if range
16211 .start
16212 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16213 .is_ge()
16214 {
16215 break;
16216 }
16217
16218 let start = range.start.to_display_point(display_snapshot);
16219 let end = range.end.to_display_point(display_snapshot);
16220 results.push((start..end, color))
16221 }
16222 }
16223 results
16224 }
16225
16226 pub fn background_highlight_row_ranges<T: 'static>(
16227 &self,
16228 search_range: Range<Anchor>,
16229 display_snapshot: &DisplaySnapshot,
16230 count: usize,
16231 ) -> Vec<RangeInclusive<DisplayPoint>> {
16232 let mut results = Vec::new();
16233 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
16234 return vec![];
16235 };
16236
16237 let start_ix = match ranges.binary_search_by(|probe| {
16238 let cmp = probe
16239 .end
16240 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16241 if cmp.is_gt() {
16242 Ordering::Greater
16243 } else {
16244 Ordering::Less
16245 }
16246 }) {
16247 Ok(i) | Err(i) => i,
16248 };
16249 let mut push_region = |start: Option<Point>, end: Option<Point>| {
16250 if let (Some(start_display), Some(end_display)) = (start, end) {
16251 results.push(
16252 start_display.to_display_point(display_snapshot)
16253 ..=end_display.to_display_point(display_snapshot),
16254 );
16255 }
16256 };
16257 let mut start_row: Option<Point> = None;
16258 let mut end_row: Option<Point> = None;
16259 if ranges.len() > count {
16260 return Vec::new();
16261 }
16262 for range in &ranges[start_ix..] {
16263 if range
16264 .start
16265 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16266 .is_ge()
16267 {
16268 break;
16269 }
16270 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
16271 if let Some(current_row) = &end_row {
16272 if end.row == current_row.row {
16273 continue;
16274 }
16275 }
16276 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
16277 if start_row.is_none() {
16278 assert_eq!(end_row, None);
16279 start_row = Some(start);
16280 end_row = Some(end);
16281 continue;
16282 }
16283 if let Some(current_end) = end_row.as_mut() {
16284 if start.row > current_end.row + 1 {
16285 push_region(start_row, end_row);
16286 start_row = Some(start);
16287 end_row = Some(end);
16288 } else {
16289 // Merge two hunks.
16290 *current_end = end;
16291 }
16292 } else {
16293 unreachable!();
16294 }
16295 }
16296 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
16297 push_region(start_row, end_row);
16298 results
16299 }
16300
16301 pub fn gutter_highlights_in_range(
16302 &self,
16303 search_range: Range<Anchor>,
16304 display_snapshot: &DisplaySnapshot,
16305 cx: &App,
16306 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16307 let mut results = Vec::new();
16308 for (color_fetcher, ranges) in self.gutter_highlights.values() {
16309 let color = color_fetcher(cx);
16310 let start_ix = match ranges.binary_search_by(|probe| {
16311 let cmp = probe
16312 .end
16313 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16314 if cmp.is_gt() {
16315 Ordering::Greater
16316 } else {
16317 Ordering::Less
16318 }
16319 }) {
16320 Ok(i) | Err(i) => i,
16321 };
16322 for range in &ranges[start_ix..] {
16323 if range
16324 .start
16325 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16326 .is_ge()
16327 {
16328 break;
16329 }
16330
16331 let start = range.start.to_display_point(display_snapshot);
16332 let end = range.end.to_display_point(display_snapshot);
16333 results.push((start..end, color))
16334 }
16335 }
16336 results
16337 }
16338
16339 /// Get the text ranges corresponding to the redaction query
16340 pub fn redacted_ranges(
16341 &self,
16342 search_range: Range<Anchor>,
16343 display_snapshot: &DisplaySnapshot,
16344 cx: &App,
16345 ) -> Vec<Range<DisplayPoint>> {
16346 display_snapshot
16347 .buffer_snapshot
16348 .redacted_ranges(search_range, |file| {
16349 if let Some(file) = file {
16350 file.is_private()
16351 && EditorSettings::get(
16352 Some(SettingsLocation {
16353 worktree_id: file.worktree_id(cx),
16354 path: file.path().as_ref(),
16355 }),
16356 cx,
16357 )
16358 .redact_private_values
16359 } else {
16360 false
16361 }
16362 })
16363 .map(|range| {
16364 range.start.to_display_point(display_snapshot)
16365 ..range.end.to_display_point(display_snapshot)
16366 })
16367 .collect()
16368 }
16369
16370 pub fn highlight_text<T: 'static>(
16371 &mut self,
16372 ranges: Vec<Range<Anchor>>,
16373 style: HighlightStyle,
16374 cx: &mut Context<Self>,
16375 ) {
16376 self.display_map.update(cx, |map, _| {
16377 map.highlight_text(TypeId::of::<T>(), ranges, style)
16378 });
16379 cx.notify();
16380 }
16381
16382 pub(crate) fn highlight_inlays<T: 'static>(
16383 &mut self,
16384 highlights: Vec<InlayHighlight>,
16385 style: HighlightStyle,
16386 cx: &mut Context<Self>,
16387 ) {
16388 self.display_map.update(cx, |map, _| {
16389 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
16390 });
16391 cx.notify();
16392 }
16393
16394 pub fn text_highlights<'a, T: 'static>(
16395 &'a self,
16396 cx: &'a App,
16397 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
16398 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
16399 }
16400
16401 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
16402 let cleared = self
16403 .display_map
16404 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
16405 if cleared {
16406 cx.notify();
16407 }
16408 }
16409
16410 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
16411 (self.read_only(cx) || self.blink_manager.read(cx).visible())
16412 && self.focus_handle.is_focused(window)
16413 }
16414
16415 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
16416 self.show_cursor_when_unfocused = is_enabled;
16417 cx.notify();
16418 }
16419
16420 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
16421 cx.notify();
16422 }
16423
16424 fn on_buffer_event(
16425 &mut self,
16426 multibuffer: &Entity<MultiBuffer>,
16427 event: &multi_buffer::Event,
16428 window: &mut Window,
16429 cx: &mut Context<Self>,
16430 ) {
16431 match event {
16432 multi_buffer::Event::Edited {
16433 singleton_buffer_edited,
16434 edited_buffer: buffer_edited,
16435 } => {
16436 self.scrollbar_marker_state.dirty = true;
16437 self.active_indent_guides_state.dirty = true;
16438 self.refresh_active_diagnostics(cx);
16439 self.refresh_code_actions(window, cx);
16440 if self.has_active_inline_completion() {
16441 self.update_visible_inline_completion(window, cx);
16442 }
16443 if let Some(buffer) = buffer_edited {
16444 let buffer_id = buffer.read(cx).remote_id();
16445 if !self.registered_buffers.contains_key(&buffer_id) {
16446 if let Some(project) = self.project.as_ref() {
16447 project.update(cx, |project, cx| {
16448 self.registered_buffers.insert(
16449 buffer_id,
16450 project.register_buffer_with_language_servers(&buffer, cx),
16451 );
16452 })
16453 }
16454 }
16455 }
16456 cx.emit(EditorEvent::BufferEdited);
16457 cx.emit(SearchEvent::MatchesInvalidated);
16458 if *singleton_buffer_edited {
16459 if let Some(project) = &self.project {
16460 #[allow(clippy::mutable_key_type)]
16461 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
16462 multibuffer
16463 .all_buffers()
16464 .into_iter()
16465 .filter_map(|buffer| {
16466 buffer.update(cx, |buffer, cx| {
16467 let language = buffer.language()?;
16468 let should_discard = project.update(cx, |project, cx| {
16469 project.is_local()
16470 && !project.has_language_servers_for(buffer, cx)
16471 });
16472 should_discard.not().then_some(language.clone())
16473 })
16474 })
16475 .collect::<HashSet<_>>()
16476 });
16477 if !languages_affected.is_empty() {
16478 self.refresh_inlay_hints(
16479 InlayHintRefreshReason::BufferEdited(languages_affected),
16480 cx,
16481 );
16482 }
16483 }
16484 }
16485
16486 let Some(project) = &self.project else { return };
16487 let (telemetry, is_via_ssh) = {
16488 let project = project.read(cx);
16489 let telemetry = project.client().telemetry().clone();
16490 let is_via_ssh = project.is_via_ssh();
16491 (telemetry, is_via_ssh)
16492 };
16493 refresh_linked_ranges(self, window, cx);
16494 telemetry.log_edit_event("editor", is_via_ssh);
16495 }
16496 multi_buffer::Event::ExcerptsAdded {
16497 buffer,
16498 predecessor,
16499 excerpts,
16500 } => {
16501 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16502 let buffer_id = buffer.read(cx).remote_id();
16503 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
16504 if let Some(project) = &self.project {
16505 get_uncommitted_diff_for_buffer(
16506 project,
16507 [buffer.clone()],
16508 self.buffer.clone(),
16509 cx,
16510 )
16511 .detach();
16512 }
16513 }
16514 cx.emit(EditorEvent::ExcerptsAdded {
16515 buffer: buffer.clone(),
16516 predecessor: *predecessor,
16517 excerpts: excerpts.clone(),
16518 });
16519 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
16520 }
16521 multi_buffer::Event::ExcerptsRemoved { ids } => {
16522 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
16523 let buffer = self.buffer.read(cx);
16524 self.registered_buffers
16525 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
16526 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
16527 cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
16528 }
16529 multi_buffer::Event::ExcerptsEdited {
16530 excerpt_ids,
16531 buffer_ids,
16532 } => {
16533 self.display_map.update(cx, |map, cx| {
16534 map.unfold_buffers(buffer_ids.iter().copied(), cx)
16535 });
16536 cx.emit(EditorEvent::ExcerptsEdited {
16537 ids: excerpt_ids.clone(),
16538 })
16539 }
16540 multi_buffer::Event::ExcerptsExpanded { ids } => {
16541 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
16542 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
16543 }
16544 multi_buffer::Event::Reparsed(buffer_id) => {
16545 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16546 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
16547
16548 cx.emit(EditorEvent::Reparsed(*buffer_id));
16549 }
16550 multi_buffer::Event::DiffHunksToggled => {
16551 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16552 }
16553 multi_buffer::Event::LanguageChanged(buffer_id) => {
16554 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
16555 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
16556 cx.emit(EditorEvent::Reparsed(*buffer_id));
16557 cx.notify();
16558 }
16559 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
16560 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
16561 multi_buffer::Event::FileHandleChanged
16562 | multi_buffer::Event::Reloaded
16563 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
16564 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
16565 multi_buffer::Event::DiagnosticsUpdated => {
16566 self.refresh_active_diagnostics(cx);
16567 self.refresh_inline_diagnostics(true, window, cx);
16568 self.scrollbar_marker_state.dirty = true;
16569 cx.notify();
16570 }
16571 _ => {}
16572 };
16573 }
16574
16575 fn on_display_map_changed(
16576 &mut self,
16577 _: Entity<DisplayMap>,
16578 _: &mut Window,
16579 cx: &mut Context<Self>,
16580 ) {
16581 cx.notify();
16582 }
16583
16584 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16585 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16586 self.update_edit_prediction_settings(cx);
16587 self.refresh_inline_completion(true, false, window, cx);
16588 self.refresh_inlay_hints(
16589 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
16590 self.selections.newest_anchor().head(),
16591 &self.buffer.read(cx).snapshot(cx),
16592 cx,
16593 )),
16594 cx,
16595 );
16596
16597 let old_cursor_shape = self.cursor_shape;
16598
16599 {
16600 let editor_settings = EditorSettings::get_global(cx);
16601 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
16602 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
16603 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
16604 }
16605
16606 if old_cursor_shape != self.cursor_shape {
16607 cx.emit(EditorEvent::CursorShapeChanged);
16608 }
16609
16610 let project_settings = ProjectSettings::get_global(cx);
16611 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
16612
16613 if self.mode == EditorMode::Full {
16614 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
16615 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
16616 if self.show_inline_diagnostics != show_inline_diagnostics {
16617 self.show_inline_diagnostics = show_inline_diagnostics;
16618 self.refresh_inline_diagnostics(false, window, cx);
16619 }
16620
16621 if self.git_blame_inline_enabled != inline_blame_enabled {
16622 self.toggle_git_blame_inline_internal(false, window, cx);
16623 }
16624 }
16625
16626 cx.notify();
16627 }
16628
16629 pub fn set_searchable(&mut self, searchable: bool) {
16630 self.searchable = searchable;
16631 }
16632
16633 pub fn searchable(&self) -> bool {
16634 self.searchable
16635 }
16636
16637 fn open_proposed_changes_editor(
16638 &mut self,
16639 _: &OpenProposedChangesEditor,
16640 window: &mut Window,
16641 cx: &mut Context<Self>,
16642 ) {
16643 let Some(workspace) = self.workspace() else {
16644 cx.propagate();
16645 return;
16646 };
16647
16648 let selections = self.selections.all::<usize>(cx);
16649 let multi_buffer = self.buffer.read(cx);
16650 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16651 let mut new_selections_by_buffer = HashMap::default();
16652 for selection in selections {
16653 for (buffer, range, _) in
16654 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
16655 {
16656 let mut range = range.to_point(buffer);
16657 range.start.column = 0;
16658 range.end.column = buffer.line_len(range.end.row);
16659 new_selections_by_buffer
16660 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
16661 .or_insert(Vec::new())
16662 .push(range)
16663 }
16664 }
16665
16666 let proposed_changes_buffers = new_selections_by_buffer
16667 .into_iter()
16668 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
16669 .collect::<Vec<_>>();
16670 let proposed_changes_editor = cx.new(|cx| {
16671 ProposedChangesEditor::new(
16672 "Proposed changes",
16673 proposed_changes_buffers,
16674 self.project.clone(),
16675 window,
16676 cx,
16677 )
16678 });
16679
16680 window.defer(cx, move |window, cx| {
16681 workspace.update(cx, |workspace, cx| {
16682 workspace.active_pane().update(cx, |pane, cx| {
16683 pane.add_item(
16684 Box::new(proposed_changes_editor),
16685 true,
16686 true,
16687 None,
16688 window,
16689 cx,
16690 );
16691 });
16692 });
16693 });
16694 }
16695
16696 pub fn open_excerpts_in_split(
16697 &mut self,
16698 _: &OpenExcerptsSplit,
16699 window: &mut Window,
16700 cx: &mut Context<Self>,
16701 ) {
16702 self.open_excerpts_common(None, true, window, cx)
16703 }
16704
16705 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
16706 self.open_excerpts_common(None, false, window, cx)
16707 }
16708
16709 fn open_excerpts_common(
16710 &mut self,
16711 jump_data: Option<JumpData>,
16712 split: bool,
16713 window: &mut Window,
16714 cx: &mut Context<Self>,
16715 ) {
16716 let Some(workspace) = self.workspace() else {
16717 cx.propagate();
16718 return;
16719 };
16720
16721 if self.buffer.read(cx).is_singleton() {
16722 cx.propagate();
16723 return;
16724 }
16725
16726 let mut new_selections_by_buffer = HashMap::default();
16727 match &jump_data {
16728 Some(JumpData::MultiBufferPoint {
16729 excerpt_id,
16730 position,
16731 anchor,
16732 line_offset_from_top,
16733 }) => {
16734 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16735 if let Some(buffer) = multi_buffer_snapshot
16736 .buffer_id_for_excerpt(*excerpt_id)
16737 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
16738 {
16739 let buffer_snapshot = buffer.read(cx).snapshot();
16740 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
16741 language::ToPoint::to_point(anchor, &buffer_snapshot)
16742 } else {
16743 buffer_snapshot.clip_point(*position, Bias::Left)
16744 };
16745 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
16746 new_selections_by_buffer.insert(
16747 buffer,
16748 (
16749 vec![jump_to_offset..jump_to_offset],
16750 Some(*line_offset_from_top),
16751 ),
16752 );
16753 }
16754 }
16755 Some(JumpData::MultiBufferRow {
16756 row,
16757 line_offset_from_top,
16758 }) => {
16759 let point = MultiBufferPoint::new(row.0, 0);
16760 if let Some((buffer, buffer_point, _)) =
16761 self.buffer.read(cx).point_to_buffer_point(point, cx)
16762 {
16763 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
16764 new_selections_by_buffer
16765 .entry(buffer)
16766 .or_insert((Vec::new(), Some(*line_offset_from_top)))
16767 .0
16768 .push(buffer_offset..buffer_offset)
16769 }
16770 }
16771 None => {
16772 let selections = self.selections.all::<usize>(cx);
16773 let multi_buffer = self.buffer.read(cx);
16774 for selection in selections {
16775 for (snapshot, range, _, anchor) in multi_buffer
16776 .snapshot(cx)
16777 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
16778 {
16779 if let Some(anchor) = anchor {
16780 // selection is in a deleted hunk
16781 let Some(buffer_id) = anchor.buffer_id else {
16782 continue;
16783 };
16784 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
16785 continue;
16786 };
16787 let offset = text::ToOffset::to_offset(
16788 &anchor.text_anchor,
16789 &buffer_handle.read(cx).snapshot(),
16790 );
16791 let range = offset..offset;
16792 new_selections_by_buffer
16793 .entry(buffer_handle)
16794 .or_insert((Vec::new(), None))
16795 .0
16796 .push(range)
16797 } else {
16798 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
16799 else {
16800 continue;
16801 };
16802 new_selections_by_buffer
16803 .entry(buffer_handle)
16804 .or_insert((Vec::new(), None))
16805 .0
16806 .push(range)
16807 }
16808 }
16809 }
16810 }
16811 }
16812
16813 if new_selections_by_buffer.is_empty() {
16814 return;
16815 }
16816
16817 // We defer the pane interaction because we ourselves are a workspace item
16818 // and activating a new item causes the pane to call a method on us reentrantly,
16819 // which panics if we're on the stack.
16820 window.defer(cx, move |window, cx| {
16821 workspace.update(cx, |workspace, cx| {
16822 let pane = if split {
16823 workspace.adjacent_pane(window, cx)
16824 } else {
16825 workspace.active_pane().clone()
16826 };
16827
16828 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
16829 let editor = buffer
16830 .read(cx)
16831 .file()
16832 .is_none()
16833 .then(|| {
16834 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
16835 // so `workspace.open_project_item` will never find them, always opening a new editor.
16836 // Instead, we try to activate the existing editor in the pane first.
16837 let (editor, pane_item_index) =
16838 pane.read(cx).items().enumerate().find_map(|(i, item)| {
16839 let editor = item.downcast::<Editor>()?;
16840 let singleton_buffer =
16841 editor.read(cx).buffer().read(cx).as_singleton()?;
16842 if singleton_buffer == buffer {
16843 Some((editor, i))
16844 } else {
16845 None
16846 }
16847 })?;
16848 pane.update(cx, |pane, cx| {
16849 pane.activate_item(pane_item_index, true, true, window, cx)
16850 });
16851 Some(editor)
16852 })
16853 .flatten()
16854 .unwrap_or_else(|| {
16855 workspace.open_project_item::<Self>(
16856 pane.clone(),
16857 buffer,
16858 true,
16859 true,
16860 window,
16861 cx,
16862 )
16863 });
16864
16865 editor.update(cx, |editor, cx| {
16866 let autoscroll = match scroll_offset {
16867 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
16868 None => Autoscroll::newest(),
16869 };
16870 let nav_history = editor.nav_history.take();
16871 editor.change_selections(Some(autoscroll), window, cx, |s| {
16872 s.select_ranges(ranges);
16873 });
16874 editor.nav_history = nav_history;
16875 });
16876 }
16877 })
16878 });
16879 }
16880
16881 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
16882 let snapshot = self.buffer.read(cx).read(cx);
16883 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
16884 Some(
16885 ranges
16886 .iter()
16887 .map(move |range| {
16888 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
16889 })
16890 .collect(),
16891 )
16892 }
16893
16894 fn selection_replacement_ranges(
16895 &self,
16896 range: Range<OffsetUtf16>,
16897 cx: &mut App,
16898 ) -> Vec<Range<OffsetUtf16>> {
16899 let selections = self.selections.all::<OffsetUtf16>(cx);
16900 let newest_selection = selections
16901 .iter()
16902 .max_by_key(|selection| selection.id)
16903 .unwrap();
16904 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
16905 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
16906 let snapshot = self.buffer.read(cx).read(cx);
16907 selections
16908 .into_iter()
16909 .map(|mut selection| {
16910 selection.start.0 =
16911 (selection.start.0 as isize).saturating_add(start_delta) as usize;
16912 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
16913 snapshot.clip_offset_utf16(selection.start, Bias::Left)
16914 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
16915 })
16916 .collect()
16917 }
16918
16919 fn report_editor_event(
16920 &self,
16921 event_type: &'static str,
16922 file_extension: Option<String>,
16923 cx: &App,
16924 ) {
16925 if cfg!(any(test, feature = "test-support")) {
16926 return;
16927 }
16928
16929 let Some(project) = &self.project else { return };
16930
16931 // If None, we are in a file without an extension
16932 let file = self
16933 .buffer
16934 .read(cx)
16935 .as_singleton()
16936 .and_then(|b| b.read(cx).file());
16937 let file_extension = file_extension.or(file
16938 .as_ref()
16939 .and_then(|file| Path::new(file.file_name(cx)).extension())
16940 .and_then(|e| e.to_str())
16941 .map(|a| a.to_string()));
16942
16943 let vim_mode = cx
16944 .global::<SettingsStore>()
16945 .raw_user_settings()
16946 .get("vim_mode")
16947 == Some(&serde_json::Value::Bool(true));
16948
16949 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
16950 let copilot_enabled = edit_predictions_provider
16951 == language::language_settings::EditPredictionProvider::Copilot;
16952 let copilot_enabled_for_language = self
16953 .buffer
16954 .read(cx)
16955 .language_settings(cx)
16956 .show_edit_predictions;
16957
16958 let project = project.read(cx);
16959 telemetry::event!(
16960 event_type,
16961 file_extension,
16962 vim_mode,
16963 copilot_enabled,
16964 copilot_enabled_for_language,
16965 edit_predictions_provider,
16966 is_via_ssh = project.is_via_ssh(),
16967 );
16968 }
16969
16970 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
16971 /// with each line being an array of {text, highlight} objects.
16972 fn copy_highlight_json(
16973 &mut self,
16974 _: &CopyHighlightJson,
16975 window: &mut Window,
16976 cx: &mut Context<Self>,
16977 ) {
16978 #[derive(Serialize)]
16979 struct Chunk<'a> {
16980 text: String,
16981 highlight: Option<&'a str>,
16982 }
16983
16984 let snapshot = self.buffer.read(cx).snapshot(cx);
16985 let range = self
16986 .selected_text_range(false, window, cx)
16987 .and_then(|selection| {
16988 if selection.range.is_empty() {
16989 None
16990 } else {
16991 Some(selection.range)
16992 }
16993 })
16994 .unwrap_or_else(|| 0..snapshot.len());
16995
16996 let chunks = snapshot.chunks(range, true);
16997 let mut lines = Vec::new();
16998 let mut line: VecDeque<Chunk> = VecDeque::new();
16999
17000 let Some(style) = self.style.as_ref() else {
17001 return;
17002 };
17003
17004 for chunk in chunks {
17005 let highlight = chunk
17006 .syntax_highlight_id
17007 .and_then(|id| id.name(&style.syntax));
17008 let mut chunk_lines = chunk.text.split('\n').peekable();
17009 while let Some(text) = chunk_lines.next() {
17010 let mut merged_with_last_token = false;
17011 if let Some(last_token) = line.back_mut() {
17012 if last_token.highlight == highlight {
17013 last_token.text.push_str(text);
17014 merged_with_last_token = true;
17015 }
17016 }
17017
17018 if !merged_with_last_token {
17019 line.push_back(Chunk {
17020 text: text.into(),
17021 highlight,
17022 });
17023 }
17024
17025 if chunk_lines.peek().is_some() {
17026 if line.len() > 1 && line.front().unwrap().text.is_empty() {
17027 line.pop_front();
17028 }
17029 if line.len() > 1 && line.back().unwrap().text.is_empty() {
17030 line.pop_back();
17031 }
17032
17033 lines.push(mem::take(&mut line));
17034 }
17035 }
17036 }
17037
17038 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
17039 return;
17040 };
17041 cx.write_to_clipboard(ClipboardItem::new_string(lines));
17042 }
17043
17044 pub fn open_context_menu(
17045 &mut self,
17046 _: &OpenContextMenu,
17047 window: &mut Window,
17048 cx: &mut Context<Self>,
17049 ) {
17050 self.request_autoscroll(Autoscroll::newest(), cx);
17051 let position = self.selections.newest_display(cx).start;
17052 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
17053 }
17054
17055 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
17056 &self.inlay_hint_cache
17057 }
17058
17059 pub fn replay_insert_event(
17060 &mut self,
17061 text: &str,
17062 relative_utf16_range: Option<Range<isize>>,
17063 window: &mut Window,
17064 cx: &mut Context<Self>,
17065 ) {
17066 if !self.input_enabled {
17067 cx.emit(EditorEvent::InputIgnored { text: text.into() });
17068 return;
17069 }
17070 if let Some(relative_utf16_range) = relative_utf16_range {
17071 let selections = self.selections.all::<OffsetUtf16>(cx);
17072 self.change_selections(None, window, cx, |s| {
17073 let new_ranges = selections.into_iter().map(|range| {
17074 let start = OffsetUtf16(
17075 range
17076 .head()
17077 .0
17078 .saturating_add_signed(relative_utf16_range.start),
17079 );
17080 let end = OffsetUtf16(
17081 range
17082 .head()
17083 .0
17084 .saturating_add_signed(relative_utf16_range.end),
17085 );
17086 start..end
17087 });
17088 s.select_ranges(new_ranges);
17089 });
17090 }
17091
17092 self.handle_input(text, window, cx);
17093 }
17094
17095 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
17096 let Some(provider) = self.semantics_provider.as_ref() else {
17097 return false;
17098 };
17099
17100 let mut supports = false;
17101 self.buffer().update(cx, |this, cx| {
17102 this.for_each_buffer(|buffer| {
17103 supports |= provider.supports_inlay_hints(buffer, cx);
17104 });
17105 });
17106
17107 supports
17108 }
17109
17110 pub fn is_focused(&self, window: &Window) -> bool {
17111 self.focus_handle.is_focused(window)
17112 }
17113
17114 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17115 cx.emit(EditorEvent::Focused);
17116
17117 if let Some(descendant) = self
17118 .last_focused_descendant
17119 .take()
17120 .and_then(|descendant| descendant.upgrade())
17121 {
17122 window.focus(&descendant);
17123 } else {
17124 if let Some(blame) = self.blame.as_ref() {
17125 blame.update(cx, GitBlame::focus)
17126 }
17127
17128 self.blink_manager.update(cx, BlinkManager::enable);
17129 self.show_cursor_names(window, cx);
17130 self.buffer.update(cx, |buffer, cx| {
17131 buffer.finalize_last_transaction(cx);
17132 if self.leader_peer_id.is_none() {
17133 buffer.set_active_selections(
17134 &self.selections.disjoint_anchors(),
17135 self.selections.line_mode,
17136 self.cursor_shape,
17137 cx,
17138 );
17139 }
17140 });
17141 }
17142 }
17143
17144 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
17145 cx.emit(EditorEvent::FocusedIn)
17146 }
17147
17148 fn handle_focus_out(
17149 &mut self,
17150 event: FocusOutEvent,
17151 _window: &mut Window,
17152 cx: &mut Context<Self>,
17153 ) {
17154 if event.blurred != self.focus_handle {
17155 self.last_focused_descendant = Some(event.blurred);
17156 }
17157 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
17158 }
17159
17160 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17161 self.blink_manager.update(cx, BlinkManager::disable);
17162 self.buffer
17163 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
17164
17165 if let Some(blame) = self.blame.as_ref() {
17166 blame.update(cx, GitBlame::blur)
17167 }
17168 if !self.hover_state.focused(window, cx) {
17169 hide_hover(self, cx);
17170 }
17171 if !self
17172 .context_menu
17173 .borrow()
17174 .as_ref()
17175 .is_some_and(|context_menu| context_menu.focused(window, cx))
17176 {
17177 self.hide_context_menu(window, cx);
17178 }
17179 self.discard_inline_completion(false, cx);
17180 cx.emit(EditorEvent::Blurred);
17181 cx.notify();
17182 }
17183
17184 pub fn register_action<A: Action>(
17185 &mut self,
17186 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
17187 ) -> Subscription {
17188 let id = self.next_editor_action_id.post_inc();
17189 let listener = Arc::new(listener);
17190 self.editor_actions.borrow_mut().insert(
17191 id,
17192 Box::new(move |window, _| {
17193 let listener = listener.clone();
17194 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
17195 let action = action.downcast_ref().unwrap();
17196 if phase == DispatchPhase::Bubble {
17197 listener(action, window, cx)
17198 }
17199 })
17200 }),
17201 );
17202
17203 let editor_actions = self.editor_actions.clone();
17204 Subscription::new(move || {
17205 editor_actions.borrow_mut().remove(&id);
17206 })
17207 }
17208
17209 pub fn file_header_size(&self) -> u32 {
17210 FILE_HEADER_HEIGHT
17211 }
17212
17213 pub fn restore(
17214 &mut self,
17215 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
17216 window: &mut Window,
17217 cx: &mut Context<Self>,
17218 ) {
17219 let workspace = self.workspace();
17220 let project = self.project.as_ref();
17221 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
17222 let mut tasks = Vec::new();
17223 for (buffer_id, changes) in revert_changes {
17224 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
17225 buffer.update(cx, |buffer, cx| {
17226 buffer.edit(
17227 changes
17228 .into_iter()
17229 .map(|(range, text)| (range, text.to_string())),
17230 None,
17231 cx,
17232 );
17233 });
17234
17235 if let Some(project) =
17236 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
17237 {
17238 project.update(cx, |project, cx| {
17239 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
17240 })
17241 }
17242 }
17243 }
17244 tasks
17245 });
17246 cx.spawn_in(window, async move |_, cx| {
17247 for (buffer, task) in save_tasks {
17248 let result = task.await;
17249 if result.is_err() {
17250 let Some(path) = buffer
17251 .read_with(cx, |buffer, cx| buffer.project_path(cx))
17252 .ok()
17253 else {
17254 continue;
17255 };
17256 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
17257 let Some(task) = cx
17258 .update_window_entity(&workspace, |workspace, window, cx| {
17259 workspace
17260 .open_path_preview(path, None, false, false, false, window, cx)
17261 })
17262 .ok()
17263 else {
17264 continue;
17265 };
17266 task.await.log_err();
17267 }
17268 }
17269 }
17270 })
17271 .detach();
17272 self.change_selections(None, window, cx, |selections| selections.refresh());
17273 }
17274
17275 pub fn to_pixel_point(
17276 &self,
17277 source: multi_buffer::Anchor,
17278 editor_snapshot: &EditorSnapshot,
17279 window: &mut Window,
17280 ) -> Option<gpui::Point<Pixels>> {
17281 let source_point = source.to_display_point(editor_snapshot);
17282 self.display_to_pixel_point(source_point, editor_snapshot, window)
17283 }
17284
17285 pub fn display_to_pixel_point(
17286 &self,
17287 source: DisplayPoint,
17288 editor_snapshot: &EditorSnapshot,
17289 window: &mut Window,
17290 ) -> Option<gpui::Point<Pixels>> {
17291 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
17292 let text_layout_details = self.text_layout_details(window);
17293 let scroll_top = text_layout_details
17294 .scroll_anchor
17295 .scroll_position(editor_snapshot)
17296 .y;
17297
17298 if source.row().as_f32() < scroll_top.floor() {
17299 return None;
17300 }
17301 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
17302 let source_y = line_height * (source.row().as_f32() - scroll_top);
17303 Some(gpui::Point::new(source_x, source_y))
17304 }
17305
17306 pub fn has_visible_completions_menu(&self) -> bool {
17307 !self.edit_prediction_preview_is_active()
17308 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
17309 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
17310 })
17311 }
17312
17313 pub fn register_addon<T: Addon>(&mut self, instance: T) {
17314 self.addons
17315 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
17316 }
17317
17318 pub fn unregister_addon<T: Addon>(&mut self) {
17319 self.addons.remove(&std::any::TypeId::of::<T>());
17320 }
17321
17322 pub fn addon<T: Addon>(&self) -> Option<&T> {
17323 let type_id = std::any::TypeId::of::<T>();
17324 self.addons
17325 .get(&type_id)
17326 .and_then(|item| item.to_any().downcast_ref::<T>())
17327 }
17328
17329 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
17330 let text_layout_details = self.text_layout_details(window);
17331 let style = &text_layout_details.editor_style;
17332 let font_id = window.text_system().resolve_font(&style.text.font());
17333 let font_size = style.text.font_size.to_pixels(window.rem_size());
17334 let line_height = style.text.line_height_in_pixels(window.rem_size());
17335 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
17336
17337 gpui::Size::new(em_width, line_height)
17338 }
17339
17340 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
17341 self.load_diff_task.clone()
17342 }
17343
17344 fn read_metadata_from_db(
17345 &mut self,
17346 item_id: u64,
17347 workspace_id: WorkspaceId,
17348 window: &mut Window,
17349 cx: &mut Context<Editor>,
17350 ) {
17351 if self.is_singleton(cx)
17352 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
17353 {
17354 let buffer_snapshot = OnceCell::new();
17355
17356 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
17357 if !selections.is_empty() {
17358 let snapshot =
17359 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
17360 self.change_selections(None, window, cx, |s| {
17361 s.select_ranges(selections.into_iter().map(|(start, end)| {
17362 snapshot.clip_offset(start, Bias::Left)
17363 ..snapshot.clip_offset(end, Bias::Right)
17364 }));
17365 });
17366 }
17367 };
17368
17369 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
17370 if !folds.is_empty() {
17371 let snapshot =
17372 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
17373 self.fold_ranges(
17374 folds
17375 .into_iter()
17376 .map(|(start, end)| {
17377 snapshot.clip_offset(start, Bias::Left)
17378 ..snapshot.clip_offset(end, Bias::Right)
17379 })
17380 .collect(),
17381 false,
17382 window,
17383 cx,
17384 );
17385 }
17386 }
17387 }
17388
17389 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
17390 }
17391}
17392
17393fn insert_extra_newline_brackets(
17394 buffer: &MultiBufferSnapshot,
17395 range: Range<usize>,
17396 language: &language::LanguageScope,
17397) -> bool {
17398 let leading_whitespace_len = buffer
17399 .reversed_chars_at(range.start)
17400 .take_while(|c| c.is_whitespace() && *c != '\n')
17401 .map(|c| c.len_utf8())
17402 .sum::<usize>();
17403 let trailing_whitespace_len = buffer
17404 .chars_at(range.end)
17405 .take_while(|c| c.is_whitespace() && *c != '\n')
17406 .map(|c| c.len_utf8())
17407 .sum::<usize>();
17408 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
17409
17410 language.brackets().any(|(pair, enabled)| {
17411 let pair_start = pair.start.trim_end();
17412 let pair_end = pair.end.trim_start();
17413
17414 enabled
17415 && pair.newline
17416 && buffer.contains_str_at(range.end, pair_end)
17417 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
17418 })
17419}
17420
17421fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
17422 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
17423 [(buffer, range, _)] => (*buffer, range.clone()),
17424 _ => return false,
17425 };
17426 let pair = {
17427 let mut result: Option<BracketMatch> = None;
17428
17429 for pair in buffer
17430 .all_bracket_ranges(range.clone())
17431 .filter(move |pair| {
17432 pair.open_range.start <= range.start && pair.close_range.end >= range.end
17433 })
17434 {
17435 let len = pair.close_range.end - pair.open_range.start;
17436
17437 if let Some(existing) = &result {
17438 let existing_len = existing.close_range.end - existing.open_range.start;
17439 if len > existing_len {
17440 continue;
17441 }
17442 }
17443
17444 result = Some(pair);
17445 }
17446
17447 result
17448 };
17449 let Some(pair) = pair else {
17450 return false;
17451 };
17452 pair.newline_only
17453 && buffer
17454 .chars_for_range(pair.open_range.end..range.start)
17455 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
17456 .all(|c| c.is_whitespace() && c != '\n')
17457}
17458
17459fn get_uncommitted_diff_for_buffer(
17460 project: &Entity<Project>,
17461 buffers: impl IntoIterator<Item = Entity<Buffer>>,
17462 buffer: Entity<MultiBuffer>,
17463 cx: &mut App,
17464) -> Task<()> {
17465 let mut tasks = Vec::new();
17466 project.update(cx, |project, cx| {
17467 for buffer in buffers {
17468 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
17469 }
17470 });
17471 cx.spawn(async move |cx| {
17472 let diffs = future::join_all(tasks).await;
17473 buffer
17474 .update(cx, |buffer, cx| {
17475 for diff in diffs.into_iter().flatten() {
17476 buffer.add_diff(diff, cx);
17477 }
17478 })
17479 .ok();
17480 })
17481}
17482
17483fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
17484 let tab_size = tab_size.get() as usize;
17485 let mut width = offset;
17486
17487 for ch in text.chars() {
17488 width += if ch == '\t' {
17489 tab_size - (width % tab_size)
17490 } else {
17491 1
17492 };
17493 }
17494
17495 width - offset
17496}
17497
17498#[cfg(test)]
17499mod tests {
17500 use super::*;
17501
17502 #[test]
17503 fn test_string_size_with_expanded_tabs() {
17504 let nz = |val| NonZeroU32::new(val).unwrap();
17505 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
17506 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
17507 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
17508 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
17509 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
17510 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
17511 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
17512 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
17513 }
17514}
17515
17516/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
17517struct WordBreakingTokenizer<'a> {
17518 input: &'a str,
17519}
17520
17521impl<'a> WordBreakingTokenizer<'a> {
17522 fn new(input: &'a str) -> Self {
17523 Self { input }
17524 }
17525}
17526
17527fn is_char_ideographic(ch: char) -> bool {
17528 use unicode_script::Script::*;
17529 use unicode_script::UnicodeScript;
17530 matches!(ch.script(), Han | Tangut | Yi)
17531}
17532
17533fn is_grapheme_ideographic(text: &str) -> bool {
17534 text.chars().any(is_char_ideographic)
17535}
17536
17537fn is_grapheme_whitespace(text: &str) -> bool {
17538 text.chars().any(|x| x.is_whitespace())
17539}
17540
17541fn should_stay_with_preceding_ideograph(text: &str) -> bool {
17542 text.chars().next().map_or(false, |ch| {
17543 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
17544 })
17545}
17546
17547#[derive(PartialEq, Eq, Debug, Clone, Copy)]
17548enum WordBreakToken<'a> {
17549 Word { token: &'a str, grapheme_len: usize },
17550 InlineWhitespace { token: &'a str, grapheme_len: usize },
17551 Newline,
17552}
17553
17554impl<'a> Iterator for WordBreakingTokenizer<'a> {
17555 /// Yields a span, the count of graphemes in the token, and whether it was
17556 /// whitespace. Note that it also breaks at word boundaries.
17557 type Item = WordBreakToken<'a>;
17558
17559 fn next(&mut self) -> Option<Self::Item> {
17560 use unicode_segmentation::UnicodeSegmentation;
17561 if self.input.is_empty() {
17562 return None;
17563 }
17564
17565 let mut iter = self.input.graphemes(true).peekable();
17566 let mut offset = 0;
17567 let mut grapheme_len = 0;
17568 if let Some(first_grapheme) = iter.next() {
17569 let is_newline = first_grapheme == "\n";
17570 let is_whitespace = is_grapheme_whitespace(first_grapheme);
17571 offset += first_grapheme.len();
17572 grapheme_len += 1;
17573 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
17574 if let Some(grapheme) = iter.peek().copied() {
17575 if should_stay_with_preceding_ideograph(grapheme) {
17576 offset += grapheme.len();
17577 grapheme_len += 1;
17578 }
17579 }
17580 } else {
17581 let mut words = self.input[offset..].split_word_bound_indices().peekable();
17582 let mut next_word_bound = words.peek().copied();
17583 if next_word_bound.map_or(false, |(i, _)| i == 0) {
17584 next_word_bound = words.next();
17585 }
17586 while let Some(grapheme) = iter.peek().copied() {
17587 if next_word_bound.map_or(false, |(i, _)| i == offset) {
17588 break;
17589 };
17590 if is_grapheme_whitespace(grapheme) != is_whitespace
17591 || (grapheme == "\n") != is_newline
17592 {
17593 break;
17594 };
17595 offset += grapheme.len();
17596 grapheme_len += 1;
17597 iter.next();
17598 }
17599 }
17600 let token = &self.input[..offset];
17601 self.input = &self.input[offset..];
17602 if token == "\n" {
17603 Some(WordBreakToken::Newline)
17604 } else if is_whitespace {
17605 Some(WordBreakToken::InlineWhitespace {
17606 token,
17607 grapheme_len,
17608 })
17609 } else {
17610 Some(WordBreakToken::Word {
17611 token,
17612 grapheme_len,
17613 })
17614 }
17615 } else {
17616 None
17617 }
17618 }
17619}
17620
17621#[test]
17622fn test_word_breaking_tokenizer() {
17623 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
17624 ("", &[]),
17625 (" ", &[whitespace(" ", 2)]),
17626 ("Ʒ", &[word("Ʒ", 1)]),
17627 ("Ǽ", &[word("Ǽ", 1)]),
17628 ("⋑", &[word("⋑", 1)]),
17629 ("⋑⋑", &[word("⋑⋑", 2)]),
17630 (
17631 "原理,进而",
17632 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
17633 ),
17634 (
17635 "hello world",
17636 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
17637 ),
17638 (
17639 "hello, world",
17640 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
17641 ),
17642 (
17643 " hello world",
17644 &[
17645 whitespace(" ", 2),
17646 word("hello", 5),
17647 whitespace(" ", 1),
17648 word("world", 5),
17649 ],
17650 ),
17651 (
17652 "这是什么 \n 钢笔",
17653 &[
17654 word("这", 1),
17655 word("是", 1),
17656 word("什", 1),
17657 word("么", 1),
17658 whitespace(" ", 1),
17659 newline(),
17660 whitespace(" ", 1),
17661 word("钢", 1),
17662 word("笔", 1),
17663 ],
17664 ),
17665 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
17666 ];
17667
17668 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
17669 WordBreakToken::Word {
17670 token,
17671 grapheme_len,
17672 }
17673 }
17674
17675 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
17676 WordBreakToken::InlineWhitespace {
17677 token,
17678 grapheme_len,
17679 }
17680 }
17681
17682 fn newline() -> WordBreakToken<'static> {
17683 WordBreakToken::Newline
17684 }
17685
17686 for (input, result) in tests {
17687 assert_eq!(
17688 WordBreakingTokenizer::new(input)
17689 .collect::<Vec<_>>()
17690 .as_slice(),
17691 *result,
17692 );
17693 }
17694}
17695
17696fn wrap_with_prefix(
17697 line_prefix: String,
17698 unwrapped_text: String,
17699 wrap_column: usize,
17700 tab_size: NonZeroU32,
17701 preserve_existing_whitespace: bool,
17702) -> String {
17703 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
17704 let mut wrapped_text = String::new();
17705 let mut current_line = line_prefix.clone();
17706
17707 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
17708 let mut current_line_len = line_prefix_len;
17709 let mut in_whitespace = false;
17710 for token in tokenizer {
17711 let have_preceding_whitespace = in_whitespace;
17712 match token {
17713 WordBreakToken::Word {
17714 token,
17715 grapheme_len,
17716 } => {
17717 in_whitespace = false;
17718 if current_line_len + grapheme_len > wrap_column
17719 && current_line_len != line_prefix_len
17720 {
17721 wrapped_text.push_str(current_line.trim_end());
17722 wrapped_text.push('\n');
17723 current_line.truncate(line_prefix.len());
17724 current_line_len = line_prefix_len;
17725 }
17726 current_line.push_str(token);
17727 current_line_len += grapheme_len;
17728 }
17729 WordBreakToken::InlineWhitespace {
17730 mut token,
17731 mut grapheme_len,
17732 } => {
17733 in_whitespace = true;
17734 if have_preceding_whitespace && !preserve_existing_whitespace {
17735 continue;
17736 }
17737 if !preserve_existing_whitespace {
17738 token = " ";
17739 grapheme_len = 1;
17740 }
17741 if current_line_len + grapheme_len > wrap_column {
17742 wrapped_text.push_str(current_line.trim_end());
17743 wrapped_text.push('\n');
17744 current_line.truncate(line_prefix.len());
17745 current_line_len = line_prefix_len;
17746 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
17747 current_line.push_str(token);
17748 current_line_len += grapheme_len;
17749 }
17750 }
17751 WordBreakToken::Newline => {
17752 in_whitespace = true;
17753 if preserve_existing_whitespace {
17754 wrapped_text.push_str(current_line.trim_end());
17755 wrapped_text.push('\n');
17756 current_line.truncate(line_prefix.len());
17757 current_line_len = line_prefix_len;
17758 } else if have_preceding_whitespace {
17759 continue;
17760 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
17761 {
17762 wrapped_text.push_str(current_line.trim_end());
17763 wrapped_text.push('\n');
17764 current_line.truncate(line_prefix.len());
17765 current_line_len = line_prefix_len;
17766 } else if current_line_len != line_prefix_len {
17767 current_line.push(' ');
17768 current_line_len += 1;
17769 }
17770 }
17771 }
17772 }
17773
17774 if !current_line.is_empty() {
17775 wrapped_text.push_str(¤t_line);
17776 }
17777 wrapped_text
17778}
17779
17780#[test]
17781fn test_wrap_with_prefix() {
17782 assert_eq!(
17783 wrap_with_prefix(
17784 "# ".to_string(),
17785 "abcdefg".to_string(),
17786 4,
17787 NonZeroU32::new(4).unwrap(),
17788 false,
17789 ),
17790 "# abcdefg"
17791 );
17792 assert_eq!(
17793 wrap_with_prefix(
17794 "".to_string(),
17795 "\thello world".to_string(),
17796 8,
17797 NonZeroU32::new(4).unwrap(),
17798 false,
17799 ),
17800 "hello\nworld"
17801 );
17802 assert_eq!(
17803 wrap_with_prefix(
17804 "// ".to_string(),
17805 "xx \nyy zz aa bb cc".to_string(),
17806 12,
17807 NonZeroU32::new(4).unwrap(),
17808 false,
17809 ),
17810 "// xx yy zz\n// aa bb cc"
17811 );
17812 assert_eq!(
17813 wrap_with_prefix(
17814 String::new(),
17815 "这是什么 \n 钢笔".to_string(),
17816 3,
17817 NonZeroU32::new(4).unwrap(),
17818 false,
17819 ),
17820 "这是什\n么 钢\n笔"
17821 );
17822}
17823
17824pub trait CollaborationHub {
17825 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
17826 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
17827 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
17828}
17829
17830impl CollaborationHub for Entity<Project> {
17831 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
17832 self.read(cx).collaborators()
17833 }
17834
17835 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
17836 self.read(cx).user_store().read(cx).participant_indices()
17837 }
17838
17839 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
17840 let this = self.read(cx);
17841 let user_ids = this.collaborators().values().map(|c| c.user_id);
17842 this.user_store().read_with(cx, |user_store, cx| {
17843 user_store.participant_names(user_ids, cx)
17844 })
17845 }
17846}
17847
17848pub trait SemanticsProvider {
17849 fn hover(
17850 &self,
17851 buffer: &Entity<Buffer>,
17852 position: text::Anchor,
17853 cx: &mut App,
17854 ) -> Option<Task<Vec<project::Hover>>>;
17855
17856 fn inlay_hints(
17857 &self,
17858 buffer_handle: Entity<Buffer>,
17859 range: Range<text::Anchor>,
17860 cx: &mut App,
17861 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
17862
17863 fn resolve_inlay_hint(
17864 &self,
17865 hint: InlayHint,
17866 buffer_handle: Entity<Buffer>,
17867 server_id: LanguageServerId,
17868 cx: &mut App,
17869 ) -> Option<Task<anyhow::Result<InlayHint>>>;
17870
17871 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
17872
17873 fn document_highlights(
17874 &self,
17875 buffer: &Entity<Buffer>,
17876 position: text::Anchor,
17877 cx: &mut App,
17878 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
17879
17880 fn definitions(
17881 &self,
17882 buffer: &Entity<Buffer>,
17883 position: text::Anchor,
17884 kind: GotoDefinitionKind,
17885 cx: &mut App,
17886 ) -> Option<Task<Result<Vec<LocationLink>>>>;
17887
17888 fn range_for_rename(
17889 &self,
17890 buffer: &Entity<Buffer>,
17891 position: text::Anchor,
17892 cx: &mut App,
17893 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
17894
17895 fn perform_rename(
17896 &self,
17897 buffer: &Entity<Buffer>,
17898 position: text::Anchor,
17899 new_name: String,
17900 cx: &mut App,
17901 ) -> Option<Task<Result<ProjectTransaction>>>;
17902}
17903
17904pub trait CompletionProvider {
17905 fn completions(
17906 &self,
17907 excerpt_id: ExcerptId,
17908 buffer: &Entity<Buffer>,
17909 buffer_position: text::Anchor,
17910 trigger: CompletionContext,
17911 window: &mut Window,
17912 cx: &mut Context<Editor>,
17913 ) -> Task<Result<Option<Vec<Completion>>>>;
17914
17915 fn resolve_completions(
17916 &self,
17917 buffer: Entity<Buffer>,
17918 completion_indices: Vec<usize>,
17919 completions: Rc<RefCell<Box<[Completion]>>>,
17920 cx: &mut Context<Editor>,
17921 ) -> Task<Result<bool>>;
17922
17923 fn apply_additional_edits_for_completion(
17924 &self,
17925 _buffer: Entity<Buffer>,
17926 _completions: Rc<RefCell<Box<[Completion]>>>,
17927 _completion_index: usize,
17928 _push_to_history: bool,
17929 _cx: &mut Context<Editor>,
17930 ) -> Task<Result<Option<language::Transaction>>> {
17931 Task::ready(Ok(None))
17932 }
17933
17934 fn is_completion_trigger(
17935 &self,
17936 buffer: &Entity<Buffer>,
17937 position: language::Anchor,
17938 text: &str,
17939 trigger_in_words: bool,
17940 cx: &mut Context<Editor>,
17941 ) -> bool;
17942
17943 fn sort_completions(&self) -> bool {
17944 true
17945 }
17946}
17947
17948pub trait CodeActionProvider {
17949 fn id(&self) -> Arc<str>;
17950
17951 fn code_actions(
17952 &self,
17953 buffer: &Entity<Buffer>,
17954 range: Range<text::Anchor>,
17955 window: &mut Window,
17956 cx: &mut App,
17957 ) -> Task<Result<Vec<CodeAction>>>;
17958
17959 fn apply_code_action(
17960 &self,
17961 buffer_handle: Entity<Buffer>,
17962 action: CodeAction,
17963 excerpt_id: ExcerptId,
17964 push_to_history: bool,
17965 window: &mut Window,
17966 cx: &mut App,
17967 ) -> Task<Result<ProjectTransaction>>;
17968}
17969
17970impl CodeActionProvider for Entity<Project> {
17971 fn id(&self) -> Arc<str> {
17972 "project".into()
17973 }
17974
17975 fn code_actions(
17976 &self,
17977 buffer: &Entity<Buffer>,
17978 range: Range<text::Anchor>,
17979 _window: &mut Window,
17980 cx: &mut App,
17981 ) -> Task<Result<Vec<CodeAction>>> {
17982 self.update(cx, |project, cx| {
17983 let code_lens = project.code_lens(buffer, range.clone(), cx);
17984 let code_actions = project.code_actions(buffer, range, None, cx);
17985 cx.background_spawn(async move {
17986 let (code_lens, code_actions) = join(code_lens, code_actions).await;
17987 Ok(code_lens
17988 .context("code lens fetch")?
17989 .into_iter()
17990 .chain(code_actions.context("code action fetch")?)
17991 .collect())
17992 })
17993 })
17994 }
17995
17996 fn apply_code_action(
17997 &self,
17998 buffer_handle: Entity<Buffer>,
17999 action: CodeAction,
18000 _excerpt_id: ExcerptId,
18001 push_to_history: bool,
18002 _window: &mut Window,
18003 cx: &mut App,
18004 ) -> Task<Result<ProjectTransaction>> {
18005 self.update(cx, |project, cx| {
18006 project.apply_code_action(buffer_handle, action, push_to_history, cx)
18007 })
18008 }
18009}
18010
18011fn snippet_completions(
18012 project: &Project,
18013 buffer: &Entity<Buffer>,
18014 buffer_position: text::Anchor,
18015 cx: &mut App,
18016) -> Task<Result<Vec<Completion>>> {
18017 let language = buffer.read(cx).language_at(buffer_position);
18018 let language_name = language.as_ref().map(|language| language.lsp_id());
18019 let snippet_store = project.snippets().read(cx);
18020 let snippets = snippet_store.snippets_for(language_name, cx);
18021
18022 if snippets.is_empty() {
18023 return Task::ready(Ok(vec![]));
18024 }
18025 let snapshot = buffer.read(cx).text_snapshot();
18026 let chars: String = snapshot
18027 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
18028 .collect();
18029
18030 let scope = language.map(|language| language.default_scope());
18031 let executor = cx.background_executor().clone();
18032
18033 cx.background_spawn(async move {
18034 let classifier = CharClassifier::new(scope).for_completion(true);
18035 let mut last_word = chars
18036 .chars()
18037 .take_while(|c| classifier.is_word(*c))
18038 .collect::<String>();
18039 last_word = last_word.chars().rev().collect();
18040
18041 if last_word.is_empty() {
18042 return Ok(vec![]);
18043 }
18044
18045 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
18046 let to_lsp = |point: &text::Anchor| {
18047 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
18048 point_to_lsp(end)
18049 };
18050 let lsp_end = to_lsp(&buffer_position);
18051
18052 let candidates = snippets
18053 .iter()
18054 .enumerate()
18055 .flat_map(|(ix, snippet)| {
18056 snippet
18057 .prefix
18058 .iter()
18059 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
18060 })
18061 .collect::<Vec<StringMatchCandidate>>();
18062
18063 let mut matches = fuzzy::match_strings(
18064 &candidates,
18065 &last_word,
18066 last_word.chars().any(|c| c.is_uppercase()),
18067 100,
18068 &Default::default(),
18069 executor,
18070 )
18071 .await;
18072
18073 // Remove all candidates where the query's start does not match the start of any word in the candidate
18074 if let Some(query_start) = last_word.chars().next() {
18075 matches.retain(|string_match| {
18076 split_words(&string_match.string).any(|word| {
18077 // Check that the first codepoint of the word as lowercase matches the first
18078 // codepoint of the query as lowercase
18079 word.chars()
18080 .flat_map(|codepoint| codepoint.to_lowercase())
18081 .zip(query_start.to_lowercase())
18082 .all(|(word_cp, query_cp)| word_cp == query_cp)
18083 })
18084 });
18085 }
18086
18087 let matched_strings = matches
18088 .into_iter()
18089 .map(|m| m.string)
18090 .collect::<HashSet<_>>();
18091
18092 let result: Vec<Completion> = snippets
18093 .into_iter()
18094 .filter_map(|snippet| {
18095 let matching_prefix = snippet
18096 .prefix
18097 .iter()
18098 .find(|prefix| matched_strings.contains(*prefix))?;
18099 let start = as_offset - last_word.len();
18100 let start = snapshot.anchor_before(start);
18101 let range = start..buffer_position;
18102 let lsp_start = to_lsp(&start);
18103 let lsp_range = lsp::Range {
18104 start: lsp_start,
18105 end: lsp_end,
18106 };
18107 Some(Completion {
18108 old_range: range,
18109 new_text: snippet.body.clone(),
18110 source: CompletionSource::Lsp {
18111 server_id: LanguageServerId(usize::MAX),
18112 resolved: true,
18113 lsp_completion: Box::new(lsp::CompletionItem {
18114 label: snippet.prefix.first().unwrap().clone(),
18115 kind: Some(CompletionItemKind::SNIPPET),
18116 label_details: snippet.description.as_ref().map(|description| {
18117 lsp::CompletionItemLabelDetails {
18118 detail: Some(description.clone()),
18119 description: None,
18120 }
18121 }),
18122 insert_text_format: Some(InsertTextFormat::SNIPPET),
18123 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18124 lsp::InsertReplaceEdit {
18125 new_text: snippet.body.clone(),
18126 insert: lsp_range,
18127 replace: lsp_range,
18128 },
18129 )),
18130 filter_text: Some(snippet.body.clone()),
18131 sort_text: Some(char::MAX.to_string()),
18132 ..lsp::CompletionItem::default()
18133 }),
18134 lsp_defaults: None,
18135 },
18136 label: CodeLabel {
18137 text: matching_prefix.clone(),
18138 runs: Vec::new(),
18139 filter_range: 0..matching_prefix.len(),
18140 },
18141 icon_path: None,
18142 documentation: snippet
18143 .description
18144 .clone()
18145 .map(|description| CompletionDocumentation::SingleLine(description.into())),
18146 confirm: None,
18147 })
18148 })
18149 .collect();
18150
18151 Ok(result)
18152 })
18153}
18154
18155impl CompletionProvider for Entity<Project> {
18156 fn completions(
18157 &self,
18158 _excerpt_id: ExcerptId,
18159 buffer: &Entity<Buffer>,
18160 buffer_position: text::Anchor,
18161 options: CompletionContext,
18162 _window: &mut Window,
18163 cx: &mut Context<Editor>,
18164 ) -> Task<Result<Option<Vec<Completion>>>> {
18165 self.update(cx, |project, cx| {
18166 let snippets = snippet_completions(project, buffer, buffer_position, cx);
18167 let project_completions = project.completions(buffer, buffer_position, options, cx);
18168 cx.background_spawn(async move {
18169 let snippets_completions = snippets.await?;
18170 match project_completions.await? {
18171 Some(mut completions) => {
18172 completions.extend(snippets_completions);
18173 Ok(Some(completions))
18174 }
18175 None => {
18176 if snippets_completions.is_empty() {
18177 Ok(None)
18178 } else {
18179 Ok(Some(snippets_completions))
18180 }
18181 }
18182 }
18183 })
18184 })
18185 }
18186
18187 fn resolve_completions(
18188 &self,
18189 buffer: Entity<Buffer>,
18190 completion_indices: Vec<usize>,
18191 completions: Rc<RefCell<Box<[Completion]>>>,
18192 cx: &mut Context<Editor>,
18193 ) -> Task<Result<bool>> {
18194 self.update(cx, |project, cx| {
18195 project.lsp_store().update(cx, |lsp_store, cx| {
18196 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
18197 })
18198 })
18199 }
18200
18201 fn apply_additional_edits_for_completion(
18202 &self,
18203 buffer: Entity<Buffer>,
18204 completions: Rc<RefCell<Box<[Completion]>>>,
18205 completion_index: usize,
18206 push_to_history: bool,
18207 cx: &mut Context<Editor>,
18208 ) -> Task<Result<Option<language::Transaction>>> {
18209 self.update(cx, |project, cx| {
18210 project.lsp_store().update(cx, |lsp_store, cx| {
18211 lsp_store.apply_additional_edits_for_completion(
18212 buffer,
18213 completions,
18214 completion_index,
18215 push_to_history,
18216 cx,
18217 )
18218 })
18219 })
18220 }
18221
18222 fn is_completion_trigger(
18223 &self,
18224 buffer: &Entity<Buffer>,
18225 position: language::Anchor,
18226 text: &str,
18227 trigger_in_words: bool,
18228 cx: &mut Context<Editor>,
18229 ) -> bool {
18230 let mut chars = text.chars();
18231 let char = if let Some(char) = chars.next() {
18232 char
18233 } else {
18234 return false;
18235 };
18236 if chars.next().is_some() {
18237 return false;
18238 }
18239
18240 let buffer = buffer.read(cx);
18241 let snapshot = buffer.snapshot();
18242 if !snapshot.settings_at(position, cx).show_completions_on_input {
18243 return false;
18244 }
18245 let classifier = snapshot.char_classifier_at(position).for_completion(true);
18246 if trigger_in_words && classifier.is_word(char) {
18247 return true;
18248 }
18249
18250 buffer.completion_triggers().contains(text)
18251 }
18252}
18253
18254impl SemanticsProvider for Entity<Project> {
18255 fn hover(
18256 &self,
18257 buffer: &Entity<Buffer>,
18258 position: text::Anchor,
18259 cx: &mut App,
18260 ) -> Option<Task<Vec<project::Hover>>> {
18261 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
18262 }
18263
18264 fn document_highlights(
18265 &self,
18266 buffer: &Entity<Buffer>,
18267 position: text::Anchor,
18268 cx: &mut App,
18269 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
18270 Some(self.update(cx, |project, cx| {
18271 project.document_highlights(buffer, position, cx)
18272 }))
18273 }
18274
18275 fn definitions(
18276 &self,
18277 buffer: &Entity<Buffer>,
18278 position: text::Anchor,
18279 kind: GotoDefinitionKind,
18280 cx: &mut App,
18281 ) -> Option<Task<Result<Vec<LocationLink>>>> {
18282 Some(self.update(cx, |project, cx| match kind {
18283 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
18284 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
18285 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
18286 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
18287 }))
18288 }
18289
18290 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
18291 // TODO: make this work for remote projects
18292 self.update(cx, |this, cx| {
18293 buffer.update(cx, |buffer, cx| {
18294 this.any_language_server_supports_inlay_hints(buffer, cx)
18295 })
18296 })
18297 }
18298
18299 fn inlay_hints(
18300 &self,
18301 buffer_handle: Entity<Buffer>,
18302 range: Range<text::Anchor>,
18303 cx: &mut App,
18304 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
18305 Some(self.update(cx, |project, cx| {
18306 project.inlay_hints(buffer_handle, range, cx)
18307 }))
18308 }
18309
18310 fn resolve_inlay_hint(
18311 &self,
18312 hint: InlayHint,
18313 buffer_handle: Entity<Buffer>,
18314 server_id: LanguageServerId,
18315 cx: &mut App,
18316 ) -> Option<Task<anyhow::Result<InlayHint>>> {
18317 Some(self.update(cx, |project, cx| {
18318 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
18319 }))
18320 }
18321
18322 fn range_for_rename(
18323 &self,
18324 buffer: &Entity<Buffer>,
18325 position: text::Anchor,
18326 cx: &mut App,
18327 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
18328 Some(self.update(cx, |project, cx| {
18329 let buffer = buffer.clone();
18330 let task = project.prepare_rename(buffer.clone(), position, cx);
18331 cx.spawn(async move |_, cx| {
18332 Ok(match task.await? {
18333 PrepareRenameResponse::Success(range) => Some(range),
18334 PrepareRenameResponse::InvalidPosition => None,
18335 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
18336 // Fallback on using TreeSitter info to determine identifier range
18337 buffer.update(cx, |buffer, _| {
18338 let snapshot = buffer.snapshot();
18339 let (range, kind) = snapshot.surrounding_word(position);
18340 if kind != Some(CharKind::Word) {
18341 return None;
18342 }
18343 Some(
18344 snapshot.anchor_before(range.start)
18345 ..snapshot.anchor_after(range.end),
18346 )
18347 })?
18348 }
18349 })
18350 })
18351 }))
18352 }
18353
18354 fn perform_rename(
18355 &self,
18356 buffer: &Entity<Buffer>,
18357 position: text::Anchor,
18358 new_name: String,
18359 cx: &mut App,
18360 ) -> Option<Task<Result<ProjectTransaction>>> {
18361 Some(self.update(cx, |project, cx| {
18362 project.perform_rename(buffer.clone(), position, new_name, cx)
18363 }))
18364 }
18365}
18366
18367fn inlay_hint_settings(
18368 location: Anchor,
18369 snapshot: &MultiBufferSnapshot,
18370 cx: &mut Context<Editor>,
18371) -> InlayHintSettings {
18372 let file = snapshot.file_at(location);
18373 let language = snapshot.language_at(location).map(|l| l.name());
18374 language_settings(language, file, cx).inlay_hints
18375}
18376
18377fn consume_contiguous_rows(
18378 contiguous_row_selections: &mut Vec<Selection<Point>>,
18379 selection: &Selection<Point>,
18380 display_map: &DisplaySnapshot,
18381 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
18382) -> (MultiBufferRow, MultiBufferRow) {
18383 contiguous_row_selections.push(selection.clone());
18384 let start_row = MultiBufferRow(selection.start.row);
18385 let mut end_row = ending_row(selection, display_map);
18386
18387 while let Some(next_selection) = selections.peek() {
18388 if next_selection.start.row <= end_row.0 {
18389 end_row = ending_row(next_selection, display_map);
18390 contiguous_row_selections.push(selections.next().unwrap().clone());
18391 } else {
18392 break;
18393 }
18394 }
18395 (start_row, end_row)
18396}
18397
18398fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
18399 if next_selection.end.column > 0 || next_selection.is_empty() {
18400 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
18401 } else {
18402 MultiBufferRow(next_selection.end.row)
18403 }
18404}
18405
18406impl EditorSnapshot {
18407 pub fn remote_selections_in_range<'a>(
18408 &'a self,
18409 range: &'a Range<Anchor>,
18410 collaboration_hub: &dyn CollaborationHub,
18411 cx: &'a App,
18412 ) -> impl 'a + Iterator<Item = RemoteSelection> {
18413 let participant_names = collaboration_hub.user_names(cx);
18414 let participant_indices = collaboration_hub.user_participant_indices(cx);
18415 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
18416 let collaborators_by_replica_id = collaborators_by_peer_id
18417 .iter()
18418 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
18419 .collect::<HashMap<_, _>>();
18420 self.buffer_snapshot
18421 .selections_in_range(range, false)
18422 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
18423 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
18424 let participant_index = participant_indices.get(&collaborator.user_id).copied();
18425 let user_name = participant_names.get(&collaborator.user_id).cloned();
18426 Some(RemoteSelection {
18427 replica_id,
18428 selection,
18429 cursor_shape,
18430 line_mode,
18431 participant_index,
18432 peer_id: collaborator.peer_id,
18433 user_name,
18434 })
18435 })
18436 }
18437
18438 pub fn hunks_for_ranges(
18439 &self,
18440 ranges: impl IntoIterator<Item = Range<Point>>,
18441 ) -> Vec<MultiBufferDiffHunk> {
18442 let mut hunks = Vec::new();
18443 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
18444 HashMap::default();
18445 for query_range in ranges {
18446 let query_rows =
18447 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
18448 for hunk in self.buffer_snapshot.diff_hunks_in_range(
18449 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
18450 ) {
18451 // Include deleted hunks that are adjacent to the query range, because
18452 // otherwise they would be missed.
18453 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
18454 if hunk.status().is_deleted() {
18455 intersects_range |= hunk.row_range.start == query_rows.end;
18456 intersects_range |= hunk.row_range.end == query_rows.start;
18457 }
18458 if intersects_range {
18459 if !processed_buffer_rows
18460 .entry(hunk.buffer_id)
18461 .or_default()
18462 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
18463 {
18464 continue;
18465 }
18466 hunks.push(hunk);
18467 }
18468 }
18469 }
18470
18471 hunks
18472 }
18473
18474 fn display_diff_hunks_for_rows<'a>(
18475 &'a self,
18476 display_rows: Range<DisplayRow>,
18477 folded_buffers: &'a HashSet<BufferId>,
18478 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
18479 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
18480 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
18481
18482 self.buffer_snapshot
18483 .diff_hunks_in_range(buffer_start..buffer_end)
18484 .filter_map(|hunk| {
18485 if folded_buffers.contains(&hunk.buffer_id) {
18486 return None;
18487 }
18488
18489 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
18490 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
18491
18492 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
18493 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
18494
18495 let display_hunk = if hunk_display_start.column() != 0 {
18496 DisplayDiffHunk::Folded {
18497 display_row: hunk_display_start.row(),
18498 }
18499 } else {
18500 let mut end_row = hunk_display_end.row();
18501 if hunk_display_end.column() > 0 {
18502 end_row.0 += 1;
18503 }
18504 let is_created_file = hunk.is_created_file();
18505 DisplayDiffHunk::Unfolded {
18506 status: hunk.status(),
18507 diff_base_byte_range: hunk.diff_base_byte_range,
18508 display_row_range: hunk_display_start.row()..end_row,
18509 multi_buffer_range: Anchor::range_in_buffer(
18510 hunk.excerpt_id,
18511 hunk.buffer_id,
18512 hunk.buffer_range,
18513 ),
18514 is_created_file,
18515 }
18516 };
18517
18518 Some(display_hunk)
18519 })
18520 }
18521
18522 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
18523 self.display_snapshot.buffer_snapshot.language_at(position)
18524 }
18525
18526 pub fn is_focused(&self) -> bool {
18527 self.is_focused
18528 }
18529
18530 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
18531 self.placeholder_text.as_ref()
18532 }
18533
18534 pub fn scroll_position(&self) -> gpui::Point<f32> {
18535 self.scroll_anchor.scroll_position(&self.display_snapshot)
18536 }
18537
18538 fn gutter_dimensions(
18539 &self,
18540 font_id: FontId,
18541 font_size: Pixels,
18542 max_line_number_width: Pixels,
18543 cx: &App,
18544 ) -> Option<GutterDimensions> {
18545 if !self.show_gutter {
18546 return None;
18547 }
18548
18549 let descent = cx.text_system().descent(font_id, font_size);
18550 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
18551 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
18552
18553 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
18554 matches!(
18555 ProjectSettings::get_global(cx).git.git_gutter,
18556 Some(GitGutterSetting::TrackedFiles)
18557 )
18558 });
18559 let gutter_settings = EditorSettings::get_global(cx).gutter;
18560 let show_line_numbers = self
18561 .show_line_numbers
18562 .unwrap_or(gutter_settings.line_numbers);
18563 let line_gutter_width = if show_line_numbers {
18564 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
18565 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
18566 max_line_number_width.max(min_width_for_number_on_gutter)
18567 } else {
18568 0.0.into()
18569 };
18570
18571 let show_code_actions = self
18572 .show_code_actions
18573 .unwrap_or(gutter_settings.code_actions);
18574
18575 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
18576 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
18577
18578 let git_blame_entries_width =
18579 self.git_blame_gutter_max_author_length
18580 .map(|max_author_length| {
18581 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
18582
18583 /// The number of characters to dedicate to gaps and margins.
18584 const SPACING_WIDTH: usize = 4;
18585
18586 let max_char_count = max_author_length
18587 .min(GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED)
18588 + ::git::SHORT_SHA_LENGTH
18589 + MAX_RELATIVE_TIMESTAMP.len()
18590 + SPACING_WIDTH;
18591
18592 em_advance * max_char_count
18593 });
18594
18595 let is_singleton = self.buffer_snapshot.is_singleton();
18596
18597 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
18598 left_padding += if !is_singleton {
18599 em_width * 4.0
18600 } else if show_code_actions || show_runnables || show_breakpoints {
18601 em_width * 3.0
18602 } else if show_git_gutter && show_line_numbers {
18603 em_width * 2.0
18604 } else if show_git_gutter || show_line_numbers {
18605 em_width
18606 } else {
18607 px(0.)
18608 };
18609
18610 let shows_folds = is_singleton && gutter_settings.folds;
18611
18612 let right_padding = if shows_folds && show_line_numbers {
18613 em_width * 4.0
18614 } else if shows_folds || (!is_singleton && show_line_numbers) {
18615 em_width * 3.0
18616 } else if show_line_numbers {
18617 em_width
18618 } else {
18619 px(0.)
18620 };
18621
18622 Some(GutterDimensions {
18623 left_padding,
18624 right_padding,
18625 width: line_gutter_width + left_padding + right_padding,
18626 margin: -descent,
18627 git_blame_entries_width,
18628 })
18629 }
18630
18631 pub fn render_crease_toggle(
18632 &self,
18633 buffer_row: MultiBufferRow,
18634 row_contains_cursor: bool,
18635 editor: Entity<Editor>,
18636 window: &mut Window,
18637 cx: &mut App,
18638 ) -> Option<AnyElement> {
18639 let folded = self.is_line_folded(buffer_row);
18640 let mut is_foldable = false;
18641
18642 if let Some(crease) = self
18643 .crease_snapshot
18644 .query_row(buffer_row, &self.buffer_snapshot)
18645 {
18646 is_foldable = true;
18647 match crease {
18648 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
18649 if let Some(render_toggle) = render_toggle {
18650 let toggle_callback =
18651 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
18652 if folded {
18653 editor.update(cx, |editor, cx| {
18654 editor.fold_at(&crate::FoldAt { buffer_row }, window, cx)
18655 });
18656 } else {
18657 editor.update(cx, |editor, cx| {
18658 editor.unfold_at(
18659 &crate::UnfoldAt { buffer_row },
18660 window,
18661 cx,
18662 )
18663 });
18664 }
18665 });
18666 return Some((render_toggle)(
18667 buffer_row,
18668 folded,
18669 toggle_callback,
18670 window,
18671 cx,
18672 ));
18673 }
18674 }
18675 }
18676 }
18677
18678 is_foldable |= self.starts_indent(buffer_row);
18679
18680 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
18681 Some(
18682 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
18683 .toggle_state(folded)
18684 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
18685 if folded {
18686 this.unfold_at(&UnfoldAt { buffer_row }, window, cx);
18687 } else {
18688 this.fold_at(&FoldAt { buffer_row }, window, cx);
18689 }
18690 }))
18691 .into_any_element(),
18692 )
18693 } else {
18694 None
18695 }
18696 }
18697
18698 pub fn render_crease_trailer(
18699 &self,
18700 buffer_row: MultiBufferRow,
18701 window: &mut Window,
18702 cx: &mut App,
18703 ) -> Option<AnyElement> {
18704 let folded = self.is_line_folded(buffer_row);
18705 if let Crease::Inline { render_trailer, .. } = self
18706 .crease_snapshot
18707 .query_row(buffer_row, &self.buffer_snapshot)?
18708 {
18709 let render_trailer = render_trailer.as_ref()?;
18710 Some(render_trailer(buffer_row, folded, window, cx))
18711 } else {
18712 None
18713 }
18714 }
18715}
18716
18717impl Deref for EditorSnapshot {
18718 type Target = DisplaySnapshot;
18719
18720 fn deref(&self) -> &Self::Target {
18721 &self.display_snapshot
18722 }
18723}
18724
18725#[derive(Clone, Debug, PartialEq, Eq)]
18726pub enum EditorEvent {
18727 InputIgnored {
18728 text: Arc<str>,
18729 },
18730 InputHandled {
18731 utf16_range_to_replace: Option<Range<isize>>,
18732 text: Arc<str>,
18733 },
18734 ExcerptsAdded {
18735 buffer: Entity<Buffer>,
18736 predecessor: ExcerptId,
18737 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
18738 },
18739 ExcerptsRemoved {
18740 ids: Vec<ExcerptId>,
18741 },
18742 BufferFoldToggled {
18743 ids: Vec<ExcerptId>,
18744 folded: bool,
18745 },
18746 ExcerptsEdited {
18747 ids: Vec<ExcerptId>,
18748 },
18749 ExcerptsExpanded {
18750 ids: Vec<ExcerptId>,
18751 },
18752 BufferEdited,
18753 Edited {
18754 transaction_id: clock::Lamport,
18755 },
18756 Reparsed(BufferId),
18757 Focused,
18758 FocusedIn,
18759 Blurred,
18760 DirtyChanged,
18761 Saved,
18762 TitleChanged,
18763 DiffBaseChanged,
18764 SelectionsChanged {
18765 local: bool,
18766 },
18767 ScrollPositionChanged {
18768 local: bool,
18769 autoscroll: bool,
18770 },
18771 Closed,
18772 TransactionUndone {
18773 transaction_id: clock::Lamport,
18774 },
18775 TransactionBegun {
18776 transaction_id: clock::Lamport,
18777 },
18778 Reloaded,
18779 CursorShapeChanged,
18780 PushedToNavHistory {
18781 anchor: Anchor,
18782 is_deactivate: bool,
18783 },
18784}
18785
18786impl EventEmitter<EditorEvent> for Editor {}
18787
18788impl Focusable for Editor {
18789 fn focus_handle(&self, _cx: &App) -> FocusHandle {
18790 self.focus_handle.clone()
18791 }
18792}
18793
18794impl Render for Editor {
18795 fn render(&mut self, _: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
18796 let settings = ThemeSettings::get_global(cx);
18797
18798 let mut text_style = match self.mode {
18799 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
18800 color: cx.theme().colors().editor_foreground,
18801 font_family: settings.ui_font.family.clone(),
18802 font_features: settings.ui_font.features.clone(),
18803 font_fallbacks: settings.ui_font.fallbacks.clone(),
18804 font_size: rems(0.875).into(),
18805 font_weight: settings.ui_font.weight,
18806 line_height: relative(settings.buffer_line_height.value()),
18807 ..Default::default()
18808 },
18809 EditorMode::Full => TextStyle {
18810 color: cx.theme().colors().editor_foreground,
18811 font_family: settings.buffer_font.family.clone(),
18812 font_features: settings.buffer_font.features.clone(),
18813 font_fallbacks: settings.buffer_font.fallbacks.clone(),
18814 font_size: settings.buffer_font_size(cx).into(),
18815 font_weight: settings.buffer_font.weight,
18816 line_height: relative(settings.buffer_line_height.value()),
18817 ..Default::default()
18818 },
18819 };
18820 if let Some(text_style_refinement) = &self.text_style_refinement {
18821 text_style.refine(text_style_refinement)
18822 }
18823
18824 let background = match self.mode {
18825 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
18826 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
18827 EditorMode::Full => cx.theme().colors().editor_background,
18828 };
18829
18830 EditorElement::new(
18831 &cx.entity(),
18832 EditorStyle {
18833 background,
18834 local_player: cx.theme().players().local(),
18835 text: text_style,
18836 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
18837 syntax: cx.theme().syntax().clone(),
18838 status: cx.theme().status().clone(),
18839 inlay_hints_style: make_inlay_hints_style(cx),
18840 inline_completion_styles: make_suggestion_styles(cx),
18841 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
18842 },
18843 )
18844 }
18845}
18846
18847impl EntityInputHandler for Editor {
18848 fn text_for_range(
18849 &mut self,
18850 range_utf16: Range<usize>,
18851 adjusted_range: &mut Option<Range<usize>>,
18852 _: &mut Window,
18853 cx: &mut Context<Self>,
18854 ) -> Option<String> {
18855 let snapshot = self.buffer.read(cx).read(cx);
18856 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
18857 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
18858 if (start.0..end.0) != range_utf16 {
18859 adjusted_range.replace(start.0..end.0);
18860 }
18861 Some(snapshot.text_for_range(start..end).collect())
18862 }
18863
18864 fn selected_text_range(
18865 &mut self,
18866 ignore_disabled_input: bool,
18867 _: &mut Window,
18868 cx: &mut Context<Self>,
18869 ) -> Option<UTF16Selection> {
18870 // Prevent the IME menu from appearing when holding down an alphabetic key
18871 // while input is disabled.
18872 if !ignore_disabled_input && !self.input_enabled {
18873 return None;
18874 }
18875
18876 let selection = self.selections.newest::<OffsetUtf16>(cx);
18877 let range = selection.range();
18878
18879 Some(UTF16Selection {
18880 range: range.start.0..range.end.0,
18881 reversed: selection.reversed,
18882 })
18883 }
18884
18885 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
18886 let snapshot = self.buffer.read(cx).read(cx);
18887 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
18888 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
18889 }
18890
18891 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18892 self.clear_highlights::<InputComposition>(cx);
18893 self.ime_transaction.take();
18894 }
18895
18896 fn replace_text_in_range(
18897 &mut self,
18898 range_utf16: Option<Range<usize>>,
18899 text: &str,
18900 window: &mut Window,
18901 cx: &mut Context<Self>,
18902 ) {
18903 if !self.input_enabled {
18904 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18905 return;
18906 }
18907
18908 self.transact(window, cx, |this, window, cx| {
18909 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
18910 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
18911 Some(this.selection_replacement_ranges(range_utf16, cx))
18912 } else {
18913 this.marked_text_ranges(cx)
18914 };
18915
18916 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
18917 let newest_selection_id = this.selections.newest_anchor().id;
18918 this.selections
18919 .all::<OffsetUtf16>(cx)
18920 .iter()
18921 .zip(ranges_to_replace.iter())
18922 .find_map(|(selection, range)| {
18923 if selection.id == newest_selection_id {
18924 Some(
18925 (range.start.0 as isize - selection.head().0 as isize)
18926 ..(range.end.0 as isize - selection.head().0 as isize),
18927 )
18928 } else {
18929 None
18930 }
18931 })
18932 });
18933
18934 cx.emit(EditorEvent::InputHandled {
18935 utf16_range_to_replace: range_to_replace,
18936 text: text.into(),
18937 });
18938
18939 if let Some(new_selected_ranges) = new_selected_ranges {
18940 this.change_selections(None, window, cx, |selections| {
18941 selections.select_ranges(new_selected_ranges)
18942 });
18943 this.backspace(&Default::default(), window, cx);
18944 }
18945
18946 this.handle_input(text, window, cx);
18947 });
18948
18949 if let Some(transaction) = self.ime_transaction {
18950 self.buffer.update(cx, |buffer, cx| {
18951 buffer.group_until_transaction(transaction, cx);
18952 });
18953 }
18954
18955 self.unmark_text(window, cx);
18956 }
18957
18958 fn replace_and_mark_text_in_range(
18959 &mut self,
18960 range_utf16: Option<Range<usize>>,
18961 text: &str,
18962 new_selected_range_utf16: Option<Range<usize>>,
18963 window: &mut Window,
18964 cx: &mut Context<Self>,
18965 ) {
18966 if !self.input_enabled {
18967 return;
18968 }
18969
18970 let transaction = self.transact(window, cx, |this, window, cx| {
18971 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
18972 let snapshot = this.buffer.read(cx).read(cx);
18973 if let Some(relative_range_utf16) = range_utf16.as_ref() {
18974 for marked_range in &mut marked_ranges {
18975 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
18976 marked_range.start.0 += relative_range_utf16.start;
18977 marked_range.start =
18978 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
18979 marked_range.end =
18980 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
18981 }
18982 }
18983 Some(marked_ranges)
18984 } else if let Some(range_utf16) = range_utf16 {
18985 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
18986 Some(this.selection_replacement_ranges(range_utf16, cx))
18987 } else {
18988 None
18989 };
18990
18991 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
18992 let newest_selection_id = this.selections.newest_anchor().id;
18993 this.selections
18994 .all::<OffsetUtf16>(cx)
18995 .iter()
18996 .zip(ranges_to_replace.iter())
18997 .find_map(|(selection, range)| {
18998 if selection.id == newest_selection_id {
18999 Some(
19000 (range.start.0 as isize - selection.head().0 as isize)
19001 ..(range.end.0 as isize - selection.head().0 as isize),
19002 )
19003 } else {
19004 None
19005 }
19006 })
19007 });
19008
19009 cx.emit(EditorEvent::InputHandled {
19010 utf16_range_to_replace: range_to_replace,
19011 text: text.into(),
19012 });
19013
19014 if let Some(ranges) = ranges_to_replace {
19015 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
19016 }
19017
19018 let marked_ranges = {
19019 let snapshot = this.buffer.read(cx).read(cx);
19020 this.selections
19021 .disjoint_anchors()
19022 .iter()
19023 .map(|selection| {
19024 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
19025 })
19026 .collect::<Vec<_>>()
19027 };
19028
19029 if text.is_empty() {
19030 this.unmark_text(window, cx);
19031 } else {
19032 this.highlight_text::<InputComposition>(
19033 marked_ranges.clone(),
19034 HighlightStyle {
19035 underline: Some(UnderlineStyle {
19036 thickness: px(1.),
19037 color: None,
19038 wavy: false,
19039 }),
19040 ..Default::default()
19041 },
19042 cx,
19043 );
19044 }
19045
19046 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
19047 let use_autoclose = this.use_autoclose;
19048 let use_auto_surround = this.use_auto_surround;
19049 this.set_use_autoclose(false);
19050 this.set_use_auto_surround(false);
19051 this.handle_input(text, window, cx);
19052 this.set_use_autoclose(use_autoclose);
19053 this.set_use_auto_surround(use_auto_surround);
19054
19055 if let Some(new_selected_range) = new_selected_range_utf16 {
19056 let snapshot = this.buffer.read(cx).read(cx);
19057 let new_selected_ranges = marked_ranges
19058 .into_iter()
19059 .map(|marked_range| {
19060 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
19061 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
19062 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
19063 snapshot.clip_offset_utf16(new_start, Bias::Left)
19064 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
19065 })
19066 .collect::<Vec<_>>();
19067
19068 drop(snapshot);
19069 this.change_selections(None, window, cx, |selections| {
19070 selections.select_ranges(new_selected_ranges)
19071 });
19072 }
19073 });
19074
19075 self.ime_transaction = self.ime_transaction.or(transaction);
19076 if let Some(transaction) = self.ime_transaction {
19077 self.buffer.update(cx, |buffer, cx| {
19078 buffer.group_until_transaction(transaction, cx);
19079 });
19080 }
19081
19082 if self.text_highlights::<InputComposition>(cx).is_none() {
19083 self.ime_transaction.take();
19084 }
19085 }
19086
19087 fn bounds_for_range(
19088 &mut self,
19089 range_utf16: Range<usize>,
19090 element_bounds: gpui::Bounds<Pixels>,
19091 window: &mut Window,
19092 cx: &mut Context<Self>,
19093 ) -> Option<gpui::Bounds<Pixels>> {
19094 let text_layout_details = self.text_layout_details(window);
19095 let gpui::Size {
19096 width: em_width,
19097 height: line_height,
19098 } = self.character_size(window);
19099
19100 let snapshot = self.snapshot(window, cx);
19101 let scroll_position = snapshot.scroll_position();
19102 let scroll_left = scroll_position.x * em_width;
19103
19104 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
19105 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
19106 + self.gutter_dimensions.width
19107 + self.gutter_dimensions.margin;
19108 let y = line_height * (start.row().as_f32() - scroll_position.y);
19109
19110 Some(Bounds {
19111 origin: element_bounds.origin + point(x, y),
19112 size: size(em_width, line_height),
19113 })
19114 }
19115
19116 fn character_index_for_point(
19117 &mut self,
19118 point: gpui::Point<Pixels>,
19119 _window: &mut Window,
19120 _cx: &mut Context<Self>,
19121 ) -> Option<usize> {
19122 let position_map = self.last_position_map.as_ref()?;
19123 if !position_map.text_hitbox.contains(&point) {
19124 return None;
19125 }
19126 let display_point = position_map.point_for_position(point).previous_valid;
19127 let anchor = position_map
19128 .snapshot
19129 .display_point_to_anchor(display_point, Bias::Left);
19130 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
19131 Some(utf16_offset.0)
19132 }
19133}
19134
19135trait SelectionExt {
19136 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
19137 fn spanned_rows(
19138 &self,
19139 include_end_if_at_line_start: bool,
19140 map: &DisplaySnapshot,
19141 ) -> Range<MultiBufferRow>;
19142}
19143
19144impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
19145 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
19146 let start = self
19147 .start
19148 .to_point(&map.buffer_snapshot)
19149 .to_display_point(map);
19150 let end = self
19151 .end
19152 .to_point(&map.buffer_snapshot)
19153 .to_display_point(map);
19154 if self.reversed {
19155 end..start
19156 } else {
19157 start..end
19158 }
19159 }
19160
19161 fn spanned_rows(
19162 &self,
19163 include_end_if_at_line_start: bool,
19164 map: &DisplaySnapshot,
19165 ) -> Range<MultiBufferRow> {
19166 let start = self.start.to_point(&map.buffer_snapshot);
19167 let mut end = self.end.to_point(&map.buffer_snapshot);
19168 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
19169 end.row -= 1;
19170 }
19171
19172 let buffer_start = map.prev_line_boundary(start).0;
19173 let buffer_end = map.next_line_boundary(end).0;
19174 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
19175 }
19176}
19177
19178impl<T: InvalidationRegion> InvalidationStack<T> {
19179 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
19180 where
19181 S: Clone + ToOffset,
19182 {
19183 while let Some(region) = self.last() {
19184 let all_selections_inside_invalidation_ranges =
19185 if selections.len() == region.ranges().len() {
19186 selections
19187 .iter()
19188 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
19189 .all(|(selection, invalidation_range)| {
19190 let head = selection.head().to_offset(buffer);
19191 invalidation_range.start <= head && invalidation_range.end >= head
19192 })
19193 } else {
19194 false
19195 };
19196
19197 if all_selections_inside_invalidation_ranges {
19198 break;
19199 } else {
19200 self.pop();
19201 }
19202 }
19203 }
19204}
19205
19206impl<T> Default for InvalidationStack<T> {
19207 fn default() -> Self {
19208 Self(Default::default())
19209 }
19210}
19211
19212impl<T> Deref for InvalidationStack<T> {
19213 type Target = Vec<T>;
19214
19215 fn deref(&self) -> &Self::Target {
19216 &self.0
19217 }
19218}
19219
19220impl<T> DerefMut for InvalidationStack<T> {
19221 fn deref_mut(&mut self) -> &mut Self::Target {
19222 &mut self.0
19223 }
19224}
19225
19226impl InvalidationRegion for SnippetState {
19227 fn ranges(&self) -> &[Range<Anchor>] {
19228 &self.ranges[self.active_index]
19229 }
19230}
19231
19232pub fn diagnostic_block_renderer(
19233 diagnostic: Diagnostic,
19234 max_message_rows: Option<u8>,
19235 allow_closing: bool,
19236) -> RenderBlock {
19237 let (text_without_backticks, code_ranges) =
19238 highlight_diagnostic_message(&diagnostic, max_message_rows);
19239
19240 Arc::new(move |cx: &mut BlockContext| {
19241 let group_id: SharedString = cx.block_id.to_string().into();
19242
19243 let mut text_style = cx.window.text_style().clone();
19244 text_style.color = diagnostic_style(diagnostic.severity, cx.theme().status());
19245 let theme_settings = ThemeSettings::get_global(cx);
19246 text_style.font_family = theme_settings.buffer_font.family.clone();
19247 text_style.font_style = theme_settings.buffer_font.style;
19248 text_style.font_features = theme_settings.buffer_font.features.clone();
19249 text_style.font_weight = theme_settings.buffer_font.weight;
19250
19251 let multi_line_diagnostic = diagnostic.message.contains('\n');
19252
19253 let buttons = |diagnostic: &Diagnostic| {
19254 if multi_line_diagnostic {
19255 v_flex()
19256 } else {
19257 h_flex()
19258 }
19259 .when(allow_closing, |div| {
19260 div.children(diagnostic.is_primary.then(|| {
19261 IconButton::new("close-block", IconName::XCircle)
19262 .icon_color(Color::Muted)
19263 .size(ButtonSize::Compact)
19264 .style(ButtonStyle::Transparent)
19265 .visible_on_hover(group_id.clone())
19266 .on_click(move |_click, window, cx| {
19267 window.dispatch_action(Box::new(Cancel), cx)
19268 })
19269 .tooltip(|window, cx| {
19270 Tooltip::for_action("Close Diagnostics", &Cancel, window, cx)
19271 })
19272 }))
19273 })
19274 .child(
19275 IconButton::new("copy-block", IconName::Copy)
19276 .icon_color(Color::Muted)
19277 .size(ButtonSize::Compact)
19278 .style(ButtonStyle::Transparent)
19279 .visible_on_hover(group_id.clone())
19280 .on_click({
19281 let message = diagnostic.message.clone();
19282 move |_click, _, cx| {
19283 cx.write_to_clipboard(ClipboardItem::new_string(message.clone()))
19284 }
19285 })
19286 .tooltip(Tooltip::text("Copy diagnostic message")),
19287 )
19288 };
19289
19290 let icon_size = buttons(&diagnostic).into_any_element().layout_as_root(
19291 AvailableSpace::min_size(),
19292 cx.window,
19293 cx.app,
19294 );
19295
19296 h_flex()
19297 .id(cx.block_id)
19298 .group(group_id.clone())
19299 .relative()
19300 .size_full()
19301 .block_mouse_down()
19302 .pl(cx.gutter_dimensions.width)
19303 .w(cx.max_width - cx.gutter_dimensions.full_width())
19304 .child(
19305 div()
19306 .flex()
19307 .w(cx.anchor_x - cx.gutter_dimensions.width - icon_size.width)
19308 .flex_shrink(),
19309 )
19310 .child(buttons(&diagnostic))
19311 .child(div().flex().flex_shrink_0().child(
19312 StyledText::new(text_without_backticks.clone()).with_default_highlights(
19313 &text_style,
19314 code_ranges.iter().map(|range| {
19315 (
19316 range.clone(),
19317 HighlightStyle {
19318 font_weight: Some(FontWeight::BOLD),
19319 ..Default::default()
19320 },
19321 )
19322 }),
19323 ),
19324 ))
19325 .into_any_element()
19326 })
19327}
19328
19329fn inline_completion_edit_text(
19330 current_snapshot: &BufferSnapshot,
19331 edits: &[(Range<Anchor>, String)],
19332 edit_preview: &EditPreview,
19333 include_deletions: bool,
19334 cx: &App,
19335) -> HighlightedText {
19336 let edits = edits
19337 .iter()
19338 .map(|(anchor, text)| {
19339 (
19340 anchor.start.text_anchor..anchor.end.text_anchor,
19341 text.clone(),
19342 )
19343 })
19344 .collect::<Vec<_>>();
19345
19346 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
19347}
19348
19349pub fn highlight_diagnostic_message(
19350 diagnostic: &Diagnostic,
19351 mut max_message_rows: Option<u8>,
19352) -> (SharedString, Vec<Range<usize>>) {
19353 let mut text_without_backticks = String::new();
19354 let mut code_ranges = Vec::new();
19355
19356 if let Some(source) = &diagnostic.source {
19357 text_without_backticks.push_str(source);
19358 code_ranges.push(0..source.len());
19359 text_without_backticks.push_str(": ");
19360 }
19361
19362 let mut prev_offset = 0;
19363 let mut in_code_block = false;
19364 let has_row_limit = max_message_rows.is_some();
19365 let mut newline_indices = diagnostic
19366 .message
19367 .match_indices('\n')
19368 .filter(|_| has_row_limit)
19369 .map(|(ix, _)| ix)
19370 .fuse()
19371 .peekable();
19372
19373 for (quote_ix, _) in diagnostic
19374 .message
19375 .match_indices('`')
19376 .chain([(diagnostic.message.len(), "")])
19377 {
19378 let mut first_newline_ix = None;
19379 let mut last_newline_ix = None;
19380 while let Some(newline_ix) = newline_indices.peek() {
19381 if *newline_ix < quote_ix {
19382 if first_newline_ix.is_none() {
19383 first_newline_ix = Some(*newline_ix);
19384 }
19385 last_newline_ix = Some(*newline_ix);
19386
19387 if let Some(rows_left) = &mut max_message_rows {
19388 if *rows_left == 0 {
19389 break;
19390 } else {
19391 *rows_left -= 1;
19392 }
19393 }
19394 let _ = newline_indices.next();
19395 } else {
19396 break;
19397 }
19398 }
19399 let prev_len = text_without_backticks.len();
19400 let new_text = &diagnostic.message[prev_offset..first_newline_ix.unwrap_or(quote_ix)];
19401 text_without_backticks.push_str(new_text);
19402 if in_code_block {
19403 code_ranges.push(prev_len..text_without_backticks.len());
19404 }
19405 prev_offset = last_newline_ix.unwrap_or(quote_ix) + 1;
19406 in_code_block = !in_code_block;
19407 if first_newline_ix.map_or(false, |newline_ix| newline_ix < quote_ix) {
19408 text_without_backticks.push_str("...");
19409 break;
19410 }
19411 }
19412
19413 (text_without_backticks.into(), code_ranges)
19414}
19415
19416fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
19417 match severity {
19418 DiagnosticSeverity::ERROR => colors.error,
19419 DiagnosticSeverity::WARNING => colors.warning,
19420 DiagnosticSeverity::INFORMATION => colors.info,
19421 DiagnosticSeverity::HINT => colors.info,
19422 _ => colors.ignored,
19423 }
19424}
19425
19426pub fn styled_runs_for_code_label<'a>(
19427 label: &'a CodeLabel,
19428 syntax_theme: &'a theme::SyntaxTheme,
19429) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
19430 let fade_out = HighlightStyle {
19431 fade_out: Some(0.35),
19432 ..Default::default()
19433 };
19434
19435 let mut prev_end = label.filter_range.end;
19436 label
19437 .runs
19438 .iter()
19439 .enumerate()
19440 .flat_map(move |(ix, (range, highlight_id))| {
19441 let style = if let Some(style) = highlight_id.style(syntax_theme) {
19442 style
19443 } else {
19444 return Default::default();
19445 };
19446 let mut muted_style = style;
19447 muted_style.highlight(fade_out);
19448
19449 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
19450 if range.start >= label.filter_range.end {
19451 if range.start > prev_end {
19452 runs.push((prev_end..range.start, fade_out));
19453 }
19454 runs.push((range.clone(), muted_style));
19455 } else if range.end <= label.filter_range.end {
19456 runs.push((range.clone(), style));
19457 } else {
19458 runs.push((range.start..label.filter_range.end, style));
19459 runs.push((label.filter_range.end..range.end, muted_style));
19460 }
19461 prev_end = cmp::max(prev_end, range.end);
19462
19463 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
19464 runs.push((prev_end..label.text.len(), fade_out));
19465 }
19466
19467 runs
19468 })
19469}
19470
19471pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
19472 let mut prev_index = 0;
19473 let mut prev_codepoint: Option<char> = None;
19474 text.char_indices()
19475 .chain([(text.len(), '\0')])
19476 .filter_map(move |(index, codepoint)| {
19477 let prev_codepoint = prev_codepoint.replace(codepoint)?;
19478 let is_boundary = index == text.len()
19479 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
19480 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
19481 if is_boundary {
19482 let chunk = &text[prev_index..index];
19483 prev_index = index;
19484 Some(chunk)
19485 } else {
19486 None
19487 }
19488 })
19489}
19490
19491pub trait RangeToAnchorExt: Sized {
19492 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
19493
19494 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
19495 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
19496 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
19497 }
19498}
19499
19500impl<T: ToOffset> RangeToAnchorExt for Range<T> {
19501 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
19502 let start_offset = self.start.to_offset(snapshot);
19503 let end_offset = self.end.to_offset(snapshot);
19504 if start_offset == end_offset {
19505 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
19506 } else {
19507 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
19508 }
19509 }
19510}
19511
19512pub trait RowExt {
19513 fn as_f32(&self) -> f32;
19514
19515 fn next_row(&self) -> Self;
19516
19517 fn previous_row(&self) -> Self;
19518
19519 fn minus(&self, other: Self) -> u32;
19520}
19521
19522impl RowExt for DisplayRow {
19523 fn as_f32(&self) -> f32 {
19524 self.0 as f32
19525 }
19526
19527 fn next_row(&self) -> Self {
19528 Self(self.0 + 1)
19529 }
19530
19531 fn previous_row(&self) -> Self {
19532 Self(self.0.saturating_sub(1))
19533 }
19534
19535 fn minus(&self, other: Self) -> u32 {
19536 self.0 - other.0
19537 }
19538}
19539
19540impl RowExt for MultiBufferRow {
19541 fn as_f32(&self) -> f32 {
19542 self.0 as f32
19543 }
19544
19545 fn next_row(&self) -> Self {
19546 Self(self.0 + 1)
19547 }
19548
19549 fn previous_row(&self) -> Self {
19550 Self(self.0.saturating_sub(1))
19551 }
19552
19553 fn minus(&self, other: Self) -> u32 {
19554 self.0 - other.0
19555 }
19556}
19557
19558trait RowRangeExt {
19559 type Row;
19560
19561 fn len(&self) -> usize;
19562
19563 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
19564}
19565
19566impl RowRangeExt for Range<MultiBufferRow> {
19567 type Row = MultiBufferRow;
19568
19569 fn len(&self) -> usize {
19570 (self.end.0 - self.start.0) as usize
19571 }
19572
19573 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
19574 (self.start.0..self.end.0).map(MultiBufferRow)
19575 }
19576}
19577
19578impl RowRangeExt for Range<DisplayRow> {
19579 type Row = DisplayRow;
19580
19581 fn len(&self) -> usize {
19582 (self.end.0 - self.start.0) as usize
19583 }
19584
19585 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
19586 (self.start.0..self.end.0).map(DisplayRow)
19587 }
19588}
19589
19590/// If select range has more than one line, we
19591/// just point the cursor to range.start.
19592fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
19593 if range.start.row == range.end.row {
19594 range
19595 } else {
19596 range.start..range.start
19597 }
19598}
19599pub struct KillRing(ClipboardItem);
19600impl Global for KillRing {}
19601
19602const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
19603
19604struct BreakpointPromptEditor {
19605 pub(crate) prompt: Entity<Editor>,
19606 editor: WeakEntity<Editor>,
19607 breakpoint_anchor: Anchor,
19608 kind: BreakpointKind,
19609 block_ids: HashSet<CustomBlockId>,
19610 gutter_dimensions: Arc<Mutex<GutterDimensions>>,
19611 _subscriptions: Vec<Subscription>,
19612}
19613
19614impl BreakpointPromptEditor {
19615 const MAX_LINES: u8 = 4;
19616
19617 fn new(
19618 editor: WeakEntity<Editor>,
19619 breakpoint_anchor: Anchor,
19620 kind: BreakpointKind,
19621 window: &mut Window,
19622 cx: &mut Context<Self>,
19623 ) -> Self {
19624 let buffer = cx.new(|cx| {
19625 Buffer::local(
19626 kind.log_message()
19627 .map(|msg| msg.to_string())
19628 .unwrap_or_default(),
19629 cx,
19630 )
19631 });
19632 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19633
19634 let prompt = cx.new(|cx| {
19635 let mut prompt = Editor::new(
19636 EditorMode::AutoHeight {
19637 max_lines: Self::MAX_LINES as usize,
19638 },
19639 buffer,
19640 None,
19641 window,
19642 cx,
19643 );
19644 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
19645 prompt.set_show_cursor_when_unfocused(false, cx);
19646 prompt.set_placeholder_text(
19647 "Message to log when breakpoint is hit. Expressions within {} are interpolated.",
19648 cx,
19649 );
19650
19651 prompt
19652 });
19653
19654 Self {
19655 prompt,
19656 editor,
19657 breakpoint_anchor,
19658 kind,
19659 gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
19660 block_ids: Default::default(),
19661 _subscriptions: vec![],
19662 }
19663 }
19664
19665 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
19666 self.block_ids.extend(block_ids)
19667 }
19668
19669 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
19670 if let Some(editor) = self.editor.upgrade() {
19671 let log_message = self
19672 .prompt
19673 .read(cx)
19674 .buffer
19675 .read(cx)
19676 .as_singleton()
19677 .expect("A multi buffer in breakpoint prompt isn't possible")
19678 .read(cx)
19679 .as_rope()
19680 .to_string();
19681
19682 editor.update(cx, |editor, cx| {
19683 editor.edit_breakpoint_at_anchor(
19684 self.breakpoint_anchor,
19685 self.kind.clone(),
19686 BreakpointEditAction::EditLogMessage(log_message.into()),
19687 cx,
19688 );
19689
19690 editor.remove_blocks(self.block_ids.clone(), None, cx);
19691 cx.focus_self(window);
19692 });
19693 }
19694 }
19695
19696 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
19697 self.editor
19698 .update(cx, |editor, cx| {
19699 editor.remove_blocks(self.block_ids.clone(), None, cx);
19700 window.focus(&editor.focus_handle);
19701 })
19702 .log_err();
19703 }
19704
19705 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
19706 let settings = ThemeSettings::get_global(cx);
19707 let text_style = TextStyle {
19708 color: if self.prompt.read(cx).read_only(cx) {
19709 cx.theme().colors().text_disabled
19710 } else {
19711 cx.theme().colors().text
19712 },
19713 font_family: settings.buffer_font.family.clone(),
19714 font_fallbacks: settings.buffer_font.fallbacks.clone(),
19715 font_size: settings.buffer_font_size(cx).into(),
19716 font_weight: settings.buffer_font.weight,
19717 line_height: relative(settings.buffer_line_height.value()),
19718 ..Default::default()
19719 };
19720 EditorElement::new(
19721 &self.prompt,
19722 EditorStyle {
19723 background: cx.theme().colors().editor_background,
19724 local_player: cx.theme().players().local(),
19725 text: text_style,
19726 ..Default::default()
19727 },
19728 )
19729 }
19730}
19731
19732impl Render for BreakpointPromptEditor {
19733 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
19734 let gutter_dimensions = *self.gutter_dimensions.lock();
19735 h_flex()
19736 .key_context("Editor")
19737 .bg(cx.theme().colors().editor_background)
19738 .border_y_1()
19739 .border_color(cx.theme().status().info_border)
19740 .size_full()
19741 .py(window.line_height() / 2.5)
19742 .on_action(cx.listener(Self::confirm))
19743 .on_action(cx.listener(Self::cancel))
19744 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
19745 .child(div().flex_1().child(self.render_prompt_editor(cx)))
19746 }
19747}
19748
19749impl Focusable for BreakpointPromptEditor {
19750 fn focus_handle(&self, cx: &App) -> FocusHandle {
19751 self.prompt.focus_handle(cx)
19752 }
19753}
19754
19755fn all_edits_insertions_or_deletions(
19756 edits: &Vec<(Range<Anchor>, String)>,
19757 snapshot: &MultiBufferSnapshot,
19758) -> bool {
19759 let mut all_insertions = true;
19760 let mut all_deletions = true;
19761
19762 for (range, new_text) in edits.iter() {
19763 let range_is_empty = range.to_offset(&snapshot).is_empty();
19764 let text_is_empty = new_text.is_empty();
19765
19766 if range_is_empty != text_is_empty {
19767 if range_is_empty {
19768 all_deletions = false;
19769 } else {
19770 all_insertions = false;
19771 }
19772 } else {
19773 return false;
19774 }
19775
19776 if !all_insertions && !all_deletions {
19777 return false;
19778 }
19779 }
19780 all_insertions || all_deletions
19781}
19782
19783struct MissingEditPredictionKeybindingTooltip;
19784
19785impl Render for MissingEditPredictionKeybindingTooltip {
19786 fn render(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
19787 ui::tooltip_container(window, cx, |container, _, cx| {
19788 container
19789 .flex_shrink_0()
19790 .max_w_80()
19791 .min_h(rems_from_px(124.))
19792 .justify_between()
19793 .child(
19794 v_flex()
19795 .flex_1()
19796 .text_ui_sm(cx)
19797 .child(Label::new("Conflict with Accept Keybinding"))
19798 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
19799 )
19800 .child(
19801 h_flex()
19802 .pb_1()
19803 .gap_1()
19804 .items_end()
19805 .w_full()
19806 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
19807 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
19808 }))
19809 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
19810 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
19811 })),
19812 )
19813 })
19814 }
19815}
19816
19817#[derive(Debug, Clone, Copy, PartialEq)]
19818pub struct LineHighlight {
19819 pub background: Background,
19820 pub border: Option<gpui::Hsla>,
19821}
19822
19823impl From<Hsla> for LineHighlight {
19824 fn from(hsla: Hsla) -> Self {
19825 Self {
19826 background: hsla.into(),
19827 border: None,
19828 }
19829 }
19830}
19831
19832impl From<Background> for LineHighlight {
19833 fn from(background: Background) -> Self {
19834 Self {
19835 background,
19836 border: None,
19837 }
19838 }
19839}