1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blame_entry_tooltip;
17mod blink_manager;
18mod clangd_ext;
19mod debounced_delay;
20pub mod display_map;
21mod editor_settings;
22mod editor_settings_controls;
23mod element;
24mod git;
25mod highlight_matching_bracket;
26mod hover_links;
27mod hover_popover;
28mod hunk_diff;
29mod indent_guides;
30mod inlay_hint_cache;
31mod inline_completion_provider;
32pub mod items;
33mod linked_editing_ranges;
34mod lsp_ext;
35mod mouse_context_menu;
36pub mod movement;
37mod persistence;
38mod proposed_changes_editor;
39mod rust_analyzer_ext;
40pub mod scroll;
41mod selections_collection;
42pub mod tasks;
43
44#[cfg(test)]
45mod editor_tests;
46mod signature_help;
47#[cfg(any(test, feature = "test-support"))]
48pub mod test;
49
50use ::git::diff::DiffHunkStatus;
51use ::git::{parse_git_remote_url, BuildPermalinkParams, GitHostingProviderRegistry};
52pub(crate) use actions::*;
53use aho_corasick::AhoCorasick;
54use anyhow::{anyhow, Context as _, Result};
55use blink_manager::BlinkManager;
56use client::{Collaborator, ParticipantIndex};
57use clock::ReplicaId;
58use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
59use convert_case::{Case, Casing};
60use debounced_delay::DebouncedDelay;
61use display_map::*;
62pub use display_map::{DisplayPoint, FoldPlaceholder};
63pub use editor_settings::{
64 CurrentLineHighlight, EditorSettings, ScrollBeyondLastLine, SearchSettings,
65};
66pub use editor_settings_controls::*;
67use element::LineWithInvisibles;
68pub use element::{
69 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
70};
71use futures::{future, FutureExt};
72use fuzzy::{StringMatch, StringMatchCandidate};
73use git::blame::GitBlame;
74use gpui::{
75 div, impl_actions, point, prelude::*, px, relative, size, uniform_list, Action, AnyElement,
76 AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardEntry,
77 ClipboardItem, Context, DispatchPhase, ElementId, EntityId, EventEmitter, FocusHandle,
78 FocusOutEvent, FocusableView, FontId, FontWeight, HighlightStyle, Hsla, InteractiveText,
79 KeyContext, ListSizingBehavior, Model, MouseButton, PaintQuad, ParentElement, Pixels, Render,
80 SharedString, Size, StrikethroughStyle, Styled, StyledText, Subscription, Task, TextStyle,
81 UTF16Selection, UnderlineStyle, UniformListScrollHandle, View, ViewContext, ViewInputHandler,
82 VisualContext, WeakFocusHandle, WeakView, WindowContext,
83};
84use highlight_matching_bracket::refresh_matching_bracket_highlights;
85use hover_popover::{hide_hover, HoverState};
86pub(crate) use hunk_diff::HoveredHunk;
87use hunk_diff::{diff_hunk_to_display, ExpandedHunks};
88use indent_guides::ActiveIndentGuidesState;
89use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
90pub use inline_completion_provider::*;
91pub use items::MAX_TAB_TITLE_LEN;
92use itertools::Itertools;
93use language::{
94 language_settings::{self, all_language_settings, InlayHintSettings},
95 markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CharKind, CodeLabel,
96 CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language, OffsetRangeExt,
97 Point, Selection, SelectionGoal, TransactionId,
98};
99use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange};
100use linked_editing_ranges::refresh_linked_ranges;
101pub use proposed_changes_editor::{
102 ProposedChangesBuffer, ProposedChangesEditor, ProposedChangesEditorToolbar,
103};
104use similar::{ChangeTag, TextDiff};
105use task::{ResolvedTask, TaskTemplate, TaskVariables};
106
107use hover_links::{find_file, HoverLink, HoveredLinkState, InlayHighlight};
108pub use lsp::CompletionContext;
109use lsp::{
110 CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity, InsertTextFormat,
111 LanguageServerId,
112};
113use mouse_context_menu::MouseContextMenu;
114use movement::TextLayoutDetails;
115pub use multi_buffer::{
116 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset,
117 ToPoint,
118};
119use multi_buffer::{
120 ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow, ToOffsetUtf16,
121};
122use ordered_float::OrderedFloat;
123use parking_lot::{Mutex, RwLock};
124use project::project_settings::{GitGutterSetting, ProjectSettings};
125use project::{
126 lsp_store::FormatTrigger, CodeAction, Completion, CompletionIntent, Item, Location, Project,
127 ProjectPath, ProjectTransaction, TaskSourceKind,
128};
129use rand::prelude::*;
130use rpc::{proto::*, ErrorExt};
131use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
132use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
133use serde::{Deserialize, Serialize};
134use settings::{update_settings_file, Settings, SettingsLocation, SettingsStore};
135use smallvec::SmallVec;
136use snippet::Snippet;
137use std::{
138 any::TypeId,
139 borrow::Cow,
140 cell::RefCell,
141 cmp::{self, Ordering, Reverse},
142 mem,
143 num::NonZeroU32,
144 ops::{ControlFlow, Deref, DerefMut, Not as _, Range, RangeInclusive},
145 path::{Path, PathBuf},
146 rc::Rc,
147 sync::Arc,
148 time::{Duration, Instant},
149};
150pub use sum_tree::Bias;
151use sum_tree::TreeMap;
152use text::{BufferId, OffsetUtf16, Rope};
153use theme::{
154 observe_buffer_font_size_adjustment, ActiveTheme, PlayerColor, StatusColors, SyntaxTheme,
155 ThemeColors, ThemeSettings,
156};
157use ui::{
158 h_flex, prelude::*, ButtonSize, ButtonStyle, Disclosure, IconButton, IconName, IconSize,
159 ListItem, Popover, PopoverMenuHandle, Tooltip,
160};
161use util::{defer, maybe, post_inc, RangeExt, ResultExt, TryFutureExt};
162use workspace::item::{ItemHandle, PreviewTabsSettings};
163use workspace::notifications::{DetachAndPromptErr, NotificationId};
164use workspace::{
165 searchable::SearchEvent, ItemNavHistory, SplitDirection, ViewId, Workspace, WorkspaceId,
166};
167use workspace::{OpenInTerminal, OpenTerminal, TabBarSettings, Toast};
168
169use crate::hover_links::find_url;
170use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
171
172pub const FILE_HEADER_HEIGHT: u32 = 1;
173pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
174pub const MULTI_BUFFER_EXCERPT_FOOTER_HEIGHT: u32 = 1;
175pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
176const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
177const MAX_LINE_LEN: usize = 1024;
178const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
179const MAX_SELECTION_HISTORY_LEN: usize = 1024;
180pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
181#[doc(hidden)]
182pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
183#[doc(hidden)]
184pub const DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75);
185
186pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(2);
187pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
188
189pub fn render_parsed_markdown(
190 element_id: impl Into<ElementId>,
191 parsed: &language::ParsedMarkdown,
192 editor_style: &EditorStyle,
193 workspace: Option<WeakView<Workspace>>,
194 cx: &mut WindowContext,
195) -> InteractiveText {
196 let code_span_background_color = cx
197 .theme()
198 .colors()
199 .editor_document_highlight_read_background;
200
201 let highlights = gpui::combine_highlights(
202 parsed.highlights.iter().filter_map(|(range, highlight)| {
203 let highlight = highlight.to_highlight_style(&editor_style.syntax)?;
204 Some((range.clone(), highlight))
205 }),
206 parsed
207 .regions
208 .iter()
209 .zip(&parsed.region_ranges)
210 .filter_map(|(region, range)| {
211 if region.code {
212 Some((
213 range.clone(),
214 HighlightStyle {
215 background_color: Some(code_span_background_color),
216 ..Default::default()
217 },
218 ))
219 } else {
220 None
221 }
222 }),
223 );
224
225 let mut links = Vec::new();
226 let mut link_ranges = Vec::new();
227 for (range, region) in parsed.region_ranges.iter().zip(&parsed.regions) {
228 if let Some(link) = region.link.clone() {
229 links.push(link);
230 link_ranges.push(range.clone());
231 }
232 }
233
234 InteractiveText::new(
235 element_id,
236 StyledText::new(parsed.text.clone()).with_highlights(&editor_style.text, highlights),
237 )
238 .on_click(link_ranges, move |clicked_range_ix, cx| {
239 match &links[clicked_range_ix] {
240 markdown::Link::Web { url } => cx.open_url(url),
241 markdown::Link::Path { path } => {
242 if let Some(workspace) = &workspace {
243 _ = workspace.update(cx, |workspace, cx| {
244 workspace.open_abs_path(path.clone(), false, cx).detach();
245 });
246 }
247 }
248 }
249 })
250}
251
252#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
253pub(crate) enum InlayId {
254 Suggestion(usize),
255 Hint(usize),
256}
257
258impl InlayId {
259 fn id(&self) -> usize {
260 match self {
261 Self::Suggestion(id) => *id,
262 Self::Hint(id) => *id,
263 }
264 }
265}
266
267enum DiffRowHighlight {}
268enum DocumentHighlightRead {}
269enum DocumentHighlightWrite {}
270enum InputComposition {}
271
272#[derive(Copy, Clone, PartialEq, Eq)]
273pub enum Direction {
274 Prev,
275 Next,
276}
277
278#[derive(Debug, Copy, Clone, PartialEq, Eq)]
279pub enum Navigated {
280 Yes,
281 No,
282}
283
284impl Navigated {
285 pub fn from_bool(yes: bool) -> Navigated {
286 if yes {
287 Navigated::Yes
288 } else {
289 Navigated::No
290 }
291 }
292}
293
294pub fn init_settings(cx: &mut AppContext) {
295 EditorSettings::register(cx);
296}
297
298pub fn init(cx: &mut AppContext) {
299 init_settings(cx);
300
301 workspace::register_project_item::<Editor>(cx);
302 workspace::FollowableViewRegistry::register::<Editor>(cx);
303 workspace::register_serializable_item::<Editor>(cx);
304
305 cx.observe_new_views(
306 |workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
307 workspace.register_action(Editor::new_file);
308 workspace.register_action(Editor::new_file_vertical);
309 workspace.register_action(Editor::new_file_horizontal);
310 },
311 )
312 .detach();
313
314 cx.on_action(move |_: &workspace::NewFile, cx| {
315 let app_state = workspace::AppState::global(cx);
316 if let Some(app_state) = app_state.upgrade() {
317 workspace::open_new(Default::default(), app_state, cx, |workspace, cx| {
318 Editor::new_file(workspace, &Default::default(), cx)
319 })
320 .detach();
321 }
322 });
323 cx.on_action(move |_: &workspace::NewWindow, cx| {
324 let app_state = workspace::AppState::global(cx);
325 if let Some(app_state) = app_state.upgrade() {
326 workspace::open_new(Default::default(), app_state, cx, |workspace, cx| {
327 Editor::new_file(workspace, &Default::default(), cx)
328 })
329 .detach();
330 }
331 });
332}
333
334pub struct SearchWithinRange;
335
336trait InvalidationRegion {
337 fn ranges(&self) -> &[Range<Anchor>];
338}
339
340#[derive(Clone, Debug, PartialEq)]
341pub enum SelectPhase {
342 Begin {
343 position: DisplayPoint,
344 add: bool,
345 click_count: usize,
346 },
347 BeginColumnar {
348 position: DisplayPoint,
349 reset: bool,
350 goal_column: u32,
351 },
352 Extend {
353 position: DisplayPoint,
354 click_count: usize,
355 },
356 Update {
357 position: DisplayPoint,
358 goal_column: u32,
359 scroll_delta: gpui::Point<f32>,
360 },
361 End,
362}
363
364#[derive(Clone, Debug)]
365pub enum SelectMode {
366 Character,
367 Word(Range<Anchor>),
368 Line(Range<Anchor>),
369 All,
370}
371
372#[derive(Copy, Clone, PartialEq, Eq, Debug)]
373pub enum EditorMode {
374 SingleLine { auto_width: bool },
375 AutoHeight { max_lines: usize },
376 Full,
377}
378
379#[derive(Clone, Debug)]
380pub enum SoftWrap {
381 None,
382 PreferLine,
383 EditorWidth,
384 Column(u32),
385 Bounded(u32),
386}
387
388#[derive(Clone)]
389pub struct EditorStyle {
390 pub background: Hsla,
391 pub local_player: PlayerColor,
392 pub text: TextStyle,
393 pub scrollbar_width: Pixels,
394 pub syntax: Arc<SyntaxTheme>,
395 pub status: StatusColors,
396 pub inlay_hints_style: HighlightStyle,
397 pub suggestions_style: HighlightStyle,
398 pub unnecessary_code_fade: f32,
399}
400
401impl Default for EditorStyle {
402 fn default() -> Self {
403 Self {
404 background: Hsla::default(),
405 local_player: PlayerColor::default(),
406 text: TextStyle::default(),
407 scrollbar_width: Pixels::default(),
408 syntax: Default::default(),
409 // HACK: Status colors don't have a real default.
410 // We should look into removing the status colors from the editor
411 // style and retrieve them directly from the theme.
412 status: StatusColors::dark(),
413 inlay_hints_style: HighlightStyle::default(),
414 suggestions_style: HighlightStyle::default(),
415 unnecessary_code_fade: Default::default(),
416 }
417 }
418}
419
420pub fn make_inlay_hints_style(cx: &WindowContext) -> HighlightStyle {
421 let show_background = all_language_settings(None, cx)
422 .language(None)
423 .inlay_hints
424 .show_background;
425
426 HighlightStyle {
427 color: Some(cx.theme().status().hint),
428 background_color: show_background.then(|| cx.theme().status().hint_background),
429 ..HighlightStyle::default()
430 }
431}
432
433type CompletionId = usize;
434
435#[derive(Clone, Debug)]
436struct CompletionState {
437 // render_inlay_ids represents the inlay hints that are inserted
438 // for rendering the inline completions. They may be discontinuous
439 // in the event that the completion provider returns some intersection
440 // with the existing content.
441 render_inlay_ids: Vec<InlayId>,
442 // text is the resulting rope that is inserted when the user accepts a completion.
443 text: Rope,
444 // position is the position of the cursor when the completion was triggered.
445 position: multi_buffer::Anchor,
446 // delete_range is the range of text that this completion state covers.
447 // if the completion is accepted, this range should be deleted.
448 delete_range: Option<Range<multi_buffer::Anchor>>,
449}
450
451#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
452struct EditorActionId(usize);
453
454impl EditorActionId {
455 pub fn post_inc(&mut self) -> Self {
456 let answer = self.0;
457
458 *self = Self(answer + 1);
459
460 Self(answer)
461 }
462}
463
464// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
465// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
466
467type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
468type GutterHighlight = (fn(&AppContext) -> Hsla, Arc<[Range<Anchor>]>);
469
470#[derive(Default)]
471struct ScrollbarMarkerState {
472 scrollbar_size: Size<Pixels>,
473 dirty: bool,
474 markers: Arc<[PaintQuad]>,
475 pending_refresh: Option<Task<Result<()>>>,
476}
477
478impl ScrollbarMarkerState {
479 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
480 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
481 }
482}
483
484#[derive(Clone, Debug)]
485struct RunnableTasks {
486 templates: Vec<(TaskSourceKind, TaskTemplate)>,
487 offset: MultiBufferOffset,
488 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
489 column: u32,
490 // Values of all named captures, including those starting with '_'
491 extra_variables: HashMap<String, String>,
492 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
493 context_range: Range<BufferOffset>,
494}
495
496#[derive(Clone)]
497struct ResolvedTasks {
498 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
499 position: Anchor,
500}
501#[derive(Copy, Clone, Debug)]
502struct MultiBufferOffset(usize);
503#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
504struct BufferOffset(usize);
505
506// Addons allow storing per-editor state in other crates (e.g. Vim)
507pub trait Addon: 'static {
508 fn extend_key_context(&self, _: &mut KeyContext, _: &AppContext) {}
509
510 fn to_any(&self) -> &dyn std::any::Any;
511}
512
513/// Zed's primary text input `View`, allowing users to edit a [`MultiBuffer`]
514///
515/// See the [module level documentation](self) for more information.
516pub struct Editor {
517 focus_handle: FocusHandle,
518 last_focused_descendant: Option<WeakFocusHandle>,
519 /// The text buffer being edited
520 buffer: Model<MultiBuffer>,
521 /// Map of how text in the buffer should be displayed.
522 /// Handles soft wraps, folds, fake inlay text insertions, etc.
523 pub display_map: Model<DisplayMap>,
524 pub selections: SelectionsCollection,
525 pub scroll_manager: ScrollManager,
526 /// When inline assist editors are linked, they all render cursors because
527 /// typing enters text into each of them, even the ones that aren't focused.
528 pub(crate) show_cursor_when_unfocused: bool,
529 columnar_selection_tail: Option<Anchor>,
530 add_selections_state: Option<AddSelectionsState>,
531 select_next_state: Option<SelectNextState>,
532 select_prev_state: Option<SelectNextState>,
533 selection_history: SelectionHistory,
534 autoclose_regions: Vec<AutocloseRegion>,
535 snippet_stack: InvalidationStack<SnippetState>,
536 select_larger_syntax_node_stack: Vec<Box<[Selection<usize>]>>,
537 ime_transaction: Option<TransactionId>,
538 active_diagnostics: Option<ActiveDiagnosticGroup>,
539 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
540 project: Option<Model<Project>>,
541 completion_provider: Option<Box<dyn CompletionProvider>>,
542 collaboration_hub: Option<Box<dyn CollaborationHub>>,
543 blink_manager: Model<BlinkManager>,
544 show_cursor_names: bool,
545 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
546 pub show_local_selections: bool,
547 mode: EditorMode,
548 show_breadcrumbs: bool,
549 show_gutter: bool,
550 show_line_numbers: Option<bool>,
551 use_relative_line_numbers: Option<bool>,
552 show_git_diff_gutter: Option<bool>,
553 show_code_actions: Option<bool>,
554 show_runnables: Option<bool>,
555 show_wrap_guides: Option<bool>,
556 show_indent_guides: Option<bool>,
557 placeholder_text: Option<Arc<str>>,
558 highlight_order: usize,
559 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
560 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
561 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
562 scrollbar_marker_state: ScrollbarMarkerState,
563 active_indent_guides_state: ActiveIndentGuidesState,
564 nav_history: Option<ItemNavHistory>,
565 context_menu: RwLock<Option<ContextMenu>>,
566 mouse_context_menu: Option<MouseContextMenu>,
567 hunk_controls_menu_handle: PopoverMenuHandle<ui::ContextMenu>,
568 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
569 signature_help_state: SignatureHelpState,
570 auto_signature_help: Option<bool>,
571 find_all_references_task_sources: Vec<Anchor>,
572 next_completion_id: CompletionId,
573 completion_documentation_pre_resolve_debounce: DebouncedDelay,
574 available_code_actions: Option<(Location, Arc<[AvailableCodeAction]>)>,
575 code_actions_task: Option<Task<Result<()>>>,
576 document_highlights_task: Option<Task<()>>,
577 linked_editing_range_task: Option<Task<Option<()>>>,
578 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
579 pending_rename: Option<RenameState>,
580 searchable: bool,
581 cursor_shape: CursorShape,
582 current_line_highlight: Option<CurrentLineHighlight>,
583 collapse_matches: bool,
584 autoindent_mode: Option<AutoindentMode>,
585 workspace: Option<(WeakView<Workspace>, Option<WorkspaceId>)>,
586 input_enabled: bool,
587 use_modal_editing: bool,
588 read_only: bool,
589 leader_peer_id: Option<PeerId>,
590 remote_id: Option<ViewId>,
591 hover_state: HoverState,
592 gutter_hovered: bool,
593 hovered_link_state: Option<HoveredLinkState>,
594 inline_completion_provider: Option<RegisteredInlineCompletionProvider>,
595 code_action_providers: Vec<Arc<dyn CodeActionProvider>>,
596 active_inline_completion: Option<CompletionState>,
597 // enable_inline_completions is a switch that Vim can use to disable
598 // inline completions based on its mode.
599 enable_inline_completions: bool,
600 show_inline_completions_override: Option<bool>,
601 inlay_hint_cache: InlayHintCache,
602 expanded_hunks: ExpandedHunks,
603 next_inlay_id: usize,
604 _subscriptions: Vec<Subscription>,
605 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
606 gutter_dimensions: GutterDimensions,
607 style: Option<EditorStyle>,
608 next_editor_action_id: EditorActionId,
609 editor_actions: Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut ViewContext<Self>)>>>>,
610 use_autoclose: bool,
611 use_auto_surround: bool,
612 auto_replace_emoji_shortcode: bool,
613 show_git_blame_gutter: bool,
614 show_git_blame_inline: bool,
615 show_git_blame_inline_delay_task: Option<Task<()>>,
616 git_blame_inline_enabled: bool,
617 serialize_dirty_buffers: bool,
618 show_selection_menu: Option<bool>,
619 blame: Option<Model<GitBlame>>,
620 blame_subscription: Option<Subscription>,
621 custom_context_menu: Option<
622 Box<
623 dyn 'static
624 + Fn(&mut Self, DisplayPoint, &mut ViewContext<Self>) -> Option<View<ui::ContextMenu>>,
625 >,
626 >,
627 last_bounds: Option<Bounds<Pixels>>,
628 expect_bounds_change: Option<Bounds<Pixels>>,
629 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
630 tasks_update_task: Option<Task<()>>,
631 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
632 file_header_size: u32,
633 breadcrumb_header: Option<String>,
634 focused_block: Option<FocusedBlock>,
635 next_scroll_position: NextScrollCursorCenterTopBottom,
636 addons: HashMap<TypeId, Box<dyn Addon>>,
637 _scroll_cursor_center_top_bottom_task: Task<()>,
638}
639
640#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
641enum NextScrollCursorCenterTopBottom {
642 #[default]
643 Center,
644 Top,
645 Bottom,
646}
647
648impl NextScrollCursorCenterTopBottom {
649 fn next(&self) -> Self {
650 match self {
651 Self::Center => Self::Top,
652 Self::Top => Self::Bottom,
653 Self::Bottom => Self::Center,
654 }
655 }
656}
657
658#[derive(Clone)]
659pub struct EditorSnapshot {
660 pub mode: EditorMode,
661 show_gutter: bool,
662 show_line_numbers: Option<bool>,
663 show_git_diff_gutter: Option<bool>,
664 show_code_actions: Option<bool>,
665 show_runnables: Option<bool>,
666 render_git_blame_gutter: bool,
667 pub display_snapshot: DisplaySnapshot,
668 pub placeholder_text: Option<Arc<str>>,
669 is_focused: bool,
670 scroll_anchor: ScrollAnchor,
671 ongoing_scroll: OngoingScroll,
672 current_line_highlight: CurrentLineHighlight,
673 gutter_hovered: bool,
674}
675
676const GIT_BLAME_GUTTER_WIDTH_CHARS: f32 = 53.;
677
678#[derive(Default, Debug, Clone, Copy)]
679pub struct GutterDimensions {
680 pub left_padding: Pixels,
681 pub right_padding: Pixels,
682 pub width: Pixels,
683 pub margin: Pixels,
684 pub git_blame_entries_width: Option<Pixels>,
685}
686
687impl GutterDimensions {
688 /// The full width of the space taken up by the gutter.
689 pub fn full_width(&self) -> Pixels {
690 self.margin + self.width
691 }
692
693 /// The width of the space reserved for the fold indicators,
694 /// use alongside 'justify_end' and `gutter_width` to
695 /// right align content with the line numbers
696 pub fn fold_area_width(&self) -> Pixels {
697 self.margin + self.right_padding
698 }
699}
700
701#[derive(Debug)]
702pub struct RemoteSelection {
703 pub replica_id: ReplicaId,
704 pub selection: Selection<Anchor>,
705 pub cursor_shape: CursorShape,
706 pub peer_id: PeerId,
707 pub line_mode: bool,
708 pub participant_index: Option<ParticipantIndex>,
709 pub user_name: Option<SharedString>,
710}
711
712#[derive(Clone, Debug)]
713struct SelectionHistoryEntry {
714 selections: Arc<[Selection<Anchor>]>,
715 select_next_state: Option<SelectNextState>,
716 select_prev_state: Option<SelectNextState>,
717 add_selections_state: Option<AddSelectionsState>,
718}
719
720enum SelectionHistoryMode {
721 Normal,
722 Undoing,
723 Redoing,
724}
725
726#[derive(Clone, PartialEq, Eq, Hash)]
727struct HoveredCursor {
728 replica_id: u16,
729 selection_id: usize,
730}
731
732impl Default for SelectionHistoryMode {
733 fn default() -> Self {
734 Self::Normal
735 }
736}
737
738#[derive(Default)]
739struct SelectionHistory {
740 #[allow(clippy::type_complexity)]
741 selections_by_transaction:
742 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
743 mode: SelectionHistoryMode,
744 undo_stack: VecDeque<SelectionHistoryEntry>,
745 redo_stack: VecDeque<SelectionHistoryEntry>,
746}
747
748impl SelectionHistory {
749 fn insert_transaction(
750 &mut self,
751 transaction_id: TransactionId,
752 selections: Arc<[Selection<Anchor>]>,
753 ) {
754 self.selections_by_transaction
755 .insert(transaction_id, (selections, None));
756 }
757
758 #[allow(clippy::type_complexity)]
759 fn transaction(
760 &self,
761 transaction_id: TransactionId,
762 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
763 self.selections_by_transaction.get(&transaction_id)
764 }
765
766 #[allow(clippy::type_complexity)]
767 fn transaction_mut(
768 &mut self,
769 transaction_id: TransactionId,
770 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
771 self.selections_by_transaction.get_mut(&transaction_id)
772 }
773
774 fn push(&mut self, entry: SelectionHistoryEntry) {
775 if !entry.selections.is_empty() {
776 match self.mode {
777 SelectionHistoryMode::Normal => {
778 self.push_undo(entry);
779 self.redo_stack.clear();
780 }
781 SelectionHistoryMode::Undoing => self.push_redo(entry),
782 SelectionHistoryMode::Redoing => self.push_undo(entry),
783 }
784 }
785 }
786
787 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
788 if self
789 .undo_stack
790 .back()
791 .map_or(true, |e| e.selections != entry.selections)
792 {
793 self.undo_stack.push_back(entry);
794 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
795 self.undo_stack.pop_front();
796 }
797 }
798 }
799
800 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
801 if self
802 .redo_stack
803 .back()
804 .map_or(true, |e| e.selections != entry.selections)
805 {
806 self.redo_stack.push_back(entry);
807 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
808 self.redo_stack.pop_front();
809 }
810 }
811 }
812}
813
814struct RowHighlight {
815 index: usize,
816 range: RangeInclusive<Anchor>,
817 color: Option<Hsla>,
818 should_autoscroll: bool,
819}
820
821#[derive(Clone, Debug)]
822struct AddSelectionsState {
823 above: bool,
824 stack: Vec<usize>,
825}
826
827#[derive(Clone)]
828struct SelectNextState {
829 query: AhoCorasick,
830 wordwise: bool,
831 done: bool,
832}
833
834impl std::fmt::Debug for SelectNextState {
835 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
836 f.debug_struct(std::any::type_name::<Self>())
837 .field("wordwise", &self.wordwise)
838 .field("done", &self.done)
839 .finish()
840 }
841}
842
843#[derive(Debug)]
844struct AutocloseRegion {
845 selection_id: usize,
846 range: Range<Anchor>,
847 pair: BracketPair,
848}
849
850#[derive(Debug)]
851struct SnippetState {
852 ranges: Vec<Vec<Range<Anchor>>>,
853 active_index: usize,
854}
855
856#[doc(hidden)]
857pub struct RenameState {
858 pub range: Range<Anchor>,
859 pub old_name: Arc<str>,
860 pub editor: View<Editor>,
861 block_id: CustomBlockId,
862}
863
864struct InvalidationStack<T>(Vec<T>);
865
866struct RegisteredInlineCompletionProvider {
867 provider: Arc<dyn InlineCompletionProviderHandle>,
868 _subscription: Subscription,
869}
870
871enum ContextMenu {
872 Completions(CompletionsMenu),
873 CodeActions(CodeActionsMenu),
874}
875
876impl ContextMenu {
877 fn select_first(
878 &mut self,
879 project: Option<&Model<Project>>,
880 cx: &mut ViewContext<Editor>,
881 ) -> bool {
882 if self.visible() {
883 match self {
884 ContextMenu::Completions(menu) => menu.select_first(project, cx),
885 ContextMenu::CodeActions(menu) => menu.select_first(cx),
886 }
887 true
888 } else {
889 false
890 }
891 }
892
893 fn select_prev(
894 &mut self,
895 project: Option<&Model<Project>>,
896 cx: &mut ViewContext<Editor>,
897 ) -> bool {
898 if self.visible() {
899 match self {
900 ContextMenu::Completions(menu) => menu.select_prev(project, cx),
901 ContextMenu::CodeActions(menu) => menu.select_prev(cx),
902 }
903 true
904 } else {
905 false
906 }
907 }
908
909 fn select_next(
910 &mut self,
911 project: Option<&Model<Project>>,
912 cx: &mut ViewContext<Editor>,
913 ) -> bool {
914 if self.visible() {
915 match self {
916 ContextMenu::Completions(menu) => menu.select_next(project, cx),
917 ContextMenu::CodeActions(menu) => menu.select_next(cx),
918 }
919 true
920 } else {
921 false
922 }
923 }
924
925 fn select_last(
926 &mut self,
927 project: Option<&Model<Project>>,
928 cx: &mut ViewContext<Editor>,
929 ) -> bool {
930 if self.visible() {
931 match self {
932 ContextMenu::Completions(menu) => menu.select_last(project, cx),
933 ContextMenu::CodeActions(menu) => menu.select_last(cx),
934 }
935 true
936 } else {
937 false
938 }
939 }
940
941 fn visible(&self) -> bool {
942 match self {
943 ContextMenu::Completions(menu) => menu.visible(),
944 ContextMenu::CodeActions(menu) => menu.visible(),
945 }
946 }
947
948 fn render(
949 &self,
950 cursor_position: DisplayPoint,
951 style: &EditorStyle,
952 max_height: Pixels,
953 workspace: Option<WeakView<Workspace>>,
954 cx: &mut ViewContext<Editor>,
955 ) -> (ContextMenuOrigin, AnyElement) {
956 match self {
957 ContextMenu::Completions(menu) => (
958 ContextMenuOrigin::EditorPoint(cursor_position),
959 menu.render(style, max_height, workspace, cx),
960 ),
961 ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, max_height, cx),
962 }
963 }
964}
965
966enum ContextMenuOrigin {
967 EditorPoint(DisplayPoint),
968 GutterIndicator(DisplayRow),
969}
970
971#[derive(Clone)]
972struct CompletionsMenu {
973 id: CompletionId,
974 sort_completions: bool,
975 initial_position: Anchor,
976 buffer: Model<Buffer>,
977 completions: Arc<RwLock<Box<[Completion]>>>,
978 match_candidates: Arc<[StringMatchCandidate]>,
979 matches: Arc<[StringMatch]>,
980 selected_item: usize,
981 scroll_handle: UniformListScrollHandle,
982 selected_completion_documentation_resolve_debounce: Arc<Mutex<DebouncedDelay>>,
983}
984
985impl CompletionsMenu {
986 fn select_first(&mut self, project: Option<&Model<Project>>, cx: &mut ViewContext<Editor>) {
987 self.selected_item = 0;
988 self.scroll_handle.scroll_to_item(self.selected_item);
989 self.attempt_resolve_selected_completion_documentation(project, cx);
990 cx.notify();
991 }
992
993 fn select_prev(&mut self, project: Option<&Model<Project>>, cx: &mut ViewContext<Editor>) {
994 if self.selected_item > 0 {
995 self.selected_item -= 1;
996 } else {
997 self.selected_item = self.matches.len() - 1;
998 }
999 self.scroll_handle.scroll_to_item(self.selected_item);
1000 self.attempt_resolve_selected_completion_documentation(project, cx);
1001 cx.notify();
1002 }
1003
1004 fn select_next(&mut self, project: Option<&Model<Project>>, cx: &mut ViewContext<Editor>) {
1005 if self.selected_item + 1 < self.matches.len() {
1006 self.selected_item += 1;
1007 } else {
1008 self.selected_item = 0;
1009 }
1010 self.scroll_handle.scroll_to_item(self.selected_item);
1011 self.attempt_resolve_selected_completion_documentation(project, cx);
1012 cx.notify();
1013 }
1014
1015 fn select_last(&mut self, project: Option<&Model<Project>>, cx: &mut ViewContext<Editor>) {
1016 self.selected_item = self.matches.len() - 1;
1017 self.scroll_handle.scroll_to_item(self.selected_item);
1018 self.attempt_resolve_selected_completion_documentation(project, cx);
1019 cx.notify();
1020 }
1021
1022 fn pre_resolve_completion_documentation(
1023 buffer: Model<Buffer>,
1024 completions: Arc<RwLock<Box<[Completion]>>>,
1025 matches: Arc<[StringMatch]>,
1026 editor: &Editor,
1027 cx: &mut ViewContext<Editor>,
1028 ) -> Task<()> {
1029 let settings = EditorSettings::get_global(cx);
1030 if !settings.show_completion_documentation {
1031 return Task::ready(());
1032 }
1033
1034 let Some(provider) = editor.completion_provider.as_ref() else {
1035 return Task::ready(());
1036 };
1037
1038 let resolve_task = provider.resolve_completions(
1039 buffer,
1040 matches.iter().map(|m| m.candidate_id).collect(),
1041 completions.clone(),
1042 cx,
1043 );
1044
1045 cx.spawn(move |this, mut cx| async move {
1046 if let Some(true) = resolve_task.await.log_err() {
1047 this.update(&mut cx, |_, cx| cx.notify()).ok();
1048 }
1049 })
1050 }
1051
1052 fn attempt_resolve_selected_completion_documentation(
1053 &mut self,
1054 project: Option<&Model<Project>>,
1055 cx: &mut ViewContext<Editor>,
1056 ) {
1057 let settings = EditorSettings::get_global(cx);
1058 if !settings.show_completion_documentation {
1059 return;
1060 }
1061
1062 let completion_index = self.matches[self.selected_item].candidate_id;
1063 let Some(project) = project else {
1064 return;
1065 };
1066
1067 let resolve_task = project.update(cx, |project, cx| {
1068 project.resolve_completions(
1069 self.buffer.clone(),
1070 vec![completion_index],
1071 self.completions.clone(),
1072 cx,
1073 )
1074 });
1075
1076 let delay_ms =
1077 EditorSettings::get_global(cx).completion_documentation_secondary_query_debounce;
1078 let delay = Duration::from_millis(delay_ms);
1079
1080 self.selected_completion_documentation_resolve_debounce
1081 .lock()
1082 .fire_new(delay, cx, |_, cx| {
1083 cx.spawn(move |this, mut cx| async move {
1084 if let Some(true) = resolve_task.await.log_err() {
1085 this.update(&mut cx, |_, cx| cx.notify()).ok();
1086 }
1087 })
1088 });
1089 }
1090
1091 fn visible(&self) -> bool {
1092 !self.matches.is_empty()
1093 }
1094
1095 fn render(
1096 &self,
1097 style: &EditorStyle,
1098 max_height: Pixels,
1099 workspace: Option<WeakView<Workspace>>,
1100 cx: &mut ViewContext<Editor>,
1101 ) -> AnyElement {
1102 let settings = EditorSettings::get_global(cx);
1103 let show_completion_documentation = settings.show_completion_documentation;
1104
1105 let widest_completion_ix = self
1106 .matches
1107 .iter()
1108 .enumerate()
1109 .max_by_key(|(_, mat)| {
1110 let completions = self.completions.read();
1111 let completion = &completions[mat.candidate_id];
1112 let documentation = &completion.documentation;
1113
1114 let mut len = completion.label.text.chars().count();
1115 if let Some(Documentation::SingleLine(text)) = documentation {
1116 if show_completion_documentation {
1117 len += text.chars().count();
1118 }
1119 }
1120
1121 len
1122 })
1123 .map(|(ix, _)| ix);
1124
1125 let completions = self.completions.clone();
1126 let matches = self.matches.clone();
1127 let selected_item = self.selected_item;
1128 let style = style.clone();
1129
1130 let multiline_docs = if show_completion_documentation {
1131 let mat = &self.matches[selected_item];
1132 let multiline_docs = match &self.completions.read()[mat.candidate_id].documentation {
1133 Some(Documentation::MultiLinePlainText(text)) => {
1134 Some(div().child(SharedString::from(text.clone())))
1135 }
1136 Some(Documentation::MultiLineMarkdown(parsed)) if !parsed.text.is_empty() => {
1137 Some(div().child(render_parsed_markdown(
1138 "completions_markdown",
1139 parsed,
1140 &style,
1141 workspace,
1142 cx,
1143 )))
1144 }
1145 _ => None,
1146 };
1147 multiline_docs.map(|div| {
1148 div.id("multiline_docs")
1149 .max_h(max_height)
1150 .flex_1()
1151 .px_1p5()
1152 .py_1()
1153 .min_w(px(260.))
1154 .max_w(px(640.))
1155 .w(px(500.))
1156 .overflow_y_scroll()
1157 .occlude()
1158 })
1159 } else {
1160 None
1161 };
1162
1163 let list = uniform_list(
1164 cx.view().clone(),
1165 "completions",
1166 matches.len(),
1167 move |_editor, range, cx| {
1168 let start_ix = range.start;
1169 let completions_guard = completions.read();
1170
1171 matches[range]
1172 .iter()
1173 .enumerate()
1174 .map(|(ix, mat)| {
1175 let item_ix = start_ix + ix;
1176 let candidate_id = mat.candidate_id;
1177 let completion = &completions_guard[candidate_id];
1178
1179 let documentation = if show_completion_documentation {
1180 &completion.documentation
1181 } else {
1182 &None
1183 };
1184
1185 let highlights = gpui::combine_highlights(
1186 mat.ranges().map(|range| (range, FontWeight::BOLD.into())),
1187 styled_runs_for_code_label(&completion.label, &style.syntax).map(
1188 |(range, mut highlight)| {
1189 // Ignore font weight for syntax highlighting, as we'll use it
1190 // for fuzzy matches.
1191 highlight.font_weight = None;
1192
1193 if completion.lsp_completion.deprecated.unwrap_or(false) {
1194 highlight.strikethrough = Some(StrikethroughStyle {
1195 thickness: 1.0.into(),
1196 ..Default::default()
1197 });
1198 highlight.color = Some(cx.theme().colors().text_muted);
1199 }
1200
1201 (range, highlight)
1202 },
1203 ),
1204 );
1205 let completion_label = StyledText::new(completion.label.text.clone())
1206 .with_highlights(&style.text, highlights);
1207 let documentation_label =
1208 if let Some(Documentation::SingleLine(text)) = documentation {
1209 if text.trim().is_empty() {
1210 None
1211 } else {
1212 Some(
1213 Label::new(text.clone())
1214 .ml_4()
1215 .size(LabelSize::Small)
1216 .color(Color::Muted),
1217 )
1218 }
1219 } else {
1220 None
1221 };
1222
1223 div().min_w(px(220.)).max_w(px(540.)).child(
1224 ListItem::new(mat.candidate_id)
1225 .inset(true)
1226 .selected(item_ix == selected_item)
1227 .on_click(cx.listener(move |editor, _event, cx| {
1228 cx.stop_propagation();
1229 if let Some(task) = editor.confirm_completion(
1230 &ConfirmCompletion {
1231 item_ix: Some(item_ix),
1232 },
1233 cx,
1234 ) {
1235 task.detach_and_log_err(cx)
1236 }
1237 }))
1238 .child(h_flex().overflow_hidden().child(completion_label))
1239 .end_slot::<Label>(documentation_label),
1240 )
1241 })
1242 .collect()
1243 },
1244 )
1245 .occlude()
1246 .max_h(max_height)
1247 .track_scroll(self.scroll_handle.clone())
1248 .with_width_from_item(widest_completion_ix)
1249 .with_sizing_behavior(ListSizingBehavior::Infer);
1250
1251 Popover::new()
1252 .child(list)
1253 .when_some(multiline_docs, |popover, multiline_docs| {
1254 popover.aside(multiline_docs)
1255 })
1256 .into_any_element()
1257 }
1258
1259 pub async fn filter(&mut self, query: Option<&str>, executor: BackgroundExecutor) {
1260 let mut matches = if let Some(query) = query {
1261 fuzzy::match_strings(
1262 &self.match_candidates,
1263 query,
1264 query.chars().any(|c| c.is_uppercase()),
1265 100,
1266 &Default::default(),
1267 executor,
1268 )
1269 .await
1270 } else {
1271 self.match_candidates
1272 .iter()
1273 .enumerate()
1274 .map(|(candidate_id, candidate)| StringMatch {
1275 candidate_id,
1276 score: Default::default(),
1277 positions: Default::default(),
1278 string: candidate.string.clone(),
1279 })
1280 .collect()
1281 };
1282
1283 // Remove all candidates where the query's start does not match the start of any word in the candidate
1284 if let Some(query) = query {
1285 if let Some(query_start) = query.chars().next() {
1286 matches.retain(|string_match| {
1287 split_words(&string_match.string).any(|word| {
1288 // Check that the first codepoint of the word as lowercase matches the first
1289 // codepoint of the query as lowercase
1290 word.chars()
1291 .flat_map(|codepoint| codepoint.to_lowercase())
1292 .zip(query_start.to_lowercase())
1293 .all(|(word_cp, query_cp)| word_cp == query_cp)
1294 })
1295 });
1296 }
1297 }
1298
1299 let completions = self.completions.read();
1300 if self.sort_completions {
1301 matches.sort_unstable_by_key(|mat| {
1302 // We do want to strike a balance here between what the language server tells us
1303 // to sort by (the sort_text) and what are "obvious" good matches (i.e. when you type
1304 // `Creat` and there is a local variable called `CreateComponent`).
1305 // So what we do is: we bucket all matches into two buckets
1306 // - Strong matches
1307 // - Weak matches
1308 // Strong matches are the ones with a high fuzzy-matcher score (the "obvious" matches)
1309 // and the Weak matches are the rest.
1310 //
1311 // For the strong matches, we sort by the language-servers score first and for the weak
1312 // matches, we prefer our fuzzy finder first.
1313 //
1314 // The thinking behind that: it's useless to take the sort_text the language-server gives
1315 // us into account when it's obviously a bad match.
1316
1317 #[derive(PartialEq, Eq, PartialOrd, Ord)]
1318 enum MatchScore<'a> {
1319 Strong {
1320 sort_text: Option<&'a str>,
1321 score: Reverse<OrderedFloat<f64>>,
1322 sort_key: (usize, &'a str),
1323 },
1324 Weak {
1325 score: Reverse<OrderedFloat<f64>>,
1326 sort_text: Option<&'a str>,
1327 sort_key: (usize, &'a str),
1328 },
1329 }
1330
1331 let completion = &completions[mat.candidate_id];
1332 let sort_key = completion.sort_key();
1333 let sort_text = completion.lsp_completion.sort_text.as_deref();
1334 let score = Reverse(OrderedFloat(mat.score));
1335
1336 if mat.score >= 0.2 {
1337 MatchScore::Strong {
1338 sort_text,
1339 score,
1340 sort_key,
1341 }
1342 } else {
1343 MatchScore::Weak {
1344 score,
1345 sort_text,
1346 sort_key,
1347 }
1348 }
1349 });
1350 }
1351
1352 for mat in &mut matches {
1353 let completion = &completions[mat.candidate_id];
1354 mat.string.clone_from(&completion.label.text);
1355 for position in &mut mat.positions {
1356 *position += completion.label.filter_range.start;
1357 }
1358 }
1359 drop(completions);
1360
1361 self.matches = matches.into();
1362 self.selected_item = 0;
1363 }
1364}
1365
1366struct AvailableCodeAction {
1367 excerpt_id: ExcerptId,
1368 action: CodeAction,
1369 provider: Arc<dyn CodeActionProvider>,
1370}
1371
1372#[derive(Clone)]
1373struct CodeActionContents {
1374 tasks: Option<Arc<ResolvedTasks>>,
1375 actions: Option<Arc<[AvailableCodeAction]>>,
1376}
1377
1378impl CodeActionContents {
1379 fn len(&self) -> usize {
1380 match (&self.tasks, &self.actions) {
1381 (Some(tasks), Some(actions)) => actions.len() + tasks.templates.len(),
1382 (Some(tasks), None) => tasks.templates.len(),
1383 (None, Some(actions)) => actions.len(),
1384 (None, None) => 0,
1385 }
1386 }
1387
1388 fn is_empty(&self) -> bool {
1389 match (&self.tasks, &self.actions) {
1390 (Some(tasks), Some(actions)) => actions.is_empty() && tasks.templates.is_empty(),
1391 (Some(tasks), None) => tasks.templates.is_empty(),
1392 (None, Some(actions)) => actions.is_empty(),
1393 (None, None) => true,
1394 }
1395 }
1396
1397 fn iter(&self) -> impl Iterator<Item = CodeActionsItem> + '_ {
1398 self.tasks
1399 .iter()
1400 .flat_map(|tasks| {
1401 tasks
1402 .templates
1403 .iter()
1404 .map(|(kind, task)| CodeActionsItem::Task(kind.clone(), task.clone()))
1405 })
1406 .chain(self.actions.iter().flat_map(|actions| {
1407 actions.iter().map(|available| CodeActionsItem::CodeAction {
1408 excerpt_id: available.excerpt_id,
1409 action: available.action.clone(),
1410 provider: available.provider.clone(),
1411 })
1412 }))
1413 }
1414 fn get(&self, index: usize) -> Option<CodeActionsItem> {
1415 match (&self.tasks, &self.actions) {
1416 (Some(tasks), Some(actions)) => {
1417 if index < tasks.templates.len() {
1418 tasks
1419 .templates
1420 .get(index)
1421 .cloned()
1422 .map(|(kind, task)| CodeActionsItem::Task(kind, task))
1423 } else {
1424 actions.get(index - tasks.templates.len()).map(|available| {
1425 CodeActionsItem::CodeAction {
1426 excerpt_id: available.excerpt_id,
1427 action: available.action.clone(),
1428 provider: available.provider.clone(),
1429 }
1430 })
1431 }
1432 }
1433 (Some(tasks), None) => tasks
1434 .templates
1435 .get(index)
1436 .cloned()
1437 .map(|(kind, task)| CodeActionsItem::Task(kind, task)),
1438 (None, Some(actions)) => {
1439 actions
1440 .get(index)
1441 .map(|available| CodeActionsItem::CodeAction {
1442 excerpt_id: available.excerpt_id,
1443 action: available.action.clone(),
1444 provider: available.provider.clone(),
1445 })
1446 }
1447 (None, None) => None,
1448 }
1449 }
1450}
1451
1452#[allow(clippy::large_enum_variant)]
1453#[derive(Clone)]
1454enum CodeActionsItem {
1455 Task(TaskSourceKind, ResolvedTask),
1456 CodeAction {
1457 excerpt_id: ExcerptId,
1458 action: CodeAction,
1459 provider: Arc<dyn CodeActionProvider>,
1460 },
1461}
1462
1463impl CodeActionsItem {
1464 fn as_task(&self) -> Option<&ResolvedTask> {
1465 let Self::Task(_, task) = self else {
1466 return None;
1467 };
1468 Some(task)
1469 }
1470 fn as_code_action(&self) -> Option<&CodeAction> {
1471 let Self::CodeAction { action, .. } = self else {
1472 return None;
1473 };
1474 Some(action)
1475 }
1476 fn label(&self) -> String {
1477 match self {
1478 Self::CodeAction { action, .. } => action.lsp_action.title.clone(),
1479 Self::Task(_, task) => task.resolved_label.clone(),
1480 }
1481 }
1482}
1483
1484struct CodeActionsMenu {
1485 actions: CodeActionContents,
1486 buffer: Model<Buffer>,
1487 selected_item: usize,
1488 scroll_handle: UniformListScrollHandle,
1489 deployed_from_indicator: Option<DisplayRow>,
1490}
1491
1492impl CodeActionsMenu {
1493 fn select_first(&mut self, cx: &mut ViewContext<Editor>) {
1494 self.selected_item = 0;
1495 self.scroll_handle.scroll_to_item(self.selected_item);
1496 cx.notify()
1497 }
1498
1499 fn select_prev(&mut self, cx: &mut ViewContext<Editor>) {
1500 if self.selected_item > 0 {
1501 self.selected_item -= 1;
1502 } else {
1503 self.selected_item = self.actions.len() - 1;
1504 }
1505 self.scroll_handle.scroll_to_item(self.selected_item);
1506 cx.notify();
1507 }
1508
1509 fn select_next(&mut self, cx: &mut ViewContext<Editor>) {
1510 if self.selected_item + 1 < self.actions.len() {
1511 self.selected_item += 1;
1512 } else {
1513 self.selected_item = 0;
1514 }
1515 self.scroll_handle.scroll_to_item(self.selected_item);
1516 cx.notify();
1517 }
1518
1519 fn select_last(&mut self, cx: &mut ViewContext<Editor>) {
1520 self.selected_item = self.actions.len() - 1;
1521 self.scroll_handle.scroll_to_item(self.selected_item);
1522 cx.notify()
1523 }
1524
1525 fn visible(&self) -> bool {
1526 !self.actions.is_empty()
1527 }
1528
1529 fn render(
1530 &self,
1531 cursor_position: DisplayPoint,
1532 _style: &EditorStyle,
1533 max_height: Pixels,
1534 cx: &mut ViewContext<Editor>,
1535 ) -> (ContextMenuOrigin, AnyElement) {
1536 let actions = self.actions.clone();
1537 let selected_item = self.selected_item;
1538 let element = uniform_list(
1539 cx.view().clone(),
1540 "code_actions_menu",
1541 self.actions.len(),
1542 move |_this, range, cx| {
1543 actions
1544 .iter()
1545 .skip(range.start)
1546 .take(range.end - range.start)
1547 .enumerate()
1548 .map(|(ix, action)| {
1549 let item_ix = range.start + ix;
1550 let selected = selected_item == item_ix;
1551 let colors = cx.theme().colors();
1552 div()
1553 .px_1()
1554 .rounded_md()
1555 .text_color(colors.text)
1556 .when(selected, |style| {
1557 style
1558 .bg(colors.element_active)
1559 .text_color(colors.text_accent)
1560 })
1561 .hover(|style| {
1562 style
1563 .bg(colors.element_hover)
1564 .text_color(colors.text_accent)
1565 })
1566 .whitespace_nowrap()
1567 .when_some(action.as_code_action(), |this, action| {
1568 this.on_mouse_down(
1569 MouseButton::Left,
1570 cx.listener(move |editor, _, cx| {
1571 cx.stop_propagation();
1572 if let Some(task) = editor.confirm_code_action(
1573 &ConfirmCodeAction {
1574 item_ix: Some(item_ix),
1575 },
1576 cx,
1577 ) {
1578 task.detach_and_log_err(cx)
1579 }
1580 }),
1581 )
1582 // TASK: It would be good to make lsp_action.title a SharedString to avoid allocating here.
1583 .child(SharedString::from(action.lsp_action.title.clone()))
1584 })
1585 .when_some(action.as_task(), |this, task| {
1586 this.on_mouse_down(
1587 MouseButton::Left,
1588 cx.listener(move |editor, _, cx| {
1589 cx.stop_propagation();
1590 if let Some(task) = editor.confirm_code_action(
1591 &ConfirmCodeAction {
1592 item_ix: Some(item_ix),
1593 },
1594 cx,
1595 ) {
1596 task.detach_and_log_err(cx)
1597 }
1598 }),
1599 )
1600 .child(SharedString::from(task.resolved_label.clone()))
1601 })
1602 })
1603 .collect()
1604 },
1605 )
1606 .elevation_1(cx)
1607 .p_1()
1608 .max_h(max_height)
1609 .occlude()
1610 .track_scroll(self.scroll_handle.clone())
1611 .with_width_from_item(
1612 self.actions
1613 .iter()
1614 .enumerate()
1615 .max_by_key(|(_, action)| match action {
1616 CodeActionsItem::Task(_, task) => task.resolved_label.chars().count(),
1617 CodeActionsItem::CodeAction { action, .. } => {
1618 action.lsp_action.title.chars().count()
1619 }
1620 })
1621 .map(|(ix, _)| ix),
1622 )
1623 .with_sizing_behavior(ListSizingBehavior::Infer)
1624 .into_any_element();
1625
1626 let cursor_position = if let Some(row) = self.deployed_from_indicator {
1627 ContextMenuOrigin::GutterIndicator(row)
1628 } else {
1629 ContextMenuOrigin::EditorPoint(cursor_position)
1630 };
1631
1632 (cursor_position, element)
1633 }
1634}
1635
1636#[derive(Debug)]
1637struct ActiveDiagnosticGroup {
1638 primary_range: Range<Anchor>,
1639 primary_message: String,
1640 group_id: usize,
1641 blocks: HashMap<CustomBlockId, Diagnostic>,
1642 is_valid: bool,
1643}
1644
1645#[derive(Serialize, Deserialize, Clone, Debug)]
1646pub struct ClipboardSelection {
1647 pub len: usize,
1648 pub is_entire_line: bool,
1649 pub first_line_indent: u32,
1650}
1651
1652#[derive(Debug)]
1653pub(crate) struct NavigationData {
1654 cursor_anchor: Anchor,
1655 cursor_position: Point,
1656 scroll_anchor: ScrollAnchor,
1657 scroll_top_row: u32,
1658}
1659
1660#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1661enum GotoDefinitionKind {
1662 Symbol,
1663 Declaration,
1664 Type,
1665 Implementation,
1666}
1667
1668#[derive(Debug, Clone)]
1669enum InlayHintRefreshReason {
1670 Toggle(bool),
1671 SettingsChange(InlayHintSettings),
1672 NewLinesShown,
1673 BufferEdited(HashSet<Arc<Language>>),
1674 RefreshRequested,
1675 ExcerptsRemoved(Vec<ExcerptId>),
1676}
1677
1678impl InlayHintRefreshReason {
1679 fn description(&self) -> &'static str {
1680 match self {
1681 Self::Toggle(_) => "toggle",
1682 Self::SettingsChange(_) => "settings change",
1683 Self::NewLinesShown => "new lines shown",
1684 Self::BufferEdited(_) => "buffer edited",
1685 Self::RefreshRequested => "refresh requested",
1686 Self::ExcerptsRemoved(_) => "excerpts removed",
1687 }
1688 }
1689}
1690
1691pub(crate) struct FocusedBlock {
1692 id: BlockId,
1693 focus_handle: WeakFocusHandle,
1694}
1695
1696impl Editor {
1697 pub fn single_line(cx: &mut ViewContext<Self>) -> Self {
1698 let buffer = cx.new_model(|cx| Buffer::local("", cx));
1699 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
1700 Self::new(
1701 EditorMode::SingleLine { auto_width: false },
1702 buffer,
1703 None,
1704 false,
1705 cx,
1706 )
1707 }
1708
1709 pub fn multi_line(cx: &mut ViewContext<Self>) -> Self {
1710 let buffer = cx.new_model(|cx| Buffer::local("", cx));
1711 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
1712 Self::new(EditorMode::Full, buffer, None, false, cx)
1713 }
1714
1715 pub fn auto_width(cx: &mut ViewContext<Self>) -> Self {
1716 let buffer = cx.new_model(|cx| Buffer::local("", cx));
1717 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
1718 Self::new(
1719 EditorMode::SingleLine { auto_width: true },
1720 buffer,
1721 None,
1722 false,
1723 cx,
1724 )
1725 }
1726
1727 pub fn auto_height(max_lines: usize, cx: &mut ViewContext<Self>) -> Self {
1728 let buffer = cx.new_model(|cx| Buffer::local("", cx));
1729 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
1730 Self::new(
1731 EditorMode::AutoHeight { max_lines },
1732 buffer,
1733 None,
1734 false,
1735 cx,
1736 )
1737 }
1738
1739 pub fn for_buffer(
1740 buffer: Model<Buffer>,
1741 project: Option<Model<Project>>,
1742 cx: &mut ViewContext<Self>,
1743 ) -> Self {
1744 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
1745 Self::new(EditorMode::Full, buffer, project, false, cx)
1746 }
1747
1748 pub fn for_multibuffer(
1749 buffer: Model<MultiBuffer>,
1750 project: Option<Model<Project>>,
1751 show_excerpt_controls: bool,
1752 cx: &mut ViewContext<Self>,
1753 ) -> Self {
1754 Self::new(EditorMode::Full, buffer, project, show_excerpt_controls, cx)
1755 }
1756
1757 pub fn clone(&self, cx: &mut ViewContext<Self>) -> Self {
1758 let show_excerpt_controls = self.display_map.read(cx).show_excerpt_controls();
1759 let mut clone = Self::new(
1760 self.mode,
1761 self.buffer.clone(),
1762 self.project.clone(),
1763 show_excerpt_controls,
1764 cx,
1765 );
1766 self.display_map.update(cx, |display_map, cx| {
1767 let snapshot = display_map.snapshot(cx);
1768 clone.display_map.update(cx, |display_map, cx| {
1769 display_map.set_state(&snapshot, cx);
1770 });
1771 });
1772 clone.selections.clone_state(&self.selections);
1773 clone.scroll_manager.clone_state(&self.scroll_manager);
1774 clone.searchable = self.searchable;
1775 clone
1776 }
1777
1778 pub fn new(
1779 mode: EditorMode,
1780 buffer: Model<MultiBuffer>,
1781 project: Option<Model<Project>>,
1782 show_excerpt_controls: bool,
1783 cx: &mut ViewContext<Self>,
1784 ) -> Self {
1785 let style = cx.text_style();
1786 let font_size = style.font_size.to_pixels(cx.rem_size());
1787 let editor = cx.view().downgrade();
1788 let fold_placeholder = FoldPlaceholder {
1789 constrain_width: true,
1790 render: Arc::new(move |fold_id, fold_range, cx| {
1791 let editor = editor.clone();
1792 div()
1793 .id(fold_id)
1794 .bg(cx.theme().colors().ghost_element_background)
1795 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1796 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1797 .rounded_sm()
1798 .size_full()
1799 .cursor_pointer()
1800 .child("⋯")
1801 .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
1802 .on_click(move |_, cx| {
1803 editor
1804 .update(cx, |editor, cx| {
1805 editor.unfold_ranges(
1806 [fold_range.start..fold_range.end],
1807 true,
1808 false,
1809 cx,
1810 );
1811 cx.stop_propagation();
1812 })
1813 .ok();
1814 })
1815 .into_any()
1816 }),
1817 merge_adjacent: true,
1818 };
1819 let file_header_size = if show_excerpt_controls { 3 } else { 2 };
1820 let display_map = cx.new_model(|cx| {
1821 DisplayMap::new(
1822 buffer.clone(),
1823 style.font(),
1824 font_size,
1825 None,
1826 show_excerpt_controls,
1827 file_header_size,
1828 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1829 MULTI_BUFFER_EXCERPT_FOOTER_HEIGHT,
1830 fold_placeholder,
1831 cx,
1832 )
1833 });
1834
1835 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1836
1837 let blink_manager = cx.new_model(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1838
1839 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1840 .then(|| language_settings::SoftWrap::PreferLine);
1841
1842 let mut project_subscriptions = Vec::new();
1843 if mode == EditorMode::Full {
1844 if let Some(project) = project.as_ref() {
1845 if buffer.read(cx).is_singleton() {
1846 project_subscriptions.push(cx.observe(project, |_, _, cx| {
1847 cx.emit(EditorEvent::TitleChanged);
1848 }));
1849 }
1850 project_subscriptions.push(cx.subscribe(project, |editor, _, event, cx| {
1851 if let project::Event::RefreshInlayHints = event {
1852 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1853 } else if let project::Event::SnippetEdit(id, snippet_edits) = event {
1854 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1855 let focus_handle = editor.focus_handle(cx);
1856 if focus_handle.is_focused(cx) {
1857 let snapshot = buffer.read(cx).snapshot();
1858 for (range, snippet) in snippet_edits {
1859 let editor_range =
1860 language::range_from_lsp(*range).to_offset(&snapshot);
1861 editor
1862 .insert_snippet(&[editor_range], snippet.clone(), cx)
1863 .ok();
1864 }
1865 }
1866 }
1867 }
1868 }));
1869 let task_inventory = project.read(cx).task_inventory().clone();
1870 project_subscriptions.push(cx.observe(&task_inventory, |editor, _, cx| {
1871 editor.tasks_update_task = Some(editor.refresh_runnables(cx));
1872 }));
1873 }
1874 }
1875
1876 let inlay_hint_settings = inlay_hint_settings(
1877 selections.newest_anchor().head(),
1878 &buffer.read(cx).snapshot(cx),
1879 cx,
1880 );
1881 let focus_handle = cx.focus_handle();
1882 cx.on_focus(&focus_handle, Self::handle_focus).detach();
1883 cx.on_focus_in(&focus_handle, Self::handle_focus_in)
1884 .detach();
1885 cx.on_focus_out(&focus_handle, Self::handle_focus_out)
1886 .detach();
1887 cx.on_blur(&focus_handle, Self::handle_blur).detach();
1888
1889 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1890 Some(false)
1891 } else {
1892 None
1893 };
1894
1895 let mut code_action_providers = Vec::new();
1896 if let Some(project) = project.clone() {
1897 code_action_providers.push(Arc::new(project) as Arc<_>);
1898 }
1899
1900 let mut this = Self {
1901 focus_handle,
1902 show_cursor_when_unfocused: false,
1903 last_focused_descendant: None,
1904 buffer: buffer.clone(),
1905 display_map: display_map.clone(),
1906 selections,
1907 scroll_manager: ScrollManager::new(cx),
1908 columnar_selection_tail: None,
1909 add_selections_state: None,
1910 select_next_state: None,
1911 select_prev_state: None,
1912 selection_history: Default::default(),
1913 autoclose_regions: Default::default(),
1914 snippet_stack: Default::default(),
1915 select_larger_syntax_node_stack: Vec::new(),
1916 ime_transaction: Default::default(),
1917 active_diagnostics: None,
1918 soft_wrap_mode_override,
1919 completion_provider: project.clone().map(|project| Box::new(project) as _),
1920 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1921 project,
1922 blink_manager: blink_manager.clone(),
1923 show_local_selections: true,
1924 mode,
1925 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1926 show_gutter: mode == EditorMode::Full,
1927 show_line_numbers: None,
1928 use_relative_line_numbers: None,
1929 show_git_diff_gutter: None,
1930 show_code_actions: None,
1931 show_runnables: None,
1932 show_wrap_guides: None,
1933 show_indent_guides,
1934 placeholder_text: None,
1935 highlight_order: 0,
1936 highlighted_rows: HashMap::default(),
1937 background_highlights: Default::default(),
1938 gutter_highlights: TreeMap::default(),
1939 scrollbar_marker_state: ScrollbarMarkerState::default(),
1940 active_indent_guides_state: ActiveIndentGuidesState::default(),
1941 nav_history: None,
1942 context_menu: RwLock::new(None),
1943 mouse_context_menu: None,
1944 hunk_controls_menu_handle: PopoverMenuHandle::default(),
1945 completion_tasks: Default::default(),
1946 signature_help_state: SignatureHelpState::default(),
1947 auto_signature_help: None,
1948 find_all_references_task_sources: Vec::new(),
1949 next_completion_id: 0,
1950 completion_documentation_pre_resolve_debounce: DebouncedDelay::new(),
1951 next_inlay_id: 0,
1952 code_action_providers,
1953 available_code_actions: Default::default(),
1954 code_actions_task: Default::default(),
1955 document_highlights_task: Default::default(),
1956 linked_editing_range_task: Default::default(),
1957 pending_rename: Default::default(),
1958 searchable: true,
1959 cursor_shape: EditorSettings::get_global(cx)
1960 .cursor_shape
1961 .unwrap_or_default(),
1962 current_line_highlight: None,
1963 autoindent_mode: Some(AutoindentMode::EachLine),
1964 collapse_matches: false,
1965 workspace: None,
1966 input_enabled: true,
1967 use_modal_editing: mode == EditorMode::Full,
1968 read_only: false,
1969 use_autoclose: true,
1970 use_auto_surround: true,
1971 auto_replace_emoji_shortcode: false,
1972 leader_peer_id: None,
1973 remote_id: None,
1974 hover_state: Default::default(),
1975 hovered_link_state: Default::default(),
1976 inline_completion_provider: None,
1977 active_inline_completion: None,
1978 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1979 expanded_hunks: ExpandedHunks::default(),
1980 gutter_hovered: false,
1981 pixel_position_of_newest_cursor: None,
1982 last_bounds: None,
1983 expect_bounds_change: None,
1984 gutter_dimensions: GutterDimensions::default(),
1985 style: None,
1986 show_cursor_names: false,
1987 hovered_cursors: Default::default(),
1988 next_editor_action_id: EditorActionId::default(),
1989 editor_actions: Rc::default(),
1990 show_inline_completions_override: None,
1991 enable_inline_completions: true,
1992 custom_context_menu: None,
1993 show_git_blame_gutter: false,
1994 show_git_blame_inline: false,
1995 show_selection_menu: None,
1996 show_git_blame_inline_delay_task: None,
1997 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1998 serialize_dirty_buffers: ProjectSettings::get_global(cx)
1999 .session
2000 .restore_unsaved_buffers,
2001 blame: None,
2002 blame_subscription: None,
2003 file_header_size,
2004 tasks: Default::default(),
2005 _subscriptions: vec![
2006 cx.observe(&buffer, Self::on_buffer_changed),
2007 cx.subscribe(&buffer, Self::on_buffer_event),
2008 cx.observe(&display_map, Self::on_display_map_changed),
2009 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2010 cx.observe_global::<SettingsStore>(Self::settings_changed),
2011 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2012 cx.observe_window_activation(|editor, cx| {
2013 let active = cx.is_window_active();
2014 editor.blink_manager.update(cx, |blink_manager, cx| {
2015 if active {
2016 blink_manager.enable(cx);
2017 } else {
2018 blink_manager.disable(cx);
2019 }
2020 });
2021 }),
2022 ],
2023 tasks_update_task: None,
2024 linked_edit_ranges: Default::default(),
2025 previous_search_ranges: None,
2026 breadcrumb_header: None,
2027 focused_block: None,
2028 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2029 addons: HashMap::default(),
2030 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2031 };
2032 this.tasks_update_task = Some(this.refresh_runnables(cx));
2033 this._subscriptions.extend(project_subscriptions);
2034
2035 this.end_selection(cx);
2036 this.scroll_manager.show_scrollbar(cx);
2037
2038 if mode == EditorMode::Full {
2039 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2040 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2041
2042 if this.git_blame_inline_enabled {
2043 this.git_blame_inline_enabled = true;
2044 this.start_git_blame_inline(false, cx);
2045 }
2046 }
2047
2048 this.report_editor_event("open", None, cx);
2049 this
2050 }
2051
2052 pub fn mouse_menu_is_focused(&self, cx: &WindowContext) -> bool {
2053 self.mouse_context_menu
2054 .as_ref()
2055 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(cx))
2056 }
2057
2058 fn key_context(&self, cx: &ViewContext<Self>) -> KeyContext {
2059 let mut key_context = KeyContext::new_with_defaults();
2060 key_context.add("Editor");
2061 let mode = match self.mode {
2062 EditorMode::SingleLine { .. } => "single_line",
2063 EditorMode::AutoHeight { .. } => "auto_height",
2064 EditorMode::Full => "full",
2065 };
2066
2067 if EditorSettings::jupyter_enabled(cx) {
2068 key_context.add("jupyter");
2069 }
2070
2071 key_context.set("mode", mode);
2072 if self.pending_rename.is_some() {
2073 key_context.add("renaming");
2074 }
2075 if self.context_menu_visible() {
2076 match self.context_menu.read().as_ref() {
2077 Some(ContextMenu::Completions(_)) => {
2078 key_context.add("menu");
2079 key_context.add("showing_completions")
2080 }
2081 Some(ContextMenu::CodeActions(_)) => {
2082 key_context.add("menu");
2083 key_context.add("showing_code_actions")
2084 }
2085 None => {}
2086 }
2087 }
2088
2089 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2090 if !self.focus_handle(cx).contains_focused(cx)
2091 || (self.is_focused(cx) || self.mouse_menu_is_focused(cx))
2092 {
2093 for addon in self.addons.values() {
2094 addon.extend_key_context(&mut key_context, cx)
2095 }
2096 }
2097
2098 if let Some(extension) = self
2099 .buffer
2100 .read(cx)
2101 .as_singleton()
2102 .and_then(|buffer| buffer.read(cx).file()?.path().extension()?.to_str())
2103 {
2104 key_context.set("extension", extension.to_string());
2105 }
2106
2107 if self.has_active_inline_completion(cx) {
2108 key_context.add("copilot_suggestion");
2109 key_context.add("inline_completion");
2110 }
2111
2112 key_context
2113 }
2114
2115 pub fn new_file(
2116 workspace: &mut Workspace,
2117 _: &workspace::NewFile,
2118 cx: &mut ViewContext<Workspace>,
2119 ) {
2120 Self::new_in_workspace(workspace, cx).detach_and_prompt_err(
2121 "Failed to create buffer",
2122 cx,
2123 |e, _| match e.error_code() {
2124 ErrorCode::RemoteUpgradeRequired => Some(format!(
2125 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2126 e.error_tag("required").unwrap_or("the latest version")
2127 )),
2128 _ => None,
2129 },
2130 );
2131 }
2132
2133 pub fn new_in_workspace(
2134 workspace: &mut Workspace,
2135 cx: &mut ViewContext<Workspace>,
2136 ) -> Task<Result<View<Editor>>> {
2137 let project = workspace.project().clone();
2138 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2139
2140 cx.spawn(|workspace, mut cx| async move {
2141 let buffer = create.await?;
2142 workspace.update(&mut cx, |workspace, cx| {
2143 let editor =
2144 cx.new_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx));
2145 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, cx);
2146 editor
2147 })
2148 })
2149 }
2150
2151 fn new_file_vertical(
2152 workspace: &mut Workspace,
2153 _: &workspace::NewFileSplitVertical,
2154 cx: &mut ViewContext<Workspace>,
2155 ) {
2156 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), cx)
2157 }
2158
2159 fn new_file_horizontal(
2160 workspace: &mut Workspace,
2161 _: &workspace::NewFileSplitHorizontal,
2162 cx: &mut ViewContext<Workspace>,
2163 ) {
2164 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), cx)
2165 }
2166
2167 fn new_file_in_direction(
2168 workspace: &mut Workspace,
2169 direction: SplitDirection,
2170 cx: &mut ViewContext<Workspace>,
2171 ) {
2172 let project = workspace.project().clone();
2173 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2174
2175 cx.spawn(|workspace, mut cx| async move {
2176 let buffer = create.await?;
2177 workspace.update(&mut cx, move |workspace, cx| {
2178 workspace.split_item(
2179 direction,
2180 Box::new(
2181 cx.new_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx)),
2182 ),
2183 cx,
2184 )
2185 })?;
2186 anyhow::Ok(())
2187 })
2188 .detach_and_prompt_err("Failed to create buffer", cx, |e, _| match e.error_code() {
2189 ErrorCode::RemoteUpgradeRequired => Some(format!(
2190 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2191 e.error_tag("required").unwrap_or("the latest version")
2192 )),
2193 _ => None,
2194 });
2195 }
2196
2197 pub fn leader_peer_id(&self) -> Option<PeerId> {
2198 self.leader_peer_id
2199 }
2200
2201 pub fn buffer(&self) -> &Model<MultiBuffer> {
2202 &self.buffer
2203 }
2204
2205 pub fn workspace(&self) -> Option<View<Workspace>> {
2206 self.workspace.as_ref()?.0.upgrade()
2207 }
2208
2209 pub fn title<'a>(&self, cx: &'a AppContext) -> Cow<'a, str> {
2210 self.buffer().read(cx).title(cx)
2211 }
2212
2213 pub fn snapshot(&mut self, cx: &mut WindowContext) -> EditorSnapshot {
2214 EditorSnapshot {
2215 mode: self.mode,
2216 show_gutter: self.show_gutter,
2217 show_line_numbers: self.show_line_numbers,
2218 show_git_diff_gutter: self.show_git_diff_gutter,
2219 show_code_actions: self.show_code_actions,
2220 show_runnables: self.show_runnables,
2221 render_git_blame_gutter: self.render_git_blame_gutter(cx),
2222 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2223 scroll_anchor: self.scroll_manager.anchor(),
2224 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2225 placeholder_text: self.placeholder_text.clone(),
2226 is_focused: self.focus_handle.is_focused(cx),
2227 current_line_highlight: self
2228 .current_line_highlight
2229 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2230 gutter_hovered: self.gutter_hovered,
2231 }
2232 }
2233
2234 pub fn language_at<T: ToOffset>(&self, point: T, cx: &AppContext) -> Option<Arc<Language>> {
2235 self.buffer.read(cx).language_at(point, cx)
2236 }
2237
2238 pub fn file_at<T: ToOffset>(
2239 &self,
2240 point: T,
2241 cx: &AppContext,
2242 ) -> Option<Arc<dyn language::File>> {
2243 self.buffer.read(cx).read(cx).file_at(point).cloned()
2244 }
2245
2246 pub fn active_excerpt(
2247 &self,
2248 cx: &AppContext,
2249 ) -> Option<(ExcerptId, Model<Buffer>, Range<text::Anchor>)> {
2250 self.buffer
2251 .read(cx)
2252 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2253 }
2254
2255 pub fn mode(&self) -> EditorMode {
2256 self.mode
2257 }
2258
2259 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2260 self.collaboration_hub.as_deref()
2261 }
2262
2263 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2264 self.collaboration_hub = Some(hub);
2265 }
2266
2267 pub fn set_custom_context_menu(
2268 &mut self,
2269 f: impl 'static
2270 + Fn(&mut Self, DisplayPoint, &mut ViewContext<Self>) -> Option<View<ui::ContextMenu>>,
2271 ) {
2272 self.custom_context_menu = Some(Box::new(f))
2273 }
2274
2275 pub fn set_completion_provider(&mut self, provider: Box<dyn CompletionProvider>) {
2276 self.completion_provider = Some(provider);
2277 }
2278
2279 pub fn set_inline_completion_provider<T>(
2280 &mut self,
2281 provider: Option<Model<T>>,
2282 cx: &mut ViewContext<Self>,
2283 ) where
2284 T: InlineCompletionProvider,
2285 {
2286 self.inline_completion_provider =
2287 provider.map(|provider| RegisteredInlineCompletionProvider {
2288 _subscription: cx.observe(&provider, |this, _, cx| {
2289 if this.focus_handle.is_focused(cx) {
2290 this.update_visible_inline_completion(cx);
2291 }
2292 }),
2293 provider: Arc::new(provider),
2294 });
2295 self.refresh_inline_completion(false, false, cx);
2296 }
2297
2298 pub fn placeholder_text(&self, _cx: &WindowContext) -> Option<&str> {
2299 self.placeholder_text.as_deref()
2300 }
2301
2302 pub fn set_placeholder_text(
2303 &mut self,
2304 placeholder_text: impl Into<Arc<str>>,
2305 cx: &mut ViewContext<Self>,
2306 ) {
2307 let placeholder_text = Some(placeholder_text.into());
2308 if self.placeholder_text != placeholder_text {
2309 self.placeholder_text = placeholder_text;
2310 cx.notify();
2311 }
2312 }
2313
2314 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut ViewContext<Self>) {
2315 self.cursor_shape = cursor_shape;
2316
2317 // Disrupt blink for immediate user feedback that the cursor shape has changed
2318 self.blink_manager.update(cx, BlinkManager::show_cursor);
2319
2320 cx.notify();
2321 }
2322
2323 pub fn set_current_line_highlight(
2324 &mut self,
2325 current_line_highlight: Option<CurrentLineHighlight>,
2326 ) {
2327 self.current_line_highlight = current_line_highlight;
2328 }
2329
2330 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2331 self.collapse_matches = collapse_matches;
2332 }
2333
2334 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2335 if self.collapse_matches {
2336 return range.start..range.start;
2337 }
2338 range.clone()
2339 }
2340
2341 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut ViewContext<Self>) {
2342 if self.display_map.read(cx).clip_at_line_ends != clip {
2343 self.display_map
2344 .update(cx, |map, _| map.clip_at_line_ends = clip);
2345 }
2346 }
2347
2348 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2349 self.input_enabled = input_enabled;
2350 }
2351
2352 pub fn set_inline_completions_enabled(&mut self, enabled: bool) {
2353 self.enable_inline_completions = enabled;
2354 }
2355
2356 pub fn set_autoindent(&mut self, autoindent: bool) {
2357 if autoindent {
2358 self.autoindent_mode = Some(AutoindentMode::EachLine);
2359 } else {
2360 self.autoindent_mode = None;
2361 }
2362 }
2363
2364 pub fn read_only(&self, cx: &AppContext) -> bool {
2365 self.read_only || self.buffer.read(cx).read_only()
2366 }
2367
2368 pub fn set_read_only(&mut self, read_only: bool) {
2369 self.read_only = read_only;
2370 }
2371
2372 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2373 self.use_autoclose = autoclose;
2374 }
2375
2376 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2377 self.use_auto_surround = auto_surround;
2378 }
2379
2380 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2381 self.auto_replace_emoji_shortcode = auto_replace;
2382 }
2383
2384 pub fn toggle_inline_completions(
2385 &mut self,
2386 _: &ToggleInlineCompletions,
2387 cx: &mut ViewContext<Self>,
2388 ) {
2389 if self.show_inline_completions_override.is_some() {
2390 self.set_show_inline_completions(None, cx);
2391 } else {
2392 let cursor = self.selections.newest_anchor().head();
2393 if let Some((buffer, cursor_buffer_position)) =
2394 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
2395 {
2396 let show_inline_completions =
2397 !self.should_show_inline_completions(&buffer, cursor_buffer_position, cx);
2398 self.set_show_inline_completions(Some(show_inline_completions), cx);
2399 }
2400 }
2401 }
2402
2403 pub fn set_show_inline_completions(
2404 &mut self,
2405 show_inline_completions: Option<bool>,
2406 cx: &mut ViewContext<Self>,
2407 ) {
2408 self.show_inline_completions_override = show_inline_completions;
2409 self.refresh_inline_completion(false, true, cx);
2410 }
2411
2412 fn should_show_inline_completions(
2413 &self,
2414 buffer: &Model<Buffer>,
2415 buffer_position: language::Anchor,
2416 cx: &AppContext,
2417 ) -> bool {
2418 if let Some(provider) = self.inline_completion_provider() {
2419 if let Some(show_inline_completions) = self.show_inline_completions_override {
2420 show_inline_completions
2421 } else {
2422 self.mode == EditorMode::Full && provider.is_enabled(buffer, buffer_position, cx)
2423 }
2424 } else {
2425 false
2426 }
2427 }
2428
2429 pub fn set_use_modal_editing(&mut self, to: bool) {
2430 self.use_modal_editing = to;
2431 }
2432
2433 pub fn use_modal_editing(&self) -> bool {
2434 self.use_modal_editing
2435 }
2436
2437 fn selections_did_change(
2438 &mut self,
2439 local: bool,
2440 old_cursor_position: &Anchor,
2441 show_completions: bool,
2442 cx: &mut ViewContext<Self>,
2443 ) {
2444 cx.invalidate_character_coordinates();
2445
2446 // Copy selections to primary selection buffer
2447 #[cfg(target_os = "linux")]
2448 if local {
2449 let selections = self.selections.all::<usize>(cx);
2450 let buffer_handle = self.buffer.read(cx).read(cx);
2451
2452 let mut text = String::new();
2453 for (index, selection) in selections.iter().enumerate() {
2454 let text_for_selection = buffer_handle
2455 .text_for_range(selection.start..selection.end)
2456 .collect::<String>();
2457
2458 text.push_str(&text_for_selection);
2459 if index != selections.len() - 1 {
2460 text.push('\n');
2461 }
2462 }
2463
2464 if !text.is_empty() {
2465 cx.write_to_primary(ClipboardItem::new_string(text));
2466 }
2467 }
2468
2469 if self.focus_handle.is_focused(cx) && self.leader_peer_id.is_none() {
2470 self.buffer.update(cx, |buffer, cx| {
2471 buffer.set_active_selections(
2472 &self.selections.disjoint_anchors(),
2473 self.selections.line_mode,
2474 self.cursor_shape,
2475 cx,
2476 )
2477 });
2478 }
2479 let display_map = self
2480 .display_map
2481 .update(cx, |display_map, cx| display_map.snapshot(cx));
2482 let buffer = &display_map.buffer_snapshot;
2483 self.add_selections_state = None;
2484 self.select_next_state = None;
2485 self.select_prev_state = None;
2486 self.select_larger_syntax_node_stack.clear();
2487 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2488 self.snippet_stack
2489 .invalidate(&self.selections.disjoint_anchors(), buffer);
2490 self.take_rename(false, cx);
2491
2492 let new_cursor_position = self.selections.newest_anchor().head();
2493
2494 self.push_to_nav_history(
2495 *old_cursor_position,
2496 Some(new_cursor_position.to_point(buffer)),
2497 cx,
2498 );
2499
2500 if local {
2501 let new_cursor_position = self.selections.newest_anchor().head();
2502 let mut context_menu = self.context_menu.write();
2503 let completion_menu = match context_menu.as_ref() {
2504 Some(ContextMenu::Completions(menu)) => Some(menu),
2505
2506 _ => {
2507 *context_menu = None;
2508 None
2509 }
2510 };
2511
2512 if let Some(completion_menu) = completion_menu {
2513 let cursor_position = new_cursor_position.to_offset(buffer);
2514 let (word_range, kind) =
2515 buffer.surrounding_word(completion_menu.initial_position, true);
2516 if kind == Some(CharKind::Word)
2517 && word_range.to_inclusive().contains(&cursor_position)
2518 {
2519 let mut completion_menu = completion_menu.clone();
2520 drop(context_menu);
2521
2522 let query = Self::completion_query(buffer, cursor_position);
2523 cx.spawn(move |this, mut cx| async move {
2524 completion_menu
2525 .filter(query.as_deref(), cx.background_executor().clone())
2526 .await;
2527
2528 this.update(&mut cx, |this, cx| {
2529 let mut context_menu = this.context_menu.write();
2530 let Some(ContextMenu::Completions(menu)) = context_menu.as_ref() else {
2531 return;
2532 };
2533
2534 if menu.id > completion_menu.id {
2535 return;
2536 }
2537
2538 *context_menu = Some(ContextMenu::Completions(completion_menu));
2539 drop(context_menu);
2540 cx.notify();
2541 })
2542 })
2543 .detach();
2544
2545 if show_completions {
2546 self.show_completions(&ShowCompletions { trigger: None }, cx);
2547 }
2548 } else {
2549 drop(context_menu);
2550 self.hide_context_menu(cx);
2551 }
2552 } else {
2553 drop(context_menu);
2554 }
2555
2556 hide_hover(self, cx);
2557
2558 if old_cursor_position.to_display_point(&display_map).row()
2559 != new_cursor_position.to_display_point(&display_map).row()
2560 {
2561 self.available_code_actions.take();
2562 }
2563 self.refresh_code_actions(cx);
2564 self.refresh_document_highlights(cx);
2565 refresh_matching_bracket_highlights(self, cx);
2566 self.discard_inline_completion(false, cx);
2567 linked_editing_ranges::refresh_linked_ranges(self, cx);
2568 if self.git_blame_inline_enabled {
2569 self.start_inline_blame_timer(cx);
2570 }
2571 }
2572
2573 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2574 cx.emit(EditorEvent::SelectionsChanged { local });
2575
2576 if self.selections.disjoint_anchors().len() == 1 {
2577 cx.emit(SearchEvent::ActiveMatchChanged)
2578 }
2579 cx.notify();
2580 }
2581
2582 pub fn change_selections<R>(
2583 &mut self,
2584 autoscroll: Option<Autoscroll>,
2585 cx: &mut ViewContext<Self>,
2586 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2587 ) -> R {
2588 self.change_selections_inner(autoscroll, true, cx, change)
2589 }
2590
2591 pub fn change_selections_inner<R>(
2592 &mut self,
2593 autoscroll: Option<Autoscroll>,
2594 request_completions: bool,
2595 cx: &mut ViewContext<Self>,
2596 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2597 ) -> R {
2598 let old_cursor_position = self.selections.newest_anchor().head();
2599 self.push_to_selection_history();
2600
2601 let (changed, result) = self.selections.change_with(cx, change);
2602
2603 if changed {
2604 if let Some(autoscroll) = autoscroll {
2605 self.request_autoscroll(autoscroll, cx);
2606 }
2607 self.selections_did_change(true, &old_cursor_position, request_completions, cx);
2608
2609 if self.should_open_signature_help_automatically(
2610 &old_cursor_position,
2611 self.signature_help_state.backspace_pressed(),
2612 cx,
2613 ) {
2614 self.show_signature_help(&ShowSignatureHelp, cx);
2615 }
2616 self.signature_help_state.set_backspace_pressed(false);
2617 }
2618
2619 result
2620 }
2621
2622 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut ViewContext<Self>)
2623 where
2624 I: IntoIterator<Item = (Range<S>, T)>,
2625 S: ToOffset,
2626 T: Into<Arc<str>>,
2627 {
2628 if self.read_only(cx) {
2629 return;
2630 }
2631
2632 self.buffer
2633 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2634 }
2635
2636 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut ViewContext<Self>)
2637 where
2638 I: IntoIterator<Item = (Range<S>, T)>,
2639 S: ToOffset,
2640 T: Into<Arc<str>>,
2641 {
2642 if self.read_only(cx) {
2643 return;
2644 }
2645
2646 self.buffer.update(cx, |buffer, cx| {
2647 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2648 });
2649 }
2650
2651 pub fn edit_with_block_indent<I, S, T>(
2652 &mut self,
2653 edits: I,
2654 original_indent_columns: Vec<u32>,
2655 cx: &mut ViewContext<Self>,
2656 ) where
2657 I: IntoIterator<Item = (Range<S>, T)>,
2658 S: ToOffset,
2659 T: Into<Arc<str>>,
2660 {
2661 if self.read_only(cx) {
2662 return;
2663 }
2664
2665 self.buffer.update(cx, |buffer, cx| {
2666 buffer.edit(
2667 edits,
2668 Some(AutoindentMode::Block {
2669 original_indent_columns,
2670 }),
2671 cx,
2672 )
2673 });
2674 }
2675
2676 fn select(&mut self, phase: SelectPhase, cx: &mut ViewContext<Self>) {
2677 self.hide_context_menu(cx);
2678
2679 match phase {
2680 SelectPhase::Begin {
2681 position,
2682 add,
2683 click_count,
2684 } => self.begin_selection(position, add, click_count, cx),
2685 SelectPhase::BeginColumnar {
2686 position,
2687 goal_column,
2688 reset,
2689 } => self.begin_columnar_selection(position, goal_column, reset, cx),
2690 SelectPhase::Extend {
2691 position,
2692 click_count,
2693 } => self.extend_selection(position, click_count, cx),
2694 SelectPhase::Update {
2695 position,
2696 goal_column,
2697 scroll_delta,
2698 } => self.update_selection(position, goal_column, scroll_delta, cx),
2699 SelectPhase::End => self.end_selection(cx),
2700 }
2701 }
2702
2703 fn extend_selection(
2704 &mut self,
2705 position: DisplayPoint,
2706 click_count: usize,
2707 cx: &mut ViewContext<Self>,
2708 ) {
2709 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2710 let tail = self.selections.newest::<usize>(cx).tail();
2711 self.begin_selection(position, false, click_count, cx);
2712
2713 let position = position.to_offset(&display_map, Bias::Left);
2714 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
2715
2716 let mut pending_selection = self
2717 .selections
2718 .pending_anchor()
2719 .expect("extend_selection not called with pending selection");
2720 if position >= tail {
2721 pending_selection.start = tail_anchor;
2722 } else {
2723 pending_selection.end = tail_anchor;
2724 pending_selection.reversed = true;
2725 }
2726
2727 let mut pending_mode = self.selections.pending_mode().unwrap();
2728 match &mut pending_mode {
2729 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
2730 _ => {}
2731 }
2732
2733 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
2734 s.set_pending(pending_selection, pending_mode)
2735 });
2736 }
2737
2738 fn begin_selection(
2739 &mut self,
2740 position: DisplayPoint,
2741 add: bool,
2742 click_count: usize,
2743 cx: &mut ViewContext<Self>,
2744 ) {
2745 if !self.focus_handle.is_focused(cx) {
2746 self.last_focused_descendant = None;
2747 cx.focus(&self.focus_handle);
2748 }
2749
2750 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2751 let buffer = &display_map.buffer_snapshot;
2752 let newest_selection = self.selections.newest_anchor().clone();
2753 let position = display_map.clip_point(position, Bias::Left);
2754
2755 let start;
2756 let end;
2757 let mode;
2758 let auto_scroll;
2759 match click_count {
2760 1 => {
2761 start = buffer.anchor_before(position.to_point(&display_map));
2762 end = start;
2763 mode = SelectMode::Character;
2764 auto_scroll = true;
2765 }
2766 2 => {
2767 let range = movement::surrounding_word(&display_map, position);
2768 start = buffer.anchor_before(range.start.to_point(&display_map));
2769 end = buffer.anchor_before(range.end.to_point(&display_map));
2770 mode = SelectMode::Word(start..end);
2771 auto_scroll = true;
2772 }
2773 3 => {
2774 let position = display_map
2775 .clip_point(position, Bias::Left)
2776 .to_point(&display_map);
2777 let line_start = display_map.prev_line_boundary(position).0;
2778 let next_line_start = buffer.clip_point(
2779 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2780 Bias::Left,
2781 );
2782 start = buffer.anchor_before(line_start);
2783 end = buffer.anchor_before(next_line_start);
2784 mode = SelectMode::Line(start..end);
2785 auto_scroll = true;
2786 }
2787 _ => {
2788 start = buffer.anchor_before(0);
2789 end = buffer.anchor_before(buffer.len());
2790 mode = SelectMode::All;
2791 auto_scroll = false;
2792 }
2793 }
2794
2795 let point_to_delete: Option<usize> = {
2796 let selected_points: Vec<Selection<Point>> =
2797 self.selections.disjoint_in_range(start..end, cx);
2798
2799 if !add || click_count > 1 {
2800 None
2801 } else if !selected_points.is_empty() {
2802 Some(selected_points[0].id)
2803 } else {
2804 let clicked_point_already_selected =
2805 self.selections.disjoint.iter().find(|selection| {
2806 selection.start.to_point(buffer) == start.to_point(buffer)
2807 || selection.end.to_point(buffer) == end.to_point(buffer)
2808 });
2809
2810 clicked_point_already_selected.map(|selection| selection.id)
2811 }
2812 };
2813
2814 let selections_count = self.selections.count();
2815
2816 self.change_selections(auto_scroll.then(Autoscroll::newest), cx, |s| {
2817 if let Some(point_to_delete) = point_to_delete {
2818 s.delete(point_to_delete);
2819
2820 if selections_count == 1 {
2821 s.set_pending_anchor_range(start..end, mode);
2822 }
2823 } else {
2824 if !add {
2825 s.clear_disjoint();
2826 } else if click_count > 1 {
2827 s.delete(newest_selection.id)
2828 }
2829
2830 s.set_pending_anchor_range(start..end, mode);
2831 }
2832 });
2833 }
2834
2835 fn begin_columnar_selection(
2836 &mut self,
2837 position: DisplayPoint,
2838 goal_column: u32,
2839 reset: bool,
2840 cx: &mut ViewContext<Self>,
2841 ) {
2842 if !self.focus_handle.is_focused(cx) {
2843 self.last_focused_descendant = None;
2844 cx.focus(&self.focus_handle);
2845 }
2846
2847 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2848
2849 if reset {
2850 let pointer_position = display_map
2851 .buffer_snapshot
2852 .anchor_before(position.to_point(&display_map));
2853
2854 self.change_selections(Some(Autoscroll::newest()), cx, |s| {
2855 s.clear_disjoint();
2856 s.set_pending_anchor_range(
2857 pointer_position..pointer_position,
2858 SelectMode::Character,
2859 );
2860 });
2861 }
2862
2863 let tail = self.selections.newest::<Point>(cx).tail();
2864 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
2865
2866 if !reset {
2867 self.select_columns(
2868 tail.to_display_point(&display_map),
2869 position,
2870 goal_column,
2871 &display_map,
2872 cx,
2873 );
2874 }
2875 }
2876
2877 fn update_selection(
2878 &mut self,
2879 position: DisplayPoint,
2880 goal_column: u32,
2881 scroll_delta: gpui::Point<f32>,
2882 cx: &mut ViewContext<Self>,
2883 ) {
2884 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2885
2886 if let Some(tail) = self.columnar_selection_tail.as_ref() {
2887 let tail = tail.to_display_point(&display_map);
2888 self.select_columns(tail, position, goal_column, &display_map, cx);
2889 } else if let Some(mut pending) = self.selections.pending_anchor() {
2890 let buffer = self.buffer.read(cx).snapshot(cx);
2891 let head;
2892 let tail;
2893 let mode = self.selections.pending_mode().unwrap();
2894 match &mode {
2895 SelectMode::Character => {
2896 head = position.to_point(&display_map);
2897 tail = pending.tail().to_point(&buffer);
2898 }
2899 SelectMode::Word(original_range) => {
2900 let original_display_range = original_range.start.to_display_point(&display_map)
2901 ..original_range.end.to_display_point(&display_map);
2902 let original_buffer_range = original_display_range.start.to_point(&display_map)
2903 ..original_display_range.end.to_point(&display_map);
2904 if movement::is_inside_word(&display_map, position)
2905 || original_display_range.contains(&position)
2906 {
2907 let word_range = movement::surrounding_word(&display_map, position);
2908 if word_range.start < original_display_range.start {
2909 head = word_range.start.to_point(&display_map);
2910 } else {
2911 head = word_range.end.to_point(&display_map);
2912 }
2913 } else {
2914 head = position.to_point(&display_map);
2915 }
2916
2917 if head <= original_buffer_range.start {
2918 tail = original_buffer_range.end;
2919 } else {
2920 tail = original_buffer_range.start;
2921 }
2922 }
2923 SelectMode::Line(original_range) => {
2924 let original_range = original_range.to_point(&display_map.buffer_snapshot);
2925
2926 let position = display_map
2927 .clip_point(position, Bias::Left)
2928 .to_point(&display_map);
2929 let line_start = display_map.prev_line_boundary(position).0;
2930 let next_line_start = buffer.clip_point(
2931 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2932 Bias::Left,
2933 );
2934
2935 if line_start < original_range.start {
2936 head = line_start
2937 } else {
2938 head = next_line_start
2939 }
2940
2941 if head <= original_range.start {
2942 tail = original_range.end;
2943 } else {
2944 tail = original_range.start;
2945 }
2946 }
2947 SelectMode::All => {
2948 return;
2949 }
2950 };
2951
2952 if head < tail {
2953 pending.start = buffer.anchor_before(head);
2954 pending.end = buffer.anchor_before(tail);
2955 pending.reversed = true;
2956 } else {
2957 pending.start = buffer.anchor_before(tail);
2958 pending.end = buffer.anchor_before(head);
2959 pending.reversed = false;
2960 }
2961
2962 self.change_selections(None, cx, |s| {
2963 s.set_pending(pending, mode);
2964 });
2965 } else {
2966 log::error!("update_selection dispatched with no pending selection");
2967 return;
2968 }
2969
2970 self.apply_scroll_delta(scroll_delta, cx);
2971 cx.notify();
2972 }
2973
2974 fn end_selection(&mut self, cx: &mut ViewContext<Self>) {
2975 self.columnar_selection_tail.take();
2976 if self.selections.pending_anchor().is_some() {
2977 let selections = self.selections.all::<usize>(cx);
2978 self.change_selections(None, cx, |s| {
2979 s.select(selections);
2980 s.clear_pending();
2981 });
2982 }
2983 }
2984
2985 fn select_columns(
2986 &mut self,
2987 tail: DisplayPoint,
2988 head: DisplayPoint,
2989 goal_column: u32,
2990 display_map: &DisplaySnapshot,
2991 cx: &mut ViewContext<Self>,
2992 ) {
2993 let start_row = cmp::min(tail.row(), head.row());
2994 let end_row = cmp::max(tail.row(), head.row());
2995 let start_column = cmp::min(tail.column(), goal_column);
2996 let end_column = cmp::max(tail.column(), goal_column);
2997 let reversed = start_column < tail.column();
2998
2999 let selection_ranges = (start_row.0..=end_row.0)
3000 .map(DisplayRow)
3001 .filter_map(|row| {
3002 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
3003 let start = display_map
3004 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3005 .to_point(display_map);
3006 let end = display_map
3007 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3008 .to_point(display_map);
3009 if reversed {
3010 Some(end..start)
3011 } else {
3012 Some(start..end)
3013 }
3014 } else {
3015 None
3016 }
3017 })
3018 .collect::<Vec<_>>();
3019
3020 self.change_selections(None, cx, |s| {
3021 s.select_ranges(selection_ranges);
3022 });
3023 cx.notify();
3024 }
3025
3026 pub fn has_pending_nonempty_selection(&self) -> bool {
3027 let pending_nonempty_selection = match self.selections.pending_anchor() {
3028 Some(Selection { start, end, .. }) => start != end,
3029 None => false,
3030 };
3031
3032 pending_nonempty_selection
3033 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3034 }
3035
3036 pub fn has_pending_selection(&self) -> bool {
3037 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3038 }
3039
3040 pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
3041 if self.clear_clicked_diff_hunks(cx) {
3042 cx.notify();
3043 return;
3044 }
3045 if self.dismiss_menus_and_popups(true, cx) {
3046 return;
3047 }
3048
3049 if self.mode == EditorMode::Full
3050 && self.change_selections(Some(Autoscroll::fit()), cx, |s| s.try_cancel())
3051 {
3052 return;
3053 }
3054
3055 cx.propagate();
3056 }
3057
3058 pub fn dismiss_menus_and_popups(
3059 &mut self,
3060 should_report_inline_completion_event: bool,
3061 cx: &mut ViewContext<Self>,
3062 ) -> bool {
3063 if self.take_rename(false, cx).is_some() {
3064 return true;
3065 }
3066
3067 if hide_hover(self, cx) {
3068 return true;
3069 }
3070
3071 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3072 return true;
3073 }
3074
3075 if self.hide_context_menu(cx).is_some() {
3076 return true;
3077 }
3078
3079 if self.mouse_context_menu.take().is_some() {
3080 return true;
3081 }
3082
3083 if self.discard_inline_completion(should_report_inline_completion_event, cx) {
3084 return true;
3085 }
3086
3087 if self.snippet_stack.pop().is_some() {
3088 return true;
3089 }
3090
3091 if self.mode == EditorMode::Full && self.active_diagnostics.is_some() {
3092 self.dismiss_diagnostics(cx);
3093 return true;
3094 }
3095
3096 false
3097 }
3098
3099 fn linked_editing_ranges_for(
3100 &self,
3101 selection: Range<text::Anchor>,
3102 cx: &AppContext,
3103 ) -> Option<HashMap<Model<Buffer>, Vec<Range<text::Anchor>>>> {
3104 if self.linked_edit_ranges.is_empty() {
3105 return None;
3106 }
3107 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3108 selection.end.buffer_id.and_then(|end_buffer_id| {
3109 if selection.start.buffer_id != Some(end_buffer_id) {
3110 return None;
3111 }
3112 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3113 let snapshot = buffer.read(cx).snapshot();
3114 self.linked_edit_ranges
3115 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3116 .map(|ranges| (ranges, snapshot, buffer))
3117 })?;
3118 use text::ToOffset as TO;
3119 // find offset from the start of current range to current cursor position
3120 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3121
3122 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3123 let start_difference = start_offset - start_byte_offset;
3124 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3125 let end_difference = end_offset - start_byte_offset;
3126 // Current range has associated linked ranges.
3127 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3128 for range in linked_ranges.iter() {
3129 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3130 let end_offset = start_offset + end_difference;
3131 let start_offset = start_offset + start_difference;
3132 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3133 continue;
3134 }
3135 if self.selections.disjoint_anchor_ranges().iter().any(|s| {
3136 if s.start.buffer_id != selection.start.buffer_id
3137 || s.end.buffer_id != selection.end.buffer_id
3138 {
3139 return false;
3140 }
3141 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3142 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3143 }) {
3144 continue;
3145 }
3146 let start = buffer_snapshot.anchor_after(start_offset);
3147 let end = buffer_snapshot.anchor_after(end_offset);
3148 linked_edits
3149 .entry(buffer.clone())
3150 .or_default()
3151 .push(start..end);
3152 }
3153 Some(linked_edits)
3154 }
3155
3156 pub fn handle_input(&mut self, text: &str, cx: &mut ViewContext<Self>) {
3157 let text: Arc<str> = text.into();
3158
3159 if self.read_only(cx) {
3160 return;
3161 }
3162
3163 let selections = self.selections.all_adjusted(cx);
3164 let mut bracket_inserted = false;
3165 let mut edits = Vec::new();
3166 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3167 let mut new_selections = Vec::with_capacity(selections.len());
3168 let mut new_autoclose_regions = Vec::new();
3169 let snapshot = self.buffer.read(cx).read(cx);
3170
3171 for (selection, autoclose_region) in
3172 self.selections_with_autoclose_regions(selections, &snapshot)
3173 {
3174 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3175 // Determine if the inserted text matches the opening or closing
3176 // bracket of any of this language's bracket pairs.
3177 let mut bracket_pair = None;
3178 let mut is_bracket_pair_start = false;
3179 let mut is_bracket_pair_end = false;
3180 if !text.is_empty() {
3181 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3182 // and they are removing the character that triggered IME popup.
3183 for (pair, enabled) in scope.brackets() {
3184 if !pair.close && !pair.surround {
3185 continue;
3186 }
3187
3188 if enabled && pair.start.ends_with(text.as_ref()) {
3189 bracket_pair = Some(pair.clone());
3190 is_bracket_pair_start = true;
3191 break;
3192 }
3193 if pair.end.as_str() == text.as_ref() {
3194 bracket_pair = Some(pair.clone());
3195 is_bracket_pair_end = true;
3196 break;
3197 }
3198 }
3199 }
3200
3201 if let Some(bracket_pair) = bracket_pair {
3202 let snapshot_settings = snapshot.settings_at(selection.start, cx);
3203 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3204 let auto_surround =
3205 self.use_auto_surround && snapshot_settings.use_auto_surround;
3206 if selection.is_empty() {
3207 if is_bracket_pair_start {
3208 let prefix_len = bracket_pair.start.len() - text.len();
3209
3210 // If the inserted text is a suffix of an opening bracket and the
3211 // selection is preceded by the rest of the opening bracket, then
3212 // insert the closing bracket.
3213 let following_text_allows_autoclose = snapshot
3214 .chars_at(selection.start)
3215 .next()
3216 .map_or(true, |c| scope.should_autoclose_before(c));
3217 let preceding_text_matches_prefix = prefix_len == 0
3218 || (selection.start.column >= (prefix_len as u32)
3219 && snapshot.contains_str_at(
3220 Point::new(
3221 selection.start.row,
3222 selection.start.column - (prefix_len as u32),
3223 ),
3224 &bracket_pair.start[..prefix_len],
3225 ));
3226
3227 if autoclose
3228 && bracket_pair.close
3229 && following_text_allows_autoclose
3230 && preceding_text_matches_prefix
3231 {
3232 let anchor = snapshot.anchor_before(selection.end);
3233 new_selections.push((selection.map(|_| anchor), text.len()));
3234 new_autoclose_regions.push((
3235 anchor,
3236 text.len(),
3237 selection.id,
3238 bracket_pair.clone(),
3239 ));
3240 edits.push((
3241 selection.range(),
3242 format!("{}{}", text, bracket_pair.end).into(),
3243 ));
3244 bracket_inserted = true;
3245 continue;
3246 }
3247 }
3248
3249 if let Some(region) = autoclose_region {
3250 // If the selection is followed by an auto-inserted closing bracket,
3251 // then don't insert that closing bracket again; just move the selection
3252 // past the closing bracket.
3253 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3254 && text.as_ref() == region.pair.end.as_str();
3255 if should_skip {
3256 let anchor = snapshot.anchor_after(selection.end);
3257 new_selections
3258 .push((selection.map(|_| anchor), region.pair.end.len()));
3259 continue;
3260 }
3261 }
3262
3263 let always_treat_brackets_as_autoclosed = snapshot
3264 .settings_at(selection.start, cx)
3265 .always_treat_brackets_as_autoclosed;
3266 if always_treat_brackets_as_autoclosed
3267 && is_bracket_pair_end
3268 && snapshot.contains_str_at(selection.end, text.as_ref())
3269 {
3270 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3271 // and the inserted text is a closing bracket and the selection is followed
3272 // by the closing bracket then move the selection past the closing bracket.
3273 let anchor = snapshot.anchor_after(selection.end);
3274 new_selections.push((selection.map(|_| anchor), text.len()));
3275 continue;
3276 }
3277 }
3278 // If an opening bracket is 1 character long and is typed while
3279 // text is selected, then surround that text with the bracket pair.
3280 else if auto_surround
3281 && bracket_pair.surround
3282 && is_bracket_pair_start
3283 && bracket_pair.start.chars().count() == 1
3284 {
3285 edits.push((selection.start..selection.start, text.clone()));
3286 edits.push((
3287 selection.end..selection.end,
3288 bracket_pair.end.as_str().into(),
3289 ));
3290 bracket_inserted = true;
3291 new_selections.push((
3292 Selection {
3293 id: selection.id,
3294 start: snapshot.anchor_after(selection.start),
3295 end: snapshot.anchor_before(selection.end),
3296 reversed: selection.reversed,
3297 goal: selection.goal,
3298 },
3299 0,
3300 ));
3301 continue;
3302 }
3303 }
3304 }
3305
3306 if self.auto_replace_emoji_shortcode
3307 && selection.is_empty()
3308 && text.as_ref().ends_with(':')
3309 {
3310 if let Some(possible_emoji_short_code) =
3311 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3312 {
3313 if !possible_emoji_short_code.is_empty() {
3314 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3315 let emoji_shortcode_start = Point::new(
3316 selection.start.row,
3317 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3318 );
3319
3320 // Remove shortcode from buffer
3321 edits.push((
3322 emoji_shortcode_start..selection.start,
3323 "".to_string().into(),
3324 ));
3325 new_selections.push((
3326 Selection {
3327 id: selection.id,
3328 start: snapshot.anchor_after(emoji_shortcode_start),
3329 end: snapshot.anchor_before(selection.start),
3330 reversed: selection.reversed,
3331 goal: selection.goal,
3332 },
3333 0,
3334 ));
3335
3336 // Insert emoji
3337 let selection_start_anchor = snapshot.anchor_after(selection.start);
3338 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3339 edits.push((selection.start..selection.end, emoji.to_string().into()));
3340
3341 continue;
3342 }
3343 }
3344 }
3345 }
3346
3347 // If not handling any auto-close operation, then just replace the selected
3348 // text with the given input and move the selection to the end of the
3349 // newly inserted text.
3350 let anchor = snapshot.anchor_after(selection.end);
3351 if !self.linked_edit_ranges.is_empty() {
3352 let start_anchor = snapshot.anchor_before(selection.start);
3353
3354 let is_word_char = text.chars().next().map_or(true, |char| {
3355 let classifier = snapshot.char_classifier_at(start_anchor.to_offset(&snapshot));
3356 classifier.is_word(char)
3357 });
3358
3359 if is_word_char {
3360 if let Some(ranges) = self
3361 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3362 {
3363 for (buffer, edits) in ranges {
3364 linked_edits
3365 .entry(buffer.clone())
3366 .or_default()
3367 .extend(edits.into_iter().map(|range| (range, text.clone())));
3368 }
3369 }
3370 }
3371 }
3372
3373 new_selections.push((selection.map(|_| anchor), 0));
3374 edits.push((selection.start..selection.end, text.clone()));
3375 }
3376
3377 drop(snapshot);
3378
3379 self.transact(cx, |this, cx| {
3380 this.buffer.update(cx, |buffer, cx| {
3381 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3382 });
3383 for (buffer, edits) in linked_edits {
3384 buffer.update(cx, |buffer, cx| {
3385 let snapshot = buffer.snapshot();
3386 let edits = edits
3387 .into_iter()
3388 .map(|(range, text)| {
3389 use text::ToPoint as TP;
3390 let end_point = TP::to_point(&range.end, &snapshot);
3391 let start_point = TP::to_point(&range.start, &snapshot);
3392 (start_point..end_point, text)
3393 })
3394 .sorted_by_key(|(range, _)| range.start)
3395 .collect::<Vec<_>>();
3396 buffer.edit(edits, None, cx);
3397 })
3398 }
3399 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3400 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3401 let snapshot = this.buffer.read(cx).read(cx);
3402 let new_selections = resolve_multiple::<usize, _>(new_anchor_selections, &snapshot)
3403 .zip(new_selection_deltas)
3404 .map(|(selection, delta)| Selection {
3405 id: selection.id,
3406 start: selection.start + delta,
3407 end: selection.end + delta,
3408 reversed: selection.reversed,
3409 goal: SelectionGoal::None,
3410 })
3411 .collect::<Vec<_>>();
3412
3413 let mut i = 0;
3414 for (position, delta, selection_id, pair) in new_autoclose_regions {
3415 let position = position.to_offset(&snapshot) + delta;
3416 let start = snapshot.anchor_before(position);
3417 let end = snapshot.anchor_after(position);
3418 while let Some(existing_state) = this.autoclose_regions.get(i) {
3419 match existing_state.range.start.cmp(&start, &snapshot) {
3420 Ordering::Less => i += 1,
3421 Ordering::Greater => break,
3422 Ordering::Equal => match end.cmp(&existing_state.range.end, &snapshot) {
3423 Ordering::Less => i += 1,
3424 Ordering::Equal => break,
3425 Ordering::Greater => break,
3426 },
3427 }
3428 }
3429 this.autoclose_regions.insert(
3430 i,
3431 AutocloseRegion {
3432 selection_id,
3433 range: start..end,
3434 pair,
3435 },
3436 );
3437 }
3438
3439 drop(snapshot);
3440 let had_active_inline_completion = this.has_active_inline_completion(cx);
3441 this.change_selections_inner(Some(Autoscroll::fit()), false, cx, |s| {
3442 s.select(new_selections)
3443 });
3444
3445 if !bracket_inserted {
3446 if let Some(on_type_format_task) =
3447 this.trigger_on_type_formatting(text.to_string(), cx)
3448 {
3449 on_type_format_task.detach_and_log_err(cx);
3450 }
3451 }
3452
3453 let editor_settings = EditorSettings::get_global(cx);
3454 if bracket_inserted
3455 && (editor_settings.auto_signature_help
3456 || editor_settings.show_signature_help_after_edits)
3457 {
3458 this.show_signature_help(&ShowSignatureHelp, cx);
3459 }
3460
3461 let trigger_in_words = !had_active_inline_completion;
3462 this.trigger_completion_on_input(&text, trigger_in_words, cx);
3463 linked_editing_ranges::refresh_linked_ranges(this, cx);
3464 this.refresh_inline_completion(true, false, cx);
3465 });
3466 }
3467
3468 fn find_possible_emoji_shortcode_at_position(
3469 snapshot: &MultiBufferSnapshot,
3470 position: Point,
3471 ) -> Option<String> {
3472 let mut chars = Vec::new();
3473 let mut found_colon = false;
3474 for char in snapshot.reversed_chars_at(position).take(100) {
3475 // Found a possible emoji shortcode in the middle of the buffer
3476 if found_colon {
3477 if char.is_whitespace() {
3478 chars.reverse();
3479 return Some(chars.iter().collect());
3480 }
3481 // If the previous character is not a whitespace, we are in the middle of a word
3482 // and we only want to complete the shortcode if the word is made up of other emojis
3483 let mut containing_word = String::new();
3484 for ch in snapshot
3485 .reversed_chars_at(position)
3486 .skip(chars.len() + 1)
3487 .take(100)
3488 {
3489 if ch.is_whitespace() {
3490 break;
3491 }
3492 containing_word.push(ch);
3493 }
3494 let containing_word = containing_word.chars().rev().collect::<String>();
3495 if util::word_consists_of_emojis(containing_word.as_str()) {
3496 chars.reverse();
3497 return Some(chars.iter().collect());
3498 }
3499 }
3500
3501 if char.is_whitespace() || !char.is_ascii() {
3502 return None;
3503 }
3504 if char == ':' {
3505 found_colon = true;
3506 } else {
3507 chars.push(char);
3508 }
3509 }
3510 // Found a possible emoji shortcode at the beginning of the buffer
3511 chars.reverse();
3512 Some(chars.iter().collect())
3513 }
3514
3515 pub fn newline(&mut self, _: &Newline, cx: &mut ViewContext<Self>) {
3516 self.transact(cx, |this, cx| {
3517 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
3518 let selections = this.selections.all::<usize>(cx);
3519 let multi_buffer = this.buffer.read(cx);
3520 let buffer = multi_buffer.snapshot(cx);
3521 selections
3522 .iter()
3523 .map(|selection| {
3524 let start_point = selection.start.to_point(&buffer);
3525 let mut indent =
3526 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3527 indent.len = cmp::min(indent.len, start_point.column);
3528 let start = selection.start;
3529 let end = selection.end;
3530 let selection_is_empty = start == end;
3531 let language_scope = buffer.language_scope_at(start);
3532 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
3533 &language_scope
3534 {
3535 let leading_whitespace_len = buffer
3536 .reversed_chars_at(start)
3537 .take_while(|c| c.is_whitespace() && *c != '\n')
3538 .map(|c| c.len_utf8())
3539 .sum::<usize>();
3540
3541 let trailing_whitespace_len = buffer
3542 .chars_at(end)
3543 .take_while(|c| c.is_whitespace() && *c != '\n')
3544 .map(|c| c.len_utf8())
3545 .sum::<usize>();
3546
3547 let insert_extra_newline =
3548 language.brackets().any(|(pair, enabled)| {
3549 let pair_start = pair.start.trim_end();
3550 let pair_end = pair.end.trim_start();
3551
3552 enabled
3553 && pair.newline
3554 && buffer.contains_str_at(
3555 end + trailing_whitespace_len,
3556 pair_end,
3557 )
3558 && buffer.contains_str_at(
3559 (start - leading_whitespace_len)
3560 .saturating_sub(pair_start.len()),
3561 pair_start,
3562 )
3563 });
3564
3565 // Comment extension on newline is allowed only for cursor selections
3566 let comment_delimiter = maybe!({
3567 if !selection_is_empty {
3568 return None;
3569 }
3570
3571 if !multi_buffer.settings_at(0, cx).extend_comment_on_newline {
3572 return None;
3573 }
3574
3575 let delimiters = language.line_comment_prefixes();
3576 let max_len_of_delimiter =
3577 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3578 let (snapshot, range) =
3579 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3580
3581 let mut index_of_first_non_whitespace = 0;
3582 let comment_candidate = snapshot
3583 .chars_for_range(range)
3584 .skip_while(|c| {
3585 let should_skip = c.is_whitespace();
3586 if should_skip {
3587 index_of_first_non_whitespace += 1;
3588 }
3589 should_skip
3590 })
3591 .take(max_len_of_delimiter)
3592 .collect::<String>();
3593 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3594 comment_candidate.starts_with(comment_prefix.as_ref())
3595 })?;
3596 let cursor_is_placed_after_comment_marker =
3597 index_of_first_non_whitespace + comment_prefix.len()
3598 <= start_point.column as usize;
3599 if cursor_is_placed_after_comment_marker {
3600 Some(comment_prefix.clone())
3601 } else {
3602 None
3603 }
3604 });
3605 (comment_delimiter, insert_extra_newline)
3606 } else {
3607 (None, false)
3608 };
3609
3610 let capacity_for_delimiter = comment_delimiter
3611 .as_deref()
3612 .map(str::len)
3613 .unwrap_or_default();
3614 let mut new_text =
3615 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3616 new_text.push('\n');
3617 new_text.extend(indent.chars());
3618 if let Some(delimiter) = &comment_delimiter {
3619 new_text.push_str(delimiter);
3620 }
3621 if insert_extra_newline {
3622 new_text = new_text.repeat(2);
3623 }
3624
3625 let anchor = buffer.anchor_after(end);
3626 let new_selection = selection.map(|_| anchor);
3627 (
3628 (start..end, new_text),
3629 (insert_extra_newline, new_selection),
3630 )
3631 })
3632 .unzip()
3633 };
3634
3635 this.edit_with_autoindent(edits, cx);
3636 let buffer = this.buffer.read(cx).snapshot(cx);
3637 let new_selections = selection_fixup_info
3638 .into_iter()
3639 .map(|(extra_newline_inserted, new_selection)| {
3640 let mut cursor = new_selection.end.to_point(&buffer);
3641 if extra_newline_inserted {
3642 cursor.row -= 1;
3643 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
3644 }
3645 new_selection.map(|_| cursor)
3646 })
3647 .collect();
3648
3649 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
3650 this.refresh_inline_completion(true, false, cx);
3651 });
3652 }
3653
3654 pub fn newline_above(&mut self, _: &NewlineAbove, cx: &mut ViewContext<Self>) {
3655 let buffer = self.buffer.read(cx);
3656 let snapshot = buffer.snapshot(cx);
3657
3658 let mut edits = Vec::new();
3659 let mut rows = Vec::new();
3660
3661 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
3662 let cursor = selection.head();
3663 let row = cursor.row;
3664
3665 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
3666
3667 let newline = "\n".to_string();
3668 edits.push((start_of_line..start_of_line, newline));
3669
3670 rows.push(row + rows_inserted as u32);
3671 }
3672
3673 self.transact(cx, |editor, cx| {
3674 editor.edit(edits, cx);
3675
3676 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
3677 let mut index = 0;
3678 s.move_cursors_with(|map, _, _| {
3679 let row = rows[index];
3680 index += 1;
3681
3682 let point = Point::new(row, 0);
3683 let boundary = map.next_line_boundary(point).1;
3684 let clipped = map.clip_point(boundary, Bias::Left);
3685
3686 (clipped, SelectionGoal::None)
3687 });
3688 });
3689
3690 let mut indent_edits = Vec::new();
3691 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3692 for row in rows {
3693 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3694 for (row, indent) in indents {
3695 if indent.len == 0 {
3696 continue;
3697 }
3698
3699 let text = match indent.kind {
3700 IndentKind::Space => " ".repeat(indent.len as usize),
3701 IndentKind::Tab => "\t".repeat(indent.len as usize),
3702 };
3703 let point = Point::new(row.0, 0);
3704 indent_edits.push((point..point, text));
3705 }
3706 }
3707 editor.edit(indent_edits, cx);
3708 });
3709 }
3710
3711 pub fn newline_below(&mut self, _: &NewlineBelow, cx: &mut ViewContext<Self>) {
3712 let buffer = self.buffer.read(cx);
3713 let snapshot = buffer.snapshot(cx);
3714
3715 let mut edits = Vec::new();
3716 let mut rows = Vec::new();
3717 let mut rows_inserted = 0;
3718
3719 for selection in self.selections.all_adjusted(cx) {
3720 let cursor = selection.head();
3721 let row = cursor.row;
3722
3723 let point = Point::new(row + 1, 0);
3724 let start_of_line = snapshot.clip_point(point, Bias::Left);
3725
3726 let newline = "\n".to_string();
3727 edits.push((start_of_line..start_of_line, newline));
3728
3729 rows_inserted += 1;
3730 rows.push(row + rows_inserted);
3731 }
3732
3733 self.transact(cx, |editor, cx| {
3734 editor.edit(edits, cx);
3735
3736 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
3737 let mut index = 0;
3738 s.move_cursors_with(|map, _, _| {
3739 let row = rows[index];
3740 index += 1;
3741
3742 let point = Point::new(row, 0);
3743 let boundary = map.next_line_boundary(point).1;
3744 let clipped = map.clip_point(boundary, Bias::Left);
3745
3746 (clipped, SelectionGoal::None)
3747 });
3748 });
3749
3750 let mut indent_edits = Vec::new();
3751 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3752 for row in rows {
3753 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3754 for (row, indent) in indents {
3755 if indent.len == 0 {
3756 continue;
3757 }
3758
3759 let text = match indent.kind {
3760 IndentKind::Space => " ".repeat(indent.len as usize),
3761 IndentKind::Tab => "\t".repeat(indent.len as usize),
3762 };
3763 let point = Point::new(row.0, 0);
3764 indent_edits.push((point..point, text));
3765 }
3766 }
3767 editor.edit(indent_edits, cx);
3768 });
3769 }
3770
3771 pub fn insert(&mut self, text: &str, cx: &mut ViewContext<Self>) {
3772 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
3773 original_indent_columns: Vec::new(),
3774 });
3775 self.insert_with_autoindent_mode(text, autoindent, cx);
3776 }
3777
3778 fn insert_with_autoindent_mode(
3779 &mut self,
3780 text: &str,
3781 autoindent_mode: Option<AutoindentMode>,
3782 cx: &mut ViewContext<Self>,
3783 ) {
3784 if self.read_only(cx) {
3785 return;
3786 }
3787
3788 let text: Arc<str> = text.into();
3789 self.transact(cx, |this, cx| {
3790 let old_selections = this.selections.all_adjusted(cx);
3791 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
3792 let anchors = {
3793 let snapshot = buffer.read(cx);
3794 old_selections
3795 .iter()
3796 .map(|s| {
3797 let anchor = snapshot.anchor_after(s.head());
3798 s.map(|_| anchor)
3799 })
3800 .collect::<Vec<_>>()
3801 };
3802 buffer.edit(
3803 old_selections
3804 .iter()
3805 .map(|s| (s.start..s.end, text.clone())),
3806 autoindent_mode,
3807 cx,
3808 );
3809 anchors
3810 });
3811
3812 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
3813 s.select_anchors(selection_anchors);
3814 })
3815 });
3816 }
3817
3818 fn trigger_completion_on_input(
3819 &mut self,
3820 text: &str,
3821 trigger_in_words: bool,
3822 cx: &mut ViewContext<Self>,
3823 ) {
3824 if self.is_completion_trigger(text, trigger_in_words, cx) {
3825 self.show_completions(
3826 &ShowCompletions {
3827 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
3828 },
3829 cx,
3830 );
3831 } else {
3832 self.hide_context_menu(cx);
3833 }
3834 }
3835
3836 fn is_completion_trigger(
3837 &self,
3838 text: &str,
3839 trigger_in_words: bool,
3840 cx: &mut ViewContext<Self>,
3841 ) -> bool {
3842 let position = self.selections.newest_anchor().head();
3843 let multibuffer = self.buffer.read(cx);
3844 let Some(buffer) = position
3845 .buffer_id
3846 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
3847 else {
3848 return false;
3849 };
3850
3851 if let Some(completion_provider) = &self.completion_provider {
3852 completion_provider.is_completion_trigger(
3853 &buffer,
3854 position.text_anchor,
3855 text,
3856 trigger_in_words,
3857 cx,
3858 )
3859 } else {
3860 false
3861 }
3862 }
3863
3864 /// If any empty selections is touching the start of its innermost containing autoclose
3865 /// region, expand it to select the brackets.
3866 fn select_autoclose_pair(&mut self, cx: &mut ViewContext<Self>) {
3867 let selections = self.selections.all::<usize>(cx);
3868 let buffer = self.buffer.read(cx).read(cx);
3869 let new_selections = self
3870 .selections_with_autoclose_regions(selections, &buffer)
3871 .map(|(mut selection, region)| {
3872 if !selection.is_empty() {
3873 return selection;
3874 }
3875
3876 if let Some(region) = region {
3877 let mut range = region.range.to_offset(&buffer);
3878 if selection.start == range.start && range.start >= region.pair.start.len() {
3879 range.start -= region.pair.start.len();
3880 if buffer.contains_str_at(range.start, ®ion.pair.start)
3881 && buffer.contains_str_at(range.end, ®ion.pair.end)
3882 {
3883 range.end += region.pair.end.len();
3884 selection.start = range.start;
3885 selection.end = range.end;
3886
3887 return selection;
3888 }
3889 }
3890 }
3891
3892 let always_treat_brackets_as_autoclosed = buffer
3893 .settings_at(selection.start, cx)
3894 .always_treat_brackets_as_autoclosed;
3895
3896 if !always_treat_brackets_as_autoclosed {
3897 return selection;
3898 }
3899
3900 if let Some(scope) = buffer.language_scope_at(selection.start) {
3901 for (pair, enabled) in scope.brackets() {
3902 if !enabled || !pair.close {
3903 continue;
3904 }
3905
3906 if buffer.contains_str_at(selection.start, &pair.end) {
3907 let pair_start_len = pair.start.len();
3908 if buffer.contains_str_at(selection.start - pair_start_len, &pair.start)
3909 {
3910 selection.start -= pair_start_len;
3911 selection.end += pair.end.len();
3912
3913 return selection;
3914 }
3915 }
3916 }
3917 }
3918
3919 selection
3920 })
3921 .collect();
3922
3923 drop(buffer);
3924 self.change_selections(None, cx, |selections| selections.select(new_selections));
3925 }
3926
3927 /// Iterate the given selections, and for each one, find the smallest surrounding
3928 /// autoclose region. This uses the ordering of the selections and the autoclose
3929 /// regions to avoid repeated comparisons.
3930 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
3931 &'a self,
3932 selections: impl IntoIterator<Item = Selection<D>>,
3933 buffer: &'a MultiBufferSnapshot,
3934 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
3935 let mut i = 0;
3936 let mut regions = self.autoclose_regions.as_slice();
3937 selections.into_iter().map(move |selection| {
3938 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
3939
3940 let mut enclosing = None;
3941 while let Some(pair_state) = regions.get(i) {
3942 if pair_state.range.end.to_offset(buffer) < range.start {
3943 regions = ®ions[i + 1..];
3944 i = 0;
3945 } else if pair_state.range.start.to_offset(buffer) > range.end {
3946 break;
3947 } else {
3948 if pair_state.selection_id == selection.id {
3949 enclosing = Some(pair_state);
3950 }
3951 i += 1;
3952 }
3953 }
3954
3955 (selection.clone(), enclosing)
3956 })
3957 }
3958
3959 /// Remove any autoclose regions that no longer contain their selection.
3960 fn invalidate_autoclose_regions(
3961 &mut self,
3962 mut selections: &[Selection<Anchor>],
3963 buffer: &MultiBufferSnapshot,
3964 ) {
3965 self.autoclose_regions.retain(|state| {
3966 let mut i = 0;
3967 while let Some(selection) = selections.get(i) {
3968 if selection.end.cmp(&state.range.start, buffer).is_lt() {
3969 selections = &selections[1..];
3970 continue;
3971 }
3972 if selection.start.cmp(&state.range.end, buffer).is_gt() {
3973 break;
3974 }
3975 if selection.id == state.selection_id {
3976 return true;
3977 } else {
3978 i += 1;
3979 }
3980 }
3981 false
3982 });
3983 }
3984
3985 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
3986 let offset = position.to_offset(buffer);
3987 let (word_range, kind) = buffer.surrounding_word(offset, true);
3988 if offset > word_range.start && kind == Some(CharKind::Word) {
3989 Some(
3990 buffer
3991 .text_for_range(word_range.start..offset)
3992 .collect::<String>(),
3993 )
3994 } else {
3995 None
3996 }
3997 }
3998
3999 pub fn toggle_inlay_hints(&mut self, _: &ToggleInlayHints, cx: &mut ViewContext<Self>) {
4000 self.refresh_inlay_hints(
4001 InlayHintRefreshReason::Toggle(!self.inlay_hint_cache.enabled),
4002 cx,
4003 );
4004 }
4005
4006 pub fn inlay_hints_enabled(&self) -> bool {
4007 self.inlay_hint_cache.enabled
4008 }
4009
4010 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut ViewContext<Self>) {
4011 if self.project.is_none() || self.mode != EditorMode::Full {
4012 return;
4013 }
4014
4015 let reason_description = reason.description();
4016 let ignore_debounce = matches!(
4017 reason,
4018 InlayHintRefreshReason::SettingsChange(_)
4019 | InlayHintRefreshReason::Toggle(_)
4020 | InlayHintRefreshReason::ExcerptsRemoved(_)
4021 );
4022 let (invalidate_cache, required_languages) = match reason {
4023 InlayHintRefreshReason::Toggle(enabled) => {
4024 self.inlay_hint_cache.enabled = enabled;
4025 if enabled {
4026 (InvalidationStrategy::RefreshRequested, None)
4027 } else {
4028 self.inlay_hint_cache.clear();
4029 self.splice_inlays(
4030 self.visible_inlay_hints(cx)
4031 .iter()
4032 .map(|inlay| inlay.id)
4033 .collect(),
4034 Vec::new(),
4035 cx,
4036 );
4037 return;
4038 }
4039 }
4040 InlayHintRefreshReason::SettingsChange(new_settings) => {
4041 match self.inlay_hint_cache.update_settings(
4042 &self.buffer,
4043 new_settings,
4044 self.visible_inlay_hints(cx),
4045 cx,
4046 ) {
4047 ControlFlow::Break(Some(InlaySplice {
4048 to_remove,
4049 to_insert,
4050 })) => {
4051 self.splice_inlays(to_remove, to_insert, cx);
4052 return;
4053 }
4054 ControlFlow::Break(None) => return,
4055 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4056 }
4057 }
4058 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4059 if let Some(InlaySplice {
4060 to_remove,
4061 to_insert,
4062 }) = self.inlay_hint_cache.remove_excerpts(excerpts_removed)
4063 {
4064 self.splice_inlays(to_remove, to_insert, cx);
4065 }
4066 return;
4067 }
4068 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4069 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4070 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4071 }
4072 InlayHintRefreshReason::RefreshRequested => {
4073 (InvalidationStrategy::RefreshRequested, None)
4074 }
4075 };
4076
4077 if let Some(InlaySplice {
4078 to_remove,
4079 to_insert,
4080 }) = self.inlay_hint_cache.spawn_hint_refresh(
4081 reason_description,
4082 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4083 invalidate_cache,
4084 ignore_debounce,
4085 cx,
4086 ) {
4087 self.splice_inlays(to_remove, to_insert, cx);
4088 }
4089 }
4090
4091 fn visible_inlay_hints(&self, cx: &ViewContext<'_, Editor>) -> Vec<Inlay> {
4092 self.display_map
4093 .read(cx)
4094 .current_inlays()
4095 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4096 .cloned()
4097 .collect()
4098 }
4099
4100 pub fn excerpts_for_inlay_hints_query(
4101 &self,
4102 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4103 cx: &mut ViewContext<Editor>,
4104 ) -> HashMap<ExcerptId, (Model<Buffer>, clock::Global, Range<usize>)> {
4105 let Some(project) = self.project.as_ref() else {
4106 return HashMap::default();
4107 };
4108 let project = project.read(cx);
4109 let multi_buffer = self.buffer().read(cx);
4110 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4111 let multi_buffer_visible_start = self
4112 .scroll_manager
4113 .anchor()
4114 .anchor
4115 .to_point(&multi_buffer_snapshot);
4116 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4117 multi_buffer_visible_start
4118 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4119 Bias::Left,
4120 );
4121 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4122 multi_buffer
4123 .range_to_buffer_ranges(multi_buffer_visible_range, cx)
4124 .into_iter()
4125 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4126 .filter_map(|(buffer_handle, excerpt_visible_range, excerpt_id)| {
4127 let buffer = buffer_handle.read(cx);
4128 let buffer_file = project::File::from_dyn(buffer.file())?;
4129 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4130 let worktree_entry = buffer_worktree
4131 .read(cx)
4132 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4133 if worktree_entry.is_ignored {
4134 return None;
4135 }
4136
4137 let language = buffer.language()?;
4138 if let Some(restrict_to_languages) = restrict_to_languages {
4139 if !restrict_to_languages.contains(language) {
4140 return None;
4141 }
4142 }
4143 Some((
4144 excerpt_id,
4145 (
4146 buffer_handle,
4147 buffer.version().clone(),
4148 excerpt_visible_range,
4149 ),
4150 ))
4151 })
4152 .collect()
4153 }
4154
4155 pub fn text_layout_details(&self, cx: &WindowContext) -> TextLayoutDetails {
4156 TextLayoutDetails {
4157 text_system: cx.text_system().clone(),
4158 editor_style: self.style.clone().unwrap(),
4159 rem_size: cx.rem_size(),
4160 scroll_anchor: self.scroll_manager.anchor(),
4161 visible_rows: self.visible_line_count(),
4162 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4163 }
4164 }
4165
4166 fn splice_inlays(
4167 &self,
4168 to_remove: Vec<InlayId>,
4169 to_insert: Vec<Inlay>,
4170 cx: &mut ViewContext<Self>,
4171 ) {
4172 self.display_map.update(cx, |display_map, cx| {
4173 display_map.splice_inlays(to_remove, to_insert, cx);
4174 });
4175 cx.notify();
4176 }
4177
4178 fn trigger_on_type_formatting(
4179 &self,
4180 input: String,
4181 cx: &mut ViewContext<Self>,
4182 ) -> Option<Task<Result<()>>> {
4183 if input.len() != 1 {
4184 return None;
4185 }
4186
4187 let project = self.project.as_ref()?;
4188 let position = self.selections.newest_anchor().head();
4189 let (buffer, buffer_position) = self
4190 .buffer
4191 .read(cx)
4192 .text_anchor_for_position(position, cx)?;
4193
4194 let settings = language_settings::language_settings(
4195 buffer.read(cx).language_at(buffer_position).as_ref(),
4196 buffer.read(cx).file(),
4197 cx,
4198 );
4199 if !settings.use_on_type_format {
4200 return None;
4201 }
4202
4203 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4204 // hence we do LSP request & edit on host side only — add formats to host's history.
4205 let push_to_lsp_host_history = true;
4206 // If this is not the host, append its history with new edits.
4207 let push_to_client_history = project.read(cx).is_via_collab();
4208
4209 let on_type_formatting = project.update(cx, |project, cx| {
4210 project.on_type_format(
4211 buffer.clone(),
4212 buffer_position,
4213 input,
4214 push_to_lsp_host_history,
4215 cx,
4216 )
4217 });
4218 Some(cx.spawn(|editor, mut cx| async move {
4219 if let Some(transaction) = on_type_formatting.await? {
4220 if push_to_client_history {
4221 buffer
4222 .update(&mut cx, |buffer, _| {
4223 buffer.push_transaction(transaction, Instant::now());
4224 })
4225 .ok();
4226 }
4227 editor.update(&mut cx, |editor, cx| {
4228 editor.refresh_document_highlights(cx);
4229 })?;
4230 }
4231 Ok(())
4232 }))
4233 }
4234
4235 pub fn show_completions(&mut self, options: &ShowCompletions, cx: &mut ViewContext<Self>) {
4236 if self.pending_rename.is_some() {
4237 return;
4238 }
4239
4240 let Some(provider) = self.completion_provider.as_ref() else {
4241 return;
4242 };
4243
4244 let position = self.selections.newest_anchor().head();
4245 let (buffer, buffer_position) =
4246 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4247 output
4248 } else {
4249 return;
4250 };
4251
4252 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4253 let is_followup_invoke = {
4254 let context_menu_state = self.context_menu.read();
4255 matches!(
4256 context_menu_state.deref(),
4257 Some(ContextMenu::Completions(_))
4258 )
4259 };
4260 let trigger_kind = match (&options.trigger, is_followup_invoke) {
4261 (_, true) => CompletionTriggerKind::TRIGGER_FOR_INCOMPLETE_COMPLETIONS,
4262 (Some(trigger), _) if buffer.read(cx).completion_triggers().contains(trigger) => {
4263 CompletionTriggerKind::TRIGGER_CHARACTER
4264 }
4265
4266 _ => CompletionTriggerKind::INVOKED,
4267 };
4268 let completion_context = CompletionContext {
4269 trigger_character: options.trigger.as_ref().and_then(|trigger| {
4270 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4271 Some(String::from(trigger))
4272 } else {
4273 None
4274 }
4275 }),
4276 trigger_kind,
4277 };
4278 let completions = provider.completions(&buffer, buffer_position, completion_context, cx);
4279 let sort_completions = provider.sort_completions();
4280
4281 let id = post_inc(&mut self.next_completion_id);
4282 let task = cx.spawn(|this, mut cx| {
4283 async move {
4284 this.update(&mut cx, |this, _| {
4285 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4286 })?;
4287 let completions = completions.await.log_err();
4288 let menu = if let Some(completions) = completions {
4289 let mut menu = CompletionsMenu {
4290 id,
4291 sort_completions,
4292 initial_position: position,
4293 match_candidates: completions
4294 .iter()
4295 .enumerate()
4296 .map(|(id, completion)| {
4297 StringMatchCandidate::new(
4298 id,
4299 completion.label.text[completion.label.filter_range.clone()]
4300 .into(),
4301 )
4302 })
4303 .collect(),
4304 buffer: buffer.clone(),
4305 completions: Arc::new(RwLock::new(completions.into())),
4306 matches: Vec::new().into(),
4307 selected_item: 0,
4308 scroll_handle: UniformListScrollHandle::new(),
4309 selected_completion_documentation_resolve_debounce: Arc::new(Mutex::new(
4310 DebouncedDelay::new(),
4311 )),
4312 };
4313 menu.filter(query.as_deref(), cx.background_executor().clone())
4314 .await;
4315
4316 if menu.matches.is_empty() {
4317 None
4318 } else {
4319 this.update(&mut cx, |editor, cx| {
4320 let completions = menu.completions.clone();
4321 let matches = menu.matches.clone();
4322
4323 let delay_ms = EditorSettings::get_global(cx)
4324 .completion_documentation_secondary_query_debounce;
4325 let delay = Duration::from_millis(delay_ms);
4326 editor
4327 .completion_documentation_pre_resolve_debounce
4328 .fire_new(delay, cx, |editor, cx| {
4329 CompletionsMenu::pre_resolve_completion_documentation(
4330 buffer,
4331 completions,
4332 matches,
4333 editor,
4334 cx,
4335 )
4336 });
4337 })
4338 .ok();
4339 Some(menu)
4340 }
4341 } else {
4342 None
4343 };
4344
4345 this.update(&mut cx, |this, cx| {
4346 let mut context_menu = this.context_menu.write();
4347 match context_menu.as_ref() {
4348 None => {}
4349
4350 Some(ContextMenu::Completions(prev_menu)) => {
4351 if prev_menu.id > id {
4352 return;
4353 }
4354 }
4355
4356 _ => return,
4357 }
4358
4359 if this.focus_handle.is_focused(cx) && menu.is_some() {
4360 let menu = menu.unwrap();
4361 *context_menu = Some(ContextMenu::Completions(menu));
4362 drop(context_menu);
4363 this.discard_inline_completion(false, cx);
4364 cx.notify();
4365 } else if this.completion_tasks.len() <= 1 {
4366 // If there are no more completion tasks and the last menu was
4367 // empty, we should hide it. If it was already hidden, we should
4368 // also show the copilot completion when available.
4369 drop(context_menu);
4370 if this.hide_context_menu(cx).is_none() {
4371 this.update_visible_inline_completion(cx);
4372 }
4373 }
4374 })?;
4375
4376 Ok::<_, anyhow::Error>(())
4377 }
4378 .log_err()
4379 });
4380
4381 self.completion_tasks.push((id, task));
4382 }
4383
4384 pub fn confirm_completion(
4385 &mut self,
4386 action: &ConfirmCompletion,
4387 cx: &mut ViewContext<Self>,
4388 ) -> Option<Task<Result<()>>> {
4389 self.do_completion(action.item_ix, CompletionIntent::Complete, cx)
4390 }
4391
4392 pub fn compose_completion(
4393 &mut self,
4394 action: &ComposeCompletion,
4395 cx: &mut ViewContext<Self>,
4396 ) -> Option<Task<Result<()>>> {
4397 self.do_completion(action.item_ix, CompletionIntent::Compose, cx)
4398 }
4399
4400 fn do_completion(
4401 &mut self,
4402 item_ix: Option<usize>,
4403 intent: CompletionIntent,
4404 cx: &mut ViewContext<Editor>,
4405 ) -> Option<Task<std::result::Result<(), anyhow::Error>>> {
4406 use language::ToOffset as _;
4407
4408 let completions_menu = if let ContextMenu::Completions(menu) = self.hide_context_menu(cx)? {
4409 menu
4410 } else {
4411 return None;
4412 };
4413
4414 let mat = completions_menu
4415 .matches
4416 .get(item_ix.unwrap_or(completions_menu.selected_item))?;
4417 let buffer_handle = completions_menu.buffer;
4418 let completions = completions_menu.completions.read();
4419 let completion = completions.get(mat.candidate_id)?;
4420 cx.stop_propagation();
4421
4422 let snippet;
4423 let text;
4424
4425 if completion.is_snippet() {
4426 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
4427 text = snippet.as_ref().unwrap().text.clone();
4428 } else {
4429 snippet = None;
4430 text = completion.new_text.clone();
4431 };
4432 let selections = self.selections.all::<usize>(cx);
4433 let buffer = buffer_handle.read(cx);
4434 let old_range = completion.old_range.to_offset(buffer);
4435 let old_text = buffer.text_for_range(old_range.clone()).collect::<String>();
4436
4437 let newest_selection = self.selections.newest_anchor();
4438 if newest_selection.start.buffer_id != Some(buffer_handle.read(cx).remote_id()) {
4439 return None;
4440 }
4441
4442 let lookbehind = newest_selection
4443 .start
4444 .text_anchor
4445 .to_offset(buffer)
4446 .saturating_sub(old_range.start);
4447 let lookahead = old_range
4448 .end
4449 .saturating_sub(newest_selection.end.text_anchor.to_offset(buffer));
4450 let mut common_prefix_len = old_text
4451 .bytes()
4452 .zip(text.bytes())
4453 .take_while(|(a, b)| a == b)
4454 .count();
4455
4456 let snapshot = self.buffer.read(cx).snapshot(cx);
4457 let mut range_to_replace: Option<Range<isize>> = None;
4458 let mut ranges = Vec::new();
4459 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4460 for selection in &selections {
4461 if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) {
4462 let start = selection.start.saturating_sub(lookbehind);
4463 let end = selection.end + lookahead;
4464 if selection.id == newest_selection.id {
4465 range_to_replace = Some(
4466 ((start + common_prefix_len) as isize - selection.start as isize)
4467 ..(end as isize - selection.start as isize),
4468 );
4469 }
4470 ranges.push(start + common_prefix_len..end);
4471 } else {
4472 common_prefix_len = 0;
4473 ranges.clear();
4474 ranges.extend(selections.iter().map(|s| {
4475 if s.id == newest_selection.id {
4476 range_to_replace = Some(
4477 old_range.start.to_offset_utf16(&snapshot).0 as isize
4478 - selection.start as isize
4479 ..old_range.end.to_offset_utf16(&snapshot).0 as isize
4480 - selection.start as isize,
4481 );
4482 old_range.clone()
4483 } else {
4484 s.start..s.end
4485 }
4486 }));
4487 break;
4488 }
4489 if !self.linked_edit_ranges.is_empty() {
4490 let start_anchor = snapshot.anchor_before(selection.head());
4491 let end_anchor = snapshot.anchor_after(selection.tail());
4492 if let Some(ranges) = self
4493 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
4494 {
4495 for (buffer, edits) in ranges {
4496 linked_edits.entry(buffer.clone()).or_default().extend(
4497 edits
4498 .into_iter()
4499 .map(|range| (range, text[common_prefix_len..].to_owned())),
4500 );
4501 }
4502 }
4503 }
4504 }
4505 let text = &text[common_prefix_len..];
4506
4507 cx.emit(EditorEvent::InputHandled {
4508 utf16_range_to_replace: range_to_replace,
4509 text: text.into(),
4510 });
4511
4512 self.transact(cx, |this, cx| {
4513 if let Some(mut snippet) = snippet {
4514 snippet.text = text.to_string();
4515 for tabstop in snippet.tabstops.iter_mut().flatten() {
4516 tabstop.start -= common_prefix_len as isize;
4517 tabstop.end -= common_prefix_len as isize;
4518 }
4519
4520 this.insert_snippet(&ranges, snippet, cx).log_err();
4521 } else {
4522 this.buffer.update(cx, |buffer, cx| {
4523 buffer.edit(
4524 ranges.iter().map(|range| (range.clone(), text)),
4525 this.autoindent_mode.clone(),
4526 cx,
4527 );
4528 });
4529 }
4530 for (buffer, edits) in linked_edits {
4531 buffer.update(cx, |buffer, cx| {
4532 let snapshot = buffer.snapshot();
4533 let edits = edits
4534 .into_iter()
4535 .map(|(range, text)| {
4536 use text::ToPoint as TP;
4537 let end_point = TP::to_point(&range.end, &snapshot);
4538 let start_point = TP::to_point(&range.start, &snapshot);
4539 (start_point..end_point, text)
4540 })
4541 .sorted_by_key(|(range, _)| range.start)
4542 .collect::<Vec<_>>();
4543 buffer.edit(edits, None, cx);
4544 })
4545 }
4546
4547 this.refresh_inline_completion(true, false, cx);
4548 });
4549
4550 let show_new_completions_on_confirm = completion
4551 .confirm
4552 .as_ref()
4553 .map_or(false, |confirm| confirm(intent, cx));
4554 if show_new_completions_on_confirm {
4555 self.show_completions(&ShowCompletions { trigger: None }, cx);
4556 }
4557
4558 let provider = self.completion_provider.as_ref()?;
4559 let apply_edits = provider.apply_additional_edits_for_completion(
4560 buffer_handle,
4561 completion.clone(),
4562 true,
4563 cx,
4564 );
4565
4566 let editor_settings = EditorSettings::get_global(cx);
4567 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
4568 // After the code completion is finished, users often want to know what signatures are needed.
4569 // so we should automatically call signature_help
4570 self.show_signature_help(&ShowSignatureHelp, cx);
4571 }
4572
4573 Some(cx.foreground_executor().spawn(async move {
4574 apply_edits.await?;
4575 Ok(())
4576 }))
4577 }
4578
4579 pub fn toggle_code_actions(&mut self, action: &ToggleCodeActions, cx: &mut ViewContext<Self>) {
4580 let mut context_menu = self.context_menu.write();
4581 if let Some(ContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
4582 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
4583 // Toggle if we're selecting the same one
4584 *context_menu = None;
4585 cx.notify();
4586 return;
4587 } else {
4588 // Otherwise, clear it and start a new one
4589 *context_menu = None;
4590 cx.notify();
4591 }
4592 }
4593 drop(context_menu);
4594 let snapshot = self.snapshot(cx);
4595 let deployed_from_indicator = action.deployed_from_indicator;
4596 let mut task = self.code_actions_task.take();
4597 let action = action.clone();
4598 cx.spawn(|editor, mut cx| async move {
4599 while let Some(prev_task) = task {
4600 prev_task.await.log_err();
4601 task = editor.update(&mut cx, |this, _| this.code_actions_task.take())?;
4602 }
4603
4604 let spawned_test_task = editor.update(&mut cx, |editor, cx| {
4605 if editor.focus_handle.is_focused(cx) {
4606 let multibuffer_point = action
4607 .deployed_from_indicator
4608 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
4609 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
4610 let (buffer, buffer_row) = snapshot
4611 .buffer_snapshot
4612 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
4613 .and_then(|(buffer_snapshot, range)| {
4614 editor
4615 .buffer
4616 .read(cx)
4617 .buffer(buffer_snapshot.remote_id())
4618 .map(|buffer| (buffer, range.start.row))
4619 })?;
4620 let (_, code_actions) = editor
4621 .available_code_actions
4622 .clone()
4623 .and_then(|(location, code_actions)| {
4624 let snapshot = location.buffer.read(cx).snapshot();
4625 let point_range = location.range.to_point(&snapshot);
4626 let point_range = point_range.start.row..=point_range.end.row;
4627 if point_range.contains(&buffer_row) {
4628 Some((location, code_actions))
4629 } else {
4630 None
4631 }
4632 })
4633 .unzip();
4634 let buffer_id = buffer.read(cx).remote_id();
4635 let tasks = editor
4636 .tasks
4637 .get(&(buffer_id, buffer_row))
4638 .map(|t| Arc::new(t.to_owned()));
4639 if tasks.is_none() && code_actions.is_none() {
4640 return None;
4641 }
4642
4643 editor.completion_tasks.clear();
4644 editor.discard_inline_completion(false, cx);
4645 let task_context =
4646 tasks
4647 .as_ref()
4648 .zip(editor.project.clone())
4649 .map(|(tasks, project)| {
4650 let position = Point::new(buffer_row, tasks.column);
4651 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
4652 let location = Location {
4653 buffer: buffer.clone(),
4654 range: range_start..range_start,
4655 };
4656 // Fill in the environmental variables from the tree-sitter captures
4657 let mut captured_task_variables = TaskVariables::default();
4658 for (capture_name, value) in tasks.extra_variables.clone() {
4659 captured_task_variables.insert(
4660 task::VariableName::Custom(capture_name.into()),
4661 value.clone(),
4662 );
4663 }
4664 project.update(cx, |project, cx| {
4665 project.task_context_for_location(
4666 captured_task_variables,
4667 location,
4668 cx,
4669 )
4670 })
4671 });
4672
4673 Some(cx.spawn(|editor, mut cx| async move {
4674 let task_context = match task_context {
4675 Some(task_context) => task_context.await,
4676 None => None,
4677 };
4678 let resolved_tasks =
4679 tasks.zip(task_context).map(|(tasks, task_context)| {
4680 Arc::new(ResolvedTasks {
4681 templates: tasks
4682 .templates
4683 .iter()
4684 .filter_map(|(kind, template)| {
4685 template
4686 .resolve_task(&kind.to_id_base(), &task_context)
4687 .map(|task| (kind.clone(), task))
4688 })
4689 .collect(),
4690 position: snapshot.buffer_snapshot.anchor_before(Point::new(
4691 multibuffer_point.row,
4692 tasks.column,
4693 )),
4694 })
4695 });
4696 let spawn_straight_away = resolved_tasks
4697 .as_ref()
4698 .map_or(false, |tasks| tasks.templates.len() == 1)
4699 && code_actions
4700 .as_ref()
4701 .map_or(true, |actions| actions.is_empty());
4702 if let Ok(task) = editor.update(&mut cx, |editor, cx| {
4703 *editor.context_menu.write() =
4704 Some(ContextMenu::CodeActions(CodeActionsMenu {
4705 buffer,
4706 actions: CodeActionContents {
4707 tasks: resolved_tasks,
4708 actions: code_actions,
4709 },
4710 selected_item: Default::default(),
4711 scroll_handle: UniformListScrollHandle::default(),
4712 deployed_from_indicator,
4713 }));
4714 if spawn_straight_away {
4715 if let Some(task) = editor.confirm_code_action(
4716 &ConfirmCodeAction { item_ix: Some(0) },
4717 cx,
4718 ) {
4719 cx.notify();
4720 return task;
4721 }
4722 }
4723 cx.notify();
4724 Task::ready(Ok(()))
4725 }) {
4726 task.await
4727 } else {
4728 Ok(())
4729 }
4730 }))
4731 } else {
4732 Some(Task::ready(Ok(())))
4733 }
4734 })?;
4735 if let Some(task) = spawned_test_task {
4736 task.await?;
4737 }
4738
4739 Ok::<_, anyhow::Error>(())
4740 })
4741 .detach_and_log_err(cx);
4742 }
4743
4744 pub fn confirm_code_action(
4745 &mut self,
4746 action: &ConfirmCodeAction,
4747 cx: &mut ViewContext<Self>,
4748 ) -> Option<Task<Result<()>>> {
4749 let actions_menu = if let ContextMenu::CodeActions(menu) = self.hide_context_menu(cx)? {
4750 menu
4751 } else {
4752 return None;
4753 };
4754 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
4755 let action = actions_menu.actions.get(action_ix)?;
4756 let title = action.label();
4757 let buffer = actions_menu.buffer;
4758 let workspace = self.workspace()?;
4759
4760 match action {
4761 CodeActionsItem::Task(task_source_kind, resolved_task) => {
4762 workspace.update(cx, |workspace, cx| {
4763 workspace::tasks::schedule_resolved_task(
4764 workspace,
4765 task_source_kind,
4766 resolved_task,
4767 false,
4768 cx,
4769 );
4770
4771 Some(Task::ready(Ok(())))
4772 })
4773 }
4774 CodeActionsItem::CodeAction {
4775 excerpt_id,
4776 action,
4777 provider,
4778 } => {
4779 let apply_code_action =
4780 provider.apply_code_action(buffer, action, excerpt_id, true, cx);
4781 let workspace = workspace.downgrade();
4782 Some(cx.spawn(|editor, cx| async move {
4783 let project_transaction = apply_code_action.await?;
4784 Self::open_project_transaction(
4785 &editor,
4786 workspace,
4787 project_transaction,
4788 title,
4789 cx,
4790 )
4791 .await
4792 }))
4793 }
4794 }
4795 }
4796
4797 pub async fn open_project_transaction(
4798 this: &WeakView<Editor>,
4799 workspace: WeakView<Workspace>,
4800 transaction: ProjectTransaction,
4801 title: String,
4802 mut cx: AsyncWindowContext,
4803 ) -> Result<()> {
4804 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
4805 cx.update(|cx| {
4806 entries.sort_unstable_by_key(|(buffer, _)| {
4807 buffer.read(cx).file().map(|f| f.path().clone())
4808 });
4809 })?;
4810
4811 // If the project transaction's edits are all contained within this editor, then
4812 // avoid opening a new editor to display them.
4813
4814 if let Some((buffer, transaction)) = entries.first() {
4815 if entries.len() == 1 {
4816 let excerpt = this.update(&mut cx, |editor, cx| {
4817 editor
4818 .buffer()
4819 .read(cx)
4820 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
4821 })?;
4822 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
4823 if excerpted_buffer == *buffer {
4824 let all_edits_within_excerpt = buffer.read_with(&cx, |buffer, _| {
4825 let excerpt_range = excerpt_range.to_offset(buffer);
4826 buffer
4827 .edited_ranges_for_transaction::<usize>(transaction)
4828 .all(|range| {
4829 excerpt_range.start <= range.start
4830 && excerpt_range.end >= range.end
4831 })
4832 })?;
4833
4834 if all_edits_within_excerpt {
4835 return Ok(());
4836 }
4837 }
4838 }
4839 }
4840 } else {
4841 return Ok(());
4842 }
4843
4844 let mut ranges_to_highlight = Vec::new();
4845 let excerpt_buffer = cx.new_model(|cx| {
4846 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
4847 for (buffer_handle, transaction) in &entries {
4848 let buffer = buffer_handle.read(cx);
4849 ranges_to_highlight.extend(
4850 multibuffer.push_excerpts_with_context_lines(
4851 buffer_handle.clone(),
4852 buffer
4853 .edited_ranges_for_transaction::<usize>(transaction)
4854 .collect(),
4855 DEFAULT_MULTIBUFFER_CONTEXT,
4856 cx,
4857 ),
4858 );
4859 }
4860 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
4861 multibuffer
4862 })?;
4863
4864 workspace.update(&mut cx, |workspace, cx| {
4865 let project = workspace.project().clone();
4866 let editor =
4867 cx.new_view(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), true, cx));
4868 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, cx);
4869 editor.update(cx, |editor, cx| {
4870 editor.highlight_background::<Self>(
4871 &ranges_to_highlight,
4872 |theme| theme.editor_highlighted_line_background,
4873 cx,
4874 );
4875 });
4876 })?;
4877
4878 Ok(())
4879 }
4880
4881 pub fn push_code_action_provider(
4882 &mut self,
4883 provider: Arc<dyn CodeActionProvider>,
4884 cx: &mut ViewContext<Self>,
4885 ) {
4886 self.code_action_providers.push(provider);
4887 self.refresh_code_actions(cx);
4888 }
4889
4890 fn refresh_code_actions(&mut self, cx: &mut ViewContext<Self>) -> Option<()> {
4891 let buffer = self.buffer.read(cx);
4892 let newest_selection = self.selections.newest_anchor().clone();
4893 let (start_buffer, start) = buffer.text_anchor_for_position(newest_selection.start, cx)?;
4894 let (end_buffer, end) = buffer.text_anchor_for_position(newest_selection.end, cx)?;
4895 if start_buffer != end_buffer {
4896 return None;
4897 }
4898
4899 self.code_actions_task = Some(cx.spawn(|this, mut cx| async move {
4900 cx.background_executor()
4901 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
4902 .await;
4903
4904 let (providers, tasks) = this.update(&mut cx, |this, cx| {
4905 let providers = this.code_action_providers.clone();
4906 let tasks = this
4907 .code_action_providers
4908 .iter()
4909 .map(|provider| provider.code_actions(&start_buffer, start..end, cx))
4910 .collect::<Vec<_>>();
4911 (providers, tasks)
4912 })?;
4913
4914 let mut actions = Vec::new();
4915 for (provider, provider_actions) in
4916 providers.into_iter().zip(future::join_all(tasks).await)
4917 {
4918 if let Some(provider_actions) = provider_actions.log_err() {
4919 actions.extend(provider_actions.into_iter().map(|action| {
4920 AvailableCodeAction {
4921 excerpt_id: newest_selection.start.excerpt_id,
4922 action,
4923 provider: provider.clone(),
4924 }
4925 }));
4926 }
4927 }
4928
4929 this.update(&mut cx, |this, cx| {
4930 this.available_code_actions = if actions.is_empty() {
4931 None
4932 } else {
4933 Some((
4934 Location {
4935 buffer: start_buffer,
4936 range: start..end,
4937 },
4938 actions.into(),
4939 ))
4940 };
4941 cx.notify();
4942 })
4943 }));
4944 None
4945 }
4946
4947 fn start_inline_blame_timer(&mut self, cx: &mut ViewContext<Self>) {
4948 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
4949 self.show_git_blame_inline = false;
4950
4951 self.show_git_blame_inline_delay_task = Some(cx.spawn(|this, mut cx| async move {
4952 cx.background_executor().timer(delay).await;
4953
4954 this.update(&mut cx, |this, cx| {
4955 this.show_git_blame_inline = true;
4956 cx.notify();
4957 })
4958 .log_err();
4959 }));
4960 }
4961 }
4962
4963 fn refresh_document_highlights(&mut self, cx: &mut ViewContext<Self>) -> Option<()> {
4964 if self.pending_rename.is_some() {
4965 return None;
4966 }
4967
4968 let project = self.project.clone()?;
4969 let buffer = self.buffer.read(cx);
4970 let newest_selection = self.selections.newest_anchor().clone();
4971 let cursor_position = newest_selection.head();
4972 let (cursor_buffer, cursor_buffer_position) =
4973 buffer.text_anchor_for_position(cursor_position, cx)?;
4974 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
4975 if cursor_buffer != tail_buffer {
4976 return None;
4977 }
4978
4979 self.document_highlights_task = Some(cx.spawn(|this, mut cx| async move {
4980 cx.background_executor()
4981 .timer(DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT)
4982 .await;
4983
4984 let highlights = if let Some(highlights) = project
4985 .update(&mut cx, |project, cx| {
4986 project.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
4987 })
4988 .log_err()
4989 {
4990 highlights.await.log_err()
4991 } else {
4992 None
4993 };
4994
4995 if let Some(highlights) = highlights {
4996 this.update(&mut cx, |this, cx| {
4997 if this.pending_rename.is_some() {
4998 return;
4999 }
5000
5001 let buffer_id = cursor_position.buffer_id;
5002 let buffer = this.buffer.read(cx);
5003 if !buffer
5004 .text_anchor_for_position(cursor_position, cx)
5005 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5006 {
5007 return;
5008 }
5009
5010 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5011 let mut write_ranges = Vec::new();
5012 let mut read_ranges = Vec::new();
5013 for highlight in highlights {
5014 for (excerpt_id, excerpt_range) in
5015 buffer.excerpts_for_buffer(&cursor_buffer, cx)
5016 {
5017 let start = highlight
5018 .range
5019 .start
5020 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5021 let end = highlight
5022 .range
5023 .end
5024 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5025 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5026 continue;
5027 }
5028
5029 let range = Anchor {
5030 buffer_id,
5031 excerpt_id,
5032 text_anchor: start,
5033 }..Anchor {
5034 buffer_id,
5035 excerpt_id,
5036 text_anchor: end,
5037 };
5038 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5039 write_ranges.push(range);
5040 } else {
5041 read_ranges.push(range);
5042 }
5043 }
5044 }
5045
5046 this.highlight_background::<DocumentHighlightRead>(
5047 &read_ranges,
5048 |theme| theme.editor_document_highlight_read_background,
5049 cx,
5050 );
5051 this.highlight_background::<DocumentHighlightWrite>(
5052 &write_ranges,
5053 |theme| theme.editor_document_highlight_write_background,
5054 cx,
5055 );
5056 cx.notify();
5057 })
5058 .log_err();
5059 }
5060 }));
5061 None
5062 }
5063
5064 pub fn refresh_inline_completion(
5065 &mut self,
5066 debounce: bool,
5067 user_requested: bool,
5068 cx: &mut ViewContext<Self>,
5069 ) -> Option<()> {
5070 let provider = self.inline_completion_provider()?;
5071 let cursor = self.selections.newest_anchor().head();
5072 let (buffer, cursor_buffer_position) =
5073 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5074
5075 if !user_requested
5076 && (!self.enable_inline_completions
5077 || !self.should_show_inline_completions(&buffer, cursor_buffer_position, cx))
5078 {
5079 self.discard_inline_completion(false, cx);
5080 return None;
5081 }
5082
5083 self.update_visible_inline_completion(cx);
5084 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
5085 Some(())
5086 }
5087
5088 fn cycle_inline_completion(
5089 &mut self,
5090 direction: Direction,
5091 cx: &mut ViewContext<Self>,
5092 ) -> Option<()> {
5093 let provider = self.inline_completion_provider()?;
5094 let cursor = self.selections.newest_anchor().head();
5095 let (buffer, cursor_buffer_position) =
5096 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5097 if !self.enable_inline_completions
5098 || !self.should_show_inline_completions(&buffer, cursor_buffer_position, cx)
5099 {
5100 return None;
5101 }
5102
5103 provider.cycle(buffer, cursor_buffer_position, direction, cx);
5104 self.update_visible_inline_completion(cx);
5105
5106 Some(())
5107 }
5108
5109 pub fn show_inline_completion(&mut self, _: &ShowInlineCompletion, cx: &mut ViewContext<Self>) {
5110 if !self.has_active_inline_completion(cx) {
5111 self.refresh_inline_completion(false, true, cx);
5112 return;
5113 }
5114
5115 self.update_visible_inline_completion(cx);
5116 }
5117
5118 pub fn display_cursor_names(&mut self, _: &DisplayCursorNames, cx: &mut ViewContext<Self>) {
5119 self.show_cursor_names(cx);
5120 }
5121
5122 fn show_cursor_names(&mut self, cx: &mut ViewContext<Self>) {
5123 self.show_cursor_names = true;
5124 cx.notify();
5125 cx.spawn(|this, mut cx| async move {
5126 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
5127 this.update(&mut cx, |this, cx| {
5128 this.show_cursor_names = false;
5129 cx.notify()
5130 })
5131 .ok()
5132 })
5133 .detach();
5134 }
5135
5136 pub fn next_inline_completion(&mut self, _: &NextInlineCompletion, cx: &mut ViewContext<Self>) {
5137 if self.has_active_inline_completion(cx) {
5138 self.cycle_inline_completion(Direction::Next, cx);
5139 } else {
5140 let is_copilot_disabled = self.refresh_inline_completion(false, true, cx).is_none();
5141 if is_copilot_disabled {
5142 cx.propagate();
5143 }
5144 }
5145 }
5146
5147 pub fn previous_inline_completion(
5148 &mut self,
5149 _: &PreviousInlineCompletion,
5150 cx: &mut ViewContext<Self>,
5151 ) {
5152 if self.has_active_inline_completion(cx) {
5153 self.cycle_inline_completion(Direction::Prev, cx);
5154 } else {
5155 let is_copilot_disabled = self.refresh_inline_completion(false, true, cx).is_none();
5156 if is_copilot_disabled {
5157 cx.propagate();
5158 }
5159 }
5160 }
5161
5162 pub fn accept_inline_completion(
5163 &mut self,
5164 _: &AcceptInlineCompletion,
5165 cx: &mut ViewContext<Self>,
5166 ) {
5167 let Some(completion) = self.take_active_inline_completion(cx) else {
5168 return;
5169 };
5170 if let Some(provider) = self.inline_completion_provider() {
5171 provider.accept(cx);
5172 }
5173
5174 cx.emit(EditorEvent::InputHandled {
5175 utf16_range_to_replace: None,
5176 text: completion.text.to_string().into(),
5177 });
5178
5179 if let Some(range) = completion.delete_range {
5180 self.change_selections(None, cx, |s| s.select_ranges([range]))
5181 }
5182 self.insert_with_autoindent_mode(&completion.text.to_string(), None, cx);
5183 self.refresh_inline_completion(true, true, cx);
5184 cx.notify();
5185 }
5186
5187 pub fn accept_partial_inline_completion(
5188 &mut self,
5189 _: &AcceptPartialInlineCompletion,
5190 cx: &mut ViewContext<Self>,
5191 ) {
5192 if self.selections.count() == 1 && self.has_active_inline_completion(cx) {
5193 if let Some(completion) = self.take_active_inline_completion(cx) {
5194 let mut partial_completion = completion
5195 .text
5196 .chars()
5197 .by_ref()
5198 .take_while(|c| c.is_alphabetic())
5199 .collect::<String>();
5200 if partial_completion.is_empty() {
5201 partial_completion = completion
5202 .text
5203 .chars()
5204 .by_ref()
5205 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
5206 .collect::<String>();
5207 }
5208
5209 cx.emit(EditorEvent::InputHandled {
5210 utf16_range_to_replace: None,
5211 text: partial_completion.clone().into(),
5212 });
5213
5214 if let Some(range) = completion.delete_range {
5215 self.change_selections(None, cx, |s| s.select_ranges([range]))
5216 }
5217 self.insert_with_autoindent_mode(&partial_completion, None, cx);
5218
5219 self.refresh_inline_completion(true, true, cx);
5220 cx.notify();
5221 }
5222 }
5223 }
5224
5225 fn discard_inline_completion(
5226 &mut self,
5227 should_report_inline_completion_event: bool,
5228 cx: &mut ViewContext<Self>,
5229 ) -> bool {
5230 if let Some(provider) = self.inline_completion_provider() {
5231 provider.discard(should_report_inline_completion_event, cx);
5232 }
5233
5234 self.take_active_inline_completion(cx).is_some()
5235 }
5236
5237 pub fn has_active_inline_completion(&self, cx: &AppContext) -> bool {
5238 if let Some(completion) = self.active_inline_completion.as_ref() {
5239 let buffer = self.buffer.read(cx).read(cx);
5240 completion.position.is_valid(&buffer)
5241 } else {
5242 false
5243 }
5244 }
5245
5246 fn take_active_inline_completion(
5247 &mut self,
5248 cx: &mut ViewContext<Self>,
5249 ) -> Option<CompletionState> {
5250 let completion = self.active_inline_completion.take()?;
5251 let render_inlay_ids = completion.render_inlay_ids.clone();
5252 self.display_map.update(cx, |map, cx| {
5253 map.splice_inlays(render_inlay_ids, Default::default(), cx);
5254 });
5255 let buffer = self.buffer.read(cx).read(cx);
5256
5257 if completion.position.is_valid(&buffer) {
5258 Some(completion)
5259 } else {
5260 None
5261 }
5262 }
5263
5264 fn update_visible_inline_completion(&mut self, cx: &mut ViewContext<Self>) {
5265 let selection = self.selections.newest_anchor();
5266 let cursor = selection.head();
5267
5268 let excerpt_id = cursor.excerpt_id;
5269
5270 if self.context_menu.read().is_none()
5271 && self.completion_tasks.is_empty()
5272 && selection.start == selection.end
5273 {
5274 if let Some(provider) = self.inline_completion_provider() {
5275 if let Some((buffer, cursor_buffer_position)) =
5276 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5277 {
5278 if let Some(proposal) =
5279 provider.active_completion_text(&buffer, cursor_buffer_position, cx)
5280 {
5281 let mut to_remove = Vec::new();
5282 if let Some(completion) = self.active_inline_completion.take() {
5283 to_remove.extend(completion.render_inlay_ids.iter());
5284 }
5285
5286 let to_add = proposal
5287 .inlays
5288 .iter()
5289 .filter_map(|inlay| {
5290 let snapshot = self.buffer.read(cx).snapshot(cx);
5291 let id = post_inc(&mut self.next_inlay_id);
5292 match inlay {
5293 InlayProposal::Hint(position, hint) => {
5294 let position =
5295 snapshot.anchor_in_excerpt(excerpt_id, *position)?;
5296 Some(Inlay::hint(id, position, hint))
5297 }
5298 InlayProposal::Suggestion(position, text) => {
5299 let position =
5300 snapshot.anchor_in_excerpt(excerpt_id, *position)?;
5301 Some(Inlay::suggestion(id, position, text.clone()))
5302 }
5303 }
5304 })
5305 .collect_vec();
5306
5307 self.active_inline_completion = Some(CompletionState {
5308 position: cursor,
5309 text: proposal.text,
5310 delete_range: proposal.delete_range.and_then(|range| {
5311 let snapshot = self.buffer.read(cx).snapshot(cx);
5312 let start = snapshot.anchor_in_excerpt(excerpt_id, range.start);
5313 let end = snapshot.anchor_in_excerpt(excerpt_id, range.end);
5314 Some(start?..end?)
5315 }),
5316 render_inlay_ids: to_add.iter().map(|i| i.id).collect(),
5317 });
5318
5319 self.display_map
5320 .update(cx, move |map, cx| map.splice_inlays(to_remove, to_add, cx));
5321
5322 cx.notify();
5323 return;
5324 }
5325 }
5326 }
5327 }
5328
5329 self.discard_inline_completion(false, cx);
5330 }
5331
5332 fn inline_completion_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
5333 Some(self.inline_completion_provider.as_ref()?.provider.clone())
5334 }
5335
5336 fn render_code_actions_indicator(
5337 &self,
5338 _style: &EditorStyle,
5339 row: DisplayRow,
5340 is_active: bool,
5341 cx: &mut ViewContext<Self>,
5342 ) -> Option<IconButton> {
5343 if self.available_code_actions.is_some() {
5344 Some(
5345 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
5346 .shape(ui::IconButtonShape::Square)
5347 .icon_size(IconSize::XSmall)
5348 .icon_color(Color::Muted)
5349 .selected(is_active)
5350 .on_click(cx.listener(move |editor, _e, cx| {
5351 editor.focus(cx);
5352 editor.toggle_code_actions(
5353 &ToggleCodeActions {
5354 deployed_from_indicator: Some(row),
5355 },
5356 cx,
5357 );
5358 })),
5359 )
5360 } else {
5361 None
5362 }
5363 }
5364
5365 fn clear_tasks(&mut self) {
5366 self.tasks.clear()
5367 }
5368
5369 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
5370 if self.tasks.insert(key, value).is_some() {
5371 // This case should hopefully be rare, but just in case...
5372 log::error!("multiple different run targets found on a single line, only the last target will be rendered")
5373 }
5374 }
5375
5376 fn render_run_indicator(
5377 &self,
5378 _style: &EditorStyle,
5379 is_active: bool,
5380 row: DisplayRow,
5381 cx: &mut ViewContext<Self>,
5382 ) -> IconButton {
5383 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
5384 .shape(ui::IconButtonShape::Square)
5385 .icon_size(IconSize::XSmall)
5386 .icon_color(Color::Muted)
5387 .selected(is_active)
5388 .on_click(cx.listener(move |editor, _e, cx| {
5389 editor.focus(cx);
5390 editor.toggle_code_actions(
5391 &ToggleCodeActions {
5392 deployed_from_indicator: Some(row),
5393 },
5394 cx,
5395 );
5396 }))
5397 }
5398
5399 pub fn context_menu_visible(&self) -> bool {
5400 self.context_menu
5401 .read()
5402 .as_ref()
5403 .map_or(false, |menu| menu.visible())
5404 }
5405
5406 fn render_context_menu(
5407 &self,
5408 cursor_position: DisplayPoint,
5409 style: &EditorStyle,
5410 max_height: Pixels,
5411 cx: &mut ViewContext<Editor>,
5412 ) -> Option<(ContextMenuOrigin, AnyElement)> {
5413 self.context_menu.read().as_ref().map(|menu| {
5414 menu.render(
5415 cursor_position,
5416 style,
5417 max_height,
5418 self.workspace.as_ref().map(|(w, _)| w.clone()),
5419 cx,
5420 )
5421 })
5422 }
5423
5424 fn hide_context_menu(&mut self, cx: &mut ViewContext<Self>) -> Option<ContextMenu> {
5425 cx.notify();
5426 self.completion_tasks.clear();
5427 let context_menu = self.context_menu.write().take();
5428 if context_menu.is_some() {
5429 self.update_visible_inline_completion(cx);
5430 }
5431 context_menu
5432 }
5433
5434 pub fn insert_snippet(
5435 &mut self,
5436 insertion_ranges: &[Range<usize>],
5437 snippet: Snippet,
5438 cx: &mut ViewContext<Self>,
5439 ) -> Result<()> {
5440 struct Tabstop<T> {
5441 is_end_tabstop: bool,
5442 ranges: Vec<Range<T>>,
5443 }
5444
5445 let tabstops = self.buffer.update(cx, |buffer, cx| {
5446 let snippet_text: Arc<str> = snippet.text.clone().into();
5447 buffer.edit(
5448 insertion_ranges
5449 .iter()
5450 .cloned()
5451 .map(|range| (range, snippet_text.clone())),
5452 Some(AutoindentMode::EachLine),
5453 cx,
5454 );
5455
5456 let snapshot = &*buffer.read(cx);
5457 let snippet = &snippet;
5458 snippet
5459 .tabstops
5460 .iter()
5461 .map(|tabstop| {
5462 let is_end_tabstop = tabstop.first().map_or(false, |tabstop| {
5463 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
5464 });
5465 let mut tabstop_ranges = tabstop
5466 .iter()
5467 .flat_map(|tabstop_range| {
5468 let mut delta = 0_isize;
5469 insertion_ranges.iter().map(move |insertion_range| {
5470 let insertion_start = insertion_range.start as isize + delta;
5471 delta +=
5472 snippet.text.len() as isize - insertion_range.len() as isize;
5473
5474 let start = ((insertion_start + tabstop_range.start) as usize)
5475 .min(snapshot.len());
5476 let end = ((insertion_start + tabstop_range.end) as usize)
5477 .min(snapshot.len());
5478 snapshot.anchor_before(start)..snapshot.anchor_after(end)
5479 })
5480 })
5481 .collect::<Vec<_>>();
5482 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
5483
5484 Tabstop {
5485 is_end_tabstop,
5486 ranges: tabstop_ranges,
5487 }
5488 })
5489 .collect::<Vec<_>>()
5490 });
5491 if let Some(tabstop) = tabstops.first() {
5492 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
5493 s.select_ranges(tabstop.ranges.iter().cloned());
5494 });
5495
5496 // If we're already at the last tabstop and it's at the end of the snippet,
5497 // we're done, we don't need to keep the state around.
5498 if !tabstop.is_end_tabstop {
5499 let ranges = tabstops
5500 .into_iter()
5501 .map(|tabstop| tabstop.ranges)
5502 .collect::<Vec<_>>();
5503 self.snippet_stack.push(SnippetState {
5504 active_index: 0,
5505 ranges,
5506 });
5507 }
5508
5509 // Check whether the just-entered snippet ends with an auto-closable bracket.
5510 if self.autoclose_regions.is_empty() {
5511 let snapshot = self.buffer.read(cx).snapshot(cx);
5512 for selection in &mut self.selections.all::<Point>(cx) {
5513 let selection_head = selection.head();
5514 let Some(scope) = snapshot.language_scope_at(selection_head) else {
5515 continue;
5516 };
5517
5518 let mut bracket_pair = None;
5519 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
5520 let prev_chars = snapshot
5521 .reversed_chars_at(selection_head)
5522 .collect::<String>();
5523 for (pair, enabled) in scope.brackets() {
5524 if enabled
5525 && pair.close
5526 && prev_chars.starts_with(pair.start.as_str())
5527 && next_chars.starts_with(pair.end.as_str())
5528 {
5529 bracket_pair = Some(pair.clone());
5530 break;
5531 }
5532 }
5533 if let Some(pair) = bracket_pair {
5534 let start = snapshot.anchor_after(selection_head);
5535 let end = snapshot.anchor_after(selection_head);
5536 self.autoclose_regions.push(AutocloseRegion {
5537 selection_id: selection.id,
5538 range: start..end,
5539 pair,
5540 });
5541 }
5542 }
5543 }
5544 }
5545 Ok(())
5546 }
5547
5548 pub fn move_to_next_snippet_tabstop(&mut self, cx: &mut ViewContext<Self>) -> bool {
5549 self.move_to_snippet_tabstop(Bias::Right, cx)
5550 }
5551
5552 pub fn move_to_prev_snippet_tabstop(&mut self, cx: &mut ViewContext<Self>) -> bool {
5553 self.move_to_snippet_tabstop(Bias::Left, cx)
5554 }
5555
5556 pub fn move_to_snippet_tabstop(&mut self, bias: Bias, cx: &mut ViewContext<Self>) -> bool {
5557 if let Some(mut snippet) = self.snippet_stack.pop() {
5558 match bias {
5559 Bias::Left => {
5560 if snippet.active_index > 0 {
5561 snippet.active_index -= 1;
5562 } else {
5563 self.snippet_stack.push(snippet);
5564 return false;
5565 }
5566 }
5567 Bias::Right => {
5568 if snippet.active_index + 1 < snippet.ranges.len() {
5569 snippet.active_index += 1;
5570 } else {
5571 self.snippet_stack.push(snippet);
5572 return false;
5573 }
5574 }
5575 }
5576 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
5577 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
5578 s.select_anchor_ranges(current_ranges.iter().cloned())
5579 });
5580 // If snippet state is not at the last tabstop, push it back on the stack
5581 if snippet.active_index + 1 < snippet.ranges.len() {
5582 self.snippet_stack.push(snippet);
5583 }
5584 return true;
5585 }
5586 }
5587
5588 false
5589 }
5590
5591 pub fn clear(&mut self, cx: &mut ViewContext<Self>) {
5592 self.transact(cx, |this, cx| {
5593 this.select_all(&SelectAll, cx);
5594 this.insert("", cx);
5595 });
5596 }
5597
5598 pub fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) {
5599 self.transact(cx, |this, cx| {
5600 this.select_autoclose_pair(cx);
5601 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
5602 if !this.linked_edit_ranges.is_empty() {
5603 let selections = this.selections.all::<MultiBufferPoint>(cx);
5604 let snapshot = this.buffer.read(cx).snapshot(cx);
5605
5606 for selection in selections.iter() {
5607 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
5608 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
5609 if selection_start.buffer_id != selection_end.buffer_id {
5610 continue;
5611 }
5612 if let Some(ranges) =
5613 this.linked_editing_ranges_for(selection_start..selection_end, cx)
5614 {
5615 for (buffer, entries) in ranges {
5616 linked_ranges.entry(buffer).or_default().extend(entries);
5617 }
5618 }
5619 }
5620 }
5621
5622 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
5623 if !this.selections.line_mode {
5624 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
5625 for selection in &mut selections {
5626 if selection.is_empty() {
5627 let old_head = selection.head();
5628 let mut new_head =
5629 movement::left(&display_map, old_head.to_display_point(&display_map))
5630 .to_point(&display_map);
5631 if let Some((buffer, line_buffer_range)) = display_map
5632 .buffer_snapshot
5633 .buffer_line_for_row(MultiBufferRow(old_head.row))
5634 {
5635 let indent_size =
5636 buffer.indent_size_for_line(line_buffer_range.start.row);
5637 let indent_len = match indent_size.kind {
5638 IndentKind::Space => {
5639 buffer.settings_at(line_buffer_range.start, cx).tab_size
5640 }
5641 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
5642 };
5643 if old_head.column <= indent_size.len && old_head.column > 0 {
5644 let indent_len = indent_len.get();
5645 new_head = cmp::min(
5646 new_head,
5647 MultiBufferPoint::new(
5648 old_head.row,
5649 ((old_head.column - 1) / indent_len) * indent_len,
5650 ),
5651 );
5652 }
5653 }
5654
5655 selection.set_head(new_head, SelectionGoal::None);
5656 }
5657 }
5658 }
5659
5660 this.signature_help_state.set_backspace_pressed(true);
5661 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
5662 this.insert("", cx);
5663 let empty_str: Arc<str> = Arc::from("");
5664 for (buffer, edits) in linked_ranges {
5665 let snapshot = buffer.read(cx).snapshot();
5666 use text::ToPoint as TP;
5667
5668 let edits = edits
5669 .into_iter()
5670 .map(|range| {
5671 let end_point = TP::to_point(&range.end, &snapshot);
5672 let mut start_point = TP::to_point(&range.start, &snapshot);
5673
5674 if end_point == start_point {
5675 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
5676 .saturating_sub(1);
5677 start_point = TP::to_point(&offset, &snapshot);
5678 };
5679
5680 (start_point..end_point, empty_str.clone())
5681 })
5682 .sorted_by_key(|(range, _)| range.start)
5683 .collect::<Vec<_>>();
5684 buffer.update(cx, |this, cx| {
5685 this.edit(edits, None, cx);
5686 })
5687 }
5688 this.refresh_inline_completion(true, false, cx);
5689 linked_editing_ranges::refresh_linked_ranges(this, cx);
5690 });
5691 }
5692
5693 pub fn delete(&mut self, _: &Delete, cx: &mut ViewContext<Self>) {
5694 self.transact(cx, |this, cx| {
5695 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
5696 let line_mode = s.line_mode;
5697 s.move_with(|map, selection| {
5698 if selection.is_empty() && !line_mode {
5699 let cursor = movement::right(map, selection.head());
5700 selection.end = cursor;
5701 selection.reversed = true;
5702 selection.goal = SelectionGoal::None;
5703 }
5704 })
5705 });
5706 this.insert("", cx);
5707 this.refresh_inline_completion(true, false, cx);
5708 });
5709 }
5710
5711 pub fn tab_prev(&mut self, _: &TabPrev, cx: &mut ViewContext<Self>) {
5712 if self.move_to_prev_snippet_tabstop(cx) {
5713 return;
5714 }
5715
5716 self.outdent(&Outdent, cx);
5717 }
5718
5719 pub fn tab(&mut self, _: &Tab, cx: &mut ViewContext<Self>) {
5720 if self.move_to_next_snippet_tabstop(cx) || self.read_only(cx) {
5721 return;
5722 }
5723
5724 let mut selections = self.selections.all_adjusted(cx);
5725 let buffer = self.buffer.read(cx);
5726 let snapshot = buffer.snapshot(cx);
5727 let rows_iter = selections.iter().map(|s| s.head().row);
5728 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
5729
5730 let mut edits = Vec::new();
5731 let mut prev_edited_row = 0;
5732 let mut row_delta = 0;
5733 for selection in &mut selections {
5734 if selection.start.row != prev_edited_row {
5735 row_delta = 0;
5736 }
5737 prev_edited_row = selection.end.row;
5738
5739 // If the selection is non-empty, then increase the indentation of the selected lines.
5740 if !selection.is_empty() {
5741 row_delta =
5742 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
5743 continue;
5744 }
5745
5746 // If the selection is empty and the cursor is in the leading whitespace before the
5747 // suggested indentation, then auto-indent the line.
5748 let cursor = selection.head();
5749 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
5750 if let Some(suggested_indent) =
5751 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
5752 {
5753 if cursor.column < suggested_indent.len
5754 && cursor.column <= current_indent.len
5755 && current_indent.len <= suggested_indent.len
5756 {
5757 selection.start = Point::new(cursor.row, suggested_indent.len);
5758 selection.end = selection.start;
5759 if row_delta == 0 {
5760 edits.extend(Buffer::edit_for_indent_size_adjustment(
5761 cursor.row,
5762 current_indent,
5763 suggested_indent,
5764 ));
5765 row_delta = suggested_indent.len - current_indent.len;
5766 }
5767 continue;
5768 }
5769 }
5770
5771 // Otherwise, insert a hard or soft tab.
5772 let settings = buffer.settings_at(cursor, cx);
5773 let tab_size = if settings.hard_tabs {
5774 IndentSize::tab()
5775 } else {
5776 let tab_size = settings.tab_size.get();
5777 let char_column = snapshot
5778 .text_for_range(Point::new(cursor.row, 0)..cursor)
5779 .flat_map(str::chars)
5780 .count()
5781 + row_delta as usize;
5782 let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size);
5783 IndentSize::spaces(chars_to_next_tab_stop)
5784 };
5785 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
5786 selection.end = selection.start;
5787 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
5788 row_delta += tab_size.len;
5789 }
5790
5791 self.transact(cx, |this, cx| {
5792 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
5793 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
5794 this.refresh_inline_completion(true, false, cx);
5795 });
5796 }
5797
5798 pub fn indent(&mut self, _: &Indent, cx: &mut ViewContext<Self>) {
5799 if self.read_only(cx) {
5800 return;
5801 }
5802 let mut selections = self.selections.all::<Point>(cx);
5803 let mut prev_edited_row = 0;
5804 let mut row_delta = 0;
5805 let mut edits = Vec::new();
5806 let buffer = self.buffer.read(cx);
5807 let snapshot = buffer.snapshot(cx);
5808 for selection in &mut selections {
5809 if selection.start.row != prev_edited_row {
5810 row_delta = 0;
5811 }
5812 prev_edited_row = selection.end.row;
5813
5814 row_delta =
5815 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
5816 }
5817
5818 self.transact(cx, |this, cx| {
5819 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
5820 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
5821 });
5822 }
5823
5824 fn indent_selection(
5825 buffer: &MultiBuffer,
5826 snapshot: &MultiBufferSnapshot,
5827 selection: &mut Selection<Point>,
5828 edits: &mut Vec<(Range<Point>, String)>,
5829 delta_for_start_row: u32,
5830 cx: &AppContext,
5831 ) -> u32 {
5832 let settings = buffer.settings_at(selection.start, cx);
5833 let tab_size = settings.tab_size.get();
5834 let indent_kind = if settings.hard_tabs {
5835 IndentKind::Tab
5836 } else {
5837 IndentKind::Space
5838 };
5839 let mut start_row = selection.start.row;
5840 let mut end_row = selection.end.row + 1;
5841
5842 // If a selection ends at the beginning of a line, don't indent
5843 // that last line.
5844 if selection.end.column == 0 && selection.end.row > selection.start.row {
5845 end_row -= 1;
5846 }
5847
5848 // Avoid re-indenting a row that has already been indented by a
5849 // previous selection, but still update this selection's column
5850 // to reflect that indentation.
5851 if delta_for_start_row > 0 {
5852 start_row += 1;
5853 selection.start.column += delta_for_start_row;
5854 if selection.end.row == selection.start.row {
5855 selection.end.column += delta_for_start_row;
5856 }
5857 }
5858
5859 let mut delta_for_end_row = 0;
5860 let has_multiple_rows = start_row + 1 != end_row;
5861 for row in start_row..end_row {
5862 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
5863 let indent_delta = match (current_indent.kind, indent_kind) {
5864 (IndentKind::Space, IndentKind::Space) => {
5865 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
5866 IndentSize::spaces(columns_to_next_tab_stop)
5867 }
5868 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
5869 (_, IndentKind::Tab) => IndentSize::tab(),
5870 };
5871
5872 let start = if has_multiple_rows || current_indent.len < selection.start.column {
5873 0
5874 } else {
5875 selection.start.column
5876 };
5877 let row_start = Point::new(row, start);
5878 edits.push((
5879 row_start..row_start,
5880 indent_delta.chars().collect::<String>(),
5881 ));
5882
5883 // Update this selection's endpoints to reflect the indentation.
5884 if row == selection.start.row {
5885 selection.start.column += indent_delta.len;
5886 }
5887 if row == selection.end.row {
5888 selection.end.column += indent_delta.len;
5889 delta_for_end_row = indent_delta.len;
5890 }
5891 }
5892
5893 if selection.start.row == selection.end.row {
5894 delta_for_start_row + delta_for_end_row
5895 } else {
5896 delta_for_end_row
5897 }
5898 }
5899
5900 pub fn outdent(&mut self, _: &Outdent, cx: &mut ViewContext<Self>) {
5901 if self.read_only(cx) {
5902 return;
5903 }
5904 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5905 let selections = self.selections.all::<Point>(cx);
5906 let mut deletion_ranges = Vec::new();
5907 let mut last_outdent = None;
5908 {
5909 let buffer = self.buffer.read(cx);
5910 let snapshot = buffer.snapshot(cx);
5911 for selection in &selections {
5912 let settings = buffer.settings_at(selection.start, cx);
5913 let tab_size = settings.tab_size.get();
5914 let mut rows = selection.spanned_rows(false, &display_map);
5915
5916 // Avoid re-outdenting a row that has already been outdented by a
5917 // previous selection.
5918 if let Some(last_row) = last_outdent {
5919 if last_row == rows.start {
5920 rows.start = rows.start.next_row();
5921 }
5922 }
5923 let has_multiple_rows = rows.len() > 1;
5924 for row in rows.iter_rows() {
5925 let indent_size = snapshot.indent_size_for_line(row);
5926 if indent_size.len > 0 {
5927 let deletion_len = match indent_size.kind {
5928 IndentKind::Space => {
5929 let columns_to_prev_tab_stop = indent_size.len % tab_size;
5930 if columns_to_prev_tab_stop == 0 {
5931 tab_size
5932 } else {
5933 columns_to_prev_tab_stop
5934 }
5935 }
5936 IndentKind::Tab => 1,
5937 };
5938 let start = if has_multiple_rows
5939 || deletion_len > selection.start.column
5940 || indent_size.len < selection.start.column
5941 {
5942 0
5943 } else {
5944 selection.start.column - deletion_len
5945 };
5946 deletion_ranges.push(
5947 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
5948 );
5949 last_outdent = Some(row);
5950 }
5951 }
5952 }
5953 }
5954
5955 self.transact(cx, |this, cx| {
5956 this.buffer.update(cx, |buffer, cx| {
5957 let empty_str: Arc<str> = Arc::default();
5958 buffer.edit(
5959 deletion_ranges
5960 .into_iter()
5961 .map(|range| (range, empty_str.clone())),
5962 None,
5963 cx,
5964 );
5965 });
5966 let selections = this.selections.all::<usize>(cx);
5967 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
5968 });
5969 }
5970
5971 pub fn delete_line(&mut self, _: &DeleteLine, cx: &mut ViewContext<Self>) {
5972 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5973 let selections = self.selections.all::<Point>(cx);
5974
5975 let mut new_cursors = Vec::new();
5976 let mut edit_ranges = Vec::new();
5977 let mut selections = selections.iter().peekable();
5978 while let Some(selection) = selections.next() {
5979 let mut rows = selection.spanned_rows(false, &display_map);
5980 let goal_display_column = selection.head().to_display_point(&display_map).column();
5981
5982 // Accumulate contiguous regions of rows that we want to delete.
5983 while let Some(next_selection) = selections.peek() {
5984 let next_rows = next_selection.spanned_rows(false, &display_map);
5985 if next_rows.start <= rows.end {
5986 rows.end = next_rows.end;
5987 selections.next().unwrap();
5988 } else {
5989 break;
5990 }
5991 }
5992
5993 let buffer = &display_map.buffer_snapshot;
5994 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
5995 let edit_end;
5996 let cursor_buffer_row;
5997 if buffer.max_point().row >= rows.end.0 {
5998 // If there's a line after the range, delete the \n from the end of the row range
5999 // and position the cursor on the next line.
6000 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
6001 cursor_buffer_row = rows.end;
6002 } else {
6003 // If there isn't a line after the range, delete the \n from the line before the
6004 // start of the row range and position the cursor there.
6005 edit_start = edit_start.saturating_sub(1);
6006 edit_end = buffer.len();
6007 cursor_buffer_row = rows.start.previous_row();
6008 }
6009
6010 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
6011 *cursor.column_mut() =
6012 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
6013
6014 new_cursors.push((
6015 selection.id,
6016 buffer.anchor_after(cursor.to_point(&display_map)),
6017 ));
6018 edit_ranges.push(edit_start..edit_end);
6019 }
6020
6021 self.transact(cx, |this, cx| {
6022 let buffer = this.buffer.update(cx, |buffer, cx| {
6023 let empty_str: Arc<str> = Arc::default();
6024 buffer.edit(
6025 edit_ranges
6026 .into_iter()
6027 .map(|range| (range, empty_str.clone())),
6028 None,
6029 cx,
6030 );
6031 buffer.snapshot(cx)
6032 });
6033 let new_selections = new_cursors
6034 .into_iter()
6035 .map(|(id, cursor)| {
6036 let cursor = cursor.to_point(&buffer);
6037 Selection {
6038 id,
6039 start: cursor,
6040 end: cursor,
6041 reversed: false,
6042 goal: SelectionGoal::None,
6043 }
6044 })
6045 .collect();
6046
6047 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
6048 s.select(new_selections);
6049 });
6050 });
6051 }
6052
6053 pub fn join_lines(&mut self, _: &JoinLines, cx: &mut ViewContext<Self>) {
6054 if self.read_only(cx) {
6055 return;
6056 }
6057 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
6058 for selection in self.selections.all::<Point>(cx) {
6059 let start = MultiBufferRow(selection.start.row);
6060 let end = if selection.start.row == selection.end.row {
6061 MultiBufferRow(selection.start.row + 1)
6062 } else {
6063 MultiBufferRow(selection.end.row)
6064 };
6065
6066 if let Some(last_row_range) = row_ranges.last_mut() {
6067 if start <= last_row_range.end {
6068 last_row_range.end = end;
6069 continue;
6070 }
6071 }
6072 row_ranges.push(start..end);
6073 }
6074
6075 let snapshot = self.buffer.read(cx).snapshot(cx);
6076 let mut cursor_positions = Vec::new();
6077 for row_range in &row_ranges {
6078 let anchor = snapshot.anchor_before(Point::new(
6079 row_range.end.previous_row().0,
6080 snapshot.line_len(row_range.end.previous_row()),
6081 ));
6082 cursor_positions.push(anchor..anchor);
6083 }
6084
6085 self.transact(cx, |this, cx| {
6086 for row_range in row_ranges.into_iter().rev() {
6087 for row in row_range.iter_rows().rev() {
6088 let end_of_line = Point::new(row.0, snapshot.line_len(row));
6089 let next_line_row = row.next_row();
6090 let indent = snapshot.indent_size_for_line(next_line_row);
6091 let start_of_next_line = Point::new(next_line_row.0, indent.len);
6092
6093 let replace = if snapshot.line_len(next_line_row) > indent.len {
6094 " "
6095 } else {
6096 ""
6097 };
6098
6099 this.buffer.update(cx, |buffer, cx| {
6100 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
6101 });
6102 }
6103 }
6104
6105 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
6106 s.select_anchor_ranges(cursor_positions)
6107 });
6108 });
6109 }
6110
6111 pub fn sort_lines_case_sensitive(
6112 &mut self,
6113 _: &SortLinesCaseSensitive,
6114 cx: &mut ViewContext<Self>,
6115 ) {
6116 self.manipulate_lines(cx, |lines| lines.sort())
6117 }
6118
6119 pub fn sort_lines_case_insensitive(
6120 &mut self,
6121 _: &SortLinesCaseInsensitive,
6122 cx: &mut ViewContext<Self>,
6123 ) {
6124 self.manipulate_lines(cx, |lines| lines.sort_by_key(|line| line.to_lowercase()))
6125 }
6126
6127 pub fn unique_lines_case_insensitive(
6128 &mut self,
6129 _: &UniqueLinesCaseInsensitive,
6130 cx: &mut ViewContext<Self>,
6131 ) {
6132 self.manipulate_lines(cx, |lines| {
6133 let mut seen = HashSet::default();
6134 lines.retain(|line| seen.insert(line.to_lowercase()));
6135 })
6136 }
6137
6138 pub fn unique_lines_case_sensitive(
6139 &mut self,
6140 _: &UniqueLinesCaseSensitive,
6141 cx: &mut ViewContext<Self>,
6142 ) {
6143 self.manipulate_lines(cx, |lines| {
6144 let mut seen = HashSet::default();
6145 lines.retain(|line| seen.insert(*line));
6146 })
6147 }
6148
6149 pub fn revert_file(&mut self, _: &RevertFile, cx: &mut ViewContext<Self>) {
6150 let mut revert_changes = HashMap::default();
6151 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
6152 for hunk in hunks_for_rows(
6153 Some(MultiBufferRow(0)..multi_buffer_snapshot.max_buffer_row()).into_iter(),
6154 &multi_buffer_snapshot,
6155 ) {
6156 Self::prepare_revert_change(&mut revert_changes, self.buffer(), &hunk, cx);
6157 }
6158 if !revert_changes.is_empty() {
6159 self.transact(cx, |editor, cx| {
6160 editor.revert(revert_changes, cx);
6161 });
6162 }
6163 }
6164
6165 pub fn revert_selected_hunks(&mut self, _: &RevertSelectedHunks, cx: &mut ViewContext<Self>) {
6166 let revert_changes = self.gather_revert_changes(&self.selections.disjoint_anchors(), cx);
6167 if !revert_changes.is_empty() {
6168 self.transact(cx, |editor, cx| {
6169 editor.revert(revert_changes, cx);
6170 });
6171 }
6172 }
6173
6174 pub fn open_active_item_in_terminal(&mut self, _: &OpenInTerminal, cx: &mut ViewContext<Self>) {
6175 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
6176 let project_path = buffer.read(cx).project_path(cx)?;
6177 let project = self.project.as_ref()?.read(cx);
6178 let entry = project.entry_for_path(&project_path, cx)?;
6179 let abs_path = project.absolute_path(&project_path, cx)?;
6180 let parent = if entry.is_symlink {
6181 abs_path.canonicalize().ok()?
6182 } else {
6183 abs_path
6184 }
6185 .parent()?
6186 .to_path_buf();
6187 Some(parent)
6188 }) {
6189 cx.dispatch_action(OpenTerminal { working_directory }.boxed_clone());
6190 }
6191 }
6192
6193 fn gather_revert_changes(
6194 &mut self,
6195 selections: &[Selection<Anchor>],
6196 cx: &mut ViewContext<'_, Editor>,
6197 ) -> HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>> {
6198 let mut revert_changes = HashMap::default();
6199 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
6200 for hunk in hunks_for_selections(&multi_buffer_snapshot, selections) {
6201 Self::prepare_revert_change(&mut revert_changes, self.buffer(), &hunk, cx);
6202 }
6203 revert_changes
6204 }
6205
6206 pub fn prepare_revert_change(
6207 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
6208 multi_buffer: &Model<MultiBuffer>,
6209 hunk: &MultiBufferDiffHunk,
6210 cx: &AppContext,
6211 ) -> Option<()> {
6212 let buffer = multi_buffer.read(cx).buffer(hunk.buffer_id)?;
6213 let buffer = buffer.read(cx);
6214 let original_text = buffer.diff_base()?.slice(hunk.diff_base_byte_range.clone());
6215 let buffer_snapshot = buffer.snapshot();
6216 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
6217 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
6218 probe
6219 .0
6220 .start
6221 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
6222 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
6223 }) {
6224 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
6225 Some(())
6226 } else {
6227 None
6228 }
6229 }
6230
6231 pub fn reverse_lines(&mut self, _: &ReverseLines, cx: &mut ViewContext<Self>) {
6232 self.manipulate_lines(cx, |lines| lines.reverse())
6233 }
6234
6235 pub fn shuffle_lines(&mut self, _: &ShuffleLines, cx: &mut ViewContext<Self>) {
6236 self.manipulate_lines(cx, |lines| lines.shuffle(&mut thread_rng()))
6237 }
6238
6239 fn manipulate_lines<Fn>(&mut self, cx: &mut ViewContext<Self>, mut callback: Fn)
6240 where
6241 Fn: FnMut(&mut Vec<&str>),
6242 {
6243 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
6244 let buffer = self.buffer.read(cx).snapshot(cx);
6245
6246 let mut edits = Vec::new();
6247
6248 let selections = self.selections.all::<Point>(cx);
6249 let mut selections = selections.iter().peekable();
6250 let mut contiguous_row_selections = Vec::new();
6251 let mut new_selections = Vec::new();
6252 let mut added_lines = 0;
6253 let mut removed_lines = 0;
6254
6255 while let Some(selection) = selections.next() {
6256 let (start_row, end_row) = consume_contiguous_rows(
6257 &mut contiguous_row_selections,
6258 selection,
6259 &display_map,
6260 &mut selections,
6261 );
6262
6263 let start_point = Point::new(start_row.0, 0);
6264 let end_point = Point::new(
6265 end_row.previous_row().0,
6266 buffer.line_len(end_row.previous_row()),
6267 );
6268 let text = buffer
6269 .text_for_range(start_point..end_point)
6270 .collect::<String>();
6271
6272 let mut lines = text.split('\n').collect_vec();
6273
6274 let lines_before = lines.len();
6275 callback(&mut lines);
6276 let lines_after = lines.len();
6277
6278 edits.push((start_point..end_point, lines.join("\n")));
6279
6280 // Selections must change based on added and removed line count
6281 let start_row =
6282 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
6283 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
6284 new_selections.push(Selection {
6285 id: selection.id,
6286 start: start_row,
6287 end: end_row,
6288 goal: SelectionGoal::None,
6289 reversed: selection.reversed,
6290 });
6291
6292 if lines_after > lines_before {
6293 added_lines += lines_after - lines_before;
6294 } else if lines_before > lines_after {
6295 removed_lines += lines_before - lines_after;
6296 }
6297 }
6298
6299 self.transact(cx, |this, cx| {
6300 let buffer = this.buffer.update(cx, |buffer, cx| {
6301 buffer.edit(edits, None, cx);
6302 buffer.snapshot(cx)
6303 });
6304
6305 // Recalculate offsets on newly edited buffer
6306 let new_selections = new_selections
6307 .iter()
6308 .map(|s| {
6309 let start_point = Point::new(s.start.0, 0);
6310 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
6311 Selection {
6312 id: s.id,
6313 start: buffer.point_to_offset(start_point),
6314 end: buffer.point_to_offset(end_point),
6315 goal: s.goal,
6316 reversed: s.reversed,
6317 }
6318 })
6319 .collect();
6320
6321 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
6322 s.select(new_selections);
6323 });
6324
6325 this.request_autoscroll(Autoscroll::fit(), cx);
6326 });
6327 }
6328
6329 pub fn convert_to_upper_case(&mut self, _: &ConvertToUpperCase, cx: &mut ViewContext<Self>) {
6330 self.manipulate_text(cx, |text| text.to_uppercase())
6331 }
6332
6333 pub fn convert_to_lower_case(&mut self, _: &ConvertToLowerCase, cx: &mut ViewContext<Self>) {
6334 self.manipulate_text(cx, |text| text.to_lowercase())
6335 }
6336
6337 pub fn convert_to_title_case(&mut self, _: &ConvertToTitleCase, cx: &mut ViewContext<Self>) {
6338 self.manipulate_text(cx, |text| {
6339 // Hack to get around the fact that to_case crate doesn't support '\n' as a word boundary
6340 // https://github.com/rutrum/convert-case/issues/16
6341 text.split('\n')
6342 .map(|line| line.to_case(Case::Title))
6343 .join("\n")
6344 })
6345 }
6346
6347 pub fn convert_to_snake_case(&mut self, _: &ConvertToSnakeCase, cx: &mut ViewContext<Self>) {
6348 self.manipulate_text(cx, |text| text.to_case(Case::Snake))
6349 }
6350
6351 pub fn convert_to_kebab_case(&mut self, _: &ConvertToKebabCase, cx: &mut ViewContext<Self>) {
6352 self.manipulate_text(cx, |text| text.to_case(Case::Kebab))
6353 }
6354
6355 pub fn convert_to_upper_camel_case(
6356 &mut self,
6357 _: &ConvertToUpperCamelCase,
6358 cx: &mut ViewContext<Self>,
6359 ) {
6360 self.manipulate_text(cx, |text| {
6361 // Hack to get around the fact that to_case crate doesn't support '\n' as a word boundary
6362 // https://github.com/rutrum/convert-case/issues/16
6363 text.split('\n')
6364 .map(|line| line.to_case(Case::UpperCamel))
6365 .join("\n")
6366 })
6367 }
6368
6369 pub fn convert_to_lower_camel_case(
6370 &mut self,
6371 _: &ConvertToLowerCamelCase,
6372 cx: &mut ViewContext<Self>,
6373 ) {
6374 self.manipulate_text(cx, |text| text.to_case(Case::Camel))
6375 }
6376
6377 pub fn convert_to_opposite_case(
6378 &mut self,
6379 _: &ConvertToOppositeCase,
6380 cx: &mut ViewContext<Self>,
6381 ) {
6382 self.manipulate_text(cx, |text| {
6383 text.chars()
6384 .fold(String::with_capacity(text.len()), |mut t, c| {
6385 if c.is_uppercase() {
6386 t.extend(c.to_lowercase());
6387 } else {
6388 t.extend(c.to_uppercase());
6389 }
6390 t
6391 })
6392 })
6393 }
6394
6395 fn manipulate_text<Fn>(&mut self, cx: &mut ViewContext<Self>, mut callback: Fn)
6396 where
6397 Fn: FnMut(&str) -> String,
6398 {
6399 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
6400 let buffer = self.buffer.read(cx).snapshot(cx);
6401
6402 let mut new_selections = Vec::new();
6403 let mut edits = Vec::new();
6404 let mut selection_adjustment = 0i32;
6405
6406 for selection in self.selections.all::<usize>(cx) {
6407 let selection_is_empty = selection.is_empty();
6408
6409 let (start, end) = if selection_is_empty {
6410 let word_range = movement::surrounding_word(
6411 &display_map,
6412 selection.start.to_display_point(&display_map),
6413 );
6414 let start = word_range.start.to_offset(&display_map, Bias::Left);
6415 let end = word_range.end.to_offset(&display_map, Bias::Left);
6416 (start, end)
6417 } else {
6418 (selection.start, selection.end)
6419 };
6420
6421 let text = buffer.text_for_range(start..end).collect::<String>();
6422 let old_length = text.len() as i32;
6423 let text = callback(&text);
6424
6425 new_selections.push(Selection {
6426 start: (start as i32 - selection_adjustment) as usize,
6427 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
6428 goal: SelectionGoal::None,
6429 ..selection
6430 });
6431
6432 selection_adjustment += old_length - text.len() as i32;
6433
6434 edits.push((start..end, text));
6435 }
6436
6437 self.transact(cx, |this, cx| {
6438 this.buffer.update(cx, |buffer, cx| {
6439 buffer.edit(edits, None, cx);
6440 });
6441
6442 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
6443 s.select(new_selections);
6444 });
6445
6446 this.request_autoscroll(Autoscroll::fit(), cx);
6447 });
6448 }
6449
6450 pub fn duplicate_line(&mut self, upwards: bool, cx: &mut ViewContext<Self>) {
6451 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
6452 let buffer = &display_map.buffer_snapshot;
6453 let selections = self.selections.all::<Point>(cx);
6454
6455 let mut edits = Vec::new();
6456 let mut selections_iter = selections.iter().peekable();
6457 while let Some(selection) = selections_iter.next() {
6458 // Avoid duplicating the same lines twice.
6459 let mut rows = selection.spanned_rows(false, &display_map);
6460
6461 while let Some(next_selection) = selections_iter.peek() {
6462 let next_rows = next_selection.spanned_rows(false, &display_map);
6463 if next_rows.start < rows.end {
6464 rows.end = next_rows.end;
6465 selections_iter.next().unwrap();
6466 } else {
6467 break;
6468 }
6469 }
6470
6471 // Copy the text from the selected row region and splice it either at the start
6472 // or end of the region.
6473 let start = Point::new(rows.start.0, 0);
6474 let end = Point::new(
6475 rows.end.previous_row().0,
6476 buffer.line_len(rows.end.previous_row()),
6477 );
6478 let text = buffer
6479 .text_for_range(start..end)
6480 .chain(Some("\n"))
6481 .collect::<String>();
6482 let insert_location = if upwards {
6483 Point::new(rows.end.0, 0)
6484 } else {
6485 start
6486 };
6487 edits.push((insert_location..insert_location, text));
6488 }
6489
6490 self.transact(cx, |this, cx| {
6491 this.buffer.update(cx, |buffer, cx| {
6492 buffer.edit(edits, None, cx);
6493 });
6494
6495 this.request_autoscroll(Autoscroll::fit(), cx);
6496 });
6497 }
6498
6499 pub fn duplicate_line_up(&mut self, _: &DuplicateLineUp, cx: &mut ViewContext<Self>) {
6500 self.duplicate_line(true, cx);
6501 }
6502
6503 pub fn duplicate_line_down(&mut self, _: &DuplicateLineDown, cx: &mut ViewContext<Self>) {
6504 self.duplicate_line(false, cx);
6505 }
6506
6507 pub fn move_line_up(&mut self, _: &MoveLineUp, cx: &mut ViewContext<Self>) {
6508 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
6509 let buffer = self.buffer.read(cx).snapshot(cx);
6510
6511 let mut edits = Vec::new();
6512 let mut unfold_ranges = Vec::new();
6513 let mut refold_ranges = Vec::new();
6514
6515 let selections = self.selections.all::<Point>(cx);
6516 let mut selections = selections.iter().peekable();
6517 let mut contiguous_row_selections = Vec::new();
6518 let mut new_selections = Vec::new();
6519
6520 while let Some(selection) = selections.next() {
6521 // Find all the selections that span a contiguous row range
6522 let (start_row, end_row) = consume_contiguous_rows(
6523 &mut contiguous_row_selections,
6524 selection,
6525 &display_map,
6526 &mut selections,
6527 );
6528
6529 // Move the text spanned by the row range to be before the line preceding the row range
6530 if start_row.0 > 0 {
6531 let range_to_move = Point::new(
6532 start_row.previous_row().0,
6533 buffer.line_len(start_row.previous_row()),
6534 )
6535 ..Point::new(
6536 end_row.previous_row().0,
6537 buffer.line_len(end_row.previous_row()),
6538 );
6539 let insertion_point = display_map
6540 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
6541 .0;
6542
6543 // Don't move lines across excerpts
6544 if buffer
6545 .excerpt_boundaries_in_range((
6546 Bound::Excluded(insertion_point),
6547 Bound::Included(range_to_move.end),
6548 ))
6549 .next()
6550 .is_none()
6551 {
6552 let text = buffer
6553 .text_for_range(range_to_move.clone())
6554 .flat_map(|s| s.chars())
6555 .skip(1)
6556 .chain(['\n'])
6557 .collect::<String>();
6558
6559 edits.push((
6560 buffer.anchor_after(range_to_move.start)
6561 ..buffer.anchor_before(range_to_move.end),
6562 String::new(),
6563 ));
6564 let insertion_anchor = buffer.anchor_after(insertion_point);
6565 edits.push((insertion_anchor..insertion_anchor, text));
6566
6567 let row_delta = range_to_move.start.row - insertion_point.row + 1;
6568
6569 // Move selections up
6570 new_selections.extend(contiguous_row_selections.drain(..).map(
6571 |mut selection| {
6572 selection.start.row -= row_delta;
6573 selection.end.row -= row_delta;
6574 selection
6575 },
6576 ));
6577
6578 // Move folds up
6579 unfold_ranges.push(range_to_move.clone());
6580 for fold in display_map.folds_in_range(
6581 buffer.anchor_before(range_to_move.start)
6582 ..buffer.anchor_after(range_to_move.end),
6583 ) {
6584 let mut start = fold.range.start.to_point(&buffer);
6585 let mut end = fold.range.end.to_point(&buffer);
6586 start.row -= row_delta;
6587 end.row -= row_delta;
6588 refold_ranges.push((start..end, fold.placeholder.clone()));
6589 }
6590 }
6591 }
6592
6593 // If we didn't move line(s), preserve the existing selections
6594 new_selections.append(&mut contiguous_row_selections);
6595 }
6596
6597 self.transact(cx, |this, cx| {
6598 this.unfold_ranges(unfold_ranges, true, true, cx);
6599 this.buffer.update(cx, |buffer, cx| {
6600 for (range, text) in edits {
6601 buffer.edit([(range, text)], None, cx);
6602 }
6603 });
6604 this.fold_ranges(refold_ranges, true, cx);
6605 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
6606 s.select(new_selections);
6607 })
6608 });
6609 }
6610
6611 pub fn move_line_down(&mut self, _: &MoveLineDown, cx: &mut ViewContext<Self>) {
6612 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
6613 let buffer = self.buffer.read(cx).snapshot(cx);
6614
6615 let mut edits = Vec::new();
6616 let mut unfold_ranges = Vec::new();
6617 let mut refold_ranges = Vec::new();
6618
6619 let selections = self.selections.all::<Point>(cx);
6620 let mut selections = selections.iter().peekable();
6621 let mut contiguous_row_selections = Vec::new();
6622 let mut new_selections = Vec::new();
6623
6624 while let Some(selection) = selections.next() {
6625 // Find all the selections that span a contiguous row range
6626 let (start_row, end_row) = consume_contiguous_rows(
6627 &mut contiguous_row_selections,
6628 selection,
6629 &display_map,
6630 &mut selections,
6631 );
6632
6633 // Move the text spanned by the row range to be after the last line of the row range
6634 if end_row.0 <= buffer.max_point().row {
6635 let range_to_move =
6636 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
6637 let insertion_point = display_map
6638 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
6639 .0;
6640
6641 // Don't move lines across excerpt boundaries
6642 if buffer
6643 .excerpt_boundaries_in_range((
6644 Bound::Excluded(range_to_move.start),
6645 Bound::Included(insertion_point),
6646 ))
6647 .next()
6648 .is_none()
6649 {
6650 let mut text = String::from("\n");
6651 text.extend(buffer.text_for_range(range_to_move.clone()));
6652 text.pop(); // Drop trailing newline
6653 edits.push((
6654 buffer.anchor_after(range_to_move.start)
6655 ..buffer.anchor_before(range_to_move.end),
6656 String::new(),
6657 ));
6658 let insertion_anchor = buffer.anchor_after(insertion_point);
6659 edits.push((insertion_anchor..insertion_anchor, text));
6660
6661 let row_delta = insertion_point.row - range_to_move.end.row + 1;
6662
6663 // Move selections down
6664 new_selections.extend(contiguous_row_selections.drain(..).map(
6665 |mut selection| {
6666 selection.start.row += row_delta;
6667 selection.end.row += row_delta;
6668 selection
6669 },
6670 ));
6671
6672 // Move folds down
6673 unfold_ranges.push(range_to_move.clone());
6674 for fold in display_map.folds_in_range(
6675 buffer.anchor_before(range_to_move.start)
6676 ..buffer.anchor_after(range_to_move.end),
6677 ) {
6678 let mut start = fold.range.start.to_point(&buffer);
6679 let mut end = fold.range.end.to_point(&buffer);
6680 start.row += row_delta;
6681 end.row += row_delta;
6682 refold_ranges.push((start..end, fold.placeholder.clone()));
6683 }
6684 }
6685 }
6686
6687 // If we didn't move line(s), preserve the existing selections
6688 new_selections.append(&mut contiguous_row_selections);
6689 }
6690
6691 self.transact(cx, |this, cx| {
6692 this.unfold_ranges(unfold_ranges, true, true, cx);
6693 this.buffer.update(cx, |buffer, cx| {
6694 for (range, text) in edits {
6695 buffer.edit([(range, text)], None, cx);
6696 }
6697 });
6698 this.fold_ranges(refold_ranges, true, cx);
6699 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
6700 });
6701 }
6702
6703 pub fn transpose(&mut self, _: &Transpose, cx: &mut ViewContext<Self>) {
6704 let text_layout_details = &self.text_layout_details(cx);
6705 self.transact(cx, |this, cx| {
6706 let edits = this.change_selections(Some(Autoscroll::fit()), cx, |s| {
6707 let mut edits: Vec<(Range<usize>, String)> = Default::default();
6708 let line_mode = s.line_mode;
6709 s.move_with(|display_map, selection| {
6710 if !selection.is_empty() || line_mode {
6711 return;
6712 }
6713
6714 let mut head = selection.head();
6715 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
6716 if head.column() == display_map.line_len(head.row()) {
6717 transpose_offset = display_map
6718 .buffer_snapshot
6719 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
6720 }
6721
6722 if transpose_offset == 0 {
6723 return;
6724 }
6725
6726 *head.column_mut() += 1;
6727 head = display_map.clip_point(head, Bias::Right);
6728 let goal = SelectionGoal::HorizontalPosition(
6729 display_map
6730 .x_for_display_point(head, text_layout_details)
6731 .into(),
6732 );
6733 selection.collapse_to(head, goal);
6734
6735 let transpose_start = display_map
6736 .buffer_snapshot
6737 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
6738 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
6739 let transpose_end = display_map
6740 .buffer_snapshot
6741 .clip_offset(transpose_offset + 1, Bias::Right);
6742 if let Some(ch) =
6743 display_map.buffer_snapshot.chars_at(transpose_start).next()
6744 {
6745 edits.push((transpose_start..transpose_offset, String::new()));
6746 edits.push((transpose_end..transpose_end, ch.to_string()));
6747 }
6748 }
6749 });
6750 edits
6751 });
6752 this.buffer
6753 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
6754 let selections = this.selections.all::<usize>(cx);
6755 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
6756 s.select(selections);
6757 });
6758 });
6759 }
6760
6761 pub fn rewrap(&mut self, _: &Rewrap, cx: &mut ViewContext<Self>) {
6762 self.rewrap_impl(true, cx)
6763 }
6764
6765 pub fn rewrap_impl(&mut self, only_text: bool, cx: &mut ViewContext<Self>) {
6766 let buffer = self.buffer.read(cx).snapshot(cx);
6767 let selections = self.selections.all::<Point>(cx);
6768 let mut selections = selections.iter().peekable();
6769
6770 let mut edits = Vec::new();
6771 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
6772
6773 while let Some(selection) = selections.next() {
6774 let mut start_row = selection.start.row;
6775 let mut end_row = selection.end.row;
6776
6777 // Skip selections that overlap with a range that has already been rewrapped.
6778 let selection_range = start_row..end_row;
6779 if rewrapped_row_ranges
6780 .iter()
6781 .any(|range| range.overlaps(&selection_range))
6782 {
6783 continue;
6784 }
6785
6786 let mut should_rewrap = !only_text;
6787
6788 if let Some(language_scope) = buffer.language_scope_at(selection.head()) {
6789 match language_scope.language_name().0.as_ref() {
6790 "Markdown" | "Plain Text" => {
6791 should_rewrap = true;
6792 }
6793 _ => {}
6794 }
6795 }
6796
6797 // Since not all lines in the selection may be at the same indent
6798 // level, choose the indent size that is the most common between all
6799 // of the lines.
6800 //
6801 // If there is a tie, we use the deepest indent.
6802 let (indent_size, indent_end) = {
6803 let mut indent_size_occurrences = HashMap::default();
6804 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
6805
6806 for row in start_row..=end_row {
6807 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
6808 rows_by_indent_size.entry(indent).or_default().push(row);
6809 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
6810 }
6811
6812 let indent_size = indent_size_occurrences
6813 .into_iter()
6814 .max_by_key(|(indent, count)| (*count, indent.len))
6815 .map(|(indent, _)| indent)
6816 .unwrap_or_default();
6817 let row = rows_by_indent_size[&indent_size][0];
6818 let indent_end = Point::new(row, indent_size.len);
6819
6820 (indent_size, indent_end)
6821 };
6822
6823 let mut line_prefix = indent_size.chars().collect::<String>();
6824
6825 if let Some(comment_prefix) =
6826 buffer
6827 .language_scope_at(selection.head())
6828 .and_then(|language| {
6829 language
6830 .line_comment_prefixes()
6831 .iter()
6832 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
6833 .cloned()
6834 })
6835 {
6836 line_prefix.push_str(&comment_prefix);
6837 should_rewrap = true;
6838 }
6839
6840 if selection.is_empty() {
6841 'expand_upwards: while start_row > 0 {
6842 let prev_row = start_row - 1;
6843 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
6844 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
6845 {
6846 start_row = prev_row;
6847 } else {
6848 break 'expand_upwards;
6849 }
6850 }
6851
6852 'expand_downwards: while end_row < buffer.max_point().row {
6853 let next_row = end_row + 1;
6854 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
6855 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
6856 {
6857 end_row = next_row;
6858 } else {
6859 break 'expand_downwards;
6860 }
6861 }
6862 }
6863
6864 if !should_rewrap {
6865 continue;
6866 }
6867
6868 let start = Point::new(start_row, 0);
6869 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
6870 let selection_text = buffer.text_for_range(start..end).collect::<String>();
6871 let Some(lines_without_prefixes) = selection_text
6872 .lines()
6873 .map(|line| {
6874 line.strip_prefix(&line_prefix)
6875 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
6876 .ok_or_else(|| {
6877 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
6878 })
6879 })
6880 .collect::<Result<Vec<_>, _>>()
6881 .log_err()
6882 else {
6883 continue;
6884 };
6885
6886 let unwrapped_text = lines_without_prefixes.join(" ");
6887 let wrap_column = buffer
6888 .settings_at(Point::new(start_row, 0), cx)
6889 .preferred_line_length as usize;
6890 let mut wrapped_text = String::new();
6891 let mut current_line = line_prefix.clone();
6892 for word in unwrapped_text.split_whitespace() {
6893 if current_line.len() + word.len() >= wrap_column {
6894 wrapped_text.push_str(¤t_line);
6895 wrapped_text.push('\n');
6896 current_line.truncate(line_prefix.len());
6897 }
6898
6899 if current_line.len() > line_prefix.len() {
6900 current_line.push(' ');
6901 }
6902
6903 current_line.push_str(word);
6904 }
6905
6906 if !current_line.is_empty() {
6907 wrapped_text.push_str(¤t_line);
6908 }
6909
6910 let diff = TextDiff::from_lines(&selection_text, &wrapped_text);
6911 let mut offset = start.to_offset(&buffer);
6912 let mut moved_since_edit = true;
6913
6914 for change in diff.iter_all_changes() {
6915 let value = change.value();
6916 match change.tag() {
6917 ChangeTag::Equal => {
6918 offset += value.len();
6919 moved_since_edit = true;
6920 }
6921 ChangeTag::Delete => {
6922 let start = buffer.anchor_after(offset);
6923 let end = buffer.anchor_before(offset + value.len());
6924
6925 if moved_since_edit {
6926 edits.push((start..end, String::new()));
6927 } else {
6928 edits.last_mut().unwrap().0.end = end;
6929 }
6930
6931 offset += value.len();
6932 moved_since_edit = false;
6933 }
6934 ChangeTag::Insert => {
6935 if moved_since_edit {
6936 let anchor = buffer.anchor_after(offset);
6937 edits.push((anchor..anchor, value.to_string()));
6938 } else {
6939 edits.last_mut().unwrap().1.push_str(value);
6940 }
6941
6942 moved_since_edit = false;
6943 }
6944 }
6945 }
6946
6947 rewrapped_row_ranges.push(start_row..=end_row);
6948 }
6949
6950 self.buffer
6951 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
6952 }
6953
6954 pub fn cut(&mut self, _: &Cut, cx: &mut ViewContext<Self>) {
6955 let mut text = String::new();
6956 let buffer = self.buffer.read(cx).snapshot(cx);
6957 let mut selections = self.selections.all::<Point>(cx);
6958 let mut clipboard_selections = Vec::with_capacity(selections.len());
6959 {
6960 let max_point = buffer.max_point();
6961 let mut is_first = true;
6962 for selection in &mut selections {
6963 let is_entire_line = selection.is_empty() || self.selections.line_mode;
6964 if is_entire_line {
6965 selection.start = Point::new(selection.start.row, 0);
6966 if !selection.is_empty() && selection.end.column == 0 {
6967 selection.end = cmp::min(max_point, selection.end);
6968 } else {
6969 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
6970 }
6971 selection.goal = SelectionGoal::None;
6972 }
6973 if is_first {
6974 is_first = false;
6975 } else {
6976 text += "\n";
6977 }
6978 let mut len = 0;
6979 for chunk in buffer.text_for_range(selection.start..selection.end) {
6980 text.push_str(chunk);
6981 len += chunk.len();
6982 }
6983 clipboard_selections.push(ClipboardSelection {
6984 len,
6985 is_entire_line,
6986 first_line_indent: buffer
6987 .indent_size_for_line(MultiBufferRow(selection.start.row))
6988 .len,
6989 });
6990 }
6991 }
6992
6993 self.transact(cx, |this, cx| {
6994 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
6995 s.select(selections);
6996 });
6997 this.insert("", cx);
6998 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
6999 text,
7000 clipboard_selections,
7001 ));
7002 });
7003 }
7004
7005 pub fn copy(&mut self, _: &Copy, cx: &mut ViewContext<Self>) {
7006 let selections = self.selections.all::<Point>(cx);
7007 let buffer = self.buffer.read(cx).read(cx);
7008 let mut text = String::new();
7009
7010 let mut clipboard_selections = Vec::with_capacity(selections.len());
7011 {
7012 let max_point = buffer.max_point();
7013 let mut is_first = true;
7014 for selection in selections.iter() {
7015 let mut start = selection.start;
7016 let mut end = selection.end;
7017 let is_entire_line = selection.is_empty() || self.selections.line_mode;
7018 if is_entire_line {
7019 start = Point::new(start.row, 0);
7020 end = cmp::min(max_point, Point::new(end.row + 1, 0));
7021 }
7022 if is_first {
7023 is_first = false;
7024 } else {
7025 text += "\n";
7026 }
7027 let mut len = 0;
7028 for chunk in buffer.text_for_range(start..end) {
7029 text.push_str(chunk);
7030 len += chunk.len();
7031 }
7032 clipboard_selections.push(ClipboardSelection {
7033 len,
7034 is_entire_line,
7035 first_line_indent: buffer.indent_size_for_line(MultiBufferRow(start.row)).len,
7036 });
7037 }
7038 }
7039
7040 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
7041 text,
7042 clipboard_selections,
7043 ));
7044 }
7045
7046 pub fn do_paste(
7047 &mut self,
7048 text: &String,
7049 clipboard_selections: Option<Vec<ClipboardSelection>>,
7050 handle_entire_lines: bool,
7051 cx: &mut ViewContext<Self>,
7052 ) {
7053 if self.read_only(cx) {
7054 return;
7055 }
7056
7057 let clipboard_text = Cow::Borrowed(text);
7058
7059 self.transact(cx, |this, cx| {
7060 if let Some(mut clipboard_selections) = clipboard_selections {
7061 let old_selections = this.selections.all::<usize>(cx);
7062 let all_selections_were_entire_line =
7063 clipboard_selections.iter().all(|s| s.is_entire_line);
7064 let first_selection_indent_column =
7065 clipboard_selections.first().map(|s| s.first_line_indent);
7066 if clipboard_selections.len() != old_selections.len() {
7067 clipboard_selections.drain(..);
7068 }
7069
7070 this.buffer.update(cx, |buffer, cx| {
7071 let snapshot = buffer.read(cx);
7072 let mut start_offset = 0;
7073 let mut edits = Vec::new();
7074 let mut original_indent_columns = Vec::new();
7075 for (ix, selection) in old_selections.iter().enumerate() {
7076 let to_insert;
7077 let entire_line;
7078 let original_indent_column;
7079 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
7080 let end_offset = start_offset + clipboard_selection.len;
7081 to_insert = &clipboard_text[start_offset..end_offset];
7082 entire_line = clipboard_selection.is_entire_line;
7083 start_offset = end_offset + 1;
7084 original_indent_column = Some(clipboard_selection.first_line_indent);
7085 } else {
7086 to_insert = clipboard_text.as_str();
7087 entire_line = all_selections_were_entire_line;
7088 original_indent_column = first_selection_indent_column
7089 }
7090
7091 // If the corresponding selection was empty when this slice of the
7092 // clipboard text was written, then the entire line containing the
7093 // selection was copied. If this selection is also currently empty,
7094 // then paste the line before the current line of the buffer.
7095 let range = if selection.is_empty() && handle_entire_lines && entire_line {
7096 let column = selection.start.to_point(&snapshot).column as usize;
7097 let line_start = selection.start - column;
7098 line_start..line_start
7099 } else {
7100 selection.range()
7101 };
7102
7103 edits.push((range, to_insert));
7104 original_indent_columns.extend(original_indent_column);
7105 }
7106 drop(snapshot);
7107
7108 buffer.edit(
7109 edits,
7110 Some(AutoindentMode::Block {
7111 original_indent_columns,
7112 }),
7113 cx,
7114 );
7115 });
7116
7117 let selections = this.selections.all::<usize>(cx);
7118 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
7119 } else {
7120 this.insert(&clipboard_text, cx);
7121 }
7122 });
7123 }
7124
7125 pub fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
7126 if let Some(item) = cx.read_from_clipboard() {
7127 let entries = item.entries();
7128
7129 match entries.first() {
7130 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
7131 // of all the pasted entries.
7132 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
7133 .do_paste(
7134 clipboard_string.text(),
7135 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
7136 true,
7137 cx,
7138 ),
7139 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, cx),
7140 }
7141 }
7142 }
7143
7144 pub fn undo(&mut self, _: &Undo, cx: &mut ViewContext<Self>) {
7145 if self.read_only(cx) {
7146 return;
7147 }
7148
7149 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
7150 if let Some((selections, _)) =
7151 self.selection_history.transaction(transaction_id).cloned()
7152 {
7153 self.change_selections(None, cx, |s| {
7154 s.select_anchors(selections.to_vec());
7155 });
7156 }
7157 self.request_autoscroll(Autoscroll::fit(), cx);
7158 self.unmark_text(cx);
7159 self.refresh_inline_completion(true, false, cx);
7160 cx.emit(EditorEvent::Edited { transaction_id });
7161 cx.emit(EditorEvent::TransactionUndone { transaction_id });
7162 }
7163 }
7164
7165 pub fn redo(&mut self, _: &Redo, cx: &mut ViewContext<Self>) {
7166 if self.read_only(cx) {
7167 return;
7168 }
7169
7170 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
7171 if let Some((_, Some(selections))) =
7172 self.selection_history.transaction(transaction_id).cloned()
7173 {
7174 self.change_selections(None, cx, |s| {
7175 s.select_anchors(selections.to_vec());
7176 });
7177 }
7178 self.request_autoscroll(Autoscroll::fit(), cx);
7179 self.unmark_text(cx);
7180 self.refresh_inline_completion(true, false, cx);
7181 cx.emit(EditorEvent::Edited { transaction_id });
7182 }
7183 }
7184
7185 pub fn finalize_last_transaction(&mut self, cx: &mut ViewContext<Self>) {
7186 self.buffer
7187 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
7188 }
7189
7190 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut ViewContext<Self>) {
7191 self.buffer
7192 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
7193 }
7194
7195 pub fn move_left(&mut self, _: &MoveLeft, cx: &mut ViewContext<Self>) {
7196 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7197 let line_mode = s.line_mode;
7198 s.move_with(|map, selection| {
7199 let cursor = if selection.is_empty() && !line_mode {
7200 movement::left(map, selection.start)
7201 } else {
7202 selection.start
7203 };
7204 selection.collapse_to(cursor, SelectionGoal::None);
7205 });
7206 })
7207 }
7208
7209 pub fn select_left(&mut self, _: &SelectLeft, cx: &mut ViewContext<Self>) {
7210 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7211 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
7212 })
7213 }
7214
7215 pub fn move_right(&mut self, _: &MoveRight, cx: &mut ViewContext<Self>) {
7216 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7217 let line_mode = s.line_mode;
7218 s.move_with(|map, selection| {
7219 let cursor = if selection.is_empty() && !line_mode {
7220 movement::right(map, selection.end)
7221 } else {
7222 selection.end
7223 };
7224 selection.collapse_to(cursor, SelectionGoal::None)
7225 });
7226 })
7227 }
7228
7229 pub fn select_right(&mut self, _: &SelectRight, cx: &mut ViewContext<Self>) {
7230 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7231 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
7232 })
7233 }
7234
7235 pub fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) {
7236 if self.take_rename(true, cx).is_some() {
7237 return;
7238 }
7239
7240 if matches!(self.mode, EditorMode::SingleLine { .. }) {
7241 cx.propagate();
7242 return;
7243 }
7244
7245 let text_layout_details = &self.text_layout_details(cx);
7246 let selection_count = self.selections.count();
7247 let first_selection = self.selections.first_anchor();
7248
7249 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7250 let line_mode = s.line_mode;
7251 s.move_with(|map, selection| {
7252 if !selection.is_empty() && !line_mode {
7253 selection.goal = SelectionGoal::None;
7254 }
7255 let (cursor, goal) = movement::up(
7256 map,
7257 selection.start,
7258 selection.goal,
7259 false,
7260 text_layout_details,
7261 );
7262 selection.collapse_to(cursor, goal);
7263 });
7264 });
7265
7266 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
7267 {
7268 cx.propagate();
7269 }
7270 }
7271
7272 pub fn move_up_by_lines(&mut self, action: &MoveUpByLines, cx: &mut ViewContext<Self>) {
7273 if self.take_rename(true, cx).is_some() {
7274 return;
7275 }
7276
7277 if matches!(self.mode, EditorMode::SingleLine { .. }) {
7278 cx.propagate();
7279 return;
7280 }
7281
7282 let text_layout_details = &self.text_layout_details(cx);
7283
7284 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7285 let line_mode = s.line_mode;
7286 s.move_with(|map, selection| {
7287 if !selection.is_empty() && !line_mode {
7288 selection.goal = SelectionGoal::None;
7289 }
7290 let (cursor, goal) = movement::up_by_rows(
7291 map,
7292 selection.start,
7293 action.lines,
7294 selection.goal,
7295 false,
7296 text_layout_details,
7297 );
7298 selection.collapse_to(cursor, goal);
7299 });
7300 })
7301 }
7302
7303 pub fn move_down_by_lines(&mut self, action: &MoveDownByLines, cx: &mut ViewContext<Self>) {
7304 if self.take_rename(true, cx).is_some() {
7305 return;
7306 }
7307
7308 if matches!(self.mode, EditorMode::SingleLine { .. }) {
7309 cx.propagate();
7310 return;
7311 }
7312
7313 let text_layout_details = &self.text_layout_details(cx);
7314
7315 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7316 let line_mode = s.line_mode;
7317 s.move_with(|map, selection| {
7318 if !selection.is_empty() && !line_mode {
7319 selection.goal = SelectionGoal::None;
7320 }
7321 let (cursor, goal) = movement::down_by_rows(
7322 map,
7323 selection.start,
7324 action.lines,
7325 selection.goal,
7326 false,
7327 text_layout_details,
7328 );
7329 selection.collapse_to(cursor, goal);
7330 });
7331 })
7332 }
7333
7334 pub fn select_down_by_lines(&mut self, action: &SelectDownByLines, cx: &mut ViewContext<Self>) {
7335 let text_layout_details = &self.text_layout_details(cx);
7336 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7337 s.move_heads_with(|map, head, goal| {
7338 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
7339 })
7340 })
7341 }
7342
7343 pub fn select_up_by_lines(&mut self, action: &SelectUpByLines, cx: &mut ViewContext<Self>) {
7344 let text_layout_details = &self.text_layout_details(cx);
7345 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7346 s.move_heads_with(|map, head, goal| {
7347 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
7348 })
7349 })
7350 }
7351
7352 pub fn select_page_up(&mut self, _: &SelectPageUp, cx: &mut ViewContext<Self>) {
7353 let Some(row_count) = self.visible_row_count() else {
7354 return;
7355 };
7356
7357 let text_layout_details = &self.text_layout_details(cx);
7358
7359 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7360 s.move_heads_with(|map, head, goal| {
7361 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
7362 })
7363 })
7364 }
7365
7366 pub fn move_page_up(&mut self, action: &MovePageUp, cx: &mut ViewContext<Self>) {
7367 if self.take_rename(true, cx).is_some() {
7368 return;
7369 }
7370
7371 if self
7372 .context_menu
7373 .write()
7374 .as_mut()
7375 .map(|menu| menu.select_first(self.project.as_ref(), cx))
7376 .unwrap_or(false)
7377 {
7378 return;
7379 }
7380
7381 if matches!(self.mode, EditorMode::SingleLine { .. }) {
7382 cx.propagate();
7383 return;
7384 }
7385
7386 let Some(row_count) = self.visible_row_count() else {
7387 return;
7388 };
7389
7390 let autoscroll = if action.center_cursor {
7391 Autoscroll::center()
7392 } else {
7393 Autoscroll::fit()
7394 };
7395
7396 let text_layout_details = &self.text_layout_details(cx);
7397
7398 self.change_selections(Some(autoscroll), cx, |s| {
7399 let line_mode = s.line_mode;
7400 s.move_with(|map, selection| {
7401 if !selection.is_empty() && !line_mode {
7402 selection.goal = SelectionGoal::None;
7403 }
7404 let (cursor, goal) = movement::up_by_rows(
7405 map,
7406 selection.end,
7407 row_count,
7408 selection.goal,
7409 false,
7410 text_layout_details,
7411 );
7412 selection.collapse_to(cursor, goal);
7413 });
7414 });
7415 }
7416
7417 pub fn select_up(&mut self, _: &SelectUp, cx: &mut ViewContext<Self>) {
7418 let text_layout_details = &self.text_layout_details(cx);
7419 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7420 s.move_heads_with(|map, head, goal| {
7421 movement::up(map, head, goal, false, text_layout_details)
7422 })
7423 })
7424 }
7425
7426 pub fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<Self>) {
7427 self.take_rename(true, cx);
7428
7429 if matches!(self.mode, EditorMode::SingleLine { .. }) {
7430 cx.propagate();
7431 return;
7432 }
7433
7434 let text_layout_details = &self.text_layout_details(cx);
7435 let selection_count = self.selections.count();
7436 let first_selection = self.selections.first_anchor();
7437
7438 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7439 let line_mode = s.line_mode;
7440 s.move_with(|map, selection| {
7441 if !selection.is_empty() && !line_mode {
7442 selection.goal = SelectionGoal::None;
7443 }
7444 let (cursor, goal) = movement::down(
7445 map,
7446 selection.end,
7447 selection.goal,
7448 false,
7449 text_layout_details,
7450 );
7451 selection.collapse_to(cursor, goal);
7452 });
7453 });
7454
7455 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
7456 {
7457 cx.propagate();
7458 }
7459 }
7460
7461 pub fn select_page_down(&mut self, _: &SelectPageDown, cx: &mut ViewContext<Self>) {
7462 let Some(row_count) = self.visible_row_count() else {
7463 return;
7464 };
7465
7466 let text_layout_details = &self.text_layout_details(cx);
7467
7468 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7469 s.move_heads_with(|map, head, goal| {
7470 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
7471 })
7472 })
7473 }
7474
7475 pub fn move_page_down(&mut self, action: &MovePageDown, cx: &mut ViewContext<Self>) {
7476 if self.take_rename(true, cx).is_some() {
7477 return;
7478 }
7479
7480 if self
7481 .context_menu
7482 .write()
7483 .as_mut()
7484 .map(|menu| menu.select_last(self.project.as_ref(), cx))
7485 .unwrap_or(false)
7486 {
7487 return;
7488 }
7489
7490 if matches!(self.mode, EditorMode::SingleLine { .. }) {
7491 cx.propagate();
7492 return;
7493 }
7494
7495 let Some(row_count) = self.visible_row_count() else {
7496 return;
7497 };
7498
7499 let autoscroll = if action.center_cursor {
7500 Autoscroll::center()
7501 } else {
7502 Autoscroll::fit()
7503 };
7504
7505 let text_layout_details = &self.text_layout_details(cx);
7506 self.change_selections(Some(autoscroll), cx, |s| {
7507 let line_mode = s.line_mode;
7508 s.move_with(|map, selection| {
7509 if !selection.is_empty() && !line_mode {
7510 selection.goal = SelectionGoal::None;
7511 }
7512 let (cursor, goal) = movement::down_by_rows(
7513 map,
7514 selection.end,
7515 row_count,
7516 selection.goal,
7517 false,
7518 text_layout_details,
7519 );
7520 selection.collapse_to(cursor, goal);
7521 });
7522 });
7523 }
7524
7525 pub fn select_down(&mut self, _: &SelectDown, cx: &mut ViewContext<Self>) {
7526 let text_layout_details = &self.text_layout_details(cx);
7527 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7528 s.move_heads_with(|map, head, goal| {
7529 movement::down(map, head, goal, false, text_layout_details)
7530 })
7531 });
7532 }
7533
7534 pub fn context_menu_first(&mut self, _: &ContextMenuFirst, cx: &mut ViewContext<Self>) {
7535 if let Some(context_menu) = self.context_menu.write().as_mut() {
7536 context_menu.select_first(self.project.as_ref(), cx);
7537 }
7538 }
7539
7540 pub fn context_menu_prev(&mut self, _: &ContextMenuPrev, cx: &mut ViewContext<Self>) {
7541 if let Some(context_menu) = self.context_menu.write().as_mut() {
7542 context_menu.select_prev(self.project.as_ref(), cx);
7543 }
7544 }
7545
7546 pub fn context_menu_next(&mut self, _: &ContextMenuNext, cx: &mut ViewContext<Self>) {
7547 if let Some(context_menu) = self.context_menu.write().as_mut() {
7548 context_menu.select_next(self.project.as_ref(), cx);
7549 }
7550 }
7551
7552 pub fn context_menu_last(&mut self, _: &ContextMenuLast, cx: &mut ViewContext<Self>) {
7553 if let Some(context_menu) = self.context_menu.write().as_mut() {
7554 context_menu.select_last(self.project.as_ref(), cx);
7555 }
7556 }
7557
7558 pub fn move_to_previous_word_start(
7559 &mut self,
7560 _: &MoveToPreviousWordStart,
7561 cx: &mut ViewContext<Self>,
7562 ) {
7563 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7564 s.move_cursors_with(|map, head, _| {
7565 (
7566 movement::previous_word_start(map, head),
7567 SelectionGoal::None,
7568 )
7569 });
7570 })
7571 }
7572
7573 pub fn move_to_previous_subword_start(
7574 &mut self,
7575 _: &MoveToPreviousSubwordStart,
7576 cx: &mut ViewContext<Self>,
7577 ) {
7578 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7579 s.move_cursors_with(|map, head, _| {
7580 (
7581 movement::previous_subword_start(map, head),
7582 SelectionGoal::None,
7583 )
7584 });
7585 })
7586 }
7587
7588 pub fn select_to_previous_word_start(
7589 &mut self,
7590 _: &SelectToPreviousWordStart,
7591 cx: &mut ViewContext<Self>,
7592 ) {
7593 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7594 s.move_heads_with(|map, head, _| {
7595 (
7596 movement::previous_word_start(map, head),
7597 SelectionGoal::None,
7598 )
7599 });
7600 })
7601 }
7602
7603 pub fn select_to_previous_subword_start(
7604 &mut self,
7605 _: &SelectToPreviousSubwordStart,
7606 cx: &mut ViewContext<Self>,
7607 ) {
7608 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7609 s.move_heads_with(|map, head, _| {
7610 (
7611 movement::previous_subword_start(map, head),
7612 SelectionGoal::None,
7613 )
7614 });
7615 })
7616 }
7617
7618 pub fn delete_to_previous_word_start(
7619 &mut self,
7620 action: &DeleteToPreviousWordStart,
7621 cx: &mut ViewContext<Self>,
7622 ) {
7623 self.transact(cx, |this, cx| {
7624 this.select_autoclose_pair(cx);
7625 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
7626 let line_mode = s.line_mode;
7627 s.move_with(|map, selection| {
7628 if selection.is_empty() && !line_mode {
7629 let cursor = if action.ignore_newlines {
7630 movement::previous_word_start(map, selection.head())
7631 } else {
7632 movement::previous_word_start_or_newline(map, selection.head())
7633 };
7634 selection.set_head(cursor, SelectionGoal::None);
7635 }
7636 });
7637 });
7638 this.insert("", cx);
7639 });
7640 }
7641
7642 pub fn delete_to_previous_subword_start(
7643 &mut self,
7644 _: &DeleteToPreviousSubwordStart,
7645 cx: &mut ViewContext<Self>,
7646 ) {
7647 self.transact(cx, |this, cx| {
7648 this.select_autoclose_pair(cx);
7649 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
7650 let line_mode = s.line_mode;
7651 s.move_with(|map, selection| {
7652 if selection.is_empty() && !line_mode {
7653 let cursor = movement::previous_subword_start(map, selection.head());
7654 selection.set_head(cursor, SelectionGoal::None);
7655 }
7656 });
7657 });
7658 this.insert("", cx);
7659 });
7660 }
7661
7662 pub fn move_to_next_word_end(&mut self, _: &MoveToNextWordEnd, cx: &mut ViewContext<Self>) {
7663 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7664 s.move_cursors_with(|map, head, _| {
7665 (movement::next_word_end(map, head), SelectionGoal::None)
7666 });
7667 })
7668 }
7669
7670 pub fn move_to_next_subword_end(
7671 &mut self,
7672 _: &MoveToNextSubwordEnd,
7673 cx: &mut ViewContext<Self>,
7674 ) {
7675 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7676 s.move_cursors_with(|map, head, _| {
7677 (movement::next_subword_end(map, head), SelectionGoal::None)
7678 });
7679 })
7680 }
7681
7682 pub fn select_to_next_word_end(&mut self, _: &SelectToNextWordEnd, cx: &mut ViewContext<Self>) {
7683 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7684 s.move_heads_with(|map, head, _| {
7685 (movement::next_word_end(map, head), SelectionGoal::None)
7686 });
7687 })
7688 }
7689
7690 pub fn select_to_next_subword_end(
7691 &mut self,
7692 _: &SelectToNextSubwordEnd,
7693 cx: &mut ViewContext<Self>,
7694 ) {
7695 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7696 s.move_heads_with(|map, head, _| {
7697 (movement::next_subword_end(map, head), SelectionGoal::None)
7698 });
7699 })
7700 }
7701
7702 pub fn delete_to_next_word_end(
7703 &mut self,
7704 action: &DeleteToNextWordEnd,
7705 cx: &mut ViewContext<Self>,
7706 ) {
7707 self.transact(cx, |this, cx| {
7708 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
7709 let line_mode = s.line_mode;
7710 s.move_with(|map, selection| {
7711 if selection.is_empty() && !line_mode {
7712 let cursor = if action.ignore_newlines {
7713 movement::next_word_end(map, selection.head())
7714 } else {
7715 movement::next_word_end_or_newline(map, selection.head())
7716 };
7717 selection.set_head(cursor, SelectionGoal::None);
7718 }
7719 });
7720 });
7721 this.insert("", cx);
7722 });
7723 }
7724
7725 pub fn delete_to_next_subword_end(
7726 &mut self,
7727 _: &DeleteToNextSubwordEnd,
7728 cx: &mut ViewContext<Self>,
7729 ) {
7730 self.transact(cx, |this, cx| {
7731 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
7732 s.move_with(|map, selection| {
7733 if selection.is_empty() {
7734 let cursor = movement::next_subword_end(map, selection.head());
7735 selection.set_head(cursor, SelectionGoal::None);
7736 }
7737 });
7738 });
7739 this.insert("", cx);
7740 });
7741 }
7742
7743 pub fn move_to_beginning_of_line(
7744 &mut self,
7745 action: &MoveToBeginningOfLine,
7746 cx: &mut ViewContext<Self>,
7747 ) {
7748 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7749 s.move_cursors_with(|map, head, _| {
7750 (
7751 movement::indented_line_beginning(map, head, action.stop_at_soft_wraps),
7752 SelectionGoal::None,
7753 )
7754 });
7755 })
7756 }
7757
7758 pub fn select_to_beginning_of_line(
7759 &mut self,
7760 action: &SelectToBeginningOfLine,
7761 cx: &mut ViewContext<Self>,
7762 ) {
7763 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7764 s.move_heads_with(|map, head, _| {
7765 (
7766 movement::indented_line_beginning(map, head, action.stop_at_soft_wraps),
7767 SelectionGoal::None,
7768 )
7769 });
7770 });
7771 }
7772
7773 pub fn delete_to_beginning_of_line(
7774 &mut self,
7775 _: &DeleteToBeginningOfLine,
7776 cx: &mut ViewContext<Self>,
7777 ) {
7778 self.transact(cx, |this, cx| {
7779 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
7780 s.move_with(|_, selection| {
7781 selection.reversed = true;
7782 });
7783 });
7784
7785 this.select_to_beginning_of_line(
7786 &SelectToBeginningOfLine {
7787 stop_at_soft_wraps: false,
7788 },
7789 cx,
7790 );
7791 this.backspace(&Backspace, cx);
7792 });
7793 }
7794
7795 pub fn move_to_end_of_line(&mut self, action: &MoveToEndOfLine, cx: &mut ViewContext<Self>) {
7796 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7797 s.move_cursors_with(|map, head, _| {
7798 (
7799 movement::line_end(map, head, action.stop_at_soft_wraps),
7800 SelectionGoal::None,
7801 )
7802 });
7803 })
7804 }
7805
7806 pub fn select_to_end_of_line(
7807 &mut self,
7808 action: &SelectToEndOfLine,
7809 cx: &mut ViewContext<Self>,
7810 ) {
7811 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7812 s.move_heads_with(|map, head, _| {
7813 (
7814 movement::line_end(map, head, action.stop_at_soft_wraps),
7815 SelectionGoal::None,
7816 )
7817 });
7818 })
7819 }
7820
7821 pub fn delete_to_end_of_line(&mut self, _: &DeleteToEndOfLine, cx: &mut ViewContext<Self>) {
7822 self.transact(cx, |this, cx| {
7823 this.select_to_end_of_line(
7824 &SelectToEndOfLine {
7825 stop_at_soft_wraps: false,
7826 },
7827 cx,
7828 );
7829 this.delete(&Delete, cx);
7830 });
7831 }
7832
7833 pub fn cut_to_end_of_line(&mut self, _: &CutToEndOfLine, cx: &mut ViewContext<Self>) {
7834 self.transact(cx, |this, cx| {
7835 this.select_to_end_of_line(
7836 &SelectToEndOfLine {
7837 stop_at_soft_wraps: false,
7838 },
7839 cx,
7840 );
7841 this.cut(&Cut, cx);
7842 });
7843 }
7844
7845 pub fn move_to_start_of_paragraph(
7846 &mut self,
7847 _: &MoveToStartOfParagraph,
7848 cx: &mut ViewContext<Self>,
7849 ) {
7850 if matches!(self.mode, EditorMode::SingleLine { .. }) {
7851 cx.propagate();
7852 return;
7853 }
7854
7855 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7856 s.move_with(|map, selection| {
7857 selection.collapse_to(
7858 movement::start_of_paragraph(map, selection.head(), 1),
7859 SelectionGoal::None,
7860 )
7861 });
7862 })
7863 }
7864
7865 pub fn move_to_end_of_paragraph(
7866 &mut self,
7867 _: &MoveToEndOfParagraph,
7868 cx: &mut ViewContext<Self>,
7869 ) {
7870 if matches!(self.mode, EditorMode::SingleLine { .. }) {
7871 cx.propagate();
7872 return;
7873 }
7874
7875 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7876 s.move_with(|map, selection| {
7877 selection.collapse_to(
7878 movement::end_of_paragraph(map, selection.head(), 1),
7879 SelectionGoal::None,
7880 )
7881 });
7882 })
7883 }
7884
7885 pub fn select_to_start_of_paragraph(
7886 &mut self,
7887 _: &SelectToStartOfParagraph,
7888 cx: &mut ViewContext<Self>,
7889 ) {
7890 if matches!(self.mode, EditorMode::SingleLine { .. }) {
7891 cx.propagate();
7892 return;
7893 }
7894
7895 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7896 s.move_heads_with(|map, head, _| {
7897 (
7898 movement::start_of_paragraph(map, head, 1),
7899 SelectionGoal::None,
7900 )
7901 });
7902 })
7903 }
7904
7905 pub fn select_to_end_of_paragraph(
7906 &mut self,
7907 _: &SelectToEndOfParagraph,
7908 cx: &mut ViewContext<Self>,
7909 ) {
7910 if matches!(self.mode, EditorMode::SingleLine { .. }) {
7911 cx.propagate();
7912 return;
7913 }
7914
7915 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7916 s.move_heads_with(|map, head, _| {
7917 (
7918 movement::end_of_paragraph(map, head, 1),
7919 SelectionGoal::None,
7920 )
7921 });
7922 })
7923 }
7924
7925 pub fn move_to_beginning(&mut self, _: &MoveToBeginning, cx: &mut ViewContext<Self>) {
7926 if matches!(self.mode, EditorMode::SingleLine { .. }) {
7927 cx.propagate();
7928 return;
7929 }
7930
7931 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7932 s.select_ranges(vec![0..0]);
7933 });
7934 }
7935
7936 pub fn select_to_beginning(&mut self, _: &SelectToBeginning, cx: &mut ViewContext<Self>) {
7937 let mut selection = self.selections.last::<Point>(cx);
7938 selection.set_head(Point::zero(), SelectionGoal::None);
7939
7940 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7941 s.select(vec![selection]);
7942 });
7943 }
7944
7945 pub fn move_to_end(&mut self, _: &MoveToEnd, cx: &mut ViewContext<Self>) {
7946 if matches!(self.mode, EditorMode::SingleLine { .. }) {
7947 cx.propagate();
7948 return;
7949 }
7950
7951 let cursor = self.buffer.read(cx).read(cx).len();
7952 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7953 s.select_ranges(vec![cursor..cursor])
7954 });
7955 }
7956
7957 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
7958 self.nav_history = nav_history;
7959 }
7960
7961 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
7962 self.nav_history.as_ref()
7963 }
7964
7965 fn push_to_nav_history(
7966 &mut self,
7967 cursor_anchor: Anchor,
7968 new_position: Option<Point>,
7969 cx: &mut ViewContext<Self>,
7970 ) {
7971 if let Some(nav_history) = self.nav_history.as_mut() {
7972 let buffer = self.buffer.read(cx).read(cx);
7973 let cursor_position = cursor_anchor.to_point(&buffer);
7974 let scroll_state = self.scroll_manager.anchor();
7975 let scroll_top_row = scroll_state.top_row(&buffer);
7976 drop(buffer);
7977
7978 if let Some(new_position) = new_position {
7979 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
7980 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
7981 return;
7982 }
7983 }
7984
7985 nav_history.push(
7986 Some(NavigationData {
7987 cursor_anchor,
7988 cursor_position,
7989 scroll_anchor: scroll_state,
7990 scroll_top_row,
7991 }),
7992 cx,
7993 );
7994 }
7995 }
7996
7997 pub fn select_to_end(&mut self, _: &SelectToEnd, cx: &mut ViewContext<Self>) {
7998 let buffer = self.buffer.read(cx).snapshot(cx);
7999 let mut selection = self.selections.first::<usize>(cx);
8000 selection.set_head(buffer.len(), SelectionGoal::None);
8001 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
8002 s.select(vec![selection]);
8003 });
8004 }
8005
8006 pub fn select_all(&mut self, _: &SelectAll, cx: &mut ViewContext<Self>) {
8007 let end = self.buffer.read(cx).read(cx).len();
8008 self.change_selections(None, cx, |s| {
8009 s.select_ranges(vec![0..end]);
8010 });
8011 }
8012
8013 pub fn select_line(&mut self, _: &SelectLine, cx: &mut ViewContext<Self>) {
8014 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8015 let mut selections = self.selections.all::<Point>(cx);
8016 let max_point = display_map.buffer_snapshot.max_point();
8017 for selection in &mut selections {
8018 let rows = selection.spanned_rows(true, &display_map);
8019 selection.start = Point::new(rows.start.0, 0);
8020 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
8021 selection.reversed = false;
8022 }
8023 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
8024 s.select(selections);
8025 });
8026 }
8027
8028 pub fn split_selection_into_lines(
8029 &mut self,
8030 _: &SplitSelectionIntoLines,
8031 cx: &mut ViewContext<Self>,
8032 ) {
8033 let mut to_unfold = Vec::new();
8034 let mut new_selection_ranges = Vec::new();
8035 {
8036 let selections = self.selections.all::<Point>(cx);
8037 let buffer = self.buffer.read(cx).read(cx);
8038 for selection in selections {
8039 for row in selection.start.row..selection.end.row {
8040 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
8041 new_selection_ranges.push(cursor..cursor);
8042 }
8043 new_selection_ranges.push(selection.end..selection.end);
8044 to_unfold.push(selection.start..selection.end);
8045 }
8046 }
8047 self.unfold_ranges(to_unfold, true, true, cx);
8048 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
8049 s.select_ranges(new_selection_ranges);
8050 });
8051 }
8052
8053 pub fn add_selection_above(&mut self, _: &AddSelectionAbove, cx: &mut ViewContext<Self>) {
8054 self.add_selection(true, cx);
8055 }
8056
8057 pub fn add_selection_below(&mut self, _: &AddSelectionBelow, cx: &mut ViewContext<Self>) {
8058 self.add_selection(false, cx);
8059 }
8060
8061 fn add_selection(&mut self, above: bool, cx: &mut ViewContext<Self>) {
8062 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8063 let mut selections = self.selections.all::<Point>(cx);
8064 let text_layout_details = self.text_layout_details(cx);
8065 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
8066 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
8067 let range = oldest_selection.display_range(&display_map).sorted();
8068
8069 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
8070 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
8071 let positions = start_x.min(end_x)..start_x.max(end_x);
8072
8073 selections.clear();
8074 let mut stack = Vec::new();
8075 for row in range.start.row().0..=range.end.row().0 {
8076 if let Some(selection) = self.selections.build_columnar_selection(
8077 &display_map,
8078 DisplayRow(row),
8079 &positions,
8080 oldest_selection.reversed,
8081 &text_layout_details,
8082 ) {
8083 stack.push(selection.id);
8084 selections.push(selection);
8085 }
8086 }
8087
8088 if above {
8089 stack.reverse();
8090 }
8091
8092 AddSelectionsState { above, stack }
8093 });
8094
8095 let last_added_selection = *state.stack.last().unwrap();
8096 let mut new_selections = Vec::new();
8097 if above == state.above {
8098 let end_row = if above {
8099 DisplayRow(0)
8100 } else {
8101 display_map.max_point().row()
8102 };
8103
8104 'outer: for selection in selections {
8105 if selection.id == last_added_selection {
8106 let range = selection.display_range(&display_map).sorted();
8107 debug_assert_eq!(range.start.row(), range.end.row());
8108 let mut row = range.start.row();
8109 let positions =
8110 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
8111 px(start)..px(end)
8112 } else {
8113 let start_x =
8114 display_map.x_for_display_point(range.start, &text_layout_details);
8115 let end_x =
8116 display_map.x_for_display_point(range.end, &text_layout_details);
8117 start_x.min(end_x)..start_x.max(end_x)
8118 };
8119
8120 while row != end_row {
8121 if above {
8122 row.0 -= 1;
8123 } else {
8124 row.0 += 1;
8125 }
8126
8127 if let Some(new_selection) = self.selections.build_columnar_selection(
8128 &display_map,
8129 row,
8130 &positions,
8131 selection.reversed,
8132 &text_layout_details,
8133 ) {
8134 state.stack.push(new_selection.id);
8135 if above {
8136 new_selections.push(new_selection);
8137 new_selections.push(selection);
8138 } else {
8139 new_selections.push(selection);
8140 new_selections.push(new_selection);
8141 }
8142
8143 continue 'outer;
8144 }
8145 }
8146 }
8147
8148 new_selections.push(selection);
8149 }
8150 } else {
8151 new_selections = selections;
8152 new_selections.retain(|s| s.id != last_added_selection);
8153 state.stack.pop();
8154 }
8155
8156 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
8157 s.select(new_selections);
8158 });
8159 if state.stack.len() > 1 {
8160 self.add_selections_state = Some(state);
8161 }
8162 }
8163
8164 pub fn select_next_match_internal(
8165 &mut self,
8166 display_map: &DisplaySnapshot,
8167 replace_newest: bool,
8168 autoscroll: Option<Autoscroll>,
8169 cx: &mut ViewContext<Self>,
8170 ) -> Result<()> {
8171 fn select_next_match_ranges(
8172 this: &mut Editor,
8173 range: Range<usize>,
8174 replace_newest: bool,
8175 auto_scroll: Option<Autoscroll>,
8176 cx: &mut ViewContext<Editor>,
8177 ) {
8178 this.unfold_ranges([range.clone()], false, true, cx);
8179 this.change_selections(auto_scroll, cx, |s| {
8180 if replace_newest {
8181 s.delete(s.newest_anchor().id);
8182 }
8183 s.insert_range(range.clone());
8184 });
8185 }
8186
8187 let buffer = &display_map.buffer_snapshot;
8188 let mut selections = self.selections.all::<usize>(cx);
8189 if let Some(mut select_next_state) = self.select_next_state.take() {
8190 let query = &select_next_state.query;
8191 if !select_next_state.done {
8192 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
8193 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
8194 let mut next_selected_range = None;
8195
8196 let bytes_after_last_selection =
8197 buffer.bytes_in_range(last_selection.end..buffer.len());
8198 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
8199 let query_matches = query
8200 .stream_find_iter(bytes_after_last_selection)
8201 .map(|result| (last_selection.end, result))
8202 .chain(
8203 query
8204 .stream_find_iter(bytes_before_first_selection)
8205 .map(|result| (0, result)),
8206 );
8207
8208 for (start_offset, query_match) in query_matches {
8209 let query_match = query_match.unwrap(); // can only fail due to I/O
8210 let offset_range =
8211 start_offset + query_match.start()..start_offset + query_match.end();
8212 let display_range = offset_range.start.to_display_point(display_map)
8213 ..offset_range.end.to_display_point(display_map);
8214
8215 if !select_next_state.wordwise
8216 || (!movement::is_inside_word(display_map, display_range.start)
8217 && !movement::is_inside_word(display_map, display_range.end))
8218 {
8219 // TODO: This is n^2, because we might check all the selections
8220 if !selections
8221 .iter()
8222 .any(|selection| selection.range().overlaps(&offset_range))
8223 {
8224 next_selected_range = Some(offset_range);
8225 break;
8226 }
8227 }
8228 }
8229
8230 if let Some(next_selected_range) = next_selected_range {
8231 select_next_match_ranges(
8232 self,
8233 next_selected_range,
8234 replace_newest,
8235 autoscroll,
8236 cx,
8237 );
8238 } else {
8239 select_next_state.done = true;
8240 }
8241 }
8242
8243 self.select_next_state = Some(select_next_state);
8244 } else {
8245 let mut only_carets = true;
8246 let mut same_text_selected = true;
8247 let mut selected_text = None;
8248
8249 let mut selections_iter = selections.iter().peekable();
8250 while let Some(selection) = selections_iter.next() {
8251 if selection.start != selection.end {
8252 only_carets = false;
8253 }
8254
8255 if same_text_selected {
8256 if selected_text.is_none() {
8257 selected_text =
8258 Some(buffer.text_for_range(selection.range()).collect::<String>());
8259 }
8260
8261 if let Some(next_selection) = selections_iter.peek() {
8262 if next_selection.range().len() == selection.range().len() {
8263 let next_selected_text = buffer
8264 .text_for_range(next_selection.range())
8265 .collect::<String>();
8266 if Some(next_selected_text) != selected_text {
8267 same_text_selected = false;
8268 selected_text = None;
8269 }
8270 } else {
8271 same_text_selected = false;
8272 selected_text = None;
8273 }
8274 }
8275 }
8276 }
8277
8278 if only_carets {
8279 for selection in &mut selections {
8280 let word_range = movement::surrounding_word(
8281 display_map,
8282 selection.start.to_display_point(display_map),
8283 );
8284 selection.start = word_range.start.to_offset(display_map, Bias::Left);
8285 selection.end = word_range.end.to_offset(display_map, Bias::Left);
8286 selection.goal = SelectionGoal::None;
8287 selection.reversed = false;
8288 select_next_match_ranges(
8289 self,
8290 selection.start..selection.end,
8291 replace_newest,
8292 autoscroll,
8293 cx,
8294 );
8295 }
8296
8297 if selections.len() == 1 {
8298 let selection = selections
8299 .last()
8300 .expect("ensured that there's only one selection");
8301 let query = buffer
8302 .text_for_range(selection.start..selection.end)
8303 .collect::<String>();
8304 let is_empty = query.is_empty();
8305 let select_state = SelectNextState {
8306 query: AhoCorasick::new(&[query])?,
8307 wordwise: true,
8308 done: is_empty,
8309 };
8310 self.select_next_state = Some(select_state);
8311 } else {
8312 self.select_next_state = None;
8313 }
8314 } else if let Some(selected_text) = selected_text {
8315 self.select_next_state = Some(SelectNextState {
8316 query: AhoCorasick::new(&[selected_text])?,
8317 wordwise: false,
8318 done: false,
8319 });
8320 self.select_next_match_internal(display_map, replace_newest, autoscroll, cx)?;
8321 }
8322 }
8323 Ok(())
8324 }
8325
8326 pub fn select_all_matches(
8327 &mut self,
8328 _action: &SelectAllMatches,
8329 cx: &mut ViewContext<Self>,
8330 ) -> Result<()> {
8331 self.push_to_selection_history();
8332 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8333
8334 self.select_next_match_internal(&display_map, false, None, cx)?;
8335 let Some(select_next_state) = self.select_next_state.as_mut() else {
8336 return Ok(());
8337 };
8338 if select_next_state.done {
8339 return Ok(());
8340 }
8341
8342 let mut new_selections = self.selections.all::<usize>(cx);
8343
8344 let buffer = &display_map.buffer_snapshot;
8345 let query_matches = select_next_state
8346 .query
8347 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
8348
8349 for query_match in query_matches {
8350 let query_match = query_match.unwrap(); // can only fail due to I/O
8351 let offset_range = query_match.start()..query_match.end();
8352 let display_range = offset_range.start.to_display_point(&display_map)
8353 ..offset_range.end.to_display_point(&display_map);
8354
8355 if !select_next_state.wordwise
8356 || (!movement::is_inside_word(&display_map, display_range.start)
8357 && !movement::is_inside_word(&display_map, display_range.end))
8358 {
8359 self.selections.change_with(cx, |selections| {
8360 new_selections.push(Selection {
8361 id: selections.new_selection_id(),
8362 start: offset_range.start,
8363 end: offset_range.end,
8364 reversed: false,
8365 goal: SelectionGoal::None,
8366 });
8367 });
8368 }
8369 }
8370
8371 new_selections.sort_by_key(|selection| selection.start);
8372 let mut ix = 0;
8373 while ix + 1 < new_selections.len() {
8374 let current_selection = &new_selections[ix];
8375 let next_selection = &new_selections[ix + 1];
8376 if current_selection.range().overlaps(&next_selection.range()) {
8377 if current_selection.id < next_selection.id {
8378 new_selections.remove(ix + 1);
8379 } else {
8380 new_selections.remove(ix);
8381 }
8382 } else {
8383 ix += 1;
8384 }
8385 }
8386
8387 select_next_state.done = true;
8388 self.unfold_ranges(
8389 new_selections.iter().map(|selection| selection.range()),
8390 false,
8391 false,
8392 cx,
8393 );
8394 self.change_selections(Some(Autoscroll::fit()), cx, |selections| {
8395 selections.select(new_selections)
8396 });
8397
8398 Ok(())
8399 }
8400
8401 pub fn select_next(&mut self, action: &SelectNext, cx: &mut ViewContext<Self>) -> Result<()> {
8402 self.push_to_selection_history();
8403 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8404 self.select_next_match_internal(
8405 &display_map,
8406 action.replace_newest,
8407 Some(Autoscroll::newest()),
8408 cx,
8409 )?;
8410 Ok(())
8411 }
8412
8413 pub fn select_previous(
8414 &mut self,
8415 action: &SelectPrevious,
8416 cx: &mut ViewContext<Self>,
8417 ) -> Result<()> {
8418 self.push_to_selection_history();
8419 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8420 let buffer = &display_map.buffer_snapshot;
8421 let mut selections = self.selections.all::<usize>(cx);
8422 if let Some(mut select_prev_state) = self.select_prev_state.take() {
8423 let query = &select_prev_state.query;
8424 if !select_prev_state.done {
8425 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
8426 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
8427 let mut next_selected_range = None;
8428 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
8429 let bytes_before_last_selection =
8430 buffer.reversed_bytes_in_range(0..last_selection.start);
8431 let bytes_after_first_selection =
8432 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
8433 let query_matches = query
8434 .stream_find_iter(bytes_before_last_selection)
8435 .map(|result| (last_selection.start, result))
8436 .chain(
8437 query
8438 .stream_find_iter(bytes_after_first_selection)
8439 .map(|result| (buffer.len(), result)),
8440 );
8441 for (end_offset, query_match) in query_matches {
8442 let query_match = query_match.unwrap(); // can only fail due to I/O
8443 let offset_range =
8444 end_offset - query_match.end()..end_offset - query_match.start();
8445 let display_range = offset_range.start.to_display_point(&display_map)
8446 ..offset_range.end.to_display_point(&display_map);
8447
8448 if !select_prev_state.wordwise
8449 || (!movement::is_inside_word(&display_map, display_range.start)
8450 && !movement::is_inside_word(&display_map, display_range.end))
8451 {
8452 next_selected_range = Some(offset_range);
8453 break;
8454 }
8455 }
8456
8457 if let Some(next_selected_range) = next_selected_range {
8458 self.unfold_ranges([next_selected_range.clone()], false, true, cx);
8459 self.change_selections(Some(Autoscroll::newest()), cx, |s| {
8460 if action.replace_newest {
8461 s.delete(s.newest_anchor().id);
8462 }
8463 s.insert_range(next_selected_range);
8464 });
8465 } else {
8466 select_prev_state.done = true;
8467 }
8468 }
8469
8470 self.select_prev_state = Some(select_prev_state);
8471 } else {
8472 let mut only_carets = true;
8473 let mut same_text_selected = true;
8474 let mut selected_text = None;
8475
8476 let mut selections_iter = selections.iter().peekable();
8477 while let Some(selection) = selections_iter.next() {
8478 if selection.start != selection.end {
8479 only_carets = false;
8480 }
8481
8482 if same_text_selected {
8483 if selected_text.is_none() {
8484 selected_text =
8485 Some(buffer.text_for_range(selection.range()).collect::<String>());
8486 }
8487
8488 if let Some(next_selection) = selections_iter.peek() {
8489 if next_selection.range().len() == selection.range().len() {
8490 let next_selected_text = buffer
8491 .text_for_range(next_selection.range())
8492 .collect::<String>();
8493 if Some(next_selected_text) != selected_text {
8494 same_text_selected = false;
8495 selected_text = None;
8496 }
8497 } else {
8498 same_text_selected = false;
8499 selected_text = None;
8500 }
8501 }
8502 }
8503 }
8504
8505 if only_carets {
8506 for selection in &mut selections {
8507 let word_range = movement::surrounding_word(
8508 &display_map,
8509 selection.start.to_display_point(&display_map),
8510 );
8511 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
8512 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
8513 selection.goal = SelectionGoal::None;
8514 selection.reversed = false;
8515 }
8516 if selections.len() == 1 {
8517 let selection = selections
8518 .last()
8519 .expect("ensured that there's only one selection");
8520 let query = buffer
8521 .text_for_range(selection.start..selection.end)
8522 .collect::<String>();
8523 let is_empty = query.is_empty();
8524 let select_state = SelectNextState {
8525 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
8526 wordwise: true,
8527 done: is_empty,
8528 };
8529 self.select_prev_state = Some(select_state);
8530 } else {
8531 self.select_prev_state = None;
8532 }
8533
8534 self.unfold_ranges(
8535 selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
8536 false,
8537 true,
8538 cx,
8539 );
8540 self.change_selections(Some(Autoscroll::newest()), cx, |s| {
8541 s.select(selections);
8542 });
8543 } else if let Some(selected_text) = selected_text {
8544 self.select_prev_state = Some(SelectNextState {
8545 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
8546 wordwise: false,
8547 done: false,
8548 });
8549 self.select_previous(action, cx)?;
8550 }
8551 }
8552 Ok(())
8553 }
8554
8555 pub fn toggle_comments(&mut self, action: &ToggleComments, cx: &mut ViewContext<Self>) {
8556 let text_layout_details = &self.text_layout_details(cx);
8557 self.transact(cx, |this, cx| {
8558 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8559 let mut edits = Vec::new();
8560 let mut selection_edit_ranges = Vec::new();
8561 let mut last_toggled_row = None;
8562 let snapshot = this.buffer.read(cx).read(cx);
8563 let empty_str: Arc<str> = Arc::default();
8564 let mut suffixes_inserted = Vec::new();
8565
8566 fn comment_prefix_range(
8567 snapshot: &MultiBufferSnapshot,
8568 row: MultiBufferRow,
8569 comment_prefix: &str,
8570 comment_prefix_whitespace: &str,
8571 ) -> Range<Point> {
8572 let start = Point::new(row.0, snapshot.indent_size_for_line(row).len);
8573
8574 let mut line_bytes = snapshot
8575 .bytes_in_range(start..snapshot.max_point())
8576 .flatten()
8577 .copied();
8578
8579 // If this line currently begins with the line comment prefix, then record
8580 // the range containing the prefix.
8581 if line_bytes
8582 .by_ref()
8583 .take(comment_prefix.len())
8584 .eq(comment_prefix.bytes())
8585 {
8586 // Include any whitespace that matches the comment prefix.
8587 let matching_whitespace_len = line_bytes
8588 .zip(comment_prefix_whitespace.bytes())
8589 .take_while(|(a, b)| a == b)
8590 .count() as u32;
8591 let end = Point::new(
8592 start.row,
8593 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
8594 );
8595 start..end
8596 } else {
8597 start..start
8598 }
8599 }
8600
8601 fn comment_suffix_range(
8602 snapshot: &MultiBufferSnapshot,
8603 row: MultiBufferRow,
8604 comment_suffix: &str,
8605 comment_suffix_has_leading_space: bool,
8606 ) -> Range<Point> {
8607 let end = Point::new(row.0, snapshot.line_len(row));
8608 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
8609
8610 let mut line_end_bytes = snapshot
8611 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
8612 .flatten()
8613 .copied();
8614
8615 let leading_space_len = if suffix_start_column > 0
8616 && line_end_bytes.next() == Some(b' ')
8617 && comment_suffix_has_leading_space
8618 {
8619 1
8620 } else {
8621 0
8622 };
8623
8624 // If this line currently begins with the line comment prefix, then record
8625 // the range containing the prefix.
8626 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
8627 let start = Point::new(end.row, suffix_start_column - leading_space_len);
8628 start..end
8629 } else {
8630 end..end
8631 }
8632 }
8633
8634 // TODO: Handle selections that cross excerpts
8635 for selection in &mut selections {
8636 let start_column = snapshot
8637 .indent_size_for_line(MultiBufferRow(selection.start.row))
8638 .len;
8639 let language = if let Some(language) =
8640 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
8641 {
8642 language
8643 } else {
8644 continue;
8645 };
8646
8647 selection_edit_ranges.clear();
8648
8649 // If multiple selections contain a given row, avoid processing that
8650 // row more than once.
8651 let mut start_row = MultiBufferRow(selection.start.row);
8652 if last_toggled_row == Some(start_row) {
8653 start_row = start_row.next_row();
8654 }
8655 let end_row =
8656 if selection.end.row > selection.start.row && selection.end.column == 0 {
8657 MultiBufferRow(selection.end.row - 1)
8658 } else {
8659 MultiBufferRow(selection.end.row)
8660 };
8661 last_toggled_row = Some(end_row);
8662
8663 if start_row > end_row {
8664 continue;
8665 }
8666
8667 // If the language has line comments, toggle those.
8668 let full_comment_prefixes = language.line_comment_prefixes();
8669 if !full_comment_prefixes.is_empty() {
8670 let first_prefix = full_comment_prefixes
8671 .first()
8672 .expect("prefixes is non-empty");
8673 let prefix_trimmed_lengths = full_comment_prefixes
8674 .iter()
8675 .map(|p| p.trim_end_matches(' ').len())
8676 .collect::<SmallVec<[usize; 4]>>();
8677
8678 let mut all_selection_lines_are_comments = true;
8679
8680 for row in start_row.0..=end_row.0 {
8681 let row = MultiBufferRow(row);
8682 if start_row < end_row && snapshot.is_line_blank(row) {
8683 continue;
8684 }
8685
8686 let prefix_range = full_comment_prefixes
8687 .iter()
8688 .zip(prefix_trimmed_lengths.iter().copied())
8689 .map(|(prefix, trimmed_prefix_len)| {
8690 comment_prefix_range(
8691 snapshot.deref(),
8692 row,
8693 &prefix[..trimmed_prefix_len],
8694 &prefix[trimmed_prefix_len..],
8695 )
8696 })
8697 .max_by_key(|range| range.end.column - range.start.column)
8698 .expect("prefixes is non-empty");
8699
8700 if prefix_range.is_empty() {
8701 all_selection_lines_are_comments = false;
8702 }
8703
8704 selection_edit_ranges.push(prefix_range);
8705 }
8706
8707 if all_selection_lines_are_comments {
8708 edits.extend(
8709 selection_edit_ranges
8710 .iter()
8711 .cloned()
8712 .map(|range| (range, empty_str.clone())),
8713 );
8714 } else {
8715 let min_column = selection_edit_ranges
8716 .iter()
8717 .map(|range| range.start.column)
8718 .min()
8719 .unwrap_or(0);
8720 edits.extend(selection_edit_ranges.iter().map(|range| {
8721 let position = Point::new(range.start.row, min_column);
8722 (position..position, first_prefix.clone())
8723 }));
8724 }
8725 } else if let Some((full_comment_prefix, comment_suffix)) =
8726 language.block_comment_delimiters()
8727 {
8728 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
8729 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
8730 let prefix_range = comment_prefix_range(
8731 snapshot.deref(),
8732 start_row,
8733 comment_prefix,
8734 comment_prefix_whitespace,
8735 );
8736 let suffix_range = comment_suffix_range(
8737 snapshot.deref(),
8738 end_row,
8739 comment_suffix.trim_start_matches(' '),
8740 comment_suffix.starts_with(' '),
8741 );
8742
8743 if prefix_range.is_empty() || suffix_range.is_empty() {
8744 edits.push((
8745 prefix_range.start..prefix_range.start,
8746 full_comment_prefix.clone(),
8747 ));
8748 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
8749 suffixes_inserted.push((end_row, comment_suffix.len()));
8750 } else {
8751 edits.push((prefix_range, empty_str.clone()));
8752 edits.push((suffix_range, empty_str.clone()));
8753 }
8754 } else {
8755 continue;
8756 }
8757 }
8758
8759 drop(snapshot);
8760 this.buffer.update(cx, |buffer, cx| {
8761 buffer.edit(edits, None, cx);
8762 });
8763
8764 // Adjust selections so that they end before any comment suffixes that
8765 // were inserted.
8766 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
8767 let mut selections = this.selections.all::<Point>(cx);
8768 let snapshot = this.buffer.read(cx).read(cx);
8769 for selection in &mut selections {
8770 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
8771 match row.cmp(&MultiBufferRow(selection.end.row)) {
8772 Ordering::Less => {
8773 suffixes_inserted.next();
8774 continue;
8775 }
8776 Ordering::Greater => break,
8777 Ordering::Equal => {
8778 if selection.end.column == snapshot.line_len(row) {
8779 if selection.is_empty() {
8780 selection.start.column -= suffix_len as u32;
8781 }
8782 selection.end.column -= suffix_len as u32;
8783 }
8784 break;
8785 }
8786 }
8787 }
8788 }
8789
8790 drop(snapshot);
8791 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
8792
8793 let selections = this.selections.all::<Point>(cx);
8794 let selections_on_single_row = selections.windows(2).all(|selections| {
8795 selections[0].start.row == selections[1].start.row
8796 && selections[0].end.row == selections[1].end.row
8797 && selections[0].start.row == selections[0].end.row
8798 });
8799 let selections_selecting = selections
8800 .iter()
8801 .any(|selection| selection.start != selection.end);
8802 let advance_downwards = action.advance_downwards
8803 && selections_on_single_row
8804 && !selections_selecting
8805 && !matches!(this.mode, EditorMode::SingleLine { .. });
8806
8807 if advance_downwards {
8808 let snapshot = this.buffer.read(cx).snapshot(cx);
8809
8810 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
8811 s.move_cursors_with(|display_snapshot, display_point, _| {
8812 let mut point = display_point.to_point(display_snapshot);
8813 point.row += 1;
8814 point = snapshot.clip_point(point, Bias::Left);
8815 let display_point = point.to_display_point(display_snapshot);
8816 let goal = SelectionGoal::HorizontalPosition(
8817 display_snapshot
8818 .x_for_display_point(display_point, text_layout_details)
8819 .into(),
8820 );
8821 (display_point, goal)
8822 })
8823 });
8824 }
8825 });
8826 }
8827
8828 pub fn select_enclosing_symbol(
8829 &mut self,
8830 _: &SelectEnclosingSymbol,
8831 cx: &mut ViewContext<Self>,
8832 ) {
8833 let buffer = self.buffer.read(cx).snapshot(cx);
8834 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
8835
8836 fn update_selection(
8837 selection: &Selection<usize>,
8838 buffer_snap: &MultiBufferSnapshot,
8839 ) -> Option<Selection<usize>> {
8840 let cursor = selection.head();
8841 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
8842 for symbol in symbols.iter().rev() {
8843 let start = symbol.range.start.to_offset(buffer_snap);
8844 let end = symbol.range.end.to_offset(buffer_snap);
8845 let new_range = start..end;
8846 if start < selection.start || end > selection.end {
8847 return Some(Selection {
8848 id: selection.id,
8849 start: new_range.start,
8850 end: new_range.end,
8851 goal: SelectionGoal::None,
8852 reversed: selection.reversed,
8853 });
8854 }
8855 }
8856 None
8857 }
8858
8859 let mut selected_larger_symbol = false;
8860 let new_selections = old_selections
8861 .iter()
8862 .map(|selection| match update_selection(selection, &buffer) {
8863 Some(new_selection) => {
8864 if new_selection.range() != selection.range() {
8865 selected_larger_symbol = true;
8866 }
8867 new_selection
8868 }
8869 None => selection.clone(),
8870 })
8871 .collect::<Vec<_>>();
8872
8873 if selected_larger_symbol {
8874 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
8875 s.select(new_selections);
8876 });
8877 }
8878 }
8879
8880 pub fn select_larger_syntax_node(
8881 &mut self,
8882 _: &SelectLargerSyntaxNode,
8883 cx: &mut ViewContext<Self>,
8884 ) {
8885 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8886 let buffer = self.buffer.read(cx).snapshot(cx);
8887 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
8888
8889 let mut stack = mem::take(&mut self.select_larger_syntax_node_stack);
8890 let mut selected_larger_node = false;
8891 let new_selections = old_selections
8892 .iter()
8893 .map(|selection| {
8894 let old_range = selection.start..selection.end;
8895 let mut new_range = old_range.clone();
8896 while let Some(containing_range) =
8897 buffer.range_for_syntax_ancestor(new_range.clone())
8898 {
8899 new_range = containing_range;
8900 if !display_map.intersects_fold(new_range.start)
8901 && !display_map.intersects_fold(new_range.end)
8902 {
8903 break;
8904 }
8905 }
8906
8907 selected_larger_node |= new_range != old_range;
8908 Selection {
8909 id: selection.id,
8910 start: new_range.start,
8911 end: new_range.end,
8912 goal: SelectionGoal::None,
8913 reversed: selection.reversed,
8914 }
8915 })
8916 .collect::<Vec<_>>();
8917
8918 if selected_larger_node {
8919 stack.push(old_selections);
8920 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
8921 s.select(new_selections);
8922 });
8923 }
8924 self.select_larger_syntax_node_stack = stack;
8925 }
8926
8927 pub fn select_smaller_syntax_node(
8928 &mut self,
8929 _: &SelectSmallerSyntaxNode,
8930 cx: &mut ViewContext<Self>,
8931 ) {
8932 let mut stack = mem::take(&mut self.select_larger_syntax_node_stack);
8933 if let Some(selections) = stack.pop() {
8934 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
8935 s.select(selections.to_vec());
8936 });
8937 }
8938 self.select_larger_syntax_node_stack = stack;
8939 }
8940
8941 fn refresh_runnables(&mut self, cx: &mut ViewContext<Self>) -> Task<()> {
8942 if !EditorSettings::get_global(cx).gutter.runnables {
8943 self.clear_tasks();
8944 return Task::ready(());
8945 }
8946 let project = self.project.clone();
8947 cx.spawn(|this, mut cx| async move {
8948 let Ok(display_snapshot) = this.update(&mut cx, |this, cx| {
8949 this.display_map.update(cx, |map, cx| map.snapshot(cx))
8950 }) else {
8951 return;
8952 };
8953
8954 let Some(project) = project else {
8955 return;
8956 };
8957
8958 let hide_runnables = project
8959 .update(&mut cx, |project, cx| {
8960 // Do not display any test indicators in non-dev server remote projects.
8961 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
8962 })
8963 .unwrap_or(true);
8964 if hide_runnables {
8965 return;
8966 }
8967 let new_rows =
8968 cx.background_executor()
8969 .spawn({
8970 let snapshot = display_snapshot.clone();
8971 async move {
8972 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
8973 }
8974 })
8975 .await;
8976 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
8977
8978 this.update(&mut cx, |this, _| {
8979 this.clear_tasks();
8980 for (key, value) in rows {
8981 this.insert_tasks(key, value);
8982 }
8983 })
8984 .ok();
8985 })
8986 }
8987 fn fetch_runnable_ranges(
8988 snapshot: &DisplaySnapshot,
8989 range: Range<Anchor>,
8990 ) -> Vec<language::RunnableRange> {
8991 snapshot.buffer_snapshot.runnable_ranges(range).collect()
8992 }
8993
8994 fn runnable_rows(
8995 project: Model<Project>,
8996 snapshot: DisplaySnapshot,
8997 runnable_ranges: Vec<RunnableRange>,
8998 mut cx: AsyncWindowContext,
8999 ) -> Vec<((BufferId, u32), RunnableTasks)> {
9000 runnable_ranges
9001 .into_iter()
9002 .filter_map(|mut runnable| {
9003 let tasks = cx
9004 .update(|cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
9005 .ok()?;
9006 if tasks.is_empty() {
9007 return None;
9008 }
9009
9010 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
9011
9012 let row = snapshot
9013 .buffer_snapshot
9014 .buffer_line_for_row(MultiBufferRow(point.row))?
9015 .1
9016 .start
9017 .row;
9018
9019 let context_range =
9020 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
9021 Some((
9022 (runnable.buffer_id, row),
9023 RunnableTasks {
9024 templates: tasks,
9025 offset: MultiBufferOffset(runnable.run_range.start),
9026 context_range,
9027 column: point.column,
9028 extra_variables: runnable.extra_captures,
9029 },
9030 ))
9031 })
9032 .collect()
9033 }
9034
9035 fn templates_with_tags(
9036 project: &Model<Project>,
9037 runnable: &mut Runnable,
9038 cx: &WindowContext<'_>,
9039 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
9040 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
9041 let (worktree_id, file) = project
9042 .buffer_for_id(runnable.buffer, cx)
9043 .and_then(|buffer| buffer.read(cx).file())
9044 .map(|file| (file.worktree_id(cx), file.clone()))
9045 .unzip();
9046
9047 (project.task_inventory().clone(), worktree_id, file)
9048 });
9049
9050 let inventory = inventory.read(cx);
9051 let tags = mem::take(&mut runnable.tags);
9052 let mut tags: Vec<_> = tags
9053 .into_iter()
9054 .flat_map(|tag| {
9055 let tag = tag.0.clone();
9056 inventory
9057 .list_tasks(
9058 file.clone(),
9059 Some(runnable.language.clone()),
9060 worktree_id,
9061 cx,
9062 )
9063 .into_iter()
9064 .filter(move |(_, template)| {
9065 template.tags.iter().any(|source_tag| source_tag == &tag)
9066 })
9067 })
9068 .sorted_by_key(|(kind, _)| kind.to_owned())
9069 .collect();
9070 if let Some((leading_tag_source, _)) = tags.first() {
9071 // Strongest source wins; if we have worktree tag binding, prefer that to
9072 // global and language bindings;
9073 // if we have a global binding, prefer that to language binding.
9074 let first_mismatch = tags
9075 .iter()
9076 .position(|(tag_source, _)| tag_source != leading_tag_source);
9077 if let Some(index) = first_mismatch {
9078 tags.truncate(index);
9079 }
9080 }
9081
9082 tags
9083 }
9084
9085 pub fn move_to_enclosing_bracket(
9086 &mut self,
9087 _: &MoveToEnclosingBracket,
9088 cx: &mut ViewContext<Self>,
9089 ) {
9090 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
9091 s.move_offsets_with(|snapshot, selection| {
9092 let Some(enclosing_bracket_ranges) =
9093 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
9094 else {
9095 return;
9096 };
9097
9098 let mut best_length = usize::MAX;
9099 let mut best_inside = false;
9100 let mut best_in_bracket_range = false;
9101 let mut best_destination = None;
9102 for (open, close) in enclosing_bracket_ranges {
9103 let close = close.to_inclusive();
9104 let length = close.end() - open.start;
9105 let inside = selection.start >= open.end && selection.end <= *close.start();
9106 let in_bracket_range = open.to_inclusive().contains(&selection.head())
9107 || close.contains(&selection.head());
9108
9109 // If best is next to a bracket and current isn't, skip
9110 if !in_bracket_range && best_in_bracket_range {
9111 continue;
9112 }
9113
9114 // Prefer smaller lengths unless best is inside and current isn't
9115 if length > best_length && (best_inside || !inside) {
9116 continue;
9117 }
9118
9119 best_length = length;
9120 best_inside = inside;
9121 best_in_bracket_range = in_bracket_range;
9122 best_destination = Some(
9123 if close.contains(&selection.start) && close.contains(&selection.end) {
9124 if inside {
9125 open.end
9126 } else {
9127 open.start
9128 }
9129 } else if inside {
9130 *close.start()
9131 } else {
9132 *close.end()
9133 },
9134 );
9135 }
9136
9137 if let Some(destination) = best_destination {
9138 selection.collapse_to(destination, SelectionGoal::None);
9139 }
9140 })
9141 });
9142 }
9143
9144 pub fn undo_selection(&mut self, _: &UndoSelection, cx: &mut ViewContext<Self>) {
9145 self.end_selection(cx);
9146 self.selection_history.mode = SelectionHistoryMode::Undoing;
9147 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
9148 self.change_selections(None, cx, |s| s.select_anchors(entry.selections.to_vec()));
9149 self.select_next_state = entry.select_next_state;
9150 self.select_prev_state = entry.select_prev_state;
9151 self.add_selections_state = entry.add_selections_state;
9152 self.request_autoscroll(Autoscroll::newest(), cx);
9153 }
9154 self.selection_history.mode = SelectionHistoryMode::Normal;
9155 }
9156
9157 pub fn redo_selection(&mut self, _: &RedoSelection, cx: &mut ViewContext<Self>) {
9158 self.end_selection(cx);
9159 self.selection_history.mode = SelectionHistoryMode::Redoing;
9160 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
9161 self.change_selections(None, cx, |s| s.select_anchors(entry.selections.to_vec()));
9162 self.select_next_state = entry.select_next_state;
9163 self.select_prev_state = entry.select_prev_state;
9164 self.add_selections_state = entry.add_selections_state;
9165 self.request_autoscroll(Autoscroll::newest(), cx);
9166 }
9167 self.selection_history.mode = SelectionHistoryMode::Normal;
9168 }
9169
9170 pub fn expand_excerpts(&mut self, action: &ExpandExcerpts, cx: &mut ViewContext<Self>) {
9171 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
9172 }
9173
9174 pub fn expand_excerpts_down(
9175 &mut self,
9176 action: &ExpandExcerptsDown,
9177 cx: &mut ViewContext<Self>,
9178 ) {
9179 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
9180 }
9181
9182 pub fn expand_excerpts_up(&mut self, action: &ExpandExcerptsUp, cx: &mut ViewContext<Self>) {
9183 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
9184 }
9185
9186 pub fn expand_excerpts_for_direction(
9187 &mut self,
9188 lines: u32,
9189 direction: ExpandExcerptDirection,
9190 cx: &mut ViewContext<Self>,
9191 ) {
9192 let selections = self.selections.disjoint_anchors();
9193
9194 let lines = if lines == 0 {
9195 EditorSettings::get_global(cx).expand_excerpt_lines
9196 } else {
9197 lines
9198 };
9199
9200 self.buffer.update(cx, |buffer, cx| {
9201 buffer.expand_excerpts(
9202 selections
9203 .iter()
9204 .map(|selection| selection.head().excerpt_id)
9205 .dedup(),
9206 lines,
9207 direction,
9208 cx,
9209 )
9210 })
9211 }
9212
9213 pub fn expand_excerpt(
9214 &mut self,
9215 excerpt: ExcerptId,
9216 direction: ExpandExcerptDirection,
9217 cx: &mut ViewContext<Self>,
9218 ) {
9219 let lines = EditorSettings::get_global(cx).expand_excerpt_lines;
9220 self.buffer.update(cx, |buffer, cx| {
9221 buffer.expand_excerpts([excerpt], lines, direction, cx)
9222 })
9223 }
9224
9225 fn go_to_diagnostic(&mut self, _: &GoToDiagnostic, cx: &mut ViewContext<Self>) {
9226 self.go_to_diagnostic_impl(Direction::Next, cx)
9227 }
9228
9229 fn go_to_prev_diagnostic(&mut self, _: &GoToPrevDiagnostic, cx: &mut ViewContext<Self>) {
9230 self.go_to_diagnostic_impl(Direction::Prev, cx)
9231 }
9232
9233 pub fn go_to_diagnostic_impl(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
9234 let buffer = self.buffer.read(cx).snapshot(cx);
9235 let selection = self.selections.newest::<usize>(cx);
9236
9237 // If there is an active Diagnostic Popover jump to its diagnostic instead.
9238 if direction == Direction::Next {
9239 if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() {
9240 let (group_id, jump_to) = popover.activation_info();
9241 if self.activate_diagnostics(group_id, cx) {
9242 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
9243 let mut new_selection = s.newest_anchor().clone();
9244 new_selection.collapse_to(jump_to, SelectionGoal::None);
9245 s.select_anchors(vec![new_selection.clone()]);
9246 });
9247 }
9248 return;
9249 }
9250 }
9251
9252 let mut active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| {
9253 active_diagnostics
9254 .primary_range
9255 .to_offset(&buffer)
9256 .to_inclusive()
9257 });
9258 let mut search_start = if let Some(active_primary_range) = active_primary_range.as_ref() {
9259 if active_primary_range.contains(&selection.head()) {
9260 *active_primary_range.start()
9261 } else {
9262 selection.head()
9263 }
9264 } else {
9265 selection.head()
9266 };
9267 let snapshot = self.snapshot(cx);
9268 loop {
9269 let diagnostics = if direction == Direction::Prev {
9270 buffer.diagnostics_in_range::<_, usize>(0..search_start, true)
9271 } else {
9272 buffer.diagnostics_in_range::<_, usize>(search_start..buffer.len(), false)
9273 }
9274 .filter(|diagnostic| !snapshot.intersects_fold(diagnostic.range.start));
9275 let group = diagnostics
9276 // relies on diagnostics_in_range to return diagnostics with the same starting range to
9277 // be sorted in a stable way
9278 // skip until we are at current active diagnostic, if it exists
9279 .skip_while(|entry| {
9280 (match direction {
9281 Direction::Prev => entry.range.start >= search_start,
9282 Direction::Next => entry.range.start <= search_start,
9283 }) && self
9284 .active_diagnostics
9285 .as_ref()
9286 .is_some_and(|a| a.group_id != entry.diagnostic.group_id)
9287 })
9288 .find_map(|entry| {
9289 if entry.diagnostic.is_primary
9290 && entry.diagnostic.severity <= DiagnosticSeverity::WARNING
9291 && !entry.range.is_empty()
9292 // if we match with the active diagnostic, skip it
9293 && Some(entry.diagnostic.group_id)
9294 != self.active_diagnostics.as_ref().map(|d| d.group_id)
9295 {
9296 Some((entry.range, entry.diagnostic.group_id))
9297 } else {
9298 None
9299 }
9300 });
9301
9302 if let Some((primary_range, group_id)) = group {
9303 if self.activate_diagnostics(group_id, cx) {
9304 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
9305 s.select(vec![Selection {
9306 id: selection.id,
9307 start: primary_range.start,
9308 end: primary_range.start,
9309 reversed: false,
9310 goal: SelectionGoal::None,
9311 }]);
9312 });
9313 }
9314 break;
9315 } else {
9316 // Cycle around to the start of the buffer, potentially moving back to the start of
9317 // the currently active diagnostic.
9318 active_primary_range.take();
9319 if direction == Direction::Prev {
9320 if search_start == buffer.len() {
9321 break;
9322 } else {
9323 search_start = buffer.len();
9324 }
9325 } else if search_start == 0 {
9326 break;
9327 } else {
9328 search_start = 0;
9329 }
9330 }
9331 }
9332 }
9333
9334 fn go_to_next_hunk(&mut self, _: &GoToHunk, cx: &mut ViewContext<Self>) {
9335 let snapshot = self
9336 .display_map
9337 .update(cx, |display_map, cx| display_map.snapshot(cx));
9338 let selection = self.selections.newest::<Point>(cx);
9339 self.go_to_hunk_after_position(&snapshot, selection.head(), cx);
9340 }
9341
9342 fn go_to_hunk_after_position(
9343 &mut self,
9344 snapshot: &DisplaySnapshot,
9345 position: Point,
9346 cx: &mut ViewContext<'_, Editor>,
9347 ) -> Option<MultiBufferDiffHunk> {
9348 if let Some(hunk) = self.go_to_next_hunk_in_direction(
9349 snapshot,
9350 position,
9351 false,
9352 snapshot
9353 .buffer_snapshot
9354 .git_diff_hunks_in_range(MultiBufferRow(position.row + 1)..MultiBufferRow::MAX),
9355 cx,
9356 ) {
9357 return Some(hunk);
9358 }
9359
9360 let wrapped_point = Point::zero();
9361 self.go_to_next_hunk_in_direction(
9362 snapshot,
9363 wrapped_point,
9364 true,
9365 snapshot.buffer_snapshot.git_diff_hunks_in_range(
9366 MultiBufferRow(wrapped_point.row + 1)..MultiBufferRow::MAX,
9367 ),
9368 cx,
9369 )
9370 }
9371
9372 fn go_to_prev_hunk(&mut self, _: &GoToPrevHunk, cx: &mut ViewContext<Self>) {
9373 let snapshot = self
9374 .display_map
9375 .update(cx, |display_map, cx| display_map.snapshot(cx));
9376 let selection = self.selections.newest::<Point>(cx);
9377
9378 self.go_to_hunk_before_position(&snapshot, selection.head(), cx);
9379 }
9380
9381 fn go_to_hunk_before_position(
9382 &mut self,
9383 snapshot: &DisplaySnapshot,
9384 position: Point,
9385 cx: &mut ViewContext<'_, Editor>,
9386 ) -> Option<MultiBufferDiffHunk> {
9387 if let Some(hunk) = self.go_to_next_hunk_in_direction(
9388 snapshot,
9389 position,
9390 false,
9391 snapshot
9392 .buffer_snapshot
9393 .git_diff_hunks_in_range_rev(MultiBufferRow(0)..MultiBufferRow(position.row)),
9394 cx,
9395 ) {
9396 return Some(hunk);
9397 }
9398
9399 let wrapped_point = snapshot.buffer_snapshot.max_point();
9400 self.go_to_next_hunk_in_direction(
9401 snapshot,
9402 wrapped_point,
9403 true,
9404 snapshot
9405 .buffer_snapshot
9406 .git_diff_hunks_in_range_rev(MultiBufferRow(0)..MultiBufferRow(wrapped_point.row)),
9407 cx,
9408 )
9409 }
9410
9411 fn go_to_next_hunk_in_direction(
9412 &mut self,
9413 snapshot: &DisplaySnapshot,
9414 initial_point: Point,
9415 is_wrapped: bool,
9416 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
9417 cx: &mut ViewContext<Editor>,
9418 ) -> Option<MultiBufferDiffHunk> {
9419 let display_point = initial_point.to_display_point(snapshot);
9420 let mut hunks = hunks
9421 .map(|hunk| (diff_hunk_to_display(&hunk, snapshot), hunk))
9422 .filter(|(display_hunk, _)| {
9423 is_wrapped || !display_hunk.contains_display_row(display_point.row())
9424 })
9425 .dedup();
9426
9427 if let Some((display_hunk, hunk)) = hunks.next() {
9428 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
9429 let row = display_hunk.start_display_row();
9430 let point = DisplayPoint::new(row, 0);
9431 s.select_display_ranges([point..point]);
9432 });
9433
9434 Some(hunk)
9435 } else {
9436 None
9437 }
9438 }
9439
9440 pub fn go_to_definition(
9441 &mut self,
9442 _: &GoToDefinition,
9443 cx: &mut ViewContext<Self>,
9444 ) -> Task<Result<Navigated>> {
9445 let definition = self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, cx);
9446 cx.spawn(|editor, mut cx| async move {
9447 if definition.await? == Navigated::Yes {
9448 return Ok(Navigated::Yes);
9449 }
9450 match editor.update(&mut cx, |editor, cx| {
9451 editor.find_all_references(&FindAllReferences, cx)
9452 })? {
9453 Some(references) => references.await,
9454 None => Ok(Navigated::No),
9455 }
9456 })
9457 }
9458
9459 pub fn go_to_declaration(
9460 &mut self,
9461 _: &GoToDeclaration,
9462 cx: &mut ViewContext<Self>,
9463 ) -> Task<Result<Navigated>> {
9464 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, cx)
9465 }
9466
9467 pub fn go_to_declaration_split(
9468 &mut self,
9469 _: &GoToDeclaration,
9470 cx: &mut ViewContext<Self>,
9471 ) -> Task<Result<Navigated>> {
9472 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, cx)
9473 }
9474
9475 pub fn go_to_implementation(
9476 &mut self,
9477 _: &GoToImplementation,
9478 cx: &mut ViewContext<Self>,
9479 ) -> Task<Result<Navigated>> {
9480 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, cx)
9481 }
9482
9483 pub fn go_to_implementation_split(
9484 &mut self,
9485 _: &GoToImplementationSplit,
9486 cx: &mut ViewContext<Self>,
9487 ) -> Task<Result<Navigated>> {
9488 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, cx)
9489 }
9490
9491 pub fn go_to_type_definition(
9492 &mut self,
9493 _: &GoToTypeDefinition,
9494 cx: &mut ViewContext<Self>,
9495 ) -> Task<Result<Navigated>> {
9496 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, cx)
9497 }
9498
9499 pub fn go_to_definition_split(
9500 &mut self,
9501 _: &GoToDefinitionSplit,
9502 cx: &mut ViewContext<Self>,
9503 ) -> Task<Result<Navigated>> {
9504 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, cx)
9505 }
9506
9507 pub fn go_to_type_definition_split(
9508 &mut self,
9509 _: &GoToTypeDefinitionSplit,
9510 cx: &mut ViewContext<Self>,
9511 ) -> Task<Result<Navigated>> {
9512 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, cx)
9513 }
9514
9515 fn go_to_definition_of_kind(
9516 &mut self,
9517 kind: GotoDefinitionKind,
9518 split: bool,
9519 cx: &mut ViewContext<Self>,
9520 ) -> Task<Result<Navigated>> {
9521 let Some(workspace) = self.workspace() else {
9522 return Task::ready(Ok(Navigated::No));
9523 };
9524 let buffer = self.buffer.read(cx);
9525 let head = self.selections.newest::<usize>(cx).head();
9526 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
9527 text_anchor
9528 } else {
9529 return Task::ready(Ok(Navigated::No));
9530 };
9531
9532 let project = workspace.read(cx).project().clone();
9533 let definitions = project.update(cx, |project, cx| match kind {
9534 GotoDefinitionKind::Symbol => project.definition(&buffer, head, cx),
9535 GotoDefinitionKind::Declaration => project.declaration(&buffer, head, cx),
9536 GotoDefinitionKind::Type => project.type_definition(&buffer, head, cx),
9537 GotoDefinitionKind::Implementation => project.implementation(&buffer, head, cx),
9538 });
9539
9540 cx.spawn(|editor, mut cx| async move {
9541 let definitions = definitions.await?;
9542 let navigated = editor
9543 .update(&mut cx, |editor, cx| {
9544 editor.navigate_to_hover_links(
9545 Some(kind),
9546 definitions
9547 .into_iter()
9548 .filter(|location| {
9549 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
9550 })
9551 .map(HoverLink::Text)
9552 .collect::<Vec<_>>(),
9553 split,
9554 cx,
9555 )
9556 })?
9557 .await?;
9558 anyhow::Ok(navigated)
9559 })
9560 }
9561
9562 pub fn open_url(&mut self, _: &OpenUrl, cx: &mut ViewContext<Self>) {
9563 let position = self.selections.newest_anchor().head();
9564 let Some((buffer, buffer_position)) =
9565 self.buffer.read(cx).text_anchor_for_position(position, cx)
9566 else {
9567 return;
9568 };
9569
9570 cx.spawn(|editor, mut cx| async move {
9571 if let Some((_, url)) = find_url(&buffer, buffer_position, cx.clone()) {
9572 editor.update(&mut cx, |_, cx| {
9573 cx.open_url(&url);
9574 })
9575 } else {
9576 Ok(())
9577 }
9578 })
9579 .detach();
9580 }
9581
9582 pub fn open_file(&mut self, _: &OpenFile, cx: &mut ViewContext<Self>) {
9583 let Some(workspace) = self.workspace() else {
9584 return;
9585 };
9586
9587 let position = self.selections.newest_anchor().head();
9588
9589 let Some((buffer, buffer_position)) =
9590 self.buffer.read(cx).text_anchor_for_position(position, cx)
9591 else {
9592 return;
9593 };
9594
9595 let Some(project) = self.project.clone() else {
9596 return;
9597 };
9598
9599 cx.spawn(|_, mut cx| async move {
9600 let result = find_file(&buffer, project, buffer_position, &mut cx).await;
9601
9602 if let Some((_, path)) = result {
9603 workspace
9604 .update(&mut cx, |workspace, cx| {
9605 workspace.open_resolved_path(path, cx)
9606 })?
9607 .await?;
9608 }
9609 anyhow::Ok(())
9610 })
9611 .detach();
9612 }
9613
9614 pub(crate) fn navigate_to_hover_links(
9615 &mut self,
9616 kind: Option<GotoDefinitionKind>,
9617 mut definitions: Vec<HoverLink>,
9618 split: bool,
9619 cx: &mut ViewContext<Editor>,
9620 ) -> Task<Result<Navigated>> {
9621 // If there is one definition, just open it directly
9622 if definitions.len() == 1 {
9623 let definition = definitions.pop().unwrap();
9624
9625 enum TargetTaskResult {
9626 Location(Option<Location>),
9627 AlreadyNavigated,
9628 }
9629
9630 let target_task = match definition {
9631 HoverLink::Text(link) => {
9632 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
9633 }
9634 HoverLink::InlayHint(lsp_location, server_id) => {
9635 let computation = self.compute_target_location(lsp_location, server_id, cx);
9636 cx.background_executor().spawn(async move {
9637 let location = computation.await?;
9638 Ok(TargetTaskResult::Location(location))
9639 })
9640 }
9641 HoverLink::Url(url) => {
9642 cx.open_url(&url);
9643 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
9644 }
9645 HoverLink::File(path) => {
9646 if let Some(workspace) = self.workspace() {
9647 cx.spawn(|_, mut cx| async move {
9648 workspace
9649 .update(&mut cx, |workspace, cx| {
9650 workspace.open_resolved_path(path, cx)
9651 })?
9652 .await
9653 .map(|_| TargetTaskResult::AlreadyNavigated)
9654 })
9655 } else {
9656 Task::ready(Ok(TargetTaskResult::Location(None)))
9657 }
9658 }
9659 };
9660 cx.spawn(|editor, mut cx| async move {
9661 let target = match target_task.await.context("target resolution task")? {
9662 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
9663 TargetTaskResult::Location(None) => return Ok(Navigated::No),
9664 TargetTaskResult::Location(Some(target)) => target,
9665 };
9666
9667 editor.update(&mut cx, |editor, cx| {
9668 let Some(workspace) = editor.workspace() else {
9669 return Navigated::No;
9670 };
9671 let pane = workspace.read(cx).active_pane().clone();
9672
9673 let range = target.range.to_offset(target.buffer.read(cx));
9674 let range = editor.range_for_match(&range);
9675
9676 if Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
9677 let buffer = target.buffer.read(cx);
9678 let range = check_multiline_range(buffer, range);
9679 editor.change_selections(Some(Autoscroll::focused()), cx, |s| {
9680 s.select_ranges([range]);
9681 });
9682 } else {
9683 cx.window_context().defer(move |cx| {
9684 let target_editor: View<Self> =
9685 workspace.update(cx, |workspace, cx| {
9686 let pane = if split {
9687 workspace.adjacent_pane(cx)
9688 } else {
9689 workspace.active_pane().clone()
9690 };
9691
9692 workspace.open_project_item(
9693 pane,
9694 target.buffer.clone(),
9695 true,
9696 true,
9697 cx,
9698 )
9699 });
9700 target_editor.update(cx, |target_editor, cx| {
9701 // When selecting a definition in a different buffer, disable the nav history
9702 // to avoid creating a history entry at the previous cursor location.
9703 pane.update(cx, |pane, _| pane.disable_history());
9704 let buffer = target.buffer.read(cx);
9705 let range = check_multiline_range(buffer, range);
9706 target_editor.change_selections(
9707 Some(Autoscroll::focused()),
9708 cx,
9709 |s| {
9710 s.select_ranges([range]);
9711 },
9712 );
9713 pane.update(cx, |pane, _| pane.enable_history());
9714 });
9715 });
9716 }
9717 Navigated::Yes
9718 })
9719 })
9720 } else if !definitions.is_empty() {
9721 cx.spawn(|editor, mut cx| async move {
9722 let (title, location_tasks, workspace) = editor
9723 .update(&mut cx, |editor, cx| {
9724 let tab_kind = match kind {
9725 Some(GotoDefinitionKind::Implementation) => "Implementations",
9726 _ => "Definitions",
9727 };
9728 let title = definitions
9729 .iter()
9730 .find_map(|definition| match definition {
9731 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
9732 let buffer = origin.buffer.read(cx);
9733 format!(
9734 "{} for {}",
9735 tab_kind,
9736 buffer
9737 .text_for_range(origin.range.clone())
9738 .collect::<String>()
9739 )
9740 }),
9741 HoverLink::InlayHint(_, _) => None,
9742 HoverLink::Url(_) => None,
9743 HoverLink::File(_) => None,
9744 })
9745 .unwrap_or(tab_kind.to_string());
9746 let location_tasks = definitions
9747 .into_iter()
9748 .map(|definition| match definition {
9749 HoverLink::Text(link) => Task::Ready(Some(Ok(Some(link.target)))),
9750 HoverLink::InlayHint(lsp_location, server_id) => {
9751 editor.compute_target_location(lsp_location, server_id, cx)
9752 }
9753 HoverLink::Url(_) => Task::ready(Ok(None)),
9754 HoverLink::File(_) => Task::ready(Ok(None)),
9755 })
9756 .collect::<Vec<_>>();
9757 (title, location_tasks, editor.workspace().clone())
9758 })
9759 .context("location tasks preparation")?;
9760
9761 let locations = future::join_all(location_tasks)
9762 .await
9763 .into_iter()
9764 .filter_map(|location| location.transpose())
9765 .collect::<Result<_>>()
9766 .context("location tasks")?;
9767
9768 let Some(workspace) = workspace else {
9769 return Ok(Navigated::No);
9770 };
9771 let opened = workspace
9772 .update(&mut cx, |workspace, cx| {
9773 Self::open_locations_in_multibuffer(workspace, locations, title, split, cx)
9774 })
9775 .ok();
9776
9777 anyhow::Ok(Navigated::from_bool(opened.is_some()))
9778 })
9779 } else {
9780 Task::ready(Ok(Navigated::No))
9781 }
9782 }
9783
9784 fn compute_target_location(
9785 &self,
9786 lsp_location: lsp::Location,
9787 server_id: LanguageServerId,
9788 cx: &mut ViewContext<Editor>,
9789 ) -> Task<anyhow::Result<Option<Location>>> {
9790 let Some(project) = self.project.clone() else {
9791 return Task::Ready(Some(Ok(None)));
9792 };
9793
9794 cx.spawn(move |editor, mut cx| async move {
9795 let location_task = editor.update(&mut cx, |editor, cx| {
9796 project.update(cx, |project, cx| {
9797 let language_server_name =
9798 editor.buffer.read(cx).as_singleton().and_then(|buffer| {
9799 project
9800 .language_server_for_buffer(buffer.read(cx), server_id, cx)
9801 .map(|(lsp_adapter, _)| lsp_adapter.name.clone())
9802 });
9803 language_server_name.map(|language_server_name| {
9804 project.open_local_buffer_via_lsp(
9805 lsp_location.uri.clone(),
9806 server_id,
9807 language_server_name,
9808 cx,
9809 )
9810 })
9811 })
9812 })?;
9813 let location = match location_task {
9814 Some(task) => Some({
9815 let target_buffer_handle = task.await.context("open local buffer")?;
9816 let range = target_buffer_handle.update(&mut cx, |target_buffer, _| {
9817 let target_start = target_buffer
9818 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
9819 let target_end = target_buffer
9820 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
9821 target_buffer.anchor_after(target_start)
9822 ..target_buffer.anchor_before(target_end)
9823 })?;
9824 Location {
9825 buffer: target_buffer_handle,
9826 range,
9827 }
9828 }),
9829 None => None,
9830 };
9831 Ok(location)
9832 })
9833 }
9834
9835 pub fn find_all_references(
9836 &mut self,
9837 _: &FindAllReferences,
9838 cx: &mut ViewContext<Self>,
9839 ) -> Option<Task<Result<Navigated>>> {
9840 let multi_buffer = self.buffer.read(cx);
9841 let selection = self.selections.newest::<usize>(cx);
9842 let head = selection.head();
9843
9844 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
9845 let head_anchor = multi_buffer_snapshot.anchor_at(
9846 head,
9847 if head < selection.tail() {
9848 Bias::Right
9849 } else {
9850 Bias::Left
9851 },
9852 );
9853
9854 match self
9855 .find_all_references_task_sources
9856 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
9857 {
9858 Ok(_) => {
9859 log::info!(
9860 "Ignoring repeated FindAllReferences invocation with the position of already running task"
9861 );
9862 return None;
9863 }
9864 Err(i) => {
9865 self.find_all_references_task_sources.insert(i, head_anchor);
9866 }
9867 }
9868
9869 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
9870 let workspace = self.workspace()?;
9871 let project = workspace.read(cx).project().clone();
9872 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
9873 Some(cx.spawn(|editor, mut cx| async move {
9874 let _cleanup = defer({
9875 let mut cx = cx.clone();
9876 move || {
9877 let _ = editor.update(&mut cx, |editor, _| {
9878 if let Ok(i) =
9879 editor
9880 .find_all_references_task_sources
9881 .binary_search_by(|anchor| {
9882 anchor.cmp(&head_anchor, &multi_buffer_snapshot)
9883 })
9884 {
9885 editor.find_all_references_task_sources.remove(i);
9886 }
9887 });
9888 }
9889 });
9890
9891 let locations = references.await?;
9892 if locations.is_empty() {
9893 return anyhow::Ok(Navigated::No);
9894 }
9895
9896 workspace.update(&mut cx, |workspace, cx| {
9897 let title = locations
9898 .first()
9899 .as_ref()
9900 .map(|location| {
9901 let buffer = location.buffer.read(cx);
9902 format!(
9903 "References to `{}`",
9904 buffer
9905 .text_for_range(location.range.clone())
9906 .collect::<String>()
9907 )
9908 })
9909 .unwrap();
9910 Self::open_locations_in_multibuffer(workspace, locations, title, false, cx);
9911 Navigated::Yes
9912 })
9913 }))
9914 }
9915
9916 /// Opens a multibuffer with the given project locations in it
9917 pub fn open_locations_in_multibuffer(
9918 workspace: &mut Workspace,
9919 mut locations: Vec<Location>,
9920 title: String,
9921 split: bool,
9922 cx: &mut ViewContext<Workspace>,
9923 ) {
9924 // If there are multiple definitions, open them in a multibuffer
9925 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
9926 let mut locations = locations.into_iter().peekable();
9927 let mut ranges_to_highlight = Vec::new();
9928 let capability = workspace.project().read(cx).capability();
9929
9930 let excerpt_buffer = cx.new_model(|cx| {
9931 let mut multibuffer = MultiBuffer::new(capability);
9932 while let Some(location) = locations.next() {
9933 let buffer = location.buffer.read(cx);
9934 let mut ranges_for_buffer = Vec::new();
9935 let range = location.range.to_offset(buffer);
9936 ranges_for_buffer.push(range.clone());
9937
9938 while let Some(next_location) = locations.peek() {
9939 if next_location.buffer == location.buffer {
9940 ranges_for_buffer.push(next_location.range.to_offset(buffer));
9941 locations.next();
9942 } else {
9943 break;
9944 }
9945 }
9946
9947 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
9948 ranges_to_highlight.extend(multibuffer.push_excerpts_with_context_lines(
9949 location.buffer.clone(),
9950 ranges_for_buffer,
9951 DEFAULT_MULTIBUFFER_CONTEXT,
9952 cx,
9953 ))
9954 }
9955
9956 multibuffer.with_title(title)
9957 });
9958
9959 let editor = cx.new_view(|cx| {
9960 Editor::for_multibuffer(excerpt_buffer, Some(workspace.project().clone()), true, cx)
9961 });
9962 editor.update(cx, |editor, cx| {
9963 if let Some(first_range) = ranges_to_highlight.first() {
9964 editor.change_selections(None, cx, |selections| {
9965 selections.clear_disjoint();
9966 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
9967 });
9968 }
9969 editor.highlight_background::<Self>(
9970 &ranges_to_highlight,
9971 |theme| theme.editor_highlighted_line_background,
9972 cx,
9973 );
9974 });
9975
9976 let item = Box::new(editor);
9977 let item_id = item.item_id();
9978
9979 if split {
9980 workspace.split_item(SplitDirection::Right, item.clone(), cx);
9981 } else {
9982 let destination_index = workspace.active_pane().update(cx, |pane, cx| {
9983 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
9984 pane.close_current_preview_item(cx)
9985 } else {
9986 None
9987 }
9988 });
9989 workspace.add_item_to_active_pane(item.clone(), destination_index, true, cx);
9990 }
9991 workspace.active_pane().update(cx, |pane, cx| {
9992 pane.set_preview_item_id(Some(item_id), cx);
9993 });
9994 }
9995
9996 pub fn rename(&mut self, _: &Rename, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
9997 use language::ToOffset as _;
9998
9999 let project = self.project.clone()?;
10000 let selection = self.selections.newest_anchor().clone();
10001 let (cursor_buffer, cursor_buffer_position) = self
10002 .buffer
10003 .read(cx)
10004 .text_anchor_for_position(selection.head(), cx)?;
10005 let (tail_buffer, cursor_buffer_position_end) = self
10006 .buffer
10007 .read(cx)
10008 .text_anchor_for_position(selection.tail(), cx)?;
10009 if tail_buffer != cursor_buffer {
10010 return None;
10011 }
10012
10013 let snapshot = cursor_buffer.read(cx).snapshot();
10014 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
10015 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
10016 let prepare_rename = project.update(cx, |project, cx| {
10017 project.prepare_rename(cursor_buffer.clone(), cursor_buffer_offset, cx)
10018 });
10019 drop(snapshot);
10020
10021 Some(cx.spawn(|this, mut cx| async move {
10022 let rename_range = if let Some(range) = prepare_rename.await? {
10023 Some(range)
10024 } else {
10025 this.update(&mut cx, |this, cx| {
10026 let buffer = this.buffer.read(cx).snapshot(cx);
10027 let mut buffer_highlights = this
10028 .document_highlights_for_position(selection.head(), &buffer)
10029 .filter(|highlight| {
10030 highlight.start.excerpt_id == selection.head().excerpt_id
10031 && highlight.end.excerpt_id == selection.head().excerpt_id
10032 });
10033 buffer_highlights
10034 .next()
10035 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
10036 })?
10037 };
10038 if let Some(rename_range) = rename_range {
10039 this.update(&mut cx, |this, cx| {
10040 let snapshot = cursor_buffer.read(cx).snapshot();
10041 let rename_buffer_range = rename_range.to_offset(&snapshot);
10042 let cursor_offset_in_rename_range =
10043 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
10044 let cursor_offset_in_rename_range_end =
10045 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
10046
10047 this.take_rename(false, cx);
10048 let buffer = this.buffer.read(cx).read(cx);
10049 let cursor_offset = selection.head().to_offset(&buffer);
10050 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
10051 let rename_end = rename_start + rename_buffer_range.len();
10052 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
10053 let mut old_highlight_id = None;
10054 let old_name: Arc<str> = buffer
10055 .chunks(rename_start..rename_end, true)
10056 .map(|chunk| {
10057 if old_highlight_id.is_none() {
10058 old_highlight_id = chunk.syntax_highlight_id;
10059 }
10060 chunk.text
10061 })
10062 .collect::<String>()
10063 .into();
10064
10065 drop(buffer);
10066
10067 // Position the selection in the rename editor so that it matches the current selection.
10068 this.show_local_selections = false;
10069 let rename_editor = cx.new_view(|cx| {
10070 let mut editor = Editor::single_line(cx);
10071 editor.buffer.update(cx, |buffer, cx| {
10072 buffer.edit([(0..0, old_name.clone())], None, cx)
10073 });
10074 let rename_selection_range = match cursor_offset_in_rename_range
10075 .cmp(&cursor_offset_in_rename_range_end)
10076 {
10077 Ordering::Equal => {
10078 editor.select_all(&SelectAll, cx);
10079 return editor;
10080 }
10081 Ordering::Less => {
10082 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
10083 }
10084 Ordering::Greater => {
10085 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
10086 }
10087 };
10088 if rename_selection_range.end > old_name.len() {
10089 editor.select_all(&SelectAll, cx);
10090 } else {
10091 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
10092 s.select_ranges([rename_selection_range]);
10093 });
10094 }
10095 editor
10096 });
10097 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
10098 if e == &EditorEvent::Focused {
10099 cx.emit(EditorEvent::FocusedIn)
10100 }
10101 })
10102 .detach();
10103
10104 let write_highlights =
10105 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
10106 let read_highlights =
10107 this.clear_background_highlights::<DocumentHighlightRead>(cx);
10108 let ranges = write_highlights
10109 .iter()
10110 .flat_map(|(_, ranges)| ranges.iter())
10111 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
10112 .cloned()
10113 .collect();
10114
10115 this.highlight_text::<Rename>(
10116 ranges,
10117 HighlightStyle {
10118 fade_out: Some(0.6),
10119 ..Default::default()
10120 },
10121 cx,
10122 );
10123 let rename_focus_handle = rename_editor.focus_handle(cx);
10124 cx.focus(&rename_focus_handle);
10125 let block_id = this.insert_blocks(
10126 [BlockProperties {
10127 style: BlockStyle::Flex,
10128 position: range.start,
10129 height: 1,
10130 render: Box::new({
10131 let rename_editor = rename_editor.clone();
10132 move |cx: &mut BlockContext| {
10133 let mut text_style = cx.editor_style.text.clone();
10134 if let Some(highlight_style) = old_highlight_id
10135 .and_then(|h| h.style(&cx.editor_style.syntax))
10136 {
10137 text_style = text_style.highlight(highlight_style);
10138 }
10139 div()
10140 .pl(cx.anchor_x)
10141 .child(EditorElement::new(
10142 &rename_editor,
10143 EditorStyle {
10144 background: cx.theme().system().transparent,
10145 local_player: cx.editor_style.local_player,
10146 text: text_style,
10147 scrollbar_width: cx.editor_style.scrollbar_width,
10148 syntax: cx.editor_style.syntax.clone(),
10149 status: cx.editor_style.status.clone(),
10150 inlay_hints_style: HighlightStyle {
10151 font_weight: Some(FontWeight::BOLD),
10152 ..make_inlay_hints_style(cx)
10153 },
10154 suggestions_style: HighlightStyle {
10155 color: Some(cx.theme().status().predictive),
10156 ..HighlightStyle::default()
10157 },
10158 ..EditorStyle::default()
10159 },
10160 ))
10161 .into_any_element()
10162 }
10163 }),
10164 disposition: BlockDisposition::Below,
10165 priority: 0,
10166 }],
10167 Some(Autoscroll::fit()),
10168 cx,
10169 )[0];
10170 this.pending_rename = Some(RenameState {
10171 range,
10172 old_name,
10173 editor: rename_editor,
10174 block_id,
10175 });
10176 })?;
10177 }
10178
10179 Ok(())
10180 }))
10181 }
10182
10183 pub fn confirm_rename(
10184 &mut self,
10185 _: &ConfirmRename,
10186 cx: &mut ViewContext<Self>,
10187 ) -> Option<Task<Result<()>>> {
10188 let rename = self.take_rename(false, cx)?;
10189 let workspace = self.workspace()?;
10190 let (start_buffer, start) = self
10191 .buffer
10192 .read(cx)
10193 .text_anchor_for_position(rename.range.start, cx)?;
10194 let (end_buffer, end) = self
10195 .buffer
10196 .read(cx)
10197 .text_anchor_for_position(rename.range.end, cx)?;
10198 if start_buffer != end_buffer {
10199 return None;
10200 }
10201
10202 let buffer = start_buffer;
10203 let range = start..end;
10204 let old_name = rename.old_name;
10205 let new_name = rename.editor.read(cx).text(cx);
10206
10207 let rename = workspace
10208 .read(cx)
10209 .project()
10210 .clone()
10211 .update(cx, |project, cx| {
10212 project.perform_rename(buffer.clone(), range.start, new_name.clone(), true, cx)
10213 });
10214 let workspace = workspace.downgrade();
10215
10216 Some(cx.spawn(|editor, mut cx| async move {
10217 let project_transaction = rename.await?;
10218 Self::open_project_transaction(
10219 &editor,
10220 workspace,
10221 project_transaction,
10222 format!("Rename: {} → {}", old_name, new_name),
10223 cx.clone(),
10224 )
10225 .await?;
10226
10227 editor.update(&mut cx, |editor, cx| {
10228 editor.refresh_document_highlights(cx);
10229 })?;
10230 Ok(())
10231 }))
10232 }
10233
10234 fn take_rename(
10235 &mut self,
10236 moving_cursor: bool,
10237 cx: &mut ViewContext<Self>,
10238 ) -> Option<RenameState> {
10239 let rename = self.pending_rename.take()?;
10240 if rename.editor.focus_handle(cx).is_focused(cx) {
10241 cx.focus(&self.focus_handle);
10242 }
10243
10244 self.remove_blocks(
10245 [rename.block_id].into_iter().collect(),
10246 Some(Autoscroll::fit()),
10247 cx,
10248 );
10249 self.clear_highlights::<Rename>(cx);
10250 self.show_local_selections = true;
10251
10252 if moving_cursor {
10253 let rename_editor = rename.editor.read(cx);
10254 let cursor_in_rename_editor = rename_editor.selections.newest::<usize>(cx).head();
10255
10256 // Update the selection to match the position of the selection inside
10257 // the rename editor.
10258 let snapshot = self.buffer.read(cx).read(cx);
10259 let rename_range = rename.range.to_offset(&snapshot);
10260 let cursor_in_editor = snapshot
10261 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
10262 .min(rename_range.end);
10263 drop(snapshot);
10264
10265 self.change_selections(None, cx, |s| {
10266 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
10267 });
10268 } else {
10269 self.refresh_document_highlights(cx);
10270 }
10271
10272 Some(rename)
10273 }
10274
10275 pub fn pending_rename(&self) -> Option<&RenameState> {
10276 self.pending_rename.as_ref()
10277 }
10278
10279 fn format(&mut self, _: &Format, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
10280 let project = match &self.project {
10281 Some(project) => project.clone(),
10282 None => return None,
10283 };
10284
10285 Some(self.perform_format(project, FormatTrigger::Manual, cx))
10286 }
10287
10288 fn perform_format(
10289 &mut self,
10290 project: Model<Project>,
10291 trigger: FormatTrigger,
10292 cx: &mut ViewContext<Self>,
10293 ) -> Task<Result<()>> {
10294 let buffer = self.buffer().clone();
10295 let mut buffers = buffer.read(cx).all_buffers();
10296 if trigger == FormatTrigger::Save {
10297 buffers.retain(|buffer| buffer.read(cx).is_dirty());
10298 }
10299
10300 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
10301 let format = project.update(cx, |project, cx| project.format(buffers, true, trigger, cx));
10302
10303 cx.spawn(|_, mut cx| async move {
10304 let transaction = futures::select_biased! {
10305 () = timeout => {
10306 log::warn!("timed out waiting for formatting");
10307 None
10308 }
10309 transaction = format.log_err().fuse() => transaction,
10310 };
10311
10312 buffer
10313 .update(&mut cx, |buffer, cx| {
10314 if let Some(transaction) = transaction {
10315 if !buffer.is_singleton() {
10316 buffer.push_transaction(&transaction.0, cx);
10317 }
10318 }
10319
10320 cx.notify();
10321 })
10322 .ok();
10323
10324 Ok(())
10325 })
10326 }
10327
10328 fn restart_language_server(&mut self, _: &RestartLanguageServer, cx: &mut ViewContext<Self>) {
10329 if let Some(project) = self.project.clone() {
10330 self.buffer.update(cx, |multi_buffer, cx| {
10331 project.update(cx, |project, cx| {
10332 project.restart_language_servers_for_buffers(multi_buffer.all_buffers(), cx);
10333 });
10334 })
10335 }
10336 }
10337
10338 fn cancel_language_server_work(
10339 &mut self,
10340 _: &CancelLanguageServerWork,
10341 cx: &mut ViewContext<Self>,
10342 ) {
10343 if let Some(project) = self.project.clone() {
10344 self.buffer.update(cx, |multi_buffer, cx| {
10345 project.update(cx, |project, cx| {
10346 project.cancel_language_server_work_for_buffers(multi_buffer.all_buffers(), cx);
10347 });
10348 })
10349 }
10350 }
10351
10352 fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) {
10353 cx.show_character_palette();
10354 }
10355
10356 fn refresh_active_diagnostics(&mut self, cx: &mut ViewContext<Editor>) {
10357 if let Some(active_diagnostics) = self.active_diagnostics.as_mut() {
10358 let buffer = self.buffer.read(cx).snapshot(cx);
10359 let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer);
10360 let is_valid = buffer
10361 .diagnostics_in_range::<_, usize>(active_diagnostics.primary_range.clone(), false)
10362 .any(|entry| {
10363 entry.diagnostic.is_primary
10364 && !entry.range.is_empty()
10365 && entry.range.start == primary_range_start
10366 && entry.diagnostic.message == active_diagnostics.primary_message
10367 });
10368
10369 if is_valid != active_diagnostics.is_valid {
10370 active_diagnostics.is_valid = is_valid;
10371 let mut new_styles = HashMap::default();
10372 for (block_id, diagnostic) in &active_diagnostics.blocks {
10373 new_styles.insert(
10374 *block_id,
10375 diagnostic_block_renderer(diagnostic.clone(), None, true, is_valid),
10376 );
10377 }
10378 self.display_map.update(cx, |display_map, _cx| {
10379 display_map.replace_blocks(new_styles)
10380 });
10381 }
10382 }
10383 }
10384
10385 fn activate_diagnostics(&mut self, group_id: usize, cx: &mut ViewContext<Self>) -> bool {
10386 self.dismiss_diagnostics(cx);
10387 let snapshot = self.snapshot(cx);
10388 self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
10389 let buffer = self.buffer.read(cx).snapshot(cx);
10390
10391 let mut primary_range = None;
10392 let mut primary_message = None;
10393 let mut group_end = Point::zero();
10394 let diagnostic_group = buffer
10395 .diagnostic_group::<MultiBufferPoint>(group_id)
10396 .filter_map(|entry| {
10397 if snapshot.is_line_folded(MultiBufferRow(entry.range.start.row))
10398 && (entry.range.start.row == entry.range.end.row
10399 || snapshot.is_line_folded(MultiBufferRow(entry.range.end.row)))
10400 {
10401 return None;
10402 }
10403 if entry.range.end > group_end {
10404 group_end = entry.range.end;
10405 }
10406 if entry.diagnostic.is_primary {
10407 primary_range = Some(entry.range.clone());
10408 primary_message = Some(entry.diagnostic.message.clone());
10409 }
10410 Some(entry)
10411 })
10412 .collect::<Vec<_>>();
10413 let primary_range = primary_range?;
10414 let primary_message = primary_message?;
10415 let primary_range =
10416 buffer.anchor_after(primary_range.start)..buffer.anchor_before(primary_range.end);
10417
10418 let blocks = display_map
10419 .insert_blocks(
10420 diagnostic_group.iter().map(|entry| {
10421 let diagnostic = entry.diagnostic.clone();
10422 let message_height = diagnostic.message.matches('\n').count() as u32 + 1;
10423 BlockProperties {
10424 style: BlockStyle::Fixed,
10425 position: buffer.anchor_after(entry.range.start),
10426 height: message_height,
10427 render: diagnostic_block_renderer(diagnostic, None, true, true),
10428 disposition: BlockDisposition::Below,
10429 priority: 0,
10430 }
10431 }),
10432 cx,
10433 )
10434 .into_iter()
10435 .zip(diagnostic_group.into_iter().map(|entry| entry.diagnostic))
10436 .collect();
10437
10438 Some(ActiveDiagnosticGroup {
10439 primary_range,
10440 primary_message,
10441 group_id,
10442 blocks,
10443 is_valid: true,
10444 })
10445 });
10446 self.active_diagnostics.is_some()
10447 }
10448
10449 fn dismiss_diagnostics(&mut self, cx: &mut ViewContext<Self>) {
10450 if let Some(active_diagnostic_group) = self.active_diagnostics.take() {
10451 self.display_map.update(cx, |display_map, cx| {
10452 display_map.remove_blocks(active_diagnostic_group.blocks.into_keys().collect(), cx);
10453 });
10454 cx.notify();
10455 }
10456 }
10457
10458 pub fn set_selections_from_remote(
10459 &mut self,
10460 selections: Vec<Selection<Anchor>>,
10461 pending_selection: Option<Selection<Anchor>>,
10462 cx: &mut ViewContext<Self>,
10463 ) {
10464 let old_cursor_position = self.selections.newest_anchor().head();
10465 self.selections.change_with(cx, |s| {
10466 s.select_anchors(selections);
10467 if let Some(pending_selection) = pending_selection {
10468 s.set_pending(pending_selection, SelectMode::Character);
10469 } else {
10470 s.clear_pending();
10471 }
10472 });
10473 self.selections_did_change(false, &old_cursor_position, true, cx);
10474 }
10475
10476 fn push_to_selection_history(&mut self) {
10477 self.selection_history.push(SelectionHistoryEntry {
10478 selections: self.selections.disjoint_anchors(),
10479 select_next_state: self.select_next_state.clone(),
10480 select_prev_state: self.select_prev_state.clone(),
10481 add_selections_state: self.add_selections_state.clone(),
10482 });
10483 }
10484
10485 pub fn transact(
10486 &mut self,
10487 cx: &mut ViewContext<Self>,
10488 update: impl FnOnce(&mut Self, &mut ViewContext<Self>),
10489 ) -> Option<TransactionId> {
10490 self.start_transaction_at(Instant::now(), cx);
10491 update(self, cx);
10492 self.end_transaction_at(Instant::now(), cx)
10493 }
10494
10495 fn start_transaction_at(&mut self, now: Instant, cx: &mut ViewContext<Self>) {
10496 self.end_selection(cx);
10497 if let Some(tx_id) = self
10498 .buffer
10499 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
10500 {
10501 self.selection_history
10502 .insert_transaction(tx_id, self.selections.disjoint_anchors());
10503 cx.emit(EditorEvent::TransactionBegun {
10504 transaction_id: tx_id,
10505 })
10506 }
10507 }
10508
10509 fn end_transaction_at(
10510 &mut self,
10511 now: Instant,
10512 cx: &mut ViewContext<Self>,
10513 ) -> Option<TransactionId> {
10514 if let Some(transaction_id) = self
10515 .buffer
10516 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
10517 {
10518 if let Some((_, end_selections)) =
10519 self.selection_history.transaction_mut(transaction_id)
10520 {
10521 *end_selections = Some(self.selections.disjoint_anchors());
10522 } else {
10523 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
10524 }
10525
10526 cx.emit(EditorEvent::Edited { transaction_id });
10527 Some(transaction_id)
10528 } else {
10529 None
10530 }
10531 }
10532
10533 pub fn fold(&mut self, _: &actions::Fold, cx: &mut ViewContext<Self>) {
10534 let mut fold_ranges = Vec::new();
10535
10536 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10537
10538 let selections = self.selections.all_adjusted(cx);
10539 for selection in selections {
10540 let range = selection.range().sorted();
10541 let buffer_start_row = range.start.row;
10542
10543 for row in (0..=range.end.row).rev() {
10544 if let Some((foldable_range, fold_text)) =
10545 display_map.foldable_range(MultiBufferRow(row))
10546 {
10547 if foldable_range.end.row >= buffer_start_row {
10548 fold_ranges.push((foldable_range, fold_text));
10549 if row <= range.start.row {
10550 break;
10551 }
10552 }
10553 }
10554 }
10555 }
10556
10557 self.fold_ranges(fold_ranges, true, cx);
10558 }
10559
10560 pub fn fold_at(&mut self, fold_at: &FoldAt, cx: &mut ViewContext<Self>) {
10561 let buffer_row = fold_at.buffer_row;
10562 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10563
10564 if let Some((fold_range, placeholder)) = display_map.foldable_range(buffer_row) {
10565 let autoscroll = self
10566 .selections
10567 .all::<Point>(cx)
10568 .iter()
10569 .any(|selection| fold_range.overlaps(&selection.range()));
10570
10571 self.fold_ranges([(fold_range, placeholder)], autoscroll, cx);
10572 }
10573 }
10574
10575 pub fn unfold_lines(&mut self, _: &UnfoldLines, cx: &mut ViewContext<Self>) {
10576 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10577 let buffer = &display_map.buffer_snapshot;
10578 let selections = self.selections.all::<Point>(cx);
10579 let ranges = selections
10580 .iter()
10581 .map(|s| {
10582 let range = s.display_range(&display_map).sorted();
10583 let mut start = range.start.to_point(&display_map);
10584 let mut end = range.end.to_point(&display_map);
10585 start.column = 0;
10586 end.column = buffer.line_len(MultiBufferRow(end.row));
10587 start..end
10588 })
10589 .collect::<Vec<_>>();
10590
10591 self.unfold_ranges(ranges, true, true, cx);
10592 }
10593
10594 pub fn unfold_at(&mut self, unfold_at: &UnfoldAt, cx: &mut ViewContext<Self>) {
10595 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10596
10597 let intersection_range = Point::new(unfold_at.buffer_row.0, 0)
10598 ..Point::new(
10599 unfold_at.buffer_row.0,
10600 display_map.buffer_snapshot.line_len(unfold_at.buffer_row),
10601 );
10602
10603 let autoscroll = self
10604 .selections
10605 .all::<Point>(cx)
10606 .iter()
10607 .any(|selection| selection.range().overlaps(&intersection_range));
10608
10609 self.unfold_ranges(std::iter::once(intersection_range), true, autoscroll, cx)
10610 }
10611
10612 pub fn fold_selected_ranges(&mut self, _: &FoldSelectedRanges, cx: &mut ViewContext<Self>) {
10613 let selections = self.selections.all::<Point>(cx);
10614 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10615 let line_mode = self.selections.line_mode;
10616 let ranges = selections.into_iter().map(|s| {
10617 if line_mode {
10618 let start = Point::new(s.start.row, 0);
10619 let end = Point::new(
10620 s.end.row,
10621 display_map
10622 .buffer_snapshot
10623 .line_len(MultiBufferRow(s.end.row)),
10624 );
10625 (start..end, display_map.fold_placeholder.clone())
10626 } else {
10627 (s.start..s.end, display_map.fold_placeholder.clone())
10628 }
10629 });
10630 self.fold_ranges(ranges, true, cx);
10631 }
10632
10633 pub fn fold_ranges<T: ToOffset + Clone>(
10634 &mut self,
10635 ranges: impl IntoIterator<Item = (Range<T>, FoldPlaceholder)>,
10636 auto_scroll: bool,
10637 cx: &mut ViewContext<Self>,
10638 ) {
10639 let mut fold_ranges = Vec::new();
10640 let mut buffers_affected = HashMap::default();
10641 let multi_buffer = self.buffer().read(cx);
10642 for (fold_range, fold_text) in ranges {
10643 if let Some((_, buffer, _)) =
10644 multi_buffer.excerpt_containing(fold_range.start.clone(), cx)
10645 {
10646 buffers_affected.insert(buffer.read(cx).remote_id(), buffer);
10647 };
10648 fold_ranges.push((fold_range, fold_text));
10649 }
10650
10651 let mut ranges = fold_ranges.into_iter().peekable();
10652 if ranges.peek().is_some() {
10653 self.display_map.update(cx, |map, cx| map.fold(ranges, cx));
10654
10655 if auto_scroll {
10656 self.request_autoscroll(Autoscroll::fit(), cx);
10657 }
10658
10659 for buffer in buffers_affected.into_values() {
10660 self.sync_expanded_diff_hunks(buffer, cx);
10661 }
10662
10663 cx.notify();
10664
10665 if let Some(active_diagnostics) = self.active_diagnostics.take() {
10666 // Clear diagnostics block when folding a range that contains it.
10667 let snapshot = self.snapshot(cx);
10668 if snapshot.intersects_fold(active_diagnostics.primary_range.start) {
10669 drop(snapshot);
10670 self.active_diagnostics = Some(active_diagnostics);
10671 self.dismiss_diagnostics(cx);
10672 } else {
10673 self.active_diagnostics = Some(active_diagnostics);
10674 }
10675 }
10676
10677 self.scrollbar_marker_state.dirty = true;
10678 }
10679 }
10680
10681 pub fn unfold_ranges<T: ToOffset + Clone>(
10682 &mut self,
10683 ranges: impl IntoIterator<Item = Range<T>>,
10684 inclusive: bool,
10685 auto_scroll: bool,
10686 cx: &mut ViewContext<Self>,
10687 ) {
10688 let mut unfold_ranges = Vec::new();
10689 let mut buffers_affected = HashMap::default();
10690 let multi_buffer = self.buffer().read(cx);
10691 for range in ranges {
10692 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
10693 buffers_affected.insert(buffer.read(cx).remote_id(), buffer);
10694 };
10695 unfold_ranges.push(range);
10696 }
10697
10698 let mut ranges = unfold_ranges.into_iter().peekable();
10699 if ranges.peek().is_some() {
10700 self.display_map
10701 .update(cx, |map, cx| map.unfold(ranges, inclusive, cx));
10702 if auto_scroll {
10703 self.request_autoscroll(Autoscroll::fit(), cx);
10704 }
10705
10706 for buffer in buffers_affected.into_values() {
10707 self.sync_expanded_diff_hunks(buffer, cx);
10708 }
10709
10710 cx.notify();
10711 self.scrollbar_marker_state.dirty = true;
10712 self.active_indent_guides_state.dirty = true;
10713 }
10714 }
10715
10716 pub fn default_fold_placeholder(&self, cx: &AppContext) -> FoldPlaceholder {
10717 self.display_map.read(cx).fold_placeholder.clone()
10718 }
10719
10720 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut ViewContext<Self>) {
10721 if hovered != self.gutter_hovered {
10722 self.gutter_hovered = hovered;
10723 cx.notify();
10724 }
10725 }
10726
10727 pub fn insert_blocks(
10728 &mut self,
10729 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
10730 autoscroll: Option<Autoscroll>,
10731 cx: &mut ViewContext<Self>,
10732 ) -> Vec<CustomBlockId> {
10733 let blocks = self
10734 .display_map
10735 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
10736 if let Some(autoscroll) = autoscroll {
10737 self.request_autoscroll(autoscroll, cx);
10738 }
10739 cx.notify();
10740 blocks
10741 }
10742
10743 pub fn resize_blocks(
10744 &mut self,
10745 heights: HashMap<CustomBlockId, u32>,
10746 autoscroll: Option<Autoscroll>,
10747 cx: &mut ViewContext<Self>,
10748 ) {
10749 self.display_map
10750 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
10751 if let Some(autoscroll) = autoscroll {
10752 self.request_autoscroll(autoscroll, cx);
10753 }
10754 cx.notify();
10755 }
10756
10757 pub fn replace_blocks(
10758 &mut self,
10759 renderers: HashMap<CustomBlockId, RenderBlock>,
10760 autoscroll: Option<Autoscroll>,
10761 cx: &mut ViewContext<Self>,
10762 ) {
10763 self.display_map
10764 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
10765 if let Some(autoscroll) = autoscroll {
10766 self.request_autoscroll(autoscroll, cx);
10767 }
10768 cx.notify();
10769 }
10770
10771 pub fn remove_blocks(
10772 &mut self,
10773 block_ids: HashSet<CustomBlockId>,
10774 autoscroll: Option<Autoscroll>,
10775 cx: &mut ViewContext<Self>,
10776 ) {
10777 self.display_map.update(cx, |display_map, cx| {
10778 display_map.remove_blocks(block_ids, cx)
10779 });
10780 if let Some(autoscroll) = autoscroll {
10781 self.request_autoscroll(autoscroll, cx);
10782 }
10783 cx.notify();
10784 }
10785
10786 pub fn row_for_block(
10787 &self,
10788 block_id: CustomBlockId,
10789 cx: &mut ViewContext<Self>,
10790 ) -> Option<DisplayRow> {
10791 self.display_map
10792 .update(cx, |map, cx| map.row_for_block(block_id, cx))
10793 }
10794
10795 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
10796 self.focused_block = Some(focused_block);
10797 }
10798
10799 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
10800 self.focused_block.take()
10801 }
10802
10803 pub fn insert_creases(
10804 &mut self,
10805 creases: impl IntoIterator<Item = Crease>,
10806 cx: &mut ViewContext<Self>,
10807 ) -> Vec<CreaseId> {
10808 self.display_map
10809 .update(cx, |map, cx| map.insert_creases(creases, cx))
10810 }
10811
10812 pub fn remove_creases(
10813 &mut self,
10814 ids: impl IntoIterator<Item = CreaseId>,
10815 cx: &mut ViewContext<Self>,
10816 ) {
10817 self.display_map
10818 .update(cx, |map, cx| map.remove_creases(ids, cx));
10819 }
10820
10821 pub fn longest_row(&self, cx: &mut AppContext) -> DisplayRow {
10822 self.display_map
10823 .update(cx, |map, cx| map.snapshot(cx))
10824 .longest_row()
10825 }
10826
10827 pub fn max_point(&self, cx: &mut AppContext) -> DisplayPoint {
10828 self.display_map
10829 .update(cx, |map, cx| map.snapshot(cx))
10830 .max_point()
10831 }
10832
10833 pub fn text(&self, cx: &AppContext) -> String {
10834 self.buffer.read(cx).read(cx).text()
10835 }
10836
10837 pub fn text_option(&self, cx: &AppContext) -> Option<String> {
10838 let text = self.text(cx);
10839 let text = text.trim();
10840
10841 if text.is_empty() {
10842 return None;
10843 }
10844
10845 Some(text.to_string())
10846 }
10847
10848 pub fn set_text(&mut self, text: impl Into<Arc<str>>, cx: &mut ViewContext<Self>) {
10849 self.transact(cx, |this, cx| {
10850 this.buffer
10851 .read(cx)
10852 .as_singleton()
10853 .expect("you can only call set_text on editors for singleton buffers")
10854 .update(cx, |buffer, cx| buffer.set_text(text, cx));
10855 });
10856 }
10857
10858 pub fn display_text(&self, cx: &mut AppContext) -> String {
10859 self.display_map
10860 .update(cx, |map, cx| map.snapshot(cx))
10861 .text()
10862 }
10863
10864 pub fn wrap_guides(&self, cx: &AppContext) -> SmallVec<[(usize, bool); 2]> {
10865 let mut wrap_guides = smallvec::smallvec![];
10866
10867 if self.show_wrap_guides == Some(false) {
10868 return wrap_guides;
10869 }
10870
10871 let settings = self.buffer.read(cx).settings_at(0, cx);
10872 if settings.show_wrap_guides {
10873 if let SoftWrap::Column(soft_wrap) = self.soft_wrap_mode(cx) {
10874 wrap_guides.push((soft_wrap as usize, true));
10875 } else if let SoftWrap::Bounded(soft_wrap) = self.soft_wrap_mode(cx) {
10876 wrap_guides.push((soft_wrap as usize, true));
10877 }
10878 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
10879 }
10880
10881 wrap_guides
10882 }
10883
10884 pub fn soft_wrap_mode(&self, cx: &AppContext) -> SoftWrap {
10885 let settings = self.buffer.read(cx).settings_at(0, cx);
10886 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
10887 match mode {
10888 language_settings::SoftWrap::None => SoftWrap::None,
10889 language_settings::SoftWrap::PreferLine => SoftWrap::PreferLine,
10890 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
10891 language_settings::SoftWrap::PreferredLineLength => {
10892 SoftWrap::Column(settings.preferred_line_length)
10893 }
10894 language_settings::SoftWrap::Bounded => {
10895 SoftWrap::Bounded(settings.preferred_line_length)
10896 }
10897 }
10898 }
10899
10900 pub fn set_soft_wrap_mode(
10901 &mut self,
10902 mode: language_settings::SoftWrap,
10903 cx: &mut ViewContext<Self>,
10904 ) {
10905 self.soft_wrap_mode_override = Some(mode);
10906 cx.notify();
10907 }
10908
10909 pub fn set_style(&mut self, style: EditorStyle, cx: &mut ViewContext<Self>) {
10910 let rem_size = cx.rem_size();
10911 self.display_map.update(cx, |map, cx| {
10912 map.set_font(
10913 style.text.font(),
10914 style.text.font_size.to_pixels(rem_size),
10915 cx,
10916 )
10917 });
10918 self.style = Some(style);
10919 }
10920
10921 pub fn style(&self) -> Option<&EditorStyle> {
10922 self.style.as_ref()
10923 }
10924
10925 // Called by the element. This method is not designed to be called outside of the editor
10926 // element's layout code because it does not notify when rewrapping is computed synchronously.
10927 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut AppContext) -> bool {
10928 self.display_map
10929 .update(cx, |map, cx| map.set_wrap_width(width, cx))
10930 }
10931
10932 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, cx: &mut ViewContext<Self>) {
10933 if self.soft_wrap_mode_override.is_some() {
10934 self.soft_wrap_mode_override.take();
10935 } else {
10936 let soft_wrap = match self.soft_wrap_mode(cx) {
10937 SoftWrap::None | SoftWrap::PreferLine => language_settings::SoftWrap::EditorWidth,
10938 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
10939 language_settings::SoftWrap::PreferLine
10940 }
10941 };
10942 self.soft_wrap_mode_override = Some(soft_wrap);
10943 }
10944 cx.notify();
10945 }
10946
10947 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, cx: &mut ViewContext<Self>) {
10948 let Some(workspace) = self.workspace() else {
10949 return;
10950 };
10951 let fs = workspace.read(cx).app_state().fs.clone();
10952 let current_show = TabBarSettings::get_global(cx).show;
10953 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
10954 setting.show = Some(!current_show);
10955 });
10956 }
10957
10958 pub fn toggle_indent_guides(&mut self, _: &ToggleIndentGuides, cx: &mut ViewContext<Self>) {
10959 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
10960 self.buffer
10961 .read(cx)
10962 .settings_at(0, cx)
10963 .indent_guides
10964 .enabled
10965 });
10966 self.show_indent_guides = Some(!currently_enabled);
10967 cx.notify();
10968 }
10969
10970 fn should_show_indent_guides(&self) -> Option<bool> {
10971 self.show_indent_guides
10972 }
10973
10974 pub fn toggle_line_numbers(&mut self, _: &ToggleLineNumbers, cx: &mut ViewContext<Self>) {
10975 let mut editor_settings = EditorSettings::get_global(cx).clone();
10976 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
10977 EditorSettings::override_global(editor_settings, cx);
10978 }
10979
10980 pub fn should_use_relative_line_numbers(&self, cx: &WindowContext) -> bool {
10981 self.use_relative_line_numbers
10982 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
10983 }
10984
10985 pub fn toggle_relative_line_numbers(
10986 &mut self,
10987 _: &ToggleRelativeLineNumbers,
10988 cx: &mut ViewContext<Self>,
10989 ) {
10990 let is_relative = self.should_use_relative_line_numbers(cx);
10991 self.set_relative_line_number(Some(!is_relative), cx)
10992 }
10993
10994 pub fn set_relative_line_number(
10995 &mut self,
10996 is_relative: Option<bool>,
10997 cx: &mut ViewContext<Self>,
10998 ) {
10999 self.use_relative_line_numbers = is_relative;
11000 cx.notify();
11001 }
11002
11003 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut ViewContext<Self>) {
11004 self.show_gutter = show_gutter;
11005 cx.notify();
11006 }
11007
11008 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut ViewContext<Self>) {
11009 self.show_line_numbers = Some(show_line_numbers);
11010 cx.notify();
11011 }
11012
11013 pub fn set_show_git_diff_gutter(
11014 &mut self,
11015 show_git_diff_gutter: bool,
11016 cx: &mut ViewContext<Self>,
11017 ) {
11018 self.show_git_diff_gutter = Some(show_git_diff_gutter);
11019 cx.notify();
11020 }
11021
11022 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut ViewContext<Self>) {
11023 self.show_code_actions = Some(show_code_actions);
11024 cx.notify();
11025 }
11026
11027 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut ViewContext<Self>) {
11028 self.show_runnables = Some(show_runnables);
11029 cx.notify();
11030 }
11031
11032 pub fn set_masked(&mut self, masked: bool, cx: &mut ViewContext<Self>) {
11033 if self.display_map.read(cx).masked != masked {
11034 self.display_map.update(cx, |map, _| map.masked = masked);
11035 }
11036 cx.notify()
11037 }
11038
11039 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut ViewContext<Self>) {
11040 self.show_wrap_guides = Some(show_wrap_guides);
11041 cx.notify();
11042 }
11043
11044 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut ViewContext<Self>) {
11045 self.show_indent_guides = Some(show_indent_guides);
11046 cx.notify();
11047 }
11048
11049 pub fn working_directory(&self, cx: &WindowContext) -> Option<PathBuf> {
11050 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
11051 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
11052 if let Some(dir) = file.abs_path(cx).parent() {
11053 return Some(dir.to_owned());
11054 }
11055 }
11056
11057 if let Some(project_path) = buffer.read(cx).project_path(cx) {
11058 return Some(project_path.path.to_path_buf());
11059 }
11060 }
11061
11062 None
11063 }
11064
11065 pub fn reveal_in_finder(&mut self, _: &RevealInFileManager, cx: &mut ViewContext<Self>) {
11066 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
11067 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
11068 cx.reveal_path(&file.abs_path(cx));
11069 }
11070 }
11071 }
11072
11073 pub fn copy_path(&mut self, _: &CopyPath, cx: &mut ViewContext<Self>) {
11074 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
11075 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
11076 if let Some(path) = file.abs_path(cx).to_str() {
11077 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
11078 }
11079 }
11080 }
11081 }
11082
11083 pub fn copy_relative_path(&mut self, _: &CopyRelativePath, cx: &mut ViewContext<Self>) {
11084 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
11085 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
11086 if let Some(path) = file.path().to_str() {
11087 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
11088 }
11089 }
11090 }
11091 }
11092
11093 pub fn toggle_git_blame(&mut self, _: &ToggleGitBlame, cx: &mut ViewContext<Self>) {
11094 self.show_git_blame_gutter = !self.show_git_blame_gutter;
11095
11096 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
11097 self.start_git_blame(true, cx);
11098 }
11099
11100 cx.notify();
11101 }
11102
11103 pub fn toggle_git_blame_inline(
11104 &mut self,
11105 _: &ToggleGitBlameInline,
11106 cx: &mut ViewContext<Self>,
11107 ) {
11108 self.toggle_git_blame_inline_internal(true, cx);
11109 cx.notify();
11110 }
11111
11112 pub fn git_blame_inline_enabled(&self) -> bool {
11113 self.git_blame_inline_enabled
11114 }
11115
11116 pub fn toggle_selection_menu(&mut self, _: &ToggleSelectionMenu, cx: &mut ViewContext<Self>) {
11117 self.show_selection_menu = self
11118 .show_selection_menu
11119 .map(|show_selections_menu| !show_selections_menu)
11120 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
11121
11122 cx.notify();
11123 }
11124
11125 pub fn selection_menu_enabled(&self, cx: &AppContext) -> bool {
11126 self.show_selection_menu
11127 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
11128 }
11129
11130 fn start_git_blame(&mut self, user_triggered: bool, cx: &mut ViewContext<Self>) {
11131 if let Some(project) = self.project.as_ref() {
11132 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
11133 return;
11134 };
11135
11136 if buffer.read(cx).file().is_none() {
11137 return;
11138 }
11139
11140 let focused = self.focus_handle(cx).contains_focused(cx);
11141
11142 let project = project.clone();
11143 let blame =
11144 cx.new_model(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
11145 self.blame_subscription = Some(cx.observe(&blame, |_, _, cx| cx.notify()));
11146 self.blame = Some(blame);
11147 }
11148 }
11149
11150 fn toggle_git_blame_inline_internal(
11151 &mut self,
11152 user_triggered: bool,
11153 cx: &mut ViewContext<Self>,
11154 ) {
11155 if self.git_blame_inline_enabled {
11156 self.git_blame_inline_enabled = false;
11157 self.show_git_blame_inline = false;
11158 self.show_git_blame_inline_delay_task.take();
11159 } else {
11160 self.git_blame_inline_enabled = true;
11161 self.start_git_blame_inline(user_triggered, cx);
11162 }
11163
11164 cx.notify();
11165 }
11166
11167 fn start_git_blame_inline(&mut self, user_triggered: bool, cx: &mut ViewContext<Self>) {
11168 self.start_git_blame(user_triggered, cx);
11169
11170 if ProjectSettings::get_global(cx)
11171 .git
11172 .inline_blame_delay()
11173 .is_some()
11174 {
11175 self.start_inline_blame_timer(cx);
11176 } else {
11177 self.show_git_blame_inline = true
11178 }
11179 }
11180
11181 pub fn blame(&self) -> Option<&Model<GitBlame>> {
11182 self.blame.as_ref()
11183 }
11184
11185 pub fn render_git_blame_gutter(&mut self, cx: &mut WindowContext) -> bool {
11186 self.show_git_blame_gutter && self.has_blame_entries(cx)
11187 }
11188
11189 pub fn render_git_blame_inline(&mut self, cx: &mut WindowContext) -> bool {
11190 self.show_git_blame_inline
11191 && self.focus_handle.is_focused(cx)
11192 && !self.newest_selection_head_on_empty_line(cx)
11193 && self.has_blame_entries(cx)
11194 }
11195
11196 fn has_blame_entries(&self, cx: &mut WindowContext) -> bool {
11197 self.blame()
11198 .map_or(false, |blame| blame.read(cx).has_generated_entries())
11199 }
11200
11201 fn newest_selection_head_on_empty_line(&mut self, cx: &mut WindowContext) -> bool {
11202 let cursor_anchor = self.selections.newest_anchor().head();
11203
11204 let snapshot = self.buffer.read(cx).snapshot(cx);
11205 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
11206
11207 snapshot.line_len(buffer_row) == 0
11208 }
11209
11210 fn get_permalink_to_line(&mut self, cx: &mut ViewContext<Self>) -> Result<url::Url> {
11211 let (path, selection, repo) = maybe!({
11212 let project_handle = self.project.as_ref()?.clone();
11213 let project = project_handle.read(cx);
11214
11215 let selection = self.selections.newest::<Point>(cx);
11216 let selection_range = selection.range();
11217
11218 let (buffer, selection) = if let Some(buffer) = self.buffer().read(cx).as_singleton() {
11219 (buffer, selection_range.start.row..selection_range.end.row)
11220 } else {
11221 let buffer_ranges = self
11222 .buffer()
11223 .read(cx)
11224 .range_to_buffer_ranges(selection_range, cx);
11225
11226 let (buffer, range, _) = if selection.reversed {
11227 buffer_ranges.first()
11228 } else {
11229 buffer_ranges.last()
11230 }?;
11231
11232 let snapshot = buffer.read(cx).snapshot();
11233 let selection = text::ToPoint::to_point(&range.start, &snapshot).row
11234 ..text::ToPoint::to_point(&range.end, &snapshot).row;
11235 (buffer.clone(), selection)
11236 };
11237
11238 let path = buffer
11239 .read(cx)
11240 .file()?
11241 .as_local()?
11242 .path()
11243 .to_str()?
11244 .to_string();
11245 let repo = project.get_repo(&buffer.read(cx).project_path(cx)?, cx)?;
11246 Some((path, selection, repo))
11247 })
11248 .ok_or_else(|| anyhow!("unable to open git repository"))?;
11249
11250 const REMOTE_NAME: &str = "origin";
11251 let origin_url = repo
11252 .remote_url(REMOTE_NAME)
11253 .ok_or_else(|| anyhow!("remote \"{REMOTE_NAME}\" not found"))?;
11254 let sha = repo
11255 .head_sha()
11256 .ok_or_else(|| anyhow!("failed to read HEAD SHA"))?;
11257
11258 let (provider, remote) =
11259 parse_git_remote_url(GitHostingProviderRegistry::default_global(cx), &origin_url)
11260 .ok_or_else(|| anyhow!("failed to parse Git remote URL"))?;
11261
11262 Ok(provider.build_permalink(
11263 remote,
11264 BuildPermalinkParams {
11265 sha: &sha,
11266 path: &path,
11267 selection: Some(selection),
11268 },
11269 ))
11270 }
11271
11272 pub fn copy_permalink_to_line(&mut self, _: &CopyPermalinkToLine, cx: &mut ViewContext<Self>) {
11273 let permalink = self.get_permalink_to_line(cx);
11274
11275 match permalink {
11276 Ok(permalink) => {
11277 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
11278 }
11279 Err(err) => {
11280 let message = format!("Failed to copy permalink: {err}");
11281
11282 Err::<(), anyhow::Error>(err).log_err();
11283
11284 if let Some(workspace) = self.workspace() {
11285 workspace.update(cx, |workspace, cx| {
11286 struct CopyPermalinkToLine;
11287
11288 workspace.show_toast(
11289 Toast::new(NotificationId::unique::<CopyPermalinkToLine>(), message),
11290 cx,
11291 )
11292 })
11293 }
11294 }
11295 }
11296 }
11297
11298 pub fn copy_file_location(&mut self, _: &CopyFileLocation, cx: &mut ViewContext<Self>) {
11299 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
11300 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
11301 if let Some(path) = file.path().to_str() {
11302 let selection = self.selections.newest::<Point>(cx).start.row + 1;
11303 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
11304 }
11305 }
11306 }
11307 }
11308
11309 pub fn open_permalink_to_line(&mut self, _: &OpenPermalinkToLine, cx: &mut ViewContext<Self>) {
11310 let permalink = self.get_permalink_to_line(cx);
11311
11312 match permalink {
11313 Ok(permalink) => {
11314 cx.open_url(permalink.as_ref());
11315 }
11316 Err(err) => {
11317 let message = format!("Failed to open permalink: {err}");
11318
11319 Err::<(), anyhow::Error>(err).log_err();
11320
11321 if let Some(workspace) = self.workspace() {
11322 workspace.update(cx, |workspace, cx| {
11323 struct OpenPermalinkToLine;
11324
11325 workspace.show_toast(
11326 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
11327 cx,
11328 )
11329 })
11330 }
11331 }
11332 }
11333 }
11334
11335 /// Adds or removes (on `None` color) a highlight for the rows corresponding to the anchor range given.
11336 /// On matching anchor range, replaces the old highlight; does not clear the other existing highlights.
11337 /// If multiple anchor ranges will produce highlights for the same row, the last range added will be used.
11338 pub fn highlight_rows<T: 'static>(
11339 &mut self,
11340 rows: RangeInclusive<Anchor>,
11341 color: Option<Hsla>,
11342 should_autoscroll: bool,
11343 cx: &mut ViewContext<Self>,
11344 ) {
11345 let snapshot = self.buffer().read(cx).snapshot(cx);
11346 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
11347 let existing_highlight_index = row_highlights.binary_search_by(|highlight| {
11348 highlight
11349 .range
11350 .start()
11351 .cmp(rows.start(), &snapshot)
11352 .then(highlight.range.end().cmp(rows.end(), &snapshot))
11353 });
11354 match (color, existing_highlight_index) {
11355 (Some(_), Ok(ix)) | (_, Err(ix)) => row_highlights.insert(
11356 ix,
11357 RowHighlight {
11358 index: post_inc(&mut self.highlight_order),
11359 range: rows,
11360 should_autoscroll,
11361 color,
11362 },
11363 ),
11364 (None, Ok(i)) => {
11365 row_highlights.remove(i);
11366 }
11367 }
11368 }
11369
11370 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
11371 pub fn clear_row_highlights<T: 'static>(&mut self) {
11372 self.highlighted_rows.remove(&TypeId::of::<T>());
11373 }
11374
11375 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
11376 pub fn highlighted_rows<T: 'static>(
11377 &self,
11378 ) -> Option<impl Iterator<Item = (&RangeInclusive<Anchor>, Option<&Hsla>)>> {
11379 Some(
11380 self.highlighted_rows
11381 .get(&TypeId::of::<T>())?
11382 .iter()
11383 .map(|highlight| (&highlight.range, highlight.color.as_ref())),
11384 )
11385 }
11386
11387 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
11388 /// Rerturns a map of display rows that are highlighted and their corresponding highlight color.
11389 /// Allows to ignore certain kinds of highlights.
11390 pub fn highlighted_display_rows(
11391 &mut self,
11392 cx: &mut WindowContext,
11393 ) -> BTreeMap<DisplayRow, Hsla> {
11394 let snapshot = self.snapshot(cx);
11395 let mut used_highlight_orders = HashMap::default();
11396 self.highlighted_rows
11397 .iter()
11398 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
11399 .fold(
11400 BTreeMap::<DisplayRow, Hsla>::new(),
11401 |mut unique_rows, highlight| {
11402 let start_row = highlight.range.start().to_display_point(&snapshot).row();
11403 let end_row = highlight.range.end().to_display_point(&snapshot).row();
11404 for row in start_row.0..=end_row.0 {
11405 let used_index =
11406 used_highlight_orders.entry(row).or_insert(highlight.index);
11407 if highlight.index >= *used_index {
11408 *used_index = highlight.index;
11409 match highlight.color {
11410 Some(hsla) => unique_rows.insert(DisplayRow(row), hsla),
11411 None => unique_rows.remove(&DisplayRow(row)),
11412 };
11413 }
11414 }
11415 unique_rows
11416 },
11417 )
11418 }
11419
11420 pub fn highlighted_display_row_for_autoscroll(
11421 &self,
11422 snapshot: &DisplaySnapshot,
11423 ) -> Option<DisplayRow> {
11424 self.highlighted_rows
11425 .values()
11426 .flat_map(|highlighted_rows| highlighted_rows.iter())
11427 .filter_map(|highlight| {
11428 if highlight.color.is_none() || !highlight.should_autoscroll {
11429 return None;
11430 }
11431 Some(highlight.range.start().to_display_point(snapshot).row())
11432 })
11433 .min()
11434 }
11435
11436 pub fn set_search_within_ranges(
11437 &mut self,
11438 ranges: &[Range<Anchor>],
11439 cx: &mut ViewContext<Self>,
11440 ) {
11441 self.highlight_background::<SearchWithinRange>(
11442 ranges,
11443 |colors| colors.editor_document_highlight_read_background,
11444 cx,
11445 )
11446 }
11447
11448 pub fn set_breadcrumb_header(&mut self, new_header: String) {
11449 self.breadcrumb_header = Some(new_header);
11450 }
11451
11452 pub fn clear_search_within_ranges(&mut self, cx: &mut ViewContext<Self>) {
11453 self.clear_background_highlights::<SearchWithinRange>(cx);
11454 }
11455
11456 pub fn highlight_background<T: 'static>(
11457 &mut self,
11458 ranges: &[Range<Anchor>],
11459 color_fetcher: fn(&ThemeColors) -> Hsla,
11460 cx: &mut ViewContext<Self>,
11461 ) {
11462 self.background_highlights
11463 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
11464 self.scrollbar_marker_state.dirty = true;
11465 cx.notify();
11466 }
11467
11468 pub fn clear_background_highlights<T: 'static>(
11469 &mut self,
11470 cx: &mut ViewContext<Self>,
11471 ) -> Option<BackgroundHighlight> {
11472 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
11473 if !text_highlights.1.is_empty() {
11474 self.scrollbar_marker_state.dirty = true;
11475 cx.notify();
11476 }
11477 Some(text_highlights)
11478 }
11479
11480 pub fn highlight_gutter<T: 'static>(
11481 &mut self,
11482 ranges: &[Range<Anchor>],
11483 color_fetcher: fn(&AppContext) -> Hsla,
11484 cx: &mut ViewContext<Self>,
11485 ) {
11486 self.gutter_highlights
11487 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
11488 cx.notify();
11489 }
11490
11491 pub fn clear_gutter_highlights<T: 'static>(
11492 &mut self,
11493 cx: &mut ViewContext<Self>,
11494 ) -> Option<GutterHighlight> {
11495 cx.notify();
11496 self.gutter_highlights.remove(&TypeId::of::<T>())
11497 }
11498
11499 #[cfg(feature = "test-support")]
11500 pub fn all_text_background_highlights(
11501 &mut self,
11502 cx: &mut ViewContext<Self>,
11503 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
11504 let snapshot = self.snapshot(cx);
11505 let buffer = &snapshot.buffer_snapshot;
11506 let start = buffer.anchor_before(0);
11507 let end = buffer.anchor_after(buffer.len());
11508 let theme = cx.theme().colors();
11509 self.background_highlights_in_range(start..end, &snapshot, theme)
11510 }
11511
11512 #[cfg(feature = "test-support")]
11513 pub fn search_background_highlights(
11514 &mut self,
11515 cx: &mut ViewContext<Self>,
11516 ) -> Vec<Range<Point>> {
11517 let snapshot = self.buffer().read(cx).snapshot(cx);
11518
11519 let highlights = self
11520 .background_highlights
11521 .get(&TypeId::of::<items::BufferSearchHighlights>());
11522
11523 if let Some((_color, ranges)) = highlights {
11524 ranges
11525 .iter()
11526 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
11527 .collect_vec()
11528 } else {
11529 vec![]
11530 }
11531 }
11532
11533 fn document_highlights_for_position<'a>(
11534 &'a self,
11535 position: Anchor,
11536 buffer: &'a MultiBufferSnapshot,
11537 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
11538 let read_highlights = self
11539 .background_highlights
11540 .get(&TypeId::of::<DocumentHighlightRead>())
11541 .map(|h| &h.1);
11542 let write_highlights = self
11543 .background_highlights
11544 .get(&TypeId::of::<DocumentHighlightWrite>())
11545 .map(|h| &h.1);
11546 let left_position = position.bias_left(buffer);
11547 let right_position = position.bias_right(buffer);
11548 read_highlights
11549 .into_iter()
11550 .chain(write_highlights)
11551 .flat_map(move |ranges| {
11552 let start_ix = match ranges.binary_search_by(|probe| {
11553 let cmp = probe.end.cmp(&left_position, buffer);
11554 if cmp.is_ge() {
11555 Ordering::Greater
11556 } else {
11557 Ordering::Less
11558 }
11559 }) {
11560 Ok(i) | Err(i) => i,
11561 };
11562
11563 ranges[start_ix..]
11564 .iter()
11565 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
11566 })
11567 }
11568
11569 pub fn has_background_highlights<T: 'static>(&self) -> bool {
11570 self.background_highlights
11571 .get(&TypeId::of::<T>())
11572 .map_or(false, |(_, highlights)| !highlights.is_empty())
11573 }
11574
11575 pub fn background_highlights_in_range(
11576 &self,
11577 search_range: Range<Anchor>,
11578 display_snapshot: &DisplaySnapshot,
11579 theme: &ThemeColors,
11580 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
11581 let mut results = Vec::new();
11582 for (color_fetcher, ranges) in self.background_highlights.values() {
11583 let color = color_fetcher(theme);
11584 let start_ix = match ranges.binary_search_by(|probe| {
11585 let cmp = probe
11586 .end
11587 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
11588 if cmp.is_gt() {
11589 Ordering::Greater
11590 } else {
11591 Ordering::Less
11592 }
11593 }) {
11594 Ok(i) | Err(i) => i,
11595 };
11596 for range in &ranges[start_ix..] {
11597 if range
11598 .start
11599 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
11600 .is_ge()
11601 {
11602 break;
11603 }
11604
11605 let start = range.start.to_display_point(display_snapshot);
11606 let end = range.end.to_display_point(display_snapshot);
11607 results.push((start..end, color))
11608 }
11609 }
11610 results
11611 }
11612
11613 pub fn background_highlight_row_ranges<T: 'static>(
11614 &self,
11615 search_range: Range<Anchor>,
11616 display_snapshot: &DisplaySnapshot,
11617 count: usize,
11618 ) -> Vec<RangeInclusive<DisplayPoint>> {
11619 let mut results = Vec::new();
11620 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
11621 return vec![];
11622 };
11623
11624 let start_ix = match ranges.binary_search_by(|probe| {
11625 let cmp = probe
11626 .end
11627 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
11628 if cmp.is_gt() {
11629 Ordering::Greater
11630 } else {
11631 Ordering::Less
11632 }
11633 }) {
11634 Ok(i) | Err(i) => i,
11635 };
11636 let mut push_region = |start: Option<Point>, end: Option<Point>| {
11637 if let (Some(start_display), Some(end_display)) = (start, end) {
11638 results.push(
11639 start_display.to_display_point(display_snapshot)
11640 ..=end_display.to_display_point(display_snapshot),
11641 );
11642 }
11643 };
11644 let mut start_row: Option<Point> = None;
11645 let mut end_row: Option<Point> = None;
11646 if ranges.len() > count {
11647 return Vec::new();
11648 }
11649 for range in &ranges[start_ix..] {
11650 if range
11651 .start
11652 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
11653 .is_ge()
11654 {
11655 break;
11656 }
11657 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
11658 if let Some(current_row) = &end_row {
11659 if end.row == current_row.row {
11660 continue;
11661 }
11662 }
11663 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
11664 if start_row.is_none() {
11665 assert_eq!(end_row, None);
11666 start_row = Some(start);
11667 end_row = Some(end);
11668 continue;
11669 }
11670 if let Some(current_end) = end_row.as_mut() {
11671 if start.row > current_end.row + 1 {
11672 push_region(start_row, end_row);
11673 start_row = Some(start);
11674 end_row = Some(end);
11675 } else {
11676 // Merge two hunks.
11677 *current_end = end;
11678 }
11679 } else {
11680 unreachable!();
11681 }
11682 }
11683 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
11684 push_region(start_row, end_row);
11685 results
11686 }
11687
11688 pub fn gutter_highlights_in_range(
11689 &self,
11690 search_range: Range<Anchor>,
11691 display_snapshot: &DisplaySnapshot,
11692 cx: &AppContext,
11693 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
11694 let mut results = Vec::new();
11695 for (color_fetcher, ranges) in self.gutter_highlights.values() {
11696 let color = color_fetcher(cx);
11697 let start_ix = match ranges.binary_search_by(|probe| {
11698 let cmp = probe
11699 .end
11700 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
11701 if cmp.is_gt() {
11702 Ordering::Greater
11703 } else {
11704 Ordering::Less
11705 }
11706 }) {
11707 Ok(i) | Err(i) => i,
11708 };
11709 for range in &ranges[start_ix..] {
11710 if range
11711 .start
11712 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
11713 .is_ge()
11714 {
11715 break;
11716 }
11717
11718 let start = range.start.to_display_point(display_snapshot);
11719 let end = range.end.to_display_point(display_snapshot);
11720 results.push((start..end, color))
11721 }
11722 }
11723 results
11724 }
11725
11726 /// Get the text ranges corresponding to the redaction query
11727 pub fn redacted_ranges(
11728 &self,
11729 search_range: Range<Anchor>,
11730 display_snapshot: &DisplaySnapshot,
11731 cx: &WindowContext,
11732 ) -> Vec<Range<DisplayPoint>> {
11733 display_snapshot
11734 .buffer_snapshot
11735 .redacted_ranges(search_range, |file| {
11736 if let Some(file) = file {
11737 file.is_private()
11738 && EditorSettings::get(
11739 Some(SettingsLocation {
11740 worktree_id: file.worktree_id(cx),
11741 path: file.path().as_ref(),
11742 }),
11743 cx,
11744 )
11745 .redact_private_values
11746 } else {
11747 false
11748 }
11749 })
11750 .map(|range| {
11751 range.start.to_display_point(display_snapshot)
11752 ..range.end.to_display_point(display_snapshot)
11753 })
11754 .collect()
11755 }
11756
11757 pub fn highlight_text<T: 'static>(
11758 &mut self,
11759 ranges: Vec<Range<Anchor>>,
11760 style: HighlightStyle,
11761 cx: &mut ViewContext<Self>,
11762 ) {
11763 self.display_map.update(cx, |map, _| {
11764 map.highlight_text(TypeId::of::<T>(), ranges, style)
11765 });
11766 cx.notify();
11767 }
11768
11769 pub(crate) fn highlight_inlays<T: 'static>(
11770 &mut self,
11771 highlights: Vec<InlayHighlight>,
11772 style: HighlightStyle,
11773 cx: &mut ViewContext<Self>,
11774 ) {
11775 self.display_map.update(cx, |map, _| {
11776 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
11777 });
11778 cx.notify();
11779 }
11780
11781 pub fn text_highlights<'a, T: 'static>(
11782 &'a self,
11783 cx: &'a AppContext,
11784 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
11785 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
11786 }
11787
11788 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut ViewContext<Self>) {
11789 let cleared = self
11790 .display_map
11791 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
11792 if cleared {
11793 cx.notify();
11794 }
11795 }
11796
11797 pub fn show_local_cursors(&self, cx: &WindowContext) -> bool {
11798 (self.read_only(cx) || self.blink_manager.read(cx).visible())
11799 && self.focus_handle.is_focused(cx)
11800 }
11801
11802 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut ViewContext<Self>) {
11803 self.show_cursor_when_unfocused = is_enabled;
11804 cx.notify();
11805 }
11806
11807 fn on_buffer_changed(&mut self, _: Model<MultiBuffer>, cx: &mut ViewContext<Self>) {
11808 cx.notify();
11809 }
11810
11811 fn on_buffer_event(
11812 &mut self,
11813 multibuffer: Model<MultiBuffer>,
11814 event: &multi_buffer::Event,
11815 cx: &mut ViewContext<Self>,
11816 ) {
11817 match event {
11818 multi_buffer::Event::Edited {
11819 singleton_buffer_edited,
11820 } => {
11821 self.scrollbar_marker_state.dirty = true;
11822 self.active_indent_guides_state.dirty = true;
11823 self.refresh_active_diagnostics(cx);
11824 self.refresh_code_actions(cx);
11825 if self.has_active_inline_completion(cx) {
11826 self.update_visible_inline_completion(cx);
11827 }
11828 cx.emit(EditorEvent::BufferEdited);
11829 cx.emit(SearchEvent::MatchesInvalidated);
11830 if *singleton_buffer_edited {
11831 if let Some(project) = &self.project {
11832 let project = project.read(cx);
11833 #[allow(clippy::mutable_key_type)]
11834 let languages_affected = multibuffer
11835 .read(cx)
11836 .all_buffers()
11837 .into_iter()
11838 .filter_map(|buffer| {
11839 let buffer = buffer.read(cx);
11840 let language = buffer.language()?;
11841 if project.is_local()
11842 && project.language_servers_for_buffer(buffer, cx).count() == 0
11843 {
11844 None
11845 } else {
11846 Some(language)
11847 }
11848 })
11849 .cloned()
11850 .collect::<HashSet<_>>();
11851 if !languages_affected.is_empty() {
11852 self.refresh_inlay_hints(
11853 InlayHintRefreshReason::BufferEdited(languages_affected),
11854 cx,
11855 );
11856 }
11857 }
11858 }
11859
11860 let Some(project) = &self.project else { return };
11861 let telemetry = project.read(cx).client().telemetry().clone();
11862 refresh_linked_ranges(self, cx);
11863 telemetry.log_edit_event("editor");
11864 }
11865 multi_buffer::Event::ExcerptsAdded {
11866 buffer,
11867 predecessor,
11868 excerpts,
11869 } => {
11870 self.tasks_update_task = Some(self.refresh_runnables(cx));
11871 cx.emit(EditorEvent::ExcerptsAdded {
11872 buffer: buffer.clone(),
11873 predecessor: *predecessor,
11874 excerpts: excerpts.clone(),
11875 });
11876 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
11877 }
11878 multi_buffer::Event::ExcerptsRemoved { ids } => {
11879 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
11880 cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
11881 }
11882 multi_buffer::Event::ExcerptsEdited { ids } => {
11883 cx.emit(EditorEvent::ExcerptsEdited { ids: ids.clone() })
11884 }
11885 multi_buffer::Event::ExcerptsExpanded { ids } => {
11886 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
11887 }
11888 multi_buffer::Event::Reparsed(buffer_id) => {
11889 self.tasks_update_task = Some(self.refresh_runnables(cx));
11890
11891 cx.emit(EditorEvent::Reparsed(*buffer_id));
11892 }
11893 multi_buffer::Event::LanguageChanged(buffer_id) => {
11894 linked_editing_ranges::refresh_linked_ranges(self, cx);
11895 cx.emit(EditorEvent::Reparsed(*buffer_id));
11896 cx.notify();
11897 }
11898 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
11899 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
11900 multi_buffer::Event::FileHandleChanged | multi_buffer::Event::Reloaded => {
11901 cx.emit(EditorEvent::TitleChanged)
11902 }
11903 multi_buffer::Event::DiffBaseChanged => {
11904 self.scrollbar_marker_state.dirty = true;
11905 cx.emit(EditorEvent::DiffBaseChanged);
11906 cx.notify();
11907 }
11908 multi_buffer::Event::DiffUpdated { buffer } => {
11909 self.sync_expanded_diff_hunks(buffer.clone(), cx);
11910 cx.notify();
11911 }
11912 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
11913 multi_buffer::Event::DiagnosticsUpdated => {
11914 self.refresh_active_diagnostics(cx);
11915 self.scrollbar_marker_state.dirty = true;
11916 cx.notify();
11917 }
11918 _ => {}
11919 };
11920 }
11921
11922 fn on_display_map_changed(&mut self, _: Model<DisplayMap>, cx: &mut ViewContext<Self>) {
11923 cx.notify();
11924 }
11925
11926 fn settings_changed(&mut self, cx: &mut ViewContext<Self>) {
11927 self.tasks_update_task = Some(self.refresh_runnables(cx));
11928 self.refresh_inline_completion(true, false, cx);
11929 self.refresh_inlay_hints(
11930 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
11931 self.selections.newest_anchor().head(),
11932 &self.buffer.read(cx).snapshot(cx),
11933 cx,
11934 )),
11935 cx,
11936 );
11937
11938 let old_cursor_shape = self.cursor_shape;
11939
11940 {
11941 let editor_settings = EditorSettings::get_global(cx);
11942 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
11943 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
11944 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
11945 }
11946
11947 if old_cursor_shape != self.cursor_shape {
11948 cx.emit(EditorEvent::CursorShapeChanged);
11949 }
11950
11951 let project_settings = ProjectSettings::get_global(cx);
11952 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
11953
11954 if self.mode == EditorMode::Full {
11955 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
11956 if self.git_blame_inline_enabled != inline_blame_enabled {
11957 self.toggle_git_blame_inline_internal(false, cx);
11958 }
11959 }
11960
11961 cx.notify();
11962 }
11963
11964 pub fn set_searchable(&mut self, searchable: bool) {
11965 self.searchable = searchable;
11966 }
11967
11968 pub fn searchable(&self) -> bool {
11969 self.searchable
11970 }
11971
11972 fn open_proposed_changes_editor(
11973 &mut self,
11974 _: &OpenProposedChangesEditor,
11975 cx: &mut ViewContext<Self>,
11976 ) {
11977 let Some(workspace) = self.workspace() else {
11978 cx.propagate();
11979 return;
11980 };
11981
11982 let buffer = self.buffer.read(cx);
11983 let mut new_selections_by_buffer = HashMap::default();
11984 for selection in self.selections.all::<usize>(cx) {
11985 for (buffer, mut range, _) in
11986 buffer.range_to_buffer_ranges(selection.start..selection.end, cx)
11987 {
11988 if selection.reversed {
11989 mem::swap(&mut range.start, &mut range.end);
11990 }
11991 let mut range = range.to_point(buffer.read(cx));
11992 range.start.column = 0;
11993 range.end.column = buffer.read(cx).line_len(range.end.row);
11994 new_selections_by_buffer
11995 .entry(buffer)
11996 .or_insert(Vec::new())
11997 .push(range)
11998 }
11999 }
12000
12001 let proposed_changes_buffers = new_selections_by_buffer
12002 .into_iter()
12003 .map(|(buffer, ranges)| ProposedChangesBuffer { buffer, ranges })
12004 .collect::<Vec<_>>();
12005 let proposed_changes_editor = cx.new_view(|cx| {
12006 ProposedChangesEditor::new(proposed_changes_buffers, self.project.clone(), cx)
12007 });
12008
12009 cx.window_context().defer(move |cx| {
12010 workspace.update(cx, |workspace, cx| {
12011 workspace.active_pane().update(cx, |pane, cx| {
12012 pane.add_item(Box::new(proposed_changes_editor), true, true, None, cx);
12013 });
12014 });
12015 });
12016 }
12017
12018 fn open_excerpts_in_split(&mut self, _: &OpenExcerptsSplit, cx: &mut ViewContext<Self>) {
12019 self.open_excerpts_common(true, cx)
12020 }
12021
12022 fn open_excerpts(&mut self, _: &OpenExcerpts, cx: &mut ViewContext<Self>) {
12023 self.open_excerpts_common(false, cx)
12024 }
12025
12026 fn open_excerpts_common(&mut self, split: bool, cx: &mut ViewContext<Self>) {
12027 let buffer = self.buffer.read(cx);
12028 if buffer.is_singleton() {
12029 cx.propagate();
12030 return;
12031 }
12032
12033 let Some(workspace) = self.workspace() else {
12034 cx.propagate();
12035 return;
12036 };
12037
12038 let mut new_selections_by_buffer = HashMap::default();
12039 for selection in self.selections.all::<usize>(cx) {
12040 for (buffer, mut range, _) in
12041 buffer.range_to_buffer_ranges(selection.start..selection.end, cx)
12042 {
12043 if selection.reversed {
12044 mem::swap(&mut range.start, &mut range.end);
12045 }
12046 new_selections_by_buffer
12047 .entry(buffer)
12048 .or_insert(Vec::new())
12049 .push(range)
12050 }
12051 }
12052
12053 // We defer the pane interaction because we ourselves are a workspace item
12054 // and activating a new item causes the pane to call a method on us reentrantly,
12055 // which panics if we're on the stack.
12056 cx.window_context().defer(move |cx| {
12057 workspace.update(cx, |workspace, cx| {
12058 let pane = if split {
12059 workspace.adjacent_pane(cx)
12060 } else {
12061 workspace.active_pane().clone()
12062 };
12063
12064 for (buffer, ranges) in new_selections_by_buffer {
12065 let editor =
12066 workspace.open_project_item::<Self>(pane.clone(), buffer, true, true, cx);
12067 editor.update(cx, |editor, cx| {
12068 editor.change_selections(Some(Autoscroll::newest()), cx, |s| {
12069 s.select_ranges(ranges);
12070 });
12071 });
12072 }
12073 })
12074 });
12075 }
12076
12077 fn jump(
12078 &mut self,
12079 path: ProjectPath,
12080 position: Point,
12081 anchor: language::Anchor,
12082 offset_from_top: u32,
12083 cx: &mut ViewContext<Self>,
12084 ) {
12085 let workspace = self.workspace();
12086 cx.spawn(|_, mut cx| async move {
12087 let workspace = workspace.ok_or_else(|| anyhow!("cannot jump without workspace"))?;
12088 let editor = workspace.update(&mut cx, |workspace, cx| {
12089 // Reset the preview item id before opening the new item
12090 workspace.active_pane().update(cx, |pane, cx| {
12091 pane.set_preview_item_id(None, cx);
12092 });
12093 workspace.open_path_preview(path, None, true, true, cx)
12094 })?;
12095 let editor = editor
12096 .await?
12097 .downcast::<Editor>()
12098 .ok_or_else(|| anyhow!("opened item was not an editor"))?
12099 .downgrade();
12100 editor.update(&mut cx, |editor, cx| {
12101 let buffer = editor
12102 .buffer()
12103 .read(cx)
12104 .as_singleton()
12105 .ok_or_else(|| anyhow!("cannot jump in a multi-buffer"))?;
12106 let buffer = buffer.read(cx);
12107 let cursor = if buffer.can_resolve(&anchor) {
12108 language::ToPoint::to_point(&anchor, buffer)
12109 } else {
12110 buffer.clip_point(position, Bias::Left)
12111 };
12112
12113 let nav_history = editor.nav_history.take();
12114 editor.change_selections(
12115 Some(Autoscroll::top_relative(offset_from_top as usize)),
12116 cx,
12117 |s| {
12118 s.select_ranges([cursor..cursor]);
12119 },
12120 );
12121 editor.nav_history = nav_history;
12122
12123 anyhow::Ok(())
12124 })??;
12125
12126 anyhow::Ok(())
12127 })
12128 .detach_and_log_err(cx);
12129 }
12130
12131 fn marked_text_ranges(&self, cx: &AppContext) -> Option<Vec<Range<OffsetUtf16>>> {
12132 let snapshot = self.buffer.read(cx).read(cx);
12133 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
12134 Some(
12135 ranges
12136 .iter()
12137 .map(move |range| {
12138 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
12139 })
12140 .collect(),
12141 )
12142 }
12143
12144 fn selection_replacement_ranges(
12145 &self,
12146 range: Range<OffsetUtf16>,
12147 cx: &AppContext,
12148 ) -> Vec<Range<OffsetUtf16>> {
12149 let selections = self.selections.all::<OffsetUtf16>(cx);
12150 let newest_selection = selections
12151 .iter()
12152 .max_by_key(|selection| selection.id)
12153 .unwrap();
12154 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
12155 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
12156 let snapshot = self.buffer.read(cx).read(cx);
12157 selections
12158 .into_iter()
12159 .map(|mut selection| {
12160 selection.start.0 =
12161 (selection.start.0 as isize).saturating_add(start_delta) as usize;
12162 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
12163 snapshot.clip_offset_utf16(selection.start, Bias::Left)
12164 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
12165 })
12166 .collect()
12167 }
12168
12169 fn report_editor_event(
12170 &self,
12171 operation: &'static str,
12172 file_extension: Option<String>,
12173 cx: &AppContext,
12174 ) {
12175 if cfg!(any(test, feature = "test-support")) {
12176 return;
12177 }
12178
12179 let Some(project) = &self.project else { return };
12180
12181 // If None, we are in a file without an extension
12182 let file = self
12183 .buffer
12184 .read(cx)
12185 .as_singleton()
12186 .and_then(|b| b.read(cx).file());
12187 let file_extension = file_extension.or(file
12188 .as_ref()
12189 .and_then(|file| Path::new(file.file_name(cx)).extension())
12190 .and_then(|e| e.to_str())
12191 .map(|a| a.to_string()));
12192
12193 let vim_mode = cx
12194 .global::<SettingsStore>()
12195 .raw_user_settings()
12196 .get("vim_mode")
12197 == Some(&serde_json::Value::Bool(true));
12198
12199 let copilot_enabled = all_language_settings(file, cx).inline_completions.provider
12200 == language::language_settings::InlineCompletionProvider::Copilot;
12201 let copilot_enabled_for_language = self
12202 .buffer
12203 .read(cx)
12204 .settings_at(0, cx)
12205 .show_inline_completions;
12206
12207 let telemetry = project.read(cx).client().telemetry().clone();
12208 telemetry.report_editor_event(
12209 file_extension,
12210 vim_mode,
12211 operation,
12212 copilot_enabled,
12213 copilot_enabled_for_language,
12214 )
12215 }
12216
12217 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
12218 /// with each line being an array of {text, highlight} objects.
12219 fn copy_highlight_json(&mut self, _: &CopyHighlightJson, cx: &mut ViewContext<Self>) {
12220 let Some(buffer) = self.buffer.read(cx).as_singleton() else {
12221 return;
12222 };
12223
12224 #[derive(Serialize)]
12225 struct Chunk<'a> {
12226 text: String,
12227 highlight: Option<&'a str>,
12228 }
12229
12230 let snapshot = buffer.read(cx).snapshot();
12231 let range = self
12232 .selected_text_range(false, cx)
12233 .and_then(|selection| {
12234 if selection.range.is_empty() {
12235 None
12236 } else {
12237 Some(selection.range)
12238 }
12239 })
12240 .unwrap_or_else(|| 0..snapshot.len());
12241
12242 let chunks = snapshot.chunks(range, true);
12243 let mut lines = Vec::new();
12244 let mut line: VecDeque<Chunk> = VecDeque::new();
12245
12246 let Some(style) = self.style.as_ref() else {
12247 return;
12248 };
12249
12250 for chunk in chunks {
12251 let highlight = chunk
12252 .syntax_highlight_id
12253 .and_then(|id| id.name(&style.syntax));
12254 let mut chunk_lines = chunk.text.split('\n').peekable();
12255 while let Some(text) = chunk_lines.next() {
12256 let mut merged_with_last_token = false;
12257 if let Some(last_token) = line.back_mut() {
12258 if last_token.highlight == highlight {
12259 last_token.text.push_str(text);
12260 merged_with_last_token = true;
12261 }
12262 }
12263
12264 if !merged_with_last_token {
12265 line.push_back(Chunk {
12266 text: text.into(),
12267 highlight,
12268 });
12269 }
12270
12271 if chunk_lines.peek().is_some() {
12272 if line.len() > 1 && line.front().unwrap().text.is_empty() {
12273 line.pop_front();
12274 }
12275 if line.len() > 1 && line.back().unwrap().text.is_empty() {
12276 line.pop_back();
12277 }
12278
12279 lines.push(mem::take(&mut line));
12280 }
12281 }
12282 }
12283
12284 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
12285 return;
12286 };
12287 cx.write_to_clipboard(ClipboardItem::new_string(lines));
12288 }
12289
12290 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
12291 &self.inlay_hint_cache
12292 }
12293
12294 pub fn replay_insert_event(
12295 &mut self,
12296 text: &str,
12297 relative_utf16_range: Option<Range<isize>>,
12298 cx: &mut ViewContext<Self>,
12299 ) {
12300 if !self.input_enabled {
12301 cx.emit(EditorEvent::InputIgnored { text: text.into() });
12302 return;
12303 }
12304 if let Some(relative_utf16_range) = relative_utf16_range {
12305 let selections = self.selections.all::<OffsetUtf16>(cx);
12306 self.change_selections(None, cx, |s| {
12307 let new_ranges = selections.into_iter().map(|range| {
12308 let start = OffsetUtf16(
12309 range
12310 .head()
12311 .0
12312 .saturating_add_signed(relative_utf16_range.start),
12313 );
12314 let end = OffsetUtf16(
12315 range
12316 .head()
12317 .0
12318 .saturating_add_signed(relative_utf16_range.end),
12319 );
12320 start..end
12321 });
12322 s.select_ranges(new_ranges);
12323 });
12324 }
12325
12326 self.handle_input(text, cx);
12327 }
12328
12329 pub fn supports_inlay_hints(&self, cx: &AppContext) -> bool {
12330 let Some(project) = self.project.as_ref() else {
12331 return false;
12332 };
12333 let project = project.read(cx);
12334
12335 let mut supports = false;
12336 self.buffer().read(cx).for_each_buffer(|buffer| {
12337 if !supports {
12338 supports = project
12339 .language_servers_for_buffer(buffer.read(cx), cx)
12340 .any(
12341 |(_, server)| match server.capabilities().inlay_hint_provider {
12342 Some(lsp::OneOf::Left(enabled)) => enabled,
12343 Some(lsp::OneOf::Right(_)) => true,
12344 None => false,
12345 },
12346 )
12347 }
12348 });
12349 supports
12350 }
12351
12352 pub fn focus(&self, cx: &mut WindowContext) {
12353 cx.focus(&self.focus_handle)
12354 }
12355
12356 pub fn is_focused(&self, cx: &WindowContext) -> bool {
12357 self.focus_handle.is_focused(cx)
12358 }
12359
12360 fn handle_focus(&mut self, cx: &mut ViewContext<Self>) {
12361 cx.emit(EditorEvent::Focused);
12362
12363 if let Some(descendant) = self
12364 .last_focused_descendant
12365 .take()
12366 .and_then(|descendant| descendant.upgrade())
12367 {
12368 cx.focus(&descendant);
12369 } else {
12370 if let Some(blame) = self.blame.as_ref() {
12371 blame.update(cx, GitBlame::focus)
12372 }
12373
12374 self.blink_manager.update(cx, BlinkManager::enable);
12375 self.show_cursor_names(cx);
12376 self.buffer.update(cx, |buffer, cx| {
12377 buffer.finalize_last_transaction(cx);
12378 if self.leader_peer_id.is_none() {
12379 buffer.set_active_selections(
12380 &self.selections.disjoint_anchors(),
12381 self.selections.line_mode,
12382 self.cursor_shape,
12383 cx,
12384 );
12385 }
12386 });
12387 }
12388 }
12389
12390 fn handle_focus_in(&mut self, cx: &mut ViewContext<Self>) {
12391 cx.emit(EditorEvent::FocusedIn)
12392 }
12393
12394 fn handle_focus_out(&mut self, event: FocusOutEvent, _cx: &mut ViewContext<Self>) {
12395 if event.blurred != self.focus_handle {
12396 self.last_focused_descendant = Some(event.blurred);
12397 }
12398 }
12399
12400 pub fn handle_blur(&mut self, cx: &mut ViewContext<Self>) {
12401 self.blink_manager.update(cx, BlinkManager::disable);
12402 self.buffer
12403 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
12404
12405 if let Some(blame) = self.blame.as_ref() {
12406 blame.update(cx, GitBlame::blur)
12407 }
12408 if !self.hover_state.focused(cx) {
12409 hide_hover(self, cx);
12410 }
12411
12412 self.hide_context_menu(cx);
12413 cx.emit(EditorEvent::Blurred);
12414 cx.notify();
12415 }
12416
12417 pub fn register_action<A: Action>(
12418 &mut self,
12419 listener: impl Fn(&A, &mut WindowContext) + 'static,
12420 ) -> Subscription {
12421 let id = self.next_editor_action_id.post_inc();
12422 let listener = Arc::new(listener);
12423 self.editor_actions.borrow_mut().insert(
12424 id,
12425 Box::new(move |cx| {
12426 let cx = cx.window_context();
12427 let listener = listener.clone();
12428 cx.on_action(TypeId::of::<A>(), move |action, phase, cx| {
12429 let action = action.downcast_ref().unwrap();
12430 if phase == DispatchPhase::Bubble {
12431 listener(action, cx)
12432 }
12433 })
12434 }),
12435 );
12436
12437 let editor_actions = self.editor_actions.clone();
12438 Subscription::new(move || {
12439 editor_actions.borrow_mut().remove(&id);
12440 })
12441 }
12442
12443 pub fn file_header_size(&self) -> u32 {
12444 self.file_header_size
12445 }
12446
12447 pub fn revert(
12448 &mut self,
12449 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
12450 cx: &mut ViewContext<Self>,
12451 ) {
12452 self.buffer().update(cx, |multi_buffer, cx| {
12453 for (buffer_id, changes) in revert_changes {
12454 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
12455 buffer.update(cx, |buffer, cx| {
12456 buffer.edit(
12457 changes.into_iter().map(|(range, text)| {
12458 (range, text.to_string().map(Arc::<str>::from))
12459 }),
12460 None,
12461 cx,
12462 );
12463 });
12464 }
12465 }
12466 });
12467 self.change_selections(None, cx, |selections| selections.refresh());
12468 }
12469
12470 pub fn to_pixel_point(
12471 &mut self,
12472 source: multi_buffer::Anchor,
12473 editor_snapshot: &EditorSnapshot,
12474 cx: &mut ViewContext<Self>,
12475 ) -> Option<gpui::Point<Pixels>> {
12476 let source_point = source.to_display_point(editor_snapshot);
12477 self.display_to_pixel_point(source_point, editor_snapshot, cx)
12478 }
12479
12480 pub fn display_to_pixel_point(
12481 &mut self,
12482 source: DisplayPoint,
12483 editor_snapshot: &EditorSnapshot,
12484 cx: &mut ViewContext<Self>,
12485 ) -> Option<gpui::Point<Pixels>> {
12486 let line_height = self.style()?.text.line_height_in_pixels(cx.rem_size());
12487 let text_layout_details = self.text_layout_details(cx);
12488 let scroll_top = text_layout_details
12489 .scroll_anchor
12490 .scroll_position(editor_snapshot)
12491 .y;
12492
12493 if source.row().as_f32() < scroll_top.floor() {
12494 return None;
12495 }
12496 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
12497 let source_y = line_height * (source.row().as_f32() - scroll_top);
12498 Some(gpui::Point::new(source_x, source_y))
12499 }
12500
12501 pub fn has_active_completions_menu(&self) -> bool {
12502 self.context_menu.read().as_ref().map_or(false, |menu| {
12503 menu.visible() && matches!(menu, ContextMenu::Completions(_))
12504 })
12505 }
12506
12507 pub fn register_addon<T: Addon>(&mut self, instance: T) {
12508 self.addons
12509 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
12510 }
12511
12512 pub fn unregister_addon<T: Addon>(&mut self) {
12513 self.addons.remove(&std::any::TypeId::of::<T>());
12514 }
12515
12516 pub fn addon<T: Addon>(&self) -> Option<&T> {
12517 let type_id = std::any::TypeId::of::<T>();
12518 self.addons
12519 .get(&type_id)
12520 .and_then(|item| item.to_any().downcast_ref::<T>())
12521 }
12522}
12523
12524fn hunks_for_selections(
12525 multi_buffer_snapshot: &MultiBufferSnapshot,
12526 selections: &[Selection<Anchor>],
12527) -> Vec<MultiBufferDiffHunk> {
12528 let buffer_rows_for_selections = selections.iter().map(|selection| {
12529 let head = selection.head();
12530 let tail = selection.tail();
12531 let start = MultiBufferRow(tail.to_point(multi_buffer_snapshot).row);
12532 let end = MultiBufferRow(head.to_point(multi_buffer_snapshot).row);
12533 if start > end {
12534 end..start
12535 } else {
12536 start..end
12537 }
12538 });
12539
12540 hunks_for_rows(buffer_rows_for_selections, multi_buffer_snapshot)
12541}
12542
12543pub fn hunks_for_rows(
12544 rows: impl Iterator<Item = Range<MultiBufferRow>>,
12545 multi_buffer_snapshot: &MultiBufferSnapshot,
12546) -> Vec<MultiBufferDiffHunk> {
12547 let mut hunks = Vec::new();
12548 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
12549 HashMap::default();
12550 for selected_multi_buffer_rows in rows {
12551 let query_rows =
12552 selected_multi_buffer_rows.start..selected_multi_buffer_rows.end.next_row();
12553 for hunk in multi_buffer_snapshot.git_diff_hunks_in_range(query_rows.clone()) {
12554 // Deleted hunk is an empty row range, no caret can be placed there and Zed allows to revert it
12555 // when the caret is just above or just below the deleted hunk.
12556 let allow_adjacent = hunk_status(&hunk) == DiffHunkStatus::Removed;
12557 let related_to_selection = if allow_adjacent {
12558 hunk.row_range.overlaps(&query_rows)
12559 || hunk.row_range.start == query_rows.end
12560 || hunk.row_range.end == query_rows.start
12561 } else {
12562 // `selected_multi_buffer_rows` are inclusive (e.g. [2..2] means 2nd row is selected)
12563 // `hunk.row_range` is exclusive (e.g. [2..3] means 2nd row is selected)
12564 hunk.row_range.overlaps(&selected_multi_buffer_rows)
12565 || selected_multi_buffer_rows.end == hunk.row_range.start
12566 };
12567 if related_to_selection {
12568 if !processed_buffer_rows
12569 .entry(hunk.buffer_id)
12570 .or_default()
12571 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
12572 {
12573 continue;
12574 }
12575 hunks.push(hunk);
12576 }
12577 }
12578 }
12579
12580 hunks
12581}
12582
12583pub trait CollaborationHub {
12584 fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap<PeerId, Collaborator>;
12585 fn user_participant_indices<'a>(
12586 &self,
12587 cx: &'a AppContext,
12588 ) -> &'a HashMap<u64, ParticipantIndex>;
12589 fn user_names(&self, cx: &AppContext) -> HashMap<u64, SharedString>;
12590}
12591
12592impl CollaborationHub for Model<Project> {
12593 fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap<PeerId, Collaborator> {
12594 self.read(cx).collaborators()
12595 }
12596
12597 fn user_participant_indices<'a>(
12598 &self,
12599 cx: &'a AppContext,
12600 ) -> &'a HashMap<u64, ParticipantIndex> {
12601 self.read(cx).user_store().read(cx).participant_indices()
12602 }
12603
12604 fn user_names(&self, cx: &AppContext) -> HashMap<u64, SharedString> {
12605 let this = self.read(cx);
12606 let user_ids = this.collaborators().values().map(|c| c.user_id);
12607 this.user_store().read_with(cx, |user_store, cx| {
12608 user_store.participant_names(user_ids, cx)
12609 })
12610 }
12611}
12612
12613pub trait CompletionProvider {
12614 fn completions(
12615 &self,
12616 buffer: &Model<Buffer>,
12617 buffer_position: text::Anchor,
12618 trigger: CompletionContext,
12619 cx: &mut ViewContext<Editor>,
12620 ) -> Task<Result<Vec<Completion>>>;
12621
12622 fn resolve_completions(
12623 &self,
12624 buffer: Model<Buffer>,
12625 completion_indices: Vec<usize>,
12626 completions: Arc<RwLock<Box<[Completion]>>>,
12627 cx: &mut ViewContext<Editor>,
12628 ) -> Task<Result<bool>>;
12629
12630 fn apply_additional_edits_for_completion(
12631 &self,
12632 buffer: Model<Buffer>,
12633 completion: Completion,
12634 push_to_history: bool,
12635 cx: &mut ViewContext<Editor>,
12636 ) -> Task<Result<Option<language::Transaction>>>;
12637
12638 fn is_completion_trigger(
12639 &self,
12640 buffer: &Model<Buffer>,
12641 position: language::Anchor,
12642 text: &str,
12643 trigger_in_words: bool,
12644 cx: &mut ViewContext<Editor>,
12645 ) -> bool;
12646
12647 fn sort_completions(&self) -> bool {
12648 true
12649 }
12650}
12651
12652pub trait CodeActionProvider {
12653 fn code_actions(
12654 &self,
12655 buffer: &Model<Buffer>,
12656 range: Range<text::Anchor>,
12657 cx: &mut WindowContext,
12658 ) -> Task<Result<Vec<CodeAction>>>;
12659
12660 fn apply_code_action(
12661 &self,
12662 buffer_handle: Model<Buffer>,
12663 action: CodeAction,
12664 excerpt_id: ExcerptId,
12665 push_to_history: bool,
12666 cx: &mut WindowContext,
12667 ) -> Task<Result<ProjectTransaction>>;
12668}
12669
12670impl CodeActionProvider for Model<Project> {
12671 fn code_actions(
12672 &self,
12673 buffer: &Model<Buffer>,
12674 range: Range<text::Anchor>,
12675 cx: &mut WindowContext,
12676 ) -> Task<Result<Vec<CodeAction>>> {
12677 self.update(cx, |project, cx| project.code_actions(buffer, range, cx))
12678 }
12679
12680 fn apply_code_action(
12681 &self,
12682 buffer_handle: Model<Buffer>,
12683 action: CodeAction,
12684 _excerpt_id: ExcerptId,
12685 push_to_history: bool,
12686 cx: &mut WindowContext,
12687 ) -> Task<Result<ProjectTransaction>> {
12688 self.update(cx, |project, cx| {
12689 project.apply_code_action(buffer_handle, action, push_to_history, cx)
12690 })
12691 }
12692}
12693
12694fn snippet_completions(
12695 project: &Project,
12696 buffer: &Model<Buffer>,
12697 buffer_position: text::Anchor,
12698 cx: &mut AppContext,
12699) -> Vec<Completion> {
12700 let language = buffer.read(cx).language_at(buffer_position);
12701 let language_name = language.as_ref().map(|language| language.lsp_id());
12702 let snippet_store = project.snippets().read(cx);
12703 let snippets = snippet_store.snippets_for(language_name, cx);
12704
12705 if snippets.is_empty() {
12706 return vec![];
12707 }
12708 let snapshot = buffer.read(cx).text_snapshot();
12709 let chunks = snapshot.reversed_chunks_in_range(text::Anchor::MIN..buffer_position);
12710
12711 let mut lines = chunks.lines();
12712 let Some(line_at) = lines.next().filter(|line| !line.is_empty()) else {
12713 return vec![];
12714 };
12715
12716 let scope = language.map(|language| language.default_scope());
12717 let classifier = CharClassifier::new(scope).for_completion(true);
12718 let mut last_word = line_at
12719 .chars()
12720 .rev()
12721 .take_while(|c| classifier.is_word(*c))
12722 .collect::<String>();
12723 last_word = last_word.chars().rev().collect();
12724 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
12725 let to_lsp = |point: &text::Anchor| {
12726 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
12727 point_to_lsp(end)
12728 };
12729 let lsp_end = to_lsp(&buffer_position);
12730 snippets
12731 .into_iter()
12732 .filter_map(|snippet| {
12733 let matching_prefix = snippet
12734 .prefix
12735 .iter()
12736 .find(|prefix| prefix.starts_with(&last_word))?;
12737 let start = as_offset - last_word.len();
12738 let start = snapshot.anchor_before(start);
12739 let range = start..buffer_position;
12740 let lsp_start = to_lsp(&start);
12741 let lsp_range = lsp::Range {
12742 start: lsp_start,
12743 end: lsp_end,
12744 };
12745 Some(Completion {
12746 old_range: range,
12747 new_text: snippet.body.clone(),
12748 label: CodeLabel {
12749 text: matching_prefix.clone(),
12750 runs: vec![],
12751 filter_range: 0..matching_prefix.len(),
12752 },
12753 server_id: LanguageServerId(usize::MAX),
12754 documentation: snippet.description.clone().map(Documentation::SingleLine),
12755 lsp_completion: lsp::CompletionItem {
12756 label: snippet.prefix.first().unwrap().clone(),
12757 kind: Some(CompletionItemKind::SNIPPET),
12758 label_details: snippet.description.as_ref().map(|description| {
12759 lsp::CompletionItemLabelDetails {
12760 detail: Some(description.clone()),
12761 description: None,
12762 }
12763 }),
12764 insert_text_format: Some(InsertTextFormat::SNIPPET),
12765 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12766 lsp::InsertReplaceEdit {
12767 new_text: snippet.body.clone(),
12768 insert: lsp_range,
12769 replace: lsp_range,
12770 },
12771 )),
12772 filter_text: Some(snippet.body.clone()),
12773 sort_text: Some(char::MAX.to_string()),
12774 ..Default::default()
12775 },
12776 confirm: None,
12777 })
12778 })
12779 .collect()
12780}
12781
12782impl CompletionProvider for Model<Project> {
12783 fn completions(
12784 &self,
12785 buffer: &Model<Buffer>,
12786 buffer_position: text::Anchor,
12787 options: CompletionContext,
12788 cx: &mut ViewContext<Editor>,
12789 ) -> Task<Result<Vec<Completion>>> {
12790 self.update(cx, |project, cx| {
12791 let snippets = snippet_completions(project, buffer, buffer_position, cx);
12792 let project_completions = project.completions(buffer, buffer_position, options, cx);
12793 cx.background_executor().spawn(async move {
12794 let mut completions = project_completions.await?;
12795 //let snippets = snippets.into_iter().;
12796 completions.extend(snippets);
12797 Ok(completions)
12798 })
12799 })
12800 }
12801
12802 fn resolve_completions(
12803 &self,
12804 buffer: Model<Buffer>,
12805 completion_indices: Vec<usize>,
12806 completions: Arc<RwLock<Box<[Completion]>>>,
12807 cx: &mut ViewContext<Editor>,
12808 ) -> Task<Result<bool>> {
12809 self.update(cx, |project, cx| {
12810 project.resolve_completions(buffer, completion_indices, completions, cx)
12811 })
12812 }
12813
12814 fn apply_additional_edits_for_completion(
12815 &self,
12816 buffer: Model<Buffer>,
12817 completion: Completion,
12818 push_to_history: bool,
12819 cx: &mut ViewContext<Editor>,
12820 ) -> Task<Result<Option<language::Transaction>>> {
12821 self.update(cx, |project, cx| {
12822 project.apply_additional_edits_for_completion(buffer, completion, push_to_history, cx)
12823 })
12824 }
12825
12826 fn is_completion_trigger(
12827 &self,
12828 buffer: &Model<Buffer>,
12829 position: language::Anchor,
12830 text: &str,
12831 trigger_in_words: bool,
12832 cx: &mut ViewContext<Editor>,
12833 ) -> bool {
12834 if !EditorSettings::get_global(cx).show_completions_on_input {
12835 return false;
12836 }
12837
12838 let mut chars = text.chars();
12839 let char = if let Some(char) = chars.next() {
12840 char
12841 } else {
12842 return false;
12843 };
12844 if chars.next().is_some() {
12845 return false;
12846 }
12847
12848 let buffer = buffer.read(cx);
12849 let classifier = buffer
12850 .snapshot()
12851 .char_classifier_at(position)
12852 .for_completion(true);
12853 if trigger_in_words && classifier.is_word(char) {
12854 return true;
12855 }
12856
12857 buffer
12858 .completion_triggers()
12859 .iter()
12860 .any(|string| string == text)
12861 }
12862}
12863
12864fn inlay_hint_settings(
12865 location: Anchor,
12866 snapshot: &MultiBufferSnapshot,
12867 cx: &mut ViewContext<'_, Editor>,
12868) -> InlayHintSettings {
12869 let file = snapshot.file_at(location);
12870 let language = snapshot.language_at(location);
12871 let settings = all_language_settings(file, cx);
12872 settings
12873 .language(language.map(|l| l.name()).as_ref())
12874 .inlay_hints
12875}
12876
12877fn consume_contiguous_rows(
12878 contiguous_row_selections: &mut Vec<Selection<Point>>,
12879 selection: &Selection<Point>,
12880 display_map: &DisplaySnapshot,
12881 selections: &mut std::iter::Peekable<std::slice::Iter<Selection<Point>>>,
12882) -> (MultiBufferRow, MultiBufferRow) {
12883 contiguous_row_selections.push(selection.clone());
12884 let start_row = MultiBufferRow(selection.start.row);
12885 let mut end_row = ending_row(selection, display_map);
12886
12887 while let Some(next_selection) = selections.peek() {
12888 if next_selection.start.row <= end_row.0 {
12889 end_row = ending_row(next_selection, display_map);
12890 contiguous_row_selections.push(selections.next().unwrap().clone());
12891 } else {
12892 break;
12893 }
12894 }
12895 (start_row, end_row)
12896}
12897
12898fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
12899 if next_selection.end.column > 0 || next_selection.is_empty() {
12900 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
12901 } else {
12902 MultiBufferRow(next_selection.end.row)
12903 }
12904}
12905
12906impl EditorSnapshot {
12907 pub fn remote_selections_in_range<'a>(
12908 &'a self,
12909 range: &'a Range<Anchor>,
12910 collaboration_hub: &dyn CollaborationHub,
12911 cx: &'a AppContext,
12912 ) -> impl 'a + Iterator<Item = RemoteSelection> {
12913 let participant_names = collaboration_hub.user_names(cx);
12914 let participant_indices = collaboration_hub.user_participant_indices(cx);
12915 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
12916 let collaborators_by_replica_id = collaborators_by_peer_id
12917 .iter()
12918 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
12919 .collect::<HashMap<_, _>>();
12920 self.buffer_snapshot
12921 .selections_in_range(range, false)
12922 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
12923 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
12924 let participant_index = participant_indices.get(&collaborator.user_id).copied();
12925 let user_name = participant_names.get(&collaborator.user_id).cloned();
12926 Some(RemoteSelection {
12927 replica_id,
12928 selection,
12929 cursor_shape,
12930 line_mode,
12931 participant_index,
12932 peer_id: collaborator.peer_id,
12933 user_name,
12934 })
12935 })
12936 }
12937
12938 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
12939 self.display_snapshot.buffer_snapshot.language_at(position)
12940 }
12941
12942 pub fn is_focused(&self) -> bool {
12943 self.is_focused
12944 }
12945
12946 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
12947 self.placeholder_text.as_ref()
12948 }
12949
12950 pub fn scroll_position(&self) -> gpui::Point<f32> {
12951 self.scroll_anchor.scroll_position(&self.display_snapshot)
12952 }
12953
12954 fn gutter_dimensions(
12955 &self,
12956 font_id: FontId,
12957 font_size: Pixels,
12958 em_width: Pixels,
12959 max_line_number_width: Pixels,
12960 cx: &AppContext,
12961 ) -> GutterDimensions {
12962 if !self.show_gutter {
12963 return GutterDimensions::default();
12964 }
12965 let descent = cx.text_system().descent(font_id, font_size);
12966
12967 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
12968 matches!(
12969 ProjectSettings::get_global(cx).git.git_gutter,
12970 Some(GitGutterSetting::TrackedFiles)
12971 )
12972 });
12973 let gutter_settings = EditorSettings::get_global(cx).gutter;
12974 let show_line_numbers = self
12975 .show_line_numbers
12976 .unwrap_or(gutter_settings.line_numbers);
12977 let line_gutter_width = if show_line_numbers {
12978 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
12979 let min_width_for_number_on_gutter = em_width * 4.0;
12980 max_line_number_width.max(min_width_for_number_on_gutter)
12981 } else {
12982 0.0.into()
12983 };
12984
12985 let show_code_actions = self
12986 .show_code_actions
12987 .unwrap_or(gutter_settings.code_actions);
12988
12989 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
12990
12991 let git_blame_entries_width = self
12992 .render_git_blame_gutter
12993 .then_some(em_width * GIT_BLAME_GUTTER_WIDTH_CHARS);
12994
12995 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
12996 left_padding += if show_code_actions || show_runnables {
12997 em_width * 3.0
12998 } else if show_git_gutter && show_line_numbers {
12999 em_width * 2.0
13000 } else if show_git_gutter || show_line_numbers {
13001 em_width
13002 } else {
13003 px(0.)
13004 };
13005
13006 let right_padding = if gutter_settings.folds && show_line_numbers {
13007 em_width * 4.0
13008 } else if gutter_settings.folds {
13009 em_width * 3.0
13010 } else if show_line_numbers {
13011 em_width
13012 } else {
13013 px(0.)
13014 };
13015
13016 GutterDimensions {
13017 left_padding,
13018 right_padding,
13019 width: line_gutter_width + left_padding + right_padding,
13020 margin: -descent,
13021 git_blame_entries_width,
13022 }
13023 }
13024
13025 pub fn render_fold_toggle(
13026 &self,
13027 buffer_row: MultiBufferRow,
13028 row_contains_cursor: bool,
13029 editor: View<Editor>,
13030 cx: &mut WindowContext,
13031 ) -> Option<AnyElement> {
13032 let folded = self.is_line_folded(buffer_row);
13033
13034 if let Some(crease) = self
13035 .crease_snapshot
13036 .query_row(buffer_row, &self.buffer_snapshot)
13037 {
13038 let toggle_callback = Arc::new(move |folded, cx: &mut WindowContext| {
13039 if folded {
13040 editor.update(cx, |editor, cx| {
13041 editor.fold_at(&crate::FoldAt { buffer_row }, cx)
13042 });
13043 } else {
13044 editor.update(cx, |editor, cx| {
13045 editor.unfold_at(&crate::UnfoldAt { buffer_row }, cx)
13046 });
13047 }
13048 });
13049
13050 Some((crease.render_toggle)(
13051 buffer_row,
13052 folded,
13053 toggle_callback,
13054 cx,
13055 ))
13056 } else if folded
13057 || (self.starts_indent(buffer_row) && (row_contains_cursor || self.gutter_hovered))
13058 {
13059 Some(
13060 Disclosure::new(("indent-fold-indicator", buffer_row.0), !folded)
13061 .selected(folded)
13062 .on_click(cx.listener_for(&editor, move |this, _e, cx| {
13063 if folded {
13064 this.unfold_at(&UnfoldAt { buffer_row }, cx);
13065 } else {
13066 this.fold_at(&FoldAt { buffer_row }, cx);
13067 }
13068 }))
13069 .into_any_element(),
13070 )
13071 } else {
13072 None
13073 }
13074 }
13075
13076 pub fn render_crease_trailer(
13077 &self,
13078 buffer_row: MultiBufferRow,
13079 cx: &mut WindowContext,
13080 ) -> Option<AnyElement> {
13081 let folded = self.is_line_folded(buffer_row);
13082 let crease = self
13083 .crease_snapshot
13084 .query_row(buffer_row, &self.buffer_snapshot)?;
13085 Some((crease.render_trailer)(buffer_row, folded, cx))
13086 }
13087}
13088
13089impl Deref for EditorSnapshot {
13090 type Target = DisplaySnapshot;
13091
13092 fn deref(&self) -> &Self::Target {
13093 &self.display_snapshot
13094 }
13095}
13096
13097#[derive(Clone, Debug, PartialEq, Eq)]
13098pub enum EditorEvent {
13099 InputIgnored {
13100 text: Arc<str>,
13101 },
13102 InputHandled {
13103 utf16_range_to_replace: Option<Range<isize>>,
13104 text: Arc<str>,
13105 },
13106 ExcerptsAdded {
13107 buffer: Model<Buffer>,
13108 predecessor: ExcerptId,
13109 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
13110 },
13111 ExcerptsRemoved {
13112 ids: Vec<ExcerptId>,
13113 },
13114 ExcerptsEdited {
13115 ids: Vec<ExcerptId>,
13116 },
13117 ExcerptsExpanded {
13118 ids: Vec<ExcerptId>,
13119 },
13120 BufferEdited,
13121 Edited {
13122 transaction_id: clock::Lamport,
13123 },
13124 Reparsed(BufferId),
13125 Focused,
13126 FocusedIn,
13127 Blurred,
13128 DirtyChanged,
13129 Saved,
13130 TitleChanged,
13131 DiffBaseChanged,
13132 SelectionsChanged {
13133 local: bool,
13134 },
13135 ScrollPositionChanged {
13136 local: bool,
13137 autoscroll: bool,
13138 },
13139 Closed,
13140 TransactionUndone {
13141 transaction_id: clock::Lamport,
13142 },
13143 TransactionBegun {
13144 transaction_id: clock::Lamport,
13145 },
13146 CursorShapeChanged,
13147}
13148
13149impl EventEmitter<EditorEvent> for Editor {}
13150
13151impl FocusableView for Editor {
13152 fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
13153 self.focus_handle.clone()
13154 }
13155}
13156
13157impl Render for Editor {
13158 fn render<'a>(&mut self, cx: &mut ViewContext<'a, Self>) -> impl IntoElement {
13159 let settings = ThemeSettings::get_global(cx);
13160
13161 let text_style = match self.mode {
13162 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
13163 color: cx.theme().colors().editor_foreground,
13164 font_family: settings.ui_font.family.clone(),
13165 font_features: settings.ui_font.features.clone(),
13166 font_fallbacks: settings.ui_font.fallbacks.clone(),
13167 font_size: rems(0.875).into(),
13168 font_weight: settings.ui_font.weight,
13169 line_height: relative(settings.buffer_line_height.value()),
13170 ..Default::default()
13171 },
13172 EditorMode::Full => TextStyle {
13173 color: cx.theme().colors().editor_foreground,
13174 font_family: settings.buffer_font.family.clone(),
13175 font_features: settings.buffer_font.features.clone(),
13176 font_fallbacks: settings.buffer_font.fallbacks.clone(),
13177 font_size: settings.buffer_font_size(cx).into(),
13178 font_weight: settings.buffer_font.weight,
13179 line_height: relative(settings.buffer_line_height.value()),
13180 ..Default::default()
13181 },
13182 };
13183
13184 let background = match self.mode {
13185 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
13186 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
13187 EditorMode::Full => cx.theme().colors().editor_background,
13188 };
13189
13190 EditorElement::new(
13191 cx.view(),
13192 EditorStyle {
13193 background,
13194 local_player: cx.theme().players().local(),
13195 text: text_style,
13196 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
13197 syntax: cx.theme().syntax().clone(),
13198 status: cx.theme().status().clone(),
13199 inlay_hints_style: make_inlay_hints_style(cx),
13200 suggestions_style: HighlightStyle {
13201 color: Some(cx.theme().status().predictive),
13202 ..HighlightStyle::default()
13203 },
13204 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
13205 },
13206 )
13207 }
13208}
13209
13210impl ViewInputHandler for Editor {
13211 fn text_for_range(
13212 &mut self,
13213 range_utf16: Range<usize>,
13214 cx: &mut ViewContext<Self>,
13215 ) -> Option<String> {
13216 Some(
13217 self.buffer
13218 .read(cx)
13219 .read(cx)
13220 .text_for_range(OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end))
13221 .collect(),
13222 )
13223 }
13224
13225 fn selected_text_range(
13226 &mut self,
13227 ignore_disabled_input: bool,
13228 cx: &mut ViewContext<Self>,
13229 ) -> Option<UTF16Selection> {
13230 // Prevent the IME menu from appearing when holding down an alphabetic key
13231 // while input is disabled.
13232 if !ignore_disabled_input && !self.input_enabled {
13233 return None;
13234 }
13235
13236 let selection = self.selections.newest::<OffsetUtf16>(cx);
13237 let range = selection.range();
13238
13239 Some(UTF16Selection {
13240 range: range.start.0..range.end.0,
13241 reversed: selection.reversed,
13242 })
13243 }
13244
13245 fn marked_text_range(&self, cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
13246 let snapshot = self.buffer.read(cx).read(cx);
13247 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
13248 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
13249 }
13250
13251 fn unmark_text(&mut self, cx: &mut ViewContext<Self>) {
13252 self.clear_highlights::<InputComposition>(cx);
13253 self.ime_transaction.take();
13254 }
13255
13256 fn replace_text_in_range(
13257 &mut self,
13258 range_utf16: Option<Range<usize>>,
13259 text: &str,
13260 cx: &mut ViewContext<Self>,
13261 ) {
13262 if !self.input_enabled {
13263 cx.emit(EditorEvent::InputIgnored { text: text.into() });
13264 return;
13265 }
13266
13267 self.transact(cx, |this, cx| {
13268 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
13269 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
13270 Some(this.selection_replacement_ranges(range_utf16, cx))
13271 } else {
13272 this.marked_text_ranges(cx)
13273 };
13274
13275 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
13276 let newest_selection_id = this.selections.newest_anchor().id;
13277 this.selections
13278 .all::<OffsetUtf16>(cx)
13279 .iter()
13280 .zip(ranges_to_replace.iter())
13281 .find_map(|(selection, range)| {
13282 if selection.id == newest_selection_id {
13283 Some(
13284 (range.start.0 as isize - selection.head().0 as isize)
13285 ..(range.end.0 as isize - selection.head().0 as isize),
13286 )
13287 } else {
13288 None
13289 }
13290 })
13291 });
13292
13293 cx.emit(EditorEvent::InputHandled {
13294 utf16_range_to_replace: range_to_replace,
13295 text: text.into(),
13296 });
13297
13298 if let Some(new_selected_ranges) = new_selected_ranges {
13299 this.change_selections(None, cx, |selections| {
13300 selections.select_ranges(new_selected_ranges)
13301 });
13302 this.backspace(&Default::default(), cx);
13303 }
13304
13305 this.handle_input(text, cx);
13306 });
13307
13308 if let Some(transaction) = self.ime_transaction {
13309 self.buffer.update(cx, |buffer, cx| {
13310 buffer.group_until_transaction(transaction, cx);
13311 });
13312 }
13313
13314 self.unmark_text(cx);
13315 }
13316
13317 fn replace_and_mark_text_in_range(
13318 &mut self,
13319 range_utf16: Option<Range<usize>>,
13320 text: &str,
13321 new_selected_range_utf16: Option<Range<usize>>,
13322 cx: &mut ViewContext<Self>,
13323 ) {
13324 if !self.input_enabled {
13325 return;
13326 }
13327
13328 let transaction = self.transact(cx, |this, cx| {
13329 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
13330 let snapshot = this.buffer.read(cx).read(cx);
13331 if let Some(relative_range_utf16) = range_utf16.as_ref() {
13332 for marked_range in &mut marked_ranges {
13333 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
13334 marked_range.start.0 += relative_range_utf16.start;
13335 marked_range.start =
13336 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
13337 marked_range.end =
13338 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
13339 }
13340 }
13341 Some(marked_ranges)
13342 } else if let Some(range_utf16) = range_utf16 {
13343 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
13344 Some(this.selection_replacement_ranges(range_utf16, cx))
13345 } else {
13346 None
13347 };
13348
13349 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
13350 let newest_selection_id = this.selections.newest_anchor().id;
13351 this.selections
13352 .all::<OffsetUtf16>(cx)
13353 .iter()
13354 .zip(ranges_to_replace.iter())
13355 .find_map(|(selection, range)| {
13356 if selection.id == newest_selection_id {
13357 Some(
13358 (range.start.0 as isize - selection.head().0 as isize)
13359 ..(range.end.0 as isize - selection.head().0 as isize),
13360 )
13361 } else {
13362 None
13363 }
13364 })
13365 });
13366
13367 cx.emit(EditorEvent::InputHandled {
13368 utf16_range_to_replace: range_to_replace,
13369 text: text.into(),
13370 });
13371
13372 if let Some(ranges) = ranges_to_replace {
13373 this.change_selections(None, cx, |s| s.select_ranges(ranges));
13374 }
13375
13376 let marked_ranges = {
13377 let snapshot = this.buffer.read(cx).read(cx);
13378 this.selections
13379 .disjoint_anchors()
13380 .iter()
13381 .map(|selection| {
13382 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
13383 })
13384 .collect::<Vec<_>>()
13385 };
13386
13387 if text.is_empty() {
13388 this.unmark_text(cx);
13389 } else {
13390 this.highlight_text::<InputComposition>(
13391 marked_ranges.clone(),
13392 HighlightStyle {
13393 underline: Some(UnderlineStyle {
13394 thickness: px(1.),
13395 color: None,
13396 wavy: false,
13397 }),
13398 ..Default::default()
13399 },
13400 cx,
13401 );
13402 }
13403
13404 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
13405 let use_autoclose = this.use_autoclose;
13406 let use_auto_surround = this.use_auto_surround;
13407 this.set_use_autoclose(false);
13408 this.set_use_auto_surround(false);
13409 this.handle_input(text, cx);
13410 this.set_use_autoclose(use_autoclose);
13411 this.set_use_auto_surround(use_auto_surround);
13412
13413 if let Some(new_selected_range) = new_selected_range_utf16 {
13414 let snapshot = this.buffer.read(cx).read(cx);
13415 let new_selected_ranges = marked_ranges
13416 .into_iter()
13417 .map(|marked_range| {
13418 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
13419 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
13420 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
13421 snapshot.clip_offset_utf16(new_start, Bias::Left)
13422 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
13423 })
13424 .collect::<Vec<_>>();
13425
13426 drop(snapshot);
13427 this.change_selections(None, cx, |selections| {
13428 selections.select_ranges(new_selected_ranges)
13429 });
13430 }
13431 });
13432
13433 self.ime_transaction = self.ime_transaction.or(transaction);
13434 if let Some(transaction) = self.ime_transaction {
13435 self.buffer.update(cx, |buffer, cx| {
13436 buffer.group_until_transaction(transaction, cx);
13437 });
13438 }
13439
13440 if self.text_highlights::<InputComposition>(cx).is_none() {
13441 self.ime_transaction.take();
13442 }
13443 }
13444
13445 fn bounds_for_range(
13446 &mut self,
13447 range_utf16: Range<usize>,
13448 element_bounds: gpui::Bounds<Pixels>,
13449 cx: &mut ViewContext<Self>,
13450 ) -> Option<gpui::Bounds<Pixels>> {
13451 let text_layout_details = self.text_layout_details(cx);
13452 let style = &text_layout_details.editor_style;
13453 let font_id = cx.text_system().resolve_font(&style.text.font());
13454 let font_size = style.text.font_size.to_pixels(cx.rem_size());
13455 let line_height = style.text.line_height_in_pixels(cx.rem_size());
13456
13457 let em_width = cx
13458 .text_system()
13459 .typographic_bounds(font_id, font_size, 'm')
13460 .unwrap()
13461 .size
13462 .width;
13463
13464 let snapshot = self.snapshot(cx);
13465 let scroll_position = snapshot.scroll_position();
13466 let scroll_left = scroll_position.x * em_width;
13467
13468 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
13469 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
13470 + self.gutter_dimensions.width;
13471 let y = line_height * (start.row().as_f32() - scroll_position.y);
13472
13473 Some(Bounds {
13474 origin: element_bounds.origin + point(x, y),
13475 size: size(em_width, line_height),
13476 })
13477 }
13478}
13479
13480trait SelectionExt {
13481 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
13482 fn spanned_rows(
13483 &self,
13484 include_end_if_at_line_start: bool,
13485 map: &DisplaySnapshot,
13486 ) -> Range<MultiBufferRow>;
13487}
13488
13489impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
13490 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
13491 let start = self
13492 .start
13493 .to_point(&map.buffer_snapshot)
13494 .to_display_point(map);
13495 let end = self
13496 .end
13497 .to_point(&map.buffer_snapshot)
13498 .to_display_point(map);
13499 if self.reversed {
13500 end..start
13501 } else {
13502 start..end
13503 }
13504 }
13505
13506 fn spanned_rows(
13507 &self,
13508 include_end_if_at_line_start: bool,
13509 map: &DisplaySnapshot,
13510 ) -> Range<MultiBufferRow> {
13511 let start = self.start.to_point(&map.buffer_snapshot);
13512 let mut end = self.end.to_point(&map.buffer_snapshot);
13513 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
13514 end.row -= 1;
13515 }
13516
13517 let buffer_start = map.prev_line_boundary(start).0;
13518 let buffer_end = map.next_line_boundary(end).0;
13519 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
13520 }
13521}
13522
13523impl<T: InvalidationRegion> InvalidationStack<T> {
13524 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
13525 where
13526 S: Clone + ToOffset,
13527 {
13528 while let Some(region) = self.last() {
13529 let all_selections_inside_invalidation_ranges =
13530 if selections.len() == region.ranges().len() {
13531 selections
13532 .iter()
13533 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
13534 .all(|(selection, invalidation_range)| {
13535 let head = selection.head().to_offset(buffer);
13536 invalidation_range.start <= head && invalidation_range.end >= head
13537 })
13538 } else {
13539 false
13540 };
13541
13542 if all_selections_inside_invalidation_ranges {
13543 break;
13544 } else {
13545 self.pop();
13546 }
13547 }
13548 }
13549}
13550
13551impl<T> Default for InvalidationStack<T> {
13552 fn default() -> Self {
13553 Self(Default::default())
13554 }
13555}
13556
13557impl<T> Deref for InvalidationStack<T> {
13558 type Target = Vec<T>;
13559
13560 fn deref(&self) -> &Self::Target {
13561 &self.0
13562 }
13563}
13564
13565impl<T> DerefMut for InvalidationStack<T> {
13566 fn deref_mut(&mut self) -> &mut Self::Target {
13567 &mut self.0
13568 }
13569}
13570
13571impl InvalidationRegion for SnippetState {
13572 fn ranges(&self) -> &[Range<Anchor>] {
13573 &self.ranges[self.active_index]
13574 }
13575}
13576
13577pub fn diagnostic_block_renderer(
13578 diagnostic: Diagnostic,
13579 max_message_rows: Option<u8>,
13580 allow_closing: bool,
13581 _is_valid: bool,
13582) -> RenderBlock {
13583 let (text_without_backticks, code_ranges) =
13584 highlight_diagnostic_message(&diagnostic, max_message_rows);
13585
13586 Box::new(move |cx: &mut BlockContext| {
13587 let group_id: SharedString = cx.block_id.to_string().into();
13588
13589 let mut text_style = cx.text_style().clone();
13590 text_style.color = diagnostic_style(diagnostic.severity, cx.theme().status());
13591 let theme_settings = ThemeSettings::get_global(cx);
13592 text_style.font_family = theme_settings.buffer_font.family.clone();
13593 text_style.font_style = theme_settings.buffer_font.style;
13594 text_style.font_features = theme_settings.buffer_font.features.clone();
13595 text_style.font_weight = theme_settings.buffer_font.weight;
13596
13597 let multi_line_diagnostic = diagnostic.message.contains('\n');
13598
13599 let buttons = |diagnostic: &Diagnostic, block_id: BlockId| {
13600 if multi_line_diagnostic {
13601 v_flex()
13602 } else {
13603 h_flex()
13604 }
13605 .when(allow_closing, |div| {
13606 div.children(diagnostic.is_primary.then(|| {
13607 IconButton::new(("close-block", EntityId::from(block_id)), IconName::XCircle)
13608 .icon_color(Color::Muted)
13609 .size(ButtonSize::Compact)
13610 .style(ButtonStyle::Transparent)
13611 .visible_on_hover(group_id.clone())
13612 .on_click(move |_click, cx| cx.dispatch_action(Box::new(Cancel)))
13613 .tooltip(|cx| Tooltip::for_action("Close Diagnostics", &Cancel, cx))
13614 }))
13615 })
13616 .child(
13617 IconButton::new(("copy-block", EntityId::from(block_id)), IconName::Copy)
13618 .icon_color(Color::Muted)
13619 .size(ButtonSize::Compact)
13620 .style(ButtonStyle::Transparent)
13621 .visible_on_hover(group_id.clone())
13622 .on_click({
13623 let message = diagnostic.message.clone();
13624 move |_click, cx| {
13625 cx.write_to_clipboard(ClipboardItem::new_string(message.clone()))
13626 }
13627 })
13628 .tooltip(|cx| Tooltip::text("Copy diagnostic message", cx)),
13629 )
13630 };
13631
13632 let icon_size = buttons(&diagnostic, cx.block_id)
13633 .into_any_element()
13634 .layout_as_root(AvailableSpace::min_size(), cx);
13635
13636 h_flex()
13637 .id(cx.block_id)
13638 .group(group_id.clone())
13639 .relative()
13640 .size_full()
13641 .pl(cx.gutter_dimensions.width)
13642 .w(cx.max_width + cx.gutter_dimensions.width)
13643 .child(
13644 div()
13645 .flex()
13646 .w(cx.anchor_x - cx.gutter_dimensions.width - icon_size.width)
13647 .flex_shrink(),
13648 )
13649 .child(buttons(&diagnostic, cx.block_id))
13650 .child(div().flex().flex_shrink_0().child(
13651 StyledText::new(text_without_backticks.clone()).with_highlights(
13652 &text_style,
13653 code_ranges.iter().map(|range| {
13654 (
13655 range.clone(),
13656 HighlightStyle {
13657 font_weight: Some(FontWeight::BOLD),
13658 ..Default::default()
13659 },
13660 )
13661 }),
13662 ),
13663 ))
13664 .into_any_element()
13665 })
13666}
13667
13668pub fn highlight_diagnostic_message(
13669 diagnostic: &Diagnostic,
13670 mut max_message_rows: Option<u8>,
13671) -> (SharedString, Vec<Range<usize>>) {
13672 let mut text_without_backticks = String::new();
13673 let mut code_ranges = Vec::new();
13674
13675 if let Some(source) = &diagnostic.source {
13676 text_without_backticks.push_str(source);
13677 code_ranges.push(0..source.len());
13678 text_without_backticks.push_str(": ");
13679 }
13680
13681 let mut prev_offset = 0;
13682 let mut in_code_block = false;
13683 let has_row_limit = max_message_rows.is_some();
13684 let mut newline_indices = diagnostic
13685 .message
13686 .match_indices('\n')
13687 .filter(|_| has_row_limit)
13688 .map(|(ix, _)| ix)
13689 .fuse()
13690 .peekable();
13691
13692 for (quote_ix, _) in diagnostic
13693 .message
13694 .match_indices('`')
13695 .chain([(diagnostic.message.len(), "")])
13696 {
13697 let mut first_newline_ix = None;
13698 let mut last_newline_ix = None;
13699 while let Some(newline_ix) = newline_indices.peek() {
13700 if *newline_ix < quote_ix {
13701 if first_newline_ix.is_none() {
13702 first_newline_ix = Some(*newline_ix);
13703 }
13704 last_newline_ix = Some(*newline_ix);
13705
13706 if let Some(rows_left) = &mut max_message_rows {
13707 if *rows_left == 0 {
13708 break;
13709 } else {
13710 *rows_left -= 1;
13711 }
13712 }
13713 let _ = newline_indices.next();
13714 } else {
13715 break;
13716 }
13717 }
13718 let prev_len = text_without_backticks.len();
13719 let new_text = &diagnostic.message[prev_offset..first_newline_ix.unwrap_or(quote_ix)];
13720 text_without_backticks.push_str(new_text);
13721 if in_code_block {
13722 code_ranges.push(prev_len..text_without_backticks.len());
13723 }
13724 prev_offset = last_newline_ix.unwrap_or(quote_ix) + 1;
13725 in_code_block = !in_code_block;
13726 if first_newline_ix.map_or(false, |newline_ix| newline_ix < quote_ix) {
13727 text_without_backticks.push_str("...");
13728 break;
13729 }
13730 }
13731
13732 (text_without_backticks.into(), code_ranges)
13733}
13734
13735fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
13736 match severity {
13737 DiagnosticSeverity::ERROR => colors.error,
13738 DiagnosticSeverity::WARNING => colors.warning,
13739 DiagnosticSeverity::INFORMATION => colors.info,
13740 DiagnosticSeverity::HINT => colors.info,
13741 _ => colors.ignored,
13742 }
13743}
13744
13745pub fn styled_runs_for_code_label<'a>(
13746 label: &'a CodeLabel,
13747 syntax_theme: &'a theme::SyntaxTheme,
13748) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
13749 let fade_out = HighlightStyle {
13750 fade_out: Some(0.35),
13751 ..Default::default()
13752 };
13753
13754 let mut prev_end = label.filter_range.end;
13755 label
13756 .runs
13757 .iter()
13758 .enumerate()
13759 .flat_map(move |(ix, (range, highlight_id))| {
13760 let style = if let Some(style) = highlight_id.style(syntax_theme) {
13761 style
13762 } else {
13763 return Default::default();
13764 };
13765 let mut muted_style = style;
13766 muted_style.highlight(fade_out);
13767
13768 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
13769 if range.start >= label.filter_range.end {
13770 if range.start > prev_end {
13771 runs.push((prev_end..range.start, fade_out));
13772 }
13773 runs.push((range.clone(), muted_style));
13774 } else if range.end <= label.filter_range.end {
13775 runs.push((range.clone(), style));
13776 } else {
13777 runs.push((range.start..label.filter_range.end, style));
13778 runs.push((label.filter_range.end..range.end, muted_style));
13779 }
13780 prev_end = cmp::max(prev_end, range.end);
13781
13782 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
13783 runs.push((prev_end..label.text.len(), fade_out));
13784 }
13785
13786 runs
13787 })
13788}
13789
13790pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
13791 let mut prev_index = 0;
13792 let mut prev_codepoint: Option<char> = None;
13793 text.char_indices()
13794 .chain([(text.len(), '\0')])
13795 .filter_map(move |(index, codepoint)| {
13796 let prev_codepoint = prev_codepoint.replace(codepoint)?;
13797 let is_boundary = index == text.len()
13798 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
13799 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
13800 if is_boundary {
13801 let chunk = &text[prev_index..index];
13802 prev_index = index;
13803 Some(chunk)
13804 } else {
13805 None
13806 }
13807 })
13808}
13809
13810pub trait RangeToAnchorExt: Sized {
13811 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
13812
13813 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
13814 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
13815 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
13816 }
13817}
13818
13819impl<T: ToOffset> RangeToAnchorExt for Range<T> {
13820 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
13821 let start_offset = self.start.to_offset(snapshot);
13822 let end_offset = self.end.to_offset(snapshot);
13823 if start_offset == end_offset {
13824 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
13825 } else {
13826 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
13827 }
13828 }
13829}
13830
13831pub trait RowExt {
13832 fn as_f32(&self) -> f32;
13833
13834 fn next_row(&self) -> Self;
13835
13836 fn previous_row(&self) -> Self;
13837
13838 fn minus(&self, other: Self) -> u32;
13839}
13840
13841impl RowExt for DisplayRow {
13842 fn as_f32(&self) -> f32 {
13843 self.0 as f32
13844 }
13845
13846 fn next_row(&self) -> Self {
13847 Self(self.0 + 1)
13848 }
13849
13850 fn previous_row(&self) -> Self {
13851 Self(self.0.saturating_sub(1))
13852 }
13853
13854 fn minus(&self, other: Self) -> u32 {
13855 self.0 - other.0
13856 }
13857}
13858
13859impl RowExt for MultiBufferRow {
13860 fn as_f32(&self) -> f32 {
13861 self.0 as f32
13862 }
13863
13864 fn next_row(&self) -> Self {
13865 Self(self.0 + 1)
13866 }
13867
13868 fn previous_row(&self) -> Self {
13869 Self(self.0.saturating_sub(1))
13870 }
13871
13872 fn minus(&self, other: Self) -> u32 {
13873 self.0 - other.0
13874 }
13875}
13876
13877trait RowRangeExt {
13878 type Row;
13879
13880 fn len(&self) -> usize;
13881
13882 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
13883}
13884
13885impl RowRangeExt for Range<MultiBufferRow> {
13886 type Row = MultiBufferRow;
13887
13888 fn len(&self) -> usize {
13889 (self.end.0 - self.start.0) as usize
13890 }
13891
13892 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
13893 (self.start.0..self.end.0).map(MultiBufferRow)
13894 }
13895}
13896
13897impl RowRangeExt for Range<DisplayRow> {
13898 type Row = DisplayRow;
13899
13900 fn len(&self) -> usize {
13901 (self.end.0 - self.start.0) as usize
13902 }
13903
13904 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
13905 (self.start.0..self.end.0).map(DisplayRow)
13906 }
13907}
13908
13909fn hunk_status(hunk: &MultiBufferDiffHunk) -> DiffHunkStatus {
13910 if hunk.diff_base_byte_range.is_empty() {
13911 DiffHunkStatus::Added
13912 } else if hunk.row_range.is_empty() {
13913 DiffHunkStatus::Removed
13914 } else {
13915 DiffHunkStatus::Modified
13916 }
13917}
13918
13919/// If select range has more than one line, we
13920/// just point the cursor to range.start.
13921fn check_multiline_range(buffer: &Buffer, range: Range<usize>) -> Range<usize> {
13922 if buffer.offset_to_point(range.start).row == buffer.offset_to_point(range.end).row {
13923 range
13924 } else {
13925 range.start..range.start
13926 }
13927}