Skip to repository content12539 lines · 459.6 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T02:31:28.331Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
editor.rs
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//!
11//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
12//!
13//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
14pub mod actions;
15pub mod blink_manager;
16mod bracket_colorization;
17mod clangd_ext;
18pub mod code_context_menus;
19mod code_lens;
20pub mod display_map;
21mod document_colors;
22mod document_links;
23mod document_symbols;
24mod editor_settings;
25mod element;
26mod fold;
27mod folding_ranges;
28mod git;
29mod highlight_matching_bracket;
30pub mod hover_links;
31pub mod hover_popover;
32mod indent_guides;
33mod inlays;
34pub mod items;
35mod jsx_tag_auto_close;
36mod linked_editing_ranges;
37mod lsp_ext;
38mod mouse_context_menu;
39pub mod movement;
40mod persistence;
41mod runnables;
42mod rust_analyzer_ext;
43pub mod scroll;
44mod selections_collection;
45pub mod semantic_tokens;
46mod split;
47pub mod split_editor_view;
48
49mod bookmarks;
50#[cfg(test)]
51mod code_completion_tests;
52#[cfg(test)]
53mod edit_prediction_tests;
54#[cfg(test)]
55mod editor_block_comment_tests;
56#[cfg(test)]
57mod editor_tests;
58mod signature_help;
59#[cfg(any(test, feature = "test-support"))]
60pub mod test;
61
62mod clipboard;
63mod code_actions;
64mod completions;
65mod config;
66mod diagnostics;
67mod edit_prediction;
68mod input;
69mod markdown_actions;
70mod navigation;
71mod rewrap;
72mod selection;
73
74pub(crate) use actions::*;
75pub use clipboard::ClipboardSelection;
76pub use code_actions::CodeActionProvider;
77use collections::TypeIdHashMap;
78pub use completions::CompletionProvider;
79#[cfg(test)]
80pub(crate) use completions::snippet_candidate_suffixes;
81pub(crate) use completions::split_words;
82use diagnostics::{ActiveDiagnostic, GlobalDiagnosticRenderer, InlineDiagnostic};
83pub use diagnostics::{DiagnosticRenderer, set_diagnostic_renderer};
84pub use display_map::{
85 ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder, HighlightKey,
86 NavigationOverlayKey, SemanticTokenHighlight,
87};
88pub use edit_prediction::make_suggestion_styles;
89pub(crate) use edit_prediction::{
90 EditDisplayMode, EditPrediction, EditPredictionPreview, EditPredictionSettings,
91 EditPredictionState, MenuEditPredictionsPolicy, RegisteredEditPredictionDelegate,
92};
93#[cfg(test)]
94pub(crate) use edit_prediction::{
95 EditPredictionKeybindAction, EditPredictionKeybindSurface, edit_prediction_edit_text,
96};
97pub use edit_prediction_types::Direction;
98pub use edit_prediction_types::EditPredictionRequestTrigger;
99pub use editor_settings::{
100 CompletionDetailAlignment, CompletionMenuItemKind, CurrentLineHighlight, DiffViewStyle,
101 DocumentColorsRenderMode, EditorSettings, EditorSettingsScrollbarProxy, OpenResultsIn,
102 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
103 ui_scrollbar_settings_from_raw,
104};
105pub use element::{
106 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
107 file_status_label_color, render_breadcrumb_text,
108};
109pub use git::blame::BlameRenderer;
110pub use git::{
111 DiffHunkDelegate, ResolvedDiffHunk, ResolvedDiffHunks, RestoreOnlyDiffHunkDelegate,
112 RestoreOnlyUnstagedDiffHunkDelegate, UncommittedDiffHunkDelegate, render_diff_hunk_controls,
113 set_blame_renderer,
114};
115pub(crate) use git::{DiffHunkKey, StoredReviewComment};
116use git::{
117 DiffReviewDragState, DiffReviewOverlay, InlineBlamePopover, update_uncommitted_diff_for_buffer,
118};
119pub(crate) use git::{DisplayDiffHunk, PhantomDiffReviewIndicator};
120pub use hover_popover::hover_markdown_style;
121pub use inlays::Inlay;
122pub use items::MAX_TAB_TITLE_LEN;
123pub use linked_editing_ranges::LinkedEdits;
124pub use lsp::CompletionContext;
125pub use lsp_ext::lsp_tasks;
126pub use multi_buffer::{
127 Anchor, AnchorRangeExt, BufferOffset, ExcerptRange, MBTextSummary, MultiBuffer,
128 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
129 ToPoint,
130};
131pub use split::{DiffStyleControls, SplittableEditor, ToggleSplitDiff};
132pub use split_editor_view::SplitEditorView;
133pub use text::Bias;
134
135use ::git::{Blame, status::FileStatus};
136use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
137use anyhow::{Context as _, Result, anyhow, bail};
138use blink_manager::BlinkManager;
139use client::{Collaborator, ParticipantIndex, parse_zed_link};
140use clock::ReplicaId;
141use code_context_menus::{
142 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
143 CompletionsMenu, ContextMenuOrigin,
144};
145use code_lens::CodeLensState;
146use collections::{BTreeMap, HashMap, HashSet, VecDeque};
147use convert_case::{Case, Casing};
148use dap::TelemetrySpawnLocation;
149use display_map::*;
150use document_colors::LspColorData;
151use document_links::LspDocumentLinks;
152use edit_prediction_types::{
153 EditPredictionDelegate, EditPredictionDelegateHandle, EditPredictionDiscardReason,
154 EditPredictionGranularity, SuggestionDisplayType,
155};
156use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
157use element::{LineWithInvisibles, PositionMap, layout_line};
158use futures::{
159 FutureExt,
160 future::{self, Shared},
161};
162use fuzzy::{StringMatch, StringMatchCandidate};
163use git::blame::{GitBlame, GlobalBlameRenderer};
164use gpui::{
165 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
166 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
167 DispatchPhase, Edges, Entity, EntityId, EntityInputHandler, EventEmitter, FocusHandle,
168 FocusOutEvent, Focusable, FontId, FontStyle, FontWeight, Global, HighlightStyle, Hsla, IsZero,
169 KeyContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement,
170 Pixels, PressureStage, Render, ScrollHandle, SharedString, SharedUri, Size, Stateful, Styled,
171 Subscription, Task, TextRun, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle,
172 UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window, div, point, prelude::*,
173 pulsating_between, px, relative, size,
174};
175use hover_links::{HoverLink, HoveredLinkState, find_file};
176use hover_popover::{HoverState, hide_hover};
177use indent_guides::ActiveIndentGuidesState;
178use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
179use itertools::{Either, Itertools};
180use language::{
181 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
182 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
183 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
184 IndentSize, Language, LanguageAwareStyling, LanguageName, LanguageRegistry, LanguageScope,
185 LocalFile, OffsetRangeExt, OutlineItem, Point, Selection, SelectionGoal, TextObject,
186 TransactionId, TreeSitterOptions, WordsQuery,
187 language_settings::{
188 self, AllLanguageSettings, LanguageSettings, LspInsertMode, RewrapBehavior,
189 WordsCompletionMode, all_language_settings,
190 },
191 point_from_lsp, point_to_lsp, text_diff_with_options,
192};
193use linked_editing_ranges::refresh_linked_ranges;
194use lsp::{
195 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
196 LanguageServerId,
197};
198use markdown::Markdown;
199use mouse_context_menu::MouseContextMenu;
200use movement::TextLayoutDetails;
201use multi_buffer::{
202 ExcerptBoundaryInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint,
203 MultiBufferRow,
204};
205use parking_lot::Mutex;
206use persistence::EditorDb;
207use project::{
208 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
209 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
210 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
211 ProjectItem, ProjectPath, ProjectTransaction,
212 bookmark_store::BookmarkStore,
213 debugger::{
214 breakpoint_store::{
215 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
216 BreakpointStore, BreakpointStoreEvent,
217 },
218 session::{Session, SessionEvent},
219 },
220 git_store::GitStoreEvent,
221 lsp_store::{
222 BufferSemanticTokens, CacheInlayHints, CompletionDocumentation, FormatTrigger,
223 LspFormatTarget, OpenLspBufferHandle,
224 },
225 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
226};
227use rand::seq::SliceRandom;
228use regex::Regex;
229use rpc::{ErrorCode, ErrorExt, proto::PeerId};
230use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, SharedScrollAnchor};
231use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
232use serde::{Deserialize, Serialize};
233use settings::{
234 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
235 update_settings_file,
236};
237use smallvec::{SmallVec, smallvec};
238use snippet::Snippet;
239use std::{
240 any::{Any, TypeId},
241 borrow::Cow,
242 cell::{OnceCell, RefCell},
243 cmp::{self, Ordering, Reverse},
244 collections::hash_map,
245 iter::{self, Peekable},
246 mem,
247 num::NonZeroU32,
248 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
249 path::{Path, PathBuf},
250 rc::Rc,
251 sync::Arc,
252 time::{Duration, Instant},
253};
254use task::TaskVariables;
255use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _, ToPoint as _};
256use theme::{
257 AccentColors, ActiveTheme, GlobalTheme, PlayerColor, StatusColors, SyntaxTheme, Theme,
258};
259use theme_settings::{ThemeSettings, observe_buffer_font_size_adjustment};
260use ui::{
261 Avatar, ContextMenu, Disclosure, IconButtonShape, Indicator, Key, KeyBinding, Tooltip,
262 prelude::*, scrollbars::ScrollbarAutoHide, tooltip_container, utils::WithRemSize,
263};
264use ui_input::ErasedEditor;
265use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
266use workspace::{
267 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, NavigationEntry, OpenInTerminal,
268 OpenTerminal, Pane, RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection,
269 TabBarSettings, Toast, ViewId, Workspace, WorkspaceId, WorkspaceSettings,
270 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
271 notifications::{DetachAndPromptErr, NotificationId, NotifyResultExt, NotifyTaskExt},
272 searchable::SearchEvent,
273};
274pub use zed_actions::editor::RevealInFileManager;
275use zed_actions::editor::{MoveDown, MoveUp};
276
277use crate::{
278 code_context_menus::CompletionsMenuSource,
279 editor_settings::MultiCursorModifier,
280 hover_links::{find_url, find_url_from_range},
281 inlays::{
282 InlineValueCache,
283 inlay_hints::{LspInlayHintData, inlay_hint_settings},
284 },
285 runnables::{ResolvedTasks, RunnableData, RunnableTaskStatus, RunnableTasks},
286 scroll::{ScrollOffset, ScrollPixelOffset},
287 selections_collection::resolve_selections_wrapping_blocks,
288 semantic_tokens::SemanticTokenState,
289 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
290};
291
292pub const FILE_HEADER_HEIGHT: u32 = 2;
293pub const BUFFER_HEADER_PADDING: Rems = rems(0.25);
294pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
295const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
296const MAX_LINE_LEN: usize = 1024;
297const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
298const MAX_SELECTION_HISTORY_LEN: usize = 1024;
299pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
300#[doc(hidden)]
301pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
302pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
303
304pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
305pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
306pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
307pub const LSP_REQUEST_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(50);
308
309pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
310pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
311
312enum ReportEditorEvent {
313 Saved { auto_saved: bool },
314 EditorOpened,
315 Closed,
316}
317
318impl ReportEditorEvent {
319 pub fn event_type(&self) -> &'static str {
320 match self {
321 Self::Saved { .. } => "Editor Saved",
322 Self::EditorOpened => "Editor Opened",
323 Self::Closed => "Editor Closed",
324 }
325 }
326}
327
328pub enum ActiveDebugLine {}
329pub enum DebugStackFrameLine {}
330
331pub enum ConflictsOuter {}
332pub enum ConflictsOurs {}
333pub enum ConflictsTheirs {}
334pub enum ConflictsOursMarker {}
335pub enum ConflictsTheirsMarker {}
336
337pub struct HunkAddedColor;
338pub struct HunkRemovedColor;
339
340#[derive(Debug, Copy, Clone, PartialEq, Eq)]
341pub enum Navigated {
342 Yes,
343 No,
344}
345
346impl Navigated {
347 pub fn from_bool(yes: bool) -> Navigated {
348 if yes { Navigated::Yes } else { Navigated::No }
349 }
350}
351
352pub fn init(cx: &mut App) {
353 cx.set_global(GlobalBlameRenderer(Arc::new(())));
354 cx.set_global(breadcrumbs::RenderBreadcrumbText(render_breadcrumb_text));
355
356 workspace::register_project_item::<Editor>(cx);
357 workspace::FollowableViewRegistry::register::<Editor>(cx);
358 workspace::register_serializable_item::<Editor>(cx);
359
360 cx.observe_new(
361 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
362 workspace.register_action(Editor::new_file);
363 workspace.register_action(Editor::new_file_split);
364 workspace.register_action(Editor::new_file_vertical);
365 workspace.register_action(Editor::new_file_horizontal);
366 workspace.register_action(Editor::cancel_language_server_work);
367 workspace.register_action(Editor::toggle_focus);
368 workspace.register_action(Editor::view_bookmarks);
369 },
370 )
371 .detach();
372
373 cx.on_action(move |_: &workspace::NewFile, cx| {
374 let app_state = workspace::AppState::global(cx);
375 workspace::open_new(
376 Default::default(),
377 app_state,
378 cx,
379 |workspace, window, cx| Editor::new_file(workspace, &Default::default(), window, cx),
380 )
381 .detach_and_log_err(cx);
382 })
383 .on_action(move |_: &workspace::NewWindow, cx| {
384 let app_state = workspace::AppState::global(cx);
385 workspace::open_new(
386 Default::default(),
387 app_state,
388 cx,
389 |workspace, window, cx| {
390 cx.activate(true);
391 Editor::new_file(workspace, &Default::default(), window, cx)
392 },
393 )
394 .detach_and_log_err(cx);
395 });
396 _ = ui_input::ERASED_EDITOR_FACTORY.set(|window, cx| {
397 Arc::new(ErasedEditorImpl(
398 cx.new(|cx| Editor::single_line(window, cx)),
399 )) as Arc<dyn ErasedEditor>
400 });
401 _ = multi_buffer::EXCERPT_CONTEXT_LINES.set(multibuffer_context_lines);
402}
403
404pub struct SearchWithinRange;
405
406trait InvalidationRegion {
407 fn ranges(&self) -> &[Range<Anchor>];
408}
409
410#[derive(Clone, Debug, PartialEq)]
411pub enum SelectPhase {
412 Begin {
413 position: DisplayPoint,
414 add: bool,
415 click_count: usize,
416 },
417 BeginColumnar {
418 position: DisplayPoint,
419 reset: bool,
420 mode: ColumnarMode,
421 goal_column: u32,
422 },
423 Extend {
424 position: DisplayPoint,
425 click_count: usize,
426 },
427 Update {
428 position: DisplayPoint,
429 goal_column: u32,
430 scroll_delta: gpui::Point<f32>,
431 },
432 End,
433}
434
435#[derive(Clone, Debug, PartialEq)]
436pub enum ColumnarMode {
437 FromMouse,
438 FromSelection,
439}
440
441#[derive(Clone, Debug)]
442pub enum SelectMode {
443 Character,
444 Word(Range<Anchor>),
445 Line(Range<Anchor>),
446 All,
447}
448
449#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
450pub enum SizingBehavior {
451 /// The editor will layout itself using `size_full` and will include the vertical
452 /// scroll margin as requested by user settings.
453 #[default]
454 Default,
455 /// The editor will layout itself using `size_full`, but will not have any
456 /// vertical overscroll.
457 ExcludeOverscrollMargin,
458 /// The editor will request a vertical size according to its content and will be
459 /// layouted without a vertical scroll margin.
460 SizeByContent,
461}
462
463#[derive(Clone, PartialEq, Eq, Debug)]
464pub enum EditorMode {
465 SingleLine,
466 AutoHeight {
467 min_lines: usize,
468 max_lines: Option<usize>,
469 },
470 Full {
471 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
472 scale_ui_elements_with_buffer_font_size: bool,
473 /// When set to `true`, the editor will render a background for the active line.
474 show_active_line_background: bool,
475 /// Determines the sizing behavior for this editor
476 sizing_behavior: SizingBehavior,
477 },
478 Minimap {
479 parent: WeakEntity<Editor>,
480 },
481}
482
483impl EditorMode {
484 pub fn full() -> Self {
485 Self::Full {
486 scale_ui_elements_with_buffer_font_size: true,
487 show_active_line_background: true,
488 sizing_behavior: SizingBehavior::Default,
489 }
490 }
491
492 #[inline]
493 pub fn is_full(&self) -> bool {
494 matches!(self, Self::Full { .. })
495 }
496
497 #[inline]
498 pub fn is_single_line(&self) -> bool {
499 matches!(self, Self::SingleLine { .. })
500 }
501
502 #[inline]
503 fn is_minimap(&self) -> bool {
504 matches!(self, Self::Minimap { .. })
505 }
506}
507
508#[derive(Copy, Clone, Debug)]
509pub enum SoftWrap {
510 /// Prefer not to wrap at all.
511 ///
512 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
513 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
514 GitDiff,
515 /// Prefer a single line generally, unless an overly long line is encountered.
516 None,
517 /// Soft wrap lines that exceed the editor width.
518 EditorWidth,
519 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
520 Bounded(u32),
521}
522
523#[derive(Clone)]
524pub struct EditorStyle {
525 pub background: Hsla,
526 pub border: Hsla,
527 pub local_player: PlayerColor,
528 pub text: TextStyle,
529 pub scrollbar_width: Pixels,
530 pub syntax: Arc<SyntaxTheme>,
531 pub status: StatusColors,
532 pub inlay_hints_style: HighlightStyle,
533 pub edit_prediction_styles: EditPredictionStyles,
534 pub unnecessary_code_fade: f32,
535 pub show_underlines: bool,
536}
537
538impl Default for EditorStyle {
539 fn default() -> Self {
540 static NONE_SYNTAX: std::sync::LazyLock<Arc<SyntaxTheme>> =
541 std::sync::LazyLock::new(|| Arc::new(SyntaxTheme::default()));
542 Self {
543 background: Hsla::default(),
544 border: Hsla::default(),
545 local_player: PlayerColor::default(),
546 text: TextStyle::default(),
547 scrollbar_width: Pixels::default(),
548 syntax: NONE_SYNTAX.clone(),
549 // HACK: Status colors don't have a real default.
550 // We should look into removing the status colors from the editor
551 // style and retrieve them directly from the theme.
552 status: StatusColors::dark(),
553 inlay_hints_style: HighlightStyle::default(),
554 edit_prediction_styles: EditPredictionStyles {
555 insertion: HighlightStyle::default(),
556 whitespace: HighlightStyle::default(),
557 },
558 unnecessary_code_fade: Default::default(),
559 show_underlines: true,
560 }
561 }
562}
563
564pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle {
565 let show_background = AllLanguageSettings::get_global(cx)
566 .defaults
567 .inlay_hints
568 .show_background;
569
570 let mut style = cx
571 .theme()
572 .syntax()
573 .style_for_name("hint")
574 .unwrap_or_default();
575
576 if style.color.is_none() {
577 style.color = Some(cx.theme().status().hint);
578 }
579
580 if !show_background {
581 style.background_color = None;
582 return style;
583 }
584
585 if style.background_color.is_none() {
586 style.background_color = Some(cx.theme().status().hint_background);
587 }
588
589 style
590}
591
592type CompletionId = usize;
593
594pub struct ContextMenuOptions {
595 pub min_entries_visible: usize,
596 pub max_entries_visible: usize,
597 pub placement: Option<ContextMenuPlacement>,
598}
599
600#[derive(Debug, Clone, PartialEq, Eq)]
601pub enum ContextMenuPlacement {
602 Above,
603 Below,
604}
605
606#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
607struct EditorActionId(usize);
608
609impl EditorActionId {
610 pub fn post_inc(&mut self) -> Self {
611 let answer = self.0;
612
613 *self = Self(answer + 1);
614
615 Self(answer)
616 }
617}
618
619type BackgroundHighlight = (
620 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
621 Arc<[Range<Anchor>]>,
622);
623type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
624
625#[derive(Default)]
626struct ScrollbarMarkerState {
627 scrollbar_size: Size<Pixels>,
628 dirty: bool,
629 markers: Arc<[PaintQuad]>,
630 pending_refresh: Option<Task<Result<()>>>,
631}
632
633impl ScrollbarMarkerState {
634 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
635 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
636 }
637}
638
639#[derive(Clone, Copy, PartialEq, Eq)]
640pub enum MinimapVisibility {
641 Disabled,
642 Enabled {
643 /// The configuration currently present in the users settings.
644 setting_configuration: bool,
645 /// Whether to override the currently set visibility from the users setting.
646 toggle_override: bool,
647 },
648}
649
650impl MinimapVisibility {
651 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
652 if mode.is_full() {
653 Self::Enabled {
654 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
655 toggle_override: false,
656 }
657 } else {
658 Self::Disabled
659 }
660 }
661
662 fn hidden(&self) -> Self {
663 match *self {
664 Self::Enabled {
665 setting_configuration,
666 ..
667 } => Self::Enabled {
668 setting_configuration,
669 toggle_override: setting_configuration,
670 },
671 Self::Disabled => Self::Disabled,
672 }
673 }
674
675 fn disabled(&self) -> bool {
676 matches!(*self, Self::Disabled)
677 }
678
679 fn settings_visibility(&self) -> bool {
680 match *self {
681 Self::Enabled {
682 setting_configuration,
683 ..
684 } => setting_configuration,
685 _ => false,
686 }
687 }
688
689 fn visible(&self) -> bool {
690 match *self {
691 Self::Enabled {
692 setting_configuration,
693 toggle_override,
694 } => setting_configuration ^ toggle_override,
695 _ => false,
696 }
697 }
698
699 fn toggle_visibility(&self) -> Self {
700 match *self {
701 Self::Enabled {
702 toggle_override,
703 setting_configuration,
704 } => Self::Enabled {
705 setting_configuration,
706 toggle_override: !toggle_override,
707 },
708 Self::Disabled => Self::Disabled,
709 }
710 }
711}
712
713#[derive(Clone, Copy, PartialEq, Eq)]
714struct BreadcrumbsVisibility {
715 setting_configuration: bool,
716 toggle_override: bool,
717}
718
719impl BreadcrumbsVisibility {
720 fn from_settings(cx: &App) -> Self {
721 Self::new(EditorSettings::get_global(cx).toolbar.breadcrumbs)
722 }
723
724 fn new(setting_configuration: bool) -> Self {
725 Self {
726 setting_configuration,
727 toggle_override: false,
728 }
729 }
730
731 fn settings_visibility(&self) -> bool {
732 self.setting_configuration
733 }
734
735 fn visible(&self) -> bool {
736 self.setting_configuration ^ self.toggle_override
737 }
738
739 fn toggle_visibility(&self) -> Self {
740 Self {
741 setting_configuration: self.setting_configuration,
742 toggle_override: !self.toggle_override,
743 }
744 }
745}
746
747#[derive(Debug, Clone, Copy, PartialEq, Eq)]
748pub enum BufferSerialization {
749 All,
750 NonDirtyBuffers,
751}
752
753impl BufferSerialization {
754 fn new(restore_unsaved_buffers: bool) -> Self {
755 if restore_unsaved_buffers {
756 Self::All
757 } else {
758 Self::NonDirtyBuffers
759 }
760 }
761}
762
763/// Addons allow storing per-editor state in other crates (e.g. Vim)
764pub trait Addon: 'static {
765 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
766
767 fn render_buffer_header_controls(
768 &self,
769 _: &ExcerptBoundaryInfo,
770 _: &language::BufferSnapshot,
771 _: &Window,
772 _: &App,
773 ) -> Option<AnyElement> {
774 None
775 }
776
777 fn extend_buffer_header_context_menu(
778 &self,
779 menu: ui::ContextMenu,
780 _: &language::BufferSnapshot,
781 _: &mut Window,
782 _: &mut App,
783 ) -> ui::ContextMenu {
784 menu
785 }
786
787 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
788 None
789 }
790
791 fn to_any(&self) -> &dyn std::any::Any;
792
793 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
794 None
795 }
796}
797
798struct ChangeLocation {
799 current: Option<Vec<Anchor>>,
800 original: Vec<Anchor>,
801}
802impl ChangeLocation {
803 fn locations(&self) -> &[Anchor] {
804 self.current.as_ref().unwrap_or(&self.original)
805 }
806}
807
808/// A set of caret positions, registered when the editor was edited.
809pub struct ChangeList {
810 changes: Vec<ChangeLocation>,
811 /// Currently "selected" change.
812 position: Option<usize>,
813}
814
815impl ChangeList {
816 pub fn new() -> Self {
817 Self {
818 changes: Vec::new(),
819 position: None,
820 }
821 }
822
823 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
824 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
825 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
826 if self.changes.is_empty() {
827 return None;
828 }
829
830 let prev = self.position.unwrap_or(self.changes.len());
831 let next = if direction == Direction::Prev {
832 prev.saturating_sub(count)
833 } else {
834 (prev + count).min(self.changes.len() - 1)
835 };
836 self.position = Some(next);
837 self.changes.get(next).map(|change| change.locations())
838 }
839
840 /// Adds a new change to the list, resetting the change list position.
841 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
842 self.position.take();
843 if let Some(last) = self.changes.last_mut()
844 && group
845 {
846 last.current = Some(new_positions)
847 } else {
848 self.changes.push(ChangeLocation {
849 original: new_positions,
850 current: None,
851 });
852 }
853 }
854
855 pub fn last(&self) -> Option<&[Anchor]> {
856 self.changes.last().map(|change| change.locations())
857 }
858
859 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
860 self.changes.last().map(|change| change.original.as_slice())
861 }
862
863 pub fn invert_last_group(&mut self) {
864 if let Some(last) = self.changes.last_mut()
865 && let Some(current) = last.current.as_mut()
866 {
867 mem::swap(&mut last.original, current);
868 }
869 }
870}
871
872enum SelectionDragState {
873 /// State when no drag related activity is detected.
874 None,
875 /// State when the mouse is down on a selection that is about to be dragged.
876 ReadyToDrag {
877 selection: Selection<Anchor>,
878 click_position: gpui::Point<Pixels>,
879 mouse_down_time: Instant,
880 },
881 /// State when the mouse is dragging the selection in the editor.
882 Dragging {
883 selection: Selection<Anchor>,
884 drop_cursor: Selection<Anchor>,
885 hide_drop_cursor: bool,
886 },
887}
888
889enum ColumnarSelectionState {
890 FromMouse {
891 selection_tail: Anchor,
892 display_point: Option<DisplayPoint>,
893 },
894 FromSelection {
895 selection_tail: Anchor,
896 },
897}
898
899/// Represents a button that shows up when hovering over lines in the gutter that don't have
900/// any button on them already (like a bookmark, breakpoint or run indicator).
901#[derive(Clone, Copy, Debug, PartialEq, Eq)]
902struct GutterHoverButton {
903 display_row: DisplayRow,
904 /// There's a small debounce between hovering over the line and showing the indicator.
905 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
906 is_active: bool,
907}
908
909enum CodeActionsForSelection {
910 None,
911 Fetching(Shared<Task<Option<ActionFetchReady>>>),
912 Ready(ActionFetchReady),
913}
914
915#[derive(Clone)]
916struct ActionFetchReady {
917 location: Location,
918 actions: Rc<[AvailableCodeAction]>,
919}
920
921/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
922///
923/// See the [module level documentation](self) for more information.
924pub struct Editor {
925 focus_handle: FocusHandle,
926 last_focused_descendant: Option<WeakFocusHandle>,
927 /// The text buffer being edited
928 buffer: Entity<MultiBuffer>,
929 /// Map of how text in the buffer should be displayed.
930 /// Handles soft wraps, folds, fake inlay text insertions, etc.
931 pub display_map: Entity<DisplayMap>,
932 placeholder_display_map: Option<Entity<DisplayMap>>,
933 pub selections: SelectionsCollection,
934 /// Manages the scroll position for the given editor.
935 ///
936 /// Whenever you want to modify the scroll position of the editor, you should
937 /// usually use the existing available APIs as opposed to directly interacting
938 /// with the scroll manager.
939 pub scroll_manager: ScrollManager,
940 /// When inline assist editors are linked, they all render cursors because
941 /// typing enters text into each of them, even the ones that aren't focused.
942 pub(crate) show_cursor_when_unfocused: bool,
943 columnar_selection_state: Option<ColumnarSelectionState>,
944 add_selections_state: Option<AddSelectionsState>,
945 select_next_state: Option<SelectNextState>,
946 select_prev_state: Option<SelectNextState>,
947 selection_history: SelectionHistory,
948 defer_selection_effects: bool,
949 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
950 autoclose_regions: Vec<AutocloseRegion>,
951 snippet_stack: InvalidationStack<SnippetState>,
952 select_syntax_node_history: SelectSyntaxNodeHistory,
953 ime_transaction: Option<TransactionId>,
954 pub diagnostics_max_severity: DiagnosticSeverity,
955 active_diagnostics: ActiveDiagnostic,
956 show_inline_diagnostics: bool,
957 inline_diagnostics_update: Task<()>,
958 inline_diagnostics_enabled: bool,
959 diagnostics_enabled: bool,
960 word_completions_enabled: bool,
961 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
962 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
963 hard_wrap: Option<usize>,
964 project: Option<Entity<Project>>,
965 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
966 completion_provider: Option<Rc<dyn CompletionProvider>>,
967 collaboration_hub: Option<Box<dyn CollaborationHub>>,
968 blink_manager: Entity<BlinkManager>,
969 show_cursor_names: bool,
970 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
971 pub show_local_selections: bool,
972 mode: EditorMode,
973 breadcrumbs_visibility: BreadcrumbsVisibility,
974 show_gutter: bool,
975 show_scrollbars: ScrollbarAxes,
976 minimap_visibility: MinimapVisibility,
977 offset_content: bool,
978 disable_expand_excerpt_buttons: bool,
979 delegate_expand_excerpts: bool,
980 delegate_open_excerpts: bool,
981 enable_lsp_data: bool,
982 needs_initial_data_update: bool,
983 enable_runnables: bool,
984 enable_code_lens: bool,
985 enable_mouse_wheel_zoom: bool,
986 show_line_numbers: Option<bool>,
987 use_relative_line_numbers: Option<bool>,
988 show_git_diff_gutter: Option<bool>,
989 show_code_actions: Option<bool>,
990 show_runnables: Option<bool>,
991 show_bookmarks: Option<bool>,
992 show_breakpoints: Option<bool>,
993 show_diff_review_button: bool,
994 show_wrap_guides: Option<bool>,
995 show_indent_guides: Option<bool>,
996 buffers_with_disabled_indent_guides: HashSet<BufferId>,
997 highlight_order: usize,
998 highlighted_rows: TypeIdHashMap<Vec<RowHighlight>>,
999 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1000 navigation_overlays: HashMap<NavigationOverlayKey, Arc<[NavigationTargetOverlay]>>,
1001 gutter_highlights: TypeIdHashMap<GutterHighlight>,
1002 allow_git_diff_scrollbar_markers: bool,
1003 scrollbar_marker_state: ScrollbarMarkerState,
1004 active_indent_guides_state: ActiveIndentGuidesState,
1005 nav_history: Option<ItemNavHistory>,
1006 context_menu: RefCell<Option<CodeContextMenu>>,
1007 context_menu_options: Option<ContextMenuOptions>,
1008 mouse_context_menu: Option<MouseContextMenu>,
1009 completion_tasks: Vec<(CompletionId, Task<()>)>,
1010 inline_blame_popover: Option<InlineBlamePopover>,
1011 inline_blame_popover_show_task: Option<Task<()>>,
1012 signature_help_state: SignatureHelpState,
1013 auto_signature_help: Option<bool>,
1014 find_all_references_task_sources: Vec<Anchor>,
1015 next_completion_id: CompletionId,
1016 code_actions_for_selection: CodeActionsForSelection,
1017 runnables_for_selection_toggle: Task<()>,
1018 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1019 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1020 debounced_selection_highlight_complete: bool,
1021 last_selection_from_search: bool,
1022 document_highlights_task: Option<Task<()>>,
1023 linked_editing_range_task: Option<Task<Option<()>>>,
1024 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1025 pending_rename: Option<RenameState>,
1026 searchable: bool,
1027 cursor_shape: CursorShape,
1028 /// Whether the cursor is offset one character to the left when something is
1029 /// selected (needed for vim visual mode)
1030 cursor_offset_on_selection: bool,
1031 current_line_highlight: Option<CurrentLineHighlight>,
1032 /// Whether to collapse search match ranges to just their start position.
1033 /// When true, navigating to a match positions the cursor at the match
1034 /// without selecting the matched text.
1035 collapse_matches: bool,
1036 autoindent_mode: Option<AutoindentMode>,
1037 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1038 input_enabled: bool,
1039 expects_character_input: bool,
1040 use_modal_editing: bool,
1041 read_only: bool,
1042 leader_id: Option<CollaboratorId>,
1043 remote_id: Option<ViewId>,
1044 pub hover_state: HoverState,
1045 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1046 prev_pressure_stage: Option<PressureStage>,
1047 gutter_hovered: bool,
1048 hovered_link_state: Option<HoveredLinkState>,
1049 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1050 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1051 active_edit_prediction: Option<EditPredictionState>,
1052 /// Used to prevent flickering as the user types while the menu is open
1053 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1054 edit_prediction_settings: EditPredictionSettings,
1055 edit_predictions_hidden_for_vim_mode: bool,
1056 show_edit_predictions_override: Option<bool>,
1057 show_completions_on_input_override: Option<bool>,
1058 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1059 edit_prediction_preview: EditPredictionPreview,
1060 in_leading_whitespace: bool,
1061 next_inlay_id: usize,
1062 next_color_inlay_id: usize,
1063 _subscriptions: Vec<Subscription>,
1064 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1065 gutter_dimensions: GutterDimensions,
1066 style: Option<EditorStyle>,
1067 text_style_refinement: Option<TextStyleRefinement>,
1068 next_editor_action_id: EditorActionId,
1069 editor_actions: Rc<
1070 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1071 >,
1072 use_autoclose: bool,
1073 use_auto_surround: bool,
1074 use_selection_highlight: bool,
1075 auto_replace_emoji_shortcode: bool,
1076 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1077 show_git_blame_gutter: bool,
1078 show_git_blame_inline: bool,
1079 show_git_blame_inline_delay_task: Option<Task<()>>,
1080 git_blame_inline_enabled: bool,
1081 buffer_serialization: Option<BufferSerialization>,
1082 show_selection_menu: Option<bool>,
1083 blame: Option<Entity<GitBlame>>,
1084 blame_subscription: Option<Subscription>,
1085 pending_blame_hover_observation: Option<Subscription>,
1086 custom_context_menu: Option<
1087 Box<
1088 dyn 'static
1089 + Fn(
1090 &mut Self,
1091 DisplayPoint,
1092 &mut Window,
1093 &mut Context<Self>,
1094 ) -> Option<Entity<ui::ContextMenu>>,
1095 >,
1096 >,
1097 last_bounds: Option<Bounds<Pixels>>,
1098 last_position_map: Option<Rc<PositionMap>>,
1099 /// The right margin (vertical scrollbar + minimap width) the editor was
1100 /// last laid out with, updated on every prepaint.
1101 /// Used later in the frame by `SplitBufferHeadersElement` to shrink the
1102 /// width available to buffer headers.
1103 last_right_margin: Pixels,
1104 /// Whether the horizontal scrollbar was laid out as visible during the last
1105 /// prepaint.
1106 /// Used by `SplitBufferHeadersElement` to clip buffer headers so they don't
1107 /// paint over the scrollbar.
1108 last_horizontal_scrollbar_visible: bool,
1109 expect_bounds_change: Option<Bounds<Pixels>>,
1110 runnables: RunnableData,
1111 bookmark_store: Option<Entity<BookmarkStore>>,
1112 breakpoint_store: Option<Entity<BreakpointStore>>,
1113 gutter_hover_button: (Option<GutterHoverButton>, Option<Task<()>>),
1114 pub(crate) gutter_diff_review_indicator: (Option<PhantomDiffReviewIndicator>, Option<Task<()>>),
1115 pub(crate) diff_review_drag_state: Option<DiffReviewDragState>,
1116 /// Active diff review overlays. Multiple overlays can be open simultaneously
1117 /// when hunks have comments stored.
1118 pub(crate) diff_review_overlays: Vec<DiffReviewOverlay>,
1119 /// Stored review comments grouped by hunk.
1120 /// Uses a Vec instead of HashMap because DiffHunkKey contains an Anchor
1121 /// which doesn't implement Hash/Eq in a way suitable for HashMap keys.
1122 stored_review_comments: Vec<(DiffHunkKey, Vec<StoredReviewComment>)>,
1123 /// Counter for generating unique comment IDs.
1124 next_review_comment_id: usize,
1125 hovered_diff_hunk_row: Option<DisplayRow>,
1126 pull_diagnostics_task: Task<()>,
1127 in_project_search: bool,
1128 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1129 breadcrumb_header: Option<String>,
1130 focused_block: Option<FocusedBlock>,
1131 next_scroll_position: NextScrollCursorCenterTopBottom,
1132 addons: TypeIdHashMap<Box<dyn Addon>>,
1133 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1134 load_diff_task: Option<Shared<Task<()>>>,
1135 diff_hunk_delegate: Option<Arc<dyn DiffHunkDelegate>>,
1136 selection_mark_mode: bool,
1137 toggle_fold_multiple_buffers: Task<()>,
1138 _scroll_cursor_center_top_bottom_task: Task<()>,
1139 serialize_selections: Task<()>,
1140 serialize_folds: Task<()>,
1141 minimap: Option<Entity<Self>>,
1142 pub change_list: ChangeList,
1143 inline_value_cache: InlineValueCache,
1144 number_deleted_lines: bool,
1145
1146 selection_drag_state: SelectionDragState,
1147 colors: Option<LspColorData>,
1148 code_lens: Option<CodeLensState>,
1149 post_scroll_update: Task<()>,
1150 refresh_colors_task: Task<()>,
1151 refresh_code_lens_task: Task<()>,
1152 use_document_folding_ranges: bool,
1153 refresh_folding_ranges_task: Task<()>,
1154 inlay_hints: Option<LspInlayHintData>,
1155 folding_newlines: Task<()>,
1156 select_next_is_case_sensitive: Option<bool>,
1157 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1158 on_local_selections_changed:
1159 Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
1160 suppress_selection_callback: bool,
1161 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1162 accent_data: Option<AccentData>,
1163 bracket_fetched_tree_sitter_chunks: HashMap<Range<text::Anchor>, HashSet<Range<BufferRow>>>,
1164 semantic_token_state: SemanticTokenState,
1165 pub(crate) refresh_matching_bracket_highlights_task: Task<()>,
1166 refresh_document_symbols_task: Shared<Task<()>>,
1167 lsp_document_links: LspDocumentLinks,
1168 lsp_document_symbols: HashMap<BufferId, Vec<OutlineItem<text::Anchor>>>,
1169 refresh_outline_symbols_at_cursor_at_cursor_task: Task<()>,
1170 outline_symbols_at_cursor: Option<(BufferId, Vec<OutlineItem<Anchor>>)>,
1171 sticky_headers_task: Task<()>,
1172 sticky_headers: Option<Vec<OutlineItem<Anchor>>>,
1173 pub(crate) colorize_brackets_task: Task<()>,
1174}
1175
1176#[derive(Debug, PartialEq)]
1177struct AccentData {
1178 colors: AccentColors,
1179 overrides: Vec<SharedString>,
1180}
1181
1182fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1183 if debounce_ms > 0 {
1184 Some(Duration::from_millis(debounce_ms))
1185 } else {
1186 None
1187 }
1188}
1189
1190#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1191enum NextScrollCursorCenterTopBottom {
1192 #[default]
1193 Center,
1194 Top,
1195 Bottom,
1196}
1197
1198impl NextScrollCursorCenterTopBottom {
1199 fn next(&self) -> Self {
1200 match self {
1201 Self::Center => Self::Top,
1202 Self::Top => Self::Bottom,
1203 Self::Bottom => Self::Center,
1204 }
1205 }
1206}
1207
1208#[derive(Clone)]
1209pub struct EditorSnapshot {
1210 pub mode: EditorMode,
1211 show_gutter: bool,
1212 offset_content: bool,
1213 show_line_numbers: Option<bool>,
1214 number_deleted_lines: bool,
1215 show_git_diff_gutter: Option<bool>,
1216 show_code_actions: Option<bool>,
1217 show_runnables: Option<bool>,
1218 show_breakpoints: Option<bool>,
1219 show_bookmarks: Option<bool>,
1220 git_blame_gutter_max_author_length: Option<usize>,
1221 pub display_snapshot: DisplaySnapshot,
1222 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1223 is_focused: bool,
1224 scroll_anchor: SharedScrollAnchor,
1225 ongoing_scroll: OngoingScroll,
1226 current_line_highlight: CurrentLineHighlight,
1227 gutter_hovered: bool,
1228 semantic_tokens_enabled: bool,
1229}
1230
1231#[derive(Clone, Debug, PartialEq)]
1232pub struct NavigationTargetOverlay {
1233 pub target_range: Range<Anchor>,
1234 pub label: NavigationOverlayLabel,
1235 pub covered_text_range: Option<Range<Anchor>>,
1236}
1237
1238#[derive(Clone, Debug, PartialEq)]
1239pub struct NavigationOverlayLabel {
1240 pub text: SharedString,
1241 pub text_color: Hsla,
1242 pub x_offset: Pixels,
1243 pub scale_factor: f32,
1244}
1245
1246#[derive(Default, Debug, Clone, Copy)]
1247pub struct GutterDimensions {
1248 pub left_padding: Pixels,
1249 pub right_padding: Pixels,
1250 pub width: Pixels,
1251 pub margin: Pixels,
1252 pub git_blame_entries_width: Option<Pixels>,
1253}
1254
1255impl GutterDimensions {
1256 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1257 Self {
1258 margin: Self::default_gutter_margin(font_id, font_size, cx),
1259 ..Default::default()
1260 }
1261 }
1262
1263 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1264 -cx.text_system().descent(font_id, font_size)
1265 }
1266 /// The full width of the space taken up by the gutter.
1267 pub fn full_width(&self) -> Pixels {
1268 self.margin + self.width
1269 }
1270}
1271
1272struct CharacterDimensions {
1273 em_width: Pixels,
1274 em_advance: Pixels,
1275 line_height: Pixels,
1276}
1277
1278#[derive(Debug)]
1279pub struct RemoteSelection {
1280 pub replica_id: ReplicaId,
1281 pub selection: Selection<Anchor>,
1282 pub cursor_shape: CursorShape,
1283 pub collaborator_id: CollaboratorId,
1284 pub line_mode: bool,
1285 pub user_name: Option<SharedString>,
1286 pub color: PlayerColor,
1287}
1288
1289#[derive(Clone, Debug)]
1290struct SelectionHistoryEntry {
1291 selections: Arc<[Selection<Anchor>]>,
1292 select_next_state: Option<SelectNextState>,
1293 select_prev_state: Option<SelectNextState>,
1294 add_selections_state: Option<AddSelectionsState>,
1295}
1296
1297#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1298enum SelectionHistoryMode {
1299 #[default]
1300 Normal,
1301 Undoing,
1302 Redoing,
1303 Skipping,
1304}
1305
1306#[derive(Clone, PartialEq, Eq, Hash)]
1307struct HoveredCursor {
1308 replica_id: ReplicaId,
1309 selection_id: usize,
1310}
1311
1312#[derive(Debug)]
1313/// SelectionEffects controls the side-effects of updating the selection.
1314///
1315/// The default behaviour does "what you mostly want":
1316/// - it pushes to the nav history if the cursor moved by >10 lines
1317/// - it re-triggers completion requests
1318/// - it scrolls to fit
1319///
1320/// You might want to modify these behaviours. For example when doing a "jump"
1321/// like go to definition, we always want to add to nav history; but when scrolling
1322/// in vim mode we never do.
1323///
1324/// Similarly, you might want to disable scrolling if you don't want the viewport to
1325/// move.
1326#[derive(Clone)]
1327pub struct SelectionEffects {
1328 nav_history: Option<bool>,
1329 completions: bool,
1330 scroll: Option<Autoscroll>,
1331 from_search: bool,
1332}
1333
1334impl Default for SelectionEffects {
1335 fn default() -> Self {
1336 Self {
1337 nav_history: None,
1338 completions: true,
1339 scroll: Some(Autoscroll::fit()),
1340 from_search: false,
1341 }
1342 }
1343}
1344impl SelectionEffects {
1345 pub fn scroll(scroll: Autoscroll) -> Self {
1346 Self {
1347 scroll: Some(scroll),
1348 ..Default::default()
1349 }
1350 }
1351
1352 pub fn no_scroll() -> Self {
1353 Self {
1354 scroll: None,
1355 ..Default::default()
1356 }
1357 }
1358
1359 pub fn completions(self, completions: bool) -> Self {
1360 Self {
1361 completions,
1362 ..self
1363 }
1364 }
1365
1366 pub fn nav_history(self, nav_history: bool) -> Self {
1367 Self {
1368 nav_history: Some(nav_history),
1369 ..self
1370 }
1371 }
1372
1373 pub fn from_search(self, from_search: bool) -> Self {
1374 Self {
1375 from_search,
1376 ..self
1377 }
1378 }
1379}
1380
1381struct DeferredSelectionEffectsState {
1382 changed: bool,
1383 effects: SelectionEffects,
1384 old_cursor_position: Anchor,
1385 history_entry: SelectionHistoryEntry,
1386}
1387
1388#[derive(Clone, Debug)]
1389pub struct TransactionSelections {
1390 pub undo: Arc<[Selection<Anchor>]>,
1391 pub redo: Option<Arc<[Selection<Anchor>]>>,
1392}
1393
1394#[derive(Default)]
1395struct SelectionHistory {
1396 selections_by_transaction: HashMap<TransactionId, TransactionSelections>,
1397 mode: SelectionHistoryMode,
1398 undo_stack: VecDeque<SelectionHistoryEntry>,
1399 redo_stack: VecDeque<SelectionHistoryEntry>,
1400}
1401
1402impl SelectionHistory {
1403 #[track_caller]
1404 fn insert_transaction(
1405 &mut self,
1406 transaction_id: TransactionId,
1407 selections: Arc<[Selection<Anchor>]>,
1408 ) {
1409 if selections.is_empty() {
1410 log::error!(
1411 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1412 std::panic::Location::caller()
1413 );
1414 return;
1415 }
1416 self.selections_by_transaction.insert(
1417 transaction_id,
1418 TransactionSelections {
1419 undo: selections,
1420 redo: None,
1421 },
1422 );
1423 }
1424
1425 fn transaction(&self, transaction_id: TransactionId) -> Option<&TransactionSelections> {
1426 self.selections_by_transaction.get(&transaction_id)
1427 }
1428
1429 fn transaction_mut(
1430 &mut self,
1431 transaction_id: TransactionId,
1432 ) -> Option<&mut TransactionSelections> {
1433 self.selections_by_transaction.get_mut(&transaction_id)
1434 }
1435
1436 fn push(&mut self, entry: SelectionHistoryEntry) {
1437 if !entry.selections.is_empty() {
1438 match self.mode {
1439 SelectionHistoryMode::Normal => {
1440 self.push_undo(entry);
1441 self.redo_stack.clear();
1442 }
1443 SelectionHistoryMode::Undoing => self.push_redo(entry),
1444 SelectionHistoryMode::Redoing => self.push_undo(entry),
1445 SelectionHistoryMode::Skipping => {}
1446 }
1447 }
1448 }
1449
1450 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1451 if self
1452 .undo_stack
1453 .back()
1454 .is_none_or(|e| e.selections != entry.selections)
1455 {
1456 self.undo_stack.push_back(entry);
1457 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1458 self.undo_stack.pop_front();
1459 }
1460 }
1461 }
1462
1463 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1464 if self
1465 .redo_stack
1466 .back()
1467 .is_none_or(|e| e.selections != entry.selections)
1468 {
1469 self.redo_stack.push_back(entry);
1470 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1471 self.redo_stack.pop_front();
1472 }
1473 }
1474 }
1475}
1476
1477#[derive(Clone, Copy)]
1478pub struct RowHighlightOptions {
1479 pub autoscroll: bool,
1480 pub include_gutter: bool,
1481}
1482
1483impl Default for RowHighlightOptions {
1484 fn default() -> Self {
1485 Self {
1486 autoscroll: Default::default(),
1487 include_gutter: true,
1488 }
1489 }
1490}
1491
1492struct RowHighlight {
1493 index: usize,
1494 range: Range<Anchor>,
1495 color: fn(&App) -> Hsla,
1496 options: RowHighlightOptions,
1497 type_id: TypeId,
1498}
1499
1500#[derive(Clone, Debug)]
1501struct AddSelectionsState {
1502 groups: Vec<AddSelectionsGroup>,
1503}
1504
1505#[derive(Clone, Debug)]
1506struct AddSelectionsGroup {
1507 above: bool,
1508 stack: Vec<usize>,
1509}
1510
1511#[derive(Clone)]
1512struct SelectNextState {
1513 query: AhoCorasick,
1514 wordwise: bool,
1515 done: bool,
1516}
1517
1518impl std::fmt::Debug for SelectNextState {
1519 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1520 f.debug_struct(std::any::type_name::<Self>())
1521 .field("wordwise", &self.wordwise)
1522 .field("done", &self.done)
1523 .finish()
1524 }
1525}
1526
1527#[derive(Debug)]
1528struct AutocloseRegion {
1529 selection_id: usize,
1530 range: Range<Anchor>,
1531 pair: BracketPair,
1532}
1533
1534#[derive(Debug)]
1535struct SnippetState {
1536 ranges: Vec<Vec<Range<Anchor>>>,
1537 active_index: usize,
1538 choices: Vec<Option<Vec<String>>>,
1539}
1540
1541#[doc(hidden)]
1542pub struct RenameState {
1543 pub range: Range<Anchor>,
1544 pub old_name: Arc<str>,
1545 pub editor: Entity<Editor>,
1546 block_id: CustomBlockId,
1547}
1548
1549struct InvalidationStack<T>(Vec<T>);
1550
1551// selections, scroll behavior, was newest selection reversed
1552type SelectSyntaxNodeHistoryState = (
1553 Box<[Selection<Anchor>]>,
1554 SelectSyntaxNodeScrollBehavior,
1555 bool,
1556);
1557
1558#[derive(Default)]
1559struct SelectSyntaxNodeHistory {
1560 stack: Vec<SelectSyntaxNodeHistoryState>,
1561 // disable temporarily to allow changing selections without losing the stack
1562 pub disable_clearing: bool,
1563}
1564
1565impl SelectSyntaxNodeHistory {
1566 pub fn try_clear(&mut self) {
1567 if !self.disable_clearing {
1568 self.stack.clear();
1569 }
1570 }
1571
1572 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1573 self.stack.push(selection);
1574 }
1575
1576 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1577 self.stack.pop()
1578 }
1579}
1580
1581enum SelectSyntaxNodeScrollBehavior {
1582 CursorTop,
1583 FitSelection,
1584 CursorBottom,
1585}
1586
1587#[derive(Debug, Clone, Copy)]
1588pub(crate) struct NavigationData {
1589 cursor_anchor: Anchor,
1590 cursor_position: Point,
1591 scroll_anchor: ScrollAnchor,
1592 scroll_top_row: u32,
1593}
1594
1595#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1596pub enum GotoDefinitionKind {
1597 Symbol,
1598 Declaration,
1599 Type,
1600 Implementation,
1601}
1602
1603pub enum FormatTarget {
1604 Buffers(HashSet<Entity<Buffer>>),
1605 Ranges(Vec<Range<MultiBufferPoint>>),
1606}
1607
1608pub(crate) struct FocusedBlock {
1609 id: BlockId,
1610 focus_handle: WeakFocusHandle,
1611}
1612
1613#[derive(Clone, Debug)]
1614pub enum JumpData {
1615 MultiBufferRow {
1616 row: MultiBufferRow,
1617 line_offset_from_top: u32,
1618 },
1619 MultiBufferPoint {
1620 anchor: language::Anchor,
1621 position: Point,
1622 line_offset_from_top: u32,
1623 },
1624}
1625
1626pub enum MultibufferSelectionMode {
1627 First,
1628 All,
1629}
1630
1631#[derive(Clone, Copy, Debug, Default)]
1632pub struct RewrapOptions {
1633 pub override_language_settings: bool,
1634 pub preserve_existing_whitespace: bool,
1635 pub line_length: Option<usize>,
1636}
1637
1638#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1639enum GutterButtonIntent {
1640 SetBookmark,
1641 SetBreakpoint,
1642}
1643
1644impl GutterButtonIntent {
1645 fn as_str(&self) -> &'static str {
1646 match self {
1647 Self::SetBookmark => "Set Bookmark",
1648 Self::SetBreakpoint => "Set Breakpoint",
1649 }
1650 }
1651
1652 fn icon(&self) -> ui::IconName {
1653 match self {
1654 Self::SetBookmark => ui::IconName::Bookmark,
1655 Self::SetBreakpoint => ui::IconName::DebugBreakpoint,
1656 }
1657 }
1658
1659 fn color(&self) -> Color {
1660 match self {
1661 Self::SetBookmark => Color::Info,
1662 Self::SetBreakpoint => Color::Hint,
1663 }
1664 }
1665
1666 fn action(&self) -> &'static dyn Action {
1667 match self {
1668 Self::SetBookmark => &ToggleBookmark,
1669 Self::SetBreakpoint => &ToggleBreakpoint,
1670 }
1671 }
1672}
1673
1674struct GutterButtonTooltip {
1675 primary: GutterButtonIntent,
1676 secondary: GutterButtonIntent,
1677 focus_handle: FocusHandle,
1678}
1679
1680impl GutterButtonTooltip {
1681 fn active_intent(&self, modifiers: Modifiers) -> GutterButtonIntent {
1682 if modifiers.secondary() {
1683 self.secondary
1684 } else {
1685 self.primary
1686 }
1687 }
1688
1689 fn meta_text(&self, intent: GutterButtonIntent) -> String {
1690 const RIGHT_CLICK_HINT: &str = "right-click for more options";
1691
1692 if self.primary == self.secondary {
1693 return RIGHT_CLICK_HINT.to_string();
1694 }
1695 let modifier_as_text = gpui::Keystroke {
1696 modifiers: Modifiers::secondary_key(),
1697 ..Default::default()
1698 };
1699 let other = match intent {
1700 GutterButtonIntent::SetBookmark => "breakpoint",
1701 GutterButtonIntent::SetBreakpoint => "bookmark",
1702 };
1703 format!("{modifier_as_text}-click to add a {other}\n{RIGHT_CLICK_HINT}")
1704 }
1705}
1706
1707impl Render for GutterButtonTooltip {
1708 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1709 let intent = self.active_intent(window.modifiers());
1710 let key_binding = KeyBinding::for_action_in(intent.action(), &self.focus_handle, cx);
1711 let meta_text = self.meta_text(intent);
1712
1713 tooltip_container(cx, move |this, _| {
1714 this.child(
1715 h_flex()
1716 .justify_between()
1717 .child(intent.as_str())
1718 .child(key_binding),
1719 )
1720 .child(
1721 Label::new(meta_text)
1722 .size(LabelSize::Small)
1723 .color(Color::Muted),
1724 )
1725 })
1726 }
1727}
1728
1729impl Editor {
1730 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1731 let buffer = cx.new(|cx| Buffer::local("", cx));
1732 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1733 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1734 }
1735
1736 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1737 let buffer = cx.new(|cx| Buffer::local("", cx));
1738 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1739 Self::new(EditorMode::full(), buffer, None, window, cx)
1740 }
1741
1742 pub fn auto_height(
1743 min_lines: usize,
1744 max_lines: usize,
1745 window: &mut Window,
1746 cx: &mut Context<Self>,
1747 ) -> Self {
1748 let buffer = cx.new(|cx| Buffer::local("", cx));
1749 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1750 Self::new(
1751 EditorMode::AutoHeight {
1752 min_lines,
1753 max_lines: Some(max_lines),
1754 },
1755 buffer,
1756 None,
1757 window,
1758 cx,
1759 )
1760 }
1761
1762 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1763 /// The editor grows as tall as needed to fit its content.
1764 pub fn auto_height_unbounded(
1765 min_lines: usize,
1766 window: &mut Window,
1767 cx: &mut Context<Self>,
1768 ) -> Self {
1769 let buffer = cx.new(|cx| Buffer::local("", cx));
1770 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1771 Self::new(
1772 EditorMode::AutoHeight {
1773 min_lines,
1774 max_lines: None,
1775 },
1776 buffer,
1777 None,
1778 window,
1779 cx,
1780 )
1781 }
1782
1783 pub fn for_buffer(
1784 buffer: Entity<Buffer>,
1785 project: Option<Entity<Project>>,
1786 window: &mut Window,
1787 cx: &mut Context<Self>,
1788 ) -> Self {
1789 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1790 Self::new(EditorMode::full(), buffer, project, window, cx)
1791 }
1792
1793 pub fn for_multibuffer(
1794 buffer: Entity<MultiBuffer>,
1795 project: Option<Entity<Project>>,
1796 window: &mut Window,
1797 cx: &mut Context<Self>,
1798 ) -> Self {
1799 Self::new(EditorMode::full(), buffer, project, window, cx)
1800 }
1801
1802 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1803 let mut clone = Self::new(
1804 self.mode.clone(),
1805 self.buffer.clone(),
1806 self.project.clone(),
1807 window,
1808 cx,
1809 );
1810 let my_snapshot = self.display_map.update(cx, |display_map, cx| {
1811 let snapshot = display_map.snapshot(cx);
1812 clone.display_map.update(cx, |display_map, cx| {
1813 display_map.set_state(&snapshot, cx);
1814 });
1815 snapshot
1816 });
1817 let clone_snapshot = clone.display_map.update(cx, |map, cx| map.snapshot(cx));
1818 clone.folds_did_change(cx);
1819 clone.selections.clone_state(&self.selections);
1820 clone
1821 .scroll_manager
1822 .clone_state(&self.scroll_manager, &my_snapshot, &clone_snapshot, cx);
1823 clone.searchable = self.searchable;
1824 clone.read_only = self.read_only;
1825 clone.buffers_with_disabled_indent_guides =
1826 self.buffers_with_disabled_indent_guides.clone();
1827 clone.enable_mouse_wheel_zoom = self.enable_mouse_wheel_zoom;
1828 clone.enable_lsp_data = self.enable_lsp_data;
1829 clone.needs_initial_data_update = self.enable_lsp_data;
1830 clone.enable_runnables = self.enable_runnables;
1831 clone.enable_code_lens = self.enable_code_lens;
1832 clone
1833 }
1834
1835 pub fn new(
1836 mode: EditorMode,
1837 buffer: Entity<MultiBuffer>,
1838 project: Option<Entity<Project>>,
1839 window: &mut Window,
1840 cx: &mut Context<Self>,
1841 ) -> Self {
1842 Editor::new_internal(mode, buffer, project, None, window, cx)
1843 }
1844
1845 pub fn refresh_sticky_headers(
1846 &mut self,
1847 display_snapshot: &DisplaySnapshot,
1848 cx: &mut Context<Editor>,
1849 ) {
1850 if !self.mode.is_full() {
1851 return;
1852 }
1853 let multi_buffer = display_snapshot.buffer_snapshot().clone();
1854 let scroll_anchor = self
1855 .scroll_manager
1856 .native_anchor(display_snapshot, cx)
1857 .anchor;
1858 let Some(buffer_snapshot) = multi_buffer.as_singleton() else {
1859 return;
1860 };
1861
1862 let buffer = buffer_snapshot.clone();
1863 let Some((buffer_visible_start, _)) = multi_buffer.anchor_to_buffer_anchor(scroll_anchor)
1864 else {
1865 return;
1866 };
1867 let buffer_visible_start = buffer_visible_start.to_point(&buffer);
1868 let max_row = buffer.max_point().row;
1869 let start_row = buffer_visible_start.row.min(max_row);
1870 let end_row = (buffer_visible_start.row + 10).min(max_row);
1871
1872 let syntax = self.style(cx).syntax.clone();
1873 let background_task = cx.background_spawn(async move {
1874 buffer
1875 .outline_items_containing(
1876 Point::new(start_row, 0)..Point::new(end_row, 0),
1877 true,
1878 Some(syntax.as_ref()),
1879 )
1880 .into_iter()
1881 .filter_map(|outline_item| {
1882 Some(OutlineItem {
1883 depth: outline_item.depth,
1884 range: multi_buffer
1885 .buffer_anchor_range_to_anchor_range(outline_item.range)?,
1886 selection_range: multi_buffer
1887 .buffer_anchor_range_to_anchor_range(outline_item.selection_range)?,
1888 source_range_for_text: multi_buffer.buffer_anchor_range_to_anchor_range(
1889 outline_item.source_range_for_text,
1890 )?,
1891 text: outline_item.text,
1892 highlight_ranges: outline_item.highlight_ranges,
1893 name_ranges: outline_item.name_ranges,
1894 body_range: outline_item.body_range.and_then(|range| {
1895 multi_buffer.buffer_anchor_range_to_anchor_range(range)
1896 }),
1897 annotation_range: outline_item.annotation_range.and_then(|range| {
1898 multi_buffer.buffer_anchor_range_to_anchor_range(range)
1899 }),
1900 })
1901 })
1902 .collect()
1903 });
1904 self.sticky_headers_task = cx.spawn(async move |this, cx| {
1905 let sticky_headers = background_task.await;
1906 this.update(cx, |this, cx| {
1907 if this.sticky_headers.as_ref() != Some(&sticky_headers) {
1908 this.sticky_headers = Some(sticky_headers);
1909 cx.notify();
1910 }
1911 })
1912 .ok();
1913 });
1914 }
1915
1916 fn new_internal(
1917 mode: EditorMode,
1918 multi_buffer: Entity<MultiBuffer>,
1919 project: Option<Entity<Project>>,
1920 display_map: Option<Entity<DisplayMap>>,
1921 window: &mut Window,
1922 cx: &mut Context<Self>,
1923 ) -> Self {
1924 debug_assert!(
1925 display_map.is_none() || mode.is_minimap(),
1926 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1927 );
1928
1929 let full_mode = mode.is_full();
1930 let is_minimap = mode.is_minimap();
1931 let diagnostics_max_severity = if full_mode {
1932 EditorSettings::get_global(cx)
1933 .diagnostics_max_severity
1934 .unwrap_or(DiagnosticSeverity::Hint)
1935 } else {
1936 DiagnosticSeverity::Off
1937 };
1938 let style = window.text_style();
1939 let font_size = style.font_size.to_pixels(window.rem_size());
1940 let editor = cx.entity().downgrade();
1941 let fold_placeholder = FoldPlaceholder {
1942 constrain_width: false,
1943 render: Arc::new(move |fold_id, fold_range, cx| {
1944 let editor = editor.clone();
1945 FoldPlaceholder::fold_element(fold_id, cx)
1946 .cursor_pointer()
1947 .child("⋯")
1948 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1949 .on_click(move |_, _window, cx| {
1950 editor
1951 .update(cx, |editor, cx| {
1952 editor.unfold_ranges(
1953 &[fold_range.start..fold_range.end],
1954 true,
1955 false,
1956 cx,
1957 );
1958 cx.stop_propagation();
1959 })
1960 .ok();
1961 })
1962 .into_any()
1963 }),
1964 merge_adjacent: true,
1965 ..FoldPlaceholder::default()
1966 };
1967 let display_map = display_map.unwrap_or_else(|| {
1968 cx.new(|cx| {
1969 DisplayMap::new(
1970 multi_buffer.clone(),
1971 style.font(),
1972 font_size,
1973 None,
1974 FILE_HEADER_HEIGHT,
1975 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1976 fold_placeholder,
1977 diagnostics_max_severity,
1978 cx,
1979 )
1980 })
1981 });
1982
1983 let selections = SelectionsCollection::new();
1984
1985 let blink_manager = cx.new(|cx| {
1986 let mut blink_manager = BlinkManager::new(
1987 CURSOR_BLINK_INTERVAL,
1988 |cx| EditorSettings::get_global(cx).cursor_blink,
1989 cx,
1990 );
1991 if is_minimap {
1992 blink_manager.disable(cx);
1993 }
1994 blink_manager
1995 });
1996
1997 let soft_wrap_mode_override =
1998 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1999
2000 let mut project_subscriptions = Vec::new();
2001 if full_mode && let Some(project) = project.as_ref() {
2002 project_subscriptions.push(cx.subscribe_in(
2003 project,
2004 window,
2005 |editor, _, event, window, cx| match event {
2006 project::Event::RefreshCodeLens { .. } => {
2007 editor.refresh_code_lenses(None, window, cx);
2008 }
2009 project::Event::RefreshDocumentColors { .. } => {
2010 editor.refresh_document_colors(None, window, cx);
2011 }
2012 project::Event::RefreshDocumentLinks { .. } => {
2013 editor.refresh_document_links(None, cx);
2014 }
2015 project::Event::RefreshFoldingRanges { .. } => {
2016 editor.refresh_folding_ranges(None, window, cx);
2017 }
2018 project::Event::RefreshDocumentSymbols { .. } => {
2019 editor.refresh_document_symbols(None, cx);
2020 }
2021 project::Event::RefreshInlayHints { server_id } => {
2022 editor.refresh_inlay_hints(
2023 InlayHintRefreshReason::RefreshRequested {
2024 server_id: *server_id,
2025 },
2026 cx,
2027 );
2028 }
2029 project::Event::RefreshSemanticTokens { .. } => {
2030 editor.refresh_semantic_tokens(None, true, cx);
2031 }
2032 project::Event::LanguageServerRemoved(_) => {
2033 editor.registered_buffers.clear();
2034 editor.register_visible_buffers(cx);
2035 editor.invalidate_semantic_tokens(None);
2036 editor.refresh_runnables(None, window, cx);
2037 editor.update_lsp_data(None, window, cx);
2038 editor.refresh_inlay_hints(InlayHintRefreshReason::ServerRemoved, cx);
2039 }
2040 project::Event::SnippetEdit(id, snippet_edits) => {
2041 // todo(lw): Non singletons
2042 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2043 let snapshot = buffer.read(cx).snapshot();
2044 let focus_handle = editor.focus_handle(cx);
2045 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2046 for (range, snippet) in snippet_edits {
2047 let buffer_range =
2048 language::range_from_lsp(*range).to_offset(&snapshot);
2049 editor
2050 .insert_snippet(
2051 &[MultiBufferOffset(buffer_range.start)
2052 ..MultiBufferOffset(buffer_range.end)],
2053 snippet.clone(),
2054 window,
2055 cx,
2056 )
2057 .ok();
2058 }
2059 }
2060 }
2061 }
2062 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2063 let buffer_id = *buffer_id;
2064 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2065 editor.register_buffer(buffer_id, cx);
2066 editor.refresh_runnables(Some(buffer_id), window, cx);
2067 editor.invalidate_semantic_tokens(Some(buffer_id));
2068 editor.update_lsp_data(Some(buffer_id), window, cx);
2069 editor.refresh_inlay_hints(
2070 InlayHintRefreshReason::LanguageServerRegistered,
2071 cx,
2072 );
2073 refresh_linked_ranges(editor, window, cx);
2074 editor.refresh_code_actions_for_selection(window, cx);
2075 editor.refresh_document_highlights(cx);
2076 }
2077 }
2078
2079 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2080 let Some(workspace) = editor.workspace() else {
2081 return;
2082 };
2083 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2084 else {
2085 return;
2086 };
2087
2088 if active_editor.entity_id() == cx.entity_id() {
2089 let entity_id = cx.entity_id();
2090 workspace.update(cx, |this, cx| {
2091 this.panes_mut()
2092 .iter_mut()
2093 .filter(|pane| pane.entity_id() != entity_id)
2094 .for_each(|p| {
2095 p.update(cx, |pane, _| {
2096 pane.nav_history_mut().rename_item(
2097 entity_id,
2098 project_path.clone(),
2099 abs_path.clone().into(),
2100 );
2101 })
2102 });
2103 });
2104
2105 Self::open_transaction_for_hidden_buffers(
2106 workspace,
2107 transaction.clone(),
2108 "Rename".to_string(),
2109 window,
2110 cx,
2111 );
2112 }
2113 }
2114
2115 project::Event::WorkspaceEditApplied(transaction) => {
2116 let Some(workspace) = editor.workspace() else {
2117 return;
2118 };
2119 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2120 else {
2121 return;
2122 };
2123
2124 if active_editor.entity_id() == cx.entity_id() {
2125 Self::open_transaction_for_hidden_buffers(
2126 workspace,
2127 transaction.clone(),
2128 "LSP Edit".to_string(),
2129 window,
2130 cx,
2131 );
2132 }
2133 }
2134
2135 _ => {}
2136 },
2137 ));
2138 if let Some(task_inventory) = project
2139 .read(cx)
2140 .task_store()
2141 .read(cx)
2142 .task_inventory()
2143 .cloned()
2144 {
2145 project_subscriptions.push(cx.observe_in(
2146 &task_inventory,
2147 window,
2148 |editor, _, window, cx| {
2149 editor.refresh_runnables(None, window, cx);
2150 },
2151 ));
2152 };
2153
2154 project_subscriptions.push(cx.subscribe_in(
2155 &project.read(cx).breakpoint_store(),
2156 window,
2157 |editor, _, event, window, cx| match event {
2158 BreakpointStoreEvent::ClearDebugLines => {
2159 editor.clear_row_highlights::<ActiveDebugLine>();
2160 editor.refresh_inline_values(cx);
2161 }
2162 BreakpointStoreEvent::SetDebugLine => {
2163 if editor.go_to_active_debug_line(window, cx) {
2164 cx.stop_propagation();
2165 }
2166
2167 editor.refresh_inline_values(cx);
2168 }
2169 _ => {}
2170 },
2171 ));
2172 let git_store = project.read(cx).git_store().clone();
2173 let project = project.clone();
2174 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2175 if let GitStoreEvent::RepositoryAdded = event {
2176 this.load_diff_task = Some(
2177 update_uncommitted_diff_for_buffer(
2178 cx.entity(),
2179 &project,
2180 this.buffer.read(cx).all_buffers(),
2181 this.buffer.clone(),
2182 cx,
2183 )
2184 .shared(),
2185 );
2186 }
2187 }));
2188 }
2189
2190 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2191
2192 let inlay_hint_settings =
2193 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2194 let focus_handle = cx.focus_handle();
2195 if !is_minimap {
2196 cx.on_focus(&focus_handle, window, Self::handle_focus)
2197 .detach();
2198 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2199 .detach();
2200 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2201 .detach();
2202 cx.on_blur(&focus_handle, window, Self::handle_blur)
2203 .detach();
2204 cx.observe_pending_input(window, Self::observe_pending_input)
2205 .detach();
2206 }
2207
2208 let show_indent_guides =
2209 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2210 Some(false)
2211 } else {
2212 None
2213 };
2214
2215 let bookmark_store = match (&mode, project.as_ref()) {
2216 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).bookmark_store()),
2217 _ => None,
2218 };
2219
2220 let breakpoint_store = match (&mode, project.as_ref()) {
2221 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2222 _ => None,
2223 };
2224
2225 let mut code_action_providers = Vec::new();
2226 let mut load_uncommitted_diff = None;
2227 if let Some(project) = project.clone() {
2228 load_uncommitted_diff = Some(
2229 update_uncommitted_diff_for_buffer(
2230 cx.entity(),
2231 &project,
2232 multi_buffer.read(cx).all_buffers(),
2233 multi_buffer.clone(),
2234 cx,
2235 )
2236 .shared(),
2237 );
2238 code_action_providers.push(Rc::new(project) as Rc<_>);
2239 }
2240
2241 let mut editor = Self {
2242 focus_handle,
2243 show_cursor_when_unfocused: false,
2244 last_focused_descendant: None,
2245 buffer: multi_buffer.clone(),
2246 display_map: display_map.clone(),
2247 placeholder_display_map: None,
2248 selections,
2249 scroll_manager: ScrollManager::new(cx),
2250 columnar_selection_state: None,
2251 add_selections_state: None,
2252 select_next_state: None,
2253 select_prev_state: None,
2254 selection_history: SelectionHistory::default(),
2255 defer_selection_effects: false,
2256 deferred_selection_effects_state: None,
2257 autoclose_regions: Vec::new(),
2258 snippet_stack: InvalidationStack::default(),
2259 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2260 ime_transaction: None,
2261 active_diagnostics: ActiveDiagnostic::None,
2262 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2263 inline_diagnostics_update: Task::ready(()),
2264 inline_diagnostics: Vec::new(),
2265 soft_wrap_mode_override,
2266 diagnostics_max_severity,
2267 hard_wrap: None,
2268 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2269 semantics_provider: project
2270 .as_ref()
2271 .map(|project| Rc::new(project.downgrade()) as _),
2272 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2273 project,
2274 blink_manager: blink_manager.clone(),
2275 show_local_selections: true,
2276 show_scrollbars: ScrollbarAxes {
2277 horizontal: full_mode,
2278 vertical: full_mode,
2279 },
2280 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2281 offset_content: !matches!(mode, EditorMode::SingleLine),
2282 breadcrumbs_visibility: BreadcrumbsVisibility::from_settings(cx),
2283 show_gutter: full_mode,
2284 show_line_numbers: (!full_mode).then_some(false),
2285 use_relative_line_numbers: None,
2286 disable_expand_excerpt_buttons: !full_mode,
2287 delegate_expand_excerpts: false,
2288 delegate_open_excerpts: false,
2289 enable_lsp_data: full_mode,
2290 needs_initial_data_update: full_mode,
2291 enable_runnables: full_mode,
2292 enable_code_lens: full_mode,
2293 enable_mouse_wheel_zoom: full_mode,
2294 show_git_diff_gutter: None,
2295 show_code_actions: None,
2296 show_runnables: None,
2297 show_bookmarks: None,
2298 show_breakpoints: None,
2299 show_diff_review_button: false,
2300 show_wrap_guides: None,
2301 show_indent_guides,
2302 buffers_with_disabled_indent_guides: HashSet::default(),
2303 highlight_order: 0,
2304 highlighted_rows: Default::default(),
2305 background_highlights: HashMap::default(),
2306 navigation_overlays: HashMap::default(),
2307 gutter_highlights: Default::default(),
2308 allow_git_diff_scrollbar_markers: false,
2309 scrollbar_marker_state: ScrollbarMarkerState::default(),
2310 active_indent_guides_state: ActiveIndentGuidesState::default(),
2311 nav_history: None,
2312 context_menu: RefCell::new(None),
2313 context_menu_options: None,
2314 mouse_context_menu: None,
2315 completion_tasks: Vec::new(),
2316 inline_blame_popover: None,
2317 inline_blame_popover_show_task: None,
2318 signature_help_state: SignatureHelpState::default(),
2319 auto_signature_help: None,
2320 find_all_references_task_sources: Vec::new(),
2321 next_completion_id: 0,
2322 next_inlay_id: 0,
2323 code_action_providers,
2324 code_actions_for_selection: CodeActionsForSelection::None,
2325 runnables_for_selection_toggle: Task::ready(()),
2326 quick_selection_highlight_task: None,
2327 debounced_selection_highlight_task: None,
2328 debounced_selection_highlight_complete: false,
2329 last_selection_from_search: false,
2330 document_highlights_task: None,
2331 linked_editing_range_task: None,
2332 pending_rename: None,
2333 searchable: !is_minimap,
2334 cursor_shape: EditorSettings::get_global(cx)
2335 .cursor_shape
2336 .unwrap_or_default(),
2337 cursor_offset_on_selection: false,
2338 current_line_highlight: None,
2339 autoindent_mode: Some(AutoindentMode::EachLine),
2340 collapse_matches: false,
2341 workspace: None,
2342 input_enabled: !is_minimap,
2343 expects_character_input: !is_minimap,
2344 use_modal_editing: full_mode,
2345 read_only: is_minimap,
2346 use_autoclose: true,
2347 use_auto_surround: true,
2348 use_selection_highlight: true,
2349 auto_replace_emoji_shortcode: false,
2350 jsx_tag_auto_close_enabled_in_any_buffer: false,
2351 leader_id: None,
2352 remote_id: None,
2353 hover_state: HoverState::default(),
2354 pending_mouse_down: None,
2355 prev_pressure_stage: None,
2356 hovered_link_state: None,
2357 edit_prediction_provider: None,
2358 active_edit_prediction: None,
2359 stale_edit_prediction_in_menu: None,
2360 edit_prediction_preview: EditPredictionPreview::Inactive {
2361 released_too_fast: false,
2362 },
2363 inline_diagnostics_enabled: full_mode,
2364 diagnostics_enabled: full_mode,
2365 word_completions_enabled: full_mode,
2366 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2367 gutter_hovered: false,
2368 pixel_position_of_newest_cursor: None,
2369 last_bounds: None,
2370 last_position_map: None,
2371 last_right_margin: Pixels::ZERO,
2372 last_horizontal_scrollbar_visible: false,
2373 expect_bounds_change: None,
2374 gutter_dimensions: GutterDimensions::default(),
2375 style: None,
2376 show_cursor_names: false,
2377 hovered_cursors: HashMap::default(),
2378 next_editor_action_id: EditorActionId::default(),
2379 editor_actions: Rc::default(),
2380 edit_predictions_hidden_for_vim_mode: false,
2381 show_edit_predictions_override: None,
2382 show_completions_on_input_override: None,
2383 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2384 edit_prediction_settings: EditPredictionSettings::Disabled,
2385 in_leading_whitespace: false,
2386 custom_context_menu: None,
2387 show_git_blame_gutter: false,
2388 show_git_blame_inline: false,
2389 show_selection_menu: None,
2390 show_git_blame_inline_delay_task: None,
2391 git_blame_inline_enabled: full_mode
2392 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2393 buffer_serialization: is_minimap.not().then(|| {
2394 BufferSerialization::new(
2395 ProjectSettings::get_global(cx)
2396 .session
2397 .restore_unsaved_buffers,
2398 )
2399 }),
2400 blame: None,
2401 blame_subscription: None,
2402 pending_blame_hover_observation: None,
2403
2404 bookmark_store,
2405 breakpoint_store,
2406 gutter_hover_button: (None, None),
2407 gutter_diff_review_indicator: (None, None),
2408 diff_review_drag_state: None,
2409 diff_review_overlays: Vec::new(),
2410 stored_review_comments: Vec::new(),
2411 next_review_comment_id: 0,
2412 hovered_diff_hunk_row: None,
2413 _subscriptions: (!is_minimap)
2414 .then(|| {
2415 vec![
2416 cx.observe(&multi_buffer, Self::on_buffer_changed),
2417 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2418 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2419 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2420 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2421 cx.observe_global_in::<GlobalTheme>(window, Self::theme_changed),
2422 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2423 ]
2424 })
2425 .unwrap_or_default(),
2426 runnables: RunnableData::new(),
2427 pull_diagnostics_task: Task::ready(()),
2428 colors: None,
2429 code_lens: None,
2430 refresh_colors_task: Task::ready(()),
2431 refresh_code_lens_task: Task::ready(()),
2432 use_document_folding_ranges: false,
2433 refresh_folding_ranges_task: Task::ready(()),
2434 inlay_hints: None,
2435 next_color_inlay_id: 0,
2436 post_scroll_update: Task::ready(()),
2437 linked_edit_ranges: Default::default(),
2438 in_project_search: false,
2439 previous_search_ranges: None,
2440 breadcrumb_header: None,
2441 focused_block: None,
2442 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2443 addons: Default::default(),
2444 registered_buffers: HashMap::default(),
2445 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2446 selection_mark_mode: false,
2447 toggle_fold_multiple_buffers: Task::ready(()),
2448 serialize_selections: Task::ready(()),
2449 serialize_folds: Task::ready(()),
2450 text_style_refinement: None,
2451 load_diff_task: load_uncommitted_diff,
2452 diff_hunk_delegate: None,
2453 minimap: None,
2454 change_list: ChangeList::new(),
2455 mode,
2456 selection_drag_state: SelectionDragState::None,
2457 folding_newlines: Task::ready(()),
2458 lookup_key: None,
2459 select_next_is_case_sensitive: None,
2460 on_local_selections_changed: None,
2461 suppress_selection_callback: false,
2462 applicable_language_settings: HashMap::default(),
2463 semantic_token_state: SemanticTokenState::new(cx, full_mode),
2464 accent_data: None,
2465 bracket_fetched_tree_sitter_chunks: HashMap::default(),
2466 number_deleted_lines: false,
2467 refresh_matching_bracket_highlights_task: Task::ready(()),
2468 refresh_document_symbols_task: Task::ready(()).shared(),
2469 lsp_document_links: LspDocumentLinks::new(cx),
2470 lsp_document_symbols: HashMap::default(),
2471 refresh_outline_symbols_at_cursor_at_cursor_task: Task::ready(()),
2472 outline_symbols_at_cursor: None,
2473 sticky_headers_task: Task::ready(()),
2474 sticky_headers: None,
2475 colorize_brackets_task: Task::ready(()),
2476 };
2477
2478 if is_minimap {
2479 return editor;
2480 }
2481
2482 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2483 editor.accent_data = editor.fetch_accent_data(cx);
2484
2485 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2486 editor
2487 ._subscriptions
2488 .push(cx.observe(breakpoints, |_, _, cx| {
2489 cx.notify();
2490 }));
2491 }
2492 editor._subscriptions.extend(project_subscriptions);
2493
2494 editor._subscriptions.push(cx.subscribe_in(
2495 &cx.entity(),
2496 window,
2497 |editor, _, e: &EditorEvent, window, cx| match e {
2498 EditorEvent::ScrollPositionChanged { local, .. } => {
2499 if *local {
2500 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2501 editor.hide_blame_popover(true, cx);
2502 let snapshot = editor.snapshot(window, cx);
2503 let new_anchor = editor
2504 .scroll_manager
2505 .native_anchor(&snapshot.display_snapshot, cx);
2506 editor.update_restoration_data(cx, move |data| {
2507 data.scroll_position = (
2508 new_anchor.top_row(snapshot.buffer_snapshot()),
2509 new_anchor.offset,
2510 );
2511 });
2512
2513 editor.update_data_on_scroll(true, window, cx);
2514 }
2515 editor.refresh_sticky_headers(&editor.snapshot(window, cx), cx);
2516 }
2517 EditorEvent::Edited { .. } => {
2518 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2519 .map(|vim_mode| vim_mode.0)
2520 .unwrap_or(false);
2521 if !vim_mode {
2522 let display_map = editor.display_snapshot(cx);
2523 let selections = editor.selections.all_adjusted_display(&display_map);
2524 let pop_state = editor
2525 .change_list
2526 .last()
2527 .map(|previous| {
2528 previous.len() == selections.len()
2529 && previous.iter().enumerate().all(|(ix, p)| {
2530 p.to_display_point(&display_map).row()
2531 == selections[ix].head().row()
2532 })
2533 })
2534 .unwrap_or(false);
2535 let new_positions = selections
2536 .into_iter()
2537 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2538 .collect();
2539 editor
2540 .change_list
2541 .push_to_change_list(pop_state, new_positions);
2542 }
2543 }
2544 _ => (),
2545 },
2546 ));
2547
2548 if let Some(dap_store) = editor
2549 .project
2550 .as_ref()
2551 .map(|project| project.read(cx).dap_store())
2552 {
2553 let weak_editor = cx.weak_entity();
2554
2555 editor
2556 ._subscriptions
2557 .push(
2558 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2559 let session_entity = cx.entity();
2560 weak_editor
2561 .update(cx, |editor, cx| {
2562 editor._subscriptions.push(
2563 cx.subscribe(&session_entity, Self::on_debug_session_event),
2564 );
2565 })
2566 .ok();
2567 }),
2568 );
2569
2570 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2571 editor
2572 ._subscriptions
2573 .push(cx.subscribe(&session, Self::on_debug_session_event));
2574 }
2575 }
2576
2577 // skip adding the initial selection to selection history
2578 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2579 editor.end_selection(window, cx);
2580 editor.selection_history.mode = SelectionHistoryMode::Normal;
2581
2582 editor.scroll_manager.show_scrollbars(window, cx);
2583 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2584
2585 if full_mode {
2586 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2587 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2588
2589 if editor.git_blame_inline_enabled {
2590 editor.start_git_blame_inline(false, window, cx);
2591 }
2592
2593 editor.go_to_active_debug_line(window, cx);
2594
2595 editor.minimap =
2596 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2597 editor.colors = Some(LspColorData::new(cx));
2598 editor.use_document_folding_ranges = true;
2599 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2600 if editor.enable_code_lens && EditorSettings::get_global(cx).code_lens.inline() {
2601 editor.code_lens = Some(CodeLensState::default());
2602 }
2603
2604 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2605 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2606 }
2607 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2608 }
2609
2610 editor
2611 }
2612
2613 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2614 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2615 }
2616
2617 pub fn deploy_mouse_context_menu(
2618 &mut self,
2619 position: gpui::Point<Pixels>,
2620 context_menu: Entity<ContextMenu>,
2621 window: &mut Window,
2622 cx: &mut Context<Self>,
2623 ) {
2624 self.mouse_context_menu = Some(MouseContextMenu::new(
2625 self,
2626 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2627 context_menu,
2628 window,
2629 cx,
2630 ));
2631 }
2632
2633 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2634 self.mouse_context_menu
2635 .as_ref()
2636 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2637 }
2638
2639 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2640 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2641 }
2642
2643 fn key_context_internal(
2644 &self,
2645 has_active_edit_prediction: bool,
2646 window: &mut Window,
2647 cx: &mut App,
2648 ) -> KeyContext {
2649 let mut key_context = KeyContext::new_with_defaults();
2650 key_context.add("Editor");
2651 let mode = match self.mode {
2652 EditorMode::SingleLine => "single_line",
2653 EditorMode::AutoHeight { .. } => "auto_height",
2654 EditorMode::Minimap { .. } => "minimap",
2655 EditorMode::Full { .. } => "full",
2656 };
2657
2658 if EditorSettings::jupyter_enabled(cx) {
2659 key_context.add("jupyter");
2660 }
2661
2662 key_context.set("mode", mode);
2663 if self.pending_rename.is_some() {
2664 key_context.add("renaming");
2665 }
2666
2667 if let Some(snippet_stack) = self.snippet_stack.last() {
2668 key_context.add("in_snippet");
2669
2670 if snippet_stack.active_index > 0 {
2671 key_context.add("has_previous_tabstop");
2672 }
2673
2674 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2675 key_context.add("has_next_tabstop");
2676 }
2677 }
2678
2679 match self.context_menu.borrow().as_ref() {
2680 Some(CodeContextMenu::Completions(menu)) => {
2681 if menu.visible() {
2682 key_context.add("menu");
2683 key_context.add("showing_completions");
2684 }
2685 }
2686 Some(CodeContextMenu::CodeActions(menu)) => {
2687 if menu.visible() {
2688 key_context.add("menu");
2689 key_context.add("showing_code_actions")
2690 }
2691 }
2692 None => {}
2693 }
2694
2695 if self.signature_help_state.has_multiple_signatures() {
2696 key_context.add("showing_signature_help");
2697 }
2698
2699 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2700 if !self.focus_handle(cx).contains_focused(window, cx)
2701 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2702 {
2703 for addon in self.addons.values() {
2704 addon.extend_key_context(&mut key_context, cx)
2705 }
2706 }
2707
2708 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2709 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2710 Some(
2711 file.full_path(cx)
2712 .extension()?
2713 .to_string_lossy()
2714 .to_lowercase(),
2715 )
2716 }) {
2717 key_context.set("extension", extension);
2718 }
2719 } else {
2720 key_context.add("multibuffer");
2721 }
2722
2723 if has_active_edit_prediction {
2724 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2725 key_context.add("copilot_suggestion");
2726 }
2727
2728 if self.in_leading_whitespace {
2729 key_context.add("in_leading_whitespace");
2730 }
2731 if self.edit_prediction_requires_modifier() {
2732 key_context.set("edit_prediction_mode", "subtle")
2733 } else {
2734 key_context.set("edit_prediction_mode", "eager");
2735 }
2736
2737 if self.selection_mark_mode {
2738 key_context.add("selection_mode");
2739 }
2740
2741 let disjoint = self.selections.disjoint_anchors();
2742 if matches!(
2743 &self.mode,
2744 EditorMode::SingleLine | EditorMode::AutoHeight { .. }
2745 ) && let [selection] = disjoint
2746 && selection.start == selection.end
2747 {
2748 let snapshot = self.snapshot(window, cx);
2749 let snapshot = snapshot.buffer_snapshot();
2750 let caret_offset = selection.end.to_offset(snapshot);
2751
2752 if caret_offset == MultiBufferOffset(0) {
2753 key_context.add("start_of_input");
2754 }
2755
2756 if caret_offset == snapshot.len() {
2757 key_context.add("end_of_input");
2758 }
2759 }
2760
2761 if self.has_any_expanded_diff_hunks(cx) {
2762 key_context.add("diffs_expanded");
2763 }
2764
2765 key_context
2766 }
2767
2768 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2769 self.last_bounds.as_ref()
2770 }
2771
2772 pub(crate) fn last_right_margin(&self) -> Pixels {
2773 self.last_right_margin
2774 }
2775
2776 pub(crate) fn last_horizontal_scrollbar_visible(&self) -> bool {
2777 self.last_horizontal_scrollbar_visible
2778 }
2779
2780 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
2781 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
2782 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
2783 && let Some(dir) = file.abs_path(cx).parent()
2784 {
2785 return Some(dir.to_owned());
2786 }
2787 }
2788
2789 None
2790 }
2791
2792 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
2793 self.active_buffer(cx).and_then(|buffer| {
2794 let buffer = buffer.read(cx);
2795 if let Some(project_path) = buffer.project_path(cx) {
2796 let project = self.project()?.read(cx);
2797 project.absolute_path(&project_path, cx)
2798 } else {
2799 buffer
2800 .file()
2801 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
2802 }
2803 })
2804 }
2805
2806 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
2807 self.show_selection_menu
2808 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
2809 }
2810
2811 pub fn toggle_selection_menu(
2812 &mut self,
2813 _: &ToggleSelectionMenu,
2814 _: &mut Window,
2815 cx: &mut Context<Self>,
2816 ) {
2817 self.show_selection_menu = self
2818 .show_selection_menu
2819 .map(|show_selections_menu| !show_selections_menu)
2820 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
2821
2822 cx.notify();
2823 }
2824
2825 pub fn new_file(
2826 workspace: &mut Workspace,
2827 _: &workspace::NewFile,
2828 window: &mut Window,
2829 cx: &mut Context<Workspace>,
2830 ) {
2831 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2832 "Failed to create buffer",
2833 window,
2834 cx,
2835 |e, _, _| match e.error_code() {
2836 ErrorCode::RemoteUpgradeRequired => Some(format!(
2837 "The remote instance of Omega does not support this yet. It must be upgraded to {}",
2838 e.error_tag("required").unwrap_or("the latest version")
2839 )),
2840 _ => None,
2841 },
2842 );
2843 }
2844
2845 pub fn new_in_workspace(
2846 workspace: &mut Workspace,
2847 window: &mut Window,
2848 cx: &mut Context<Workspace>,
2849 ) -> Task<Result<Entity<Editor>>> {
2850 let project = workspace.project().clone();
2851 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
2852
2853 cx.spawn_in(window, async move |workspace, cx| {
2854 let buffer = create.await?;
2855 workspace.update_in(cx, |workspace, window, cx| {
2856 let editor =
2857 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2858 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2859 editor
2860 })
2861 })
2862 }
2863
2864 fn new_file_vertical(
2865 workspace: &mut Workspace,
2866 _: &workspace::NewFileSplitVertical,
2867 window: &mut Window,
2868 cx: &mut Context<Workspace>,
2869 ) {
2870 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2871 }
2872
2873 fn new_file_horizontal(
2874 workspace: &mut Workspace,
2875 _: &workspace::NewFileSplitHorizontal,
2876 window: &mut Window,
2877 cx: &mut Context<Workspace>,
2878 ) {
2879 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2880 }
2881
2882 fn new_file_split(
2883 workspace: &mut Workspace,
2884 action: &workspace::NewFileSplit,
2885 window: &mut Window,
2886 cx: &mut Context<Workspace>,
2887 ) {
2888 Self::new_file_in_direction(workspace, action.0, window, cx)
2889 }
2890
2891 fn new_file_in_direction(
2892 workspace: &mut Workspace,
2893 direction: SplitDirection,
2894 window: &mut Window,
2895 cx: &mut Context<Workspace>,
2896 ) {
2897 let project = workspace.project().clone();
2898 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
2899
2900 cx.spawn_in(window, async move |workspace, cx| {
2901 let buffer = create.await?;
2902 workspace.update_in(cx, move |workspace, window, cx| {
2903 workspace.split_item(
2904 direction,
2905 Box::new(
2906 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2907 ),
2908 window,
2909 cx,
2910 )
2911 })?;
2912 anyhow::Ok(())
2913 })
2914 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2915 match e.error_code() {
2916 ErrorCode::RemoteUpgradeRequired => Some(format!(
2917 "The remote instance of Omega does not support this yet. It must be upgraded to {}",
2918 e.error_tag("required").unwrap_or("the latest version")
2919 )),
2920 _ => None,
2921 }
2922 });
2923 }
2924
2925 pub fn leader_id(&self) -> Option<CollaboratorId> {
2926 self.leader_id
2927 }
2928
2929 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2930 &self.buffer
2931 }
2932
2933 pub fn project(&self) -> Option<&Entity<Project>> {
2934 self.project.as_ref()
2935 }
2936
2937 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2938 self.workspace.as_ref()?.0.upgrade()
2939 }
2940
2941 /// Detaches a task and shows an error notification in the workspace if available,
2942 /// otherwise just logs the error.
2943 pub fn detach_and_notify_err<R, E>(
2944 &self,
2945 task: Task<Result<R, E>>,
2946 window: &mut Window,
2947 cx: &mut App,
2948 ) where
2949 E: std::fmt::Debug + std::fmt::Display + 'static,
2950 R: 'static,
2951 {
2952 if let Some(workspace) = self.workspace() {
2953 task.detach_and_notify_err(workspace.downgrade(), window, cx);
2954 } else {
2955 task.detach_and_log_err(cx);
2956 }
2957 }
2958
2959 /// Returns the workspace serialization ID if this editor should be serialized.
2960 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2961 self.workspace
2962 .as_ref()
2963 .filter(|_| self.should_serialize_buffer())
2964 .and_then(|workspace| workspace.1)
2965 }
2966
2967 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2968 self.buffer().read(cx).title(cx)
2969 }
2970
2971 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2972 let git_blame_gutter_max_author_length = self
2973 .render_git_blame_gutter(cx)
2974 .then(|| {
2975 if let Some(blame) = self.blame.as_ref() {
2976 let max_author_length =
2977 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2978 Some(max_author_length)
2979 } else {
2980 None
2981 }
2982 })
2983 .flatten();
2984
2985 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2986
2987 EditorSnapshot {
2988 mode: self.mode.clone(),
2989 show_gutter: self.show_gutter,
2990 offset_content: self.offset_content,
2991 show_line_numbers: self.show_line_numbers,
2992 number_deleted_lines: self.number_deleted_lines,
2993 show_git_diff_gutter: self.show_git_diff_gutter,
2994 semantic_tokens_enabled: self.semantic_token_state.enabled(),
2995 show_code_actions: self.show_code_actions,
2996 show_runnables: self.show_runnables,
2997 show_bookmarks: self.show_bookmarks,
2998 show_breakpoints: self.show_breakpoints,
2999 git_blame_gutter_max_author_length,
3000 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
3001 display_snapshot,
3002 placeholder_display_snapshot: self
3003 .placeholder_display_map
3004 .as_ref()
3005 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
3006 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
3007 is_focused: self.focus_handle.is_focused(window),
3008 current_line_highlight: self
3009 .current_line_highlight
3010 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
3011 gutter_hovered: self.gutter_hovered,
3012 }
3013 }
3014
3015 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
3016 self.buffer.read(cx).language_at(point, cx)
3017 }
3018
3019 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
3020 self.buffer.read(cx).read(cx).file_at(point).cloned()
3021 }
3022
3023 pub fn active_buffer(&self, cx: &App) -> Option<Entity<Buffer>> {
3024 let multibuffer = self.buffer.read(cx);
3025 let snapshot = multibuffer.snapshot(cx);
3026 let (anchor, _) =
3027 snapshot.anchor_to_buffer_anchor(self.selections.newest_anchor().head())?;
3028 multibuffer.buffer(anchor.buffer_id)
3029 }
3030
3031 pub fn mode(&self) -> &EditorMode {
3032 &self.mode
3033 }
3034
3035 pub fn set_mode(&mut self, mode: EditorMode) {
3036 self.mode = mode;
3037 }
3038
3039 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
3040 self.collaboration_hub.as_deref()
3041 }
3042
3043 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
3044 self.collaboration_hub = Some(hub);
3045 }
3046
3047 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3048 self.in_project_search = in_project_search;
3049 }
3050
3051 pub fn set_custom_context_menu(
3052 &mut self,
3053 f: impl 'static
3054 + Fn(
3055 &mut Self,
3056 DisplayPoint,
3057 &mut Window,
3058 &mut Context<Self>,
3059 ) -> Option<Entity<ui::ContextMenu>>,
3060 ) {
3061 self.custom_context_menu = Some(Box::new(f))
3062 }
3063
3064 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3065 self.semantics_provider.clone()
3066 }
3067
3068 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3069 self.semantics_provider = provider;
3070 }
3071
3072 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3073 self.placeholder_display_map
3074 .as_ref()
3075 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3076 }
3077
3078 pub fn set_placeholder_text(
3079 &mut self,
3080 placeholder_text: &str,
3081 window: &mut Window,
3082 cx: &mut Context<Self>,
3083 ) {
3084 let multibuffer = cx
3085 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3086
3087 let style = window.text_style();
3088
3089 self.placeholder_display_map = Some(cx.new(|cx| {
3090 DisplayMap::new(
3091 multibuffer,
3092 style.font(),
3093 style.font_size.to_pixels(window.rem_size()),
3094 None,
3095 FILE_HEADER_HEIGHT,
3096 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3097 Default::default(),
3098 DiagnosticSeverity::Off,
3099 cx,
3100 )
3101 }));
3102 cx.notify();
3103 }
3104
3105 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3106 self.cursor_shape = cursor_shape;
3107
3108 // Disrupt blink for immediate user feedback that the cursor shape has changed
3109 self.blink_manager.update(cx, BlinkManager::show_cursor);
3110
3111 cx.notify();
3112 }
3113
3114 pub fn show_cursor(&mut self, cx: &mut Context<Self>) {
3115 self.blink_manager.update(cx, BlinkManager::show_cursor);
3116 }
3117
3118 pub fn cursor_shape(&self) -> CursorShape {
3119 self.cursor_shape
3120 }
3121
3122 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3123 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3124 }
3125
3126 /// Returns the anchor to use as the rename target for a selection.
3127 ///
3128 /// In selection-based modes, like vim's visual mode and helix, the rendered
3129 /// block cursor sits one position to the left of the selection's head,
3130 /// since the head is the exclusive end of a forward selection. Using the
3131 /// head
3132 /// directly would place the rename one character past the symbol, for
3133 /// example, trailing whitespace, so for a non-empty forward selection we
3134 /// shift one point left to land back on the symbol under the cursor.
3135 fn rename_target_anchor(&self, selection: &Selection<Anchor>, cx: &mut App) -> Anchor {
3136 let head = selection.head();
3137
3138 if self.cursor_offset_on_selection
3139 && !selection.reversed
3140 && selection.start != selection.end
3141 {
3142 let display_map = self.display_snapshot(cx);
3143 let display_head = head.to_display_point(&display_map);
3144
3145 if display_head.column() > 0 {
3146 return display_map.display_point_to_anchor(
3147 movement::left(&display_map, display_head),
3148 Bias::Left,
3149 );
3150 }
3151 }
3152 head
3153 }
3154
3155 pub fn set_current_line_highlight(
3156 &mut self,
3157 current_line_highlight: Option<CurrentLineHighlight>,
3158 ) {
3159 self.current_line_highlight = current_line_highlight;
3160 }
3161
3162 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3163 self.collapse_matches = collapse_matches;
3164 }
3165
3166 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3167 if self.collapse_matches {
3168 return range.start..range.start;
3169 }
3170 range.clone()
3171 }
3172
3173 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3174 self.display_map.read(cx).clip_at_line_ends
3175 }
3176
3177 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3178 if self.display_map.read(cx).clip_at_line_ends != clip {
3179 self.display_map
3180 .update(cx, |map, _| map.clip_at_line_ends = clip);
3181 }
3182 }
3183
3184 pub fn capability(&self, cx: &App) -> Capability {
3185 if self.read_only {
3186 Capability::ReadOnly
3187 } else {
3188 self.buffer.read(cx).capability()
3189 }
3190 }
3191
3192 pub fn read_only(&self, cx: &App) -> bool {
3193 self.read_only || self.buffer.read(cx).read_only()
3194 }
3195
3196 pub fn set_read_only(&mut self, read_only: bool) {
3197 self.read_only = read_only;
3198 }
3199
3200 pub fn set_use_selection_highlight(&mut self, highlight: bool) {
3201 self.use_selection_highlight = highlight;
3202 }
3203
3204 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3205 self.buffer_serialization = should_serialize.then(|| {
3206 BufferSerialization::new(
3207 ProjectSettings::get_global(cx)
3208 .session
3209 .restore_unsaved_buffers,
3210 )
3211 })
3212 }
3213
3214 fn should_serialize_buffer(&self) -> bool {
3215 self.buffer_serialization.is_some()
3216 }
3217
3218 pub fn set_use_modal_editing(&mut self, to: bool) {
3219 self.use_modal_editing = to;
3220 }
3221
3222 pub fn use_modal_editing(&self) -> bool {
3223 self.use_modal_editing
3224 }
3225
3226 /// Inserted text is normalized to LF line endings before being applied.
3227 /// Normalize before measuring inserted text for post-edit offsets.
3228 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3229 where
3230 I: IntoIterator<Item = (Range<S>, T)>,
3231 S: ToOffset,
3232 T: Into<Arc<str>>,
3233 {
3234 if self.read_only(cx) {
3235 return;
3236 }
3237
3238 self.buffer
3239 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3240 }
3241
3242 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3243 where
3244 I: IntoIterator<Item = (Range<S>, T)>,
3245 S: ToOffset,
3246 T: Into<Arc<str>>,
3247 {
3248 if self.read_only(cx) {
3249 return;
3250 }
3251
3252 self.buffer.update(cx, |buffer, cx| {
3253 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3254 });
3255 }
3256
3257 pub fn edit_with_block_indent<I, S, T>(
3258 &mut self,
3259 edits: I,
3260 original_indent_columns: Vec<Option<u32>>,
3261 cx: &mut Context<Self>,
3262 ) where
3263 I: IntoIterator<Item = (Range<S>, T)>,
3264 S: ToOffset,
3265 T: Into<Arc<str>>,
3266 {
3267 if self.read_only(cx) {
3268 return;
3269 }
3270
3271 self.buffer.update(cx, |buffer, cx| {
3272 buffer.edit(
3273 edits,
3274 Some(AutoindentMode::Block {
3275 original_indent_columns,
3276 }),
3277 cx,
3278 )
3279 });
3280 }
3281
3282 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3283 self.selection_mark_mode = false;
3284 self.selection_drag_state = SelectionDragState::None;
3285
3286 if self.dismiss_menus_and_popups(true, window, cx) {
3287 cx.notify();
3288 return;
3289 }
3290 if self.clear_expanded_diff_hunks(cx) {
3291 cx.notify();
3292 return;
3293 }
3294 if self.show_git_blame_gutter {
3295 self.show_git_blame_gutter = false;
3296 cx.notify();
3297 return;
3298 }
3299
3300 if self.mode.is_full()
3301 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3302 {
3303 cx.notify();
3304 return;
3305 }
3306
3307 cx.propagate();
3308 }
3309
3310 pub fn dismiss_menus_and_popups(
3311 &mut self,
3312 is_user_requested: bool,
3313 window: &mut Window,
3314 cx: &mut Context<Self>,
3315 ) -> bool {
3316 let mut dismissed = false;
3317
3318 dismissed |= self.take_rename(false, window, cx).is_some();
3319 dismissed |= self.hide_blame_popover(true, cx);
3320 dismissed |= hide_hover(self, cx);
3321 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
3322 dismissed |= self.hide_context_menu(window, cx).is_some();
3323 dismissed |= self.mouse_context_menu.take().is_some();
3324 dismissed |= is_user_requested
3325 && self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
3326 dismissed |= self.snippet_stack.pop().is_some();
3327 if self.diff_review_drag_state.is_some() {
3328 self.cancel_diff_review_drag(cx);
3329 dismissed = true;
3330 }
3331 if !self.diff_review_overlays.is_empty() {
3332 self.dismiss_all_diff_review_overlays(cx);
3333 dismissed = true;
3334 }
3335
3336 if self.mode.is_full() && self.has_active_diagnostic_group() {
3337 self.dismiss_diagnostics(cx);
3338 dismissed = true;
3339 }
3340
3341 dismissed
3342 }
3343
3344 fn open_transaction_for_hidden_buffers(
3345 workspace: Entity<Workspace>,
3346 transaction: ProjectTransaction,
3347 title: String,
3348 window: &mut Window,
3349 cx: &mut Context<Self>,
3350 ) {
3351 if transaction.0.is_empty() {
3352 return;
3353 }
3354
3355 let edited_buffers_already_open = {
3356 let other_editors: Vec<Entity<Editor>> = workspace
3357 .read(cx)
3358 .panes()
3359 .iter()
3360 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
3361 .filter(|editor| editor.entity_id() != cx.entity_id())
3362 .collect();
3363
3364 transaction.0.keys().all(|buffer| {
3365 other_editors.iter().any(|editor| {
3366 let multi_buffer = editor.read(cx).buffer();
3367 multi_buffer.read(cx).is_singleton()
3368 && multi_buffer
3369 .read(cx)
3370 .as_singleton()
3371 .map_or(false, |singleton| {
3372 singleton.entity_id() == buffer.entity_id()
3373 })
3374 })
3375 })
3376 };
3377 if !edited_buffers_already_open {
3378 let workspace = workspace.downgrade();
3379 cx.defer_in(window, move |_, window, cx| {
3380 cx.spawn_in(window, async move |editor, cx| {
3381 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
3382 .await
3383 .ok()
3384 })
3385 .detach();
3386 });
3387 }
3388 }
3389
3390 pub async fn open_project_transaction(
3391 editor: &WeakEntity<Editor>,
3392 workspace: WeakEntity<Workspace>,
3393 transaction: ProjectTransaction,
3394 title: String,
3395 cx: &mut AsyncWindowContext,
3396 ) -> Result<()> {
3397 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
3398 cx.update(|_, cx| {
3399 entries.sort_unstable_by_key(|(buffer, _)| {
3400 buffer.read(cx).file().map(|f| f.path().clone())
3401 });
3402 })?;
3403 if entries.is_empty() {
3404 return Ok(());
3405 }
3406
3407 // If the project transaction's edits are all contained within this editor, then
3408 // avoid opening a new editor to display them.
3409
3410 if let [(buffer, transaction)] = &*entries {
3411 let cursor_excerpt = editor.update(cx, |editor, cx| {
3412 let snapshot = editor.buffer().read(cx).snapshot(cx);
3413 let head = editor.selections.newest_anchor().head();
3414 let (buffer_snapshot, excerpt_range) = snapshot.excerpt_containing(head..head)?;
3415 if buffer_snapshot.remote_id() != buffer.read(cx).remote_id() {
3416 return None;
3417 }
3418 Some(excerpt_range)
3419 })?;
3420
3421 if let Some(excerpt_range) = cursor_excerpt {
3422 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
3423 let excerpt_range = excerpt_range.context.to_offset(buffer);
3424 buffer
3425 .edited_ranges_for_transaction::<usize>(transaction)
3426 .all(|range| {
3427 excerpt_range.start <= range.start && excerpt_range.end >= range.end
3428 })
3429 });
3430
3431 if all_edits_within_excerpt {
3432 return Ok(());
3433 }
3434 }
3435 }
3436
3437 let mut ranges_to_highlight = Vec::new();
3438 let excerpt_buffer = cx.new(|cx| {
3439 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
3440 for (buffer_handle, transaction) in &entries {
3441 let edited_ranges = buffer_handle
3442 .read(cx)
3443 .edited_ranges_for_transaction::<Point>(transaction)
3444 .collect::<Vec<_>>();
3445 multibuffer.set_excerpts_for_path(
3446 PathKey::for_buffer(buffer_handle, cx),
3447 buffer_handle.clone(),
3448 edited_ranges.clone(),
3449 multibuffer_context_lines(cx),
3450 cx,
3451 );
3452 let snapshot = multibuffer.snapshot(cx);
3453 let buffer_snapshot = buffer_handle.read(cx).snapshot();
3454 ranges_to_highlight.extend(edited_ranges.into_iter().filter_map(|range| {
3455 let text_range = buffer_snapshot.anchor_range_inside(range);
3456 let start = snapshot.anchor_in_buffer(text_range.start)?;
3457 let end = snapshot.anchor_in_buffer(text_range.end)?;
3458 Some(start..end)
3459 }));
3460 }
3461 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
3462 multibuffer
3463 });
3464
3465 workspace.update_in(cx, |workspace, window, cx| {
3466 let project = workspace.project().clone();
3467 let editor =
3468 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
3469 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
3470 editor.update(cx, |editor, cx| {
3471 editor.highlight_background(
3472 HighlightKey::Editor,
3473 &ranges_to_highlight,
3474 |_, theme| theme.colors().editor_highlighted_line_background,
3475 cx,
3476 );
3477 });
3478 })?;
3479
3480 Ok(())
3481 }
3482
3483 pub fn has_mouse_context_menu(&self) -> bool {
3484 self.mouse_context_menu.is_some()
3485 }
3486
3487 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
3488 if self.pending_rename.is_some() {
3489 return None;
3490 }
3491
3492 let provider = self.semantics_provider.clone()?;
3493 let buffer = self.buffer.read(cx);
3494 let newest_selection = self.selections.newest_anchor().clone();
3495 let cursor_position = newest_selection.head();
3496 let (cursor_buffer, cursor_buffer_position) =
3497 buffer.text_anchor_for_position(cursor_position, cx)?;
3498 let (tail_buffer, tail_buffer_position) =
3499 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
3500 if cursor_buffer != tail_buffer {
3501 return None;
3502 }
3503
3504 let snapshot = cursor_buffer.read(cx).snapshot();
3505 let word_ranges = cx.background_spawn(async move {
3506 // this might look odd to put on the background thread, but
3507 // `surrounding_word` can be quite expensive as it calls into
3508 // tree-sitter language scopes
3509 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
3510 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
3511 (start_word_range, end_word_range)
3512 });
3513
3514 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
3515 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
3516 let (start_word_range, end_word_range) = word_ranges.await;
3517 if start_word_range != end_word_range {
3518 this.update(cx, |this, cx| {
3519 this.document_highlights_task.take();
3520 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
3521 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
3522 })
3523 .ok();
3524 return;
3525 }
3526 cx.background_executor()
3527 .timer(Duration::from_millis(debounce))
3528 .await;
3529
3530 let highlights = if let Some(highlights) = cx.update(|cx| {
3531 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
3532 }) {
3533 highlights.await.log_err()
3534 } else {
3535 None
3536 };
3537
3538 if let Some(highlights) = highlights {
3539 this.update(cx, |this, cx| {
3540 if this.pending_rename.is_some() {
3541 return;
3542 }
3543
3544 let buffer = this.buffer.read(cx);
3545 if buffer
3546 .text_anchor_for_position(cursor_position, cx)
3547 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
3548 {
3549 return;
3550 }
3551
3552 let mut write_ranges = Vec::new();
3553 let mut read_ranges = Vec::new();
3554 let multibuffer_snapshot = buffer.snapshot(cx);
3555 for highlight in highlights {
3556 for range in
3557 multibuffer_snapshot.buffer_range_to_excerpt_ranges(highlight.range)
3558 {
3559 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
3560 write_ranges.push(range);
3561 } else {
3562 read_ranges.push(range);
3563 }
3564 }
3565 }
3566
3567 this.highlight_background(
3568 HighlightKey::DocumentHighlightRead,
3569 &read_ranges,
3570 |_, theme| theme.colors().editor_document_highlight_read_background,
3571 cx,
3572 );
3573 this.highlight_background(
3574 HighlightKey::DocumentHighlightWrite,
3575 &write_ranges,
3576 |_, theme| theme.colors().editor_document_highlight_write_background,
3577 cx,
3578 );
3579 cx.notify();
3580 })
3581 .log_err();
3582 }
3583 }));
3584 None
3585 }
3586
3587 fn prepare_highlight_query_from_selection(
3588 &mut self,
3589 snapshot: &DisplaySnapshot,
3590 cx: &mut Context<Editor>,
3591 ) -> Option<(String, Range<Anchor>)> {
3592 if matches!(self.mode, EditorMode::SingleLine) {
3593 return None;
3594 }
3595 if !self.use_selection_highlight || !EditorSettings::get_global(cx).selection_highlight {
3596 return None;
3597 }
3598 // When the current selection was set by search navigation, suppress selection
3599 // occurrence highlights to avoid confusing non-matching occurrences with actual
3600 // search results (e.g. `^something` matches 3 line-start occurrences, but a
3601 // literal highlight would also mark a mid-line "something" that never matched
3602 // the regex). A manual selection made by the user clears this flag, restoring
3603 // the normal occurrence-highlight behavior.
3604 if self.last_selection_from_search
3605 && self.has_background_highlights(HighlightKey::BufferSearchHighlights)
3606 {
3607 return None;
3608 }
3609 if self.selections.count() != 1 || self.selections.line_mode() {
3610 return None;
3611 }
3612 let selection = self.selections.newest::<Point>(&snapshot);
3613 // If the selection spans multiple rows OR it is empty
3614 if selection.start.row != selection.end.row
3615 || selection.start.column == selection.end.column
3616 {
3617 return None;
3618 }
3619 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
3620 let query = snapshot
3621 .buffer_snapshot()
3622 .text_for_range(selection_anchor_range.clone())
3623 .collect::<String>();
3624 if query.trim().is_empty() {
3625 return None;
3626 }
3627 Some((query, selection_anchor_range))
3628 }
3629
3630 #[ztracing::instrument(skip_all)]
3631 fn update_selection_occurrence_highlights(
3632 &mut self,
3633 multi_buffer_snapshot: MultiBufferSnapshot,
3634 query_text: String,
3635 query_range: Range<Anchor>,
3636 multi_buffer_range_to_query: Range<Point>,
3637 use_debounce: bool,
3638 window: &mut Window,
3639 cx: &mut Context<Editor>,
3640 ) -> Task<()> {
3641 cx.spawn_in(window, async move |editor, cx| {
3642 if use_debounce {
3643 cx.background_executor()
3644 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
3645 .await;
3646 }
3647 let match_task = cx.background_spawn(async move {
3648 let buffer_ranges = multi_buffer_snapshot
3649 .range_to_buffer_ranges(
3650 multi_buffer_range_to_query.start..multi_buffer_range_to_query.end,
3651 )
3652 .into_iter()
3653 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
3654 let mut match_ranges = Vec::new();
3655 let Ok(regex) = project::search::SearchQuery::text(
3656 query_text,
3657 false,
3658 false,
3659 false,
3660 Default::default(),
3661 Default::default(),
3662 false,
3663 None,
3664 ) else {
3665 return Vec::default();
3666 };
3667 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
3668 for (buffer_snapshot, search_range, _) in buffer_ranges {
3669 match_ranges.extend(
3670 regex
3671 .search(
3672 &buffer_snapshot,
3673 Some(search_range.start.0..search_range.end.0),
3674 )
3675 .await
3676 .into_iter()
3677 .filter_map(|match_range| {
3678 let match_start = buffer_snapshot
3679 .anchor_after(search_range.start + match_range.start);
3680 let match_end = buffer_snapshot
3681 .anchor_before(search_range.start + match_range.end);
3682 {
3683 let range = multi_buffer_snapshot
3684 .anchor_in_buffer(match_start)?
3685 ..multi_buffer_snapshot.anchor_in_buffer(match_end)?;
3686 Some(range).filter(|match_anchor_range| {
3687 match_anchor_range != &query_range
3688 })
3689 }
3690 }),
3691 );
3692 }
3693 match_ranges
3694 });
3695 let match_ranges = match_task.await;
3696 editor
3697 .update_in(cx, |editor, _, cx| {
3698 if use_debounce {
3699 editor.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
3700 editor.debounced_selection_highlight_complete = true;
3701 } else if editor.debounced_selection_highlight_complete {
3702 return;
3703 }
3704 if !match_ranges.is_empty() {
3705 editor.highlight_background(
3706 HighlightKey::SelectedTextHighlight,
3707 &match_ranges,
3708 |_, theme| theme.colors().editor_document_highlight_bracket_background,
3709 cx,
3710 )
3711 }
3712 })
3713 .log_err();
3714 })
3715 }
3716
3717 #[ztracing::instrument(skip_all)]
3718 fn refresh_outline_symbols_at_cursor(&mut self, cx: &mut Context<Editor>) {
3719 if !self.lsp_data_enabled() {
3720 return;
3721 }
3722 let cursor = self.selections.newest_anchor().head();
3723 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
3724
3725 if self.uses_lsp_document_symbols(cursor, &multi_buffer_snapshot, cx) {
3726 self.outline_symbols_at_cursor =
3727 self.lsp_symbols_at_cursor(cursor, &multi_buffer_snapshot, cx);
3728 cx.emit(EditorEvent::OutlineSymbolsChanged);
3729 cx.notify();
3730 } else {
3731 let syntax = cx.theme().syntax().clone();
3732 let background_task = cx.background_spawn(async move {
3733 multi_buffer_snapshot.symbols_containing(cursor, Some(&syntax))
3734 });
3735 self.refresh_outline_symbols_at_cursor_at_cursor_task =
3736 cx.spawn(async move |this, cx| {
3737 let symbols = background_task.await;
3738 this.update(cx, |this, cx| {
3739 this.outline_symbols_at_cursor = symbols;
3740 cx.emit(EditorEvent::OutlineSymbolsChanged);
3741 cx.notify();
3742 })
3743 .ok();
3744 });
3745 }
3746 }
3747
3748 #[ztracing::instrument(skip_all)]
3749 fn refresh_selected_text_highlights(
3750 &mut self,
3751 snapshot: &DisplaySnapshot,
3752 on_buffer_edit: bool,
3753 window: &mut Window,
3754 cx: &mut Context<Editor>,
3755 ) {
3756 let Some((query_text, query_range)) =
3757 self.prepare_highlight_query_from_selection(snapshot, cx)
3758 else {
3759 self.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
3760 self.quick_selection_highlight_task.take();
3761 self.debounced_selection_highlight_task.take();
3762 self.debounced_selection_highlight_complete = false;
3763 return;
3764 };
3765 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3766 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
3767 let query_changed = self
3768 .quick_selection_highlight_task
3769 .as_ref()
3770 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range);
3771 if query_changed {
3772 self.debounced_selection_highlight_complete = false;
3773 }
3774 if on_buffer_edit || query_changed {
3775 self.quick_selection_highlight_task = Some((
3776 query_range.clone(),
3777 self.update_selection_occurrence_highlights(
3778 snapshot.buffer.clone(),
3779 query_text.clone(),
3780 query_range.clone(),
3781 self.multi_buffer_visible_range(&display_snapshot, cx),
3782 false,
3783 window,
3784 cx,
3785 ),
3786 ));
3787 }
3788 if on_buffer_edit
3789 || self
3790 .debounced_selection_highlight_task
3791 .as_ref()
3792 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
3793 {
3794 let multi_buffer_start = multi_buffer_snapshot
3795 .anchor_before(MultiBufferOffset(0))
3796 .to_point(&multi_buffer_snapshot);
3797 let multi_buffer_end = multi_buffer_snapshot
3798 .anchor_after(multi_buffer_snapshot.len())
3799 .to_point(&multi_buffer_snapshot);
3800 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
3801 self.debounced_selection_highlight_task = Some((
3802 query_range.clone(),
3803 self.update_selection_occurrence_highlights(
3804 snapshot.buffer.clone(),
3805 query_text,
3806 query_range,
3807 multi_buffer_full_range,
3808 true,
3809 window,
3810 cx,
3811 ),
3812 ));
3813 }
3814 }
3815
3816 pub fn multi_buffer_visible_range(
3817 &self,
3818 display_snapshot: &DisplaySnapshot,
3819 cx: &App,
3820 ) -> Range<Point> {
3821 let visible_start = self
3822 .scroll_manager
3823 .native_anchor(display_snapshot, cx)
3824 .anchor
3825 .to_point(display_snapshot.buffer_snapshot())
3826 .to_display_point(display_snapshot);
3827
3828 let mut target_end = visible_start;
3829 *target_end.row_mut() += self.visible_line_count().unwrap_or(0.).ceil() as u32;
3830
3831 visible_start.to_point(display_snapshot)
3832 ..display_snapshot
3833 .clip_point(target_end, Bias::Right)
3834 .to_point(display_snapshot)
3835 }
3836
3837 pub fn display_cursor_names(
3838 &mut self,
3839 _: &DisplayCursorNames,
3840 window: &mut Window,
3841 cx: &mut Context<Self>,
3842 ) {
3843 self.show_cursor_names(window, cx);
3844 }
3845
3846 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3847 self.show_cursor_names = true;
3848 cx.notify();
3849 cx.spawn_in(window, async move |this, cx| {
3850 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
3851 this.update(cx, |this, cx| {
3852 this.show_cursor_names = false;
3853 cx.notify()
3854 })
3855 .ok()
3856 })
3857 .detach();
3858 }
3859
3860 fn handle_modifiers_changed(
3861 &mut self,
3862 modifiers: Modifiers,
3863 position_map: &PositionMap,
3864 window: &mut Window,
3865 cx: &mut Context<Self>,
3866 ) {
3867 self.update_edit_prediction_settings(cx);
3868
3869 // Ensure that the edit prediction preview is updated, even when not
3870 // enabled, if there's an active edit prediction preview.
3871 if self.show_edit_predictions_in_menu()
3872 || self.edit_prediction_requires_modifier()
3873 || matches!(
3874 self.edit_prediction_preview,
3875 EditPredictionPreview::Active { .. }
3876 )
3877 {
3878 self.update_edit_prediction_preview(&modifiers, window, cx);
3879 }
3880
3881 self.update_selection_mode(&modifiers, position_map, window, cx);
3882
3883 let mouse_position = window.mouse_position();
3884 if !position_map.text_hitbox.is_hovered(window) {
3885 if self.gutter_hover_button.0.is_some() {
3886 cx.notify();
3887 }
3888 return;
3889 }
3890
3891 self.update_hovered_link(
3892 position_map.point_for_position(mouse_position),
3893 Some(mouse_position),
3894 &position_map.snapshot,
3895 modifiers,
3896 window,
3897 cx,
3898 )
3899 }
3900
3901 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
3902 match EditorSettings::get_global(cx).multi_cursor_modifier {
3903 MultiCursorModifier::Alt => modifiers.secondary(),
3904 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
3905 }
3906 }
3907
3908 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
3909 match EditorSettings::get_global(cx).multi_cursor_modifier {
3910 MultiCursorModifier::Alt => modifiers.alt,
3911 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
3912 }
3913 }
3914
3915 fn columnar_selection_mode(
3916 modifiers: &Modifiers,
3917 cx: &mut Context<Self>,
3918 ) -> Option<ColumnarMode> {
3919 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
3920 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
3921 Some(ColumnarMode::FromMouse)
3922 } else if Self::is_alt_pressed(modifiers, cx) {
3923 Some(ColumnarMode::FromSelection)
3924 } else {
3925 None
3926 }
3927 } else {
3928 None
3929 }
3930 }
3931
3932 fn update_selection_mode(
3933 &mut self,
3934 modifiers: &Modifiers,
3935 position_map: &PositionMap,
3936 window: &mut Window,
3937 cx: &mut Context<Self>,
3938 ) {
3939 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
3940 return;
3941 };
3942 if self.selections.pending_anchor().is_none() {
3943 return;
3944 }
3945
3946 let mouse_position = window.mouse_position();
3947 let point_for_position = position_map.point_for_position(mouse_position);
3948 let position = point_for_position.previous_valid;
3949
3950 self.select(
3951 SelectPhase::BeginColumnar {
3952 position,
3953 reset: false,
3954 mode,
3955 goal_column: point_for_position.exact_unclipped.column(),
3956 },
3957 window,
3958 cx,
3959 );
3960 }
3961
3962 fn active_run_indicators(
3963 &mut self,
3964 range: Range<DisplayRow>,
3965 window: &mut Window,
3966 cx: &mut Context<Self>,
3967 ) -> HashSet<DisplayRow> {
3968 let snapshot = self.snapshot(window, cx);
3969
3970 let offset_range_start =
3971 snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left);
3972
3973 let offset_range_end =
3974 snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
3975
3976 self.runnables
3977 .all_runnables()
3978 .filter_map(|tasks| {
3979 let multibuffer_point = tasks.offset.to_point(&snapshot.buffer_snapshot());
3980 if multibuffer_point < offset_range_start || multibuffer_point > offset_range_end {
3981 return None;
3982 }
3983 let multibuffer_row = MultiBufferRow(multibuffer_point.row);
3984 let buffer_folded = snapshot
3985 .buffer_snapshot()
3986 .buffer_line_for_row(multibuffer_row)
3987 .map(|(buffer_snapshot, _)| buffer_snapshot.remote_id())
3988 .map(|buffer_id| self.is_buffer_folded(buffer_id, cx))
3989 .unwrap_or(false);
3990 if buffer_folded {
3991 return None;
3992 }
3993
3994 if snapshot.is_line_folded(multibuffer_row) {
3995 // Skip folded indicators, unless it's the starting line of a fold.
3996 if multibuffer_row
3997 .0
3998 .checked_sub(1)
3999 .is_some_and(|previous_row| {
4000 snapshot.is_line_folded(MultiBufferRow(previous_row))
4001 })
4002 {
4003 return None;
4004 }
4005 }
4006
4007 let display_row = multibuffer_point.to_display_point(&snapshot).row();
4008 Some(display_row)
4009 })
4010 .collect()
4011 }
4012
4013 fn active_bookmarks(
4014 &self,
4015 range: Range<DisplayRow>,
4016 window: &mut Window,
4017 cx: &mut Context<Self>,
4018 ) -> HashSet<DisplayRow> {
4019 let mut bookmark_display_points = HashSet::default();
4020
4021 let Some(bookmark_store) = self.bookmark_store.clone() else {
4022 return bookmark_display_points;
4023 };
4024
4025 let snapshot = self.snapshot(window, cx);
4026
4027 let multi_buffer_snapshot = snapshot.buffer_snapshot();
4028 let Some(project) = self.project() else {
4029 return bookmark_display_points;
4030 };
4031
4032 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
4033 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
4034
4035 for (buffer_snapshot, range, _excerpt_range) in
4036 multi_buffer_snapshot.range_to_buffer_ranges(range.start..range.end)
4037 {
4038 let Some(buffer) = project
4039 .read(cx)
4040 .buffer_for_id(buffer_snapshot.remote_id(), cx)
4041 else {
4042 continue;
4043 };
4044 let bookmarks = bookmark_store.update(cx, |store, cx| {
4045 store.bookmarks_for_buffer(
4046 buffer,
4047 buffer_snapshot.anchor_before(range.start)
4048 ..buffer_snapshot.anchor_after(range.end),
4049 &buffer_snapshot,
4050 cx,
4051 )
4052 });
4053 for bookmark in bookmarks {
4054 let Some(multi_buffer_anchor) =
4055 multi_buffer_snapshot.anchor_in_buffer(bookmark.anchor)
4056 else {
4057 continue;
4058 };
4059 let position = multi_buffer_anchor
4060 .to_point(&multi_buffer_snapshot)
4061 .to_display_point(&snapshot);
4062
4063 bookmark_display_points.insert(position.row());
4064 }
4065 }
4066
4067 bookmark_display_points
4068 }
4069
4070 fn render_bookmark(&self, row: DisplayRow, cx: &mut Context<Self>) -> IconButton {
4071 let focus_handle = self.focus_handle.clone();
4072 IconButton::new(("bookmark indicator", row.0 as usize), IconName::Bookmark)
4073 .icon_size(IconSize::XSmall)
4074 .size(ui::ButtonSize::None)
4075 .icon_color(Color::Info)
4076 .style(ButtonStyle::Transparent)
4077 .on_click(cx.listener(move |editor, _, _window, cx| {
4078 editor.toggle_bookmark_at_row(row, cx);
4079 }))
4080 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
4081 editor.set_gutter_context_menu(row, None, event.position(), window, cx);
4082 }))
4083 .tooltip(move |_window, cx| {
4084 Tooltip::with_meta_in(
4085 "Remove Bookmark",
4086 Some(&ToggleBookmark),
4087 SharedString::from("Right-click for more options"),
4088 &focus_handle,
4089 cx,
4090 )
4091 })
4092 }
4093
4094 /// Get all display points of breakpoints that will be rendered within editor
4095 ///
4096 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
4097 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
4098 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
4099 fn active_breakpoints(
4100 &self,
4101 range: Range<DisplayRow>,
4102 window: &mut Window,
4103 cx: &mut Context<Self>,
4104 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
4105 let mut breakpoint_display_points = HashMap::default();
4106
4107 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
4108 return breakpoint_display_points;
4109 };
4110
4111 let snapshot = self.snapshot(window, cx);
4112
4113 let multi_buffer_snapshot = snapshot.buffer_snapshot();
4114
4115 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
4116 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
4117
4118 for (buffer_snapshot, range, _) in
4119 multi_buffer_snapshot.range_to_buffer_ranges(range.start..range.end)
4120 {
4121 let Some(buffer) = self.buffer().read(cx).buffer(buffer_snapshot.remote_id()) else {
4122 continue;
4123 };
4124 let breakpoints = breakpoint_store.read(cx).breakpoints(
4125 &buffer,
4126 Some(
4127 buffer_snapshot.anchor_before(range.start)
4128 ..buffer_snapshot.anchor_after(range.end),
4129 ),
4130 &buffer_snapshot,
4131 cx,
4132 );
4133 for (breakpoint, state) in breakpoints {
4134 let Some(multi_buffer_anchor) =
4135 multi_buffer_snapshot.anchor_in_excerpt(breakpoint.position)
4136 else {
4137 continue;
4138 };
4139 let position = multi_buffer_anchor
4140 .to_point(&multi_buffer_snapshot)
4141 .to_display_point(&snapshot);
4142
4143 breakpoint_display_points.insert(
4144 position.row(),
4145 (multi_buffer_anchor, breakpoint.bp.clone(), state),
4146 );
4147 }
4148 }
4149
4150 breakpoint_display_points
4151 }
4152
4153 fn gutter_context_menu(
4154 &self,
4155 anchor: Anchor,
4156 display_row: DisplayRow,
4157 window: &mut Window,
4158 cx: &mut Context<Self>,
4159 ) -> Entity<ContextMenu> {
4160 let weak_editor = cx.weak_entity();
4161 let focus_handle = self.focus_handle(cx);
4162
4163 let row = self
4164 .buffer
4165 .read(cx)
4166 .snapshot(cx)
4167 .summary_for_anchor::<Point>(&anchor)
4168 .row;
4169
4170 let breakpoint = self
4171 .breakpoint_at_row(row, window, cx)
4172 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
4173
4174 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
4175 "Edit Log Breakpoint"
4176 } else {
4177 "Set Log Breakpoint"
4178 };
4179
4180 let condition_breakpoint_msg = if breakpoint
4181 .as_ref()
4182 .is_some_and(|bp| bp.1.condition.is_some())
4183 {
4184 "Edit Condition Breakpoint"
4185 } else {
4186 "Set Condition Breakpoint"
4187 };
4188
4189 let hit_condition_breakpoint_msg = if breakpoint
4190 .as_ref()
4191 .is_some_and(|bp| bp.1.hit_condition.is_some())
4192 {
4193 "Edit Hit Condition Breakpoint"
4194 } else {
4195 "Set Hit Condition Breakpoint"
4196 };
4197
4198 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
4199 "Unset Breakpoint"
4200 } else {
4201 "Set Breakpoint"
4202 };
4203
4204 let git_blame_msg = if self.show_git_blame_gutter {
4205 "Close Git Blame"
4206 } else {
4207 "Open Git Blame"
4208 };
4209
4210 let bookmark = self.bookmark_at_row(row, window, cx);
4211
4212 let set_bookmark_msg = if bookmark.as_ref().is_some() {
4213 "Remove Bookmark"
4214 } else {
4215 "Add Bookmark"
4216 };
4217 let has_bookmark = bookmark.as_ref().is_some();
4218
4219 let clear_runnable_task_status = self
4220 .runnable_task_key_for_display_row(display_row, window, cx)
4221 .filter(|(buffer_id, buffer_row)| {
4222 matches!(
4223 self.runnable_task_status(*buffer_id, *buffer_row),
4224 Some(RunnableTaskStatus::Passed | RunnableTaskStatus::Failed)
4225 )
4226 });
4227
4228 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
4229
4230 let toggle_state_entry: Option<(&str, Box<dyn Action>)> =
4231 breakpoint.as_ref().map(|bp| match bp.1.state {
4232 BreakpointState::Enabled => {
4233 ("Disable", crate::actions::DisableBreakpoint.boxed_clone())
4234 }
4235 BreakpointState::Disabled => {
4236 ("Enable", crate::actions::EnableBreakpoint.boxed_clone())
4237 }
4238 });
4239
4240 let (anchor, breakpoint) =
4241 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
4242
4243 ContextMenu::build(window, cx, |menu, _, _cx| {
4244 menu.on_blur_subscription(Subscription::new(|| {}))
4245 .context(focus_handle)
4246 .when_some(
4247 clear_runnable_task_status,
4248 |this, (buffer_id, buffer_row)| {
4249 this.entry("Clear Run Status", None, {
4250 let weak_editor = weak_editor.clone();
4251 move |_window, cx| {
4252 weak_editor
4253 .update(cx, |this, cx| {
4254 this.clear_runnable_task_status(buffer_id, buffer_row, cx);
4255 })
4256 .log_err();
4257 }
4258 })
4259 .separator()
4260 },
4261 )
4262 .when(run_to_cursor, |this| {
4263 let weak_editor = weak_editor.clone();
4264 this.entry(
4265 "Run to Cursor",
4266 Some(RunToCursor.boxed_clone()),
4267 move |window, cx| {
4268 weak_editor
4269 .update(cx, |editor, cx| {
4270 editor.change_selections(
4271 SelectionEffects::no_scroll(),
4272 window,
4273 cx,
4274 |s| {
4275 s.select_ranges(
4276 [Point::new(row, 0)..Point::new(row, 0)],
4277 )
4278 },
4279 );
4280 })
4281 .ok();
4282
4283 window.dispatch_action(Box::new(RunToCursor), cx);
4284 },
4285 )
4286 .separator()
4287 })
4288 .when_some(toggle_state_entry, |this, (msg, action)| {
4289 this.entry(msg, Some(action), {
4290 let weak_editor = weak_editor.clone();
4291 let breakpoint = breakpoint.clone();
4292 move |_window, cx| {
4293 weak_editor
4294 .update(cx, |this, cx| {
4295 this.edit_breakpoint_at_anchor(
4296 anchor,
4297 breakpoint.as_ref().clone(),
4298 BreakpointEditAction::InvertState,
4299 cx,
4300 );
4301 })
4302 .log_err();
4303 }
4304 })
4305 })
4306 .entry(
4307 set_breakpoint_msg,
4308 Some(crate::actions::ToggleBreakpoint.boxed_clone()),
4309 {
4310 let weak_editor = weak_editor.clone();
4311 let breakpoint = breakpoint.clone();
4312 move |_window, cx| {
4313 weak_editor
4314 .update(cx, |this, cx| {
4315 this.edit_breakpoint_at_anchor(
4316 anchor,
4317 breakpoint.as_ref().clone(),
4318 BreakpointEditAction::Toggle,
4319 cx,
4320 );
4321 })
4322 .log_err();
4323 }
4324 },
4325 )
4326 .entry(
4327 log_breakpoint_msg,
4328 Some(crate::actions::EditLogBreakpoint.boxed_clone()),
4329 {
4330 let breakpoint = breakpoint.clone();
4331 let weak_editor = weak_editor.clone();
4332 move |window, cx| {
4333 weak_editor
4334 .update(cx, |this, cx| {
4335 this.add_edit_breakpoint_block(
4336 anchor,
4337 breakpoint.as_ref(),
4338 BreakpointPromptEditAction::Log,
4339 window,
4340 cx,
4341 );
4342 })
4343 .log_err();
4344 }
4345 },
4346 )
4347 .entry(condition_breakpoint_msg, None, {
4348 let breakpoint = breakpoint.clone();
4349 let weak_editor = weak_editor.clone();
4350 move |window, cx| {
4351 weak_editor
4352 .update(cx, |this, cx| {
4353 this.add_edit_breakpoint_block(
4354 anchor,
4355 breakpoint.as_ref(),
4356 BreakpointPromptEditAction::Condition,
4357 window,
4358 cx,
4359 );
4360 })
4361 .log_err();
4362 }
4363 })
4364 .entry(hit_condition_breakpoint_msg, None, {
4365 let breakpoint = breakpoint.clone();
4366 let weak_editor = weak_editor.clone();
4367 move |window, cx| {
4368 weak_editor
4369 .update(cx, |this, cx| {
4370 this.add_edit_breakpoint_block(
4371 anchor,
4372 breakpoint.as_ref(),
4373 BreakpointPromptEditAction::HitCondition,
4374 window,
4375 cx,
4376 );
4377 })
4378 .log_err();
4379 }
4380 })
4381 .separator()
4382 .entry(git_blame_msg, Some(Blame.boxed_clone()), {
4383 let weak_editor = weak_editor.clone();
4384 move |window, cx| {
4385 weak_editor
4386 .update(cx, |this, cx| {
4387 this.toggle_git_blame(&Blame, window, cx);
4388 })
4389 .log_err();
4390 }
4391 })
4392 .separator()
4393 .entry(set_bookmark_msg, Some(ToggleBookmark.boxed_clone()), {
4394 let weak_editor = weak_editor.clone();
4395 move |_window, cx| {
4396 weak_editor
4397 .update(cx, |this, cx| {
4398 this.toggle_bookmark_at_anchor(anchor, cx);
4399 })
4400 .log_err();
4401 }
4402 })
4403 .when(has_bookmark, |this| {
4404 this.entry(
4405 "Edit Bookmark",
4406 Some(EditBookmark.boxed_clone()),
4407 move |window, cx| {
4408 weak_editor
4409 .update(cx, |this, cx| {
4410 this.edit_bookmark_at_anchor(anchor, window, cx);
4411 })
4412 .log_err();
4413 },
4414 )
4415 })
4416 })
4417 }
4418
4419 fn render_breakpoint(
4420 &self,
4421 position: Anchor,
4422 row: DisplayRow,
4423 breakpoint: &Breakpoint,
4424 state: Option<BreakpointSessionState>,
4425 cx: &mut Context<Self>,
4426 ) -> IconButton {
4427 let is_rejected = state.is_some_and(|s| !s.verified);
4428
4429 let (color, icon) = {
4430 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
4431 (false, false) => ui::IconName::DebugBreakpoint,
4432 (true, false) => ui::IconName::DebugLogBreakpoint,
4433 (false, true) => ui::IconName::DebugDisabledBreakpoint,
4434 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
4435 };
4436
4437 let color = if is_rejected {
4438 Color::Disabled
4439 } else {
4440 Color::Debugger
4441 };
4442
4443 (color, icon)
4444 };
4445
4446 let breakpoint = Arc::from(breakpoint.clone());
4447
4448 let alt_as_text = gpui::Keystroke {
4449 modifiers: Modifiers::secondary_key(),
4450 ..Default::default()
4451 };
4452 let primary_action_text = "Unset breakpoint";
4453 let focus_handle = self.focus_handle.clone();
4454 let has_context_menu = self.has_mouse_context_menu();
4455
4456 let meta = if is_rejected {
4457 SharedString::from("No executable code is associated with this line.")
4458 } else if !breakpoint.is_disabled() {
4459 SharedString::from(format!(
4460 "{alt_as_text}-click to disable\nright-click for more options"
4461 ))
4462 } else {
4463 SharedString::from("Right-click for more options")
4464 };
4465 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
4466 .icon_size(IconSize::XSmall)
4467 .size(ui::ButtonSize::None)
4468 .when(is_rejected, |this| {
4469 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
4470 })
4471 .icon_color(color)
4472 .style(ButtonStyle::Transparent)
4473 .on_click(cx.listener({
4474 move |editor, event: &ClickEvent, window, cx| {
4475 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
4476 BreakpointEditAction::InvertState
4477 } else {
4478 BreakpointEditAction::Toggle
4479 };
4480
4481 window.focus(&editor.focus_handle(cx), cx);
4482 editor.edit_breakpoint_at_anchor(
4483 position,
4484 breakpoint.as_ref().clone(),
4485 edit_action,
4486 cx,
4487 );
4488 }
4489 }))
4490 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
4491 editor.set_gutter_context_menu(row, Some(position), event.position(), window, cx);
4492 }))
4493 .when(!has_context_menu, |button| {
4494 button.tooltip(move |_window, cx| {
4495 Tooltip::with_meta_in(
4496 primary_action_text,
4497 Some(&ToggleBreakpoint),
4498 meta.clone(),
4499 &focus_handle,
4500 cx,
4501 )
4502 })
4503 })
4504 }
4505
4506 fn render_gutter_hover_button(
4507 &self,
4508 position: Anchor,
4509 row: DisplayRow,
4510 window: &mut Window,
4511 cx: &mut Context<Self>,
4512 ) -> IconButton {
4513 let gutter_settings = EditorSettings::get_global(cx).gutter;
4514 let show_bookmarks = self.show_bookmarks.unwrap_or(gutter_settings.bookmarks);
4515 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
4516
4517 let [primary, secondary] = match [show_breakpoints, show_bookmarks] {
4518 [true, true] => [
4519 GutterButtonIntent::SetBreakpoint,
4520 GutterButtonIntent::SetBookmark,
4521 ],
4522 [true, false] => [GutterButtonIntent::SetBreakpoint; 2],
4523 [false, true] => [GutterButtonIntent::SetBookmark; 2],
4524 [false, false] => {
4525 log::error!("Trying to place gutter_hover without anything enabled!!");
4526 [GutterButtonIntent::SetBookmark; 2]
4527 }
4528 };
4529
4530 let intent = if window.modifiers().secondary() {
4531 secondary
4532 } else {
4533 primary
4534 };
4535
4536 let focus_handle = self.focus_handle.clone();
4537 let has_context_menu = self.has_mouse_context_menu();
4538 IconButton::new(("add_breakpoint_button", row.0 as usize), intent.icon())
4539 .icon_size(IconSize::XSmall)
4540 .size(ui::ButtonSize::None)
4541 .icon_color(intent.color())
4542 .style(ButtonStyle::Transparent)
4543 .on_click(cx.listener({
4544 move |editor, _: &ClickEvent, window, cx| {
4545 window.focus(&editor.focus_handle(cx), cx);
4546 let intent = if window.modifiers().secondary() {
4547 secondary
4548 } else {
4549 primary
4550 };
4551
4552 match intent {
4553 GutterButtonIntent::SetBookmark => editor.toggle_bookmark_at_row(row, cx),
4554 GutterButtonIntent::SetBreakpoint => editor.edit_breakpoint_at_anchor(
4555 position,
4556 Breakpoint::new_standard(),
4557 BreakpointEditAction::Toggle,
4558 cx,
4559 ),
4560 }
4561 }
4562 }))
4563 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
4564 editor.set_gutter_context_menu(row, Some(position), event.position(), window, cx);
4565 }))
4566 .when(!has_context_menu, |button| {
4567 button.tooltip(move |_window, cx| {
4568 cx.new(|_| GutterButtonTooltip {
4569 primary,
4570 secondary,
4571 focus_handle: focus_handle.clone(),
4572 })
4573 .into()
4574 })
4575 })
4576 }
4577
4578 fn build_tasks_context(
4579 project: &Entity<Project>,
4580 buffer: &Entity<Buffer>,
4581 buffer_row: u32,
4582 tasks: &Arc<RunnableTasks>,
4583 cx: &mut Context<Self>,
4584 ) -> Task<Result<Option<task::TaskContext>>> {
4585 let position = Point::new(buffer_row, tasks.column);
4586 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
4587 let location = Location {
4588 buffer: buffer.clone(),
4589 range: range_start..range_start,
4590 };
4591 // Fill in the environmental variables from the tree-sitter captures
4592 let mut captured_task_variables = TaskVariables::default();
4593 for (capture_name, value) in tasks.extra_variables.clone() {
4594 captured_task_variables.insert(
4595 task::VariableName::Custom(capture_name.into()),
4596 value.clone(),
4597 );
4598 }
4599 project.update(cx, |project, cx| {
4600 project.task_store().update(cx, |task_store, cx| {
4601 task_store.task_context_for_location(captured_task_variables, location, cx)
4602 })
4603 })
4604 }
4605
4606 pub fn context_menu_visible(&self) -> bool {
4607 !self.edit_prediction_preview_is_active()
4608 && self
4609 .context_menu
4610 .borrow()
4611 .as_ref()
4612 .is_some_and(|menu| menu.visible())
4613 }
4614
4615 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
4616 self.context_menu
4617 .borrow()
4618 .as_ref()
4619 .map(|menu| menu.origin())
4620 }
4621
4622 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
4623 self.context_menu_options = Some(options);
4624 }
4625
4626 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
4627 if self.read_only(cx) {
4628 cx.theme().players().read_only()
4629 } else {
4630 self.style.as_ref().unwrap().local_player
4631 }
4632 }
4633
4634 pub fn render_context_menu(
4635 &mut self,
4636 max_height_in_lines: u32,
4637 window: &mut Window,
4638 cx: &mut Context<Editor>,
4639 ) -> Option<AnyElement> {
4640 let menu = self.context_menu.borrow();
4641 let menu = menu.as_ref()?;
4642 if !menu.visible() {
4643 return None;
4644 };
4645 self.style
4646 .as_ref()
4647 .map(|style| menu.render(style, max_height_in_lines, window, cx))
4648 }
4649
4650 fn render_context_menu_aside(
4651 &mut self,
4652 max_size: Size<Pixels>,
4653 window: &mut Window,
4654 cx: &mut Context<Editor>,
4655 ) -> Option<AnyElement> {
4656 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
4657 if menu.visible() {
4658 menu.render_aside(max_size, window, cx)
4659 } else {
4660 None
4661 }
4662 })
4663 }
4664
4665 fn hide_context_menu(
4666 &mut self,
4667 window: &mut Window,
4668 cx: &mut Context<Self>,
4669 ) -> Option<CodeContextMenu> {
4670 cx.notify();
4671 self.completion_tasks.clear();
4672 let context_menu = self.context_menu.borrow_mut().take();
4673 self.stale_edit_prediction_in_menu.take();
4674 self.update_visible_edit_prediction(window, cx);
4675 if let Some(CodeContextMenu::Completions(_)) = &context_menu
4676 && let Some(completion_provider) = &self.completion_provider
4677 {
4678 completion_provider.selection_changed(None, window, cx);
4679 }
4680 context_menu
4681 }
4682
4683 fn show_snippet_choices(
4684 &mut self,
4685 choices: &Vec<String>,
4686 selection: Range<Anchor>,
4687 cx: &mut Context<Self>,
4688 ) {
4689 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
4690 let Some((buffer_snapshot, range)) =
4691 buffer_snapshot.anchor_range_to_buffer_anchor_range(selection.clone())
4692 else {
4693 return;
4694 };
4695 let Some(buffer) = self.buffer.read(cx).buffer(buffer_snapshot.remote_id()) else {
4696 return;
4697 };
4698
4699 let id = post_inc(&mut self.next_completion_id);
4700 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
4701 let mut context_menu = self.context_menu.borrow_mut();
4702 let old_menu = context_menu.take();
4703 *context_menu = Some(CodeContextMenu::Completions(
4704 CompletionsMenu::new_snippet_choices(
4705 id,
4706 true,
4707 choices,
4708 selection.start,
4709 range,
4710 buffer,
4711 old_menu.map(|menu| menu.primary_scroll_handle()),
4712 snippet_sort_order,
4713 ),
4714 ));
4715 }
4716
4717 pub fn insert_snippet(
4718 &mut self,
4719 insertion_ranges: &[Range<MultiBufferOffset>],
4720 snippet: Snippet,
4721 window: &mut Window,
4722 cx: &mut Context<Self>,
4723 ) -> Result<()> {
4724 struct Tabstop<T> {
4725 is_end_tabstop: bool,
4726 ranges: Vec<Range<T>>,
4727 choices: Option<Vec<String>>,
4728 }
4729
4730 let tabstops = self.buffer.update(cx, |buffer, cx| {
4731 let snippet_text: Arc<str> = snippet.text.clone().into();
4732 let edits = insertion_ranges
4733 .iter()
4734 .cloned()
4735 .map(|range| (range, snippet_text.clone()));
4736 let autoindent_mode = AutoindentMode::Block {
4737 original_indent_columns: Vec::new(),
4738 };
4739 buffer.edit(edits, Some(autoindent_mode), cx);
4740
4741 let snapshot = &*buffer.read(cx);
4742 let snippet = &snippet;
4743 snippet
4744 .tabstops
4745 .iter()
4746 .map(|tabstop| {
4747 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
4748 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
4749 });
4750 let mut tabstop_ranges = tabstop
4751 .ranges
4752 .iter()
4753 .flat_map(|tabstop_range| {
4754 let mut delta = 0_isize;
4755 insertion_ranges.iter().map(move |insertion_range| {
4756 let insertion_start = insertion_range.start + delta;
4757 delta += snippet.text.len() as isize
4758 - (insertion_range.end - insertion_range.start) as isize;
4759
4760 let start =
4761 (insertion_start + tabstop_range.start).min(snapshot.len());
4762 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
4763 snapshot.anchor_before(start)..snapshot.anchor_after(end)
4764 })
4765 })
4766 .collect::<Vec<_>>();
4767 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
4768
4769 Tabstop {
4770 is_end_tabstop,
4771 ranges: tabstop_ranges,
4772 choices: tabstop.choices.clone(),
4773 }
4774 })
4775 .collect::<Vec<_>>()
4776 });
4777 if let Some(tabstop) = tabstops.first() {
4778 self.change_selections(Default::default(), window, cx, |s| {
4779 // Reverse order so that the first range is the newest created selection.
4780 // Completions will use it and autoscroll will prioritize it.
4781 s.select_ranges(tabstop.ranges.iter().rev().cloned());
4782 });
4783
4784 if let Some(choices) = &tabstop.choices
4785 && let Some(selection) = tabstop.ranges.first()
4786 {
4787 self.show_snippet_choices(choices, selection.clone(), cx)
4788 }
4789
4790 // If we're already at the last tabstop and it's at the end of the snippet,
4791 // we're done, we don't need to keep the state around.
4792 if !tabstop.is_end_tabstop {
4793 let choices = tabstops
4794 .iter()
4795 .map(|tabstop| tabstop.choices.clone())
4796 .collect();
4797
4798 let ranges = tabstops
4799 .into_iter()
4800 .map(|tabstop| tabstop.ranges)
4801 .collect::<Vec<_>>();
4802
4803 self.snippet_stack.push(SnippetState {
4804 active_index: 0,
4805 ranges,
4806 choices,
4807 });
4808 }
4809
4810 // Check whether the just-entered snippet ends with an auto-closable bracket.
4811 if self.autoclose_regions.is_empty() {
4812 let snapshot = self.buffer.read(cx).snapshot(cx);
4813 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
4814 let selection_head = selection.head();
4815 let Some(scope) = snapshot.language_scope_at(selection_head) else {
4816 continue;
4817 };
4818
4819 let mut bracket_pair = None;
4820 let max_lookup_length = scope
4821 .brackets()
4822 .map(|(pair, _)| {
4823 pair.start
4824 .as_str()
4825 .chars()
4826 .count()
4827 .max(pair.end.as_str().chars().count())
4828 })
4829 .max();
4830 if let Some(max_lookup_length) = max_lookup_length {
4831 let next_text = snapshot
4832 .chars_at(selection_head)
4833 .take(max_lookup_length)
4834 .collect::<String>();
4835 let prev_text = snapshot
4836 .reversed_chars_at(selection_head)
4837 .take(max_lookup_length)
4838 .collect::<String>();
4839
4840 for (pair, enabled) in scope.brackets() {
4841 if enabled
4842 && pair.close
4843 && prev_text.starts_with(pair.start.as_str())
4844 && next_text.starts_with(pair.end.as_str())
4845 {
4846 bracket_pair = Some(pair.clone());
4847 break;
4848 }
4849 }
4850 }
4851
4852 if let Some(pair) = bracket_pair {
4853 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
4854 let autoclose_enabled =
4855 self.use_autoclose && snapshot_settings.use_autoclose;
4856 if autoclose_enabled {
4857 let start = snapshot.anchor_after(selection_head);
4858 let end = snapshot.anchor_after(selection_head);
4859 self.autoclose_regions.push(AutocloseRegion {
4860 selection_id: selection.id,
4861 range: start..end,
4862 pair,
4863 });
4864 }
4865 }
4866 }
4867 }
4868 }
4869 Ok(())
4870 }
4871
4872 pub fn move_to_next_snippet_tabstop(
4873 &mut self,
4874 window: &mut Window,
4875 cx: &mut Context<Self>,
4876 ) -> bool {
4877 self.move_to_snippet_tabstop(Bias::Right, window, cx)
4878 }
4879
4880 pub fn move_to_prev_snippet_tabstop(
4881 &mut self,
4882 window: &mut Window,
4883 cx: &mut Context<Self>,
4884 ) -> bool {
4885 self.move_to_snippet_tabstop(Bias::Left, window, cx)
4886 }
4887
4888 pub fn move_to_snippet_tabstop(
4889 &mut self,
4890 bias: Bias,
4891 window: &mut Window,
4892 cx: &mut Context<Self>,
4893 ) -> bool {
4894 if let Some(mut snippet) = self.snippet_stack.pop() {
4895 match bias {
4896 Bias::Left => {
4897 if snippet.active_index > 0 {
4898 snippet.active_index -= 1;
4899 } else {
4900 self.snippet_stack.push(snippet);
4901 return false;
4902 }
4903 }
4904 Bias::Right => {
4905 if snippet.active_index + 1 < snippet.ranges.len() {
4906 snippet.active_index += 1;
4907 } else {
4908 self.snippet_stack.push(snippet);
4909 return false;
4910 }
4911 }
4912 }
4913 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
4914 self.change_selections(Default::default(), window, cx, |s| {
4915 // Reverse order so that the first range is the newest created selection.
4916 // Completions will use it and autoscroll will prioritize it.
4917 s.select_ranges(current_ranges.iter().rev().cloned())
4918 });
4919
4920 if let Some(choices) = &snippet.choices[snippet.active_index]
4921 && let Some(selection) = current_ranges.first()
4922 {
4923 self.show_snippet_choices(choices, selection.clone(), cx);
4924 }
4925
4926 // If snippet state is not at the last tabstop, push it back on the stack
4927 if snippet.active_index + 1 < snippet.ranges.len() {
4928 self.snippet_stack.push(snippet);
4929 }
4930 return true;
4931 }
4932 }
4933
4934 false
4935 }
4936
4937 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4938 self.transact(window, cx, |this, window, cx| {
4939 this.select_all(&SelectAll, window, cx);
4940 this.insert("", window, cx);
4941 });
4942 }
4943
4944 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
4945 if self.read_only(cx) {
4946 return;
4947 }
4948 self.transact(window, cx, |this, window, cx| {
4949 this.select_autoclose_pair(window, cx);
4950
4951 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
4952
4953 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4954 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
4955 for selection in &mut selections {
4956 if selection.is_empty() {
4957 let old_head = selection.head();
4958 let mut new_head =
4959 movement::left(&display_map, old_head.to_display_point(&display_map))
4960 .to_point(&display_map);
4961 if let Some((buffer, line_buffer_range)) = display_map
4962 .buffer_snapshot()
4963 .buffer_line_for_row(MultiBufferRow(old_head.row))
4964 {
4965 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
4966 let indent_len = match indent_size.kind {
4967 IndentKind::Space => {
4968 buffer.settings_at(line_buffer_range.start, cx).tab_size
4969 }
4970 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
4971 };
4972 if old_head.column <= indent_size.len && old_head.column > 0 {
4973 let indent_len = indent_len.get();
4974 new_head = cmp::min(
4975 new_head,
4976 MultiBufferPoint::new(
4977 old_head.row,
4978 ((old_head.column - 1) / indent_len) * indent_len,
4979 ),
4980 );
4981 }
4982 }
4983
4984 selection.set_head(new_head, SelectionGoal::None);
4985 }
4986 }
4987
4988 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
4989 this.insert("", window, cx);
4990 linked_edits.apply_with_left_expansion(cx);
4991 this.refresh_edit_prediction(
4992 true,
4993 false,
4994 EditPredictionRequestTrigger::BufferEdit,
4995 window,
4996 cx,
4997 );
4998 refresh_linked_ranges(this, window, cx);
4999 });
5000 }
5001
5002 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
5003 if self.read_only(cx) {
5004 return;
5005 }
5006 self.transact(window, cx, |this, window, cx| {
5007 this.change_selections(Default::default(), window, cx, |s| {
5008 s.move_with(&mut |map, selection| {
5009 if selection.is_empty() {
5010 let cursor = movement::right(map, selection.head());
5011 selection.end = cursor;
5012 selection.reversed = true;
5013 selection.goal = SelectionGoal::None;
5014 }
5015 })
5016 });
5017 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
5018 this.insert("", window, cx);
5019 linked_edits.apply(cx);
5020 this.refresh_edit_prediction(
5021 true,
5022 false,
5023 EditPredictionRequestTrigger::BufferEdit,
5024 window,
5025 cx,
5026 );
5027 refresh_linked_ranges(this, window, cx);
5028 });
5029 }
5030
5031 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
5032 if self.mode.is_single_line() {
5033 cx.propagate();
5034 return;
5035 }
5036
5037 if self.move_to_prev_snippet_tabstop(window, cx) {
5038 return;
5039 }
5040 self.outdent(&Outdent, window, cx);
5041 }
5042
5043 pub fn next_snippet_tabstop(
5044 &mut self,
5045 _: &NextSnippetTabstop,
5046 window: &mut Window,
5047 cx: &mut Context<Self>,
5048 ) {
5049 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
5050 cx.propagate();
5051 return;
5052 }
5053
5054 if self.move_to_next_snippet_tabstop(window, cx) {
5055 return;
5056 }
5057 cx.propagate();
5058 }
5059
5060 pub fn previous_snippet_tabstop(
5061 &mut self,
5062 _: &PreviousSnippetTabstop,
5063 window: &mut Window,
5064 cx: &mut Context<Self>,
5065 ) {
5066 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
5067 cx.propagate();
5068 return;
5069 }
5070
5071 if self.move_to_prev_snippet_tabstop(window, cx) {
5072 return;
5073 }
5074 cx.propagate();
5075 }
5076
5077 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
5078 if self.mode.is_single_line() {
5079 cx.propagate();
5080 return;
5081 }
5082
5083 if self.move_to_next_snippet_tabstop(window, cx) {
5084 return;
5085 }
5086 if self.read_only(cx) {
5087 return;
5088 }
5089 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
5090 let buffer = self.buffer.read(cx);
5091 let snapshot = buffer.snapshot(cx);
5092 let rows_iter = selections.iter().map(|s| s.head().row);
5093 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
5094
5095 let has_some_cursor_in_whitespace = selections
5096 .iter()
5097 .filter(|selection| selection.is_empty())
5098 .any(|selection| {
5099 let cursor = selection.head();
5100 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
5101 cursor.column < current_indent.len
5102 });
5103
5104 let mut edits = Vec::new();
5105 let mut prev_edited_row = 0;
5106 let mut row_delta = 0;
5107 for selection in &mut selections {
5108 if selection.start.row != prev_edited_row {
5109 row_delta = 0;
5110 }
5111 prev_edited_row = selection.end.row;
5112
5113 // If cursor is after a list prefix, make selection non-empty to trigger line indent
5114 if selection.is_empty() {
5115 let cursor = selection.head();
5116 let settings = buffer.language_settings_at(cursor, cx);
5117 if settings.indent_list_on_tab {
5118 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
5119 if input::is_list_prefix_row(
5120 MultiBufferRow(cursor.row),
5121 &snapshot,
5122 &language,
5123 ) {
5124 row_delta = Self::indent_selection(
5125 buffer, &snapshot, selection, &mut edits, row_delta, cx,
5126 );
5127 continue;
5128 }
5129 }
5130 }
5131 }
5132
5133 // If the selection is non-empty, then increase the indentation of the selected lines.
5134 if !selection.is_empty() {
5135 row_delta =
5136 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
5137 continue;
5138 }
5139
5140 let cursor = selection.head();
5141 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
5142 if let Some(suggested_indent) =
5143 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
5144 {
5145 // Don't do anything if already at suggested indent
5146 // and there is any other cursor which is not
5147 if has_some_cursor_in_whitespace
5148 && cursor.column == current_indent.len
5149 && current_indent.len == suggested_indent.len
5150 {
5151 continue;
5152 }
5153
5154 // Adjust line and move cursor to suggested indent
5155 // if cursor is not at suggested indent
5156 if cursor.column < suggested_indent.len
5157 && cursor.column <= current_indent.len
5158 && current_indent.len <= suggested_indent.len
5159 {
5160 selection.start = Point::new(cursor.row, suggested_indent.len);
5161 selection.end = selection.start;
5162 if row_delta == 0 {
5163 edits.extend(Buffer::edit_for_indent_size_adjustment(
5164 cursor.row,
5165 current_indent,
5166 suggested_indent,
5167 ));
5168 row_delta = suggested_indent.len - current_indent.len;
5169 }
5170 continue;
5171 }
5172
5173 // If current indent is more than suggested indent
5174 // only move cursor to current indent and skip indent
5175 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
5176 selection.start = Point::new(cursor.row, current_indent.len);
5177 selection.end = selection.start;
5178 continue;
5179 }
5180 }
5181
5182 // Otherwise, insert a hard or soft tab.
5183 let settings = buffer.language_settings_at(cursor, cx);
5184 let tab_size = if settings.hard_tabs {
5185 IndentSize::tab()
5186 } else {
5187 let tab_size = settings.tab_size.get();
5188 let indent_remainder = snapshot
5189 .text_for_range(Point::new(cursor.row, 0)..cursor)
5190 .flat_map(str::chars)
5191 .fold(row_delta % tab_size, |counter: u32, c| {
5192 if c == '\t' {
5193 0
5194 } else {
5195 (counter + 1) % tab_size
5196 }
5197 });
5198
5199 let chars_to_next_tab_stop = tab_size - indent_remainder;
5200 IndentSize::spaces(chars_to_next_tab_stop)
5201 };
5202 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
5203 selection.end = selection.start;
5204 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
5205 row_delta += tab_size.len;
5206 }
5207
5208 self.transact(window, cx, |this, window, cx| {
5209 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
5210 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
5211 this.refresh_edit_prediction(
5212 true,
5213 false,
5214 EditPredictionRequestTrigger::BufferEdit,
5215 window,
5216 cx,
5217 );
5218 });
5219 }
5220
5221 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
5222 if self.read_only(cx) {
5223 return;
5224 }
5225 if self.mode.is_single_line() {
5226 cx.propagate();
5227 return;
5228 }
5229
5230 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
5231 let mut prev_edited_row = 0;
5232 let mut row_delta = 0;
5233 let mut edits = Vec::new();
5234 let buffer = self.buffer.read(cx);
5235 let snapshot = buffer.snapshot(cx);
5236 for selection in &mut selections {
5237 if selection.start.row != prev_edited_row {
5238 row_delta = 0;
5239 }
5240 prev_edited_row = selection.end.row;
5241
5242 row_delta =
5243 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
5244 }
5245
5246 self.transact(window, cx, |this, window, cx| {
5247 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
5248 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
5249 });
5250 }
5251
5252 fn indent_selection(
5253 buffer: &MultiBuffer,
5254 snapshot: &MultiBufferSnapshot,
5255 selection: &mut Selection<Point>,
5256 edits: &mut Vec<(Range<Point>, String)>,
5257 delta_for_start_row: u32,
5258 cx: &App,
5259 ) -> u32 {
5260 let settings = buffer.language_settings_at(selection.start, cx);
5261 let tab_size = settings.tab_size.get();
5262 let indent_kind = if settings.hard_tabs {
5263 IndentKind::Tab
5264 } else {
5265 IndentKind::Space
5266 };
5267 let mut start_row = selection.start.row;
5268 let mut end_row = selection.end.row + 1;
5269
5270 // If a selection ends at the beginning of a line, don't indent
5271 // that last line.
5272 if selection.end.column == 0 && selection.end.row > selection.start.row {
5273 end_row -= 1;
5274 }
5275
5276 // Avoid re-indenting a row that has already been indented by a
5277 // previous selection, but still update this selection's column
5278 // to reflect that indentation.
5279 if delta_for_start_row > 0 {
5280 start_row += 1;
5281 selection.start.column += delta_for_start_row;
5282 if selection.end.row == selection.start.row {
5283 selection.end.column += delta_for_start_row;
5284 }
5285 }
5286
5287 let mut delta_for_end_row = 0;
5288 let has_multiple_rows = start_row + 1 != end_row;
5289 for row in start_row..end_row {
5290 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
5291 let indent_delta = match (current_indent.kind, indent_kind) {
5292 (IndentKind::Space, IndentKind::Space) => {
5293 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
5294 IndentSize::spaces(columns_to_next_tab_stop)
5295 }
5296 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
5297 (_, IndentKind::Tab) => IndentSize::tab(),
5298 };
5299
5300 let start = if has_multiple_rows || current_indent.len < selection.start.column {
5301 0
5302 } else {
5303 selection.start.column
5304 };
5305 let row_start = Point::new(row, start);
5306 edits.push((
5307 row_start..row_start,
5308 indent_delta.chars().collect::<String>(),
5309 ));
5310
5311 // Update this selection's endpoints to reflect the indentation.
5312 if row == selection.start.row {
5313 selection.start.column += indent_delta.len;
5314 }
5315 if row == selection.end.row {
5316 selection.end.column += indent_delta.len;
5317 delta_for_end_row = indent_delta.len;
5318 }
5319 }
5320
5321 if selection.start.row == selection.end.row {
5322 delta_for_start_row + delta_for_end_row
5323 } else {
5324 delta_for_end_row
5325 }
5326 }
5327
5328 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
5329 if self.read_only(cx) {
5330 return;
5331 }
5332 if self.mode.is_single_line() {
5333 cx.propagate();
5334 return;
5335 }
5336
5337 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5338 let selections = self.selections.all::<Point>(&display_map);
5339 let mut deletion_ranges = Vec::new();
5340 let mut last_outdent = None;
5341 {
5342 let buffer = self.buffer.read(cx);
5343 let snapshot = buffer.snapshot(cx);
5344 for selection in &selections {
5345 let settings = buffer.language_settings_at(selection.start, cx);
5346 let tab_size = settings.tab_size;
5347 let mut rows = selection.spanned_rows(false, &display_map);
5348
5349 // Avoid re-outdenting a row that has already been outdented by a
5350 // previous selection.
5351 if let Some(last_row) = last_outdent
5352 && last_row == rows.start
5353 {
5354 rows.start = rows.start.next_row();
5355 }
5356 let has_multiple_rows = rows.len() > 1;
5357 for row in rows.iter_rows() {
5358 let indent_size = snapshot.indent_size_for_line(row);
5359 if indent_size.len > 0 {
5360 let deletion_len = indent_size.outdent_len(tab_size);
5361 let start = if has_multiple_rows
5362 || deletion_len > selection.start.column
5363 || indent_size.len < selection.start.column
5364 {
5365 0
5366 } else {
5367 selection.start.column - deletion_len
5368 };
5369 deletion_ranges.push(
5370 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
5371 );
5372 last_outdent = Some(row);
5373 }
5374 }
5375 }
5376 }
5377
5378 self.transact(window, cx, |this, window, cx| {
5379 this.buffer.update(cx, |buffer, cx| {
5380 let empty_str: Arc<str> = Arc::default();
5381 buffer.edit(
5382 deletion_ranges
5383 .into_iter()
5384 .map(|range| (range, empty_str.clone())),
5385 None,
5386 cx,
5387 );
5388 });
5389 let selections = this
5390 .selections
5391 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
5392 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
5393 });
5394 }
5395
5396 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
5397 if self.read_only(cx) {
5398 return;
5399 }
5400 if self.mode.is_single_line() {
5401 cx.propagate();
5402 return;
5403 }
5404
5405 let selections = self
5406 .selections
5407 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
5408 .into_iter()
5409 .map(|s| s.range());
5410
5411 self.transact(window, cx, |this, window, cx| {
5412 this.buffer.update(cx, |buffer, cx| {
5413 buffer.autoindent_ranges(selections, cx);
5414 });
5415 let selections = this
5416 .selections
5417 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
5418 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
5419 });
5420 }
5421
5422 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
5423 if self.read_only(cx) {
5424 return;
5425 }
5426 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5427 let selections = self.selections.all::<Point>(&display_map);
5428
5429 let mut new_cursors = Vec::new();
5430 let mut edit_ranges = Vec::new();
5431 let mut selections = selections.iter().peekable();
5432 while let Some(selection) = selections.next() {
5433 let mut rows = selection.spanned_rows(false, &display_map);
5434
5435 // Accumulate contiguous regions of rows that we want to delete.
5436 while let Some(next_selection) = selections.peek() {
5437 let next_rows = next_selection.spanned_rows(false, &display_map);
5438 if next_rows.start <= rows.end {
5439 rows.end = next_rows.end;
5440 selections.next().unwrap();
5441 } else {
5442 break;
5443 }
5444 }
5445
5446 let buffer = display_map.buffer_snapshot();
5447 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
5448 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
5449 // If there's a line after the range, delete the \n from the end of the row range
5450 (
5451 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
5452 rows.end,
5453 )
5454 } else {
5455 // If there isn't a line after the range, delete the \n from the line before the
5456 // start of the row range
5457 edit_start = edit_start.saturating_sub_usize(1);
5458 (buffer.len(), rows.start.previous_row())
5459 };
5460
5461 let text_layout_details = self.text_layout_details(window, cx);
5462 let x = display_map.x_for_display_point(
5463 selection.head().to_display_point(&display_map),
5464 &text_layout_details,
5465 );
5466 let row = Point::new(target_row.0, 0)
5467 .to_display_point(&display_map)
5468 .row();
5469 let column = display_map.display_column_for_x(row, x, &text_layout_details);
5470
5471 new_cursors.push((
5472 selection.id,
5473 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
5474 SelectionGoal::None,
5475 ));
5476 edit_ranges.push(edit_start..edit_end);
5477 }
5478
5479 self.transact(window, cx, |this, window, cx| {
5480 let buffer = this.buffer.update(cx, |buffer, cx| {
5481 let empty_str: Arc<str> = Arc::default();
5482 buffer.edit(
5483 edit_ranges
5484 .into_iter()
5485 .map(|range| (range, empty_str.clone())),
5486 None,
5487 cx,
5488 );
5489 buffer.snapshot(cx)
5490 });
5491 let new_selections = new_cursors
5492 .into_iter()
5493 .map(|(id, cursor, goal)| {
5494 let cursor = cursor.to_point(&buffer);
5495 Selection {
5496 id,
5497 start: cursor,
5498 end: cursor,
5499 reversed: false,
5500 goal,
5501 }
5502 })
5503 .collect();
5504
5505 this.change_selections(Default::default(), window, cx, |s| {
5506 s.select(new_selections);
5507 });
5508 });
5509 }
5510
5511 pub fn join_lines_impl(
5512 &mut self,
5513 insert_whitespace: bool,
5514 window: &mut Window,
5515 cx: &mut Context<Self>,
5516 ) {
5517 if self.read_only(cx) {
5518 return;
5519 }
5520 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
5521 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
5522 let start = MultiBufferRow(selection.start.row);
5523 // Treat single line selections as if they include the next line. Otherwise this action
5524 // would do nothing for single line selections individual cursors.
5525 let end = if selection.start.row == selection.end.row {
5526 MultiBufferRow(selection.start.row + 1)
5527 } else if selection.end.column == 0 {
5528 // If the selection ends at the start of a line, it's logically at the end of the
5529 // previous line (plus its newline).
5530 // Don't include the end line unless there's only one line selected.
5531 if selection.start.row + 1 == selection.end.row {
5532 MultiBufferRow(selection.end.row)
5533 } else {
5534 MultiBufferRow(selection.end.row - 1)
5535 }
5536 } else {
5537 MultiBufferRow(selection.end.row)
5538 };
5539
5540 if let Some(last_row_range) = row_ranges.last_mut()
5541 && start <= last_row_range.end
5542 {
5543 last_row_range.end = end;
5544 continue;
5545 }
5546 row_ranges.push(start..end);
5547 }
5548
5549 let snapshot = self.buffer.read(cx).snapshot(cx);
5550 let mut cursor_positions = Vec::new();
5551 for row_range in &row_ranges {
5552 let anchor = snapshot.anchor_before(Point::new(
5553 row_range.end.previous_row().0,
5554 snapshot.line_len(row_range.end.previous_row()),
5555 ));
5556 cursor_positions.push(anchor..anchor);
5557 }
5558
5559 self.transact(window, cx, |this, window, cx| {
5560 for row_range in row_ranges.into_iter().rev() {
5561 for row in row_range.iter_rows().rev() {
5562 let end_of_line = Point::new(row.0, snapshot.line_len(row));
5563 let next_line_row = row.next_row();
5564 let indent = snapshot.indent_size_for_line(next_line_row);
5565 let mut join_start_column = indent.len;
5566
5567 if let Some(language_scope) =
5568 snapshot.language_scope_at(Point::new(next_line_row.0, indent.len))
5569 {
5570 let line_end =
5571 Point::new(next_line_row.0, snapshot.line_len(next_line_row));
5572 let line_text_after_indent = snapshot
5573 .text_for_range(Point::new(next_line_row.0, indent.len)..line_end)
5574 .collect::<String>();
5575
5576 if !line_text_after_indent.is_empty() {
5577 let block_prefix = language_scope
5578 .block_comment()
5579 .map(|c| c.prefix.as_ref())
5580 .filter(|p| !p.is_empty());
5581 let doc_prefix = language_scope
5582 .documentation_comment()
5583 .map(|c| c.prefix.as_ref())
5584 .filter(|p| !p.is_empty());
5585 let comment_prefixes = language_scope
5586 .line_comment_prefixes()
5587 .iter()
5588 .map(|p| p.as_ref())
5589 .chain(block_prefix)
5590 .chain(doc_prefix)
5591 .map(|prefix| (prefix, false));
5592 let all_prefixes = comment_prefixes.chain(
5593 language_scope
5594 .unordered_list()
5595 .iter()
5596 .map(|prefix| (prefix.as_ref(), true)),
5597 );
5598
5599 let mut longest_prefix_len = None;
5600 for (prefix, is_unordered_list) in all_prefixes {
5601 let trimmed = prefix.trim_end();
5602 let matches_full_prefix =
5603 line_text_after_indent.starts_with(prefix);
5604 let nextline_is_bare_prefix = line_text_after_indent == trimmed;
5605 if matches_full_prefix
5606 || (!is_unordered_list
5607 && line_text_after_indent.starts_with(trimmed))
5608 || nextline_is_bare_prefix
5609 {
5610 let candidate_len = if matches_full_prefix {
5611 prefix.len()
5612 } else {
5613 trimmed.len()
5614 };
5615 if longest_prefix_len.map_or(true, |len| candidate_len > len) {
5616 longest_prefix_len = Some(candidate_len);
5617 }
5618 }
5619 }
5620
5621 if let Some(prefix_len) = longest_prefix_len {
5622 join_start_column =
5623 join_start_column.saturating_add(prefix_len as u32);
5624 }
5625 }
5626 }
5627
5628 let start_of_next_line = Point::new(next_line_row.0, join_start_column);
5629
5630 let replace = if snapshot.line_len(next_line_row) > join_start_column
5631 && insert_whitespace
5632 {
5633 " "
5634 } else {
5635 ""
5636 };
5637
5638 this.buffer.update(cx, |buffer, cx| {
5639 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
5640 });
5641 }
5642 }
5643
5644 this.change_selections(Default::default(), window, cx, |s| {
5645 s.select_anchor_ranges(cursor_positions)
5646 });
5647 });
5648 }
5649
5650 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
5651 self.join_lines_impl(true, window, cx);
5652 }
5653
5654 pub fn sort_lines_case_sensitive(
5655 &mut self,
5656 _: &SortLinesCaseSensitive,
5657 window: &mut Window,
5658 cx: &mut Context<Self>,
5659 ) {
5660 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
5661 }
5662
5663 pub fn sort_lines_by_length(
5664 &mut self,
5665 _: &SortLinesByLength,
5666 window: &mut Window,
5667 cx: &mut Context<Self>,
5668 ) {
5669 self.manipulate_immutable_lines(window, cx, |lines| {
5670 lines.sort_by_key(|&line| line.chars().count())
5671 })
5672 }
5673
5674 pub fn sort_lines_case_insensitive(
5675 &mut self,
5676 _: &SortLinesCaseInsensitive,
5677 window: &mut Window,
5678 cx: &mut Context<Self>,
5679 ) {
5680 self.manipulate_immutable_lines(window, cx, |lines| {
5681 lines.sort_by_key(|line| line.to_lowercase())
5682 })
5683 }
5684
5685 pub fn unique_lines_case_insensitive(
5686 &mut self,
5687 _: &UniqueLinesCaseInsensitive,
5688 window: &mut Window,
5689 cx: &mut Context<Self>,
5690 ) {
5691 self.manipulate_immutable_lines(window, cx, |lines| {
5692 let mut seen = HashSet::default();
5693 lines.retain(|line| seen.insert(line.to_lowercase()));
5694 })
5695 }
5696
5697 pub fn unique_lines_case_sensitive(
5698 &mut self,
5699 _: &UniqueLinesCaseSensitive,
5700 window: &mut Window,
5701 cx: &mut Context<Self>,
5702 ) {
5703 self.manipulate_immutable_lines(window, cx, |lines| {
5704 let mut seen = HashSet::default();
5705 lines.retain(|line| seen.insert(*line));
5706 })
5707 }
5708
5709 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
5710 let snapshot = self.buffer.read(cx).snapshot(cx);
5711 for selection in self.selections.disjoint_anchors_arc().iter() {
5712 if snapshot
5713 .language_at(selection.start)
5714 .and_then(|lang| lang.config().wrap_characters.as_ref())
5715 .is_some()
5716 {
5717 return true;
5718 }
5719 }
5720 false
5721 }
5722
5723 fn wrap_selections_in_tag(
5724 &mut self,
5725 _: &WrapSelectionsInTag,
5726 window: &mut Window,
5727 cx: &mut Context<Self>,
5728 ) {
5729 if self.read_only(cx) {
5730 return;
5731 }
5732
5733 let snapshot = self.buffer.read(cx).snapshot(cx);
5734
5735 let mut edits = Vec::new();
5736 let mut boundaries = Vec::new();
5737
5738 for selection in self
5739 .selections
5740 .all_adjusted(&self.display_snapshot(cx))
5741 .iter()
5742 {
5743 let Some(wrap_config) = snapshot
5744 .language_at(selection.start)
5745 .and_then(|lang| lang.config().wrap_characters.clone())
5746 else {
5747 continue;
5748 };
5749
5750 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
5751 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
5752
5753 let start_before = snapshot.anchor_before(selection.start);
5754 let end_after = snapshot.anchor_after(selection.end);
5755
5756 edits.push((start_before..start_before, open_tag));
5757 edits.push((end_after..end_after, close_tag));
5758
5759 boundaries.push((
5760 start_before,
5761 end_after,
5762 wrap_config.start_prefix.len(),
5763 wrap_config.end_suffix.len(),
5764 ));
5765 }
5766
5767 if edits.is_empty() {
5768 return;
5769 }
5770
5771 self.transact(window, cx, |this, window, cx| {
5772 let buffer = this.buffer.update(cx, |buffer, cx| {
5773 buffer.edit(edits, None, cx);
5774 buffer.snapshot(cx)
5775 });
5776
5777 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
5778 for (start_before, end_after, start_prefix_len, end_suffix_len) in
5779 boundaries.into_iter()
5780 {
5781 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
5782 let close_offset = end_after
5783 .to_offset(&buffer)
5784 .saturating_sub_usize(end_suffix_len);
5785 new_selections.push(open_offset..open_offset);
5786 new_selections.push(close_offset..close_offset);
5787 }
5788
5789 this.change_selections(Default::default(), window, cx, |s| {
5790 s.select_ranges(new_selections);
5791 });
5792
5793 this.request_autoscroll(Autoscroll::fit(), cx);
5794 });
5795 }
5796
5797 pub fn toggle_read_only(
5798 &mut self,
5799 _: &workspace::ToggleReadOnlyFile,
5800 _: &mut Window,
5801 cx: &mut Context<Self>,
5802 ) {
5803 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
5804 buffer.update(cx, |buffer, cx| {
5805 buffer.set_capability(
5806 match buffer.capability() {
5807 Capability::ReadWrite => Capability::Read,
5808 Capability::Read => Capability::ReadWrite,
5809 Capability::ReadOnly => Capability::ReadOnly,
5810 },
5811 cx,
5812 );
5813 })
5814 }
5815 }
5816
5817 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
5818 let Some(project) = self.project.clone() else {
5819 return;
5820 };
5821 let task = self.reload(project, window, cx);
5822 self.detach_and_notify_err(task, window, cx);
5823 }
5824
5825 pub fn open_active_item_in_terminal(
5826 &mut self,
5827 _: &OpenInTerminal,
5828 window: &mut Window,
5829 cx: &mut Context<Self>,
5830 ) {
5831 if let Some(working_directory) = self.active_buffer(cx).and_then(|buffer| {
5832 let project_path = buffer.read(cx).project_path(cx)?;
5833 let project = self.project()?.read(cx);
5834 let entry = project.entry_for_path(&project_path, cx)?;
5835 let parent = match &entry.canonical_path {
5836 Some(canonical_path) => canonical_path.to_path_buf(),
5837 None => project.absolute_path(&project_path, cx)?,
5838 }
5839 .parent()?
5840 .to_path_buf();
5841 Some(parent)
5842 }) {
5843 window.dispatch_action(
5844 OpenTerminal {
5845 working_directory,
5846 local: false,
5847 }
5848 .boxed_clone(),
5849 cx,
5850 );
5851 }
5852 }
5853
5854 fn set_gutter_context_menu(
5855 &mut self,
5856 display_row: DisplayRow,
5857 position: Option<Anchor>,
5858 clicked_point: gpui::Point<Pixels>,
5859 window: &mut Window,
5860 cx: &mut Context<Self>,
5861 ) {
5862 let display_snapshot = self.display_snapshot(cx);
5863 let display_point = display_row.as_display_point();
5864 let source = display_snapshot.display_point_to_anchor(display_point, Bias::Left);
5865 let anchor = position.unwrap_or(source);
5866
5867 // Every entry in this menu either requires a worktree-file-backed buffer
5868 // (breakpoints, bookmarks, run to cursor) or is meaningless without one
5869 // (git blame), so don't open it for e.g. untitled buffers.
5870 if !display_snapshot
5871 .buffer_snapshot()
5872 .anchor_to_buffer_anchor(anchor)
5873 .is_some_and(|(_, buffer_snapshot)| {
5874 project::File::from_dyn(buffer_snapshot.file()).is_some()
5875 })
5876 {
5877 return;
5878 }
5879
5880 let context_menu = self.gutter_context_menu(anchor, display_row, window, cx);
5881
5882 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
5883 self,
5884 source,
5885 clicked_point,
5886 context_menu,
5887 window,
5888 cx,
5889 );
5890 }
5891
5892 fn add_edit_block(
5893 &mut self,
5894 anchor: Anchor,
5895 base_text: &str,
5896 placeholder_text: &str,
5897 confirm: Option<PromptEditorCallback>,
5898 cancel: Option<PromptEditorCallback>,
5899 window: &mut Window,
5900 cx: &mut Context<Self>,
5901 ) {
5902 let weak_editor = cx.weak_entity();
5903 let bp_prompt = cx.new(|cx| {
5904 let mut prompt_editor =
5905 PromptEditor::new(weak_editor, placeholder_text, base_text, window, cx);
5906
5907 if let Some(callback) = confirm {
5908 prompt_editor = prompt_editor.on_confirm(callback);
5909 }
5910 if let Some(callback) = cancel {
5911 prompt_editor = prompt_editor.on_cancel(callback);
5912 }
5913
5914 prompt_editor
5915 });
5916
5917 let height = bp_prompt.update(cx, |this, cx| {
5918 this.prompt
5919 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
5920 });
5921 let cloned_prompt = bp_prompt.clone();
5922 let blocks = vec![BlockProperties {
5923 style: BlockStyle::Sticky,
5924 placement: BlockPlacement::Above(anchor),
5925 height: Some(height),
5926 render: Arc::new(move |cx| {
5927 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
5928 cloned_prompt.clone().into_any_element()
5929 }),
5930 priority: 0,
5931 }];
5932
5933 let focus_handle = bp_prompt.focus_handle(cx);
5934 window.focus(&focus_handle, cx);
5935
5936 let block_ids = self.insert_blocks(blocks, None, cx);
5937 bp_prompt.update(cx, |prompt, _| {
5938 prompt.add_block_ids(block_ids);
5939 });
5940 }
5941
5942 fn add_edit_breakpoint_block(
5943 &mut self,
5944 anchor: Anchor,
5945 breakpoint: &Breakpoint,
5946 edit_action: BreakpointPromptEditAction,
5947 window: &mut Window,
5948 cx: &mut Context<Self>,
5949 ) {
5950 let base_text: &str = match edit_action {
5951 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
5952 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
5953 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
5954 }
5955 .map(|msg| msg.as_ref())
5956 .unwrap_or_default();
5957
5958 let placeholder_text = match edit_action {
5959 BreakpointPromptEditAction::Log => {
5960 "Message to log when a breakpoint is hit. Expressions within {} are interpolated."
5961 }
5962 BreakpointPromptEditAction::Condition => {
5963 "Condition when a breakpoint is hit. Expressions within {} are interpolated."
5964 }
5965 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
5966 };
5967
5968 let breakpoint = breakpoint.clone();
5969 self.add_edit_block(
5970 anchor,
5971 base_text,
5972 placeholder_text,
5973 Some(Box::new(move |message: String, editor: &mut Self, cx| {
5974 editor.edit_breakpoint_at_anchor(
5975 anchor,
5976 breakpoint,
5977 match edit_action {
5978 BreakpointPromptEditAction::Log => {
5979 BreakpointEditAction::EditLogMessage(message.into())
5980 }
5981 BreakpointPromptEditAction::Condition => {
5982 BreakpointEditAction::EditCondition(message.into())
5983 }
5984 BreakpointPromptEditAction::HitCondition => {
5985 BreakpointEditAction::EditHitCondition(message.into())
5986 }
5987 },
5988 cx,
5989 );
5990 })),
5991 None,
5992 window,
5993 cx,
5994 );
5995 }
5996
5997 pub(crate) fn breakpoint_at_row(
5998 &self,
5999 row: u32,
6000 window: &mut Window,
6001 cx: &mut Context<Self>,
6002 ) -> Option<(Anchor, Breakpoint)> {
6003 let snapshot = self.snapshot(window, cx);
6004 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
6005
6006 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
6007 }
6008
6009 pub(crate) fn breakpoint_at_anchor(
6010 &self,
6011 breakpoint_position: Anchor,
6012 snapshot: &EditorSnapshot,
6013 cx: &mut Context<Self>,
6014 ) -> Option<(Anchor, Breakpoint)> {
6015 let (breakpoint_position, _) = snapshot
6016 .buffer_snapshot()
6017 .anchor_to_buffer_anchor(breakpoint_position)?;
6018 let buffer = self.buffer.read(cx).buffer(breakpoint_position.buffer_id)?;
6019
6020 let buffer_snapshot = buffer.read(cx).snapshot();
6021
6022 let row = buffer_snapshot
6023 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position)
6024 .row;
6025
6026 let line_len = buffer_snapshot.line_len(row);
6027 let anchor_end = buffer_snapshot.anchor_after(Point::new(row, line_len));
6028
6029 self.breakpoint_store
6030 .as_ref()?
6031 .read_with(cx, |breakpoint_store, cx| {
6032 breakpoint_store
6033 .breakpoints(
6034 &buffer,
6035 Some(breakpoint_position..anchor_end),
6036 &buffer_snapshot,
6037 cx,
6038 )
6039 .next()
6040 .and_then(|(bp, _)| {
6041 let breakpoint_row = buffer_snapshot
6042 .summary_for_anchor::<text::PointUtf16>(&bp.position)
6043 .row;
6044
6045 if breakpoint_row == row {
6046 snapshot
6047 .buffer_snapshot()
6048 .anchor_in_excerpt(bp.position)
6049 .map(|position| (position, bp.bp.clone()))
6050 } else {
6051 None
6052 }
6053 })
6054 })
6055 }
6056
6057 pub(crate) fn bookmark_at_row(
6058 &self,
6059 row: u32,
6060 window: &mut Window,
6061 cx: &mut Context<Self>,
6062 ) -> Option<Anchor> {
6063 let snapshot = self.snapshot(window, cx);
6064 let bookmark_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
6065
6066 self.bookmark_at_anchor(bookmark_position, &snapshot, cx)
6067 }
6068
6069 pub(crate) fn bookmark_at_anchor(
6070 &self,
6071 bookmark_position: Anchor,
6072 snapshot: &EditorSnapshot,
6073 cx: &mut Context<Self>,
6074 ) -> Option<Anchor> {
6075 let (bookmark_position, _) = snapshot
6076 .buffer_snapshot()
6077 .anchor_to_buffer_anchor(bookmark_position)?;
6078 let buffer = self.buffer.read(cx).buffer(bookmark_position.buffer_id)?;
6079
6080 let buffer_snapshot = buffer.read(cx).snapshot();
6081
6082 let row = buffer_snapshot
6083 .summary_for_anchor::<text::PointUtf16>(&bookmark_position)
6084 .row;
6085
6086 let line_len = buffer_snapshot.line_len(row);
6087 let anchor_end = buffer_snapshot.anchor_after(Point::new(row, line_len));
6088
6089 self.bookmark_store
6090 .as_ref()?
6091 .update(cx, |bookmark_store, cx| {
6092 bookmark_store
6093 .bookmarks_for_buffer(
6094 buffer,
6095 bookmark_position..anchor_end,
6096 &buffer_snapshot,
6097 cx,
6098 )
6099 .first()
6100 .and_then(|bookmark| {
6101 let bookmark_row = buffer_snapshot
6102 .summary_for_anchor::<text::PointUtf16>(&bookmark.anchor)
6103 .row;
6104
6105 if bookmark_row == row {
6106 snapshot
6107 .buffer_snapshot()
6108 .anchor_in_excerpt(bookmark.anchor)
6109 } else {
6110 None
6111 }
6112 })
6113 })
6114 }
6115
6116 pub fn edit_log_breakpoint(
6117 &mut self,
6118 _: &EditLogBreakpoint,
6119 window: &mut Window,
6120 cx: &mut Context<Self>,
6121 ) {
6122 if self.breakpoint_store.is_none() {
6123 return;
6124 }
6125
6126 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
6127 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
6128 message: None,
6129 state: BreakpointState::Enabled,
6130 condition: None,
6131 hit_condition: None,
6132 });
6133
6134 self.add_edit_breakpoint_block(
6135 anchor,
6136 &breakpoint,
6137 BreakpointPromptEditAction::Log,
6138 window,
6139 cx,
6140 );
6141 }
6142 }
6143
6144 fn breakpoints_at_cursors(
6145 &self,
6146 window: &mut Window,
6147 cx: &mut Context<Self>,
6148 ) -> Vec<(Anchor, Option<Breakpoint>)> {
6149 let snapshot = self.snapshot(window, cx);
6150 let cursors = self
6151 .selections
6152 .disjoint_anchors_arc()
6153 .iter()
6154 .map(|selection| {
6155 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
6156
6157 let breakpoint_position = self
6158 .breakpoint_at_row(cursor_position.row, window, cx)
6159 .map(|bp| bp.0)
6160 .unwrap_or_else(|| {
6161 snapshot
6162 .display_snapshot
6163 .buffer_snapshot()
6164 .anchor_after(Point::new(cursor_position.row, 0))
6165 });
6166
6167 let breakpoint = self
6168 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
6169 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
6170
6171 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
6172 })
6173 // There might be multiple cursors on the same line; all of them should have the same anchors though as their breakpoints positions, which makes it possible to sort and dedup the list.
6174 .collect::<HashMap<Anchor, _>>();
6175
6176 cursors.into_iter().collect()
6177 }
6178
6179 pub fn enable_breakpoint(
6180 &mut self,
6181 _: &crate::actions::EnableBreakpoint,
6182 window: &mut Window,
6183 cx: &mut Context<Self>,
6184 ) {
6185 if self.breakpoint_store.is_none() {
6186 return;
6187 }
6188
6189 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
6190 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
6191 continue;
6192 };
6193 self.edit_breakpoint_at_anchor(
6194 anchor,
6195 breakpoint,
6196 BreakpointEditAction::InvertState,
6197 cx,
6198 );
6199 }
6200 }
6201
6202 pub fn align_selections(
6203 &mut self,
6204 _: &crate::actions::AlignSelections,
6205 window: &mut Window,
6206 cx: &mut Context<Self>,
6207 ) {
6208 if self.read_only(cx) {
6209 return;
6210 }
6211
6212 let display_snapshot = self.display_snapshot(cx);
6213
6214 struct CursorData {
6215 anchor: Anchor,
6216 point: Point,
6217 }
6218 let cursor_data: Vec<CursorData> = self
6219 .selections
6220 .disjoint_anchors()
6221 .iter()
6222 .map(|selection| {
6223 let anchor = if selection.reversed {
6224 selection.head()
6225 } else {
6226 selection.tail()
6227 };
6228 CursorData {
6229 anchor: anchor,
6230 point: anchor.to_point(&display_snapshot.buffer_snapshot()),
6231 }
6232 })
6233 .collect();
6234
6235 let rows_anchors_count: Vec<usize> = cursor_data
6236 .iter()
6237 .map(|cursor| cursor.point.row)
6238 .chunk_by(|&row| row)
6239 .into_iter()
6240 .map(|(_, group)| group.count())
6241 .collect();
6242 let max_columns = rows_anchors_count.iter().max().copied().unwrap_or(0);
6243 let mut rows_column_offset = vec![0; rows_anchors_count.len()];
6244 let mut edits = Vec::new();
6245
6246 for column_idx in 0..max_columns {
6247 let mut cursor_index = 0;
6248
6249 // Calculate target_column => position that the selections will go
6250 let mut target_column = 0;
6251 for (row_idx, cursor_count) in rows_anchors_count.iter().enumerate() {
6252 // Skip rows that don't have this column
6253 if column_idx >= *cursor_count {
6254 cursor_index += cursor_count;
6255 continue;
6256 }
6257
6258 let point = &cursor_data[cursor_index + column_idx].point;
6259 let adjusted_column = point.column + rows_column_offset[row_idx];
6260 if adjusted_column > target_column {
6261 target_column = adjusted_column;
6262 }
6263 cursor_index += cursor_count;
6264 }
6265
6266 // Collect edits for this column
6267 cursor_index = 0;
6268 for (row_idx, cursor_count) in rows_anchors_count.iter().enumerate() {
6269 // Skip rows that don't have this column
6270 if column_idx >= *cursor_count {
6271 cursor_index += *cursor_count;
6272 continue;
6273 }
6274
6275 let point = &cursor_data[cursor_index + column_idx].point;
6276 let spaces_needed = target_column - point.column - rows_column_offset[row_idx];
6277 if spaces_needed > 0 {
6278 let anchor = cursor_data[cursor_index + column_idx]
6279 .anchor
6280 .bias_left(&display_snapshot);
6281 edits.push((anchor..anchor, " ".repeat(spaces_needed as usize)));
6282 }
6283 rows_column_offset[row_idx] += spaces_needed;
6284
6285 cursor_index += *cursor_count;
6286 }
6287 }
6288
6289 if !edits.is_empty() {
6290 self.transact(window, cx, |editor, _window, cx| {
6291 editor.edit(edits, cx);
6292 });
6293 }
6294 }
6295
6296 pub fn disable_breakpoint(
6297 &mut self,
6298 _: &crate::actions::DisableBreakpoint,
6299 window: &mut Window,
6300 cx: &mut Context<Self>,
6301 ) {
6302 if self.breakpoint_store.is_none() {
6303 return;
6304 }
6305
6306 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
6307 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
6308 continue;
6309 };
6310 self.edit_breakpoint_at_anchor(
6311 anchor,
6312 breakpoint,
6313 BreakpointEditAction::InvertState,
6314 cx,
6315 );
6316 }
6317 }
6318
6319 pub fn toggle_breakpoint(
6320 &mut self,
6321 _: &crate::actions::ToggleBreakpoint,
6322 window: &mut Window,
6323 cx: &mut Context<Self>,
6324 ) {
6325 if self.breakpoint_store.is_none() {
6326 return;
6327 }
6328
6329 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
6330 if let Some(breakpoint) = breakpoint {
6331 self.edit_breakpoint_at_anchor(
6332 anchor,
6333 breakpoint,
6334 BreakpointEditAction::Toggle,
6335 cx,
6336 );
6337 } else {
6338 self.edit_breakpoint_at_anchor(
6339 anchor,
6340 Breakpoint::new_standard(),
6341 BreakpointEditAction::Toggle,
6342 cx,
6343 );
6344 }
6345 }
6346 }
6347
6348 pub fn edit_breakpoint_at_anchor(
6349 &mut self,
6350 breakpoint_position: Anchor,
6351 breakpoint: Breakpoint,
6352 edit_action: BreakpointEditAction,
6353 cx: &mut Context<Self>,
6354 ) {
6355 let Some(breakpoint_store) = &self.breakpoint_store else {
6356 return;
6357 };
6358 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
6359 let Some((position, _)) = buffer_snapshot.anchor_to_buffer_anchor(breakpoint_position)
6360 else {
6361 return;
6362 };
6363 let Some(buffer) = self.buffer.read(cx).buffer(position.buffer_id) else {
6364 return;
6365 };
6366
6367 breakpoint_store.update(cx, |breakpoint_store, cx| {
6368 breakpoint_store.toggle_breakpoint(
6369 buffer,
6370 BreakpointWithPosition {
6371 position,
6372 bp: breakpoint,
6373 },
6374 edit_action,
6375 cx,
6376 );
6377 });
6378
6379 cx.notify();
6380 }
6381
6382 #[cfg(any(test, feature = "test-support"))]
6383 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
6384 self.breakpoint_store.clone()
6385 }
6386
6387 fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
6388 maybe!({
6389 let breakpoint_store = self.breakpoint_store.as_ref()?;
6390
6391 let (active_stack_frame, debug_line_pane_id) = {
6392 let store = breakpoint_store.read(cx);
6393 let active_stack_frame = store.active_position().cloned();
6394 let debug_line_pane_id = store.active_debug_line_pane_id();
6395 (active_stack_frame, debug_line_pane_id)
6396 };
6397
6398 let Some(active_stack_frame) = active_stack_frame else {
6399 self.clear_row_highlights::<ActiveDebugLine>();
6400 return None;
6401 };
6402
6403 if let Some(debug_line_pane_id) = debug_line_pane_id {
6404 if let Some(workspace) = self
6405 .workspace
6406 .as_ref()
6407 .and_then(|(workspace, _)| workspace.upgrade())
6408 {
6409 let editor_pane_id = workspace
6410 .read(cx)
6411 .pane_for_item_id(cx.entity_id())
6412 .map(|pane| pane.entity_id());
6413
6414 if editor_pane_id.is_some_and(|id| id != debug_line_pane_id) {
6415 self.clear_row_highlights::<ActiveDebugLine>();
6416 return None;
6417 }
6418 }
6419 }
6420
6421 let position = active_stack_frame.position;
6422
6423 let snapshot = self.buffer.read(cx).snapshot(cx);
6424 let multibuffer_anchor = snapshot.anchor_in_excerpt(position)?;
6425
6426 self.clear_row_highlights::<ActiveDebugLine>();
6427
6428 self.go_to_line::<ActiveDebugLine>(
6429 multibuffer_anchor,
6430 |cx| cx.theme().colors().editor_debugger_active_line_background,
6431 window,
6432 cx,
6433 );
6434
6435 cx.notify();
6436
6437 Some(())
6438 })
6439 .is_some()
6440 }
6441
6442 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
6443 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
6444 }
6445
6446 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
6447 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
6448 }
6449
6450 pub fn rotate_selections_forward(
6451 &mut self,
6452 _: &RotateSelectionsForward,
6453 window: &mut Window,
6454 cx: &mut Context<Self>,
6455 ) {
6456 self.rotate_selections(window, cx, false)
6457 }
6458
6459 pub fn rotate_selections_backward(
6460 &mut self,
6461 _: &RotateSelectionsBackward,
6462 window: &mut Window,
6463 cx: &mut Context<Self>,
6464 ) {
6465 self.rotate_selections(window, cx, true)
6466 }
6467
6468 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
6469 if self.read_only(cx) {
6470 return;
6471 }
6472 let display_snapshot = self.display_snapshot(cx);
6473 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
6474
6475 if selections.len() < 2 {
6476 return;
6477 }
6478
6479 let (edits, new_selections) = {
6480 let buffer = self.buffer.read(cx).read(cx);
6481 let has_selections = selections.iter().any(|s| !s.is_empty());
6482 if has_selections {
6483 let mut selected_texts: Vec<String> = selections
6484 .iter()
6485 .map(|selection| {
6486 buffer
6487 .text_for_range(selection.start..selection.end)
6488 .collect()
6489 })
6490 .collect();
6491
6492 if reverse {
6493 selected_texts.rotate_left(1);
6494 } else {
6495 selected_texts.rotate_right(1);
6496 }
6497
6498 let mut offset_delta: i64 = 0;
6499 let mut new_selections = Vec::new();
6500 let edits: Vec<_> = selections
6501 .iter()
6502 .zip(selected_texts.iter())
6503 .map(|(selection, new_text)| {
6504 let old_len = (selection.end.0 - selection.start.0) as i64;
6505 let new_len = new_text.len() as i64;
6506 let adjusted_start =
6507 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
6508 let adjusted_end =
6509 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
6510
6511 new_selections.push(Selection {
6512 id: selection.id,
6513 start: adjusted_start,
6514 end: adjusted_end,
6515 reversed: selection.reversed,
6516 goal: selection.goal,
6517 });
6518
6519 offset_delta += new_len - old_len;
6520 (selection.start..selection.end, new_text.clone())
6521 })
6522 .collect();
6523 (edits, new_selections)
6524 } else {
6525 let mut all_rows: Vec<u32> = selections
6526 .iter()
6527 .map(|selection| buffer.offset_to_point(selection.start).row)
6528 .collect();
6529 all_rows.sort_unstable();
6530 all_rows.dedup();
6531
6532 if all_rows.len() < 2 {
6533 return;
6534 }
6535
6536 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
6537 .iter()
6538 .map(|&row| {
6539 let start = Point::new(row, 0);
6540 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
6541 buffer.point_to_offset(start)..buffer.point_to_offset(end)
6542 })
6543 .collect();
6544
6545 let mut line_texts: Vec<String> = line_ranges
6546 .iter()
6547 .map(|range| buffer.text_for_range(range.clone()).collect())
6548 .collect();
6549
6550 if reverse {
6551 line_texts.rotate_left(1);
6552 } else {
6553 line_texts.rotate_right(1);
6554 }
6555
6556 let edits = line_ranges
6557 .iter()
6558 .zip(line_texts.iter())
6559 .map(|(range, new_text)| (range.clone(), new_text.clone()))
6560 .collect();
6561
6562 let num_rows = all_rows.len();
6563 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
6564 .iter()
6565 .enumerate()
6566 .map(|(i, &row)| (row, i))
6567 .collect();
6568
6569 // Compute new line start offsets after rotation (handles CRLF)
6570 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
6571 let first_line_start = line_ranges[0].start.0;
6572 let mut new_line_starts: Vec<usize> = vec![first_line_start];
6573 for text in line_texts.iter().take(num_rows - 1) {
6574 let prev_start = *new_line_starts.last().unwrap();
6575 new_line_starts.push(prev_start + text.len() + newline_len);
6576 }
6577
6578 let new_selections = selections
6579 .iter()
6580 .map(|selection| {
6581 let point = buffer.offset_to_point(selection.start);
6582 let old_index = row_to_index[&point.row];
6583 let new_index = if reverse {
6584 (old_index + num_rows - 1) % num_rows
6585 } else {
6586 (old_index + 1) % num_rows
6587 };
6588 let new_offset =
6589 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
6590 Selection {
6591 id: selection.id,
6592 start: new_offset,
6593 end: new_offset,
6594 reversed: selection.reversed,
6595 goal: selection.goal,
6596 }
6597 })
6598 .collect();
6599
6600 (edits, new_selections)
6601 }
6602 };
6603
6604 self.transact(window, cx, |this, window, cx| {
6605 this.buffer.update(cx, |buffer, cx| {
6606 buffer.edit(edits, None, cx);
6607 });
6608 this.change_selections(Default::default(), window, cx, |s| {
6609 s.select(new_selections);
6610 });
6611 });
6612 }
6613
6614 fn manipulate_lines<M>(
6615 &mut self,
6616 window: &mut Window,
6617 cx: &mut Context<Self>,
6618 mut manipulate: M,
6619 ) where
6620 M: FnMut(&str) -> LineManipulationResult,
6621 {
6622 if self.read_only(cx) {
6623 return;
6624 }
6625
6626 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
6627 let buffer = self.buffer.read(cx).snapshot(cx);
6628
6629 let mut edits = Vec::new();
6630
6631 let selections = self.selections.all::<Point>(&display_map);
6632 let mut selections = selections.iter().peekable();
6633 let mut contiguous_row_selections = Vec::new();
6634 let mut new_selections = Vec::new();
6635 let mut added_lines = 0;
6636 let mut removed_lines = 0;
6637
6638 while let Some(selection) = selections.next() {
6639 let (start_row, end_row) = consume_contiguous_rows(
6640 &mut contiguous_row_selections,
6641 selection,
6642 &display_map,
6643 &mut selections,
6644 );
6645
6646 let start_point = Point::new(start_row.0, 0);
6647 let end_point = Point::new(
6648 end_row.previous_row().0,
6649 buffer.line_len(end_row.previous_row()),
6650 );
6651 let text = buffer
6652 .text_for_range(start_point..end_point)
6653 .collect::<String>();
6654
6655 let LineManipulationResult {
6656 new_text,
6657 line_count_before,
6658 line_count_after,
6659 } = manipulate(&text);
6660
6661 edits.push((start_point..end_point, new_text));
6662
6663 // Selections must change based on added and removed line count
6664 let start_row =
6665 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
6666 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
6667 new_selections.push(Selection {
6668 id: selection.id,
6669 start: start_row,
6670 end: end_row,
6671 goal: SelectionGoal::None,
6672 reversed: selection.reversed,
6673 });
6674
6675 if line_count_after > line_count_before {
6676 added_lines += line_count_after - line_count_before;
6677 } else if line_count_before > line_count_after {
6678 removed_lines += line_count_before - line_count_after;
6679 }
6680 }
6681
6682 self.transact(window, cx, |this, window, cx| {
6683 let buffer = this.buffer.update(cx, |buffer, cx| {
6684 buffer.edit(edits, None, cx);
6685 buffer.snapshot(cx)
6686 });
6687
6688 // Recalculate offsets on newly edited buffer
6689 let new_selections = new_selections
6690 .iter()
6691 .map(|s| {
6692 let start_point = Point::new(s.start.0, 0);
6693 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
6694 Selection {
6695 id: s.id,
6696 start: buffer.point_to_offset(start_point),
6697 end: buffer.point_to_offset(end_point),
6698 goal: s.goal,
6699 reversed: s.reversed,
6700 }
6701 })
6702 .collect();
6703
6704 this.change_selections(Default::default(), window, cx, |s| {
6705 s.select(new_selections);
6706 });
6707
6708 this.request_autoscroll(Autoscroll::fit(), cx);
6709 });
6710 }
6711
6712 fn manipulate_immutable_lines<Fn>(
6713 &mut self,
6714 window: &mut Window,
6715 cx: &mut Context<Self>,
6716 mut callback: Fn,
6717 ) where
6718 Fn: FnMut(&mut Vec<&str>),
6719 {
6720 self.manipulate_lines(window, cx, |text| {
6721 let mut lines: Vec<&str> = text.split('\n').collect();
6722 let line_count_before = lines.len();
6723
6724 callback(&mut lines);
6725
6726 LineManipulationResult {
6727 new_text: lines.join("\n"),
6728 line_count_before,
6729 line_count_after: lines.len(),
6730 }
6731 });
6732 }
6733
6734 fn manipulate_mutable_lines<Fn>(
6735 &mut self,
6736 window: &mut Window,
6737 cx: &mut Context<Self>,
6738 mut callback: Fn,
6739 ) where
6740 Fn: FnMut(&mut Vec<Cow<'_, str>>),
6741 {
6742 self.manipulate_lines(window, cx, |text| {
6743 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
6744 let line_count_before = lines.len();
6745
6746 callback(&mut lines);
6747
6748 LineManipulationResult {
6749 new_text: lines.join("\n"),
6750 line_count_before,
6751 line_count_after: lines.len(),
6752 }
6753 });
6754 }
6755
6756 pub fn convert_indentation_to_spaces(
6757 &mut self,
6758 _: &ConvertIndentationToSpaces,
6759 window: &mut Window,
6760 cx: &mut Context<Self>,
6761 ) {
6762 let settings = self.buffer.read(cx).language_settings(cx);
6763 let tab_size = settings.tab_size.get() as usize;
6764
6765 self.manipulate_mutable_lines(window, cx, |lines| {
6766 // Allocates a reasonably sized scratch buffer once for the whole loop
6767 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
6768 // Avoids recomputing spaces that could be inserted many times
6769 let space_cache: Vec<Vec<char>> = (1..=tab_size)
6770 .map(|n| IndentSize::spaces(n as u32).chars().collect())
6771 .collect();
6772
6773 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
6774 let mut chars = line.as_ref().chars();
6775 let mut col = 0;
6776 let mut changed = false;
6777
6778 for ch in chars.by_ref() {
6779 match ch {
6780 ' ' => {
6781 reindented_line.push(' ');
6782 col += 1;
6783 }
6784 '\t' => {
6785 // \t are converted to spaces depending on the current column
6786 let spaces_len = tab_size - (col % tab_size);
6787 reindented_line.extend(&space_cache[spaces_len - 1]);
6788 col += spaces_len;
6789 changed = true;
6790 }
6791 _ => {
6792 // If we dont append before break, the character is consumed
6793 reindented_line.push(ch);
6794 break;
6795 }
6796 }
6797 }
6798
6799 if !changed {
6800 reindented_line.clear();
6801 continue;
6802 }
6803 // Append the rest of the line and replace old reference with new one
6804 reindented_line.extend(chars);
6805 *line = Cow::Owned(reindented_line.clone());
6806 reindented_line.clear();
6807 }
6808 });
6809 }
6810
6811 pub fn convert_indentation_to_tabs(
6812 &mut self,
6813 _: &ConvertIndentationToTabs,
6814 window: &mut Window,
6815 cx: &mut Context<Self>,
6816 ) {
6817 let settings = self.buffer.read(cx).language_settings(cx);
6818 let tab_size = settings.tab_size.get() as usize;
6819
6820 self.manipulate_mutable_lines(window, cx, |lines| {
6821 // Allocates a reasonably sized buffer once for the whole loop
6822 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
6823 // Avoids recomputing spaces that could be inserted many times
6824 let space_cache: Vec<Vec<char>> = (1..=tab_size)
6825 .map(|n| IndentSize::spaces(n as u32).chars().collect())
6826 .collect();
6827
6828 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
6829 let mut chars = line.chars();
6830 let mut spaces_count = 0;
6831 let mut first_non_indent_char = None;
6832 let mut changed = false;
6833
6834 for ch in chars.by_ref() {
6835 match ch {
6836 ' ' => {
6837 // Keep track of spaces. Append \t when we reach tab_size
6838 spaces_count += 1;
6839 changed = true;
6840 if spaces_count == tab_size {
6841 reindented_line.push('\t');
6842 spaces_count = 0;
6843 }
6844 }
6845 '\t' => {
6846 reindented_line.push('\t');
6847 spaces_count = 0;
6848 }
6849 _ => {
6850 // Dont append it yet, we might have remaining spaces
6851 first_non_indent_char = Some(ch);
6852 break;
6853 }
6854 }
6855 }
6856
6857 if !changed {
6858 reindented_line.clear();
6859 continue;
6860 }
6861 // Remaining spaces that didn't make a full tab stop
6862 if spaces_count > 0 {
6863 reindented_line.extend(&space_cache[spaces_count - 1]);
6864 }
6865 // If we consume an extra character that was not indentation, add it back
6866 if let Some(extra_char) = first_non_indent_char {
6867 reindented_line.push(extra_char);
6868 }
6869 // Append the rest of the line and replace old reference with new one
6870 reindented_line.extend(chars);
6871 *line = Cow::Owned(reindented_line.clone());
6872 reindented_line.clear();
6873 }
6874 });
6875 }
6876
6877 pub fn convert_to_upper_case(
6878 &mut self,
6879 _: &ConvertToUpperCase,
6880 window: &mut Window,
6881 cx: &mut Context<Self>,
6882 ) {
6883 self.manipulate_text(window, cx, |text| text.to_uppercase())
6884 }
6885
6886 pub fn convert_to_lower_case(
6887 &mut self,
6888 _: &ConvertToLowerCase,
6889 window: &mut Window,
6890 cx: &mut Context<Self>,
6891 ) {
6892 self.manipulate_text(window, cx, |text| text.to_lowercase())
6893 }
6894
6895 pub fn convert_to_title_case(
6896 &mut self,
6897 _: &ConvertToTitleCase,
6898 window: &mut Window,
6899 cx: &mut Context<Self>,
6900 ) {
6901 self.manipulate_text(window, cx, |text| {
6902 Self::convert_text_case(text, Case::Title)
6903 })
6904 }
6905
6906 pub fn convert_to_snake_case(
6907 &mut self,
6908 _: &ConvertToSnakeCase,
6909 window: &mut Window,
6910 cx: &mut Context<Self>,
6911 ) {
6912 self.manipulate_text(window, cx, |text| {
6913 Self::convert_text_case(text, Case::Snake)
6914 })
6915 }
6916
6917 pub fn convert_to_kebab_case(
6918 &mut self,
6919 _: &ConvertToKebabCase,
6920 window: &mut Window,
6921 cx: &mut Context<Self>,
6922 ) {
6923 self.manipulate_text(window, cx, |text| {
6924 Self::convert_text_case(text, Case::Kebab)
6925 })
6926 }
6927
6928 pub fn convert_to_upper_camel_case(
6929 &mut self,
6930 _: &ConvertToUpperCamelCase,
6931 window: &mut Window,
6932 cx: &mut Context<Self>,
6933 ) {
6934 self.manipulate_text(window, cx, |text| {
6935 Self::convert_text_case(text, Case::UpperCamel)
6936 })
6937 }
6938
6939 pub fn convert_to_lower_camel_case(
6940 &mut self,
6941 _: &ConvertToLowerCamelCase,
6942 window: &mut Window,
6943 cx: &mut Context<Self>,
6944 ) {
6945 self.manipulate_text(window, cx, |text| {
6946 Self::convert_text_case(text, Case::Camel)
6947 })
6948 }
6949
6950 pub fn convert_to_opposite_case(
6951 &mut self,
6952 _: &ConvertToOppositeCase,
6953 window: &mut Window,
6954 cx: &mut Context<Self>,
6955 ) {
6956 self.manipulate_text(window, cx, |text| {
6957 text.chars()
6958 .fold(String::with_capacity(text.len()), |mut t, c| {
6959 if c.is_uppercase() {
6960 t.extend(c.to_lowercase());
6961 } else {
6962 t.extend(c.to_uppercase());
6963 }
6964 t
6965 })
6966 })
6967 }
6968
6969 pub fn convert_to_sentence_case(
6970 &mut self,
6971 _: &ConvertToSentenceCase,
6972 window: &mut Window,
6973 cx: &mut Context<Self>,
6974 ) {
6975 self.manipulate_text(window, cx, |text| {
6976 Self::convert_text_case(text, Case::Sentence)
6977 })
6978 }
6979
6980 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
6981 self.manipulate_text(window, cx, |text| {
6982 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
6983 if has_upper_case_characters {
6984 text.to_lowercase()
6985 } else {
6986 text.to_uppercase()
6987 }
6988 })
6989 }
6990
6991 pub fn convert_to_rot13(
6992 &mut self,
6993 _: &ConvertToRot13,
6994 window: &mut Window,
6995 cx: &mut Context<Self>,
6996 ) {
6997 self.manipulate_text(window, cx, |text| {
6998 text.chars()
6999 .map(|c| match c {
7000 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
7001 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
7002 _ => c,
7003 })
7004 .collect()
7005 })
7006 }
7007
7008 fn convert_text_case(text: &str, case: Case) -> String {
7009 text.lines()
7010 .map(|line| {
7011 let trimmed_start = line.trim_start();
7012 let leading = &line[..line.len() - trimmed_start.len()];
7013 let trimmed = trimmed_start.trim_end();
7014 let trailing = &trimmed_start[trimmed.len()..];
7015 format!("{}{}{}", leading, trimmed.to_case(case), trailing)
7016 })
7017 .join("\n")
7018 }
7019
7020 pub fn convert_to_rot47(
7021 &mut self,
7022 _: &ConvertToRot47,
7023 window: &mut Window,
7024 cx: &mut Context<Self>,
7025 ) {
7026 self.manipulate_text(window, cx, |text| {
7027 text.chars()
7028 .map(|c| {
7029 let code_point = c as u32;
7030 if code_point >= 33 && code_point <= 126 {
7031 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
7032 }
7033 c
7034 })
7035 .collect()
7036 })
7037 }
7038
7039 pub fn convert_to_base64(
7040 &mut self,
7041 _: &ConvertToBase64,
7042 window: &mut Window,
7043 cx: &mut Context<Self>,
7044 ) {
7045 use base64::Engine as _;
7046 self.manipulate_text(window, cx, |text| {
7047 base64::engine::general_purpose::STANDARD.encode(text)
7048 })
7049 }
7050
7051 pub fn convert_from_base64(
7052 &mut self,
7053 _: &ConvertFromBase64,
7054 window: &mut Window,
7055 cx: &mut Context<Self>,
7056 ) {
7057 use base64::Engine as _;
7058 self.manipulate_text(
7059 window,
7060 cx,
7061 |text| match base64::engine::general_purpose::STANDARD.decode(text) {
7062 Ok(bytes) => String::from_utf8(bytes).unwrap_or_else(|_| text.to_string()),
7063 Err(_) => text.to_string(),
7064 },
7065 )
7066 }
7067
7068 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
7069 where
7070 Fn: FnMut(&str) -> String,
7071 {
7072 if self.read_only(cx) {
7073 return;
7074 }
7075 let buffer = self.buffer.read(cx).snapshot(cx);
7076
7077 let mut new_selections = Vec::new();
7078 let mut edits = Vec::new();
7079
7080 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
7081 let selection_is_empty = selection.is_empty();
7082
7083 let (start, end) = if selection_is_empty {
7084 let (word_range, _) = buffer.surrounding_word(selection.start, None);
7085 (word_range.start, word_range.end)
7086 } else {
7087 (
7088 buffer.point_to_offset(selection.start),
7089 buffer.point_to_offset(selection.end),
7090 )
7091 };
7092
7093 let old_text = buffer.text_for_range(start..end).collect::<String>();
7094 let new_text = callback(&old_text);
7095
7096 new_selections.push(Selection {
7097 start: buffer.anchor_before(start),
7098 end: buffer.anchor_after(end),
7099 goal: SelectionGoal::None,
7100 id: selection.id,
7101 reversed: selection.reversed,
7102 });
7103
7104 if new_text != old_text {
7105 edits.push((start..end, new_text));
7106 }
7107 }
7108
7109 if edits.is_empty() {
7110 return;
7111 }
7112
7113 self.transact(window, cx, |this, window, cx| {
7114 this.buffer.update(cx, |buffer, cx| {
7115 buffer.edit(edits, None, cx);
7116 });
7117
7118 this.change_selections(Default::default(), window, cx, |s| {
7119 s.select(new_selections);
7120 });
7121
7122 this.request_autoscroll(Autoscroll::fit(), cx);
7123 });
7124 }
7125
7126 pub fn move_selection_on_drop(
7127 &mut self,
7128 selection: &Selection<Anchor>,
7129 target: DisplayPoint,
7130 is_cut: bool,
7131 window: &mut Window,
7132 cx: &mut Context<Self>,
7133 ) {
7134 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7135 let buffer = display_map.buffer_snapshot();
7136 let mut edits = Vec::new();
7137 let insert_point = display_map
7138 .clip_point(target, Bias::Left)
7139 .to_point(&display_map);
7140 let text = buffer
7141 .text_for_range(selection.start..selection.end)
7142 .collect::<String>();
7143 if is_cut {
7144 edits.push(((selection.start..selection.end), String::new()));
7145 }
7146 let insert_anchor = buffer.anchor_before(insert_point);
7147 edits.push(((insert_anchor..insert_anchor), text));
7148 let last_edit_start = insert_anchor.bias_left(buffer);
7149 let last_edit_end = insert_anchor.bias_right(buffer);
7150 self.transact(window, cx, |this, window, cx| {
7151 this.buffer.update(cx, |buffer, cx| {
7152 buffer.edit(edits, None, cx);
7153 });
7154 this.change_selections(Default::default(), window, cx, |s| {
7155 s.select_anchor_ranges([last_edit_start..last_edit_end]);
7156 });
7157 });
7158 }
7159
7160 pub fn clear_selection_drag_state(&mut self) {
7161 self.selection_drag_state = SelectionDragState::None;
7162 }
7163
7164 pub fn duplicate(
7165 &mut self,
7166 upwards: bool,
7167 whole_lines: bool,
7168 window: &mut Window,
7169 cx: &mut Context<Self>,
7170 ) {
7171 if self.read_only(cx) {
7172 return;
7173 }
7174
7175 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7176 let buffer = display_map.buffer_snapshot();
7177 let selections = self.selections.all::<Point>(&display_map);
7178
7179 let mut edits = Vec::new();
7180 let mut selections_iter = selections.iter().peekable();
7181 while let Some(selection) = selections_iter.next() {
7182 let mut rows = selection.spanned_rows(false, &display_map);
7183 // duplicate line-wise
7184 if whole_lines || selection.start == selection.end {
7185 // Avoid duplicating the same lines twice.
7186 while let Some(next_selection) = selections_iter.peek() {
7187 let next_rows = next_selection.spanned_rows(false, &display_map);
7188 if next_rows.start < rows.end {
7189 rows.end = next_rows.end;
7190 selections_iter.next().unwrap();
7191 } else {
7192 break;
7193 }
7194 }
7195
7196 // Copy the text from the selected row region and splice it either at the start
7197 // or end of the region.
7198 let start = Point::new(rows.start.0, 0);
7199 let end = Point::new(
7200 rows.end.previous_row().0,
7201 buffer.line_len(rows.end.previous_row()),
7202 );
7203
7204 let mut text = buffer.text_for_range(start..end).collect::<String>();
7205
7206 let insert_location = if upwards {
7207 // When duplicating upward, we need to insert before the current line.
7208 // If we're on the last line and it doesn't end with a newline,
7209 // we need to add a newline before the duplicated content.
7210 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
7211 && buffer.max_point().column > 0
7212 && !text.ends_with('\n');
7213
7214 if needs_leading_newline {
7215 text.insert(0, '\n');
7216 end
7217 } else {
7218 text.push('\n');
7219 Point::new(rows.start.0, 0)
7220 }
7221 } else {
7222 text.push('\n');
7223 start
7224 };
7225 edits.push((insert_location..insert_location, text));
7226 } else {
7227 // duplicate character-wise
7228 let start = selection.start;
7229 let end = selection.end;
7230 let text = buffer.text_for_range(start..end).collect::<String>();
7231 edits.push((selection.end..selection.end, text));
7232 }
7233 }
7234
7235 self.transact(window, cx, |this, window, cx| {
7236 this.buffer.update(cx, |buffer, cx| {
7237 buffer.edit(edits, None, cx);
7238 });
7239
7240 // When duplicating upward with whole lines, move the cursor to the duplicated line
7241 if upwards && whole_lines {
7242 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
7243
7244 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7245 let mut new_ranges = Vec::new();
7246 let selections = s.all::<Point>(&display_map);
7247 let mut selections_iter = selections.iter().peekable();
7248
7249 while let Some(first_selection) = selections_iter.next() {
7250 // Group contiguous selections together to find the total row span
7251 let mut group_selections = vec![first_selection];
7252 let mut rows = first_selection.spanned_rows(false, &display_map);
7253
7254 while let Some(next_selection) = selections_iter.peek() {
7255 let next_rows = next_selection.spanned_rows(false, &display_map);
7256 if next_rows.start < rows.end {
7257 rows.end = next_rows.end;
7258 group_selections.push(selections_iter.next().unwrap());
7259 } else {
7260 break;
7261 }
7262 }
7263
7264 let row_count = rows.end.0 - rows.start.0;
7265
7266 // Move all selections in this group up by the total number of duplicated rows
7267 for selection in group_selections {
7268 let new_start = Point::new(
7269 selection.start.row.saturating_sub(row_count),
7270 selection.start.column,
7271 );
7272
7273 let new_end = Point::new(
7274 selection.end.row.saturating_sub(row_count),
7275 selection.end.column,
7276 );
7277
7278 new_ranges.push(new_start..new_end);
7279 }
7280 }
7281
7282 s.select_ranges(new_ranges);
7283 });
7284 }
7285
7286 this.request_autoscroll(Autoscroll::fit(), cx);
7287 });
7288 }
7289
7290 pub fn duplicate_line_up(
7291 &mut self,
7292 _: &DuplicateLineUp,
7293 window: &mut Window,
7294 cx: &mut Context<Self>,
7295 ) {
7296 self.duplicate(true, true, window, cx);
7297 }
7298
7299 pub fn duplicate_line_down(
7300 &mut self,
7301 _: &DuplicateLineDown,
7302 window: &mut Window,
7303 cx: &mut Context<Self>,
7304 ) {
7305 self.duplicate(false, true, window, cx);
7306 }
7307
7308 pub fn duplicate_selection(
7309 &mut self,
7310 _: &DuplicateSelection,
7311 window: &mut Window,
7312 cx: &mut Context<Self>,
7313 ) {
7314 self.duplicate(false, false, window, cx);
7315 }
7316
7317 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
7318 if self.read_only(cx) {
7319 return;
7320 }
7321 if self.mode.is_single_line() {
7322 cx.propagate();
7323 return;
7324 }
7325
7326 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7327 let buffer = self.buffer.read(cx).snapshot(cx);
7328
7329 let mut edits = Vec::new();
7330 let mut unfold_ranges = Vec::new();
7331 let mut refold_creases = Vec::new();
7332
7333 let selections = self.selections.all::<Point>(&display_map);
7334 let mut selections = selections.iter().peekable();
7335 let mut contiguous_row_selections = Vec::new();
7336 let mut new_selections = Vec::new();
7337
7338 while let Some(selection) = selections.next() {
7339 // Find all the selections that span a contiguous row range
7340 let (start_row, end_row) = consume_contiguous_rows(
7341 &mut contiguous_row_selections,
7342 selection,
7343 &display_map,
7344 &mut selections,
7345 );
7346
7347 // Move the text spanned by the row range to be before the line preceding the row range
7348 if start_row.0 > 0 {
7349 let range_to_move = Point::new(
7350 start_row.previous_row().0,
7351 buffer.line_len(start_row.previous_row()),
7352 )
7353 ..Point::new(
7354 end_row.previous_row().0,
7355 buffer.line_len(end_row.previous_row()),
7356 );
7357 let insertion_point = display_map
7358 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
7359 .0;
7360
7361 // Don't move lines across excerpts
7362 if buffer
7363 .excerpt_containing(insertion_point..range_to_move.end)
7364 .is_some()
7365 {
7366 let text = buffer
7367 .text_for_range(range_to_move.clone())
7368 .flat_map(|s| s.chars())
7369 .skip(1)
7370 .chain(['\n'])
7371 .collect::<String>();
7372
7373 edits.push((
7374 buffer.anchor_after(range_to_move.start)
7375 ..buffer.anchor_before(range_to_move.end),
7376 String::new(),
7377 ));
7378 let insertion_anchor = buffer.anchor_after(insertion_point);
7379 edits.push((insertion_anchor..insertion_anchor, text));
7380
7381 let row_delta = range_to_move.start.row - insertion_point.row + 1;
7382
7383 // Move selections up
7384 new_selections.extend(contiguous_row_selections.drain(..).map(
7385 |mut selection| {
7386 selection.start.row -= row_delta;
7387 selection.end.row -= row_delta;
7388 selection
7389 },
7390 ));
7391
7392 // Move folds up
7393 unfold_ranges.push(range_to_move.clone());
7394 for fold in display_map.folds_in_range(
7395 buffer.anchor_before(range_to_move.start)
7396 ..buffer.anchor_after(range_to_move.end),
7397 ) {
7398 let mut start = fold.range.start.to_point(&buffer);
7399 let mut end = fold.range.end.to_point(&buffer);
7400 start.row -= row_delta;
7401 end.row -= row_delta;
7402 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
7403 }
7404 }
7405 }
7406
7407 // If we didn't move line(s), preserve the existing selections
7408 new_selections.append(&mut contiguous_row_selections);
7409 }
7410
7411 self.transact(window, cx, |this, window, cx| {
7412 this.unfold_ranges(&unfold_ranges, true, true, cx);
7413 this.buffer.update(cx, |buffer, cx| {
7414 for (range, text) in edits {
7415 buffer.edit([(range, text)], None, cx);
7416 }
7417 });
7418 this.fold_creases(refold_creases, true, window, cx);
7419 this.change_selections(Default::default(), window, cx, |s| {
7420 s.select(new_selections);
7421 })
7422 });
7423 }
7424
7425 pub fn move_line_down(
7426 &mut self,
7427 _: &MoveLineDown,
7428 window: &mut Window,
7429 cx: &mut Context<Self>,
7430 ) {
7431 if self.read_only(cx) {
7432 return;
7433 }
7434 if self.mode.is_single_line() {
7435 cx.propagate();
7436 return;
7437 }
7438
7439 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7440 let buffer = self.buffer.read(cx).snapshot(cx);
7441
7442 let mut edits = Vec::new();
7443 let mut unfold_ranges = Vec::new();
7444 let mut refold_creases = Vec::new();
7445
7446 let selections = self.selections.all::<Point>(&display_map);
7447 let mut selections = selections.iter().peekable();
7448 let mut contiguous_row_selections = Vec::new();
7449 let mut new_selections = Vec::new();
7450
7451 while let Some(selection) = selections.next() {
7452 // Find all the selections that span a contiguous row range
7453 let (start_row, end_row) = consume_contiguous_rows(
7454 &mut contiguous_row_selections,
7455 selection,
7456 &display_map,
7457 &mut selections,
7458 );
7459
7460 // Move the text spanned by the row range to be after the last line of the row range
7461 if end_row.0 <= buffer.max_point().row {
7462 let range_to_move =
7463 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
7464 let insertion_point = display_map
7465 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
7466 .0;
7467
7468 // Don't move lines across excerpt boundaries
7469 if buffer
7470 .excerpt_containing(range_to_move.start..insertion_point)
7471 .is_some()
7472 {
7473 let mut text = String::from("\n");
7474 text.extend(buffer.text_for_range(range_to_move.clone()));
7475 text.pop(); // Drop trailing newline
7476 edits.push((
7477 buffer.anchor_after(range_to_move.start)
7478 ..buffer.anchor_before(range_to_move.end),
7479 String::new(),
7480 ));
7481 let insertion_anchor = buffer.anchor_after(insertion_point);
7482 edits.push((insertion_anchor..insertion_anchor, text));
7483
7484 let row_delta = insertion_point.row - range_to_move.end.row + 1;
7485
7486 // Move selections down
7487 new_selections.extend(contiguous_row_selections.drain(..).map(
7488 |mut selection| {
7489 selection.start.row += row_delta;
7490 selection.end.row += row_delta;
7491 selection
7492 },
7493 ));
7494
7495 // Move folds down
7496 unfold_ranges.push(range_to_move.clone());
7497 for fold in display_map.folds_in_range(
7498 buffer.anchor_before(range_to_move.start)
7499 ..buffer.anchor_after(range_to_move.end),
7500 ) {
7501 let mut start = fold.range.start.to_point(&buffer);
7502 let mut end = fold.range.end.to_point(&buffer);
7503 start.row += row_delta;
7504 end.row += row_delta;
7505 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
7506 }
7507 }
7508 }
7509
7510 // If we didn't move line(s), preserve the existing selections
7511 new_selections.append(&mut contiguous_row_selections);
7512 }
7513
7514 self.transact(window, cx, |this, window, cx| {
7515 this.unfold_ranges(&unfold_ranges, true, true, cx);
7516 this.buffer.update(cx, |buffer, cx| {
7517 for (range, text) in edits {
7518 buffer.edit([(range, text)], None, cx);
7519 }
7520 });
7521 this.fold_creases(refold_creases, true, window, cx);
7522 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
7523 });
7524 }
7525
7526 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
7527 if self.read_only(cx) {
7528 return;
7529 }
7530 let text_layout_details = &self.text_layout_details(window, cx);
7531 self.transact(window, cx, |this, window, cx| {
7532 let edits = this.change_selections(Default::default(), window, cx, |s| {
7533 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
7534 s.move_with(&mut |display_map, selection| {
7535 if !selection.is_empty() {
7536 return;
7537 }
7538
7539 let mut head = selection.head();
7540 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
7541 if head.column() == display_map.line_len(head.row()) {
7542 transpose_offset = display_map
7543 .buffer_snapshot()
7544 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
7545 }
7546
7547 if transpose_offset == MultiBufferOffset(0) {
7548 return;
7549 }
7550
7551 *head.column_mut() += 1;
7552 head = display_map.clip_point(head, Bias::Right);
7553 let goal = SelectionGoal::HorizontalPosition(
7554 display_map
7555 .x_for_display_point(head, text_layout_details)
7556 .into(),
7557 );
7558 selection.collapse_to(head, goal);
7559
7560 let transpose_start = display_map
7561 .buffer_snapshot()
7562 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
7563 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
7564 let transpose_end = display_map
7565 .buffer_snapshot()
7566 .clip_offset(transpose_offset + 1usize, Bias::Right);
7567 if let Some(ch) = display_map
7568 .buffer_snapshot()
7569 .chars_at(transpose_start)
7570 .next()
7571 {
7572 edits.push((transpose_start..transpose_offset, String::new()));
7573 edits.push((transpose_end..transpose_end, ch.to_string()));
7574 }
7575 }
7576 });
7577 edits
7578 });
7579 this.buffer
7580 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
7581 let selections = this
7582 .selections
7583 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
7584 this.change_selections(Default::default(), window, cx, |s| {
7585 s.select(selections);
7586 });
7587 });
7588 }
7589
7590 fn restore_selections(
7591 &mut self,
7592 selections: Option<Arc<[Selection<Anchor>]>>,
7593 window: &mut Window,
7594 cx: &mut Context<Self>,
7595 ) {
7596 if let Some(selections) = selections.filter(|selections| !selections.is_empty()) {
7597 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7598 s.select_anchors(selections.to_vec());
7599 });
7600 }
7601 }
7602
7603 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
7604 if self.read_only(cx) {
7605 return;
7606 }
7607
7608 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
7609 let transaction = self.selection_history.transaction(transaction_id);
7610 if transaction.is_none() {
7611 log::error!("No selection history for undone transaction; selection unchanged");
7612 }
7613 let selections = transaction.map(|transaction| transaction.undo.clone());
7614 self.restore_selections(selections, window, cx);
7615 self.request_autoscroll(Autoscroll::fit(), cx);
7616 self.unmark_text(window, cx);
7617 self.refresh_edit_prediction(
7618 true,
7619 false,
7620 EditPredictionRequestTrigger::BufferEdit,
7621 window,
7622 cx,
7623 );
7624 cx.emit(EditorEvent::Edited { transaction_id });
7625 cx.emit(EditorEvent::TransactionUndone { transaction_id });
7626 }
7627 }
7628
7629 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
7630 if self.read_only(cx) {
7631 return;
7632 }
7633
7634 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
7635 let selections = self
7636 .selection_history
7637 .transaction(transaction_id)
7638 .and_then(|transaction| transaction.redo.clone());
7639 self.restore_selections(selections, window, cx);
7640 self.request_autoscroll(Autoscroll::fit(), cx);
7641 self.unmark_text(window, cx);
7642 self.refresh_edit_prediction(
7643 true,
7644 false,
7645 EditPredictionRequestTrigger::BufferEdit,
7646 window,
7647 cx,
7648 );
7649 cx.emit(EditorEvent::Edited { transaction_id });
7650 }
7651 }
7652
7653 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
7654 self.buffer
7655 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
7656 }
7657
7658 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
7659 self.buffer
7660 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
7661 }
7662
7663 pub fn context_menu_first(
7664 &mut self,
7665 _: &ContextMenuFirst,
7666 window: &mut Window,
7667 cx: &mut Context<Self>,
7668 ) {
7669 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
7670 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
7671 }
7672 }
7673
7674 pub fn context_menu_prev(
7675 &mut self,
7676 _: &ContextMenuPrevious,
7677 window: &mut Window,
7678 cx: &mut Context<Self>,
7679 ) {
7680 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
7681 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
7682 }
7683 }
7684
7685 pub fn context_menu_next(
7686 &mut self,
7687 _: &ContextMenuNext,
7688 window: &mut Window,
7689 cx: &mut Context<Self>,
7690 ) {
7691 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
7692 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
7693 }
7694 }
7695
7696 pub fn context_menu_last(
7697 &mut self,
7698 _: &ContextMenuLast,
7699 window: &mut Window,
7700 cx: &mut Context<Self>,
7701 ) {
7702 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
7703 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
7704 }
7705 }
7706
7707 pub fn signature_help_prev(
7708 &mut self,
7709 _: &SignatureHelpPrevious,
7710 _: &mut Window,
7711 cx: &mut Context<Self>,
7712 ) {
7713 if let Some(popover) = self.signature_help_state.popover_mut() {
7714 if popover.current_signature == 0 {
7715 popover.current_signature = popover.signatures.len() - 1;
7716 } else {
7717 popover.current_signature -= 1;
7718 }
7719 cx.notify();
7720 }
7721 }
7722
7723 pub fn signature_help_next(
7724 &mut self,
7725 _: &SignatureHelpNext,
7726 _: &mut Window,
7727 cx: &mut Context<Self>,
7728 ) {
7729 if let Some(popover) = self.signature_help_state.popover_mut() {
7730 if popover.current_signature + 1 == popover.signatures.len() {
7731 popover.current_signature = 0;
7732 } else {
7733 popover.current_signature += 1;
7734 }
7735 cx.notify();
7736 }
7737 }
7738
7739 pub fn rename(
7740 &mut self,
7741 _: &Rename,
7742 window: &mut Window,
7743 cx: &mut Context<Self>,
7744 ) -> Option<Task<Result<()>>> {
7745 use language::ToOffset as _;
7746
7747 if self.read_only(cx) {
7748 return None;
7749 }
7750 let provider = self.semantics_provider.clone()?;
7751 let selection = self.selections.newest_anchor().clone();
7752 let cursor = self.rename_target_anchor(&selection, cx);
7753 let (cursor_buffer, cursor_buffer_position) =
7754 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7755 let (head_buffer, head_buffer_position) = self
7756 .buffer
7757 .read(cx)
7758 .text_anchor_for_position(selection.head(), cx)?;
7759 let (tail_buffer, cursor_buffer_position_end) = self
7760 .buffer
7761 .read(cx)
7762 .text_anchor_for_position(selection.tail(), cx)?;
7763 if tail_buffer != cursor_buffer || head_buffer != cursor_buffer {
7764 return None;
7765 }
7766
7767 let snapshot = cursor_buffer.read(cx).snapshot();
7768 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
7769 let head_buffer_offset = head_buffer_position.to_offset(&snapshot);
7770 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
7771 let prepare_rename = provider.range_for_rename(&cursor_buffer, cursor_buffer_position, cx);
7772 drop(snapshot);
7773
7774 Some(cx.spawn_in(window, async move |this, cx| {
7775 let rename_range = prepare_rename.await?;
7776 if let Some(rename_range) = rename_range {
7777 this.update_in(cx, |this, window, cx| {
7778 let snapshot = cursor_buffer.read(cx).snapshot();
7779 let rename_buffer_range = rename_range.to_offset(&snapshot);
7780 let cursor_offset_in_rename_range =
7781 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
7782 let head_offset_in_rename_range =
7783 head_buffer_offset.saturating_sub(rename_buffer_range.start);
7784 let tail_offset_in_rename_range =
7785 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
7786
7787 this.take_rename(false, window, cx);
7788 let buffer = this.buffer.read(cx).read(cx);
7789 let cursor_offset = cursor.to_offset(&buffer);
7790 let rename_start =
7791 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
7792 let rename_end = rename_start + rename_buffer_range.len();
7793 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
7794 let mut old_highlight_id = None;
7795 let old_name: Arc<str> = buffer
7796 .chunks(
7797 rename_start..rename_end,
7798 LanguageAwareStyling {
7799 tree_sitter: true,
7800 diagnostics: true,
7801 },
7802 )
7803 .map(|chunk| {
7804 if old_highlight_id.is_none() {
7805 old_highlight_id = chunk.syntax_highlight_id;
7806 }
7807 chunk.text
7808 })
7809 .collect::<String>()
7810 .into();
7811
7812 drop(buffer);
7813
7814 // Position the selection in the rename editor so that it matches the current selection.
7815 this.show_local_selections = false;
7816 let rename_editor = cx.new(|cx| {
7817 let mut editor = Editor::single_line(window, cx);
7818 editor.buffer.update(cx, |buffer, cx| {
7819 buffer.edit(
7820 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
7821 None,
7822 cx,
7823 )
7824 });
7825 let head_offset_in_rename_range =
7826 MultiBufferOffset(head_offset_in_rename_range);
7827 let tail_offset_in_rename_range =
7828 MultiBufferOffset(tail_offset_in_rename_range);
7829 let rename_selection_range =
7830 match head_offset_in_rename_range.cmp(&tail_offset_in_rename_range) {
7831 Ordering::Equal => {
7832 editor.select_all(&SelectAll, window, cx);
7833 return editor;
7834 }
7835 Ordering::Less => {
7836 head_offset_in_rename_range..tail_offset_in_rename_range
7837 }
7838 Ordering::Greater => {
7839 tail_offset_in_rename_range..head_offset_in_rename_range
7840 }
7841 };
7842 if rename_selection_range.end.0 > old_name.len() {
7843 editor.select_all(&SelectAll, window, cx);
7844 } else {
7845 editor.change_selections(Default::default(), window, cx, |s| {
7846 s.select_ranges([rename_selection_range]);
7847 });
7848 }
7849 editor
7850 });
7851 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
7852 if e == &EditorEvent::Focused {
7853 cx.emit(EditorEvent::FocusedIn)
7854 }
7855 })
7856 .detach();
7857
7858 let write_highlights =
7859 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
7860 let read_highlights =
7861 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
7862 let ranges = write_highlights
7863 .iter()
7864 .flat_map(|(_, ranges)| ranges.iter())
7865 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
7866 .cloned()
7867 .collect();
7868
7869 this.highlight_text(
7870 HighlightKey::Rename,
7871 ranges,
7872 HighlightStyle {
7873 fade_out: Some(0.6),
7874 ..Default::default()
7875 },
7876 cx,
7877 );
7878 let rename_focus_handle = rename_editor.focus_handle(cx);
7879 window.focus(&rename_focus_handle, cx);
7880 let block_id = this.insert_blocks(
7881 [BlockProperties {
7882 style: BlockStyle::Flex,
7883 placement: BlockPlacement::Below(range.start),
7884 height: Some(1),
7885 render: Arc::new({
7886 let rename_editor = rename_editor.clone();
7887 move |cx: &mut BlockContext| {
7888 let mut text_style = cx.editor_style.text.clone();
7889 if let Some(highlight_style) = old_highlight_id
7890 .and_then(|h| cx.editor_style.syntax.get(h).cloned())
7891 {
7892 text_style = text_style.highlight(highlight_style);
7893 }
7894 div()
7895 .block_mouse_except_scroll()
7896 .pl(cx.anchor_x)
7897 .child(EditorElement::new(
7898 &rename_editor,
7899 EditorStyle {
7900 background: cx.theme().system().transparent,
7901 local_player: cx.editor_style.local_player,
7902 text: text_style,
7903 scrollbar_width: cx.editor_style.scrollbar_width,
7904 syntax: cx.editor_style.syntax.clone(),
7905 status: cx.editor_style.status.clone(),
7906 inlay_hints_style: HighlightStyle {
7907 font_weight: Some(FontWeight::BOLD),
7908 ..make_inlay_hints_style(cx.app)
7909 },
7910 edit_prediction_styles: make_suggestion_styles(
7911 cx.app,
7912 ),
7913 ..EditorStyle::default()
7914 },
7915 ))
7916 .into_any_element()
7917 }
7918 }),
7919 priority: 0,
7920 }],
7921 Some(Autoscroll::fit()),
7922 cx,
7923 )[0];
7924 this.pending_rename = Some(RenameState {
7925 range,
7926 old_name,
7927 editor: rename_editor,
7928 block_id,
7929 });
7930 })?;
7931 }
7932
7933 Ok(())
7934 }))
7935 }
7936
7937 pub fn confirm_rename(
7938 &mut self,
7939 _: &ConfirmRename,
7940 window: &mut Window,
7941 cx: &mut Context<Self>,
7942 ) -> Option<Task<Result<()>>> {
7943 if self.read_only(cx) {
7944 return None;
7945 }
7946 let rename = self.take_rename(false, window, cx)?;
7947 let workspace = self.workspace()?.downgrade();
7948 let (buffer, start) = self
7949 .buffer
7950 .read(cx)
7951 .text_anchor_for_position(rename.range.start, cx)?;
7952 let (end_buffer, _) = self
7953 .buffer
7954 .read(cx)
7955 .text_anchor_for_position(rename.range.end, cx)?;
7956 if buffer != end_buffer {
7957 return None;
7958 }
7959
7960 let old_name = rename.old_name;
7961 let new_name = rename.editor.read(cx).text(cx);
7962
7963 let rename = self.semantics_provider.as_ref()?.perform_rename(
7964 &buffer,
7965 start,
7966 new_name.clone(),
7967 cx,
7968 )?;
7969
7970 Some(cx.spawn_in(window, async move |editor, cx| {
7971 let project_transaction = rename.await?;
7972 Self::open_project_transaction(
7973 &editor,
7974 workspace,
7975 project_transaction,
7976 format!("Rename: {} → {}", old_name, new_name),
7977 cx,
7978 )
7979 .await?;
7980
7981 editor.update(cx, |editor, cx| {
7982 editor.refresh_document_highlights(cx);
7983 })?;
7984 Ok(())
7985 }))
7986 }
7987
7988 fn take_rename(
7989 &mut self,
7990 moving_cursor: bool,
7991 window: &mut Window,
7992 cx: &mut Context<Self>,
7993 ) -> Option<RenameState> {
7994 let rename = self.pending_rename.take()?;
7995 if rename.editor.focus_handle(cx).is_focused(window) {
7996 window.focus(&self.focus_handle, cx);
7997 }
7998
7999 self.remove_blocks(
8000 [rename.block_id].into_iter().collect(),
8001 Some(Autoscroll::fit()),
8002 cx,
8003 );
8004 self.clear_highlights(HighlightKey::Rename, cx);
8005 self.show_local_selections = true;
8006
8007 if moving_cursor {
8008 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
8009 editor
8010 .selections
8011 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
8012 .head()
8013 });
8014
8015 // Update the selection to match the position of the selection inside
8016 // the rename editor.
8017 let snapshot = self.buffer.read(cx).read(cx);
8018 let rename_range = rename.range.to_offset(&snapshot);
8019 let cursor_in_editor = snapshot
8020 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
8021 .min(rename_range.end);
8022 drop(snapshot);
8023
8024 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
8025 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
8026 });
8027 } else {
8028 self.refresh_document_highlights(cx);
8029 }
8030
8031 Some(rename)
8032 }
8033
8034 pub fn pending_rename(&self) -> Option<&RenameState> {
8035 self.pending_rename.as_ref()
8036 }
8037
8038 fn can_format_selections(&self, cx: &App) -> bool {
8039 if !self.mode.is_full() {
8040 return false;
8041 }
8042
8043 let Some(project) = &self.project else {
8044 return false;
8045 };
8046
8047 let project = project.read(cx);
8048 let multi_buffer = self.buffer.read(cx);
8049 let snapshot = multi_buffer.snapshot(cx);
8050
8051 self.selections
8052 .disjoint_anchor_ranges()
8053 .flat_map(|range| [range.start, range.end])
8054 .filter_map(|anchor| snapshot.anchor_to_buffer_anchor(anchor))
8055 .filter_map(|(_, buffer_snapshot)| multi_buffer.buffer(buffer_snapshot.remote_id()))
8056 .any(|buffer| project.supports_range_formatting(&buffer, cx))
8057 }
8058
8059 fn format(
8060 &mut self,
8061 _: &Format,
8062 window: &mut Window,
8063 cx: &mut Context<Self>,
8064 ) -> Option<Task<Result<()>>> {
8065 if self.read_only(cx) {
8066 return None;
8067 }
8068
8069 let project = match &self.project {
8070 Some(project) => project.clone(),
8071 None => return None,
8072 };
8073
8074 Some(self.perform_format(
8075 project,
8076 FormatTrigger::Manual,
8077 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
8078 window,
8079 cx,
8080 ))
8081 }
8082
8083 fn format_selections(
8084 &mut self,
8085 _: &FormatSelections,
8086 window: &mut Window,
8087 cx: &mut Context<Self>,
8088 ) -> Option<Task<Result<()>>> {
8089 if self.read_only(cx) {
8090 return None;
8091 }
8092
8093 let project = match &self.project {
8094 Some(project) => project.clone(),
8095 None => return None,
8096 };
8097
8098 let ranges = self
8099 .selections
8100 .all_adjusted(&self.display_snapshot(cx))
8101 .into_iter()
8102 .map(|selection| selection.range())
8103 .collect_vec();
8104
8105 Some(self.perform_format(
8106 project,
8107 FormatTrigger::Manual,
8108 FormatTarget::Ranges(ranges),
8109 window,
8110 cx,
8111 ))
8112 }
8113
8114 fn perform_format(
8115 &mut self,
8116 project: Entity<Project>,
8117 trigger: FormatTrigger,
8118 target: FormatTarget,
8119 window: &mut Window,
8120 cx: &mut Context<Self>,
8121 ) -> Task<Result<()>> {
8122 let buffer = self.buffer.clone();
8123 let (buffers, target) = match target {
8124 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
8125 FormatTarget::Ranges(selection_ranges) => {
8126 let multi_buffer = buffer.read(cx);
8127 let snapshot = multi_buffer.read(cx);
8128 let mut buffers = HashSet::default();
8129 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
8130 BTreeMap::new();
8131 for selection_range in selection_ranges {
8132 for (buffer_snapshot, buffer_range, _) in
8133 snapshot.range_to_buffer_ranges(selection_range.start..selection_range.end)
8134 {
8135 let buffer_id = buffer_snapshot.remote_id();
8136 let start = buffer_snapshot.anchor_before(buffer_range.start);
8137 let end = buffer_snapshot.anchor_after(buffer_range.end);
8138 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
8139 buffer_id_to_ranges
8140 .entry(buffer_id)
8141 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
8142 .or_insert_with(|| vec![start..end]);
8143 }
8144 }
8145 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
8146 }
8147 };
8148
8149 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
8150 let selections_prev = transaction_id_prev
8151 .and_then(|transaction_id_prev| {
8152 // default to selections as they were after the last edit, if we have them,
8153 // instead of how they are now.
8154 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
8155 // will take you back to where you made the last edit, instead of staying where you scrolled
8156 self.selection_history
8157 .transaction(transaction_id_prev)
8158 .map(|t| t.undo.clone())
8159 })
8160 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
8161
8162 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
8163 let format = project.update(cx, |project, cx| {
8164 project.format(buffers, target, true, trigger, cx)
8165 });
8166
8167 cx.spawn_in(window, async move |editor, cx| {
8168 let transaction = futures::select_biased! {
8169 transaction = format.log_err().fuse() => transaction,
8170 () = timeout => {
8171 log::warn!("timed out waiting for formatting");
8172 None
8173 }
8174 };
8175
8176 buffer.update(cx, |buffer, cx| {
8177 if let Some(transaction) = transaction
8178 && !buffer.is_singleton()
8179 {
8180 buffer.push_transaction(&transaction.0, cx);
8181 }
8182 cx.notify();
8183 });
8184
8185 if let Some(transaction_id_now) =
8186 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
8187 {
8188 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
8189 if has_new_transaction {
8190 editor
8191 .update(cx, |editor, _| {
8192 editor
8193 .selection_history
8194 .insert_transaction(transaction_id_now, selections_prev);
8195 })
8196 .ok();
8197 }
8198 }
8199
8200 Ok(())
8201 })
8202 }
8203
8204 fn organize_imports(
8205 &mut self,
8206 _: &OrganizeImports,
8207 window: &mut Window,
8208 cx: &mut Context<Self>,
8209 ) -> Option<Task<Result<()>>> {
8210 if self.read_only(cx) {
8211 return None;
8212 }
8213 let project = match &self.project {
8214 Some(project) => project.clone(),
8215 None => return None,
8216 };
8217 Some(self.perform_code_action_kind(
8218 project,
8219 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
8220 window,
8221 cx,
8222 ))
8223 }
8224
8225 fn perform_code_action_kind(
8226 &mut self,
8227 project: Entity<Project>,
8228 kind: CodeActionKind,
8229 window: &mut Window,
8230 cx: &mut Context<Self>,
8231 ) -> Task<Result<()>> {
8232 let buffer = self.buffer.clone();
8233 let buffers = buffer.read(cx).all_buffers();
8234 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
8235 let apply_action = project.update(cx, |project, cx| {
8236 project.apply_code_action_kind(buffers, kind, true, cx)
8237 });
8238 cx.spawn_in(window, async move |_, cx| {
8239 let transaction = futures::select_biased! {
8240 () = timeout => {
8241 log::warn!("timed out waiting for executing code action");
8242 None
8243 }
8244 transaction = apply_action.log_err().fuse() => transaction,
8245 };
8246 buffer.update(cx, |buffer, cx| {
8247 // check if we need this
8248 if let Some(transaction) = transaction
8249 && !buffer.is_singleton()
8250 {
8251 buffer.push_transaction(&transaction.0, cx);
8252 }
8253 cx.notify();
8254 });
8255 Ok(())
8256 })
8257 }
8258
8259 fn restart_language_server(
8260 &mut self,
8261 _: &RestartLanguageServer,
8262 _: &mut Window,
8263 cx: &mut Context<Self>,
8264 ) {
8265 if let Some(project) = self.project.clone() {
8266 self.buffer.update(cx, |multi_buffer, cx| {
8267 project.update(cx, |project, cx| {
8268 project.restart_language_servers_for_buffers(
8269 multi_buffer.all_buffers().into_iter().collect(),
8270 HashSet::default(),
8271 true,
8272 cx,
8273 );
8274 });
8275 })
8276 }
8277 }
8278
8279 fn stop_language_server(
8280 &mut self,
8281 _: &StopLanguageServer,
8282 _: &mut Window,
8283 cx: &mut Context<Self>,
8284 ) {
8285 if let Some(project) = self.project.clone() {
8286 self.buffer.update(cx, |multi_buffer, cx| {
8287 project.update(cx, |project, cx| {
8288 project.stop_language_servers_for_buffers(
8289 multi_buffer.all_buffers().into_iter().collect(),
8290 HashSet::default(),
8291 cx,
8292 );
8293 });
8294 });
8295 }
8296 }
8297
8298 fn cancel_language_server_work(
8299 workspace: &mut Workspace,
8300 _: &actions::CancelLanguageServerWork,
8301 _: &mut Window,
8302 cx: &mut Context<Workspace>,
8303 ) {
8304 let project = workspace.project();
8305 let buffers = workspace
8306 .active_item(cx)
8307 .and_then(|item| item.act_as::<Editor>(cx))
8308 .map_or(HashSet::default(), |editor| {
8309 editor.read(cx).buffer.read(cx).all_buffers()
8310 });
8311 project.update(cx, |project, cx| {
8312 project.cancel_language_server_work_for_buffers(buffers, cx);
8313 });
8314 }
8315
8316 fn show_character_palette(
8317 &mut self,
8318 _: &ShowCharacterPalette,
8319 window: &mut Window,
8320 _: &mut Context<Self>,
8321 ) {
8322 window.show_character_palette();
8323 }
8324
8325 pub fn supports_minimap(&self, cx: &App) -> bool {
8326 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
8327 }
8328
8329 pub fn toggle_minimap(
8330 &mut self,
8331 _: &ToggleMinimap,
8332 window: &mut Window,
8333 cx: &mut Context<Editor>,
8334 ) {
8335 if self.supports_minimap(cx) {
8336 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
8337 }
8338 }
8339
8340 pub fn transact(
8341 &mut self,
8342 window: &mut Window,
8343 cx: &mut Context<Self>,
8344 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
8345 ) -> Option<TransactionId> {
8346 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
8347 this.start_transaction_at(Instant::now(), window, cx);
8348 update(this, window, cx);
8349 this.end_transaction_at(Instant::now(), cx)
8350 })
8351 }
8352
8353 pub fn start_transaction_at(
8354 &mut self,
8355 now: Instant,
8356 window: &mut Window,
8357 cx: &mut Context<Self>,
8358 ) -> Option<TransactionId> {
8359 self.end_selection(window, cx);
8360 if let Some(tx_id) = self
8361 .buffer
8362 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
8363 {
8364 self.selection_history
8365 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
8366 cx.emit(EditorEvent::TransactionBegun {
8367 transaction_id: tx_id,
8368 });
8369 Some(tx_id)
8370 } else {
8371 None
8372 }
8373 }
8374
8375 pub fn end_transaction_at(
8376 &mut self,
8377 now: Instant,
8378 cx: &mut Context<Self>,
8379 ) -> Option<TransactionId> {
8380 if let Some(transaction_id) = self
8381 .buffer
8382 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
8383 {
8384 if let Some(transaction) = self.selection_history.transaction_mut(transaction_id) {
8385 transaction.redo = Some(self.selections.disjoint_anchors_arc());
8386 } else {
8387 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
8388 }
8389
8390 cx.emit(EditorEvent::Edited { transaction_id });
8391 Some(transaction_id)
8392 } else {
8393 None
8394 }
8395 }
8396
8397 pub fn modify_transaction_selection_history(
8398 &mut self,
8399 transaction_id: TransactionId,
8400 modify: impl FnOnce(&mut TransactionSelections),
8401 ) -> bool {
8402 self.selection_history
8403 .transaction_mut(transaction_id)
8404 .map(modify)
8405 .is_some()
8406 }
8407
8408 pub fn toggle_focus(
8409 workspace: &mut Workspace,
8410 _: &actions::ToggleFocus,
8411 window: &mut Window,
8412 cx: &mut Context<Workspace>,
8413 ) {
8414 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
8415 return;
8416 };
8417 workspace.activate_item(&item, true, true, window, cx);
8418 }
8419
8420 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
8421 if hovered != self.gutter_hovered {
8422 self.gutter_hovered = hovered;
8423 cx.notify();
8424 }
8425 }
8426
8427 pub fn insert_blocks(
8428 &mut self,
8429 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
8430 autoscroll: Option<Autoscroll>,
8431 cx: &mut Context<Self>,
8432 ) -> Vec<CustomBlockId> {
8433 let blocks = self
8434 .display_map
8435 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
8436 if let Some(autoscroll) = autoscroll {
8437 self.request_autoscroll(autoscroll, cx);
8438 }
8439 cx.notify();
8440 blocks
8441 }
8442
8443 pub fn resize_blocks(
8444 &mut self,
8445 heights: HashMap<CustomBlockId, u32>,
8446 autoscroll: Option<Autoscroll>,
8447 cx: &mut Context<Self>,
8448 ) {
8449 self.display_map
8450 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
8451 if let Some(autoscroll) = autoscroll {
8452 self.request_autoscroll(autoscroll, cx);
8453 }
8454 cx.notify();
8455 }
8456
8457 pub fn replace_blocks(
8458 &mut self,
8459 renderers: HashMap<CustomBlockId, RenderBlock>,
8460 autoscroll: Option<Autoscroll>,
8461 cx: &mut Context<Self>,
8462 ) {
8463 self.display_map
8464 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
8465 if let Some(autoscroll) = autoscroll {
8466 self.request_autoscroll(autoscroll, cx);
8467 }
8468 cx.notify();
8469 }
8470
8471 pub fn remove_blocks(
8472 &mut self,
8473 block_ids: HashSet<CustomBlockId>,
8474 autoscroll: Option<Autoscroll>,
8475 cx: &mut Context<Self>,
8476 ) {
8477 self.display_map.update(cx, |display_map, cx| {
8478 display_map.remove_blocks(block_ids, cx)
8479 });
8480 if let Some(autoscroll) = autoscroll {
8481 self.request_autoscroll(autoscroll, cx);
8482 }
8483 cx.notify();
8484 }
8485
8486 pub fn row_for_block(
8487 &self,
8488 block_id: CustomBlockId,
8489 cx: &mut Context<Self>,
8490 ) -> Option<DisplayRow> {
8491 self.display_map
8492 .update(cx, |map, cx| map.row_for_block(block_id, cx))
8493 }
8494
8495 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
8496 self.focused_block = Some(focused_block);
8497 }
8498
8499 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
8500 self.focused_block.take()
8501 }
8502
8503 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
8504 self.display_map
8505 .update(cx, |map, cx| map.snapshot(cx))
8506 .longest_row()
8507 }
8508
8509 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
8510 self.display_map
8511 .update(cx, |map, cx| map.snapshot(cx))
8512 .max_point()
8513 }
8514
8515 pub fn text(&self, cx: &App) -> String {
8516 self.buffer.read(cx).read(cx).text()
8517 }
8518
8519 pub fn is_empty(&self, cx: &App) -> bool {
8520 self.buffer.read(cx).read(cx).is_empty()
8521 }
8522
8523 pub fn text_option(&self, cx: &App) -> Option<String> {
8524 let text = self.text(cx);
8525 let text = text.trim();
8526
8527 if text.is_empty() {
8528 return None;
8529 }
8530
8531 Some(text.to_string())
8532 }
8533
8534 pub fn set_text(
8535 &mut self,
8536 text: impl Into<Arc<str>>,
8537 window: &mut Window,
8538 cx: &mut Context<Self>,
8539 ) {
8540 self.transact(window, cx, |this, _, cx| {
8541 this.buffer
8542 .read(cx)
8543 .as_singleton()
8544 .expect("you can only call set_text on editors for singleton buffers")
8545 .update(cx, |buffer, cx| buffer.set_text(text, cx));
8546 });
8547 }
8548
8549 pub fn display_text(&self, cx: &mut App) -> String {
8550 self.display_map
8551 .update(cx, |map, cx| map.snapshot(cx))
8552 .text()
8553 }
8554
8555 fn create_minimap(
8556 &self,
8557 minimap_settings: MinimapSettings,
8558 window: &mut Window,
8559 cx: &mut Context<Self>,
8560 ) -> Option<Entity<Self>> {
8561 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
8562 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
8563 }
8564
8565 fn initialize_new_minimap(
8566 &self,
8567 minimap_settings: MinimapSettings,
8568 window: &mut Window,
8569 cx: &mut Context<Self>,
8570 ) -> Entity<Self> {
8571 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
8572 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
8573
8574 let mut minimap = Editor::new_internal(
8575 EditorMode::Minimap {
8576 parent: cx.weak_entity(),
8577 },
8578 self.buffer.clone(),
8579 None,
8580 Some(self.display_map.clone()),
8581 window,
8582 cx,
8583 );
8584 let my_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8585 let minimap_snapshot = minimap.display_map.update(cx, |map, cx| map.snapshot(cx));
8586 minimap.scroll_manager.clone_state(
8587 &self.scroll_manager,
8588 &my_snapshot,
8589 &minimap_snapshot,
8590 cx,
8591 );
8592 minimap.set_text_style_refinement(TextStyleRefinement {
8593 font_size: Some(MINIMAP_FONT_SIZE),
8594 font_weight: Some(MINIMAP_FONT_WEIGHT),
8595 font_family: Some(MINIMAP_FONT_FAMILY),
8596 ..Default::default()
8597 });
8598 minimap.update_minimap_configuration(minimap_settings, cx);
8599 cx.new(|_| minimap)
8600 }
8601
8602 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
8603 let current_line_highlight = minimap_settings
8604 .current_line_highlight
8605 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
8606 self.set_current_line_highlight(Some(current_line_highlight));
8607 }
8608
8609 pub fn minimap(&self) -> Option<&Entity<Self>> {
8610 self.minimap
8611 .as_ref()
8612 .filter(|_| self.minimap_visibility.visible())
8613 }
8614
8615 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
8616 if self.display_map.read(cx).masked != masked {
8617 self.display_map.update(cx, |map, _| map.masked = masked);
8618 }
8619 cx.notify()
8620 }
8621
8622 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
8623 self.active_buffer(cx)?
8624 .read(cx)
8625 .file()
8626 .and_then(|f| f.as_local())
8627 }
8628
8629 fn reveal_in_finder(
8630 &mut self,
8631 _: &RevealInFileManager,
8632 _window: &mut Window,
8633 cx: &mut Context<Self>,
8634 ) {
8635 if let Some(path) = self.target_file_abs_path(cx) {
8636 if let Some(project) = self.project() {
8637 project.update(cx, |project, cx| project.reveal_path(&path, cx));
8638 } else {
8639 cx.reveal_path(&path);
8640 }
8641 }
8642 }
8643
8644 fn copy_path(
8645 &mut self,
8646 _: &zed_actions::workspace::CopyPath,
8647 _window: &mut Window,
8648 cx: &mut Context<Self>,
8649 ) {
8650 if let Some(path) = self.target_file_abs_path(cx)
8651 && let Some(path) = path.to_str()
8652 {
8653 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
8654 } else {
8655 cx.propagate();
8656 }
8657 }
8658
8659 fn copy_relative_path(
8660 &mut self,
8661 _: &zed_actions::workspace::CopyRelativePath,
8662 _window: &mut Window,
8663 cx: &mut Context<Self>,
8664 ) {
8665 if let Some(path) = self.active_buffer(cx).and_then(|buffer| {
8666 let project = self.project()?.read(cx);
8667 let path = buffer.read(cx).file()?.path();
8668 let path = path.display(project.path_style(cx));
8669 Some(path)
8670 }) {
8671 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
8672 } else {
8673 cx.propagate();
8674 }
8675 }
8676
8677 pub fn copy_file_name_without_extension(
8678 &mut self,
8679 _: &CopyFileNameWithoutExtension,
8680 _: &mut Window,
8681 cx: &mut Context<Self>,
8682 ) {
8683 if let Some(file_stem) = self.active_buffer(cx).and_then(|buffer| {
8684 let file = buffer.read(cx).file()?;
8685 file.path().file_stem()
8686 }) {
8687 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
8688 }
8689 }
8690
8691 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
8692 if let Some(file_name) = self.active_buffer(cx).and_then(|buffer| {
8693 let file = buffer.read(cx).file()?;
8694 Some(file.file_name(cx))
8695 }) {
8696 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
8697 }
8698 }
8699
8700 pub fn copy_file_location(
8701 &mut self,
8702 _: &CopyFileLocation,
8703 _: &mut Window,
8704 cx: &mut Context<Self>,
8705 ) {
8706 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
8707 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
8708
8709 if let Some(file_location) = maybe!({
8710 let (buffer, range) = multi_buffer_snapshot
8711 .range_to_buffer_range(selection.range())
8712 .or_else(|| {
8713 // A selection that spans multiple buffers has no single location,
8714 // so fall back to the buffer the latest cursor is in.
8715 let (buffer, point) =
8716 multi_buffer_snapshot.point_to_buffer_point(selection.head())?;
8717 Some((buffer, point..point))
8718 })?;
8719
8720 let start_line = range.start.row + 1;
8721 let end_line = range.end.row + 1;
8722
8723 let end_line = if range.end.column == 0 && end_line > start_line {
8724 end_line - 1
8725 } else {
8726 end_line
8727 };
8728
8729 let project = self.project()?.read(cx);
8730 let file = buffer.file()?;
8731 let path = file.path().display(project.path_style(cx));
8732
8733 let location = if start_line == end_line {
8734 format!("{path}:{start_line}")
8735 } else {
8736 format!("{path}:{start_line}-{end_line}")
8737 };
8738 Some(location)
8739 }) {
8740 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
8741 }
8742 }
8743
8744 pub fn insert_uuid_v4(
8745 &mut self,
8746 _: &InsertUuidV4,
8747 window: &mut Window,
8748 cx: &mut Context<Self>,
8749 ) {
8750 self.insert_uuid(UuidVersion::V4, window, cx);
8751 }
8752
8753 pub fn insert_uuid_v7(
8754 &mut self,
8755 _: &InsertUuidV7,
8756 window: &mut Window,
8757 cx: &mut Context<Self>,
8758 ) {
8759 self.insert_uuid(UuidVersion::V7, window, cx);
8760 }
8761
8762 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
8763 if self.read_only(cx) {
8764 return;
8765 }
8766 self.transact(window, cx, |this, window, cx| {
8767 let edits = this
8768 .selections
8769 .all::<Point>(&this.display_snapshot(cx))
8770 .into_iter()
8771 .map(|selection| {
8772 let uuid = match version {
8773 UuidVersion::V4 => uuid::Uuid::new_v4(),
8774 UuidVersion::V7 => uuid::Uuid::now_v7(),
8775 };
8776
8777 (selection.range(), uuid.to_string())
8778 });
8779 this.edit(edits, cx);
8780 this.refresh_edit_prediction(
8781 true,
8782 false,
8783 EditPredictionRequestTrigger::BufferEdit,
8784 window,
8785 cx,
8786 );
8787 });
8788 }
8789
8790 pub fn open_selections_in_multibuffer(
8791 &mut self,
8792 _: &OpenSelectionsInMultibuffer,
8793 window: &mut Window,
8794 cx: &mut Context<Self>,
8795 ) {
8796 let multibuffer = self.buffer.read(cx);
8797
8798 let Some(buffer) = multibuffer.as_singleton() else {
8799 return;
8800 };
8801 let buffer_snapshot = buffer.read(cx).snapshot();
8802
8803 let Some(workspace) = self.workspace() else {
8804 return;
8805 };
8806
8807 let title = multibuffer.title(cx).to_string();
8808
8809 let locations = self
8810 .selections
8811 .all_anchors(&self.display_snapshot(cx))
8812 .iter()
8813 .map(|selection| {
8814 (
8815 buffer.clone(),
8816 (selection.start.text_anchor_in(&buffer_snapshot)
8817 ..selection.end.text_anchor_in(&buffer_snapshot))
8818 .to_point(buffer.read(cx)),
8819 )
8820 })
8821 .into_group_map();
8822
8823 cx.spawn_in(window, async move |_, cx| {
8824 workspace.update_in(cx, |workspace, window, cx| {
8825 Self::open_locations_in_multibuffer(
8826 workspace,
8827 locations,
8828 format!("Selections for '{title}'"),
8829 false,
8830 false,
8831 MultibufferSelectionMode::All,
8832 window,
8833 cx,
8834 );
8835 })
8836 })
8837 .detach();
8838 }
8839
8840 /// Adds a row highlight for the given range. If a row has multiple highlights, the
8841 /// last highlight added will be used.
8842 ///
8843 /// If the range ends at the beginning of a line, then that line will not be highlighted.
8844 pub fn highlight_rows<T: 'static>(
8845 &mut self,
8846 range: Range<Anchor>,
8847 color: fn(&App) -> Hsla,
8848 options: RowHighlightOptions,
8849 cx: &mut Context<Self>,
8850 ) {
8851 let snapshot = self.buffer().read(cx).snapshot(cx);
8852 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
8853 let ix = row_highlights.binary_search_by(|highlight| {
8854 Ordering::Equal
8855 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
8856 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
8857 });
8858
8859 if let Err(mut ix) = ix {
8860 let index = post_inc(&mut self.highlight_order);
8861
8862 // If this range intersects with the preceding highlight, then merge it with
8863 // the preceding highlight. Otherwise insert a new highlight.
8864 let mut merged = false;
8865 if ix > 0 {
8866 let prev_highlight = &mut row_highlights[ix - 1];
8867 if prev_highlight
8868 .range
8869 .end
8870 .cmp(&range.start, &snapshot)
8871 .is_ge()
8872 {
8873 ix -= 1;
8874 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
8875 prev_highlight.range.end = range.end;
8876 }
8877 merged = true;
8878 prev_highlight.index = index;
8879 prev_highlight.color = color;
8880 prev_highlight.options = options;
8881 }
8882 }
8883
8884 if !merged {
8885 row_highlights.insert(
8886 ix,
8887 RowHighlight {
8888 range,
8889 index,
8890 color,
8891 options,
8892 type_id: TypeId::of::<T>(),
8893 },
8894 );
8895 }
8896
8897 // If any of the following highlights intersect with this one, merge them.
8898 while let Some(next_highlight) = row_highlights.get(ix + 1) {
8899 let highlight = &row_highlights[ix];
8900 if next_highlight
8901 .range
8902 .start
8903 .cmp(&highlight.range.end, &snapshot)
8904 .is_le()
8905 {
8906 if next_highlight
8907 .range
8908 .end
8909 .cmp(&highlight.range.end, &snapshot)
8910 .is_gt()
8911 {
8912 row_highlights[ix].range.end = next_highlight.range.end;
8913 }
8914 row_highlights.remove(ix + 1);
8915 } else {
8916 break;
8917 }
8918 }
8919 }
8920 }
8921
8922 /// Remove any highlighted row ranges of the given type that intersect the
8923 /// given ranges.
8924 pub fn remove_highlighted_rows<T: 'static>(
8925 &mut self,
8926 ranges_to_remove: Vec<Range<Anchor>>,
8927 cx: &mut Context<Self>,
8928 ) {
8929 let snapshot = self.buffer().read(cx).snapshot(cx);
8930 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
8931 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
8932 row_highlights.retain(|highlight| {
8933 while let Some(range_to_remove) = ranges_to_remove.peek() {
8934 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
8935 Ordering::Less | Ordering::Equal => {
8936 ranges_to_remove.next();
8937 }
8938 Ordering::Greater => {
8939 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
8940 Ordering::Less | Ordering::Equal => {
8941 return false;
8942 }
8943 Ordering::Greater => break,
8944 }
8945 }
8946 }
8947 }
8948
8949 true
8950 })
8951 }
8952
8953 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
8954 pub fn clear_row_highlights<T: 'static>(&mut self) {
8955 self.highlighted_rows.remove(&TypeId::of::<T>());
8956 }
8957
8958 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
8959 pub fn highlighted_rows<'a, T: 'static>(
8960 &'a self,
8961 cx: &'a App,
8962 ) -> impl 'a + Iterator<Item = (Range<Anchor>, Hsla)> {
8963 self.highlighted_rows
8964 .get(&TypeId::of::<T>())
8965 .map_or(&[] as &[_], |vec| vec.as_slice())
8966 .iter()
8967 .map(|highlight| (highlight.range.clone(), (highlight.color)(cx)))
8968 }
8969
8970 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
8971 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
8972 /// Allows to ignore certain kinds of highlights.
8973 pub fn highlighted_display_rows(
8974 &self,
8975 window: &mut Window,
8976 cx: &mut App,
8977 ) -> BTreeMap<DisplayRow, LineHighlight> {
8978 let snapshot = self.snapshot(window, cx);
8979 let mut used_highlight_orders = HashMap::default();
8980 self.highlighted_rows
8981 .values()
8982 .flat_map(|highlighted_rows| highlighted_rows.iter())
8983 .fold(
8984 BTreeMap::<DisplayRow, LineHighlight>::new(),
8985 |mut unique_rows, highlight| {
8986 let start = highlight.range.start.to_display_point(&snapshot);
8987 let end = highlight.range.end.to_display_point(&snapshot);
8988 let start_row = start.row().0;
8989 let end_row = if !highlight.range.end.is_max() && end.column() == 0 {
8990 end.row().0.saturating_sub(1)
8991 } else {
8992 end.row().0
8993 };
8994 for row in start_row..=end_row {
8995 let used_index =
8996 used_highlight_orders.entry(row).or_insert(highlight.index);
8997 if highlight.index >= *used_index {
8998 *used_index = highlight.index;
8999 unique_rows.insert(
9000 DisplayRow(row),
9001 LineHighlight {
9002 include_gutter: highlight.options.include_gutter,
9003 border: None,
9004 background: (highlight.color)(cx).into(),
9005 type_id: Some(highlight.type_id),
9006 },
9007 );
9008 }
9009 }
9010 unique_rows
9011 },
9012 )
9013 }
9014
9015 pub fn highlighted_display_row_for_autoscroll(
9016 &self,
9017 snapshot: &DisplaySnapshot,
9018 ) -> Option<DisplayRow> {
9019 self.highlighted_rows
9020 .values()
9021 .flat_map(|highlighted_rows| highlighted_rows.iter())
9022 .filter_map(|highlight| {
9023 if highlight.options.autoscroll {
9024 Some(highlight.range.start.to_display_point(snapshot).row())
9025 } else {
9026 None
9027 }
9028 })
9029 .min()
9030 }
9031
9032 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
9033 self.highlight_background(
9034 HighlightKey::SearchWithinRange,
9035 ranges,
9036 |_, colors| colors.colors().editor_document_highlight_read_background,
9037 cx,
9038 )
9039 }
9040
9041 pub fn set_breadcrumb_header(&mut self, new_header: String) {
9042 self.breadcrumb_header = Some(new_header);
9043 }
9044
9045 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
9046 self.clear_background_highlights(HighlightKey::SearchWithinRange, cx);
9047 }
9048
9049 pub fn highlight_background(
9050 &mut self,
9051 key: HighlightKey,
9052 ranges: &[Range<Anchor>],
9053 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
9054 cx: &mut Context<Self>,
9055 ) {
9056 self.background_highlights
9057 .insert(key, (Arc::new(color_fetcher), Arc::from(ranges)));
9058 self.scrollbar_marker_state.dirty = true;
9059 cx.notify();
9060 }
9061
9062 pub fn clear_background_highlights(
9063 &mut self,
9064 key: HighlightKey,
9065 cx: &mut Context<Self>,
9066 ) -> Option<BackgroundHighlight> {
9067 let text_highlights = self.background_highlights.remove(&key)?;
9068 if !text_highlights.1.is_empty() {
9069 self.scrollbar_marker_state.dirty = true;
9070 cx.notify();
9071 }
9072 Some(text_highlights)
9073 }
9074
9075 pub fn highlight_gutter<T: 'static>(
9076 &mut self,
9077 ranges: impl Into<Vec<Range<Anchor>>>,
9078 color_fetcher: fn(&App) -> Hsla,
9079 cx: &mut Context<Self>,
9080 ) {
9081 self.gutter_highlights
9082 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
9083 cx.notify();
9084 }
9085
9086 pub fn clear_gutter_highlights<T: 'static>(
9087 &mut self,
9088 cx: &mut Context<Self>,
9089 ) -> Option<GutterHighlight> {
9090 cx.notify();
9091 self.gutter_highlights.remove(&TypeId::of::<T>())
9092 }
9093
9094 pub fn insert_gutter_highlight<T: 'static>(
9095 &mut self,
9096 range: Range<Anchor>,
9097 color_fetcher: fn(&App) -> Hsla,
9098 cx: &mut Context<Self>,
9099 ) {
9100 let snapshot = self.buffer().read(cx).snapshot(cx);
9101 let mut highlights = self
9102 .gutter_highlights
9103 .remove(&TypeId::of::<T>())
9104 .map(|(_, highlights)| highlights)
9105 .unwrap_or_default();
9106 let ix = highlights.binary_search_by(|highlight| {
9107 Ordering::Equal
9108 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
9109 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
9110 });
9111 if let Err(ix) = ix {
9112 highlights.insert(ix, range);
9113 }
9114 self.gutter_highlights
9115 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
9116 }
9117
9118 pub fn remove_gutter_highlights<T: 'static>(
9119 &mut self,
9120 ranges_to_remove: Vec<Range<Anchor>>,
9121 cx: &mut Context<Self>,
9122 ) {
9123 let snapshot = self.buffer().read(cx).snapshot(cx);
9124 let Some((color_fetcher, mut gutter_highlights)) =
9125 self.gutter_highlights.remove(&TypeId::of::<T>())
9126 else {
9127 return;
9128 };
9129 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
9130 gutter_highlights.retain(|highlight| {
9131 while let Some(range_to_remove) = ranges_to_remove.peek() {
9132 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
9133 Ordering::Less | Ordering::Equal => {
9134 ranges_to_remove.next();
9135 }
9136 Ordering::Greater => {
9137 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
9138 Ordering::Less | Ordering::Equal => {
9139 return false;
9140 }
9141 Ordering::Greater => break,
9142 }
9143 }
9144 }
9145 }
9146
9147 true
9148 });
9149 self.gutter_highlights
9150 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
9151 }
9152
9153 #[cfg(any(test, feature = "test-support"))]
9154 pub fn all_text_highlights(
9155 &self,
9156 window: &mut Window,
9157 cx: &mut Context<Self>,
9158 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
9159 let snapshot = self.snapshot(window, cx);
9160 self.display_map.update(cx, |display_map, _| {
9161 display_map
9162 .all_text_highlights()
9163 .map(|(_, highlight)| {
9164 let (style, ranges) = highlight.as_ref();
9165 (
9166 *style,
9167 ranges
9168 .iter()
9169 .map(|range| range.clone().to_display_points(&snapshot))
9170 .collect(),
9171 )
9172 })
9173 .collect()
9174 })
9175 }
9176
9177 #[cfg(any(test, feature = "test-support"))]
9178 pub fn all_text_background_highlights(
9179 &self,
9180 window: &mut Window,
9181 cx: &mut Context<Self>,
9182 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
9183 let snapshot = self.snapshot(window, cx);
9184 let buffer = &snapshot.buffer_snapshot();
9185 let start = buffer.anchor_before(MultiBufferOffset(0));
9186 let end = buffer.anchor_after(buffer.len());
9187 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
9188 }
9189
9190 #[cfg(any(test, feature = "test-support"))]
9191 pub fn sorted_background_highlights_in_range(
9192 &self,
9193 search_range: Range<Anchor>,
9194 display_snapshot: &DisplaySnapshot,
9195 theme: &Theme,
9196 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
9197 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
9198 res.sort_by(|a, b| {
9199 a.0.start
9200 .cmp(&b.0.start)
9201 .then_with(|| a.0.end.cmp(&b.0.end))
9202 .then_with(|| a.1.cmp(&b.1))
9203 });
9204 res
9205 }
9206
9207 #[cfg(any(test, feature = "test-support"))]
9208 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
9209 let snapshot = self.buffer().read(cx).snapshot(cx);
9210
9211 let highlights = self
9212 .background_highlights
9213 .get(&HighlightKey::BufferSearchHighlights);
9214
9215 if let Some((_color, ranges)) = highlights {
9216 ranges
9217 .iter()
9218 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
9219 .collect_vec()
9220 } else {
9221 vec![]
9222 }
9223 }
9224
9225 pub fn has_background_highlights(&self, key: HighlightKey) -> bool {
9226 self.background_highlights
9227 .get(&key)
9228 .is_some_and(|(_, highlights)| !highlights.is_empty())
9229 }
9230
9231 /// Returns all background highlights for a given range.
9232 ///
9233 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
9234 pub fn background_highlights_in_range(
9235 &self,
9236 search_range: Range<Anchor>,
9237 display_snapshot: &DisplaySnapshot,
9238 theme: &Theme,
9239 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
9240 let mut results = Vec::new();
9241 for (color_fetcher, ranges) in self.background_highlights.values() {
9242 let start_ix = match ranges.binary_search_by(|probe| {
9243 let cmp = probe
9244 .end
9245 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
9246 if cmp.is_gt() {
9247 Ordering::Greater
9248 } else {
9249 Ordering::Less
9250 }
9251 }) {
9252 Ok(i) | Err(i) => i,
9253 };
9254 for (index, range) in ranges[start_ix..].iter().enumerate() {
9255 if range
9256 .start
9257 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
9258 .is_ge()
9259 {
9260 break;
9261 }
9262
9263 let color = color_fetcher(&(start_ix + index), theme);
9264 let start = range.start.to_display_point(display_snapshot);
9265 let end = range.end.to_display_point(display_snapshot);
9266 results.push((start..end, color))
9267 }
9268 }
9269 results
9270 }
9271
9272 pub fn gutter_highlights_in_range(
9273 &self,
9274 search_range: Range<Anchor>,
9275 display_snapshot: &DisplaySnapshot,
9276 cx: &App,
9277 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
9278 let mut results = Vec::new();
9279 for (color_fetcher, ranges) in self.gutter_highlights.values() {
9280 let color = color_fetcher(cx);
9281 let start_ix = match ranges.binary_search_by(|probe| {
9282 let cmp = probe
9283 .end
9284 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
9285 if cmp.is_gt() {
9286 Ordering::Greater
9287 } else {
9288 Ordering::Less
9289 }
9290 }) {
9291 Ok(i) | Err(i) => i,
9292 };
9293 for range in &ranges[start_ix..] {
9294 if range
9295 .start
9296 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
9297 .is_ge()
9298 {
9299 break;
9300 }
9301
9302 let start = range.start.to_display_point(display_snapshot);
9303 let end = range.end.to_display_point(display_snapshot);
9304 results.push((start..end, color))
9305 }
9306 }
9307 results
9308 }
9309
9310 /// Get the text ranges corresponding to the redaction query
9311 pub fn redacted_ranges(
9312 &self,
9313 search_range: Range<Anchor>,
9314 display_snapshot: &DisplaySnapshot,
9315 cx: &App,
9316 ) -> Vec<Range<DisplayPoint>> {
9317 display_snapshot
9318 .buffer_snapshot()
9319 .redacted_ranges(search_range, |file| {
9320 if let Some(file) = file {
9321 file.is_private()
9322 && EditorSettings::get(
9323 Some(SettingsLocation {
9324 worktree_id: file.worktree_id(cx),
9325 path: file.path().as_ref(),
9326 }),
9327 cx,
9328 )
9329 .redact_private_values
9330 } else {
9331 false
9332 }
9333 })
9334 .map(|range| {
9335 range.start.to_display_point(display_snapshot)
9336 ..range.end.to_display_point(display_snapshot)
9337 })
9338 .collect()
9339 }
9340
9341 pub fn highlight_text_key(
9342 &mut self,
9343 key: HighlightKey,
9344 ranges: Vec<Range<Anchor>>,
9345 style: HighlightStyle,
9346 merge: bool,
9347 cx: &mut Context<Self>,
9348 ) {
9349 self.display_map.update(cx, |map, cx| {
9350 map.highlight_text(key, ranges, style, merge, cx);
9351 });
9352 cx.notify();
9353 }
9354
9355 pub fn highlight_text(
9356 &mut self,
9357 key: HighlightKey,
9358 ranges: Vec<Range<Anchor>>,
9359 style: HighlightStyle,
9360 cx: &mut Context<Self>,
9361 ) {
9362 self.display_map.update(cx, |map, cx| {
9363 map.highlight_text(key, ranges, style, false, cx)
9364 });
9365 cx.notify();
9366 }
9367
9368 pub fn text_highlights<'a>(
9369 &'a self,
9370 key: HighlightKey,
9371 cx: &'a App,
9372 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
9373 self.display_map.read(cx).text_highlights(key)
9374 }
9375
9376 pub fn set_navigation_overlays(
9377 &mut self,
9378 key: NavigationOverlayKey,
9379 overlays: Vec<NavigationTargetOverlay>,
9380 cx: &mut Context<Self>,
9381 ) {
9382 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
9383 let mut covered_text_ranges = overlays
9384 .iter()
9385 .filter_map(|overlay| overlay.covered_text_range.clone())
9386 .collect::<Vec<_>>();
9387 covered_text_ranges.sort_by(|left, right| {
9388 left.start
9389 .cmp(&right.start, &buffer_snapshot)
9390 .then_with(|| left.end.cmp(&right.end, &buffer_snapshot))
9391 });
9392
9393 self.display_map.update(cx, |map, cx| {
9394 map.clear_highlights(HighlightKey::NavigationOverlay(key));
9395 if !covered_text_ranges.is_empty() {
9396 map.highlight_text(
9397 HighlightKey::NavigationOverlay(key),
9398 covered_text_ranges,
9399 HighlightStyle {
9400 fade_out: Some(1.0),
9401 ..Default::default()
9402 },
9403 false,
9404 cx,
9405 );
9406 }
9407 });
9408
9409 if overlays.is_empty() {
9410 self.navigation_overlays.remove(&key);
9411 } else {
9412 self.navigation_overlays.insert(key, Arc::from(overlays));
9413 }
9414
9415 cx.notify();
9416 }
9417
9418 pub fn clear_navigation_overlays(&mut self, key: NavigationOverlayKey, cx: &mut Context<Self>) {
9419 let removed = self.navigation_overlays.remove(&key).is_some();
9420 let cleared = self.display_map.update(cx, |map, _| {
9421 map.clear_highlights(HighlightKey::NavigationOverlay(key))
9422 });
9423 if removed || cleared {
9424 cx.notify();
9425 }
9426 }
9427
9428 pub(crate) fn navigation_overlay_sets(
9429 &self,
9430 ) -> &HashMap<NavigationOverlayKey, Arc<[NavigationTargetOverlay]>> {
9431 &self.navigation_overlays
9432 }
9433
9434 pub fn clear_highlights(&mut self, key: HighlightKey, cx: &mut Context<Self>) {
9435 let cleared = self
9436 .display_map
9437 .update(cx, |map, _| map.clear_highlights(key));
9438 if cleared {
9439 cx.notify();
9440 }
9441 }
9442
9443 pub fn clear_highlights_with(
9444 &mut self,
9445 f: &mut dyn FnMut(&HighlightKey) -> bool,
9446 cx: &mut Context<Self>,
9447 ) {
9448 let cleared = self
9449 .display_map
9450 .update(cx, |map, _| map.clear_highlights_with(f));
9451 if cleared {
9452 cx.notify();
9453 }
9454 }
9455
9456 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
9457 (self.read_only(cx) || self.blink_manager.read(cx).visible())
9458 && self.focus_handle.is_focused(window)
9459 }
9460
9461 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
9462 self.show_cursor_when_unfocused = is_enabled;
9463 cx.notify();
9464 }
9465
9466 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
9467 cx.notify();
9468 }
9469
9470 fn on_debug_session_event(
9471 &mut self,
9472 _session: Entity<Session>,
9473 event: &SessionEvent,
9474 cx: &mut Context<Self>,
9475 ) {
9476 if let SessionEvent::InvalidateInlineValue = event {
9477 self.refresh_inline_values(cx);
9478 }
9479 }
9480
9481 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
9482 let Some(semantics) = self.semantics_provider.clone() else {
9483 return;
9484 };
9485
9486 if !self.inline_value_cache.enabled {
9487 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
9488 self.splice_inlays(&inlays, Vec::new(), cx);
9489 return;
9490 }
9491
9492 let current_execution_position = self
9493 .highlighted_rows
9494 .get(&TypeId::of::<ActiveDebugLine>())
9495 .and_then(|lines| lines.last().map(|line| line.range.end));
9496
9497 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
9498 let inline_values = editor
9499 .update(cx, |editor, cx| {
9500 let Some(current_execution_position) = current_execution_position else {
9501 return Some(Task::ready(Ok(Vec::new())));
9502 };
9503
9504 let (buffer, buffer_anchor) =
9505 editor.buffer.read_with(cx, |multibuffer, cx| {
9506 let multibuffer_snapshot = multibuffer.snapshot(cx);
9507 let (buffer_anchor, _) = multibuffer_snapshot
9508 .anchor_to_buffer_anchor(current_execution_position)?;
9509 let buffer = multibuffer.buffer(buffer_anchor.buffer_id)?;
9510 Some((buffer, buffer_anchor))
9511 })?;
9512
9513 let range = buffer.read(cx).anchor_before(0)..buffer_anchor;
9514
9515 semantics.inline_values(buffer, range, cx)
9516 })
9517 .ok()
9518 .flatten()?
9519 .await
9520 .context("refreshing debugger inlays")
9521 .log_err()?;
9522
9523 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
9524
9525 for (buffer_id, inline_value) in inline_values
9526 .into_iter()
9527 .map(|hint| (hint.position.buffer_id, hint))
9528 {
9529 buffer_inline_values
9530 .entry(buffer_id)
9531 .or_default()
9532 .push(inline_value);
9533 }
9534
9535 editor
9536 .update(cx, |editor, cx| {
9537 let snapshot = editor.buffer.read(cx).snapshot(cx);
9538 let mut new_inlays = Vec::default();
9539
9540 for (_buffer_id, inline_values) in buffer_inline_values {
9541 for hint in inline_values {
9542 let Some(anchor) = snapshot.anchor_in_excerpt(hint.position) else {
9543 continue;
9544 };
9545 let inlay = Inlay::debugger(
9546 post_inc(&mut editor.next_inlay_id),
9547 anchor,
9548 hint.text(),
9549 );
9550 if !inlay.text().chars().contains(&'\n') {
9551 new_inlays.push(inlay);
9552 }
9553 }
9554 }
9555
9556 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
9557 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
9558
9559 editor.splice_inlays(&inlay_ids, new_inlays, cx);
9560 })
9561 .ok()?;
9562 Some(())
9563 });
9564 }
9565
9566 fn on_buffer_event(
9567 &mut self,
9568 multibuffer: &Entity<MultiBuffer>,
9569 event: &multi_buffer::Event,
9570 window: &mut Window,
9571 cx: &mut Context<Self>,
9572 ) {
9573 match event {
9574 multi_buffer::Event::Edited {
9575 edited_buffer,
9576 source,
9577 } => {
9578 self.scrollbar_marker_state.dirty = true;
9579 self.active_indent_guides_state.dirty = true;
9580 self.refresh_active_diagnostics(cx);
9581 self.refresh_code_actions_for_selection(window, cx);
9582 self.refresh_single_line_folds(window, cx);
9583 let snapshot = self.snapshot(window, cx);
9584 self.refresh_matching_bracket_highlights(&snapshot, cx);
9585 self.refresh_outline_symbols_at_cursor(cx);
9586 self.refresh_sticky_headers(&snapshot, cx);
9587 if source.is_local() && self.has_active_edit_prediction() {
9588 self.update_visible_edit_prediction(window, cx);
9589 }
9590
9591 // Clean up orphaned review comments after edits
9592 self.cleanup_orphaned_review_comments(cx);
9593
9594 if let Some(buffer) = edited_buffer {
9595 if buffer.read(cx).file().is_none() {
9596 cx.emit(EditorEvent::TitleChanged);
9597 }
9598
9599 if self.project.is_some() {
9600 let buffer_id = buffer.read(cx).remote_id();
9601 self.register_buffer(buffer_id, cx);
9602 self.update_lsp_data(Some(buffer_id), window, cx);
9603 self.refresh_inlay_hints(
9604 InlayHintRefreshReason::BufferEdited(buffer_id),
9605 cx,
9606 );
9607 }
9608 }
9609
9610 cx.emit(EditorEvent::BufferEdited);
9611 cx.emit(SearchEvent::MatchesInvalidated);
9612
9613 let Some(project) = &self.project else { return };
9614 let (telemetry, is_via_ssh) = {
9615 let project = project.read(cx);
9616 let telemetry = project.client().telemetry().clone();
9617 let is_via_ssh = project.is_via_remote_server();
9618 (telemetry, is_via_ssh)
9619 };
9620 telemetry.log_edit_event("editor", is_via_ssh);
9621 }
9622 multi_buffer::Event::BufferRangesUpdated {
9623 buffer,
9624 ranges,
9625 path_key,
9626 } => {
9627 if let Some(hovered_link_state) = self.hovered_link_state.as_mut() {
9628 hovered_link_state.symbol_range = None;
9629 }
9630 self.refresh_document_highlights(cx);
9631 let buffer_id = buffer.read(cx).remote_id();
9632 if self.buffer.read(cx).diff_for(buffer_id).is_none()
9633 && let Some(project) = &self.project
9634 {
9635 update_uncommitted_diff_for_buffer(
9636 cx.entity(),
9637 project,
9638 [buffer.clone()],
9639 self.buffer.clone(),
9640 cx,
9641 )
9642 .detach();
9643 }
9644 self.register_visible_buffers(cx);
9645 self.update_lsp_data(Some(buffer_id), window, cx);
9646 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
9647 self.refresh_runnables(None, window, cx);
9648 self.bracket_fetched_tree_sitter_chunks
9649 .retain(|range, _| range.start.buffer_id != buffer_id);
9650 self.colorize_brackets(false, cx);
9651 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
9652 self.semantic_token_state.invalidate_buffer(&buffer_id);
9653 cx.emit(EditorEvent::BufferRangesUpdated {
9654 buffer: buffer.clone(),
9655 ranges: ranges.clone(),
9656 path_key: path_key.clone(),
9657 });
9658 }
9659 multi_buffer::Event::BuffersRemoved { removed_buffer_ids } => {
9660 if let Some(inlay_hints) = &mut self.inlay_hints {
9661 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
9662 }
9663 self.refresh_inlay_hints(
9664 InlayHintRefreshReason::BuffersRemoved(removed_buffer_ids.clone()),
9665 cx,
9666 );
9667 for buffer_id in removed_buffer_ids {
9668 self.registered_buffers.remove(buffer_id);
9669 self.clear_runnables(Some(*buffer_id));
9670 self.semantic_token_state.invalidate_buffer(buffer_id);
9671 self.lsp_document_symbols.remove(buffer_id);
9672 self.lsp_document_links.per_buffer.remove(buffer_id);
9673 self.display_map.update(cx, |display_map, cx| {
9674 display_map.invalidate_semantic_highlights(*buffer_id);
9675 display_map.clear_lsp_folding_ranges(*buffer_id, cx);
9676 });
9677 }
9678
9679 self.display_map.update(cx, |display_map, cx| {
9680 display_map.unfold_buffers(removed_buffer_ids.iter().copied(), cx);
9681 });
9682
9683 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
9684 cx.emit(EditorEvent::BuffersRemoved {
9685 removed_buffer_ids: removed_buffer_ids.clone(),
9686 });
9687 }
9688 multi_buffer::Event::BuffersEdited { buffer_ids } => {
9689 self.display_map.update(cx, |map, cx| {
9690 map.unfold_buffers(buffer_ids.iter().copied(), cx)
9691 });
9692 cx.emit(EditorEvent::BuffersEdited {
9693 buffer_ids: buffer_ids.clone(),
9694 });
9695 }
9696 multi_buffer::Event::Reparsed(buffer_id) => {
9697 self.refresh_runnables(Some(*buffer_id), window, cx);
9698 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
9699 self.colorize_brackets(true, cx);
9700 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
9701
9702 cx.emit(EditorEvent::Reparsed(*buffer_id));
9703 }
9704 multi_buffer::Event::DiffHunksToggled => {
9705 self.refresh_runnables(None, window, cx);
9706 }
9707 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
9708 if !is_fresh_language {
9709 self.registered_buffers.remove(&buffer_id);
9710 }
9711 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
9712 cx.emit(EditorEvent::Reparsed(*buffer_id));
9713 self.update_edit_prediction_settings(cx);
9714 cx.notify();
9715 }
9716 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
9717 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
9718 multi_buffer::Event::FileHandleChanged => {
9719 cx.emit(EditorEvent::TitleChanged);
9720 cx.emit(EditorEvent::FileHandleChanged);
9721 }
9722 multi_buffer::Event::Reloaded | multi_buffer::Event::BufferDiffChanged => {
9723 cx.emit(EditorEvent::TitleChanged)
9724 }
9725 multi_buffer::Event::DiagnosticsUpdated => {
9726 self.update_diagnostics_state(window, cx);
9727 }
9728 _ => {}
9729 };
9730 }
9731
9732 fn on_display_map_changed(
9733 &mut self,
9734 _: Entity<DisplayMap>,
9735 _: &mut Window,
9736 cx: &mut Context<Self>,
9737 ) {
9738 cx.notify();
9739 }
9740
9741 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
9742 if !self.mode.is_full() {
9743 return None;
9744 }
9745
9746 let theme_settings = theme_settings::ThemeSettings::get_global(cx);
9747 let theme = cx.theme();
9748 let accent_colors = theme.accents().clone();
9749 let editor_background = theme.colors().editor_background;
9750 let auto_accent_colors =
9751 AccentColors(crate::bracket_colorization::bracket_colorization_accents(
9752 &accent_colors.0,
9753 theme.appearance,
9754 editor_background,
9755 ));
9756
9757 let accent_overrides = theme_settings
9758 .theme_overrides
9759 .get(theme.name.as_ref())
9760 .map(|theme_style| &theme_style.accents)
9761 .into_iter()
9762 .flatten()
9763 .chain(
9764 theme_settings
9765 .experimental_theme_overrides
9766 .as_ref()
9767 .map(|overrides| &overrides.accents)
9768 .into_iter()
9769 .flatten(),
9770 )
9771 .flat_map(|accent| accent.0.clone().map(SharedString::from))
9772 .collect();
9773
9774 Some(AccentData {
9775 colors: auto_accent_colors,
9776 overrides: accent_overrides,
9777 })
9778 }
9779
9780 fn fetch_applicable_language_settings(
9781 &self,
9782 cx: &App,
9783 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
9784 if !self.mode.is_full() {
9785 return HashMap::default();
9786 }
9787
9788 self.buffer().read(cx).all_buffers().into_iter().fold(
9789 HashMap::default(),
9790 |mut acc, buffer| {
9791 let buffer = buffer.read(cx);
9792 let language = buffer.language().map(|language| language.name());
9793 if let hash_map::Entry::Vacant(v) = acc.entry(language) {
9794 v.insert(LanguageSettings::for_buffer(&buffer, cx).into_owned());
9795 }
9796 acc
9797 },
9798 )
9799 }
9800
9801 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9802 let new_language_settings = self.fetch_applicable_language_settings(cx);
9803 let language_settings_changed = new_language_settings != self.applicable_language_settings;
9804 self.applicable_language_settings = new_language_settings;
9805
9806 let new_accents = self.fetch_accent_data(cx);
9807 let accents_changed = new_accents != self.accent_data;
9808 self.accent_data = new_accents;
9809
9810 if self.diagnostics_enabled() {
9811 let new_severity = EditorSettings::get_global(cx)
9812 .diagnostics_max_severity
9813 .unwrap_or(DiagnosticSeverity::Hint);
9814 self.set_max_diagnostics_severity(new_severity, cx);
9815 }
9816 self.refresh_runnables(None, window, cx);
9817 self.update_edit_prediction_settings(cx);
9818 self.refresh_edit_prediction(
9819 true,
9820 false,
9821 EditPredictionRequestTrigger::SettingsChanged,
9822 window,
9823 cx,
9824 );
9825 self.refresh_inline_values(cx);
9826
9827 let old_cursor_shape = self.cursor_shape;
9828 let old_breadcrumbs_visible = self.breadcrumbs_visible();
9829
9830 {
9831 let editor_settings = EditorSettings::get_global(cx);
9832 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
9833 if self.breadcrumbs_visibility.settings_visibility()
9834 != editor_settings.toolbar.breadcrumbs
9835 {
9836 self.breadcrumbs_visibility =
9837 BreadcrumbsVisibility::new(editor_settings.toolbar.breadcrumbs);
9838 }
9839 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
9840 }
9841
9842 if old_cursor_shape != self.cursor_shape {
9843 cx.emit(EditorEvent::CursorShapeChanged);
9844 }
9845
9846 if old_breadcrumbs_visible != self.breadcrumbs_visible() {
9847 cx.emit(EditorEvent::BreadcrumbsChanged);
9848 }
9849
9850 let (restore_unsaved_buffers, show_inline_diagnostics, inline_blame_enabled) = {
9851 let project_settings = ProjectSettings::get_global(cx);
9852 (
9853 project_settings.session.restore_unsaved_buffers,
9854 project_settings.diagnostics.inline.enabled,
9855 project_settings.git.inline_blame.enabled,
9856 )
9857 };
9858 self.buffer_serialization = self
9859 .should_serialize_buffer()
9860 .then(|| BufferSerialization::new(restore_unsaved_buffers));
9861
9862 if self.mode.is_full() {
9863 if self.show_inline_diagnostics != show_inline_diagnostics {
9864 self.show_inline_diagnostics = show_inline_diagnostics;
9865 self.refresh_inline_diagnostics(false, window, cx);
9866 }
9867
9868 if self.git_blame_inline_enabled != inline_blame_enabled {
9869 self.toggle_git_blame_inline_internal(false, window, cx);
9870 }
9871
9872 let minimap_settings = EditorSettings::get_global(cx).minimap;
9873 if self.minimap_visibility != MinimapVisibility::Disabled {
9874 if self.minimap_visibility.settings_visibility()
9875 != minimap_settings.minimap_enabled()
9876 {
9877 self.set_minimap_visibility(
9878 MinimapVisibility::for_mode(self.mode(), cx),
9879 window,
9880 cx,
9881 );
9882 } else if let Some(minimap_entity) = self.minimap.as_ref() {
9883 minimap_entity.update(cx, |minimap_editor, cx| {
9884 minimap_editor.update_minimap_configuration(minimap_settings, cx)
9885 })
9886 }
9887 }
9888
9889 if language_settings_changed || accents_changed {
9890 self.colorize_brackets(true, cx);
9891 }
9892
9893 if language_settings_changed {
9894 self.clear_disabled_lsp_folding_ranges(window, cx);
9895 self.refresh_document_symbols(None, cx);
9896 self.refresh_outline_symbols_at_cursor(cx);
9897 }
9898
9899 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
9900 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
9901 }) {
9902 if !inlay_splice.is_empty() {
9903 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
9904 }
9905 self.refresh_document_colors(None, window, cx);
9906 }
9907
9908 let code_lens_inline =
9909 self.enable_code_lens && EditorSettings::get_global(cx).code_lens.inline();
9910 let was_inline = self.code_lens.is_some();
9911 if code_lens_inline != was_inline {
9912 self.toggle_code_lens(code_lens_inline, window, cx);
9913 }
9914
9915 let lsp_document_links_enabled = EditorSettings::get_global(cx).lsp_document_links;
9916 if lsp_document_links_enabled != self.lsp_document_links.enabled {
9917 self.lsp_document_links.enabled = lsp_document_links_enabled;
9918 if lsp_document_links_enabled {
9919 self.refresh_document_links(None, cx);
9920 } else {
9921 self.lsp_document_links.per_buffer.clear();
9922 self.lsp_document_links.refresh_task = Task::ready(());
9923 }
9924 }
9925
9926 self.refresh_inlay_hints(
9927 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
9928 self.selections.newest_anchor().head(),
9929 &self.buffer.read(cx).snapshot(cx),
9930 cx,
9931 )),
9932 cx,
9933 );
9934
9935 let new_semantic_token_rules = ProjectSettings::get_global(cx)
9936 .global_lsp_settings
9937 .semantic_token_rules
9938 .clone();
9939 let semantic_token_rules_changed = self
9940 .semantic_token_state
9941 .update_rules(new_semantic_token_rules);
9942 if language_settings_changed || semantic_token_rules_changed {
9943 self.invalidate_semantic_tokens(None);
9944 self.refresh_semantic_tokens(None, false, cx);
9945 }
9946 }
9947
9948 cx.notify();
9949 }
9950
9951 fn theme_changed(&mut self, _: &mut Window, cx: &mut Context<Self>) {
9952 if !self.mode.is_full() {
9953 return;
9954 }
9955
9956 let new_accents = self.fetch_accent_data(cx);
9957 if new_accents != self.accent_data {
9958 self.accent_data = new_accents;
9959 self.colorize_brackets(true, cx);
9960 }
9961
9962 self.invalidate_semantic_tokens(None);
9963 self.refresh_semantic_tokens(None, false, cx);
9964 self.refresh_outline_symbols_at_cursor(cx);
9965 }
9966
9967 pub fn set_searchable(&mut self, searchable: bool) {
9968 self.searchable = searchable;
9969 }
9970
9971 pub fn searchable(&self) -> bool {
9972 self.searchable
9973 }
9974
9975 pub fn open_excerpts_in_split(
9976 &mut self,
9977 _: &OpenExcerptsSplit,
9978 window: &mut Window,
9979 cx: &mut Context<Self>,
9980 ) {
9981 self.open_excerpts_common(None, true, window, cx)
9982 }
9983
9984 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
9985 self.open_excerpts_common(None, false, window, cx)
9986 }
9987
9988 pub(crate) fn open_excerpts_common(
9989 &mut self,
9990 jump_data: Option<JumpData>,
9991 split: bool,
9992 window: &mut Window,
9993 cx: &mut Context<Self>,
9994 ) {
9995 if self.buffer.read(cx).is_singleton() {
9996 cx.propagate();
9997 return;
9998 }
9999
10000 let mut new_selections_by_buffer = HashMap::default();
10001 match &jump_data {
10002 Some(JumpData::MultiBufferPoint {
10003 anchor,
10004 position,
10005 line_offset_from_top,
10006 }) => {
10007 if let Some(buffer) = self.buffer.read(cx).buffer(anchor.buffer_id) {
10008 let buffer_snapshot = buffer.read(cx).snapshot();
10009 let jump_to_point = if buffer_snapshot.can_resolve(&anchor) {
10010 language::ToPoint::to_point(anchor, &buffer_snapshot)
10011 } else {
10012 buffer_snapshot.clip_point(*position, Bias::Left)
10013 };
10014 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
10015 new_selections_by_buffer.insert(
10016 buffer,
10017 (
10018 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
10019 Some(*line_offset_from_top),
10020 ),
10021 );
10022 }
10023 }
10024 Some(JumpData::MultiBufferRow {
10025 row,
10026 line_offset_from_top,
10027 }) => {
10028 let point = MultiBufferPoint::new(row.0, 0);
10029 if let Some((buffer, buffer_point)) =
10030 self.buffer.read(cx).point_to_buffer_point(point, cx)
10031 {
10032 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
10033 new_selections_by_buffer
10034 .entry(buffer)
10035 .or_insert((Vec::new(), Some(*line_offset_from_top)))
10036 .0
10037 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
10038 }
10039 }
10040 None => {
10041 let selections = self
10042 .selections
10043 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
10044 let multi_buffer = self.buffer.read(cx);
10045 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
10046 for selection in selections {
10047 for (snapshot, range, anchor) in multi_buffer_snapshot
10048 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
10049 {
10050 if let Some((text_anchor, _)) = anchor.and_then(|anchor| {
10051 multi_buffer_snapshot.anchor_to_buffer_anchor(anchor)
10052 }) {
10053 let Some(buffer_handle) = multi_buffer.buffer(text_anchor.buffer_id)
10054 else {
10055 continue;
10056 };
10057 let offset = text::ToOffset::to_offset(
10058 &text_anchor,
10059 &buffer_handle.read(cx).snapshot(),
10060 );
10061 let range = BufferOffset(offset)..BufferOffset(offset);
10062 new_selections_by_buffer
10063 .entry(buffer_handle)
10064 .or_insert((Vec::new(), None))
10065 .0
10066 .push(range)
10067 } else {
10068 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
10069 else {
10070 continue;
10071 };
10072 new_selections_by_buffer
10073 .entry(buffer_handle)
10074 .or_insert((Vec::new(), None))
10075 .0
10076 .push(range)
10077 }
10078 }
10079 }
10080 }
10081 }
10082
10083 if self.delegate_open_excerpts {
10084 let selections_by_buffer: HashMap<_, _> = new_selections_by_buffer
10085 .into_iter()
10086 .map(|(buffer, value)| (buffer.read(cx).remote_id(), value))
10087 .collect();
10088 if !selections_by_buffer.is_empty() {
10089 cx.emit(EditorEvent::OpenExcerptsRequested {
10090 selections_by_buffer,
10091 split,
10092 });
10093 }
10094 return;
10095 }
10096
10097 let Some(workspace) = self.workspace() else {
10098 cx.propagate();
10099 return;
10100 };
10101
10102 new_selections_by_buffer
10103 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
10104
10105 if new_selections_by_buffer.is_empty() {
10106 return;
10107 }
10108
10109 Self::open_buffers_in_workspace(
10110 workspace.downgrade(),
10111 new_selections_by_buffer,
10112 split,
10113 window,
10114 cx,
10115 );
10116 }
10117
10118 pub(crate) fn open_buffers_in_workspace(
10119 workspace: WeakEntity<Workspace>,
10120 new_selections_by_buffer: HashMap<
10121 Entity<language::Buffer>,
10122 (Vec<Range<BufferOffset>>, Option<u32>),
10123 >,
10124 split: bool,
10125 window: &mut Window,
10126 cx: &mut App,
10127 ) {
10128 // We defer the pane interaction because we ourselves are a workspace item
10129 // and activating a new item causes the pane to call a method on us reentrantly,
10130 // which panics if we're on the stack.
10131 window.defer(cx, move |window, cx| {
10132 workspace
10133 .update(cx, |workspace, cx| {
10134 let pane = if split {
10135 workspace.adjacent_pane(window, cx)
10136 } else {
10137 workspace.active_pane().clone()
10138 };
10139
10140 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
10141 let buffer_read = buffer.read(cx);
10142 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
10143 (true, project::File::from_dyn(Some(file)).is_some())
10144 } else {
10145 (false, false)
10146 };
10147
10148 // If project file is none workspace.open_project_item will fail to open the excerpt
10149 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
10150 // so we check if there's a tab match in that case first
10151 let editor = (!has_file || !is_project_file)
10152 .then(|| {
10153 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
10154 // so `workspace.open_project_item` will never find them, always opening a new editor.
10155 // Instead, we try to activate the existing editor in the pane first.
10156 let (editor, pane_item_index, pane_item_id) =
10157 pane.read(cx).items().enumerate().find_map(|(i, item)| {
10158 let editor = item.downcast::<Editor>()?;
10159 let singleton_buffer =
10160 editor.read(cx).buffer().read(cx).as_singleton()?;
10161 if singleton_buffer == buffer {
10162 Some((editor, i, item.item_id()))
10163 } else {
10164 None
10165 }
10166 })?;
10167 pane.update(cx, |pane, cx| {
10168 pane.activate_item(pane_item_index, true, true, window, cx);
10169 if !PreviewTabsSettings::get_global(cx)
10170 .enable_preview_from_multibuffer
10171 {
10172 pane.unpreview_item_if_preview(pane_item_id);
10173 }
10174 });
10175 Some(editor)
10176 })
10177 .flatten()
10178 .unwrap_or_else(|| {
10179 let keep_old_preview = PreviewTabsSettings::get_global(cx)
10180 .enable_keep_preview_on_code_navigation;
10181 let allow_new_preview = PreviewTabsSettings::get_global(cx)
10182 .enable_preview_from_multibuffer;
10183 workspace.open_project_item::<Self>(
10184 pane.clone(),
10185 buffer,
10186 true,
10187 true,
10188 keep_old_preview,
10189 allow_new_preview,
10190 window,
10191 cx,
10192 )
10193 });
10194
10195 editor.update(cx, |editor, cx| {
10196 if has_file && !is_project_file {
10197 editor.set_read_only(true);
10198 }
10199 let autoscroll = match scroll_offset {
10200 Some(scroll_offset) => {
10201 Autoscroll::top_relative(scroll_offset as ScrollOffset)
10202 }
10203 None => Autoscroll::newest(),
10204 };
10205 let nav_history = editor.nav_history.take();
10206 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
10207 let Some(buffer_snapshot) = multibuffer_snapshot.as_singleton() else {
10208 return;
10209 };
10210 editor.change_selections(
10211 SelectionEffects::scroll(autoscroll),
10212 window,
10213 cx,
10214 |s| {
10215 s.select_ranges(ranges.into_iter().map(|range| {
10216 let range = buffer_snapshot.anchor_before(range.start)
10217 ..buffer_snapshot.anchor_after(range.end);
10218 multibuffer_snapshot
10219 .buffer_anchor_range_to_anchor_range(range)
10220 .unwrap()
10221 }));
10222 },
10223 );
10224 editor.nav_history = nav_history;
10225 });
10226 }
10227 })
10228 .ok();
10229 });
10230 }
10231
10232 fn selection_replacement_ranges(
10233 &self,
10234 range: Range<MultiBufferOffsetUtf16>,
10235 cx: &mut App,
10236 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
10237 let selections = self
10238 .selections
10239 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
10240 let newest_selection = selections
10241 .iter()
10242 .max_by_key(|selection| selection.id)
10243 .unwrap();
10244 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
10245 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
10246 let snapshot = self.buffer.read(cx).read(cx);
10247 selections
10248 .into_iter()
10249 .map(|mut selection| {
10250 selection.start.0.0 =
10251 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
10252 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
10253 snapshot.clip_offset_utf16(selection.start, Bias::Left)
10254 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
10255 })
10256 .collect()
10257 }
10258
10259 fn report_editor_event(
10260 &self,
10261 reported_event: ReportEditorEvent,
10262 file_extension: Option<String>,
10263 cx: &App,
10264 ) {
10265 if cfg!(any(test, feature = "test-support")) {
10266 return;
10267 }
10268
10269 let Some(project) = &self.project else { return };
10270
10271 // If None, we are in a file without an extension
10272 let file = self
10273 .buffer
10274 .read(cx)
10275 .as_singleton()
10276 .and_then(|b| b.read(cx).file());
10277 let file_extension = file_extension.or(file
10278 .as_ref()
10279 .and_then(|file| Path::new(file.file_name(cx)).extension())
10280 .and_then(|e| e.to_str())
10281 .map(|a| a.to_string()));
10282
10283 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
10284 .map(|vim_mode| vim_mode.0)
10285 .unwrap_or(false);
10286
10287 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
10288 let copilot_enabled = edit_predictions_provider
10289 == language::language_settings::EditPredictionProvider::Copilot;
10290 let copilot_enabled_for_language = self
10291 .buffer
10292 .read(cx)
10293 .language_settings(cx)
10294 .show_edit_predictions;
10295
10296 let project = project.read(cx);
10297 let event_type = reported_event.event_type();
10298
10299 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
10300 telemetry::event!(
10301 event_type,
10302 type = if auto_saved {"autosave"} else {"manual"},
10303 file_extension,
10304 vim_mode,
10305 copilot_enabled,
10306 copilot_enabled_for_language,
10307 edit_predictions_provider,
10308 is_via_ssh = project.is_via_remote_server(),
10309 );
10310 } else {
10311 telemetry::event!(
10312 event_type,
10313 file_extension,
10314 vim_mode,
10315 copilot_enabled,
10316 copilot_enabled_for_language,
10317 edit_predictions_provider,
10318 is_via_ssh = project.is_via_remote_server(),
10319 );
10320 };
10321 }
10322
10323 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
10324 /// with each line being an array of {text, highlight} objects.
10325 fn copy_highlight_json(
10326 &mut self,
10327 _: &CopyHighlightJson,
10328 _: &mut Window,
10329 cx: &mut Context<Self>,
10330 ) {
10331 #[derive(Serialize)]
10332 struct Chunk<'a> {
10333 text: String,
10334 highlight: Option<&'a str>,
10335 }
10336
10337 let snapshot = self.buffer.read(cx).snapshot(cx);
10338 let mut selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
10339 let max_point = snapshot.max_point();
10340
10341 let range = if self.selections.line_mode() {
10342 selection.start = Point::new(selection.start.row, 0);
10343 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10344 selection.goal = SelectionGoal::None;
10345 selection.range()
10346 } else if selection.is_empty() {
10347 Point::new(0, 0)..max_point
10348 } else {
10349 selection.range()
10350 };
10351
10352 let chunks = snapshot.chunks(
10353 range,
10354 LanguageAwareStyling {
10355 tree_sitter: true,
10356 diagnostics: true,
10357 },
10358 );
10359 let mut lines = Vec::new();
10360 let mut line: VecDeque<Chunk> = VecDeque::new();
10361
10362 let Some(style) = self.style.as_ref() else {
10363 return;
10364 };
10365
10366 for chunk in chunks {
10367 let highlight = chunk
10368 .syntax_highlight_id
10369 .and_then(|id| style.syntax.get_capture_name(id));
10370
10371 let mut chunk_lines = chunk.text.split('\n').peekable();
10372 while let Some(text) = chunk_lines.next() {
10373 let mut merged_with_last_token = false;
10374 if let Some(last_token) = line.back_mut()
10375 && last_token.highlight == highlight
10376 {
10377 last_token.text.push_str(text);
10378 merged_with_last_token = true;
10379 }
10380
10381 if !merged_with_last_token {
10382 line.push_back(Chunk {
10383 text: text.into(),
10384 highlight,
10385 });
10386 }
10387
10388 if chunk_lines.peek().is_some() {
10389 if line.len() > 1 && line.front().unwrap().text.is_empty() {
10390 line.pop_front();
10391 }
10392 if line.len() > 1 && line.back().unwrap().text.is_empty() {
10393 line.pop_back();
10394 }
10395
10396 lines.push(mem::take(&mut line));
10397 }
10398 }
10399 }
10400
10401 if line.iter().any(|chunk| !chunk.text.is_empty()) {
10402 lines.push(line);
10403 }
10404
10405 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
10406 return;
10407 };
10408 cx.write_to_clipboard(ClipboardItem::new_string(lines));
10409 }
10410
10411 pub fn open_context_menu(
10412 &mut self,
10413 _: &OpenContextMenu,
10414 window: &mut Window,
10415 cx: &mut Context<Self>,
10416 ) {
10417 self.request_autoscroll(Autoscroll::newest(), cx);
10418 let position = self
10419 .selections
10420 .newest_display(&self.display_snapshot(cx))
10421 .start;
10422 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
10423 }
10424
10425 pub fn is_focused(&self, window: &Window) -> bool {
10426 self.focus_handle.is_focused(window)
10427 }
10428
10429 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10430 cx.emit(EditorEvent::Focused);
10431
10432 if let Some(descendant) = self
10433 .last_focused_descendant
10434 .take()
10435 .and_then(|descendant| descendant.upgrade())
10436 {
10437 window.focus(&descendant, cx);
10438 } else {
10439 if let Some(blame) = self.blame.as_ref() {
10440 blame.update(cx, GitBlame::focus)
10441 }
10442
10443 self.blink_manager.update(cx, BlinkManager::enable);
10444 self.show_cursor_names(window, cx);
10445 self.buffer.update(cx, |buffer, cx| {
10446 buffer.finalize_last_transaction(cx);
10447 if self.leader_id.is_none() {
10448 buffer.set_active_selections(
10449 &self.selections.disjoint_anchors_arc(),
10450 self.selections.line_mode(),
10451 self.cursor_shape,
10452 cx,
10453 );
10454 }
10455 });
10456
10457 if cx.is_cursor_visible()
10458 && let Some(position_map) = self.last_position_map.clone()
10459 {
10460 EditorElement::mouse_moved(
10461 self,
10462 &MouseMoveEvent {
10463 position: window.mouse_position(),
10464 pressed_button: None,
10465 modifiers: window.modifiers(),
10466 },
10467 &position_map,
10468 None,
10469 window,
10470 cx,
10471 );
10472 }
10473 }
10474 }
10475
10476 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
10477 cx.emit(EditorEvent::FocusedIn)
10478 }
10479
10480 fn handle_focus_out(
10481 &mut self,
10482 event: FocusOutEvent,
10483 _window: &mut Window,
10484 cx: &mut Context<Self>,
10485 ) {
10486 if event.blurred != self.focus_handle {
10487 self.last_focused_descendant = Some(event.blurred);
10488 }
10489 self.selection_drag_state = SelectionDragState::None;
10490 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
10491 }
10492
10493 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10494 self.blink_manager.update(cx, BlinkManager::disable);
10495 self.buffer
10496 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
10497
10498 if let Some(blame) = self.blame.as_ref() {
10499 blame.update(cx, GitBlame::blur)
10500 }
10501 if !self.hover_state.focused(window, cx) {
10502 hide_hover(self, cx);
10503 }
10504 if !self
10505 .context_menu
10506 .borrow()
10507 .as_ref()
10508 .is_some_and(|context_menu| context_menu.focused(window, cx))
10509 {
10510 self.hide_context_menu(window, cx);
10511 }
10512 self.take_active_edit_prediction(true, cx);
10513 cx.emit(EditorEvent::Blurred);
10514 cx.notify();
10515 }
10516
10517 pub fn register_action_renderer(
10518 &mut self,
10519 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
10520 ) -> Subscription {
10521 let id = self.next_editor_action_id.post_inc();
10522 self.editor_actions
10523 .borrow_mut()
10524 .insert(id, Box::new(listener));
10525
10526 let editor_actions = self.editor_actions.clone();
10527 Subscription::new(move || {
10528 editor_actions.borrow_mut().remove(&id);
10529 })
10530 }
10531
10532 pub fn register_action<A: Action>(
10533 &mut self,
10534 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
10535 ) -> Subscription {
10536 let id = self.next_editor_action_id.post_inc();
10537 let listener = Arc::new(listener);
10538 self.editor_actions.borrow_mut().insert(
10539 id,
10540 Box::new(move |_, window, _| {
10541 let listener = listener.clone();
10542 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
10543 let action = action.downcast_ref().unwrap();
10544 if phase == DispatchPhase::Bubble {
10545 listener(action, window, cx)
10546 }
10547 })
10548 }),
10549 );
10550
10551 let editor_actions = self.editor_actions.clone();
10552 Subscription::new(move || {
10553 editor_actions.borrow_mut().remove(&id);
10554 })
10555 }
10556
10557 pub fn file_header_size(&self) -> u32 {
10558 FILE_HEADER_HEIGHT
10559 }
10560
10561 pub fn restore(
10562 &mut self,
10563 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10564 window: &mut Window,
10565 cx: &mut Context<Self>,
10566 ) {
10567 self.buffer().update(cx, |multi_buffer, cx| {
10568 for (buffer_id, changes) in revert_changes {
10569 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
10570 buffer.update(cx, |buffer, cx| {
10571 buffer.edit(
10572 changes
10573 .into_iter()
10574 .map(|(range, text)| (range, text.to_string())),
10575 None,
10576 cx,
10577 );
10578 });
10579 }
10580 }
10581 });
10582 let selections = self
10583 .selections
10584 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
10585 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10586 s.select(selections);
10587 });
10588 }
10589
10590 pub fn to_pixel_point(
10591 &mut self,
10592 source: Anchor,
10593 editor_snapshot: &EditorSnapshot,
10594 window: &mut Window,
10595 cx: &mut App,
10596 ) -> Option<gpui::Point<Pixels>> {
10597 let source_point = source.to_display_point(editor_snapshot);
10598 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
10599 }
10600
10601 pub fn display_to_pixel_point(
10602 &mut self,
10603 source: DisplayPoint,
10604 editor_snapshot: &EditorSnapshot,
10605 window: &mut Window,
10606 cx: &mut App,
10607 ) -> Option<gpui::Point<Pixels>> {
10608 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
10609 let text_layout_details = self.text_layout_details(window, cx);
10610 let mut scroll_top = text_layout_details
10611 .scroll_anchor
10612 .scroll_position(editor_snapshot)
10613 .y;
10614 if !line_height.is_zero() {
10615 scroll_top =
10616 window.pixel_snap_f64(scroll_top * f64::from(line_height)) / f64::from(line_height);
10617 }
10618
10619 if source.row().as_f64() < scroll_top.floor() {
10620 return None;
10621 }
10622 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
10623 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
10624 Some(gpui::Point::new(source_x, source_y))
10625 }
10626
10627 pub fn register_addon<T: Addon>(&mut self, instance: T) {
10628 if self.mode.is_minimap() {
10629 return;
10630 }
10631 self.addons
10632 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
10633 }
10634
10635 pub fn unregister_addon<T: Addon>(&mut self) {
10636 self.addons.remove(&std::any::TypeId::of::<T>());
10637 }
10638
10639 pub fn addon<T: Addon>(&self) -> Option<&T> {
10640 let type_id = std::any::TypeId::of::<T>();
10641 self.addons
10642 .get(&type_id)
10643 .and_then(|item| item.to_any().downcast_ref::<T>())
10644 }
10645
10646 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
10647 let type_id = std::any::TypeId::of::<T>();
10648 self.addons
10649 .get_mut(&type_id)
10650 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
10651 }
10652
10653 fn character_dimensions(&self, window: &mut Window, cx: &mut App) -> CharacterDimensions {
10654 let text_layout_details = self.text_layout_details(window, cx);
10655 let style = &text_layout_details.editor_style;
10656 let font_id = window.text_system().resolve_font(&style.text.font());
10657 let font_size = style.text.font_size.to_pixels(window.rem_size());
10658 let line_height = style.text.line_height_in_pixels(window.rem_size());
10659 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
10660 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
10661
10662 CharacterDimensions {
10663 em_width,
10664 em_advance,
10665 line_height,
10666 }
10667 }
10668
10669 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
10670 self.load_diff_task.clone()
10671 }
10672
10673 fn read_metadata_from_db(
10674 &mut self,
10675 item_id: u64,
10676 workspace_id: WorkspaceId,
10677 window: &mut Window,
10678 cx: &mut Context<Editor>,
10679 ) {
10680 if self.buffer_kind(cx) == ItemBufferKind::Singleton
10681 && !self.mode.is_minimap()
10682 && WorkspaceSettings::get(None, cx).restore_on_startup
10683 != RestoreOnStartupBehavior::EmptyTab
10684 {
10685 let buffer_snapshot = OnceCell::new();
10686
10687 // Get file path for path-based fold lookup
10688 let file_path: Option<Arc<Path>> =
10689 self.buffer().read(cx).as_singleton().and_then(|buffer| {
10690 project::File::from_dyn(buffer.read(cx).file())
10691 .map(|file| Arc::from(file.abs_path(cx)))
10692 });
10693
10694 // Try file_folds (path-based) first, fallback to editor_folds (migration)
10695 let db = EditorDb::global(cx);
10696 let (folds, needs_migration) = if let Some(ref path) = file_path {
10697 if let Some(folds) = db.get_file_folds(workspace_id, path).log_err()
10698 && !folds.is_empty()
10699 {
10700 (Some(folds), false)
10701 } else if let Some(folds) = db.get_editor_folds(item_id, workspace_id).log_err()
10702 && !folds.is_empty()
10703 {
10704 // Found old editor_folds data, will migrate to file_folds
10705 (Some(folds), true)
10706 } else {
10707 (None, false)
10708 }
10709 } else {
10710 // No file path, try editor_folds as fallback
10711 let folds = db.get_editor_folds(item_id, workspace_id).log_err();
10712 (folds.filter(|f| !f.is_empty()), false)
10713 };
10714
10715 if let Some(folds) = folds {
10716 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
10717 let snapshot_len = snapshot.len().0;
10718
10719 // Helper: search for fingerprint in buffer, return offset if found
10720 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
10721 // Ensure we start at a character boundary (defensive)
10722 let search_start = snapshot
10723 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
10724 .0;
10725 let search_end = snapshot_len.saturating_sub(fingerprint.len());
10726
10727 let mut byte_offset = search_start;
10728 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
10729 if byte_offset > search_end {
10730 break;
10731 }
10732 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
10733 return Some(byte_offset);
10734 }
10735 byte_offset += ch.len_utf8();
10736 }
10737 None
10738 };
10739
10740 // Track search position to handle duplicate fingerprints correctly.
10741 // Folds are stored in document order, so we advance after each match.
10742 let mut search_start = 0usize;
10743
10744 // Collect db_folds for migration (only folds with valid fingerprints)
10745 let mut db_folds_for_migration: Vec<(usize, usize, String, String)> = Vec::new();
10746
10747 let valid_folds: Vec<_> = folds
10748 .into_iter()
10749 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
10750 // Skip folds without fingerprints (old data before migration)
10751 let sfp = start_fp?;
10752 let efp = end_fp?;
10753 let efp_len = efp.len();
10754
10755 // Fast path: check if fingerprints match at stored offsets
10756 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
10757 let start_matches = stored_start < snapshot_len
10758 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
10759 let efp_check_pos = stored_end.saturating_sub(efp_len);
10760 let end_matches = efp_check_pos >= stored_start
10761 && stored_end <= snapshot_len
10762 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
10763
10764 let (new_start, new_end) = if start_matches && end_matches {
10765 // Offsets unchanged, use stored values
10766 (stored_start, stored_end)
10767 } else if sfp == efp {
10768 // Short fold: identical fingerprints can only match once per search
10769 // Use stored fold length to compute new_end
10770 let new_start = find_fingerprint(&sfp, search_start)?;
10771 let fold_len = stored_end - stored_start;
10772 let new_end = new_start + fold_len;
10773 (new_start, new_end)
10774 } else {
10775 // Slow path: search for fingerprints in buffer
10776 let new_start = find_fingerprint(&sfp, search_start)?;
10777 // Search for end_fp after start, then add efp_len to get actual fold end
10778 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
10779 let new_end = efp_pos + efp_len;
10780 (new_start, new_end)
10781 };
10782
10783 // Advance search position for next fold
10784 search_start = new_end;
10785
10786 // Validate fold makes sense (end must be after start)
10787 if new_end <= new_start {
10788 return None;
10789 }
10790
10791 // Collect for migration if needed
10792 if needs_migration {
10793 db_folds_for_migration.push((new_start, new_end, sfp, efp));
10794 }
10795
10796 Some(
10797 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
10798 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
10799 )
10800 })
10801 .collect();
10802
10803 if !valid_folds.is_empty() {
10804 self.fold_ranges(valid_folds, false, window, cx);
10805
10806 // Migrate from editor_folds to file_folds if we loaded from old table
10807 if needs_migration {
10808 if let Some(ref path) = file_path {
10809 let path = path.clone();
10810 let db = EditorDb::global(cx);
10811 cx.spawn(async move |_, _| {
10812 db.save_file_folds(workspace_id, path, db_folds_for_migration)
10813 .await
10814 .log_err();
10815 })
10816 .detach();
10817 }
10818 }
10819 }
10820 }
10821
10822 if let Some(selections) = db.get_editor_selections(item_id, workspace_id).log_err()
10823 && !selections.is_empty()
10824 {
10825 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
10826 // skip adding the initial selection to selection history
10827 self.selection_history.mode = SelectionHistoryMode::Skipping;
10828 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10829 s.select_ranges(selections.into_iter().map(|(start, end)| {
10830 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
10831 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
10832 }));
10833 });
10834 self.selection_history.mode = SelectionHistoryMode::Normal;
10835 };
10836 }
10837
10838 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
10839 }
10840
10841 pub(crate) fn lsp_data_enabled(&self) -> bool {
10842 self.enable_lsp_data && self.mode().is_full()
10843 }
10844
10845 fn update_lsp_data(
10846 &mut self,
10847 for_buffer: Option<BufferId>,
10848 window: &mut Window,
10849 cx: &mut Context<'_, Self>,
10850 ) {
10851 if !self.lsp_data_enabled() {
10852 return;
10853 }
10854
10855 if let Some(buffer_id) = for_buffer {
10856 self.pull_diagnostics(buffer_id, window, cx);
10857 }
10858 self.refresh_semantic_tokens(for_buffer, false, cx);
10859 self.refresh_document_colors(for_buffer, window, cx);
10860 self.refresh_document_links(for_buffer, cx);
10861 self.refresh_folding_ranges(for_buffer, window, cx);
10862 self.refresh_code_lenses(for_buffer, window, cx);
10863 self.refresh_document_symbols(for_buffer, cx);
10864 }
10865
10866 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
10867 if !self.lsp_data_enabled() {
10868 return;
10869 }
10870 let visible_buffers: Vec<_> = self
10871 .visible_buffers(cx)
10872 .into_iter()
10873 .filter(|buffer| self.is_lsp_relevant(buffer.read(cx).file(), cx))
10874 .collect();
10875 for visible_buffer in visible_buffers {
10876 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
10877 }
10878 }
10879
10880 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
10881 if !self.lsp_data_enabled() {
10882 return;
10883 }
10884
10885 if !self.registered_buffers.contains_key(&buffer_id)
10886 && let Some(project) = self.project.as_ref()
10887 {
10888 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
10889 project.update(cx, |project, cx| {
10890 self.registered_buffers.insert(
10891 buffer_id,
10892 project.register_buffer_with_language_servers(&buffer, cx),
10893 );
10894 });
10895 } else {
10896 self.registered_buffers.remove(&buffer_id);
10897 }
10898 }
10899 }
10900
10901 fn create_style(&self, cx: &App) -> EditorStyle {
10902 let settings = ThemeSettings::get_global(cx);
10903
10904 let mut text_style = match self.mode {
10905 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
10906 color: cx.theme().colors().editor_foreground,
10907 font_family: settings.ui_font.family.clone(),
10908 font_features: settings.ui_font.features.clone(),
10909 font_fallbacks: settings.ui_font.fallbacks.clone(),
10910 font_size: rems(0.875).into(),
10911 font_weight: settings.ui_font.weight,
10912 line_height: relative(settings.buffer_line_height.value()),
10913 ..Default::default()
10914 },
10915 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
10916 color: cx.theme().colors().editor_foreground,
10917 font_family: settings.buffer_font.family.clone(),
10918 font_features: settings.buffer_font.features.clone(),
10919 font_fallbacks: settings.buffer_font.fallbacks.clone(),
10920 font_size: settings.buffer_font_size(cx).into(),
10921 font_weight: settings.buffer_font.weight,
10922 line_height: relative(settings.buffer_line_height.value()),
10923 ..Default::default()
10924 },
10925 };
10926 if let Some(text_style_refinement) = &self.text_style_refinement {
10927 text_style.refine(text_style_refinement)
10928 }
10929
10930 let background = match self.mode {
10931 EditorMode::SingleLine => cx.theme().system().transparent,
10932 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
10933 EditorMode::Full { .. } => cx.theme().colors().editor_background,
10934 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
10935 };
10936
10937 EditorStyle {
10938 background,
10939 border: cx.theme().colors().border,
10940 local_player: cx.theme().players().local(),
10941 text: text_style,
10942 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
10943 syntax: cx.theme().syntax().clone(),
10944 status: cx.theme().status().clone(),
10945 inlay_hints_style: make_inlay_hints_style(cx),
10946 edit_prediction_styles: make_suggestion_styles(cx),
10947 unnecessary_code_fade: settings.unnecessary_code_fade,
10948 show_underlines: self.diagnostics_enabled(),
10949 }
10950 }
10951
10952 fn breadcrumbs_inner(&self, cx: &App) -> Option<Vec<HighlightedText>> {
10953 let multi_buffer = self.buffer().read(cx);
10954 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
10955 let mut breadcrumbs = if let Some(buffer) = multi_buffer.as_singleton() {
10956 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
10957 buffer
10958 .read(cx)
10959 .snapshot()
10960 .resolve_file_path(
10961 self.project
10962 .as_ref()
10963 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
10964 .unwrap_or_default(),
10965 cx,
10966 )
10967 .unwrap_or_else(|| multi_buffer.title(cx).to_string())
10968 });
10969 vec![HighlightedText {
10970 text: text.into(),
10971 highlights: vec![],
10972 }]
10973 } else {
10974 Vec::new()
10975 };
10976
10977 if let Some((buffer_id, symbols)) = self.outline_symbols_at_cursor.as_ref()
10978 && multi_buffer.buffer(*buffer_id).is_some()
10979 {
10980 breadcrumbs.extend(symbols.iter().map(|symbol| HighlightedText {
10981 text: symbol.text.clone(),
10982 highlights: symbol.highlight_ranges.clone(),
10983 }));
10984 }
10985
10986 if breadcrumbs.is_empty() {
10987 None
10988 } else {
10989 Some(breadcrumbs)
10990 }
10991 }
10992
10993 fn disable_lsp_data(&mut self) {
10994 self.enable_lsp_data = false;
10995 }
10996
10997 fn disable_runnables(&mut self) {
10998 self.enable_runnables = false;
10999 }
11000
11001 pub fn disable_code_lens(&mut self, cx: &mut Context<Self>) {
11002 self.enable_code_lens = false;
11003 self.clear_code_lenses(cx);
11004 }
11005
11006 pub fn disable_mouse_wheel_zoom(&mut self) {
11007 self.enable_mouse_wheel_zoom = false;
11008 }
11009
11010 fn update_data_on_scroll(
11011 &mut self,
11012 debounce: bool,
11013 window: &mut Window,
11014 cx: &mut Context<'_, Self>,
11015 ) {
11016 if debounce {
11017 self.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
11018 cx.background_executor()
11019 .timer(Duration::from_millis(50))
11020 .await;
11021 editor
11022 .update_in(cx, |editor, window, cx| {
11023 editor.do_update_data_on_scroll(window, cx);
11024 })
11025 .ok();
11026 });
11027 } else {
11028 self.post_scroll_update = Task::ready(());
11029 self.do_update_data_on_scroll(window, cx);
11030 }
11031 }
11032
11033 fn do_update_data_on_scroll(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) {
11034 self.register_visible_buffers(cx);
11035 self.colorize_brackets(false, cx);
11036 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
11037 self.resolve_visible_code_lenses(cx);
11038
11039 if !self.buffer().read(cx).is_singleton() || self.needs_initial_data_update {
11040 self.needs_initial_data_update = false;
11041 self.update_lsp_data(None, window, cx);
11042 self.refresh_runnables(None, window, cx);
11043 }
11044 }
11045
11046 /// Returns the current cursor's vertical offset, in display rows, from the
11047 /// top of the visible viewport.
11048 /// Returns `None` if the cursor is not currently on screen.
11049 pub fn cursor_top_offset(&self, cx: &mut Context<Self>) -> Option<ScrollOffset> {
11050 let visible = self.visible_line_count()?;
11051 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11052 let scroll_top = self.scroll_manager.scroll_position(&display_map, cx).y;
11053 let cursor_display_row = self
11054 .selections
11055 .newest::<Point>(&display_map)
11056 .head()
11057 .to_display_point(&display_map)
11058 .row()
11059 .as_f64();
11060
11061 match cursor_display_row - scroll_top {
11062 offset if offset < 0.0 || offset >= visible => None,
11063 offset => Some(offset),
11064 }
11065 }
11066}
11067
11068fn process_completion_for_edit(
11069 completion: &Completion,
11070 intent: CompletionIntent,
11071 buffer: &Entity<Buffer>,
11072 cursor_position: &text::Anchor,
11073 cx: &mut Context<Editor>,
11074) -> CompletionEdit {
11075 let buffer = buffer.read(cx);
11076 let buffer_snapshot = buffer.snapshot();
11077 let (snippet, new_text) = if completion.is_snippet() {
11078 let mut snippet_source = completion.new_text.clone();
11079 // Workaround for typescript language server issues so that methods don't expand within
11080 // strings and functions with type expressions. The previous point is used because the query
11081 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
11082 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
11083 let previous_point = if previous_point.column > 0 {
11084 cursor_position.to_previous_offset(&buffer_snapshot)
11085 } else {
11086 cursor_position.to_offset(&buffer_snapshot)
11087 };
11088 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
11089 && scope.prefers_label_for_snippet_in_completion()
11090 && let Some(label) = completion.label()
11091 && matches!(
11092 completion.kind(),
11093 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
11094 )
11095 {
11096 snippet_source = label;
11097 }
11098 match Snippet::parse(&snippet_source).log_err() {
11099 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
11100 None => (None, completion.new_text.clone()),
11101 }
11102 } else {
11103 (None, completion.new_text.clone())
11104 };
11105
11106 let mut range_to_replace = {
11107 let replace_range = &completion.replace_range;
11108 if let CompletionSource::Lsp {
11109 insert_range: Some(insert_range),
11110 ..
11111 } = &completion.source
11112 {
11113 debug_assert_eq!(
11114 insert_range.start, replace_range.start,
11115 "insert_range and replace_range should start at the same position"
11116 );
11117 debug_assert!(
11118 insert_range
11119 .start
11120 .cmp(cursor_position, &buffer_snapshot)
11121 .is_le(),
11122 "insert_range should start before or at cursor position"
11123 );
11124 debug_assert!(
11125 replace_range
11126 .start
11127 .cmp(cursor_position, &buffer_snapshot)
11128 .is_le(),
11129 "replace_range should start before or at cursor position"
11130 );
11131
11132 let should_replace = match intent {
11133 CompletionIntent::CompleteWithInsert => false,
11134 CompletionIntent::CompleteWithReplace => true,
11135 CompletionIntent::Complete | CompletionIntent::Compose => {
11136 let insert_mode = LanguageSettings::for_buffer(&buffer, cx)
11137 .completions
11138 .lsp_insert_mode;
11139 match insert_mode {
11140 LspInsertMode::Insert => false,
11141 LspInsertMode::Replace => true,
11142 LspInsertMode::ReplaceSubsequence => {
11143 let mut text_to_replace = buffer.chars_for_range(
11144 buffer.anchor_before(replace_range.start)
11145 ..buffer.anchor_after(replace_range.end),
11146 );
11147 let mut current_needle = text_to_replace.next();
11148 for haystack_ch in completion.label.text.chars() {
11149 if let Some(needle_ch) = current_needle
11150 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
11151 {
11152 current_needle = text_to_replace.next();
11153 }
11154 }
11155 current_needle.is_none()
11156 }
11157 LspInsertMode::ReplaceSuffix => {
11158 if replace_range
11159 .end
11160 .cmp(cursor_position, &buffer_snapshot)
11161 .is_gt()
11162 {
11163 let range_after_cursor = *cursor_position..replace_range.end;
11164 let text_after_cursor = buffer
11165 .text_for_range(
11166 buffer.anchor_before(range_after_cursor.start)
11167 ..buffer.anchor_after(range_after_cursor.end),
11168 )
11169 .collect::<String>()
11170 .to_ascii_lowercase();
11171 completion
11172 .label
11173 .text
11174 .to_ascii_lowercase()
11175 .ends_with(&text_after_cursor)
11176 } else {
11177 true
11178 }
11179 }
11180 }
11181 }
11182 };
11183
11184 if should_replace {
11185 replace_range.clone()
11186 } else {
11187 insert_range.clone()
11188 }
11189 } else {
11190 replace_range.clone()
11191 }
11192 };
11193
11194 if range_to_replace
11195 .end
11196 .cmp(cursor_position, &buffer_snapshot)
11197 .is_lt()
11198 {
11199 range_to_replace.end = *cursor_position;
11200 }
11201
11202 CompletionEdit {
11203 new_text,
11204 replace_range: range_to_replace,
11205 snippet,
11206 }
11207}
11208
11209struct CompletionEdit {
11210 new_text: String,
11211 replace_range: Range<text::Anchor>,
11212 snippet: Option<Snippet>,
11213}
11214
11215pub trait CollaborationHub {
11216 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
11217 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
11218 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
11219}
11220
11221impl CollaborationHub for Entity<Project> {
11222 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
11223 self.read(cx).collaborators()
11224 }
11225
11226 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
11227 self.read(cx).user_store().read(cx).participant_indices()
11228 }
11229
11230 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
11231 let this = self.read(cx);
11232 let user_ids = this.collaborators().values().map(|c| c.user_id);
11233 this.user_store().read(cx).participant_names(user_ids, cx)
11234 }
11235}
11236
11237pub trait SemanticsProvider {
11238 fn hover(
11239 &self,
11240 buffer: &Entity<Buffer>,
11241 position: text::Anchor,
11242 cx: &mut App,
11243 ) -> Option<Task<Option<Vec<project::Hover>>>>;
11244
11245 fn inline_values(
11246 &self,
11247 buffer_handle: Entity<Buffer>,
11248 range: Range<text::Anchor>,
11249 cx: &mut App,
11250 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
11251
11252 fn applicable_inlay_chunks(
11253 &self,
11254 buffer: &Entity<Buffer>,
11255 ranges: &[Range<text::Anchor>],
11256 cx: &mut App,
11257 ) -> Vec<Range<BufferRow>>;
11258
11259 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
11260
11261 fn inlay_hints(
11262 &self,
11263 invalidate: InvalidationStrategy,
11264 buffer: Entity<Buffer>,
11265 ranges: Vec<Range<text::Anchor>>,
11266 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
11267 cx: &mut App,
11268 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
11269
11270 fn semantic_tokens(
11271 &self,
11272 buffer: Entity<Buffer>,
11273 cx: &mut App,
11274 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>>;
11275
11276 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
11277
11278 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
11279
11280 fn document_highlights(
11281 &self,
11282 buffer: &Entity<Buffer>,
11283 position: text::Anchor,
11284 cx: &mut App,
11285 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
11286
11287 fn definitions(
11288 &self,
11289 buffer: &Entity<Buffer>,
11290 position: text::Anchor,
11291 kind: GotoDefinitionKind,
11292 cx: &mut App,
11293 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
11294
11295 fn range_for_rename(
11296 &self,
11297 buffer: &Entity<Buffer>,
11298 position: text::Anchor,
11299 cx: &mut App,
11300 ) -> Task<Result<Option<Range<text::Anchor>>>>;
11301
11302 fn perform_rename(
11303 &self,
11304 buffer: &Entity<Buffer>,
11305 position: text::Anchor,
11306 new_name: String,
11307 cx: &mut App,
11308 ) -> Option<Task<Result<ProjectTransaction>>>;
11309}
11310
11311impl SemanticsProvider for WeakEntity<Project> {
11312 fn hover(
11313 &self,
11314 buffer: &Entity<Buffer>,
11315 position: text::Anchor,
11316 cx: &mut App,
11317 ) -> Option<Task<Option<Vec<project::Hover>>>> {
11318 self.update(cx, |project, cx| project.hover(buffer, position, cx))
11319 .ok()
11320 }
11321
11322 fn document_highlights(
11323 &self,
11324 buffer: &Entity<Buffer>,
11325 position: text::Anchor,
11326 cx: &mut App,
11327 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
11328 self.update(cx, |project, cx| {
11329 project.document_highlights(buffer, position, cx)
11330 })
11331 .ok()
11332 }
11333
11334 fn definitions(
11335 &self,
11336 buffer: &Entity<Buffer>,
11337 position: text::Anchor,
11338 kind: GotoDefinitionKind,
11339 cx: &mut App,
11340 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
11341 self.update(cx, |project, cx| match kind {
11342 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
11343 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
11344 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
11345 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
11346 })
11347 .ok()
11348 }
11349
11350 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
11351 self.update(cx, |project, cx| {
11352 if project
11353 .active_debug_session(cx)
11354 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
11355 {
11356 return true;
11357 }
11358
11359 buffer.update(cx, |buffer, cx| {
11360 project.any_language_server_supports_inlay_hints(buffer, cx)
11361 })
11362 })
11363 .unwrap_or(false)
11364 }
11365
11366 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
11367 self.update(cx, |project, cx| {
11368 buffer.update(cx, |buffer, cx| {
11369 project.any_language_server_supports_semantic_tokens(buffer, cx)
11370 })
11371 })
11372 .unwrap_or(false)
11373 }
11374
11375 fn inline_values(
11376 &self,
11377 buffer_handle: Entity<Buffer>,
11378 range: Range<text::Anchor>,
11379 cx: &mut App,
11380 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
11381 self.update(cx, |project, cx| {
11382 let (session, active_stack_frame) = project.active_debug_session(cx)?;
11383
11384 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
11385 })
11386 .ok()
11387 .flatten()
11388 }
11389
11390 fn applicable_inlay_chunks(
11391 &self,
11392 buffer: &Entity<Buffer>,
11393 ranges: &[Range<text::Anchor>],
11394 cx: &mut App,
11395 ) -> Vec<Range<BufferRow>> {
11396 self.update(cx, |project, cx| {
11397 project.lsp_store().update(cx, |lsp_store, cx| {
11398 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
11399 })
11400 })
11401 .unwrap_or_default()
11402 }
11403
11404 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
11405 self.update(cx, |project, cx| {
11406 project.lsp_store().update(cx, |lsp_store, _| {
11407 lsp_store.invalidate_inlay_hints(for_buffers)
11408 })
11409 })
11410 .ok();
11411 }
11412
11413 fn inlay_hints(
11414 &self,
11415 invalidate: InvalidationStrategy,
11416 buffer: Entity<Buffer>,
11417 ranges: Vec<Range<text::Anchor>>,
11418 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
11419 cx: &mut App,
11420 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
11421 self.update(cx, |project, cx| {
11422 project.lsp_store().update(cx, |lsp_store, cx| {
11423 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
11424 })
11425 })
11426 .ok()
11427 }
11428
11429 fn semantic_tokens(
11430 &self,
11431 buffer: Entity<Buffer>,
11432 cx: &mut App,
11433 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>> {
11434 self.update(cx, |this, cx| {
11435 this.lsp_store()
11436 .update(cx, |lsp_store, cx| lsp_store.semantic_tokens(buffer, cx))
11437 })
11438 .ok()
11439 }
11440
11441 fn range_for_rename(
11442 &self,
11443 buffer: &Entity<Buffer>,
11444 position: text::Anchor,
11445 cx: &mut App,
11446 ) -> Task<Result<Option<Range<text::Anchor>>>> {
11447 let Some(this) = self.upgrade() else {
11448 return Task::ready(Ok(None));
11449 };
11450
11451 this.update(cx, |project, cx| {
11452 let buffer = buffer.clone();
11453 let task = project.prepare_rename(buffer.clone(), position, cx);
11454 cx.spawn(async move |_, cx| {
11455 Ok(match task.await? {
11456 PrepareRenameResponse::Success(range) => Some(range),
11457 PrepareRenameResponse::InvalidPosition => None,
11458 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
11459 // Fallback on using TreeSitter info to determine identifier range
11460 buffer.read_with(cx, |buffer, _| {
11461 let snapshot = buffer.snapshot();
11462 let (range, kind) = snapshot.surrounding_word(position, None);
11463 if kind != Some(CharKind::Word) {
11464 return None;
11465 }
11466 Some(
11467 snapshot.anchor_before(range.start)
11468 ..snapshot.anchor_after(range.end),
11469 )
11470 })
11471 }
11472 })
11473 })
11474 })
11475 }
11476
11477 fn perform_rename(
11478 &self,
11479 buffer: &Entity<Buffer>,
11480 position: text::Anchor,
11481 new_name: String,
11482 cx: &mut App,
11483 ) -> Option<Task<Result<ProjectTransaction>>> {
11484 self.update(cx, |project, cx| {
11485 project.perform_rename(buffer.clone(), position, new_name, cx)
11486 })
11487 .ok()
11488 }
11489}
11490
11491fn consume_contiguous_rows(
11492 contiguous_row_selections: &mut Vec<Selection<Point>>,
11493 selection: &Selection<Point>,
11494 display_map: &DisplaySnapshot,
11495 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
11496) -> (MultiBufferRow, MultiBufferRow) {
11497 contiguous_row_selections.push(selection.clone());
11498 let start_row = starting_row(selection, display_map);
11499 let mut end_row = ending_row(selection, display_map);
11500
11501 while let Some(next_selection) = selections.peek() {
11502 if next_selection.start.row <= end_row.0 {
11503 end_row = ending_row(next_selection, display_map);
11504 contiguous_row_selections.push(selections.next().unwrap().clone());
11505 } else {
11506 break;
11507 }
11508 }
11509 (start_row, end_row)
11510}
11511
11512fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
11513 if selection.start.column > 0 {
11514 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
11515 } else {
11516 MultiBufferRow(selection.start.row)
11517 }
11518}
11519
11520fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
11521 if next_selection.end.column > 0 || next_selection.is_empty() {
11522 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
11523 } else {
11524 MultiBufferRow(next_selection.end.row)
11525 }
11526}
11527
11528impl EditorSnapshot {
11529 pub fn remote_selections_in_range<'a>(
11530 &'a self,
11531 range: &'a Range<Anchor>,
11532 collaboration_hub: &dyn CollaborationHub,
11533 cx: &'a App,
11534 ) -> impl 'a + Iterator<Item = RemoteSelection> {
11535 let participant_names = collaboration_hub.user_names(cx);
11536 let participant_indices = collaboration_hub.user_participant_indices(cx);
11537 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
11538 let collaborators_by_replica_id = collaborators_by_peer_id
11539 .values()
11540 .map(|collaborator| (collaborator.replica_id, collaborator))
11541 .collect::<HashMap<_, _>>();
11542 self.buffer_snapshot()
11543 .selections_in_range(range, false)
11544 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
11545 if replica_id == ReplicaId::AGENT {
11546 Some(RemoteSelection {
11547 replica_id,
11548 selection,
11549 cursor_shape,
11550 line_mode,
11551 collaborator_id: CollaboratorId::Agent,
11552 user_name: Some("Agent".into()),
11553 color: cx.theme().players().agent(),
11554 })
11555 } else {
11556 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
11557 let participant_index = participant_indices.get(&collaborator.user_id).copied();
11558 let user_name = participant_names.get(&collaborator.user_id).cloned();
11559 Some(RemoteSelection {
11560 replica_id,
11561 selection,
11562 cursor_shape,
11563 line_mode,
11564 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
11565 user_name,
11566 color: if let Some(index) = participant_index {
11567 cx.theme().players().color_for_participant(index.0)
11568 } else {
11569 cx.theme().players().absent()
11570 },
11571 })
11572 }
11573 })
11574 }
11575
11576 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
11577 self.display_snapshot
11578 .buffer_snapshot()
11579 .language_at(position)
11580 }
11581
11582 pub fn is_focused(&self) -> bool {
11583 self.is_focused
11584 }
11585
11586 pub fn placeholder_text(&self) -> Option<String> {
11587 self.placeholder_display_snapshot
11588 .as_ref()
11589 .map(|display_map| display_map.text())
11590 }
11591
11592 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
11593 self.scroll_anchor.scroll_position(&self.display_snapshot)
11594 }
11595
11596 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
11597 let digit_count = self.widest_line_number().ilog10() + 1;
11598 column_pixels(style, digit_count as usize, window)
11599 }
11600
11601 pub fn gutter_dimensions(
11602 &self,
11603 font_id: FontId,
11604 font_size: Pixels,
11605 style: &EditorStyle,
11606 window: &mut Window,
11607 cx: &App,
11608 ) -> GutterDimensions {
11609 if self.show_gutter
11610 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
11611 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
11612 {
11613 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
11614 matches!(
11615 ProjectSettings::get_global(cx).git.git_gutter,
11616 GitGutterSetting::TrackedFiles
11617 )
11618 });
11619 let gutter_settings = EditorSettings::get_global(cx).gutter;
11620 let show_line_numbers = self
11621 .show_line_numbers
11622 .unwrap_or(gutter_settings.line_numbers);
11623 let line_gutter_width = if show_line_numbers {
11624 // Avoid flicker-like gutter resizes when the line number gains another digit by
11625 // only resizing the gutter on files with > 10**min_line_number_digits lines.
11626 let min_width_for_number_on_gutter =
11627 ch_advance * gutter_settings.min_line_number_digits as f32;
11628 self.max_line_number_width(style, window)
11629 .max(min_width_for_number_on_gutter)
11630 } else {
11631 0.0.into()
11632 };
11633
11634 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
11635 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
11636 let show_bookmarks = self.show_bookmarks.unwrap_or(gutter_settings.bookmarks);
11637
11638 let git_blame_entries_width =
11639 self.git_blame_gutter_max_author_length
11640 .map(|max_author_length| {
11641 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
11642 const MAX_RELATIVE_TIMESTAMP: &str = "2 years, 11 months ago";
11643
11644 let max_char_count = max_author_length.min(renderer.max_author_length())
11645 + ::git::SHORT_SHA_LENGTH
11646 + MAX_RELATIVE_TIMESTAMP.len();
11647
11648 ch_advance * max_char_count
11649 + renderer.blame_entry_non_text_width(window, cx)
11650 });
11651
11652 let is_singleton = self.buffer_snapshot().is_singleton();
11653
11654 let left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO)
11655 + if !is_singleton {
11656 ch_width * 4.0
11657 // runnables, breakpoints and bookmarks are shown in the same place
11658 // if all three are there only the runnable is shown
11659 } else if show_runnables || show_breakpoints || show_bookmarks {
11660 ch_width * 3.0
11661 } else if show_git_gutter && show_line_numbers {
11662 ch_width * 2.0
11663 } else if show_git_gutter || show_line_numbers {
11664 ch_width
11665 } else {
11666 px(0.)
11667 };
11668
11669 let shows_folds = is_singleton && gutter_settings.folds;
11670
11671 let right_padding = if shows_folds && show_line_numbers {
11672 ch_width * 4.0
11673 } else if shows_folds || (!is_singleton && show_line_numbers) {
11674 ch_width * 3.0
11675 } else if show_line_numbers {
11676 ch_width
11677 } else {
11678 px(0.)
11679 };
11680
11681 GutterDimensions {
11682 left_padding,
11683 right_padding,
11684 width: line_gutter_width + left_padding + right_padding,
11685 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
11686 git_blame_entries_width,
11687 }
11688 } else if self.offset_content {
11689 GutterDimensions::default_with_margin(font_id, font_size, cx)
11690 } else {
11691 GutterDimensions::default()
11692 }
11693 }
11694
11695 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
11696 ///
11697 /// This is positive if `base` is before `line`.
11698 fn relative_line_delta(
11699 &self,
11700 current_selection_head: DisplayRow,
11701 first_visible_row: DisplayRow,
11702 consider_wrapped_lines: bool,
11703 ) -> i64 {
11704 let current_selection_head = current_selection_head.as_display_point().to_point(self);
11705 let first_visible_row = first_visible_row.as_display_point().to_point(self);
11706
11707 if consider_wrapped_lines {
11708 let wrap_snapshot = self.wrap_snapshot();
11709 let base_wrap_row = wrap_snapshot
11710 .make_wrap_point(current_selection_head, Bias::Left)
11711 .row();
11712 let wrap_row = wrap_snapshot
11713 .make_wrap_point(first_visible_row, Bias::Left)
11714 .row();
11715
11716 wrap_row.0 as i64 - base_wrap_row.0 as i64
11717 } else {
11718 let fold_snapshot = self.fold_snapshot();
11719 let base_fold_row = fold_snapshot
11720 .to_fold_point(self.to_inlay_point(current_selection_head), Bias::Left)
11721 .row();
11722 let fold_row = fold_snapshot
11723 .to_fold_point(self.to_inlay_point(first_visible_row), Bias::Left)
11724 .row();
11725
11726 fold_row as i64 - base_fold_row as i64
11727 }
11728 }
11729
11730 /// Returns the unsigned relative line number to display for each row in `rows`.
11731 ///
11732 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
11733 pub fn calculate_relative_line_numbers(
11734 &self,
11735 rows: &Range<DisplayRow>,
11736 current_selection_head: DisplayRow,
11737 count_wrapped_lines: bool,
11738 ) -> HashMap<DisplayRow, u32> {
11739 let mut row_infos = self
11740 .row_infos(rows.start)
11741 .take(rows.len())
11742 .enumerate()
11743 .map(|(index, row_info)| (DisplayRow(rows.start.0 + index as u32), row_info))
11744 .filter(|(_row, row_info)| {
11745 row_info.buffer_row.is_some()
11746 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
11747 })
11748 .peekable();
11749
11750 // We find the first row that actually passes the filter and calculate its
11751 // delta independently. This ensures accuracy when scrolling, as the first
11752 // visible row in `rows` might be a wrap part that is filtered out, which
11753 // would otherwise offset the counter for subsequent lines if we used
11754 // `rows.start` as the base for enumeration.
11755 let Some((first_row, _)) = row_infos.peek() else {
11756 return HashMap::default();
11757 };
11758
11759 let mut current_delta =
11760 self.relative_line_delta(current_selection_head, *first_row, count_wrapped_lines);
11761
11762 row_infos
11763 .filter_map(|(row, row_info)| {
11764 let is_deleted = row_info
11765 .diff_status
11766 .is_some_and(|status| status.is_deleted());
11767
11768 if !self.number_deleted_lines && is_deleted {
11769 // Even if we don't number this line, it still counts as a unit
11770 // of distance for the relative numbers of lines below it.
11771 current_delta += 1;
11772 return None;
11773 }
11774
11775 // We want to ensure here that the current line has absolute
11776 // numbering, even if we are in a soft-wrapped line. With the
11777 // exception that if we are in a deleted line, we should number this
11778 // relative with 0, as otherwise it would have no line number at all
11779 let relative_line_number = current_delta.unsigned_abs() as u32;
11780 current_delta += 1;
11781
11782 (relative_line_number != 0 || is_deleted).then_some((row, relative_line_number))
11783 })
11784 .collect()
11785 }
11786}
11787
11788pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
11789 let font_size = style.text.font_size.to_pixels(window.rem_size());
11790 let layout = window.text_system().shape_line(
11791 SharedString::from(" ".repeat(column)),
11792 font_size,
11793 &[TextRun {
11794 len: column,
11795 font: style.text.font(),
11796 color: Hsla::default(),
11797 ..Default::default()
11798 }],
11799 None,
11800 );
11801
11802 layout.width
11803}
11804
11805impl Deref for EditorSnapshot {
11806 type Target = DisplaySnapshot;
11807
11808 fn deref(&self) -> &Self::Target {
11809 &self.display_snapshot
11810 }
11811}
11812
11813#[derive(Clone, Debug, PartialEq, Eq)]
11814pub enum EditorEvent {
11815 /// Emitted when the stored review comments change (added, removed, or updated).
11816 ReviewCommentsChanged {
11817 /// The new total count of review comments.
11818 total_count: usize,
11819 },
11820 InputIgnored {
11821 text: Arc<str>,
11822 },
11823 InputHandled {
11824 utf16_range_to_replace: Option<Range<isize>>,
11825 text: Arc<str>,
11826 },
11827 BufferRangesUpdated {
11828 buffer: Entity<Buffer>,
11829 path_key: PathKey,
11830 ranges: Vec<ExcerptRange<text::Anchor>>,
11831 },
11832 BuffersRemoved {
11833 removed_buffer_ids: Vec<BufferId>,
11834 },
11835 BuffersEdited {
11836 buffer_ids: Vec<BufferId>,
11837 },
11838 BufferFoldToggled {
11839 ids: Vec<BufferId>,
11840 folded: bool,
11841 },
11842 ExpandExcerptsRequested {
11843 excerpt_anchors: Vec<Anchor>,
11844 lines: u32,
11845 direction: ExpandExcerptDirection,
11846 },
11847 OpenExcerptsRequested {
11848 selections_by_buffer: HashMap<BufferId, (Vec<Range<BufferOffset>>, Option<u32>)>,
11849 split: bool,
11850 },
11851 /// Emitted when an underlying buffer changes, including edits made through another editor.
11852 BufferEdited,
11853 /// Emitted when this editor creates, undoes, or redoes an edit transaction.
11854 Edited {
11855 /// The transaction that changed the editor's buffer.
11856 transaction_id: clock::Lamport,
11857 },
11858 Reparsed(BufferId),
11859 Focused,
11860 FocusedIn,
11861 Blurred,
11862 DirtyChanged,
11863 Saved,
11864 TitleChanged,
11865 FileHandleChanged,
11866 SelectionsChanged {
11867 local: bool,
11868 },
11869 ScrollPositionChanged {
11870 local: bool,
11871 autoscroll: bool,
11872 },
11873 TransactionUndone {
11874 transaction_id: clock::Lamport,
11875 },
11876 TransactionBegun {
11877 transaction_id: clock::Lamport,
11878 },
11879 CursorShapeChanged,
11880 BreadcrumbsChanged,
11881 OutlineSymbolsChanged,
11882 PushedToNavHistory {
11883 anchor: Anchor,
11884 is_deactivate: bool,
11885 },
11886}
11887
11888impl EventEmitter<EditorEvent> for Editor {}
11889
11890impl Focusable for Editor {
11891 fn focus_handle(&self, _cx: &App) -> FocusHandle {
11892 self.focus_handle.clone()
11893 }
11894}
11895
11896impl Render for Editor {
11897 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
11898 EditorElement::new(&cx.entity(), self.create_style(cx))
11899 }
11900}
11901
11902trait SelectionExt {
11903 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
11904 fn spanned_rows(
11905 &self,
11906 include_end_if_at_line_start: bool,
11907 map: &DisplaySnapshot,
11908 ) -> Range<MultiBufferRow>;
11909}
11910
11911impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
11912 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
11913 let start = self
11914 .start
11915 .to_point(map.buffer_snapshot())
11916 .to_display_point(map);
11917 let end = self
11918 .end
11919 .to_point(map.buffer_snapshot())
11920 .to_display_point(map);
11921 if self.reversed {
11922 end..start
11923 } else {
11924 start..end
11925 }
11926 }
11927
11928 fn spanned_rows(
11929 &self,
11930 include_end_if_at_line_start: bool,
11931 map: &DisplaySnapshot,
11932 ) -> Range<MultiBufferRow> {
11933 let start = self.start.to_point(map.buffer_snapshot());
11934 let mut end = self.end.to_point(map.buffer_snapshot());
11935 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
11936 end.row -= 1;
11937 }
11938
11939 let buffer_start = map.prev_line_boundary(start).0;
11940 let buffer_end = map.next_line_boundary(end).0;
11941 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
11942 }
11943}
11944
11945impl<T: InvalidationRegion> InvalidationStack<T> {
11946 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
11947 where
11948 S: Clone + ToOffset,
11949 {
11950 while let Some(region) = self.last() {
11951 let all_selections_inside_invalidation_ranges =
11952 if selections.len() == region.ranges().len() {
11953 selections
11954 .iter()
11955 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
11956 .all(|(selection, invalidation_range)| {
11957 let head = selection.head().to_offset(buffer);
11958 invalidation_range.start <= head && invalidation_range.end >= head
11959 })
11960 } else {
11961 false
11962 };
11963
11964 if all_selections_inside_invalidation_ranges {
11965 break;
11966 } else {
11967 self.pop();
11968 }
11969 }
11970 }
11971}
11972
11973#[derive(Clone)]
11974struct ErasedEditorImpl(Entity<Editor>);
11975
11976impl ui_input::ErasedEditor for ErasedEditorImpl {
11977 fn text(&self, cx: &App) -> String {
11978 self.0.read(cx).text(cx)
11979 }
11980
11981 fn set_text(&self, text: &str, window: &mut Window, cx: &mut App) {
11982 self.0.update(cx, |this, cx| {
11983 this.set_text(text, window, cx);
11984 })
11985 }
11986
11987 fn clear(&self, window: &mut Window, cx: &mut App) {
11988 self.0.update(cx, |this, cx| this.clear(window, cx));
11989 }
11990
11991 fn set_placeholder_text(&self, text: &str, window: &mut Window, cx: &mut App) {
11992 self.0.update(cx, |this, cx| {
11993 this.set_placeholder_text(text, window, cx);
11994 });
11995 }
11996
11997 fn set_multiline(&self, max_lines: Option<usize>, _window: &mut Window, cx: &mut App) {
11998 self.0.update(cx, |this, cx| {
11999 if let Some(max_lines) = max_lines {
12000 this.set_mode(EditorMode::AutoHeight {
12001 min_lines: 1,
12002 max_lines: Some(max_lines),
12003 });
12004 this.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
12005 } else {
12006 this.set_mode(EditorMode::SingleLine);
12007 }
12008 cx.notify();
12009 });
12010 }
12011
12012 fn focus_handle(&self, cx: &App) -> FocusHandle {
12013 self.0.read(cx).focus_handle(cx)
12014 }
12015
12016 fn render(&self, _: &mut Window, cx: &App) -> AnyElement {
12017 let settings = ThemeSettings::get_global(cx);
12018 let theme_color = cx.theme().colors();
12019
12020 let text_style = TextStyle {
12021 font_family: settings.ui_font.family.clone(),
12022 font_features: settings.ui_font.features.clone(),
12023 font_size: rems(0.875).into(),
12024 font_weight: settings.ui_font.weight,
12025 font_style: FontStyle::Normal,
12026 line_height: relative(1.2),
12027 color: theme_color.text,
12028 ..Default::default()
12029 };
12030 let editor_style = EditorStyle {
12031 background: theme_color.ghost_element_background,
12032 local_player: cx.theme().players().local(),
12033 syntax: cx.theme().syntax().clone(),
12034 text: text_style,
12035 ..Default::default()
12036 };
12037 EditorElement::new(&self.0, editor_style).into_any()
12038 }
12039
12040 fn as_any(&self) -> &dyn Any {
12041 &self.0
12042 }
12043
12044 fn move_selection_to_end(&self, window: &mut Window, cx: &mut App) {
12045 self.0.update(cx, |editor, cx| {
12046 let editor_offset = editor.buffer().read(cx).len(cx);
12047 editor.change_selections(
12048 SelectionEffects::scroll(Autoscroll::Next),
12049 window,
12050 cx,
12051 |s| s.select_ranges(Some(editor_offset..editor_offset)),
12052 );
12053 });
12054 }
12055
12056 fn select_all(&self, window: &mut Window, cx: &mut App) {
12057 self.0.update(cx, |editor, cx| {
12058 editor.select_all(&Default::default(), window, cx);
12059 });
12060 }
12061
12062 fn subscribe(
12063 &self,
12064 mut callback: Box<dyn FnMut(ui_input::ErasedEditorEvent, &mut Window, &mut App) + 'static>,
12065 window: &mut Window,
12066 cx: &mut App,
12067 ) -> Subscription {
12068 window.subscribe(&self.0, cx, move |_, event: &EditorEvent, window, cx| {
12069 let event = match event {
12070 EditorEvent::BufferEdited => ui_input::ErasedEditorEvent::BufferEdited,
12071 EditorEvent::Blurred => ui_input::ErasedEditorEvent::Blurred,
12072 _ => return,
12073 };
12074 (callback)(event, window, cx);
12075 })
12076 }
12077
12078 fn set_masked(&self, masked: bool, _window: &mut Window, cx: &mut App) {
12079 self.0.update(cx, |editor, cx| {
12080 editor.set_masked(masked, cx);
12081 });
12082 }
12083
12084 fn set_read_only(&self, read_only: bool, cx: &mut App) {
12085 self.0.update(cx, |editor, cx| {
12086 editor.set_read_only(read_only);
12087 cx.notify();
12088 });
12089 }
12090}
12091impl<T> Default for InvalidationStack<T> {
12092 fn default() -> Self {
12093 Self(Default::default())
12094 }
12095}
12096
12097impl<T> Deref for InvalidationStack<T> {
12098 type Target = Vec<T>;
12099
12100 fn deref(&self) -> &Self::Target {
12101 &self.0
12102 }
12103}
12104
12105impl<T> DerefMut for InvalidationStack<T> {
12106 fn deref_mut(&mut self) -> &mut Self::Target {
12107 &mut self.0
12108 }
12109}
12110
12111impl InvalidationRegion for SnippetState {
12112 fn ranges(&self) -> &[Range<Anchor>] {
12113 &self.ranges[self.active_index]
12114 }
12115}
12116
12117pub fn styled_runs_for_code_label<'a>(
12118 label: &'a CodeLabel,
12119 syntax_theme: &'a theme::SyntaxTheme,
12120 local_player: &'a theme::PlayerColor,
12121) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
12122 let fade_out = HighlightStyle {
12123 fade_out: Some(0.35),
12124 ..Default::default()
12125 };
12126
12127 if label.runs.is_empty() {
12128 let desc_start = label.filter_range.end;
12129 let fade_run =
12130 (desc_start < label.text.len()).then(|| (desc_start..label.text.len(), fade_out));
12131 return Either::Left(fade_run.into_iter());
12132 }
12133
12134 let mut prev_end = label.filter_range.end;
12135 Either::Right(
12136 label
12137 .runs
12138 .iter()
12139 .enumerate()
12140 .flat_map(move |(ix, (range, highlight_id))| {
12141 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
12142 HighlightStyle {
12143 color: Some(local_player.cursor),
12144 ..Default::default()
12145 }
12146 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
12147 HighlightStyle {
12148 background_color: Some(local_player.selection),
12149 ..Default::default()
12150 }
12151 } else if let Some(style) = syntax_theme.get(*highlight_id).cloned() {
12152 style
12153 } else {
12154 return Default::default();
12155 };
12156
12157 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
12158 let muted_style = style.highlight(fade_out);
12159 if range.start >= label.filter_range.end {
12160 if range.start > prev_end {
12161 runs.push((prev_end..range.start, fade_out));
12162 }
12163 runs.push((range.clone(), muted_style));
12164 } else if range.end <= label.filter_range.end {
12165 runs.push((range.clone(), style));
12166 } else {
12167 runs.push((range.start..label.filter_range.end, style));
12168 runs.push((label.filter_range.end..range.end, muted_style));
12169 }
12170 prev_end = cmp::max(prev_end, range.end);
12171
12172 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
12173 runs.push((prev_end..label.text.len(), fade_out));
12174 }
12175
12176 runs
12177 }),
12178 )
12179}
12180
12181pub trait RangeToAnchorExt: Sized {
12182 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
12183
12184 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
12185 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
12186 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
12187 }
12188}
12189
12190impl<T: ToOffset> RangeToAnchorExt for Range<T> {
12191 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
12192 let start_offset = self.start.to_offset(snapshot);
12193 let end_offset = self.end.to_offset(snapshot);
12194 if start_offset == end_offset {
12195 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
12196 } else {
12197 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
12198 }
12199 }
12200}
12201
12202pub trait RowExt {
12203 fn as_f64(&self) -> f64;
12204
12205 fn next_row(&self) -> Self;
12206
12207 fn previous_row(&self) -> Self;
12208
12209 fn minus(&self, other: Self) -> u32;
12210}
12211
12212impl RowExt for DisplayRow {
12213 fn as_f64(&self) -> f64 {
12214 self.0 as _
12215 }
12216
12217 fn next_row(&self) -> Self {
12218 Self(self.0 + 1)
12219 }
12220
12221 fn previous_row(&self) -> Self {
12222 Self(self.0.saturating_sub(1))
12223 }
12224
12225 fn minus(&self, other: Self) -> u32 {
12226 self.0 - other.0
12227 }
12228}
12229
12230impl RowExt for MultiBufferRow {
12231 fn as_f64(&self) -> f64 {
12232 self.0 as _
12233 }
12234
12235 fn next_row(&self) -> Self {
12236 Self(self.0 + 1)
12237 }
12238
12239 fn previous_row(&self) -> Self {
12240 Self(self.0.saturating_sub(1))
12241 }
12242
12243 fn minus(&self, other: Self) -> u32 {
12244 self.0 - other.0
12245 }
12246}
12247
12248trait RowRangeExt {
12249 type Row;
12250
12251 fn len(&self) -> usize;
12252
12253 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
12254}
12255
12256impl RowRangeExt for Range<MultiBufferRow> {
12257 type Row = MultiBufferRow;
12258
12259 fn len(&self) -> usize {
12260 (self.end.0 - self.start.0) as usize
12261 }
12262
12263 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
12264 (self.start.0..self.end.0).map(MultiBufferRow)
12265 }
12266}
12267
12268impl RowRangeExt for Range<DisplayRow> {
12269 type Row = DisplayRow;
12270
12271 fn len(&self) -> usize {
12272 (self.end.0 - self.start.0) as usize
12273 }
12274
12275 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
12276 (self.start.0..self.end.0).map(DisplayRow)
12277 }
12278}
12279
12280/// If select range has more than one line, we
12281/// just point the cursor to range.start.
12282fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
12283 if range.start.row == range.end.row {
12284 range
12285 } else {
12286 range.start..range.start
12287 }
12288}
12289
12290const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
12291
12292#[derive(Copy, Clone, Debug)]
12293enum BreakpointPromptEditAction {
12294 Log,
12295 Condition,
12296 HitCondition,
12297}
12298
12299type PromptEditorCallback = Box<dyn FnOnce(String, &mut Editor, &mut Context<Editor>) + 'static>;
12300
12301struct PromptEditor {
12302 pub(crate) prompt: Entity<Editor>,
12303 editor: WeakEntity<Editor>,
12304 confirm_callback: Option<PromptEditorCallback>,
12305 cancel_callback: Option<PromptEditorCallback>,
12306 block_ids: HashSet<CustomBlockId>,
12307 editor_margins: Arc<Mutex<EditorMargins>>,
12308 _subscriptions: Vec<Subscription>,
12309}
12310
12311impl PromptEditor {
12312 const MAX_LINES: u8 = 4;
12313
12314 fn new(
12315 editor: WeakEntity<Editor>,
12316 placeholder_text: &str,
12317 base_text: &str,
12318 window: &mut Window,
12319 cx: &mut Context<Self>,
12320 ) -> Self {
12321 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
12322 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12323
12324 let prompt = cx.new(|cx| {
12325 let mut prompt = Editor::new(
12326 EditorMode::AutoHeight {
12327 min_lines: 1,
12328 max_lines: Some(Self::MAX_LINES as usize),
12329 },
12330 buffer,
12331 None,
12332 window,
12333 cx,
12334 );
12335 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
12336 prompt.set_show_cursor_when_unfocused(false, cx);
12337 prompt.set_placeholder_text(placeholder_text, window, cx);
12338
12339 prompt
12340 });
12341
12342 Self {
12343 prompt,
12344 editor,
12345 confirm_callback: None,
12346 cancel_callback: None,
12347 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
12348 block_ids: Default::default(),
12349 _subscriptions: vec![],
12350 }
12351 }
12352
12353 fn on_confirm(mut self, confirm: PromptEditorCallback) -> Self {
12354 self.confirm_callback = Some(confirm);
12355 self
12356 }
12357
12358 fn on_cancel(mut self, cancel: PromptEditorCallback) -> Self {
12359 self.cancel_callback = Some(cancel);
12360 self
12361 }
12362
12363 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
12364 self.block_ids.extend(block_ids)
12365 }
12366
12367 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
12368 if let Some(editor) = self.editor.upgrade() {
12369 let message = self
12370 .prompt
12371 .read(cx)
12372 .buffer
12373 .read(cx)
12374 .as_singleton()
12375 .expect("A multi buffer in prompt isn't possible")
12376 .read(cx)
12377 .as_rope()
12378 .to_string();
12379
12380 editor.update(cx, |editor, cx| {
12381 if let Some(confirm) = self.confirm_callback.take() {
12382 confirm(message, editor, cx);
12383 }
12384
12385 editor.remove_blocks(self.block_ids.clone(), None, cx);
12386 cx.focus_self(window);
12387 });
12388 }
12389 }
12390
12391 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
12392 self.editor
12393 .update(cx, |editor, cx| {
12394 let message = self
12395 .prompt
12396 .read(cx)
12397 .buffer
12398 .read(cx)
12399 .as_singleton()
12400 .expect("A multi buffer in prompt isn't possible")
12401 .read(cx)
12402 .as_rope()
12403 .to_string();
12404
12405 if let Some(cancel) = self.cancel_callback.take() {
12406 cancel(message, editor, cx);
12407 }
12408
12409 editor.remove_blocks(self.block_ids.clone(), None, cx);
12410 window.focus(&editor.focus_handle, cx);
12411 })
12412 .log_err();
12413 }
12414
12415 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
12416 let settings = ThemeSettings::get_global(cx);
12417 let text_style = TextStyle {
12418 color: if self.prompt.read(cx).read_only(cx) {
12419 cx.theme().colors().text_disabled
12420 } else {
12421 cx.theme().colors().text
12422 },
12423 font_family: settings.buffer_font.family.clone(),
12424 font_fallbacks: settings.buffer_font.fallbacks.clone(),
12425 font_size: settings.buffer_font_size(cx).into(),
12426 font_weight: settings.buffer_font.weight,
12427 line_height: relative(settings.buffer_line_height.value()),
12428 ..Default::default()
12429 };
12430 EditorElement::new(
12431 &self.prompt,
12432 EditorStyle {
12433 background: cx.theme().colors().editor_background,
12434 local_player: cx.theme().players().local(),
12435 text: text_style,
12436 ..Default::default()
12437 },
12438 )
12439 }
12440
12441 fn render_close_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
12442 let focus_handle = self.prompt.focus_handle(cx);
12443 IconButton::new("cancel", IconName::Close)
12444 .icon_color(Color::Muted)
12445 .shape(IconButtonShape::Square)
12446 .tooltip(move |_window, cx| {
12447 Tooltip::for_action_in("Cancel", &menu::Cancel, &focus_handle, cx)
12448 })
12449 .on_click(cx.listener(|this, _, window, cx| {
12450 this.cancel(&menu::Cancel, window, cx);
12451 }))
12452 }
12453
12454 fn render_confirm_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
12455 let focus_handle = self.prompt.focus_handle(cx);
12456 IconButton::new("confirm", IconName::Return)
12457 .icon_color(Color::Muted)
12458 .shape(IconButtonShape::Square)
12459 .tooltip(move |_window, cx| {
12460 Tooltip::for_action_in("Confirm", &menu::Confirm, &focus_handle, cx)
12461 })
12462 .on_click(cx.listener(|this, _, window, cx| {
12463 this.confirm(&menu::Confirm, window, cx);
12464 }))
12465 }
12466}
12467
12468impl Render for PromptEditor {
12469 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
12470 let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
12471 let editor_margins = *self.editor_margins.lock();
12472 let gutter_dimensions = editor_margins.gutter;
12473 let left_gutter_width = gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0);
12474 let right_padding = editor_margins.right + px(9.);
12475 h_flex()
12476 .key_context("Editor")
12477 .bg(cx.theme().colors().editor_background)
12478 .border_y_1()
12479 .border_color(cx.theme().status().info_border)
12480 .size_full()
12481 .py(window.line_height() / 2.5)
12482 .pr(right_padding)
12483 .on_action(cx.listener(Self::confirm))
12484 .on_action(cx.listener(Self::cancel))
12485 .child(
12486 WithRemSize::new(ui_font_size)
12487 .h_full()
12488 .w(left_gutter_width)
12489 .flex()
12490 .flex_row()
12491 .flex_shrink_0()
12492 .items_center()
12493 .justify_center()
12494 .gap_1()
12495 .child(self.render_close_button(cx)),
12496 )
12497 .child(
12498 h_flex()
12499 .w_full()
12500 .justify_between()
12501 .child(div().flex_1().child(self.render_prompt_editor(cx)))
12502 .child(
12503 WithRemSize::new(ui_font_size)
12504 .flex()
12505 .flex_row()
12506 .items_center()
12507 .child(self.render_confirm_button(cx)),
12508 ),
12509 )
12510 }
12511}
12512
12513impl Focusable for PromptEditor {
12514 fn focus_handle(&self, cx: &App) -> FocusHandle {
12515 self.prompt.focus_handle(cx)
12516 }
12517}
12518
12519#[derive(Debug, Clone, Copy, PartialEq)]
12520pub struct LineHighlight {
12521 pub background: Background,
12522 pub border: Option<gpui::Hsla>,
12523 pub include_gutter: bool,
12524 pub type_id: Option<TypeId>,
12525}
12526
12527struct LineManipulationResult {
12528 pub new_text: String,
12529 pub line_count_before: usize,
12530 pub line_count_after: usize,
12531}
12532
12533pub fn multibuffer_context_lines(cx: &App) -> u32 {
12534 EditorSettings::try_get(cx)
12535 .map(|settings| settings.excerpt_context_lines)
12536 .unwrap_or(2)
12537 .min(32)
12538}
12539