1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18mod code_context_menus;
19pub mod commit_tooltip;
20pub mod display_map;
21mod editor_settings;
22mod editor_settings_controls;
23mod element;
24mod git;
25mod highlight_matching_bracket;
26mod hover_links;
27mod hover_popover;
28mod indent_guides;
29mod inlay_hint_cache;
30pub mod items;
31mod jsx_tag_auto_close;
32mod linked_editing_ranges;
33mod lsp_ext;
34mod mouse_context_menu;
35pub mod movement;
36mod persistence;
37mod proposed_changes_editor;
38mod rust_analyzer_ext;
39pub mod scroll;
40mod selections_collection;
41pub mod tasks;
42
43#[cfg(test)]
44mod editor_tests;
45#[cfg(test)]
46mod inline_completion_tests;
47mod signature_help;
48#[cfg(any(test, feature = "test-support"))]
49pub mod test;
50
51pub(crate) use actions::*;
52pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
53use aho_corasick::AhoCorasick;
54use anyhow::{anyhow, Context as _, Result};
55use blink_manager::BlinkManager;
56use buffer_diff::DiffHunkStatus;
57use client::{Collaborator, ParticipantIndex};
58use clock::ReplicaId;
59use collections::{BTreeMap, HashMap, HashSet, VecDeque};
60use convert_case::{Case, Casing};
61use display_map::*;
62pub use display_map::{DisplayPoint, FoldPlaceholder};
63pub use editor_settings::{
64 CurrentLineHighlight, EditorSettings, ScrollBeyondLastLine, SearchSettings, ShowScrollbar,
65};
66pub use editor_settings_controls::*;
67use element::{layout_line, AcceptEditPredictionBinding, LineWithInvisibles, PositionMap};
68pub use element::{
69 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
70};
71use feature_flags::{Debugger, FeatureFlagAppExt};
72use futures::{
73 future::{self, join, Shared},
74 FutureExt,
75};
76use fuzzy::StringMatchCandidate;
77
78use ::git::Restore;
79use code_context_menus::{
80 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
81 CompletionsMenu, ContextMenuOrigin,
82};
83use git::blame::GitBlame;
84use gpui::{
85 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size, Action, Animation,
86 AnimationExt, AnyElement, App, AppContext, AsyncWindowContext, AvailableSpace, Background,
87 Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context, DispatchPhase, Edges, Entity,
88 EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent, Focusable, FontId, FontWeight,
89 Global, HighlightStyle, Hsla, KeyContext, Modifiers, MouseButton, MouseDownEvent, PaintQuad,
90 ParentElement, Pixels, Render, SharedString, Size, Stateful, Styled, StyledText, Subscription,
91 Task, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle,
92 WeakEntity, WeakFocusHandle, Window,
93};
94use highlight_matching_bracket::refresh_matching_bracket_highlights;
95use hover_links::{find_file, HoverLink, HoveredLinkState, InlayHighlight};
96use hover_popover::{hide_hover, HoverState};
97use indent_guides::ActiveIndentGuidesState;
98use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
99pub use inline_completion::Direction;
100use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
101pub use items::MAX_TAB_TITLE_LEN;
102use itertools::Itertools;
103use language::{
104 language_settings::{
105 self, all_language_settings, language_settings, InlayHintSettings, RewrapBehavior,
106 WordsCompletionMode,
107 },
108 point_from_lsp, text_diff_with_options, AutoindentMode, BracketMatch, BracketPair, Buffer,
109 Capability, CharKind, CodeLabel, CursorShape, Diagnostic, DiffOptions, EditPredictionsMode,
110 EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point,
111 Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
112};
113use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange};
114use linked_editing_ranges::refresh_linked_ranges;
115use mouse_context_menu::MouseContextMenu;
116use persistence::DB;
117use project::{
118 debugger::breakpoint_store::{BreakpointEditAction, BreakpointStore, BreakpointStoreEvent},
119 ProjectPath,
120};
121
122pub use proposed_changes_editor::{
123 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
124};
125use smallvec::smallvec;
126use std::{cell::OnceCell, iter::Peekable};
127use task::{ResolvedTask, TaskTemplate, TaskVariables};
128
129pub use lsp::CompletionContext;
130use lsp::{
131 CodeActionKind, CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity,
132 InsertTextFormat, LanguageServerId, LanguageServerName,
133};
134
135use language::BufferSnapshot;
136use movement::TextLayoutDetails;
137pub use multi_buffer::{
138 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, RowInfo,
139 ToOffset, ToPoint,
140};
141use multi_buffer::{
142 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
143 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
144};
145use parking_lot::Mutex;
146use project::{
147 debugger::breakpoint_store::{Breakpoint, BreakpointKind},
148 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
149 project_settings::{GitGutterSetting, ProjectSettings},
150 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
151 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
152 TaskSourceKind,
153};
154use rand::prelude::*;
155use rpc::{proto::*, ErrorExt};
156use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
157use selections_collection::{
158 resolve_selections, MutableSelectionsCollection, SelectionsCollection,
159};
160use serde::{Deserialize, Serialize};
161use settings::{update_settings_file, Settings, SettingsLocation, SettingsStore};
162use smallvec::SmallVec;
163use snippet::Snippet;
164use std::sync::Arc;
165use std::{
166 any::TypeId,
167 borrow::Cow,
168 cell::RefCell,
169 cmp::{self, Ordering, Reverse},
170 mem,
171 num::NonZeroU32,
172 ops::{ControlFlow, Deref, DerefMut, Not as _, Range, RangeInclusive},
173 path::{Path, PathBuf},
174 rc::Rc,
175 time::{Duration, Instant},
176};
177pub use sum_tree::Bias;
178use sum_tree::TreeMap;
179use text::{BufferId, OffsetUtf16, Rope};
180use theme::{
181 observe_buffer_font_size_adjustment, ActiveTheme, PlayerColor, StatusColors, SyntaxTheme,
182 ThemeColors, ThemeSettings,
183};
184use ui::{
185 h_flex, prelude::*, ButtonSize, ButtonStyle, Disclosure, IconButton, IconName, IconSize, Key,
186 Tooltip,
187};
188use util::{maybe, post_inc, RangeExt, ResultExt, TryFutureExt};
189use workspace::{
190 item::{ItemHandle, PreviewTabsSettings},
191 ItemId, RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME,
192};
193use workspace::{
194 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
195 WorkspaceSettings,
196};
197use workspace::{
198 searchable::SearchEvent, ItemNavHistory, SplitDirection, ViewId, Workspace, WorkspaceId,
199};
200use workspace::{Item as WorkspaceItem, OpenInTerminal, OpenTerminal, TabBarSettings, Toast};
201
202use crate::hover_links::{find_url, find_url_from_range};
203use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
204
205pub const FILE_HEADER_HEIGHT: u32 = 2;
206pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
207pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
208const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
209const MAX_LINE_LEN: usize = 1024;
210const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
211const MAX_SELECTION_HISTORY_LEN: usize = 1024;
212pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
213#[doc(hidden)]
214pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
215
216pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
217pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
218pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
219
220pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
221pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
222pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
223
224const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
225 alt: true,
226 shift: true,
227 control: false,
228 platform: false,
229 function: false,
230};
231
232#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
233pub enum InlayId {
234 InlineCompletion(usize),
235 Hint(usize),
236}
237
238impl InlayId {
239 fn id(&self) -> usize {
240 match self {
241 Self::InlineCompletion(id) => *id,
242 Self::Hint(id) => *id,
243 }
244 }
245}
246
247pub enum DebugCurrentRowHighlight {}
248enum DocumentHighlightRead {}
249enum DocumentHighlightWrite {}
250enum InputComposition {}
251enum SelectedTextHighlight {}
252
253#[derive(Debug, Copy, Clone, PartialEq, Eq)]
254pub enum Navigated {
255 Yes,
256 No,
257}
258
259impl Navigated {
260 pub fn from_bool(yes: bool) -> Navigated {
261 if yes {
262 Navigated::Yes
263 } else {
264 Navigated::No
265 }
266 }
267}
268
269#[derive(Debug, Clone, PartialEq, Eq)]
270enum DisplayDiffHunk {
271 Folded {
272 display_row: DisplayRow,
273 },
274 Unfolded {
275 is_created_file: bool,
276 diff_base_byte_range: Range<usize>,
277 display_row_range: Range<DisplayRow>,
278 multi_buffer_range: Range<Anchor>,
279 status: DiffHunkStatus,
280 },
281}
282
283pub fn init_settings(cx: &mut App) {
284 EditorSettings::register(cx);
285}
286
287pub fn init(cx: &mut App) {
288 init_settings(cx);
289
290 workspace::register_project_item::<Editor>(cx);
291 workspace::FollowableViewRegistry::register::<Editor>(cx);
292 workspace::register_serializable_item::<Editor>(cx);
293
294 cx.observe_new(
295 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
296 workspace.register_action(Editor::new_file);
297 workspace.register_action(Editor::new_file_vertical);
298 workspace.register_action(Editor::new_file_horizontal);
299 workspace.register_action(Editor::cancel_language_server_work);
300 },
301 )
302 .detach();
303
304 cx.on_action(move |_: &workspace::NewFile, cx| {
305 let app_state = workspace::AppState::global(cx);
306 if let Some(app_state) = app_state.upgrade() {
307 workspace::open_new(
308 Default::default(),
309 app_state,
310 cx,
311 |workspace, window, cx| {
312 Editor::new_file(workspace, &Default::default(), window, cx)
313 },
314 )
315 .detach();
316 }
317 });
318 cx.on_action(move |_: &workspace::NewWindow, cx| {
319 let app_state = workspace::AppState::global(cx);
320 if let Some(app_state) = app_state.upgrade() {
321 workspace::open_new(
322 Default::default(),
323 app_state,
324 cx,
325 |workspace, window, cx| {
326 cx.activate(true);
327 Editor::new_file(workspace, &Default::default(), window, cx)
328 },
329 )
330 .detach();
331 }
332 });
333}
334
335pub struct SearchWithinRange;
336
337trait InvalidationRegion {
338 fn ranges(&self) -> &[Range<Anchor>];
339}
340
341#[derive(Clone, Debug, PartialEq)]
342pub enum SelectPhase {
343 Begin {
344 position: DisplayPoint,
345 add: bool,
346 click_count: usize,
347 },
348 BeginColumnar {
349 position: DisplayPoint,
350 reset: bool,
351 goal_column: u32,
352 },
353 Extend {
354 position: DisplayPoint,
355 click_count: usize,
356 },
357 Update {
358 position: DisplayPoint,
359 goal_column: u32,
360 scroll_delta: gpui::Point<f32>,
361 },
362 End,
363}
364
365#[derive(Clone, Debug)]
366pub enum SelectMode {
367 Character,
368 Word(Range<Anchor>),
369 Line(Range<Anchor>),
370 All,
371}
372
373#[derive(Copy, Clone, PartialEq, Eq, Debug)]
374pub enum EditorMode {
375 SingleLine { auto_width: bool },
376 AutoHeight { max_lines: usize },
377 Full,
378}
379
380#[derive(Copy, Clone, Debug)]
381pub enum SoftWrap {
382 /// Prefer not to wrap at all.
383 ///
384 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
385 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
386 GitDiff,
387 /// Prefer a single line generally, unless an overly long line is encountered.
388 None,
389 /// Soft wrap lines that exceed the editor width.
390 EditorWidth,
391 /// Soft wrap lines at the preferred line length.
392 Column(u32),
393 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
394 Bounded(u32),
395}
396
397#[derive(Clone)]
398pub struct EditorStyle {
399 pub background: Hsla,
400 pub local_player: PlayerColor,
401 pub text: TextStyle,
402 pub scrollbar_width: Pixels,
403 pub syntax: Arc<SyntaxTheme>,
404 pub status: StatusColors,
405 pub inlay_hints_style: HighlightStyle,
406 pub inline_completion_styles: InlineCompletionStyles,
407 pub unnecessary_code_fade: f32,
408}
409
410impl Default for EditorStyle {
411 fn default() -> Self {
412 Self {
413 background: Hsla::default(),
414 local_player: PlayerColor::default(),
415 text: TextStyle::default(),
416 scrollbar_width: Pixels::default(),
417 syntax: Default::default(),
418 // HACK: Status colors don't have a real default.
419 // We should look into removing the status colors from the editor
420 // style and retrieve them directly from the theme.
421 status: StatusColors::dark(),
422 inlay_hints_style: HighlightStyle::default(),
423 inline_completion_styles: InlineCompletionStyles {
424 insertion: HighlightStyle::default(),
425 whitespace: HighlightStyle::default(),
426 },
427 unnecessary_code_fade: Default::default(),
428 }
429 }
430}
431
432pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
433 let show_background = language_settings::language_settings(None, None, cx)
434 .inlay_hints
435 .show_background;
436
437 HighlightStyle {
438 color: Some(cx.theme().status().hint),
439 background_color: show_background.then(|| cx.theme().status().hint_background),
440 ..HighlightStyle::default()
441 }
442}
443
444pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
445 InlineCompletionStyles {
446 insertion: HighlightStyle {
447 color: Some(cx.theme().status().predictive),
448 ..HighlightStyle::default()
449 },
450 whitespace: HighlightStyle {
451 background_color: Some(cx.theme().status().created_background),
452 ..HighlightStyle::default()
453 },
454 }
455}
456
457type CompletionId = usize;
458
459pub(crate) enum EditDisplayMode {
460 TabAccept,
461 DiffPopover,
462 Inline,
463}
464
465enum InlineCompletion {
466 Edit {
467 edits: Vec<(Range<Anchor>, String)>,
468 edit_preview: Option<EditPreview>,
469 display_mode: EditDisplayMode,
470 snapshot: BufferSnapshot,
471 },
472 Move {
473 target: Anchor,
474 snapshot: BufferSnapshot,
475 },
476}
477
478struct InlineCompletionState {
479 inlay_ids: Vec<InlayId>,
480 completion: InlineCompletion,
481 completion_id: Option<SharedString>,
482 invalidation_range: Range<Anchor>,
483}
484
485enum EditPredictionSettings {
486 Disabled,
487 Enabled {
488 show_in_menu: bool,
489 preview_requires_modifier: bool,
490 },
491}
492
493enum InlineCompletionHighlight {}
494
495#[derive(Debug, Clone)]
496struct InlineDiagnostic {
497 message: SharedString,
498 group_id: usize,
499 is_primary: bool,
500 start: Point,
501 severity: DiagnosticSeverity,
502}
503
504pub enum MenuInlineCompletionsPolicy {
505 Never,
506 ByProvider,
507}
508
509pub enum EditPredictionPreview {
510 /// Modifier is not pressed
511 Inactive { released_too_fast: bool },
512 /// Modifier pressed
513 Active {
514 since: Instant,
515 previous_scroll_position: Option<ScrollAnchor>,
516 },
517}
518
519impl EditPredictionPreview {
520 pub fn released_too_fast(&self) -> bool {
521 match self {
522 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
523 EditPredictionPreview::Active { .. } => false,
524 }
525 }
526
527 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
528 if let EditPredictionPreview::Active {
529 previous_scroll_position,
530 ..
531 } = self
532 {
533 *previous_scroll_position = scroll_position;
534 }
535 }
536}
537
538#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
539struct EditorActionId(usize);
540
541impl EditorActionId {
542 pub fn post_inc(&mut self) -> Self {
543 let answer = self.0;
544
545 *self = Self(answer + 1);
546
547 Self(answer)
548 }
549}
550
551// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
552// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
553
554type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
555type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
556
557#[derive(Default)]
558struct ScrollbarMarkerState {
559 scrollbar_size: Size<Pixels>,
560 dirty: bool,
561 markers: Arc<[PaintQuad]>,
562 pending_refresh: Option<Task<Result<()>>>,
563}
564
565impl ScrollbarMarkerState {
566 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
567 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
568 }
569}
570
571#[derive(Clone, Debug)]
572struct RunnableTasks {
573 templates: Vec<(TaskSourceKind, TaskTemplate)>,
574 offset: multi_buffer::Anchor,
575 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
576 column: u32,
577 // Values of all named captures, including those starting with '_'
578 extra_variables: HashMap<String, String>,
579 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
580 context_range: Range<BufferOffset>,
581}
582
583impl RunnableTasks {
584 fn resolve<'a>(
585 &'a self,
586 cx: &'a task::TaskContext,
587 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
588 self.templates.iter().filter_map(|(kind, template)| {
589 template
590 .resolve_task(&kind.to_id_base(), cx)
591 .map(|task| (kind.clone(), task))
592 })
593 }
594}
595
596#[derive(Clone)]
597struct ResolvedTasks {
598 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
599 position: Anchor,
600}
601
602#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
603struct BufferOffset(usize);
604
605// Addons allow storing per-editor state in other crates (e.g. Vim)
606pub trait Addon: 'static {
607 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
608
609 fn render_buffer_header_controls(
610 &self,
611 _: &ExcerptInfo,
612 _: &Window,
613 _: &App,
614 ) -> Option<AnyElement> {
615 None
616 }
617
618 fn to_any(&self) -> &dyn std::any::Any;
619}
620
621/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
622///
623/// See the [module level documentation](self) for more information.
624pub struct Editor {
625 focus_handle: FocusHandle,
626 last_focused_descendant: Option<WeakFocusHandle>,
627 /// The text buffer being edited
628 buffer: Entity<MultiBuffer>,
629 /// Map of how text in the buffer should be displayed.
630 /// Handles soft wraps, folds, fake inlay text insertions, etc.
631 pub display_map: Entity<DisplayMap>,
632 pub selections: SelectionsCollection,
633 pub scroll_manager: ScrollManager,
634 /// When inline assist editors are linked, they all render cursors because
635 /// typing enters text into each of them, even the ones that aren't focused.
636 pub(crate) show_cursor_when_unfocused: bool,
637 columnar_selection_tail: Option<Anchor>,
638 add_selections_state: Option<AddSelectionsState>,
639 select_next_state: Option<SelectNextState>,
640 select_prev_state: Option<SelectNextState>,
641 selection_history: SelectionHistory,
642 autoclose_regions: Vec<AutocloseRegion>,
643 snippet_stack: InvalidationStack<SnippetState>,
644 select_larger_syntax_node_stack: Vec<Box<[Selection<usize>]>>,
645 ime_transaction: Option<TransactionId>,
646 active_diagnostics: Option<ActiveDiagnosticGroup>,
647 show_inline_diagnostics: bool,
648 inline_diagnostics_update: Task<()>,
649 inline_diagnostics_enabled: bool,
650 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
651 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
652 hard_wrap: Option<usize>,
653
654 // TODO: make this a access method
655 pub project: Option<Entity<Project>>,
656 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
657 completion_provider: Option<Box<dyn CompletionProvider>>,
658 collaboration_hub: Option<Box<dyn CollaborationHub>>,
659 blink_manager: Entity<BlinkManager>,
660 show_cursor_names: bool,
661 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
662 pub show_local_selections: bool,
663 mode: EditorMode,
664 show_breadcrumbs: bool,
665 show_gutter: bool,
666 show_scrollbars: bool,
667 show_line_numbers: Option<bool>,
668 use_relative_line_numbers: Option<bool>,
669 show_git_diff_gutter: Option<bool>,
670 show_code_actions: Option<bool>,
671 show_runnables: Option<bool>,
672 show_breakpoints: Option<bool>,
673 show_wrap_guides: Option<bool>,
674 show_indent_guides: Option<bool>,
675 placeholder_text: Option<Arc<str>>,
676 highlight_order: usize,
677 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
678 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
679 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
680 scrollbar_marker_state: ScrollbarMarkerState,
681 active_indent_guides_state: ActiveIndentGuidesState,
682 nav_history: Option<ItemNavHistory>,
683 context_menu: RefCell<Option<CodeContextMenu>>,
684 mouse_context_menu: Option<MouseContextMenu>,
685 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
686 signature_help_state: SignatureHelpState,
687 auto_signature_help: Option<bool>,
688 find_all_references_task_sources: Vec<Anchor>,
689 next_completion_id: CompletionId,
690 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
691 code_actions_task: Option<Task<Result<()>>>,
692 selection_highlight_task: Option<Task<()>>,
693 document_highlights_task: Option<Task<()>>,
694 linked_editing_range_task: Option<Task<Option<()>>>,
695 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
696 pending_rename: Option<RenameState>,
697 searchable: bool,
698 cursor_shape: CursorShape,
699 current_line_highlight: Option<CurrentLineHighlight>,
700 collapse_matches: bool,
701 autoindent_mode: Option<AutoindentMode>,
702 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
703 input_enabled: bool,
704 use_modal_editing: bool,
705 read_only: bool,
706 leader_peer_id: Option<PeerId>,
707 remote_id: Option<ViewId>,
708 hover_state: HoverState,
709 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
710 gutter_hovered: bool,
711 hovered_link_state: Option<HoveredLinkState>,
712 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
713 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
714 active_inline_completion: Option<InlineCompletionState>,
715 /// Used to prevent flickering as the user types while the menu is open
716 stale_inline_completion_in_menu: Option<InlineCompletionState>,
717 edit_prediction_settings: EditPredictionSettings,
718 inline_completions_hidden_for_vim_mode: bool,
719 show_inline_completions_override: Option<bool>,
720 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
721 edit_prediction_preview: EditPredictionPreview,
722 edit_prediction_indent_conflict: bool,
723 edit_prediction_requires_modifier_in_indent_conflict: bool,
724 inlay_hint_cache: InlayHintCache,
725 next_inlay_id: usize,
726 _subscriptions: Vec<Subscription>,
727 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
728 gutter_dimensions: GutterDimensions,
729 style: Option<EditorStyle>,
730 text_style_refinement: Option<TextStyleRefinement>,
731 next_editor_action_id: EditorActionId,
732 editor_actions:
733 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
734 use_autoclose: bool,
735 use_auto_surround: bool,
736 auto_replace_emoji_shortcode: bool,
737 jsx_tag_auto_close_enabled_in_any_buffer: bool,
738 show_git_blame_gutter: bool,
739 show_git_blame_inline: bool,
740 show_git_blame_inline_delay_task: Option<Task<()>>,
741 git_blame_inline_tooltip: Option<WeakEntity<crate::commit_tooltip::CommitTooltip>>,
742 git_blame_inline_enabled: bool,
743 serialize_dirty_buffers: bool,
744 show_selection_menu: Option<bool>,
745 blame: Option<Entity<GitBlame>>,
746 blame_subscription: Option<Subscription>,
747 custom_context_menu: Option<
748 Box<
749 dyn 'static
750 + Fn(
751 &mut Self,
752 DisplayPoint,
753 &mut Window,
754 &mut Context<Self>,
755 ) -> Option<Entity<ui::ContextMenu>>,
756 >,
757 >,
758 last_bounds: Option<Bounds<Pixels>>,
759 last_position_map: Option<Rc<PositionMap>>,
760 expect_bounds_change: Option<Bounds<Pixels>>,
761 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
762 tasks_update_task: Option<Task<()>>,
763 pub breakpoint_store: Option<Entity<BreakpointStore>>,
764 /// Allow's a user to create a breakpoint by selecting this indicator
765 /// It should be None while a user is not hovering over the gutter
766 /// Otherwise it represents the point that the breakpoint will be shown
767 pub gutter_breakpoint_indicator: Option<DisplayPoint>,
768 in_project_search: bool,
769 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
770 breadcrumb_header: Option<String>,
771 focused_block: Option<FocusedBlock>,
772 next_scroll_position: NextScrollCursorCenterTopBottom,
773 addons: HashMap<TypeId, Box<dyn Addon>>,
774 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
775 load_diff_task: Option<Shared<Task<()>>>,
776 selection_mark_mode: bool,
777 toggle_fold_multiple_buffers: Task<()>,
778 _scroll_cursor_center_top_bottom_task: Task<()>,
779 serialize_selections: Task<()>,
780 serialize_folds: Task<()>,
781}
782
783#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
784enum NextScrollCursorCenterTopBottom {
785 #[default]
786 Center,
787 Top,
788 Bottom,
789}
790
791impl NextScrollCursorCenterTopBottom {
792 fn next(&self) -> Self {
793 match self {
794 Self::Center => Self::Top,
795 Self::Top => Self::Bottom,
796 Self::Bottom => Self::Center,
797 }
798 }
799}
800
801#[derive(Clone)]
802pub struct EditorSnapshot {
803 pub mode: EditorMode,
804 show_gutter: bool,
805 show_line_numbers: Option<bool>,
806 show_git_diff_gutter: Option<bool>,
807 show_code_actions: Option<bool>,
808 show_runnables: Option<bool>,
809 show_breakpoints: Option<bool>,
810 git_blame_gutter_max_author_length: Option<usize>,
811 pub display_snapshot: DisplaySnapshot,
812 pub placeholder_text: Option<Arc<str>>,
813 is_focused: bool,
814 scroll_anchor: ScrollAnchor,
815 ongoing_scroll: OngoingScroll,
816 current_line_highlight: CurrentLineHighlight,
817 gutter_hovered: bool,
818}
819
820const GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED: usize = 20;
821
822#[derive(Default, Debug, Clone, Copy)]
823pub struct GutterDimensions {
824 pub left_padding: Pixels,
825 pub right_padding: Pixels,
826 pub width: Pixels,
827 pub margin: Pixels,
828 pub git_blame_entries_width: Option<Pixels>,
829}
830
831impl GutterDimensions {
832 /// The full width of the space taken up by the gutter.
833 pub fn full_width(&self) -> Pixels {
834 self.margin + self.width
835 }
836
837 /// The width of the space reserved for the fold indicators,
838 /// use alongside 'justify_end' and `gutter_width` to
839 /// right align content with the line numbers
840 pub fn fold_area_width(&self) -> Pixels {
841 self.margin + self.right_padding
842 }
843}
844
845#[derive(Debug)]
846pub struct RemoteSelection {
847 pub replica_id: ReplicaId,
848 pub selection: Selection<Anchor>,
849 pub cursor_shape: CursorShape,
850 pub peer_id: PeerId,
851 pub line_mode: bool,
852 pub participant_index: Option<ParticipantIndex>,
853 pub user_name: Option<SharedString>,
854}
855
856#[derive(Clone, Debug)]
857struct SelectionHistoryEntry {
858 selections: Arc<[Selection<Anchor>]>,
859 select_next_state: Option<SelectNextState>,
860 select_prev_state: Option<SelectNextState>,
861 add_selections_state: Option<AddSelectionsState>,
862}
863
864enum SelectionHistoryMode {
865 Normal,
866 Undoing,
867 Redoing,
868}
869
870#[derive(Clone, PartialEq, Eq, Hash)]
871struct HoveredCursor {
872 replica_id: u16,
873 selection_id: usize,
874}
875
876impl Default for SelectionHistoryMode {
877 fn default() -> Self {
878 Self::Normal
879 }
880}
881
882#[derive(Default)]
883struct SelectionHistory {
884 #[allow(clippy::type_complexity)]
885 selections_by_transaction:
886 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
887 mode: SelectionHistoryMode,
888 undo_stack: VecDeque<SelectionHistoryEntry>,
889 redo_stack: VecDeque<SelectionHistoryEntry>,
890}
891
892impl SelectionHistory {
893 fn insert_transaction(
894 &mut self,
895 transaction_id: TransactionId,
896 selections: Arc<[Selection<Anchor>]>,
897 ) {
898 self.selections_by_transaction
899 .insert(transaction_id, (selections, None));
900 }
901
902 #[allow(clippy::type_complexity)]
903 fn transaction(
904 &self,
905 transaction_id: TransactionId,
906 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
907 self.selections_by_transaction.get(&transaction_id)
908 }
909
910 #[allow(clippy::type_complexity)]
911 fn transaction_mut(
912 &mut self,
913 transaction_id: TransactionId,
914 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
915 self.selections_by_transaction.get_mut(&transaction_id)
916 }
917
918 fn push(&mut self, entry: SelectionHistoryEntry) {
919 if !entry.selections.is_empty() {
920 match self.mode {
921 SelectionHistoryMode::Normal => {
922 self.push_undo(entry);
923 self.redo_stack.clear();
924 }
925 SelectionHistoryMode::Undoing => self.push_redo(entry),
926 SelectionHistoryMode::Redoing => self.push_undo(entry),
927 }
928 }
929 }
930
931 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
932 if self
933 .undo_stack
934 .back()
935 .map_or(true, |e| e.selections != entry.selections)
936 {
937 self.undo_stack.push_back(entry);
938 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
939 self.undo_stack.pop_front();
940 }
941 }
942 }
943
944 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
945 if self
946 .redo_stack
947 .back()
948 .map_or(true, |e| e.selections != entry.selections)
949 {
950 self.redo_stack.push_back(entry);
951 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
952 self.redo_stack.pop_front();
953 }
954 }
955 }
956}
957
958struct RowHighlight {
959 index: usize,
960 range: Range<Anchor>,
961 color: Hsla,
962 should_autoscroll: bool,
963}
964
965#[derive(Clone, Debug)]
966struct AddSelectionsState {
967 above: bool,
968 stack: Vec<usize>,
969}
970
971#[derive(Clone)]
972struct SelectNextState {
973 query: AhoCorasick,
974 wordwise: bool,
975 done: bool,
976}
977
978impl std::fmt::Debug for SelectNextState {
979 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
980 f.debug_struct(std::any::type_name::<Self>())
981 .field("wordwise", &self.wordwise)
982 .field("done", &self.done)
983 .finish()
984 }
985}
986
987#[derive(Debug)]
988struct AutocloseRegion {
989 selection_id: usize,
990 range: Range<Anchor>,
991 pair: BracketPair,
992}
993
994#[derive(Debug)]
995struct SnippetState {
996 ranges: Vec<Vec<Range<Anchor>>>,
997 active_index: usize,
998 choices: Vec<Option<Vec<String>>>,
999}
1000
1001#[doc(hidden)]
1002pub struct RenameState {
1003 pub range: Range<Anchor>,
1004 pub old_name: Arc<str>,
1005 pub editor: Entity<Editor>,
1006 block_id: CustomBlockId,
1007}
1008
1009struct InvalidationStack<T>(Vec<T>);
1010
1011struct RegisteredInlineCompletionProvider {
1012 provider: Arc<dyn InlineCompletionProviderHandle>,
1013 _subscription: Subscription,
1014}
1015
1016#[derive(Debug, PartialEq, Eq)]
1017struct ActiveDiagnosticGroup {
1018 primary_range: Range<Anchor>,
1019 primary_message: String,
1020 group_id: usize,
1021 blocks: HashMap<CustomBlockId, Diagnostic>,
1022 is_valid: bool,
1023}
1024
1025#[derive(Serialize, Deserialize, Clone, Debug)]
1026pub struct ClipboardSelection {
1027 /// The number of bytes in this selection.
1028 pub len: usize,
1029 /// Whether this was a full-line selection.
1030 pub is_entire_line: bool,
1031 /// The indentation of the first line when this content was originally copied.
1032 pub first_line_indent: u32,
1033}
1034
1035#[derive(Debug)]
1036pub(crate) struct NavigationData {
1037 cursor_anchor: Anchor,
1038 cursor_position: Point,
1039 scroll_anchor: ScrollAnchor,
1040 scroll_top_row: u32,
1041}
1042
1043#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1044pub enum GotoDefinitionKind {
1045 Symbol,
1046 Declaration,
1047 Type,
1048 Implementation,
1049}
1050
1051#[derive(Debug, Clone)]
1052enum InlayHintRefreshReason {
1053 ModifiersChanged(bool),
1054 Toggle(bool),
1055 SettingsChange(InlayHintSettings),
1056 NewLinesShown,
1057 BufferEdited(HashSet<Arc<Language>>),
1058 RefreshRequested,
1059 ExcerptsRemoved(Vec<ExcerptId>),
1060}
1061
1062impl InlayHintRefreshReason {
1063 fn description(&self) -> &'static str {
1064 match self {
1065 Self::ModifiersChanged(_) => "modifiers changed",
1066 Self::Toggle(_) => "toggle",
1067 Self::SettingsChange(_) => "settings change",
1068 Self::NewLinesShown => "new lines shown",
1069 Self::BufferEdited(_) => "buffer edited",
1070 Self::RefreshRequested => "refresh requested",
1071 Self::ExcerptsRemoved(_) => "excerpts removed",
1072 }
1073 }
1074}
1075
1076pub enum FormatTarget {
1077 Buffers,
1078 Ranges(Vec<Range<MultiBufferPoint>>),
1079}
1080
1081pub(crate) struct FocusedBlock {
1082 id: BlockId,
1083 focus_handle: WeakFocusHandle,
1084}
1085
1086#[derive(Clone)]
1087enum JumpData {
1088 MultiBufferRow {
1089 row: MultiBufferRow,
1090 line_offset_from_top: u32,
1091 },
1092 MultiBufferPoint {
1093 excerpt_id: ExcerptId,
1094 position: Point,
1095 anchor: text::Anchor,
1096 line_offset_from_top: u32,
1097 },
1098}
1099
1100pub enum MultibufferSelectionMode {
1101 First,
1102 All,
1103}
1104
1105#[derive(Clone, Copy, Debug, Default)]
1106pub struct RewrapOptions {
1107 pub override_language_settings: bool,
1108 pub preserve_existing_whitespace: bool,
1109}
1110
1111impl Editor {
1112 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1113 let buffer = cx.new(|cx| Buffer::local("", cx));
1114 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1115 Self::new(
1116 EditorMode::SingleLine { auto_width: false },
1117 buffer,
1118 None,
1119 window,
1120 cx,
1121 )
1122 }
1123
1124 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1125 let buffer = cx.new(|cx| Buffer::local("", cx));
1126 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1127 Self::new(EditorMode::Full, buffer, None, window, cx)
1128 }
1129
1130 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1131 let buffer = cx.new(|cx| Buffer::local("", cx));
1132 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1133 Self::new(
1134 EditorMode::SingleLine { auto_width: true },
1135 buffer,
1136 None,
1137 window,
1138 cx,
1139 )
1140 }
1141
1142 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1143 let buffer = cx.new(|cx| Buffer::local("", cx));
1144 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1145 Self::new(
1146 EditorMode::AutoHeight { max_lines },
1147 buffer,
1148 None,
1149 window,
1150 cx,
1151 )
1152 }
1153
1154 pub fn for_buffer(
1155 buffer: Entity<Buffer>,
1156 project: Option<Entity<Project>>,
1157 window: &mut Window,
1158 cx: &mut Context<Self>,
1159 ) -> Self {
1160 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1161 Self::new(EditorMode::Full, buffer, project, window, cx)
1162 }
1163
1164 pub fn for_multibuffer(
1165 buffer: Entity<MultiBuffer>,
1166 project: Option<Entity<Project>>,
1167 window: &mut Window,
1168 cx: &mut Context<Self>,
1169 ) -> Self {
1170 Self::new(EditorMode::Full, buffer, project, window, cx)
1171 }
1172
1173 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1174 let mut clone = Self::new(
1175 self.mode,
1176 self.buffer.clone(),
1177 self.project.clone(),
1178 window,
1179 cx,
1180 );
1181 self.display_map.update(cx, |display_map, cx| {
1182 let snapshot = display_map.snapshot(cx);
1183 clone.display_map.update(cx, |display_map, cx| {
1184 display_map.set_state(&snapshot, cx);
1185 });
1186 });
1187 clone.folds_did_change(cx);
1188 clone.selections.clone_state(&self.selections);
1189 clone.scroll_manager.clone_state(&self.scroll_manager);
1190 clone.searchable = self.searchable;
1191 clone
1192 }
1193
1194 pub fn new(
1195 mode: EditorMode,
1196 buffer: Entity<MultiBuffer>,
1197 project: Option<Entity<Project>>,
1198 window: &mut Window,
1199 cx: &mut Context<Self>,
1200 ) -> Self {
1201 let style = window.text_style();
1202 let font_size = style.font_size.to_pixels(window.rem_size());
1203 let editor = cx.entity().downgrade();
1204 let fold_placeholder = FoldPlaceholder {
1205 constrain_width: true,
1206 render: Arc::new(move |fold_id, fold_range, cx| {
1207 let editor = editor.clone();
1208 div()
1209 .id(fold_id)
1210 .bg(cx.theme().colors().ghost_element_background)
1211 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1212 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1213 .rounded_xs()
1214 .size_full()
1215 .cursor_pointer()
1216 .child("⋯")
1217 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1218 .on_click(move |_, _window, cx| {
1219 editor
1220 .update(cx, |editor, cx| {
1221 editor.unfold_ranges(
1222 &[fold_range.start..fold_range.end],
1223 true,
1224 false,
1225 cx,
1226 );
1227 cx.stop_propagation();
1228 })
1229 .ok();
1230 })
1231 .into_any()
1232 }),
1233 merge_adjacent: true,
1234 ..Default::default()
1235 };
1236 let display_map = cx.new(|cx| {
1237 DisplayMap::new(
1238 buffer.clone(),
1239 style.font(),
1240 font_size,
1241 None,
1242 FILE_HEADER_HEIGHT,
1243 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1244 fold_placeholder,
1245 cx,
1246 )
1247 });
1248
1249 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1250
1251 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1252
1253 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1254 .then(|| language_settings::SoftWrap::None);
1255
1256 let mut project_subscriptions = Vec::new();
1257 if mode == EditorMode::Full {
1258 if let Some(project) = project.as_ref() {
1259 project_subscriptions.push(cx.subscribe_in(
1260 project,
1261 window,
1262 |editor, _, event, window, cx| match event {
1263 project::Event::RefreshCodeLens => {
1264 // we always query lens with actions, without storing them, always refreshing them
1265 }
1266 project::Event::RefreshInlayHints => {
1267 editor
1268 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1269 }
1270 project::Event::SnippetEdit(id, snippet_edits) => {
1271 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1272 let focus_handle = editor.focus_handle(cx);
1273 if focus_handle.is_focused(window) {
1274 let snapshot = buffer.read(cx).snapshot();
1275 for (range, snippet) in snippet_edits {
1276 let editor_range =
1277 language::range_from_lsp(*range).to_offset(&snapshot);
1278 editor
1279 .insert_snippet(
1280 &[editor_range],
1281 snippet.clone(),
1282 window,
1283 cx,
1284 )
1285 .ok();
1286 }
1287 }
1288 }
1289 }
1290 _ => {}
1291 },
1292 ));
1293 if let Some(task_inventory) = project
1294 .read(cx)
1295 .task_store()
1296 .read(cx)
1297 .task_inventory()
1298 .cloned()
1299 {
1300 project_subscriptions.push(cx.observe_in(
1301 &task_inventory,
1302 window,
1303 |editor, _, window, cx| {
1304 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1305 },
1306 ));
1307 };
1308
1309 project_subscriptions.push(cx.subscribe_in(
1310 &project.read(cx).breakpoint_store(),
1311 window,
1312 |editor, _, event, window, cx| match event {
1313 BreakpointStoreEvent::ActiveDebugLineChanged => {
1314 editor.go_to_active_debug_line(window, cx);
1315 }
1316 _ => {}
1317 },
1318 ));
1319 }
1320 }
1321
1322 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1323
1324 let inlay_hint_settings =
1325 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1326 let focus_handle = cx.focus_handle();
1327 cx.on_focus(&focus_handle, window, Self::handle_focus)
1328 .detach();
1329 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1330 .detach();
1331 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1332 .detach();
1333 cx.on_blur(&focus_handle, window, Self::handle_blur)
1334 .detach();
1335
1336 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1337 Some(false)
1338 } else {
1339 None
1340 };
1341
1342 let breakpoint_store = match (mode, project.as_ref()) {
1343 (EditorMode::Full, Some(project)) => Some(project.read(cx).breakpoint_store()),
1344 _ => None,
1345 };
1346
1347 let mut code_action_providers = Vec::new();
1348 let mut load_uncommitted_diff = None;
1349 if let Some(project) = project.clone() {
1350 load_uncommitted_diff = Some(
1351 get_uncommitted_diff_for_buffer(
1352 &project,
1353 buffer.read(cx).all_buffers(),
1354 buffer.clone(),
1355 cx,
1356 )
1357 .shared(),
1358 );
1359 code_action_providers.push(Rc::new(project) as Rc<_>);
1360 }
1361
1362 let mut this = Self {
1363 focus_handle,
1364 show_cursor_when_unfocused: false,
1365 last_focused_descendant: None,
1366 buffer: buffer.clone(),
1367 display_map: display_map.clone(),
1368 selections,
1369 scroll_manager: ScrollManager::new(cx),
1370 columnar_selection_tail: None,
1371 add_selections_state: None,
1372 select_next_state: None,
1373 select_prev_state: None,
1374 selection_history: Default::default(),
1375 autoclose_regions: Default::default(),
1376 snippet_stack: Default::default(),
1377 select_larger_syntax_node_stack: Vec::new(),
1378 ime_transaction: Default::default(),
1379 active_diagnostics: None,
1380 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1381 inline_diagnostics_update: Task::ready(()),
1382 inline_diagnostics: Vec::new(),
1383 soft_wrap_mode_override,
1384 hard_wrap: None,
1385 completion_provider: project.clone().map(|project| Box::new(project) as _),
1386 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1387 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1388 project,
1389 blink_manager: blink_manager.clone(),
1390 show_local_selections: true,
1391 show_scrollbars: true,
1392 mode,
1393 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1394 show_gutter: mode == EditorMode::Full,
1395 show_line_numbers: None,
1396 use_relative_line_numbers: None,
1397 show_git_diff_gutter: None,
1398 show_code_actions: None,
1399 show_runnables: None,
1400 show_breakpoints: None,
1401 show_wrap_guides: None,
1402 show_indent_guides,
1403 placeholder_text: None,
1404 highlight_order: 0,
1405 highlighted_rows: HashMap::default(),
1406 background_highlights: Default::default(),
1407 gutter_highlights: TreeMap::default(),
1408 scrollbar_marker_state: ScrollbarMarkerState::default(),
1409 active_indent_guides_state: ActiveIndentGuidesState::default(),
1410 nav_history: None,
1411 context_menu: RefCell::new(None),
1412 mouse_context_menu: None,
1413 completion_tasks: Default::default(),
1414 signature_help_state: SignatureHelpState::default(),
1415 auto_signature_help: None,
1416 find_all_references_task_sources: Vec::new(),
1417 next_completion_id: 0,
1418 next_inlay_id: 0,
1419 code_action_providers,
1420 available_code_actions: Default::default(),
1421 code_actions_task: Default::default(),
1422 selection_highlight_task: Default::default(),
1423 document_highlights_task: Default::default(),
1424 linked_editing_range_task: Default::default(),
1425 pending_rename: Default::default(),
1426 searchable: true,
1427 cursor_shape: EditorSettings::get_global(cx)
1428 .cursor_shape
1429 .unwrap_or_default(),
1430 current_line_highlight: None,
1431 autoindent_mode: Some(AutoindentMode::EachLine),
1432 collapse_matches: false,
1433 workspace: None,
1434 input_enabled: true,
1435 use_modal_editing: mode == EditorMode::Full,
1436 read_only: false,
1437 use_autoclose: true,
1438 use_auto_surround: true,
1439 auto_replace_emoji_shortcode: false,
1440 jsx_tag_auto_close_enabled_in_any_buffer: false,
1441 leader_peer_id: None,
1442 remote_id: None,
1443 hover_state: Default::default(),
1444 pending_mouse_down: None,
1445 hovered_link_state: Default::default(),
1446 edit_prediction_provider: None,
1447 active_inline_completion: None,
1448 stale_inline_completion_in_menu: None,
1449 edit_prediction_preview: EditPredictionPreview::Inactive {
1450 released_too_fast: false,
1451 },
1452 inline_diagnostics_enabled: mode == EditorMode::Full,
1453 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1454
1455 gutter_hovered: false,
1456 pixel_position_of_newest_cursor: None,
1457 last_bounds: None,
1458 last_position_map: None,
1459 expect_bounds_change: None,
1460 gutter_dimensions: GutterDimensions::default(),
1461 style: None,
1462 show_cursor_names: false,
1463 hovered_cursors: Default::default(),
1464 next_editor_action_id: EditorActionId::default(),
1465 editor_actions: Rc::default(),
1466 inline_completions_hidden_for_vim_mode: false,
1467 show_inline_completions_override: None,
1468 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1469 edit_prediction_settings: EditPredictionSettings::Disabled,
1470 edit_prediction_indent_conflict: false,
1471 edit_prediction_requires_modifier_in_indent_conflict: true,
1472 custom_context_menu: None,
1473 show_git_blame_gutter: false,
1474 show_git_blame_inline: false,
1475 show_selection_menu: None,
1476 show_git_blame_inline_delay_task: None,
1477 git_blame_inline_tooltip: None,
1478 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1479 serialize_dirty_buffers: ProjectSettings::get_global(cx)
1480 .session
1481 .restore_unsaved_buffers,
1482 blame: None,
1483 blame_subscription: None,
1484 tasks: Default::default(),
1485
1486 breakpoint_store,
1487 gutter_breakpoint_indicator: None,
1488 _subscriptions: vec![
1489 cx.observe(&buffer, Self::on_buffer_changed),
1490 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1491 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1492 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1493 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1494 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1495 cx.observe_window_activation(window, |editor, window, cx| {
1496 let active = window.is_window_active();
1497 editor.blink_manager.update(cx, |blink_manager, cx| {
1498 if active {
1499 blink_manager.enable(cx);
1500 } else {
1501 blink_manager.disable(cx);
1502 }
1503 });
1504 }),
1505 ],
1506 tasks_update_task: None,
1507 linked_edit_ranges: Default::default(),
1508 in_project_search: false,
1509 previous_search_ranges: None,
1510 breadcrumb_header: None,
1511 focused_block: None,
1512 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1513 addons: HashMap::default(),
1514 registered_buffers: HashMap::default(),
1515 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1516 selection_mark_mode: false,
1517 toggle_fold_multiple_buffers: Task::ready(()),
1518 serialize_selections: Task::ready(()),
1519 serialize_folds: Task::ready(()),
1520 text_style_refinement: None,
1521 load_diff_task: load_uncommitted_diff,
1522 };
1523 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1524 this._subscriptions
1525 .push(cx.observe(breakpoints, |_, _, cx| {
1526 cx.notify();
1527 }));
1528 }
1529 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1530 this._subscriptions.extend(project_subscriptions);
1531
1532 this.end_selection(window, cx);
1533 this.scroll_manager.show_scrollbar(window, cx);
1534 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1535
1536 if mode == EditorMode::Full {
1537 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1538 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1539
1540 if this.git_blame_inline_enabled {
1541 this.git_blame_inline_enabled = true;
1542 this.start_git_blame_inline(false, window, cx);
1543 }
1544
1545 this.go_to_active_debug_line(window, cx);
1546
1547 if let Some(buffer) = buffer.read(cx).as_singleton() {
1548 if let Some(project) = this.project.as_ref() {
1549 let handle = project.update(cx, |project, cx| {
1550 project.register_buffer_with_language_servers(&buffer, cx)
1551 });
1552 this.registered_buffers
1553 .insert(buffer.read(cx).remote_id(), handle);
1554 }
1555 }
1556 }
1557
1558 this.report_editor_event("Editor Opened", None, cx);
1559 this
1560 }
1561
1562 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
1563 self.mouse_context_menu
1564 .as_ref()
1565 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
1566 }
1567
1568 fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
1569 self.key_context_internal(self.has_active_inline_completion(), window, cx)
1570 }
1571
1572 fn key_context_internal(
1573 &self,
1574 has_active_edit_prediction: bool,
1575 window: &Window,
1576 cx: &App,
1577 ) -> KeyContext {
1578 let mut key_context = KeyContext::new_with_defaults();
1579 key_context.add("Editor");
1580 let mode = match self.mode {
1581 EditorMode::SingleLine { .. } => "single_line",
1582 EditorMode::AutoHeight { .. } => "auto_height",
1583 EditorMode::Full => "full",
1584 };
1585
1586 if EditorSettings::jupyter_enabled(cx) {
1587 key_context.add("jupyter");
1588 }
1589
1590 key_context.set("mode", mode);
1591 if self.pending_rename.is_some() {
1592 key_context.add("renaming");
1593 }
1594
1595 match self.context_menu.borrow().as_ref() {
1596 Some(CodeContextMenu::Completions(_)) => {
1597 key_context.add("menu");
1598 key_context.add("showing_completions");
1599 }
1600 Some(CodeContextMenu::CodeActions(_)) => {
1601 key_context.add("menu");
1602 key_context.add("showing_code_actions")
1603 }
1604 None => {}
1605 }
1606
1607 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
1608 if !self.focus_handle(cx).contains_focused(window, cx)
1609 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
1610 {
1611 for addon in self.addons.values() {
1612 addon.extend_key_context(&mut key_context, cx)
1613 }
1614 }
1615
1616 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
1617 if let Some(extension) = singleton_buffer
1618 .read(cx)
1619 .file()
1620 .and_then(|file| file.path().extension()?.to_str())
1621 {
1622 key_context.set("extension", extension.to_string());
1623 }
1624 } else {
1625 key_context.add("multibuffer");
1626 }
1627
1628 if has_active_edit_prediction {
1629 if self.edit_prediction_in_conflict() {
1630 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
1631 } else {
1632 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
1633 key_context.add("copilot_suggestion");
1634 }
1635 }
1636
1637 if self.selection_mark_mode {
1638 key_context.add("selection_mode");
1639 }
1640
1641 key_context
1642 }
1643
1644 pub fn edit_prediction_in_conflict(&self) -> bool {
1645 if !self.show_edit_predictions_in_menu() {
1646 return false;
1647 }
1648
1649 let showing_completions = self
1650 .context_menu
1651 .borrow()
1652 .as_ref()
1653 .map_or(false, |context| {
1654 matches!(context, CodeContextMenu::Completions(_))
1655 });
1656
1657 showing_completions
1658 || self.edit_prediction_requires_modifier()
1659 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
1660 // bindings to insert tab characters.
1661 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
1662 }
1663
1664 pub fn accept_edit_prediction_keybind(
1665 &self,
1666 window: &Window,
1667 cx: &App,
1668 ) -> AcceptEditPredictionBinding {
1669 let key_context = self.key_context_internal(true, window, cx);
1670 let in_conflict = self.edit_prediction_in_conflict();
1671
1672 AcceptEditPredictionBinding(
1673 window
1674 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
1675 .into_iter()
1676 .filter(|binding| {
1677 !in_conflict
1678 || binding
1679 .keystrokes()
1680 .first()
1681 .map_or(false, |keystroke| keystroke.modifiers.modified())
1682 })
1683 .rev()
1684 .min_by_key(|binding| {
1685 binding
1686 .keystrokes()
1687 .first()
1688 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
1689 }),
1690 )
1691 }
1692
1693 pub fn new_file(
1694 workspace: &mut Workspace,
1695 _: &workspace::NewFile,
1696 window: &mut Window,
1697 cx: &mut Context<Workspace>,
1698 ) {
1699 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
1700 "Failed to create buffer",
1701 window,
1702 cx,
1703 |e, _, _| match e.error_code() {
1704 ErrorCode::RemoteUpgradeRequired => Some(format!(
1705 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1706 e.error_tag("required").unwrap_or("the latest version")
1707 )),
1708 _ => None,
1709 },
1710 );
1711 }
1712
1713 pub fn new_in_workspace(
1714 workspace: &mut Workspace,
1715 window: &mut Window,
1716 cx: &mut Context<Workspace>,
1717 ) -> Task<Result<Entity<Editor>>> {
1718 let project = workspace.project().clone();
1719 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1720
1721 cx.spawn_in(window, async move |workspace, cx| {
1722 let buffer = create.await?;
1723 workspace.update_in(cx, |workspace, window, cx| {
1724 let editor =
1725 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
1726 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
1727 editor
1728 })
1729 })
1730 }
1731
1732 fn new_file_vertical(
1733 workspace: &mut Workspace,
1734 _: &workspace::NewFileSplitVertical,
1735 window: &mut Window,
1736 cx: &mut Context<Workspace>,
1737 ) {
1738 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
1739 }
1740
1741 fn new_file_horizontal(
1742 workspace: &mut Workspace,
1743 _: &workspace::NewFileSplitHorizontal,
1744 window: &mut Window,
1745 cx: &mut Context<Workspace>,
1746 ) {
1747 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
1748 }
1749
1750 fn new_file_in_direction(
1751 workspace: &mut Workspace,
1752 direction: SplitDirection,
1753 window: &mut Window,
1754 cx: &mut Context<Workspace>,
1755 ) {
1756 let project = workspace.project().clone();
1757 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1758
1759 cx.spawn_in(window, async move |workspace, cx| {
1760 let buffer = create.await?;
1761 workspace.update_in(cx, move |workspace, window, cx| {
1762 workspace.split_item(
1763 direction,
1764 Box::new(
1765 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
1766 ),
1767 window,
1768 cx,
1769 )
1770 })?;
1771 anyhow::Ok(())
1772 })
1773 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
1774 match e.error_code() {
1775 ErrorCode::RemoteUpgradeRequired => Some(format!(
1776 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1777 e.error_tag("required").unwrap_or("the latest version")
1778 )),
1779 _ => None,
1780 }
1781 });
1782 }
1783
1784 pub fn leader_peer_id(&self) -> Option<PeerId> {
1785 self.leader_peer_id
1786 }
1787
1788 pub fn buffer(&self) -> &Entity<MultiBuffer> {
1789 &self.buffer
1790 }
1791
1792 pub fn workspace(&self) -> Option<Entity<Workspace>> {
1793 self.workspace.as_ref()?.0.upgrade()
1794 }
1795
1796 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
1797 self.buffer().read(cx).title(cx)
1798 }
1799
1800 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
1801 let git_blame_gutter_max_author_length = self
1802 .render_git_blame_gutter(cx)
1803 .then(|| {
1804 if let Some(blame) = self.blame.as_ref() {
1805 let max_author_length =
1806 blame.update(cx, |blame, cx| blame.max_author_length(cx));
1807 Some(max_author_length)
1808 } else {
1809 None
1810 }
1811 })
1812 .flatten();
1813
1814 EditorSnapshot {
1815 mode: self.mode,
1816 show_gutter: self.show_gutter,
1817 show_line_numbers: self.show_line_numbers,
1818 show_git_diff_gutter: self.show_git_diff_gutter,
1819 show_code_actions: self.show_code_actions,
1820 show_runnables: self.show_runnables,
1821 show_breakpoints: self.show_breakpoints,
1822 git_blame_gutter_max_author_length,
1823 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
1824 scroll_anchor: self.scroll_manager.anchor(),
1825 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
1826 placeholder_text: self.placeholder_text.clone(),
1827 is_focused: self.focus_handle.is_focused(window),
1828 current_line_highlight: self
1829 .current_line_highlight
1830 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
1831 gutter_hovered: self.gutter_hovered,
1832 }
1833 }
1834
1835 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
1836 self.buffer.read(cx).language_at(point, cx)
1837 }
1838
1839 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
1840 self.buffer.read(cx).read(cx).file_at(point).cloned()
1841 }
1842
1843 pub fn active_excerpt(
1844 &self,
1845 cx: &App,
1846 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
1847 self.buffer
1848 .read(cx)
1849 .excerpt_containing(self.selections.newest_anchor().head(), cx)
1850 }
1851
1852 pub fn mode(&self) -> EditorMode {
1853 self.mode
1854 }
1855
1856 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
1857 self.collaboration_hub.as_deref()
1858 }
1859
1860 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
1861 self.collaboration_hub = Some(hub);
1862 }
1863
1864 pub fn set_in_project_search(&mut self, in_project_search: bool) {
1865 self.in_project_search = in_project_search;
1866 }
1867
1868 pub fn set_custom_context_menu(
1869 &mut self,
1870 f: impl 'static
1871 + Fn(
1872 &mut Self,
1873 DisplayPoint,
1874 &mut Window,
1875 &mut Context<Self>,
1876 ) -> Option<Entity<ui::ContextMenu>>,
1877 ) {
1878 self.custom_context_menu = Some(Box::new(f))
1879 }
1880
1881 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
1882 self.completion_provider = provider;
1883 }
1884
1885 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
1886 self.semantics_provider.clone()
1887 }
1888
1889 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
1890 self.semantics_provider = provider;
1891 }
1892
1893 pub fn set_edit_prediction_provider<T>(
1894 &mut self,
1895 provider: Option<Entity<T>>,
1896 window: &mut Window,
1897 cx: &mut Context<Self>,
1898 ) where
1899 T: EditPredictionProvider,
1900 {
1901 self.edit_prediction_provider =
1902 provider.map(|provider| RegisteredInlineCompletionProvider {
1903 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
1904 if this.focus_handle.is_focused(window) {
1905 this.update_visible_inline_completion(window, cx);
1906 }
1907 }),
1908 provider: Arc::new(provider),
1909 });
1910 self.update_edit_prediction_settings(cx);
1911 self.refresh_inline_completion(false, false, window, cx);
1912 }
1913
1914 pub fn placeholder_text(&self) -> Option<&str> {
1915 self.placeholder_text.as_deref()
1916 }
1917
1918 pub fn set_placeholder_text(
1919 &mut self,
1920 placeholder_text: impl Into<Arc<str>>,
1921 cx: &mut Context<Self>,
1922 ) {
1923 let placeholder_text = Some(placeholder_text.into());
1924 if self.placeholder_text != placeholder_text {
1925 self.placeholder_text = placeholder_text;
1926 cx.notify();
1927 }
1928 }
1929
1930 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
1931 self.cursor_shape = cursor_shape;
1932
1933 // Disrupt blink for immediate user feedback that the cursor shape has changed
1934 self.blink_manager.update(cx, BlinkManager::show_cursor);
1935
1936 cx.notify();
1937 }
1938
1939 pub fn set_current_line_highlight(
1940 &mut self,
1941 current_line_highlight: Option<CurrentLineHighlight>,
1942 ) {
1943 self.current_line_highlight = current_line_highlight;
1944 }
1945
1946 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
1947 self.collapse_matches = collapse_matches;
1948 }
1949
1950 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
1951 let buffers = self.buffer.read(cx).all_buffers();
1952 let Some(project) = self.project.as_ref() else {
1953 return;
1954 };
1955 project.update(cx, |project, cx| {
1956 for buffer in buffers {
1957 self.registered_buffers
1958 .entry(buffer.read(cx).remote_id())
1959 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
1960 }
1961 })
1962 }
1963
1964 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
1965 if self.collapse_matches {
1966 return range.start..range.start;
1967 }
1968 range.clone()
1969 }
1970
1971 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
1972 if self.display_map.read(cx).clip_at_line_ends != clip {
1973 self.display_map
1974 .update(cx, |map, _| map.clip_at_line_ends = clip);
1975 }
1976 }
1977
1978 pub fn set_input_enabled(&mut self, input_enabled: bool) {
1979 self.input_enabled = input_enabled;
1980 }
1981
1982 pub fn set_inline_completions_hidden_for_vim_mode(
1983 &mut self,
1984 hidden: bool,
1985 window: &mut Window,
1986 cx: &mut Context<Self>,
1987 ) {
1988 if hidden != self.inline_completions_hidden_for_vim_mode {
1989 self.inline_completions_hidden_for_vim_mode = hidden;
1990 if hidden {
1991 self.update_visible_inline_completion(window, cx);
1992 } else {
1993 self.refresh_inline_completion(true, false, window, cx);
1994 }
1995 }
1996 }
1997
1998 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
1999 self.menu_inline_completions_policy = value;
2000 }
2001
2002 pub fn set_autoindent(&mut self, autoindent: bool) {
2003 if autoindent {
2004 self.autoindent_mode = Some(AutoindentMode::EachLine);
2005 } else {
2006 self.autoindent_mode = None;
2007 }
2008 }
2009
2010 pub fn read_only(&self, cx: &App) -> bool {
2011 self.read_only || self.buffer.read(cx).read_only()
2012 }
2013
2014 pub fn set_read_only(&mut self, read_only: bool) {
2015 self.read_only = read_only;
2016 }
2017
2018 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2019 self.use_autoclose = autoclose;
2020 }
2021
2022 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2023 self.use_auto_surround = auto_surround;
2024 }
2025
2026 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2027 self.auto_replace_emoji_shortcode = auto_replace;
2028 }
2029
2030 pub fn toggle_edit_predictions(
2031 &mut self,
2032 _: &ToggleEditPrediction,
2033 window: &mut Window,
2034 cx: &mut Context<Self>,
2035 ) {
2036 if self.show_inline_completions_override.is_some() {
2037 self.set_show_edit_predictions(None, window, cx);
2038 } else {
2039 let show_edit_predictions = !self.edit_predictions_enabled();
2040 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2041 }
2042 }
2043
2044 pub fn set_show_edit_predictions(
2045 &mut self,
2046 show_edit_predictions: Option<bool>,
2047 window: &mut Window,
2048 cx: &mut Context<Self>,
2049 ) {
2050 self.show_inline_completions_override = show_edit_predictions;
2051 self.update_edit_prediction_settings(cx);
2052
2053 if let Some(false) = show_edit_predictions {
2054 self.discard_inline_completion(false, cx);
2055 } else {
2056 self.refresh_inline_completion(false, true, window, cx);
2057 }
2058 }
2059
2060 fn inline_completions_disabled_in_scope(
2061 &self,
2062 buffer: &Entity<Buffer>,
2063 buffer_position: language::Anchor,
2064 cx: &App,
2065 ) -> bool {
2066 let snapshot = buffer.read(cx).snapshot();
2067 let settings = snapshot.settings_at(buffer_position, cx);
2068
2069 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2070 return false;
2071 };
2072
2073 scope.override_name().map_or(false, |scope_name| {
2074 settings
2075 .edit_predictions_disabled_in
2076 .iter()
2077 .any(|s| s == scope_name)
2078 })
2079 }
2080
2081 pub fn set_use_modal_editing(&mut self, to: bool) {
2082 self.use_modal_editing = to;
2083 }
2084
2085 pub fn use_modal_editing(&self) -> bool {
2086 self.use_modal_editing
2087 }
2088
2089 fn selections_did_change(
2090 &mut self,
2091 local: bool,
2092 old_cursor_position: &Anchor,
2093 show_completions: bool,
2094 window: &mut Window,
2095 cx: &mut Context<Self>,
2096 ) {
2097 window.invalidate_character_coordinates();
2098
2099 // Copy selections to primary selection buffer
2100 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2101 if local {
2102 let selections = self.selections.all::<usize>(cx);
2103 let buffer_handle = self.buffer.read(cx).read(cx);
2104
2105 let mut text = String::new();
2106 for (index, selection) in selections.iter().enumerate() {
2107 let text_for_selection = buffer_handle
2108 .text_for_range(selection.start..selection.end)
2109 .collect::<String>();
2110
2111 text.push_str(&text_for_selection);
2112 if index != selections.len() - 1 {
2113 text.push('\n');
2114 }
2115 }
2116
2117 if !text.is_empty() {
2118 cx.write_to_primary(ClipboardItem::new_string(text));
2119 }
2120 }
2121
2122 if self.focus_handle.is_focused(window) && self.leader_peer_id.is_none() {
2123 self.buffer.update(cx, |buffer, cx| {
2124 buffer.set_active_selections(
2125 &self.selections.disjoint_anchors(),
2126 self.selections.line_mode,
2127 self.cursor_shape,
2128 cx,
2129 )
2130 });
2131 }
2132 let display_map = self
2133 .display_map
2134 .update(cx, |display_map, cx| display_map.snapshot(cx));
2135 let buffer = &display_map.buffer_snapshot;
2136 self.add_selections_state = None;
2137 self.select_next_state = None;
2138 self.select_prev_state = None;
2139 self.select_larger_syntax_node_stack.clear();
2140 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2141 self.snippet_stack
2142 .invalidate(&self.selections.disjoint_anchors(), buffer);
2143 self.take_rename(false, window, cx);
2144
2145 let new_cursor_position = self.selections.newest_anchor().head();
2146
2147 self.push_to_nav_history(
2148 *old_cursor_position,
2149 Some(new_cursor_position.to_point(buffer)),
2150 cx,
2151 );
2152
2153 if local {
2154 let new_cursor_position = self.selections.newest_anchor().head();
2155 let mut context_menu = self.context_menu.borrow_mut();
2156 let completion_menu = match context_menu.as_ref() {
2157 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2158 _ => {
2159 *context_menu = None;
2160 None
2161 }
2162 };
2163 if let Some(buffer_id) = new_cursor_position.buffer_id {
2164 if !self.registered_buffers.contains_key(&buffer_id) {
2165 if let Some(project) = self.project.as_ref() {
2166 project.update(cx, |project, cx| {
2167 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2168 return;
2169 };
2170 self.registered_buffers.insert(
2171 buffer_id,
2172 project.register_buffer_with_language_servers(&buffer, cx),
2173 );
2174 })
2175 }
2176 }
2177 }
2178
2179 if let Some(completion_menu) = completion_menu {
2180 let cursor_position = new_cursor_position.to_offset(buffer);
2181 let (word_range, kind) =
2182 buffer.surrounding_word(completion_menu.initial_position, true);
2183 if kind == Some(CharKind::Word)
2184 && word_range.to_inclusive().contains(&cursor_position)
2185 {
2186 let mut completion_menu = completion_menu.clone();
2187 drop(context_menu);
2188
2189 let query = Self::completion_query(buffer, cursor_position);
2190 cx.spawn(async move |this, cx| {
2191 completion_menu
2192 .filter(query.as_deref(), cx.background_executor().clone())
2193 .await;
2194
2195 this.update(cx, |this, cx| {
2196 let mut context_menu = this.context_menu.borrow_mut();
2197 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2198 else {
2199 return;
2200 };
2201
2202 if menu.id > completion_menu.id {
2203 return;
2204 }
2205
2206 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2207 drop(context_menu);
2208 cx.notify();
2209 })
2210 })
2211 .detach();
2212
2213 if show_completions {
2214 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2215 }
2216 } else {
2217 drop(context_menu);
2218 self.hide_context_menu(window, cx);
2219 }
2220 } else {
2221 drop(context_menu);
2222 }
2223
2224 hide_hover(self, cx);
2225
2226 if old_cursor_position.to_display_point(&display_map).row()
2227 != new_cursor_position.to_display_point(&display_map).row()
2228 {
2229 self.available_code_actions.take();
2230 }
2231 self.refresh_code_actions(window, cx);
2232 self.refresh_document_highlights(cx);
2233 self.refresh_selected_text_highlights(window, cx);
2234 refresh_matching_bracket_highlights(self, window, cx);
2235 self.update_visible_inline_completion(window, cx);
2236 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2237 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2238 if self.git_blame_inline_enabled {
2239 self.start_inline_blame_timer(window, cx);
2240 }
2241 }
2242
2243 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2244 cx.emit(EditorEvent::SelectionsChanged { local });
2245
2246 let selections = &self.selections.disjoint;
2247 if selections.len() == 1 {
2248 cx.emit(SearchEvent::ActiveMatchChanged)
2249 }
2250 if local
2251 && self.is_singleton(cx)
2252 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
2253 {
2254 if let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) {
2255 let background_executor = cx.background_executor().clone();
2256 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2257 let snapshot = self.buffer().read(cx).snapshot(cx);
2258 let selections = selections.clone();
2259 self.serialize_selections = cx.background_spawn(async move {
2260 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2261 let selections = selections
2262 .iter()
2263 .map(|selection| {
2264 (
2265 selection.start.to_offset(&snapshot),
2266 selection.end.to_offset(&snapshot),
2267 )
2268 })
2269 .collect();
2270 DB.save_editor_selections(editor_id, workspace_id, selections)
2271 .await
2272 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2273 .log_err();
2274 });
2275 }
2276 }
2277
2278 cx.notify();
2279 }
2280
2281 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2282 if !self.is_singleton(cx)
2283 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2284 {
2285 return;
2286 }
2287
2288 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2289 return;
2290 };
2291 let background_executor = cx.background_executor().clone();
2292 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2293 let snapshot = self.buffer().read(cx).snapshot(cx);
2294 let folds = self.display_map.update(cx, |display_map, cx| {
2295 display_map
2296 .snapshot(cx)
2297 .folds_in_range(0..snapshot.len())
2298 .map(|fold| {
2299 (
2300 fold.range.start.to_offset(&snapshot),
2301 fold.range.end.to_offset(&snapshot),
2302 )
2303 })
2304 .collect()
2305 });
2306 self.serialize_folds = cx.background_spawn(async move {
2307 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2308 DB.save_editor_folds(editor_id, workspace_id, folds)
2309 .await
2310 .with_context(|| format!("persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"))
2311 .log_err();
2312 });
2313 }
2314
2315 pub fn sync_selections(
2316 &mut self,
2317 other: Entity<Editor>,
2318 cx: &mut Context<Self>,
2319 ) -> gpui::Subscription {
2320 let other_selections = other.read(cx).selections.disjoint.to_vec();
2321 self.selections.change_with(cx, |selections| {
2322 selections.select_anchors(other_selections);
2323 });
2324
2325 let other_subscription =
2326 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2327 EditorEvent::SelectionsChanged { local: true } => {
2328 let other_selections = other.read(cx).selections.disjoint.to_vec();
2329 if other_selections.is_empty() {
2330 return;
2331 }
2332 this.selections.change_with(cx, |selections| {
2333 selections.select_anchors(other_selections);
2334 });
2335 }
2336 _ => {}
2337 });
2338
2339 let this_subscription =
2340 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2341 EditorEvent::SelectionsChanged { local: true } => {
2342 let these_selections = this.selections.disjoint.to_vec();
2343 if these_selections.is_empty() {
2344 return;
2345 }
2346 other.update(cx, |other_editor, cx| {
2347 other_editor.selections.change_with(cx, |selections| {
2348 selections.select_anchors(these_selections);
2349 })
2350 });
2351 }
2352 _ => {}
2353 });
2354
2355 Subscription::join(other_subscription, this_subscription)
2356 }
2357
2358 pub fn change_selections<R>(
2359 &mut self,
2360 autoscroll: Option<Autoscroll>,
2361 window: &mut Window,
2362 cx: &mut Context<Self>,
2363 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2364 ) -> R {
2365 self.change_selections_inner(autoscroll, true, window, cx, change)
2366 }
2367
2368 fn change_selections_inner<R>(
2369 &mut self,
2370 autoscroll: Option<Autoscroll>,
2371 request_completions: bool,
2372 window: &mut Window,
2373 cx: &mut Context<Self>,
2374 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2375 ) -> R {
2376 let old_cursor_position = self.selections.newest_anchor().head();
2377 self.push_to_selection_history();
2378
2379 let (changed, result) = self.selections.change_with(cx, change);
2380
2381 if changed {
2382 if let Some(autoscroll) = autoscroll {
2383 self.request_autoscroll(autoscroll, cx);
2384 }
2385 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2386
2387 if self.should_open_signature_help_automatically(
2388 &old_cursor_position,
2389 self.signature_help_state.backspace_pressed(),
2390 cx,
2391 ) {
2392 self.show_signature_help(&ShowSignatureHelp, window, cx);
2393 }
2394 self.signature_help_state.set_backspace_pressed(false);
2395 }
2396
2397 result
2398 }
2399
2400 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2401 where
2402 I: IntoIterator<Item = (Range<S>, T)>,
2403 S: ToOffset,
2404 T: Into<Arc<str>>,
2405 {
2406 if self.read_only(cx) {
2407 return;
2408 }
2409
2410 self.buffer
2411 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2412 }
2413
2414 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2415 where
2416 I: IntoIterator<Item = (Range<S>, T)>,
2417 S: ToOffset,
2418 T: Into<Arc<str>>,
2419 {
2420 if self.read_only(cx) {
2421 return;
2422 }
2423
2424 self.buffer.update(cx, |buffer, cx| {
2425 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2426 });
2427 }
2428
2429 pub fn edit_with_block_indent<I, S, T>(
2430 &mut self,
2431 edits: I,
2432 original_indent_columns: Vec<Option<u32>>,
2433 cx: &mut Context<Self>,
2434 ) where
2435 I: IntoIterator<Item = (Range<S>, T)>,
2436 S: ToOffset,
2437 T: Into<Arc<str>>,
2438 {
2439 if self.read_only(cx) {
2440 return;
2441 }
2442
2443 self.buffer.update(cx, |buffer, cx| {
2444 buffer.edit(
2445 edits,
2446 Some(AutoindentMode::Block {
2447 original_indent_columns,
2448 }),
2449 cx,
2450 )
2451 });
2452 }
2453
2454 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2455 self.hide_context_menu(window, cx);
2456
2457 match phase {
2458 SelectPhase::Begin {
2459 position,
2460 add,
2461 click_count,
2462 } => self.begin_selection(position, add, click_count, window, cx),
2463 SelectPhase::BeginColumnar {
2464 position,
2465 goal_column,
2466 reset,
2467 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
2468 SelectPhase::Extend {
2469 position,
2470 click_count,
2471 } => self.extend_selection(position, click_count, window, cx),
2472 SelectPhase::Update {
2473 position,
2474 goal_column,
2475 scroll_delta,
2476 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
2477 SelectPhase::End => self.end_selection(window, cx),
2478 }
2479 }
2480
2481 fn extend_selection(
2482 &mut self,
2483 position: DisplayPoint,
2484 click_count: usize,
2485 window: &mut Window,
2486 cx: &mut Context<Self>,
2487 ) {
2488 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2489 let tail = self.selections.newest::<usize>(cx).tail();
2490 self.begin_selection(position, false, click_count, window, cx);
2491
2492 let position = position.to_offset(&display_map, Bias::Left);
2493 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
2494
2495 let mut pending_selection = self
2496 .selections
2497 .pending_anchor()
2498 .expect("extend_selection not called with pending selection");
2499 if position >= tail {
2500 pending_selection.start = tail_anchor;
2501 } else {
2502 pending_selection.end = tail_anchor;
2503 pending_selection.reversed = true;
2504 }
2505
2506 let mut pending_mode = self.selections.pending_mode().unwrap();
2507 match &mut pending_mode {
2508 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
2509 _ => {}
2510 }
2511
2512 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
2513 s.set_pending(pending_selection, pending_mode)
2514 });
2515 }
2516
2517 fn begin_selection(
2518 &mut self,
2519 position: DisplayPoint,
2520 add: bool,
2521 click_count: usize,
2522 window: &mut Window,
2523 cx: &mut Context<Self>,
2524 ) {
2525 if !self.focus_handle.is_focused(window) {
2526 self.last_focused_descendant = None;
2527 window.focus(&self.focus_handle);
2528 }
2529
2530 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2531 let buffer = &display_map.buffer_snapshot;
2532 let newest_selection = self.selections.newest_anchor().clone();
2533 let position = display_map.clip_point(position, Bias::Left);
2534
2535 let start;
2536 let end;
2537 let mode;
2538 let mut auto_scroll;
2539 match click_count {
2540 1 => {
2541 start = buffer.anchor_before(position.to_point(&display_map));
2542 end = start;
2543 mode = SelectMode::Character;
2544 auto_scroll = true;
2545 }
2546 2 => {
2547 let range = movement::surrounding_word(&display_map, position);
2548 start = buffer.anchor_before(range.start.to_point(&display_map));
2549 end = buffer.anchor_before(range.end.to_point(&display_map));
2550 mode = SelectMode::Word(start..end);
2551 auto_scroll = true;
2552 }
2553 3 => {
2554 let position = display_map
2555 .clip_point(position, Bias::Left)
2556 .to_point(&display_map);
2557 let line_start = display_map.prev_line_boundary(position).0;
2558 let next_line_start = buffer.clip_point(
2559 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2560 Bias::Left,
2561 );
2562 start = buffer.anchor_before(line_start);
2563 end = buffer.anchor_before(next_line_start);
2564 mode = SelectMode::Line(start..end);
2565 auto_scroll = true;
2566 }
2567 _ => {
2568 start = buffer.anchor_before(0);
2569 end = buffer.anchor_before(buffer.len());
2570 mode = SelectMode::All;
2571 auto_scroll = false;
2572 }
2573 }
2574 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
2575
2576 let point_to_delete: Option<usize> = {
2577 let selected_points: Vec<Selection<Point>> =
2578 self.selections.disjoint_in_range(start..end, cx);
2579
2580 if !add || click_count > 1 {
2581 None
2582 } else if !selected_points.is_empty() {
2583 Some(selected_points[0].id)
2584 } else {
2585 let clicked_point_already_selected =
2586 self.selections.disjoint.iter().find(|selection| {
2587 selection.start.to_point(buffer) == start.to_point(buffer)
2588 || selection.end.to_point(buffer) == end.to_point(buffer)
2589 });
2590
2591 clicked_point_already_selected.map(|selection| selection.id)
2592 }
2593 };
2594
2595 let selections_count = self.selections.count();
2596
2597 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
2598 if let Some(point_to_delete) = point_to_delete {
2599 s.delete(point_to_delete);
2600
2601 if selections_count == 1 {
2602 s.set_pending_anchor_range(start..end, mode);
2603 }
2604 } else {
2605 if !add {
2606 s.clear_disjoint();
2607 } else if click_count > 1 {
2608 s.delete(newest_selection.id)
2609 }
2610
2611 s.set_pending_anchor_range(start..end, mode);
2612 }
2613 });
2614 }
2615
2616 fn begin_columnar_selection(
2617 &mut self,
2618 position: DisplayPoint,
2619 goal_column: u32,
2620 reset: bool,
2621 window: &mut Window,
2622 cx: &mut Context<Self>,
2623 ) {
2624 if !self.focus_handle.is_focused(window) {
2625 self.last_focused_descendant = None;
2626 window.focus(&self.focus_handle);
2627 }
2628
2629 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2630
2631 if reset {
2632 let pointer_position = display_map
2633 .buffer_snapshot
2634 .anchor_before(position.to_point(&display_map));
2635
2636 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
2637 s.clear_disjoint();
2638 s.set_pending_anchor_range(
2639 pointer_position..pointer_position,
2640 SelectMode::Character,
2641 );
2642 });
2643 }
2644
2645 let tail = self.selections.newest::<Point>(cx).tail();
2646 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
2647
2648 if !reset {
2649 self.select_columns(
2650 tail.to_display_point(&display_map),
2651 position,
2652 goal_column,
2653 &display_map,
2654 window,
2655 cx,
2656 );
2657 }
2658 }
2659
2660 fn update_selection(
2661 &mut self,
2662 position: DisplayPoint,
2663 goal_column: u32,
2664 scroll_delta: gpui::Point<f32>,
2665 window: &mut Window,
2666 cx: &mut Context<Self>,
2667 ) {
2668 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2669
2670 if let Some(tail) = self.columnar_selection_tail.as_ref() {
2671 let tail = tail.to_display_point(&display_map);
2672 self.select_columns(tail, position, goal_column, &display_map, window, cx);
2673 } else if let Some(mut pending) = self.selections.pending_anchor() {
2674 let buffer = self.buffer.read(cx).snapshot(cx);
2675 let head;
2676 let tail;
2677 let mode = self.selections.pending_mode().unwrap();
2678 match &mode {
2679 SelectMode::Character => {
2680 head = position.to_point(&display_map);
2681 tail = pending.tail().to_point(&buffer);
2682 }
2683 SelectMode::Word(original_range) => {
2684 let original_display_range = original_range.start.to_display_point(&display_map)
2685 ..original_range.end.to_display_point(&display_map);
2686 let original_buffer_range = original_display_range.start.to_point(&display_map)
2687 ..original_display_range.end.to_point(&display_map);
2688 if movement::is_inside_word(&display_map, position)
2689 || original_display_range.contains(&position)
2690 {
2691 let word_range = movement::surrounding_word(&display_map, position);
2692 if word_range.start < original_display_range.start {
2693 head = word_range.start.to_point(&display_map);
2694 } else {
2695 head = word_range.end.to_point(&display_map);
2696 }
2697 } else {
2698 head = position.to_point(&display_map);
2699 }
2700
2701 if head <= original_buffer_range.start {
2702 tail = original_buffer_range.end;
2703 } else {
2704 tail = original_buffer_range.start;
2705 }
2706 }
2707 SelectMode::Line(original_range) => {
2708 let original_range = original_range.to_point(&display_map.buffer_snapshot);
2709
2710 let position = display_map
2711 .clip_point(position, Bias::Left)
2712 .to_point(&display_map);
2713 let line_start = display_map.prev_line_boundary(position).0;
2714 let next_line_start = buffer.clip_point(
2715 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2716 Bias::Left,
2717 );
2718
2719 if line_start < original_range.start {
2720 head = line_start
2721 } else {
2722 head = next_line_start
2723 }
2724
2725 if head <= original_range.start {
2726 tail = original_range.end;
2727 } else {
2728 tail = original_range.start;
2729 }
2730 }
2731 SelectMode::All => {
2732 return;
2733 }
2734 };
2735
2736 if head < tail {
2737 pending.start = buffer.anchor_before(head);
2738 pending.end = buffer.anchor_before(tail);
2739 pending.reversed = true;
2740 } else {
2741 pending.start = buffer.anchor_before(tail);
2742 pending.end = buffer.anchor_before(head);
2743 pending.reversed = false;
2744 }
2745
2746 self.change_selections(None, window, cx, |s| {
2747 s.set_pending(pending, mode);
2748 });
2749 } else {
2750 log::error!("update_selection dispatched with no pending selection");
2751 return;
2752 }
2753
2754 self.apply_scroll_delta(scroll_delta, window, cx);
2755 cx.notify();
2756 }
2757
2758 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2759 self.columnar_selection_tail.take();
2760 if self.selections.pending_anchor().is_some() {
2761 let selections = self.selections.all::<usize>(cx);
2762 self.change_selections(None, window, cx, |s| {
2763 s.select(selections);
2764 s.clear_pending();
2765 });
2766 }
2767 }
2768
2769 fn select_columns(
2770 &mut self,
2771 tail: DisplayPoint,
2772 head: DisplayPoint,
2773 goal_column: u32,
2774 display_map: &DisplaySnapshot,
2775 window: &mut Window,
2776 cx: &mut Context<Self>,
2777 ) {
2778 let start_row = cmp::min(tail.row(), head.row());
2779 let end_row = cmp::max(tail.row(), head.row());
2780 let start_column = cmp::min(tail.column(), goal_column);
2781 let end_column = cmp::max(tail.column(), goal_column);
2782 let reversed = start_column < tail.column();
2783
2784 let selection_ranges = (start_row.0..=end_row.0)
2785 .map(DisplayRow)
2786 .filter_map(|row| {
2787 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
2788 let start = display_map
2789 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
2790 .to_point(display_map);
2791 let end = display_map
2792 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
2793 .to_point(display_map);
2794 if reversed {
2795 Some(end..start)
2796 } else {
2797 Some(start..end)
2798 }
2799 } else {
2800 None
2801 }
2802 })
2803 .collect::<Vec<_>>();
2804
2805 self.change_selections(None, window, cx, |s| {
2806 s.select_ranges(selection_ranges);
2807 });
2808 cx.notify();
2809 }
2810
2811 pub fn has_pending_nonempty_selection(&self) -> bool {
2812 let pending_nonempty_selection = match self.selections.pending_anchor() {
2813 Some(Selection { start, end, .. }) => start != end,
2814 None => false,
2815 };
2816
2817 pending_nonempty_selection
2818 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
2819 }
2820
2821 pub fn has_pending_selection(&self) -> bool {
2822 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
2823 }
2824
2825 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
2826 self.selection_mark_mode = false;
2827
2828 if self.clear_expanded_diff_hunks(cx) {
2829 cx.notify();
2830 return;
2831 }
2832 if self.dismiss_menus_and_popups(true, window, cx) {
2833 return;
2834 }
2835
2836 if self.mode == EditorMode::Full
2837 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
2838 {
2839 return;
2840 }
2841
2842 cx.propagate();
2843 }
2844
2845 pub fn dismiss_menus_and_popups(
2846 &mut self,
2847 is_user_requested: bool,
2848 window: &mut Window,
2849 cx: &mut Context<Self>,
2850 ) -> bool {
2851 if self.take_rename(false, window, cx).is_some() {
2852 return true;
2853 }
2854
2855 if hide_hover(self, cx) {
2856 return true;
2857 }
2858
2859 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
2860 return true;
2861 }
2862
2863 if self.hide_context_menu(window, cx).is_some() {
2864 return true;
2865 }
2866
2867 if self.mouse_context_menu.take().is_some() {
2868 return true;
2869 }
2870
2871 if is_user_requested && self.discard_inline_completion(true, cx) {
2872 return true;
2873 }
2874
2875 if self.snippet_stack.pop().is_some() {
2876 return true;
2877 }
2878
2879 if self.mode == EditorMode::Full && self.active_diagnostics.is_some() {
2880 self.dismiss_diagnostics(cx);
2881 return true;
2882 }
2883
2884 false
2885 }
2886
2887 fn linked_editing_ranges_for(
2888 &self,
2889 selection: Range<text::Anchor>,
2890 cx: &App,
2891 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
2892 if self.linked_edit_ranges.is_empty() {
2893 return None;
2894 }
2895 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
2896 selection.end.buffer_id.and_then(|end_buffer_id| {
2897 if selection.start.buffer_id != Some(end_buffer_id) {
2898 return None;
2899 }
2900 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
2901 let snapshot = buffer.read(cx).snapshot();
2902 self.linked_edit_ranges
2903 .get(end_buffer_id, selection.start..selection.end, &snapshot)
2904 .map(|ranges| (ranges, snapshot, buffer))
2905 })?;
2906 use text::ToOffset as TO;
2907 // find offset from the start of current range to current cursor position
2908 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
2909
2910 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
2911 let start_difference = start_offset - start_byte_offset;
2912 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
2913 let end_difference = end_offset - start_byte_offset;
2914 // Current range has associated linked ranges.
2915 let mut linked_edits = HashMap::<_, Vec<_>>::default();
2916 for range in linked_ranges.iter() {
2917 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
2918 let end_offset = start_offset + end_difference;
2919 let start_offset = start_offset + start_difference;
2920 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
2921 continue;
2922 }
2923 if self.selections.disjoint_anchor_ranges().any(|s| {
2924 if s.start.buffer_id != selection.start.buffer_id
2925 || s.end.buffer_id != selection.end.buffer_id
2926 {
2927 return false;
2928 }
2929 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
2930 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
2931 }) {
2932 continue;
2933 }
2934 let start = buffer_snapshot.anchor_after(start_offset);
2935 let end = buffer_snapshot.anchor_after(end_offset);
2936 linked_edits
2937 .entry(buffer.clone())
2938 .or_default()
2939 .push(start..end);
2940 }
2941 Some(linked_edits)
2942 }
2943
2944 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
2945 let text: Arc<str> = text.into();
2946
2947 if self.read_only(cx) {
2948 return;
2949 }
2950
2951 let selections = self.selections.all_adjusted(cx);
2952 let mut bracket_inserted = false;
2953 let mut edits = Vec::new();
2954 let mut linked_edits = HashMap::<_, Vec<_>>::default();
2955 let mut new_selections = Vec::with_capacity(selections.len());
2956 let mut new_autoclose_regions = Vec::new();
2957 let snapshot = self.buffer.read(cx).read(cx);
2958
2959 for (selection, autoclose_region) in
2960 self.selections_with_autoclose_regions(selections, &snapshot)
2961 {
2962 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
2963 // Determine if the inserted text matches the opening or closing
2964 // bracket of any of this language's bracket pairs.
2965 let mut bracket_pair = None;
2966 let mut is_bracket_pair_start = false;
2967 let mut is_bracket_pair_end = false;
2968 if !text.is_empty() {
2969 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
2970 // and they are removing the character that triggered IME popup.
2971 for (pair, enabled) in scope.brackets() {
2972 if !pair.close && !pair.surround {
2973 continue;
2974 }
2975
2976 if enabled && pair.start.ends_with(text.as_ref()) {
2977 let prefix_len = pair.start.len() - text.len();
2978 let preceding_text_matches_prefix = prefix_len == 0
2979 || (selection.start.column >= (prefix_len as u32)
2980 && snapshot.contains_str_at(
2981 Point::new(
2982 selection.start.row,
2983 selection.start.column - (prefix_len as u32),
2984 ),
2985 &pair.start[..prefix_len],
2986 ));
2987 if preceding_text_matches_prefix {
2988 bracket_pair = Some(pair.clone());
2989 is_bracket_pair_start = true;
2990 break;
2991 }
2992 }
2993 if pair.end.as_str() == text.as_ref() {
2994 bracket_pair = Some(pair.clone());
2995 is_bracket_pair_end = true;
2996 break;
2997 }
2998 }
2999 }
3000
3001 if let Some(bracket_pair) = bracket_pair {
3002 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3003 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3004 let auto_surround =
3005 self.use_auto_surround && snapshot_settings.use_auto_surround;
3006 if selection.is_empty() {
3007 if is_bracket_pair_start {
3008 // If the inserted text is a suffix of an opening bracket and the
3009 // selection is preceded by the rest of the opening bracket, then
3010 // insert the closing bracket.
3011 let following_text_allows_autoclose = snapshot
3012 .chars_at(selection.start)
3013 .next()
3014 .map_or(true, |c| scope.should_autoclose_before(c));
3015
3016 let preceding_text_allows_autoclose = selection.start.column == 0
3017 || snapshot.reversed_chars_at(selection.start).next().map_or(
3018 true,
3019 |c| {
3020 bracket_pair.start != bracket_pair.end
3021 || !snapshot
3022 .char_classifier_at(selection.start)
3023 .is_word(c)
3024 },
3025 );
3026
3027 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3028 && bracket_pair.start.len() == 1
3029 {
3030 let target = bracket_pair.start.chars().next().unwrap();
3031 let current_line_count = snapshot
3032 .reversed_chars_at(selection.start)
3033 .take_while(|&c| c != '\n')
3034 .filter(|&c| c == target)
3035 .count();
3036 current_line_count % 2 == 1
3037 } else {
3038 false
3039 };
3040
3041 if autoclose
3042 && bracket_pair.close
3043 && following_text_allows_autoclose
3044 && preceding_text_allows_autoclose
3045 && !is_closing_quote
3046 {
3047 let anchor = snapshot.anchor_before(selection.end);
3048 new_selections.push((selection.map(|_| anchor), text.len()));
3049 new_autoclose_regions.push((
3050 anchor,
3051 text.len(),
3052 selection.id,
3053 bracket_pair.clone(),
3054 ));
3055 edits.push((
3056 selection.range(),
3057 format!("{}{}", text, bracket_pair.end).into(),
3058 ));
3059 bracket_inserted = true;
3060 continue;
3061 }
3062 }
3063
3064 if let Some(region) = autoclose_region {
3065 // If the selection is followed by an auto-inserted closing bracket,
3066 // then don't insert that closing bracket again; just move the selection
3067 // past the closing bracket.
3068 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3069 && text.as_ref() == region.pair.end.as_str();
3070 if should_skip {
3071 let anchor = snapshot.anchor_after(selection.end);
3072 new_selections
3073 .push((selection.map(|_| anchor), region.pair.end.len()));
3074 continue;
3075 }
3076 }
3077
3078 let always_treat_brackets_as_autoclosed = snapshot
3079 .language_settings_at(selection.start, cx)
3080 .always_treat_brackets_as_autoclosed;
3081 if always_treat_brackets_as_autoclosed
3082 && is_bracket_pair_end
3083 && snapshot.contains_str_at(selection.end, text.as_ref())
3084 {
3085 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3086 // and the inserted text is a closing bracket and the selection is followed
3087 // by the closing bracket then move the selection past the closing bracket.
3088 let anchor = snapshot.anchor_after(selection.end);
3089 new_selections.push((selection.map(|_| anchor), text.len()));
3090 continue;
3091 }
3092 }
3093 // If an opening bracket is 1 character long and is typed while
3094 // text is selected, then surround that text with the bracket pair.
3095 else if auto_surround
3096 && bracket_pair.surround
3097 && is_bracket_pair_start
3098 && bracket_pair.start.chars().count() == 1
3099 {
3100 edits.push((selection.start..selection.start, text.clone()));
3101 edits.push((
3102 selection.end..selection.end,
3103 bracket_pair.end.as_str().into(),
3104 ));
3105 bracket_inserted = true;
3106 new_selections.push((
3107 Selection {
3108 id: selection.id,
3109 start: snapshot.anchor_after(selection.start),
3110 end: snapshot.anchor_before(selection.end),
3111 reversed: selection.reversed,
3112 goal: selection.goal,
3113 },
3114 0,
3115 ));
3116 continue;
3117 }
3118 }
3119 }
3120
3121 if self.auto_replace_emoji_shortcode
3122 && selection.is_empty()
3123 && text.as_ref().ends_with(':')
3124 {
3125 if let Some(possible_emoji_short_code) =
3126 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3127 {
3128 if !possible_emoji_short_code.is_empty() {
3129 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3130 let emoji_shortcode_start = Point::new(
3131 selection.start.row,
3132 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3133 );
3134
3135 // Remove shortcode from buffer
3136 edits.push((
3137 emoji_shortcode_start..selection.start,
3138 "".to_string().into(),
3139 ));
3140 new_selections.push((
3141 Selection {
3142 id: selection.id,
3143 start: snapshot.anchor_after(emoji_shortcode_start),
3144 end: snapshot.anchor_before(selection.start),
3145 reversed: selection.reversed,
3146 goal: selection.goal,
3147 },
3148 0,
3149 ));
3150
3151 // Insert emoji
3152 let selection_start_anchor = snapshot.anchor_after(selection.start);
3153 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3154 edits.push((selection.start..selection.end, emoji.to_string().into()));
3155
3156 continue;
3157 }
3158 }
3159 }
3160 }
3161
3162 // If not handling any auto-close operation, then just replace the selected
3163 // text with the given input and move the selection to the end of the
3164 // newly inserted text.
3165 let anchor = snapshot.anchor_after(selection.end);
3166 if !self.linked_edit_ranges.is_empty() {
3167 let start_anchor = snapshot.anchor_before(selection.start);
3168
3169 let is_word_char = text.chars().next().map_or(true, |char| {
3170 let classifier = snapshot.char_classifier_at(start_anchor.to_offset(&snapshot));
3171 classifier.is_word(char)
3172 });
3173
3174 if is_word_char {
3175 if let Some(ranges) = self
3176 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3177 {
3178 for (buffer, edits) in ranges {
3179 linked_edits
3180 .entry(buffer.clone())
3181 .or_default()
3182 .extend(edits.into_iter().map(|range| (range, text.clone())));
3183 }
3184 }
3185 }
3186 }
3187
3188 new_selections.push((selection.map(|_| anchor), 0));
3189 edits.push((selection.start..selection.end, text.clone()));
3190 }
3191
3192 drop(snapshot);
3193
3194 self.transact(window, cx, |this, window, cx| {
3195 let initial_buffer_versions =
3196 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3197
3198 this.buffer.update(cx, |buffer, cx| {
3199 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3200 });
3201 for (buffer, edits) in linked_edits {
3202 buffer.update(cx, |buffer, cx| {
3203 let snapshot = buffer.snapshot();
3204 let edits = edits
3205 .into_iter()
3206 .map(|(range, text)| {
3207 use text::ToPoint as TP;
3208 let end_point = TP::to_point(&range.end, &snapshot);
3209 let start_point = TP::to_point(&range.start, &snapshot);
3210 (start_point..end_point, text)
3211 })
3212 .sorted_by_key(|(range, _)| range.start)
3213 .collect::<Vec<_>>();
3214 buffer.edit(edits, None, cx);
3215 })
3216 }
3217 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3218 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3219 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3220 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3221 .zip(new_selection_deltas)
3222 .map(|(selection, delta)| Selection {
3223 id: selection.id,
3224 start: selection.start + delta,
3225 end: selection.end + delta,
3226 reversed: selection.reversed,
3227 goal: SelectionGoal::None,
3228 })
3229 .collect::<Vec<_>>();
3230
3231 let mut i = 0;
3232 for (position, delta, selection_id, pair) in new_autoclose_regions {
3233 let position = position.to_offset(&map.buffer_snapshot) + delta;
3234 let start = map.buffer_snapshot.anchor_before(position);
3235 let end = map.buffer_snapshot.anchor_after(position);
3236 while let Some(existing_state) = this.autoclose_regions.get(i) {
3237 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3238 Ordering::Less => i += 1,
3239 Ordering::Greater => break,
3240 Ordering::Equal => {
3241 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3242 Ordering::Less => i += 1,
3243 Ordering::Equal => break,
3244 Ordering::Greater => break,
3245 }
3246 }
3247 }
3248 }
3249 this.autoclose_regions.insert(
3250 i,
3251 AutocloseRegion {
3252 selection_id,
3253 range: start..end,
3254 pair,
3255 },
3256 );
3257 }
3258
3259 let had_active_inline_completion = this.has_active_inline_completion();
3260 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3261 s.select(new_selections)
3262 });
3263
3264 if !bracket_inserted {
3265 if let Some(on_type_format_task) =
3266 this.trigger_on_type_formatting(text.to_string(), window, cx)
3267 {
3268 on_type_format_task.detach_and_log_err(cx);
3269 }
3270 }
3271
3272 let editor_settings = EditorSettings::get_global(cx);
3273 if bracket_inserted
3274 && (editor_settings.auto_signature_help
3275 || editor_settings.show_signature_help_after_edits)
3276 {
3277 this.show_signature_help(&ShowSignatureHelp, window, cx);
3278 }
3279
3280 let trigger_in_words =
3281 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3282 if this.hard_wrap.is_some() {
3283 let latest: Range<Point> = this.selections.newest(cx).range();
3284 if latest.is_empty()
3285 && this
3286 .buffer()
3287 .read(cx)
3288 .snapshot(cx)
3289 .line_len(MultiBufferRow(latest.start.row))
3290 == latest.start.column
3291 {
3292 this.rewrap_impl(
3293 RewrapOptions {
3294 override_language_settings: true,
3295 preserve_existing_whitespace: true,
3296 },
3297 cx,
3298 )
3299 }
3300 }
3301 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3302 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3303 this.refresh_inline_completion(true, false, window, cx);
3304 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3305 });
3306 }
3307
3308 fn find_possible_emoji_shortcode_at_position(
3309 snapshot: &MultiBufferSnapshot,
3310 position: Point,
3311 ) -> Option<String> {
3312 let mut chars = Vec::new();
3313 let mut found_colon = false;
3314 for char in snapshot.reversed_chars_at(position).take(100) {
3315 // Found a possible emoji shortcode in the middle of the buffer
3316 if found_colon {
3317 if char.is_whitespace() {
3318 chars.reverse();
3319 return Some(chars.iter().collect());
3320 }
3321 // If the previous character is not a whitespace, we are in the middle of a word
3322 // and we only want to complete the shortcode if the word is made up of other emojis
3323 let mut containing_word = String::new();
3324 for ch in snapshot
3325 .reversed_chars_at(position)
3326 .skip(chars.len() + 1)
3327 .take(100)
3328 {
3329 if ch.is_whitespace() {
3330 break;
3331 }
3332 containing_word.push(ch);
3333 }
3334 let containing_word = containing_word.chars().rev().collect::<String>();
3335 if util::word_consists_of_emojis(containing_word.as_str()) {
3336 chars.reverse();
3337 return Some(chars.iter().collect());
3338 }
3339 }
3340
3341 if char.is_whitespace() || !char.is_ascii() {
3342 return None;
3343 }
3344 if char == ':' {
3345 found_colon = true;
3346 } else {
3347 chars.push(char);
3348 }
3349 }
3350 // Found a possible emoji shortcode at the beginning of the buffer
3351 chars.reverse();
3352 Some(chars.iter().collect())
3353 }
3354
3355 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3356 self.transact(window, cx, |this, window, cx| {
3357 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
3358 let selections = this.selections.all::<usize>(cx);
3359 let multi_buffer = this.buffer.read(cx);
3360 let buffer = multi_buffer.snapshot(cx);
3361 selections
3362 .iter()
3363 .map(|selection| {
3364 let start_point = selection.start.to_point(&buffer);
3365 let mut indent =
3366 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3367 indent.len = cmp::min(indent.len, start_point.column);
3368 let start = selection.start;
3369 let end = selection.end;
3370 let selection_is_empty = start == end;
3371 let language_scope = buffer.language_scope_at(start);
3372 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
3373 &language_scope
3374 {
3375 let insert_extra_newline =
3376 insert_extra_newline_brackets(&buffer, start..end, language)
3377 || insert_extra_newline_tree_sitter(&buffer, start..end);
3378
3379 // Comment extension on newline is allowed only for cursor selections
3380 let comment_delimiter = maybe!({
3381 if !selection_is_empty {
3382 return None;
3383 }
3384
3385 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3386 return None;
3387 }
3388
3389 let delimiters = language.line_comment_prefixes();
3390 let max_len_of_delimiter =
3391 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3392 let (snapshot, range) =
3393 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3394
3395 let mut index_of_first_non_whitespace = 0;
3396 let comment_candidate = snapshot
3397 .chars_for_range(range)
3398 .skip_while(|c| {
3399 let should_skip = c.is_whitespace();
3400 if should_skip {
3401 index_of_first_non_whitespace += 1;
3402 }
3403 should_skip
3404 })
3405 .take(max_len_of_delimiter)
3406 .collect::<String>();
3407 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3408 comment_candidate.starts_with(comment_prefix.as_ref())
3409 })?;
3410 let cursor_is_placed_after_comment_marker =
3411 index_of_first_non_whitespace + comment_prefix.len()
3412 <= start_point.column as usize;
3413 if cursor_is_placed_after_comment_marker {
3414 Some(comment_prefix.clone())
3415 } else {
3416 None
3417 }
3418 });
3419 (comment_delimiter, insert_extra_newline)
3420 } else {
3421 (None, false)
3422 };
3423
3424 let capacity_for_delimiter = comment_delimiter
3425 .as_deref()
3426 .map(str::len)
3427 .unwrap_or_default();
3428 let mut new_text =
3429 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3430 new_text.push('\n');
3431 new_text.extend(indent.chars());
3432 if let Some(delimiter) = &comment_delimiter {
3433 new_text.push_str(delimiter);
3434 }
3435 if insert_extra_newline {
3436 new_text = new_text.repeat(2);
3437 }
3438
3439 let anchor = buffer.anchor_after(end);
3440 let new_selection = selection.map(|_| anchor);
3441 (
3442 (start..end, new_text),
3443 (insert_extra_newline, new_selection),
3444 )
3445 })
3446 .unzip()
3447 };
3448
3449 this.edit_with_autoindent(edits, cx);
3450 let buffer = this.buffer.read(cx).snapshot(cx);
3451 let new_selections = selection_fixup_info
3452 .into_iter()
3453 .map(|(extra_newline_inserted, new_selection)| {
3454 let mut cursor = new_selection.end.to_point(&buffer);
3455 if extra_newline_inserted {
3456 cursor.row -= 1;
3457 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
3458 }
3459 new_selection.map(|_| cursor)
3460 })
3461 .collect();
3462
3463 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3464 s.select(new_selections)
3465 });
3466 this.refresh_inline_completion(true, false, window, cx);
3467 });
3468 }
3469
3470 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
3471 let buffer = self.buffer.read(cx);
3472 let snapshot = buffer.snapshot(cx);
3473
3474 let mut edits = Vec::new();
3475 let mut rows = Vec::new();
3476
3477 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
3478 let cursor = selection.head();
3479 let row = cursor.row;
3480
3481 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
3482
3483 let newline = "\n".to_string();
3484 edits.push((start_of_line..start_of_line, newline));
3485
3486 rows.push(row + rows_inserted as u32);
3487 }
3488
3489 self.transact(window, cx, |editor, window, cx| {
3490 editor.edit(edits, cx);
3491
3492 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3493 let mut index = 0;
3494 s.move_cursors_with(|map, _, _| {
3495 let row = rows[index];
3496 index += 1;
3497
3498 let point = Point::new(row, 0);
3499 let boundary = map.next_line_boundary(point).1;
3500 let clipped = map.clip_point(boundary, Bias::Left);
3501
3502 (clipped, SelectionGoal::None)
3503 });
3504 });
3505
3506 let mut indent_edits = Vec::new();
3507 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3508 for row in rows {
3509 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3510 for (row, indent) in indents {
3511 if indent.len == 0 {
3512 continue;
3513 }
3514
3515 let text = match indent.kind {
3516 IndentKind::Space => " ".repeat(indent.len as usize),
3517 IndentKind::Tab => "\t".repeat(indent.len as usize),
3518 };
3519 let point = Point::new(row.0, 0);
3520 indent_edits.push((point..point, text));
3521 }
3522 }
3523 editor.edit(indent_edits, cx);
3524 });
3525 }
3526
3527 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
3528 let buffer = self.buffer.read(cx);
3529 let snapshot = buffer.snapshot(cx);
3530
3531 let mut edits = Vec::new();
3532 let mut rows = Vec::new();
3533 let mut rows_inserted = 0;
3534
3535 for selection in self.selections.all_adjusted(cx) {
3536 let cursor = selection.head();
3537 let row = cursor.row;
3538
3539 let point = Point::new(row + 1, 0);
3540 let start_of_line = snapshot.clip_point(point, Bias::Left);
3541
3542 let newline = "\n".to_string();
3543 edits.push((start_of_line..start_of_line, newline));
3544
3545 rows_inserted += 1;
3546 rows.push(row + rows_inserted);
3547 }
3548
3549 self.transact(window, cx, |editor, window, cx| {
3550 editor.edit(edits, cx);
3551
3552 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3553 let mut index = 0;
3554 s.move_cursors_with(|map, _, _| {
3555 let row = rows[index];
3556 index += 1;
3557
3558 let point = Point::new(row, 0);
3559 let boundary = map.next_line_boundary(point).1;
3560 let clipped = map.clip_point(boundary, Bias::Left);
3561
3562 (clipped, SelectionGoal::None)
3563 });
3564 });
3565
3566 let mut indent_edits = Vec::new();
3567 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3568 for row in rows {
3569 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3570 for (row, indent) in indents {
3571 if indent.len == 0 {
3572 continue;
3573 }
3574
3575 let text = match indent.kind {
3576 IndentKind::Space => " ".repeat(indent.len as usize),
3577 IndentKind::Tab => "\t".repeat(indent.len as usize),
3578 };
3579 let point = Point::new(row.0, 0);
3580 indent_edits.push((point..point, text));
3581 }
3582 }
3583 editor.edit(indent_edits, cx);
3584 });
3585 }
3586
3587 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3588 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
3589 original_indent_columns: Vec::new(),
3590 });
3591 self.insert_with_autoindent_mode(text, autoindent, window, cx);
3592 }
3593
3594 fn insert_with_autoindent_mode(
3595 &mut self,
3596 text: &str,
3597 autoindent_mode: Option<AutoindentMode>,
3598 window: &mut Window,
3599 cx: &mut Context<Self>,
3600 ) {
3601 if self.read_only(cx) {
3602 return;
3603 }
3604
3605 let text: Arc<str> = text.into();
3606 self.transact(window, cx, |this, window, cx| {
3607 let old_selections = this.selections.all_adjusted(cx);
3608 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
3609 let anchors = {
3610 let snapshot = buffer.read(cx);
3611 old_selections
3612 .iter()
3613 .map(|s| {
3614 let anchor = snapshot.anchor_after(s.head());
3615 s.map(|_| anchor)
3616 })
3617 .collect::<Vec<_>>()
3618 };
3619 buffer.edit(
3620 old_selections
3621 .iter()
3622 .map(|s| (s.start..s.end, text.clone())),
3623 autoindent_mode,
3624 cx,
3625 );
3626 anchors
3627 });
3628
3629 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3630 s.select_anchors(selection_anchors);
3631 });
3632
3633 cx.notify();
3634 });
3635 }
3636
3637 fn trigger_completion_on_input(
3638 &mut self,
3639 text: &str,
3640 trigger_in_words: bool,
3641 window: &mut Window,
3642 cx: &mut Context<Self>,
3643 ) {
3644 let ignore_completion_provider = self
3645 .context_menu
3646 .borrow()
3647 .as_ref()
3648 .map(|menu| match menu {
3649 CodeContextMenu::Completions(completions_menu) => {
3650 completions_menu.ignore_completion_provider
3651 }
3652 CodeContextMenu::CodeActions(_) => false,
3653 })
3654 .unwrap_or(false);
3655
3656 if ignore_completion_provider {
3657 self.show_word_completions(&ShowWordCompletions, window, cx);
3658 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
3659 self.show_completions(
3660 &ShowCompletions {
3661 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
3662 },
3663 window,
3664 cx,
3665 );
3666 } else {
3667 self.hide_context_menu(window, cx);
3668 }
3669 }
3670
3671 fn is_completion_trigger(
3672 &self,
3673 text: &str,
3674 trigger_in_words: bool,
3675 cx: &mut Context<Self>,
3676 ) -> bool {
3677 let position = self.selections.newest_anchor().head();
3678 let multibuffer = self.buffer.read(cx);
3679 let Some(buffer) = position
3680 .buffer_id
3681 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
3682 else {
3683 return false;
3684 };
3685
3686 if let Some(completion_provider) = &self.completion_provider {
3687 completion_provider.is_completion_trigger(
3688 &buffer,
3689 position.text_anchor,
3690 text,
3691 trigger_in_words,
3692 cx,
3693 )
3694 } else {
3695 false
3696 }
3697 }
3698
3699 /// If any empty selections is touching the start of its innermost containing autoclose
3700 /// region, expand it to select the brackets.
3701 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3702 let selections = self.selections.all::<usize>(cx);
3703 let buffer = self.buffer.read(cx).read(cx);
3704 let new_selections = self
3705 .selections_with_autoclose_regions(selections, &buffer)
3706 .map(|(mut selection, region)| {
3707 if !selection.is_empty() {
3708 return selection;
3709 }
3710
3711 if let Some(region) = region {
3712 let mut range = region.range.to_offset(&buffer);
3713 if selection.start == range.start && range.start >= region.pair.start.len() {
3714 range.start -= region.pair.start.len();
3715 if buffer.contains_str_at(range.start, ®ion.pair.start)
3716 && buffer.contains_str_at(range.end, ®ion.pair.end)
3717 {
3718 range.end += region.pair.end.len();
3719 selection.start = range.start;
3720 selection.end = range.end;
3721
3722 return selection;
3723 }
3724 }
3725 }
3726
3727 let always_treat_brackets_as_autoclosed = buffer
3728 .language_settings_at(selection.start, cx)
3729 .always_treat_brackets_as_autoclosed;
3730
3731 if !always_treat_brackets_as_autoclosed {
3732 return selection;
3733 }
3734
3735 if let Some(scope) = buffer.language_scope_at(selection.start) {
3736 for (pair, enabled) in scope.brackets() {
3737 if !enabled || !pair.close {
3738 continue;
3739 }
3740
3741 if buffer.contains_str_at(selection.start, &pair.end) {
3742 let pair_start_len = pair.start.len();
3743 if buffer.contains_str_at(
3744 selection.start.saturating_sub(pair_start_len),
3745 &pair.start,
3746 ) {
3747 selection.start -= pair_start_len;
3748 selection.end += pair.end.len();
3749
3750 return selection;
3751 }
3752 }
3753 }
3754 }
3755
3756 selection
3757 })
3758 .collect();
3759
3760 drop(buffer);
3761 self.change_selections(None, window, cx, |selections| {
3762 selections.select(new_selections)
3763 });
3764 }
3765
3766 /// Iterate the given selections, and for each one, find the smallest surrounding
3767 /// autoclose region. This uses the ordering of the selections and the autoclose
3768 /// regions to avoid repeated comparisons.
3769 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
3770 &'a self,
3771 selections: impl IntoIterator<Item = Selection<D>>,
3772 buffer: &'a MultiBufferSnapshot,
3773 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
3774 let mut i = 0;
3775 let mut regions = self.autoclose_regions.as_slice();
3776 selections.into_iter().map(move |selection| {
3777 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
3778
3779 let mut enclosing = None;
3780 while let Some(pair_state) = regions.get(i) {
3781 if pair_state.range.end.to_offset(buffer) < range.start {
3782 regions = ®ions[i + 1..];
3783 i = 0;
3784 } else if pair_state.range.start.to_offset(buffer) > range.end {
3785 break;
3786 } else {
3787 if pair_state.selection_id == selection.id {
3788 enclosing = Some(pair_state);
3789 }
3790 i += 1;
3791 }
3792 }
3793
3794 (selection, enclosing)
3795 })
3796 }
3797
3798 /// Remove any autoclose regions that no longer contain their selection.
3799 fn invalidate_autoclose_regions(
3800 &mut self,
3801 mut selections: &[Selection<Anchor>],
3802 buffer: &MultiBufferSnapshot,
3803 ) {
3804 self.autoclose_regions.retain(|state| {
3805 let mut i = 0;
3806 while let Some(selection) = selections.get(i) {
3807 if selection.end.cmp(&state.range.start, buffer).is_lt() {
3808 selections = &selections[1..];
3809 continue;
3810 }
3811 if selection.start.cmp(&state.range.end, buffer).is_gt() {
3812 break;
3813 }
3814 if selection.id == state.selection_id {
3815 return true;
3816 } else {
3817 i += 1;
3818 }
3819 }
3820 false
3821 });
3822 }
3823
3824 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
3825 let offset = position.to_offset(buffer);
3826 let (word_range, kind) = buffer.surrounding_word(offset, true);
3827 if offset > word_range.start && kind == Some(CharKind::Word) {
3828 Some(
3829 buffer
3830 .text_for_range(word_range.start..offset)
3831 .collect::<String>(),
3832 )
3833 } else {
3834 None
3835 }
3836 }
3837
3838 pub fn toggle_inlay_hints(
3839 &mut self,
3840 _: &ToggleInlayHints,
3841 _: &mut Window,
3842 cx: &mut Context<Self>,
3843 ) {
3844 self.refresh_inlay_hints(
3845 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
3846 cx,
3847 );
3848 }
3849
3850 pub fn inlay_hints_enabled(&self) -> bool {
3851 self.inlay_hint_cache.enabled
3852 }
3853
3854 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
3855 if self.semantics_provider.is_none() || self.mode != EditorMode::Full {
3856 return;
3857 }
3858
3859 let reason_description = reason.description();
3860 let ignore_debounce = matches!(
3861 reason,
3862 InlayHintRefreshReason::SettingsChange(_)
3863 | InlayHintRefreshReason::Toggle(_)
3864 | InlayHintRefreshReason::ExcerptsRemoved(_)
3865 | InlayHintRefreshReason::ModifiersChanged(_)
3866 );
3867 let (invalidate_cache, required_languages) = match reason {
3868 InlayHintRefreshReason::ModifiersChanged(enabled) => {
3869 match self.inlay_hint_cache.modifiers_override(enabled) {
3870 Some(enabled) => {
3871 if enabled {
3872 (InvalidationStrategy::RefreshRequested, None)
3873 } else {
3874 self.splice_inlays(
3875 &self
3876 .visible_inlay_hints(cx)
3877 .iter()
3878 .map(|inlay| inlay.id)
3879 .collect::<Vec<InlayId>>(),
3880 Vec::new(),
3881 cx,
3882 );
3883 return;
3884 }
3885 }
3886 None => return,
3887 }
3888 }
3889 InlayHintRefreshReason::Toggle(enabled) => {
3890 if self.inlay_hint_cache.toggle(enabled) {
3891 if enabled {
3892 (InvalidationStrategy::RefreshRequested, None)
3893 } else {
3894 self.splice_inlays(
3895 &self
3896 .visible_inlay_hints(cx)
3897 .iter()
3898 .map(|inlay| inlay.id)
3899 .collect::<Vec<InlayId>>(),
3900 Vec::new(),
3901 cx,
3902 );
3903 return;
3904 }
3905 } else {
3906 return;
3907 }
3908 }
3909 InlayHintRefreshReason::SettingsChange(new_settings) => {
3910 match self.inlay_hint_cache.update_settings(
3911 &self.buffer,
3912 new_settings,
3913 self.visible_inlay_hints(cx),
3914 cx,
3915 ) {
3916 ControlFlow::Break(Some(InlaySplice {
3917 to_remove,
3918 to_insert,
3919 })) => {
3920 self.splice_inlays(&to_remove, to_insert, cx);
3921 return;
3922 }
3923 ControlFlow::Break(None) => return,
3924 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
3925 }
3926 }
3927 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
3928 if let Some(InlaySplice {
3929 to_remove,
3930 to_insert,
3931 }) = self.inlay_hint_cache.remove_excerpts(excerpts_removed)
3932 {
3933 self.splice_inlays(&to_remove, to_insert, cx);
3934 }
3935 return;
3936 }
3937 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
3938 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
3939 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
3940 }
3941 InlayHintRefreshReason::RefreshRequested => {
3942 (InvalidationStrategy::RefreshRequested, None)
3943 }
3944 };
3945
3946 if let Some(InlaySplice {
3947 to_remove,
3948 to_insert,
3949 }) = self.inlay_hint_cache.spawn_hint_refresh(
3950 reason_description,
3951 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
3952 invalidate_cache,
3953 ignore_debounce,
3954 cx,
3955 ) {
3956 self.splice_inlays(&to_remove, to_insert, cx);
3957 }
3958 }
3959
3960 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
3961 self.display_map
3962 .read(cx)
3963 .current_inlays()
3964 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
3965 .cloned()
3966 .collect()
3967 }
3968
3969 pub fn excerpts_for_inlay_hints_query(
3970 &self,
3971 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
3972 cx: &mut Context<Editor>,
3973 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
3974 let Some(project) = self.project.as_ref() else {
3975 return HashMap::default();
3976 };
3977 let project = project.read(cx);
3978 let multi_buffer = self.buffer().read(cx);
3979 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
3980 let multi_buffer_visible_start = self
3981 .scroll_manager
3982 .anchor()
3983 .anchor
3984 .to_point(&multi_buffer_snapshot);
3985 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
3986 multi_buffer_visible_start
3987 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
3988 Bias::Left,
3989 );
3990 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
3991 multi_buffer_snapshot
3992 .range_to_buffer_ranges(multi_buffer_visible_range)
3993 .into_iter()
3994 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
3995 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
3996 let buffer_file = project::File::from_dyn(buffer.file())?;
3997 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
3998 let worktree_entry = buffer_worktree
3999 .read(cx)
4000 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4001 if worktree_entry.is_ignored {
4002 return None;
4003 }
4004
4005 let language = buffer.language()?;
4006 if let Some(restrict_to_languages) = restrict_to_languages {
4007 if !restrict_to_languages.contains(language) {
4008 return None;
4009 }
4010 }
4011 Some((
4012 excerpt_id,
4013 (
4014 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4015 buffer.version().clone(),
4016 excerpt_visible_range,
4017 ),
4018 ))
4019 })
4020 .collect()
4021 }
4022
4023 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4024 TextLayoutDetails {
4025 text_system: window.text_system().clone(),
4026 editor_style: self.style.clone().unwrap(),
4027 rem_size: window.rem_size(),
4028 scroll_anchor: self.scroll_manager.anchor(),
4029 visible_rows: self.visible_line_count(),
4030 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4031 }
4032 }
4033
4034 pub fn splice_inlays(
4035 &self,
4036 to_remove: &[InlayId],
4037 to_insert: Vec<Inlay>,
4038 cx: &mut Context<Self>,
4039 ) {
4040 self.display_map.update(cx, |display_map, cx| {
4041 display_map.splice_inlays(to_remove, to_insert, cx)
4042 });
4043 cx.notify();
4044 }
4045
4046 fn trigger_on_type_formatting(
4047 &self,
4048 input: String,
4049 window: &mut Window,
4050 cx: &mut Context<Self>,
4051 ) -> Option<Task<Result<()>>> {
4052 if input.len() != 1 {
4053 return None;
4054 }
4055
4056 let project = self.project.as_ref()?;
4057 let position = self.selections.newest_anchor().head();
4058 let (buffer, buffer_position) = self
4059 .buffer
4060 .read(cx)
4061 .text_anchor_for_position(position, cx)?;
4062
4063 let settings = language_settings::language_settings(
4064 buffer
4065 .read(cx)
4066 .language_at(buffer_position)
4067 .map(|l| l.name()),
4068 buffer.read(cx).file(),
4069 cx,
4070 );
4071 if !settings.use_on_type_format {
4072 return None;
4073 }
4074
4075 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4076 // hence we do LSP request & edit on host side only — add formats to host's history.
4077 let push_to_lsp_host_history = true;
4078 // If this is not the host, append its history with new edits.
4079 let push_to_client_history = project.read(cx).is_via_collab();
4080
4081 let on_type_formatting = project.update(cx, |project, cx| {
4082 project.on_type_format(
4083 buffer.clone(),
4084 buffer_position,
4085 input,
4086 push_to_lsp_host_history,
4087 cx,
4088 )
4089 });
4090 Some(cx.spawn_in(window, async move |editor, cx| {
4091 if let Some(transaction) = on_type_formatting.await? {
4092 if push_to_client_history {
4093 buffer
4094 .update(cx, |buffer, _| {
4095 buffer.push_transaction(transaction, Instant::now());
4096 })
4097 .ok();
4098 }
4099 editor.update(cx, |editor, cx| {
4100 editor.refresh_document_highlights(cx);
4101 })?;
4102 }
4103 Ok(())
4104 }))
4105 }
4106
4107 pub fn show_word_completions(
4108 &mut self,
4109 _: &ShowWordCompletions,
4110 window: &mut Window,
4111 cx: &mut Context<Self>,
4112 ) {
4113 self.open_completions_menu(true, None, window, cx);
4114 }
4115
4116 pub fn show_completions(
4117 &mut self,
4118 options: &ShowCompletions,
4119 window: &mut Window,
4120 cx: &mut Context<Self>,
4121 ) {
4122 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4123 }
4124
4125 fn open_completions_menu(
4126 &mut self,
4127 ignore_completion_provider: bool,
4128 trigger: Option<&str>,
4129 window: &mut Window,
4130 cx: &mut Context<Self>,
4131 ) {
4132 if self.pending_rename.is_some() {
4133 return;
4134 }
4135 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4136 return;
4137 }
4138
4139 let position = self.selections.newest_anchor().head();
4140 if position.diff_base_anchor.is_some() {
4141 return;
4142 }
4143 let (buffer, buffer_position) =
4144 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4145 output
4146 } else {
4147 return;
4148 };
4149 let buffer_snapshot = buffer.read(cx).snapshot();
4150 let show_completion_documentation = buffer_snapshot
4151 .settings_at(buffer_position, cx)
4152 .show_completion_documentation;
4153
4154 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4155
4156 let trigger_kind = match trigger {
4157 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4158 CompletionTriggerKind::TRIGGER_CHARACTER
4159 }
4160 _ => CompletionTriggerKind::INVOKED,
4161 };
4162 let completion_context = CompletionContext {
4163 trigger_character: trigger.and_then(|trigger| {
4164 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4165 Some(String::from(trigger))
4166 } else {
4167 None
4168 }
4169 }),
4170 trigger_kind,
4171 };
4172
4173 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4174 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4175 let word_to_exclude = buffer_snapshot
4176 .text_for_range(old_range.clone())
4177 .collect::<String>();
4178 (
4179 buffer_snapshot.anchor_before(old_range.start)
4180 ..buffer_snapshot.anchor_after(old_range.end),
4181 Some(word_to_exclude),
4182 )
4183 } else {
4184 (buffer_position..buffer_position, None)
4185 };
4186
4187 let completion_settings = language_settings(
4188 buffer_snapshot
4189 .language_at(buffer_position)
4190 .map(|language| language.name()),
4191 buffer_snapshot.file(),
4192 cx,
4193 )
4194 .completions;
4195
4196 // The document can be large, so stay in reasonable bounds when searching for words,
4197 // otherwise completion pop-up might be slow to appear.
4198 const WORD_LOOKUP_ROWS: u32 = 5_000;
4199 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4200 let min_word_search = buffer_snapshot.clip_point(
4201 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4202 Bias::Left,
4203 );
4204 let max_word_search = buffer_snapshot.clip_point(
4205 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4206 Bias::Right,
4207 );
4208 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4209 ..buffer_snapshot.point_to_offset(max_word_search);
4210
4211 let provider = self
4212 .completion_provider
4213 .as_ref()
4214 .filter(|_| !ignore_completion_provider);
4215 let skip_digits = query
4216 .as_ref()
4217 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4218
4219 let (mut words, provided_completions) = match provider {
4220 Some(provider) => {
4221 let completions =
4222 provider.completions(&buffer, buffer_position, completion_context, window, cx);
4223
4224 let words = match completion_settings.words {
4225 WordsCompletionMode::Disabled => Task::ready(HashMap::default()),
4226 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4227 .background_spawn(async move {
4228 buffer_snapshot.words_in_range(WordsQuery {
4229 fuzzy_contents: None,
4230 range: word_search_range,
4231 skip_digits,
4232 })
4233 }),
4234 };
4235
4236 (words, completions)
4237 }
4238 None => (
4239 cx.background_spawn(async move {
4240 buffer_snapshot.words_in_range(WordsQuery {
4241 fuzzy_contents: None,
4242 range: word_search_range,
4243 skip_digits,
4244 })
4245 }),
4246 Task::ready(Ok(None)),
4247 ),
4248 };
4249
4250 let sort_completions = provider
4251 .as_ref()
4252 .map_or(true, |provider| provider.sort_completions());
4253
4254 let id = post_inc(&mut self.next_completion_id);
4255 let task = cx.spawn_in(window, async move |editor, cx| {
4256 async move {
4257 editor.update(cx, |this, _| {
4258 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4259 })?;
4260
4261 let mut completions = Vec::new();
4262 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
4263 completions.extend(provided_completions);
4264 if completion_settings.words == WordsCompletionMode::Fallback {
4265 words = Task::ready(HashMap::default());
4266 }
4267 }
4268
4269 let mut words = words.await;
4270 if let Some(word_to_exclude) = &word_to_exclude {
4271 words.remove(word_to_exclude);
4272 }
4273 for lsp_completion in &completions {
4274 words.remove(&lsp_completion.new_text);
4275 }
4276 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
4277 old_range: old_range.clone(),
4278 new_text: word.clone(),
4279 label: CodeLabel::plain(word, None),
4280 documentation: None,
4281 source: CompletionSource::BufferWord {
4282 word_range,
4283 resolved: false,
4284 },
4285 confirm: None,
4286 }));
4287
4288 let menu = if completions.is_empty() {
4289 None
4290 } else {
4291 let mut menu = CompletionsMenu::new(
4292 id,
4293 sort_completions,
4294 show_completion_documentation,
4295 ignore_completion_provider,
4296 position,
4297 buffer.clone(),
4298 completions.into(),
4299 );
4300
4301 menu.filter(query.as_deref(), cx.background_executor().clone())
4302 .await;
4303
4304 menu.visible().then_some(menu)
4305 };
4306
4307 editor.update_in(cx, |editor, window, cx| {
4308 match editor.context_menu.borrow().as_ref() {
4309 None => {}
4310 Some(CodeContextMenu::Completions(prev_menu)) => {
4311 if prev_menu.id > id {
4312 return;
4313 }
4314 }
4315 _ => return,
4316 }
4317
4318 if editor.focus_handle.is_focused(window) && menu.is_some() {
4319 let mut menu = menu.unwrap();
4320 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
4321
4322 *editor.context_menu.borrow_mut() =
4323 Some(CodeContextMenu::Completions(menu));
4324
4325 if editor.show_edit_predictions_in_menu() {
4326 editor.update_visible_inline_completion(window, cx);
4327 } else {
4328 editor.discard_inline_completion(false, cx);
4329 }
4330
4331 cx.notify();
4332 } else if editor.completion_tasks.len() <= 1 {
4333 // If there are no more completion tasks and the last menu was
4334 // empty, we should hide it.
4335 let was_hidden = editor.hide_context_menu(window, cx).is_none();
4336 // If it was already hidden and we don't show inline
4337 // completions in the menu, we should also show the
4338 // inline-completion when available.
4339 if was_hidden && editor.show_edit_predictions_in_menu() {
4340 editor.update_visible_inline_completion(window, cx);
4341 }
4342 }
4343 })?;
4344
4345 anyhow::Ok(())
4346 }
4347 .log_err()
4348 .await
4349 });
4350
4351 self.completion_tasks.push((id, task));
4352 }
4353
4354 pub fn confirm_completion(
4355 &mut self,
4356 action: &ConfirmCompletion,
4357 window: &mut Window,
4358 cx: &mut Context<Self>,
4359 ) -> Option<Task<Result<()>>> {
4360 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
4361 }
4362
4363 pub fn compose_completion(
4364 &mut self,
4365 action: &ComposeCompletion,
4366 window: &mut Window,
4367 cx: &mut Context<Self>,
4368 ) -> Option<Task<Result<()>>> {
4369 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
4370 }
4371
4372 fn do_completion(
4373 &mut self,
4374 item_ix: Option<usize>,
4375 intent: CompletionIntent,
4376 window: &mut Window,
4377 cx: &mut Context<Editor>,
4378 ) -> Option<Task<std::result::Result<(), anyhow::Error>>> {
4379 use language::ToOffset as _;
4380
4381 let completions_menu =
4382 if let CodeContextMenu::Completions(menu) = self.hide_context_menu(window, cx)? {
4383 menu
4384 } else {
4385 return None;
4386 };
4387
4388 let entries = completions_menu.entries.borrow();
4389 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
4390 if self.show_edit_predictions_in_menu() {
4391 self.discard_inline_completion(true, cx);
4392 }
4393 let candidate_id = mat.candidate_id;
4394 drop(entries);
4395
4396 let buffer_handle = completions_menu.buffer;
4397 let completion = completions_menu
4398 .completions
4399 .borrow()
4400 .get(candidate_id)?
4401 .clone();
4402 cx.stop_propagation();
4403
4404 let snippet;
4405 let text;
4406
4407 if completion.is_snippet() {
4408 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
4409 text = snippet.as_ref().unwrap().text.clone();
4410 } else {
4411 snippet = None;
4412 text = completion.new_text.clone();
4413 };
4414 let selections = self.selections.all::<usize>(cx);
4415 let buffer = buffer_handle.read(cx);
4416 let old_range = completion.old_range.to_offset(buffer);
4417 let old_text = buffer.text_for_range(old_range.clone()).collect::<String>();
4418
4419 let newest_selection = self.selections.newest_anchor();
4420 if newest_selection.start.buffer_id != Some(buffer_handle.read(cx).remote_id()) {
4421 return None;
4422 }
4423
4424 let lookbehind = newest_selection
4425 .start
4426 .text_anchor
4427 .to_offset(buffer)
4428 .saturating_sub(old_range.start);
4429 let lookahead = old_range
4430 .end
4431 .saturating_sub(newest_selection.end.text_anchor.to_offset(buffer));
4432 let mut common_prefix_len = old_text
4433 .bytes()
4434 .zip(text.bytes())
4435 .take_while(|(a, b)| a == b)
4436 .count();
4437
4438 let snapshot = self.buffer.read(cx).snapshot(cx);
4439 let mut range_to_replace: Option<Range<isize>> = None;
4440 let mut ranges = Vec::new();
4441 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4442 for selection in &selections {
4443 if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) {
4444 let start = selection.start.saturating_sub(lookbehind);
4445 let end = selection.end + lookahead;
4446 if selection.id == newest_selection.id {
4447 range_to_replace = Some(
4448 ((start + common_prefix_len) as isize - selection.start as isize)
4449 ..(end as isize - selection.start as isize),
4450 );
4451 }
4452 ranges.push(start + common_prefix_len..end);
4453 } else {
4454 common_prefix_len = 0;
4455 ranges.clear();
4456 ranges.extend(selections.iter().map(|s| {
4457 if s.id == newest_selection.id {
4458 range_to_replace = Some(
4459 old_range.start.to_offset_utf16(&snapshot).0 as isize
4460 - selection.start as isize
4461 ..old_range.end.to_offset_utf16(&snapshot).0 as isize
4462 - selection.start as isize,
4463 );
4464 old_range.clone()
4465 } else {
4466 s.start..s.end
4467 }
4468 }));
4469 break;
4470 }
4471 if !self.linked_edit_ranges.is_empty() {
4472 let start_anchor = snapshot.anchor_before(selection.head());
4473 let end_anchor = snapshot.anchor_after(selection.tail());
4474 if let Some(ranges) = self
4475 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
4476 {
4477 for (buffer, edits) in ranges {
4478 linked_edits.entry(buffer.clone()).or_default().extend(
4479 edits
4480 .into_iter()
4481 .map(|range| (range, text[common_prefix_len..].to_owned())),
4482 );
4483 }
4484 }
4485 }
4486 }
4487 let text = &text[common_prefix_len..];
4488
4489 cx.emit(EditorEvent::InputHandled {
4490 utf16_range_to_replace: range_to_replace,
4491 text: text.into(),
4492 });
4493
4494 self.transact(window, cx, |this, window, cx| {
4495 if let Some(mut snippet) = snippet {
4496 snippet.text = text.to_string();
4497 for tabstop in snippet
4498 .tabstops
4499 .iter_mut()
4500 .flat_map(|tabstop| tabstop.ranges.iter_mut())
4501 {
4502 tabstop.start -= common_prefix_len as isize;
4503 tabstop.end -= common_prefix_len as isize;
4504 }
4505
4506 this.insert_snippet(&ranges, snippet, window, cx).log_err();
4507 } else {
4508 this.buffer.update(cx, |buffer, cx| {
4509 buffer.edit(
4510 ranges.iter().map(|range| (range.clone(), text)),
4511 this.autoindent_mode.clone(),
4512 cx,
4513 );
4514 });
4515 }
4516 for (buffer, edits) in linked_edits {
4517 buffer.update(cx, |buffer, cx| {
4518 let snapshot = buffer.snapshot();
4519 let edits = edits
4520 .into_iter()
4521 .map(|(range, text)| {
4522 use text::ToPoint as TP;
4523 let end_point = TP::to_point(&range.end, &snapshot);
4524 let start_point = TP::to_point(&range.start, &snapshot);
4525 (start_point..end_point, text)
4526 })
4527 .sorted_by_key(|(range, _)| range.start)
4528 .collect::<Vec<_>>();
4529 buffer.edit(edits, None, cx);
4530 })
4531 }
4532
4533 this.refresh_inline_completion(true, false, window, cx);
4534 });
4535
4536 let show_new_completions_on_confirm = completion
4537 .confirm
4538 .as_ref()
4539 .map_or(false, |confirm| confirm(intent, window, cx));
4540 if show_new_completions_on_confirm {
4541 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
4542 }
4543
4544 let provider = self.completion_provider.as_ref()?;
4545 drop(completion);
4546 let apply_edits = provider.apply_additional_edits_for_completion(
4547 buffer_handle,
4548 completions_menu.completions.clone(),
4549 candidate_id,
4550 true,
4551 cx,
4552 );
4553
4554 let editor_settings = EditorSettings::get_global(cx);
4555 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
4556 // After the code completion is finished, users often want to know what signatures are needed.
4557 // so we should automatically call signature_help
4558 self.show_signature_help(&ShowSignatureHelp, window, cx);
4559 }
4560
4561 Some(cx.foreground_executor().spawn(async move {
4562 apply_edits.await?;
4563 Ok(())
4564 }))
4565 }
4566
4567 pub fn toggle_code_actions(
4568 &mut self,
4569 action: &ToggleCodeActions,
4570 window: &mut Window,
4571 cx: &mut Context<Self>,
4572 ) {
4573 let mut context_menu = self.context_menu.borrow_mut();
4574 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
4575 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
4576 // Toggle if we're selecting the same one
4577 *context_menu = None;
4578 cx.notify();
4579 return;
4580 } else {
4581 // Otherwise, clear it and start a new one
4582 *context_menu = None;
4583 cx.notify();
4584 }
4585 }
4586 drop(context_menu);
4587 let snapshot = self.snapshot(window, cx);
4588 let deployed_from_indicator = action.deployed_from_indicator;
4589 let mut task = self.code_actions_task.take();
4590 let action = action.clone();
4591 cx.spawn_in(window, async move |editor, cx| {
4592 while let Some(prev_task) = task {
4593 prev_task.await.log_err();
4594 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
4595 }
4596
4597 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
4598 if editor.focus_handle.is_focused(window) {
4599 let multibuffer_point = action
4600 .deployed_from_indicator
4601 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
4602 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
4603 let (buffer, buffer_row) = snapshot
4604 .buffer_snapshot
4605 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
4606 .and_then(|(buffer_snapshot, range)| {
4607 editor
4608 .buffer
4609 .read(cx)
4610 .buffer(buffer_snapshot.remote_id())
4611 .map(|buffer| (buffer, range.start.row))
4612 })?;
4613 let (_, code_actions) = editor
4614 .available_code_actions
4615 .clone()
4616 .and_then(|(location, code_actions)| {
4617 let snapshot = location.buffer.read(cx).snapshot();
4618 let point_range = location.range.to_point(&snapshot);
4619 let point_range = point_range.start.row..=point_range.end.row;
4620 if point_range.contains(&buffer_row) {
4621 Some((location, code_actions))
4622 } else {
4623 None
4624 }
4625 })
4626 .unzip();
4627 let buffer_id = buffer.read(cx).remote_id();
4628 let tasks = editor
4629 .tasks
4630 .get(&(buffer_id, buffer_row))
4631 .map(|t| Arc::new(t.to_owned()));
4632 if tasks.is_none() && code_actions.is_none() {
4633 return None;
4634 }
4635
4636 editor.completion_tasks.clear();
4637 editor.discard_inline_completion(false, cx);
4638 let task_context =
4639 tasks
4640 .as_ref()
4641 .zip(editor.project.clone())
4642 .map(|(tasks, project)| {
4643 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
4644 });
4645
4646 Some(cx.spawn_in(window, async move |editor, cx| {
4647 let task_context = match task_context {
4648 Some(task_context) => task_context.await,
4649 None => None,
4650 };
4651 let resolved_tasks =
4652 tasks.zip(task_context).map(|(tasks, task_context)| {
4653 Rc::new(ResolvedTasks {
4654 templates: tasks.resolve(&task_context).collect(),
4655 position: snapshot.buffer_snapshot.anchor_before(Point::new(
4656 multibuffer_point.row,
4657 tasks.column,
4658 )),
4659 })
4660 });
4661 let spawn_straight_away = resolved_tasks
4662 .as_ref()
4663 .map_or(false, |tasks| tasks.templates.len() == 1)
4664 && code_actions
4665 .as_ref()
4666 .map_or(true, |actions| actions.is_empty());
4667 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
4668 *editor.context_menu.borrow_mut() =
4669 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
4670 buffer,
4671 actions: CodeActionContents {
4672 tasks: resolved_tasks,
4673 actions: code_actions,
4674 },
4675 selected_item: Default::default(),
4676 scroll_handle: UniformListScrollHandle::default(),
4677 deployed_from_indicator,
4678 }));
4679 if spawn_straight_away {
4680 if let Some(task) = editor.confirm_code_action(
4681 &ConfirmCodeAction { item_ix: Some(0) },
4682 window,
4683 cx,
4684 ) {
4685 cx.notify();
4686 return task;
4687 }
4688 }
4689 cx.notify();
4690 Task::ready(Ok(()))
4691 }) {
4692 task.await
4693 } else {
4694 Ok(())
4695 }
4696 }))
4697 } else {
4698 Some(Task::ready(Ok(())))
4699 }
4700 })?;
4701 if let Some(task) = spawned_test_task {
4702 task.await?;
4703 }
4704
4705 Ok::<_, anyhow::Error>(())
4706 })
4707 .detach_and_log_err(cx);
4708 }
4709
4710 pub fn confirm_code_action(
4711 &mut self,
4712 action: &ConfirmCodeAction,
4713 window: &mut Window,
4714 cx: &mut Context<Self>,
4715 ) -> Option<Task<Result<()>>> {
4716 let actions_menu =
4717 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
4718 menu
4719 } else {
4720 return None;
4721 };
4722 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
4723 let action = actions_menu.actions.get(action_ix)?;
4724 let title = action.label();
4725 let buffer = actions_menu.buffer;
4726 let workspace = self.workspace()?;
4727
4728 match action {
4729 CodeActionsItem::Task(task_source_kind, resolved_task) => {
4730 workspace.update(cx, |workspace, cx| {
4731 workspace::tasks::schedule_resolved_task(
4732 workspace,
4733 task_source_kind,
4734 resolved_task,
4735 false,
4736 cx,
4737 );
4738
4739 Some(Task::ready(Ok(())))
4740 })
4741 }
4742 CodeActionsItem::CodeAction {
4743 excerpt_id,
4744 action,
4745 provider,
4746 } => {
4747 let apply_code_action =
4748 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
4749 let workspace = workspace.downgrade();
4750 Some(cx.spawn_in(window, async move |editor, cx| {
4751 let project_transaction = apply_code_action.await?;
4752 Self::open_project_transaction(
4753 &editor,
4754 workspace,
4755 project_transaction,
4756 title,
4757 cx,
4758 )
4759 .await
4760 }))
4761 }
4762 }
4763 }
4764
4765 pub async fn open_project_transaction(
4766 this: &WeakEntity<Editor>,
4767 workspace: WeakEntity<Workspace>,
4768 transaction: ProjectTransaction,
4769 title: String,
4770 cx: &mut AsyncWindowContext,
4771 ) -> Result<()> {
4772 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
4773 cx.update(|_, cx| {
4774 entries.sort_unstable_by_key(|(buffer, _)| {
4775 buffer.read(cx).file().map(|f| f.path().clone())
4776 });
4777 })?;
4778
4779 // If the project transaction's edits are all contained within this editor, then
4780 // avoid opening a new editor to display them.
4781
4782 if let Some((buffer, transaction)) = entries.first() {
4783 if entries.len() == 1 {
4784 let excerpt = this.update(cx, |editor, cx| {
4785 editor
4786 .buffer()
4787 .read(cx)
4788 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
4789 })?;
4790 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
4791 if excerpted_buffer == *buffer {
4792 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
4793 let excerpt_range = excerpt_range.to_offset(buffer);
4794 buffer
4795 .edited_ranges_for_transaction::<usize>(transaction)
4796 .all(|range| {
4797 excerpt_range.start <= range.start
4798 && excerpt_range.end >= range.end
4799 })
4800 })?;
4801
4802 if all_edits_within_excerpt {
4803 return Ok(());
4804 }
4805 }
4806 }
4807 }
4808 } else {
4809 return Ok(());
4810 }
4811
4812 let mut ranges_to_highlight = Vec::new();
4813 let excerpt_buffer = cx.new(|cx| {
4814 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
4815 for (buffer_handle, transaction) in &entries {
4816 let buffer = buffer_handle.read(cx);
4817 ranges_to_highlight.extend(
4818 multibuffer.push_excerpts_with_context_lines(
4819 buffer_handle.clone(),
4820 buffer
4821 .edited_ranges_for_transaction::<usize>(transaction)
4822 .collect(),
4823 DEFAULT_MULTIBUFFER_CONTEXT,
4824 cx,
4825 ),
4826 );
4827 }
4828 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
4829 multibuffer
4830 })?;
4831
4832 workspace.update_in(cx, |workspace, window, cx| {
4833 let project = workspace.project().clone();
4834 let editor =
4835 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
4836 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
4837 editor.update(cx, |editor, cx| {
4838 editor.highlight_background::<Self>(
4839 &ranges_to_highlight,
4840 |theme| theme.editor_highlighted_line_background,
4841 cx,
4842 );
4843 });
4844 })?;
4845
4846 Ok(())
4847 }
4848
4849 pub fn clear_code_action_providers(&mut self) {
4850 self.code_action_providers.clear();
4851 self.available_code_actions.take();
4852 }
4853
4854 pub fn add_code_action_provider(
4855 &mut self,
4856 provider: Rc<dyn CodeActionProvider>,
4857 window: &mut Window,
4858 cx: &mut Context<Self>,
4859 ) {
4860 if self
4861 .code_action_providers
4862 .iter()
4863 .any(|existing_provider| existing_provider.id() == provider.id())
4864 {
4865 return;
4866 }
4867
4868 self.code_action_providers.push(provider);
4869 self.refresh_code_actions(window, cx);
4870 }
4871
4872 pub fn remove_code_action_provider(
4873 &mut self,
4874 id: Arc<str>,
4875 window: &mut Window,
4876 cx: &mut Context<Self>,
4877 ) {
4878 self.code_action_providers
4879 .retain(|provider| provider.id() != id);
4880 self.refresh_code_actions(window, cx);
4881 }
4882
4883 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
4884 let buffer = self.buffer.read(cx);
4885 let newest_selection = self.selections.newest_anchor().clone();
4886 if newest_selection.head().diff_base_anchor.is_some() {
4887 return None;
4888 }
4889 let (start_buffer, start) = buffer.text_anchor_for_position(newest_selection.start, cx)?;
4890 let (end_buffer, end) = buffer.text_anchor_for_position(newest_selection.end, cx)?;
4891 if start_buffer != end_buffer {
4892 return None;
4893 }
4894
4895 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
4896 cx.background_executor()
4897 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
4898 .await;
4899
4900 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
4901 let providers = this.code_action_providers.clone();
4902 let tasks = this
4903 .code_action_providers
4904 .iter()
4905 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
4906 .collect::<Vec<_>>();
4907 (providers, tasks)
4908 })?;
4909
4910 let mut actions = Vec::new();
4911 for (provider, provider_actions) in
4912 providers.into_iter().zip(future::join_all(tasks).await)
4913 {
4914 if let Some(provider_actions) = provider_actions.log_err() {
4915 actions.extend(provider_actions.into_iter().map(|action| {
4916 AvailableCodeAction {
4917 excerpt_id: newest_selection.start.excerpt_id,
4918 action,
4919 provider: provider.clone(),
4920 }
4921 }));
4922 }
4923 }
4924
4925 this.update(cx, |this, cx| {
4926 this.available_code_actions = if actions.is_empty() {
4927 None
4928 } else {
4929 Some((
4930 Location {
4931 buffer: start_buffer,
4932 range: start..end,
4933 },
4934 actions.into(),
4935 ))
4936 };
4937 cx.notify();
4938 })
4939 }));
4940 None
4941 }
4942
4943 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4944 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
4945 self.show_git_blame_inline = false;
4946
4947 self.show_git_blame_inline_delay_task =
4948 Some(cx.spawn_in(window, async move |this, cx| {
4949 cx.background_executor().timer(delay).await;
4950
4951 this.update(cx, |this, cx| {
4952 this.show_git_blame_inline = true;
4953 cx.notify();
4954 })
4955 .log_err();
4956 }));
4957 }
4958 }
4959
4960 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
4961 if self.pending_rename.is_some() {
4962 return None;
4963 }
4964
4965 let provider = self.semantics_provider.clone()?;
4966 let buffer = self.buffer.read(cx);
4967 let newest_selection = self.selections.newest_anchor().clone();
4968 let cursor_position = newest_selection.head();
4969 let (cursor_buffer, cursor_buffer_position) =
4970 buffer.text_anchor_for_position(cursor_position, cx)?;
4971 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
4972 if cursor_buffer != tail_buffer {
4973 return None;
4974 }
4975 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
4976 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
4977 cx.background_executor()
4978 .timer(Duration::from_millis(debounce))
4979 .await;
4980
4981 let highlights = if let Some(highlights) = cx
4982 .update(|cx| {
4983 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
4984 })
4985 .ok()
4986 .flatten()
4987 {
4988 highlights.await.log_err()
4989 } else {
4990 None
4991 };
4992
4993 if let Some(highlights) = highlights {
4994 this.update(cx, |this, cx| {
4995 if this.pending_rename.is_some() {
4996 return;
4997 }
4998
4999 let buffer_id = cursor_position.buffer_id;
5000 let buffer = this.buffer.read(cx);
5001 if !buffer
5002 .text_anchor_for_position(cursor_position, cx)
5003 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5004 {
5005 return;
5006 }
5007
5008 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5009 let mut write_ranges = Vec::new();
5010 let mut read_ranges = Vec::new();
5011 for highlight in highlights {
5012 for (excerpt_id, excerpt_range) in
5013 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5014 {
5015 let start = highlight
5016 .range
5017 .start
5018 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5019 let end = highlight
5020 .range
5021 .end
5022 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5023 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5024 continue;
5025 }
5026
5027 let range = Anchor {
5028 buffer_id,
5029 excerpt_id,
5030 text_anchor: start,
5031 diff_base_anchor: None,
5032 }..Anchor {
5033 buffer_id,
5034 excerpt_id,
5035 text_anchor: end,
5036 diff_base_anchor: None,
5037 };
5038 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5039 write_ranges.push(range);
5040 } else {
5041 read_ranges.push(range);
5042 }
5043 }
5044 }
5045
5046 this.highlight_background::<DocumentHighlightRead>(
5047 &read_ranges,
5048 |theme| theme.editor_document_highlight_read_background,
5049 cx,
5050 );
5051 this.highlight_background::<DocumentHighlightWrite>(
5052 &write_ranges,
5053 |theme| theme.editor_document_highlight_write_background,
5054 cx,
5055 );
5056 cx.notify();
5057 })
5058 .log_err();
5059 }
5060 }));
5061 None
5062 }
5063
5064 pub fn refresh_selected_text_highlights(
5065 &mut self,
5066 window: &mut Window,
5067 cx: &mut Context<Editor>,
5068 ) {
5069 if matches!(self.mode, EditorMode::SingleLine { .. }) {
5070 return;
5071 }
5072 self.selection_highlight_task.take();
5073 if !EditorSettings::get_global(cx).selection_highlight {
5074 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5075 return;
5076 }
5077 if self.selections.count() != 1 || self.selections.line_mode {
5078 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5079 return;
5080 }
5081 let selection = self.selections.newest::<Point>(cx);
5082 if selection.is_empty() || selection.start.row != selection.end.row {
5083 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5084 return;
5085 }
5086 let debounce = EditorSettings::get_global(cx).selection_highlight_debounce;
5087 self.selection_highlight_task = Some(cx.spawn_in(window, async move |editor, cx| {
5088 cx.background_executor()
5089 .timer(Duration::from_millis(debounce))
5090 .await;
5091 let Some(Some(matches_task)) = editor
5092 .update_in(cx, |editor, _, cx| {
5093 if editor.selections.count() != 1 || editor.selections.line_mode {
5094 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5095 return None;
5096 }
5097 let selection = editor.selections.newest::<Point>(cx);
5098 if selection.is_empty() || selection.start.row != selection.end.row {
5099 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5100 return None;
5101 }
5102 let buffer = editor.buffer().read(cx).snapshot(cx);
5103 let query = buffer.text_for_range(selection.range()).collect::<String>();
5104 if query.trim().is_empty() {
5105 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5106 return None;
5107 }
5108 Some(cx.background_spawn(async move {
5109 let mut ranges = Vec::new();
5110 let selection_anchors = selection.range().to_anchors(&buffer);
5111 for range in [buffer.anchor_before(0)..buffer.anchor_after(buffer.len())] {
5112 for (search_buffer, search_range, excerpt_id) in
5113 buffer.range_to_buffer_ranges(range)
5114 {
5115 ranges.extend(
5116 project::search::SearchQuery::text(
5117 query.clone(),
5118 false,
5119 false,
5120 false,
5121 Default::default(),
5122 Default::default(),
5123 None,
5124 )
5125 .unwrap()
5126 .search(search_buffer, Some(search_range.clone()))
5127 .await
5128 .into_iter()
5129 .filter_map(
5130 |match_range| {
5131 let start = search_buffer.anchor_after(
5132 search_range.start + match_range.start,
5133 );
5134 let end = search_buffer.anchor_before(
5135 search_range.start + match_range.end,
5136 );
5137 let range = Anchor::range_in_buffer(
5138 excerpt_id,
5139 search_buffer.remote_id(),
5140 start..end,
5141 );
5142 (range != selection_anchors).then_some(range)
5143 },
5144 ),
5145 );
5146 }
5147 }
5148 ranges
5149 }))
5150 })
5151 .log_err()
5152 else {
5153 return;
5154 };
5155 let matches = matches_task.await;
5156 editor
5157 .update_in(cx, |editor, _, cx| {
5158 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5159 if !matches.is_empty() {
5160 editor.highlight_background::<SelectedTextHighlight>(
5161 &matches,
5162 |theme| theme.editor_document_highlight_bracket_background,
5163 cx,
5164 )
5165 }
5166 })
5167 .log_err();
5168 }));
5169 }
5170
5171 pub fn refresh_inline_completion(
5172 &mut self,
5173 debounce: bool,
5174 user_requested: bool,
5175 window: &mut Window,
5176 cx: &mut Context<Self>,
5177 ) -> Option<()> {
5178 let provider = self.edit_prediction_provider()?;
5179 let cursor = self.selections.newest_anchor().head();
5180 let (buffer, cursor_buffer_position) =
5181 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5182
5183 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
5184 self.discard_inline_completion(false, cx);
5185 return None;
5186 }
5187
5188 if !user_requested
5189 && (!self.should_show_edit_predictions()
5190 || !self.is_focused(window)
5191 || buffer.read(cx).is_empty())
5192 {
5193 self.discard_inline_completion(false, cx);
5194 return None;
5195 }
5196
5197 self.update_visible_inline_completion(window, cx);
5198 provider.refresh(
5199 self.project.clone(),
5200 buffer,
5201 cursor_buffer_position,
5202 debounce,
5203 cx,
5204 );
5205 Some(())
5206 }
5207
5208 fn show_edit_predictions_in_menu(&self) -> bool {
5209 match self.edit_prediction_settings {
5210 EditPredictionSettings::Disabled => false,
5211 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
5212 }
5213 }
5214
5215 pub fn edit_predictions_enabled(&self) -> bool {
5216 match self.edit_prediction_settings {
5217 EditPredictionSettings::Disabled => false,
5218 EditPredictionSettings::Enabled { .. } => true,
5219 }
5220 }
5221
5222 fn edit_prediction_requires_modifier(&self) -> bool {
5223 match self.edit_prediction_settings {
5224 EditPredictionSettings::Disabled => false,
5225 EditPredictionSettings::Enabled {
5226 preview_requires_modifier,
5227 ..
5228 } => preview_requires_modifier,
5229 }
5230 }
5231
5232 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
5233 if self.edit_prediction_provider.is_none() {
5234 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5235 } else {
5236 let selection = self.selections.newest_anchor();
5237 let cursor = selection.head();
5238
5239 if let Some((buffer, cursor_buffer_position)) =
5240 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5241 {
5242 self.edit_prediction_settings =
5243 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5244 }
5245 }
5246 }
5247
5248 fn edit_prediction_settings_at_position(
5249 &self,
5250 buffer: &Entity<Buffer>,
5251 buffer_position: language::Anchor,
5252 cx: &App,
5253 ) -> EditPredictionSettings {
5254 if self.mode != EditorMode::Full
5255 || !self.show_inline_completions_override.unwrap_or(true)
5256 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
5257 {
5258 return EditPredictionSettings::Disabled;
5259 }
5260
5261 let buffer = buffer.read(cx);
5262
5263 let file = buffer.file();
5264
5265 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
5266 return EditPredictionSettings::Disabled;
5267 };
5268
5269 let by_provider = matches!(
5270 self.menu_inline_completions_policy,
5271 MenuInlineCompletionsPolicy::ByProvider
5272 );
5273
5274 let show_in_menu = by_provider
5275 && self
5276 .edit_prediction_provider
5277 .as_ref()
5278 .map_or(false, |provider| {
5279 provider.provider.show_completions_in_menu()
5280 });
5281
5282 let preview_requires_modifier =
5283 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
5284
5285 EditPredictionSettings::Enabled {
5286 show_in_menu,
5287 preview_requires_modifier,
5288 }
5289 }
5290
5291 fn should_show_edit_predictions(&self) -> bool {
5292 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
5293 }
5294
5295 pub fn edit_prediction_preview_is_active(&self) -> bool {
5296 matches!(
5297 self.edit_prediction_preview,
5298 EditPredictionPreview::Active { .. }
5299 )
5300 }
5301
5302 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
5303 let cursor = self.selections.newest_anchor().head();
5304 if let Some((buffer, cursor_position)) =
5305 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5306 {
5307 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
5308 } else {
5309 false
5310 }
5311 }
5312
5313 fn edit_predictions_enabled_in_buffer(
5314 &self,
5315 buffer: &Entity<Buffer>,
5316 buffer_position: language::Anchor,
5317 cx: &App,
5318 ) -> bool {
5319 maybe!({
5320 if self.read_only(cx) {
5321 return Some(false);
5322 }
5323 let provider = self.edit_prediction_provider()?;
5324 if !provider.is_enabled(&buffer, buffer_position, cx) {
5325 return Some(false);
5326 }
5327 let buffer = buffer.read(cx);
5328 let Some(file) = buffer.file() else {
5329 return Some(true);
5330 };
5331 let settings = all_language_settings(Some(file), cx);
5332 Some(settings.edit_predictions_enabled_for_file(file, cx))
5333 })
5334 .unwrap_or(false)
5335 }
5336
5337 fn cycle_inline_completion(
5338 &mut self,
5339 direction: Direction,
5340 window: &mut Window,
5341 cx: &mut Context<Self>,
5342 ) -> Option<()> {
5343 let provider = self.edit_prediction_provider()?;
5344 let cursor = self.selections.newest_anchor().head();
5345 let (buffer, cursor_buffer_position) =
5346 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5347 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
5348 return None;
5349 }
5350
5351 provider.cycle(buffer, cursor_buffer_position, direction, cx);
5352 self.update_visible_inline_completion(window, cx);
5353
5354 Some(())
5355 }
5356
5357 pub fn show_inline_completion(
5358 &mut self,
5359 _: &ShowEditPrediction,
5360 window: &mut Window,
5361 cx: &mut Context<Self>,
5362 ) {
5363 if !self.has_active_inline_completion() {
5364 self.refresh_inline_completion(false, true, window, cx);
5365 return;
5366 }
5367
5368 self.update_visible_inline_completion(window, cx);
5369 }
5370
5371 pub fn display_cursor_names(
5372 &mut self,
5373 _: &DisplayCursorNames,
5374 window: &mut Window,
5375 cx: &mut Context<Self>,
5376 ) {
5377 self.show_cursor_names(window, cx);
5378 }
5379
5380 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5381 self.show_cursor_names = true;
5382 cx.notify();
5383 cx.spawn_in(window, async move |this, cx| {
5384 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
5385 this.update(cx, |this, cx| {
5386 this.show_cursor_names = false;
5387 cx.notify()
5388 })
5389 .ok()
5390 })
5391 .detach();
5392 }
5393
5394 pub fn next_edit_prediction(
5395 &mut self,
5396 _: &NextEditPrediction,
5397 window: &mut Window,
5398 cx: &mut Context<Self>,
5399 ) {
5400 if self.has_active_inline_completion() {
5401 self.cycle_inline_completion(Direction::Next, window, cx);
5402 } else {
5403 let is_copilot_disabled = self
5404 .refresh_inline_completion(false, true, window, cx)
5405 .is_none();
5406 if is_copilot_disabled {
5407 cx.propagate();
5408 }
5409 }
5410 }
5411
5412 pub fn previous_edit_prediction(
5413 &mut self,
5414 _: &PreviousEditPrediction,
5415 window: &mut Window,
5416 cx: &mut Context<Self>,
5417 ) {
5418 if self.has_active_inline_completion() {
5419 self.cycle_inline_completion(Direction::Prev, window, cx);
5420 } else {
5421 let is_copilot_disabled = self
5422 .refresh_inline_completion(false, true, window, cx)
5423 .is_none();
5424 if is_copilot_disabled {
5425 cx.propagate();
5426 }
5427 }
5428 }
5429
5430 pub fn accept_edit_prediction(
5431 &mut self,
5432 _: &AcceptEditPrediction,
5433 window: &mut Window,
5434 cx: &mut Context<Self>,
5435 ) {
5436 if self.show_edit_predictions_in_menu() {
5437 self.hide_context_menu(window, cx);
5438 }
5439
5440 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5441 return;
5442 };
5443
5444 self.report_inline_completion_event(
5445 active_inline_completion.completion_id.clone(),
5446 true,
5447 cx,
5448 );
5449
5450 match &active_inline_completion.completion {
5451 InlineCompletion::Move { target, .. } => {
5452 let target = *target;
5453
5454 if let Some(position_map) = &self.last_position_map {
5455 if position_map
5456 .visible_row_range
5457 .contains(&target.to_display_point(&position_map.snapshot).row())
5458 || !self.edit_prediction_requires_modifier()
5459 {
5460 self.unfold_ranges(&[target..target], true, false, cx);
5461 // Note that this is also done in vim's handler of the Tab action.
5462 self.change_selections(
5463 Some(Autoscroll::newest()),
5464 window,
5465 cx,
5466 |selections| {
5467 selections.select_anchor_ranges([target..target]);
5468 },
5469 );
5470 self.clear_row_highlights::<EditPredictionPreview>();
5471
5472 self.edit_prediction_preview
5473 .set_previous_scroll_position(None);
5474 } else {
5475 self.edit_prediction_preview
5476 .set_previous_scroll_position(Some(
5477 position_map.snapshot.scroll_anchor,
5478 ));
5479
5480 self.highlight_rows::<EditPredictionPreview>(
5481 target..target,
5482 cx.theme().colors().editor_highlighted_line_background,
5483 true,
5484 cx,
5485 );
5486 self.request_autoscroll(Autoscroll::fit(), cx);
5487 }
5488 }
5489 }
5490 InlineCompletion::Edit { edits, .. } => {
5491 if let Some(provider) = self.edit_prediction_provider() {
5492 provider.accept(cx);
5493 }
5494
5495 let snapshot = self.buffer.read(cx).snapshot(cx);
5496 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
5497
5498 self.buffer.update(cx, |buffer, cx| {
5499 buffer.edit(edits.iter().cloned(), None, cx)
5500 });
5501
5502 self.change_selections(None, window, cx, |s| {
5503 s.select_anchor_ranges([last_edit_end..last_edit_end])
5504 });
5505
5506 self.update_visible_inline_completion(window, cx);
5507 if self.active_inline_completion.is_none() {
5508 self.refresh_inline_completion(true, true, window, cx);
5509 }
5510
5511 cx.notify();
5512 }
5513 }
5514
5515 self.edit_prediction_requires_modifier_in_indent_conflict = false;
5516 }
5517
5518 pub fn accept_partial_inline_completion(
5519 &mut self,
5520 _: &AcceptPartialEditPrediction,
5521 window: &mut Window,
5522 cx: &mut Context<Self>,
5523 ) {
5524 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5525 return;
5526 };
5527 if self.selections.count() != 1 {
5528 return;
5529 }
5530
5531 self.report_inline_completion_event(
5532 active_inline_completion.completion_id.clone(),
5533 true,
5534 cx,
5535 );
5536
5537 match &active_inline_completion.completion {
5538 InlineCompletion::Move { target, .. } => {
5539 let target = *target;
5540 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
5541 selections.select_anchor_ranges([target..target]);
5542 });
5543 }
5544 InlineCompletion::Edit { edits, .. } => {
5545 // Find an insertion that starts at the cursor position.
5546 let snapshot = self.buffer.read(cx).snapshot(cx);
5547 let cursor_offset = self.selections.newest::<usize>(cx).head();
5548 let insertion = edits.iter().find_map(|(range, text)| {
5549 let range = range.to_offset(&snapshot);
5550 if range.is_empty() && range.start == cursor_offset {
5551 Some(text)
5552 } else {
5553 None
5554 }
5555 });
5556
5557 if let Some(text) = insertion {
5558 let mut partial_completion = text
5559 .chars()
5560 .by_ref()
5561 .take_while(|c| c.is_alphabetic())
5562 .collect::<String>();
5563 if partial_completion.is_empty() {
5564 partial_completion = text
5565 .chars()
5566 .by_ref()
5567 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
5568 .collect::<String>();
5569 }
5570
5571 cx.emit(EditorEvent::InputHandled {
5572 utf16_range_to_replace: None,
5573 text: partial_completion.clone().into(),
5574 });
5575
5576 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
5577
5578 self.refresh_inline_completion(true, true, window, cx);
5579 cx.notify();
5580 } else {
5581 self.accept_edit_prediction(&Default::default(), window, cx);
5582 }
5583 }
5584 }
5585 }
5586
5587 fn discard_inline_completion(
5588 &mut self,
5589 should_report_inline_completion_event: bool,
5590 cx: &mut Context<Self>,
5591 ) -> bool {
5592 if should_report_inline_completion_event {
5593 let completion_id = self
5594 .active_inline_completion
5595 .as_ref()
5596 .and_then(|active_completion| active_completion.completion_id.clone());
5597
5598 self.report_inline_completion_event(completion_id, false, cx);
5599 }
5600
5601 if let Some(provider) = self.edit_prediction_provider() {
5602 provider.discard(cx);
5603 }
5604
5605 self.take_active_inline_completion(cx)
5606 }
5607
5608 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
5609 let Some(provider) = self.edit_prediction_provider() else {
5610 return;
5611 };
5612
5613 let Some((_, buffer, _)) = self
5614 .buffer
5615 .read(cx)
5616 .excerpt_containing(self.selections.newest_anchor().head(), cx)
5617 else {
5618 return;
5619 };
5620
5621 let extension = buffer
5622 .read(cx)
5623 .file()
5624 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
5625
5626 let event_type = match accepted {
5627 true => "Edit Prediction Accepted",
5628 false => "Edit Prediction Discarded",
5629 };
5630 telemetry::event!(
5631 event_type,
5632 provider = provider.name(),
5633 prediction_id = id,
5634 suggestion_accepted = accepted,
5635 file_extension = extension,
5636 );
5637 }
5638
5639 pub fn has_active_inline_completion(&self) -> bool {
5640 self.active_inline_completion.is_some()
5641 }
5642
5643 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
5644 let Some(active_inline_completion) = self.active_inline_completion.take() else {
5645 return false;
5646 };
5647
5648 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
5649 self.clear_highlights::<InlineCompletionHighlight>(cx);
5650 self.stale_inline_completion_in_menu = Some(active_inline_completion);
5651 true
5652 }
5653
5654 /// Returns true when we're displaying the edit prediction popover below the cursor
5655 /// like we are not previewing and the LSP autocomplete menu is visible
5656 /// or we are in `when_holding_modifier` mode.
5657 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
5658 if self.edit_prediction_preview_is_active()
5659 || !self.show_edit_predictions_in_menu()
5660 || !self.edit_predictions_enabled()
5661 {
5662 return false;
5663 }
5664
5665 if self.has_visible_completions_menu() {
5666 return true;
5667 }
5668
5669 has_completion && self.edit_prediction_requires_modifier()
5670 }
5671
5672 fn handle_modifiers_changed(
5673 &mut self,
5674 modifiers: Modifiers,
5675 position_map: &PositionMap,
5676 window: &mut Window,
5677 cx: &mut Context<Self>,
5678 ) {
5679 if self.show_edit_predictions_in_menu() {
5680 self.update_edit_prediction_preview(&modifiers, window, cx);
5681 }
5682
5683 self.update_selection_mode(&modifiers, position_map, window, cx);
5684
5685 let mouse_position = window.mouse_position();
5686 if !position_map.text_hitbox.is_hovered(window) {
5687 return;
5688 }
5689
5690 self.update_hovered_link(
5691 position_map.point_for_position(mouse_position),
5692 &position_map.snapshot,
5693 modifiers,
5694 window,
5695 cx,
5696 )
5697 }
5698
5699 fn update_selection_mode(
5700 &mut self,
5701 modifiers: &Modifiers,
5702 position_map: &PositionMap,
5703 window: &mut Window,
5704 cx: &mut Context<Self>,
5705 ) {
5706 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
5707 return;
5708 }
5709
5710 let mouse_position = window.mouse_position();
5711 let point_for_position = position_map.point_for_position(mouse_position);
5712 let position = point_for_position.previous_valid;
5713
5714 self.select(
5715 SelectPhase::BeginColumnar {
5716 position,
5717 reset: false,
5718 goal_column: point_for_position.exact_unclipped.column(),
5719 },
5720 window,
5721 cx,
5722 );
5723 }
5724
5725 fn update_edit_prediction_preview(
5726 &mut self,
5727 modifiers: &Modifiers,
5728 window: &mut Window,
5729 cx: &mut Context<Self>,
5730 ) {
5731 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
5732 let Some(accept_keystroke) = accept_keybind.keystroke() else {
5733 return;
5734 };
5735
5736 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
5737 if matches!(
5738 self.edit_prediction_preview,
5739 EditPredictionPreview::Inactive { .. }
5740 ) {
5741 self.edit_prediction_preview = EditPredictionPreview::Active {
5742 previous_scroll_position: None,
5743 since: Instant::now(),
5744 };
5745
5746 self.update_visible_inline_completion(window, cx);
5747 cx.notify();
5748 }
5749 } else if let EditPredictionPreview::Active {
5750 previous_scroll_position,
5751 since,
5752 } = self.edit_prediction_preview
5753 {
5754 if let (Some(previous_scroll_position), Some(position_map)) =
5755 (previous_scroll_position, self.last_position_map.as_ref())
5756 {
5757 self.set_scroll_position(
5758 previous_scroll_position
5759 .scroll_position(&position_map.snapshot.display_snapshot),
5760 window,
5761 cx,
5762 );
5763 }
5764
5765 self.edit_prediction_preview = EditPredictionPreview::Inactive {
5766 released_too_fast: since.elapsed() < Duration::from_millis(200),
5767 };
5768 self.clear_row_highlights::<EditPredictionPreview>();
5769 self.update_visible_inline_completion(window, cx);
5770 cx.notify();
5771 }
5772 }
5773
5774 fn update_visible_inline_completion(
5775 &mut self,
5776 _window: &mut Window,
5777 cx: &mut Context<Self>,
5778 ) -> Option<()> {
5779 let selection = self.selections.newest_anchor();
5780 let cursor = selection.head();
5781 let multibuffer = self.buffer.read(cx).snapshot(cx);
5782 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
5783 let excerpt_id = cursor.excerpt_id;
5784
5785 let show_in_menu = self.show_edit_predictions_in_menu();
5786 let completions_menu_has_precedence = !show_in_menu
5787 && (self.context_menu.borrow().is_some()
5788 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
5789
5790 if completions_menu_has_precedence
5791 || !offset_selection.is_empty()
5792 || self
5793 .active_inline_completion
5794 .as_ref()
5795 .map_or(false, |completion| {
5796 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
5797 let invalidation_range = invalidation_range.start..=invalidation_range.end;
5798 !invalidation_range.contains(&offset_selection.head())
5799 })
5800 {
5801 self.discard_inline_completion(false, cx);
5802 return None;
5803 }
5804
5805 self.take_active_inline_completion(cx);
5806 let Some(provider) = self.edit_prediction_provider() else {
5807 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5808 return None;
5809 };
5810
5811 let (buffer, cursor_buffer_position) =
5812 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5813
5814 self.edit_prediction_settings =
5815 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5816
5817 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
5818
5819 if self.edit_prediction_indent_conflict {
5820 let cursor_point = cursor.to_point(&multibuffer);
5821
5822 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
5823
5824 if let Some((_, indent)) = indents.iter().next() {
5825 if indent.len == cursor_point.column {
5826 self.edit_prediction_indent_conflict = false;
5827 }
5828 }
5829 }
5830
5831 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
5832 let edits = inline_completion
5833 .edits
5834 .into_iter()
5835 .flat_map(|(range, new_text)| {
5836 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
5837 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
5838 Some((start..end, new_text))
5839 })
5840 .collect::<Vec<_>>();
5841 if edits.is_empty() {
5842 return None;
5843 }
5844
5845 let first_edit_start = edits.first().unwrap().0.start;
5846 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
5847 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
5848
5849 let last_edit_end = edits.last().unwrap().0.end;
5850 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
5851 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
5852
5853 let cursor_row = cursor.to_point(&multibuffer).row;
5854
5855 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
5856
5857 let mut inlay_ids = Vec::new();
5858 let invalidation_row_range;
5859 let move_invalidation_row_range = if cursor_row < edit_start_row {
5860 Some(cursor_row..edit_end_row)
5861 } else if cursor_row > edit_end_row {
5862 Some(edit_start_row..cursor_row)
5863 } else {
5864 None
5865 };
5866 let is_move =
5867 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
5868 let completion = if is_move {
5869 invalidation_row_range =
5870 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
5871 let target = first_edit_start;
5872 InlineCompletion::Move { target, snapshot }
5873 } else {
5874 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
5875 && !self.inline_completions_hidden_for_vim_mode;
5876
5877 if show_completions_in_buffer {
5878 if edits
5879 .iter()
5880 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
5881 {
5882 let mut inlays = Vec::new();
5883 for (range, new_text) in &edits {
5884 let inlay = Inlay::inline_completion(
5885 post_inc(&mut self.next_inlay_id),
5886 range.start,
5887 new_text.as_str(),
5888 );
5889 inlay_ids.push(inlay.id);
5890 inlays.push(inlay);
5891 }
5892
5893 self.splice_inlays(&[], inlays, cx);
5894 } else {
5895 let background_color = cx.theme().status().deleted_background;
5896 self.highlight_text::<InlineCompletionHighlight>(
5897 edits.iter().map(|(range, _)| range.clone()).collect(),
5898 HighlightStyle {
5899 background_color: Some(background_color),
5900 ..Default::default()
5901 },
5902 cx,
5903 );
5904 }
5905 }
5906
5907 invalidation_row_range = edit_start_row..edit_end_row;
5908
5909 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
5910 if provider.show_tab_accept_marker() {
5911 EditDisplayMode::TabAccept
5912 } else {
5913 EditDisplayMode::Inline
5914 }
5915 } else {
5916 EditDisplayMode::DiffPopover
5917 };
5918
5919 InlineCompletion::Edit {
5920 edits,
5921 edit_preview: inline_completion.edit_preview,
5922 display_mode,
5923 snapshot,
5924 }
5925 };
5926
5927 let invalidation_range = multibuffer
5928 .anchor_before(Point::new(invalidation_row_range.start, 0))
5929 ..multibuffer.anchor_after(Point::new(
5930 invalidation_row_range.end,
5931 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
5932 ));
5933
5934 self.stale_inline_completion_in_menu = None;
5935 self.active_inline_completion = Some(InlineCompletionState {
5936 inlay_ids,
5937 completion,
5938 completion_id: inline_completion.id,
5939 invalidation_range,
5940 });
5941
5942 cx.notify();
5943
5944 Some(())
5945 }
5946
5947 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
5948 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
5949 }
5950
5951 fn render_code_actions_indicator(
5952 &self,
5953 _style: &EditorStyle,
5954 row: DisplayRow,
5955 is_active: bool,
5956 breakpoint: Option<&(Anchor, Breakpoint)>,
5957 cx: &mut Context<Self>,
5958 ) -> Option<IconButton> {
5959 let color = Color::Muted;
5960
5961 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
5962 let bp_kind = Arc::new(
5963 breakpoint
5964 .map(|(_, bp)| bp.kind.clone())
5965 .unwrap_or(BreakpointKind::Standard),
5966 );
5967
5968 if self.available_code_actions.is_some() {
5969 Some(
5970 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
5971 .shape(ui::IconButtonShape::Square)
5972 .icon_size(IconSize::XSmall)
5973 .icon_color(color)
5974 .toggle_state(is_active)
5975 .tooltip({
5976 let focus_handle = self.focus_handle.clone();
5977 move |window, cx| {
5978 Tooltip::for_action_in(
5979 "Toggle Code Actions",
5980 &ToggleCodeActions {
5981 deployed_from_indicator: None,
5982 },
5983 &focus_handle,
5984 window,
5985 cx,
5986 )
5987 }
5988 })
5989 .on_click(cx.listener(move |editor, _e, window, cx| {
5990 window.focus(&editor.focus_handle(cx));
5991 editor.toggle_code_actions(
5992 &ToggleCodeActions {
5993 deployed_from_indicator: Some(row),
5994 },
5995 window,
5996 cx,
5997 );
5998 }))
5999 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6000 editor.set_breakpoint_context_menu(
6001 row,
6002 position,
6003 bp_kind.clone(),
6004 event.down.position,
6005 window,
6006 cx,
6007 );
6008 })),
6009 )
6010 } else {
6011 None
6012 }
6013 }
6014
6015 fn clear_tasks(&mut self) {
6016 self.tasks.clear()
6017 }
6018
6019 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6020 if self.tasks.insert(key, value).is_some() {
6021 // This case should hopefully be rare, but just in case...
6022 log::error!("multiple different run targets found on a single line, only the last target will be rendered")
6023 }
6024 }
6025
6026 /// Get all display points of breakpoints that will be rendered within editor
6027 ///
6028 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6029 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6030 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6031 fn active_breakpoints(
6032 &mut self,
6033 range: Range<DisplayRow>,
6034 window: &mut Window,
6035 cx: &mut Context<Self>,
6036 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
6037 let mut breakpoint_display_points = HashMap::default();
6038
6039 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
6040 return breakpoint_display_points;
6041 };
6042
6043 let snapshot = self.snapshot(window, cx);
6044
6045 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
6046 let Some(project) = self.project.as_ref() else {
6047 return breakpoint_display_points;
6048 };
6049
6050 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
6051 let buffer_snapshot = buffer.read(cx).snapshot();
6052
6053 for breakpoint in
6054 breakpoint_store
6055 .read(cx)
6056 .breakpoints(&buffer, None, buffer_snapshot.clone(), cx)
6057 {
6058 let point = buffer_snapshot.summary_for_anchor::<Point>(&breakpoint.0);
6059 let mut anchor = multi_buffer_snapshot.anchor_before(point);
6060 anchor.text_anchor = breakpoint.0;
6061
6062 breakpoint_display_points.insert(
6063 snapshot
6064 .point_to_display_point(
6065 MultiBufferPoint {
6066 row: point.row,
6067 column: point.column,
6068 },
6069 Bias::Left,
6070 )
6071 .row(),
6072 (anchor, breakpoint.1.clone()),
6073 );
6074 }
6075
6076 return breakpoint_display_points;
6077 }
6078
6079 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
6080 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
6081 for excerpt_boundary in multi_buffer_snapshot.excerpt_boundaries_in_range(range) {
6082 let info = excerpt_boundary.next;
6083
6084 let Some(excerpt_ranges) = multi_buffer_snapshot.range_for_excerpt(info.id) else {
6085 continue;
6086 };
6087
6088 let Some(buffer) =
6089 project.read_with(cx, |this, cx| this.buffer_for_id(info.buffer_id, cx))
6090 else {
6091 continue;
6092 };
6093
6094 if buffer.read(cx).file().is_none() {
6095 continue;
6096 }
6097 let breakpoints = breakpoint_store.read(cx).breakpoints(
6098 &buffer,
6099 Some(info.range.context.start..info.range.context.end),
6100 info.buffer.clone(),
6101 cx,
6102 );
6103
6104 // To translate a breakpoint's position within a singular buffer to a multi buffer
6105 // position we need to know it's excerpt starting location, it's position within
6106 // the singular buffer, and if that position is within the excerpt's range.
6107 let excerpt_head = excerpt_ranges
6108 .start
6109 .to_display_point(&snapshot.display_snapshot);
6110
6111 let buffer_start = info
6112 .buffer
6113 .summary_for_anchor::<Point>(&info.range.context.start);
6114
6115 for (anchor, breakpoint) in breakpoints {
6116 let as_row = info.buffer.summary_for_anchor::<Point>(&anchor).row;
6117 let delta = as_row - buffer_start.row;
6118
6119 let position = excerpt_head + DisplayPoint::new(DisplayRow(delta), 0);
6120
6121 let anchor = snapshot.display_point_to_anchor(position, Bias::Left);
6122
6123 breakpoint_display_points.insert(position.row(), (anchor, breakpoint.clone()));
6124 }
6125 }
6126
6127 breakpoint_display_points
6128 }
6129
6130 fn breakpoint_context_menu(
6131 &self,
6132 anchor: Anchor,
6133 kind: Arc<BreakpointKind>,
6134 window: &mut Window,
6135 cx: &mut Context<Self>,
6136 ) -> Entity<ui::ContextMenu> {
6137 let weak_editor = cx.weak_entity();
6138 let focus_handle = self.focus_handle(cx);
6139
6140 let second_entry_msg = if kind.log_message().is_some() {
6141 "Edit Log Breakpoint"
6142 } else {
6143 "Add Log Breakpoint"
6144 };
6145
6146 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6147 menu.on_blur_subscription(Subscription::new(|| {}))
6148 .context(focus_handle)
6149 .entry("Toggle Breakpoint", None, {
6150 let weak_editor = weak_editor.clone();
6151 move |_window, cx| {
6152 weak_editor
6153 .update(cx, |this, cx| {
6154 this.edit_breakpoint_at_anchor(
6155 anchor,
6156 BreakpointKind::Standard,
6157 BreakpointEditAction::Toggle,
6158 cx,
6159 );
6160 })
6161 .log_err();
6162 }
6163 })
6164 .entry(second_entry_msg, None, move |window, cx| {
6165 weak_editor
6166 .update(cx, |this, cx| {
6167 this.add_edit_breakpoint_block(anchor, kind.as_ref(), window, cx);
6168 })
6169 .log_err();
6170 })
6171 })
6172 }
6173
6174 fn render_breakpoint(
6175 &self,
6176 position: Anchor,
6177 row: DisplayRow,
6178 kind: &BreakpointKind,
6179 cx: &mut Context<Self>,
6180 ) -> IconButton {
6181 let color = if self
6182 .gutter_breakpoint_indicator
6183 .is_some_and(|gutter_bp| gutter_bp.row() == row)
6184 {
6185 Color::Hint
6186 } else {
6187 Color::Debugger
6188 };
6189
6190 let icon = match &kind {
6191 BreakpointKind::Standard => ui::IconName::DebugBreakpoint,
6192 BreakpointKind::Log(_) => ui::IconName::DebugLogBreakpoint,
6193 };
6194 let arc_kind = Arc::new(kind.clone());
6195 let arc_kind2 = arc_kind.clone();
6196
6197 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
6198 .icon_size(IconSize::XSmall)
6199 .size(ui::ButtonSize::None)
6200 .icon_color(color)
6201 .style(ButtonStyle::Transparent)
6202 .on_click(cx.listener(move |editor, _e, window, cx| {
6203 window.focus(&editor.focus_handle(cx));
6204 editor.edit_breakpoint_at_anchor(
6205 position,
6206 arc_kind.as_ref().clone(),
6207 BreakpointEditAction::Toggle,
6208 cx,
6209 );
6210 }))
6211 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6212 editor.set_breakpoint_context_menu(
6213 row,
6214 Some(position),
6215 arc_kind2.clone(),
6216 event.down.position,
6217 window,
6218 cx,
6219 );
6220 }))
6221 }
6222
6223 fn build_tasks_context(
6224 project: &Entity<Project>,
6225 buffer: &Entity<Buffer>,
6226 buffer_row: u32,
6227 tasks: &Arc<RunnableTasks>,
6228 cx: &mut Context<Self>,
6229 ) -> Task<Option<task::TaskContext>> {
6230 let position = Point::new(buffer_row, tasks.column);
6231 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
6232 let location = Location {
6233 buffer: buffer.clone(),
6234 range: range_start..range_start,
6235 };
6236 // Fill in the environmental variables from the tree-sitter captures
6237 let mut captured_task_variables = TaskVariables::default();
6238 for (capture_name, value) in tasks.extra_variables.clone() {
6239 captured_task_variables.insert(
6240 task::VariableName::Custom(capture_name.into()),
6241 value.clone(),
6242 );
6243 }
6244 project.update(cx, |project, cx| {
6245 project.task_store().update(cx, |task_store, cx| {
6246 task_store.task_context_for_location(captured_task_variables, location, cx)
6247 })
6248 })
6249 }
6250
6251 pub fn spawn_nearest_task(
6252 &mut self,
6253 action: &SpawnNearestTask,
6254 window: &mut Window,
6255 cx: &mut Context<Self>,
6256 ) {
6257 let Some((workspace, _)) = self.workspace.clone() else {
6258 return;
6259 };
6260 let Some(project) = self.project.clone() else {
6261 return;
6262 };
6263
6264 // Try to find a closest, enclosing node using tree-sitter that has a
6265 // task
6266 let Some((buffer, buffer_row, tasks)) = self
6267 .find_enclosing_node_task(cx)
6268 // Or find the task that's closest in row-distance.
6269 .or_else(|| self.find_closest_task(cx))
6270 else {
6271 return;
6272 };
6273
6274 let reveal_strategy = action.reveal;
6275 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6276 cx.spawn_in(window, async move |_, cx| {
6277 let context = task_context.await?;
6278 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
6279
6280 let resolved = resolved_task.resolved.as_mut()?;
6281 resolved.reveal = reveal_strategy;
6282
6283 workspace
6284 .update(cx, |workspace, cx| {
6285 workspace::tasks::schedule_resolved_task(
6286 workspace,
6287 task_source_kind,
6288 resolved_task,
6289 false,
6290 cx,
6291 );
6292 })
6293 .ok()
6294 })
6295 .detach();
6296 }
6297
6298 fn find_closest_task(
6299 &mut self,
6300 cx: &mut Context<Self>,
6301 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6302 let cursor_row = self.selections.newest_adjusted(cx).head().row;
6303
6304 let ((buffer_id, row), tasks) = self
6305 .tasks
6306 .iter()
6307 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
6308
6309 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
6310 let tasks = Arc::new(tasks.to_owned());
6311 Some((buffer, *row, tasks))
6312 }
6313
6314 fn find_enclosing_node_task(
6315 &mut self,
6316 cx: &mut Context<Self>,
6317 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6318 let snapshot = self.buffer.read(cx).snapshot(cx);
6319 let offset = self.selections.newest::<usize>(cx).head();
6320 let excerpt = snapshot.excerpt_containing(offset..offset)?;
6321 let buffer_id = excerpt.buffer().remote_id();
6322
6323 let layer = excerpt.buffer().syntax_layer_at(offset)?;
6324 let mut cursor = layer.node().walk();
6325
6326 while cursor.goto_first_child_for_byte(offset).is_some() {
6327 if cursor.node().end_byte() == offset {
6328 cursor.goto_next_sibling();
6329 }
6330 }
6331
6332 // Ascend to the smallest ancestor that contains the range and has a task.
6333 loop {
6334 let node = cursor.node();
6335 let node_range = node.byte_range();
6336 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
6337
6338 // Check if this node contains our offset
6339 if node_range.start <= offset && node_range.end >= offset {
6340 // If it contains offset, check for task
6341 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
6342 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
6343 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
6344 }
6345 }
6346
6347 if !cursor.goto_parent() {
6348 break;
6349 }
6350 }
6351 None
6352 }
6353
6354 fn render_run_indicator(
6355 &self,
6356 _style: &EditorStyle,
6357 is_active: bool,
6358 row: DisplayRow,
6359 breakpoint: Option<(Anchor, Breakpoint)>,
6360 cx: &mut Context<Self>,
6361 ) -> IconButton {
6362 let color = Color::Muted;
6363
6364 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6365 let bp_kind = Arc::new(
6366 breakpoint
6367 .map(|(_, bp)| bp.kind)
6368 .unwrap_or(BreakpointKind::Standard),
6369 );
6370
6371 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
6372 .shape(ui::IconButtonShape::Square)
6373 .icon_size(IconSize::XSmall)
6374 .icon_color(color)
6375 .toggle_state(is_active)
6376 .on_click(cx.listener(move |editor, _e, window, cx| {
6377 window.focus(&editor.focus_handle(cx));
6378 editor.toggle_code_actions(
6379 &ToggleCodeActions {
6380 deployed_from_indicator: Some(row),
6381 },
6382 window,
6383 cx,
6384 );
6385 }))
6386 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6387 editor.set_breakpoint_context_menu(
6388 row,
6389 position,
6390 bp_kind.clone(),
6391 event.down.position,
6392 window,
6393 cx,
6394 );
6395 }))
6396 }
6397
6398 pub fn context_menu_visible(&self) -> bool {
6399 !self.edit_prediction_preview_is_active()
6400 && self
6401 .context_menu
6402 .borrow()
6403 .as_ref()
6404 .map_or(false, |menu| menu.visible())
6405 }
6406
6407 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
6408 self.context_menu
6409 .borrow()
6410 .as_ref()
6411 .map(|menu| menu.origin())
6412 }
6413
6414 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
6415 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
6416
6417 fn render_edit_prediction_popover(
6418 &mut self,
6419 text_bounds: &Bounds<Pixels>,
6420 content_origin: gpui::Point<Pixels>,
6421 editor_snapshot: &EditorSnapshot,
6422 visible_row_range: Range<DisplayRow>,
6423 scroll_top: f32,
6424 scroll_bottom: f32,
6425 line_layouts: &[LineWithInvisibles],
6426 line_height: Pixels,
6427 scroll_pixel_position: gpui::Point<Pixels>,
6428 newest_selection_head: Option<DisplayPoint>,
6429 editor_width: Pixels,
6430 style: &EditorStyle,
6431 window: &mut Window,
6432 cx: &mut App,
6433 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6434 let active_inline_completion = self.active_inline_completion.as_ref()?;
6435
6436 if self.edit_prediction_visible_in_cursor_popover(true) {
6437 return None;
6438 }
6439
6440 match &active_inline_completion.completion {
6441 InlineCompletion::Move { target, .. } => {
6442 let target_display_point = target.to_display_point(editor_snapshot);
6443
6444 if self.edit_prediction_requires_modifier() {
6445 if !self.edit_prediction_preview_is_active() {
6446 return None;
6447 }
6448
6449 self.render_edit_prediction_modifier_jump_popover(
6450 text_bounds,
6451 content_origin,
6452 visible_row_range,
6453 line_layouts,
6454 line_height,
6455 scroll_pixel_position,
6456 newest_selection_head,
6457 target_display_point,
6458 window,
6459 cx,
6460 )
6461 } else {
6462 self.render_edit_prediction_eager_jump_popover(
6463 text_bounds,
6464 content_origin,
6465 editor_snapshot,
6466 visible_row_range,
6467 scroll_top,
6468 scroll_bottom,
6469 line_height,
6470 scroll_pixel_position,
6471 target_display_point,
6472 editor_width,
6473 window,
6474 cx,
6475 )
6476 }
6477 }
6478 InlineCompletion::Edit {
6479 display_mode: EditDisplayMode::Inline,
6480 ..
6481 } => None,
6482 InlineCompletion::Edit {
6483 display_mode: EditDisplayMode::TabAccept,
6484 edits,
6485 ..
6486 } => {
6487 let range = &edits.first()?.0;
6488 let target_display_point = range.end.to_display_point(editor_snapshot);
6489
6490 self.render_edit_prediction_end_of_line_popover(
6491 "Accept",
6492 editor_snapshot,
6493 visible_row_range,
6494 target_display_point,
6495 line_height,
6496 scroll_pixel_position,
6497 content_origin,
6498 editor_width,
6499 window,
6500 cx,
6501 )
6502 }
6503 InlineCompletion::Edit {
6504 edits,
6505 edit_preview,
6506 display_mode: EditDisplayMode::DiffPopover,
6507 snapshot,
6508 } => self.render_edit_prediction_diff_popover(
6509 text_bounds,
6510 content_origin,
6511 editor_snapshot,
6512 visible_row_range,
6513 line_layouts,
6514 line_height,
6515 scroll_pixel_position,
6516 newest_selection_head,
6517 editor_width,
6518 style,
6519 edits,
6520 edit_preview,
6521 snapshot,
6522 window,
6523 cx,
6524 ),
6525 }
6526 }
6527
6528 fn render_edit_prediction_modifier_jump_popover(
6529 &mut self,
6530 text_bounds: &Bounds<Pixels>,
6531 content_origin: gpui::Point<Pixels>,
6532 visible_row_range: Range<DisplayRow>,
6533 line_layouts: &[LineWithInvisibles],
6534 line_height: Pixels,
6535 scroll_pixel_position: gpui::Point<Pixels>,
6536 newest_selection_head: Option<DisplayPoint>,
6537 target_display_point: DisplayPoint,
6538 window: &mut Window,
6539 cx: &mut App,
6540 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6541 let scrolled_content_origin =
6542 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
6543
6544 const SCROLL_PADDING_Y: Pixels = px(12.);
6545
6546 if target_display_point.row() < visible_row_range.start {
6547 return self.render_edit_prediction_scroll_popover(
6548 |_| SCROLL_PADDING_Y,
6549 IconName::ArrowUp,
6550 visible_row_range,
6551 line_layouts,
6552 newest_selection_head,
6553 scrolled_content_origin,
6554 window,
6555 cx,
6556 );
6557 } else if target_display_point.row() >= visible_row_range.end {
6558 return self.render_edit_prediction_scroll_popover(
6559 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
6560 IconName::ArrowDown,
6561 visible_row_range,
6562 line_layouts,
6563 newest_selection_head,
6564 scrolled_content_origin,
6565 window,
6566 cx,
6567 );
6568 }
6569
6570 const POLE_WIDTH: Pixels = px(2.);
6571
6572 let line_layout =
6573 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
6574 let target_column = target_display_point.column() as usize;
6575
6576 let target_x = line_layout.x_for_index(target_column);
6577 let target_y =
6578 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
6579
6580 let flag_on_right = target_x < text_bounds.size.width / 2.;
6581
6582 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
6583 border_color.l += 0.001;
6584
6585 let mut element = v_flex()
6586 .items_end()
6587 .when(flag_on_right, |el| el.items_start())
6588 .child(if flag_on_right {
6589 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
6590 .rounded_bl(px(0.))
6591 .rounded_tl(px(0.))
6592 .border_l_2()
6593 .border_color(border_color)
6594 } else {
6595 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
6596 .rounded_br(px(0.))
6597 .rounded_tr(px(0.))
6598 .border_r_2()
6599 .border_color(border_color)
6600 })
6601 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
6602 .into_any();
6603
6604 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6605
6606 let mut origin = scrolled_content_origin + point(target_x, target_y)
6607 - point(
6608 if flag_on_right {
6609 POLE_WIDTH
6610 } else {
6611 size.width - POLE_WIDTH
6612 },
6613 size.height - line_height,
6614 );
6615
6616 origin.x = origin.x.max(content_origin.x);
6617
6618 element.prepaint_at(origin, window, cx);
6619
6620 Some((element, origin))
6621 }
6622
6623 fn render_edit_prediction_scroll_popover(
6624 &mut self,
6625 to_y: impl Fn(Size<Pixels>) -> Pixels,
6626 scroll_icon: IconName,
6627 visible_row_range: Range<DisplayRow>,
6628 line_layouts: &[LineWithInvisibles],
6629 newest_selection_head: Option<DisplayPoint>,
6630 scrolled_content_origin: gpui::Point<Pixels>,
6631 window: &mut Window,
6632 cx: &mut App,
6633 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6634 let mut element = self
6635 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
6636 .into_any();
6637
6638 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6639
6640 let cursor = newest_selection_head?;
6641 let cursor_row_layout =
6642 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
6643 let cursor_column = cursor.column() as usize;
6644
6645 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
6646
6647 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
6648
6649 element.prepaint_at(origin, window, cx);
6650 Some((element, origin))
6651 }
6652
6653 fn render_edit_prediction_eager_jump_popover(
6654 &mut self,
6655 text_bounds: &Bounds<Pixels>,
6656 content_origin: gpui::Point<Pixels>,
6657 editor_snapshot: &EditorSnapshot,
6658 visible_row_range: Range<DisplayRow>,
6659 scroll_top: f32,
6660 scroll_bottom: f32,
6661 line_height: Pixels,
6662 scroll_pixel_position: gpui::Point<Pixels>,
6663 target_display_point: DisplayPoint,
6664 editor_width: Pixels,
6665 window: &mut Window,
6666 cx: &mut App,
6667 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6668 if target_display_point.row().as_f32() < scroll_top {
6669 let mut element = self
6670 .render_edit_prediction_line_popover(
6671 "Jump to Edit",
6672 Some(IconName::ArrowUp),
6673 window,
6674 cx,
6675 )?
6676 .into_any();
6677
6678 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6679 let offset = point(
6680 (text_bounds.size.width - size.width) / 2.,
6681 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
6682 );
6683
6684 let origin = text_bounds.origin + offset;
6685 element.prepaint_at(origin, window, cx);
6686 Some((element, origin))
6687 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
6688 let mut element = self
6689 .render_edit_prediction_line_popover(
6690 "Jump to Edit",
6691 Some(IconName::ArrowDown),
6692 window,
6693 cx,
6694 )?
6695 .into_any();
6696
6697 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6698 let offset = point(
6699 (text_bounds.size.width - size.width) / 2.,
6700 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
6701 );
6702
6703 let origin = text_bounds.origin + offset;
6704 element.prepaint_at(origin, window, cx);
6705 Some((element, origin))
6706 } else {
6707 self.render_edit_prediction_end_of_line_popover(
6708 "Jump to Edit",
6709 editor_snapshot,
6710 visible_row_range,
6711 target_display_point,
6712 line_height,
6713 scroll_pixel_position,
6714 content_origin,
6715 editor_width,
6716 window,
6717 cx,
6718 )
6719 }
6720 }
6721
6722 fn render_edit_prediction_end_of_line_popover(
6723 self: &mut Editor,
6724 label: &'static str,
6725 editor_snapshot: &EditorSnapshot,
6726 visible_row_range: Range<DisplayRow>,
6727 target_display_point: DisplayPoint,
6728 line_height: Pixels,
6729 scroll_pixel_position: gpui::Point<Pixels>,
6730 content_origin: gpui::Point<Pixels>,
6731 editor_width: Pixels,
6732 window: &mut Window,
6733 cx: &mut App,
6734 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6735 let target_line_end = DisplayPoint::new(
6736 target_display_point.row(),
6737 editor_snapshot.line_len(target_display_point.row()),
6738 );
6739
6740 let mut element = self
6741 .render_edit_prediction_line_popover(label, None, window, cx)?
6742 .into_any();
6743
6744 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6745
6746 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
6747
6748 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
6749 let mut origin = start_point
6750 + line_origin
6751 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
6752 origin.x = origin.x.max(content_origin.x);
6753
6754 let max_x = content_origin.x + editor_width - size.width;
6755
6756 if origin.x > max_x {
6757 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
6758
6759 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
6760 origin.y += offset;
6761 IconName::ArrowUp
6762 } else {
6763 origin.y -= offset;
6764 IconName::ArrowDown
6765 };
6766
6767 element = self
6768 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
6769 .into_any();
6770
6771 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6772
6773 origin.x = content_origin.x + editor_width - size.width - px(2.);
6774 }
6775
6776 element.prepaint_at(origin, window, cx);
6777 Some((element, origin))
6778 }
6779
6780 fn render_edit_prediction_diff_popover(
6781 self: &Editor,
6782 text_bounds: &Bounds<Pixels>,
6783 content_origin: gpui::Point<Pixels>,
6784 editor_snapshot: &EditorSnapshot,
6785 visible_row_range: Range<DisplayRow>,
6786 line_layouts: &[LineWithInvisibles],
6787 line_height: Pixels,
6788 scroll_pixel_position: gpui::Point<Pixels>,
6789 newest_selection_head: Option<DisplayPoint>,
6790 editor_width: Pixels,
6791 style: &EditorStyle,
6792 edits: &Vec<(Range<Anchor>, String)>,
6793 edit_preview: &Option<language::EditPreview>,
6794 snapshot: &language::BufferSnapshot,
6795 window: &mut Window,
6796 cx: &mut App,
6797 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6798 let edit_start = edits
6799 .first()
6800 .unwrap()
6801 .0
6802 .start
6803 .to_display_point(editor_snapshot);
6804 let edit_end = edits
6805 .last()
6806 .unwrap()
6807 .0
6808 .end
6809 .to_display_point(editor_snapshot);
6810
6811 let is_visible = visible_row_range.contains(&edit_start.row())
6812 || visible_row_range.contains(&edit_end.row());
6813 if !is_visible {
6814 return None;
6815 }
6816
6817 let highlighted_edits =
6818 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
6819
6820 let styled_text = highlighted_edits.to_styled_text(&style.text);
6821 let line_count = highlighted_edits.text.lines().count();
6822
6823 const BORDER_WIDTH: Pixels = px(1.);
6824
6825 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
6826 let has_keybind = keybind.is_some();
6827
6828 let mut element = h_flex()
6829 .items_start()
6830 .child(
6831 h_flex()
6832 .bg(cx.theme().colors().editor_background)
6833 .border(BORDER_WIDTH)
6834 .shadow_sm()
6835 .border_color(cx.theme().colors().border)
6836 .rounded_l_lg()
6837 .when(line_count > 1, |el| el.rounded_br_lg())
6838 .pr_1()
6839 .child(styled_text),
6840 )
6841 .child(
6842 h_flex()
6843 .h(line_height + BORDER_WIDTH * px(2.))
6844 .px_1p5()
6845 .gap_1()
6846 // Workaround: For some reason, there's a gap if we don't do this
6847 .ml(-BORDER_WIDTH)
6848 .shadow(smallvec![gpui::BoxShadow {
6849 color: gpui::black().opacity(0.05),
6850 offset: point(px(1.), px(1.)),
6851 blur_radius: px(2.),
6852 spread_radius: px(0.),
6853 }])
6854 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
6855 .border(BORDER_WIDTH)
6856 .border_color(cx.theme().colors().border)
6857 .rounded_r_lg()
6858 .id("edit_prediction_diff_popover_keybind")
6859 .when(!has_keybind, |el| {
6860 let status_colors = cx.theme().status();
6861
6862 el.bg(status_colors.error_background)
6863 .border_color(status_colors.error.opacity(0.6))
6864 .child(Icon::new(IconName::Info).color(Color::Error))
6865 .cursor_default()
6866 .hoverable_tooltip(move |_window, cx| {
6867 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
6868 })
6869 })
6870 .children(keybind),
6871 )
6872 .into_any();
6873
6874 let longest_row =
6875 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
6876 let longest_line_width = if visible_row_range.contains(&longest_row) {
6877 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
6878 } else {
6879 layout_line(
6880 longest_row,
6881 editor_snapshot,
6882 style,
6883 editor_width,
6884 |_| false,
6885 window,
6886 cx,
6887 )
6888 .width
6889 };
6890
6891 let viewport_bounds =
6892 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
6893 right: -EditorElement::SCROLLBAR_WIDTH,
6894 ..Default::default()
6895 });
6896
6897 let x_after_longest =
6898 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
6899 - scroll_pixel_position.x;
6900
6901 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6902
6903 // Fully visible if it can be displayed within the window (allow overlapping other
6904 // panes). However, this is only allowed if the popover starts within text_bounds.
6905 let can_position_to_the_right = x_after_longest < text_bounds.right()
6906 && x_after_longest + element_bounds.width < viewport_bounds.right();
6907
6908 let mut origin = if can_position_to_the_right {
6909 point(
6910 x_after_longest,
6911 text_bounds.origin.y + edit_start.row().as_f32() * line_height
6912 - scroll_pixel_position.y,
6913 )
6914 } else {
6915 let cursor_row = newest_selection_head.map(|head| head.row());
6916 let above_edit = edit_start
6917 .row()
6918 .0
6919 .checked_sub(line_count as u32)
6920 .map(DisplayRow);
6921 let below_edit = Some(edit_end.row() + 1);
6922 let above_cursor =
6923 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
6924 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
6925
6926 // Place the edit popover adjacent to the edit if there is a location
6927 // available that is onscreen and does not obscure the cursor. Otherwise,
6928 // place it adjacent to the cursor.
6929 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
6930 .into_iter()
6931 .flatten()
6932 .find(|&start_row| {
6933 let end_row = start_row + line_count as u32;
6934 visible_row_range.contains(&start_row)
6935 && visible_row_range.contains(&end_row)
6936 && cursor_row.map_or(true, |cursor_row| {
6937 !((start_row..end_row).contains(&cursor_row))
6938 })
6939 })?;
6940
6941 content_origin
6942 + point(
6943 -scroll_pixel_position.x,
6944 row_target.as_f32() * line_height - scroll_pixel_position.y,
6945 )
6946 };
6947
6948 origin.x -= BORDER_WIDTH;
6949
6950 window.defer_draw(element, origin, 1);
6951
6952 // Do not return an element, since it will already be drawn due to defer_draw.
6953 None
6954 }
6955
6956 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
6957 px(30.)
6958 }
6959
6960 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
6961 if self.read_only(cx) {
6962 cx.theme().players().read_only()
6963 } else {
6964 self.style.as_ref().unwrap().local_player
6965 }
6966 }
6967
6968 fn render_edit_prediction_accept_keybind(
6969 &self,
6970 window: &mut Window,
6971 cx: &App,
6972 ) -> Option<AnyElement> {
6973 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
6974 let accept_keystroke = accept_binding.keystroke()?;
6975
6976 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
6977
6978 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
6979 Color::Accent
6980 } else {
6981 Color::Muted
6982 };
6983
6984 h_flex()
6985 .px_0p5()
6986 .when(is_platform_style_mac, |parent| parent.gap_0p5())
6987 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
6988 .text_size(TextSize::XSmall.rems(cx))
6989 .child(h_flex().children(ui::render_modifiers(
6990 &accept_keystroke.modifiers,
6991 PlatformStyle::platform(),
6992 Some(modifiers_color),
6993 Some(IconSize::XSmall.rems().into()),
6994 true,
6995 )))
6996 .when(is_platform_style_mac, |parent| {
6997 parent.child(accept_keystroke.key.clone())
6998 })
6999 .when(!is_platform_style_mac, |parent| {
7000 parent.child(
7001 Key::new(
7002 util::capitalize(&accept_keystroke.key),
7003 Some(Color::Default),
7004 )
7005 .size(Some(IconSize::XSmall.rems().into())),
7006 )
7007 })
7008 .into_any()
7009 .into()
7010 }
7011
7012 fn render_edit_prediction_line_popover(
7013 &self,
7014 label: impl Into<SharedString>,
7015 icon: Option<IconName>,
7016 window: &mut Window,
7017 cx: &App,
7018 ) -> Option<Stateful<Div>> {
7019 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
7020
7021 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7022 let has_keybind = keybind.is_some();
7023
7024 let result = h_flex()
7025 .id("ep-line-popover")
7026 .py_0p5()
7027 .pl_1()
7028 .pr(padding_right)
7029 .gap_1()
7030 .rounded_md()
7031 .border_1()
7032 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7033 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7034 .shadow_sm()
7035 .when(!has_keybind, |el| {
7036 let status_colors = cx.theme().status();
7037
7038 el.bg(status_colors.error_background)
7039 .border_color(status_colors.error.opacity(0.6))
7040 .pl_2()
7041 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7042 .cursor_default()
7043 .hoverable_tooltip(move |_window, cx| {
7044 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7045 })
7046 })
7047 .children(keybind)
7048 .child(
7049 Label::new(label)
7050 .size(LabelSize::Small)
7051 .when(!has_keybind, |el| {
7052 el.color(cx.theme().status().error.into()).strikethrough()
7053 }),
7054 )
7055 .when(!has_keybind, |el| {
7056 el.child(
7057 h_flex().ml_1().child(
7058 Icon::new(IconName::Info)
7059 .size(IconSize::Small)
7060 .color(cx.theme().status().error.into()),
7061 ),
7062 )
7063 })
7064 .when_some(icon, |element, icon| {
7065 element.child(
7066 div()
7067 .mt(px(1.5))
7068 .child(Icon::new(icon).size(IconSize::Small)),
7069 )
7070 });
7071
7072 Some(result)
7073 }
7074
7075 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
7076 let accent_color = cx.theme().colors().text_accent;
7077 let editor_bg_color = cx.theme().colors().editor_background;
7078 editor_bg_color.blend(accent_color.opacity(0.1))
7079 }
7080
7081 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
7082 let accent_color = cx.theme().colors().text_accent;
7083 let editor_bg_color = cx.theme().colors().editor_background;
7084 editor_bg_color.blend(accent_color.opacity(0.6))
7085 }
7086
7087 fn render_edit_prediction_cursor_popover(
7088 &self,
7089 min_width: Pixels,
7090 max_width: Pixels,
7091 cursor_point: Point,
7092 style: &EditorStyle,
7093 accept_keystroke: Option<&gpui::Keystroke>,
7094 _window: &Window,
7095 cx: &mut Context<Editor>,
7096 ) -> Option<AnyElement> {
7097 let provider = self.edit_prediction_provider.as_ref()?;
7098
7099 if provider.provider.needs_terms_acceptance(cx) {
7100 return Some(
7101 h_flex()
7102 .min_w(min_width)
7103 .flex_1()
7104 .px_2()
7105 .py_1()
7106 .gap_3()
7107 .elevation_2(cx)
7108 .hover(|style| style.bg(cx.theme().colors().element_hover))
7109 .id("accept-terms")
7110 .cursor_pointer()
7111 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
7112 .on_click(cx.listener(|this, _event, window, cx| {
7113 cx.stop_propagation();
7114 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
7115 window.dispatch_action(
7116 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
7117 cx,
7118 );
7119 }))
7120 .child(
7121 h_flex()
7122 .flex_1()
7123 .gap_2()
7124 .child(Icon::new(IconName::ZedPredict))
7125 .child(Label::new("Accept Terms of Service"))
7126 .child(div().w_full())
7127 .child(
7128 Icon::new(IconName::ArrowUpRight)
7129 .color(Color::Muted)
7130 .size(IconSize::Small),
7131 )
7132 .into_any_element(),
7133 )
7134 .into_any(),
7135 );
7136 }
7137
7138 let is_refreshing = provider.provider.is_refreshing(cx);
7139
7140 fn pending_completion_container() -> Div {
7141 h_flex()
7142 .h_full()
7143 .flex_1()
7144 .gap_2()
7145 .child(Icon::new(IconName::ZedPredict))
7146 }
7147
7148 let completion = match &self.active_inline_completion {
7149 Some(prediction) => {
7150 if !self.has_visible_completions_menu() {
7151 const RADIUS: Pixels = px(6.);
7152 const BORDER_WIDTH: Pixels = px(1.);
7153
7154 return Some(
7155 h_flex()
7156 .elevation_2(cx)
7157 .border(BORDER_WIDTH)
7158 .border_color(cx.theme().colors().border)
7159 .when(accept_keystroke.is_none(), |el| {
7160 el.border_color(cx.theme().status().error)
7161 })
7162 .rounded(RADIUS)
7163 .rounded_tl(px(0.))
7164 .overflow_hidden()
7165 .child(div().px_1p5().child(match &prediction.completion {
7166 InlineCompletion::Move { target, snapshot } => {
7167 use text::ToPoint as _;
7168 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
7169 {
7170 Icon::new(IconName::ZedPredictDown)
7171 } else {
7172 Icon::new(IconName::ZedPredictUp)
7173 }
7174 }
7175 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
7176 }))
7177 .child(
7178 h_flex()
7179 .gap_1()
7180 .py_1()
7181 .px_2()
7182 .rounded_r(RADIUS - BORDER_WIDTH)
7183 .border_l_1()
7184 .border_color(cx.theme().colors().border)
7185 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7186 .when(self.edit_prediction_preview.released_too_fast(), |el| {
7187 el.child(
7188 Label::new("Hold")
7189 .size(LabelSize::Small)
7190 .when(accept_keystroke.is_none(), |el| {
7191 el.strikethrough()
7192 })
7193 .line_height_style(LineHeightStyle::UiLabel),
7194 )
7195 })
7196 .id("edit_prediction_cursor_popover_keybind")
7197 .when(accept_keystroke.is_none(), |el| {
7198 let status_colors = cx.theme().status();
7199
7200 el.bg(status_colors.error_background)
7201 .border_color(status_colors.error.opacity(0.6))
7202 .child(Icon::new(IconName::Info).color(Color::Error))
7203 .cursor_default()
7204 .hoverable_tooltip(move |_window, cx| {
7205 cx.new(|_| MissingEditPredictionKeybindingTooltip)
7206 .into()
7207 })
7208 })
7209 .when_some(
7210 accept_keystroke.as_ref(),
7211 |el, accept_keystroke| {
7212 el.child(h_flex().children(ui::render_modifiers(
7213 &accept_keystroke.modifiers,
7214 PlatformStyle::platform(),
7215 Some(Color::Default),
7216 Some(IconSize::XSmall.rems().into()),
7217 false,
7218 )))
7219 },
7220 ),
7221 )
7222 .into_any(),
7223 );
7224 }
7225
7226 self.render_edit_prediction_cursor_popover_preview(
7227 prediction,
7228 cursor_point,
7229 style,
7230 cx,
7231 )?
7232 }
7233
7234 None if is_refreshing => match &self.stale_inline_completion_in_menu {
7235 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
7236 stale_completion,
7237 cursor_point,
7238 style,
7239 cx,
7240 )?,
7241
7242 None => {
7243 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
7244 }
7245 },
7246
7247 None => pending_completion_container().child(Label::new("No Prediction")),
7248 };
7249
7250 let completion = if is_refreshing {
7251 completion
7252 .with_animation(
7253 "loading-completion",
7254 Animation::new(Duration::from_secs(2))
7255 .repeat()
7256 .with_easing(pulsating_between(0.4, 0.8)),
7257 |label, delta| label.opacity(delta),
7258 )
7259 .into_any_element()
7260 } else {
7261 completion.into_any_element()
7262 };
7263
7264 let has_completion = self.active_inline_completion.is_some();
7265
7266 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7267 Some(
7268 h_flex()
7269 .min_w(min_width)
7270 .max_w(max_width)
7271 .flex_1()
7272 .elevation_2(cx)
7273 .border_color(cx.theme().colors().border)
7274 .child(
7275 div()
7276 .flex_1()
7277 .py_1()
7278 .px_2()
7279 .overflow_hidden()
7280 .child(completion),
7281 )
7282 .when_some(accept_keystroke, |el, accept_keystroke| {
7283 if !accept_keystroke.modifiers.modified() {
7284 return el;
7285 }
7286
7287 el.child(
7288 h_flex()
7289 .h_full()
7290 .border_l_1()
7291 .rounded_r_lg()
7292 .border_color(cx.theme().colors().border)
7293 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7294 .gap_1()
7295 .py_1()
7296 .px_2()
7297 .child(
7298 h_flex()
7299 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7300 .when(is_platform_style_mac, |parent| parent.gap_1())
7301 .child(h_flex().children(ui::render_modifiers(
7302 &accept_keystroke.modifiers,
7303 PlatformStyle::platform(),
7304 Some(if !has_completion {
7305 Color::Muted
7306 } else {
7307 Color::Default
7308 }),
7309 None,
7310 false,
7311 ))),
7312 )
7313 .child(Label::new("Preview").into_any_element())
7314 .opacity(if has_completion { 1.0 } else { 0.4 }),
7315 )
7316 })
7317 .into_any(),
7318 )
7319 }
7320
7321 fn render_edit_prediction_cursor_popover_preview(
7322 &self,
7323 completion: &InlineCompletionState,
7324 cursor_point: Point,
7325 style: &EditorStyle,
7326 cx: &mut Context<Editor>,
7327 ) -> Option<Div> {
7328 use text::ToPoint as _;
7329
7330 fn render_relative_row_jump(
7331 prefix: impl Into<String>,
7332 current_row: u32,
7333 target_row: u32,
7334 ) -> Div {
7335 let (row_diff, arrow) = if target_row < current_row {
7336 (current_row - target_row, IconName::ArrowUp)
7337 } else {
7338 (target_row - current_row, IconName::ArrowDown)
7339 };
7340
7341 h_flex()
7342 .child(
7343 Label::new(format!("{}{}", prefix.into(), row_diff))
7344 .color(Color::Muted)
7345 .size(LabelSize::Small),
7346 )
7347 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
7348 }
7349
7350 match &completion.completion {
7351 InlineCompletion::Move {
7352 target, snapshot, ..
7353 } => Some(
7354 h_flex()
7355 .px_2()
7356 .gap_2()
7357 .flex_1()
7358 .child(
7359 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
7360 Icon::new(IconName::ZedPredictDown)
7361 } else {
7362 Icon::new(IconName::ZedPredictUp)
7363 },
7364 )
7365 .child(Label::new("Jump to Edit")),
7366 ),
7367
7368 InlineCompletion::Edit {
7369 edits,
7370 edit_preview,
7371 snapshot,
7372 display_mode: _,
7373 } => {
7374 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
7375
7376 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
7377 &snapshot,
7378 &edits,
7379 edit_preview.as_ref()?,
7380 true,
7381 cx,
7382 )
7383 .first_line_preview();
7384
7385 let styled_text = gpui::StyledText::new(highlighted_edits.text)
7386 .with_default_highlights(&style.text, highlighted_edits.highlights);
7387
7388 let preview = h_flex()
7389 .gap_1()
7390 .min_w_16()
7391 .child(styled_text)
7392 .when(has_more_lines, |parent| parent.child("…"));
7393
7394 let left = if first_edit_row != cursor_point.row {
7395 render_relative_row_jump("", cursor_point.row, first_edit_row)
7396 .into_any_element()
7397 } else {
7398 Icon::new(IconName::ZedPredict).into_any_element()
7399 };
7400
7401 Some(
7402 h_flex()
7403 .h_full()
7404 .flex_1()
7405 .gap_2()
7406 .pr_1()
7407 .overflow_x_hidden()
7408 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7409 .child(left)
7410 .child(preview),
7411 )
7412 }
7413 }
7414 }
7415
7416 fn render_context_menu(
7417 &self,
7418 style: &EditorStyle,
7419 max_height_in_lines: u32,
7420 y_flipped: bool,
7421 window: &mut Window,
7422 cx: &mut Context<Editor>,
7423 ) -> Option<AnyElement> {
7424 let menu = self.context_menu.borrow();
7425 let menu = menu.as_ref()?;
7426 if !menu.visible() {
7427 return None;
7428 };
7429 Some(menu.render(style, max_height_in_lines, y_flipped, window, cx))
7430 }
7431
7432 fn render_context_menu_aside(
7433 &mut self,
7434 max_size: Size<Pixels>,
7435 window: &mut Window,
7436 cx: &mut Context<Editor>,
7437 ) -> Option<AnyElement> {
7438 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
7439 if menu.visible() {
7440 menu.render_aside(self, max_size, window, cx)
7441 } else {
7442 None
7443 }
7444 })
7445 }
7446
7447 fn hide_context_menu(
7448 &mut self,
7449 window: &mut Window,
7450 cx: &mut Context<Self>,
7451 ) -> Option<CodeContextMenu> {
7452 cx.notify();
7453 self.completion_tasks.clear();
7454 let context_menu = self.context_menu.borrow_mut().take();
7455 self.stale_inline_completion_in_menu.take();
7456 self.update_visible_inline_completion(window, cx);
7457 context_menu
7458 }
7459
7460 fn show_snippet_choices(
7461 &mut self,
7462 choices: &Vec<String>,
7463 selection: Range<Anchor>,
7464 cx: &mut Context<Self>,
7465 ) {
7466 if selection.start.buffer_id.is_none() {
7467 return;
7468 }
7469 let buffer_id = selection.start.buffer_id.unwrap();
7470 let buffer = self.buffer().read(cx).buffer(buffer_id);
7471 let id = post_inc(&mut self.next_completion_id);
7472
7473 if let Some(buffer) = buffer {
7474 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
7475 CompletionsMenu::new_snippet_choices(id, true, choices, selection, buffer),
7476 ));
7477 }
7478 }
7479
7480 pub fn insert_snippet(
7481 &mut self,
7482 insertion_ranges: &[Range<usize>],
7483 snippet: Snippet,
7484 window: &mut Window,
7485 cx: &mut Context<Self>,
7486 ) -> Result<()> {
7487 struct Tabstop<T> {
7488 is_end_tabstop: bool,
7489 ranges: Vec<Range<T>>,
7490 choices: Option<Vec<String>>,
7491 }
7492
7493 let tabstops = self.buffer.update(cx, |buffer, cx| {
7494 let snippet_text: Arc<str> = snippet.text.clone().into();
7495 buffer.edit(
7496 insertion_ranges
7497 .iter()
7498 .cloned()
7499 .map(|range| (range, snippet_text.clone())),
7500 Some(AutoindentMode::EachLine),
7501 cx,
7502 );
7503
7504 let snapshot = &*buffer.read(cx);
7505 let snippet = &snippet;
7506 snippet
7507 .tabstops
7508 .iter()
7509 .map(|tabstop| {
7510 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
7511 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
7512 });
7513 let mut tabstop_ranges = tabstop
7514 .ranges
7515 .iter()
7516 .flat_map(|tabstop_range| {
7517 let mut delta = 0_isize;
7518 insertion_ranges.iter().map(move |insertion_range| {
7519 let insertion_start = insertion_range.start as isize + delta;
7520 delta +=
7521 snippet.text.len() as isize - insertion_range.len() as isize;
7522
7523 let start = ((insertion_start + tabstop_range.start) as usize)
7524 .min(snapshot.len());
7525 let end = ((insertion_start + tabstop_range.end) as usize)
7526 .min(snapshot.len());
7527 snapshot.anchor_before(start)..snapshot.anchor_after(end)
7528 })
7529 })
7530 .collect::<Vec<_>>();
7531 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
7532
7533 Tabstop {
7534 is_end_tabstop,
7535 ranges: tabstop_ranges,
7536 choices: tabstop.choices.clone(),
7537 }
7538 })
7539 .collect::<Vec<_>>()
7540 });
7541 if let Some(tabstop) = tabstops.first() {
7542 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7543 s.select_ranges(tabstop.ranges.iter().cloned());
7544 });
7545
7546 if let Some(choices) = &tabstop.choices {
7547 if let Some(selection) = tabstop.ranges.first() {
7548 self.show_snippet_choices(choices, selection.clone(), cx)
7549 }
7550 }
7551
7552 // If we're already at the last tabstop and it's at the end of the snippet,
7553 // we're done, we don't need to keep the state around.
7554 if !tabstop.is_end_tabstop {
7555 let choices = tabstops
7556 .iter()
7557 .map(|tabstop| tabstop.choices.clone())
7558 .collect();
7559
7560 let ranges = tabstops
7561 .into_iter()
7562 .map(|tabstop| tabstop.ranges)
7563 .collect::<Vec<_>>();
7564
7565 self.snippet_stack.push(SnippetState {
7566 active_index: 0,
7567 ranges,
7568 choices,
7569 });
7570 }
7571
7572 // Check whether the just-entered snippet ends with an auto-closable bracket.
7573 if self.autoclose_regions.is_empty() {
7574 let snapshot = self.buffer.read(cx).snapshot(cx);
7575 for selection in &mut self.selections.all::<Point>(cx) {
7576 let selection_head = selection.head();
7577 let Some(scope) = snapshot.language_scope_at(selection_head) else {
7578 continue;
7579 };
7580
7581 let mut bracket_pair = None;
7582 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
7583 let prev_chars = snapshot
7584 .reversed_chars_at(selection_head)
7585 .collect::<String>();
7586 for (pair, enabled) in scope.brackets() {
7587 if enabled
7588 && pair.close
7589 && prev_chars.starts_with(pair.start.as_str())
7590 && next_chars.starts_with(pair.end.as_str())
7591 {
7592 bracket_pair = Some(pair.clone());
7593 break;
7594 }
7595 }
7596 if let Some(pair) = bracket_pair {
7597 let start = snapshot.anchor_after(selection_head);
7598 let end = snapshot.anchor_after(selection_head);
7599 self.autoclose_regions.push(AutocloseRegion {
7600 selection_id: selection.id,
7601 range: start..end,
7602 pair,
7603 });
7604 }
7605 }
7606 }
7607 }
7608 Ok(())
7609 }
7610
7611 pub fn move_to_next_snippet_tabstop(
7612 &mut self,
7613 window: &mut Window,
7614 cx: &mut Context<Self>,
7615 ) -> bool {
7616 self.move_to_snippet_tabstop(Bias::Right, window, cx)
7617 }
7618
7619 pub fn move_to_prev_snippet_tabstop(
7620 &mut self,
7621 window: &mut Window,
7622 cx: &mut Context<Self>,
7623 ) -> bool {
7624 self.move_to_snippet_tabstop(Bias::Left, window, cx)
7625 }
7626
7627 pub fn move_to_snippet_tabstop(
7628 &mut self,
7629 bias: Bias,
7630 window: &mut Window,
7631 cx: &mut Context<Self>,
7632 ) -> bool {
7633 if let Some(mut snippet) = self.snippet_stack.pop() {
7634 match bias {
7635 Bias::Left => {
7636 if snippet.active_index > 0 {
7637 snippet.active_index -= 1;
7638 } else {
7639 self.snippet_stack.push(snippet);
7640 return false;
7641 }
7642 }
7643 Bias::Right => {
7644 if snippet.active_index + 1 < snippet.ranges.len() {
7645 snippet.active_index += 1;
7646 } else {
7647 self.snippet_stack.push(snippet);
7648 return false;
7649 }
7650 }
7651 }
7652 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
7653 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7654 s.select_anchor_ranges(current_ranges.iter().cloned())
7655 });
7656
7657 if let Some(choices) = &snippet.choices[snippet.active_index] {
7658 if let Some(selection) = current_ranges.first() {
7659 self.show_snippet_choices(&choices, selection.clone(), cx);
7660 }
7661 }
7662
7663 // If snippet state is not at the last tabstop, push it back on the stack
7664 if snippet.active_index + 1 < snippet.ranges.len() {
7665 self.snippet_stack.push(snippet);
7666 }
7667 return true;
7668 }
7669 }
7670
7671 false
7672 }
7673
7674 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7675 self.transact(window, cx, |this, window, cx| {
7676 this.select_all(&SelectAll, window, cx);
7677 this.insert("", window, cx);
7678 });
7679 }
7680
7681 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
7682 self.transact(window, cx, |this, window, cx| {
7683 this.select_autoclose_pair(window, cx);
7684 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
7685 if !this.linked_edit_ranges.is_empty() {
7686 let selections = this.selections.all::<MultiBufferPoint>(cx);
7687 let snapshot = this.buffer.read(cx).snapshot(cx);
7688
7689 for selection in selections.iter() {
7690 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
7691 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
7692 if selection_start.buffer_id != selection_end.buffer_id {
7693 continue;
7694 }
7695 if let Some(ranges) =
7696 this.linked_editing_ranges_for(selection_start..selection_end, cx)
7697 {
7698 for (buffer, entries) in ranges {
7699 linked_ranges.entry(buffer).or_default().extend(entries);
7700 }
7701 }
7702 }
7703 }
7704
7705 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
7706 if !this.selections.line_mode {
7707 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
7708 for selection in &mut selections {
7709 if selection.is_empty() {
7710 let old_head = selection.head();
7711 let mut new_head =
7712 movement::left(&display_map, old_head.to_display_point(&display_map))
7713 .to_point(&display_map);
7714 if let Some((buffer, line_buffer_range)) = display_map
7715 .buffer_snapshot
7716 .buffer_line_for_row(MultiBufferRow(old_head.row))
7717 {
7718 let indent_size =
7719 buffer.indent_size_for_line(line_buffer_range.start.row);
7720 let indent_len = match indent_size.kind {
7721 IndentKind::Space => {
7722 buffer.settings_at(line_buffer_range.start, cx).tab_size
7723 }
7724 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
7725 };
7726 if old_head.column <= indent_size.len && old_head.column > 0 {
7727 let indent_len = indent_len.get();
7728 new_head = cmp::min(
7729 new_head,
7730 MultiBufferPoint::new(
7731 old_head.row,
7732 ((old_head.column - 1) / indent_len) * indent_len,
7733 ),
7734 );
7735 }
7736 }
7737
7738 selection.set_head(new_head, SelectionGoal::None);
7739 }
7740 }
7741 }
7742
7743 this.signature_help_state.set_backspace_pressed(true);
7744 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7745 s.select(selections)
7746 });
7747 this.insert("", window, cx);
7748 let empty_str: Arc<str> = Arc::from("");
7749 for (buffer, edits) in linked_ranges {
7750 let snapshot = buffer.read(cx).snapshot();
7751 use text::ToPoint as TP;
7752
7753 let edits = edits
7754 .into_iter()
7755 .map(|range| {
7756 let end_point = TP::to_point(&range.end, &snapshot);
7757 let mut start_point = TP::to_point(&range.start, &snapshot);
7758
7759 if end_point == start_point {
7760 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
7761 .saturating_sub(1);
7762 start_point =
7763 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
7764 };
7765
7766 (start_point..end_point, empty_str.clone())
7767 })
7768 .sorted_by_key(|(range, _)| range.start)
7769 .collect::<Vec<_>>();
7770 buffer.update(cx, |this, cx| {
7771 this.edit(edits, None, cx);
7772 })
7773 }
7774 this.refresh_inline_completion(true, false, window, cx);
7775 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
7776 });
7777 }
7778
7779 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
7780 self.transact(window, cx, |this, window, cx| {
7781 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7782 let line_mode = s.line_mode;
7783 s.move_with(|map, selection| {
7784 if selection.is_empty() && !line_mode {
7785 let cursor = movement::right(map, selection.head());
7786 selection.end = cursor;
7787 selection.reversed = true;
7788 selection.goal = SelectionGoal::None;
7789 }
7790 })
7791 });
7792 this.insert("", window, cx);
7793 this.refresh_inline_completion(true, false, window, cx);
7794 });
7795 }
7796
7797 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
7798 if self.move_to_prev_snippet_tabstop(window, cx) {
7799 return;
7800 }
7801
7802 self.outdent(&Outdent, window, cx);
7803 }
7804
7805 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
7806 if self.move_to_next_snippet_tabstop(window, cx) || self.read_only(cx) {
7807 return;
7808 }
7809
7810 let mut selections = self.selections.all_adjusted(cx);
7811 let buffer = self.buffer.read(cx);
7812 let snapshot = buffer.snapshot(cx);
7813 let rows_iter = selections.iter().map(|s| s.head().row);
7814 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
7815
7816 let mut edits = Vec::new();
7817 let mut prev_edited_row = 0;
7818 let mut row_delta = 0;
7819 for selection in &mut selections {
7820 if selection.start.row != prev_edited_row {
7821 row_delta = 0;
7822 }
7823 prev_edited_row = selection.end.row;
7824
7825 // If the selection is non-empty, then increase the indentation of the selected lines.
7826 if !selection.is_empty() {
7827 row_delta =
7828 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
7829 continue;
7830 }
7831
7832 // If the selection is empty and the cursor is in the leading whitespace before the
7833 // suggested indentation, then auto-indent the line.
7834 let cursor = selection.head();
7835 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
7836 if let Some(suggested_indent) =
7837 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
7838 {
7839 if cursor.column < suggested_indent.len
7840 && cursor.column <= current_indent.len
7841 && current_indent.len <= suggested_indent.len
7842 {
7843 selection.start = Point::new(cursor.row, suggested_indent.len);
7844 selection.end = selection.start;
7845 if row_delta == 0 {
7846 edits.extend(Buffer::edit_for_indent_size_adjustment(
7847 cursor.row,
7848 current_indent,
7849 suggested_indent,
7850 ));
7851 row_delta = suggested_indent.len - current_indent.len;
7852 }
7853 continue;
7854 }
7855 }
7856
7857 // Otherwise, insert a hard or soft tab.
7858 let settings = buffer.language_settings_at(cursor, cx);
7859 let tab_size = if settings.hard_tabs {
7860 IndentSize::tab()
7861 } else {
7862 let tab_size = settings.tab_size.get();
7863 let char_column = snapshot
7864 .text_for_range(Point::new(cursor.row, 0)..cursor)
7865 .flat_map(str::chars)
7866 .count()
7867 + row_delta as usize;
7868 let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size);
7869 IndentSize::spaces(chars_to_next_tab_stop)
7870 };
7871 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
7872 selection.end = selection.start;
7873 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
7874 row_delta += tab_size.len;
7875 }
7876
7877 self.transact(window, cx, |this, window, cx| {
7878 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
7879 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7880 s.select(selections)
7881 });
7882 this.refresh_inline_completion(true, false, window, cx);
7883 });
7884 }
7885
7886 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
7887 if self.read_only(cx) {
7888 return;
7889 }
7890 let mut selections = self.selections.all::<Point>(cx);
7891 let mut prev_edited_row = 0;
7892 let mut row_delta = 0;
7893 let mut edits = Vec::new();
7894 let buffer = self.buffer.read(cx);
7895 let snapshot = buffer.snapshot(cx);
7896 for selection in &mut selections {
7897 if selection.start.row != prev_edited_row {
7898 row_delta = 0;
7899 }
7900 prev_edited_row = selection.end.row;
7901
7902 row_delta =
7903 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
7904 }
7905
7906 self.transact(window, cx, |this, window, cx| {
7907 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
7908 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7909 s.select(selections)
7910 });
7911 });
7912 }
7913
7914 fn indent_selection(
7915 buffer: &MultiBuffer,
7916 snapshot: &MultiBufferSnapshot,
7917 selection: &mut Selection<Point>,
7918 edits: &mut Vec<(Range<Point>, String)>,
7919 delta_for_start_row: u32,
7920 cx: &App,
7921 ) -> u32 {
7922 let settings = buffer.language_settings_at(selection.start, cx);
7923 let tab_size = settings.tab_size.get();
7924 let indent_kind = if settings.hard_tabs {
7925 IndentKind::Tab
7926 } else {
7927 IndentKind::Space
7928 };
7929 let mut start_row = selection.start.row;
7930 let mut end_row = selection.end.row + 1;
7931
7932 // If a selection ends at the beginning of a line, don't indent
7933 // that last line.
7934 if selection.end.column == 0 && selection.end.row > selection.start.row {
7935 end_row -= 1;
7936 }
7937
7938 // Avoid re-indenting a row that has already been indented by a
7939 // previous selection, but still update this selection's column
7940 // to reflect that indentation.
7941 if delta_for_start_row > 0 {
7942 start_row += 1;
7943 selection.start.column += delta_for_start_row;
7944 if selection.end.row == selection.start.row {
7945 selection.end.column += delta_for_start_row;
7946 }
7947 }
7948
7949 let mut delta_for_end_row = 0;
7950 let has_multiple_rows = start_row + 1 != end_row;
7951 for row in start_row..end_row {
7952 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
7953 let indent_delta = match (current_indent.kind, indent_kind) {
7954 (IndentKind::Space, IndentKind::Space) => {
7955 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
7956 IndentSize::spaces(columns_to_next_tab_stop)
7957 }
7958 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
7959 (_, IndentKind::Tab) => IndentSize::tab(),
7960 };
7961
7962 let start = if has_multiple_rows || current_indent.len < selection.start.column {
7963 0
7964 } else {
7965 selection.start.column
7966 };
7967 let row_start = Point::new(row, start);
7968 edits.push((
7969 row_start..row_start,
7970 indent_delta.chars().collect::<String>(),
7971 ));
7972
7973 // Update this selection's endpoints to reflect the indentation.
7974 if row == selection.start.row {
7975 selection.start.column += indent_delta.len;
7976 }
7977 if row == selection.end.row {
7978 selection.end.column += indent_delta.len;
7979 delta_for_end_row = indent_delta.len;
7980 }
7981 }
7982
7983 if selection.start.row == selection.end.row {
7984 delta_for_start_row + delta_for_end_row
7985 } else {
7986 delta_for_end_row
7987 }
7988 }
7989
7990 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
7991 if self.read_only(cx) {
7992 return;
7993 }
7994 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7995 let selections = self.selections.all::<Point>(cx);
7996 let mut deletion_ranges = Vec::new();
7997 let mut last_outdent = None;
7998 {
7999 let buffer = self.buffer.read(cx);
8000 let snapshot = buffer.snapshot(cx);
8001 for selection in &selections {
8002 let settings = buffer.language_settings_at(selection.start, cx);
8003 let tab_size = settings.tab_size.get();
8004 let mut rows = selection.spanned_rows(false, &display_map);
8005
8006 // Avoid re-outdenting a row that has already been outdented by a
8007 // previous selection.
8008 if let Some(last_row) = last_outdent {
8009 if last_row == rows.start {
8010 rows.start = rows.start.next_row();
8011 }
8012 }
8013 let has_multiple_rows = rows.len() > 1;
8014 for row in rows.iter_rows() {
8015 let indent_size = snapshot.indent_size_for_line(row);
8016 if indent_size.len > 0 {
8017 let deletion_len = match indent_size.kind {
8018 IndentKind::Space => {
8019 let columns_to_prev_tab_stop = indent_size.len % tab_size;
8020 if columns_to_prev_tab_stop == 0 {
8021 tab_size
8022 } else {
8023 columns_to_prev_tab_stop
8024 }
8025 }
8026 IndentKind::Tab => 1,
8027 };
8028 let start = if has_multiple_rows
8029 || deletion_len > selection.start.column
8030 || indent_size.len < selection.start.column
8031 {
8032 0
8033 } else {
8034 selection.start.column - deletion_len
8035 };
8036 deletion_ranges.push(
8037 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
8038 );
8039 last_outdent = Some(row);
8040 }
8041 }
8042 }
8043 }
8044
8045 self.transact(window, cx, |this, window, cx| {
8046 this.buffer.update(cx, |buffer, cx| {
8047 let empty_str: Arc<str> = Arc::default();
8048 buffer.edit(
8049 deletion_ranges
8050 .into_iter()
8051 .map(|range| (range, empty_str.clone())),
8052 None,
8053 cx,
8054 );
8055 });
8056 let selections = this.selections.all::<usize>(cx);
8057 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8058 s.select(selections)
8059 });
8060 });
8061 }
8062
8063 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
8064 if self.read_only(cx) {
8065 return;
8066 }
8067 let selections = self
8068 .selections
8069 .all::<usize>(cx)
8070 .into_iter()
8071 .map(|s| s.range());
8072
8073 self.transact(window, cx, |this, window, cx| {
8074 this.buffer.update(cx, |buffer, cx| {
8075 buffer.autoindent_ranges(selections, cx);
8076 });
8077 let selections = this.selections.all::<usize>(cx);
8078 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8079 s.select(selections)
8080 });
8081 });
8082 }
8083
8084 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
8085 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8086 let selections = self.selections.all::<Point>(cx);
8087
8088 let mut new_cursors = Vec::new();
8089 let mut edit_ranges = Vec::new();
8090 let mut selections = selections.iter().peekable();
8091 while let Some(selection) = selections.next() {
8092 let mut rows = selection.spanned_rows(false, &display_map);
8093 let goal_display_column = selection.head().to_display_point(&display_map).column();
8094
8095 // Accumulate contiguous regions of rows that we want to delete.
8096 while let Some(next_selection) = selections.peek() {
8097 let next_rows = next_selection.spanned_rows(false, &display_map);
8098 if next_rows.start <= rows.end {
8099 rows.end = next_rows.end;
8100 selections.next().unwrap();
8101 } else {
8102 break;
8103 }
8104 }
8105
8106 let buffer = &display_map.buffer_snapshot;
8107 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
8108 let edit_end;
8109 let cursor_buffer_row;
8110 if buffer.max_point().row >= rows.end.0 {
8111 // If there's a line after the range, delete the \n from the end of the row range
8112 // and position the cursor on the next line.
8113 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
8114 cursor_buffer_row = rows.end;
8115 } else {
8116 // If there isn't a line after the range, delete the \n from the line before the
8117 // start of the row range and position the cursor there.
8118 edit_start = edit_start.saturating_sub(1);
8119 edit_end = buffer.len();
8120 cursor_buffer_row = rows.start.previous_row();
8121 }
8122
8123 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
8124 *cursor.column_mut() =
8125 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
8126
8127 new_cursors.push((
8128 selection.id,
8129 buffer.anchor_after(cursor.to_point(&display_map)),
8130 ));
8131 edit_ranges.push(edit_start..edit_end);
8132 }
8133
8134 self.transact(window, cx, |this, window, cx| {
8135 let buffer = this.buffer.update(cx, |buffer, cx| {
8136 let empty_str: Arc<str> = Arc::default();
8137 buffer.edit(
8138 edit_ranges
8139 .into_iter()
8140 .map(|range| (range, empty_str.clone())),
8141 None,
8142 cx,
8143 );
8144 buffer.snapshot(cx)
8145 });
8146 let new_selections = new_cursors
8147 .into_iter()
8148 .map(|(id, cursor)| {
8149 let cursor = cursor.to_point(&buffer);
8150 Selection {
8151 id,
8152 start: cursor,
8153 end: cursor,
8154 reversed: false,
8155 goal: SelectionGoal::None,
8156 }
8157 })
8158 .collect();
8159
8160 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8161 s.select(new_selections);
8162 });
8163 });
8164 }
8165
8166 pub fn join_lines_impl(
8167 &mut self,
8168 insert_whitespace: bool,
8169 window: &mut Window,
8170 cx: &mut Context<Self>,
8171 ) {
8172 if self.read_only(cx) {
8173 return;
8174 }
8175 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
8176 for selection in self.selections.all::<Point>(cx) {
8177 let start = MultiBufferRow(selection.start.row);
8178 // Treat single line selections as if they include the next line. Otherwise this action
8179 // would do nothing for single line selections individual cursors.
8180 let end = if selection.start.row == selection.end.row {
8181 MultiBufferRow(selection.start.row + 1)
8182 } else {
8183 MultiBufferRow(selection.end.row)
8184 };
8185
8186 if let Some(last_row_range) = row_ranges.last_mut() {
8187 if start <= last_row_range.end {
8188 last_row_range.end = end;
8189 continue;
8190 }
8191 }
8192 row_ranges.push(start..end);
8193 }
8194
8195 let snapshot = self.buffer.read(cx).snapshot(cx);
8196 let mut cursor_positions = Vec::new();
8197 for row_range in &row_ranges {
8198 let anchor = snapshot.anchor_before(Point::new(
8199 row_range.end.previous_row().0,
8200 snapshot.line_len(row_range.end.previous_row()),
8201 ));
8202 cursor_positions.push(anchor..anchor);
8203 }
8204
8205 self.transact(window, cx, |this, window, cx| {
8206 for row_range in row_ranges.into_iter().rev() {
8207 for row in row_range.iter_rows().rev() {
8208 let end_of_line = Point::new(row.0, snapshot.line_len(row));
8209 let next_line_row = row.next_row();
8210 let indent = snapshot.indent_size_for_line(next_line_row);
8211 let start_of_next_line = Point::new(next_line_row.0, indent.len);
8212
8213 let replace =
8214 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
8215 " "
8216 } else {
8217 ""
8218 };
8219
8220 this.buffer.update(cx, |buffer, cx| {
8221 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
8222 });
8223 }
8224 }
8225
8226 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8227 s.select_anchor_ranges(cursor_positions)
8228 });
8229 });
8230 }
8231
8232 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
8233 self.join_lines_impl(true, window, cx);
8234 }
8235
8236 pub fn sort_lines_case_sensitive(
8237 &mut self,
8238 _: &SortLinesCaseSensitive,
8239 window: &mut Window,
8240 cx: &mut Context<Self>,
8241 ) {
8242 self.manipulate_lines(window, cx, |lines| lines.sort())
8243 }
8244
8245 pub fn sort_lines_case_insensitive(
8246 &mut self,
8247 _: &SortLinesCaseInsensitive,
8248 window: &mut Window,
8249 cx: &mut Context<Self>,
8250 ) {
8251 self.manipulate_lines(window, cx, |lines| {
8252 lines.sort_by_key(|line| line.to_lowercase())
8253 })
8254 }
8255
8256 pub fn unique_lines_case_insensitive(
8257 &mut self,
8258 _: &UniqueLinesCaseInsensitive,
8259 window: &mut Window,
8260 cx: &mut Context<Self>,
8261 ) {
8262 self.manipulate_lines(window, cx, |lines| {
8263 let mut seen = HashSet::default();
8264 lines.retain(|line| seen.insert(line.to_lowercase()));
8265 })
8266 }
8267
8268 pub fn unique_lines_case_sensitive(
8269 &mut self,
8270 _: &UniqueLinesCaseSensitive,
8271 window: &mut Window,
8272 cx: &mut Context<Self>,
8273 ) {
8274 self.manipulate_lines(window, cx, |lines| {
8275 let mut seen = HashSet::default();
8276 lines.retain(|line| seen.insert(*line));
8277 })
8278 }
8279
8280 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
8281 let Some(project) = self.project.clone() else {
8282 return;
8283 };
8284 self.reload(project, window, cx)
8285 .detach_and_notify_err(window, cx);
8286 }
8287
8288 pub fn restore_file(
8289 &mut self,
8290 _: &::git::RestoreFile,
8291 window: &mut Window,
8292 cx: &mut Context<Self>,
8293 ) {
8294 let mut buffer_ids = HashSet::default();
8295 let snapshot = self.buffer().read(cx).snapshot(cx);
8296 for selection in self.selections.all::<usize>(cx) {
8297 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
8298 }
8299
8300 let buffer = self.buffer().read(cx);
8301 let ranges = buffer_ids
8302 .into_iter()
8303 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
8304 .collect::<Vec<_>>();
8305
8306 self.restore_hunks_in_ranges(ranges, window, cx);
8307 }
8308
8309 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
8310 let selections = self
8311 .selections
8312 .all(cx)
8313 .into_iter()
8314 .map(|s| s.range())
8315 .collect();
8316 self.restore_hunks_in_ranges(selections, window, cx);
8317 }
8318
8319 fn restore_hunks_in_ranges(
8320 &mut self,
8321 ranges: Vec<Range<Point>>,
8322 window: &mut Window,
8323 cx: &mut Context<Editor>,
8324 ) {
8325 let mut revert_changes = HashMap::default();
8326 let chunk_by = self
8327 .snapshot(window, cx)
8328 .hunks_for_ranges(ranges)
8329 .into_iter()
8330 .chunk_by(|hunk| hunk.buffer_id);
8331 for (buffer_id, hunks) in &chunk_by {
8332 let hunks = hunks.collect::<Vec<_>>();
8333 for hunk in &hunks {
8334 self.prepare_restore_change(&mut revert_changes, hunk, cx);
8335 }
8336 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
8337 }
8338 drop(chunk_by);
8339 if !revert_changes.is_empty() {
8340 self.transact(window, cx, |editor, window, cx| {
8341 editor.restore(revert_changes, window, cx);
8342 });
8343 }
8344 }
8345
8346 pub fn open_active_item_in_terminal(
8347 &mut self,
8348 _: &OpenInTerminal,
8349 window: &mut Window,
8350 cx: &mut Context<Self>,
8351 ) {
8352 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
8353 let project_path = buffer.read(cx).project_path(cx)?;
8354 let project = self.project.as_ref()?.read(cx);
8355 let entry = project.entry_for_path(&project_path, cx)?;
8356 let parent = match &entry.canonical_path {
8357 Some(canonical_path) => canonical_path.to_path_buf(),
8358 None => project.absolute_path(&project_path, cx)?,
8359 }
8360 .parent()?
8361 .to_path_buf();
8362 Some(parent)
8363 }) {
8364 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
8365 }
8366 }
8367
8368 fn set_breakpoint_context_menu(
8369 &mut self,
8370 row: DisplayRow,
8371 position: Option<Anchor>,
8372 kind: Arc<BreakpointKind>,
8373 clicked_point: gpui::Point<Pixels>,
8374 window: &mut Window,
8375 cx: &mut Context<Self>,
8376 ) {
8377 if !cx.has_flag::<Debugger>() {
8378 return;
8379 }
8380 let source = self
8381 .buffer
8382 .read(cx)
8383 .snapshot(cx)
8384 .anchor_before(Point::new(row.0, 0u32));
8385
8386 let context_menu =
8387 self.breakpoint_context_menu(position.unwrap_or(source), kind, window, cx);
8388
8389 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
8390 self,
8391 source,
8392 clicked_point,
8393 context_menu,
8394 window,
8395 cx,
8396 );
8397 }
8398
8399 fn add_edit_breakpoint_block(
8400 &mut self,
8401 anchor: Anchor,
8402 kind: &BreakpointKind,
8403 window: &mut Window,
8404 cx: &mut Context<Self>,
8405 ) {
8406 let weak_editor = cx.weak_entity();
8407 let bp_prompt =
8408 cx.new(|cx| BreakpointPromptEditor::new(weak_editor, anchor, kind.clone(), window, cx));
8409
8410 let height = bp_prompt.update(cx, |this, cx| {
8411 this.prompt
8412 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
8413 });
8414 let cloned_prompt = bp_prompt.clone();
8415 let blocks = vec![BlockProperties {
8416 style: BlockStyle::Sticky,
8417 placement: BlockPlacement::Above(anchor),
8418 height,
8419 render: Arc::new(move |cx| {
8420 *cloned_prompt.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
8421 cloned_prompt.clone().into_any_element()
8422 }),
8423 priority: 0,
8424 }];
8425
8426 let focus_handle = bp_prompt.focus_handle(cx);
8427 window.focus(&focus_handle);
8428
8429 let block_ids = self.insert_blocks(blocks, None, cx);
8430 bp_prompt.update(cx, |prompt, _| {
8431 prompt.add_block_ids(block_ids);
8432 });
8433 }
8434
8435 pub(crate) fn breakpoint_at_cursor_head(
8436 &self,
8437 window: &mut Window,
8438 cx: &mut Context<Self>,
8439 ) -> Option<(Anchor, Breakpoint)> {
8440 let cursor_position: Point = self.selections.newest(cx).head();
8441 let snapshot = self.snapshot(window, cx);
8442 // We Set the column position to zero so this function interacts correctly
8443 // between calls by clicking on the gutter & using an action to toggle a
8444 // breakpoint. Otherwise, toggling a breakpoint through an action wouldn't
8445 // untoggle a breakpoint that was added through clicking on the gutter
8446 let cursor_position = snapshot
8447 .display_snapshot
8448 .buffer_snapshot
8449 .anchor_before(Point::new(cursor_position.row, 0));
8450
8451 let project = self.project.clone();
8452
8453 let buffer_id = cursor_position.text_anchor.buffer_id?;
8454 let enclosing_excerpt = snapshot
8455 .buffer_snapshot
8456 .excerpt_ids_for_range(cursor_position..cursor_position)
8457 .next()?;
8458 let buffer = project?.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
8459 let buffer_snapshot = buffer.read(cx).snapshot();
8460
8461 let row = buffer_snapshot
8462 .summary_for_anchor::<text::PointUtf16>(&cursor_position.text_anchor)
8463 .row;
8464
8465 let bp = self
8466 .breakpoint_store
8467 .as_ref()?
8468 .read_with(cx, |breakpoint_store, cx| {
8469 breakpoint_store
8470 .breakpoints(
8471 &buffer,
8472 Some(cursor_position.text_anchor..(text::Anchor::MAX)),
8473 buffer_snapshot.clone(),
8474 cx,
8475 )
8476 .next()
8477 .and_then(move |(anchor, bp)| {
8478 let breakpoint_row = buffer_snapshot
8479 .summary_for_anchor::<text::PointUtf16>(anchor)
8480 .row;
8481
8482 if breakpoint_row == row {
8483 snapshot
8484 .buffer_snapshot
8485 .anchor_in_excerpt(enclosing_excerpt, *anchor)
8486 .map(|anchor| (anchor, bp.clone()))
8487 } else {
8488 None
8489 }
8490 })
8491 });
8492 bp
8493 }
8494
8495 pub fn edit_log_breakpoint(
8496 &mut self,
8497 _: &EditLogBreakpoint,
8498 window: &mut Window,
8499 cx: &mut Context<Self>,
8500 ) {
8501 let (anchor, bp) = self
8502 .breakpoint_at_cursor_head(window, cx)
8503 .unwrap_or_else(|| {
8504 let cursor_position: Point = self.selections.newest(cx).head();
8505
8506 let breakpoint_position = self
8507 .snapshot(window, cx)
8508 .display_snapshot
8509 .buffer_snapshot
8510 .anchor_before(Point::new(cursor_position.row, 0));
8511
8512 (
8513 breakpoint_position,
8514 Breakpoint {
8515 kind: BreakpointKind::Standard,
8516 },
8517 )
8518 });
8519
8520 self.add_edit_breakpoint_block(anchor, &bp.kind, window, cx);
8521 }
8522
8523 pub fn toggle_breakpoint(
8524 &mut self,
8525 _: &crate::actions::ToggleBreakpoint,
8526 window: &mut Window,
8527 cx: &mut Context<Self>,
8528 ) {
8529 let edit_action = BreakpointEditAction::Toggle;
8530
8531 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8532 self.edit_breakpoint_at_anchor(anchor, breakpoint.kind, edit_action, cx);
8533 } else {
8534 let cursor_position: Point = self.selections.newest(cx).head();
8535
8536 let breakpoint_position = self
8537 .snapshot(window, cx)
8538 .display_snapshot
8539 .buffer_snapshot
8540 .anchor_before(Point::new(cursor_position.row, 0));
8541
8542 self.edit_breakpoint_at_anchor(
8543 breakpoint_position,
8544 BreakpointKind::Standard,
8545 edit_action,
8546 cx,
8547 );
8548 }
8549 }
8550
8551 pub fn edit_breakpoint_at_anchor(
8552 &mut self,
8553 breakpoint_position: Anchor,
8554 kind: BreakpointKind,
8555 edit_action: BreakpointEditAction,
8556 cx: &mut Context<Self>,
8557 ) {
8558 let Some(breakpoint_store) = &self.breakpoint_store else {
8559 return;
8560 };
8561
8562 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
8563 if breakpoint_position == Anchor::min() {
8564 self.buffer()
8565 .read(cx)
8566 .excerpt_buffer_ids()
8567 .into_iter()
8568 .next()
8569 } else {
8570 None
8571 }
8572 }) else {
8573 return;
8574 };
8575
8576 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
8577 return;
8578 };
8579
8580 breakpoint_store.update(cx, |breakpoint_store, cx| {
8581 breakpoint_store.toggle_breakpoint(
8582 buffer,
8583 (breakpoint_position.text_anchor, Breakpoint { kind }),
8584 edit_action,
8585 cx,
8586 );
8587 });
8588
8589 cx.notify();
8590 }
8591
8592 #[cfg(any(test, feature = "test-support"))]
8593 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
8594 self.breakpoint_store.clone()
8595 }
8596
8597 pub fn prepare_restore_change(
8598 &self,
8599 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
8600 hunk: &MultiBufferDiffHunk,
8601 cx: &mut App,
8602 ) -> Option<()> {
8603 if hunk.is_created_file() {
8604 return None;
8605 }
8606 let buffer = self.buffer.read(cx);
8607 let diff = buffer.diff_for(hunk.buffer_id)?;
8608 let buffer = buffer.buffer(hunk.buffer_id)?;
8609 let buffer = buffer.read(cx);
8610 let original_text = diff
8611 .read(cx)
8612 .base_text()
8613 .as_rope()
8614 .slice(hunk.diff_base_byte_range.clone());
8615 let buffer_snapshot = buffer.snapshot();
8616 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
8617 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
8618 probe
8619 .0
8620 .start
8621 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
8622 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
8623 }) {
8624 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
8625 Some(())
8626 } else {
8627 None
8628 }
8629 }
8630
8631 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
8632 self.manipulate_lines(window, cx, |lines| lines.reverse())
8633 }
8634
8635 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
8636 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
8637 }
8638
8639 fn manipulate_lines<Fn>(
8640 &mut self,
8641 window: &mut Window,
8642 cx: &mut Context<Self>,
8643 mut callback: Fn,
8644 ) where
8645 Fn: FnMut(&mut Vec<&str>),
8646 {
8647 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8648 let buffer = self.buffer.read(cx).snapshot(cx);
8649
8650 let mut edits = Vec::new();
8651
8652 let selections = self.selections.all::<Point>(cx);
8653 let mut selections = selections.iter().peekable();
8654 let mut contiguous_row_selections = Vec::new();
8655 let mut new_selections = Vec::new();
8656 let mut added_lines = 0;
8657 let mut removed_lines = 0;
8658
8659 while let Some(selection) = selections.next() {
8660 let (start_row, end_row) = consume_contiguous_rows(
8661 &mut contiguous_row_selections,
8662 selection,
8663 &display_map,
8664 &mut selections,
8665 );
8666
8667 let start_point = Point::new(start_row.0, 0);
8668 let end_point = Point::new(
8669 end_row.previous_row().0,
8670 buffer.line_len(end_row.previous_row()),
8671 );
8672 let text = buffer
8673 .text_for_range(start_point..end_point)
8674 .collect::<String>();
8675
8676 let mut lines = text.split('\n').collect_vec();
8677
8678 let lines_before = lines.len();
8679 callback(&mut lines);
8680 let lines_after = lines.len();
8681
8682 edits.push((start_point..end_point, lines.join("\n")));
8683
8684 // Selections must change based on added and removed line count
8685 let start_row =
8686 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
8687 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
8688 new_selections.push(Selection {
8689 id: selection.id,
8690 start: start_row,
8691 end: end_row,
8692 goal: SelectionGoal::None,
8693 reversed: selection.reversed,
8694 });
8695
8696 if lines_after > lines_before {
8697 added_lines += lines_after - lines_before;
8698 } else if lines_before > lines_after {
8699 removed_lines += lines_before - lines_after;
8700 }
8701 }
8702
8703 self.transact(window, cx, |this, window, cx| {
8704 let buffer = this.buffer.update(cx, |buffer, cx| {
8705 buffer.edit(edits, None, cx);
8706 buffer.snapshot(cx)
8707 });
8708
8709 // Recalculate offsets on newly edited buffer
8710 let new_selections = new_selections
8711 .iter()
8712 .map(|s| {
8713 let start_point = Point::new(s.start.0, 0);
8714 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
8715 Selection {
8716 id: s.id,
8717 start: buffer.point_to_offset(start_point),
8718 end: buffer.point_to_offset(end_point),
8719 goal: s.goal,
8720 reversed: s.reversed,
8721 }
8722 })
8723 .collect();
8724
8725 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8726 s.select(new_selections);
8727 });
8728
8729 this.request_autoscroll(Autoscroll::fit(), cx);
8730 });
8731 }
8732
8733 pub fn convert_to_upper_case(
8734 &mut self,
8735 _: &ConvertToUpperCase,
8736 window: &mut Window,
8737 cx: &mut Context<Self>,
8738 ) {
8739 self.manipulate_text(window, cx, |text| text.to_uppercase())
8740 }
8741
8742 pub fn convert_to_lower_case(
8743 &mut self,
8744 _: &ConvertToLowerCase,
8745 window: &mut Window,
8746 cx: &mut Context<Self>,
8747 ) {
8748 self.manipulate_text(window, cx, |text| text.to_lowercase())
8749 }
8750
8751 pub fn convert_to_title_case(
8752 &mut self,
8753 _: &ConvertToTitleCase,
8754 window: &mut Window,
8755 cx: &mut Context<Self>,
8756 ) {
8757 self.manipulate_text(window, cx, |text| {
8758 text.split('\n')
8759 .map(|line| line.to_case(Case::Title))
8760 .join("\n")
8761 })
8762 }
8763
8764 pub fn convert_to_snake_case(
8765 &mut self,
8766 _: &ConvertToSnakeCase,
8767 window: &mut Window,
8768 cx: &mut Context<Self>,
8769 ) {
8770 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
8771 }
8772
8773 pub fn convert_to_kebab_case(
8774 &mut self,
8775 _: &ConvertToKebabCase,
8776 window: &mut Window,
8777 cx: &mut Context<Self>,
8778 ) {
8779 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
8780 }
8781
8782 pub fn convert_to_upper_camel_case(
8783 &mut self,
8784 _: &ConvertToUpperCamelCase,
8785 window: &mut Window,
8786 cx: &mut Context<Self>,
8787 ) {
8788 self.manipulate_text(window, cx, |text| {
8789 text.split('\n')
8790 .map(|line| line.to_case(Case::UpperCamel))
8791 .join("\n")
8792 })
8793 }
8794
8795 pub fn convert_to_lower_camel_case(
8796 &mut self,
8797 _: &ConvertToLowerCamelCase,
8798 window: &mut Window,
8799 cx: &mut Context<Self>,
8800 ) {
8801 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
8802 }
8803
8804 pub fn convert_to_opposite_case(
8805 &mut self,
8806 _: &ConvertToOppositeCase,
8807 window: &mut Window,
8808 cx: &mut Context<Self>,
8809 ) {
8810 self.manipulate_text(window, cx, |text| {
8811 text.chars()
8812 .fold(String::with_capacity(text.len()), |mut t, c| {
8813 if c.is_uppercase() {
8814 t.extend(c.to_lowercase());
8815 } else {
8816 t.extend(c.to_uppercase());
8817 }
8818 t
8819 })
8820 })
8821 }
8822
8823 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
8824 where
8825 Fn: FnMut(&str) -> String,
8826 {
8827 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8828 let buffer = self.buffer.read(cx).snapshot(cx);
8829
8830 let mut new_selections = Vec::new();
8831 let mut edits = Vec::new();
8832 let mut selection_adjustment = 0i32;
8833
8834 for selection in self.selections.all::<usize>(cx) {
8835 let selection_is_empty = selection.is_empty();
8836
8837 let (start, end) = if selection_is_empty {
8838 let word_range = movement::surrounding_word(
8839 &display_map,
8840 selection.start.to_display_point(&display_map),
8841 );
8842 let start = word_range.start.to_offset(&display_map, Bias::Left);
8843 let end = word_range.end.to_offset(&display_map, Bias::Left);
8844 (start, end)
8845 } else {
8846 (selection.start, selection.end)
8847 };
8848
8849 let text = buffer.text_for_range(start..end).collect::<String>();
8850 let old_length = text.len() as i32;
8851 let text = callback(&text);
8852
8853 new_selections.push(Selection {
8854 start: (start as i32 - selection_adjustment) as usize,
8855 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
8856 goal: SelectionGoal::None,
8857 ..selection
8858 });
8859
8860 selection_adjustment += old_length - text.len() as i32;
8861
8862 edits.push((start..end, text));
8863 }
8864
8865 self.transact(window, cx, |this, window, cx| {
8866 this.buffer.update(cx, |buffer, cx| {
8867 buffer.edit(edits, None, cx);
8868 });
8869
8870 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8871 s.select(new_selections);
8872 });
8873
8874 this.request_autoscroll(Autoscroll::fit(), cx);
8875 });
8876 }
8877
8878 pub fn duplicate(
8879 &mut self,
8880 upwards: bool,
8881 whole_lines: bool,
8882 window: &mut Window,
8883 cx: &mut Context<Self>,
8884 ) {
8885 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8886 let buffer = &display_map.buffer_snapshot;
8887 let selections = self.selections.all::<Point>(cx);
8888
8889 let mut edits = Vec::new();
8890 let mut selections_iter = selections.iter().peekable();
8891 while let Some(selection) = selections_iter.next() {
8892 let mut rows = selection.spanned_rows(false, &display_map);
8893 // duplicate line-wise
8894 if whole_lines || selection.start == selection.end {
8895 // Avoid duplicating the same lines twice.
8896 while let Some(next_selection) = selections_iter.peek() {
8897 let next_rows = next_selection.spanned_rows(false, &display_map);
8898 if next_rows.start < rows.end {
8899 rows.end = next_rows.end;
8900 selections_iter.next().unwrap();
8901 } else {
8902 break;
8903 }
8904 }
8905
8906 // Copy the text from the selected row region and splice it either at the start
8907 // or end of the region.
8908 let start = Point::new(rows.start.0, 0);
8909 let end = Point::new(
8910 rows.end.previous_row().0,
8911 buffer.line_len(rows.end.previous_row()),
8912 );
8913 let text = buffer
8914 .text_for_range(start..end)
8915 .chain(Some("\n"))
8916 .collect::<String>();
8917 let insert_location = if upwards {
8918 Point::new(rows.end.0, 0)
8919 } else {
8920 start
8921 };
8922 edits.push((insert_location..insert_location, text));
8923 } else {
8924 // duplicate character-wise
8925 let start = selection.start;
8926 let end = selection.end;
8927 let text = buffer.text_for_range(start..end).collect::<String>();
8928 edits.push((selection.end..selection.end, text));
8929 }
8930 }
8931
8932 self.transact(window, cx, |this, _, cx| {
8933 this.buffer.update(cx, |buffer, cx| {
8934 buffer.edit(edits, None, cx);
8935 });
8936
8937 this.request_autoscroll(Autoscroll::fit(), cx);
8938 });
8939 }
8940
8941 pub fn duplicate_line_up(
8942 &mut self,
8943 _: &DuplicateLineUp,
8944 window: &mut Window,
8945 cx: &mut Context<Self>,
8946 ) {
8947 self.duplicate(true, true, window, cx);
8948 }
8949
8950 pub fn duplicate_line_down(
8951 &mut self,
8952 _: &DuplicateLineDown,
8953 window: &mut Window,
8954 cx: &mut Context<Self>,
8955 ) {
8956 self.duplicate(false, true, window, cx);
8957 }
8958
8959 pub fn duplicate_selection(
8960 &mut self,
8961 _: &DuplicateSelection,
8962 window: &mut Window,
8963 cx: &mut Context<Self>,
8964 ) {
8965 self.duplicate(false, false, window, cx);
8966 }
8967
8968 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
8969 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8970 let buffer = self.buffer.read(cx).snapshot(cx);
8971
8972 let mut edits = Vec::new();
8973 let mut unfold_ranges = Vec::new();
8974 let mut refold_creases = Vec::new();
8975
8976 let selections = self.selections.all::<Point>(cx);
8977 let mut selections = selections.iter().peekable();
8978 let mut contiguous_row_selections = Vec::new();
8979 let mut new_selections = Vec::new();
8980
8981 while let Some(selection) = selections.next() {
8982 // Find all the selections that span a contiguous row range
8983 let (start_row, end_row) = consume_contiguous_rows(
8984 &mut contiguous_row_selections,
8985 selection,
8986 &display_map,
8987 &mut selections,
8988 );
8989
8990 // Move the text spanned by the row range to be before the line preceding the row range
8991 if start_row.0 > 0 {
8992 let range_to_move = Point::new(
8993 start_row.previous_row().0,
8994 buffer.line_len(start_row.previous_row()),
8995 )
8996 ..Point::new(
8997 end_row.previous_row().0,
8998 buffer.line_len(end_row.previous_row()),
8999 );
9000 let insertion_point = display_map
9001 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
9002 .0;
9003
9004 // Don't move lines across excerpts
9005 if buffer
9006 .excerpt_containing(insertion_point..range_to_move.end)
9007 .is_some()
9008 {
9009 let text = buffer
9010 .text_for_range(range_to_move.clone())
9011 .flat_map(|s| s.chars())
9012 .skip(1)
9013 .chain(['\n'])
9014 .collect::<String>();
9015
9016 edits.push((
9017 buffer.anchor_after(range_to_move.start)
9018 ..buffer.anchor_before(range_to_move.end),
9019 String::new(),
9020 ));
9021 let insertion_anchor = buffer.anchor_after(insertion_point);
9022 edits.push((insertion_anchor..insertion_anchor, text));
9023
9024 let row_delta = range_to_move.start.row - insertion_point.row + 1;
9025
9026 // Move selections up
9027 new_selections.extend(contiguous_row_selections.drain(..).map(
9028 |mut selection| {
9029 selection.start.row -= row_delta;
9030 selection.end.row -= row_delta;
9031 selection
9032 },
9033 ));
9034
9035 // Move folds up
9036 unfold_ranges.push(range_to_move.clone());
9037 for fold in display_map.folds_in_range(
9038 buffer.anchor_before(range_to_move.start)
9039 ..buffer.anchor_after(range_to_move.end),
9040 ) {
9041 let mut start = fold.range.start.to_point(&buffer);
9042 let mut end = fold.range.end.to_point(&buffer);
9043 start.row -= row_delta;
9044 end.row -= row_delta;
9045 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9046 }
9047 }
9048 }
9049
9050 // If we didn't move line(s), preserve the existing selections
9051 new_selections.append(&mut contiguous_row_selections);
9052 }
9053
9054 self.transact(window, cx, |this, window, cx| {
9055 this.unfold_ranges(&unfold_ranges, true, true, cx);
9056 this.buffer.update(cx, |buffer, cx| {
9057 for (range, text) in edits {
9058 buffer.edit([(range, text)], None, cx);
9059 }
9060 });
9061 this.fold_creases(refold_creases, true, window, cx);
9062 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9063 s.select(new_selections);
9064 })
9065 });
9066 }
9067
9068 pub fn move_line_down(
9069 &mut self,
9070 _: &MoveLineDown,
9071 window: &mut Window,
9072 cx: &mut Context<Self>,
9073 ) {
9074 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9075 let buffer = self.buffer.read(cx).snapshot(cx);
9076
9077 let mut edits = Vec::new();
9078 let mut unfold_ranges = Vec::new();
9079 let mut refold_creases = Vec::new();
9080
9081 let selections = self.selections.all::<Point>(cx);
9082 let mut selections = selections.iter().peekable();
9083 let mut contiguous_row_selections = Vec::new();
9084 let mut new_selections = Vec::new();
9085
9086 while let Some(selection) = selections.next() {
9087 // Find all the selections that span a contiguous row range
9088 let (start_row, end_row) = consume_contiguous_rows(
9089 &mut contiguous_row_selections,
9090 selection,
9091 &display_map,
9092 &mut selections,
9093 );
9094
9095 // Move the text spanned by the row range to be after the last line of the row range
9096 if end_row.0 <= buffer.max_point().row {
9097 let range_to_move =
9098 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
9099 let insertion_point = display_map
9100 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
9101 .0;
9102
9103 // Don't move lines across excerpt boundaries
9104 if buffer
9105 .excerpt_containing(range_to_move.start..insertion_point)
9106 .is_some()
9107 {
9108 let mut text = String::from("\n");
9109 text.extend(buffer.text_for_range(range_to_move.clone()));
9110 text.pop(); // Drop trailing newline
9111 edits.push((
9112 buffer.anchor_after(range_to_move.start)
9113 ..buffer.anchor_before(range_to_move.end),
9114 String::new(),
9115 ));
9116 let insertion_anchor = buffer.anchor_after(insertion_point);
9117 edits.push((insertion_anchor..insertion_anchor, text));
9118
9119 let row_delta = insertion_point.row - range_to_move.end.row + 1;
9120
9121 // Move selections down
9122 new_selections.extend(contiguous_row_selections.drain(..).map(
9123 |mut selection| {
9124 selection.start.row += row_delta;
9125 selection.end.row += row_delta;
9126 selection
9127 },
9128 ));
9129
9130 // Move folds down
9131 unfold_ranges.push(range_to_move.clone());
9132 for fold in display_map.folds_in_range(
9133 buffer.anchor_before(range_to_move.start)
9134 ..buffer.anchor_after(range_to_move.end),
9135 ) {
9136 let mut start = fold.range.start.to_point(&buffer);
9137 let mut end = fold.range.end.to_point(&buffer);
9138 start.row += row_delta;
9139 end.row += row_delta;
9140 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9141 }
9142 }
9143 }
9144
9145 // If we didn't move line(s), preserve the existing selections
9146 new_selections.append(&mut contiguous_row_selections);
9147 }
9148
9149 self.transact(window, cx, |this, window, cx| {
9150 this.unfold_ranges(&unfold_ranges, true, true, cx);
9151 this.buffer.update(cx, |buffer, cx| {
9152 for (range, text) in edits {
9153 buffer.edit([(range, text)], None, cx);
9154 }
9155 });
9156 this.fold_creases(refold_creases, true, window, cx);
9157 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9158 s.select(new_selections)
9159 });
9160 });
9161 }
9162
9163 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
9164 let text_layout_details = &self.text_layout_details(window);
9165 self.transact(window, cx, |this, window, cx| {
9166 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9167 let mut edits: Vec<(Range<usize>, String)> = Default::default();
9168 let line_mode = s.line_mode;
9169 s.move_with(|display_map, selection| {
9170 if !selection.is_empty() || line_mode {
9171 return;
9172 }
9173
9174 let mut head = selection.head();
9175 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
9176 if head.column() == display_map.line_len(head.row()) {
9177 transpose_offset = display_map
9178 .buffer_snapshot
9179 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9180 }
9181
9182 if transpose_offset == 0 {
9183 return;
9184 }
9185
9186 *head.column_mut() += 1;
9187 head = display_map.clip_point(head, Bias::Right);
9188 let goal = SelectionGoal::HorizontalPosition(
9189 display_map
9190 .x_for_display_point(head, text_layout_details)
9191 .into(),
9192 );
9193 selection.collapse_to(head, goal);
9194
9195 let transpose_start = display_map
9196 .buffer_snapshot
9197 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9198 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
9199 let transpose_end = display_map
9200 .buffer_snapshot
9201 .clip_offset(transpose_offset + 1, Bias::Right);
9202 if let Some(ch) =
9203 display_map.buffer_snapshot.chars_at(transpose_start).next()
9204 {
9205 edits.push((transpose_start..transpose_offset, String::new()));
9206 edits.push((transpose_end..transpose_end, ch.to_string()));
9207 }
9208 }
9209 });
9210 edits
9211 });
9212 this.buffer
9213 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9214 let selections = this.selections.all::<usize>(cx);
9215 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9216 s.select(selections);
9217 });
9218 });
9219 }
9220
9221 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
9222 self.rewrap_impl(RewrapOptions::default(), cx)
9223 }
9224
9225 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
9226 let buffer = self.buffer.read(cx).snapshot(cx);
9227 let selections = self.selections.all::<Point>(cx);
9228 let mut selections = selections.iter().peekable();
9229
9230 let mut edits = Vec::new();
9231 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
9232
9233 while let Some(selection) = selections.next() {
9234 let mut start_row = selection.start.row;
9235 let mut end_row = selection.end.row;
9236
9237 // Skip selections that overlap with a range that has already been rewrapped.
9238 let selection_range = start_row..end_row;
9239 if rewrapped_row_ranges
9240 .iter()
9241 .any(|range| range.overlaps(&selection_range))
9242 {
9243 continue;
9244 }
9245
9246 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
9247
9248 // Since not all lines in the selection may be at the same indent
9249 // level, choose the indent size that is the most common between all
9250 // of the lines.
9251 //
9252 // If there is a tie, we use the deepest indent.
9253 let (indent_size, indent_end) = {
9254 let mut indent_size_occurrences = HashMap::default();
9255 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
9256
9257 for row in start_row..=end_row {
9258 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
9259 rows_by_indent_size.entry(indent).or_default().push(row);
9260 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
9261 }
9262
9263 let indent_size = indent_size_occurrences
9264 .into_iter()
9265 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
9266 .map(|(indent, _)| indent)
9267 .unwrap_or_default();
9268 let row = rows_by_indent_size[&indent_size][0];
9269 let indent_end = Point::new(row, indent_size.len);
9270
9271 (indent_size, indent_end)
9272 };
9273
9274 let mut line_prefix = indent_size.chars().collect::<String>();
9275
9276 let mut inside_comment = false;
9277 if let Some(comment_prefix) =
9278 buffer
9279 .language_scope_at(selection.head())
9280 .and_then(|language| {
9281 language
9282 .line_comment_prefixes()
9283 .iter()
9284 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
9285 .cloned()
9286 })
9287 {
9288 line_prefix.push_str(&comment_prefix);
9289 inside_comment = true;
9290 }
9291
9292 let language_settings = buffer.language_settings_at(selection.head(), cx);
9293 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
9294 RewrapBehavior::InComments => inside_comment,
9295 RewrapBehavior::InSelections => !selection.is_empty(),
9296 RewrapBehavior::Anywhere => true,
9297 };
9298
9299 let should_rewrap = options.override_language_settings
9300 || allow_rewrap_based_on_language
9301 || self.hard_wrap.is_some();
9302 if !should_rewrap {
9303 continue;
9304 }
9305
9306 if selection.is_empty() {
9307 'expand_upwards: while start_row > 0 {
9308 let prev_row = start_row - 1;
9309 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
9310 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
9311 {
9312 start_row = prev_row;
9313 } else {
9314 break 'expand_upwards;
9315 }
9316 }
9317
9318 'expand_downwards: while end_row < buffer.max_point().row {
9319 let next_row = end_row + 1;
9320 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
9321 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
9322 {
9323 end_row = next_row;
9324 } else {
9325 break 'expand_downwards;
9326 }
9327 }
9328 }
9329
9330 let start = Point::new(start_row, 0);
9331 let start_offset = start.to_offset(&buffer);
9332 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
9333 let selection_text = buffer.text_for_range(start..end).collect::<String>();
9334 let Some(lines_without_prefixes) = selection_text
9335 .lines()
9336 .map(|line| {
9337 line.strip_prefix(&line_prefix)
9338 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
9339 .ok_or_else(|| {
9340 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
9341 })
9342 })
9343 .collect::<Result<Vec<_>, _>>()
9344 .log_err()
9345 else {
9346 continue;
9347 };
9348
9349 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
9350 buffer
9351 .language_settings_at(Point::new(start_row, 0), cx)
9352 .preferred_line_length as usize
9353 });
9354 let wrapped_text = wrap_with_prefix(
9355 line_prefix,
9356 lines_without_prefixes.join("\n"),
9357 wrap_column,
9358 tab_size,
9359 options.preserve_existing_whitespace,
9360 );
9361
9362 // TODO: should always use char-based diff while still supporting cursor behavior that
9363 // matches vim.
9364 let mut diff_options = DiffOptions::default();
9365 if options.override_language_settings {
9366 diff_options.max_word_diff_len = 0;
9367 diff_options.max_word_diff_line_count = 0;
9368 } else {
9369 diff_options.max_word_diff_len = usize::MAX;
9370 diff_options.max_word_diff_line_count = usize::MAX;
9371 }
9372
9373 for (old_range, new_text) in
9374 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
9375 {
9376 let edit_start = buffer.anchor_after(start_offset + old_range.start);
9377 let edit_end = buffer.anchor_after(start_offset + old_range.end);
9378 edits.push((edit_start..edit_end, new_text));
9379 }
9380
9381 rewrapped_row_ranges.push(start_row..=end_row);
9382 }
9383
9384 self.buffer
9385 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9386 }
9387
9388 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
9389 let mut text = String::new();
9390 let buffer = self.buffer.read(cx).snapshot(cx);
9391 let mut selections = self.selections.all::<Point>(cx);
9392 let mut clipboard_selections = Vec::with_capacity(selections.len());
9393 {
9394 let max_point = buffer.max_point();
9395 let mut is_first = true;
9396 for selection in &mut selections {
9397 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9398 if is_entire_line {
9399 selection.start = Point::new(selection.start.row, 0);
9400 if !selection.is_empty() && selection.end.column == 0 {
9401 selection.end = cmp::min(max_point, selection.end);
9402 } else {
9403 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
9404 }
9405 selection.goal = SelectionGoal::None;
9406 }
9407 if is_first {
9408 is_first = false;
9409 } else {
9410 text += "\n";
9411 }
9412 let mut len = 0;
9413 for chunk in buffer.text_for_range(selection.start..selection.end) {
9414 text.push_str(chunk);
9415 len += chunk.len();
9416 }
9417 clipboard_selections.push(ClipboardSelection {
9418 len,
9419 is_entire_line,
9420 first_line_indent: buffer
9421 .indent_size_for_line(MultiBufferRow(selection.start.row))
9422 .len,
9423 });
9424 }
9425 }
9426
9427 self.transact(window, cx, |this, window, cx| {
9428 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9429 s.select(selections);
9430 });
9431 this.insert("", window, cx);
9432 });
9433 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
9434 }
9435
9436 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
9437 let item = self.cut_common(window, cx);
9438 cx.write_to_clipboard(item);
9439 }
9440
9441 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
9442 self.change_selections(None, window, cx, |s| {
9443 s.move_with(|snapshot, sel| {
9444 if sel.is_empty() {
9445 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
9446 }
9447 });
9448 });
9449 let item = self.cut_common(window, cx);
9450 cx.set_global(KillRing(item))
9451 }
9452
9453 pub fn kill_ring_yank(
9454 &mut self,
9455 _: &KillRingYank,
9456 window: &mut Window,
9457 cx: &mut Context<Self>,
9458 ) {
9459 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
9460 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
9461 (kill_ring.text().to_string(), kill_ring.metadata_json())
9462 } else {
9463 return;
9464 }
9465 } else {
9466 return;
9467 };
9468 self.do_paste(&text, metadata, false, window, cx);
9469 }
9470
9471 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
9472 self.do_copy(true, cx);
9473 }
9474
9475 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
9476 self.do_copy(false, cx);
9477 }
9478
9479 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
9480 let selections = self.selections.all::<Point>(cx);
9481 let buffer = self.buffer.read(cx).read(cx);
9482 let mut text = String::new();
9483
9484 let mut clipboard_selections = Vec::with_capacity(selections.len());
9485 {
9486 let max_point = buffer.max_point();
9487 let mut is_first = true;
9488 for selection in &selections {
9489 let mut start = selection.start;
9490 let mut end = selection.end;
9491 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9492 if is_entire_line {
9493 start = Point::new(start.row, 0);
9494 end = cmp::min(max_point, Point::new(end.row + 1, 0));
9495 }
9496
9497 let mut trimmed_selections = Vec::new();
9498 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
9499 let row = MultiBufferRow(start.row);
9500 let first_indent = buffer.indent_size_for_line(row);
9501 if first_indent.len == 0 || start.column > first_indent.len {
9502 trimmed_selections.push(start..end);
9503 } else {
9504 trimmed_selections.push(
9505 Point::new(row.0, first_indent.len)
9506 ..Point::new(row.0, buffer.line_len(row)),
9507 );
9508 for row in start.row + 1..=end.row {
9509 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
9510 if row_indent_size.len >= first_indent.len {
9511 trimmed_selections.push(
9512 Point::new(row, first_indent.len)
9513 ..Point::new(row, buffer.line_len(MultiBufferRow(row))),
9514 );
9515 } else {
9516 trimmed_selections.clear();
9517 trimmed_selections.push(start..end);
9518 break;
9519 }
9520 }
9521 }
9522 } else {
9523 trimmed_selections.push(start..end);
9524 }
9525
9526 for trimmed_range in trimmed_selections {
9527 if is_first {
9528 is_first = false;
9529 } else {
9530 text += "\n";
9531 }
9532 let mut len = 0;
9533 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
9534 text.push_str(chunk);
9535 len += chunk.len();
9536 }
9537 clipboard_selections.push(ClipboardSelection {
9538 len,
9539 is_entire_line,
9540 first_line_indent: buffer
9541 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
9542 .len,
9543 });
9544 }
9545 }
9546 }
9547
9548 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
9549 text,
9550 clipboard_selections,
9551 ));
9552 }
9553
9554 pub fn do_paste(
9555 &mut self,
9556 text: &String,
9557 clipboard_selections: Option<Vec<ClipboardSelection>>,
9558 handle_entire_lines: bool,
9559 window: &mut Window,
9560 cx: &mut Context<Self>,
9561 ) {
9562 if self.read_only(cx) {
9563 return;
9564 }
9565
9566 let clipboard_text = Cow::Borrowed(text);
9567
9568 self.transact(window, cx, |this, window, cx| {
9569 if let Some(mut clipboard_selections) = clipboard_selections {
9570 let old_selections = this.selections.all::<usize>(cx);
9571 let all_selections_were_entire_line =
9572 clipboard_selections.iter().all(|s| s.is_entire_line);
9573 let first_selection_indent_column =
9574 clipboard_selections.first().map(|s| s.first_line_indent);
9575 if clipboard_selections.len() != old_selections.len() {
9576 clipboard_selections.drain(..);
9577 }
9578 let cursor_offset = this.selections.last::<usize>(cx).head();
9579 let mut auto_indent_on_paste = true;
9580
9581 this.buffer.update(cx, |buffer, cx| {
9582 let snapshot = buffer.read(cx);
9583 auto_indent_on_paste = snapshot
9584 .language_settings_at(cursor_offset, cx)
9585 .auto_indent_on_paste;
9586
9587 let mut start_offset = 0;
9588 let mut edits = Vec::new();
9589 let mut original_indent_columns = Vec::new();
9590 for (ix, selection) in old_selections.iter().enumerate() {
9591 let to_insert;
9592 let entire_line;
9593 let original_indent_column;
9594 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
9595 let end_offset = start_offset + clipboard_selection.len;
9596 to_insert = &clipboard_text[start_offset..end_offset];
9597 entire_line = clipboard_selection.is_entire_line;
9598 start_offset = end_offset + 1;
9599 original_indent_column = Some(clipboard_selection.first_line_indent);
9600 } else {
9601 to_insert = clipboard_text.as_str();
9602 entire_line = all_selections_were_entire_line;
9603 original_indent_column = first_selection_indent_column
9604 }
9605
9606 // If the corresponding selection was empty when this slice of the
9607 // clipboard text was written, then the entire line containing the
9608 // selection was copied. If this selection is also currently empty,
9609 // then paste the line before the current line of the buffer.
9610 let range = if selection.is_empty() && handle_entire_lines && entire_line {
9611 let column = selection.start.to_point(&snapshot).column as usize;
9612 let line_start = selection.start - column;
9613 line_start..line_start
9614 } else {
9615 selection.range()
9616 };
9617
9618 edits.push((range, to_insert));
9619 original_indent_columns.push(original_indent_column);
9620 }
9621 drop(snapshot);
9622
9623 buffer.edit(
9624 edits,
9625 if auto_indent_on_paste {
9626 Some(AutoindentMode::Block {
9627 original_indent_columns,
9628 })
9629 } else {
9630 None
9631 },
9632 cx,
9633 );
9634 });
9635
9636 let selections = this.selections.all::<usize>(cx);
9637 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9638 s.select(selections)
9639 });
9640 } else {
9641 this.insert(&clipboard_text, window, cx);
9642 }
9643 });
9644 }
9645
9646 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
9647 if let Some(item) = cx.read_from_clipboard() {
9648 let entries = item.entries();
9649
9650 match entries.first() {
9651 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
9652 // of all the pasted entries.
9653 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
9654 .do_paste(
9655 clipboard_string.text(),
9656 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
9657 true,
9658 window,
9659 cx,
9660 ),
9661 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
9662 }
9663 }
9664 }
9665
9666 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
9667 if self.read_only(cx) {
9668 return;
9669 }
9670
9671 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
9672 if let Some((selections, _)) =
9673 self.selection_history.transaction(transaction_id).cloned()
9674 {
9675 self.change_selections(None, window, cx, |s| {
9676 s.select_anchors(selections.to_vec());
9677 });
9678 } else {
9679 log::error!(
9680 "No entry in selection_history found for undo. \
9681 This may correspond to a bug where undo does not update the selection. \
9682 If this is occurring, please add details to \
9683 https://github.com/zed-industries/zed/issues/22692"
9684 );
9685 }
9686 self.request_autoscroll(Autoscroll::fit(), cx);
9687 self.unmark_text(window, cx);
9688 self.refresh_inline_completion(true, false, window, cx);
9689 cx.emit(EditorEvent::Edited { transaction_id });
9690 cx.emit(EditorEvent::TransactionUndone { transaction_id });
9691 }
9692 }
9693
9694 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
9695 if self.read_only(cx) {
9696 return;
9697 }
9698
9699 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
9700 if let Some((_, Some(selections))) =
9701 self.selection_history.transaction(transaction_id).cloned()
9702 {
9703 self.change_selections(None, window, cx, |s| {
9704 s.select_anchors(selections.to_vec());
9705 });
9706 } else {
9707 log::error!(
9708 "No entry in selection_history found for redo. \
9709 This may correspond to a bug where undo does not update the selection. \
9710 If this is occurring, please add details to \
9711 https://github.com/zed-industries/zed/issues/22692"
9712 );
9713 }
9714 self.request_autoscroll(Autoscroll::fit(), cx);
9715 self.unmark_text(window, cx);
9716 self.refresh_inline_completion(true, false, window, cx);
9717 cx.emit(EditorEvent::Edited { transaction_id });
9718 }
9719 }
9720
9721 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
9722 self.buffer
9723 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
9724 }
9725
9726 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
9727 self.buffer
9728 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
9729 }
9730
9731 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
9732 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9733 let line_mode = s.line_mode;
9734 s.move_with(|map, selection| {
9735 let cursor = if selection.is_empty() && !line_mode {
9736 movement::left(map, selection.start)
9737 } else {
9738 selection.start
9739 };
9740 selection.collapse_to(cursor, SelectionGoal::None);
9741 });
9742 })
9743 }
9744
9745 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
9746 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9747 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
9748 })
9749 }
9750
9751 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
9752 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9753 let line_mode = s.line_mode;
9754 s.move_with(|map, selection| {
9755 let cursor = if selection.is_empty() && !line_mode {
9756 movement::right(map, selection.end)
9757 } else {
9758 selection.end
9759 };
9760 selection.collapse_to(cursor, SelectionGoal::None)
9761 });
9762 })
9763 }
9764
9765 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
9766 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9767 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
9768 })
9769 }
9770
9771 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
9772 if self.take_rename(true, window, cx).is_some() {
9773 return;
9774 }
9775
9776 if matches!(self.mode, EditorMode::SingleLine { .. }) {
9777 cx.propagate();
9778 return;
9779 }
9780
9781 let text_layout_details = &self.text_layout_details(window);
9782 let selection_count = self.selections.count();
9783 let first_selection = self.selections.first_anchor();
9784
9785 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9786 let line_mode = s.line_mode;
9787 s.move_with(|map, selection| {
9788 if !selection.is_empty() && !line_mode {
9789 selection.goal = SelectionGoal::None;
9790 }
9791 let (cursor, goal) = movement::up(
9792 map,
9793 selection.start,
9794 selection.goal,
9795 false,
9796 text_layout_details,
9797 );
9798 selection.collapse_to(cursor, goal);
9799 });
9800 });
9801
9802 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
9803 {
9804 cx.propagate();
9805 }
9806 }
9807
9808 pub fn move_up_by_lines(
9809 &mut self,
9810 action: &MoveUpByLines,
9811 window: &mut Window,
9812 cx: &mut Context<Self>,
9813 ) {
9814 if self.take_rename(true, window, cx).is_some() {
9815 return;
9816 }
9817
9818 if matches!(self.mode, EditorMode::SingleLine { .. }) {
9819 cx.propagate();
9820 return;
9821 }
9822
9823 let text_layout_details = &self.text_layout_details(window);
9824
9825 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9826 let line_mode = s.line_mode;
9827 s.move_with(|map, selection| {
9828 if !selection.is_empty() && !line_mode {
9829 selection.goal = SelectionGoal::None;
9830 }
9831 let (cursor, goal) = movement::up_by_rows(
9832 map,
9833 selection.start,
9834 action.lines,
9835 selection.goal,
9836 false,
9837 text_layout_details,
9838 );
9839 selection.collapse_to(cursor, goal);
9840 });
9841 })
9842 }
9843
9844 pub fn move_down_by_lines(
9845 &mut self,
9846 action: &MoveDownByLines,
9847 window: &mut Window,
9848 cx: &mut Context<Self>,
9849 ) {
9850 if self.take_rename(true, window, cx).is_some() {
9851 return;
9852 }
9853
9854 if matches!(self.mode, EditorMode::SingleLine { .. }) {
9855 cx.propagate();
9856 return;
9857 }
9858
9859 let text_layout_details = &self.text_layout_details(window);
9860
9861 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9862 let line_mode = s.line_mode;
9863 s.move_with(|map, selection| {
9864 if !selection.is_empty() && !line_mode {
9865 selection.goal = SelectionGoal::None;
9866 }
9867 let (cursor, goal) = movement::down_by_rows(
9868 map,
9869 selection.start,
9870 action.lines,
9871 selection.goal,
9872 false,
9873 text_layout_details,
9874 );
9875 selection.collapse_to(cursor, goal);
9876 });
9877 })
9878 }
9879
9880 pub fn select_down_by_lines(
9881 &mut self,
9882 action: &SelectDownByLines,
9883 window: &mut Window,
9884 cx: &mut Context<Self>,
9885 ) {
9886 let text_layout_details = &self.text_layout_details(window);
9887 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9888 s.move_heads_with(|map, head, goal| {
9889 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
9890 })
9891 })
9892 }
9893
9894 pub fn select_up_by_lines(
9895 &mut self,
9896 action: &SelectUpByLines,
9897 window: &mut Window,
9898 cx: &mut Context<Self>,
9899 ) {
9900 let text_layout_details = &self.text_layout_details(window);
9901 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9902 s.move_heads_with(|map, head, goal| {
9903 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
9904 })
9905 })
9906 }
9907
9908 pub fn select_page_up(
9909 &mut self,
9910 _: &SelectPageUp,
9911 window: &mut Window,
9912 cx: &mut Context<Self>,
9913 ) {
9914 let Some(row_count) = self.visible_row_count() else {
9915 return;
9916 };
9917
9918 let text_layout_details = &self.text_layout_details(window);
9919
9920 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9921 s.move_heads_with(|map, head, goal| {
9922 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
9923 })
9924 })
9925 }
9926
9927 pub fn move_page_up(
9928 &mut self,
9929 action: &MovePageUp,
9930 window: &mut Window,
9931 cx: &mut Context<Self>,
9932 ) {
9933 if self.take_rename(true, window, cx).is_some() {
9934 return;
9935 }
9936
9937 if self
9938 .context_menu
9939 .borrow_mut()
9940 .as_mut()
9941 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
9942 .unwrap_or(false)
9943 {
9944 return;
9945 }
9946
9947 if matches!(self.mode, EditorMode::SingleLine { .. }) {
9948 cx.propagate();
9949 return;
9950 }
9951
9952 let Some(row_count) = self.visible_row_count() else {
9953 return;
9954 };
9955
9956 let autoscroll = if action.center_cursor {
9957 Autoscroll::center()
9958 } else {
9959 Autoscroll::fit()
9960 };
9961
9962 let text_layout_details = &self.text_layout_details(window);
9963
9964 self.change_selections(Some(autoscroll), window, cx, |s| {
9965 let line_mode = s.line_mode;
9966 s.move_with(|map, selection| {
9967 if !selection.is_empty() && !line_mode {
9968 selection.goal = SelectionGoal::None;
9969 }
9970 let (cursor, goal) = movement::up_by_rows(
9971 map,
9972 selection.end,
9973 row_count,
9974 selection.goal,
9975 false,
9976 text_layout_details,
9977 );
9978 selection.collapse_to(cursor, goal);
9979 });
9980 });
9981 }
9982
9983 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
9984 let text_layout_details = &self.text_layout_details(window);
9985 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9986 s.move_heads_with(|map, head, goal| {
9987 movement::up(map, head, goal, false, text_layout_details)
9988 })
9989 })
9990 }
9991
9992 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
9993 self.take_rename(true, window, cx);
9994
9995 if matches!(self.mode, EditorMode::SingleLine { .. }) {
9996 cx.propagate();
9997 return;
9998 }
9999
10000 let text_layout_details = &self.text_layout_details(window);
10001 let selection_count = self.selections.count();
10002 let first_selection = self.selections.first_anchor();
10003
10004 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10005 let line_mode = s.line_mode;
10006 s.move_with(|map, selection| {
10007 if !selection.is_empty() && !line_mode {
10008 selection.goal = SelectionGoal::None;
10009 }
10010 let (cursor, goal) = movement::down(
10011 map,
10012 selection.end,
10013 selection.goal,
10014 false,
10015 text_layout_details,
10016 );
10017 selection.collapse_to(cursor, goal);
10018 });
10019 });
10020
10021 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10022 {
10023 cx.propagate();
10024 }
10025 }
10026
10027 pub fn select_page_down(
10028 &mut self,
10029 _: &SelectPageDown,
10030 window: &mut Window,
10031 cx: &mut Context<Self>,
10032 ) {
10033 let Some(row_count) = self.visible_row_count() else {
10034 return;
10035 };
10036
10037 let text_layout_details = &self.text_layout_details(window);
10038
10039 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10040 s.move_heads_with(|map, head, goal| {
10041 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
10042 })
10043 })
10044 }
10045
10046 pub fn move_page_down(
10047 &mut self,
10048 action: &MovePageDown,
10049 window: &mut Window,
10050 cx: &mut Context<Self>,
10051 ) {
10052 if self.take_rename(true, window, cx).is_some() {
10053 return;
10054 }
10055
10056 if self
10057 .context_menu
10058 .borrow_mut()
10059 .as_mut()
10060 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
10061 .unwrap_or(false)
10062 {
10063 return;
10064 }
10065
10066 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10067 cx.propagate();
10068 return;
10069 }
10070
10071 let Some(row_count) = self.visible_row_count() else {
10072 return;
10073 };
10074
10075 let autoscroll = if action.center_cursor {
10076 Autoscroll::center()
10077 } else {
10078 Autoscroll::fit()
10079 };
10080
10081 let text_layout_details = &self.text_layout_details(window);
10082 self.change_selections(Some(autoscroll), window, cx, |s| {
10083 let line_mode = s.line_mode;
10084 s.move_with(|map, selection| {
10085 if !selection.is_empty() && !line_mode {
10086 selection.goal = SelectionGoal::None;
10087 }
10088 let (cursor, goal) = movement::down_by_rows(
10089 map,
10090 selection.end,
10091 row_count,
10092 selection.goal,
10093 false,
10094 text_layout_details,
10095 );
10096 selection.collapse_to(cursor, goal);
10097 });
10098 });
10099 }
10100
10101 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
10102 let text_layout_details = &self.text_layout_details(window);
10103 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10104 s.move_heads_with(|map, head, goal| {
10105 movement::down(map, head, goal, false, text_layout_details)
10106 })
10107 });
10108 }
10109
10110 pub fn context_menu_first(
10111 &mut self,
10112 _: &ContextMenuFirst,
10113 _window: &mut Window,
10114 cx: &mut Context<Self>,
10115 ) {
10116 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10117 context_menu.select_first(self.completion_provider.as_deref(), cx);
10118 }
10119 }
10120
10121 pub fn context_menu_prev(
10122 &mut self,
10123 _: &ContextMenuPrevious,
10124 _window: &mut Window,
10125 cx: &mut Context<Self>,
10126 ) {
10127 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10128 context_menu.select_prev(self.completion_provider.as_deref(), cx);
10129 }
10130 }
10131
10132 pub fn context_menu_next(
10133 &mut self,
10134 _: &ContextMenuNext,
10135 _window: &mut Window,
10136 cx: &mut Context<Self>,
10137 ) {
10138 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10139 context_menu.select_next(self.completion_provider.as_deref(), cx);
10140 }
10141 }
10142
10143 pub fn context_menu_last(
10144 &mut self,
10145 _: &ContextMenuLast,
10146 _window: &mut Window,
10147 cx: &mut Context<Self>,
10148 ) {
10149 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10150 context_menu.select_last(self.completion_provider.as_deref(), cx);
10151 }
10152 }
10153
10154 pub fn move_to_previous_word_start(
10155 &mut self,
10156 _: &MoveToPreviousWordStart,
10157 window: &mut Window,
10158 cx: &mut Context<Self>,
10159 ) {
10160 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10161 s.move_cursors_with(|map, head, _| {
10162 (
10163 movement::previous_word_start(map, head),
10164 SelectionGoal::None,
10165 )
10166 });
10167 })
10168 }
10169
10170 pub fn move_to_previous_subword_start(
10171 &mut self,
10172 _: &MoveToPreviousSubwordStart,
10173 window: &mut Window,
10174 cx: &mut Context<Self>,
10175 ) {
10176 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10177 s.move_cursors_with(|map, head, _| {
10178 (
10179 movement::previous_subword_start(map, head),
10180 SelectionGoal::None,
10181 )
10182 });
10183 })
10184 }
10185
10186 pub fn select_to_previous_word_start(
10187 &mut self,
10188 _: &SelectToPreviousWordStart,
10189 window: &mut Window,
10190 cx: &mut Context<Self>,
10191 ) {
10192 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10193 s.move_heads_with(|map, head, _| {
10194 (
10195 movement::previous_word_start(map, head),
10196 SelectionGoal::None,
10197 )
10198 });
10199 })
10200 }
10201
10202 pub fn select_to_previous_subword_start(
10203 &mut self,
10204 _: &SelectToPreviousSubwordStart,
10205 window: &mut Window,
10206 cx: &mut Context<Self>,
10207 ) {
10208 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10209 s.move_heads_with(|map, head, _| {
10210 (
10211 movement::previous_subword_start(map, head),
10212 SelectionGoal::None,
10213 )
10214 });
10215 })
10216 }
10217
10218 pub fn delete_to_previous_word_start(
10219 &mut self,
10220 action: &DeleteToPreviousWordStart,
10221 window: &mut Window,
10222 cx: &mut Context<Self>,
10223 ) {
10224 self.transact(window, cx, |this, window, cx| {
10225 this.select_autoclose_pair(window, cx);
10226 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10227 let line_mode = s.line_mode;
10228 s.move_with(|map, selection| {
10229 if selection.is_empty() && !line_mode {
10230 let cursor = if action.ignore_newlines {
10231 movement::previous_word_start(map, selection.head())
10232 } else {
10233 movement::previous_word_start_or_newline(map, selection.head())
10234 };
10235 selection.set_head(cursor, SelectionGoal::None);
10236 }
10237 });
10238 });
10239 this.insert("", window, cx);
10240 });
10241 }
10242
10243 pub fn delete_to_previous_subword_start(
10244 &mut self,
10245 _: &DeleteToPreviousSubwordStart,
10246 window: &mut Window,
10247 cx: &mut Context<Self>,
10248 ) {
10249 self.transact(window, cx, |this, window, cx| {
10250 this.select_autoclose_pair(window, cx);
10251 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10252 let line_mode = s.line_mode;
10253 s.move_with(|map, selection| {
10254 if selection.is_empty() && !line_mode {
10255 let cursor = movement::previous_subword_start(map, selection.head());
10256 selection.set_head(cursor, SelectionGoal::None);
10257 }
10258 });
10259 });
10260 this.insert("", window, cx);
10261 });
10262 }
10263
10264 pub fn move_to_next_word_end(
10265 &mut self,
10266 _: &MoveToNextWordEnd,
10267 window: &mut Window,
10268 cx: &mut Context<Self>,
10269 ) {
10270 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10271 s.move_cursors_with(|map, head, _| {
10272 (movement::next_word_end(map, head), SelectionGoal::None)
10273 });
10274 })
10275 }
10276
10277 pub fn move_to_next_subword_end(
10278 &mut self,
10279 _: &MoveToNextSubwordEnd,
10280 window: &mut Window,
10281 cx: &mut Context<Self>,
10282 ) {
10283 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10284 s.move_cursors_with(|map, head, _| {
10285 (movement::next_subword_end(map, head), SelectionGoal::None)
10286 });
10287 })
10288 }
10289
10290 pub fn select_to_next_word_end(
10291 &mut self,
10292 _: &SelectToNextWordEnd,
10293 window: &mut Window,
10294 cx: &mut Context<Self>,
10295 ) {
10296 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10297 s.move_heads_with(|map, head, _| {
10298 (movement::next_word_end(map, head), SelectionGoal::None)
10299 });
10300 })
10301 }
10302
10303 pub fn select_to_next_subword_end(
10304 &mut self,
10305 _: &SelectToNextSubwordEnd,
10306 window: &mut Window,
10307 cx: &mut Context<Self>,
10308 ) {
10309 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10310 s.move_heads_with(|map, head, _| {
10311 (movement::next_subword_end(map, head), SelectionGoal::None)
10312 });
10313 })
10314 }
10315
10316 pub fn delete_to_next_word_end(
10317 &mut self,
10318 action: &DeleteToNextWordEnd,
10319 window: &mut Window,
10320 cx: &mut Context<Self>,
10321 ) {
10322 self.transact(window, cx, |this, window, cx| {
10323 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10324 let line_mode = s.line_mode;
10325 s.move_with(|map, selection| {
10326 if selection.is_empty() && !line_mode {
10327 let cursor = if action.ignore_newlines {
10328 movement::next_word_end(map, selection.head())
10329 } else {
10330 movement::next_word_end_or_newline(map, selection.head())
10331 };
10332 selection.set_head(cursor, SelectionGoal::None);
10333 }
10334 });
10335 });
10336 this.insert("", window, cx);
10337 });
10338 }
10339
10340 pub fn delete_to_next_subword_end(
10341 &mut self,
10342 _: &DeleteToNextSubwordEnd,
10343 window: &mut Window,
10344 cx: &mut Context<Self>,
10345 ) {
10346 self.transact(window, cx, |this, window, cx| {
10347 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10348 s.move_with(|map, selection| {
10349 if selection.is_empty() {
10350 let cursor = movement::next_subword_end(map, selection.head());
10351 selection.set_head(cursor, SelectionGoal::None);
10352 }
10353 });
10354 });
10355 this.insert("", window, cx);
10356 });
10357 }
10358
10359 pub fn move_to_beginning_of_line(
10360 &mut self,
10361 action: &MoveToBeginningOfLine,
10362 window: &mut Window,
10363 cx: &mut Context<Self>,
10364 ) {
10365 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10366 s.move_cursors_with(|map, head, _| {
10367 (
10368 movement::indented_line_beginning(
10369 map,
10370 head,
10371 action.stop_at_soft_wraps,
10372 action.stop_at_indent,
10373 ),
10374 SelectionGoal::None,
10375 )
10376 });
10377 })
10378 }
10379
10380 pub fn select_to_beginning_of_line(
10381 &mut self,
10382 action: &SelectToBeginningOfLine,
10383 window: &mut Window,
10384 cx: &mut Context<Self>,
10385 ) {
10386 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10387 s.move_heads_with(|map, head, _| {
10388 (
10389 movement::indented_line_beginning(
10390 map,
10391 head,
10392 action.stop_at_soft_wraps,
10393 action.stop_at_indent,
10394 ),
10395 SelectionGoal::None,
10396 )
10397 });
10398 });
10399 }
10400
10401 pub fn delete_to_beginning_of_line(
10402 &mut self,
10403 action: &DeleteToBeginningOfLine,
10404 window: &mut Window,
10405 cx: &mut Context<Self>,
10406 ) {
10407 self.transact(window, cx, |this, window, cx| {
10408 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10409 s.move_with(|_, selection| {
10410 selection.reversed = true;
10411 });
10412 });
10413
10414 this.select_to_beginning_of_line(
10415 &SelectToBeginningOfLine {
10416 stop_at_soft_wraps: false,
10417 stop_at_indent: action.stop_at_indent,
10418 },
10419 window,
10420 cx,
10421 );
10422 this.backspace(&Backspace, window, cx);
10423 });
10424 }
10425
10426 pub fn move_to_end_of_line(
10427 &mut self,
10428 action: &MoveToEndOfLine,
10429 window: &mut Window,
10430 cx: &mut Context<Self>,
10431 ) {
10432 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10433 s.move_cursors_with(|map, head, _| {
10434 (
10435 movement::line_end(map, head, action.stop_at_soft_wraps),
10436 SelectionGoal::None,
10437 )
10438 });
10439 })
10440 }
10441
10442 pub fn select_to_end_of_line(
10443 &mut self,
10444 action: &SelectToEndOfLine,
10445 window: &mut Window,
10446 cx: &mut Context<Self>,
10447 ) {
10448 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10449 s.move_heads_with(|map, head, _| {
10450 (
10451 movement::line_end(map, head, action.stop_at_soft_wraps),
10452 SelectionGoal::None,
10453 )
10454 });
10455 })
10456 }
10457
10458 pub fn delete_to_end_of_line(
10459 &mut self,
10460 _: &DeleteToEndOfLine,
10461 window: &mut Window,
10462 cx: &mut Context<Self>,
10463 ) {
10464 self.transact(window, cx, |this, window, cx| {
10465 this.select_to_end_of_line(
10466 &SelectToEndOfLine {
10467 stop_at_soft_wraps: false,
10468 },
10469 window,
10470 cx,
10471 );
10472 this.delete(&Delete, window, cx);
10473 });
10474 }
10475
10476 pub fn cut_to_end_of_line(
10477 &mut self,
10478 _: &CutToEndOfLine,
10479 window: &mut Window,
10480 cx: &mut Context<Self>,
10481 ) {
10482 self.transact(window, cx, |this, window, cx| {
10483 this.select_to_end_of_line(
10484 &SelectToEndOfLine {
10485 stop_at_soft_wraps: false,
10486 },
10487 window,
10488 cx,
10489 );
10490 this.cut(&Cut, window, cx);
10491 });
10492 }
10493
10494 pub fn move_to_start_of_paragraph(
10495 &mut self,
10496 _: &MoveToStartOfParagraph,
10497 window: &mut Window,
10498 cx: &mut Context<Self>,
10499 ) {
10500 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10501 cx.propagate();
10502 return;
10503 }
10504
10505 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10506 s.move_with(|map, selection| {
10507 selection.collapse_to(
10508 movement::start_of_paragraph(map, selection.head(), 1),
10509 SelectionGoal::None,
10510 )
10511 });
10512 })
10513 }
10514
10515 pub fn move_to_end_of_paragraph(
10516 &mut self,
10517 _: &MoveToEndOfParagraph,
10518 window: &mut Window,
10519 cx: &mut Context<Self>,
10520 ) {
10521 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10522 cx.propagate();
10523 return;
10524 }
10525
10526 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10527 s.move_with(|map, selection| {
10528 selection.collapse_to(
10529 movement::end_of_paragraph(map, selection.head(), 1),
10530 SelectionGoal::None,
10531 )
10532 });
10533 })
10534 }
10535
10536 pub fn select_to_start_of_paragraph(
10537 &mut self,
10538 _: &SelectToStartOfParagraph,
10539 window: &mut Window,
10540 cx: &mut Context<Self>,
10541 ) {
10542 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10543 cx.propagate();
10544 return;
10545 }
10546
10547 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10548 s.move_heads_with(|map, head, _| {
10549 (
10550 movement::start_of_paragraph(map, head, 1),
10551 SelectionGoal::None,
10552 )
10553 });
10554 })
10555 }
10556
10557 pub fn select_to_end_of_paragraph(
10558 &mut self,
10559 _: &SelectToEndOfParagraph,
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_heads_with(|map, head, _| {
10570 (
10571 movement::end_of_paragraph(map, head, 1),
10572 SelectionGoal::None,
10573 )
10574 });
10575 })
10576 }
10577
10578 pub fn move_to_start_of_excerpt(
10579 &mut self,
10580 _: &MoveToStartOfExcerpt,
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::start_of_excerpt(
10593 map,
10594 selection.head(),
10595 workspace::searchable::Direction::Prev,
10596 ),
10597 SelectionGoal::None,
10598 )
10599 });
10600 })
10601 }
10602
10603 pub fn move_to_start_of_next_excerpt(
10604 &mut self,
10605 _: &MoveToStartOfNextExcerpt,
10606 window: &mut Window,
10607 cx: &mut Context<Self>,
10608 ) {
10609 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10610 cx.propagate();
10611 return;
10612 }
10613
10614 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10615 s.move_with(|map, selection| {
10616 selection.collapse_to(
10617 movement::start_of_excerpt(
10618 map,
10619 selection.head(),
10620 workspace::searchable::Direction::Next,
10621 ),
10622 SelectionGoal::None,
10623 )
10624 });
10625 })
10626 }
10627
10628 pub fn move_to_end_of_excerpt(
10629 &mut self,
10630 _: &MoveToEndOfExcerpt,
10631 window: &mut Window,
10632 cx: &mut Context<Self>,
10633 ) {
10634 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10635 cx.propagate();
10636 return;
10637 }
10638
10639 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10640 s.move_with(|map, selection| {
10641 selection.collapse_to(
10642 movement::end_of_excerpt(
10643 map,
10644 selection.head(),
10645 workspace::searchable::Direction::Next,
10646 ),
10647 SelectionGoal::None,
10648 )
10649 });
10650 })
10651 }
10652
10653 pub fn move_to_end_of_previous_excerpt(
10654 &mut self,
10655 _: &MoveToEndOfPreviousExcerpt,
10656 window: &mut Window,
10657 cx: &mut Context<Self>,
10658 ) {
10659 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10660 cx.propagate();
10661 return;
10662 }
10663
10664 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10665 s.move_with(|map, selection| {
10666 selection.collapse_to(
10667 movement::end_of_excerpt(
10668 map,
10669 selection.head(),
10670 workspace::searchable::Direction::Prev,
10671 ),
10672 SelectionGoal::None,
10673 )
10674 });
10675 })
10676 }
10677
10678 pub fn select_to_start_of_excerpt(
10679 &mut self,
10680 _: &SelectToStartOfExcerpt,
10681 window: &mut Window,
10682 cx: &mut Context<Self>,
10683 ) {
10684 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10685 cx.propagate();
10686 return;
10687 }
10688
10689 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10690 s.move_heads_with(|map, head, _| {
10691 (
10692 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
10693 SelectionGoal::None,
10694 )
10695 });
10696 })
10697 }
10698
10699 pub fn select_to_start_of_next_excerpt(
10700 &mut self,
10701 _: &SelectToStartOfNextExcerpt,
10702 window: &mut Window,
10703 cx: &mut Context<Self>,
10704 ) {
10705 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10706 cx.propagate();
10707 return;
10708 }
10709
10710 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10711 s.move_heads_with(|map, head, _| {
10712 (
10713 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
10714 SelectionGoal::None,
10715 )
10716 });
10717 })
10718 }
10719
10720 pub fn select_to_end_of_excerpt(
10721 &mut self,
10722 _: &SelectToEndOfExcerpt,
10723 window: &mut Window,
10724 cx: &mut Context<Self>,
10725 ) {
10726 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10727 cx.propagate();
10728 return;
10729 }
10730
10731 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10732 s.move_heads_with(|map, head, _| {
10733 (
10734 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
10735 SelectionGoal::None,
10736 )
10737 });
10738 })
10739 }
10740
10741 pub fn select_to_end_of_previous_excerpt(
10742 &mut self,
10743 _: &SelectToEndOfPreviousExcerpt,
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::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
10756 SelectionGoal::None,
10757 )
10758 });
10759 })
10760 }
10761
10762 pub fn move_to_beginning(
10763 &mut self,
10764 _: &MoveToBeginning,
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.select_ranges(vec![0..0]);
10775 });
10776 }
10777
10778 pub fn select_to_beginning(
10779 &mut self,
10780 _: &SelectToBeginning,
10781 window: &mut Window,
10782 cx: &mut Context<Self>,
10783 ) {
10784 let mut selection = self.selections.last::<Point>(cx);
10785 selection.set_head(Point::zero(), SelectionGoal::None);
10786
10787 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10788 s.select(vec![selection]);
10789 });
10790 }
10791
10792 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
10793 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10794 cx.propagate();
10795 return;
10796 }
10797
10798 let cursor = self.buffer.read(cx).read(cx).len();
10799 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10800 s.select_ranges(vec![cursor..cursor])
10801 });
10802 }
10803
10804 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
10805 self.nav_history = nav_history;
10806 }
10807
10808 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
10809 self.nav_history.as_ref()
10810 }
10811
10812 fn push_to_nav_history(
10813 &mut self,
10814 cursor_anchor: Anchor,
10815 new_position: Option<Point>,
10816 cx: &mut Context<Self>,
10817 ) {
10818 if let Some(nav_history) = self.nav_history.as_mut() {
10819 let buffer = self.buffer.read(cx).read(cx);
10820 let cursor_position = cursor_anchor.to_point(&buffer);
10821 let scroll_state = self.scroll_manager.anchor();
10822 let scroll_top_row = scroll_state.top_row(&buffer);
10823 drop(buffer);
10824
10825 if let Some(new_position) = new_position {
10826 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
10827 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
10828 return;
10829 }
10830 }
10831
10832 nav_history.push(
10833 Some(NavigationData {
10834 cursor_anchor,
10835 cursor_position,
10836 scroll_anchor: scroll_state,
10837 scroll_top_row,
10838 }),
10839 cx,
10840 );
10841 }
10842 }
10843
10844 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
10845 let buffer = self.buffer.read(cx).snapshot(cx);
10846 let mut selection = self.selections.first::<usize>(cx);
10847 selection.set_head(buffer.len(), SelectionGoal::None);
10848 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10849 s.select(vec![selection]);
10850 });
10851 }
10852
10853 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
10854 let end = self.buffer.read(cx).read(cx).len();
10855 self.change_selections(None, window, cx, |s| {
10856 s.select_ranges(vec![0..end]);
10857 });
10858 }
10859
10860 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
10861 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10862 let mut selections = self.selections.all::<Point>(cx);
10863 let max_point = display_map.buffer_snapshot.max_point();
10864 for selection in &mut selections {
10865 let rows = selection.spanned_rows(true, &display_map);
10866 selection.start = Point::new(rows.start.0, 0);
10867 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
10868 selection.reversed = false;
10869 }
10870 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10871 s.select(selections);
10872 });
10873 }
10874
10875 pub fn split_selection_into_lines(
10876 &mut self,
10877 _: &SplitSelectionIntoLines,
10878 window: &mut Window,
10879 cx: &mut Context<Self>,
10880 ) {
10881 let selections = self
10882 .selections
10883 .all::<Point>(cx)
10884 .into_iter()
10885 .map(|selection| selection.start..selection.end)
10886 .collect::<Vec<_>>();
10887 self.unfold_ranges(&selections, true, true, cx);
10888
10889 let mut new_selection_ranges = Vec::new();
10890 {
10891 let buffer = self.buffer.read(cx).read(cx);
10892 for selection in selections {
10893 for row in selection.start.row..selection.end.row {
10894 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
10895 new_selection_ranges.push(cursor..cursor);
10896 }
10897
10898 let is_multiline_selection = selection.start.row != selection.end.row;
10899 // Don't insert last one if it's a multi-line selection ending at the start of a line,
10900 // so this action feels more ergonomic when paired with other selection operations
10901 let should_skip_last = is_multiline_selection && selection.end.column == 0;
10902 if !should_skip_last {
10903 new_selection_ranges.push(selection.end..selection.end);
10904 }
10905 }
10906 }
10907 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10908 s.select_ranges(new_selection_ranges);
10909 });
10910 }
10911
10912 pub fn add_selection_above(
10913 &mut self,
10914 _: &AddSelectionAbove,
10915 window: &mut Window,
10916 cx: &mut Context<Self>,
10917 ) {
10918 self.add_selection(true, window, cx);
10919 }
10920
10921 pub fn add_selection_below(
10922 &mut self,
10923 _: &AddSelectionBelow,
10924 window: &mut Window,
10925 cx: &mut Context<Self>,
10926 ) {
10927 self.add_selection(false, window, cx);
10928 }
10929
10930 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
10931 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10932 let mut selections = self.selections.all::<Point>(cx);
10933 let text_layout_details = self.text_layout_details(window);
10934 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
10935 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
10936 let range = oldest_selection.display_range(&display_map).sorted();
10937
10938 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
10939 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
10940 let positions = start_x.min(end_x)..start_x.max(end_x);
10941
10942 selections.clear();
10943 let mut stack = Vec::new();
10944 for row in range.start.row().0..=range.end.row().0 {
10945 if let Some(selection) = self.selections.build_columnar_selection(
10946 &display_map,
10947 DisplayRow(row),
10948 &positions,
10949 oldest_selection.reversed,
10950 &text_layout_details,
10951 ) {
10952 stack.push(selection.id);
10953 selections.push(selection);
10954 }
10955 }
10956
10957 if above {
10958 stack.reverse();
10959 }
10960
10961 AddSelectionsState { above, stack }
10962 });
10963
10964 let last_added_selection = *state.stack.last().unwrap();
10965 let mut new_selections = Vec::new();
10966 if above == state.above {
10967 let end_row = if above {
10968 DisplayRow(0)
10969 } else {
10970 display_map.max_point().row()
10971 };
10972
10973 'outer: for selection in selections {
10974 if selection.id == last_added_selection {
10975 let range = selection.display_range(&display_map).sorted();
10976 debug_assert_eq!(range.start.row(), range.end.row());
10977 let mut row = range.start.row();
10978 let positions =
10979 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
10980 px(start)..px(end)
10981 } else {
10982 let start_x =
10983 display_map.x_for_display_point(range.start, &text_layout_details);
10984 let end_x =
10985 display_map.x_for_display_point(range.end, &text_layout_details);
10986 start_x.min(end_x)..start_x.max(end_x)
10987 };
10988
10989 while row != end_row {
10990 if above {
10991 row.0 -= 1;
10992 } else {
10993 row.0 += 1;
10994 }
10995
10996 if let Some(new_selection) = self.selections.build_columnar_selection(
10997 &display_map,
10998 row,
10999 &positions,
11000 selection.reversed,
11001 &text_layout_details,
11002 ) {
11003 state.stack.push(new_selection.id);
11004 if above {
11005 new_selections.push(new_selection);
11006 new_selections.push(selection);
11007 } else {
11008 new_selections.push(selection);
11009 new_selections.push(new_selection);
11010 }
11011
11012 continue 'outer;
11013 }
11014 }
11015 }
11016
11017 new_selections.push(selection);
11018 }
11019 } else {
11020 new_selections = selections;
11021 new_selections.retain(|s| s.id != last_added_selection);
11022 state.stack.pop();
11023 }
11024
11025 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11026 s.select(new_selections);
11027 });
11028 if state.stack.len() > 1 {
11029 self.add_selections_state = Some(state);
11030 }
11031 }
11032
11033 pub fn select_next_match_internal(
11034 &mut self,
11035 display_map: &DisplaySnapshot,
11036 replace_newest: bool,
11037 autoscroll: Option<Autoscroll>,
11038 window: &mut Window,
11039 cx: &mut Context<Self>,
11040 ) -> Result<()> {
11041 fn select_next_match_ranges(
11042 this: &mut Editor,
11043 range: Range<usize>,
11044 replace_newest: bool,
11045 auto_scroll: Option<Autoscroll>,
11046 window: &mut Window,
11047 cx: &mut Context<Editor>,
11048 ) {
11049 this.unfold_ranges(&[range.clone()], false, true, cx);
11050 this.change_selections(auto_scroll, window, cx, |s| {
11051 if replace_newest {
11052 s.delete(s.newest_anchor().id);
11053 }
11054 s.insert_range(range.clone());
11055 });
11056 }
11057
11058 let buffer = &display_map.buffer_snapshot;
11059 let mut selections = self.selections.all::<usize>(cx);
11060 if let Some(mut select_next_state) = self.select_next_state.take() {
11061 let query = &select_next_state.query;
11062 if !select_next_state.done {
11063 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11064 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11065 let mut next_selected_range = None;
11066
11067 let bytes_after_last_selection =
11068 buffer.bytes_in_range(last_selection.end..buffer.len());
11069 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
11070 let query_matches = query
11071 .stream_find_iter(bytes_after_last_selection)
11072 .map(|result| (last_selection.end, result))
11073 .chain(
11074 query
11075 .stream_find_iter(bytes_before_first_selection)
11076 .map(|result| (0, result)),
11077 );
11078
11079 for (start_offset, query_match) in query_matches {
11080 let query_match = query_match.unwrap(); // can only fail due to I/O
11081 let offset_range =
11082 start_offset + query_match.start()..start_offset + query_match.end();
11083 let display_range = offset_range.start.to_display_point(display_map)
11084 ..offset_range.end.to_display_point(display_map);
11085
11086 if !select_next_state.wordwise
11087 || (!movement::is_inside_word(display_map, display_range.start)
11088 && !movement::is_inside_word(display_map, display_range.end))
11089 {
11090 // TODO: This is n^2, because we might check all the selections
11091 if !selections
11092 .iter()
11093 .any(|selection| selection.range().overlaps(&offset_range))
11094 {
11095 next_selected_range = Some(offset_range);
11096 break;
11097 }
11098 }
11099 }
11100
11101 if let Some(next_selected_range) = next_selected_range {
11102 select_next_match_ranges(
11103 self,
11104 next_selected_range,
11105 replace_newest,
11106 autoscroll,
11107 window,
11108 cx,
11109 );
11110 } else {
11111 select_next_state.done = true;
11112 }
11113 }
11114
11115 self.select_next_state = Some(select_next_state);
11116 } else {
11117 let mut only_carets = true;
11118 let mut same_text_selected = true;
11119 let mut selected_text = None;
11120
11121 let mut selections_iter = selections.iter().peekable();
11122 while let Some(selection) = selections_iter.next() {
11123 if selection.start != selection.end {
11124 only_carets = false;
11125 }
11126
11127 if same_text_selected {
11128 if selected_text.is_none() {
11129 selected_text =
11130 Some(buffer.text_for_range(selection.range()).collect::<String>());
11131 }
11132
11133 if let Some(next_selection) = selections_iter.peek() {
11134 if next_selection.range().len() == selection.range().len() {
11135 let next_selected_text = buffer
11136 .text_for_range(next_selection.range())
11137 .collect::<String>();
11138 if Some(next_selected_text) != selected_text {
11139 same_text_selected = false;
11140 selected_text = None;
11141 }
11142 } else {
11143 same_text_selected = false;
11144 selected_text = None;
11145 }
11146 }
11147 }
11148 }
11149
11150 if only_carets {
11151 for selection in &mut selections {
11152 let word_range = movement::surrounding_word(
11153 display_map,
11154 selection.start.to_display_point(display_map),
11155 );
11156 selection.start = word_range.start.to_offset(display_map, Bias::Left);
11157 selection.end = word_range.end.to_offset(display_map, Bias::Left);
11158 selection.goal = SelectionGoal::None;
11159 selection.reversed = false;
11160 select_next_match_ranges(
11161 self,
11162 selection.start..selection.end,
11163 replace_newest,
11164 autoscroll,
11165 window,
11166 cx,
11167 );
11168 }
11169
11170 if selections.len() == 1 {
11171 let selection = selections
11172 .last()
11173 .expect("ensured that there's only one selection");
11174 let query = buffer
11175 .text_for_range(selection.start..selection.end)
11176 .collect::<String>();
11177 let is_empty = query.is_empty();
11178 let select_state = SelectNextState {
11179 query: AhoCorasick::new(&[query])?,
11180 wordwise: true,
11181 done: is_empty,
11182 };
11183 self.select_next_state = Some(select_state);
11184 } else {
11185 self.select_next_state = None;
11186 }
11187 } else if let Some(selected_text) = selected_text {
11188 self.select_next_state = Some(SelectNextState {
11189 query: AhoCorasick::new(&[selected_text])?,
11190 wordwise: false,
11191 done: false,
11192 });
11193 self.select_next_match_internal(
11194 display_map,
11195 replace_newest,
11196 autoscroll,
11197 window,
11198 cx,
11199 )?;
11200 }
11201 }
11202 Ok(())
11203 }
11204
11205 pub fn select_all_matches(
11206 &mut self,
11207 _action: &SelectAllMatches,
11208 window: &mut Window,
11209 cx: &mut Context<Self>,
11210 ) -> Result<()> {
11211 self.push_to_selection_history();
11212 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11213
11214 self.select_next_match_internal(&display_map, false, None, window, cx)?;
11215 let Some(select_next_state) = self.select_next_state.as_mut() else {
11216 return Ok(());
11217 };
11218 if select_next_state.done {
11219 return Ok(());
11220 }
11221
11222 let mut new_selections = self.selections.all::<usize>(cx);
11223
11224 let buffer = &display_map.buffer_snapshot;
11225 let query_matches = select_next_state
11226 .query
11227 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
11228
11229 for query_match in query_matches {
11230 let query_match = query_match.unwrap(); // can only fail due to I/O
11231 let offset_range = query_match.start()..query_match.end();
11232 let display_range = offset_range.start.to_display_point(&display_map)
11233 ..offset_range.end.to_display_point(&display_map);
11234
11235 if !select_next_state.wordwise
11236 || (!movement::is_inside_word(&display_map, display_range.start)
11237 && !movement::is_inside_word(&display_map, display_range.end))
11238 {
11239 self.selections.change_with(cx, |selections| {
11240 new_selections.push(Selection {
11241 id: selections.new_selection_id(),
11242 start: offset_range.start,
11243 end: offset_range.end,
11244 reversed: false,
11245 goal: SelectionGoal::None,
11246 });
11247 });
11248 }
11249 }
11250
11251 new_selections.sort_by_key(|selection| selection.start);
11252 let mut ix = 0;
11253 while ix + 1 < new_selections.len() {
11254 let current_selection = &new_selections[ix];
11255 let next_selection = &new_selections[ix + 1];
11256 if current_selection.range().overlaps(&next_selection.range()) {
11257 if current_selection.id < next_selection.id {
11258 new_selections.remove(ix + 1);
11259 } else {
11260 new_selections.remove(ix);
11261 }
11262 } else {
11263 ix += 1;
11264 }
11265 }
11266
11267 let reversed = self.selections.oldest::<usize>(cx).reversed;
11268
11269 for selection in new_selections.iter_mut() {
11270 selection.reversed = reversed;
11271 }
11272
11273 select_next_state.done = true;
11274 self.unfold_ranges(
11275 &new_selections
11276 .iter()
11277 .map(|selection| selection.range())
11278 .collect::<Vec<_>>(),
11279 false,
11280 false,
11281 cx,
11282 );
11283 self.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
11284 selections.select(new_selections)
11285 });
11286
11287 Ok(())
11288 }
11289
11290 pub fn select_next(
11291 &mut self,
11292 action: &SelectNext,
11293 window: &mut Window,
11294 cx: &mut Context<Self>,
11295 ) -> Result<()> {
11296 self.push_to_selection_history();
11297 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11298 self.select_next_match_internal(
11299 &display_map,
11300 action.replace_newest,
11301 Some(Autoscroll::newest()),
11302 window,
11303 cx,
11304 )?;
11305 Ok(())
11306 }
11307
11308 pub fn select_previous(
11309 &mut self,
11310 action: &SelectPrevious,
11311 window: &mut Window,
11312 cx: &mut Context<Self>,
11313 ) -> Result<()> {
11314 self.push_to_selection_history();
11315 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11316 let buffer = &display_map.buffer_snapshot;
11317 let mut selections = self.selections.all::<usize>(cx);
11318 if let Some(mut select_prev_state) = self.select_prev_state.take() {
11319 let query = &select_prev_state.query;
11320 if !select_prev_state.done {
11321 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11322 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11323 let mut next_selected_range = None;
11324 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
11325 let bytes_before_last_selection =
11326 buffer.reversed_bytes_in_range(0..last_selection.start);
11327 let bytes_after_first_selection =
11328 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
11329 let query_matches = query
11330 .stream_find_iter(bytes_before_last_selection)
11331 .map(|result| (last_selection.start, result))
11332 .chain(
11333 query
11334 .stream_find_iter(bytes_after_first_selection)
11335 .map(|result| (buffer.len(), result)),
11336 );
11337 for (end_offset, query_match) in query_matches {
11338 let query_match = query_match.unwrap(); // can only fail due to I/O
11339 let offset_range =
11340 end_offset - query_match.end()..end_offset - query_match.start();
11341 let display_range = offset_range.start.to_display_point(&display_map)
11342 ..offset_range.end.to_display_point(&display_map);
11343
11344 if !select_prev_state.wordwise
11345 || (!movement::is_inside_word(&display_map, display_range.start)
11346 && !movement::is_inside_word(&display_map, display_range.end))
11347 {
11348 next_selected_range = Some(offset_range);
11349 break;
11350 }
11351 }
11352
11353 if let Some(next_selected_range) = next_selected_range {
11354 self.unfold_ranges(&[next_selected_range.clone()], false, true, cx);
11355 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
11356 if action.replace_newest {
11357 s.delete(s.newest_anchor().id);
11358 }
11359 s.insert_range(next_selected_range);
11360 });
11361 } else {
11362 select_prev_state.done = true;
11363 }
11364 }
11365
11366 self.select_prev_state = Some(select_prev_state);
11367 } else {
11368 let mut only_carets = true;
11369 let mut same_text_selected = true;
11370 let mut selected_text = None;
11371
11372 let mut selections_iter = selections.iter().peekable();
11373 while let Some(selection) = selections_iter.next() {
11374 if selection.start != selection.end {
11375 only_carets = false;
11376 }
11377
11378 if same_text_selected {
11379 if selected_text.is_none() {
11380 selected_text =
11381 Some(buffer.text_for_range(selection.range()).collect::<String>());
11382 }
11383
11384 if let Some(next_selection) = selections_iter.peek() {
11385 if next_selection.range().len() == selection.range().len() {
11386 let next_selected_text = buffer
11387 .text_for_range(next_selection.range())
11388 .collect::<String>();
11389 if Some(next_selected_text) != selected_text {
11390 same_text_selected = false;
11391 selected_text = None;
11392 }
11393 } else {
11394 same_text_selected = false;
11395 selected_text = None;
11396 }
11397 }
11398 }
11399 }
11400
11401 if only_carets {
11402 for selection in &mut selections {
11403 let word_range = movement::surrounding_word(
11404 &display_map,
11405 selection.start.to_display_point(&display_map),
11406 );
11407 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
11408 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
11409 selection.goal = SelectionGoal::None;
11410 selection.reversed = false;
11411 }
11412 if selections.len() == 1 {
11413 let selection = selections
11414 .last()
11415 .expect("ensured that there's only one selection");
11416 let query = buffer
11417 .text_for_range(selection.start..selection.end)
11418 .collect::<String>();
11419 let is_empty = query.is_empty();
11420 let select_state = SelectNextState {
11421 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
11422 wordwise: true,
11423 done: is_empty,
11424 };
11425 self.select_prev_state = Some(select_state);
11426 } else {
11427 self.select_prev_state = None;
11428 }
11429
11430 self.unfold_ranges(
11431 &selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
11432 false,
11433 true,
11434 cx,
11435 );
11436 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
11437 s.select(selections);
11438 });
11439 } else if let Some(selected_text) = selected_text {
11440 self.select_prev_state = Some(SelectNextState {
11441 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
11442 wordwise: false,
11443 done: false,
11444 });
11445 self.select_previous(action, window, cx)?;
11446 }
11447 }
11448 Ok(())
11449 }
11450
11451 pub fn toggle_comments(
11452 &mut self,
11453 action: &ToggleComments,
11454 window: &mut Window,
11455 cx: &mut Context<Self>,
11456 ) {
11457 if self.read_only(cx) {
11458 return;
11459 }
11460 let text_layout_details = &self.text_layout_details(window);
11461 self.transact(window, cx, |this, window, cx| {
11462 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
11463 let mut edits = Vec::new();
11464 let mut selection_edit_ranges = Vec::new();
11465 let mut last_toggled_row = None;
11466 let snapshot = this.buffer.read(cx).read(cx);
11467 let empty_str: Arc<str> = Arc::default();
11468 let mut suffixes_inserted = Vec::new();
11469 let ignore_indent = action.ignore_indent;
11470
11471 fn comment_prefix_range(
11472 snapshot: &MultiBufferSnapshot,
11473 row: MultiBufferRow,
11474 comment_prefix: &str,
11475 comment_prefix_whitespace: &str,
11476 ignore_indent: bool,
11477 ) -> Range<Point> {
11478 let indent_size = if ignore_indent {
11479 0
11480 } else {
11481 snapshot.indent_size_for_line(row).len
11482 };
11483
11484 let start = Point::new(row.0, indent_size);
11485
11486 let mut line_bytes = snapshot
11487 .bytes_in_range(start..snapshot.max_point())
11488 .flatten()
11489 .copied();
11490
11491 // If this line currently begins with the line comment prefix, then record
11492 // the range containing the prefix.
11493 if line_bytes
11494 .by_ref()
11495 .take(comment_prefix.len())
11496 .eq(comment_prefix.bytes())
11497 {
11498 // Include any whitespace that matches the comment prefix.
11499 let matching_whitespace_len = line_bytes
11500 .zip(comment_prefix_whitespace.bytes())
11501 .take_while(|(a, b)| a == b)
11502 .count() as u32;
11503 let end = Point::new(
11504 start.row,
11505 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
11506 );
11507 start..end
11508 } else {
11509 start..start
11510 }
11511 }
11512
11513 fn comment_suffix_range(
11514 snapshot: &MultiBufferSnapshot,
11515 row: MultiBufferRow,
11516 comment_suffix: &str,
11517 comment_suffix_has_leading_space: bool,
11518 ) -> Range<Point> {
11519 let end = Point::new(row.0, snapshot.line_len(row));
11520 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
11521
11522 let mut line_end_bytes = snapshot
11523 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
11524 .flatten()
11525 .copied();
11526
11527 let leading_space_len = if suffix_start_column > 0
11528 && line_end_bytes.next() == Some(b' ')
11529 && comment_suffix_has_leading_space
11530 {
11531 1
11532 } else {
11533 0
11534 };
11535
11536 // If this line currently begins with the line comment prefix, then record
11537 // the range containing the prefix.
11538 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
11539 let start = Point::new(end.row, suffix_start_column - leading_space_len);
11540 start..end
11541 } else {
11542 end..end
11543 }
11544 }
11545
11546 // TODO: Handle selections that cross excerpts
11547 for selection in &mut selections {
11548 let start_column = snapshot
11549 .indent_size_for_line(MultiBufferRow(selection.start.row))
11550 .len;
11551 let language = if let Some(language) =
11552 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
11553 {
11554 language
11555 } else {
11556 continue;
11557 };
11558
11559 selection_edit_ranges.clear();
11560
11561 // If multiple selections contain a given row, avoid processing that
11562 // row more than once.
11563 let mut start_row = MultiBufferRow(selection.start.row);
11564 if last_toggled_row == Some(start_row) {
11565 start_row = start_row.next_row();
11566 }
11567 let end_row =
11568 if selection.end.row > selection.start.row && selection.end.column == 0 {
11569 MultiBufferRow(selection.end.row - 1)
11570 } else {
11571 MultiBufferRow(selection.end.row)
11572 };
11573 last_toggled_row = Some(end_row);
11574
11575 if start_row > end_row {
11576 continue;
11577 }
11578
11579 // If the language has line comments, toggle those.
11580 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
11581
11582 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
11583 if ignore_indent {
11584 full_comment_prefixes = full_comment_prefixes
11585 .into_iter()
11586 .map(|s| Arc::from(s.trim_end()))
11587 .collect();
11588 }
11589
11590 if !full_comment_prefixes.is_empty() {
11591 let first_prefix = full_comment_prefixes
11592 .first()
11593 .expect("prefixes is non-empty");
11594 let prefix_trimmed_lengths = full_comment_prefixes
11595 .iter()
11596 .map(|p| p.trim_end_matches(' ').len())
11597 .collect::<SmallVec<[usize; 4]>>();
11598
11599 let mut all_selection_lines_are_comments = true;
11600
11601 for row in start_row.0..=end_row.0 {
11602 let row = MultiBufferRow(row);
11603 if start_row < end_row && snapshot.is_line_blank(row) {
11604 continue;
11605 }
11606
11607 let prefix_range = full_comment_prefixes
11608 .iter()
11609 .zip(prefix_trimmed_lengths.iter().copied())
11610 .map(|(prefix, trimmed_prefix_len)| {
11611 comment_prefix_range(
11612 snapshot.deref(),
11613 row,
11614 &prefix[..trimmed_prefix_len],
11615 &prefix[trimmed_prefix_len..],
11616 ignore_indent,
11617 )
11618 })
11619 .max_by_key(|range| range.end.column - range.start.column)
11620 .expect("prefixes is non-empty");
11621
11622 if prefix_range.is_empty() {
11623 all_selection_lines_are_comments = false;
11624 }
11625
11626 selection_edit_ranges.push(prefix_range);
11627 }
11628
11629 if all_selection_lines_are_comments {
11630 edits.extend(
11631 selection_edit_ranges
11632 .iter()
11633 .cloned()
11634 .map(|range| (range, empty_str.clone())),
11635 );
11636 } else {
11637 let min_column = selection_edit_ranges
11638 .iter()
11639 .map(|range| range.start.column)
11640 .min()
11641 .unwrap_or(0);
11642 edits.extend(selection_edit_ranges.iter().map(|range| {
11643 let position = Point::new(range.start.row, min_column);
11644 (position..position, first_prefix.clone())
11645 }));
11646 }
11647 } else if let Some((full_comment_prefix, comment_suffix)) =
11648 language.block_comment_delimiters()
11649 {
11650 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
11651 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
11652 let prefix_range = comment_prefix_range(
11653 snapshot.deref(),
11654 start_row,
11655 comment_prefix,
11656 comment_prefix_whitespace,
11657 ignore_indent,
11658 );
11659 let suffix_range = comment_suffix_range(
11660 snapshot.deref(),
11661 end_row,
11662 comment_suffix.trim_start_matches(' '),
11663 comment_suffix.starts_with(' '),
11664 );
11665
11666 if prefix_range.is_empty() || suffix_range.is_empty() {
11667 edits.push((
11668 prefix_range.start..prefix_range.start,
11669 full_comment_prefix.clone(),
11670 ));
11671 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
11672 suffixes_inserted.push((end_row, comment_suffix.len()));
11673 } else {
11674 edits.push((prefix_range, empty_str.clone()));
11675 edits.push((suffix_range, empty_str.clone()));
11676 }
11677 } else {
11678 continue;
11679 }
11680 }
11681
11682 drop(snapshot);
11683 this.buffer.update(cx, |buffer, cx| {
11684 buffer.edit(edits, None, cx);
11685 });
11686
11687 // Adjust selections so that they end before any comment suffixes that
11688 // were inserted.
11689 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
11690 let mut selections = this.selections.all::<Point>(cx);
11691 let snapshot = this.buffer.read(cx).read(cx);
11692 for selection in &mut selections {
11693 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
11694 match row.cmp(&MultiBufferRow(selection.end.row)) {
11695 Ordering::Less => {
11696 suffixes_inserted.next();
11697 continue;
11698 }
11699 Ordering::Greater => break,
11700 Ordering::Equal => {
11701 if selection.end.column == snapshot.line_len(row) {
11702 if selection.is_empty() {
11703 selection.start.column -= suffix_len as u32;
11704 }
11705 selection.end.column -= suffix_len as u32;
11706 }
11707 break;
11708 }
11709 }
11710 }
11711 }
11712
11713 drop(snapshot);
11714 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11715 s.select(selections)
11716 });
11717
11718 let selections = this.selections.all::<Point>(cx);
11719 let selections_on_single_row = selections.windows(2).all(|selections| {
11720 selections[0].start.row == selections[1].start.row
11721 && selections[0].end.row == selections[1].end.row
11722 && selections[0].start.row == selections[0].end.row
11723 });
11724 let selections_selecting = selections
11725 .iter()
11726 .any(|selection| selection.start != selection.end);
11727 let advance_downwards = action.advance_downwards
11728 && selections_on_single_row
11729 && !selections_selecting
11730 && !matches!(this.mode, EditorMode::SingleLine { .. });
11731
11732 if advance_downwards {
11733 let snapshot = this.buffer.read(cx).snapshot(cx);
11734
11735 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11736 s.move_cursors_with(|display_snapshot, display_point, _| {
11737 let mut point = display_point.to_point(display_snapshot);
11738 point.row += 1;
11739 point = snapshot.clip_point(point, Bias::Left);
11740 let display_point = point.to_display_point(display_snapshot);
11741 let goal = SelectionGoal::HorizontalPosition(
11742 display_snapshot
11743 .x_for_display_point(display_point, text_layout_details)
11744 .into(),
11745 );
11746 (display_point, goal)
11747 })
11748 });
11749 }
11750 });
11751 }
11752
11753 pub fn select_enclosing_symbol(
11754 &mut self,
11755 _: &SelectEnclosingSymbol,
11756 window: &mut Window,
11757 cx: &mut Context<Self>,
11758 ) {
11759 let buffer = self.buffer.read(cx).snapshot(cx);
11760 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
11761
11762 fn update_selection(
11763 selection: &Selection<usize>,
11764 buffer_snap: &MultiBufferSnapshot,
11765 ) -> Option<Selection<usize>> {
11766 let cursor = selection.head();
11767 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
11768 for symbol in symbols.iter().rev() {
11769 let start = symbol.range.start.to_offset(buffer_snap);
11770 let end = symbol.range.end.to_offset(buffer_snap);
11771 let new_range = start..end;
11772 if start < selection.start || end > selection.end {
11773 return Some(Selection {
11774 id: selection.id,
11775 start: new_range.start,
11776 end: new_range.end,
11777 goal: SelectionGoal::None,
11778 reversed: selection.reversed,
11779 });
11780 }
11781 }
11782 None
11783 }
11784
11785 let mut selected_larger_symbol = false;
11786 let new_selections = old_selections
11787 .iter()
11788 .map(|selection| match update_selection(selection, &buffer) {
11789 Some(new_selection) => {
11790 if new_selection.range() != selection.range() {
11791 selected_larger_symbol = true;
11792 }
11793 new_selection
11794 }
11795 None => selection.clone(),
11796 })
11797 .collect::<Vec<_>>();
11798
11799 if selected_larger_symbol {
11800 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11801 s.select(new_selections);
11802 });
11803 }
11804 }
11805
11806 pub fn select_larger_syntax_node(
11807 &mut self,
11808 _: &SelectLargerSyntaxNode,
11809 window: &mut Window,
11810 cx: &mut Context<Self>,
11811 ) {
11812 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11813 let buffer = self.buffer.read(cx).snapshot(cx);
11814 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
11815
11816 let mut stack = mem::take(&mut self.select_larger_syntax_node_stack);
11817 let mut selected_larger_node = false;
11818 let new_selections = old_selections
11819 .iter()
11820 .map(|selection| {
11821 let old_range = selection.start..selection.end;
11822 let mut new_range = old_range.clone();
11823 let mut new_node = None;
11824 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
11825 {
11826 new_node = Some(node);
11827 new_range = match containing_range {
11828 MultiOrSingleBufferOffsetRange::Single(_) => break,
11829 MultiOrSingleBufferOffsetRange::Multi(range) => range,
11830 };
11831 if !display_map.intersects_fold(new_range.start)
11832 && !display_map.intersects_fold(new_range.end)
11833 {
11834 break;
11835 }
11836 }
11837
11838 if let Some(node) = new_node {
11839 // Log the ancestor, to support using this action as a way to explore TreeSitter
11840 // nodes. Parent and grandparent are also logged because this operation will not
11841 // visit nodes that have the same range as their parent.
11842 log::info!("Node: {node:?}");
11843 let parent = node.parent();
11844 log::info!("Parent: {parent:?}");
11845 let grandparent = parent.and_then(|x| x.parent());
11846 log::info!("Grandparent: {grandparent:?}");
11847 }
11848
11849 selected_larger_node |= new_range != old_range;
11850 Selection {
11851 id: selection.id,
11852 start: new_range.start,
11853 end: new_range.end,
11854 goal: SelectionGoal::None,
11855 reversed: selection.reversed,
11856 }
11857 })
11858 .collect::<Vec<_>>();
11859
11860 if selected_larger_node {
11861 stack.push(old_selections);
11862 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11863 s.select(new_selections);
11864 });
11865 }
11866 self.select_larger_syntax_node_stack = stack;
11867 }
11868
11869 pub fn select_smaller_syntax_node(
11870 &mut self,
11871 _: &SelectSmallerSyntaxNode,
11872 window: &mut Window,
11873 cx: &mut Context<Self>,
11874 ) {
11875 let mut stack = mem::take(&mut self.select_larger_syntax_node_stack);
11876 if let Some(selections) = stack.pop() {
11877 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11878 s.select(selections.to_vec());
11879 });
11880 }
11881 self.select_larger_syntax_node_stack = stack;
11882 }
11883
11884 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
11885 if !EditorSettings::get_global(cx).gutter.runnables {
11886 self.clear_tasks();
11887 return Task::ready(());
11888 }
11889 let project = self.project.as_ref().map(Entity::downgrade);
11890 cx.spawn_in(window, async move |this, cx| {
11891 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
11892 let Some(project) = project.and_then(|p| p.upgrade()) else {
11893 return;
11894 };
11895 let Ok(display_snapshot) = this.update(cx, |this, cx| {
11896 this.display_map.update(cx, |map, cx| map.snapshot(cx))
11897 }) else {
11898 return;
11899 };
11900
11901 let hide_runnables = project
11902 .update(cx, |project, cx| {
11903 // Do not display any test indicators in non-dev server remote projects.
11904 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
11905 })
11906 .unwrap_or(true);
11907 if hide_runnables {
11908 return;
11909 }
11910 let new_rows =
11911 cx.background_spawn({
11912 let snapshot = display_snapshot.clone();
11913 async move {
11914 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
11915 }
11916 })
11917 .await;
11918
11919 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
11920 this.update(cx, |this, _| {
11921 this.clear_tasks();
11922 for (key, value) in rows {
11923 this.insert_tasks(key, value);
11924 }
11925 })
11926 .ok();
11927 })
11928 }
11929 fn fetch_runnable_ranges(
11930 snapshot: &DisplaySnapshot,
11931 range: Range<Anchor>,
11932 ) -> Vec<language::RunnableRange> {
11933 snapshot.buffer_snapshot.runnable_ranges(range).collect()
11934 }
11935
11936 fn runnable_rows(
11937 project: Entity<Project>,
11938 snapshot: DisplaySnapshot,
11939 runnable_ranges: Vec<RunnableRange>,
11940 mut cx: AsyncWindowContext,
11941 ) -> Vec<((BufferId, u32), RunnableTasks)> {
11942 runnable_ranges
11943 .into_iter()
11944 .filter_map(|mut runnable| {
11945 let tasks = cx
11946 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
11947 .ok()?;
11948 if tasks.is_empty() {
11949 return None;
11950 }
11951
11952 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
11953
11954 let row = snapshot
11955 .buffer_snapshot
11956 .buffer_line_for_row(MultiBufferRow(point.row))?
11957 .1
11958 .start
11959 .row;
11960
11961 let context_range =
11962 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
11963 Some((
11964 (runnable.buffer_id, row),
11965 RunnableTasks {
11966 templates: tasks,
11967 offset: snapshot
11968 .buffer_snapshot
11969 .anchor_before(runnable.run_range.start),
11970 context_range,
11971 column: point.column,
11972 extra_variables: runnable.extra_captures,
11973 },
11974 ))
11975 })
11976 .collect()
11977 }
11978
11979 fn templates_with_tags(
11980 project: &Entity<Project>,
11981 runnable: &mut Runnable,
11982 cx: &mut App,
11983 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
11984 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
11985 let (worktree_id, file) = project
11986 .buffer_for_id(runnable.buffer, cx)
11987 .and_then(|buffer| buffer.read(cx).file())
11988 .map(|file| (file.worktree_id(cx), file.clone()))
11989 .unzip();
11990
11991 (
11992 project.task_store().read(cx).task_inventory().cloned(),
11993 worktree_id,
11994 file,
11995 )
11996 });
11997
11998 let tags = mem::take(&mut runnable.tags);
11999 let mut tags: Vec<_> = tags
12000 .into_iter()
12001 .flat_map(|tag| {
12002 let tag = tag.0.clone();
12003 inventory
12004 .as_ref()
12005 .into_iter()
12006 .flat_map(|inventory| {
12007 inventory.read(cx).list_tasks(
12008 file.clone(),
12009 Some(runnable.language.clone()),
12010 worktree_id,
12011 cx,
12012 )
12013 })
12014 .filter(move |(_, template)| {
12015 template.tags.iter().any(|source_tag| source_tag == &tag)
12016 })
12017 })
12018 .sorted_by_key(|(kind, _)| kind.to_owned())
12019 .collect();
12020 if let Some((leading_tag_source, _)) = tags.first() {
12021 // Strongest source wins; if we have worktree tag binding, prefer that to
12022 // global and language bindings;
12023 // if we have a global binding, prefer that to language binding.
12024 let first_mismatch = tags
12025 .iter()
12026 .position(|(tag_source, _)| tag_source != leading_tag_source);
12027 if let Some(index) = first_mismatch {
12028 tags.truncate(index);
12029 }
12030 }
12031
12032 tags
12033 }
12034
12035 pub fn move_to_enclosing_bracket(
12036 &mut self,
12037 _: &MoveToEnclosingBracket,
12038 window: &mut Window,
12039 cx: &mut Context<Self>,
12040 ) {
12041 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12042 s.move_offsets_with(|snapshot, selection| {
12043 let Some(enclosing_bracket_ranges) =
12044 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
12045 else {
12046 return;
12047 };
12048
12049 let mut best_length = usize::MAX;
12050 let mut best_inside = false;
12051 let mut best_in_bracket_range = false;
12052 let mut best_destination = None;
12053 for (open, close) in enclosing_bracket_ranges {
12054 let close = close.to_inclusive();
12055 let length = close.end() - open.start;
12056 let inside = selection.start >= open.end && selection.end <= *close.start();
12057 let in_bracket_range = open.to_inclusive().contains(&selection.head())
12058 || close.contains(&selection.head());
12059
12060 // If best is next to a bracket and current isn't, skip
12061 if !in_bracket_range && best_in_bracket_range {
12062 continue;
12063 }
12064
12065 // Prefer smaller lengths unless best is inside and current isn't
12066 if length > best_length && (best_inside || !inside) {
12067 continue;
12068 }
12069
12070 best_length = length;
12071 best_inside = inside;
12072 best_in_bracket_range = in_bracket_range;
12073 best_destination = Some(
12074 if close.contains(&selection.start) && close.contains(&selection.end) {
12075 if inside {
12076 open.end
12077 } else {
12078 open.start
12079 }
12080 } else if inside {
12081 *close.start()
12082 } else {
12083 *close.end()
12084 },
12085 );
12086 }
12087
12088 if let Some(destination) = best_destination {
12089 selection.collapse_to(destination, SelectionGoal::None);
12090 }
12091 })
12092 });
12093 }
12094
12095 pub fn undo_selection(
12096 &mut self,
12097 _: &UndoSelection,
12098 window: &mut Window,
12099 cx: &mut Context<Self>,
12100 ) {
12101 self.end_selection(window, cx);
12102 self.selection_history.mode = SelectionHistoryMode::Undoing;
12103 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
12104 self.change_selections(None, window, cx, |s| {
12105 s.select_anchors(entry.selections.to_vec())
12106 });
12107 self.select_next_state = entry.select_next_state;
12108 self.select_prev_state = entry.select_prev_state;
12109 self.add_selections_state = entry.add_selections_state;
12110 self.request_autoscroll(Autoscroll::newest(), cx);
12111 }
12112 self.selection_history.mode = SelectionHistoryMode::Normal;
12113 }
12114
12115 pub fn redo_selection(
12116 &mut self,
12117 _: &RedoSelection,
12118 window: &mut Window,
12119 cx: &mut Context<Self>,
12120 ) {
12121 self.end_selection(window, cx);
12122 self.selection_history.mode = SelectionHistoryMode::Redoing;
12123 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
12124 self.change_selections(None, window, cx, |s| {
12125 s.select_anchors(entry.selections.to_vec())
12126 });
12127 self.select_next_state = entry.select_next_state;
12128 self.select_prev_state = entry.select_prev_state;
12129 self.add_selections_state = entry.add_selections_state;
12130 self.request_autoscroll(Autoscroll::newest(), cx);
12131 }
12132 self.selection_history.mode = SelectionHistoryMode::Normal;
12133 }
12134
12135 pub fn expand_excerpts(
12136 &mut self,
12137 action: &ExpandExcerpts,
12138 _: &mut Window,
12139 cx: &mut Context<Self>,
12140 ) {
12141 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
12142 }
12143
12144 pub fn expand_excerpts_down(
12145 &mut self,
12146 action: &ExpandExcerptsDown,
12147 _: &mut Window,
12148 cx: &mut Context<Self>,
12149 ) {
12150 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
12151 }
12152
12153 pub fn expand_excerpts_up(
12154 &mut self,
12155 action: &ExpandExcerptsUp,
12156 _: &mut Window,
12157 cx: &mut Context<Self>,
12158 ) {
12159 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
12160 }
12161
12162 pub fn expand_excerpts_for_direction(
12163 &mut self,
12164 lines: u32,
12165 direction: ExpandExcerptDirection,
12166
12167 cx: &mut Context<Self>,
12168 ) {
12169 let selections = self.selections.disjoint_anchors();
12170
12171 let lines = if lines == 0 {
12172 EditorSettings::get_global(cx).expand_excerpt_lines
12173 } else {
12174 lines
12175 };
12176
12177 self.buffer.update(cx, |buffer, cx| {
12178 let snapshot = buffer.snapshot(cx);
12179 let mut excerpt_ids = selections
12180 .iter()
12181 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
12182 .collect::<Vec<_>>();
12183 excerpt_ids.sort();
12184 excerpt_ids.dedup();
12185 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
12186 })
12187 }
12188
12189 pub fn expand_excerpt(
12190 &mut self,
12191 excerpt: ExcerptId,
12192 direction: ExpandExcerptDirection,
12193 window: &mut Window,
12194 cx: &mut Context<Self>,
12195 ) {
12196 let current_scroll_position = self.scroll_position(cx);
12197 let lines = EditorSettings::get_global(cx).expand_excerpt_lines;
12198 self.buffer.update(cx, |buffer, cx| {
12199 buffer.expand_excerpts([excerpt], lines, direction, cx)
12200 });
12201 if direction == ExpandExcerptDirection::Down {
12202 let new_scroll_position = current_scroll_position + gpui::Point::new(0.0, lines as f32);
12203 self.set_scroll_position(new_scroll_position, window, cx);
12204 }
12205 }
12206
12207 pub fn go_to_singleton_buffer_point(
12208 &mut self,
12209 point: Point,
12210 window: &mut Window,
12211 cx: &mut Context<Self>,
12212 ) {
12213 self.go_to_singleton_buffer_range(point..point, window, cx);
12214 }
12215
12216 pub fn go_to_singleton_buffer_range(
12217 &mut self,
12218 range: Range<Point>,
12219 window: &mut Window,
12220 cx: &mut Context<Self>,
12221 ) {
12222 let multibuffer = self.buffer().read(cx);
12223 let Some(buffer) = multibuffer.as_singleton() else {
12224 return;
12225 };
12226 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
12227 return;
12228 };
12229 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
12230 return;
12231 };
12232 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
12233 s.select_anchor_ranges([start..end])
12234 });
12235 }
12236
12237 fn go_to_diagnostic(
12238 &mut self,
12239 _: &GoToDiagnostic,
12240 window: &mut Window,
12241 cx: &mut Context<Self>,
12242 ) {
12243 self.go_to_diagnostic_impl(Direction::Next, window, cx)
12244 }
12245
12246 fn go_to_prev_diagnostic(
12247 &mut self,
12248 _: &GoToPreviousDiagnostic,
12249 window: &mut Window,
12250 cx: &mut Context<Self>,
12251 ) {
12252 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
12253 }
12254
12255 pub fn go_to_diagnostic_impl(
12256 &mut self,
12257 direction: Direction,
12258 window: &mut Window,
12259 cx: &mut Context<Self>,
12260 ) {
12261 let buffer = self.buffer.read(cx).snapshot(cx);
12262 let selection = self.selections.newest::<usize>(cx);
12263
12264 // If there is an active Diagnostic Popover jump to its diagnostic instead.
12265 if direction == Direction::Next {
12266 if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() {
12267 let Some(buffer_id) = popover.local_diagnostic.range.start.buffer_id else {
12268 return;
12269 };
12270 self.activate_diagnostics(
12271 buffer_id,
12272 popover.local_diagnostic.diagnostic.group_id,
12273 window,
12274 cx,
12275 );
12276 if let Some(active_diagnostics) = self.active_diagnostics.as_ref() {
12277 let primary_range_start = active_diagnostics.primary_range.start;
12278 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12279 let mut new_selection = s.newest_anchor().clone();
12280 new_selection.collapse_to(primary_range_start, SelectionGoal::None);
12281 s.select_anchors(vec![new_selection.clone()]);
12282 });
12283 self.refresh_inline_completion(false, true, window, cx);
12284 }
12285 return;
12286 }
12287 }
12288
12289 let active_group_id = self
12290 .active_diagnostics
12291 .as_ref()
12292 .map(|active_group| active_group.group_id);
12293 let active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| {
12294 active_diagnostics
12295 .primary_range
12296 .to_offset(&buffer)
12297 .to_inclusive()
12298 });
12299 let search_start = if let Some(active_primary_range) = active_primary_range.as_ref() {
12300 if active_primary_range.contains(&selection.head()) {
12301 *active_primary_range.start()
12302 } else {
12303 selection.head()
12304 }
12305 } else {
12306 selection.head()
12307 };
12308
12309 let snapshot = self.snapshot(window, cx);
12310 let primary_diagnostics_before = buffer
12311 .diagnostics_in_range::<usize>(0..search_start)
12312 .filter(|entry| entry.diagnostic.is_primary)
12313 .filter(|entry| entry.range.start != entry.range.end)
12314 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
12315 .filter(|entry| !snapshot.intersects_fold(entry.range.start))
12316 .collect::<Vec<_>>();
12317 let last_same_group_diagnostic_before = active_group_id.and_then(|active_group_id| {
12318 primary_diagnostics_before
12319 .iter()
12320 .position(|entry| entry.diagnostic.group_id == active_group_id)
12321 });
12322
12323 let primary_diagnostics_after = buffer
12324 .diagnostics_in_range::<usize>(search_start..buffer.len())
12325 .filter(|entry| entry.diagnostic.is_primary)
12326 .filter(|entry| entry.range.start != entry.range.end)
12327 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
12328 .filter(|diagnostic| !snapshot.intersects_fold(diagnostic.range.start))
12329 .collect::<Vec<_>>();
12330 let last_same_group_diagnostic_after = active_group_id.and_then(|active_group_id| {
12331 primary_diagnostics_after
12332 .iter()
12333 .enumerate()
12334 .rev()
12335 .find_map(|(i, entry)| {
12336 if entry.diagnostic.group_id == active_group_id {
12337 Some(i)
12338 } else {
12339 None
12340 }
12341 })
12342 });
12343
12344 let next_primary_diagnostic = match direction {
12345 Direction::Prev => primary_diagnostics_before
12346 .iter()
12347 .take(last_same_group_diagnostic_before.unwrap_or(usize::MAX))
12348 .rev()
12349 .next(),
12350 Direction::Next => primary_diagnostics_after
12351 .iter()
12352 .skip(
12353 last_same_group_diagnostic_after
12354 .map(|index| index + 1)
12355 .unwrap_or(0),
12356 )
12357 .next(),
12358 };
12359
12360 // Cycle around to the start of the buffer, potentially moving back to the start of
12361 // the currently active diagnostic.
12362 let cycle_around = || match direction {
12363 Direction::Prev => primary_diagnostics_after
12364 .iter()
12365 .rev()
12366 .chain(primary_diagnostics_before.iter().rev())
12367 .next(),
12368 Direction::Next => primary_diagnostics_before
12369 .iter()
12370 .chain(primary_diagnostics_after.iter())
12371 .next(),
12372 };
12373
12374 if let Some((primary_range, group_id)) = next_primary_diagnostic
12375 .or_else(cycle_around)
12376 .map(|entry| (&entry.range, entry.diagnostic.group_id))
12377 {
12378 let Some(buffer_id) = buffer.anchor_after(primary_range.start).buffer_id else {
12379 return;
12380 };
12381 self.activate_diagnostics(buffer_id, group_id, window, cx);
12382 if self.active_diagnostics.is_some() {
12383 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12384 s.select(vec![Selection {
12385 id: selection.id,
12386 start: primary_range.start,
12387 end: primary_range.start,
12388 reversed: false,
12389 goal: SelectionGoal::None,
12390 }]);
12391 });
12392 self.refresh_inline_completion(false, true, window, cx);
12393 }
12394 }
12395 }
12396
12397 fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
12398 let snapshot = self.snapshot(window, cx);
12399 let selection = self.selections.newest::<Point>(cx);
12400 self.go_to_hunk_before_or_after_position(
12401 &snapshot,
12402 selection.head(),
12403 Direction::Next,
12404 window,
12405 cx,
12406 );
12407 }
12408
12409 fn go_to_hunk_before_or_after_position(
12410 &mut self,
12411 snapshot: &EditorSnapshot,
12412 position: Point,
12413 direction: Direction,
12414 window: &mut Window,
12415 cx: &mut Context<Editor>,
12416 ) {
12417 let row = if direction == Direction::Next {
12418 self.hunk_after_position(snapshot, position)
12419 .map(|hunk| hunk.row_range.start)
12420 } else {
12421 self.hunk_before_position(snapshot, position)
12422 };
12423
12424 if let Some(row) = row {
12425 let destination = Point::new(row.0, 0);
12426 let autoscroll = Autoscroll::center();
12427
12428 self.unfold_ranges(&[destination..destination], false, false, cx);
12429 self.change_selections(Some(autoscroll), window, cx, |s| {
12430 s.select_ranges([destination..destination]);
12431 });
12432 }
12433 }
12434
12435 fn hunk_after_position(
12436 &mut self,
12437 snapshot: &EditorSnapshot,
12438 position: Point,
12439 ) -> Option<MultiBufferDiffHunk> {
12440 snapshot
12441 .buffer_snapshot
12442 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
12443 .find(|hunk| hunk.row_range.start.0 > position.row)
12444 .or_else(|| {
12445 snapshot
12446 .buffer_snapshot
12447 .diff_hunks_in_range(Point::zero()..position)
12448 .find(|hunk| hunk.row_range.end.0 < position.row)
12449 })
12450 }
12451
12452 fn go_to_prev_hunk(
12453 &mut self,
12454 _: &GoToPreviousHunk,
12455 window: &mut Window,
12456 cx: &mut Context<Self>,
12457 ) {
12458 let snapshot = self.snapshot(window, cx);
12459 let selection = self.selections.newest::<Point>(cx);
12460 self.go_to_hunk_before_or_after_position(
12461 &snapshot,
12462 selection.head(),
12463 Direction::Prev,
12464 window,
12465 cx,
12466 );
12467 }
12468
12469 fn hunk_before_position(
12470 &mut self,
12471 snapshot: &EditorSnapshot,
12472 position: Point,
12473 ) -> Option<MultiBufferRow> {
12474 snapshot
12475 .buffer_snapshot
12476 .diff_hunk_before(position)
12477 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
12478 }
12479
12480 fn go_to_line<T: 'static>(
12481 &mut self,
12482 position: Anchor,
12483 highlight_color: Option<Hsla>,
12484 window: &mut Window,
12485 cx: &mut Context<Self>,
12486 ) {
12487 let snapshot = self.snapshot(window, cx).display_snapshot;
12488 let position = position.to_point(&snapshot.buffer_snapshot);
12489 let start = snapshot
12490 .buffer_snapshot
12491 .clip_point(Point::new(position.row, 0), Bias::Left);
12492 let end = start + Point::new(1, 0);
12493 let start = snapshot.buffer_snapshot.anchor_before(start);
12494 let end = snapshot.buffer_snapshot.anchor_before(end);
12495
12496 self.clear_row_highlights::<T>();
12497 self.highlight_rows::<T>(
12498 start..end,
12499 highlight_color
12500 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
12501 true,
12502 cx,
12503 );
12504 self.request_autoscroll(Autoscroll::center(), cx);
12505 }
12506
12507 pub fn go_to_definition(
12508 &mut self,
12509 _: &GoToDefinition,
12510 window: &mut Window,
12511 cx: &mut Context<Self>,
12512 ) -> Task<Result<Navigated>> {
12513 let definition =
12514 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
12515 cx.spawn_in(window, async move |editor, cx| {
12516 if definition.await? == Navigated::Yes {
12517 return Ok(Navigated::Yes);
12518 }
12519 match editor.update_in(cx, |editor, window, cx| {
12520 editor.find_all_references(&FindAllReferences, window, cx)
12521 })? {
12522 Some(references) => references.await,
12523 None => Ok(Navigated::No),
12524 }
12525 })
12526 }
12527
12528 pub fn go_to_declaration(
12529 &mut self,
12530 _: &GoToDeclaration,
12531 window: &mut Window,
12532 cx: &mut Context<Self>,
12533 ) -> Task<Result<Navigated>> {
12534 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
12535 }
12536
12537 pub fn go_to_declaration_split(
12538 &mut self,
12539 _: &GoToDeclaration,
12540 window: &mut Window,
12541 cx: &mut Context<Self>,
12542 ) -> Task<Result<Navigated>> {
12543 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
12544 }
12545
12546 pub fn go_to_implementation(
12547 &mut self,
12548 _: &GoToImplementation,
12549 window: &mut Window,
12550 cx: &mut Context<Self>,
12551 ) -> Task<Result<Navigated>> {
12552 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
12553 }
12554
12555 pub fn go_to_implementation_split(
12556 &mut self,
12557 _: &GoToImplementationSplit,
12558 window: &mut Window,
12559 cx: &mut Context<Self>,
12560 ) -> Task<Result<Navigated>> {
12561 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
12562 }
12563
12564 pub fn go_to_type_definition(
12565 &mut self,
12566 _: &GoToTypeDefinition,
12567 window: &mut Window,
12568 cx: &mut Context<Self>,
12569 ) -> Task<Result<Navigated>> {
12570 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
12571 }
12572
12573 pub fn go_to_definition_split(
12574 &mut self,
12575 _: &GoToDefinitionSplit,
12576 window: &mut Window,
12577 cx: &mut Context<Self>,
12578 ) -> Task<Result<Navigated>> {
12579 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
12580 }
12581
12582 pub fn go_to_type_definition_split(
12583 &mut self,
12584 _: &GoToTypeDefinitionSplit,
12585 window: &mut Window,
12586 cx: &mut Context<Self>,
12587 ) -> Task<Result<Navigated>> {
12588 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
12589 }
12590
12591 fn go_to_definition_of_kind(
12592 &mut self,
12593 kind: GotoDefinitionKind,
12594 split: bool,
12595 window: &mut Window,
12596 cx: &mut Context<Self>,
12597 ) -> Task<Result<Navigated>> {
12598 let Some(provider) = self.semantics_provider.clone() else {
12599 return Task::ready(Ok(Navigated::No));
12600 };
12601 let head = self.selections.newest::<usize>(cx).head();
12602 let buffer = self.buffer.read(cx);
12603 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
12604 text_anchor
12605 } else {
12606 return Task::ready(Ok(Navigated::No));
12607 };
12608
12609 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
12610 return Task::ready(Ok(Navigated::No));
12611 };
12612
12613 cx.spawn_in(window, async move |editor, cx| {
12614 let definitions = definitions.await?;
12615 let navigated = editor
12616 .update_in(cx, |editor, window, cx| {
12617 editor.navigate_to_hover_links(
12618 Some(kind),
12619 definitions
12620 .into_iter()
12621 .filter(|location| {
12622 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
12623 })
12624 .map(HoverLink::Text)
12625 .collect::<Vec<_>>(),
12626 split,
12627 window,
12628 cx,
12629 )
12630 })?
12631 .await?;
12632 anyhow::Ok(navigated)
12633 })
12634 }
12635
12636 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
12637 let selection = self.selections.newest_anchor();
12638 let head = selection.head();
12639 let tail = selection.tail();
12640
12641 let Some((buffer, start_position)) =
12642 self.buffer.read(cx).text_anchor_for_position(head, cx)
12643 else {
12644 return;
12645 };
12646
12647 let end_position = if head != tail {
12648 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
12649 return;
12650 };
12651 Some(pos)
12652 } else {
12653 None
12654 };
12655
12656 let url_finder = cx.spawn_in(window, async move |editor, cx| {
12657 let url = if let Some(end_pos) = end_position {
12658 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
12659 } else {
12660 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
12661 };
12662
12663 if let Some(url) = url {
12664 editor.update(cx, |_, cx| {
12665 cx.open_url(&url);
12666 })
12667 } else {
12668 Ok(())
12669 }
12670 });
12671
12672 url_finder.detach();
12673 }
12674
12675 pub fn open_selected_filename(
12676 &mut self,
12677 _: &OpenSelectedFilename,
12678 window: &mut Window,
12679 cx: &mut Context<Self>,
12680 ) {
12681 let Some(workspace) = self.workspace() else {
12682 return;
12683 };
12684
12685 let position = self.selections.newest_anchor().head();
12686
12687 let Some((buffer, buffer_position)) =
12688 self.buffer.read(cx).text_anchor_for_position(position, cx)
12689 else {
12690 return;
12691 };
12692
12693 let project = self.project.clone();
12694
12695 cx.spawn_in(window, async move |_, cx| {
12696 let result = find_file(&buffer, project, buffer_position, cx).await;
12697
12698 if let Some((_, path)) = result {
12699 workspace
12700 .update_in(cx, |workspace, window, cx| {
12701 workspace.open_resolved_path(path, window, cx)
12702 })?
12703 .await?;
12704 }
12705 anyhow::Ok(())
12706 })
12707 .detach();
12708 }
12709
12710 pub(crate) fn navigate_to_hover_links(
12711 &mut self,
12712 kind: Option<GotoDefinitionKind>,
12713 mut definitions: Vec<HoverLink>,
12714 split: bool,
12715 window: &mut Window,
12716 cx: &mut Context<Editor>,
12717 ) -> Task<Result<Navigated>> {
12718 // If there is one definition, just open it directly
12719 if definitions.len() == 1 {
12720 let definition = definitions.pop().unwrap();
12721
12722 enum TargetTaskResult {
12723 Location(Option<Location>),
12724 AlreadyNavigated,
12725 }
12726
12727 let target_task = match definition {
12728 HoverLink::Text(link) => {
12729 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
12730 }
12731 HoverLink::InlayHint(lsp_location, server_id) => {
12732 let computation =
12733 self.compute_target_location(lsp_location, server_id, window, cx);
12734 cx.background_spawn(async move {
12735 let location = computation.await?;
12736 Ok(TargetTaskResult::Location(location))
12737 })
12738 }
12739 HoverLink::Url(url) => {
12740 cx.open_url(&url);
12741 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
12742 }
12743 HoverLink::File(path) => {
12744 if let Some(workspace) = self.workspace() {
12745 cx.spawn_in(window, async move |_, cx| {
12746 workspace
12747 .update_in(cx, |workspace, window, cx| {
12748 workspace.open_resolved_path(path, window, cx)
12749 })?
12750 .await
12751 .map(|_| TargetTaskResult::AlreadyNavigated)
12752 })
12753 } else {
12754 Task::ready(Ok(TargetTaskResult::Location(None)))
12755 }
12756 }
12757 };
12758 cx.spawn_in(window, async move |editor, cx| {
12759 let target = match target_task.await.context("target resolution task")? {
12760 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
12761 TargetTaskResult::Location(None) => return Ok(Navigated::No),
12762 TargetTaskResult::Location(Some(target)) => target,
12763 };
12764
12765 editor.update_in(cx, |editor, window, cx| {
12766 let Some(workspace) = editor.workspace() else {
12767 return Navigated::No;
12768 };
12769 let pane = workspace.read(cx).active_pane().clone();
12770
12771 let range = target.range.to_point(target.buffer.read(cx));
12772 let range = editor.range_for_match(&range);
12773 let range = collapse_multiline_range(range);
12774
12775 if !split
12776 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
12777 {
12778 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
12779 } else {
12780 window.defer(cx, move |window, cx| {
12781 let target_editor: Entity<Self> =
12782 workspace.update(cx, |workspace, cx| {
12783 let pane = if split {
12784 workspace.adjacent_pane(window, cx)
12785 } else {
12786 workspace.active_pane().clone()
12787 };
12788
12789 workspace.open_project_item(
12790 pane,
12791 target.buffer.clone(),
12792 true,
12793 true,
12794 window,
12795 cx,
12796 )
12797 });
12798 target_editor.update(cx, |target_editor, cx| {
12799 // When selecting a definition in a different buffer, disable the nav history
12800 // to avoid creating a history entry at the previous cursor location.
12801 pane.update(cx, |pane, _| pane.disable_history());
12802 target_editor.go_to_singleton_buffer_range(range, window, cx);
12803 pane.update(cx, |pane, _| pane.enable_history());
12804 });
12805 });
12806 }
12807 Navigated::Yes
12808 })
12809 })
12810 } else if !definitions.is_empty() {
12811 cx.spawn_in(window, async move |editor, cx| {
12812 let (title, location_tasks, workspace) = editor
12813 .update_in(cx, |editor, window, cx| {
12814 let tab_kind = match kind {
12815 Some(GotoDefinitionKind::Implementation) => "Implementations",
12816 _ => "Definitions",
12817 };
12818 let title = definitions
12819 .iter()
12820 .find_map(|definition| match definition {
12821 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
12822 let buffer = origin.buffer.read(cx);
12823 format!(
12824 "{} for {}",
12825 tab_kind,
12826 buffer
12827 .text_for_range(origin.range.clone())
12828 .collect::<String>()
12829 )
12830 }),
12831 HoverLink::InlayHint(_, _) => None,
12832 HoverLink::Url(_) => None,
12833 HoverLink::File(_) => None,
12834 })
12835 .unwrap_or(tab_kind.to_string());
12836 let location_tasks = definitions
12837 .into_iter()
12838 .map(|definition| match definition {
12839 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
12840 HoverLink::InlayHint(lsp_location, server_id) => editor
12841 .compute_target_location(lsp_location, server_id, window, cx),
12842 HoverLink::Url(_) => Task::ready(Ok(None)),
12843 HoverLink::File(_) => Task::ready(Ok(None)),
12844 })
12845 .collect::<Vec<_>>();
12846 (title, location_tasks, editor.workspace().clone())
12847 })
12848 .context("location tasks preparation")?;
12849
12850 let locations = future::join_all(location_tasks)
12851 .await
12852 .into_iter()
12853 .filter_map(|location| location.transpose())
12854 .collect::<Result<_>>()
12855 .context("location tasks")?;
12856
12857 let Some(workspace) = workspace else {
12858 return Ok(Navigated::No);
12859 };
12860 let opened = workspace
12861 .update_in(cx, |workspace, window, cx| {
12862 Self::open_locations_in_multibuffer(
12863 workspace,
12864 locations,
12865 title,
12866 split,
12867 MultibufferSelectionMode::First,
12868 window,
12869 cx,
12870 )
12871 })
12872 .ok();
12873
12874 anyhow::Ok(Navigated::from_bool(opened.is_some()))
12875 })
12876 } else {
12877 Task::ready(Ok(Navigated::No))
12878 }
12879 }
12880
12881 fn compute_target_location(
12882 &self,
12883 lsp_location: lsp::Location,
12884 server_id: LanguageServerId,
12885 window: &mut Window,
12886 cx: &mut Context<Self>,
12887 ) -> Task<anyhow::Result<Option<Location>>> {
12888 let Some(project) = self.project.clone() else {
12889 return Task::ready(Ok(None));
12890 };
12891
12892 cx.spawn_in(window, async move |editor, cx| {
12893 let location_task = editor.update(cx, |_, cx| {
12894 project.update(cx, |project, cx| {
12895 let language_server_name = project
12896 .language_server_statuses(cx)
12897 .find(|(id, _)| server_id == *id)
12898 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
12899 language_server_name.map(|language_server_name| {
12900 project.open_local_buffer_via_lsp(
12901 lsp_location.uri.clone(),
12902 server_id,
12903 language_server_name,
12904 cx,
12905 )
12906 })
12907 })
12908 })?;
12909 let location = match location_task {
12910 Some(task) => Some({
12911 let target_buffer_handle = task.await.context("open local buffer")?;
12912 let range = target_buffer_handle.update(cx, |target_buffer, _| {
12913 let target_start = target_buffer
12914 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
12915 let target_end = target_buffer
12916 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
12917 target_buffer.anchor_after(target_start)
12918 ..target_buffer.anchor_before(target_end)
12919 })?;
12920 Location {
12921 buffer: target_buffer_handle,
12922 range,
12923 }
12924 }),
12925 None => None,
12926 };
12927 Ok(location)
12928 })
12929 }
12930
12931 pub fn find_all_references(
12932 &mut self,
12933 _: &FindAllReferences,
12934 window: &mut Window,
12935 cx: &mut Context<Self>,
12936 ) -> Option<Task<Result<Navigated>>> {
12937 let selection = self.selections.newest::<usize>(cx);
12938 let multi_buffer = self.buffer.read(cx);
12939 let head = selection.head();
12940
12941 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
12942 let head_anchor = multi_buffer_snapshot.anchor_at(
12943 head,
12944 if head < selection.tail() {
12945 Bias::Right
12946 } else {
12947 Bias::Left
12948 },
12949 );
12950
12951 match self
12952 .find_all_references_task_sources
12953 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
12954 {
12955 Ok(_) => {
12956 log::info!(
12957 "Ignoring repeated FindAllReferences invocation with the position of already running task"
12958 );
12959 return None;
12960 }
12961 Err(i) => {
12962 self.find_all_references_task_sources.insert(i, head_anchor);
12963 }
12964 }
12965
12966 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
12967 let workspace = self.workspace()?;
12968 let project = workspace.read(cx).project().clone();
12969 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
12970 Some(cx.spawn_in(window, async move |editor, cx| {
12971 let _cleanup = cx.on_drop(&editor, move |editor, _| {
12972 if let Ok(i) = editor
12973 .find_all_references_task_sources
12974 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
12975 {
12976 editor.find_all_references_task_sources.remove(i);
12977 }
12978 });
12979
12980 let locations = references.await?;
12981 if locations.is_empty() {
12982 return anyhow::Ok(Navigated::No);
12983 }
12984
12985 workspace.update_in(cx, |workspace, window, cx| {
12986 let title = locations
12987 .first()
12988 .as_ref()
12989 .map(|location| {
12990 let buffer = location.buffer.read(cx);
12991 format!(
12992 "References to `{}`",
12993 buffer
12994 .text_for_range(location.range.clone())
12995 .collect::<String>()
12996 )
12997 })
12998 .unwrap();
12999 Self::open_locations_in_multibuffer(
13000 workspace,
13001 locations,
13002 title,
13003 false,
13004 MultibufferSelectionMode::First,
13005 window,
13006 cx,
13007 );
13008 Navigated::Yes
13009 })
13010 }))
13011 }
13012
13013 /// Opens a multibuffer with the given project locations in it
13014 pub fn open_locations_in_multibuffer(
13015 workspace: &mut Workspace,
13016 mut locations: Vec<Location>,
13017 title: String,
13018 split: bool,
13019 multibuffer_selection_mode: MultibufferSelectionMode,
13020 window: &mut Window,
13021 cx: &mut Context<Workspace>,
13022 ) {
13023 // If there are multiple definitions, open them in a multibuffer
13024 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
13025 let mut locations = locations.into_iter().peekable();
13026 let mut ranges = Vec::new();
13027 let capability = workspace.project().read(cx).capability();
13028
13029 let excerpt_buffer = cx.new(|cx| {
13030 let mut multibuffer = MultiBuffer::new(capability);
13031 while let Some(location) = locations.next() {
13032 let buffer = location.buffer.read(cx);
13033 let mut ranges_for_buffer = Vec::new();
13034 let range = location.range.to_offset(buffer);
13035 ranges_for_buffer.push(range.clone());
13036
13037 while let Some(next_location) = locations.peek() {
13038 if next_location.buffer == location.buffer {
13039 ranges_for_buffer.push(next_location.range.to_offset(buffer));
13040 locations.next();
13041 } else {
13042 break;
13043 }
13044 }
13045
13046 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
13047 ranges.extend(multibuffer.push_excerpts_with_context_lines(
13048 location.buffer.clone(),
13049 ranges_for_buffer,
13050 DEFAULT_MULTIBUFFER_CONTEXT,
13051 cx,
13052 ))
13053 }
13054
13055 multibuffer.with_title(title)
13056 });
13057
13058 let editor = cx.new(|cx| {
13059 Editor::for_multibuffer(
13060 excerpt_buffer,
13061 Some(workspace.project().clone()),
13062 window,
13063 cx,
13064 )
13065 });
13066 editor.update(cx, |editor, cx| {
13067 match multibuffer_selection_mode {
13068 MultibufferSelectionMode::First => {
13069 if let Some(first_range) = ranges.first() {
13070 editor.change_selections(None, window, cx, |selections| {
13071 selections.clear_disjoint();
13072 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
13073 });
13074 }
13075 editor.highlight_background::<Self>(
13076 &ranges,
13077 |theme| theme.editor_highlighted_line_background,
13078 cx,
13079 );
13080 }
13081 MultibufferSelectionMode::All => {
13082 editor.change_selections(None, window, cx, |selections| {
13083 selections.clear_disjoint();
13084 selections.select_anchor_ranges(ranges);
13085 });
13086 }
13087 }
13088 editor.register_buffers_with_language_servers(cx);
13089 });
13090
13091 let item = Box::new(editor);
13092 let item_id = item.item_id();
13093
13094 if split {
13095 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
13096 } else {
13097 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
13098 let (preview_item_id, preview_item_idx) =
13099 workspace.active_pane().update(cx, |pane, _| {
13100 (pane.preview_item_id(), pane.preview_item_idx())
13101 });
13102
13103 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
13104
13105 if let Some(preview_item_id) = preview_item_id {
13106 workspace.active_pane().update(cx, |pane, cx| {
13107 pane.remove_item(preview_item_id, false, false, window, cx);
13108 });
13109 }
13110 } else {
13111 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
13112 }
13113 }
13114 workspace.active_pane().update(cx, |pane, cx| {
13115 pane.set_preview_item_id(Some(item_id), cx);
13116 });
13117 }
13118
13119 pub fn rename(
13120 &mut self,
13121 _: &Rename,
13122 window: &mut Window,
13123 cx: &mut Context<Self>,
13124 ) -> Option<Task<Result<()>>> {
13125 use language::ToOffset as _;
13126
13127 let provider = self.semantics_provider.clone()?;
13128 let selection = self.selections.newest_anchor().clone();
13129 let (cursor_buffer, cursor_buffer_position) = self
13130 .buffer
13131 .read(cx)
13132 .text_anchor_for_position(selection.head(), cx)?;
13133 let (tail_buffer, cursor_buffer_position_end) = self
13134 .buffer
13135 .read(cx)
13136 .text_anchor_for_position(selection.tail(), cx)?;
13137 if tail_buffer != cursor_buffer {
13138 return None;
13139 }
13140
13141 let snapshot = cursor_buffer.read(cx).snapshot();
13142 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
13143 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
13144 let prepare_rename = provider
13145 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
13146 .unwrap_or_else(|| Task::ready(Ok(None)));
13147 drop(snapshot);
13148
13149 Some(cx.spawn_in(window, async move |this, cx| {
13150 let rename_range = if let Some(range) = prepare_rename.await? {
13151 Some(range)
13152 } else {
13153 this.update(cx, |this, cx| {
13154 let buffer = this.buffer.read(cx).snapshot(cx);
13155 let mut buffer_highlights = this
13156 .document_highlights_for_position(selection.head(), &buffer)
13157 .filter(|highlight| {
13158 highlight.start.excerpt_id == selection.head().excerpt_id
13159 && highlight.end.excerpt_id == selection.head().excerpt_id
13160 });
13161 buffer_highlights
13162 .next()
13163 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
13164 })?
13165 };
13166 if let Some(rename_range) = rename_range {
13167 this.update_in(cx, |this, window, cx| {
13168 let snapshot = cursor_buffer.read(cx).snapshot();
13169 let rename_buffer_range = rename_range.to_offset(&snapshot);
13170 let cursor_offset_in_rename_range =
13171 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
13172 let cursor_offset_in_rename_range_end =
13173 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
13174
13175 this.take_rename(false, window, cx);
13176 let buffer = this.buffer.read(cx).read(cx);
13177 let cursor_offset = selection.head().to_offset(&buffer);
13178 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
13179 let rename_end = rename_start + rename_buffer_range.len();
13180 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
13181 let mut old_highlight_id = None;
13182 let old_name: Arc<str> = buffer
13183 .chunks(rename_start..rename_end, true)
13184 .map(|chunk| {
13185 if old_highlight_id.is_none() {
13186 old_highlight_id = chunk.syntax_highlight_id;
13187 }
13188 chunk.text
13189 })
13190 .collect::<String>()
13191 .into();
13192
13193 drop(buffer);
13194
13195 // Position the selection in the rename editor so that it matches the current selection.
13196 this.show_local_selections = false;
13197 let rename_editor = cx.new(|cx| {
13198 let mut editor = Editor::single_line(window, cx);
13199 editor.buffer.update(cx, |buffer, cx| {
13200 buffer.edit([(0..0, old_name.clone())], None, cx)
13201 });
13202 let rename_selection_range = match cursor_offset_in_rename_range
13203 .cmp(&cursor_offset_in_rename_range_end)
13204 {
13205 Ordering::Equal => {
13206 editor.select_all(&SelectAll, window, cx);
13207 return editor;
13208 }
13209 Ordering::Less => {
13210 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
13211 }
13212 Ordering::Greater => {
13213 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
13214 }
13215 };
13216 if rename_selection_range.end > old_name.len() {
13217 editor.select_all(&SelectAll, window, cx);
13218 } else {
13219 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13220 s.select_ranges([rename_selection_range]);
13221 });
13222 }
13223 editor
13224 });
13225 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
13226 if e == &EditorEvent::Focused {
13227 cx.emit(EditorEvent::FocusedIn)
13228 }
13229 })
13230 .detach();
13231
13232 let write_highlights =
13233 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
13234 let read_highlights =
13235 this.clear_background_highlights::<DocumentHighlightRead>(cx);
13236 let ranges = write_highlights
13237 .iter()
13238 .flat_map(|(_, ranges)| ranges.iter())
13239 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
13240 .cloned()
13241 .collect();
13242
13243 this.highlight_text::<Rename>(
13244 ranges,
13245 HighlightStyle {
13246 fade_out: Some(0.6),
13247 ..Default::default()
13248 },
13249 cx,
13250 );
13251 let rename_focus_handle = rename_editor.focus_handle(cx);
13252 window.focus(&rename_focus_handle);
13253 let block_id = this.insert_blocks(
13254 [BlockProperties {
13255 style: BlockStyle::Flex,
13256 placement: BlockPlacement::Below(range.start),
13257 height: 1,
13258 render: Arc::new({
13259 let rename_editor = rename_editor.clone();
13260 move |cx: &mut BlockContext| {
13261 let mut text_style = cx.editor_style.text.clone();
13262 if let Some(highlight_style) = old_highlight_id
13263 .and_then(|h| h.style(&cx.editor_style.syntax))
13264 {
13265 text_style = text_style.highlight(highlight_style);
13266 }
13267 div()
13268 .block_mouse_down()
13269 .pl(cx.anchor_x)
13270 .child(EditorElement::new(
13271 &rename_editor,
13272 EditorStyle {
13273 background: cx.theme().system().transparent,
13274 local_player: cx.editor_style.local_player,
13275 text: text_style,
13276 scrollbar_width: cx.editor_style.scrollbar_width,
13277 syntax: cx.editor_style.syntax.clone(),
13278 status: cx.editor_style.status.clone(),
13279 inlay_hints_style: HighlightStyle {
13280 font_weight: Some(FontWeight::BOLD),
13281 ..make_inlay_hints_style(cx.app)
13282 },
13283 inline_completion_styles: make_suggestion_styles(
13284 cx.app,
13285 ),
13286 ..EditorStyle::default()
13287 },
13288 ))
13289 .into_any_element()
13290 }
13291 }),
13292 priority: 0,
13293 }],
13294 Some(Autoscroll::fit()),
13295 cx,
13296 )[0];
13297 this.pending_rename = Some(RenameState {
13298 range,
13299 old_name,
13300 editor: rename_editor,
13301 block_id,
13302 });
13303 })?;
13304 }
13305
13306 Ok(())
13307 }))
13308 }
13309
13310 pub fn confirm_rename(
13311 &mut self,
13312 _: &ConfirmRename,
13313 window: &mut Window,
13314 cx: &mut Context<Self>,
13315 ) -> Option<Task<Result<()>>> {
13316 let rename = self.take_rename(false, window, cx)?;
13317 let workspace = self.workspace()?.downgrade();
13318 let (buffer, start) = self
13319 .buffer
13320 .read(cx)
13321 .text_anchor_for_position(rename.range.start, cx)?;
13322 let (end_buffer, _) = self
13323 .buffer
13324 .read(cx)
13325 .text_anchor_for_position(rename.range.end, cx)?;
13326 if buffer != end_buffer {
13327 return None;
13328 }
13329
13330 let old_name = rename.old_name;
13331 let new_name = rename.editor.read(cx).text(cx);
13332
13333 let rename = self.semantics_provider.as_ref()?.perform_rename(
13334 &buffer,
13335 start,
13336 new_name.clone(),
13337 cx,
13338 )?;
13339
13340 Some(cx.spawn_in(window, async move |editor, cx| {
13341 let project_transaction = rename.await?;
13342 Self::open_project_transaction(
13343 &editor,
13344 workspace,
13345 project_transaction,
13346 format!("Rename: {} → {}", old_name, new_name),
13347 cx,
13348 )
13349 .await?;
13350
13351 editor.update(cx, |editor, cx| {
13352 editor.refresh_document_highlights(cx);
13353 })?;
13354 Ok(())
13355 }))
13356 }
13357
13358 fn take_rename(
13359 &mut self,
13360 moving_cursor: bool,
13361 window: &mut Window,
13362 cx: &mut Context<Self>,
13363 ) -> Option<RenameState> {
13364 let rename = self.pending_rename.take()?;
13365 if rename.editor.focus_handle(cx).is_focused(window) {
13366 window.focus(&self.focus_handle);
13367 }
13368
13369 self.remove_blocks(
13370 [rename.block_id].into_iter().collect(),
13371 Some(Autoscroll::fit()),
13372 cx,
13373 );
13374 self.clear_highlights::<Rename>(cx);
13375 self.show_local_selections = true;
13376
13377 if moving_cursor {
13378 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
13379 editor.selections.newest::<usize>(cx).head()
13380 });
13381
13382 // Update the selection to match the position of the selection inside
13383 // the rename editor.
13384 let snapshot = self.buffer.read(cx).read(cx);
13385 let rename_range = rename.range.to_offset(&snapshot);
13386 let cursor_in_editor = snapshot
13387 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
13388 .min(rename_range.end);
13389 drop(snapshot);
13390
13391 self.change_selections(None, window, cx, |s| {
13392 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
13393 });
13394 } else {
13395 self.refresh_document_highlights(cx);
13396 }
13397
13398 Some(rename)
13399 }
13400
13401 pub fn pending_rename(&self) -> Option<&RenameState> {
13402 self.pending_rename.as_ref()
13403 }
13404
13405 fn format(
13406 &mut self,
13407 _: &Format,
13408 window: &mut Window,
13409 cx: &mut Context<Self>,
13410 ) -> Option<Task<Result<()>>> {
13411 let project = match &self.project {
13412 Some(project) => project.clone(),
13413 None => return None,
13414 };
13415
13416 Some(self.perform_format(
13417 project,
13418 FormatTrigger::Manual,
13419 FormatTarget::Buffers,
13420 window,
13421 cx,
13422 ))
13423 }
13424
13425 fn format_selections(
13426 &mut self,
13427 _: &FormatSelections,
13428 window: &mut Window,
13429 cx: &mut Context<Self>,
13430 ) -> Option<Task<Result<()>>> {
13431 let project = match &self.project {
13432 Some(project) => project.clone(),
13433 None => return None,
13434 };
13435
13436 let ranges = self
13437 .selections
13438 .all_adjusted(cx)
13439 .into_iter()
13440 .map(|selection| selection.range())
13441 .collect_vec();
13442
13443 Some(self.perform_format(
13444 project,
13445 FormatTrigger::Manual,
13446 FormatTarget::Ranges(ranges),
13447 window,
13448 cx,
13449 ))
13450 }
13451
13452 fn perform_format(
13453 &mut self,
13454 project: Entity<Project>,
13455 trigger: FormatTrigger,
13456 target: FormatTarget,
13457 window: &mut Window,
13458 cx: &mut Context<Self>,
13459 ) -> Task<Result<()>> {
13460 let buffer = self.buffer.clone();
13461 let (buffers, target) = match target {
13462 FormatTarget::Buffers => {
13463 let mut buffers = buffer.read(cx).all_buffers();
13464 if trigger == FormatTrigger::Save {
13465 buffers.retain(|buffer| buffer.read(cx).is_dirty());
13466 }
13467 (buffers, LspFormatTarget::Buffers)
13468 }
13469 FormatTarget::Ranges(selection_ranges) => {
13470 let multi_buffer = buffer.read(cx);
13471 let snapshot = multi_buffer.read(cx);
13472 let mut buffers = HashSet::default();
13473 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
13474 BTreeMap::new();
13475 for selection_range in selection_ranges {
13476 for (buffer, buffer_range, _) in
13477 snapshot.range_to_buffer_ranges(selection_range)
13478 {
13479 let buffer_id = buffer.remote_id();
13480 let start = buffer.anchor_before(buffer_range.start);
13481 let end = buffer.anchor_after(buffer_range.end);
13482 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
13483 buffer_id_to_ranges
13484 .entry(buffer_id)
13485 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
13486 .or_insert_with(|| vec![start..end]);
13487 }
13488 }
13489 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
13490 }
13491 };
13492
13493 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
13494 let format = project.update(cx, |project, cx| {
13495 project.format(buffers, target, true, trigger, cx)
13496 });
13497
13498 cx.spawn_in(window, async move |_, cx| {
13499 let transaction = futures::select_biased! {
13500 transaction = format.log_err().fuse() => transaction,
13501 () = timeout => {
13502 log::warn!("timed out waiting for formatting");
13503 None
13504 }
13505 };
13506
13507 buffer
13508 .update(cx, |buffer, cx| {
13509 if let Some(transaction) = transaction {
13510 if !buffer.is_singleton() {
13511 buffer.push_transaction(&transaction.0, cx);
13512 }
13513 }
13514 cx.notify();
13515 })
13516 .ok();
13517
13518 Ok(())
13519 })
13520 }
13521
13522 fn organize_imports(
13523 &mut self,
13524 _: &OrganizeImports,
13525 window: &mut Window,
13526 cx: &mut Context<Self>,
13527 ) -> Option<Task<Result<()>>> {
13528 let project = match &self.project {
13529 Some(project) => project.clone(),
13530 None => return None,
13531 };
13532 Some(self.perform_code_action_kind(
13533 project,
13534 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
13535 window,
13536 cx,
13537 ))
13538 }
13539
13540 fn perform_code_action_kind(
13541 &mut self,
13542 project: Entity<Project>,
13543 kind: CodeActionKind,
13544 window: &mut Window,
13545 cx: &mut Context<Self>,
13546 ) -> Task<Result<()>> {
13547 let buffer = self.buffer.clone();
13548 let buffers = buffer.read(cx).all_buffers();
13549 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
13550 let apply_action = project.update(cx, |project, cx| {
13551 project.apply_code_action_kind(buffers, kind, true, cx)
13552 });
13553 cx.spawn_in(window, async move |_, cx| {
13554 let transaction = futures::select_biased! {
13555 () = timeout => {
13556 log::warn!("timed out waiting for executing code action");
13557 None
13558 }
13559 transaction = apply_action.log_err().fuse() => transaction,
13560 };
13561 buffer
13562 .update(cx, |buffer, cx| {
13563 // check if we need this
13564 if let Some(transaction) = transaction {
13565 if !buffer.is_singleton() {
13566 buffer.push_transaction(&transaction.0, cx);
13567 }
13568 }
13569 cx.notify();
13570 })
13571 .ok();
13572 Ok(())
13573 })
13574 }
13575
13576 fn restart_language_server(
13577 &mut self,
13578 _: &RestartLanguageServer,
13579 _: &mut Window,
13580 cx: &mut Context<Self>,
13581 ) {
13582 if let Some(project) = self.project.clone() {
13583 self.buffer.update(cx, |multi_buffer, cx| {
13584 project.update(cx, |project, cx| {
13585 project.restart_language_servers_for_buffers(
13586 multi_buffer.all_buffers().into_iter().collect(),
13587 cx,
13588 );
13589 });
13590 })
13591 }
13592 }
13593
13594 fn cancel_language_server_work(
13595 workspace: &mut Workspace,
13596 _: &actions::CancelLanguageServerWork,
13597 _: &mut Window,
13598 cx: &mut Context<Workspace>,
13599 ) {
13600 let project = workspace.project();
13601 let buffers = workspace
13602 .active_item(cx)
13603 .and_then(|item| item.act_as::<Editor>(cx))
13604 .map_or(HashSet::default(), |editor| {
13605 editor.read(cx).buffer.read(cx).all_buffers()
13606 });
13607 project.update(cx, |project, cx| {
13608 project.cancel_language_server_work_for_buffers(buffers, cx);
13609 });
13610 }
13611
13612 fn show_character_palette(
13613 &mut self,
13614 _: &ShowCharacterPalette,
13615 window: &mut Window,
13616 _: &mut Context<Self>,
13617 ) {
13618 window.show_character_palette();
13619 }
13620
13621 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
13622 if let Some(active_diagnostics) = self.active_diagnostics.as_mut() {
13623 let buffer = self.buffer.read(cx).snapshot(cx);
13624 let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer);
13625 let primary_range_end = active_diagnostics.primary_range.end.to_offset(&buffer);
13626 let is_valid = buffer
13627 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
13628 .any(|entry| {
13629 entry.diagnostic.is_primary
13630 && !entry.range.is_empty()
13631 && entry.range.start == primary_range_start
13632 && entry.diagnostic.message == active_diagnostics.primary_message
13633 });
13634
13635 if is_valid != active_diagnostics.is_valid {
13636 active_diagnostics.is_valid = is_valid;
13637 if is_valid {
13638 let mut new_styles = HashMap::default();
13639 for (block_id, diagnostic) in &active_diagnostics.blocks {
13640 new_styles.insert(
13641 *block_id,
13642 diagnostic_block_renderer(diagnostic.clone(), None, true),
13643 );
13644 }
13645 self.display_map.update(cx, |display_map, _cx| {
13646 display_map.replace_blocks(new_styles);
13647 });
13648 } else {
13649 self.dismiss_diagnostics(cx);
13650 }
13651 }
13652 }
13653 }
13654
13655 fn activate_diagnostics(
13656 &mut self,
13657 buffer_id: BufferId,
13658 group_id: usize,
13659 window: &mut Window,
13660 cx: &mut Context<Self>,
13661 ) {
13662 self.dismiss_diagnostics(cx);
13663 let snapshot = self.snapshot(window, cx);
13664 self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
13665 let buffer = self.buffer.read(cx).snapshot(cx);
13666
13667 let mut primary_range = None;
13668 let mut primary_message = None;
13669 let diagnostic_group = buffer
13670 .diagnostic_group(buffer_id, group_id)
13671 .filter_map(|entry| {
13672 let start = entry.range.start;
13673 let end = entry.range.end;
13674 if snapshot.is_line_folded(MultiBufferRow(start.row))
13675 && (start.row == end.row
13676 || snapshot.is_line_folded(MultiBufferRow(end.row)))
13677 {
13678 return None;
13679 }
13680 if entry.diagnostic.is_primary {
13681 primary_range = Some(entry.range.clone());
13682 primary_message = Some(entry.diagnostic.message.clone());
13683 }
13684 Some(entry)
13685 })
13686 .collect::<Vec<_>>();
13687 let primary_range = primary_range?;
13688 let primary_message = primary_message?;
13689
13690 let blocks = display_map
13691 .insert_blocks(
13692 diagnostic_group.iter().map(|entry| {
13693 let diagnostic = entry.diagnostic.clone();
13694 let message_height = diagnostic.message.matches('\n').count() as u32 + 1;
13695 BlockProperties {
13696 style: BlockStyle::Fixed,
13697 placement: BlockPlacement::Below(
13698 buffer.anchor_after(entry.range.start),
13699 ),
13700 height: message_height,
13701 render: diagnostic_block_renderer(diagnostic, None, true),
13702 priority: 0,
13703 }
13704 }),
13705 cx,
13706 )
13707 .into_iter()
13708 .zip(diagnostic_group.into_iter().map(|entry| entry.diagnostic))
13709 .collect();
13710
13711 Some(ActiveDiagnosticGroup {
13712 primary_range: buffer.anchor_before(primary_range.start)
13713 ..buffer.anchor_after(primary_range.end),
13714 primary_message,
13715 group_id,
13716 blocks,
13717 is_valid: true,
13718 })
13719 });
13720 }
13721
13722 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
13723 if let Some(active_diagnostic_group) = self.active_diagnostics.take() {
13724 self.display_map.update(cx, |display_map, cx| {
13725 display_map.remove_blocks(active_diagnostic_group.blocks.into_keys().collect(), cx);
13726 });
13727 cx.notify();
13728 }
13729 }
13730
13731 /// Disable inline diagnostics rendering for this editor.
13732 pub fn disable_inline_diagnostics(&mut self) {
13733 self.inline_diagnostics_enabled = false;
13734 self.inline_diagnostics_update = Task::ready(());
13735 self.inline_diagnostics.clear();
13736 }
13737
13738 pub fn inline_diagnostics_enabled(&self) -> bool {
13739 self.inline_diagnostics_enabled
13740 }
13741
13742 pub fn show_inline_diagnostics(&self) -> bool {
13743 self.show_inline_diagnostics
13744 }
13745
13746 pub fn toggle_inline_diagnostics(
13747 &mut self,
13748 _: &ToggleInlineDiagnostics,
13749 window: &mut Window,
13750 cx: &mut Context<'_, Editor>,
13751 ) {
13752 self.show_inline_diagnostics = !self.show_inline_diagnostics;
13753 self.refresh_inline_diagnostics(false, window, cx);
13754 }
13755
13756 fn refresh_inline_diagnostics(
13757 &mut self,
13758 debounce: bool,
13759 window: &mut Window,
13760 cx: &mut Context<Self>,
13761 ) {
13762 if !self.inline_diagnostics_enabled || !self.show_inline_diagnostics {
13763 self.inline_diagnostics_update = Task::ready(());
13764 self.inline_diagnostics.clear();
13765 return;
13766 }
13767
13768 let debounce_ms = ProjectSettings::get_global(cx)
13769 .diagnostics
13770 .inline
13771 .update_debounce_ms;
13772 let debounce = if debounce && debounce_ms > 0 {
13773 Some(Duration::from_millis(debounce_ms))
13774 } else {
13775 None
13776 };
13777 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
13778 if let Some(debounce) = debounce {
13779 cx.background_executor().timer(debounce).await;
13780 }
13781 let Some(snapshot) = editor
13782 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
13783 .ok()
13784 else {
13785 return;
13786 };
13787
13788 let new_inline_diagnostics = cx
13789 .background_spawn(async move {
13790 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
13791 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
13792 let message = diagnostic_entry
13793 .diagnostic
13794 .message
13795 .split_once('\n')
13796 .map(|(line, _)| line)
13797 .map(SharedString::new)
13798 .unwrap_or_else(|| {
13799 SharedString::from(diagnostic_entry.diagnostic.message)
13800 });
13801 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
13802 let (Ok(i) | Err(i)) = inline_diagnostics
13803 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
13804 inline_diagnostics.insert(
13805 i,
13806 (
13807 start_anchor,
13808 InlineDiagnostic {
13809 message,
13810 group_id: diagnostic_entry.diagnostic.group_id,
13811 start: diagnostic_entry.range.start.to_point(&snapshot),
13812 is_primary: diagnostic_entry.diagnostic.is_primary,
13813 severity: diagnostic_entry.diagnostic.severity,
13814 },
13815 ),
13816 );
13817 }
13818 inline_diagnostics
13819 })
13820 .await;
13821
13822 editor
13823 .update(cx, |editor, cx| {
13824 editor.inline_diagnostics = new_inline_diagnostics;
13825 cx.notify();
13826 })
13827 .ok();
13828 });
13829 }
13830
13831 pub fn set_selections_from_remote(
13832 &mut self,
13833 selections: Vec<Selection<Anchor>>,
13834 pending_selection: Option<Selection<Anchor>>,
13835 window: &mut Window,
13836 cx: &mut Context<Self>,
13837 ) {
13838 let old_cursor_position = self.selections.newest_anchor().head();
13839 self.selections.change_with(cx, |s| {
13840 s.select_anchors(selections);
13841 if let Some(pending_selection) = pending_selection {
13842 s.set_pending(pending_selection, SelectMode::Character);
13843 } else {
13844 s.clear_pending();
13845 }
13846 });
13847 self.selections_did_change(false, &old_cursor_position, true, window, cx);
13848 }
13849
13850 fn push_to_selection_history(&mut self) {
13851 self.selection_history.push(SelectionHistoryEntry {
13852 selections: self.selections.disjoint_anchors(),
13853 select_next_state: self.select_next_state.clone(),
13854 select_prev_state: self.select_prev_state.clone(),
13855 add_selections_state: self.add_selections_state.clone(),
13856 });
13857 }
13858
13859 pub fn transact(
13860 &mut self,
13861 window: &mut Window,
13862 cx: &mut Context<Self>,
13863 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
13864 ) -> Option<TransactionId> {
13865 self.start_transaction_at(Instant::now(), window, cx);
13866 update(self, window, cx);
13867 self.end_transaction_at(Instant::now(), cx)
13868 }
13869
13870 pub fn start_transaction_at(
13871 &mut self,
13872 now: Instant,
13873 window: &mut Window,
13874 cx: &mut Context<Self>,
13875 ) {
13876 self.end_selection(window, cx);
13877 if let Some(tx_id) = self
13878 .buffer
13879 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
13880 {
13881 self.selection_history
13882 .insert_transaction(tx_id, self.selections.disjoint_anchors());
13883 cx.emit(EditorEvent::TransactionBegun {
13884 transaction_id: tx_id,
13885 })
13886 }
13887 }
13888
13889 pub fn end_transaction_at(
13890 &mut self,
13891 now: Instant,
13892 cx: &mut Context<Self>,
13893 ) -> Option<TransactionId> {
13894 if let Some(transaction_id) = self
13895 .buffer
13896 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
13897 {
13898 if let Some((_, end_selections)) =
13899 self.selection_history.transaction_mut(transaction_id)
13900 {
13901 *end_selections = Some(self.selections.disjoint_anchors());
13902 } else {
13903 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
13904 }
13905
13906 cx.emit(EditorEvent::Edited { transaction_id });
13907 Some(transaction_id)
13908 } else {
13909 None
13910 }
13911 }
13912
13913 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
13914 if self.selection_mark_mode {
13915 self.change_selections(None, window, cx, |s| {
13916 s.move_with(|_, sel| {
13917 sel.collapse_to(sel.head(), SelectionGoal::None);
13918 });
13919 })
13920 }
13921 self.selection_mark_mode = true;
13922 cx.notify();
13923 }
13924
13925 pub fn swap_selection_ends(
13926 &mut self,
13927 _: &actions::SwapSelectionEnds,
13928 window: &mut Window,
13929 cx: &mut Context<Self>,
13930 ) {
13931 self.change_selections(None, window, cx, |s| {
13932 s.move_with(|_, sel| {
13933 if sel.start != sel.end {
13934 sel.reversed = !sel.reversed
13935 }
13936 });
13937 });
13938 self.request_autoscroll(Autoscroll::newest(), cx);
13939 cx.notify();
13940 }
13941
13942 pub fn toggle_fold(
13943 &mut self,
13944 _: &actions::ToggleFold,
13945 window: &mut Window,
13946 cx: &mut Context<Self>,
13947 ) {
13948 if self.is_singleton(cx) {
13949 let selection = self.selections.newest::<Point>(cx);
13950
13951 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13952 let range = if selection.is_empty() {
13953 let point = selection.head().to_display_point(&display_map);
13954 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
13955 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
13956 .to_point(&display_map);
13957 start..end
13958 } else {
13959 selection.range()
13960 };
13961 if display_map.folds_in_range(range).next().is_some() {
13962 self.unfold_lines(&Default::default(), window, cx)
13963 } else {
13964 self.fold(&Default::default(), window, cx)
13965 }
13966 } else {
13967 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
13968 let buffer_ids: HashSet<_> = self
13969 .selections
13970 .disjoint_anchor_ranges()
13971 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
13972 .collect();
13973
13974 let should_unfold = buffer_ids
13975 .iter()
13976 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
13977
13978 for buffer_id in buffer_ids {
13979 if should_unfold {
13980 self.unfold_buffer(buffer_id, cx);
13981 } else {
13982 self.fold_buffer(buffer_id, cx);
13983 }
13984 }
13985 }
13986 }
13987
13988 pub fn toggle_fold_recursive(
13989 &mut self,
13990 _: &actions::ToggleFoldRecursive,
13991 window: &mut Window,
13992 cx: &mut Context<Self>,
13993 ) {
13994 let selection = self.selections.newest::<Point>(cx);
13995
13996 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13997 let range = if selection.is_empty() {
13998 let point = selection.head().to_display_point(&display_map);
13999 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14000 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14001 .to_point(&display_map);
14002 start..end
14003 } else {
14004 selection.range()
14005 };
14006 if display_map.folds_in_range(range).next().is_some() {
14007 self.unfold_recursive(&Default::default(), window, cx)
14008 } else {
14009 self.fold_recursive(&Default::default(), window, cx)
14010 }
14011 }
14012
14013 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
14014 if self.is_singleton(cx) {
14015 let mut to_fold = Vec::new();
14016 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14017 let selections = self.selections.all_adjusted(cx);
14018
14019 for selection in selections {
14020 let range = selection.range().sorted();
14021 let buffer_start_row = range.start.row;
14022
14023 if range.start.row != range.end.row {
14024 let mut found = false;
14025 let mut row = range.start.row;
14026 while row <= range.end.row {
14027 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
14028 {
14029 found = true;
14030 row = crease.range().end.row + 1;
14031 to_fold.push(crease);
14032 } else {
14033 row += 1
14034 }
14035 }
14036 if found {
14037 continue;
14038 }
14039 }
14040
14041 for row in (0..=range.start.row).rev() {
14042 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14043 if crease.range().end.row >= buffer_start_row {
14044 to_fold.push(crease);
14045 if row <= range.start.row {
14046 break;
14047 }
14048 }
14049 }
14050 }
14051 }
14052
14053 self.fold_creases(to_fold, true, window, cx);
14054 } else {
14055 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14056 let buffer_ids = self
14057 .selections
14058 .disjoint_anchor_ranges()
14059 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14060 .collect::<HashSet<_>>();
14061 for buffer_id in buffer_ids {
14062 self.fold_buffer(buffer_id, cx);
14063 }
14064 }
14065 }
14066
14067 fn fold_at_level(
14068 &mut self,
14069 fold_at: &FoldAtLevel,
14070 window: &mut Window,
14071 cx: &mut Context<Self>,
14072 ) {
14073 if !self.buffer.read(cx).is_singleton() {
14074 return;
14075 }
14076
14077 let fold_at_level = fold_at.0;
14078 let snapshot = self.buffer.read(cx).snapshot(cx);
14079 let mut to_fold = Vec::new();
14080 let mut stack = vec![(0, snapshot.max_row().0, 1)];
14081
14082 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
14083 while start_row < end_row {
14084 match self
14085 .snapshot(window, cx)
14086 .crease_for_buffer_row(MultiBufferRow(start_row))
14087 {
14088 Some(crease) => {
14089 let nested_start_row = crease.range().start.row + 1;
14090 let nested_end_row = crease.range().end.row;
14091
14092 if current_level < fold_at_level {
14093 stack.push((nested_start_row, nested_end_row, current_level + 1));
14094 } else if current_level == fold_at_level {
14095 to_fold.push(crease);
14096 }
14097
14098 start_row = nested_end_row + 1;
14099 }
14100 None => start_row += 1,
14101 }
14102 }
14103 }
14104
14105 self.fold_creases(to_fold, true, window, cx);
14106 }
14107
14108 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
14109 if self.buffer.read(cx).is_singleton() {
14110 let mut fold_ranges = Vec::new();
14111 let snapshot = self.buffer.read(cx).snapshot(cx);
14112
14113 for row in 0..snapshot.max_row().0 {
14114 if let Some(foldable_range) = self
14115 .snapshot(window, cx)
14116 .crease_for_buffer_row(MultiBufferRow(row))
14117 {
14118 fold_ranges.push(foldable_range);
14119 }
14120 }
14121
14122 self.fold_creases(fold_ranges, true, window, cx);
14123 } else {
14124 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
14125 editor
14126 .update_in(cx, |editor, _, cx| {
14127 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
14128 editor.fold_buffer(buffer_id, cx);
14129 }
14130 })
14131 .ok();
14132 });
14133 }
14134 }
14135
14136 pub fn fold_function_bodies(
14137 &mut self,
14138 _: &actions::FoldFunctionBodies,
14139 window: &mut Window,
14140 cx: &mut Context<Self>,
14141 ) {
14142 let snapshot = self.buffer.read(cx).snapshot(cx);
14143
14144 let ranges = snapshot
14145 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
14146 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
14147 .collect::<Vec<_>>();
14148
14149 let creases = ranges
14150 .into_iter()
14151 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
14152 .collect();
14153
14154 self.fold_creases(creases, true, window, cx);
14155 }
14156
14157 pub fn fold_recursive(
14158 &mut self,
14159 _: &actions::FoldRecursive,
14160 window: &mut Window,
14161 cx: &mut Context<Self>,
14162 ) {
14163 let mut to_fold = Vec::new();
14164 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14165 let selections = self.selections.all_adjusted(cx);
14166
14167 for selection in selections {
14168 let range = selection.range().sorted();
14169 let buffer_start_row = range.start.row;
14170
14171 if range.start.row != range.end.row {
14172 let mut found = false;
14173 for row in range.start.row..=range.end.row {
14174 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14175 found = true;
14176 to_fold.push(crease);
14177 }
14178 }
14179 if found {
14180 continue;
14181 }
14182 }
14183
14184 for row in (0..=range.start.row).rev() {
14185 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14186 if crease.range().end.row >= buffer_start_row {
14187 to_fold.push(crease);
14188 } else {
14189 break;
14190 }
14191 }
14192 }
14193 }
14194
14195 self.fold_creases(to_fold, true, window, cx);
14196 }
14197
14198 pub fn fold_at(&mut self, fold_at: &FoldAt, window: &mut Window, cx: &mut Context<Self>) {
14199 let buffer_row = fold_at.buffer_row;
14200 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14201
14202 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
14203 let autoscroll = self
14204 .selections
14205 .all::<Point>(cx)
14206 .iter()
14207 .any(|selection| crease.range().overlaps(&selection.range()));
14208
14209 self.fold_creases(vec![crease], autoscroll, window, cx);
14210 }
14211 }
14212
14213 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
14214 if self.is_singleton(cx) {
14215 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14216 let buffer = &display_map.buffer_snapshot;
14217 let selections = self.selections.all::<Point>(cx);
14218 let ranges = selections
14219 .iter()
14220 .map(|s| {
14221 let range = s.display_range(&display_map).sorted();
14222 let mut start = range.start.to_point(&display_map);
14223 let mut end = range.end.to_point(&display_map);
14224 start.column = 0;
14225 end.column = buffer.line_len(MultiBufferRow(end.row));
14226 start..end
14227 })
14228 .collect::<Vec<_>>();
14229
14230 self.unfold_ranges(&ranges, true, true, cx);
14231 } else {
14232 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14233 let buffer_ids = self
14234 .selections
14235 .disjoint_anchor_ranges()
14236 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14237 .collect::<HashSet<_>>();
14238 for buffer_id in buffer_ids {
14239 self.unfold_buffer(buffer_id, cx);
14240 }
14241 }
14242 }
14243
14244 pub fn unfold_recursive(
14245 &mut self,
14246 _: &UnfoldRecursive,
14247 _window: &mut Window,
14248 cx: &mut Context<Self>,
14249 ) {
14250 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14251 let selections = self.selections.all::<Point>(cx);
14252 let ranges = selections
14253 .iter()
14254 .map(|s| {
14255 let mut range = s.display_range(&display_map).sorted();
14256 *range.start.column_mut() = 0;
14257 *range.end.column_mut() = display_map.line_len(range.end.row());
14258 let start = range.start.to_point(&display_map);
14259 let end = range.end.to_point(&display_map);
14260 start..end
14261 })
14262 .collect::<Vec<_>>();
14263
14264 self.unfold_ranges(&ranges, true, true, cx);
14265 }
14266
14267 pub fn unfold_at(
14268 &mut self,
14269 unfold_at: &UnfoldAt,
14270 _window: &mut Window,
14271 cx: &mut Context<Self>,
14272 ) {
14273 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14274
14275 let intersection_range = Point::new(unfold_at.buffer_row.0, 0)
14276 ..Point::new(
14277 unfold_at.buffer_row.0,
14278 display_map.buffer_snapshot.line_len(unfold_at.buffer_row),
14279 );
14280
14281 let autoscroll = self
14282 .selections
14283 .all::<Point>(cx)
14284 .iter()
14285 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
14286
14287 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
14288 }
14289
14290 pub fn unfold_all(
14291 &mut self,
14292 _: &actions::UnfoldAll,
14293 _window: &mut Window,
14294 cx: &mut Context<Self>,
14295 ) {
14296 if self.buffer.read(cx).is_singleton() {
14297 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14298 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
14299 } else {
14300 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
14301 editor
14302 .update(cx, |editor, cx| {
14303 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
14304 editor.unfold_buffer(buffer_id, cx);
14305 }
14306 })
14307 .ok();
14308 });
14309 }
14310 }
14311
14312 pub fn fold_selected_ranges(
14313 &mut self,
14314 _: &FoldSelectedRanges,
14315 window: &mut Window,
14316 cx: &mut Context<Self>,
14317 ) {
14318 let selections = self.selections.all::<Point>(cx);
14319 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14320 let line_mode = self.selections.line_mode;
14321 let ranges = selections
14322 .into_iter()
14323 .map(|s| {
14324 if line_mode {
14325 let start = Point::new(s.start.row, 0);
14326 let end = Point::new(
14327 s.end.row,
14328 display_map
14329 .buffer_snapshot
14330 .line_len(MultiBufferRow(s.end.row)),
14331 );
14332 Crease::simple(start..end, display_map.fold_placeholder.clone())
14333 } else {
14334 Crease::simple(s.start..s.end, display_map.fold_placeholder.clone())
14335 }
14336 })
14337 .collect::<Vec<_>>();
14338 self.fold_creases(ranges, true, window, cx);
14339 }
14340
14341 pub fn fold_ranges<T: ToOffset + Clone>(
14342 &mut self,
14343 ranges: Vec<Range<T>>,
14344 auto_scroll: bool,
14345 window: &mut Window,
14346 cx: &mut Context<Self>,
14347 ) {
14348 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14349 let ranges = ranges
14350 .into_iter()
14351 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
14352 .collect::<Vec<_>>();
14353 self.fold_creases(ranges, auto_scroll, window, cx);
14354 }
14355
14356 pub fn fold_creases<T: ToOffset + Clone>(
14357 &mut self,
14358 creases: Vec<Crease<T>>,
14359 auto_scroll: bool,
14360 window: &mut Window,
14361 cx: &mut Context<Self>,
14362 ) {
14363 if creases.is_empty() {
14364 return;
14365 }
14366
14367 let mut buffers_affected = HashSet::default();
14368 let multi_buffer = self.buffer().read(cx);
14369 for crease in &creases {
14370 if let Some((_, buffer, _)) =
14371 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
14372 {
14373 buffers_affected.insert(buffer.read(cx).remote_id());
14374 };
14375 }
14376
14377 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
14378
14379 if auto_scroll {
14380 self.request_autoscroll(Autoscroll::fit(), cx);
14381 }
14382
14383 cx.notify();
14384
14385 if let Some(active_diagnostics) = self.active_diagnostics.take() {
14386 // Clear diagnostics block when folding a range that contains it.
14387 let snapshot = self.snapshot(window, cx);
14388 if snapshot.intersects_fold(active_diagnostics.primary_range.start) {
14389 drop(snapshot);
14390 self.active_diagnostics = Some(active_diagnostics);
14391 self.dismiss_diagnostics(cx);
14392 } else {
14393 self.active_diagnostics = Some(active_diagnostics);
14394 }
14395 }
14396
14397 self.scrollbar_marker_state.dirty = true;
14398 self.folds_did_change(cx);
14399 }
14400
14401 /// Removes any folds whose ranges intersect any of the given ranges.
14402 pub fn unfold_ranges<T: ToOffset + Clone>(
14403 &mut self,
14404 ranges: &[Range<T>],
14405 inclusive: bool,
14406 auto_scroll: bool,
14407 cx: &mut Context<Self>,
14408 ) {
14409 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
14410 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
14411 });
14412 self.folds_did_change(cx);
14413 }
14414
14415 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
14416 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
14417 return;
14418 }
14419 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
14420 self.display_map.update(cx, |display_map, cx| {
14421 display_map.fold_buffers([buffer_id], cx)
14422 });
14423 cx.emit(EditorEvent::BufferFoldToggled {
14424 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
14425 folded: true,
14426 });
14427 cx.notify();
14428 }
14429
14430 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
14431 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
14432 return;
14433 }
14434 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
14435 self.display_map.update(cx, |display_map, cx| {
14436 display_map.unfold_buffers([buffer_id], cx);
14437 });
14438 cx.emit(EditorEvent::BufferFoldToggled {
14439 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
14440 folded: false,
14441 });
14442 cx.notify();
14443 }
14444
14445 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
14446 self.display_map.read(cx).is_buffer_folded(buffer)
14447 }
14448
14449 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
14450 self.display_map.read(cx).folded_buffers()
14451 }
14452
14453 /// Removes any folds with the given ranges.
14454 pub fn remove_folds_with_type<T: ToOffset + Clone>(
14455 &mut self,
14456 ranges: &[Range<T>],
14457 type_id: TypeId,
14458 auto_scroll: bool,
14459 cx: &mut Context<Self>,
14460 ) {
14461 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
14462 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
14463 });
14464 self.folds_did_change(cx);
14465 }
14466
14467 fn remove_folds_with<T: ToOffset + Clone>(
14468 &mut self,
14469 ranges: &[Range<T>],
14470 auto_scroll: bool,
14471 cx: &mut Context<Self>,
14472 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
14473 ) {
14474 if ranges.is_empty() {
14475 return;
14476 }
14477
14478 let mut buffers_affected = HashSet::default();
14479 let multi_buffer = self.buffer().read(cx);
14480 for range in ranges {
14481 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
14482 buffers_affected.insert(buffer.read(cx).remote_id());
14483 };
14484 }
14485
14486 self.display_map.update(cx, update);
14487
14488 if auto_scroll {
14489 self.request_autoscroll(Autoscroll::fit(), cx);
14490 }
14491
14492 cx.notify();
14493 self.scrollbar_marker_state.dirty = true;
14494 self.active_indent_guides_state.dirty = true;
14495 }
14496
14497 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
14498 self.display_map.read(cx).fold_placeholder.clone()
14499 }
14500
14501 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
14502 self.buffer.update(cx, |buffer, cx| {
14503 buffer.set_all_diff_hunks_expanded(cx);
14504 });
14505 }
14506
14507 pub fn expand_all_diff_hunks(
14508 &mut self,
14509 _: &ExpandAllDiffHunks,
14510 _window: &mut Window,
14511 cx: &mut Context<Self>,
14512 ) {
14513 self.buffer.update(cx, |buffer, cx| {
14514 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
14515 });
14516 }
14517
14518 pub fn toggle_selected_diff_hunks(
14519 &mut self,
14520 _: &ToggleSelectedDiffHunks,
14521 _window: &mut Window,
14522 cx: &mut Context<Self>,
14523 ) {
14524 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
14525 self.toggle_diff_hunks_in_ranges(ranges, cx);
14526 }
14527
14528 pub fn diff_hunks_in_ranges<'a>(
14529 &'a self,
14530 ranges: &'a [Range<Anchor>],
14531 buffer: &'a MultiBufferSnapshot,
14532 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
14533 ranges.iter().flat_map(move |range| {
14534 let end_excerpt_id = range.end.excerpt_id;
14535 let range = range.to_point(buffer);
14536 let mut peek_end = range.end;
14537 if range.end.row < buffer.max_row().0 {
14538 peek_end = Point::new(range.end.row + 1, 0);
14539 }
14540 buffer
14541 .diff_hunks_in_range(range.start..peek_end)
14542 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
14543 })
14544 }
14545
14546 pub fn has_stageable_diff_hunks_in_ranges(
14547 &self,
14548 ranges: &[Range<Anchor>],
14549 snapshot: &MultiBufferSnapshot,
14550 ) -> bool {
14551 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
14552 hunks.any(|hunk| hunk.status().has_secondary_hunk())
14553 }
14554
14555 pub fn toggle_staged_selected_diff_hunks(
14556 &mut self,
14557 _: &::git::ToggleStaged,
14558 _: &mut Window,
14559 cx: &mut Context<Self>,
14560 ) {
14561 let snapshot = self.buffer.read(cx).snapshot(cx);
14562 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
14563 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
14564 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
14565 }
14566
14567 pub fn stage_and_next(
14568 &mut self,
14569 _: &::git::StageAndNext,
14570 window: &mut Window,
14571 cx: &mut Context<Self>,
14572 ) {
14573 self.do_stage_or_unstage_and_next(true, window, cx);
14574 }
14575
14576 pub fn unstage_and_next(
14577 &mut self,
14578 _: &::git::UnstageAndNext,
14579 window: &mut Window,
14580 cx: &mut Context<Self>,
14581 ) {
14582 self.do_stage_or_unstage_and_next(false, window, cx);
14583 }
14584
14585 pub fn stage_or_unstage_diff_hunks(
14586 &mut self,
14587 stage: bool,
14588 ranges: Vec<Range<Anchor>>,
14589 cx: &mut Context<Self>,
14590 ) {
14591 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
14592 cx.spawn(async move |this, cx| {
14593 task.await?;
14594 this.update(cx, |this, cx| {
14595 let snapshot = this.buffer.read(cx).snapshot(cx);
14596 let chunk_by = this
14597 .diff_hunks_in_ranges(&ranges, &snapshot)
14598 .chunk_by(|hunk| hunk.buffer_id);
14599 for (buffer_id, hunks) in &chunk_by {
14600 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
14601 }
14602 })
14603 })
14604 .detach_and_log_err(cx);
14605 }
14606
14607 fn save_buffers_for_ranges_if_needed(
14608 &mut self,
14609 ranges: &[Range<Anchor>],
14610 cx: &mut Context<'_, Editor>,
14611 ) -> Task<Result<()>> {
14612 let multibuffer = self.buffer.read(cx);
14613 let snapshot = multibuffer.read(cx);
14614 let buffer_ids: HashSet<_> = ranges
14615 .iter()
14616 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
14617 .collect();
14618 drop(snapshot);
14619
14620 let mut buffers = HashSet::default();
14621 for buffer_id in buffer_ids {
14622 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
14623 let buffer = buffer_entity.read(cx);
14624 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
14625 {
14626 buffers.insert(buffer_entity);
14627 }
14628 }
14629 }
14630
14631 if let Some(project) = &self.project {
14632 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
14633 } else {
14634 Task::ready(Ok(()))
14635 }
14636 }
14637
14638 fn do_stage_or_unstage_and_next(
14639 &mut self,
14640 stage: bool,
14641 window: &mut Window,
14642 cx: &mut Context<Self>,
14643 ) {
14644 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
14645
14646 if ranges.iter().any(|range| range.start != range.end) {
14647 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
14648 return;
14649 }
14650
14651 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
14652 let snapshot = self.snapshot(window, cx);
14653 let position = self.selections.newest::<Point>(cx).head();
14654 let mut row = snapshot
14655 .buffer_snapshot
14656 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14657 .find(|hunk| hunk.row_range.start.0 > position.row)
14658 .map(|hunk| hunk.row_range.start);
14659
14660 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
14661 // Outside of the project diff editor, wrap around to the beginning.
14662 if !all_diff_hunks_expanded {
14663 row = row.or_else(|| {
14664 snapshot
14665 .buffer_snapshot
14666 .diff_hunks_in_range(Point::zero()..position)
14667 .find(|hunk| hunk.row_range.end.0 < position.row)
14668 .map(|hunk| hunk.row_range.start)
14669 });
14670 }
14671
14672 if let Some(row) = row {
14673 let destination = Point::new(row.0, 0);
14674 let autoscroll = Autoscroll::center();
14675
14676 self.unfold_ranges(&[destination..destination], false, false, cx);
14677 self.change_selections(Some(autoscroll), window, cx, |s| {
14678 s.select_ranges([destination..destination]);
14679 });
14680 }
14681 }
14682
14683 fn do_stage_or_unstage(
14684 &self,
14685 stage: bool,
14686 buffer_id: BufferId,
14687 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
14688 cx: &mut App,
14689 ) -> Option<()> {
14690 let project = self.project.as_ref()?;
14691 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
14692 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
14693 let buffer_snapshot = buffer.read(cx).snapshot();
14694 let file_exists = buffer_snapshot
14695 .file()
14696 .is_some_and(|file| file.disk_state().exists());
14697 diff.update(cx, |diff, cx| {
14698 diff.stage_or_unstage_hunks(
14699 stage,
14700 &hunks
14701 .map(|hunk| buffer_diff::DiffHunk {
14702 buffer_range: hunk.buffer_range,
14703 diff_base_byte_range: hunk.diff_base_byte_range,
14704 secondary_status: hunk.secondary_status,
14705 range: Point::zero()..Point::zero(), // unused
14706 })
14707 .collect::<Vec<_>>(),
14708 &buffer_snapshot,
14709 file_exists,
14710 cx,
14711 )
14712 });
14713 None
14714 }
14715
14716 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
14717 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
14718 self.buffer
14719 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
14720 }
14721
14722 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
14723 self.buffer.update(cx, |buffer, cx| {
14724 let ranges = vec![Anchor::min()..Anchor::max()];
14725 if !buffer.all_diff_hunks_expanded()
14726 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
14727 {
14728 buffer.collapse_diff_hunks(ranges, cx);
14729 true
14730 } else {
14731 false
14732 }
14733 })
14734 }
14735
14736 fn toggle_diff_hunks_in_ranges(
14737 &mut self,
14738 ranges: Vec<Range<Anchor>>,
14739 cx: &mut Context<'_, Editor>,
14740 ) {
14741 self.buffer.update(cx, |buffer, cx| {
14742 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
14743 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
14744 })
14745 }
14746
14747 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
14748 self.buffer.update(cx, |buffer, cx| {
14749 let snapshot = buffer.snapshot(cx);
14750 let excerpt_id = range.end.excerpt_id;
14751 let point_range = range.to_point(&snapshot);
14752 let expand = !buffer.single_hunk_is_expanded(range, cx);
14753 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
14754 })
14755 }
14756
14757 pub(crate) fn apply_all_diff_hunks(
14758 &mut self,
14759 _: &ApplyAllDiffHunks,
14760 window: &mut Window,
14761 cx: &mut Context<Self>,
14762 ) {
14763 let buffers = self.buffer.read(cx).all_buffers();
14764 for branch_buffer in buffers {
14765 branch_buffer.update(cx, |branch_buffer, cx| {
14766 branch_buffer.merge_into_base(Vec::new(), cx);
14767 });
14768 }
14769
14770 if let Some(project) = self.project.clone() {
14771 self.save(true, project, window, cx).detach_and_log_err(cx);
14772 }
14773 }
14774
14775 pub(crate) fn apply_selected_diff_hunks(
14776 &mut self,
14777 _: &ApplyDiffHunk,
14778 window: &mut Window,
14779 cx: &mut Context<Self>,
14780 ) {
14781 let snapshot = self.snapshot(window, cx);
14782 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
14783 let mut ranges_by_buffer = HashMap::default();
14784 self.transact(window, cx, |editor, _window, cx| {
14785 for hunk in hunks {
14786 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
14787 ranges_by_buffer
14788 .entry(buffer.clone())
14789 .or_insert_with(Vec::new)
14790 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
14791 }
14792 }
14793
14794 for (buffer, ranges) in ranges_by_buffer {
14795 buffer.update(cx, |buffer, cx| {
14796 buffer.merge_into_base(ranges, cx);
14797 });
14798 }
14799 });
14800
14801 if let Some(project) = self.project.clone() {
14802 self.save(true, project, window, cx).detach_and_log_err(cx);
14803 }
14804 }
14805
14806 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
14807 if hovered != self.gutter_hovered {
14808 self.gutter_hovered = hovered;
14809 cx.notify();
14810 }
14811 }
14812
14813 pub fn insert_blocks(
14814 &mut self,
14815 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
14816 autoscroll: Option<Autoscroll>,
14817 cx: &mut Context<Self>,
14818 ) -> Vec<CustomBlockId> {
14819 let blocks = self
14820 .display_map
14821 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
14822 if let Some(autoscroll) = autoscroll {
14823 self.request_autoscroll(autoscroll, cx);
14824 }
14825 cx.notify();
14826 blocks
14827 }
14828
14829 pub fn resize_blocks(
14830 &mut self,
14831 heights: HashMap<CustomBlockId, u32>,
14832 autoscroll: Option<Autoscroll>,
14833 cx: &mut Context<Self>,
14834 ) {
14835 self.display_map
14836 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
14837 if let Some(autoscroll) = autoscroll {
14838 self.request_autoscroll(autoscroll, cx);
14839 }
14840 cx.notify();
14841 }
14842
14843 pub fn replace_blocks(
14844 &mut self,
14845 renderers: HashMap<CustomBlockId, RenderBlock>,
14846 autoscroll: Option<Autoscroll>,
14847 cx: &mut Context<Self>,
14848 ) {
14849 self.display_map
14850 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
14851 if let Some(autoscroll) = autoscroll {
14852 self.request_autoscroll(autoscroll, cx);
14853 }
14854 cx.notify();
14855 }
14856
14857 pub fn remove_blocks(
14858 &mut self,
14859 block_ids: HashSet<CustomBlockId>,
14860 autoscroll: Option<Autoscroll>,
14861 cx: &mut Context<Self>,
14862 ) {
14863 self.display_map.update(cx, |display_map, cx| {
14864 display_map.remove_blocks(block_ids, cx)
14865 });
14866 if let Some(autoscroll) = autoscroll {
14867 self.request_autoscroll(autoscroll, cx);
14868 }
14869 cx.notify();
14870 }
14871
14872 pub fn row_for_block(
14873 &self,
14874 block_id: CustomBlockId,
14875 cx: &mut Context<Self>,
14876 ) -> Option<DisplayRow> {
14877 self.display_map
14878 .update(cx, |map, cx| map.row_for_block(block_id, cx))
14879 }
14880
14881 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
14882 self.focused_block = Some(focused_block);
14883 }
14884
14885 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
14886 self.focused_block.take()
14887 }
14888
14889 pub fn insert_creases(
14890 &mut self,
14891 creases: impl IntoIterator<Item = Crease<Anchor>>,
14892 cx: &mut Context<Self>,
14893 ) -> Vec<CreaseId> {
14894 self.display_map
14895 .update(cx, |map, cx| map.insert_creases(creases, cx))
14896 }
14897
14898 pub fn remove_creases(
14899 &mut self,
14900 ids: impl IntoIterator<Item = CreaseId>,
14901 cx: &mut Context<Self>,
14902 ) {
14903 self.display_map
14904 .update(cx, |map, cx| map.remove_creases(ids, cx));
14905 }
14906
14907 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
14908 self.display_map
14909 .update(cx, |map, cx| map.snapshot(cx))
14910 .longest_row()
14911 }
14912
14913 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
14914 self.display_map
14915 .update(cx, |map, cx| map.snapshot(cx))
14916 .max_point()
14917 }
14918
14919 pub fn text(&self, cx: &App) -> String {
14920 self.buffer.read(cx).read(cx).text()
14921 }
14922
14923 pub fn is_empty(&self, cx: &App) -> bool {
14924 self.buffer.read(cx).read(cx).is_empty()
14925 }
14926
14927 pub fn text_option(&self, cx: &App) -> Option<String> {
14928 let text = self.text(cx);
14929 let text = text.trim();
14930
14931 if text.is_empty() {
14932 return None;
14933 }
14934
14935 Some(text.to_string())
14936 }
14937
14938 pub fn set_text(
14939 &mut self,
14940 text: impl Into<Arc<str>>,
14941 window: &mut Window,
14942 cx: &mut Context<Self>,
14943 ) {
14944 self.transact(window, cx, |this, _, cx| {
14945 this.buffer
14946 .read(cx)
14947 .as_singleton()
14948 .expect("you can only call set_text on editors for singleton buffers")
14949 .update(cx, |buffer, cx| buffer.set_text(text, cx));
14950 });
14951 }
14952
14953 pub fn display_text(&self, cx: &mut App) -> String {
14954 self.display_map
14955 .update(cx, |map, cx| map.snapshot(cx))
14956 .text()
14957 }
14958
14959 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
14960 let mut wrap_guides = smallvec::smallvec![];
14961
14962 if self.show_wrap_guides == Some(false) {
14963 return wrap_guides;
14964 }
14965
14966 let settings = self.buffer.read(cx).language_settings(cx);
14967 if settings.show_wrap_guides {
14968 match self.soft_wrap_mode(cx) {
14969 SoftWrap::Column(soft_wrap) => {
14970 wrap_guides.push((soft_wrap as usize, true));
14971 }
14972 SoftWrap::Bounded(soft_wrap) => {
14973 wrap_guides.push((soft_wrap as usize, true));
14974 }
14975 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
14976 }
14977 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
14978 }
14979
14980 wrap_guides
14981 }
14982
14983 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
14984 let settings = self.buffer.read(cx).language_settings(cx);
14985 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
14986 match mode {
14987 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
14988 SoftWrap::None
14989 }
14990 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
14991 language_settings::SoftWrap::PreferredLineLength => {
14992 SoftWrap::Column(settings.preferred_line_length)
14993 }
14994 language_settings::SoftWrap::Bounded => {
14995 SoftWrap::Bounded(settings.preferred_line_length)
14996 }
14997 }
14998 }
14999
15000 pub fn set_soft_wrap_mode(
15001 &mut self,
15002 mode: language_settings::SoftWrap,
15003
15004 cx: &mut Context<Self>,
15005 ) {
15006 self.soft_wrap_mode_override = Some(mode);
15007 cx.notify();
15008 }
15009
15010 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
15011 self.hard_wrap = hard_wrap;
15012 cx.notify();
15013 }
15014
15015 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
15016 self.text_style_refinement = Some(style);
15017 }
15018
15019 /// called by the Element so we know what style we were most recently rendered with.
15020 pub(crate) fn set_style(
15021 &mut self,
15022 style: EditorStyle,
15023 window: &mut Window,
15024 cx: &mut Context<Self>,
15025 ) {
15026 let rem_size = window.rem_size();
15027 self.display_map.update(cx, |map, cx| {
15028 map.set_font(
15029 style.text.font(),
15030 style.text.font_size.to_pixels(rem_size),
15031 cx,
15032 )
15033 });
15034 self.style = Some(style);
15035 }
15036
15037 pub fn style(&self) -> Option<&EditorStyle> {
15038 self.style.as_ref()
15039 }
15040
15041 // Called by the element. This method is not designed to be called outside of the editor
15042 // element's layout code because it does not notify when rewrapping is computed synchronously.
15043 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
15044 self.display_map
15045 .update(cx, |map, cx| map.set_wrap_width(width, cx))
15046 }
15047
15048 pub fn set_soft_wrap(&mut self) {
15049 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
15050 }
15051
15052 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
15053 if self.soft_wrap_mode_override.is_some() {
15054 self.soft_wrap_mode_override.take();
15055 } else {
15056 let soft_wrap = match self.soft_wrap_mode(cx) {
15057 SoftWrap::GitDiff => return,
15058 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
15059 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
15060 language_settings::SoftWrap::None
15061 }
15062 };
15063 self.soft_wrap_mode_override = Some(soft_wrap);
15064 }
15065 cx.notify();
15066 }
15067
15068 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
15069 let Some(workspace) = self.workspace() else {
15070 return;
15071 };
15072 let fs = workspace.read(cx).app_state().fs.clone();
15073 let current_show = TabBarSettings::get_global(cx).show;
15074 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
15075 setting.show = Some(!current_show);
15076 });
15077 }
15078
15079 pub fn toggle_indent_guides(
15080 &mut self,
15081 _: &ToggleIndentGuides,
15082 _: &mut Window,
15083 cx: &mut Context<Self>,
15084 ) {
15085 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
15086 self.buffer
15087 .read(cx)
15088 .language_settings(cx)
15089 .indent_guides
15090 .enabled
15091 });
15092 self.show_indent_guides = Some(!currently_enabled);
15093 cx.notify();
15094 }
15095
15096 fn should_show_indent_guides(&self) -> Option<bool> {
15097 self.show_indent_guides
15098 }
15099
15100 pub fn toggle_line_numbers(
15101 &mut self,
15102 _: &ToggleLineNumbers,
15103 _: &mut Window,
15104 cx: &mut Context<Self>,
15105 ) {
15106 let mut editor_settings = EditorSettings::get_global(cx).clone();
15107 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
15108 EditorSettings::override_global(editor_settings, cx);
15109 }
15110
15111 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
15112 if let Some(show_line_numbers) = self.show_line_numbers {
15113 return show_line_numbers;
15114 }
15115 EditorSettings::get_global(cx).gutter.line_numbers
15116 }
15117
15118 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
15119 self.use_relative_line_numbers
15120 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
15121 }
15122
15123 pub fn toggle_relative_line_numbers(
15124 &mut self,
15125 _: &ToggleRelativeLineNumbers,
15126 _: &mut Window,
15127 cx: &mut Context<Self>,
15128 ) {
15129 let is_relative = self.should_use_relative_line_numbers(cx);
15130 self.set_relative_line_number(Some(!is_relative), cx)
15131 }
15132
15133 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
15134 self.use_relative_line_numbers = is_relative;
15135 cx.notify();
15136 }
15137
15138 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
15139 self.show_gutter = show_gutter;
15140 cx.notify();
15141 }
15142
15143 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
15144 self.show_scrollbars = show_scrollbars;
15145 cx.notify();
15146 }
15147
15148 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
15149 self.show_line_numbers = Some(show_line_numbers);
15150 cx.notify();
15151 }
15152
15153 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
15154 self.show_git_diff_gutter = Some(show_git_diff_gutter);
15155 cx.notify();
15156 }
15157
15158 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
15159 self.show_code_actions = Some(show_code_actions);
15160 cx.notify();
15161 }
15162
15163 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
15164 self.show_runnables = Some(show_runnables);
15165 cx.notify();
15166 }
15167
15168 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
15169 self.show_breakpoints = Some(show_breakpoints);
15170 cx.notify();
15171 }
15172
15173 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
15174 if self.display_map.read(cx).masked != masked {
15175 self.display_map.update(cx, |map, _| map.masked = masked);
15176 }
15177 cx.notify()
15178 }
15179
15180 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
15181 self.show_wrap_guides = Some(show_wrap_guides);
15182 cx.notify();
15183 }
15184
15185 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
15186 self.show_indent_guides = Some(show_indent_guides);
15187 cx.notify();
15188 }
15189
15190 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
15191 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
15192 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
15193 if let Some(dir) = file.abs_path(cx).parent() {
15194 return Some(dir.to_owned());
15195 }
15196 }
15197
15198 if let Some(project_path) = buffer.read(cx).project_path(cx) {
15199 return Some(project_path.path.to_path_buf());
15200 }
15201 }
15202
15203 None
15204 }
15205
15206 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
15207 self.active_excerpt(cx)?
15208 .1
15209 .read(cx)
15210 .file()
15211 .and_then(|f| f.as_local())
15212 }
15213
15214 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15215 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15216 let buffer = buffer.read(cx);
15217 if let Some(project_path) = buffer.project_path(cx) {
15218 let project = self.project.as_ref()?.read(cx);
15219 project.absolute_path(&project_path, cx)
15220 } else {
15221 buffer
15222 .file()
15223 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
15224 }
15225 })
15226 }
15227
15228 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15229 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15230 let project_path = buffer.read(cx).project_path(cx)?;
15231 let project = self.project.as_ref()?.read(cx);
15232 let entry = project.entry_for_path(&project_path, cx)?;
15233 let path = entry.path.to_path_buf();
15234 Some(path)
15235 })
15236 }
15237
15238 pub fn reveal_in_finder(
15239 &mut self,
15240 _: &RevealInFileManager,
15241 _window: &mut Window,
15242 cx: &mut Context<Self>,
15243 ) {
15244 if let Some(target) = self.target_file(cx) {
15245 cx.reveal_path(&target.abs_path(cx));
15246 }
15247 }
15248
15249 pub fn copy_path(
15250 &mut self,
15251 _: &zed_actions::workspace::CopyPath,
15252 _window: &mut Window,
15253 cx: &mut Context<Self>,
15254 ) {
15255 if let Some(path) = self.target_file_abs_path(cx) {
15256 if let Some(path) = path.to_str() {
15257 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15258 }
15259 }
15260 }
15261
15262 pub fn copy_relative_path(
15263 &mut self,
15264 _: &zed_actions::workspace::CopyRelativePath,
15265 _window: &mut Window,
15266 cx: &mut Context<Self>,
15267 ) {
15268 if let Some(path) = self.target_file_path(cx) {
15269 if let Some(path) = path.to_str() {
15270 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15271 }
15272 }
15273 }
15274
15275 pub fn project_path(&self, cx: &mut Context<Self>) -> Option<ProjectPath> {
15276 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
15277 buffer.read_with(cx, |buffer, cx| buffer.project_path(cx))
15278 } else {
15279 None
15280 }
15281 }
15282
15283 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) {
15284 let _ = maybe!({
15285 let breakpoint_store = self.breakpoint_store.as_ref()?;
15286
15287 let Some((_, _, active_position)) =
15288 breakpoint_store.read(cx).active_position().cloned()
15289 else {
15290 self.clear_row_highlights::<DebugCurrentRowHighlight>();
15291 return None;
15292 };
15293
15294 let snapshot = self
15295 .project
15296 .as_ref()?
15297 .read(cx)
15298 .buffer_for_id(active_position.buffer_id?, cx)?
15299 .read(cx)
15300 .snapshot();
15301
15302 for (id, ExcerptRange { context, .. }) in self
15303 .buffer
15304 .read(cx)
15305 .excerpts_for_buffer(active_position.buffer_id?, cx)
15306 {
15307 if context.start.cmp(&active_position, &snapshot).is_ge()
15308 || context.end.cmp(&active_position, &snapshot).is_lt()
15309 {
15310 continue;
15311 }
15312 let snapshot = self.buffer.read(cx).snapshot(cx);
15313 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, active_position)?;
15314
15315 self.clear_row_highlights::<DebugCurrentRowHighlight>();
15316 self.go_to_line::<DebugCurrentRowHighlight>(
15317 multibuffer_anchor,
15318 Some(cx.theme().colors().editor_debugger_active_line_background),
15319 window,
15320 cx,
15321 );
15322
15323 cx.notify();
15324 }
15325
15326 Some(())
15327 });
15328 }
15329
15330 pub fn copy_file_name_without_extension(
15331 &mut self,
15332 _: &CopyFileNameWithoutExtension,
15333 _: &mut Window,
15334 cx: &mut Context<Self>,
15335 ) {
15336 if let Some(file) = self.target_file(cx) {
15337 if let Some(file_stem) = file.path().file_stem() {
15338 if let Some(name) = file_stem.to_str() {
15339 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
15340 }
15341 }
15342 }
15343 }
15344
15345 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
15346 if let Some(file) = self.target_file(cx) {
15347 if let Some(file_name) = file.path().file_name() {
15348 if let Some(name) = file_name.to_str() {
15349 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
15350 }
15351 }
15352 }
15353 }
15354
15355 pub fn toggle_git_blame(
15356 &mut self,
15357 _: &::git::Blame,
15358 window: &mut Window,
15359 cx: &mut Context<Self>,
15360 ) {
15361 self.show_git_blame_gutter = !self.show_git_blame_gutter;
15362
15363 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
15364 self.start_git_blame(true, window, cx);
15365 }
15366
15367 cx.notify();
15368 }
15369
15370 pub fn toggle_git_blame_inline(
15371 &mut self,
15372 _: &ToggleGitBlameInline,
15373 window: &mut Window,
15374 cx: &mut Context<Self>,
15375 ) {
15376 self.toggle_git_blame_inline_internal(true, window, cx);
15377 cx.notify();
15378 }
15379
15380 pub fn git_blame_inline_enabled(&self) -> bool {
15381 self.git_blame_inline_enabled
15382 }
15383
15384 pub fn toggle_selection_menu(
15385 &mut self,
15386 _: &ToggleSelectionMenu,
15387 _: &mut Window,
15388 cx: &mut Context<Self>,
15389 ) {
15390 self.show_selection_menu = self
15391 .show_selection_menu
15392 .map(|show_selections_menu| !show_selections_menu)
15393 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
15394
15395 cx.notify();
15396 }
15397
15398 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
15399 self.show_selection_menu
15400 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
15401 }
15402
15403 fn start_git_blame(
15404 &mut self,
15405 user_triggered: bool,
15406 window: &mut Window,
15407 cx: &mut Context<Self>,
15408 ) {
15409 if let Some(project) = self.project.as_ref() {
15410 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
15411 return;
15412 };
15413
15414 if buffer.read(cx).file().is_none() {
15415 return;
15416 }
15417
15418 let focused = self.focus_handle(cx).contains_focused(window, cx);
15419
15420 let project = project.clone();
15421 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
15422 self.blame_subscription =
15423 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
15424 self.blame = Some(blame);
15425 }
15426 }
15427
15428 fn toggle_git_blame_inline_internal(
15429 &mut self,
15430 user_triggered: bool,
15431 window: &mut Window,
15432 cx: &mut Context<Self>,
15433 ) {
15434 if self.git_blame_inline_enabled {
15435 self.git_blame_inline_enabled = false;
15436 self.show_git_blame_inline = false;
15437 self.show_git_blame_inline_delay_task.take();
15438 } else {
15439 self.git_blame_inline_enabled = true;
15440 self.start_git_blame_inline(user_triggered, window, cx);
15441 }
15442
15443 cx.notify();
15444 }
15445
15446 fn start_git_blame_inline(
15447 &mut self,
15448 user_triggered: bool,
15449 window: &mut Window,
15450 cx: &mut Context<Self>,
15451 ) {
15452 self.start_git_blame(user_triggered, window, cx);
15453
15454 if ProjectSettings::get_global(cx)
15455 .git
15456 .inline_blame_delay()
15457 .is_some()
15458 {
15459 self.start_inline_blame_timer(window, cx);
15460 } else {
15461 self.show_git_blame_inline = true
15462 }
15463 }
15464
15465 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
15466 self.blame.as_ref()
15467 }
15468
15469 pub fn show_git_blame_gutter(&self) -> bool {
15470 self.show_git_blame_gutter
15471 }
15472
15473 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
15474 self.show_git_blame_gutter && self.has_blame_entries(cx)
15475 }
15476
15477 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
15478 self.show_git_blame_inline
15479 && (self.focus_handle.is_focused(window)
15480 || self
15481 .git_blame_inline_tooltip
15482 .as_ref()
15483 .and_then(|t| t.upgrade())
15484 .is_some())
15485 && !self.newest_selection_head_on_empty_line(cx)
15486 && self.has_blame_entries(cx)
15487 }
15488
15489 fn has_blame_entries(&self, cx: &App) -> bool {
15490 self.blame()
15491 .map_or(false, |blame| blame.read(cx).has_generated_entries())
15492 }
15493
15494 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
15495 let cursor_anchor = self.selections.newest_anchor().head();
15496
15497 let snapshot = self.buffer.read(cx).snapshot(cx);
15498 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
15499
15500 snapshot.line_len(buffer_row) == 0
15501 }
15502
15503 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
15504 let buffer_and_selection = maybe!({
15505 let selection = self.selections.newest::<Point>(cx);
15506 let selection_range = selection.range();
15507
15508 let multi_buffer = self.buffer().read(cx);
15509 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15510 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
15511
15512 let (buffer, range, _) = if selection.reversed {
15513 buffer_ranges.first()
15514 } else {
15515 buffer_ranges.last()
15516 }?;
15517
15518 let selection = text::ToPoint::to_point(&range.start, &buffer).row
15519 ..text::ToPoint::to_point(&range.end, &buffer).row;
15520 Some((
15521 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
15522 selection,
15523 ))
15524 });
15525
15526 let Some((buffer, selection)) = buffer_and_selection else {
15527 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
15528 };
15529
15530 let Some(project) = self.project.as_ref() else {
15531 return Task::ready(Err(anyhow!("editor does not have project")));
15532 };
15533
15534 project.update(cx, |project, cx| {
15535 project.get_permalink_to_line(&buffer, selection, cx)
15536 })
15537 }
15538
15539 pub fn copy_permalink_to_line(
15540 &mut self,
15541 _: &CopyPermalinkToLine,
15542 window: &mut Window,
15543 cx: &mut Context<Self>,
15544 ) {
15545 let permalink_task = self.get_permalink_to_line(cx);
15546 let workspace = self.workspace();
15547
15548 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
15549 Ok(permalink) => {
15550 cx.update(|_, cx| {
15551 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
15552 })
15553 .ok();
15554 }
15555 Err(err) => {
15556 let message = format!("Failed to copy permalink: {err}");
15557
15558 Err::<(), anyhow::Error>(err).log_err();
15559
15560 if let Some(workspace) = workspace {
15561 workspace
15562 .update_in(cx, |workspace, _, cx| {
15563 struct CopyPermalinkToLine;
15564
15565 workspace.show_toast(
15566 Toast::new(
15567 NotificationId::unique::<CopyPermalinkToLine>(),
15568 message,
15569 ),
15570 cx,
15571 )
15572 })
15573 .ok();
15574 }
15575 }
15576 })
15577 .detach();
15578 }
15579
15580 pub fn copy_file_location(
15581 &mut self,
15582 _: &CopyFileLocation,
15583 _: &mut Window,
15584 cx: &mut Context<Self>,
15585 ) {
15586 let selection = self.selections.newest::<Point>(cx).start.row + 1;
15587 if let Some(file) = self.target_file(cx) {
15588 if let Some(path) = file.path().to_str() {
15589 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
15590 }
15591 }
15592 }
15593
15594 pub fn open_permalink_to_line(
15595 &mut self,
15596 _: &OpenPermalinkToLine,
15597 window: &mut Window,
15598 cx: &mut Context<Self>,
15599 ) {
15600 let permalink_task = self.get_permalink_to_line(cx);
15601 let workspace = self.workspace();
15602
15603 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
15604 Ok(permalink) => {
15605 cx.update(|_, cx| {
15606 cx.open_url(permalink.as_ref());
15607 })
15608 .ok();
15609 }
15610 Err(err) => {
15611 let message = format!("Failed to open permalink: {err}");
15612
15613 Err::<(), anyhow::Error>(err).log_err();
15614
15615 if let Some(workspace) = workspace {
15616 workspace
15617 .update(cx, |workspace, cx| {
15618 struct OpenPermalinkToLine;
15619
15620 workspace.show_toast(
15621 Toast::new(
15622 NotificationId::unique::<OpenPermalinkToLine>(),
15623 message,
15624 ),
15625 cx,
15626 )
15627 })
15628 .ok();
15629 }
15630 }
15631 })
15632 .detach();
15633 }
15634
15635 pub fn insert_uuid_v4(
15636 &mut self,
15637 _: &InsertUuidV4,
15638 window: &mut Window,
15639 cx: &mut Context<Self>,
15640 ) {
15641 self.insert_uuid(UuidVersion::V4, window, cx);
15642 }
15643
15644 pub fn insert_uuid_v7(
15645 &mut self,
15646 _: &InsertUuidV7,
15647 window: &mut Window,
15648 cx: &mut Context<Self>,
15649 ) {
15650 self.insert_uuid(UuidVersion::V7, window, cx);
15651 }
15652
15653 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
15654 self.transact(window, cx, |this, window, cx| {
15655 let edits = this
15656 .selections
15657 .all::<Point>(cx)
15658 .into_iter()
15659 .map(|selection| {
15660 let uuid = match version {
15661 UuidVersion::V4 => uuid::Uuid::new_v4(),
15662 UuidVersion::V7 => uuid::Uuid::now_v7(),
15663 };
15664
15665 (selection.range(), uuid.to_string())
15666 });
15667 this.edit(edits, cx);
15668 this.refresh_inline_completion(true, false, window, cx);
15669 });
15670 }
15671
15672 pub fn open_selections_in_multibuffer(
15673 &mut self,
15674 _: &OpenSelectionsInMultibuffer,
15675 window: &mut Window,
15676 cx: &mut Context<Self>,
15677 ) {
15678 let multibuffer = self.buffer.read(cx);
15679
15680 let Some(buffer) = multibuffer.as_singleton() else {
15681 return;
15682 };
15683
15684 let Some(workspace) = self.workspace() else {
15685 return;
15686 };
15687
15688 let locations = self
15689 .selections
15690 .disjoint_anchors()
15691 .iter()
15692 .map(|range| Location {
15693 buffer: buffer.clone(),
15694 range: range.start.text_anchor..range.end.text_anchor,
15695 })
15696 .collect::<Vec<_>>();
15697
15698 let title = multibuffer.title(cx).to_string();
15699
15700 cx.spawn_in(window, async move |_, cx| {
15701 workspace.update_in(cx, |workspace, window, cx| {
15702 Self::open_locations_in_multibuffer(
15703 workspace,
15704 locations,
15705 format!("Selections for '{title}'"),
15706 false,
15707 MultibufferSelectionMode::All,
15708 window,
15709 cx,
15710 );
15711 })
15712 })
15713 .detach();
15714 }
15715
15716 /// Adds a row highlight for the given range. If a row has multiple highlights, the
15717 /// last highlight added will be used.
15718 ///
15719 /// If the range ends at the beginning of a line, then that line will not be highlighted.
15720 pub fn highlight_rows<T: 'static>(
15721 &mut self,
15722 range: Range<Anchor>,
15723 color: Hsla,
15724 should_autoscroll: bool,
15725 cx: &mut Context<Self>,
15726 ) {
15727 let snapshot = self.buffer().read(cx).snapshot(cx);
15728 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
15729 let ix = row_highlights.binary_search_by(|highlight| {
15730 Ordering::Equal
15731 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
15732 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
15733 });
15734
15735 if let Err(mut ix) = ix {
15736 let index = post_inc(&mut self.highlight_order);
15737
15738 // If this range intersects with the preceding highlight, then merge it with
15739 // the preceding highlight. Otherwise insert a new highlight.
15740 let mut merged = false;
15741 if ix > 0 {
15742 let prev_highlight = &mut row_highlights[ix - 1];
15743 if prev_highlight
15744 .range
15745 .end
15746 .cmp(&range.start, &snapshot)
15747 .is_ge()
15748 {
15749 ix -= 1;
15750 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
15751 prev_highlight.range.end = range.end;
15752 }
15753 merged = true;
15754 prev_highlight.index = index;
15755 prev_highlight.color = color;
15756 prev_highlight.should_autoscroll = should_autoscroll;
15757 }
15758 }
15759
15760 if !merged {
15761 row_highlights.insert(
15762 ix,
15763 RowHighlight {
15764 range: range.clone(),
15765 index,
15766 color,
15767 should_autoscroll,
15768 },
15769 );
15770 }
15771
15772 // If any of the following highlights intersect with this one, merge them.
15773 while let Some(next_highlight) = row_highlights.get(ix + 1) {
15774 let highlight = &row_highlights[ix];
15775 if next_highlight
15776 .range
15777 .start
15778 .cmp(&highlight.range.end, &snapshot)
15779 .is_le()
15780 {
15781 if next_highlight
15782 .range
15783 .end
15784 .cmp(&highlight.range.end, &snapshot)
15785 .is_gt()
15786 {
15787 row_highlights[ix].range.end = next_highlight.range.end;
15788 }
15789 row_highlights.remove(ix + 1);
15790 } else {
15791 break;
15792 }
15793 }
15794 }
15795 }
15796
15797 /// Remove any highlighted row ranges of the given type that intersect the
15798 /// given ranges.
15799 pub fn remove_highlighted_rows<T: 'static>(
15800 &mut self,
15801 ranges_to_remove: Vec<Range<Anchor>>,
15802 cx: &mut Context<Self>,
15803 ) {
15804 let snapshot = self.buffer().read(cx).snapshot(cx);
15805 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
15806 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
15807 row_highlights.retain(|highlight| {
15808 while let Some(range_to_remove) = ranges_to_remove.peek() {
15809 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
15810 Ordering::Less | Ordering::Equal => {
15811 ranges_to_remove.next();
15812 }
15813 Ordering::Greater => {
15814 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
15815 Ordering::Less | Ordering::Equal => {
15816 return false;
15817 }
15818 Ordering::Greater => break,
15819 }
15820 }
15821 }
15822 }
15823
15824 true
15825 })
15826 }
15827
15828 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
15829 pub fn clear_row_highlights<T: 'static>(&mut self) {
15830 self.highlighted_rows.remove(&TypeId::of::<T>());
15831 }
15832
15833 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
15834 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
15835 self.highlighted_rows
15836 .get(&TypeId::of::<T>())
15837 .map_or(&[] as &[_], |vec| vec.as_slice())
15838 .iter()
15839 .map(|highlight| (highlight.range.clone(), highlight.color))
15840 }
15841
15842 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
15843 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
15844 /// Allows to ignore certain kinds of highlights.
15845 pub fn highlighted_display_rows(
15846 &self,
15847 window: &mut Window,
15848 cx: &mut App,
15849 ) -> BTreeMap<DisplayRow, LineHighlight> {
15850 let snapshot = self.snapshot(window, cx);
15851 let mut used_highlight_orders = HashMap::default();
15852 self.highlighted_rows
15853 .iter()
15854 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
15855 .fold(
15856 BTreeMap::<DisplayRow, LineHighlight>::new(),
15857 |mut unique_rows, highlight| {
15858 let start = highlight.range.start.to_display_point(&snapshot);
15859 let end = highlight.range.end.to_display_point(&snapshot);
15860 let start_row = start.row().0;
15861 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
15862 && end.column() == 0
15863 {
15864 end.row().0.saturating_sub(1)
15865 } else {
15866 end.row().0
15867 };
15868 for row in start_row..=end_row {
15869 let used_index =
15870 used_highlight_orders.entry(row).or_insert(highlight.index);
15871 if highlight.index >= *used_index {
15872 *used_index = highlight.index;
15873 unique_rows.insert(DisplayRow(row), highlight.color.into());
15874 }
15875 }
15876 unique_rows
15877 },
15878 )
15879 }
15880
15881 pub fn highlighted_display_row_for_autoscroll(
15882 &self,
15883 snapshot: &DisplaySnapshot,
15884 ) -> Option<DisplayRow> {
15885 self.highlighted_rows
15886 .values()
15887 .flat_map(|highlighted_rows| highlighted_rows.iter())
15888 .filter_map(|highlight| {
15889 if highlight.should_autoscroll {
15890 Some(highlight.range.start.to_display_point(snapshot).row())
15891 } else {
15892 None
15893 }
15894 })
15895 .min()
15896 }
15897
15898 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
15899 self.highlight_background::<SearchWithinRange>(
15900 ranges,
15901 |colors| colors.editor_document_highlight_read_background,
15902 cx,
15903 )
15904 }
15905
15906 pub fn set_breadcrumb_header(&mut self, new_header: String) {
15907 self.breadcrumb_header = Some(new_header);
15908 }
15909
15910 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
15911 self.clear_background_highlights::<SearchWithinRange>(cx);
15912 }
15913
15914 pub fn highlight_background<T: 'static>(
15915 &mut self,
15916 ranges: &[Range<Anchor>],
15917 color_fetcher: fn(&ThemeColors) -> Hsla,
15918 cx: &mut Context<Self>,
15919 ) {
15920 self.background_highlights
15921 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
15922 self.scrollbar_marker_state.dirty = true;
15923 cx.notify();
15924 }
15925
15926 pub fn clear_background_highlights<T: 'static>(
15927 &mut self,
15928 cx: &mut Context<Self>,
15929 ) -> Option<BackgroundHighlight> {
15930 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
15931 if !text_highlights.1.is_empty() {
15932 self.scrollbar_marker_state.dirty = true;
15933 cx.notify();
15934 }
15935 Some(text_highlights)
15936 }
15937
15938 pub fn highlight_gutter<T: 'static>(
15939 &mut self,
15940 ranges: &[Range<Anchor>],
15941 color_fetcher: fn(&App) -> Hsla,
15942 cx: &mut Context<Self>,
15943 ) {
15944 self.gutter_highlights
15945 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
15946 cx.notify();
15947 }
15948
15949 pub fn clear_gutter_highlights<T: 'static>(
15950 &mut self,
15951 cx: &mut Context<Self>,
15952 ) -> Option<GutterHighlight> {
15953 cx.notify();
15954 self.gutter_highlights.remove(&TypeId::of::<T>())
15955 }
15956
15957 #[cfg(feature = "test-support")]
15958 pub fn all_text_background_highlights(
15959 &self,
15960 window: &mut Window,
15961 cx: &mut Context<Self>,
15962 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
15963 let snapshot = self.snapshot(window, cx);
15964 let buffer = &snapshot.buffer_snapshot;
15965 let start = buffer.anchor_before(0);
15966 let end = buffer.anchor_after(buffer.len());
15967 let theme = cx.theme().colors();
15968 self.background_highlights_in_range(start..end, &snapshot, theme)
15969 }
15970
15971 #[cfg(feature = "test-support")]
15972 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
15973 let snapshot = self.buffer().read(cx).snapshot(cx);
15974
15975 let highlights = self
15976 .background_highlights
15977 .get(&TypeId::of::<items::BufferSearchHighlights>());
15978
15979 if let Some((_color, ranges)) = highlights {
15980 ranges
15981 .iter()
15982 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
15983 .collect_vec()
15984 } else {
15985 vec![]
15986 }
15987 }
15988
15989 fn document_highlights_for_position<'a>(
15990 &'a self,
15991 position: Anchor,
15992 buffer: &'a MultiBufferSnapshot,
15993 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
15994 let read_highlights = self
15995 .background_highlights
15996 .get(&TypeId::of::<DocumentHighlightRead>())
15997 .map(|h| &h.1);
15998 let write_highlights = self
15999 .background_highlights
16000 .get(&TypeId::of::<DocumentHighlightWrite>())
16001 .map(|h| &h.1);
16002 let left_position = position.bias_left(buffer);
16003 let right_position = position.bias_right(buffer);
16004 read_highlights
16005 .into_iter()
16006 .chain(write_highlights)
16007 .flat_map(move |ranges| {
16008 let start_ix = match ranges.binary_search_by(|probe| {
16009 let cmp = probe.end.cmp(&left_position, buffer);
16010 if cmp.is_ge() {
16011 Ordering::Greater
16012 } else {
16013 Ordering::Less
16014 }
16015 }) {
16016 Ok(i) | Err(i) => i,
16017 };
16018
16019 ranges[start_ix..]
16020 .iter()
16021 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
16022 })
16023 }
16024
16025 pub fn has_background_highlights<T: 'static>(&self) -> bool {
16026 self.background_highlights
16027 .get(&TypeId::of::<T>())
16028 .map_or(false, |(_, highlights)| !highlights.is_empty())
16029 }
16030
16031 pub fn background_highlights_in_range(
16032 &self,
16033 search_range: Range<Anchor>,
16034 display_snapshot: &DisplaySnapshot,
16035 theme: &ThemeColors,
16036 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16037 let mut results = Vec::new();
16038 for (color_fetcher, ranges) in self.background_highlights.values() {
16039 let color = color_fetcher(theme);
16040 let start_ix = match ranges.binary_search_by(|probe| {
16041 let cmp = probe
16042 .end
16043 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16044 if cmp.is_gt() {
16045 Ordering::Greater
16046 } else {
16047 Ordering::Less
16048 }
16049 }) {
16050 Ok(i) | Err(i) => i,
16051 };
16052 for range in &ranges[start_ix..] {
16053 if range
16054 .start
16055 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16056 .is_ge()
16057 {
16058 break;
16059 }
16060
16061 let start = range.start.to_display_point(display_snapshot);
16062 let end = range.end.to_display_point(display_snapshot);
16063 results.push((start..end, color))
16064 }
16065 }
16066 results
16067 }
16068
16069 pub fn background_highlight_row_ranges<T: 'static>(
16070 &self,
16071 search_range: Range<Anchor>,
16072 display_snapshot: &DisplaySnapshot,
16073 count: usize,
16074 ) -> Vec<RangeInclusive<DisplayPoint>> {
16075 let mut results = Vec::new();
16076 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
16077 return vec![];
16078 };
16079
16080 let start_ix = match ranges.binary_search_by(|probe| {
16081 let cmp = probe
16082 .end
16083 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16084 if cmp.is_gt() {
16085 Ordering::Greater
16086 } else {
16087 Ordering::Less
16088 }
16089 }) {
16090 Ok(i) | Err(i) => i,
16091 };
16092 let mut push_region = |start: Option<Point>, end: Option<Point>| {
16093 if let (Some(start_display), Some(end_display)) = (start, end) {
16094 results.push(
16095 start_display.to_display_point(display_snapshot)
16096 ..=end_display.to_display_point(display_snapshot),
16097 );
16098 }
16099 };
16100 let mut start_row: Option<Point> = None;
16101 let mut end_row: Option<Point> = None;
16102 if ranges.len() > count {
16103 return Vec::new();
16104 }
16105 for range in &ranges[start_ix..] {
16106 if range
16107 .start
16108 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16109 .is_ge()
16110 {
16111 break;
16112 }
16113 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
16114 if let Some(current_row) = &end_row {
16115 if end.row == current_row.row {
16116 continue;
16117 }
16118 }
16119 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
16120 if start_row.is_none() {
16121 assert_eq!(end_row, None);
16122 start_row = Some(start);
16123 end_row = Some(end);
16124 continue;
16125 }
16126 if let Some(current_end) = end_row.as_mut() {
16127 if start.row > current_end.row + 1 {
16128 push_region(start_row, end_row);
16129 start_row = Some(start);
16130 end_row = Some(end);
16131 } else {
16132 // Merge two hunks.
16133 *current_end = end;
16134 }
16135 } else {
16136 unreachable!();
16137 }
16138 }
16139 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
16140 push_region(start_row, end_row);
16141 results
16142 }
16143
16144 pub fn gutter_highlights_in_range(
16145 &self,
16146 search_range: Range<Anchor>,
16147 display_snapshot: &DisplaySnapshot,
16148 cx: &App,
16149 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16150 let mut results = Vec::new();
16151 for (color_fetcher, ranges) in self.gutter_highlights.values() {
16152 let color = color_fetcher(cx);
16153 let start_ix = match ranges.binary_search_by(|probe| {
16154 let cmp = probe
16155 .end
16156 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16157 if cmp.is_gt() {
16158 Ordering::Greater
16159 } else {
16160 Ordering::Less
16161 }
16162 }) {
16163 Ok(i) | Err(i) => i,
16164 };
16165 for range in &ranges[start_ix..] {
16166 if range
16167 .start
16168 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16169 .is_ge()
16170 {
16171 break;
16172 }
16173
16174 let start = range.start.to_display_point(display_snapshot);
16175 let end = range.end.to_display_point(display_snapshot);
16176 results.push((start..end, color))
16177 }
16178 }
16179 results
16180 }
16181
16182 /// Get the text ranges corresponding to the redaction query
16183 pub fn redacted_ranges(
16184 &self,
16185 search_range: Range<Anchor>,
16186 display_snapshot: &DisplaySnapshot,
16187 cx: &App,
16188 ) -> Vec<Range<DisplayPoint>> {
16189 display_snapshot
16190 .buffer_snapshot
16191 .redacted_ranges(search_range, |file| {
16192 if let Some(file) = file {
16193 file.is_private()
16194 && EditorSettings::get(
16195 Some(SettingsLocation {
16196 worktree_id: file.worktree_id(cx),
16197 path: file.path().as_ref(),
16198 }),
16199 cx,
16200 )
16201 .redact_private_values
16202 } else {
16203 false
16204 }
16205 })
16206 .map(|range| {
16207 range.start.to_display_point(display_snapshot)
16208 ..range.end.to_display_point(display_snapshot)
16209 })
16210 .collect()
16211 }
16212
16213 pub fn highlight_text<T: 'static>(
16214 &mut self,
16215 ranges: Vec<Range<Anchor>>,
16216 style: HighlightStyle,
16217 cx: &mut Context<Self>,
16218 ) {
16219 self.display_map.update(cx, |map, _| {
16220 map.highlight_text(TypeId::of::<T>(), ranges, style)
16221 });
16222 cx.notify();
16223 }
16224
16225 pub(crate) fn highlight_inlays<T: 'static>(
16226 &mut self,
16227 highlights: Vec<InlayHighlight>,
16228 style: HighlightStyle,
16229 cx: &mut Context<Self>,
16230 ) {
16231 self.display_map.update(cx, |map, _| {
16232 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
16233 });
16234 cx.notify();
16235 }
16236
16237 pub fn text_highlights<'a, T: 'static>(
16238 &'a self,
16239 cx: &'a App,
16240 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
16241 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
16242 }
16243
16244 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
16245 let cleared = self
16246 .display_map
16247 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
16248 if cleared {
16249 cx.notify();
16250 }
16251 }
16252
16253 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
16254 (self.read_only(cx) || self.blink_manager.read(cx).visible())
16255 && self.focus_handle.is_focused(window)
16256 }
16257
16258 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
16259 self.show_cursor_when_unfocused = is_enabled;
16260 cx.notify();
16261 }
16262
16263 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
16264 cx.notify();
16265 }
16266
16267 fn on_buffer_event(
16268 &mut self,
16269 multibuffer: &Entity<MultiBuffer>,
16270 event: &multi_buffer::Event,
16271 window: &mut Window,
16272 cx: &mut Context<Self>,
16273 ) {
16274 match event {
16275 multi_buffer::Event::Edited {
16276 singleton_buffer_edited,
16277 edited_buffer: buffer_edited,
16278 } => {
16279 self.scrollbar_marker_state.dirty = true;
16280 self.active_indent_guides_state.dirty = true;
16281 self.refresh_active_diagnostics(cx);
16282 self.refresh_code_actions(window, cx);
16283 if self.has_active_inline_completion() {
16284 self.update_visible_inline_completion(window, cx);
16285 }
16286 if let Some(buffer) = buffer_edited {
16287 let buffer_id = buffer.read(cx).remote_id();
16288 if !self.registered_buffers.contains_key(&buffer_id) {
16289 if let Some(project) = self.project.as_ref() {
16290 project.update(cx, |project, cx| {
16291 self.registered_buffers.insert(
16292 buffer_id,
16293 project.register_buffer_with_language_servers(&buffer, cx),
16294 );
16295 })
16296 }
16297 }
16298 }
16299 cx.emit(EditorEvent::BufferEdited);
16300 cx.emit(SearchEvent::MatchesInvalidated);
16301 if *singleton_buffer_edited {
16302 if let Some(project) = &self.project {
16303 #[allow(clippy::mutable_key_type)]
16304 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
16305 multibuffer
16306 .all_buffers()
16307 .into_iter()
16308 .filter_map(|buffer| {
16309 buffer.update(cx, |buffer, cx| {
16310 let language = buffer.language()?;
16311 let should_discard = project.update(cx, |project, cx| {
16312 project.is_local()
16313 && !project.has_language_servers_for(buffer, cx)
16314 });
16315 should_discard.not().then_some(language.clone())
16316 })
16317 })
16318 .collect::<HashSet<_>>()
16319 });
16320 if !languages_affected.is_empty() {
16321 self.refresh_inlay_hints(
16322 InlayHintRefreshReason::BufferEdited(languages_affected),
16323 cx,
16324 );
16325 }
16326 }
16327 }
16328
16329 let Some(project) = &self.project else { return };
16330 let (telemetry, is_via_ssh) = {
16331 let project = project.read(cx);
16332 let telemetry = project.client().telemetry().clone();
16333 let is_via_ssh = project.is_via_ssh();
16334 (telemetry, is_via_ssh)
16335 };
16336 refresh_linked_ranges(self, window, cx);
16337 telemetry.log_edit_event("editor", is_via_ssh);
16338 }
16339 multi_buffer::Event::ExcerptsAdded {
16340 buffer,
16341 predecessor,
16342 excerpts,
16343 } => {
16344 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16345 let buffer_id = buffer.read(cx).remote_id();
16346 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
16347 if let Some(project) = &self.project {
16348 get_uncommitted_diff_for_buffer(
16349 project,
16350 [buffer.clone()],
16351 self.buffer.clone(),
16352 cx,
16353 )
16354 .detach();
16355 }
16356 }
16357 cx.emit(EditorEvent::ExcerptsAdded {
16358 buffer: buffer.clone(),
16359 predecessor: *predecessor,
16360 excerpts: excerpts.clone(),
16361 });
16362 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
16363 }
16364 multi_buffer::Event::ExcerptsRemoved { ids } => {
16365 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
16366 let buffer = self.buffer.read(cx);
16367 self.registered_buffers
16368 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
16369 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
16370 cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
16371 }
16372 multi_buffer::Event::ExcerptsEdited {
16373 excerpt_ids,
16374 buffer_ids,
16375 } => {
16376 self.display_map.update(cx, |map, cx| {
16377 map.unfold_buffers(buffer_ids.iter().copied(), cx)
16378 });
16379 cx.emit(EditorEvent::ExcerptsEdited {
16380 ids: excerpt_ids.clone(),
16381 })
16382 }
16383 multi_buffer::Event::ExcerptsExpanded { ids } => {
16384 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
16385 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
16386 }
16387 multi_buffer::Event::Reparsed(buffer_id) => {
16388 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16389 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
16390
16391 cx.emit(EditorEvent::Reparsed(*buffer_id));
16392 }
16393 multi_buffer::Event::DiffHunksToggled => {
16394 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16395 }
16396 multi_buffer::Event::LanguageChanged(buffer_id) => {
16397 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
16398 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
16399 cx.emit(EditorEvent::Reparsed(*buffer_id));
16400 cx.notify();
16401 }
16402 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
16403 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
16404 multi_buffer::Event::FileHandleChanged
16405 | multi_buffer::Event::Reloaded
16406 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
16407 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
16408 multi_buffer::Event::DiagnosticsUpdated => {
16409 self.refresh_active_diagnostics(cx);
16410 self.refresh_inline_diagnostics(true, window, cx);
16411 self.scrollbar_marker_state.dirty = true;
16412 cx.notify();
16413 }
16414 _ => {}
16415 };
16416 }
16417
16418 fn on_display_map_changed(
16419 &mut self,
16420 _: Entity<DisplayMap>,
16421 _: &mut Window,
16422 cx: &mut Context<Self>,
16423 ) {
16424 cx.notify();
16425 }
16426
16427 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16428 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16429 self.update_edit_prediction_settings(cx);
16430 self.refresh_inline_completion(true, false, window, cx);
16431 self.refresh_inlay_hints(
16432 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
16433 self.selections.newest_anchor().head(),
16434 &self.buffer.read(cx).snapshot(cx),
16435 cx,
16436 )),
16437 cx,
16438 );
16439
16440 let old_cursor_shape = self.cursor_shape;
16441
16442 {
16443 let editor_settings = EditorSettings::get_global(cx);
16444 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
16445 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
16446 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
16447 }
16448
16449 if old_cursor_shape != self.cursor_shape {
16450 cx.emit(EditorEvent::CursorShapeChanged);
16451 }
16452
16453 let project_settings = ProjectSettings::get_global(cx);
16454 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
16455
16456 if self.mode == EditorMode::Full {
16457 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
16458 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
16459 if self.show_inline_diagnostics != show_inline_diagnostics {
16460 self.show_inline_diagnostics = show_inline_diagnostics;
16461 self.refresh_inline_diagnostics(false, window, cx);
16462 }
16463
16464 if self.git_blame_inline_enabled != inline_blame_enabled {
16465 self.toggle_git_blame_inline_internal(false, window, cx);
16466 }
16467 }
16468
16469 cx.notify();
16470 }
16471
16472 pub fn set_searchable(&mut self, searchable: bool) {
16473 self.searchable = searchable;
16474 }
16475
16476 pub fn searchable(&self) -> bool {
16477 self.searchable
16478 }
16479
16480 fn open_proposed_changes_editor(
16481 &mut self,
16482 _: &OpenProposedChangesEditor,
16483 window: &mut Window,
16484 cx: &mut Context<Self>,
16485 ) {
16486 let Some(workspace) = self.workspace() else {
16487 cx.propagate();
16488 return;
16489 };
16490
16491 let selections = self.selections.all::<usize>(cx);
16492 let multi_buffer = self.buffer.read(cx);
16493 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16494 let mut new_selections_by_buffer = HashMap::default();
16495 for selection in selections {
16496 for (buffer, range, _) in
16497 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
16498 {
16499 let mut range = range.to_point(buffer);
16500 range.start.column = 0;
16501 range.end.column = buffer.line_len(range.end.row);
16502 new_selections_by_buffer
16503 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
16504 .or_insert(Vec::new())
16505 .push(range)
16506 }
16507 }
16508
16509 let proposed_changes_buffers = new_selections_by_buffer
16510 .into_iter()
16511 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
16512 .collect::<Vec<_>>();
16513 let proposed_changes_editor = cx.new(|cx| {
16514 ProposedChangesEditor::new(
16515 "Proposed changes",
16516 proposed_changes_buffers,
16517 self.project.clone(),
16518 window,
16519 cx,
16520 )
16521 });
16522
16523 window.defer(cx, move |window, cx| {
16524 workspace.update(cx, |workspace, cx| {
16525 workspace.active_pane().update(cx, |pane, cx| {
16526 pane.add_item(
16527 Box::new(proposed_changes_editor),
16528 true,
16529 true,
16530 None,
16531 window,
16532 cx,
16533 );
16534 });
16535 });
16536 });
16537 }
16538
16539 pub fn open_excerpts_in_split(
16540 &mut self,
16541 _: &OpenExcerptsSplit,
16542 window: &mut Window,
16543 cx: &mut Context<Self>,
16544 ) {
16545 self.open_excerpts_common(None, true, window, cx)
16546 }
16547
16548 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
16549 self.open_excerpts_common(None, false, window, cx)
16550 }
16551
16552 fn open_excerpts_common(
16553 &mut self,
16554 jump_data: Option<JumpData>,
16555 split: bool,
16556 window: &mut Window,
16557 cx: &mut Context<Self>,
16558 ) {
16559 let Some(workspace) = self.workspace() else {
16560 cx.propagate();
16561 return;
16562 };
16563
16564 if self.buffer.read(cx).is_singleton() {
16565 cx.propagate();
16566 return;
16567 }
16568
16569 let mut new_selections_by_buffer = HashMap::default();
16570 match &jump_data {
16571 Some(JumpData::MultiBufferPoint {
16572 excerpt_id,
16573 position,
16574 anchor,
16575 line_offset_from_top,
16576 }) => {
16577 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16578 if let Some(buffer) = multi_buffer_snapshot
16579 .buffer_id_for_excerpt(*excerpt_id)
16580 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
16581 {
16582 let buffer_snapshot = buffer.read(cx).snapshot();
16583 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
16584 language::ToPoint::to_point(anchor, &buffer_snapshot)
16585 } else {
16586 buffer_snapshot.clip_point(*position, Bias::Left)
16587 };
16588 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
16589 new_selections_by_buffer.insert(
16590 buffer,
16591 (
16592 vec![jump_to_offset..jump_to_offset],
16593 Some(*line_offset_from_top),
16594 ),
16595 );
16596 }
16597 }
16598 Some(JumpData::MultiBufferRow {
16599 row,
16600 line_offset_from_top,
16601 }) => {
16602 let point = MultiBufferPoint::new(row.0, 0);
16603 if let Some((buffer, buffer_point, _)) =
16604 self.buffer.read(cx).point_to_buffer_point(point, cx)
16605 {
16606 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
16607 new_selections_by_buffer
16608 .entry(buffer)
16609 .or_insert((Vec::new(), Some(*line_offset_from_top)))
16610 .0
16611 .push(buffer_offset..buffer_offset)
16612 }
16613 }
16614 None => {
16615 let selections = self.selections.all::<usize>(cx);
16616 let multi_buffer = self.buffer.read(cx);
16617 for selection in selections {
16618 for (snapshot, range, _, anchor) in multi_buffer
16619 .snapshot(cx)
16620 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
16621 {
16622 if let Some(anchor) = anchor {
16623 // selection is in a deleted hunk
16624 let Some(buffer_id) = anchor.buffer_id else {
16625 continue;
16626 };
16627 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
16628 continue;
16629 };
16630 let offset = text::ToOffset::to_offset(
16631 &anchor.text_anchor,
16632 &buffer_handle.read(cx).snapshot(),
16633 );
16634 let range = offset..offset;
16635 new_selections_by_buffer
16636 .entry(buffer_handle)
16637 .or_insert((Vec::new(), None))
16638 .0
16639 .push(range)
16640 } else {
16641 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
16642 else {
16643 continue;
16644 };
16645 new_selections_by_buffer
16646 .entry(buffer_handle)
16647 .or_insert((Vec::new(), None))
16648 .0
16649 .push(range)
16650 }
16651 }
16652 }
16653 }
16654 }
16655
16656 if new_selections_by_buffer.is_empty() {
16657 return;
16658 }
16659
16660 // We defer the pane interaction because we ourselves are a workspace item
16661 // and activating a new item causes the pane to call a method on us reentrantly,
16662 // which panics if we're on the stack.
16663 window.defer(cx, move |window, cx| {
16664 workspace.update(cx, |workspace, cx| {
16665 let pane = if split {
16666 workspace.adjacent_pane(window, cx)
16667 } else {
16668 workspace.active_pane().clone()
16669 };
16670
16671 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
16672 let editor = buffer
16673 .read(cx)
16674 .file()
16675 .is_none()
16676 .then(|| {
16677 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
16678 // so `workspace.open_project_item` will never find them, always opening a new editor.
16679 // Instead, we try to activate the existing editor in the pane first.
16680 let (editor, pane_item_index) =
16681 pane.read(cx).items().enumerate().find_map(|(i, item)| {
16682 let editor = item.downcast::<Editor>()?;
16683 let singleton_buffer =
16684 editor.read(cx).buffer().read(cx).as_singleton()?;
16685 if singleton_buffer == buffer {
16686 Some((editor, i))
16687 } else {
16688 None
16689 }
16690 })?;
16691 pane.update(cx, |pane, cx| {
16692 pane.activate_item(pane_item_index, true, true, window, cx)
16693 });
16694 Some(editor)
16695 })
16696 .flatten()
16697 .unwrap_or_else(|| {
16698 workspace.open_project_item::<Self>(
16699 pane.clone(),
16700 buffer,
16701 true,
16702 true,
16703 window,
16704 cx,
16705 )
16706 });
16707
16708 editor.update(cx, |editor, cx| {
16709 let autoscroll = match scroll_offset {
16710 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
16711 None => Autoscroll::newest(),
16712 };
16713 let nav_history = editor.nav_history.take();
16714 editor.change_selections(Some(autoscroll), window, cx, |s| {
16715 s.select_ranges(ranges);
16716 });
16717 editor.nav_history = nav_history;
16718 });
16719 }
16720 })
16721 });
16722 }
16723
16724 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
16725 let snapshot = self.buffer.read(cx).read(cx);
16726 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
16727 Some(
16728 ranges
16729 .iter()
16730 .map(move |range| {
16731 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
16732 })
16733 .collect(),
16734 )
16735 }
16736
16737 fn selection_replacement_ranges(
16738 &self,
16739 range: Range<OffsetUtf16>,
16740 cx: &mut App,
16741 ) -> Vec<Range<OffsetUtf16>> {
16742 let selections = self.selections.all::<OffsetUtf16>(cx);
16743 let newest_selection = selections
16744 .iter()
16745 .max_by_key(|selection| selection.id)
16746 .unwrap();
16747 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
16748 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
16749 let snapshot = self.buffer.read(cx).read(cx);
16750 selections
16751 .into_iter()
16752 .map(|mut selection| {
16753 selection.start.0 =
16754 (selection.start.0 as isize).saturating_add(start_delta) as usize;
16755 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
16756 snapshot.clip_offset_utf16(selection.start, Bias::Left)
16757 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
16758 })
16759 .collect()
16760 }
16761
16762 fn report_editor_event(
16763 &self,
16764 event_type: &'static str,
16765 file_extension: Option<String>,
16766 cx: &App,
16767 ) {
16768 if cfg!(any(test, feature = "test-support")) {
16769 return;
16770 }
16771
16772 let Some(project) = &self.project else { return };
16773
16774 // If None, we are in a file without an extension
16775 let file = self
16776 .buffer
16777 .read(cx)
16778 .as_singleton()
16779 .and_then(|b| b.read(cx).file());
16780 let file_extension = file_extension.or(file
16781 .as_ref()
16782 .and_then(|file| Path::new(file.file_name(cx)).extension())
16783 .and_then(|e| e.to_str())
16784 .map(|a| a.to_string()));
16785
16786 let vim_mode = cx
16787 .global::<SettingsStore>()
16788 .raw_user_settings()
16789 .get("vim_mode")
16790 == Some(&serde_json::Value::Bool(true));
16791
16792 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
16793 let copilot_enabled = edit_predictions_provider
16794 == language::language_settings::EditPredictionProvider::Copilot;
16795 let copilot_enabled_for_language = self
16796 .buffer
16797 .read(cx)
16798 .language_settings(cx)
16799 .show_edit_predictions;
16800
16801 let project = project.read(cx);
16802 telemetry::event!(
16803 event_type,
16804 file_extension,
16805 vim_mode,
16806 copilot_enabled,
16807 copilot_enabled_for_language,
16808 edit_predictions_provider,
16809 is_via_ssh = project.is_via_ssh(),
16810 );
16811 }
16812
16813 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
16814 /// with each line being an array of {text, highlight} objects.
16815 fn copy_highlight_json(
16816 &mut self,
16817 _: &CopyHighlightJson,
16818 window: &mut Window,
16819 cx: &mut Context<Self>,
16820 ) {
16821 #[derive(Serialize)]
16822 struct Chunk<'a> {
16823 text: String,
16824 highlight: Option<&'a str>,
16825 }
16826
16827 let snapshot = self.buffer.read(cx).snapshot(cx);
16828 let range = self
16829 .selected_text_range(false, window, cx)
16830 .and_then(|selection| {
16831 if selection.range.is_empty() {
16832 None
16833 } else {
16834 Some(selection.range)
16835 }
16836 })
16837 .unwrap_or_else(|| 0..snapshot.len());
16838
16839 let chunks = snapshot.chunks(range, true);
16840 let mut lines = Vec::new();
16841 let mut line: VecDeque<Chunk> = VecDeque::new();
16842
16843 let Some(style) = self.style.as_ref() else {
16844 return;
16845 };
16846
16847 for chunk in chunks {
16848 let highlight = chunk
16849 .syntax_highlight_id
16850 .and_then(|id| id.name(&style.syntax));
16851 let mut chunk_lines = chunk.text.split('\n').peekable();
16852 while let Some(text) = chunk_lines.next() {
16853 let mut merged_with_last_token = false;
16854 if let Some(last_token) = line.back_mut() {
16855 if last_token.highlight == highlight {
16856 last_token.text.push_str(text);
16857 merged_with_last_token = true;
16858 }
16859 }
16860
16861 if !merged_with_last_token {
16862 line.push_back(Chunk {
16863 text: text.into(),
16864 highlight,
16865 });
16866 }
16867
16868 if chunk_lines.peek().is_some() {
16869 if line.len() > 1 && line.front().unwrap().text.is_empty() {
16870 line.pop_front();
16871 }
16872 if line.len() > 1 && line.back().unwrap().text.is_empty() {
16873 line.pop_back();
16874 }
16875
16876 lines.push(mem::take(&mut line));
16877 }
16878 }
16879 }
16880
16881 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
16882 return;
16883 };
16884 cx.write_to_clipboard(ClipboardItem::new_string(lines));
16885 }
16886
16887 pub fn open_context_menu(
16888 &mut self,
16889 _: &OpenContextMenu,
16890 window: &mut Window,
16891 cx: &mut Context<Self>,
16892 ) {
16893 self.request_autoscroll(Autoscroll::newest(), cx);
16894 let position = self.selections.newest_display(cx).start;
16895 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
16896 }
16897
16898 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
16899 &self.inlay_hint_cache
16900 }
16901
16902 pub fn replay_insert_event(
16903 &mut self,
16904 text: &str,
16905 relative_utf16_range: Option<Range<isize>>,
16906 window: &mut Window,
16907 cx: &mut Context<Self>,
16908 ) {
16909 if !self.input_enabled {
16910 cx.emit(EditorEvent::InputIgnored { text: text.into() });
16911 return;
16912 }
16913 if let Some(relative_utf16_range) = relative_utf16_range {
16914 let selections = self.selections.all::<OffsetUtf16>(cx);
16915 self.change_selections(None, window, cx, |s| {
16916 let new_ranges = selections.into_iter().map(|range| {
16917 let start = OffsetUtf16(
16918 range
16919 .head()
16920 .0
16921 .saturating_add_signed(relative_utf16_range.start),
16922 );
16923 let end = OffsetUtf16(
16924 range
16925 .head()
16926 .0
16927 .saturating_add_signed(relative_utf16_range.end),
16928 );
16929 start..end
16930 });
16931 s.select_ranges(new_ranges);
16932 });
16933 }
16934
16935 self.handle_input(text, window, cx);
16936 }
16937
16938 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
16939 let Some(provider) = self.semantics_provider.as_ref() else {
16940 return false;
16941 };
16942
16943 let mut supports = false;
16944 self.buffer().update(cx, |this, cx| {
16945 this.for_each_buffer(|buffer| {
16946 supports |= provider.supports_inlay_hints(buffer, cx);
16947 });
16948 });
16949
16950 supports
16951 }
16952
16953 pub fn is_focused(&self, window: &Window) -> bool {
16954 self.focus_handle.is_focused(window)
16955 }
16956
16957 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16958 cx.emit(EditorEvent::Focused);
16959
16960 if let Some(descendant) = self
16961 .last_focused_descendant
16962 .take()
16963 .and_then(|descendant| descendant.upgrade())
16964 {
16965 window.focus(&descendant);
16966 } else {
16967 if let Some(blame) = self.blame.as_ref() {
16968 blame.update(cx, GitBlame::focus)
16969 }
16970
16971 self.blink_manager.update(cx, BlinkManager::enable);
16972 self.show_cursor_names(window, cx);
16973 self.buffer.update(cx, |buffer, cx| {
16974 buffer.finalize_last_transaction(cx);
16975 if self.leader_peer_id.is_none() {
16976 buffer.set_active_selections(
16977 &self.selections.disjoint_anchors(),
16978 self.selections.line_mode,
16979 self.cursor_shape,
16980 cx,
16981 );
16982 }
16983 });
16984 }
16985 }
16986
16987 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
16988 cx.emit(EditorEvent::FocusedIn)
16989 }
16990
16991 fn handle_focus_out(
16992 &mut self,
16993 event: FocusOutEvent,
16994 _window: &mut Window,
16995 cx: &mut Context<Self>,
16996 ) {
16997 if event.blurred != self.focus_handle {
16998 self.last_focused_descendant = Some(event.blurred);
16999 }
17000 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
17001 }
17002
17003 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17004 self.blink_manager.update(cx, BlinkManager::disable);
17005 self.buffer
17006 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
17007
17008 if let Some(blame) = self.blame.as_ref() {
17009 blame.update(cx, GitBlame::blur)
17010 }
17011 if !self.hover_state.focused(window, cx) {
17012 hide_hover(self, cx);
17013 }
17014 if !self
17015 .context_menu
17016 .borrow()
17017 .as_ref()
17018 .is_some_and(|context_menu| context_menu.focused(window, cx))
17019 {
17020 self.hide_context_menu(window, cx);
17021 }
17022 self.discard_inline_completion(false, cx);
17023 cx.emit(EditorEvent::Blurred);
17024 cx.notify();
17025 }
17026
17027 pub fn register_action<A: Action>(
17028 &mut self,
17029 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
17030 ) -> Subscription {
17031 let id = self.next_editor_action_id.post_inc();
17032 let listener = Arc::new(listener);
17033 self.editor_actions.borrow_mut().insert(
17034 id,
17035 Box::new(move |window, _| {
17036 let listener = listener.clone();
17037 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
17038 let action = action.downcast_ref().unwrap();
17039 if phase == DispatchPhase::Bubble {
17040 listener(action, window, cx)
17041 }
17042 })
17043 }),
17044 );
17045
17046 let editor_actions = self.editor_actions.clone();
17047 Subscription::new(move || {
17048 editor_actions.borrow_mut().remove(&id);
17049 })
17050 }
17051
17052 pub fn file_header_size(&self) -> u32 {
17053 FILE_HEADER_HEIGHT
17054 }
17055
17056 pub fn restore(
17057 &mut self,
17058 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
17059 window: &mut Window,
17060 cx: &mut Context<Self>,
17061 ) {
17062 let workspace = self.workspace();
17063 let project = self.project.as_ref();
17064 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
17065 let mut tasks = Vec::new();
17066 for (buffer_id, changes) in revert_changes {
17067 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
17068 buffer.update(cx, |buffer, cx| {
17069 buffer.edit(
17070 changes
17071 .into_iter()
17072 .map(|(range, text)| (range, text.to_string())),
17073 None,
17074 cx,
17075 );
17076 });
17077
17078 if let Some(project) =
17079 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
17080 {
17081 project.update(cx, |project, cx| {
17082 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
17083 })
17084 }
17085 }
17086 }
17087 tasks
17088 });
17089 cx.spawn_in(window, async move |_, cx| {
17090 for (buffer, task) in save_tasks {
17091 let result = task.await;
17092 if result.is_err() {
17093 let Some(path) = buffer
17094 .read_with(cx, |buffer, cx| buffer.project_path(cx))
17095 .ok()
17096 else {
17097 continue;
17098 };
17099 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
17100 let Some(task) = cx
17101 .update_window_entity(&workspace, |workspace, window, cx| {
17102 workspace
17103 .open_path_preview(path, None, false, false, false, window, cx)
17104 })
17105 .ok()
17106 else {
17107 continue;
17108 };
17109 task.await.log_err();
17110 }
17111 }
17112 }
17113 })
17114 .detach();
17115 self.change_selections(None, window, cx, |selections| selections.refresh());
17116 }
17117
17118 pub fn to_pixel_point(
17119 &self,
17120 source: multi_buffer::Anchor,
17121 editor_snapshot: &EditorSnapshot,
17122 window: &mut Window,
17123 ) -> Option<gpui::Point<Pixels>> {
17124 let source_point = source.to_display_point(editor_snapshot);
17125 self.display_to_pixel_point(source_point, editor_snapshot, window)
17126 }
17127
17128 pub fn display_to_pixel_point(
17129 &self,
17130 source: DisplayPoint,
17131 editor_snapshot: &EditorSnapshot,
17132 window: &mut Window,
17133 ) -> Option<gpui::Point<Pixels>> {
17134 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
17135 let text_layout_details = self.text_layout_details(window);
17136 let scroll_top = text_layout_details
17137 .scroll_anchor
17138 .scroll_position(editor_snapshot)
17139 .y;
17140
17141 if source.row().as_f32() < scroll_top.floor() {
17142 return None;
17143 }
17144 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
17145 let source_y = line_height * (source.row().as_f32() - scroll_top);
17146 Some(gpui::Point::new(source_x, source_y))
17147 }
17148
17149 pub fn has_visible_completions_menu(&self) -> bool {
17150 !self.edit_prediction_preview_is_active()
17151 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
17152 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
17153 })
17154 }
17155
17156 pub fn register_addon<T: Addon>(&mut self, instance: T) {
17157 self.addons
17158 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
17159 }
17160
17161 pub fn unregister_addon<T: Addon>(&mut self) {
17162 self.addons.remove(&std::any::TypeId::of::<T>());
17163 }
17164
17165 pub fn addon<T: Addon>(&self) -> Option<&T> {
17166 let type_id = std::any::TypeId::of::<T>();
17167 self.addons
17168 .get(&type_id)
17169 .and_then(|item| item.to_any().downcast_ref::<T>())
17170 }
17171
17172 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
17173 let text_layout_details = self.text_layout_details(window);
17174 let style = &text_layout_details.editor_style;
17175 let font_id = window.text_system().resolve_font(&style.text.font());
17176 let font_size = style.text.font_size.to_pixels(window.rem_size());
17177 let line_height = style.text.line_height_in_pixels(window.rem_size());
17178 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
17179
17180 gpui::Size::new(em_width, line_height)
17181 }
17182
17183 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
17184 self.load_diff_task.clone()
17185 }
17186
17187 fn read_metadata_from_db(
17188 &mut self,
17189 item_id: u64,
17190 workspace_id: WorkspaceId,
17191 window: &mut Window,
17192 cx: &mut Context<Editor>,
17193 ) {
17194 if self.is_singleton(cx)
17195 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
17196 {
17197 let buffer_snapshot = OnceCell::new();
17198
17199 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
17200 if !selections.is_empty() {
17201 let snapshot =
17202 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
17203 self.change_selections(None, window, cx, |s| {
17204 s.select_ranges(selections.into_iter().map(|(start, end)| {
17205 snapshot.clip_offset(start, Bias::Left)
17206 ..snapshot.clip_offset(end, Bias::Right)
17207 }));
17208 });
17209 }
17210 };
17211
17212 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
17213 if !folds.is_empty() {
17214 let snapshot =
17215 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
17216 self.fold_ranges(
17217 folds
17218 .into_iter()
17219 .map(|(start, end)| {
17220 snapshot.clip_offset(start, Bias::Left)
17221 ..snapshot.clip_offset(end, Bias::Right)
17222 })
17223 .collect(),
17224 false,
17225 window,
17226 cx,
17227 );
17228 }
17229 }
17230 }
17231
17232 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
17233 }
17234}
17235
17236fn insert_extra_newline_brackets(
17237 buffer: &MultiBufferSnapshot,
17238 range: Range<usize>,
17239 language: &language::LanguageScope,
17240) -> bool {
17241 let leading_whitespace_len = buffer
17242 .reversed_chars_at(range.start)
17243 .take_while(|c| c.is_whitespace() && *c != '\n')
17244 .map(|c| c.len_utf8())
17245 .sum::<usize>();
17246 let trailing_whitespace_len = buffer
17247 .chars_at(range.end)
17248 .take_while(|c| c.is_whitespace() && *c != '\n')
17249 .map(|c| c.len_utf8())
17250 .sum::<usize>();
17251 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
17252
17253 language.brackets().any(|(pair, enabled)| {
17254 let pair_start = pair.start.trim_end();
17255 let pair_end = pair.end.trim_start();
17256
17257 enabled
17258 && pair.newline
17259 && buffer.contains_str_at(range.end, pair_end)
17260 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
17261 })
17262}
17263
17264fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
17265 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
17266 [(buffer, range, _)] => (*buffer, range.clone()),
17267 _ => return false,
17268 };
17269 let pair = {
17270 let mut result: Option<BracketMatch> = None;
17271
17272 for pair in buffer
17273 .all_bracket_ranges(range.clone())
17274 .filter(move |pair| {
17275 pair.open_range.start <= range.start && pair.close_range.end >= range.end
17276 })
17277 {
17278 let len = pair.close_range.end - pair.open_range.start;
17279
17280 if let Some(existing) = &result {
17281 let existing_len = existing.close_range.end - existing.open_range.start;
17282 if len > existing_len {
17283 continue;
17284 }
17285 }
17286
17287 result = Some(pair);
17288 }
17289
17290 result
17291 };
17292 let Some(pair) = pair else {
17293 return false;
17294 };
17295 pair.newline_only
17296 && buffer
17297 .chars_for_range(pair.open_range.end..range.start)
17298 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
17299 .all(|c| c.is_whitespace() && c != '\n')
17300}
17301
17302fn get_uncommitted_diff_for_buffer(
17303 project: &Entity<Project>,
17304 buffers: impl IntoIterator<Item = Entity<Buffer>>,
17305 buffer: Entity<MultiBuffer>,
17306 cx: &mut App,
17307) -> Task<()> {
17308 let mut tasks = Vec::new();
17309 project.update(cx, |project, cx| {
17310 for buffer in buffers {
17311 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
17312 }
17313 });
17314 cx.spawn(async move |cx| {
17315 let diffs = future::join_all(tasks).await;
17316 buffer
17317 .update(cx, |buffer, cx| {
17318 for diff in diffs.into_iter().flatten() {
17319 buffer.add_diff(diff, cx);
17320 }
17321 })
17322 .ok();
17323 })
17324}
17325
17326fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
17327 let tab_size = tab_size.get() as usize;
17328 let mut width = offset;
17329
17330 for ch in text.chars() {
17331 width += if ch == '\t' {
17332 tab_size - (width % tab_size)
17333 } else {
17334 1
17335 };
17336 }
17337
17338 width - offset
17339}
17340
17341#[cfg(test)]
17342mod tests {
17343 use super::*;
17344
17345 #[test]
17346 fn test_string_size_with_expanded_tabs() {
17347 let nz = |val| NonZeroU32::new(val).unwrap();
17348 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
17349 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
17350 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
17351 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
17352 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
17353 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
17354 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
17355 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
17356 }
17357}
17358
17359/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
17360struct WordBreakingTokenizer<'a> {
17361 input: &'a str,
17362}
17363
17364impl<'a> WordBreakingTokenizer<'a> {
17365 fn new(input: &'a str) -> Self {
17366 Self { input }
17367 }
17368}
17369
17370fn is_char_ideographic(ch: char) -> bool {
17371 use unicode_script::Script::*;
17372 use unicode_script::UnicodeScript;
17373 matches!(ch.script(), Han | Tangut | Yi)
17374}
17375
17376fn is_grapheme_ideographic(text: &str) -> bool {
17377 text.chars().any(is_char_ideographic)
17378}
17379
17380fn is_grapheme_whitespace(text: &str) -> bool {
17381 text.chars().any(|x| x.is_whitespace())
17382}
17383
17384fn should_stay_with_preceding_ideograph(text: &str) -> bool {
17385 text.chars().next().map_or(false, |ch| {
17386 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
17387 })
17388}
17389
17390#[derive(PartialEq, Eq, Debug, Clone, Copy)]
17391enum WordBreakToken<'a> {
17392 Word { token: &'a str, grapheme_len: usize },
17393 InlineWhitespace { token: &'a str, grapheme_len: usize },
17394 Newline,
17395}
17396
17397impl<'a> Iterator for WordBreakingTokenizer<'a> {
17398 /// Yields a span, the count of graphemes in the token, and whether it was
17399 /// whitespace. Note that it also breaks at word boundaries.
17400 type Item = WordBreakToken<'a>;
17401
17402 fn next(&mut self) -> Option<Self::Item> {
17403 use unicode_segmentation::UnicodeSegmentation;
17404 if self.input.is_empty() {
17405 return None;
17406 }
17407
17408 let mut iter = self.input.graphemes(true).peekable();
17409 let mut offset = 0;
17410 let mut grapheme_len = 0;
17411 if let Some(first_grapheme) = iter.next() {
17412 let is_newline = first_grapheme == "\n";
17413 let is_whitespace = is_grapheme_whitespace(first_grapheme);
17414 offset += first_grapheme.len();
17415 grapheme_len += 1;
17416 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
17417 if let Some(grapheme) = iter.peek().copied() {
17418 if should_stay_with_preceding_ideograph(grapheme) {
17419 offset += grapheme.len();
17420 grapheme_len += 1;
17421 }
17422 }
17423 } else {
17424 let mut words = self.input[offset..].split_word_bound_indices().peekable();
17425 let mut next_word_bound = words.peek().copied();
17426 if next_word_bound.map_or(false, |(i, _)| i == 0) {
17427 next_word_bound = words.next();
17428 }
17429 while let Some(grapheme) = iter.peek().copied() {
17430 if next_word_bound.map_or(false, |(i, _)| i == offset) {
17431 break;
17432 };
17433 if is_grapheme_whitespace(grapheme) != is_whitespace
17434 || (grapheme == "\n") != is_newline
17435 {
17436 break;
17437 };
17438 offset += grapheme.len();
17439 grapheme_len += 1;
17440 iter.next();
17441 }
17442 }
17443 let token = &self.input[..offset];
17444 self.input = &self.input[offset..];
17445 if token == "\n" {
17446 Some(WordBreakToken::Newline)
17447 } else if is_whitespace {
17448 Some(WordBreakToken::InlineWhitespace {
17449 token,
17450 grapheme_len,
17451 })
17452 } else {
17453 Some(WordBreakToken::Word {
17454 token,
17455 grapheme_len,
17456 })
17457 }
17458 } else {
17459 None
17460 }
17461 }
17462}
17463
17464#[test]
17465fn test_word_breaking_tokenizer() {
17466 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
17467 ("", &[]),
17468 (" ", &[whitespace(" ", 2)]),
17469 ("Ʒ", &[word("Ʒ", 1)]),
17470 ("Ǽ", &[word("Ǽ", 1)]),
17471 ("⋑", &[word("⋑", 1)]),
17472 ("⋑⋑", &[word("⋑⋑", 2)]),
17473 (
17474 "原理,进而",
17475 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
17476 ),
17477 (
17478 "hello world",
17479 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
17480 ),
17481 (
17482 "hello, world",
17483 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
17484 ),
17485 (
17486 " hello world",
17487 &[
17488 whitespace(" ", 2),
17489 word("hello", 5),
17490 whitespace(" ", 1),
17491 word("world", 5),
17492 ],
17493 ),
17494 (
17495 "这是什么 \n 钢笔",
17496 &[
17497 word("这", 1),
17498 word("是", 1),
17499 word("什", 1),
17500 word("么", 1),
17501 whitespace(" ", 1),
17502 newline(),
17503 whitespace(" ", 1),
17504 word("钢", 1),
17505 word("笔", 1),
17506 ],
17507 ),
17508 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
17509 ];
17510
17511 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
17512 WordBreakToken::Word {
17513 token,
17514 grapheme_len,
17515 }
17516 }
17517
17518 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
17519 WordBreakToken::InlineWhitespace {
17520 token,
17521 grapheme_len,
17522 }
17523 }
17524
17525 fn newline() -> WordBreakToken<'static> {
17526 WordBreakToken::Newline
17527 }
17528
17529 for (input, result) in tests {
17530 assert_eq!(
17531 WordBreakingTokenizer::new(input)
17532 .collect::<Vec<_>>()
17533 .as_slice(),
17534 *result,
17535 );
17536 }
17537}
17538
17539fn wrap_with_prefix(
17540 line_prefix: String,
17541 unwrapped_text: String,
17542 wrap_column: usize,
17543 tab_size: NonZeroU32,
17544 preserve_existing_whitespace: bool,
17545) -> String {
17546 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
17547 let mut wrapped_text = String::new();
17548 let mut current_line = line_prefix.clone();
17549
17550 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
17551 let mut current_line_len = line_prefix_len;
17552 let mut in_whitespace = false;
17553 for token in tokenizer {
17554 let have_preceding_whitespace = in_whitespace;
17555 match token {
17556 WordBreakToken::Word {
17557 token,
17558 grapheme_len,
17559 } => {
17560 in_whitespace = false;
17561 if current_line_len + grapheme_len > wrap_column
17562 && current_line_len != line_prefix_len
17563 {
17564 wrapped_text.push_str(current_line.trim_end());
17565 wrapped_text.push('\n');
17566 current_line.truncate(line_prefix.len());
17567 current_line_len = line_prefix_len;
17568 }
17569 current_line.push_str(token);
17570 current_line_len += grapheme_len;
17571 }
17572 WordBreakToken::InlineWhitespace {
17573 mut token,
17574 mut grapheme_len,
17575 } => {
17576 in_whitespace = true;
17577 if have_preceding_whitespace && !preserve_existing_whitespace {
17578 continue;
17579 }
17580 if !preserve_existing_whitespace {
17581 token = " ";
17582 grapheme_len = 1;
17583 }
17584 if current_line_len + grapheme_len > wrap_column {
17585 wrapped_text.push_str(current_line.trim_end());
17586 wrapped_text.push('\n');
17587 current_line.truncate(line_prefix.len());
17588 current_line_len = line_prefix_len;
17589 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
17590 current_line.push_str(token);
17591 current_line_len += grapheme_len;
17592 }
17593 }
17594 WordBreakToken::Newline => {
17595 in_whitespace = true;
17596 if preserve_existing_whitespace {
17597 wrapped_text.push_str(current_line.trim_end());
17598 wrapped_text.push('\n');
17599 current_line.truncate(line_prefix.len());
17600 current_line_len = line_prefix_len;
17601 } else if have_preceding_whitespace {
17602 continue;
17603 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
17604 {
17605 wrapped_text.push_str(current_line.trim_end());
17606 wrapped_text.push('\n');
17607 current_line.truncate(line_prefix.len());
17608 current_line_len = line_prefix_len;
17609 } else if current_line_len != line_prefix_len {
17610 current_line.push(' ');
17611 current_line_len += 1;
17612 }
17613 }
17614 }
17615 }
17616
17617 if !current_line.is_empty() {
17618 wrapped_text.push_str(¤t_line);
17619 }
17620 wrapped_text
17621}
17622
17623#[test]
17624fn test_wrap_with_prefix() {
17625 assert_eq!(
17626 wrap_with_prefix(
17627 "# ".to_string(),
17628 "abcdefg".to_string(),
17629 4,
17630 NonZeroU32::new(4).unwrap(),
17631 false,
17632 ),
17633 "# abcdefg"
17634 );
17635 assert_eq!(
17636 wrap_with_prefix(
17637 "".to_string(),
17638 "\thello world".to_string(),
17639 8,
17640 NonZeroU32::new(4).unwrap(),
17641 false,
17642 ),
17643 "hello\nworld"
17644 );
17645 assert_eq!(
17646 wrap_with_prefix(
17647 "// ".to_string(),
17648 "xx \nyy zz aa bb cc".to_string(),
17649 12,
17650 NonZeroU32::new(4).unwrap(),
17651 false,
17652 ),
17653 "// xx yy zz\n// aa bb cc"
17654 );
17655 assert_eq!(
17656 wrap_with_prefix(
17657 String::new(),
17658 "这是什么 \n 钢笔".to_string(),
17659 3,
17660 NonZeroU32::new(4).unwrap(),
17661 false,
17662 ),
17663 "这是什\n么 钢\n笔"
17664 );
17665}
17666
17667pub trait CollaborationHub {
17668 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
17669 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
17670 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
17671}
17672
17673impl CollaborationHub for Entity<Project> {
17674 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
17675 self.read(cx).collaborators()
17676 }
17677
17678 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
17679 self.read(cx).user_store().read(cx).participant_indices()
17680 }
17681
17682 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
17683 let this = self.read(cx);
17684 let user_ids = this.collaborators().values().map(|c| c.user_id);
17685 this.user_store().read_with(cx, |user_store, cx| {
17686 user_store.participant_names(user_ids, cx)
17687 })
17688 }
17689}
17690
17691pub trait SemanticsProvider {
17692 fn hover(
17693 &self,
17694 buffer: &Entity<Buffer>,
17695 position: text::Anchor,
17696 cx: &mut App,
17697 ) -> Option<Task<Vec<project::Hover>>>;
17698
17699 fn inlay_hints(
17700 &self,
17701 buffer_handle: Entity<Buffer>,
17702 range: Range<text::Anchor>,
17703 cx: &mut App,
17704 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
17705
17706 fn resolve_inlay_hint(
17707 &self,
17708 hint: InlayHint,
17709 buffer_handle: Entity<Buffer>,
17710 server_id: LanguageServerId,
17711 cx: &mut App,
17712 ) -> Option<Task<anyhow::Result<InlayHint>>>;
17713
17714 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
17715
17716 fn document_highlights(
17717 &self,
17718 buffer: &Entity<Buffer>,
17719 position: text::Anchor,
17720 cx: &mut App,
17721 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
17722
17723 fn definitions(
17724 &self,
17725 buffer: &Entity<Buffer>,
17726 position: text::Anchor,
17727 kind: GotoDefinitionKind,
17728 cx: &mut App,
17729 ) -> Option<Task<Result<Vec<LocationLink>>>>;
17730
17731 fn range_for_rename(
17732 &self,
17733 buffer: &Entity<Buffer>,
17734 position: text::Anchor,
17735 cx: &mut App,
17736 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
17737
17738 fn perform_rename(
17739 &self,
17740 buffer: &Entity<Buffer>,
17741 position: text::Anchor,
17742 new_name: String,
17743 cx: &mut App,
17744 ) -> Option<Task<Result<ProjectTransaction>>>;
17745}
17746
17747pub trait CompletionProvider {
17748 fn completions(
17749 &self,
17750 buffer: &Entity<Buffer>,
17751 buffer_position: text::Anchor,
17752 trigger: CompletionContext,
17753 window: &mut Window,
17754 cx: &mut Context<Editor>,
17755 ) -> Task<Result<Option<Vec<Completion>>>>;
17756
17757 fn resolve_completions(
17758 &self,
17759 buffer: Entity<Buffer>,
17760 completion_indices: Vec<usize>,
17761 completions: Rc<RefCell<Box<[Completion]>>>,
17762 cx: &mut Context<Editor>,
17763 ) -> Task<Result<bool>>;
17764
17765 fn apply_additional_edits_for_completion(
17766 &self,
17767 _buffer: Entity<Buffer>,
17768 _completions: Rc<RefCell<Box<[Completion]>>>,
17769 _completion_index: usize,
17770 _push_to_history: bool,
17771 _cx: &mut Context<Editor>,
17772 ) -> Task<Result<Option<language::Transaction>>> {
17773 Task::ready(Ok(None))
17774 }
17775
17776 fn is_completion_trigger(
17777 &self,
17778 buffer: &Entity<Buffer>,
17779 position: language::Anchor,
17780 text: &str,
17781 trigger_in_words: bool,
17782 cx: &mut Context<Editor>,
17783 ) -> bool;
17784
17785 fn sort_completions(&self) -> bool {
17786 true
17787 }
17788}
17789
17790pub trait CodeActionProvider {
17791 fn id(&self) -> Arc<str>;
17792
17793 fn code_actions(
17794 &self,
17795 buffer: &Entity<Buffer>,
17796 range: Range<text::Anchor>,
17797 window: &mut Window,
17798 cx: &mut App,
17799 ) -> Task<Result<Vec<CodeAction>>>;
17800
17801 fn apply_code_action(
17802 &self,
17803 buffer_handle: Entity<Buffer>,
17804 action: CodeAction,
17805 excerpt_id: ExcerptId,
17806 push_to_history: bool,
17807 window: &mut Window,
17808 cx: &mut App,
17809 ) -> Task<Result<ProjectTransaction>>;
17810}
17811
17812impl CodeActionProvider for Entity<Project> {
17813 fn id(&self) -> Arc<str> {
17814 "project".into()
17815 }
17816
17817 fn code_actions(
17818 &self,
17819 buffer: &Entity<Buffer>,
17820 range: Range<text::Anchor>,
17821 _window: &mut Window,
17822 cx: &mut App,
17823 ) -> Task<Result<Vec<CodeAction>>> {
17824 self.update(cx, |project, cx| {
17825 let code_lens = project.code_lens(buffer, range.clone(), cx);
17826 let code_actions = project.code_actions(buffer, range, None, cx);
17827 cx.background_spawn(async move {
17828 let (code_lens, code_actions) = join(code_lens, code_actions).await;
17829 Ok(code_lens
17830 .context("code lens fetch")?
17831 .into_iter()
17832 .chain(code_actions.context("code action fetch")?)
17833 .collect())
17834 })
17835 })
17836 }
17837
17838 fn apply_code_action(
17839 &self,
17840 buffer_handle: Entity<Buffer>,
17841 action: CodeAction,
17842 _excerpt_id: ExcerptId,
17843 push_to_history: bool,
17844 _window: &mut Window,
17845 cx: &mut App,
17846 ) -> Task<Result<ProjectTransaction>> {
17847 self.update(cx, |project, cx| {
17848 project.apply_code_action(buffer_handle, action, push_to_history, cx)
17849 })
17850 }
17851}
17852
17853fn snippet_completions(
17854 project: &Project,
17855 buffer: &Entity<Buffer>,
17856 buffer_position: text::Anchor,
17857 cx: &mut App,
17858) -> Task<Result<Vec<Completion>>> {
17859 let language = buffer.read(cx).language_at(buffer_position);
17860 let language_name = language.as_ref().map(|language| language.lsp_id());
17861 let snippet_store = project.snippets().read(cx);
17862 let snippets = snippet_store.snippets_for(language_name, cx);
17863
17864 if snippets.is_empty() {
17865 return Task::ready(Ok(vec![]));
17866 }
17867 let snapshot = buffer.read(cx).text_snapshot();
17868 let chars: String = snapshot
17869 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
17870 .collect();
17871
17872 let scope = language.map(|language| language.default_scope());
17873 let executor = cx.background_executor().clone();
17874
17875 cx.background_spawn(async move {
17876 let classifier = CharClassifier::new(scope).for_completion(true);
17877 let mut last_word = chars
17878 .chars()
17879 .take_while(|c| classifier.is_word(*c))
17880 .collect::<String>();
17881 last_word = last_word.chars().rev().collect();
17882
17883 if last_word.is_empty() {
17884 return Ok(vec![]);
17885 }
17886
17887 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
17888 let to_lsp = |point: &text::Anchor| {
17889 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
17890 point_to_lsp(end)
17891 };
17892 let lsp_end = to_lsp(&buffer_position);
17893
17894 let candidates = snippets
17895 .iter()
17896 .enumerate()
17897 .flat_map(|(ix, snippet)| {
17898 snippet
17899 .prefix
17900 .iter()
17901 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
17902 })
17903 .collect::<Vec<StringMatchCandidate>>();
17904
17905 let mut matches = fuzzy::match_strings(
17906 &candidates,
17907 &last_word,
17908 last_word.chars().any(|c| c.is_uppercase()),
17909 100,
17910 &Default::default(),
17911 executor,
17912 )
17913 .await;
17914
17915 // Remove all candidates where the query's start does not match the start of any word in the candidate
17916 if let Some(query_start) = last_word.chars().next() {
17917 matches.retain(|string_match| {
17918 split_words(&string_match.string).any(|word| {
17919 // Check that the first codepoint of the word as lowercase matches the first
17920 // codepoint of the query as lowercase
17921 word.chars()
17922 .flat_map(|codepoint| codepoint.to_lowercase())
17923 .zip(query_start.to_lowercase())
17924 .all(|(word_cp, query_cp)| word_cp == query_cp)
17925 })
17926 });
17927 }
17928
17929 let matched_strings = matches
17930 .into_iter()
17931 .map(|m| m.string)
17932 .collect::<HashSet<_>>();
17933
17934 let result: Vec<Completion> = snippets
17935 .into_iter()
17936 .filter_map(|snippet| {
17937 let matching_prefix = snippet
17938 .prefix
17939 .iter()
17940 .find(|prefix| matched_strings.contains(*prefix))?;
17941 let start = as_offset - last_word.len();
17942 let start = snapshot.anchor_before(start);
17943 let range = start..buffer_position;
17944 let lsp_start = to_lsp(&start);
17945 let lsp_range = lsp::Range {
17946 start: lsp_start,
17947 end: lsp_end,
17948 };
17949 Some(Completion {
17950 old_range: range,
17951 new_text: snippet.body.clone(),
17952 source: CompletionSource::Lsp {
17953 server_id: LanguageServerId(usize::MAX),
17954 resolved: true,
17955 lsp_completion: Box::new(lsp::CompletionItem {
17956 label: snippet.prefix.first().unwrap().clone(),
17957 kind: Some(CompletionItemKind::SNIPPET),
17958 label_details: snippet.description.as_ref().map(|description| {
17959 lsp::CompletionItemLabelDetails {
17960 detail: Some(description.clone()),
17961 description: None,
17962 }
17963 }),
17964 insert_text_format: Some(InsertTextFormat::SNIPPET),
17965 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17966 lsp::InsertReplaceEdit {
17967 new_text: snippet.body.clone(),
17968 insert: lsp_range,
17969 replace: lsp_range,
17970 },
17971 )),
17972 filter_text: Some(snippet.body.clone()),
17973 sort_text: Some(char::MAX.to_string()),
17974 ..lsp::CompletionItem::default()
17975 }),
17976 lsp_defaults: None,
17977 },
17978 label: CodeLabel {
17979 text: matching_prefix.clone(),
17980 runs: Vec::new(),
17981 filter_range: 0..matching_prefix.len(),
17982 },
17983 documentation: snippet
17984 .description
17985 .clone()
17986 .map(|description| CompletionDocumentation::SingleLine(description.into())),
17987 confirm: None,
17988 })
17989 })
17990 .collect();
17991
17992 Ok(result)
17993 })
17994}
17995
17996impl CompletionProvider for Entity<Project> {
17997 fn completions(
17998 &self,
17999 buffer: &Entity<Buffer>,
18000 buffer_position: text::Anchor,
18001 options: CompletionContext,
18002 _window: &mut Window,
18003 cx: &mut Context<Editor>,
18004 ) -> Task<Result<Option<Vec<Completion>>>> {
18005 self.update(cx, |project, cx| {
18006 let snippets = snippet_completions(project, buffer, buffer_position, cx);
18007 let project_completions = project.completions(buffer, buffer_position, options, cx);
18008 cx.background_spawn(async move {
18009 let snippets_completions = snippets.await?;
18010 match project_completions.await? {
18011 Some(mut completions) => {
18012 completions.extend(snippets_completions);
18013 Ok(Some(completions))
18014 }
18015 None => {
18016 if snippets_completions.is_empty() {
18017 Ok(None)
18018 } else {
18019 Ok(Some(snippets_completions))
18020 }
18021 }
18022 }
18023 })
18024 })
18025 }
18026
18027 fn resolve_completions(
18028 &self,
18029 buffer: Entity<Buffer>,
18030 completion_indices: Vec<usize>,
18031 completions: Rc<RefCell<Box<[Completion]>>>,
18032 cx: &mut Context<Editor>,
18033 ) -> Task<Result<bool>> {
18034 self.update(cx, |project, cx| {
18035 project.lsp_store().update(cx, |lsp_store, cx| {
18036 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
18037 })
18038 })
18039 }
18040
18041 fn apply_additional_edits_for_completion(
18042 &self,
18043 buffer: Entity<Buffer>,
18044 completions: Rc<RefCell<Box<[Completion]>>>,
18045 completion_index: usize,
18046 push_to_history: bool,
18047 cx: &mut Context<Editor>,
18048 ) -> Task<Result<Option<language::Transaction>>> {
18049 self.update(cx, |project, cx| {
18050 project.lsp_store().update(cx, |lsp_store, cx| {
18051 lsp_store.apply_additional_edits_for_completion(
18052 buffer,
18053 completions,
18054 completion_index,
18055 push_to_history,
18056 cx,
18057 )
18058 })
18059 })
18060 }
18061
18062 fn is_completion_trigger(
18063 &self,
18064 buffer: &Entity<Buffer>,
18065 position: language::Anchor,
18066 text: &str,
18067 trigger_in_words: bool,
18068 cx: &mut Context<Editor>,
18069 ) -> bool {
18070 let mut chars = text.chars();
18071 let char = if let Some(char) = chars.next() {
18072 char
18073 } else {
18074 return false;
18075 };
18076 if chars.next().is_some() {
18077 return false;
18078 }
18079
18080 let buffer = buffer.read(cx);
18081 let snapshot = buffer.snapshot();
18082 if !snapshot.settings_at(position, cx).show_completions_on_input {
18083 return false;
18084 }
18085 let classifier = snapshot.char_classifier_at(position).for_completion(true);
18086 if trigger_in_words && classifier.is_word(char) {
18087 return true;
18088 }
18089
18090 buffer.completion_triggers().contains(text)
18091 }
18092}
18093
18094impl SemanticsProvider for Entity<Project> {
18095 fn hover(
18096 &self,
18097 buffer: &Entity<Buffer>,
18098 position: text::Anchor,
18099 cx: &mut App,
18100 ) -> Option<Task<Vec<project::Hover>>> {
18101 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
18102 }
18103
18104 fn document_highlights(
18105 &self,
18106 buffer: &Entity<Buffer>,
18107 position: text::Anchor,
18108 cx: &mut App,
18109 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
18110 Some(self.update(cx, |project, cx| {
18111 project.document_highlights(buffer, position, cx)
18112 }))
18113 }
18114
18115 fn definitions(
18116 &self,
18117 buffer: &Entity<Buffer>,
18118 position: text::Anchor,
18119 kind: GotoDefinitionKind,
18120 cx: &mut App,
18121 ) -> Option<Task<Result<Vec<LocationLink>>>> {
18122 Some(self.update(cx, |project, cx| match kind {
18123 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
18124 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
18125 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
18126 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
18127 }))
18128 }
18129
18130 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
18131 // TODO: make this work for remote projects
18132 self.update(cx, |this, cx| {
18133 buffer.update(cx, |buffer, cx| {
18134 this.any_language_server_supports_inlay_hints(buffer, cx)
18135 })
18136 })
18137 }
18138
18139 fn inlay_hints(
18140 &self,
18141 buffer_handle: Entity<Buffer>,
18142 range: Range<text::Anchor>,
18143 cx: &mut App,
18144 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
18145 Some(self.update(cx, |project, cx| {
18146 project.inlay_hints(buffer_handle, range, cx)
18147 }))
18148 }
18149
18150 fn resolve_inlay_hint(
18151 &self,
18152 hint: InlayHint,
18153 buffer_handle: Entity<Buffer>,
18154 server_id: LanguageServerId,
18155 cx: &mut App,
18156 ) -> Option<Task<anyhow::Result<InlayHint>>> {
18157 Some(self.update(cx, |project, cx| {
18158 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
18159 }))
18160 }
18161
18162 fn range_for_rename(
18163 &self,
18164 buffer: &Entity<Buffer>,
18165 position: text::Anchor,
18166 cx: &mut App,
18167 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
18168 Some(self.update(cx, |project, cx| {
18169 let buffer = buffer.clone();
18170 let task = project.prepare_rename(buffer.clone(), position, cx);
18171 cx.spawn(async move |_, cx| {
18172 Ok(match task.await? {
18173 PrepareRenameResponse::Success(range) => Some(range),
18174 PrepareRenameResponse::InvalidPosition => None,
18175 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
18176 // Fallback on using TreeSitter info to determine identifier range
18177 buffer.update(cx, |buffer, _| {
18178 let snapshot = buffer.snapshot();
18179 let (range, kind) = snapshot.surrounding_word(position);
18180 if kind != Some(CharKind::Word) {
18181 return None;
18182 }
18183 Some(
18184 snapshot.anchor_before(range.start)
18185 ..snapshot.anchor_after(range.end),
18186 )
18187 })?
18188 }
18189 })
18190 })
18191 }))
18192 }
18193
18194 fn perform_rename(
18195 &self,
18196 buffer: &Entity<Buffer>,
18197 position: text::Anchor,
18198 new_name: String,
18199 cx: &mut App,
18200 ) -> Option<Task<Result<ProjectTransaction>>> {
18201 Some(self.update(cx, |project, cx| {
18202 project.perform_rename(buffer.clone(), position, new_name, cx)
18203 }))
18204 }
18205}
18206
18207fn inlay_hint_settings(
18208 location: Anchor,
18209 snapshot: &MultiBufferSnapshot,
18210 cx: &mut Context<Editor>,
18211) -> InlayHintSettings {
18212 let file = snapshot.file_at(location);
18213 let language = snapshot.language_at(location).map(|l| l.name());
18214 language_settings(language, file, cx).inlay_hints
18215}
18216
18217fn consume_contiguous_rows(
18218 contiguous_row_selections: &mut Vec<Selection<Point>>,
18219 selection: &Selection<Point>,
18220 display_map: &DisplaySnapshot,
18221 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
18222) -> (MultiBufferRow, MultiBufferRow) {
18223 contiguous_row_selections.push(selection.clone());
18224 let start_row = MultiBufferRow(selection.start.row);
18225 let mut end_row = ending_row(selection, display_map);
18226
18227 while let Some(next_selection) = selections.peek() {
18228 if next_selection.start.row <= end_row.0 {
18229 end_row = ending_row(next_selection, display_map);
18230 contiguous_row_selections.push(selections.next().unwrap().clone());
18231 } else {
18232 break;
18233 }
18234 }
18235 (start_row, end_row)
18236}
18237
18238fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
18239 if next_selection.end.column > 0 || next_selection.is_empty() {
18240 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
18241 } else {
18242 MultiBufferRow(next_selection.end.row)
18243 }
18244}
18245
18246impl EditorSnapshot {
18247 pub fn remote_selections_in_range<'a>(
18248 &'a self,
18249 range: &'a Range<Anchor>,
18250 collaboration_hub: &dyn CollaborationHub,
18251 cx: &'a App,
18252 ) -> impl 'a + Iterator<Item = RemoteSelection> {
18253 let participant_names = collaboration_hub.user_names(cx);
18254 let participant_indices = collaboration_hub.user_participant_indices(cx);
18255 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
18256 let collaborators_by_replica_id = collaborators_by_peer_id
18257 .iter()
18258 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
18259 .collect::<HashMap<_, _>>();
18260 self.buffer_snapshot
18261 .selections_in_range(range, false)
18262 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
18263 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
18264 let participant_index = participant_indices.get(&collaborator.user_id).copied();
18265 let user_name = participant_names.get(&collaborator.user_id).cloned();
18266 Some(RemoteSelection {
18267 replica_id,
18268 selection,
18269 cursor_shape,
18270 line_mode,
18271 participant_index,
18272 peer_id: collaborator.peer_id,
18273 user_name,
18274 })
18275 })
18276 }
18277
18278 pub fn hunks_for_ranges(
18279 &self,
18280 ranges: impl IntoIterator<Item = Range<Point>>,
18281 ) -> Vec<MultiBufferDiffHunk> {
18282 let mut hunks = Vec::new();
18283 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
18284 HashMap::default();
18285 for query_range in ranges {
18286 let query_rows =
18287 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
18288 for hunk in self.buffer_snapshot.diff_hunks_in_range(
18289 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
18290 ) {
18291 // Include deleted hunks that are adjacent to the query range, because
18292 // otherwise they would be missed.
18293 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
18294 if hunk.status().is_deleted() {
18295 intersects_range |= hunk.row_range.start == query_rows.end;
18296 intersects_range |= hunk.row_range.end == query_rows.start;
18297 }
18298 if intersects_range {
18299 if !processed_buffer_rows
18300 .entry(hunk.buffer_id)
18301 .or_default()
18302 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
18303 {
18304 continue;
18305 }
18306 hunks.push(hunk);
18307 }
18308 }
18309 }
18310
18311 hunks
18312 }
18313
18314 fn display_diff_hunks_for_rows<'a>(
18315 &'a self,
18316 display_rows: Range<DisplayRow>,
18317 folded_buffers: &'a HashSet<BufferId>,
18318 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
18319 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
18320 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
18321
18322 self.buffer_snapshot
18323 .diff_hunks_in_range(buffer_start..buffer_end)
18324 .filter_map(|hunk| {
18325 if folded_buffers.contains(&hunk.buffer_id) {
18326 return None;
18327 }
18328
18329 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
18330 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
18331
18332 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
18333 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
18334
18335 let display_hunk = if hunk_display_start.column() != 0 {
18336 DisplayDiffHunk::Folded {
18337 display_row: hunk_display_start.row(),
18338 }
18339 } else {
18340 let mut end_row = hunk_display_end.row();
18341 if hunk_display_end.column() > 0 {
18342 end_row.0 += 1;
18343 }
18344 let is_created_file = hunk.is_created_file();
18345 DisplayDiffHunk::Unfolded {
18346 status: hunk.status(),
18347 diff_base_byte_range: hunk.diff_base_byte_range,
18348 display_row_range: hunk_display_start.row()..end_row,
18349 multi_buffer_range: Anchor::range_in_buffer(
18350 hunk.excerpt_id,
18351 hunk.buffer_id,
18352 hunk.buffer_range,
18353 ),
18354 is_created_file,
18355 }
18356 };
18357
18358 Some(display_hunk)
18359 })
18360 }
18361
18362 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
18363 self.display_snapshot.buffer_snapshot.language_at(position)
18364 }
18365
18366 pub fn is_focused(&self) -> bool {
18367 self.is_focused
18368 }
18369
18370 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
18371 self.placeholder_text.as_ref()
18372 }
18373
18374 pub fn scroll_position(&self) -> gpui::Point<f32> {
18375 self.scroll_anchor.scroll_position(&self.display_snapshot)
18376 }
18377
18378 fn gutter_dimensions(
18379 &self,
18380 font_id: FontId,
18381 font_size: Pixels,
18382 max_line_number_width: Pixels,
18383 cx: &App,
18384 ) -> Option<GutterDimensions> {
18385 if !self.show_gutter {
18386 return None;
18387 }
18388
18389 let descent = cx.text_system().descent(font_id, font_size);
18390 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
18391 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
18392
18393 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
18394 matches!(
18395 ProjectSettings::get_global(cx).git.git_gutter,
18396 Some(GitGutterSetting::TrackedFiles)
18397 )
18398 });
18399 let gutter_settings = EditorSettings::get_global(cx).gutter;
18400 let show_line_numbers = self
18401 .show_line_numbers
18402 .unwrap_or(gutter_settings.line_numbers);
18403 let line_gutter_width = if show_line_numbers {
18404 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
18405 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
18406 max_line_number_width.max(min_width_for_number_on_gutter)
18407 } else {
18408 0.0.into()
18409 };
18410
18411 let show_code_actions = self
18412 .show_code_actions
18413 .unwrap_or(gutter_settings.code_actions);
18414
18415 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
18416 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
18417
18418 let git_blame_entries_width =
18419 self.git_blame_gutter_max_author_length
18420 .map(|max_author_length| {
18421 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
18422
18423 /// The number of characters to dedicate to gaps and margins.
18424 const SPACING_WIDTH: usize = 4;
18425
18426 let max_char_count = max_author_length
18427 .min(GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED)
18428 + ::git::SHORT_SHA_LENGTH
18429 + MAX_RELATIVE_TIMESTAMP.len()
18430 + SPACING_WIDTH;
18431
18432 em_advance * max_char_count
18433 });
18434
18435 let is_singleton = self.buffer_snapshot.is_singleton();
18436
18437 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
18438 left_padding += if !is_singleton {
18439 em_width * 4.0
18440 } else if show_code_actions || show_runnables || show_breakpoints {
18441 em_width * 3.0
18442 } else if show_git_gutter && show_line_numbers {
18443 em_width * 2.0
18444 } else if show_git_gutter || show_line_numbers {
18445 em_width
18446 } else {
18447 px(0.)
18448 };
18449
18450 let shows_folds = is_singleton && gutter_settings.folds;
18451
18452 let right_padding = if shows_folds && show_line_numbers {
18453 em_width * 4.0
18454 } else if shows_folds || (!is_singleton && show_line_numbers) {
18455 em_width * 3.0
18456 } else if show_line_numbers {
18457 em_width
18458 } else {
18459 px(0.)
18460 };
18461
18462 Some(GutterDimensions {
18463 left_padding,
18464 right_padding,
18465 width: line_gutter_width + left_padding + right_padding,
18466 margin: -descent,
18467 git_blame_entries_width,
18468 })
18469 }
18470
18471 pub fn render_crease_toggle(
18472 &self,
18473 buffer_row: MultiBufferRow,
18474 row_contains_cursor: bool,
18475 editor: Entity<Editor>,
18476 window: &mut Window,
18477 cx: &mut App,
18478 ) -> Option<AnyElement> {
18479 let folded = self.is_line_folded(buffer_row);
18480 let mut is_foldable = false;
18481
18482 if let Some(crease) = self
18483 .crease_snapshot
18484 .query_row(buffer_row, &self.buffer_snapshot)
18485 {
18486 is_foldable = true;
18487 match crease {
18488 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
18489 if let Some(render_toggle) = render_toggle {
18490 let toggle_callback =
18491 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
18492 if folded {
18493 editor.update(cx, |editor, cx| {
18494 editor.fold_at(&crate::FoldAt { buffer_row }, window, cx)
18495 });
18496 } else {
18497 editor.update(cx, |editor, cx| {
18498 editor.unfold_at(
18499 &crate::UnfoldAt { buffer_row },
18500 window,
18501 cx,
18502 )
18503 });
18504 }
18505 });
18506 return Some((render_toggle)(
18507 buffer_row,
18508 folded,
18509 toggle_callback,
18510 window,
18511 cx,
18512 ));
18513 }
18514 }
18515 }
18516 }
18517
18518 is_foldable |= self.starts_indent(buffer_row);
18519
18520 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
18521 Some(
18522 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
18523 .toggle_state(folded)
18524 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
18525 if folded {
18526 this.unfold_at(&UnfoldAt { buffer_row }, window, cx);
18527 } else {
18528 this.fold_at(&FoldAt { buffer_row }, window, cx);
18529 }
18530 }))
18531 .into_any_element(),
18532 )
18533 } else {
18534 None
18535 }
18536 }
18537
18538 pub fn render_crease_trailer(
18539 &self,
18540 buffer_row: MultiBufferRow,
18541 window: &mut Window,
18542 cx: &mut App,
18543 ) -> Option<AnyElement> {
18544 let folded = self.is_line_folded(buffer_row);
18545 if let Crease::Inline { render_trailer, .. } = self
18546 .crease_snapshot
18547 .query_row(buffer_row, &self.buffer_snapshot)?
18548 {
18549 let render_trailer = render_trailer.as_ref()?;
18550 Some(render_trailer(buffer_row, folded, window, cx))
18551 } else {
18552 None
18553 }
18554 }
18555}
18556
18557impl Deref for EditorSnapshot {
18558 type Target = DisplaySnapshot;
18559
18560 fn deref(&self) -> &Self::Target {
18561 &self.display_snapshot
18562 }
18563}
18564
18565#[derive(Clone, Debug, PartialEq, Eq)]
18566pub enum EditorEvent {
18567 InputIgnored {
18568 text: Arc<str>,
18569 },
18570 InputHandled {
18571 utf16_range_to_replace: Option<Range<isize>>,
18572 text: Arc<str>,
18573 },
18574 ExcerptsAdded {
18575 buffer: Entity<Buffer>,
18576 predecessor: ExcerptId,
18577 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
18578 },
18579 ExcerptsRemoved {
18580 ids: Vec<ExcerptId>,
18581 },
18582 BufferFoldToggled {
18583 ids: Vec<ExcerptId>,
18584 folded: bool,
18585 },
18586 ExcerptsEdited {
18587 ids: Vec<ExcerptId>,
18588 },
18589 ExcerptsExpanded {
18590 ids: Vec<ExcerptId>,
18591 },
18592 BufferEdited,
18593 Edited {
18594 transaction_id: clock::Lamport,
18595 },
18596 Reparsed(BufferId),
18597 Focused,
18598 FocusedIn,
18599 Blurred,
18600 DirtyChanged,
18601 Saved,
18602 TitleChanged,
18603 DiffBaseChanged,
18604 SelectionsChanged {
18605 local: bool,
18606 },
18607 ScrollPositionChanged {
18608 local: bool,
18609 autoscroll: bool,
18610 },
18611 Closed,
18612 TransactionUndone {
18613 transaction_id: clock::Lamport,
18614 },
18615 TransactionBegun {
18616 transaction_id: clock::Lamport,
18617 },
18618 Reloaded,
18619 CursorShapeChanged,
18620}
18621
18622impl EventEmitter<EditorEvent> for Editor {}
18623
18624impl Focusable for Editor {
18625 fn focus_handle(&self, _cx: &App) -> FocusHandle {
18626 self.focus_handle.clone()
18627 }
18628}
18629
18630impl Render for Editor {
18631 fn render(&mut self, _: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
18632 let settings = ThemeSettings::get_global(cx);
18633
18634 let mut text_style = match self.mode {
18635 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
18636 color: cx.theme().colors().editor_foreground,
18637 font_family: settings.ui_font.family.clone(),
18638 font_features: settings.ui_font.features.clone(),
18639 font_fallbacks: settings.ui_font.fallbacks.clone(),
18640 font_size: rems(0.875).into(),
18641 font_weight: settings.ui_font.weight,
18642 line_height: relative(settings.buffer_line_height.value()),
18643 ..Default::default()
18644 },
18645 EditorMode::Full => TextStyle {
18646 color: cx.theme().colors().editor_foreground,
18647 font_family: settings.buffer_font.family.clone(),
18648 font_features: settings.buffer_font.features.clone(),
18649 font_fallbacks: settings.buffer_font.fallbacks.clone(),
18650 font_size: settings.buffer_font_size(cx).into(),
18651 font_weight: settings.buffer_font.weight,
18652 line_height: relative(settings.buffer_line_height.value()),
18653 ..Default::default()
18654 },
18655 };
18656 if let Some(text_style_refinement) = &self.text_style_refinement {
18657 text_style.refine(text_style_refinement)
18658 }
18659
18660 let background = match self.mode {
18661 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
18662 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
18663 EditorMode::Full => cx.theme().colors().editor_background,
18664 };
18665
18666 EditorElement::new(
18667 &cx.entity(),
18668 EditorStyle {
18669 background,
18670 local_player: cx.theme().players().local(),
18671 text: text_style,
18672 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
18673 syntax: cx.theme().syntax().clone(),
18674 status: cx.theme().status().clone(),
18675 inlay_hints_style: make_inlay_hints_style(cx),
18676 inline_completion_styles: make_suggestion_styles(cx),
18677 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
18678 },
18679 )
18680 }
18681}
18682
18683impl EntityInputHandler for Editor {
18684 fn text_for_range(
18685 &mut self,
18686 range_utf16: Range<usize>,
18687 adjusted_range: &mut Option<Range<usize>>,
18688 _: &mut Window,
18689 cx: &mut Context<Self>,
18690 ) -> Option<String> {
18691 let snapshot = self.buffer.read(cx).read(cx);
18692 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
18693 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
18694 if (start.0..end.0) != range_utf16 {
18695 adjusted_range.replace(start.0..end.0);
18696 }
18697 Some(snapshot.text_for_range(start..end).collect())
18698 }
18699
18700 fn selected_text_range(
18701 &mut self,
18702 ignore_disabled_input: bool,
18703 _: &mut Window,
18704 cx: &mut Context<Self>,
18705 ) -> Option<UTF16Selection> {
18706 // Prevent the IME menu from appearing when holding down an alphabetic key
18707 // while input is disabled.
18708 if !ignore_disabled_input && !self.input_enabled {
18709 return None;
18710 }
18711
18712 let selection = self.selections.newest::<OffsetUtf16>(cx);
18713 let range = selection.range();
18714
18715 Some(UTF16Selection {
18716 range: range.start.0..range.end.0,
18717 reversed: selection.reversed,
18718 })
18719 }
18720
18721 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
18722 let snapshot = self.buffer.read(cx).read(cx);
18723 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
18724 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
18725 }
18726
18727 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18728 self.clear_highlights::<InputComposition>(cx);
18729 self.ime_transaction.take();
18730 }
18731
18732 fn replace_text_in_range(
18733 &mut self,
18734 range_utf16: Option<Range<usize>>,
18735 text: &str,
18736 window: &mut Window,
18737 cx: &mut Context<Self>,
18738 ) {
18739 if !self.input_enabled {
18740 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18741 return;
18742 }
18743
18744 self.transact(window, cx, |this, window, cx| {
18745 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
18746 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
18747 Some(this.selection_replacement_ranges(range_utf16, cx))
18748 } else {
18749 this.marked_text_ranges(cx)
18750 };
18751
18752 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
18753 let newest_selection_id = this.selections.newest_anchor().id;
18754 this.selections
18755 .all::<OffsetUtf16>(cx)
18756 .iter()
18757 .zip(ranges_to_replace.iter())
18758 .find_map(|(selection, range)| {
18759 if selection.id == newest_selection_id {
18760 Some(
18761 (range.start.0 as isize - selection.head().0 as isize)
18762 ..(range.end.0 as isize - selection.head().0 as isize),
18763 )
18764 } else {
18765 None
18766 }
18767 })
18768 });
18769
18770 cx.emit(EditorEvent::InputHandled {
18771 utf16_range_to_replace: range_to_replace,
18772 text: text.into(),
18773 });
18774
18775 if let Some(new_selected_ranges) = new_selected_ranges {
18776 this.change_selections(None, window, cx, |selections| {
18777 selections.select_ranges(new_selected_ranges)
18778 });
18779 this.backspace(&Default::default(), window, cx);
18780 }
18781
18782 this.handle_input(text, window, cx);
18783 });
18784
18785 if let Some(transaction) = self.ime_transaction {
18786 self.buffer.update(cx, |buffer, cx| {
18787 buffer.group_until_transaction(transaction, cx);
18788 });
18789 }
18790
18791 self.unmark_text(window, cx);
18792 }
18793
18794 fn replace_and_mark_text_in_range(
18795 &mut self,
18796 range_utf16: Option<Range<usize>>,
18797 text: &str,
18798 new_selected_range_utf16: Option<Range<usize>>,
18799 window: &mut Window,
18800 cx: &mut Context<Self>,
18801 ) {
18802 if !self.input_enabled {
18803 return;
18804 }
18805
18806 let transaction = self.transact(window, cx, |this, window, cx| {
18807 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
18808 let snapshot = this.buffer.read(cx).read(cx);
18809 if let Some(relative_range_utf16) = range_utf16.as_ref() {
18810 for marked_range in &mut marked_ranges {
18811 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
18812 marked_range.start.0 += relative_range_utf16.start;
18813 marked_range.start =
18814 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
18815 marked_range.end =
18816 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
18817 }
18818 }
18819 Some(marked_ranges)
18820 } else if let Some(range_utf16) = range_utf16 {
18821 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
18822 Some(this.selection_replacement_ranges(range_utf16, cx))
18823 } else {
18824 None
18825 };
18826
18827 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
18828 let newest_selection_id = this.selections.newest_anchor().id;
18829 this.selections
18830 .all::<OffsetUtf16>(cx)
18831 .iter()
18832 .zip(ranges_to_replace.iter())
18833 .find_map(|(selection, range)| {
18834 if selection.id == newest_selection_id {
18835 Some(
18836 (range.start.0 as isize - selection.head().0 as isize)
18837 ..(range.end.0 as isize - selection.head().0 as isize),
18838 )
18839 } else {
18840 None
18841 }
18842 })
18843 });
18844
18845 cx.emit(EditorEvent::InputHandled {
18846 utf16_range_to_replace: range_to_replace,
18847 text: text.into(),
18848 });
18849
18850 if let Some(ranges) = ranges_to_replace {
18851 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
18852 }
18853
18854 let marked_ranges = {
18855 let snapshot = this.buffer.read(cx).read(cx);
18856 this.selections
18857 .disjoint_anchors()
18858 .iter()
18859 .map(|selection| {
18860 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
18861 })
18862 .collect::<Vec<_>>()
18863 };
18864
18865 if text.is_empty() {
18866 this.unmark_text(window, cx);
18867 } else {
18868 this.highlight_text::<InputComposition>(
18869 marked_ranges.clone(),
18870 HighlightStyle {
18871 underline: Some(UnderlineStyle {
18872 thickness: px(1.),
18873 color: None,
18874 wavy: false,
18875 }),
18876 ..Default::default()
18877 },
18878 cx,
18879 );
18880 }
18881
18882 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
18883 let use_autoclose = this.use_autoclose;
18884 let use_auto_surround = this.use_auto_surround;
18885 this.set_use_autoclose(false);
18886 this.set_use_auto_surround(false);
18887 this.handle_input(text, window, cx);
18888 this.set_use_autoclose(use_autoclose);
18889 this.set_use_auto_surround(use_auto_surround);
18890
18891 if let Some(new_selected_range) = new_selected_range_utf16 {
18892 let snapshot = this.buffer.read(cx).read(cx);
18893 let new_selected_ranges = marked_ranges
18894 .into_iter()
18895 .map(|marked_range| {
18896 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
18897 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
18898 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
18899 snapshot.clip_offset_utf16(new_start, Bias::Left)
18900 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
18901 })
18902 .collect::<Vec<_>>();
18903
18904 drop(snapshot);
18905 this.change_selections(None, window, cx, |selections| {
18906 selections.select_ranges(new_selected_ranges)
18907 });
18908 }
18909 });
18910
18911 self.ime_transaction = self.ime_transaction.or(transaction);
18912 if let Some(transaction) = self.ime_transaction {
18913 self.buffer.update(cx, |buffer, cx| {
18914 buffer.group_until_transaction(transaction, cx);
18915 });
18916 }
18917
18918 if self.text_highlights::<InputComposition>(cx).is_none() {
18919 self.ime_transaction.take();
18920 }
18921 }
18922
18923 fn bounds_for_range(
18924 &mut self,
18925 range_utf16: Range<usize>,
18926 element_bounds: gpui::Bounds<Pixels>,
18927 window: &mut Window,
18928 cx: &mut Context<Self>,
18929 ) -> Option<gpui::Bounds<Pixels>> {
18930 let text_layout_details = self.text_layout_details(window);
18931 let gpui::Size {
18932 width: em_width,
18933 height: line_height,
18934 } = self.character_size(window);
18935
18936 let snapshot = self.snapshot(window, cx);
18937 let scroll_position = snapshot.scroll_position();
18938 let scroll_left = scroll_position.x * em_width;
18939
18940 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
18941 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
18942 + self.gutter_dimensions.width
18943 + self.gutter_dimensions.margin;
18944 let y = line_height * (start.row().as_f32() - scroll_position.y);
18945
18946 Some(Bounds {
18947 origin: element_bounds.origin + point(x, y),
18948 size: size(em_width, line_height),
18949 })
18950 }
18951
18952 fn character_index_for_point(
18953 &mut self,
18954 point: gpui::Point<Pixels>,
18955 _window: &mut Window,
18956 _cx: &mut Context<Self>,
18957 ) -> Option<usize> {
18958 let position_map = self.last_position_map.as_ref()?;
18959 if !position_map.text_hitbox.contains(&point) {
18960 return None;
18961 }
18962 let display_point = position_map.point_for_position(point).previous_valid;
18963 let anchor = position_map
18964 .snapshot
18965 .display_point_to_anchor(display_point, Bias::Left);
18966 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
18967 Some(utf16_offset.0)
18968 }
18969}
18970
18971trait SelectionExt {
18972 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
18973 fn spanned_rows(
18974 &self,
18975 include_end_if_at_line_start: bool,
18976 map: &DisplaySnapshot,
18977 ) -> Range<MultiBufferRow>;
18978}
18979
18980impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
18981 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
18982 let start = self
18983 .start
18984 .to_point(&map.buffer_snapshot)
18985 .to_display_point(map);
18986 let end = self
18987 .end
18988 .to_point(&map.buffer_snapshot)
18989 .to_display_point(map);
18990 if self.reversed {
18991 end..start
18992 } else {
18993 start..end
18994 }
18995 }
18996
18997 fn spanned_rows(
18998 &self,
18999 include_end_if_at_line_start: bool,
19000 map: &DisplaySnapshot,
19001 ) -> Range<MultiBufferRow> {
19002 let start = self.start.to_point(&map.buffer_snapshot);
19003 let mut end = self.end.to_point(&map.buffer_snapshot);
19004 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
19005 end.row -= 1;
19006 }
19007
19008 let buffer_start = map.prev_line_boundary(start).0;
19009 let buffer_end = map.next_line_boundary(end).0;
19010 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
19011 }
19012}
19013
19014impl<T: InvalidationRegion> InvalidationStack<T> {
19015 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
19016 where
19017 S: Clone + ToOffset,
19018 {
19019 while let Some(region) = self.last() {
19020 let all_selections_inside_invalidation_ranges =
19021 if selections.len() == region.ranges().len() {
19022 selections
19023 .iter()
19024 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
19025 .all(|(selection, invalidation_range)| {
19026 let head = selection.head().to_offset(buffer);
19027 invalidation_range.start <= head && invalidation_range.end >= head
19028 })
19029 } else {
19030 false
19031 };
19032
19033 if all_selections_inside_invalidation_ranges {
19034 break;
19035 } else {
19036 self.pop();
19037 }
19038 }
19039 }
19040}
19041
19042impl<T> Default for InvalidationStack<T> {
19043 fn default() -> Self {
19044 Self(Default::default())
19045 }
19046}
19047
19048impl<T> Deref for InvalidationStack<T> {
19049 type Target = Vec<T>;
19050
19051 fn deref(&self) -> &Self::Target {
19052 &self.0
19053 }
19054}
19055
19056impl<T> DerefMut for InvalidationStack<T> {
19057 fn deref_mut(&mut self) -> &mut Self::Target {
19058 &mut self.0
19059 }
19060}
19061
19062impl InvalidationRegion for SnippetState {
19063 fn ranges(&self) -> &[Range<Anchor>] {
19064 &self.ranges[self.active_index]
19065 }
19066}
19067
19068pub fn diagnostic_block_renderer(
19069 diagnostic: Diagnostic,
19070 max_message_rows: Option<u8>,
19071 allow_closing: bool,
19072) -> RenderBlock {
19073 let (text_without_backticks, code_ranges) =
19074 highlight_diagnostic_message(&diagnostic, max_message_rows);
19075
19076 Arc::new(move |cx: &mut BlockContext| {
19077 let group_id: SharedString = cx.block_id.to_string().into();
19078
19079 let mut text_style = cx.window.text_style().clone();
19080 text_style.color = diagnostic_style(diagnostic.severity, cx.theme().status());
19081 let theme_settings = ThemeSettings::get_global(cx);
19082 text_style.font_family = theme_settings.buffer_font.family.clone();
19083 text_style.font_style = theme_settings.buffer_font.style;
19084 text_style.font_features = theme_settings.buffer_font.features.clone();
19085 text_style.font_weight = theme_settings.buffer_font.weight;
19086
19087 let multi_line_diagnostic = diagnostic.message.contains('\n');
19088
19089 let buttons = |diagnostic: &Diagnostic| {
19090 if multi_line_diagnostic {
19091 v_flex()
19092 } else {
19093 h_flex()
19094 }
19095 .when(allow_closing, |div| {
19096 div.children(diagnostic.is_primary.then(|| {
19097 IconButton::new("close-block", IconName::XCircle)
19098 .icon_color(Color::Muted)
19099 .size(ButtonSize::Compact)
19100 .style(ButtonStyle::Transparent)
19101 .visible_on_hover(group_id.clone())
19102 .on_click(move |_click, window, cx| {
19103 window.dispatch_action(Box::new(Cancel), cx)
19104 })
19105 .tooltip(|window, cx| {
19106 Tooltip::for_action("Close Diagnostics", &Cancel, window, cx)
19107 })
19108 }))
19109 })
19110 .child(
19111 IconButton::new("copy-block", IconName::Copy)
19112 .icon_color(Color::Muted)
19113 .size(ButtonSize::Compact)
19114 .style(ButtonStyle::Transparent)
19115 .visible_on_hover(group_id.clone())
19116 .on_click({
19117 let message = diagnostic.message.clone();
19118 move |_click, _, cx| {
19119 cx.write_to_clipboard(ClipboardItem::new_string(message.clone()))
19120 }
19121 })
19122 .tooltip(Tooltip::text("Copy diagnostic message")),
19123 )
19124 };
19125
19126 let icon_size = buttons(&diagnostic).into_any_element().layout_as_root(
19127 AvailableSpace::min_size(),
19128 cx.window,
19129 cx.app,
19130 );
19131
19132 h_flex()
19133 .id(cx.block_id)
19134 .group(group_id.clone())
19135 .relative()
19136 .size_full()
19137 .block_mouse_down()
19138 .pl(cx.gutter_dimensions.width)
19139 .w(cx.max_width - cx.gutter_dimensions.full_width())
19140 .child(
19141 div()
19142 .flex()
19143 .w(cx.anchor_x - cx.gutter_dimensions.width - icon_size.width)
19144 .flex_shrink(),
19145 )
19146 .child(buttons(&diagnostic))
19147 .child(div().flex().flex_shrink_0().child(
19148 StyledText::new(text_without_backticks.clone()).with_default_highlights(
19149 &text_style,
19150 code_ranges.iter().map(|range| {
19151 (
19152 range.clone(),
19153 HighlightStyle {
19154 font_weight: Some(FontWeight::BOLD),
19155 ..Default::default()
19156 },
19157 )
19158 }),
19159 ),
19160 ))
19161 .into_any_element()
19162 })
19163}
19164
19165fn inline_completion_edit_text(
19166 current_snapshot: &BufferSnapshot,
19167 edits: &[(Range<Anchor>, String)],
19168 edit_preview: &EditPreview,
19169 include_deletions: bool,
19170 cx: &App,
19171) -> HighlightedText {
19172 let edits = edits
19173 .iter()
19174 .map(|(anchor, text)| {
19175 (
19176 anchor.start.text_anchor..anchor.end.text_anchor,
19177 text.clone(),
19178 )
19179 })
19180 .collect::<Vec<_>>();
19181
19182 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
19183}
19184
19185pub fn highlight_diagnostic_message(
19186 diagnostic: &Diagnostic,
19187 mut max_message_rows: Option<u8>,
19188) -> (SharedString, Vec<Range<usize>>) {
19189 let mut text_without_backticks = String::new();
19190 let mut code_ranges = Vec::new();
19191
19192 if let Some(source) = &diagnostic.source {
19193 text_without_backticks.push_str(source);
19194 code_ranges.push(0..source.len());
19195 text_without_backticks.push_str(": ");
19196 }
19197
19198 let mut prev_offset = 0;
19199 let mut in_code_block = false;
19200 let has_row_limit = max_message_rows.is_some();
19201 let mut newline_indices = diagnostic
19202 .message
19203 .match_indices('\n')
19204 .filter(|_| has_row_limit)
19205 .map(|(ix, _)| ix)
19206 .fuse()
19207 .peekable();
19208
19209 for (quote_ix, _) in diagnostic
19210 .message
19211 .match_indices('`')
19212 .chain([(diagnostic.message.len(), "")])
19213 {
19214 let mut first_newline_ix = None;
19215 let mut last_newline_ix = None;
19216 while let Some(newline_ix) = newline_indices.peek() {
19217 if *newline_ix < quote_ix {
19218 if first_newline_ix.is_none() {
19219 first_newline_ix = Some(*newline_ix);
19220 }
19221 last_newline_ix = Some(*newline_ix);
19222
19223 if let Some(rows_left) = &mut max_message_rows {
19224 if *rows_left == 0 {
19225 break;
19226 } else {
19227 *rows_left -= 1;
19228 }
19229 }
19230 let _ = newline_indices.next();
19231 } else {
19232 break;
19233 }
19234 }
19235 let prev_len = text_without_backticks.len();
19236 let new_text = &diagnostic.message[prev_offset..first_newline_ix.unwrap_or(quote_ix)];
19237 text_without_backticks.push_str(new_text);
19238 if in_code_block {
19239 code_ranges.push(prev_len..text_without_backticks.len());
19240 }
19241 prev_offset = last_newline_ix.unwrap_or(quote_ix) + 1;
19242 in_code_block = !in_code_block;
19243 if first_newline_ix.map_or(false, |newline_ix| newline_ix < quote_ix) {
19244 text_without_backticks.push_str("...");
19245 break;
19246 }
19247 }
19248
19249 (text_without_backticks.into(), code_ranges)
19250}
19251
19252fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
19253 match severity {
19254 DiagnosticSeverity::ERROR => colors.error,
19255 DiagnosticSeverity::WARNING => colors.warning,
19256 DiagnosticSeverity::INFORMATION => colors.info,
19257 DiagnosticSeverity::HINT => colors.info,
19258 _ => colors.ignored,
19259 }
19260}
19261
19262pub fn styled_runs_for_code_label<'a>(
19263 label: &'a CodeLabel,
19264 syntax_theme: &'a theme::SyntaxTheme,
19265) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
19266 let fade_out = HighlightStyle {
19267 fade_out: Some(0.35),
19268 ..Default::default()
19269 };
19270
19271 let mut prev_end = label.filter_range.end;
19272 label
19273 .runs
19274 .iter()
19275 .enumerate()
19276 .flat_map(move |(ix, (range, highlight_id))| {
19277 let style = if let Some(style) = highlight_id.style(syntax_theme) {
19278 style
19279 } else {
19280 return Default::default();
19281 };
19282 let mut muted_style = style;
19283 muted_style.highlight(fade_out);
19284
19285 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
19286 if range.start >= label.filter_range.end {
19287 if range.start > prev_end {
19288 runs.push((prev_end..range.start, fade_out));
19289 }
19290 runs.push((range.clone(), muted_style));
19291 } else if range.end <= label.filter_range.end {
19292 runs.push((range.clone(), style));
19293 } else {
19294 runs.push((range.start..label.filter_range.end, style));
19295 runs.push((label.filter_range.end..range.end, muted_style));
19296 }
19297 prev_end = cmp::max(prev_end, range.end);
19298
19299 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
19300 runs.push((prev_end..label.text.len(), fade_out));
19301 }
19302
19303 runs
19304 })
19305}
19306
19307pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
19308 let mut prev_index = 0;
19309 let mut prev_codepoint: Option<char> = None;
19310 text.char_indices()
19311 .chain([(text.len(), '\0')])
19312 .filter_map(move |(index, codepoint)| {
19313 let prev_codepoint = prev_codepoint.replace(codepoint)?;
19314 let is_boundary = index == text.len()
19315 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
19316 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
19317 if is_boundary {
19318 let chunk = &text[prev_index..index];
19319 prev_index = index;
19320 Some(chunk)
19321 } else {
19322 None
19323 }
19324 })
19325}
19326
19327pub trait RangeToAnchorExt: Sized {
19328 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
19329
19330 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
19331 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
19332 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
19333 }
19334}
19335
19336impl<T: ToOffset> RangeToAnchorExt for Range<T> {
19337 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
19338 let start_offset = self.start.to_offset(snapshot);
19339 let end_offset = self.end.to_offset(snapshot);
19340 if start_offset == end_offset {
19341 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
19342 } else {
19343 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
19344 }
19345 }
19346}
19347
19348pub trait RowExt {
19349 fn as_f32(&self) -> f32;
19350
19351 fn next_row(&self) -> Self;
19352
19353 fn previous_row(&self) -> Self;
19354
19355 fn minus(&self, other: Self) -> u32;
19356}
19357
19358impl RowExt for DisplayRow {
19359 fn as_f32(&self) -> f32 {
19360 self.0 as f32
19361 }
19362
19363 fn next_row(&self) -> Self {
19364 Self(self.0 + 1)
19365 }
19366
19367 fn previous_row(&self) -> Self {
19368 Self(self.0.saturating_sub(1))
19369 }
19370
19371 fn minus(&self, other: Self) -> u32 {
19372 self.0 - other.0
19373 }
19374}
19375
19376impl RowExt for MultiBufferRow {
19377 fn as_f32(&self) -> f32 {
19378 self.0 as f32
19379 }
19380
19381 fn next_row(&self) -> Self {
19382 Self(self.0 + 1)
19383 }
19384
19385 fn previous_row(&self) -> Self {
19386 Self(self.0.saturating_sub(1))
19387 }
19388
19389 fn minus(&self, other: Self) -> u32 {
19390 self.0 - other.0
19391 }
19392}
19393
19394trait RowRangeExt {
19395 type Row;
19396
19397 fn len(&self) -> usize;
19398
19399 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
19400}
19401
19402impl RowRangeExt for Range<MultiBufferRow> {
19403 type Row = MultiBufferRow;
19404
19405 fn len(&self) -> usize {
19406 (self.end.0 - self.start.0) as usize
19407 }
19408
19409 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
19410 (self.start.0..self.end.0).map(MultiBufferRow)
19411 }
19412}
19413
19414impl RowRangeExt for Range<DisplayRow> {
19415 type Row = DisplayRow;
19416
19417 fn len(&self) -> usize {
19418 (self.end.0 - self.start.0) as usize
19419 }
19420
19421 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
19422 (self.start.0..self.end.0).map(DisplayRow)
19423 }
19424}
19425
19426/// If select range has more than one line, we
19427/// just point the cursor to range.start.
19428fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
19429 if range.start.row == range.end.row {
19430 range
19431 } else {
19432 range.start..range.start
19433 }
19434}
19435pub struct KillRing(ClipboardItem);
19436impl Global for KillRing {}
19437
19438const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
19439
19440struct BreakpointPromptEditor {
19441 pub(crate) prompt: Entity<Editor>,
19442 editor: WeakEntity<Editor>,
19443 breakpoint_anchor: Anchor,
19444 kind: BreakpointKind,
19445 block_ids: HashSet<CustomBlockId>,
19446 gutter_dimensions: Arc<Mutex<GutterDimensions>>,
19447 _subscriptions: Vec<Subscription>,
19448}
19449
19450impl BreakpointPromptEditor {
19451 const MAX_LINES: u8 = 4;
19452
19453 fn new(
19454 editor: WeakEntity<Editor>,
19455 breakpoint_anchor: Anchor,
19456 kind: BreakpointKind,
19457 window: &mut Window,
19458 cx: &mut Context<Self>,
19459 ) -> Self {
19460 let buffer = cx.new(|cx| {
19461 Buffer::local(
19462 kind.log_message()
19463 .map(|msg| msg.to_string())
19464 .unwrap_or_default(),
19465 cx,
19466 )
19467 });
19468 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19469
19470 let prompt = cx.new(|cx| {
19471 let mut prompt = Editor::new(
19472 EditorMode::AutoHeight {
19473 max_lines: Self::MAX_LINES as usize,
19474 },
19475 buffer,
19476 None,
19477 window,
19478 cx,
19479 );
19480 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
19481 prompt.set_show_cursor_when_unfocused(false, cx);
19482 prompt.set_placeholder_text(
19483 "Message to log when breakpoint is hit. Expressions within {} are interpolated.",
19484 cx,
19485 );
19486
19487 prompt
19488 });
19489
19490 Self {
19491 prompt,
19492 editor,
19493 breakpoint_anchor,
19494 kind,
19495 gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
19496 block_ids: Default::default(),
19497 _subscriptions: vec![],
19498 }
19499 }
19500
19501 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
19502 self.block_ids.extend(block_ids)
19503 }
19504
19505 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
19506 if let Some(editor) = self.editor.upgrade() {
19507 let log_message = self
19508 .prompt
19509 .read(cx)
19510 .buffer
19511 .read(cx)
19512 .as_singleton()
19513 .expect("A multi buffer in breakpoint prompt isn't possible")
19514 .read(cx)
19515 .as_rope()
19516 .to_string();
19517
19518 editor.update(cx, |editor, cx| {
19519 editor.edit_breakpoint_at_anchor(
19520 self.breakpoint_anchor,
19521 self.kind.clone(),
19522 BreakpointEditAction::EditLogMessage(log_message.into()),
19523 cx,
19524 );
19525
19526 editor.remove_blocks(self.block_ids.clone(), None, cx);
19527 cx.focus_self(window);
19528 });
19529 }
19530 }
19531
19532 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
19533 self.editor
19534 .update(cx, |editor, cx| {
19535 editor.remove_blocks(self.block_ids.clone(), None, cx);
19536 window.focus(&editor.focus_handle);
19537 })
19538 .log_err();
19539 }
19540
19541 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
19542 let settings = ThemeSettings::get_global(cx);
19543 let text_style = TextStyle {
19544 color: if self.prompt.read(cx).read_only(cx) {
19545 cx.theme().colors().text_disabled
19546 } else {
19547 cx.theme().colors().text
19548 },
19549 font_family: settings.buffer_font.family.clone(),
19550 font_fallbacks: settings.buffer_font.fallbacks.clone(),
19551 font_size: settings.buffer_font_size(cx).into(),
19552 font_weight: settings.buffer_font.weight,
19553 line_height: relative(settings.buffer_line_height.value()),
19554 ..Default::default()
19555 };
19556 EditorElement::new(
19557 &self.prompt,
19558 EditorStyle {
19559 background: cx.theme().colors().editor_background,
19560 local_player: cx.theme().players().local(),
19561 text: text_style,
19562 ..Default::default()
19563 },
19564 )
19565 }
19566}
19567
19568impl Render for BreakpointPromptEditor {
19569 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
19570 let gutter_dimensions = *self.gutter_dimensions.lock();
19571 h_flex()
19572 .key_context("Editor")
19573 .bg(cx.theme().colors().editor_background)
19574 .border_y_1()
19575 .border_color(cx.theme().status().info_border)
19576 .size_full()
19577 .py(window.line_height() / 2.5)
19578 .on_action(cx.listener(Self::confirm))
19579 .on_action(cx.listener(Self::cancel))
19580 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
19581 .child(div().flex_1().child(self.render_prompt_editor(cx)))
19582 }
19583}
19584
19585impl Focusable for BreakpointPromptEditor {
19586 fn focus_handle(&self, cx: &App) -> FocusHandle {
19587 self.prompt.focus_handle(cx)
19588 }
19589}
19590
19591fn all_edits_insertions_or_deletions(
19592 edits: &Vec<(Range<Anchor>, String)>,
19593 snapshot: &MultiBufferSnapshot,
19594) -> bool {
19595 let mut all_insertions = true;
19596 let mut all_deletions = true;
19597
19598 for (range, new_text) in edits.iter() {
19599 let range_is_empty = range.to_offset(&snapshot).is_empty();
19600 let text_is_empty = new_text.is_empty();
19601
19602 if range_is_empty != text_is_empty {
19603 if range_is_empty {
19604 all_deletions = false;
19605 } else {
19606 all_insertions = false;
19607 }
19608 } else {
19609 return false;
19610 }
19611
19612 if !all_insertions && !all_deletions {
19613 return false;
19614 }
19615 }
19616 all_insertions || all_deletions
19617}
19618
19619struct MissingEditPredictionKeybindingTooltip;
19620
19621impl Render for MissingEditPredictionKeybindingTooltip {
19622 fn render(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
19623 ui::tooltip_container(window, cx, |container, _, cx| {
19624 container
19625 .flex_shrink_0()
19626 .max_w_80()
19627 .min_h(rems_from_px(124.))
19628 .justify_between()
19629 .child(
19630 v_flex()
19631 .flex_1()
19632 .text_ui_sm(cx)
19633 .child(Label::new("Conflict with Accept Keybinding"))
19634 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
19635 )
19636 .child(
19637 h_flex()
19638 .pb_1()
19639 .gap_1()
19640 .items_end()
19641 .w_full()
19642 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
19643 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
19644 }))
19645 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
19646 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
19647 })),
19648 )
19649 })
19650 }
19651}
19652
19653#[derive(Debug, Clone, Copy, PartialEq)]
19654pub struct LineHighlight {
19655 pub background: Background,
19656 pub border: Option<gpui::Hsla>,
19657}
19658
19659impl From<Hsla> for LineHighlight {
19660 fn from(hsla: Hsla) -> Self {
19661 Self {
19662 background: hsla.into(),
19663 border: None,
19664 }
19665 }
19666}
19667
19668impl From<Background> for LineHighlight {
19669 fn from(background: Background) -> Self {
19670 Self {
19671 background,
19672 border: None,
19673 }
19674 }
19675}