1mod blink_manager;
2pub mod display_map;
3mod editor_settings;
4mod element;
5mod inlay_hint_cache;
6
7mod git;
8mod highlight_matching_bracket;
9mod hover_popover;
10pub mod items;
11mod link_go_to_definition;
12mod mouse_context_menu;
13pub mod movement;
14mod persistence;
15mod rust_analyzer_ext;
16pub mod scroll;
17pub mod selections_collection;
18
19#[cfg(test)]
20mod editor_tests;
21#[cfg(any(test, feature = "test-support"))]
22pub mod test;
23use ::git::diff::DiffHunk;
24use aho_corasick::AhoCorasick;
25use anyhow::{anyhow, Context as _, Result};
26use blink_manager::BlinkManager;
27use client::{Client, Collaborator, ParticipantIndex};
28use clock::ReplicaId;
29use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
30use convert_case::{Case, Casing};
31use copilot::Copilot;
32pub use display_map::DisplayPoint;
33use display_map::*;
34pub use editor_settings::EditorSettings;
35pub use element::{
36 Cursor, EditorElement, HighlightedRange, HighlightedRangeLine, LineWithInvisibles,
37};
38use futures::FutureExt;
39use fuzzy::{StringMatch, StringMatchCandidate};
40use git::diff_hunk_to_display;
41use gpui::{
42 actions, div, impl_actions, point, prelude::*, px, relative, rems, size, uniform_list, Action,
43 AnyElement, AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Context,
44 DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight,
45 HighlightStyle, Hsla, InputHandler, InteractiveText, KeyContext, Model, MouseButton,
46 ParentElement, Pixels, Render, SharedString, Styled, StyledText, Subscription, Task, TextStyle,
47 UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WhiteSpace, WindowContext,
48};
49use highlight_matching_bracket::refresh_matching_bracket_highlights;
50use hover_popover::{hide_hover, HoverState};
51use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
52pub use items::MAX_TAB_TITLE_LEN;
53use itertools::Itertools;
54pub use language::{char_kind, CharKind};
55use language::{
56 language_settings::{self, all_language_settings, InlayHintSettings},
57 markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CodeAction,
58 CodeLabel, Completion, CursorShape, Diagnostic, Documentation, IndentKind, IndentSize,
59 Language, LanguageRegistry, LanguageServerName, OffsetRangeExt, Point, Selection,
60 SelectionGoal, TransactionId,
61};
62
63use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState};
64use lsp::{DiagnosticSeverity, LanguageServerId};
65use mouse_context_menu::MouseContextMenu;
66use movement::TextLayoutDetails;
67use multi_buffer::ToOffsetUtf16;
68pub use multi_buffer::{
69 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset,
70 ToPoint,
71};
72use ordered_float::OrderedFloat;
73use parking_lot::RwLock;
74use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction};
75use rand::prelude::*;
76use rpc::proto::{self, *};
77use scroll::{
78 autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide,
79};
80use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
81use serde::{Deserialize, Serialize};
82use settings::{Settings, SettingsStore};
83use smallvec::SmallVec;
84use snippet::Snippet;
85use std::{
86 any::TypeId,
87 borrow::Cow,
88 cmp::{self, Ordering, Reverse},
89 mem,
90 num::NonZeroU32,
91 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
92 path::Path,
93 sync::Arc,
94 sync::Weak,
95 time::{Duration, Instant},
96};
97pub use sum_tree::Bias;
98use sum_tree::TreeMap;
99use text::{OffsetUtf16, Rope};
100use theme::{
101 observe_buffer_font_size_adjustment, ActiveTheme, PlayerColor, StatusColors, SyntaxTheme,
102 ThemeColors, ThemeSettings,
103};
104use ui::{
105 h_flex, prelude::*, ButtonSize, ButtonStyle, IconButton, IconName, IconSize, ListItem, Popover,
106 Tooltip,
107};
108use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
109use workspace::{searchable::SearchEvent, ItemNavHistory, Pane, SplitDirection, ViewId, Workspace};
110
111const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
112const MAX_LINE_LEN: usize = 1024;
113const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
114const MAX_SELECTION_HISTORY_LEN: usize = 1024;
115const COPILOT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75);
116pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
117pub const DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75);
118
119pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2);
120
121pub fn render_parsed_markdown(
122 element_id: impl Into<ElementId>,
123 parsed: &language::ParsedMarkdown,
124 editor_style: &EditorStyle,
125 workspace: Option<WeakView<Workspace>>,
126 cx: &mut ViewContext<Editor>,
127) -> InteractiveText {
128 let code_span_background_color = cx
129 .theme()
130 .colors()
131 .editor_document_highlight_read_background;
132
133 let highlights = gpui::combine_highlights(
134 parsed.highlights.iter().filter_map(|(range, highlight)| {
135 let highlight = highlight.to_highlight_style(&editor_style.syntax)?;
136 Some((range.clone(), highlight))
137 }),
138 parsed
139 .regions
140 .iter()
141 .zip(&parsed.region_ranges)
142 .filter_map(|(region, range)| {
143 if region.code {
144 Some((
145 range.clone(),
146 HighlightStyle {
147 background_color: Some(code_span_background_color),
148 ..Default::default()
149 },
150 ))
151 } else {
152 None
153 }
154 }),
155 );
156
157 let mut links = Vec::new();
158 let mut link_ranges = Vec::new();
159 for (range, region) in parsed.region_ranges.iter().zip(&parsed.regions) {
160 if let Some(link) = region.link.clone() {
161 links.push(link);
162 link_ranges.push(range.clone());
163 }
164 }
165
166 InteractiveText::new(
167 element_id,
168 StyledText::new(parsed.text.clone()).with_highlights(&editor_style.text, highlights),
169 )
170 .on_click(link_ranges, move |clicked_range_ix, cx| {
171 match &links[clicked_range_ix] {
172 markdown::Link::Web { url } => cx.open_url(url),
173 markdown::Link::Path { path } => {
174 if let Some(workspace) = &workspace {
175 _ = workspace.update(cx, |workspace, cx| {
176 workspace.open_abs_path(path.clone(), false, cx).detach();
177 });
178 }
179 }
180 }
181 })
182}
183
184#[derive(PartialEq, Clone, Deserialize, Default)]
185pub struct SelectNext {
186 #[serde(default)]
187 pub replace_newest: bool,
188}
189
190#[derive(PartialEq, Clone, Deserialize, Default)]
191pub struct SelectPrevious {
192 #[serde(default)]
193 pub replace_newest: bool,
194}
195
196#[derive(PartialEq, Clone, Deserialize, Default)]
197pub struct SelectAllMatches {
198 #[serde(default)]
199 pub replace_newest: bool,
200}
201
202#[derive(PartialEq, Clone, Deserialize, Default)]
203pub struct SelectToBeginningOfLine {
204 #[serde(default)]
205 stop_at_soft_wraps: bool,
206}
207
208#[derive(PartialEq, Clone, Deserialize, Default)]
209pub struct MovePageUp {
210 #[serde(default)]
211 center_cursor: bool,
212}
213
214#[derive(PartialEq, Clone, Deserialize, Default)]
215pub struct MovePageDown {
216 #[serde(default)]
217 center_cursor: bool,
218}
219
220#[derive(PartialEq, Clone, Deserialize, Default)]
221pub struct SelectToEndOfLine {
222 #[serde(default)]
223 stop_at_soft_wraps: bool,
224}
225
226#[derive(PartialEq, Clone, Deserialize, Default)]
227pub struct ToggleCodeActions {
228 #[serde(default)]
229 pub deployed_from_indicator: bool,
230}
231
232#[derive(PartialEq, Clone, Deserialize, Default)]
233pub struct ConfirmCompletion {
234 #[serde(default)]
235 pub item_ix: Option<usize>,
236}
237
238#[derive(PartialEq, Clone, Deserialize, Default)]
239pub struct ConfirmCodeAction {
240 #[serde(default)]
241 pub item_ix: Option<usize>,
242}
243
244#[derive(PartialEq, Clone, Deserialize, Default)]
245pub struct ToggleComments {
246 #[serde(default)]
247 pub advance_downwards: bool,
248}
249
250#[derive(PartialEq, Clone, Deserialize, Default)]
251pub struct FoldAt {
252 pub buffer_row: u32,
253}
254
255#[derive(PartialEq, Clone, Deserialize, Default)]
256pub struct UnfoldAt {
257 pub buffer_row: u32,
258}
259
260impl_actions!(
261 editor,
262 [
263 SelectNext,
264 SelectPrevious,
265 SelectAllMatches,
266 SelectToBeginningOfLine,
267 MovePageUp,
268 MovePageDown,
269 SelectToEndOfLine,
270 ToggleCodeActions,
271 ConfirmCompletion,
272 ConfirmCodeAction,
273 ToggleComments,
274 FoldAt,
275 UnfoldAt
276 ]
277);
278
279#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
280pub enum InlayId {
281 Suggestion(usize),
282 Hint(usize),
283}
284
285impl InlayId {
286 fn id(&self) -> usize {
287 match self {
288 Self::Suggestion(id) => *id,
289 Self::Hint(id) => *id,
290 }
291 }
292}
293
294actions!(
295 editor,
296 [
297 AddSelectionAbove,
298 AddSelectionBelow,
299 Backspace,
300 Cancel,
301 ConfirmRename,
302 ContextMenuFirst,
303 ContextMenuLast,
304 ContextMenuNext,
305 ContextMenuPrev,
306 ConvertToKebabCase,
307 ConvertToLowerCamelCase,
308 ConvertToLowerCase,
309 ConvertToSnakeCase,
310 ConvertToTitleCase,
311 ConvertToUpperCamelCase,
312 ConvertToUpperCase,
313 Copy,
314 CopyHighlightJson,
315 CopyPath,
316 CopyRelativePath,
317 Cut,
318 CutToEndOfLine,
319 Delete,
320 DeleteLine,
321 DeleteToBeginningOfLine,
322 DeleteToEndOfLine,
323 DeleteToNextSubwordEnd,
324 DeleteToNextWordEnd,
325 DeleteToPreviousSubwordStart,
326 DeleteToPreviousWordStart,
327 DuplicateLine,
328 ExpandMacroRecursively,
329 FindAllReferences,
330 Fold,
331 FoldSelectedRanges,
332 Format,
333 GoToDefinition,
334 GoToDefinitionSplit,
335 GoToDiagnostic,
336 GoToHunk,
337 GoToPrevDiagnostic,
338 GoToPrevHunk,
339 GoToTypeDefinition,
340 GoToTypeDefinitionSplit,
341 HalfPageDown,
342 HalfPageUp,
343 Hover,
344 Indent,
345 JoinLines,
346 LineDown,
347 LineUp,
348 MoveDown,
349 MoveLeft,
350 MoveLineDown,
351 MoveLineUp,
352 MoveRight,
353 MoveToBeginning,
354 MoveToBeginningOfLine,
355 MoveToEnclosingBracket,
356 MoveToEnd,
357 MoveToEndOfLine,
358 MoveToEndOfParagraph,
359 MoveToNextSubwordEnd,
360 MoveToNextWordEnd,
361 MoveToPreviousSubwordStart,
362 MoveToPreviousWordStart,
363 MoveToStartOfParagraph,
364 MoveUp,
365 Newline,
366 NewlineAbove,
367 NewlineBelow,
368 NextScreen,
369 OpenExcerpts,
370 Outdent,
371 PageDown,
372 PageUp,
373 Paste,
374 Redo,
375 RedoSelection,
376 Rename,
377 RestartLanguageServer,
378 RevealInFinder,
379 ReverseLines,
380 ScrollCursorBottom,
381 ScrollCursorCenter,
382 ScrollCursorTop,
383 SelectAll,
384 SelectDown,
385 SelectLargerSyntaxNode,
386 SelectLeft,
387 SelectLine,
388 SelectRight,
389 SelectSmallerSyntaxNode,
390 SelectToBeginning,
391 SelectToEnd,
392 SelectToEndOfParagraph,
393 SelectToNextSubwordEnd,
394 SelectToNextWordEnd,
395 SelectToPreviousSubwordStart,
396 SelectToPreviousWordStart,
397 SelectToStartOfParagraph,
398 SelectUp,
399 ShowCharacterPalette,
400 ShowCompletions,
401 ShuffleLines,
402 SortLinesCaseInsensitive,
403 SortLinesCaseSensitive,
404 SplitSelectionIntoLines,
405 Tab,
406 TabPrev,
407 ToggleInlayHints,
408 ToggleSoftWrap,
409 Transpose,
410 Undo,
411 UndoSelection,
412 UnfoldLines,
413 ]
414);
415
416enum DocumentHighlightRead {}
417enum DocumentHighlightWrite {}
418enum InputComposition {}
419
420#[derive(Copy, Clone, PartialEq, Eq)]
421pub enum Direction {
422 Prev,
423 Next,
424}
425
426pub fn init_settings(cx: &mut AppContext) {
427 EditorSettings::register(cx);
428}
429
430pub fn init(cx: &mut AppContext) {
431 init_settings(cx);
432
433 workspace::register_project_item::<Editor>(cx);
434 workspace::register_followable_item::<Editor>(cx);
435 workspace::register_deserializable_item::<Editor>(cx);
436 cx.observe_new_views(
437 |workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
438 workspace.register_action(Editor::new_file);
439 workspace.register_action(Editor::new_file_in_direction);
440 },
441 )
442 .detach();
443
444 cx.on_action(move |_: &workspace::NewFile, cx| {
445 let app_state = cx.global::<Weak<workspace::AppState>>();
446 if let Some(app_state) = app_state.upgrade() {
447 workspace::open_new(&app_state, cx, |workspace, cx| {
448 Editor::new_file(workspace, &Default::default(), cx)
449 })
450 .detach();
451 }
452 });
453 cx.on_action(move |_: &workspace::NewWindow, cx| {
454 let app_state = cx.global::<Weak<workspace::AppState>>();
455 if let Some(app_state) = app_state.upgrade() {
456 workspace::open_new(&app_state, cx, |workspace, cx| {
457 Editor::new_file(workspace, &Default::default(), cx)
458 })
459 .detach();
460 }
461 });
462}
463
464trait InvalidationRegion {
465 fn ranges(&self) -> &[Range<Anchor>];
466}
467
468#[derive(Clone, Debug, PartialEq)]
469pub enum SelectPhase {
470 Begin {
471 position: DisplayPoint,
472 add: bool,
473 click_count: usize,
474 },
475 BeginColumnar {
476 position: DisplayPoint,
477 goal_column: u32,
478 },
479 Extend {
480 position: DisplayPoint,
481 click_count: usize,
482 },
483 Update {
484 position: DisplayPoint,
485 goal_column: u32,
486 scroll_position: gpui::Point<f32>,
487 },
488 End,
489}
490
491#[derive(Clone, Debug)]
492pub enum SelectMode {
493 Character,
494 Word(Range<Anchor>),
495 Line(Range<Anchor>),
496 All,
497}
498
499#[derive(Copy, Clone, PartialEq, Eq, Debug)]
500pub enum EditorMode {
501 SingleLine,
502 AutoHeight { max_lines: usize },
503 Full,
504}
505
506#[derive(Clone, Debug)]
507pub enum SoftWrap {
508 None,
509 EditorWidth,
510 Column(u32),
511}
512
513#[derive(Clone)]
514pub struct EditorStyle {
515 pub background: Hsla,
516 pub local_player: PlayerColor,
517 pub text: TextStyle,
518 pub scrollbar_width: Pixels,
519 pub syntax: Arc<SyntaxTheme>,
520 pub status: StatusColors,
521 pub inlays_style: HighlightStyle,
522 pub suggestions_style: HighlightStyle,
523}
524
525impl Default for EditorStyle {
526 fn default() -> Self {
527 Self {
528 background: Hsla::default(),
529 local_player: PlayerColor::default(),
530 text: TextStyle::default(),
531 scrollbar_width: Pixels::default(),
532 syntax: Default::default(),
533 // HACK: Status colors don't have a real default.
534 // We should look into removing the status colors from the editor
535 // style and retrieve them directly from the theme.
536 status: StatusColors::dark(),
537 inlays_style: HighlightStyle::default(),
538 suggestions_style: HighlightStyle::default(),
539 }
540 }
541}
542
543type CompletionId = usize;
544
545// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
546// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
547
548type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Vec<Range<Anchor>>);
549type InlayBackgroundHighlight = (fn(&ThemeColors) -> Hsla, Vec<InlayHighlight>);
550
551pub struct Editor {
552 handle: WeakView<Self>,
553 focus_handle: FocusHandle,
554 buffer: Model<MultiBuffer>,
555 display_map: Model<DisplayMap>,
556 pub selections: SelectionsCollection,
557 pub scroll_manager: ScrollManager,
558 columnar_selection_tail: Option<Anchor>,
559 add_selections_state: Option<AddSelectionsState>,
560 select_next_state: Option<SelectNextState>,
561 select_prev_state: Option<SelectNextState>,
562 selection_history: SelectionHistory,
563 autoclose_regions: Vec<AutocloseRegion>,
564 snippet_stack: InvalidationStack<SnippetState>,
565 select_larger_syntax_node_stack: Vec<Box<[Selection<usize>]>>,
566 ime_transaction: Option<TransactionId>,
567 active_diagnostics: Option<ActiveDiagnosticGroup>,
568 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
569 project: Option<Model<Project>>,
570 collaboration_hub: Option<Box<dyn CollaborationHub>>,
571 blink_manager: Model<BlinkManager>,
572 pub show_local_selections: bool,
573 mode: EditorMode,
574 show_gutter: bool,
575 show_wrap_guides: Option<bool>,
576 placeholder_text: Option<Arc<str>>,
577 highlighted_rows: Option<Range<u32>>,
578 background_highlights: BTreeMap<TypeId, BackgroundHighlight>,
579 inlay_background_highlights: TreeMap<Option<TypeId>, InlayBackgroundHighlight>,
580 nav_history: Option<ItemNavHistory>,
581 context_menu: RwLock<Option<ContextMenu>>,
582 mouse_context_menu: Option<MouseContextMenu>,
583 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
584 next_completion_id: CompletionId,
585 available_code_actions: Option<(Model<Buffer>, Arc<[CodeAction]>)>,
586 code_actions_task: Option<Task<()>>,
587 document_highlights_task: Option<Task<()>>,
588 pending_rename: Option<RenameState>,
589 searchable: bool,
590 cursor_shape: CursorShape,
591 collapse_matches: bool,
592 autoindent_mode: Option<AutoindentMode>,
593 workspace: Option<(WeakView<Workspace>, i64)>,
594 keymap_context_layers: BTreeMap<TypeId, KeyContext>,
595 input_enabled: bool,
596 read_only: bool,
597 leader_peer_id: Option<PeerId>,
598 remote_id: Option<ViewId>,
599 hover_state: HoverState,
600 gutter_hovered: bool,
601 link_go_to_definition_state: LinkGoToDefinitionState,
602 copilot_state: CopilotState,
603 inlay_hint_cache: InlayHintCache,
604 next_inlay_id: usize,
605 _subscriptions: Vec<Subscription>,
606 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
607 gutter_width: Pixels,
608 style: Option<EditorStyle>,
609 editor_actions: Vec<Box<dyn Fn(&mut ViewContext<Self>)>>,
610 show_copilot_suggestions: bool,
611}
612
613pub struct EditorSnapshot {
614 pub mode: EditorMode,
615 pub show_gutter: bool,
616 pub display_snapshot: DisplaySnapshot,
617 pub placeholder_text: Option<Arc<str>>,
618 is_focused: bool,
619 scroll_anchor: ScrollAnchor,
620 ongoing_scroll: OngoingScroll,
621}
622
623pub struct RemoteSelection {
624 pub replica_id: ReplicaId,
625 pub selection: Selection<Anchor>,
626 pub cursor_shape: CursorShape,
627 pub peer_id: PeerId,
628 pub line_mode: bool,
629 pub participant_index: Option<ParticipantIndex>,
630}
631
632#[derive(Clone, Debug)]
633struct SelectionHistoryEntry {
634 selections: Arc<[Selection<Anchor>]>,
635 select_next_state: Option<SelectNextState>,
636 select_prev_state: Option<SelectNextState>,
637 add_selections_state: Option<AddSelectionsState>,
638}
639
640enum SelectionHistoryMode {
641 Normal,
642 Undoing,
643 Redoing,
644}
645
646impl Default for SelectionHistoryMode {
647 fn default() -> Self {
648 Self::Normal
649 }
650}
651
652#[derive(Default)]
653struct SelectionHistory {
654 #[allow(clippy::type_complexity)]
655 selections_by_transaction:
656 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
657 mode: SelectionHistoryMode,
658 undo_stack: VecDeque<SelectionHistoryEntry>,
659 redo_stack: VecDeque<SelectionHistoryEntry>,
660}
661
662impl SelectionHistory {
663 fn insert_transaction(
664 &mut self,
665 transaction_id: TransactionId,
666 selections: Arc<[Selection<Anchor>]>,
667 ) {
668 self.selections_by_transaction
669 .insert(transaction_id, (selections, None));
670 }
671
672 #[allow(clippy::type_complexity)]
673 fn transaction(
674 &self,
675 transaction_id: TransactionId,
676 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
677 self.selections_by_transaction.get(&transaction_id)
678 }
679
680 #[allow(clippy::type_complexity)]
681 fn transaction_mut(
682 &mut self,
683 transaction_id: TransactionId,
684 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
685 self.selections_by_transaction.get_mut(&transaction_id)
686 }
687
688 fn push(&mut self, entry: SelectionHistoryEntry) {
689 if !entry.selections.is_empty() {
690 match self.mode {
691 SelectionHistoryMode::Normal => {
692 self.push_undo(entry);
693 self.redo_stack.clear();
694 }
695 SelectionHistoryMode::Undoing => self.push_redo(entry),
696 SelectionHistoryMode::Redoing => self.push_undo(entry),
697 }
698 }
699 }
700
701 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
702 if self
703 .undo_stack
704 .back()
705 .map_or(true, |e| e.selections != entry.selections)
706 {
707 self.undo_stack.push_back(entry);
708 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
709 self.undo_stack.pop_front();
710 }
711 }
712 }
713
714 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
715 if self
716 .redo_stack
717 .back()
718 .map_or(true, |e| e.selections != entry.selections)
719 {
720 self.redo_stack.push_back(entry);
721 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
722 self.redo_stack.pop_front();
723 }
724 }
725 }
726}
727
728#[derive(Clone, Debug)]
729struct AddSelectionsState {
730 above: bool,
731 stack: Vec<usize>,
732}
733
734#[derive(Clone)]
735struct SelectNextState {
736 query: AhoCorasick,
737 wordwise: bool,
738 done: bool,
739}
740
741impl std::fmt::Debug for SelectNextState {
742 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
743 f.debug_struct(std::any::type_name::<Self>())
744 .field("wordwise", &self.wordwise)
745 .field("done", &self.done)
746 .finish()
747 }
748}
749
750#[derive(Debug)]
751struct AutocloseRegion {
752 selection_id: usize,
753 range: Range<Anchor>,
754 pair: BracketPair,
755}
756
757#[derive(Debug)]
758struct SnippetState {
759 ranges: Vec<Vec<Range<Anchor>>>,
760 active_index: usize,
761}
762
763pub struct RenameState {
764 pub range: Range<Anchor>,
765 pub old_name: Arc<str>,
766 pub editor: View<Editor>,
767 block_id: BlockId,
768}
769
770struct InvalidationStack<T>(Vec<T>);
771
772enum ContextMenu {
773 Completions(CompletionsMenu),
774 CodeActions(CodeActionsMenu),
775}
776
777impl ContextMenu {
778 fn select_first(
779 &mut self,
780 project: Option<&Model<Project>>,
781 cx: &mut ViewContext<Editor>,
782 ) -> bool {
783 if self.visible() {
784 match self {
785 ContextMenu::Completions(menu) => menu.select_first(project, cx),
786 ContextMenu::CodeActions(menu) => menu.select_first(cx),
787 }
788 true
789 } else {
790 false
791 }
792 }
793
794 fn select_prev(
795 &mut self,
796 project: Option<&Model<Project>>,
797 cx: &mut ViewContext<Editor>,
798 ) -> bool {
799 if self.visible() {
800 match self {
801 ContextMenu::Completions(menu) => menu.select_prev(project, cx),
802 ContextMenu::CodeActions(menu) => menu.select_prev(cx),
803 }
804 true
805 } else {
806 false
807 }
808 }
809
810 fn select_next(
811 &mut self,
812 project: Option<&Model<Project>>,
813 cx: &mut ViewContext<Editor>,
814 ) -> bool {
815 if self.visible() {
816 match self {
817 ContextMenu::Completions(menu) => menu.select_next(project, cx),
818 ContextMenu::CodeActions(menu) => menu.select_next(cx),
819 }
820 true
821 } else {
822 false
823 }
824 }
825
826 fn select_last(
827 &mut self,
828 project: Option<&Model<Project>>,
829 cx: &mut ViewContext<Editor>,
830 ) -> bool {
831 if self.visible() {
832 match self {
833 ContextMenu::Completions(menu) => menu.select_last(project, cx),
834 ContextMenu::CodeActions(menu) => menu.select_last(cx),
835 }
836 true
837 } else {
838 false
839 }
840 }
841
842 fn visible(&self) -> bool {
843 match self {
844 ContextMenu::Completions(menu) => menu.visible(),
845 ContextMenu::CodeActions(menu) => menu.visible(),
846 }
847 }
848
849 fn render(
850 &self,
851 cursor_position: DisplayPoint,
852 style: &EditorStyle,
853 max_height: Pixels,
854 workspace: Option<WeakView<Workspace>>,
855 cx: &mut ViewContext<Editor>,
856 ) -> (DisplayPoint, AnyElement) {
857 match self {
858 ContextMenu::Completions(menu) => (
859 cursor_position,
860 menu.render(style, max_height, workspace, cx),
861 ),
862 ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, max_height, cx),
863 }
864 }
865}
866
867#[derive(Clone)]
868struct CompletionsMenu {
869 id: CompletionId,
870 initial_position: Anchor,
871 buffer: Model<Buffer>,
872 completions: Arc<RwLock<Box<[Completion]>>>,
873 match_candidates: Arc<[StringMatchCandidate]>,
874 matches: Arc<[StringMatch]>,
875 selected_item: usize,
876 scroll_handle: UniformListScrollHandle,
877}
878
879impl CompletionsMenu {
880 fn select_first(&mut self, project: Option<&Model<Project>>, cx: &mut ViewContext<Editor>) {
881 self.selected_item = 0;
882 self.scroll_handle.scroll_to_item(self.selected_item);
883 self.attempt_resolve_selected_completion_documentation(project, cx);
884 cx.notify();
885 }
886
887 fn select_prev(&mut self, project: Option<&Model<Project>>, cx: &mut ViewContext<Editor>) {
888 if self.selected_item > 0 {
889 self.selected_item -= 1;
890 } else {
891 self.selected_item = self.matches.len() - 1;
892 }
893 self.scroll_handle.scroll_to_item(self.selected_item);
894 self.attempt_resolve_selected_completion_documentation(project, cx);
895 cx.notify();
896 }
897
898 fn select_next(&mut self, project: Option<&Model<Project>>, cx: &mut ViewContext<Editor>) {
899 if self.selected_item + 1 < self.matches.len() {
900 self.selected_item += 1;
901 } else {
902 self.selected_item = 0;
903 }
904 self.scroll_handle.scroll_to_item(self.selected_item);
905 self.attempt_resolve_selected_completion_documentation(project, cx);
906 cx.notify();
907 }
908
909 fn select_last(&mut self, project: Option<&Model<Project>>, cx: &mut ViewContext<Editor>) {
910 self.selected_item = self.matches.len() - 1;
911 self.scroll_handle.scroll_to_item(self.selected_item);
912 self.attempt_resolve_selected_completion_documentation(project, cx);
913 cx.notify();
914 }
915
916 fn pre_resolve_completion_documentation(
917 &self,
918 editor: &Editor,
919 cx: &mut ViewContext<Editor>,
920 ) -> Option<Task<()>> {
921 let settings = EditorSettings::get_global(cx);
922 if !settings.show_completion_documentation {
923 return None;
924 }
925
926 let Some(project) = editor.project.clone() else {
927 return None;
928 };
929
930 let client = project.read(cx).client();
931 let language_registry = project.read(cx).languages().clone();
932
933 let is_remote = project.read(cx).is_remote();
934 let project_id = project.read(cx).remote_id();
935
936 let completions = self.completions.clone();
937 let completion_indices: Vec<_> = self.matches.iter().map(|m| m.candidate_id).collect();
938
939 Some(cx.spawn(move |this, mut cx| async move {
940 if is_remote {
941 let Some(project_id) = project_id else {
942 log::error!("Remote project without remote_id");
943 return;
944 };
945
946 for completion_index in completion_indices {
947 let completions_guard = completions.read();
948 let completion = &completions_guard[completion_index];
949 if completion.documentation.is_some() {
950 continue;
951 }
952
953 let server_id = completion.server_id;
954 let completion = completion.lsp_completion.clone();
955 drop(completions_guard);
956
957 Self::resolve_completion_documentation_remote(
958 project_id,
959 server_id,
960 completions.clone(),
961 completion_index,
962 completion,
963 client.clone(),
964 language_registry.clone(),
965 )
966 .await;
967
968 _ = this.update(&mut cx, |_, cx| cx.notify());
969 }
970 } else {
971 for completion_index in completion_indices {
972 let completions_guard = completions.read();
973 let completion = &completions_guard[completion_index];
974 if completion.documentation.is_some() {
975 continue;
976 }
977
978 let server_id = completion.server_id;
979 let completion = completion.lsp_completion.clone();
980 drop(completions_guard);
981
982 let server = project
983 .read_with(&mut cx, |project, _| {
984 project.language_server_for_id(server_id)
985 })
986 .ok()
987 .flatten();
988 let Some(server) = server else {
989 return;
990 };
991
992 Self::resolve_completion_documentation_local(
993 server,
994 completions.clone(),
995 completion_index,
996 completion,
997 language_registry.clone(),
998 )
999 .await;
1000
1001 _ = this.update(&mut cx, |_, cx| cx.notify());
1002 }
1003 }
1004 }))
1005 }
1006
1007 fn attempt_resolve_selected_completion_documentation(
1008 &mut self,
1009 project: Option<&Model<Project>>,
1010 cx: &mut ViewContext<Editor>,
1011 ) {
1012 let settings = EditorSettings::get_global(cx);
1013 if !settings.show_completion_documentation {
1014 return;
1015 }
1016
1017 let completion_index = self.matches[self.selected_item].candidate_id;
1018 let Some(project) = project else {
1019 return;
1020 };
1021 let language_registry = project.read(cx).languages().clone();
1022
1023 let completions = self.completions.clone();
1024 let completions_guard = completions.read();
1025 let completion = &completions_guard[completion_index];
1026 if completion.documentation.is_some() {
1027 return;
1028 }
1029
1030 let server_id = completion.server_id;
1031 let completion = completion.lsp_completion.clone();
1032 drop(completions_guard);
1033
1034 if project.read(cx).is_remote() {
1035 let Some(project_id) = project.read(cx).remote_id() else {
1036 log::error!("Remote project without remote_id");
1037 return;
1038 };
1039
1040 let client = project.read(cx).client();
1041
1042 cx.spawn(move |this, mut cx| async move {
1043 Self::resolve_completion_documentation_remote(
1044 project_id,
1045 server_id,
1046 completions.clone(),
1047 completion_index,
1048 completion,
1049 client,
1050 language_registry.clone(),
1051 )
1052 .await;
1053
1054 _ = this.update(&mut cx, |_, cx| cx.notify());
1055 })
1056 .detach();
1057 } else {
1058 let Some(server) = project.read(cx).language_server_for_id(server_id) else {
1059 return;
1060 };
1061
1062 cx.spawn(move |this, mut cx| async move {
1063 Self::resolve_completion_documentation_local(
1064 server,
1065 completions,
1066 completion_index,
1067 completion,
1068 language_registry,
1069 )
1070 .await;
1071
1072 _ = this.update(&mut cx, |_, cx| cx.notify());
1073 })
1074 .detach();
1075 }
1076 }
1077
1078 async fn resolve_completion_documentation_remote(
1079 project_id: u64,
1080 server_id: LanguageServerId,
1081 completions: Arc<RwLock<Box<[Completion]>>>,
1082 completion_index: usize,
1083 completion: lsp::CompletionItem,
1084 client: Arc<Client>,
1085 language_registry: Arc<LanguageRegistry>,
1086 ) {
1087 let request = proto::ResolveCompletionDocumentation {
1088 project_id,
1089 language_server_id: server_id.0 as u64,
1090 lsp_completion: serde_json::to_string(&completion).unwrap().into_bytes(),
1091 };
1092
1093 let Some(response) = client
1094 .request(request)
1095 .await
1096 .context("completion documentation resolve proto request")
1097 .log_err()
1098 else {
1099 return;
1100 };
1101
1102 if response.text.is_empty() {
1103 let mut completions = completions.write();
1104 let completion = &mut completions[completion_index];
1105 completion.documentation = Some(Documentation::Undocumented);
1106 }
1107
1108 let documentation = if response.is_markdown {
1109 Documentation::MultiLineMarkdown(
1110 markdown::parse_markdown(&response.text, &language_registry, None).await,
1111 )
1112 } else if response.text.lines().count() <= 1 {
1113 Documentation::SingleLine(response.text)
1114 } else {
1115 Documentation::MultiLinePlainText(response.text)
1116 };
1117
1118 let mut completions = completions.write();
1119 let completion = &mut completions[completion_index];
1120 completion.documentation = Some(documentation);
1121 }
1122
1123 async fn resolve_completion_documentation_local(
1124 server: Arc<lsp::LanguageServer>,
1125 completions: Arc<RwLock<Box<[Completion]>>>,
1126 completion_index: usize,
1127 completion: lsp::CompletionItem,
1128 language_registry: Arc<LanguageRegistry>,
1129 ) {
1130 let can_resolve = server
1131 .capabilities()
1132 .completion_provider
1133 .as_ref()
1134 .and_then(|options| options.resolve_provider)
1135 .unwrap_or(false);
1136 if !can_resolve {
1137 return;
1138 }
1139
1140 let request = server.request::<lsp::request::ResolveCompletionItem>(completion);
1141 let Some(completion_item) = request.await.log_err() else {
1142 return;
1143 };
1144
1145 if let Some(lsp_documentation) = completion_item.documentation {
1146 let documentation = language::prepare_completion_documentation(
1147 &lsp_documentation,
1148 &language_registry,
1149 None, // TODO: Try to reasonably work out which language the completion is for
1150 )
1151 .await;
1152
1153 let mut completions = completions.write();
1154 let completion = &mut completions[completion_index];
1155 completion.documentation = Some(documentation);
1156 } else {
1157 let mut completions = completions.write();
1158 let completion = &mut completions[completion_index];
1159 completion.documentation = Some(Documentation::Undocumented);
1160 }
1161 }
1162
1163 fn visible(&self) -> bool {
1164 !self.matches.is_empty()
1165 }
1166
1167 fn render(
1168 &self,
1169 style: &EditorStyle,
1170 max_height: Pixels,
1171 workspace: Option<WeakView<Workspace>>,
1172 cx: &mut ViewContext<Editor>,
1173 ) -> AnyElement {
1174 let settings = EditorSettings::get_global(cx);
1175 let show_completion_documentation = settings.show_completion_documentation;
1176
1177 let widest_completion_ix = self
1178 .matches
1179 .iter()
1180 .enumerate()
1181 .max_by_key(|(_, mat)| {
1182 let completions = self.completions.read();
1183 let completion = &completions[mat.candidate_id];
1184 let documentation = &completion.documentation;
1185
1186 let mut len = completion.label.text.chars().count();
1187 if let Some(Documentation::SingleLine(text)) = documentation {
1188 if show_completion_documentation {
1189 len += text.chars().count();
1190 }
1191 }
1192
1193 len
1194 })
1195 .map(|(ix, _)| ix);
1196
1197 let completions = self.completions.clone();
1198 let matches = self.matches.clone();
1199 let selected_item = self.selected_item;
1200 let style = style.clone();
1201
1202 let multiline_docs = {
1203 let mat = &self.matches[selected_item];
1204 let multiline_docs = match &self.completions.read()[mat.candidate_id].documentation {
1205 Some(Documentation::MultiLinePlainText(text)) => {
1206 Some(div().child(SharedString::from(text.clone())))
1207 }
1208 Some(Documentation::MultiLineMarkdown(parsed)) => Some(div().child(
1209 render_parsed_markdown("completions_markdown", parsed, &style, workspace, cx),
1210 )),
1211 _ => None,
1212 };
1213 multiline_docs.map(|div| {
1214 div.id("multiline_docs")
1215 .max_h(max_height)
1216 .flex_1()
1217 .px_1p5()
1218 .py_1()
1219 .min_w(px(260.))
1220 .max_w(px(640.))
1221 .w(px(500.))
1222 .overflow_y_scroll()
1223 // Prevent a mouse down on documentation from being propagated to the editor,
1224 // because that would move the cursor.
1225 .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
1226 })
1227 };
1228
1229 let list = uniform_list(
1230 cx.view().clone(),
1231 "completions",
1232 matches.len(),
1233 move |_editor, range, cx| {
1234 let start_ix = range.start;
1235 let completions_guard = completions.read();
1236
1237 matches[range]
1238 .iter()
1239 .enumerate()
1240 .map(|(ix, mat)| {
1241 let item_ix = start_ix + ix;
1242 let candidate_id = mat.candidate_id;
1243 let completion = &completions_guard[candidate_id];
1244
1245 let documentation = if show_completion_documentation {
1246 &completion.documentation
1247 } else {
1248 &None
1249 };
1250
1251 let highlights = gpui::combine_highlights(
1252 mat.ranges().map(|range| (range, FontWeight::BOLD.into())),
1253 styled_runs_for_code_label(&completion.label, &style.syntax).map(
1254 |(range, mut highlight)| {
1255 // Ignore font weight for syntax highlighting, as we'll use it
1256 // for fuzzy matches.
1257 highlight.font_weight = None;
1258 (range, highlight)
1259 },
1260 ),
1261 );
1262 let completion_label = StyledText::new(completion.label.text.clone())
1263 .with_highlights(&style.text, highlights);
1264 let documentation_label =
1265 if let Some(Documentation::SingleLine(text)) = documentation {
1266 if text.trim().is_empty() {
1267 None
1268 } else {
1269 Some(
1270 h_flex().ml_4().child(
1271 Label::new(text.clone())
1272 .size(LabelSize::Small)
1273 .color(Color::Muted),
1274 ),
1275 )
1276 }
1277 } else {
1278 None
1279 };
1280
1281 div().min_w(px(220.)).max_w(px(540.)).child(
1282 ListItem::new(mat.candidate_id)
1283 .inset(true)
1284 .selected(item_ix == selected_item)
1285 .on_click(cx.listener(move |editor, _event, cx| {
1286 cx.stop_propagation();
1287 editor
1288 .confirm_completion(
1289 &ConfirmCompletion {
1290 item_ix: Some(item_ix),
1291 },
1292 cx,
1293 )
1294 .map(|task| task.detach_and_log_err(cx));
1295 }))
1296 .child(h_flex().overflow_hidden().child(completion_label))
1297 .end_slot::<Div>(documentation_label),
1298 )
1299 })
1300 .collect()
1301 },
1302 )
1303 .max_h(max_height)
1304 .track_scroll(self.scroll_handle.clone())
1305 .with_width_from_item(widest_completion_ix);
1306
1307 Popover::new()
1308 .child(list)
1309 .when_some(multiline_docs, |popover, multiline_docs| {
1310 popover.aside(multiline_docs)
1311 })
1312 .into_any_element()
1313 }
1314
1315 pub async fn filter(&mut self, query: Option<&str>, executor: BackgroundExecutor) {
1316 let mut matches = if let Some(query) = query {
1317 fuzzy::match_strings(
1318 &self.match_candidates,
1319 query,
1320 query.chars().any(|c| c.is_uppercase()),
1321 100,
1322 &Default::default(),
1323 executor,
1324 )
1325 .await
1326 } else {
1327 self.match_candidates
1328 .iter()
1329 .enumerate()
1330 .map(|(candidate_id, candidate)| StringMatch {
1331 candidate_id,
1332 score: Default::default(),
1333 positions: Default::default(),
1334 string: candidate.string.clone(),
1335 })
1336 .collect()
1337 };
1338
1339 // Remove all candidates where the query's start does not match the start of any word in the candidate
1340 if let Some(query) = query {
1341 if let Some(query_start) = query.chars().next() {
1342 matches.retain(|string_match| {
1343 split_words(&string_match.string).any(|word| {
1344 // Check that the first codepoint of the word as lowercase matches the first
1345 // codepoint of the query as lowercase
1346 word.chars()
1347 .flat_map(|codepoint| codepoint.to_lowercase())
1348 .zip(query_start.to_lowercase())
1349 .all(|(word_cp, query_cp)| word_cp == query_cp)
1350 })
1351 });
1352 }
1353 }
1354
1355 let completions = self.completions.read();
1356 matches.sort_unstable_by_key(|mat| {
1357 let completion = &completions[mat.candidate_id];
1358 (
1359 completion.lsp_completion.sort_text.as_ref(),
1360 Reverse(OrderedFloat(mat.score)),
1361 completion.sort_key(),
1362 )
1363 });
1364
1365 for mat in &mut matches {
1366 let completion = &completions[mat.candidate_id];
1367 mat.string = completion.label.text.clone();
1368 for position in &mut mat.positions {
1369 *position += completion.label.filter_range.start;
1370 }
1371 }
1372 drop(completions);
1373
1374 self.matches = matches.into();
1375 self.selected_item = 0;
1376 }
1377}
1378
1379#[derive(Clone)]
1380struct CodeActionsMenu {
1381 actions: Arc<[CodeAction]>,
1382 buffer: Model<Buffer>,
1383 selected_item: usize,
1384 scroll_handle: UniformListScrollHandle,
1385 deployed_from_indicator: bool,
1386}
1387
1388impl CodeActionsMenu {
1389 fn select_first(&mut self, cx: &mut ViewContext<Editor>) {
1390 self.selected_item = 0;
1391 self.scroll_handle.scroll_to_item(self.selected_item);
1392 cx.notify()
1393 }
1394
1395 fn select_prev(&mut self, cx: &mut ViewContext<Editor>) {
1396 if self.selected_item > 0 {
1397 self.selected_item -= 1;
1398 } else {
1399 self.selected_item = self.actions.len() - 1;
1400 }
1401 self.scroll_handle.scroll_to_item(self.selected_item);
1402 cx.notify();
1403 }
1404
1405 fn select_next(&mut self, cx: &mut ViewContext<Editor>) {
1406 if self.selected_item + 1 < self.actions.len() {
1407 self.selected_item += 1;
1408 } else {
1409 self.selected_item = 0;
1410 }
1411 self.scroll_handle.scroll_to_item(self.selected_item);
1412 cx.notify();
1413 }
1414
1415 fn select_last(&mut self, cx: &mut ViewContext<Editor>) {
1416 self.selected_item = self.actions.len() - 1;
1417 self.scroll_handle.scroll_to_item(self.selected_item);
1418 cx.notify()
1419 }
1420
1421 fn visible(&self) -> bool {
1422 !self.actions.is_empty()
1423 }
1424
1425 fn render(
1426 &self,
1427 mut cursor_position: DisplayPoint,
1428 _style: &EditorStyle,
1429 max_height: Pixels,
1430 cx: &mut ViewContext<Editor>,
1431 ) -> (DisplayPoint, AnyElement) {
1432 let actions = self.actions.clone();
1433 let selected_item = self.selected_item;
1434
1435 let element = uniform_list(
1436 cx.view().clone(),
1437 "code_actions_menu",
1438 self.actions.len(),
1439 move |_this, range, cx| {
1440 actions[range.clone()]
1441 .iter()
1442 .enumerate()
1443 .map(|(ix, action)| {
1444 let item_ix = range.start + ix;
1445 let selected = selected_item == item_ix;
1446 let colors = cx.theme().colors();
1447 div()
1448 .px_2()
1449 .text_color(colors.text)
1450 .when(selected, |style| {
1451 style
1452 .bg(colors.element_active)
1453 .text_color(colors.text_accent)
1454 })
1455 .hover(|style| {
1456 style
1457 .bg(colors.element_hover)
1458 .text_color(colors.text_accent)
1459 })
1460 .on_mouse_down(
1461 MouseButton::Left,
1462 cx.listener(move |editor, _, cx| {
1463 cx.stop_propagation();
1464 editor
1465 .confirm_code_action(
1466 &ConfirmCodeAction {
1467 item_ix: Some(item_ix),
1468 },
1469 cx,
1470 )
1471 .map(|task| task.detach_and_log_err(cx));
1472 }),
1473 )
1474 // TASK: It would be good to make lsp_action.title a SharedString to avoid allocating here.
1475 .child(SharedString::from(action.lsp_action.title.clone()))
1476 })
1477 .collect()
1478 },
1479 )
1480 .elevation_1(cx)
1481 .px_2()
1482 .py_1()
1483 .max_h(max_height)
1484 .track_scroll(self.scroll_handle.clone())
1485 .with_width_from_item(
1486 self.actions
1487 .iter()
1488 .enumerate()
1489 .max_by_key(|(_, action)| action.lsp_action.title.chars().count())
1490 .map(|(ix, _)| ix),
1491 )
1492 .into_any_element();
1493
1494 if self.deployed_from_indicator {
1495 *cursor_position.column_mut() = 0;
1496 }
1497
1498 (cursor_position, element)
1499 }
1500}
1501
1502pub struct CopilotState {
1503 excerpt_id: Option<ExcerptId>,
1504 pending_refresh: Task<Option<()>>,
1505 pending_cycling_refresh: Task<Option<()>>,
1506 cycled: bool,
1507 completions: Vec<copilot::Completion>,
1508 active_completion_index: usize,
1509 suggestion: Option<Inlay>,
1510}
1511
1512impl Default for CopilotState {
1513 fn default() -> Self {
1514 Self {
1515 excerpt_id: None,
1516 pending_cycling_refresh: Task::ready(Some(())),
1517 pending_refresh: Task::ready(Some(())),
1518 completions: Default::default(),
1519 active_completion_index: 0,
1520 cycled: false,
1521 suggestion: None,
1522 }
1523 }
1524}
1525
1526impl CopilotState {
1527 fn active_completion(&self) -> Option<&copilot::Completion> {
1528 self.completions.get(self.active_completion_index)
1529 }
1530
1531 fn text_for_active_completion(
1532 &self,
1533 cursor: Anchor,
1534 buffer: &MultiBufferSnapshot,
1535 ) -> Option<&str> {
1536 use language::ToOffset as _;
1537
1538 let completion = self.active_completion()?;
1539 let excerpt_id = self.excerpt_id?;
1540 let completion_buffer = buffer.buffer_for_excerpt(excerpt_id)?;
1541 if excerpt_id != cursor.excerpt_id
1542 || !completion.range.start.is_valid(completion_buffer)
1543 || !completion.range.end.is_valid(completion_buffer)
1544 {
1545 return None;
1546 }
1547
1548 let mut completion_range = completion.range.to_offset(&completion_buffer);
1549 let prefix_len = Self::common_prefix(
1550 completion_buffer.chars_for_range(completion_range.clone()),
1551 completion.text.chars(),
1552 );
1553 completion_range.start += prefix_len;
1554 let suffix_len = Self::common_prefix(
1555 completion_buffer.reversed_chars_for_range(completion_range.clone()),
1556 completion.text[prefix_len..].chars().rev(),
1557 );
1558 completion_range.end = completion_range.end.saturating_sub(suffix_len);
1559
1560 if completion_range.is_empty()
1561 && completion_range.start == cursor.text_anchor.to_offset(&completion_buffer)
1562 {
1563 Some(&completion.text[prefix_len..completion.text.len() - suffix_len])
1564 } else {
1565 None
1566 }
1567 }
1568
1569 fn cycle_completions(&mut self, direction: Direction) {
1570 match direction {
1571 Direction::Prev => {
1572 self.active_completion_index = if self.active_completion_index == 0 {
1573 self.completions.len().saturating_sub(1)
1574 } else {
1575 self.active_completion_index - 1
1576 };
1577 }
1578 Direction::Next => {
1579 if self.completions.len() == 0 {
1580 self.active_completion_index = 0
1581 } else {
1582 self.active_completion_index =
1583 (self.active_completion_index + 1) % self.completions.len();
1584 }
1585 }
1586 }
1587 }
1588
1589 fn push_completion(&mut self, new_completion: copilot::Completion) {
1590 for completion in &self.completions {
1591 if completion.text == new_completion.text && completion.range == new_completion.range {
1592 return;
1593 }
1594 }
1595 self.completions.push(new_completion);
1596 }
1597
1598 fn common_prefix<T1: Iterator<Item = char>, T2: Iterator<Item = char>>(a: T1, b: T2) -> usize {
1599 a.zip(b)
1600 .take_while(|(a, b)| a == b)
1601 .map(|(a, _)| a.len_utf8())
1602 .sum()
1603 }
1604}
1605
1606#[derive(Debug)]
1607struct ActiveDiagnosticGroup {
1608 primary_range: Range<Anchor>,
1609 primary_message: String,
1610 blocks: HashMap<BlockId, Diagnostic>,
1611 is_valid: bool,
1612}
1613
1614#[derive(Serialize, Deserialize)]
1615pub struct ClipboardSelection {
1616 pub len: usize,
1617 pub is_entire_line: bool,
1618 pub first_line_indent: u32,
1619}
1620
1621#[derive(Debug)]
1622pub struct NavigationData {
1623 cursor_anchor: Anchor,
1624 cursor_position: Point,
1625 scroll_anchor: ScrollAnchor,
1626 scroll_top_row: u32,
1627}
1628
1629pub struct EditorCreated(pub View<Editor>);
1630
1631enum GotoDefinitionKind {
1632 Symbol,
1633 Type,
1634}
1635
1636#[derive(Debug, Clone)]
1637enum InlayHintRefreshReason {
1638 Toggle(bool),
1639 SettingsChange(InlayHintSettings),
1640 NewLinesShown,
1641 BufferEdited(HashSet<Arc<Language>>),
1642 RefreshRequested,
1643 ExcerptsRemoved(Vec<ExcerptId>),
1644}
1645impl InlayHintRefreshReason {
1646 fn description(&self) -> &'static str {
1647 match self {
1648 Self::Toggle(_) => "toggle",
1649 Self::SettingsChange(_) => "settings change",
1650 Self::NewLinesShown => "new lines shown",
1651 Self::BufferEdited(_) => "buffer edited",
1652 Self::RefreshRequested => "refresh requested",
1653 Self::ExcerptsRemoved(_) => "excerpts removed",
1654 }
1655 }
1656}
1657
1658impl Editor {
1659 pub fn single_line(cx: &mut ViewContext<Self>) -> Self {
1660 let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), String::new()));
1661 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
1662 Self::new(EditorMode::SingleLine, buffer, None, cx)
1663 }
1664
1665 pub fn multi_line(cx: &mut ViewContext<Self>) -> Self {
1666 let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), String::new()));
1667 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
1668 Self::new(EditorMode::Full, buffer, None, cx)
1669 }
1670
1671 pub fn auto_height(max_lines: usize, cx: &mut ViewContext<Self>) -> Self {
1672 let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), String::new()));
1673 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
1674 Self::new(EditorMode::AutoHeight { max_lines }, buffer, None, cx)
1675 }
1676
1677 pub fn for_buffer(
1678 buffer: Model<Buffer>,
1679 project: Option<Model<Project>>,
1680 cx: &mut ViewContext<Self>,
1681 ) -> Self {
1682 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
1683 Self::new(EditorMode::Full, buffer, project, cx)
1684 }
1685
1686 pub fn for_multibuffer(
1687 buffer: Model<MultiBuffer>,
1688 project: Option<Model<Project>>,
1689 cx: &mut ViewContext<Self>,
1690 ) -> Self {
1691 Self::new(EditorMode::Full, buffer, project, cx)
1692 }
1693
1694 pub fn clone(&self, cx: &mut ViewContext<Self>) -> Self {
1695 let mut clone = Self::new(self.mode, self.buffer.clone(), self.project.clone(), cx);
1696 self.display_map.update(cx, |display_map, cx| {
1697 let snapshot = display_map.snapshot(cx);
1698 clone.display_map.update(cx, |display_map, cx| {
1699 display_map.set_state(&snapshot, cx);
1700 });
1701 });
1702 clone.selections.clone_state(&self.selections);
1703 clone.scroll_manager.clone_state(&self.scroll_manager);
1704 clone.searchable = self.searchable;
1705 clone
1706 }
1707
1708 fn new(
1709 mode: EditorMode,
1710 buffer: Model<MultiBuffer>,
1711 project: Option<Model<Project>>,
1712 cx: &mut ViewContext<Self>,
1713 ) -> Self {
1714 let style = cx.text_style();
1715 let font_size = style.font_size.to_pixels(cx.rem_size());
1716 let display_map = cx.new_model(|cx| {
1717 DisplayMap::new(buffer.clone(), style.font(), font_size, None, 2, 1, cx)
1718 });
1719
1720 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1721
1722 let blink_manager = cx.new_model(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1723
1724 let soft_wrap_mode_override =
1725 (mode == EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1726
1727 let mut project_subscriptions = Vec::new();
1728 if mode == EditorMode::Full {
1729 if let Some(project) = project.as_ref() {
1730 if buffer.read(cx).is_singleton() {
1731 project_subscriptions.push(cx.observe(project, |_, _, cx| {
1732 cx.emit(EditorEvent::TitleChanged);
1733 }));
1734 }
1735 project_subscriptions.push(cx.subscribe(project, |editor, _, event, cx| {
1736 if let project::Event::RefreshInlayHints = event {
1737 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1738 };
1739 }));
1740 }
1741 }
1742
1743 let inlay_hint_settings = inlay_hint_settings(
1744 selections.newest_anchor().head(),
1745 &buffer.read(cx).snapshot(cx),
1746 cx,
1747 );
1748
1749 let focus_handle = cx.focus_handle();
1750 cx.on_focus(&focus_handle, Self::handle_focus).detach();
1751 cx.on_blur(&focus_handle, Self::handle_blur).detach();
1752
1753 let mut this = Self {
1754 handle: cx.view().downgrade(),
1755 focus_handle,
1756 buffer: buffer.clone(),
1757 display_map: display_map.clone(),
1758 selections,
1759 scroll_manager: ScrollManager::new(),
1760 columnar_selection_tail: None,
1761 add_selections_state: None,
1762 select_next_state: None,
1763 select_prev_state: None,
1764 selection_history: Default::default(),
1765 autoclose_regions: Default::default(),
1766 snippet_stack: Default::default(),
1767 select_larger_syntax_node_stack: Vec::new(),
1768 ime_transaction: Default::default(),
1769 active_diagnostics: None,
1770 soft_wrap_mode_override,
1771 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1772 project,
1773 blink_manager: blink_manager.clone(),
1774 show_local_selections: true,
1775 mode,
1776 show_gutter: mode == EditorMode::Full,
1777 show_wrap_guides: None,
1778 placeholder_text: None,
1779 highlighted_rows: None,
1780 background_highlights: Default::default(),
1781 inlay_background_highlights: Default::default(),
1782 nav_history: None,
1783 context_menu: RwLock::new(None),
1784 mouse_context_menu: None,
1785 completion_tasks: Default::default(),
1786 next_completion_id: 0,
1787 next_inlay_id: 0,
1788 available_code_actions: Default::default(),
1789 code_actions_task: Default::default(),
1790 document_highlights_task: Default::default(),
1791 pending_rename: Default::default(),
1792 searchable: true,
1793 cursor_shape: Default::default(),
1794 autoindent_mode: Some(AutoindentMode::EachLine),
1795 collapse_matches: false,
1796 workspace: None,
1797 keymap_context_layers: Default::default(),
1798 input_enabled: true,
1799 read_only: false,
1800 leader_peer_id: None,
1801 remote_id: None,
1802 hover_state: Default::default(),
1803 link_go_to_definition_state: Default::default(),
1804 copilot_state: Default::default(),
1805 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1806 gutter_hovered: false,
1807 pixel_position_of_newest_cursor: None,
1808 gutter_width: Default::default(),
1809 style: None,
1810 editor_actions: Default::default(),
1811 show_copilot_suggestions: mode == EditorMode::Full,
1812 _subscriptions: vec![
1813 cx.observe(&buffer, Self::on_buffer_changed),
1814 cx.subscribe(&buffer, Self::on_buffer_event),
1815 cx.observe(&display_map, Self::on_display_map_changed),
1816 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1817 cx.observe_global::<SettingsStore>(Self::settings_changed),
1818 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1819 cx.observe_window_activation(|editor, cx| {
1820 let active = cx.is_window_active();
1821 editor.blink_manager.update(cx, |blink_manager, cx| {
1822 if active {
1823 blink_manager.enable(cx);
1824 } else {
1825 blink_manager.show_cursor(cx);
1826 blink_manager.disable(cx);
1827 }
1828 });
1829 }),
1830 ],
1831 };
1832
1833 this._subscriptions.extend(project_subscriptions);
1834
1835 this.end_selection(cx);
1836 this.scroll_manager.show_scrollbar(cx);
1837
1838 if mode == EditorMode::Full {
1839 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1840 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1841 }
1842
1843 this.report_editor_event("open", None, cx);
1844 this
1845 }
1846
1847 fn key_context(&self, cx: &AppContext) -> KeyContext {
1848 let mut key_context = KeyContext::default();
1849 key_context.add("Editor");
1850 let mode = match self.mode {
1851 EditorMode::SingleLine => "single_line",
1852 EditorMode::AutoHeight { .. } => "auto_height",
1853 EditorMode::Full => "full",
1854 };
1855 key_context.set("mode", mode);
1856 if self.pending_rename.is_some() {
1857 key_context.add("renaming");
1858 }
1859 if self.context_menu_visible() {
1860 match self.context_menu.read().as_ref() {
1861 Some(ContextMenu::Completions(_)) => {
1862 key_context.add("menu");
1863 key_context.add("showing_completions")
1864 }
1865 Some(ContextMenu::CodeActions(_)) => {
1866 key_context.add("menu");
1867 key_context.add("showing_code_actions")
1868 }
1869 None => {}
1870 }
1871 }
1872
1873 for layer in self.keymap_context_layers.values() {
1874 key_context.extend(layer);
1875 }
1876
1877 if let Some(extension) = self
1878 .buffer
1879 .read(cx)
1880 .as_singleton()
1881 .and_then(|buffer| buffer.read(cx).file()?.path().extension()?.to_str())
1882 {
1883 key_context.set("extension", extension.to_string());
1884 }
1885
1886 key_context
1887 }
1888
1889 pub fn new_file(
1890 workspace: &mut Workspace,
1891 _: &workspace::NewFile,
1892 cx: &mut ViewContext<Workspace>,
1893 ) {
1894 let project = workspace.project().clone();
1895 if project.read(cx).is_remote() {
1896 cx.propagate();
1897 } else if let Some(buffer) = project
1898 .update(cx, |project, cx| project.create_buffer("", None, cx))
1899 .log_err()
1900 {
1901 workspace.add_item(
1902 Box::new(cx.new_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx))),
1903 cx,
1904 );
1905 }
1906 }
1907
1908 pub fn new_file_in_direction(
1909 workspace: &mut Workspace,
1910 action: &workspace::NewFileInDirection,
1911 cx: &mut ViewContext<Workspace>,
1912 ) {
1913 let project = workspace.project().clone();
1914 if project.read(cx).is_remote() {
1915 cx.propagate();
1916 } else if let Some(buffer) = project
1917 .update(cx, |project, cx| project.create_buffer("", None, cx))
1918 .log_err()
1919 {
1920 workspace.split_item(
1921 action.0,
1922 Box::new(cx.new_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx))),
1923 cx,
1924 );
1925 }
1926 }
1927
1928 pub fn replica_id(&self, cx: &AppContext) -> ReplicaId {
1929 self.buffer.read(cx).replica_id()
1930 }
1931
1932 pub fn leader_peer_id(&self) -> Option<PeerId> {
1933 self.leader_peer_id
1934 }
1935
1936 pub fn buffer(&self) -> &Model<MultiBuffer> {
1937 &self.buffer
1938 }
1939
1940 pub fn workspace(&self) -> Option<View<Workspace>> {
1941 self.workspace.as_ref()?.0.upgrade()
1942 }
1943
1944 pub fn pane(&self, cx: &AppContext) -> Option<View<Pane>> {
1945 self.workspace()?.read(cx).pane_for(&self.handle.upgrade()?)
1946 }
1947
1948 pub fn title<'a>(&self, cx: &'a AppContext) -> Cow<'a, str> {
1949 self.buffer().read(cx).title(cx)
1950 }
1951
1952 pub fn snapshot(&mut self, cx: &mut WindowContext) -> EditorSnapshot {
1953 EditorSnapshot {
1954 mode: self.mode,
1955 show_gutter: self.show_gutter,
1956 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
1957 scroll_anchor: self.scroll_manager.anchor(),
1958 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
1959 placeholder_text: self.placeholder_text.clone(),
1960 is_focused: self.focus_handle.is_focused(cx),
1961 }
1962 }
1963
1964 pub fn language_at<'a, T: ToOffset>(
1965 &self,
1966 point: T,
1967 cx: &'a AppContext,
1968 ) -> Option<Arc<Language>> {
1969 self.buffer.read(cx).language_at(point, cx)
1970 }
1971
1972 pub fn file_at<'a, T: ToOffset>(
1973 &self,
1974 point: T,
1975 cx: &'a AppContext,
1976 ) -> Option<Arc<dyn language::File>> {
1977 self.buffer.read(cx).read(cx).file_at(point).cloned()
1978 }
1979
1980 pub fn active_excerpt(
1981 &self,
1982 cx: &AppContext,
1983 ) -> Option<(ExcerptId, Model<Buffer>, Range<text::Anchor>)> {
1984 self.buffer
1985 .read(cx)
1986 .excerpt_containing(self.selections.newest_anchor().head(), cx)
1987 }
1988
1989 pub fn mode(&self) -> EditorMode {
1990 self.mode
1991 }
1992
1993 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
1994 self.collaboration_hub.as_deref()
1995 }
1996
1997 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
1998 self.collaboration_hub = Some(hub);
1999 }
2000
2001 pub fn placeholder_text(&self) -> Option<&str> {
2002 self.placeholder_text.as_deref()
2003 }
2004
2005 pub fn set_placeholder_text(
2006 &mut self,
2007 placeholder_text: impl Into<Arc<str>>,
2008 cx: &mut ViewContext<Self>,
2009 ) {
2010 let placeholder_text = Some(placeholder_text.into());
2011 if self.placeholder_text != placeholder_text {
2012 self.placeholder_text = placeholder_text;
2013 cx.notify();
2014 }
2015 }
2016
2017 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut ViewContext<Self>) {
2018 self.cursor_shape = cursor_shape;
2019 cx.notify();
2020 }
2021
2022 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2023 self.collapse_matches = collapse_matches;
2024 }
2025
2026 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2027 if self.collapse_matches {
2028 return range.start..range.start;
2029 }
2030 range.clone()
2031 }
2032
2033 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut ViewContext<Self>) {
2034 if self.display_map.read(cx).clip_at_line_ends != clip {
2035 self.display_map
2036 .update(cx, |map, _| map.clip_at_line_ends = clip);
2037 }
2038 }
2039
2040 pub fn set_keymap_context_layer<Tag: 'static>(
2041 &mut self,
2042 context: KeyContext,
2043 cx: &mut ViewContext<Self>,
2044 ) {
2045 self.keymap_context_layers
2046 .insert(TypeId::of::<Tag>(), context);
2047 cx.notify();
2048 }
2049
2050 pub fn remove_keymap_context_layer<Tag: 'static>(&mut self, cx: &mut ViewContext<Self>) {
2051 self.keymap_context_layers.remove(&TypeId::of::<Tag>());
2052 cx.notify();
2053 }
2054
2055 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2056 self.input_enabled = input_enabled;
2057 }
2058
2059 pub fn set_autoindent(&mut self, autoindent: bool) {
2060 if autoindent {
2061 self.autoindent_mode = Some(AutoindentMode::EachLine);
2062 } else {
2063 self.autoindent_mode = None;
2064 }
2065 }
2066
2067 pub fn read_only(&self, cx: &AppContext) -> bool {
2068 self.read_only || self.buffer.read(cx).read_only()
2069 }
2070
2071 pub fn set_read_only(&mut self, read_only: bool) {
2072 self.read_only = read_only;
2073 }
2074
2075 pub fn set_show_copilot_suggestions(&mut self, show_copilot_suggestions: bool) {
2076 self.show_copilot_suggestions = show_copilot_suggestions;
2077 }
2078
2079 fn selections_did_change(
2080 &mut self,
2081 local: bool,
2082 old_cursor_position: &Anchor,
2083 cx: &mut ViewContext<Self>,
2084 ) {
2085 if self.focus_handle.is_focused(cx) && self.leader_peer_id.is_none() {
2086 self.buffer.update(cx, |buffer, cx| {
2087 buffer.set_active_selections(
2088 &self.selections.disjoint_anchors(),
2089 self.selections.line_mode,
2090 self.cursor_shape,
2091 cx,
2092 )
2093 });
2094 }
2095
2096 let display_map = self
2097 .display_map
2098 .update(cx, |display_map, cx| display_map.snapshot(cx));
2099 let buffer = &display_map.buffer_snapshot;
2100 self.add_selections_state = None;
2101 self.select_next_state = None;
2102 self.select_prev_state = None;
2103 self.select_larger_syntax_node_stack.clear();
2104 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2105 self.snippet_stack
2106 .invalidate(&self.selections.disjoint_anchors(), buffer);
2107 self.take_rename(false, cx);
2108
2109 let new_cursor_position = self.selections.newest_anchor().head();
2110
2111 self.push_to_nav_history(
2112 old_cursor_position.clone(),
2113 Some(new_cursor_position.to_point(buffer)),
2114 cx,
2115 );
2116
2117 if local {
2118 let new_cursor_position = self.selections.newest_anchor().head();
2119 let mut context_menu = self.context_menu.write();
2120 let completion_menu = match context_menu.as_ref() {
2121 Some(ContextMenu::Completions(menu)) => Some(menu),
2122
2123 _ => {
2124 *context_menu = None;
2125 None
2126 }
2127 };
2128
2129 if let Some(completion_menu) = completion_menu {
2130 let cursor_position = new_cursor_position.to_offset(buffer);
2131 let (word_range, kind) =
2132 buffer.surrounding_word(completion_menu.initial_position.clone());
2133 if kind == Some(CharKind::Word)
2134 && word_range.to_inclusive().contains(&cursor_position)
2135 {
2136 let mut completion_menu = completion_menu.clone();
2137 drop(context_menu);
2138
2139 let query = Self::completion_query(buffer, cursor_position);
2140 cx.spawn(move |this, mut cx| async move {
2141 completion_menu
2142 .filter(query.as_deref(), cx.background_executor().clone())
2143 .await;
2144
2145 this.update(&mut cx, |this, cx| {
2146 let mut context_menu = this.context_menu.write();
2147 let Some(ContextMenu::Completions(menu)) = context_menu.as_ref() else {
2148 return;
2149 };
2150
2151 if menu.id > completion_menu.id {
2152 return;
2153 }
2154
2155 *context_menu = Some(ContextMenu::Completions(completion_menu));
2156 drop(context_menu);
2157 cx.notify();
2158 })
2159 })
2160 .detach();
2161
2162 self.show_completions(&ShowCompletions, cx);
2163 } else {
2164 drop(context_menu);
2165 self.hide_context_menu(cx);
2166 }
2167 } else {
2168 drop(context_menu);
2169 }
2170
2171 hide_hover(self, cx);
2172
2173 if old_cursor_position.to_display_point(&display_map).row()
2174 != new_cursor_position.to_display_point(&display_map).row()
2175 {
2176 self.available_code_actions.take();
2177 }
2178 self.refresh_code_actions(cx);
2179 self.refresh_document_highlights(cx);
2180 refresh_matching_bracket_highlights(self, cx);
2181 self.discard_copilot_suggestion(cx);
2182 }
2183
2184 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2185 cx.emit(EditorEvent::SelectionsChanged { local });
2186
2187 if self.selections.disjoint_anchors().len() == 1 {
2188 cx.emit(SearchEvent::ActiveMatchChanged)
2189 }
2190
2191 cx.notify();
2192 }
2193
2194 pub fn change_selections<R>(
2195 &mut self,
2196 autoscroll: Option<Autoscroll>,
2197 cx: &mut ViewContext<Self>,
2198 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2199 ) -> R {
2200 let old_cursor_position = self.selections.newest_anchor().head();
2201 self.push_to_selection_history();
2202
2203 let (changed, result) = self.selections.change_with(cx, change);
2204
2205 if changed {
2206 if let Some(autoscroll) = autoscroll {
2207 self.request_autoscroll(autoscroll, cx);
2208 }
2209 self.selections_did_change(true, &old_cursor_position, cx);
2210 }
2211
2212 result
2213 }
2214
2215 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut ViewContext<Self>)
2216 where
2217 I: IntoIterator<Item = (Range<S>, T)>,
2218 S: ToOffset,
2219 T: Into<Arc<str>>,
2220 {
2221 if self.read_only(cx) {
2222 return;
2223 }
2224
2225 self.buffer
2226 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2227 }
2228
2229 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut ViewContext<Self>)
2230 where
2231 I: IntoIterator<Item = (Range<S>, T)>,
2232 S: ToOffset,
2233 T: Into<Arc<str>>,
2234 {
2235 if self.read_only(cx) {
2236 return;
2237 }
2238
2239 self.buffer.update(cx, |buffer, cx| {
2240 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2241 });
2242 }
2243
2244 pub fn edit_with_block_indent<I, S, T>(
2245 &mut self,
2246 edits: I,
2247 original_indent_columns: Vec<u32>,
2248 cx: &mut ViewContext<Self>,
2249 ) where
2250 I: IntoIterator<Item = (Range<S>, T)>,
2251 S: ToOffset,
2252 T: Into<Arc<str>>,
2253 {
2254 if self.read_only(cx) {
2255 return;
2256 }
2257
2258 self.buffer.update(cx, |buffer, cx| {
2259 buffer.edit(
2260 edits,
2261 Some(AutoindentMode::Block {
2262 original_indent_columns,
2263 }),
2264 cx,
2265 )
2266 });
2267 }
2268
2269 fn select(&mut self, phase: SelectPhase, cx: &mut ViewContext<Self>) {
2270 self.hide_context_menu(cx);
2271
2272 match phase {
2273 SelectPhase::Begin {
2274 position,
2275 add,
2276 click_count,
2277 } => self.begin_selection(position, add, click_count, cx),
2278 SelectPhase::BeginColumnar {
2279 position,
2280 goal_column,
2281 } => self.begin_columnar_selection(position, goal_column, cx),
2282 SelectPhase::Extend {
2283 position,
2284 click_count,
2285 } => self.extend_selection(position, click_count, cx),
2286 SelectPhase::Update {
2287 position,
2288 goal_column,
2289 scroll_position,
2290 } => self.update_selection(position, goal_column, scroll_position, cx),
2291 SelectPhase::End => self.end_selection(cx),
2292 }
2293 }
2294
2295 fn extend_selection(
2296 &mut self,
2297 position: DisplayPoint,
2298 click_count: usize,
2299 cx: &mut ViewContext<Self>,
2300 ) {
2301 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2302 let tail = self.selections.newest::<usize>(cx).tail();
2303 self.begin_selection(position, false, click_count, cx);
2304
2305 let position = position.to_offset(&display_map, Bias::Left);
2306 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
2307
2308 let mut pending_selection = self
2309 .selections
2310 .pending_anchor()
2311 .expect("extend_selection not called with pending selection");
2312 if position >= tail {
2313 pending_selection.start = tail_anchor;
2314 } else {
2315 pending_selection.end = tail_anchor;
2316 pending_selection.reversed = true;
2317 }
2318
2319 let mut pending_mode = self.selections.pending_mode().unwrap();
2320 match &mut pending_mode {
2321 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
2322 _ => {}
2323 }
2324
2325 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
2326 s.set_pending(pending_selection, pending_mode)
2327 });
2328 }
2329
2330 fn begin_selection(
2331 &mut self,
2332 position: DisplayPoint,
2333 add: bool,
2334 click_count: usize,
2335 cx: &mut ViewContext<Self>,
2336 ) {
2337 if !self.focus_handle.is_focused(cx) {
2338 cx.focus(&self.focus_handle);
2339 }
2340
2341 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2342 let buffer = &display_map.buffer_snapshot;
2343 let newest_selection = self.selections.newest_anchor().clone();
2344 let position = display_map.clip_point(position, Bias::Left);
2345
2346 let start;
2347 let end;
2348 let mode;
2349 let auto_scroll;
2350 match click_count {
2351 1 => {
2352 start = buffer.anchor_before(position.to_point(&display_map));
2353 end = start.clone();
2354 mode = SelectMode::Character;
2355 auto_scroll = true;
2356 }
2357 2 => {
2358 let range = movement::surrounding_word(&display_map, position);
2359 start = buffer.anchor_before(range.start.to_point(&display_map));
2360 end = buffer.anchor_before(range.end.to_point(&display_map));
2361 mode = SelectMode::Word(start.clone()..end.clone());
2362 auto_scroll = true;
2363 }
2364 3 => {
2365 let position = display_map
2366 .clip_point(position, Bias::Left)
2367 .to_point(&display_map);
2368 let line_start = display_map.prev_line_boundary(position).0;
2369 let next_line_start = buffer.clip_point(
2370 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2371 Bias::Left,
2372 );
2373 start = buffer.anchor_before(line_start);
2374 end = buffer.anchor_before(next_line_start);
2375 mode = SelectMode::Line(start.clone()..end.clone());
2376 auto_scroll = true;
2377 }
2378 _ => {
2379 start = buffer.anchor_before(0);
2380 end = buffer.anchor_before(buffer.len());
2381 mode = SelectMode::All;
2382 auto_scroll = false;
2383 }
2384 }
2385
2386 self.change_selections(auto_scroll.then(|| Autoscroll::newest()), cx, |s| {
2387 if !add {
2388 s.clear_disjoint();
2389 } else if click_count > 1 {
2390 s.delete(newest_selection.id)
2391 }
2392
2393 s.set_pending_anchor_range(start..end, mode);
2394 });
2395 }
2396
2397 fn begin_columnar_selection(
2398 &mut self,
2399 position: DisplayPoint,
2400 goal_column: u32,
2401 cx: &mut ViewContext<Self>,
2402 ) {
2403 if !self.focus_handle.is_focused(cx) {
2404 cx.focus(&self.focus_handle);
2405 }
2406
2407 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2408 let tail = self.selections.newest::<Point>(cx).tail();
2409 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
2410
2411 self.select_columns(
2412 tail.to_display_point(&display_map),
2413 position,
2414 goal_column,
2415 &display_map,
2416 cx,
2417 );
2418 }
2419
2420 fn update_selection(
2421 &mut self,
2422 position: DisplayPoint,
2423 goal_column: u32,
2424 scroll_position: gpui::Point<f32>,
2425 cx: &mut ViewContext<Self>,
2426 ) {
2427 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2428
2429 if let Some(tail) = self.columnar_selection_tail.as_ref() {
2430 let tail = tail.to_display_point(&display_map);
2431 self.select_columns(tail, position, goal_column, &display_map, cx);
2432 } else if let Some(mut pending) = self.selections.pending_anchor() {
2433 let buffer = self.buffer.read(cx).snapshot(cx);
2434 let head;
2435 let tail;
2436 let mode = self.selections.pending_mode().unwrap();
2437 match &mode {
2438 SelectMode::Character => {
2439 head = position.to_point(&display_map);
2440 tail = pending.tail().to_point(&buffer);
2441 }
2442 SelectMode::Word(original_range) => {
2443 let original_display_range = original_range.start.to_display_point(&display_map)
2444 ..original_range.end.to_display_point(&display_map);
2445 let original_buffer_range = original_display_range.start.to_point(&display_map)
2446 ..original_display_range.end.to_point(&display_map);
2447 if movement::is_inside_word(&display_map, position)
2448 || original_display_range.contains(&position)
2449 {
2450 let word_range = movement::surrounding_word(&display_map, position);
2451 if word_range.start < original_display_range.start {
2452 head = word_range.start.to_point(&display_map);
2453 } else {
2454 head = word_range.end.to_point(&display_map);
2455 }
2456 } else {
2457 head = position.to_point(&display_map);
2458 }
2459
2460 if head <= original_buffer_range.start {
2461 tail = original_buffer_range.end;
2462 } else {
2463 tail = original_buffer_range.start;
2464 }
2465 }
2466 SelectMode::Line(original_range) => {
2467 let original_range = original_range.to_point(&display_map.buffer_snapshot);
2468
2469 let position = display_map
2470 .clip_point(position, Bias::Left)
2471 .to_point(&display_map);
2472 let line_start = display_map.prev_line_boundary(position).0;
2473 let next_line_start = buffer.clip_point(
2474 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2475 Bias::Left,
2476 );
2477
2478 if line_start < original_range.start {
2479 head = line_start
2480 } else {
2481 head = next_line_start
2482 }
2483
2484 if head <= original_range.start {
2485 tail = original_range.end;
2486 } else {
2487 tail = original_range.start;
2488 }
2489 }
2490 SelectMode::All => {
2491 return;
2492 }
2493 };
2494
2495 if head < tail {
2496 pending.start = buffer.anchor_before(head);
2497 pending.end = buffer.anchor_before(tail);
2498 pending.reversed = true;
2499 } else {
2500 pending.start = buffer.anchor_before(tail);
2501 pending.end = buffer.anchor_before(head);
2502 pending.reversed = false;
2503 }
2504
2505 self.change_selections(None, cx, |s| {
2506 s.set_pending(pending, mode);
2507 });
2508 } else {
2509 log::error!("update_selection dispatched with no pending selection");
2510 return;
2511 }
2512
2513 self.set_scroll_position(scroll_position, cx);
2514 cx.notify();
2515 }
2516
2517 fn end_selection(&mut self, cx: &mut ViewContext<Self>) {
2518 self.columnar_selection_tail.take();
2519 if self.selections.pending_anchor().is_some() {
2520 let selections = self.selections.all::<usize>(cx);
2521 self.change_selections(None, cx, |s| {
2522 s.select(selections);
2523 s.clear_pending();
2524 });
2525 }
2526 }
2527
2528 fn select_columns(
2529 &mut self,
2530 tail: DisplayPoint,
2531 head: DisplayPoint,
2532 goal_column: u32,
2533 display_map: &DisplaySnapshot,
2534 cx: &mut ViewContext<Self>,
2535 ) {
2536 let start_row = cmp::min(tail.row(), head.row());
2537 let end_row = cmp::max(tail.row(), head.row());
2538 let start_column = cmp::min(tail.column(), goal_column);
2539 let end_column = cmp::max(tail.column(), goal_column);
2540 let reversed = start_column < tail.column();
2541
2542 let selection_ranges = (start_row..=end_row)
2543 .filter_map(|row| {
2544 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
2545 let start = display_map
2546 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
2547 .to_point(display_map);
2548 let end = display_map
2549 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
2550 .to_point(display_map);
2551 if reversed {
2552 Some(end..start)
2553 } else {
2554 Some(start..end)
2555 }
2556 } else {
2557 None
2558 }
2559 })
2560 .collect::<Vec<_>>();
2561
2562 self.change_selections(None, cx, |s| {
2563 s.select_ranges(selection_ranges);
2564 });
2565 cx.notify();
2566 }
2567
2568 pub fn has_pending_nonempty_selection(&self) -> bool {
2569 let pending_nonempty_selection = match self.selections.pending_anchor() {
2570 Some(Selection { start, end, .. }) => start != end,
2571 None => false,
2572 };
2573 pending_nonempty_selection || self.columnar_selection_tail.is_some()
2574 }
2575
2576 pub fn has_pending_selection(&self) -> bool {
2577 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
2578 }
2579
2580 pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
2581 if self.take_rename(false, cx).is_some() {
2582 return;
2583 }
2584
2585 if hide_hover(self, cx) {
2586 return;
2587 }
2588
2589 if self.hide_context_menu(cx).is_some() {
2590 return;
2591 }
2592
2593 if self.discard_copilot_suggestion(cx) {
2594 return;
2595 }
2596
2597 if self.snippet_stack.pop().is_some() {
2598 return;
2599 }
2600
2601 if self.mode == EditorMode::Full {
2602 if self.active_diagnostics.is_some() {
2603 self.dismiss_diagnostics(cx);
2604 return;
2605 }
2606
2607 if self.change_selections(Some(Autoscroll::fit()), cx, |s| s.try_cancel()) {
2608 return;
2609 }
2610 }
2611
2612 cx.propagate();
2613 }
2614
2615 pub fn handle_input(&mut self, text: &str, cx: &mut ViewContext<Self>) {
2616 let text: Arc<str> = text.into();
2617
2618 if self.read_only(cx) {
2619 return;
2620 }
2621
2622 let selections = self.selections.all_adjusted(cx);
2623 let mut brace_inserted = false;
2624 let mut edits = Vec::new();
2625 let mut new_selections = Vec::with_capacity(selections.len());
2626 let mut new_autoclose_regions = Vec::new();
2627 let snapshot = self.buffer.read(cx).read(cx);
2628
2629 for (selection, autoclose_region) in
2630 self.selections_with_autoclose_regions(selections, &snapshot)
2631 {
2632 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
2633 // Determine if the inserted text matches the opening or closing
2634 // bracket of any of this language's bracket pairs.
2635 let mut bracket_pair = None;
2636 let mut is_bracket_pair_start = false;
2637 if !text.is_empty() {
2638 // `text` can be empty when an user is using IME (e.g. Chinese Wubi Simplified)
2639 // and they are removing the character that triggered IME popup.
2640 for (pair, enabled) in scope.brackets() {
2641 if enabled && pair.close && pair.start.ends_with(text.as_ref()) {
2642 bracket_pair = Some(pair.clone());
2643 is_bracket_pair_start = true;
2644 break;
2645 } else if pair.end.as_str() == text.as_ref() {
2646 bracket_pair = Some(pair.clone());
2647 break;
2648 }
2649 }
2650 }
2651
2652 if let Some(bracket_pair) = bracket_pair {
2653 if selection.is_empty() {
2654 if is_bracket_pair_start {
2655 let prefix_len = bracket_pair.start.len() - text.len();
2656
2657 // If the inserted text is a suffix of an opening bracket and the
2658 // selection is preceded by the rest of the opening bracket, then
2659 // insert the closing bracket.
2660 let following_text_allows_autoclose = snapshot
2661 .chars_at(selection.start)
2662 .next()
2663 .map_or(true, |c| scope.should_autoclose_before(c));
2664 let preceding_text_matches_prefix = prefix_len == 0
2665 || (selection.start.column >= (prefix_len as u32)
2666 && snapshot.contains_str_at(
2667 Point::new(
2668 selection.start.row,
2669 selection.start.column - (prefix_len as u32),
2670 ),
2671 &bracket_pair.start[..prefix_len],
2672 ));
2673 if following_text_allows_autoclose && preceding_text_matches_prefix {
2674 let anchor = snapshot.anchor_before(selection.end);
2675 new_selections.push((selection.map(|_| anchor), text.len()));
2676 new_autoclose_regions.push((
2677 anchor,
2678 text.len(),
2679 selection.id,
2680 bracket_pair.clone(),
2681 ));
2682 edits.push((
2683 selection.range(),
2684 format!("{}{}", text, bracket_pair.end).into(),
2685 ));
2686 brace_inserted = true;
2687 continue;
2688 }
2689 }
2690
2691 if let Some(region) = autoclose_region {
2692 // If the selection is followed by an auto-inserted closing bracket,
2693 // then don't insert that closing bracket again; just move the selection
2694 // past the closing bracket.
2695 let should_skip = selection.end == region.range.end.to_point(&snapshot)
2696 && text.as_ref() == region.pair.end.as_str();
2697 if should_skip {
2698 let anchor = snapshot.anchor_after(selection.end);
2699 new_selections
2700 .push((selection.map(|_| anchor), region.pair.end.len()));
2701 continue;
2702 }
2703 }
2704 }
2705 // If an opening bracket is 1 character long and is typed while
2706 // text is selected, then surround that text with the bracket pair.
2707 else if is_bracket_pair_start && bracket_pair.start.chars().count() == 1 {
2708 edits.push((selection.start..selection.start, text.clone()));
2709 edits.push((
2710 selection.end..selection.end,
2711 bracket_pair.end.as_str().into(),
2712 ));
2713 brace_inserted = true;
2714 new_selections.push((
2715 Selection {
2716 id: selection.id,
2717 start: snapshot.anchor_after(selection.start),
2718 end: snapshot.anchor_before(selection.end),
2719 reversed: selection.reversed,
2720 goal: selection.goal,
2721 },
2722 0,
2723 ));
2724 continue;
2725 }
2726 }
2727 }
2728
2729 // If not handling any auto-close operation, then just replace the selected
2730 // text with the given input and move the selection to the end of the
2731 // newly inserted text.
2732 let anchor = snapshot.anchor_after(selection.end);
2733 new_selections.push((selection.map(|_| anchor), 0));
2734 edits.push((selection.start..selection.end, text.clone()));
2735 }
2736
2737 drop(snapshot);
2738 self.transact(cx, |this, cx| {
2739 this.buffer.update(cx, |buffer, cx| {
2740 buffer.edit(edits, this.autoindent_mode.clone(), cx);
2741 });
2742
2743 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
2744 let new_selection_deltas = new_selections.iter().map(|e| e.1);
2745 let snapshot = this.buffer.read(cx).read(cx);
2746 let new_selections = resolve_multiple::<usize, _>(new_anchor_selections, &snapshot)
2747 .zip(new_selection_deltas)
2748 .map(|(selection, delta)| Selection {
2749 id: selection.id,
2750 start: selection.start + delta,
2751 end: selection.end + delta,
2752 reversed: selection.reversed,
2753 goal: SelectionGoal::None,
2754 })
2755 .collect::<Vec<_>>();
2756
2757 let mut i = 0;
2758 for (position, delta, selection_id, pair) in new_autoclose_regions {
2759 let position = position.to_offset(&snapshot) + delta;
2760 let start = snapshot.anchor_before(position);
2761 let end = snapshot.anchor_after(position);
2762 while let Some(existing_state) = this.autoclose_regions.get(i) {
2763 match existing_state.range.start.cmp(&start, &snapshot) {
2764 Ordering::Less => i += 1,
2765 Ordering::Greater => break,
2766 Ordering::Equal => match end.cmp(&existing_state.range.end, &snapshot) {
2767 Ordering::Less => i += 1,
2768 Ordering::Equal => break,
2769 Ordering::Greater => break,
2770 },
2771 }
2772 }
2773 this.autoclose_regions.insert(
2774 i,
2775 AutocloseRegion {
2776 selection_id,
2777 range: start..end,
2778 pair,
2779 },
2780 );
2781 }
2782
2783 drop(snapshot);
2784 let had_active_copilot_suggestion = this.has_active_copilot_suggestion(cx);
2785 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
2786
2787 if !brace_inserted && EditorSettings::get_global(cx).use_on_type_format {
2788 if let Some(on_type_format_task) =
2789 this.trigger_on_type_formatting(text.to_string(), cx)
2790 {
2791 on_type_format_task.detach_and_log_err(cx);
2792 }
2793 }
2794
2795 if had_active_copilot_suggestion {
2796 this.refresh_copilot_suggestions(true, cx);
2797 if !this.has_active_copilot_suggestion(cx) {
2798 this.trigger_completion_on_input(&text, cx);
2799 }
2800 } else {
2801 this.trigger_completion_on_input(&text, cx);
2802 this.refresh_copilot_suggestions(true, cx);
2803 }
2804 });
2805 }
2806
2807 pub fn newline(&mut self, _: &Newline, cx: &mut ViewContext<Self>) {
2808 self.transact(cx, |this, cx| {
2809 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
2810 let selections = this.selections.all::<usize>(cx);
2811 let multi_buffer = this.buffer.read(cx);
2812 let buffer = multi_buffer.snapshot(cx);
2813 selections
2814 .iter()
2815 .map(|selection| {
2816 let start_point = selection.start.to_point(&buffer);
2817 let mut indent = buffer.indent_size_for_line(start_point.row);
2818 indent.len = cmp::min(indent.len, start_point.column);
2819 let start = selection.start;
2820 let end = selection.end;
2821 let is_cursor = start == end;
2822 let language_scope = buffer.language_scope_at(start);
2823 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
2824 &language_scope
2825 {
2826 let leading_whitespace_len = buffer
2827 .reversed_chars_at(start)
2828 .take_while(|c| c.is_whitespace() && *c != '\n')
2829 .map(|c| c.len_utf8())
2830 .sum::<usize>();
2831
2832 let trailing_whitespace_len = buffer
2833 .chars_at(end)
2834 .take_while(|c| c.is_whitespace() && *c != '\n')
2835 .map(|c| c.len_utf8())
2836 .sum::<usize>();
2837
2838 let insert_extra_newline =
2839 language.brackets().any(|(pair, enabled)| {
2840 let pair_start = pair.start.trim_end();
2841 let pair_end = pair.end.trim_start();
2842
2843 enabled
2844 && pair.newline
2845 && buffer.contains_str_at(
2846 end + trailing_whitespace_len,
2847 pair_end,
2848 )
2849 && buffer.contains_str_at(
2850 (start - leading_whitespace_len)
2851 .saturating_sub(pair_start.len()),
2852 pair_start,
2853 )
2854 });
2855 // Comment extension on newline is allowed only for cursor selections
2856 let comment_delimiter = language.line_comment_prefix().filter(|_| {
2857 let is_comment_extension_enabled =
2858 multi_buffer.settings_at(0, cx).extend_comment_on_newline;
2859 is_cursor && is_comment_extension_enabled
2860 });
2861 let comment_delimiter = if let Some(delimiter) = comment_delimiter {
2862 buffer
2863 .buffer_line_for_row(start_point.row)
2864 .is_some_and(|(snapshot, range)| {
2865 let mut index_of_first_non_whitespace = 0;
2866 let line_starts_with_comment = snapshot
2867 .chars_for_range(range)
2868 .skip_while(|c| {
2869 let should_skip = c.is_whitespace();
2870 if should_skip {
2871 index_of_first_non_whitespace += 1;
2872 }
2873 should_skip
2874 })
2875 .take(delimiter.len())
2876 .eq(delimiter.chars());
2877 let cursor_is_placed_after_comment_marker =
2878 index_of_first_non_whitespace + delimiter.len()
2879 <= start_point.column as usize;
2880 line_starts_with_comment
2881 && cursor_is_placed_after_comment_marker
2882 })
2883 .then(|| delimiter.clone())
2884 } else {
2885 None
2886 };
2887 (comment_delimiter, insert_extra_newline)
2888 } else {
2889 (None, false)
2890 };
2891
2892 let capacity_for_delimiter = comment_delimiter
2893 .as_deref()
2894 .map(str::len)
2895 .unwrap_or_default();
2896 let mut new_text =
2897 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
2898 new_text.push_str("\n");
2899 new_text.extend(indent.chars());
2900 if let Some(delimiter) = &comment_delimiter {
2901 new_text.push_str(&delimiter);
2902 }
2903 if insert_extra_newline {
2904 new_text = new_text.repeat(2);
2905 }
2906
2907 let anchor = buffer.anchor_after(end);
2908 let new_selection = selection.map(|_| anchor);
2909 (
2910 (start..end, new_text),
2911 (insert_extra_newline, new_selection),
2912 )
2913 })
2914 .unzip()
2915 };
2916
2917 this.edit_with_autoindent(edits, cx);
2918 let buffer = this.buffer.read(cx).snapshot(cx);
2919 let new_selections = selection_fixup_info
2920 .into_iter()
2921 .map(|(extra_newline_inserted, new_selection)| {
2922 let mut cursor = new_selection.end.to_point(&buffer);
2923 if extra_newline_inserted {
2924 cursor.row -= 1;
2925 cursor.column = buffer.line_len(cursor.row);
2926 }
2927 new_selection.map(|_| cursor)
2928 })
2929 .collect();
2930
2931 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
2932 this.refresh_copilot_suggestions(true, cx);
2933 });
2934 }
2935
2936 pub fn newline_above(&mut self, _: &NewlineAbove, cx: &mut ViewContext<Self>) {
2937 let buffer = self.buffer.read(cx);
2938 let snapshot = buffer.snapshot(cx);
2939
2940 let mut edits = Vec::new();
2941 let mut rows = Vec::new();
2942 let mut rows_inserted = 0;
2943
2944 for selection in self.selections.all_adjusted(cx) {
2945 let cursor = selection.head();
2946 let row = cursor.row;
2947
2948 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
2949
2950 let newline = "\n".to_string();
2951 edits.push((start_of_line..start_of_line, newline));
2952
2953 rows.push(row + rows_inserted);
2954 rows_inserted += 1;
2955 }
2956
2957 self.transact(cx, |editor, cx| {
2958 editor.edit(edits, cx);
2959
2960 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
2961 let mut index = 0;
2962 s.move_cursors_with(|map, _, _| {
2963 let row = rows[index];
2964 index += 1;
2965
2966 let point = Point::new(row, 0);
2967 let boundary = map.next_line_boundary(point).1;
2968 let clipped = map.clip_point(boundary, Bias::Left);
2969
2970 (clipped, SelectionGoal::None)
2971 });
2972 });
2973
2974 let mut indent_edits = Vec::new();
2975 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
2976 for row in rows {
2977 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
2978 for (row, indent) in indents {
2979 if indent.len == 0 {
2980 continue;
2981 }
2982
2983 let text = match indent.kind {
2984 IndentKind::Space => " ".repeat(indent.len as usize),
2985 IndentKind::Tab => "\t".repeat(indent.len as usize),
2986 };
2987 let point = Point::new(row, 0);
2988 indent_edits.push((point..point, text));
2989 }
2990 }
2991 editor.edit(indent_edits, cx);
2992 });
2993 }
2994
2995 pub fn newline_below(&mut self, _: &NewlineBelow, cx: &mut ViewContext<Self>) {
2996 let buffer = self.buffer.read(cx);
2997 let snapshot = buffer.snapshot(cx);
2998
2999 let mut edits = Vec::new();
3000 let mut rows = Vec::new();
3001 let mut rows_inserted = 0;
3002
3003 for selection in self.selections.all_adjusted(cx) {
3004 let cursor = selection.head();
3005 let row = cursor.row;
3006
3007 let point = Point::new(row + 1, 0);
3008 let start_of_line = snapshot.clip_point(point, Bias::Left);
3009
3010 let newline = "\n".to_string();
3011 edits.push((start_of_line..start_of_line, newline));
3012
3013 rows_inserted += 1;
3014 rows.push(row + rows_inserted);
3015 }
3016
3017 self.transact(cx, |editor, cx| {
3018 editor.edit(edits, cx);
3019
3020 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
3021 let mut index = 0;
3022 s.move_cursors_with(|map, _, _| {
3023 let row = rows[index];
3024 index += 1;
3025
3026 let point = Point::new(row, 0);
3027 let boundary = map.next_line_boundary(point).1;
3028 let clipped = map.clip_point(boundary, Bias::Left);
3029
3030 (clipped, SelectionGoal::None)
3031 });
3032 });
3033
3034 let mut indent_edits = Vec::new();
3035 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3036 for row in rows {
3037 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3038 for (row, indent) in indents {
3039 if indent.len == 0 {
3040 continue;
3041 }
3042
3043 let text = match indent.kind {
3044 IndentKind::Space => " ".repeat(indent.len as usize),
3045 IndentKind::Tab => "\t".repeat(indent.len as usize),
3046 };
3047 let point = Point::new(row, 0);
3048 indent_edits.push((point..point, text));
3049 }
3050 }
3051 editor.edit(indent_edits, cx);
3052 });
3053 }
3054
3055 pub fn insert(&mut self, text: &str, cx: &mut ViewContext<Self>) {
3056 self.insert_with_autoindent_mode(
3057 text,
3058 Some(AutoindentMode::Block {
3059 original_indent_columns: Vec::new(),
3060 }),
3061 cx,
3062 );
3063 }
3064
3065 fn insert_with_autoindent_mode(
3066 &mut self,
3067 text: &str,
3068 autoindent_mode: Option<AutoindentMode>,
3069 cx: &mut ViewContext<Self>,
3070 ) {
3071 if self.read_only(cx) {
3072 return;
3073 }
3074
3075 let text: Arc<str> = text.into();
3076 self.transact(cx, |this, cx| {
3077 let old_selections = this.selections.all_adjusted(cx);
3078 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
3079 let anchors = {
3080 let snapshot = buffer.read(cx);
3081 old_selections
3082 .iter()
3083 .map(|s| {
3084 let anchor = snapshot.anchor_after(s.head());
3085 s.map(|_| anchor)
3086 })
3087 .collect::<Vec<_>>()
3088 };
3089 buffer.edit(
3090 old_selections
3091 .iter()
3092 .map(|s| (s.start..s.end, text.clone())),
3093 autoindent_mode,
3094 cx,
3095 );
3096 anchors
3097 });
3098
3099 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
3100 s.select_anchors(selection_anchors);
3101 })
3102 });
3103 }
3104
3105 fn trigger_completion_on_input(&mut self, text: &str, cx: &mut ViewContext<Self>) {
3106 if !EditorSettings::get_global(cx).show_completions_on_input {
3107 return;
3108 }
3109
3110 let selection = self.selections.newest_anchor();
3111 if self
3112 .buffer
3113 .read(cx)
3114 .is_completion_trigger(selection.head(), text, cx)
3115 {
3116 self.show_completions(&ShowCompletions, cx);
3117 } else {
3118 self.hide_context_menu(cx);
3119 }
3120 }
3121
3122 /// If any empty selections is touching the start of its innermost containing autoclose
3123 /// region, expand it to select the brackets.
3124 fn select_autoclose_pair(&mut self, cx: &mut ViewContext<Self>) {
3125 let selections = self.selections.all::<usize>(cx);
3126 let buffer = self.buffer.read(cx).read(cx);
3127 let mut new_selections = Vec::new();
3128 for (mut selection, region) in self.selections_with_autoclose_regions(selections, &buffer) {
3129 if let (Some(region), true) = (region, selection.is_empty()) {
3130 let mut range = region.range.to_offset(&buffer);
3131 if selection.start == range.start {
3132 if range.start >= region.pair.start.len() {
3133 range.start -= region.pair.start.len();
3134 if buffer.contains_str_at(range.start, ®ion.pair.start) {
3135 if buffer.contains_str_at(range.end, ®ion.pair.end) {
3136 range.end += region.pair.end.len();
3137 selection.start = range.start;
3138 selection.end = range.end;
3139 }
3140 }
3141 }
3142 }
3143 }
3144 new_selections.push(selection);
3145 }
3146
3147 drop(buffer);
3148 self.change_selections(None, cx, |selections| selections.select(new_selections));
3149 }
3150
3151 /// Iterate the given selections, and for each one, find the smallest surrounding
3152 /// autoclose region. This uses the ordering of the selections and the autoclose
3153 /// regions to avoid repeated comparisons.
3154 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
3155 &'a self,
3156 selections: impl IntoIterator<Item = Selection<D>>,
3157 buffer: &'a MultiBufferSnapshot,
3158 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
3159 let mut i = 0;
3160 let mut regions = self.autoclose_regions.as_slice();
3161 selections.into_iter().map(move |selection| {
3162 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
3163
3164 let mut enclosing = None;
3165 while let Some(pair_state) = regions.get(i) {
3166 if pair_state.range.end.to_offset(buffer) < range.start {
3167 regions = ®ions[i + 1..];
3168 i = 0;
3169 } else if pair_state.range.start.to_offset(buffer) > range.end {
3170 break;
3171 } else {
3172 if pair_state.selection_id == selection.id {
3173 enclosing = Some(pair_state);
3174 }
3175 i += 1;
3176 }
3177 }
3178
3179 (selection.clone(), enclosing)
3180 })
3181 }
3182
3183 /// Remove any autoclose regions that no longer contain their selection.
3184 fn invalidate_autoclose_regions(
3185 &mut self,
3186 mut selections: &[Selection<Anchor>],
3187 buffer: &MultiBufferSnapshot,
3188 ) {
3189 self.autoclose_regions.retain(|state| {
3190 let mut i = 0;
3191 while let Some(selection) = selections.get(i) {
3192 if selection.end.cmp(&state.range.start, buffer).is_lt() {
3193 selections = &selections[1..];
3194 continue;
3195 }
3196 if selection.start.cmp(&state.range.end, buffer).is_gt() {
3197 break;
3198 }
3199 if selection.id == state.selection_id {
3200 return true;
3201 } else {
3202 i += 1;
3203 }
3204 }
3205 false
3206 });
3207 }
3208
3209 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
3210 let offset = position.to_offset(buffer);
3211 let (word_range, kind) = buffer.surrounding_word(offset);
3212 if offset > word_range.start && kind == Some(CharKind::Word) {
3213 Some(
3214 buffer
3215 .text_for_range(word_range.start..offset)
3216 .collect::<String>(),
3217 )
3218 } else {
3219 None
3220 }
3221 }
3222
3223 pub fn toggle_inlay_hints(&mut self, _: &ToggleInlayHints, cx: &mut ViewContext<Self>) {
3224 self.refresh_inlay_hints(
3225 InlayHintRefreshReason::Toggle(!self.inlay_hint_cache.enabled),
3226 cx,
3227 );
3228 }
3229
3230 pub fn inlay_hints_enabled(&self) -> bool {
3231 self.inlay_hint_cache.enabled
3232 }
3233
3234 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut ViewContext<Self>) {
3235 if self.project.is_none() || self.mode != EditorMode::Full {
3236 return;
3237 }
3238
3239 let reason_description = reason.description();
3240 let (invalidate_cache, required_languages) = match reason {
3241 InlayHintRefreshReason::Toggle(enabled) => {
3242 self.inlay_hint_cache.enabled = enabled;
3243 if enabled {
3244 (InvalidationStrategy::RefreshRequested, None)
3245 } else {
3246 self.inlay_hint_cache.clear();
3247 self.splice_inlay_hints(
3248 self.visible_inlay_hints(cx)
3249 .iter()
3250 .map(|inlay| inlay.id)
3251 .collect(),
3252 Vec::new(),
3253 cx,
3254 );
3255 return;
3256 }
3257 }
3258 InlayHintRefreshReason::SettingsChange(new_settings) => {
3259 match self.inlay_hint_cache.update_settings(
3260 &self.buffer,
3261 new_settings,
3262 self.visible_inlay_hints(cx),
3263 cx,
3264 ) {
3265 ControlFlow::Break(Some(InlaySplice {
3266 to_remove,
3267 to_insert,
3268 })) => {
3269 self.splice_inlay_hints(to_remove, to_insert, cx);
3270 return;
3271 }
3272 ControlFlow::Break(None) => return,
3273 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
3274 }
3275 }
3276 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
3277 if let Some(InlaySplice {
3278 to_remove,
3279 to_insert,
3280 }) = self.inlay_hint_cache.remove_excerpts(excerpts_removed)
3281 {
3282 self.splice_inlay_hints(to_remove, to_insert, cx);
3283 }
3284 return;
3285 }
3286 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
3287 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
3288 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
3289 }
3290 InlayHintRefreshReason::RefreshRequested => {
3291 (InvalidationStrategy::RefreshRequested, None)
3292 }
3293 };
3294
3295 if let Some(InlaySplice {
3296 to_remove,
3297 to_insert,
3298 }) = self.inlay_hint_cache.spawn_hint_refresh(
3299 reason_description,
3300 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
3301 invalidate_cache,
3302 cx,
3303 ) {
3304 self.splice_inlay_hints(to_remove, to_insert, cx);
3305 }
3306 }
3307
3308 fn visible_inlay_hints(&self, cx: &ViewContext<'_, Editor>) -> Vec<Inlay> {
3309 self.display_map
3310 .read(cx)
3311 .current_inlays()
3312 .filter(move |inlay| {
3313 Some(inlay.id) != self.copilot_state.suggestion.as_ref().map(|h| h.id)
3314 })
3315 .cloned()
3316 .collect()
3317 }
3318
3319 pub fn excerpts_for_inlay_hints_query(
3320 &self,
3321 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
3322 cx: &mut ViewContext<Editor>,
3323 ) -> HashMap<ExcerptId, (Model<Buffer>, clock::Global, Range<usize>)> {
3324 let Some(project) = self.project.as_ref() else {
3325 return HashMap::default();
3326 };
3327 let project = project.read(cx);
3328 let multi_buffer = self.buffer().read(cx);
3329 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
3330 let multi_buffer_visible_start = self
3331 .scroll_manager
3332 .anchor()
3333 .anchor
3334 .to_point(&multi_buffer_snapshot);
3335 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
3336 multi_buffer_visible_start
3337 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
3338 Bias::Left,
3339 );
3340 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
3341 multi_buffer
3342 .range_to_buffer_ranges(multi_buffer_visible_range, cx)
3343 .into_iter()
3344 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
3345 .filter_map(|(buffer_handle, excerpt_visible_range, excerpt_id)| {
3346 let buffer = buffer_handle.read(cx);
3347 let buffer_file = project::worktree::File::from_dyn(buffer.file())?;
3348 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
3349 let worktree_entry = buffer_worktree
3350 .read(cx)
3351 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
3352 if worktree_entry.is_ignored {
3353 return None;
3354 }
3355
3356 let language = buffer.language()?;
3357 if let Some(restrict_to_languages) = restrict_to_languages {
3358 if !restrict_to_languages.contains(language) {
3359 return None;
3360 }
3361 }
3362 Some((
3363 excerpt_id,
3364 (
3365 buffer_handle,
3366 buffer.version().clone(),
3367 excerpt_visible_range,
3368 ),
3369 ))
3370 })
3371 .collect()
3372 }
3373
3374 pub fn text_layout_details(&self, cx: &WindowContext) -> TextLayoutDetails {
3375 TextLayoutDetails {
3376 text_system: cx.text_system().clone(),
3377 editor_style: self.style.clone().unwrap(),
3378 rem_size: cx.rem_size(),
3379 }
3380 }
3381
3382 fn splice_inlay_hints(
3383 &self,
3384 to_remove: Vec<InlayId>,
3385 to_insert: Vec<Inlay>,
3386 cx: &mut ViewContext<Self>,
3387 ) {
3388 self.display_map.update(cx, |display_map, cx| {
3389 display_map.splice_inlays(to_remove, to_insert, cx);
3390 });
3391 cx.notify();
3392 }
3393
3394 fn trigger_on_type_formatting(
3395 &self,
3396 input: String,
3397 cx: &mut ViewContext<Self>,
3398 ) -> Option<Task<Result<()>>> {
3399 if input.len() != 1 {
3400 return None;
3401 }
3402
3403 let project = self.project.as_ref()?;
3404 let position = self.selections.newest_anchor().head();
3405 let (buffer, buffer_position) = self
3406 .buffer
3407 .read(cx)
3408 .text_anchor_for_position(position.clone(), cx)?;
3409
3410 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
3411 // hence we do LSP request & edit on host side only — add formats to host's history.
3412 let push_to_lsp_host_history = true;
3413 // If this is not the host, append its history with new edits.
3414 let push_to_client_history = project.read(cx).is_remote();
3415
3416 let on_type_formatting = project.update(cx, |project, cx| {
3417 project.on_type_format(
3418 buffer.clone(),
3419 buffer_position,
3420 input,
3421 push_to_lsp_host_history,
3422 cx,
3423 )
3424 });
3425 Some(cx.spawn(|editor, mut cx| async move {
3426 if let Some(transaction) = on_type_formatting.await? {
3427 if push_to_client_history {
3428 buffer
3429 .update(&mut cx, |buffer, _| {
3430 buffer.push_transaction(transaction, Instant::now());
3431 })
3432 .ok();
3433 }
3434 editor.update(&mut cx, |editor, cx| {
3435 editor.refresh_document_highlights(cx);
3436 })?;
3437 }
3438 Ok(())
3439 }))
3440 }
3441
3442 fn show_completions(&mut self, _: &ShowCompletions, cx: &mut ViewContext<Self>) {
3443 if self.pending_rename.is_some() {
3444 return;
3445 }
3446
3447 let project = if let Some(project) = self.project.clone() {
3448 project
3449 } else {
3450 return;
3451 };
3452
3453 let position = self.selections.newest_anchor().head();
3454 let (buffer, buffer_position) = if let Some(output) = self
3455 .buffer
3456 .read(cx)
3457 .text_anchor_for_position(position.clone(), cx)
3458 {
3459 output
3460 } else {
3461 return;
3462 };
3463
3464 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position.clone());
3465 let completions = project.update(cx, |project, cx| {
3466 project.completions(&buffer, buffer_position, cx)
3467 });
3468
3469 let id = post_inc(&mut self.next_completion_id);
3470 let task = cx.spawn(|this, mut cx| {
3471 async move {
3472 let completions = completions.await.log_err();
3473 let (menu, pre_resolve_task) = if let Some(completions) = completions {
3474 let mut menu = CompletionsMenu {
3475 id,
3476 initial_position: position,
3477 match_candidates: completions
3478 .iter()
3479 .enumerate()
3480 .map(|(id, completion)| {
3481 StringMatchCandidate::new(
3482 id,
3483 completion.label.text[completion.label.filter_range.clone()]
3484 .into(),
3485 )
3486 })
3487 .collect(),
3488 buffer,
3489 completions: Arc::new(RwLock::new(completions.into())),
3490 matches: Vec::new().into(),
3491 selected_item: 0,
3492 scroll_handle: UniformListScrollHandle::new(),
3493 };
3494 menu.filter(query.as_deref(), cx.background_executor().clone())
3495 .await;
3496
3497 if menu.matches.is_empty() {
3498 (None, None)
3499 } else {
3500 let pre_resolve_task = this
3501 .update(&mut cx, |editor, cx| {
3502 menu.pre_resolve_completion_documentation(editor, cx)
3503 })
3504 .ok()
3505 .flatten();
3506 (Some(menu), pre_resolve_task)
3507 }
3508 } else {
3509 (None, None)
3510 };
3511
3512 this.update(&mut cx, |this, cx| {
3513 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
3514
3515 let mut context_menu = this.context_menu.write();
3516 match context_menu.as_ref() {
3517 None => {}
3518
3519 Some(ContextMenu::Completions(prev_menu)) => {
3520 if prev_menu.id > id {
3521 return;
3522 }
3523 }
3524
3525 _ => return,
3526 }
3527
3528 if this.focus_handle.is_focused(cx) && menu.is_some() {
3529 let menu = menu.unwrap();
3530 *context_menu = Some(ContextMenu::Completions(menu));
3531 drop(context_menu);
3532 this.discard_copilot_suggestion(cx);
3533 cx.notify();
3534 } else if this.completion_tasks.len() <= 1 {
3535 // If there are no more completion tasks and the last menu was
3536 // empty, we should hide it. If it was already hidden, we should
3537 // also show the copilot suggestion when available.
3538 drop(context_menu);
3539 if this.hide_context_menu(cx).is_none() {
3540 this.update_visible_copilot_suggestion(cx);
3541 }
3542 }
3543 })?;
3544
3545 if let Some(pre_resolve_task) = pre_resolve_task {
3546 pre_resolve_task.await;
3547 }
3548
3549 Ok::<_, anyhow::Error>(())
3550 }
3551 .log_err()
3552 });
3553
3554 self.completion_tasks.push((id, task));
3555 }
3556
3557 pub fn confirm_completion(
3558 &mut self,
3559 action: &ConfirmCompletion,
3560 cx: &mut ViewContext<Self>,
3561 ) -> Option<Task<Result<()>>> {
3562 use language::ToOffset as _;
3563
3564 let completions_menu = if let ContextMenu::Completions(menu) = self.hide_context_menu(cx)? {
3565 menu
3566 } else {
3567 return None;
3568 };
3569
3570 let mat = completions_menu
3571 .matches
3572 .get(action.item_ix.unwrap_or(completions_menu.selected_item))?;
3573 let buffer_handle = completions_menu.buffer;
3574 let completions = completions_menu.completions.read();
3575 let completion = completions.get(mat.candidate_id)?;
3576
3577 let snippet;
3578 let text;
3579 if completion.is_snippet() {
3580 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
3581 text = snippet.as_ref().unwrap().text.clone();
3582 } else {
3583 snippet = None;
3584 text = completion.new_text.clone();
3585 };
3586 let selections = self.selections.all::<usize>(cx);
3587 let buffer = buffer_handle.read(cx);
3588 let old_range = completion.old_range.to_offset(buffer);
3589 let old_text = buffer.text_for_range(old_range.clone()).collect::<String>();
3590
3591 let newest_selection = self.selections.newest_anchor();
3592 if newest_selection.start.buffer_id != Some(buffer_handle.read(cx).remote_id()) {
3593 return None;
3594 }
3595
3596 let lookbehind = newest_selection
3597 .start
3598 .text_anchor
3599 .to_offset(buffer)
3600 .saturating_sub(old_range.start);
3601 let lookahead = old_range
3602 .end
3603 .saturating_sub(newest_selection.end.text_anchor.to_offset(buffer));
3604 let mut common_prefix_len = old_text
3605 .bytes()
3606 .zip(text.bytes())
3607 .take_while(|(a, b)| a == b)
3608 .count();
3609
3610 let snapshot = self.buffer.read(cx).snapshot(cx);
3611 let mut range_to_replace: Option<Range<isize>> = None;
3612 let mut ranges = Vec::new();
3613 for selection in &selections {
3614 if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) {
3615 let start = selection.start.saturating_sub(lookbehind);
3616 let end = selection.end + lookahead;
3617 if selection.id == newest_selection.id {
3618 range_to_replace = Some(
3619 ((start + common_prefix_len) as isize - selection.start as isize)
3620 ..(end as isize - selection.start as isize),
3621 );
3622 }
3623 ranges.push(start + common_prefix_len..end);
3624 } else {
3625 common_prefix_len = 0;
3626 ranges.clear();
3627 ranges.extend(selections.iter().map(|s| {
3628 if s.id == newest_selection.id {
3629 range_to_replace = Some(
3630 old_range.start.to_offset_utf16(&snapshot).0 as isize
3631 - selection.start as isize
3632 ..old_range.end.to_offset_utf16(&snapshot).0 as isize
3633 - selection.start as isize,
3634 );
3635 old_range.clone()
3636 } else {
3637 s.start..s.end
3638 }
3639 }));
3640 break;
3641 }
3642 }
3643 let text = &text[common_prefix_len..];
3644
3645 cx.emit(EditorEvent::InputHandled {
3646 utf16_range_to_replace: range_to_replace,
3647 text: text.into(),
3648 });
3649
3650 self.transact(cx, |this, cx| {
3651 if let Some(mut snippet) = snippet {
3652 snippet.text = text.to_string();
3653 for tabstop in snippet.tabstops.iter_mut().flatten() {
3654 tabstop.start -= common_prefix_len as isize;
3655 tabstop.end -= common_prefix_len as isize;
3656 }
3657
3658 this.insert_snippet(&ranges, snippet, cx).log_err();
3659 } else {
3660 this.buffer.update(cx, |buffer, cx| {
3661 buffer.edit(
3662 ranges.iter().map(|range| (range.clone(), text)),
3663 this.autoindent_mode.clone(),
3664 cx,
3665 );
3666 });
3667 }
3668
3669 this.refresh_copilot_suggestions(true, cx);
3670 });
3671
3672 let project = self.project.clone()?;
3673 let apply_edits = project.update(cx, |project, cx| {
3674 project.apply_additional_edits_for_completion(
3675 buffer_handle,
3676 completion.clone(),
3677 true,
3678 cx,
3679 )
3680 });
3681 Some(cx.foreground_executor().spawn(async move {
3682 apply_edits.await?;
3683 Ok(())
3684 }))
3685 }
3686
3687 pub fn toggle_code_actions(&mut self, action: &ToggleCodeActions, cx: &mut ViewContext<Self>) {
3688 let mut context_menu = self.context_menu.write();
3689 if matches!(context_menu.as_ref(), Some(ContextMenu::CodeActions(_))) {
3690 *context_menu = None;
3691 cx.notify();
3692 return;
3693 }
3694 drop(context_menu);
3695
3696 let deployed_from_indicator = action.deployed_from_indicator;
3697 let mut task = self.code_actions_task.take();
3698 cx.spawn(|this, mut cx| async move {
3699 while let Some(prev_task) = task {
3700 prev_task.await;
3701 task = this.update(&mut cx, |this, _| this.code_actions_task.take())?;
3702 }
3703
3704 this.update(&mut cx, |this, cx| {
3705 if this.focus_handle.is_focused(cx) {
3706 if let Some((buffer, actions)) = this.available_code_actions.clone() {
3707 this.completion_tasks.clear();
3708 this.discard_copilot_suggestion(cx);
3709 *this.context_menu.write() =
3710 Some(ContextMenu::CodeActions(CodeActionsMenu {
3711 buffer,
3712 actions,
3713 selected_item: Default::default(),
3714 scroll_handle: UniformListScrollHandle::default(),
3715 deployed_from_indicator,
3716 }));
3717 cx.notify();
3718 }
3719 }
3720 })?;
3721
3722 Ok::<_, anyhow::Error>(())
3723 })
3724 .detach_and_log_err(cx);
3725 }
3726
3727 pub fn confirm_code_action(
3728 &mut self,
3729 action: &ConfirmCodeAction,
3730 cx: &mut ViewContext<Self>,
3731 ) -> Option<Task<Result<()>>> {
3732 let actions_menu = if let ContextMenu::CodeActions(menu) = self.hide_context_menu(cx)? {
3733 menu
3734 } else {
3735 return None;
3736 };
3737 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
3738 let action = actions_menu.actions.get(action_ix)?.clone();
3739 let title = action.lsp_action.title.clone();
3740 let buffer = actions_menu.buffer;
3741 let workspace = self.workspace()?;
3742
3743 let apply_code_actions = workspace
3744 .read(cx)
3745 .project()
3746 .clone()
3747 .update(cx, |project, cx| {
3748 project.apply_code_action(buffer, action, true, cx)
3749 });
3750 let workspace = workspace.downgrade();
3751 Some(cx.spawn(|editor, cx| async move {
3752 let project_transaction = apply_code_actions.await?;
3753 Self::open_project_transaction(&editor, workspace, project_transaction, title, cx).await
3754 }))
3755 }
3756
3757 async fn open_project_transaction(
3758 this: &WeakView<Editor>,
3759 workspace: WeakView<Workspace>,
3760 transaction: ProjectTransaction,
3761 title: String,
3762 mut cx: AsyncWindowContext,
3763 ) -> Result<()> {
3764 let replica_id = this.update(&mut cx, |this, cx| this.replica_id(cx))?;
3765
3766 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
3767 cx.update(|_, cx| {
3768 entries.sort_unstable_by_key(|(buffer, _)| {
3769 buffer.read(cx).file().map(|f| f.path().clone())
3770 });
3771 })?;
3772
3773 // If the project transaction's edits are all contained within this editor, then
3774 // avoid opening a new editor to display them.
3775
3776 if let Some((buffer, transaction)) = entries.first() {
3777 if entries.len() == 1 {
3778 let excerpt = this.update(&mut cx, |editor, cx| {
3779 editor
3780 .buffer()
3781 .read(cx)
3782 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
3783 })?;
3784 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
3785 if excerpted_buffer == *buffer {
3786 let all_edits_within_excerpt = buffer.read_with(&cx, |buffer, _| {
3787 let excerpt_range = excerpt_range.to_offset(buffer);
3788 buffer
3789 .edited_ranges_for_transaction::<usize>(transaction)
3790 .all(|range| {
3791 excerpt_range.start <= range.start
3792 && excerpt_range.end >= range.end
3793 })
3794 })?;
3795
3796 if all_edits_within_excerpt {
3797 return Ok(());
3798 }
3799 }
3800 }
3801 }
3802 } else {
3803 return Ok(());
3804 }
3805
3806 let mut ranges_to_highlight = Vec::new();
3807 let excerpt_buffer = cx.new_model(|cx| {
3808 let mut multibuffer =
3809 MultiBuffer::new(replica_id, Capability::ReadWrite).with_title(title);
3810 for (buffer_handle, transaction) in &entries {
3811 let buffer = buffer_handle.read(cx);
3812 ranges_to_highlight.extend(
3813 multibuffer.push_excerpts_with_context_lines(
3814 buffer_handle.clone(),
3815 buffer
3816 .edited_ranges_for_transaction::<usize>(transaction)
3817 .collect(),
3818 1,
3819 cx,
3820 ),
3821 );
3822 }
3823 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
3824 multibuffer
3825 })?;
3826
3827 workspace.update(&mut cx, |workspace, cx| {
3828 let project = workspace.project().clone();
3829 let editor =
3830 cx.new_view(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), cx));
3831 workspace.add_item(Box::new(editor.clone()), cx);
3832 editor.update(cx, |editor, cx| {
3833 editor.highlight_background::<Self>(
3834 ranges_to_highlight,
3835 |theme| theme.editor_highlighted_line_background,
3836 cx,
3837 );
3838 });
3839 })?;
3840
3841 Ok(())
3842 }
3843
3844 fn refresh_code_actions(&mut self, cx: &mut ViewContext<Self>) -> Option<()> {
3845 let project = self.project.clone()?;
3846 let buffer = self.buffer.read(cx);
3847 let newest_selection = self.selections.newest_anchor().clone();
3848 let (start_buffer, start) = buffer.text_anchor_for_position(newest_selection.start, cx)?;
3849 let (end_buffer, end) = buffer.text_anchor_for_position(newest_selection.end, cx)?;
3850 if start_buffer != end_buffer {
3851 return None;
3852 }
3853
3854 self.code_actions_task = Some(cx.spawn(|this, mut cx| async move {
3855 cx.background_executor()
3856 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
3857 .await;
3858
3859 let actions = if let Ok(code_actions) = project.update(&mut cx, |project, cx| {
3860 project.code_actions(&start_buffer, start..end, cx)
3861 }) {
3862 code_actions.await.log_err()
3863 } else {
3864 None
3865 };
3866
3867 this.update(&mut cx, |this, cx| {
3868 this.available_code_actions = actions.and_then(|actions| {
3869 if actions.is_empty() {
3870 None
3871 } else {
3872 Some((start_buffer, actions.into()))
3873 }
3874 });
3875 cx.notify();
3876 })
3877 .log_err();
3878 }));
3879 None
3880 }
3881
3882 fn refresh_document_highlights(&mut self, cx: &mut ViewContext<Self>) -> Option<()> {
3883 if self.pending_rename.is_some() {
3884 return None;
3885 }
3886
3887 let project = self.project.clone()?;
3888 let buffer = self.buffer.read(cx);
3889 let newest_selection = self.selections.newest_anchor().clone();
3890 let cursor_position = newest_selection.head();
3891 let (cursor_buffer, cursor_buffer_position) =
3892 buffer.text_anchor_for_position(cursor_position.clone(), cx)?;
3893 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
3894 if cursor_buffer != tail_buffer {
3895 return None;
3896 }
3897
3898 self.document_highlights_task = Some(cx.spawn(|this, mut cx| async move {
3899 cx.background_executor()
3900 .timer(DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT)
3901 .await;
3902
3903 let highlights = if let Some(highlights) = project
3904 .update(&mut cx, |project, cx| {
3905 project.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
3906 })
3907 .log_err()
3908 {
3909 highlights.await.log_err()
3910 } else {
3911 None
3912 };
3913
3914 if let Some(highlights) = highlights {
3915 this.update(&mut cx, |this, cx| {
3916 if this.pending_rename.is_some() {
3917 return;
3918 }
3919
3920 let buffer_id = cursor_position.buffer_id;
3921 let buffer = this.buffer.read(cx);
3922 if !buffer
3923 .text_anchor_for_position(cursor_position, cx)
3924 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
3925 {
3926 return;
3927 }
3928
3929 let cursor_buffer_snapshot = cursor_buffer.read(cx);
3930 let mut write_ranges = Vec::new();
3931 let mut read_ranges = Vec::new();
3932 for highlight in highlights {
3933 for (excerpt_id, excerpt_range) in
3934 buffer.excerpts_for_buffer(&cursor_buffer, cx)
3935 {
3936 let start = highlight
3937 .range
3938 .start
3939 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
3940 let end = highlight
3941 .range
3942 .end
3943 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
3944 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
3945 continue;
3946 }
3947
3948 let range = Anchor {
3949 buffer_id,
3950 excerpt_id: excerpt_id.clone(),
3951 text_anchor: start,
3952 }..Anchor {
3953 buffer_id,
3954 excerpt_id,
3955 text_anchor: end,
3956 };
3957 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
3958 write_ranges.push(range);
3959 } else {
3960 read_ranges.push(range);
3961 }
3962 }
3963 }
3964
3965 this.highlight_background::<DocumentHighlightRead>(
3966 read_ranges,
3967 |theme| theme.editor_document_highlight_read_background,
3968 cx,
3969 );
3970 this.highlight_background::<DocumentHighlightWrite>(
3971 write_ranges,
3972 |theme| theme.editor_document_highlight_write_background,
3973 cx,
3974 );
3975 cx.notify();
3976 })
3977 .log_err();
3978 }
3979 }));
3980 None
3981 }
3982
3983 fn refresh_copilot_suggestions(
3984 &mut self,
3985 debounce: bool,
3986 cx: &mut ViewContext<Self>,
3987 ) -> Option<()> {
3988 let copilot = Copilot::global(cx)?;
3989 if !self.show_copilot_suggestions || !copilot.read(cx).status().is_authorized() {
3990 self.clear_copilot_suggestions(cx);
3991 return None;
3992 }
3993 self.update_visible_copilot_suggestion(cx);
3994
3995 let snapshot = self.buffer.read(cx).snapshot(cx);
3996 let cursor = self.selections.newest_anchor().head();
3997 if !self.is_copilot_enabled_at(cursor, &snapshot, cx) {
3998 self.clear_copilot_suggestions(cx);
3999 return None;
4000 }
4001
4002 let (buffer, buffer_position) =
4003 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
4004 self.copilot_state.pending_refresh = cx.spawn(|this, mut cx| async move {
4005 if debounce {
4006 cx.background_executor()
4007 .timer(COPILOT_DEBOUNCE_TIMEOUT)
4008 .await;
4009 }
4010
4011 let completions = copilot
4012 .update(&mut cx, |copilot, cx| {
4013 copilot.completions(&buffer, buffer_position, cx)
4014 })
4015 .log_err()
4016 .unwrap_or(Task::ready(Ok(Vec::new())))
4017 .await
4018 .log_err()
4019 .into_iter()
4020 .flatten()
4021 .collect_vec();
4022
4023 this.update(&mut cx, |this, cx| {
4024 if !completions.is_empty() {
4025 this.copilot_state.cycled = false;
4026 this.copilot_state.pending_cycling_refresh = Task::ready(None);
4027 this.copilot_state.completions.clear();
4028 this.copilot_state.active_completion_index = 0;
4029 this.copilot_state.excerpt_id = Some(cursor.excerpt_id);
4030 for completion in completions {
4031 this.copilot_state.push_completion(completion);
4032 }
4033 this.update_visible_copilot_suggestion(cx);
4034 }
4035 })
4036 .log_err()?;
4037 Some(())
4038 });
4039
4040 Some(())
4041 }
4042
4043 fn cycle_copilot_suggestions(
4044 &mut self,
4045 direction: Direction,
4046 cx: &mut ViewContext<Self>,
4047 ) -> Option<()> {
4048 let copilot = Copilot::global(cx)?;
4049 if !self.show_copilot_suggestions || !copilot.read(cx).status().is_authorized() {
4050 return None;
4051 }
4052
4053 if self.copilot_state.cycled {
4054 self.copilot_state.cycle_completions(direction);
4055 self.update_visible_copilot_suggestion(cx);
4056 } else {
4057 let cursor = self.selections.newest_anchor().head();
4058 let (buffer, buffer_position) =
4059 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
4060 self.copilot_state.pending_cycling_refresh = cx.spawn(|this, mut cx| async move {
4061 let completions = copilot
4062 .update(&mut cx, |copilot, cx| {
4063 copilot.completions_cycling(&buffer, buffer_position, cx)
4064 })
4065 .log_err()?
4066 .await;
4067
4068 this.update(&mut cx, |this, cx| {
4069 this.copilot_state.cycled = true;
4070 for completion in completions.log_err().into_iter().flatten() {
4071 this.copilot_state.push_completion(completion);
4072 }
4073 this.copilot_state.cycle_completions(direction);
4074 this.update_visible_copilot_suggestion(cx);
4075 })
4076 .log_err()?;
4077
4078 Some(())
4079 });
4080 }
4081
4082 Some(())
4083 }
4084
4085 fn copilot_suggest(&mut self, _: &copilot::Suggest, cx: &mut ViewContext<Self>) {
4086 if !self.has_active_copilot_suggestion(cx) {
4087 self.refresh_copilot_suggestions(false, cx);
4088 return;
4089 }
4090
4091 self.update_visible_copilot_suggestion(cx);
4092 }
4093
4094 fn next_copilot_suggestion(&mut self, _: &copilot::NextSuggestion, cx: &mut ViewContext<Self>) {
4095 if self.has_active_copilot_suggestion(cx) {
4096 self.cycle_copilot_suggestions(Direction::Next, cx);
4097 } else {
4098 let is_copilot_disabled = self.refresh_copilot_suggestions(false, cx).is_none();
4099 if is_copilot_disabled {
4100 cx.propagate();
4101 }
4102 }
4103 }
4104
4105 fn previous_copilot_suggestion(
4106 &mut self,
4107 _: &copilot::PreviousSuggestion,
4108 cx: &mut ViewContext<Self>,
4109 ) {
4110 if self.has_active_copilot_suggestion(cx) {
4111 self.cycle_copilot_suggestions(Direction::Prev, cx);
4112 } else {
4113 let is_copilot_disabled = self.refresh_copilot_suggestions(false, cx).is_none();
4114 if is_copilot_disabled {
4115 cx.propagate();
4116 }
4117 }
4118 }
4119
4120 fn accept_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) -> bool {
4121 if let Some(suggestion) = self.take_active_copilot_suggestion(cx) {
4122 if let Some((copilot, completion)) =
4123 Copilot::global(cx).zip(self.copilot_state.active_completion())
4124 {
4125 copilot
4126 .update(cx, |copilot, cx| copilot.accept_completion(completion, cx))
4127 .detach_and_log_err(cx);
4128
4129 self.report_copilot_event(Some(completion.uuid.clone()), true, cx)
4130 }
4131 cx.emit(EditorEvent::InputHandled {
4132 utf16_range_to_replace: None,
4133 text: suggestion.text.to_string().into(),
4134 });
4135 self.insert_with_autoindent_mode(&suggestion.text.to_string(), None, cx);
4136 cx.notify();
4137 true
4138 } else {
4139 false
4140 }
4141 }
4142
4143 fn discard_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) -> bool {
4144 if let Some(suggestion) = self.take_active_copilot_suggestion(cx) {
4145 if let Some(copilot) = Copilot::global(cx) {
4146 copilot
4147 .update(cx, |copilot, cx| {
4148 copilot.discard_completions(&self.copilot_state.completions, cx)
4149 })
4150 .detach_and_log_err(cx);
4151
4152 self.report_copilot_event(None, false, cx)
4153 }
4154
4155 self.display_map.update(cx, |map, cx| {
4156 map.splice_inlays(vec![suggestion.id], Vec::new(), cx)
4157 });
4158 cx.notify();
4159 true
4160 } else {
4161 false
4162 }
4163 }
4164
4165 fn is_copilot_enabled_at(
4166 &self,
4167 location: Anchor,
4168 snapshot: &MultiBufferSnapshot,
4169 cx: &mut ViewContext<Self>,
4170 ) -> bool {
4171 let file = snapshot.file_at(location);
4172 let language = snapshot.language_at(location);
4173 let settings = all_language_settings(file, cx);
4174 self.show_copilot_suggestions
4175 && settings.copilot_enabled(language, file.map(|f| f.path().as_ref()))
4176 }
4177
4178 fn has_active_copilot_suggestion(&self, cx: &AppContext) -> bool {
4179 if let Some(suggestion) = self.copilot_state.suggestion.as_ref() {
4180 let buffer = self.buffer.read(cx).read(cx);
4181 suggestion.position.is_valid(&buffer)
4182 } else {
4183 false
4184 }
4185 }
4186
4187 fn take_active_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) -> Option<Inlay> {
4188 let suggestion = self.copilot_state.suggestion.take()?;
4189 self.display_map.update(cx, |map, cx| {
4190 map.splice_inlays(vec![suggestion.id], Default::default(), cx);
4191 });
4192 let buffer = self.buffer.read(cx).read(cx);
4193
4194 if suggestion.position.is_valid(&buffer) {
4195 Some(suggestion)
4196 } else {
4197 None
4198 }
4199 }
4200
4201 fn update_visible_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) {
4202 let snapshot = self.buffer.read(cx).snapshot(cx);
4203 let selection = self.selections.newest_anchor();
4204 let cursor = selection.head();
4205
4206 if self.context_menu.read().is_some()
4207 || !self.completion_tasks.is_empty()
4208 || selection.start != selection.end
4209 {
4210 self.discard_copilot_suggestion(cx);
4211 } else if let Some(text) = self
4212 .copilot_state
4213 .text_for_active_completion(cursor, &snapshot)
4214 {
4215 let text = Rope::from(text);
4216 let mut to_remove = Vec::new();
4217 if let Some(suggestion) = self.copilot_state.suggestion.take() {
4218 to_remove.push(suggestion.id);
4219 }
4220
4221 let suggestion_inlay =
4222 Inlay::suggestion(post_inc(&mut self.next_inlay_id), cursor, text);
4223 self.copilot_state.suggestion = Some(suggestion_inlay.clone());
4224 self.display_map.update(cx, move |map, cx| {
4225 map.splice_inlays(to_remove, vec![suggestion_inlay], cx)
4226 });
4227 cx.notify();
4228 } else {
4229 self.discard_copilot_suggestion(cx);
4230 }
4231 }
4232
4233 fn clear_copilot_suggestions(&mut self, cx: &mut ViewContext<Self>) {
4234 self.copilot_state = Default::default();
4235 self.discard_copilot_suggestion(cx);
4236 }
4237
4238 pub fn render_code_actions_indicator(
4239 &self,
4240 _style: &EditorStyle,
4241 is_active: bool,
4242 cx: &mut ViewContext<Self>,
4243 ) -> Option<IconButton> {
4244 if self.available_code_actions.is_some() {
4245 Some(
4246 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
4247 .icon_size(IconSize::Small)
4248 .icon_color(Color::Muted)
4249 .selected(is_active)
4250 .on_click(cx.listener(|editor, _e, cx| {
4251 editor.toggle_code_actions(
4252 &ToggleCodeActions {
4253 deployed_from_indicator: true,
4254 },
4255 cx,
4256 );
4257 })),
4258 )
4259 } else {
4260 None
4261 }
4262 }
4263
4264 pub fn render_fold_indicators(
4265 &self,
4266 fold_data: Vec<Option<(FoldStatus, u32, bool)>>,
4267 _style: &EditorStyle,
4268 gutter_hovered: bool,
4269 _line_height: Pixels,
4270 _gutter_margin: Pixels,
4271 cx: &mut ViewContext<Self>,
4272 ) -> Vec<Option<IconButton>> {
4273 fold_data
4274 .iter()
4275 .enumerate()
4276 .map(|(ix, fold_data)| {
4277 fold_data
4278 .map(|(fold_status, buffer_row, active)| {
4279 (active || gutter_hovered || fold_status == FoldStatus::Folded).then(|| {
4280 IconButton::new(ix as usize, ui::IconName::ChevronDown)
4281 .on_click(cx.listener(move |editor, _e, cx| match fold_status {
4282 FoldStatus::Folded => {
4283 editor.unfold_at(&UnfoldAt { buffer_row }, cx);
4284 }
4285 FoldStatus::Foldable => {
4286 editor.fold_at(&FoldAt { buffer_row }, cx);
4287 }
4288 }))
4289 .icon_color(ui::Color::Muted)
4290 .icon_size(ui::IconSize::Small)
4291 .selected(fold_status == FoldStatus::Folded)
4292 .selected_icon(ui::IconName::ChevronRight)
4293 .size(ui::ButtonSize::None)
4294 })
4295 })
4296 .flatten()
4297 })
4298 .collect()
4299 }
4300
4301 pub fn context_menu_visible(&self) -> bool {
4302 self.context_menu
4303 .read()
4304 .as_ref()
4305 .map_or(false, |menu| menu.visible())
4306 }
4307
4308 pub fn render_context_menu(
4309 &self,
4310 cursor_position: DisplayPoint,
4311 style: &EditorStyle,
4312 max_height: Pixels,
4313 cx: &mut ViewContext<Editor>,
4314 ) -> Option<(DisplayPoint, AnyElement)> {
4315 self.context_menu.read().as_ref().map(|menu| {
4316 menu.render(
4317 cursor_position,
4318 style,
4319 max_height,
4320 self.workspace.as_ref().map(|(w, _)| w.clone()),
4321 cx,
4322 )
4323 })
4324 }
4325
4326 fn hide_context_menu(&mut self, cx: &mut ViewContext<Self>) -> Option<ContextMenu> {
4327 cx.notify();
4328 self.completion_tasks.clear();
4329 let context_menu = self.context_menu.write().take();
4330 if context_menu.is_some() {
4331 self.update_visible_copilot_suggestion(cx);
4332 }
4333 context_menu
4334 }
4335
4336 pub fn insert_snippet(
4337 &mut self,
4338 insertion_ranges: &[Range<usize>],
4339 snippet: Snippet,
4340 cx: &mut ViewContext<Self>,
4341 ) -> Result<()> {
4342 let tabstops = self.buffer.update(cx, |buffer, cx| {
4343 let snippet_text: Arc<str> = snippet.text.clone().into();
4344 buffer.edit(
4345 insertion_ranges
4346 .iter()
4347 .cloned()
4348 .map(|range| (range, snippet_text.clone())),
4349 Some(AutoindentMode::EachLine),
4350 cx,
4351 );
4352
4353 let snapshot = &*buffer.read(cx);
4354 let snippet = &snippet;
4355 snippet
4356 .tabstops
4357 .iter()
4358 .map(|tabstop| {
4359 let mut tabstop_ranges = tabstop
4360 .iter()
4361 .flat_map(|tabstop_range| {
4362 let mut delta = 0_isize;
4363 insertion_ranges.iter().map(move |insertion_range| {
4364 let insertion_start = insertion_range.start as isize + delta;
4365 delta +=
4366 snippet.text.len() as isize - insertion_range.len() as isize;
4367
4368 let start = snapshot.anchor_before(
4369 (insertion_start + tabstop_range.start) as usize,
4370 );
4371 let end = snapshot
4372 .anchor_after((insertion_start + tabstop_range.end) as usize);
4373 start..end
4374 })
4375 })
4376 .collect::<Vec<_>>();
4377 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
4378 tabstop_ranges
4379 })
4380 .collect::<Vec<_>>()
4381 });
4382
4383 if let Some(tabstop) = tabstops.first() {
4384 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
4385 s.select_ranges(tabstop.iter().cloned());
4386 });
4387 self.snippet_stack.push(SnippetState {
4388 active_index: 0,
4389 ranges: tabstops,
4390 });
4391 }
4392
4393 Ok(())
4394 }
4395
4396 pub fn move_to_next_snippet_tabstop(&mut self, cx: &mut ViewContext<Self>) -> bool {
4397 self.move_to_snippet_tabstop(Bias::Right, cx)
4398 }
4399
4400 pub fn move_to_prev_snippet_tabstop(&mut self, cx: &mut ViewContext<Self>) -> bool {
4401 self.move_to_snippet_tabstop(Bias::Left, cx)
4402 }
4403
4404 pub fn move_to_snippet_tabstop(&mut self, bias: Bias, cx: &mut ViewContext<Self>) -> bool {
4405 if let Some(mut snippet) = self.snippet_stack.pop() {
4406 match bias {
4407 Bias::Left => {
4408 if snippet.active_index > 0 {
4409 snippet.active_index -= 1;
4410 } else {
4411 self.snippet_stack.push(snippet);
4412 return false;
4413 }
4414 }
4415 Bias::Right => {
4416 if snippet.active_index + 1 < snippet.ranges.len() {
4417 snippet.active_index += 1;
4418 } else {
4419 self.snippet_stack.push(snippet);
4420 return false;
4421 }
4422 }
4423 }
4424 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
4425 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
4426 s.select_anchor_ranges(current_ranges.iter().cloned())
4427 });
4428 // If snippet state is not at the last tabstop, push it back on the stack
4429 if snippet.active_index + 1 < snippet.ranges.len() {
4430 self.snippet_stack.push(snippet);
4431 }
4432 return true;
4433 }
4434 }
4435
4436 false
4437 }
4438
4439 pub fn clear(&mut self, cx: &mut ViewContext<Self>) {
4440 self.transact(cx, |this, cx| {
4441 this.select_all(&SelectAll, cx);
4442 this.insert("", cx);
4443 });
4444 }
4445
4446 pub fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) {
4447 self.transact(cx, |this, cx| {
4448 this.select_autoclose_pair(cx);
4449 let mut selections = this.selections.all::<Point>(cx);
4450 if !this.selections.line_mode {
4451 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4452 for selection in &mut selections {
4453 if selection.is_empty() {
4454 let old_head = selection.head();
4455 let mut new_head =
4456 movement::left(&display_map, old_head.to_display_point(&display_map))
4457 .to_point(&display_map);
4458 if let Some((buffer, line_buffer_range)) = display_map
4459 .buffer_snapshot
4460 .buffer_line_for_row(old_head.row)
4461 {
4462 let indent_size =
4463 buffer.indent_size_for_line(line_buffer_range.start.row);
4464 let indent_len = match indent_size.kind {
4465 IndentKind::Space => {
4466 buffer.settings_at(line_buffer_range.start, cx).tab_size
4467 }
4468 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
4469 };
4470 if old_head.column <= indent_size.len && old_head.column > 0 {
4471 let indent_len = indent_len.get();
4472 new_head = cmp::min(
4473 new_head,
4474 Point::new(
4475 old_head.row,
4476 ((old_head.column - 1) / indent_len) * indent_len,
4477 ),
4478 );
4479 }
4480 }
4481
4482 selection.set_head(new_head, SelectionGoal::None);
4483 }
4484 }
4485 }
4486
4487 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
4488 this.insert("", cx);
4489 this.refresh_copilot_suggestions(true, cx);
4490 });
4491 }
4492
4493 pub fn delete(&mut self, _: &Delete, cx: &mut ViewContext<Self>) {
4494 self.transact(cx, |this, cx| {
4495 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
4496 let line_mode = s.line_mode;
4497 s.move_with(|map, selection| {
4498 if selection.is_empty() && !line_mode {
4499 let cursor = movement::right(map, selection.head());
4500 selection.end = cursor;
4501 selection.reversed = true;
4502 selection.goal = SelectionGoal::None;
4503 }
4504 })
4505 });
4506 this.insert("", cx);
4507 this.refresh_copilot_suggestions(true, cx);
4508 });
4509 }
4510
4511 pub fn tab_prev(&mut self, _: &TabPrev, cx: &mut ViewContext<Self>) {
4512 if self.move_to_prev_snippet_tabstop(cx) {
4513 return;
4514 }
4515
4516 self.outdent(&Outdent, cx);
4517 }
4518
4519 pub fn tab(&mut self, _: &Tab, cx: &mut ViewContext<Self>) {
4520 if self.move_to_next_snippet_tabstop(cx) || self.read_only(cx) {
4521 return;
4522 }
4523
4524 let mut selections = self.selections.all_adjusted(cx);
4525 let buffer = self.buffer.read(cx);
4526 let snapshot = buffer.snapshot(cx);
4527 let rows_iter = selections.iter().map(|s| s.head().row);
4528 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
4529
4530 let mut edits = Vec::new();
4531 let mut prev_edited_row = 0;
4532 let mut row_delta = 0;
4533 for selection in &mut selections {
4534 if selection.start.row != prev_edited_row {
4535 row_delta = 0;
4536 }
4537 prev_edited_row = selection.end.row;
4538
4539 // If the selection is non-empty, then increase the indentation of the selected lines.
4540 if !selection.is_empty() {
4541 row_delta =
4542 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
4543 continue;
4544 }
4545
4546 // If the selection is empty and the cursor is in the leading whitespace before the
4547 // suggested indentation, then auto-indent the line.
4548 let cursor = selection.head();
4549 let current_indent = snapshot.indent_size_for_line(cursor.row);
4550 if let Some(suggested_indent) = suggested_indents.get(&cursor.row).copied() {
4551 if cursor.column < suggested_indent.len
4552 && cursor.column <= current_indent.len
4553 && current_indent.len <= suggested_indent.len
4554 {
4555 selection.start = Point::new(cursor.row, suggested_indent.len);
4556 selection.end = selection.start;
4557 if row_delta == 0 {
4558 edits.extend(Buffer::edit_for_indent_size_adjustment(
4559 cursor.row,
4560 current_indent,
4561 suggested_indent,
4562 ));
4563 row_delta = suggested_indent.len - current_indent.len;
4564 }
4565 continue;
4566 }
4567 }
4568
4569 // Accept copilot suggestion if there is only one selection and the cursor is not
4570 // in the leading whitespace.
4571 if self.selections.count() == 1
4572 && cursor.column >= current_indent.len
4573 && self.has_active_copilot_suggestion(cx)
4574 {
4575 self.accept_copilot_suggestion(cx);
4576 return;
4577 }
4578
4579 // Otherwise, insert a hard or soft tab.
4580 let settings = buffer.settings_at(cursor, cx);
4581 let tab_size = if settings.hard_tabs {
4582 IndentSize::tab()
4583 } else {
4584 let tab_size = settings.tab_size.get();
4585 let char_column = snapshot
4586 .text_for_range(Point::new(cursor.row, 0)..cursor)
4587 .flat_map(str::chars)
4588 .count()
4589 + row_delta as usize;
4590 let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size);
4591 IndentSize::spaces(chars_to_next_tab_stop)
4592 };
4593 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
4594 selection.end = selection.start;
4595 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
4596 row_delta += tab_size.len;
4597 }
4598
4599 self.transact(cx, |this, cx| {
4600 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
4601 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
4602 this.refresh_copilot_suggestions(true, cx);
4603 });
4604 }
4605
4606 pub fn indent(&mut self, _: &Indent, cx: &mut ViewContext<Self>) {
4607 let mut selections = self.selections.all::<Point>(cx);
4608 let mut prev_edited_row = 0;
4609 let mut row_delta = 0;
4610 let mut edits = Vec::new();
4611 let buffer = self.buffer.read(cx);
4612 let snapshot = buffer.snapshot(cx);
4613 for selection in &mut selections {
4614 if selection.start.row != prev_edited_row {
4615 row_delta = 0;
4616 }
4617 prev_edited_row = selection.end.row;
4618
4619 row_delta =
4620 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
4621 }
4622
4623 self.transact(cx, |this, cx| {
4624 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
4625 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
4626 });
4627 }
4628
4629 fn indent_selection(
4630 buffer: &MultiBuffer,
4631 snapshot: &MultiBufferSnapshot,
4632 selection: &mut Selection<Point>,
4633 edits: &mut Vec<(Range<Point>, String)>,
4634 delta_for_start_row: u32,
4635 cx: &AppContext,
4636 ) -> u32 {
4637 let settings = buffer.settings_at(selection.start, cx);
4638 let tab_size = settings.tab_size.get();
4639 let indent_kind = if settings.hard_tabs {
4640 IndentKind::Tab
4641 } else {
4642 IndentKind::Space
4643 };
4644 let mut start_row = selection.start.row;
4645 let mut end_row = selection.end.row + 1;
4646
4647 // If a selection ends at the beginning of a line, don't indent
4648 // that last line.
4649 if selection.end.column == 0 {
4650 end_row -= 1;
4651 }
4652
4653 // Avoid re-indenting a row that has already been indented by a
4654 // previous selection, but still update this selection's column
4655 // to reflect that indentation.
4656 if delta_for_start_row > 0 {
4657 start_row += 1;
4658 selection.start.column += delta_for_start_row;
4659 if selection.end.row == selection.start.row {
4660 selection.end.column += delta_for_start_row;
4661 }
4662 }
4663
4664 let mut delta_for_end_row = 0;
4665 for row in start_row..end_row {
4666 let current_indent = snapshot.indent_size_for_line(row);
4667 let indent_delta = match (current_indent.kind, indent_kind) {
4668 (IndentKind::Space, IndentKind::Space) => {
4669 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
4670 IndentSize::spaces(columns_to_next_tab_stop)
4671 }
4672 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
4673 (_, IndentKind::Tab) => IndentSize::tab(),
4674 };
4675
4676 let row_start = Point::new(row, 0);
4677 edits.push((
4678 row_start..row_start,
4679 indent_delta.chars().collect::<String>(),
4680 ));
4681
4682 // Update this selection's endpoints to reflect the indentation.
4683 if row == selection.start.row {
4684 selection.start.column += indent_delta.len;
4685 }
4686 if row == selection.end.row {
4687 selection.end.column += indent_delta.len;
4688 delta_for_end_row = indent_delta.len;
4689 }
4690 }
4691
4692 if selection.start.row == selection.end.row {
4693 delta_for_start_row + delta_for_end_row
4694 } else {
4695 delta_for_end_row
4696 }
4697 }
4698
4699 pub fn outdent(&mut self, _: &Outdent, cx: &mut ViewContext<Self>) {
4700 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4701 let selections = self.selections.all::<Point>(cx);
4702 let mut deletion_ranges = Vec::new();
4703 let mut last_outdent = None;
4704 {
4705 let buffer = self.buffer.read(cx);
4706 let snapshot = buffer.snapshot(cx);
4707 for selection in &selections {
4708 let settings = buffer.settings_at(selection.start, cx);
4709 let tab_size = settings.tab_size.get();
4710 let mut rows = selection.spanned_rows(false, &display_map);
4711
4712 // Avoid re-outdenting a row that has already been outdented by a
4713 // previous selection.
4714 if let Some(last_row) = last_outdent {
4715 if last_row == rows.start {
4716 rows.start += 1;
4717 }
4718 }
4719
4720 for row in rows {
4721 let indent_size = snapshot.indent_size_for_line(row);
4722 if indent_size.len > 0 {
4723 let deletion_len = match indent_size.kind {
4724 IndentKind::Space => {
4725 let columns_to_prev_tab_stop = indent_size.len % tab_size;
4726 if columns_to_prev_tab_stop == 0 {
4727 tab_size
4728 } else {
4729 columns_to_prev_tab_stop
4730 }
4731 }
4732 IndentKind::Tab => 1,
4733 };
4734 deletion_ranges.push(Point::new(row, 0)..Point::new(row, deletion_len));
4735 last_outdent = Some(row);
4736 }
4737 }
4738 }
4739 }
4740
4741 self.transact(cx, |this, cx| {
4742 this.buffer.update(cx, |buffer, cx| {
4743 let empty_str: Arc<str> = "".into();
4744 buffer.edit(
4745 deletion_ranges
4746 .into_iter()
4747 .map(|range| (range, empty_str.clone())),
4748 None,
4749 cx,
4750 );
4751 });
4752 let selections = this.selections.all::<usize>(cx);
4753 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
4754 });
4755 }
4756
4757 pub fn delete_line(&mut self, _: &DeleteLine, cx: &mut ViewContext<Self>) {
4758 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4759 let selections = self.selections.all::<Point>(cx);
4760
4761 let mut new_cursors = Vec::new();
4762 let mut edit_ranges = Vec::new();
4763 let mut selections = selections.iter().peekable();
4764 while let Some(selection) = selections.next() {
4765 let mut rows = selection.spanned_rows(false, &display_map);
4766 let goal_display_column = selection.head().to_display_point(&display_map).column();
4767
4768 // Accumulate contiguous regions of rows that we want to delete.
4769 while let Some(next_selection) = selections.peek() {
4770 let next_rows = next_selection.spanned_rows(false, &display_map);
4771 if next_rows.start <= rows.end {
4772 rows.end = next_rows.end;
4773 selections.next().unwrap();
4774 } else {
4775 break;
4776 }
4777 }
4778
4779 let buffer = &display_map.buffer_snapshot;
4780 let mut edit_start = Point::new(rows.start, 0).to_offset(buffer);
4781 let edit_end;
4782 let cursor_buffer_row;
4783 if buffer.max_point().row >= rows.end {
4784 // If there's a line after the range, delete the \n from the end of the row range
4785 // and position the cursor on the next line.
4786 edit_end = Point::new(rows.end, 0).to_offset(buffer);
4787 cursor_buffer_row = rows.end;
4788 } else {
4789 // If there isn't a line after the range, delete the \n from the line before the
4790 // start of the row range and position the cursor there.
4791 edit_start = edit_start.saturating_sub(1);
4792 edit_end = buffer.len();
4793 cursor_buffer_row = rows.start.saturating_sub(1);
4794 }
4795
4796 let mut cursor = Point::new(cursor_buffer_row, 0).to_display_point(&display_map);
4797 *cursor.column_mut() =
4798 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
4799
4800 new_cursors.push((
4801 selection.id,
4802 buffer.anchor_after(cursor.to_point(&display_map)),
4803 ));
4804 edit_ranges.push(edit_start..edit_end);
4805 }
4806
4807 self.transact(cx, |this, cx| {
4808 let buffer = this.buffer.update(cx, |buffer, cx| {
4809 let empty_str: Arc<str> = "".into();
4810 buffer.edit(
4811 edit_ranges
4812 .into_iter()
4813 .map(|range| (range, empty_str.clone())),
4814 None,
4815 cx,
4816 );
4817 buffer.snapshot(cx)
4818 });
4819 let new_selections = new_cursors
4820 .into_iter()
4821 .map(|(id, cursor)| {
4822 let cursor = cursor.to_point(&buffer);
4823 Selection {
4824 id,
4825 start: cursor,
4826 end: cursor,
4827 reversed: false,
4828 goal: SelectionGoal::None,
4829 }
4830 })
4831 .collect();
4832
4833 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
4834 s.select(new_selections);
4835 });
4836 });
4837 }
4838
4839 pub fn join_lines(&mut self, _: &JoinLines, cx: &mut ViewContext<Self>) {
4840 let mut row_ranges = Vec::<Range<u32>>::new();
4841 for selection in self.selections.all::<Point>(cx) {
4842 let start = selection.start.row;
4843 let end = if selection.start.row == selection.end.row {
4844 selection.start.row + 1
4845 } else {
4846 selection.end.row
4847 };
4848
4849 if let Some(last_row_range) = row_ranges.last_mut() {
4850 if start <= last_row_range.end {
4851 last_row_range.end = end;
4852 continue;
4853 }
4854 }
4855 row_ranges.push(start..end);
4856 }
4857
4858 let snapshot = self.buffer.read(cx).snapshot(cx);
4859 let mut cursor_positions = Vec::new();
4860 for row_range in &row_ranges {
4861 let anchor = snapshot.anchor_before(Point::new(
4862 row_range.end - 1,
4863 snapshot.line_len(row_range.end - 1),
4864 ));
4865 cursor_positions.push(anchor.clone()..anchor);
4866 }
4867
4868 self.transact(cx, |this, cx| {
4869 for row_range in row_ranges.into_iter().rev() {
4870 for row in row_range.rev() {
4871 let end_of_line = Point::new(row, snapshot.line_len(row));
4872 let indent = snapshot.indent_size_for_line(row + 1);
4873 let start_of_next_line = Point::new(row + 1, indent.len);
4874
4875 let replace = if snapshot.line_len(row + 1) > indent.len {
4876 " "
4877 } else {
4878 ""
4879 };
4880
4881 this.buffer.update(cx, |buffer, cx| {
4882 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
4883 });
4884 }
4885 }
4886
4887 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
4888 s.select_anchor_ranges(cursor_positions)
4889 });
4890 });
4891 }
4892
4893 pub fn sort_lines_case_sensitive(
4894 &mut self,
4895 _: &SortLinesCaseSensitive,
4896 cx: &mut ViewContext<Self>,
4897 ) {
4898 self.manipulate_lines(cx, |lines| lines.sort())
4899 }
4900
4901 pub fn sort_lines_case_insensitive(
4902 &mut self,
4903 _: &SortLinesCaseInsensitive,
4904 cx: &mut ViewContext<Self>,
4905 ) {
4906 self.manipulate_lines(cx, |lines| lines.sort_by_key(|line| line.to_lowercase()))
4907 }
4908
4909 pub fn reverse_lines(&mut self, _: &ReverseLines, cx: &mut ViewContext<Self>) {
4910 self.manipulate_lines(cx, |lines| lines.reverse())
4911 }
4912
4913 pub fn shuffle_lines(&mut self, _: &ShuffleLines, cx: &mut ViewContext<Self>) {
4914 self.manipulate_lines(cx, |lines| lines.shuffle(&mut thread_rng()))
4915 }
4916
4917 fn manipulate_lines<Fn>(&mut self, cx: &mut ViewContext<Self>, mut callback: Fn)
4918 where
4919 Fn: FnMut(&mut [&str]),
4920 {
4921 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4922 let buffer = self.buffer.read(cx).snapshot(cx);
4923
4924 let mut edits = Vec::new();
4925
4926 let selections = self.selections.all::<Point>(cx);
4927 let mut selections = selections.iter().peekable();
4928 let mut contiguous_row_selections = Vec::new();
4929 let mut new_selections = Vec::new();
4930
4931 while let Some(selection) = selections.next() {
4932 let (start_row, end_row) = consume_contiguous_rows(
4933 &mut contiguous_row_selections,
4934 selection,
4935 &display_map,
4936 &mut selections,
4937 );
4938
4939 let start_point = Point::new(start_row, 0);
4940 let end_point = Point::new(end_row - 1, buffer.line_len(end_row - 1));
4941 let text = buffer
4942 .text_for_range(start_point..end_point)
4943 .collect::<String>();
4944 let mut lines = text.split("\n").collect_vec();
4945
4946 let lines_len = lines.len();
4947 callback(&mut lines);
4948
4949 // This is a current limitation with selections.
4950 // If we wanted to support removing or adding lines, we'd need to fix the logic associated with selections.
4951 debug_assert!(
4952 lines.len() == lines_len,
4953 "callback should not change the number of lines"
4954 );
4955
4956 edits.push((start_point..end_point, lines.join("\n")));
4957 let start_anchor = buffer.anchor_after(start_point);
4958 let end_anchor = buffer.anchor_before(end_point);
4959
4960 // Make selection and push
4961 new_selections.push(Selection {
4962 id: selection.id,
4963 start: start_anchor.to_offset(&buffer),
4964 end: end_anchor.to_offset(&buffer),
4965 goal: SelectionGoal::None,
4966 reversed: selection.reversed,
4967 });
4968 }
4969
4970 self.transact(cx, |this, cx| {
4971 this.buffer.update(cx, |buffer, cx| {
4972 buffer.edit(edits, None, cx);
4973 });
4974
4975 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
4976 s.select(new_selections);
4977 });
4978
4979 this.request_autoscroll(Autoscroll::fit(), cx);
4980 });
4981 }
4982
4983 pub fn convert_to_upper_case(&mut self, _: &ConvertToUpperCase, cx: &mut ViewContext<Self>) {
4984 self.manipulate_text(cx, |text| text.to_uppercase())
4985 }
4986
4987 pub fn convert_to_lower_case(&mut self, _: &ConvertToLowerCase, cx: &mut ViewContext<Self>) {
4988 self.manipulate_text(cx, |text| text.to_lowercase())
4989 }
4990
4991 pub fn convert_to_title_case(&mut self, _: &ConvertToTitleCase, cx: &mut ViewContext<Self>) {
4992 self.manipulate_text(cx, |text| {
4993 // Hack to get around the fact that to_case crate doesn't support '\n' as a word boundary
4994 // https://github.com/rutrum/convert-case/issues/16
4995 text.split("\n")
4996 .map(|line| line.to_case(Case::Title))
4997 .join("\n")
4998 })
4999 }
5000
5001 pub fn convert_to_snake_case(&mut self, _: &ConvertToSnakeCase, cx: &mut ViewContext<Self>) {
5002 self.manipulate_text(cx, |text| text.to_case(Case::Snake))
5003 }
5004
5005 pub fn convert_to_kebab_case(&mut self, _: &ConvertToKebabCase, cx: &mut ViewContext<Self>) {
5006 self.manipulate_text(cx, |text| text.to_case(Case::Kebab))
5007 }
5008
5009 pub fn convert_to_upper_camel_case(
5010 &mut self,
5011 _: &ConvertToUpperCamelCase,
5012 cx: &mut ViewContext<Self>,
5013 ) {
5014 self.manipulate_text(cx, |text| {
5015 // Hack to get around the fact that to_case crate doesn't support '\n' as a word boundary
5016 // https://github.com/rutrum/convert-case/issues/16
5017 text.split("\n")
5018 .map(|line| line.to_case(Case::UpperCamel))
5019 .join("\n")
5020 })
5021 }
5022
5023 pub fn convert_to_lower_camel_case(
5024 &mut self,
5025 _: &ConvertToLowerCamelCase,
5026 cx: &mut ViewContext<Self>,
5027 ) {
5028 self.manipulate_text(cx, |text| text.to_case(Case::Camel))
5029 }
5030
5031 fn manipulate_text<Fn>(&mut self, cx: &mut ViewContext<Self>, mut callback: Fn)
5032 where
5033 Fn: FnMut(&str) -> String,
5034 {
5035 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5036 let buffer = self.buffer.read(cx).snapshot(cx);
5037
5038 let mut new_selections = Vec::new();
5039 let mut edits = Vec::new();
5040 let mut selection_adjustment = 0i32;
5041
5042 for selection in self.selections.all::<usize>(cx) {
5043 let selection_is_empty = selection.is_empty();
5044
5045 let (start, end) = if selection_is_empty {
5046 let word_range = movement::surrounding_word(
5047 &display_map,
5048 selection.start.to_display_point(&display_map),
5049 );
5050 let start = word_range.start.to_offset(&display_map, Bias::Left);
5051 let end = word_range.end.to_offset(&display_map, Bias::Left);
5052 (start, end)
5053 } else {
5054 (selection.start, selection.end)
5055 };
5056
5057 let text = buffer.text_for_range(start..end).collect::<String>();
5058 let old_length = text.len() as i32;
5059 let text = callback(&text);
5060
5061 new_selections.push(Selection {
5062 start: (start as i32 - selection_adjustment) as usize,
5063 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
5064 goal: SelectionGoal::None,
5065 ..selection
5066 });
5067
5068 selection_adjustment += old_length - text.len() as i32;
5069
5070 edits.push((start..end, text));
5071 }
5072
5073 self.transact(cx, |this, cx| {
5074 this.buffer.update(cx, |buffer, cx| {
5075 buffer.edit(edits, None, cx);
5076 });
5077
5078 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
5079 s.select(new_selections);
5080 });
5081
5082 this.request_autoscroll(Autoscroll::fit(), cx);
5083 });
5084 }
5085
5086 pub fn duplicate_line(&mut self, _: &DuplicateLine, cx: &mut ViewContext<Self>) {
5087 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5088 let buffer = &display_map.buffer_snapshot;
5089 let selections = self.selections.all::<Point>(cx);
5090
5091 let mut edits = Vec::new();
5092 let mut selections_iter = selections.iter().peekable();
5093 while let Some(selection) = selections_iter.next() {
5094 // Avoid duplicating the same lines twice.
5095 let mut rows = selection.spanned_rows(false, &display_map);
5096
5097 while let Some(next_selection) = selections_iter.peek() {
5098 let next_rows = next_selection.spanned_rows(false, &display_map);
5099 if next_rows.start < rows.end {
5100 rows.end = next_rows.end;
5101 selections_iter.next().unwrap();
5102 } else {
5103 break;
5104 }
5105 }
5106
5107 // Copy the text from the selected row region and splice it at the start of the region.
5108 let start = Point::new(rows.start, 0);
5109 let end = Point::new(rows.end - 1, buffer.line_len(rows.end - 1));
5110 let text = buffer
5111 .text_for_range(start..end)
5112 .chain(Some("\n"))
5113 .collect::<String>();
5114 edits.push((start..start, text));
5115 }
5116
5117 self.transact(cx, |this, cx| {
5118 this.buffer.update(cx, |buffer, cx| {
5119 buffer.edit(edits, None, cx);
5120 });
5121
5122 this.request_autoscroll(Autoscroll::fit(), cx);
5123 });
5124 }
5125
5126 pub fn move_line_up(&mut self, _: &MoveLineUp, cx: &mut ViewContext<Self>) {
5127 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5128 let buffer = self.buffer.read(cx).snapshot(cx);
5129
5130 let mut edits = Vec::new();
5131 let mut unfold_ranges = Vec::new();
5132 let mut refold_ranges = Vec::new();
5133
5134 let selections = self.selections.all::<Point>(cx);
5135 let mut selections = selections.iter().peekable();
5136 let mut contiguous_row_selections = Vec::new();
5137 let mut new_selections = Vec::new();
5138
5139 while let Some(selection) = selections.next() {
5140 // Find all the selections that span a contiguous row range
5141 let (start_row, end_row) = consume_contiguous_rows(
5142 &mut contiguous_row_selections,
5143 selection,
5144 &display_map,
5145 &mut selections,
5146 );
5147
5148 // Move the text spanned by the row range to be before the line preceding the row range
5149 if start_row > 0 {
5150 let range_to_move = Point::new(start_row - 1, buffer.line_len(start_row - 1))
5151 ..Point::new(end_row - 1, buffer.line_len(end_row - 1));
5152 let insertion_point = display_map
5153 .prev_line_boundary(Point::new(start_row - 1, 0))
5154 .0;
5155
5156 // Don't move lines across excerpts
5157 if buffer
5158 .excerpt_boundaries_in_range((
5159 Bound::Excluded(insertion_point),
5160 Bound::Included(range_to_move.end),
5161 ))
5162 .next()
5163 .is_none()
5164 {
5165 let text = buffer
5166 .text_for_range(range_to_move.clone())
5167 .flat_map(|s| s.chars())
5168 .skip(1)
5169 .chain(['\n'])
5170 .collect::<String>();
5171
5172 edits.push((
5173 buffer.anchor_after(range_to_move.start)
5174 ..buffer.anchor_before(range_to_move.end),
5175 String::new(),
5176 ));
5177 let insertion_anchor = buffer.anchor_after(insertion_point);
5178 edits.push((insertion_anchor..insertion_anchor, text));
5179
5180 let row_delta = range_to_move.start.row - insertion_point.row + 1;
5181
5182 // Move selections up
5183 new_selections.extend(contiguous_row_selections.drain(..).map(
5184 |mut selection| {
5185 selection.start.row -= row_delta;
5186 selection.end.row -= row_delta;
5187 selection
5188 },
5189 ));
5190
5191 // Move folds up
5192 unfold_ranges.push(range_to_move.clone());
5193 for fold in display_map.folds_in_range(
5194 buffer.anchor_before(range_to_move.start)
5195 ..buffer.anchor_after(range_to_move.end),
5196 ) {
5197 let mut start = fold.range.start.to_point(&buffer);
5198 let mut end = fold.range.end.to_point(&buffer);
5199 start.row -= row_delta;
5200 end.row -= row_delta;
5201 refold_ranges.push(start..end);
5202 }
5203 }
5204 }
5205
5206 // If we didn't move line(s), preserve the existing selections
5207 new_selections.append(&mut contiguous_row_selections);
5208 }
5209
5210 self.transact(cx, |this, cx| {
5211 this.unfold_ranges(unfold_ranges, true, true, cx);
5212 this.buffer.update(cx, |buffer, cx| {
5213 for (range, text) in edits {
5214 buffer.edit([(range, text)], None, cx);
5215 }
5216 });
5217 this.fold_ranges(refold_ranges, true, cx);
5218 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
5219 s.select(new_selections);
5220 })
5221 });
5222 }
5223
5224 pub fn move_line_down(&mut self, _: &MoveLineDown, cx: &mut ViewContext<Self>) {
5225 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5226 let buffer = self.buffer.read(cx).snapshot(cx);
5227
5228 let mut edits = Vec::new();
5229 let mut unfold_ranges = Vec::new();
5230 let mut refold_ranges = Vec::new();
5231
5232 let selections = self.selections.all::<Point>(cx);
5233 let mut selections = selections.iter().peekable();
5234 let mut contiguous_row_selections = Vec::new();
5235 let mut new_selections = Vec::new();
5236
5237 while let Some(selection) = selections.next() {
5238 // Find all the selections that span a contiguous row range
5239 let (start_row, end_row) = consume_contiguous_rows(
5240 &mut contiguous_row_selections,
5241 selection,
5242 &display_map,
5243 &mut selections,
5244 );
5245
5246 // Move the text spanned by the row range to be after the last line of the row range
5247 if end_row <= buffer.max_point().row {
5248 let range_to_move = Point::new(start_row, 0)..Point::new(end_row, 0);
5249 let insertion_point = display_map.next_line_boundary(Point::new(end_row, 0)).0;
5250
5251 // Don't move lines across excerpt boundaries
5252 if buffer
5253 .excerpt_boundaries_in_range((
5254 Bound::Excluded(range_to_move.start),
5255 Bound::Included(insertion_point),
5256 ))
5257 .next()
5258 .is_none()
5259 {
5260 let mut text = String::from("\n");
5261 text.extend(buffer.text_for_range(range_to_move.clone()));
5262 text.pop(); // Drop trailing newline
5263 edits.push((
5264 buffer.anchor_after(range_to_move.start)
5265 ..buffer.anchor_before(range_to_move.end),
5266 String::new(),
5267 ));
5268 let insertion_anchor = buffer.anchor_after(insertion_point);
5269 edits.push((insertion_anchor..insertion_anchor, text));
5270
5271 let row_delta = insertion_point.row - range_to_move.end.row + 1;
5272
5273 // Move selections down
5274 new_selections.extend(contiguous_row_selections.drain(..).map(
5275 |mut selection| {
5276 selection.start.row += row_delta;
5277 selection.end.row += row_delta;
5278 selection
5279 },
5280 ));
5281
5282 // Move folds down
5283 unfold_ranges.push(range_to_move.clone());
5284 for fold in display_map.folds_in_range(
5285 buffer.anchor_before(range_to_move.start)
5286 ..buffer.anchor_after(range_to_move.end),
5287 ) {
5288 let mut start = fold.range.start.to_point(&buffer);
5289 let mut end = fold.range.end.to_point(&buffer);
5290 start.row += row_delta;
5291 end.row += row_delta;
5292 refold_ranges.push(start..end);
5293 }
5294 }
5295 }
5296
5297 // If we didn't move line(s), preserve the existing selections
5298 new_selections.append(&mut contiguous_row_selections);
5299 }
5300
5301 self.transact(cx, |this, cx| {
5302 this.unfold_ranges(unfold_ranges, true, true, cx);
5303 this.buffer.update(cx, |buffer, cx| {
5304 for (range, text) in edits {
5305 buffer.edit([(range, text)], None, cx);
5306 }
5307 });
5308 this.fold_ranges(refold_ranges, true, cx);
5309 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
5310 });
5311 }
5312
5313 pub fn transpose(&mut self, _: &Transpose, cx: &mut ViewContext<Self>) {
5314 let text_layout_details = &self.text_layout_details(cx);
5315 self.transact(cx, |this, cx| {
5316 let edits = this.change_selections(Some(Autoscroll::fit()), cx, |s| {
5317 let mut edits: Vec<(Range<usize>, String)> = Default::default();
5318 let line_mode = s.line_mode;
5319 s.move_with(|display_map, selection| {
5320 if !selection.is_empty() || line_mode {
5321 return;
5322 }
5323
5324 let mut head = selection.head();
5325 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
5326 if head.column() == display_map.line_len(head.row()) {
5327 transpose_offset = display_map
5328 .buffer_snapshot
5329 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
5330 }
5331
5332 if transpose_offset == 0 {
5333 return;
5334 }
5335
5336 *head.column_mut() += 1;
5337 head = display_map.clip_point(head, Bias::Right);
5338 let goal = SelectionGoal::HorizontalPosition(
5339 display_map
5340 .x_for_display_point(head, &text_layout_details)
5341 .into(),
5342 );
5343 selection.collapse_to(head, goal);
5344
5345 let transpose_start = display_map
5346 .buffer_snapshot
5347 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
5348 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
5349 let transpose_end = display_map
5350 .buffer_snapshot
5351 .clip_offset(transpose_offset + 1, Bias::Right);
5352 if let Some(ch) =
5353 display_map.buffer_snapshot.chars_at(transpose_start).next()
5354 {
5355 edits.push((transpose_start..transpose_offset, String::new()));
5356 edits.push((transpose_end..transpose_end, ch.to_string()));
5357 }
5358 }
5359 });
5360 edits
5361 });
5362 this.buffer
5363 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
5364 let selections = this.selections.all::<usize>(cx);
5365 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
5366 s.select(selections);
5367 });
5368 });
5369 }
5370
5371 pub fn cut(&mut self, _: &Cut, cx: &mut ViewContext<Self>) {
5372 let mut text = String::new();
5373 let buffer = self.buffer.read(cx).snapshot(cx);
5374 let mut selections = self.selections.all::<Point>(cx);
5375 let mut clipboard_selections = Vec::with_capacity(selections.len());
5376 {
5377 let max_point = buffer.max_point();
5378 let mut is_first = true;
5379 for selection in &mut selections {
5380 let is_entire_line = selection.is_empty() || self.selections.line_mode;
5381 if is_entire_line {
5382 selection.start = Point::new(selection.start.row, 0);
5383 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
5384 selection.goal = SelectionGoal::None;
5385 }
5386 if is_first {
5387 is_first = false;
5388 } else {
5389 text += "\n";
5390 }
5391 let mut len = 0;
5392 for chunk in buffer.text_for_range(selection.start..selection.end) {
5393 text.push_str(chunk);
5394 len += chunk.len();
5395 }
5396 clipboard_selections.push(ClipboardSelection {
5397 len,
5398 is_entire_line,
5399 first_line_indent: buffer.indent_size_for_line(selection.start.row).len,
5400 });
5401 }
5402 }
5403
5404 self.transact(cx, |this, cx| {
5405 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
5406 s.select(selections);
5407 });
5408 this.insert("", cx);
5409 cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
5410 });
5411 }
5412
5413 pub fn copy(&mut self, _: &Copy, cx: &mut ViewContext<Self>) {
5414 let selections = self.selections.all::<Point>(cx);
5415 let buffer = self.buffer.read(cx).read(cx);
5416 let mut text = String::new();
5417
5418 let mut clipboard_selections = Vec::with_capacity(selections.len());
5419 {
5420 let max_point = buffer.max_point();
5421 let mut is_first = true;
5422 for selection in selections.iter() {
5423 let mut start = selection.start;
5424 let mut end = selection.end;
5425 let is_entire_line = selection.is_empty() || self.selections.line_mode;
5426 if is_entire_line {
5427 start = Point::new(start.row, 0);
5428 end = cmp::min(max_point, Point::new(end.row + 1, 0));
5429 }
5430 if is_first {
5431 is_first = false;
5432 } else {
5433 text += "\n";
5434 }
5435 let mut len = 0;
5436 for chunk in buffer.text_for_range(start..end) {
5437 text.push_str(chunk);
5438 len += chunk.len();
5439 }
5440 clipboard_selections.push(ClipboardSelection {
5441 len,
5442 is_entire_line,
5443 first_line_indent: buffer.indent_size_for_line(start.row).len,
5444 });
5445 }
5446 }
5447
5448 cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
5449 }
5450
5451 pub fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
5452 if self.read_only(cx) {
5453 return;
5454 }
5455
5456 self.transact(cx, |this, cx| {
5457 if let Some(item) = cx.read_from_clipboard() {
5458 let clipboard_text = Cow::Borrowed(item.text());
5459 if let Some(mut clipboard_selections) = item.metadata::<Vec<ClipboardSelection>>() {
5460 let old_selections = this.selections.all::<usize>(cx);
5461 let all_selections_were_entire_line =
5462 clipboard_selections.iter().all(|s| s.is_entire_line);
5463 let first_selection_indent_column =
5464 clipboard_selections.first().map(|s| s.first_line_indent);
5465 if clipboard_selections.len() != old_selections.len() {
5466 clipboard_selections.drain(..);
5467 }
5468
5469 this.buffer.update(cx, |buffer, cx| {
5470 let snapshot = buffer.read(cx);
5471 let mut start_offset = 0;
5472 let mut edits = Vec::new();
5473 let mut original_indent_columns = Vec::new();
5474 let line_mode = this.selections.line_mode;
5475 for (ix, selection) in old_selections.iter().enumerate() {
5476 let to_insert;
5477 let entire_line;
5478 let original_indent_column;
5479 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
5480 let end_offset = start_offset + clipboard_selection.len;
5481 to_insert = &clipboard_text[start_offset..end_offset];
5482 entire_line = clipboard_selection.is_entire_line;
5483 start_offset = end_offset + 1;
5484 original_indent_column =
5485 Some(clipboard_selection.first_line_indent);
5486 } else {
5487 to_insert = clipboard_text.as_str();
5488 entire_line = all_selections_were_entire_line;
5489 original_indent_column = first_selection_indent_column
5490 }
5491
5492 // If the corresponding selection was empty when this slice of the
5493 // clipboard text was written, then the entire line containing the
5494 // selection was copied. If this selection is also currently empty,
5495 // then paste the line before the current line of the buffer.
5496 let range = if selection.is_empty() && !line_mode && entire_line {
5497 let column = selection.start.to_point(&snapshot).column as usize;
5498 let line_start = selection.start - column;
5499 line_start..line_start
5500 } else {
5501 selection.range()
5502 };
5503
5504 edits.push((range, to_insert));
5505 original_indent_columns.extend(original_indent_column);
5506 }
5507 drop(snapshot);
5508
5509 buffer.edit(
5510 edits,
5511 Some(AutoindentMode::Block {
5512 original_indent_columns,
5513 }),
5514 cx,
5515 );
5516 });
5517
5518 let selections = this.selections.all::<usize>(cx);
5519 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
5520 } else {
5521 this.insert(&clipboard_text, cx);
5522 }
5523 }
5524 });
5525 }
5526
5527 pub fn undo(&mut self, _: &Undo, cx: &mut ViewContext<Self>) {
5528 if self.read_only(cx) {
5529 return;
5530 }
5531
5532 if let Some(tx_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
5533 if let Some((selections, _)) = self.selection_history.transaction(tx_id).cloned() {
5534 self.change_selections(None, cx, |s| {
5535 s.select_anchors(selections.to_vec());
5536 });
5537 }
5538 self.request_autoscroll(Autoscroll::fit(), cx);
5539 self.unmark_text(cx);
5540 self.refresh_copilot_suggestions(true, cx);
5541 cx.emit(EditorEvent::Edited);
5542 }
5543 }
5544
5545 pub fn redo(&mut self, _: &Redo, cx: &mut ViewContext<Self>) {
5546 if self.read_only(cx) {
5547 return;
5548 }
5549
5550 if let Some(tx_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
5551 if let Some((_, Some(selections))) = self.selection_history.transaction(tx_id).cloned()
5552 {
5553 self.change_selections(None, cx, |s| {
5554 s.select_anchors(selections.to_vec());
5555 });
5556 }
5557 self.request_autoscroll(Autoscroll::fit(), cx);
5558 self.unmark_text(cx);
5559 self.refresh_copilot_suggestions(true, cx);
5560 cx.emit(EditorEvent::Edited);
5561 }
5562 }
5563
5564 pub fn finalize_last_transaction(&mut self, cx: &mut ViewContext<Self>) {
5565 self.buffer
5566 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
5567 }
5568
5569 pub fn move_left(&mut self, _: &MoveLeft, cx: &mut ViewContext<Self>) {
5570 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
5571 let line_mode = s.line_mode;
5572 s.move_with(|map, selection| {
5573 let cursor = if selection.is_empty() && !line_mode {
5574 movement::left(map, selection.start)
5575 } else {
5576 selection.start
5577 };
5578 selection.collapse_to(cursor, SelectionGoal::None);
5579 });
5580 })
5581 }
5582
5583 pub fn select_left(&mut self, _: &SelectLeft, cx: &mut ViewContext<Self>) {
5584 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
5585 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
5586 })
5587 }
5588
5589 pub fn move_right(&mut self, _: &MoveRight, cx: &mut ViewContext<Self>) {
5590 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
5591 let line_mode = s.line_mode;
5592 s.move_with(|map, selection| {
5593 let cursor = if selection.is_empty() && !line_mode {
5594 movement::right(map, selection.end)
5595 } else {
5596 selection.end
5597 };
5598 selection.collapse_to(cursor, SelectionGoal::None)
5599 });
5600 })
5601 }
5602
5603 pub fn select_right(&mut self, _: &SelectRight, cx: &mut ViewContext<Self>) {
5604 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
5605 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
5606 })
5607 }
5608
5609 pub fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) {
5610 if self.take_rename(true, cx).is_some() {
5611 return;
5612 }
5613
5614 if matches!(self.mode, EditorMode::SingleLine) {
5615 cx.propagate();
5616 return;
5617 }
5618
5619 let text_layout_details = &self.text_layout_details(cx);
5620
5621 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
5622 let line_mode = s.line_mode;
5623 s.move_with(|map, selection| {
5624 if !selection.is_empty() && !line_mode {
5625 selection.goal = SelectionGoal::None;
5626 }
5627 let (cursor, goal) = movement::up(
5628 map,
5629 selection.start,
5630 selection.goal,
5631 false,
5632 &text_layout_details,
5633 );
5634 selection.collapse_to(cursor, goal);
5635 });
5636 })
5637 }
5638
5639 pub fn move_page_up(&mut self, action: &MovePageUp, cx: &mut ViewContext<Self>) {
5640 if self.take_rename(true, cx).is_some() {
5641 return;
5642 }
5643
5644 if matches!(self.mode, EditorMode::SingleLine) {
5645 cx.propagate();
5646 return;
5647 }
5648
5649 let row_count = if let Some(row_count) = self.visible_line_count() {
5650 row_count as u32 - 1
5651 } else {
5652 return;
5653 };
5654
5655 let autoscroll = if action.center_cursor {
5656 Autoscroll::center()
5657 } else {
5658 Autoscroll::fit()
5659 };
5660
5661 let text_layout_details = &self.text_layout_details(cx);
5662
5663 self.change_selections(Some(autoscroll), cx, |s| {
5664 let line_mode = s.line_mode;
5665 s.move_with(|map, selection| {
5666 if !selection.is_empty() && !line_mode {
5667 selection.goal = SelectionGoal::None;
5668 }
5669 let (cursor, goal) = movement::up_by_rows(
5670 map,
5671 selection.end,
5672 row_count,
5673 selection.goal,
5674 false,
5675 &text_layout_details,
5676 );
5677 selection.collapse_to(cursor, goal);
5678 });
5679 });
5680 }
5681
5682 pub fn select_up(&mut self, _: &SelectUp, cx: &mut ViewContext<Self>) {
5683 let text_layout_details = &self.text_layout_details(cx);
5684 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
5685 s.move_heads_with(|map, head, goal| {
5686 movement::up(map, head, goal, false, &text_layout_details)
5687 })
5688 })
5689 }
5690
5691 pub fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<Self>) {
5692 self.take_rename(true, cx);
5693
5694 if self.mode == EditorMode::SingleLine {
5695 cx.propagate();
5696 return;
5697 }
5698
5699 let text_layout_details = &self.text_layout_details(cx);
5700 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
5701 let line_mode = s.line_mode;
5702 s.move_with(|map, selection| {
5703 if !selection.is_empty() && !line_mode {
5704 selection.goal = SelectionGoal::None;
5705 }
5706 let (cursor, goal) = movement::down(
5707 map,
5708 selection.end,
5709 selection.goal,
5710 false,
5711 &text_layout_details,
5712 );
5713 selection.collapse_to(cursor, goal);
5714 });
5715 });
5716 }
5717
5718 pub fn move_page_down(&mut self, action: &MovePageDown, cx: &mut ViewContext<Self>) {
5719 if self.take_rename(true, cx).is_some() {
5720 return;
5721 }
5722
5723 if self
5724 .context_menu
5725 .write()
5726 .as_mut()
5727 .map(|menu| menu.select_last(self.project.as_ref(), cx))
5728 .unwrap_or(false)
5729 {
5730 return;
5731 }
5732
5733 if matches!(self.mode, EditorMode::SingleLine) {
5734 cx.propagate();
5735 return;
5736 }
5737
5738 let row_count = if let Some(row_count) = self.visible_line_count() {
5739 row_count as u32 - 1
5740 } else {
5741 return;
5742 };
5743
5744 let autoscroll = if action.center_cursor {
5745 Autoscroll::center()
5746 } else {
5747 Autoscroll::fit()
5748 };
5749
5750 let text_layout_details = &self.text_layout_details(cx);
5751 self.change_selections(Some(autoscroll), cx, |s| {
5752 let line_mode = s.line_mode;
5753 s.move_with(|map, selection| {
5754 if !selection.is_empty() && !line_mode {
5755 selection.goal = SelectionGoal::None;
5756 }
5757 let (cursor, goal) = movement::down_by_rows(
5758 map,
5759 selection.end,
5760 row_count,
5761 selection.goal,
5762 false,
5763 &text_layout_details,
5764 );
5765 selection.collapse_to(cursor, goal);
5766 });
5767 });
5768 }
5769
5770 pub fn select_down(&mut self, _: &SelectDown, cx: &mut ViewContext<Self>) {
5771 let text_layout_details = &self.text_layout_details(cx);
5772 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
5773 s.move_heads_with(|map, head, goal| {
5774 movement::down(map, head, goal, false, &text_layout_details)
5775 })
5776 });
5777 }
5778
5779 pub fn context_menu_first(&mut self, _: &ContextMenuFirst, cx: &mut ViewContext<Self>) {
5780 if let Some(context_menu) = self.context_menu.write().as_mut() {
5781 context_menu.select_first(self.project.as_ref(), cx);
5782 }
5783 }
5784
5785 pub fn context_menu_prev(&mut self, _: &ContextMenuPrev, cx: &mut ViewContext<Self>) {
5786 if let Some(context_menu) = self.context_menu.write().as_mut() {
5787 context_menu.select_prev(self.project.as_ref(), cx);
5788 }
5789 }
5790
5791 pub fn context_menu_next(&mut self, _: &ContextMenuNext, cx: &mut ViewContext<Self>) {
5792 if let Some(context_menu) = self.context_menu.write().as_mut() {
5793 context_menu.select_next(self.project.as_ref(), cx);
5794 }
5795 }
5796
5797 pub fn context_menu_last(&mut self, _: &ContextMenuLast, cx: &mut ViewContext<Self>) {
5798 if let Some(context_menu) = self.context_menu.write().as_mut() {
5799 context_menu.select_last(self.project.as_ref(), cx);
5800 }
5801 }
5802
5803 pub fn move_to_previous_word_start(
5804 &mut self,
5805 _: &MoveToPreviousWordStart,
5806 cx: &mut ViewContext<Self>,
5807 ) {
5808 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
5809 s.move_cursors_with(|map, head, _| {
5810 (
5811 movement::previous_word_start(map, head),
5812 SelectionGoal::None,
5813 )
5814 });
5815 })
5816 }
5817
5818 pub fn move_to_previous_subword_start(
5819 &mut self,
5820 _: &MoveToPreviousSubwordStart,
5821 cx: &mut ViewContext<Self>,
5822 ) {
5823 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
5824 s.move_cursors_with(|map, head, _| {
5825 (
5826 movement::previous_subword_start(map, head),
5827 SelectionGoal::None,
5828 )
5829 });
5830 })
5831 }
5832
5833 pub fn select_to_previous_word_start(
5834 &mut self,
5835 _: &SelectToPreviousWordStart,
5836 cx: &mut ViewContext<Self>,
5837 ) {
5838 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
5839 s.move_heads_with(|map, head, _| {
5840 (
5841 movement::previous_word_start(map, head),
5842 SelectionGoal::None,
5843 )
5844 });
5845 })
5846 }
5847
5848 pub fn select_to_previous_subword_start(
5849 &mut self,
5850 _: &SelectToPreviousSubwordStart,
5851 cx: &mut ViewContext<Self>,
5852 ) {
5853 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
5854 s.move_heads_with(|map, head, _| {
5855 (
5856 movement::previous_subword_start(map, head),
5857 SelectionGoal::None,
5858 )
5859 });
5860 })
5861 }
5862
5863 pub fn delete_to_previous_word_start(
5864 &mut self,
5865 _: &DeleteToPreviousWordStart,
5866 cx: &mut ViewContext<Self>,
5867 ) {
5868 self.transact(cx, |this, cx| {
5869 this.select_autoclose_pair(cx);
5870 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
5871 let line_mode = s.line_mode;
5872 s.move_with(|map, selection| {
5873 if selection.is_empty() && !line_mode {
5874 let cursor = movement::previous_word_start(map, selection.head());
5875 selection.set_head(cursor, SelectionGoal::None);
5876 }
5877 });
5878 });
5879 this.insert("", cx);
5880 });
5881 }
5882
5883 pub fn delete_to_previous_subword_start(
5884 &mut self,
5885 _: &DeleteToPreviousSubwordStart,
5886 cx: &mut ViewContext<Self>,
5887 ) {
5888 self.transact(cx, |this, cx| {
5889 this.select_autoclose_pair(cx);
5890 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
5891 let line_mode = s.line_mode;
5892 s.move_with(|map, selection| {
5893 if selection.is_empty() && !line_mode {
5894 let cursor = movement::previous_subword_start(map, selection.head());
5895 selection.set_head(cursor, SelectionGoal::None);
5896 }
5897 });
5898 });
5899 this.insert("", cx);
5900 });
5901 }
5902
5903 pub fn move_to_next_word_end(&mut self, _: &MoveToNextWordEnd, cx: &mut ViewContext<Self>) {
5904 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
5905 s.move_cursors_with(|map, head, _| {
5906 (movement::next_word_end(map, head), SelectionGoal::None)
5907 });
5908 })
5909 }
5910
5911 pub fn move_to_next_subword_end(
5912 &mut self,
5913 _: &MoveToNextSubwordEnd,
5914 cx: &mut ViewContext<Self>,
5915 ) {
5916 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
5917 s.move_cursors_with(|map, head, _| {
5918 (movement::next_subword_end(map, head), SelectionGoal::None)
5919 });
5920 })
5921 }
5922
5923 pub fn select_to_next_word_end(&mut self, _: &SelectToNextWordEnd, cx: &mut ViewContext<Self>) {
5924 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
5925 s.move_heads_with(|map, head, _| {
5926 (movement::next_word_end(map, head), SelectionGoal::None)
5927 });
5928 })
5929 }
5930
5931 pub fn select_to_next_subword_end(
5932 &mut self,
5933 _: &SelectToNextSubwordEnd,
5934 cx: &mut ViewContext<Self>,
5935 ) {
5936 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
5937 s.move_heads_with(|map, head, _| {
5938 (movement::next_subword_end(map, head), SelectionGoal::None)
5939 });
5940 })
5941 }
5942
5943 pub fn delete_to_next_word_end(&mut self, _: &DeleteToNextWordEnd, cx: &mut ViewContext<Self>) {
5944 self.transact(cx, |this, cx| {
5945 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
5946 let line_mode = s.line_mode;
5947 s.move_with(|map, selection| {
5948 if selection.is_empty() && !line_mode {
5949 let cursor = movement::next_word_end(map, selection.head());
5950 selection.set_head(cursor, SelectionGoal::None);
5951 }
5952 });
5953 });
5954 this.insert("", cx);
5955 });
5956 }
5957
5958 pub fn delete_to_next_subword_end(
5959 &mut self,
5960 _: &DeleteToNextSubwordEnd,
5961 cx: &mut ViewContext<Self>,
5962 ) {
5963 self.transact(cx, |this, cx| {
5964 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
5965 s.move_with(|map, selection| {
5966 if selection.is_empty() {
5967 let cursor = movement::next_subword_end(map, selection.head());
5968 selection.set_head(cursor, SelectionGoal::None);
5969 }
5970 });
5971 });
5972 this.insert("", cx);
5973 });
5974 }
5975
5976 pub fn move_to_beginning_of_line(
5977 &mut self,
5978 _: &MoveToBeginningOfLine,
5979 cx: &mut ViewContext<Self>,
5980 ) {
5981 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
5982 s.move_cursors_with(|map, head, _| {
5983 (
5984 movement::indented_line_beginning(map, head, true),
5985 SelectionGoal::None,
5986 )
5987 });
5988 })
5989 }
5990
5991 pub fn select_to_beginning_of_line(
5992 &mut self,
5993 action: &SelectToBeginningOfLine,
5994 cx: &mut ViewContext<Self>,
5995 ) {
5996 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
5997 s.move_heads_with(|map, head, _| {
5998 (
5999 movement::indented_line_beginning(map, head, action.stop_at_soft_wraps),
6000 SelectionGoal::None,
6001 )
6002 });
6003 });
6004 }
6005
6006 pub fn delete_to_beginning_of_line(
6007 &mut self,
6008 _: &DeleteToBeginningOfLine,
6009 cx: &mut ViewContext<Self>,
6010 ) {
6011 self.transact(cx, |this, cx| {
6012 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
6013 s.move_with(|_, selection| {
6014 selection.reversed = true;
6015 });
6016 });
6017
6018 this.select_to_beginning_of_line(
6019 &SelectToBeginningOfLine {
6020 stop_at_soft_wraps: false,
6021 },
6022 cx,
6023 );
6024 this.backspace(&Backspace, cx);
6025 });
6026 }
6027
6028 pub fn move_to_end_of_line(&mut self, _: &MoveToEndOfLine, cx: &mut ViewContext<Self>) {
6029 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6030 s.move_cursors_with(|map, head, _| {
6031 (movement::line_end(map, head, true), SelectionGoal::None)
6032 });
6033 })
6034 }
6035
6036 pub fn select_to_end_of_line(
6037 &mut self,
6038 action: &SelectToEndOfLine,
6039 cx: &mut ViewContext<Self>,
6040 ) {
6041 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6042 s.move_heads_with(|map, head, _| {
6043 (
6044 movement::line_end(map, head, action.stop_at_soft_wraps),
6045 SelectionGoal::None,
6046 )
6047 });
6048 })
6049 }
6050
6051 pub fn delete_to_end_of_line(&mut self, _: &DeleteToEndOfLine, cx: &mut ViewContext<Self>) {
6052 self.transact(cx, |this, cx| {
6053 this.select_to_end_of_line(
6054 &SelectToEndOfLine {
6055 stop_at_soft_wraps: false,
6056 },
6057 cx,
6058 );
6059 this.delete(&Delete, cx);
6060 });
6061 }
6062
6063 pub fn cut_to_end_of_line(&mut self, _: &CutToEndOfLine, cx: &mut ViewContext<Self>) {
6064 self.transact(cx, |this, cx| {
6065 this.select_to_end_of_line(
6066 &SelectToEndOfLine {
6067 stop_at_soft_wraps: false,
6068 },
6069 cx,
6070 );
6071 this.cut(&Cut, cx);
6072 });
6073 }
6074
6075 pub fn move_to_start_of_paragraph(
6076 &mut self,
6077 _: &MoveToStartOfParagraph,
6078 cx: &mut ViewContext<Self>,
6079 ) {
6080 if matches!(self.mode, EditorMode::SingleLine) {
6081 cx.propagate();
6082 return;
6083 }
6084
6085 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6086 s.move_with(|map, selection| {
6087 selection.collapse_to(
6088 movement::start_of_paragraph(map, selection.head(), 1),
6089 SelectionGoal::None,
6090 )
6091 });
6092 })
6093 }
6094
6095 pub fn move_to_end_of_paragraph(
6096 &mut self,
6097 _: &MoveToEndOfParagraph,
6098 cx: &mut ViewContext<Self>,
6099 ) {
6100 if matches!(self.mode, EditorMode::SingleLine) {
6101 cx.propagate();
6102 return;
6103 }
6104
6105 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6106 s.move_with(|map, selection| {
6107 selection.collapse_to(
6108 movement::end_of_paragraph(map, selection.head(), 1),
6109 SelectionGoal::None,
6110 )
6111 });
6112 })
6113 }
6114
6115 pub fn select_to_start_of_paragraph(
6116 &mut self,
6117 _: &SelectToStartOfParagraph,
6118 cx: &mut ViewContext<Self>,
6119 ) {
6120 if matches!(self.mode, EditorMode::SingleLine) {
6121 cx.propagate();
6122 return;
6123 }
6124
6125 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6126 s.move_heads_with(|map, head, _| {
6127 (
6128 movement::start_of_paragraph(map, head, 1),
6129 SelectionGoal::None,
6130 )
6131 });
6132 })
6133 }
6134
6135 pub fn select_to_end_of_paragraph(
6136 &mut self,
6137 _: &SelectToEndOfParagraph,
6138 cx: &mut ViewContext<Self>,
6139 ) {
6140 if matches!(self.mode, EditorMode::SingleLine) {
6141 cx.propagate();
6142 return;
6143 }
6144
6145 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6146 s.move_heads_with(|map, head, _| {
6147 (
6148 movement::end_of_paragraph(map, head, 1),
6149 SelectionGoal::None,
6150 )
6151 });
6152 })
6153 }
6154
6155 pub fn move_to_beginning(&mut self, _: &MoveToBeginning, cx: &mut ViewContext<Self>) {
6156 if matches!(self.mode, EditorMode::SingleLine) {
6157 cx.propagate();
6158 return;
6159 }
6160
6161 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6162 s.select_ranges(vec![0..0]);
6163 });
6164 }
6165
6166 pub fn select_to_beginning(&mut self, _: &SelectToBeginning, cx: &mut ViewContext<Self>) {
6167 let mut selection = self.selections.last::<Point>(cx);
6168 selection.set_head(Point::zero(), SelectionGoal::None);
6169
6170 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6171 s.select(vec![selection]);
6172 });
6173 }
6174
6175 pub fn move_to_end(&mut self, _: &MoveToEnd, cx: &mut ViewContext<Self>) {
6176 if matches!(self.mode, EditorMode::SingleLine) {
6177 cx.propagate();
6178 return;
6179 }
6180
6181 let cursor = self.buffer.read(cx).read(cx).len();
6182 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6183 s.select_ranges(vec![cursor..cursor])
6184 });
6185 }
6186
6187 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
6188 self.nav_history = nav_history;
6189 }
6190
6191 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
6192 self.nav_history.as_ref()
6193 }
6194
6195 fn push_to_nav_history(
6196 &mut self,
6197 cursor_anchor: Anchor,
6198 new_position: Option<Point>,
6199 cx: &mut ViewContext<Self>,
6200 ) {
6201 if let Some(nav_history) = self.nav_history.as_mut() {
6202 let buffer = self.buffer.read(cx).read(cx);
6203 let cursor_position = cursor_anchor.to_point(&buffer);
6204 let scroll_state = self.scroll_manager.anchor();
6205 let scroll_top_row = scroll_state.top_row(&buffer);
6206 drop(buffer);
6207
6208 if let Some(new_position) = new_position {
6209 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
6210 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
6211 return;
6212 }
6213 }
6214
6215 nav_history.push(
6216 Some(NavigationData {
6217 cursor_anchor,
6218 cursor_position,
6219 scroll_anchor: scroll_state,
6220 scroll_top_row,
6221 }),
6222 cx,
6223 );
6224 }
6225 }
6226
6227 pub fn select_to_end(&mut self, _: &SelectToEnd, cx: &mut ViewContext<Self>) {
6228 let buffer = self.buffer.read(cx).snapshot(cx);
6229 let mut selection = self.selections.first::<usize>(cx);
6230 selection.set_head(buffer.len(), SelectionGoal::None);
6231 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6232 s.select(vec![selection]);
6233 });
6234 }
6235
6236 pub fn select_all(&mut self, _: &SelectAll, cx: &mut ViewContext<Self>) {
6237 let end = self.buffer.read(cx).read(cx).len();
6238 self.change_selections(None, cx, |s| {
6239 s.select_ranges(vec![0..end]);
6240 });
6241 }
6242
6243 pub fn select_line(&mut self, _: &SelectLine, cx: &mut ViewContext<Self>) {
6244 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
6245 let mut selections = self.selections.all::<Point>(cx);
6246 let max_point = display_map.buffer_snapshot.max_point();
6247 for selection in &mut selections {
6248 let rows = selection.spanned_rows(true, &display_map);
6249 selection.start = Point::new(rows.start, 0);
6250 selection.end = cmp::min(max_point, Point::new(rows.end, 0));
6251 selection.reversed = false;
6252 }
6253 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6254 s.select(selections);
6255 });
6256 }
6257
6258 pub fn split_selection_into_lines(
6259 &mut self,
6260 _: &SplitSelectionIntoLines,
6261 cx: &mut ViewContext<Self>,
6262 ) {
6263 let mut to_unfold = Vec::new();
6264 let mut new_selection_ranges = Vec::new();
6265 {
6266 let selections = self.selections.all::<Point>(cx);
6267 let buffer = self.buffer.read(cx).read(cx);
6268 for selection in selections {
6269 for row in selection.start.row..selection.end.row {
6270 let cursor = Point::new(row, buffer.line_len(row));
6271 new_selection_ranges.push(cursor..cursor);
6272 }
6273 new_selection_ranges.push(selection.end..selection.end);
6274 to_unfold.push(selection.start..selection.end);
6275 }
6276 }
6277 self.unfold_ranges(to_unfold, true, true, cx);
6278 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6279 s.select_ranges(new_selection_ranges);
6280 });
6281 }
6282
6283 pub fn add_selection_above(&mut self, _: &AddSelectionAbove, cx: &mut ViewContext<Self>) {
6284 self.add_selection(true, cx);
6285 }
6286
6287 pub fn add_selection_below(&mut self, _: &AddSelectionBelow, cx: &mut ViewContext<Self>) {
6288 self.add_selection(false, cx);
6289 }
6290
6291 fn add_selection(&mut self, above: bool, cx: &mut ViewContext<Self>) {
6292 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
6293 let mut selections = self.selections.all::<Point>(cx);
6294 let text_layout_details = self.text_layout_details(cx);
6295 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
6296 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
6297 let range = oldest_selection.display_range(&display_map).sorted();
6298
6299 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
6300 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
6301 let positions = start_x.min(end_x)..start_x.max(end_x);
6302
6303 selections.clear();
6304 let mut stack = Vec::new();
6305 for row in range.start.row()..=range.end.row() {
6306 if let Some(selection) = self.selections.build_columnar_selection(
6307 &display_map,
6308 row,
6309 &positions,
6310 oldest_selection.reversed,
6311 &text_layout_details,
6312 ) {
6313 stack.push(selection.id);
6314 selections.push(selection);
6315 }
6316 }
6317
6318 if above {
6319 stack.reverse();
6320 }
6321
6322 AddSelectionsState { above, stack }
6323 });
6324
6325 let last_added_selection = *state.stack.last().unwrap();
6326 let mut new_selections = Vec::new();
6327 if above == state.above {
6328 let end_row = if above {
6329 0
6330 } else {
6331 display_map.max_point().row()
6332 };
6333
6334 'outer: for selection in selections {
6335 if selection.id == last_added_selection {
6336 let range = selection.display_range(&display_map).sorted();
6337 debug_assert_eq!(range.start.row(), range.end.row());
6338 let mut row = range.start.row();
6339 let positions =
6340 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
6341 px(start)..px(end)
6342 } else {
6343 let start_x =
6344 display_map.x_for_display_point(range.start, &text_layout_details);
6345 let end_x =
6346 display_map.x_for_display_point(range.end, &text_layout_details);
6347 start_x.min(end_x)..start_x.max(end_x)
6348 };
6349
6350 while row != end_row {
6351 if above {
6352 row -= 1;
6353 } else {
6354 row += 1;
6355 }
6356
6357 if let Some(new_selection) = self.selections.build_columnar_selection(
6358 &display_map,
6359 row,
6360 &positions,
6361 selection.reversed,
6362 &text_layout_details,
6363 ) {
6364 state.stack.push(new_selection.id);
6365 if above {
6366 new_selections.push(new_selection);
6367 new_selections.push(selection);
6368 } else {
6369 new_selections.push(selection);
6370 new_selections.push(new_selection);
6371 }
6372
6373 continue 'outer;
6374 }
6375 }
6376 }
6377
6378 new_selections.push(selection);
6379 }
6380 } else {
6381 new_selections = selections;
6382 new_selections.retain(|s| s.id != last_added_selection);
6383 state.stack.pop();
6384 }
6385
6386 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6387 s.select(new_selections);
6388 });
6389 if state.stack.len() > 1 {
6390 self.add_selections_state = Some(state);
6391 }
6392 }
6393
6394 pub fn select_next_match_internal(
6395 &mut self,
6396 display_map: &DisplaySnapshot,
6397 replace_newest: bool,
6398 autoscroll: Option<Autoscroll>,
6399 cx: &mut ViewContext<Self>,
6400 ) -> Result<()> {
6401 fn select_next_match_ranges(
6402 this: &mut Editor,
6403 range: Range<usize>,
6404 replace_newest: bool,
6405 auto_scroll: Option<Autoscroll>,
6406 cx: &mut ViewContext<Editor>,
6407 ) {
6408 this.unfold_ranges([range.clone()], false, true, cx);
6409 this.change_selections(auto_scroll, cx, |s| {
6410 if replace_newest {
6411 s.delete(s.newest_anchor().id);
6412 }
6413 s.insert_range(range.clone());
6414 });
6415 }
6416
6417 let buffer = &display_map.buffer_snapshot;
6418 let mut selections = self.selections.all::<usize>(cx);
6419 if let Some(mut select_next_state) = self.select_next_state.take() {
6420 let query = &select_next_state.query;
6421 if !select_next_state.done {
6422 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
6423 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
6424 let mut next_selected_range = None;
6425
6426 let bytes_after_last_selection =
6427 buffer.bytes_in_range(last_selection.end..buffer.len());
6428 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
6429 let query_matches = query
6430 .stream_find_iter(bytes_after_last_selection)
6431 .map(|result| (last_selection.end, result))
6432 .chain(
6433 query
6434 .stream_find_iter(bytes_before_first_selection)
6435 .map(|result| (0, result)),
6436 );
6437
6438 for (start_offset, query_match) in query_matches {
6439 let query_match = query_match.unwrap(); // can only fail due to I/O
6440 let offset_range =
6441 start_offset + query_match.start()..start_offset + query_match.end();
6442 let display_range = offset_range.start.to_display_point(&display_map)
6443 ..offset_range.end.to_display_point(&display_map);
6444
6445 if !select_next_state.wordwise
6446 || (!movement::is_inside_word(&display_map, display_range.start)
6447 && !movement::is_inside_word(&display_map, display_range.end))
6448 {
6449 if selections
6450 .iter()
6451 .find(|selection| selection.range().overlaps(&offset_range))
6452 .is_none()
6453 {
6454 next_selected_range = Some(offset_range);
6455 break;
6456 }
6457 }
6458 }
6459
6460 if let Some(next_selected_range) = next_selected_range {
6461 select_next_match_ranges(
6462 self,
6463 next_selected_range,
6464 replace_newest,
6465 autoscroll,
6466 cx,
6467 );
6468 } else {
6469 select_next_state.done = true;
6470 }
6471 }
6472
6473 self.select_next_state = Some(select_next_state);
6474 } else if selections.len() == 1 {
6475 let selection = selections.last_mut().unwrap();
6476 if selection.start == selection.end {
6477 let word_range = movement::surrounding_word(
6478 &display_map,
6479 selection.start.to_display_point(&display_map),
6480 );
6481 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
6482 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
6483 selection.goal = SelectionGoal::None;
6484 selection.reversed = false;
6485
6486 let query = buffer
6487 .text_for_range(selection.start..selection.end)
6488 .collect::<String>();
6489
6490 let is_empty = query.is_empty();
6491 let select_state = SelectNextState {
6492 query: AhoCorasick::new(&[query])?,
6493 wordwise: true,
6494 done: is_empty,
6495 };
6496 select_next_match_ranges(
6497 self,
6498 selection.start..selection.end,
6499 replace_newest,
6500 autoscroll,
6501 cx,
6502 );
6503 self.select_next_state = Some(select_state);
6504 } else {
6505 let query = buffer
6506 .text_for_range(selection.start..selection.end)
6507 .collect::<String>();
6508 self.select_next_state = Some(SelectNextState {
6509 query: AhoCorasick::new(&[query])?,
6510 wordwise: false,
6511 done: false,
6512 });
6513 self.select_next_match_internal(display_map, replace_newest, autoscroll, cx)?;
6514 }
6515 }
6516 Ok(())
6517 }
6518
6519 pub fn select_all_matches(
6520 &mut self,
6521 action: &SelectAllMatches,
6522 cx: &mut ViewContext<Self>,
6523 ) -> Result<()> {
6524 self.push_to_selection_history();
6525 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
6526
6527 loop {
6528 self.select_next_match_internal(&display_map, action.replace_newest, None, cx)?;
6529
6530 if self
6531 .select_next_state
6532 .as_ref()
6533 .map(|selection_state| selection_state.done)
6534 .unwrap_or(true)
6535 {
6536 break;
6537 }
6538 }
6539
6540 Ok(())
6541 }
6542
6543 pub fn select_next(&mut self, action: &SelectNext, cx: &mut ViewContext<Self>) -> Result<()> {
6544 self.push_to_selection_history();
6545 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
6546 self.select_next_match_internal(
6547 &display_map,
6548 action.replace_newest,
6549 Some(Autoscroll::newest()),
6550 cx,
6551 )?;
6552 Ok(())
6553 }
6554
6555 pub fn select_previous(
6556 &mut self,
6557 action: &SelectPrevious,
6558 cx: &mut ViewContext<Self>,
6559 ) -> Result<()> {
6560 self.push_to_selection_history();
6561 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
6562 let buffer = &display_map.buffer_snapshot;
6563 let mut selections = self.selections.all::<usize>(cx);
6564 if let Some(mut select_prev_state) = self.select_prev_state.take() {
6565 let query = &select_prev_state.query;
6566 if !select_prev_state.done {
6567 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
6568 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
6569 let mut next_selected_range = None;
6570 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
6571 let bytes_before_last_selection =
6572 buffer.reversed_bytes_in_range(0..last_selection.start);
6573 let bytes_after_first_selection =
6574 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
6575 let query_matches = query
6576 .stream_find_iter(bytes_before_last_selection)
6577 .map(|result| (last_selection.start, result))
6578 .chain(
6579 query
6580 .stream_find_iter(bytes_after_first_selection)
6581 .map(|result| (buffer.len(), result)),
6582 );
6583 for (end_offset, query_match) in query_matches {
6584 let query_match = query_match.unwrap(); // can only fail due to I/O
6585 let offset_range =
6586 end_offset - query_match.end()..end_offset - query_match.start();
6587 let display_range = offset_range.start.to_display_point(&display_map)
6588 ..offset_range.end.to_display_point(&display_map);
6589
6590 if !select_prev_state.wordwise
6591 || (!movement::is_inside_word(&display_map, display_range.start)
6592 && !movement::is_inside_word(&display_map, display_range.end))
6593 {
6594 next_selected_range = Some(offset_range);
6595 break;
6596 }
6597 }
6598
6599 if let Some(next_selected_range) = next_selected_range {
6600 self.unfold_ranges([next_selected_range.clone()], false, true, cx);
6601 self.change_selections(Some(Autoscroll::newest()), cx, |s| {
6602 if action.replace_newest {
6603 s.delete(s.newest_anchor().id);
6604 }
6605 s.insert_range(next_selected_range);
6606 });
6607 } else {
6608 select_prev_state.done = true;
6609 }
6610 }
6611
6612 self.select_prev_state = Some(select_prev_state);
6613 } else if selections.len() == 1 {
6614 let selection = selections.last_mut().unwrap();
6615 if selection.start == selection.end {
6616 let word_range = movement::surrounding_word(
6617 &display_map,
6618 selection.start.to_display_point(&display_map),
6619 );
6620 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
6621 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
6622 selection.goal = SelectionGoal::None;
6623 selection.reversed = false;
6624
6625 let query = buffer
6626 .text_for_range(selection.start..selection.end)
6627 .collect::<String>();
6628 let query = query.chars().rev().collect::<String>();
6629 let select_state = SelectNextState {
6630 query: AhoCorasick::new(&[query])?,
6631 wordwise: true,
6632 done: false,
6633 };
6634 self.unfold_ranges([selection.start..selection.end], false, true, cx);
6635 self.change_selections(Some(Autoscroll::newest()), cx, |s| {
6636 s.select(selections);
6637 });
6638 self.select_prev_state = Some(select_state);
6639 } else {
6640 let query = buffer
6641 .text_for_range(selection.start..selection.end)
6642 .collect::<String>();
6643 let query = query.chars().rev().collect::<String>();
6644 self.select_prev_state = Some(SelectNextState {
6645 query: AhoCorasick::new(&[query])?,
6646 wordwise: false,
6647 done: false,
6648 });
6649 self.select_previous(action, cx)?;
6650 }
6651 }
6652 Ok(())
6653 }
6654
6655 pub fn toggle_comments(&mut self, action: &ToggleComments, cx: &mut ViewContext<Self>) {
6656 let text_layout_details = &self.text_layout_details(cx);
6657 self.transact(cx, |this, cx| {
6658 let mut selections = this.selections.all::<Point>(cx);
6659 let mut edits = Vec::new();
6660 let mut selection_edit_ranges = Vec::new();
6661 let mut last_toggled_row = None;
6662 let snapshot = this.buffer.read(cx).read(cx);
6663 let empty_str: Arc<str> = "".into();
6664 let mut suffixes_inserted = Vec::new();
6665
6666 fn comment_prefix_range(
6667 snapshot: &MultiBufferSnapshot,
6668 row: u32,
6669 comment_prefix: &str,
6670 comment_prefix_whitespace: &str,
6671 ) -> Range<Point> {
6672 let start = Point::new(row, snapshot.indent_size_for_line(row).len);
6673
6674 let mut line_bytes = snapshot
6675 .bytes_in_range(start..snapshot.max_point())
6676 .flatten()
6677 .copied();
6678
6679 // If this line currently begins with the line comment prefix, then record
6680 // the range containing the prefix.
6681 if line_bytes
6682 .by_ref()
6683 .take(comment_prefix.len())
6684 .eq(comment_prefix.bytes())
6685 {
6686 // Include any whitespace that matches the comment prefix.
6687 let matching_whitespace_len = line_bytes
6688 .zip(comment_prefix_whitespace.bytes())
6689 .take_while(|(a, b)| a == b)
6690 .count() as u32;
6691 let end = Point::new(
6692 start.row,
6693 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
6694 );
6695 start..end
6696 } else {
6697 start..start
6698 }
6699 }
6700
6701 fn comment_suffix_range(
6702 snapshot: &MultiBufferSnapshot,
6703 row: u32,
6704 comment_suffix: &str,
6705 comment_suffix_has_leading_space: bool,
6706 ) -> Range<Point> {
6707 let end = Point::new(row, snapshot.line_len(row));
6708 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
6709
6710 let mut line_end_bytes = snapshot
6711 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
6712 .flatten()
6713 .copied();
6714
6715 let leading_space_len = if suffix_start_column > 0
6716 && line_end_bytes.next() == Some(b' ')
6717 && comment_suffix_has_leading_space
6718 {
6719 1
6720 } else {
6721 0
6722 };
6723
6724 // If this line currently begins with the line comment prefix, then record
6725 // the range containing the prefix.
6726 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
6727 let start = Point::new(end.row, suffix_start_column - leading_space_len);
6728 start..end
6729 } else {
6730 end..end
6731 }
6732 }
6733
6734 // TODO: Handle selections that cross excerpts
6735 for selection in &mut selections {
6736 let start_column = snapshot.indent_size_for_line(selection.start.row).len;
6737 let language = if let Some(language) =
6738 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
6739 {
6740 language
6741 } else {
6742 continue;
6743 };
6744
6745 selection_edit_ranges.clear();
6746
6747 // If multiple selections contain a given row, avoid processing that
6748 // row more than once.
6749 let mut start_row = selection.start.row;
6750 if last_toggled_row == Some(start_row) {
6751 start_row += 1;
6752 }
6753 let end_row =
6754 if selection.end.row > selection.start.row && selection.end.column == 0 {
6755 selection.end.row - 1
6756 } else {
6757 selection.end.row
6758 };
6759 last_toggled_row = Some(end_row);
6760
6761 if start_row > end_row {
6762 continue;
6763 }
6764
6765 // If the language has line comments, toggle those.
6766 if let Some(full_comment_prefix) = language.line_comment_prefix() {
6767 // Split the comment prefix's trailing whitespace into a separate string,
6768 // as that portion won't be used for detecting if a line is a comment.
6769 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
6770 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
6771 let mut all_selection_lines_are_comments = true;
6772
6773 for row in start_row..=end_row {
6774 if snapshot.is_line_blank(row) && start_row < end_row {
6775 continue;
6776 }
6777
6778 let prefix_range = comment_prefix_range(
6779 snapshot.deref(),
6780 row,
6781 comment_prefix,
6782 comment_prefix_whitespace,
6783 );
6784 if prefix_range.is_empty() {
6785 all_selection_lines_are_comments = false;
6786 }
6787 selection_edit_ranges.push(prefix_range);
6788 }
6789
6790 if all_selection_lines_are_comments {
6791 edits.extend(
6792 selection_edit_ranges
6793 .iter()
6794 .cloned()
6795 .map(|range| (range, empty_str.clone())),
6796 );
6797 } else {
6798 let min_column = selection_edit_ranges
6799 .iter()
6800 .map(|r| r.start.column)
6801 .min()
6802 .unwrap_or(0);
6803 edits.extend(selection_edit_ranges.iter().map(|range| {
6804 let position = Point::new(range.start.row, min_column);
6805 (position..position, full_comment_prefix.clone())
6806 }));
6807 }
6808 } else if let Some((full_comment_prefix, comment_suffix)) =
6809 language.block_comment_delimiters()
6810 {
6811 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
6812 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
6813 let prefix_range = comment_prefix_range(
6814 snapshot.deref(),
6815 start_row,
6816 comment_prefix,
6817 comment_prefix_whitespace,
6818 );
6819 let suffix_range = comment_suffix_range(
6820 snapshot.deref(),
6821 end_row,
6822 comment_suffix.trim_start_matches(' '),
6823 comment_suffix.starts_with(' '),
6824 );
6825
6826 if prefix_range.is_empty() || suffix_range.is_empty() {
6827 edits.push((
6828 prefix_range.start..prefix_range.start,
6829 full_comment_prefix.clone(),
6830 ));
6831 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
6832 suffixes_inserted.push((end_row, comment_suffix.len()));
6833 } else {
6834 edits.push((prefix_range, empty_str.clone()));
6835 edits.push((suffix_range, empty_str.clone()));
6836 }
6837 } else {
6838 continue;
6839 }
6840 }
6841
6842 drop(snapshot);
6843 this.buffer.update(cx, |buffer, cx| {
6844 buffer.edit(edits, None, cx);
6845 });
6846
6847 // Adjust selections so that they end before any comment suffixes that
6848 // were inserted.
6849 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
6850 let mut selections = this.selections.all::<Point>(cx);
6851 let snapshot = this.buffer.read(cx).read(cx);
6852 for selection in &mut selections {
6853 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
6854 match row.cmp(&selection.end.row) {
6855 Ordering::Less => {
6856 suffixes_inserted.next();
6857 continue;
6858 }
6859 Ordering::Greater => break,
6860 Ordering::Equal => {
6861 if selection.end.column == snapshot.line_len(row) {
6862 if selection.is_empty() {
6863 selection.start.column -= suffix_len as u32;
6864 }
6865 selection.end.column -= suffix_len as u32;
6866 }
6867 break;
6868 }
6869 }
6870 }
6871 }
6872
6873 drop(snapshot);
6874 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
6875
6876 let selections = this.selections.all::<Point>(cx);
6877 let selections_on_single_row = selections.windows(2).all(|selections| {
6878 selections[0].start.row == selections[1].start.row
6879 && selections[0].end.row == selections[1].end.row
6880 && selections[0].start.row == selections[0].end.row
6881 });
6882 let selections_selecting = selections
6883 .iter()
6884 .any(|selection| selection.start != selection.end);
6885 let advance_downwards = action.advance_downwards
6886 && selections_on_single_row
6887 && !selections_selecting
6888 && this.mode != EditorMode::SingleLine;
6889
6890 if advance_downwards {
6891 let snapshot = this.buffer.read(cx).snapshot(cx);
6892
6893 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
6894 s.move_cursors_with(|display_snapshot, display_point, _| {
6895 let mut point = display_point.to_point(display_snapshot);
6896 point.row += 1;
6897 point = snapshot.clip_point(point, Bias::Left);
6898 let display_point = point.to_display_point(display_snapshot);
6899 let goal = SelectionGoal::HorizontalPosition(
6900 display_snapshot
6901 .x_for_display_point(display_point, &text_layout_details)
6902 .into(),
6903 );
6904 (display_point, goal)
6905 })
6906 });
6907 }
6908 });
6909 }
6910
6911 pub fn select_larger_syntax_node(
6912 &mut self,
6913 _: &SelectLargerSyntaxNode,
6914 cx: &mut ViewContext<Self>,
6915 ) {
6916 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
6917 let buffer = self.buffer.read(cx).snapshot(cx);
6918 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
6919
6920 let mut stack = mem::take(&mut self.select_larger_syntax_node_stack);
6921 let mut selected_larger_node = false;
6922 let new_selections = old_selections
6923 .iter()
6924 .map(|selection| {
6925 let old_range = selection.start..selection.end;
6926 let mut new_range = old_range.clone();
6927 while let Some(containing_range) =
6928 buffer.range_for_syntax_ancestor(new_range.clone())
6929 {
6930 new_range = containing_range;
6931 if !display_map.intersects_fold(new_range.start)
6932 && !display_map.intersects_fold(new_range.end)
6933 {
6934 break;
6935 }
6936 }
6937
6938 selected_larger_node |= new_range != old_range;
6939 Selection {
6940 id: selection.id,
6941 start: new_range.start,
6942 end: new_range.end,
6943 goal: SelectionGoal::None,
6944 reversed: selection.reversed,
6945 }
6946 })
6947 .collect::<Vec<_>>();
6948
6949 if selected_larger_node {
6950 stack.push(old_selections);
6951 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6952 s.select(new_selections);
6953 });
6954 }
6955 self.select_larger_syntax_node_stack = stack;
6956 }
6957
6958 pub fn select_smaller_syntax_node(
6959 &mut self,
6960 _: &SelectSmallerSyntaxNode,
6961 cx: &mut ViewContext<Self>,
6962 ) {
6963 let mut stack = mem::take(&mut self.select_larger_syntax_node_stack);
6964 if let Some(selections) = stack.pop() {
6965 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6966 s.select(selections.to_vec());
6967 });
6968 }
6969 self.select_larger_syntax_node_stack = stack;
6970 }
6971
6972 pub fn move_to_enclosing_bracket(
6973 &mut self,
6974 _: &MoveToEnclosingBracket,
6975 cx: &mut ViewContext<Self>,
6976 ) {
6977 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6978 s.move_offsets_with(|snapshot, selection| {
6979 let Some(enclosing_bracket_ranges) =
6980 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
6981 else {
6982 return;
6983 };
6984
6985 let mut best_length = usize::MAX;
6986 let mut best_inside = false;
6987 let mut best_in_bracket_range = false;
6988 let mut best_destination = None;
6989 for (open, close) in enclosing_bracket_ranges {
6990 let close = close.to_inclusive();
6991 let length = close.end() - open.start;
6992 let inside = selection.start >= open.end && selection.end <= *close.start();
6993 let in_bracket_range = open.to_inclusive().contains(&selection.head())
6994 || close.contains(&selection.head());
6995
6996 // If best is next to a bracket and current isn't, skip
6997 if !in_bracket_range && best_in_bracket_range {
6998 continue;
6999 }
7000
7001 // Prefer smaller lengths unless best is inside and current isn't
7002 if length > best_length && (best_inside || !inside) {
7003 continue;
7004 }
7005
7006 best_length = length;
7007 best_inside = inside;
7008 best_in_bracket_range = in_bracket_range;
7009 best_destination = Some(
7010 if close.contains(&selection.start) && close.contains(&selection.end) {
7011 if inside {
7012 open.end
7013 } else {
7014 open.start
7015 }
7016 } else {
7017 if inside {
7018 *close.start()
7019 } else {
7020 *close.end()
7021 }
7022 },
7023 );
7024 }
7025
7026 if let Some(destination) = best_destination {
7027 selection.collapse_to(destination, SelectionGoal::None);
7028 }
7029 })
7030 });
7031 }
7032
7033 pub fn undo_selection(&mut self, _: &UndoSelection, cx: &mut ViewContext<Self>) {
7034 self.end_selection(cx);
7035 self.selection_history.mode = SelectionHistoryMode::Undoing;
7036 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
7037 self.change_selections(None, cx, |s| s.select_anchors(entry.selections.to_vec()));
7038 self.select_next_state = entry.select_next_state;
7039 self.select_prev_state = entry.select_prev_state;
7040 self.add_selections_state = entry.add_selections_state;
7041 self.request_autoscroll(Autoscroll::newest(), cx);
7042 }
7043 self.selection_history.mode = SelectionHistoryMode::Normal;
7044 }
7045
7046 pub fn redo_selection(&mut self, _: &RedoSelection, cx: &mut ViewContext<Self>) {
7047 self.end_selection(cx);
7048 self.selection_history.mode = SelectionHistoryMode::Redoing;
7049 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
7050 self.change_selections(None, cx, |s| s.select_anchors(entry.selections.to_vec()));
7051 self.select_next_state = entry.select_next_state;
7052 self.select_prev_state = entry.select_prev_state;
7053 self.add_selections_state = entry.add_selections_state;
7054 self.request_autoscroll(Autoscroll::newest(), cx);
7055 }
7056 self.selection_history.mode = SelectionHistoryMode::Normal;
7057 }
7058
7059 fn go_to_diagnostic(&mut self, _: &GoToDiagnostic, cx: &mut ViewContext<Self>) {
7060 self.go_to_diagnostic_impl(Direction::Next, cx)
7061 }
7062
7063 fn go_to_prev_diagnostic(&mut self, _: &GoToPrevDiagnostic, cx: &mut ViewContext<Self>) {
7064 self.go_to_diagnostic_impl(Direction::Prev, cx)
7065 }
7066
7067 pub fn go_to_diagnostic_impl(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
7068 let buffer = self.buffer.read(cx).snapshot(cx);
7069 let selection = self.selections.newest::<usize>(cx);
7070
7071 // If there is an active Diagnostic Popover jump to its diagnostic instead.
7072 if direction == Direction::Next {
7073 if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() {
7074 let (group_id, jump_to) = popover.activation_info();
7075 if self.activate_diagnostics(group_id, cx) {
7076 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7077 let mut new_selection = s.newest_anchor().clone();
7078 new_selection.collapse_to(jump_to, SelectionGoal::None);
7079 s.select_anchors(vec![new_selection.clone()]);
7080 });
7081 }
7082 return;
7083 }
7084 }
7085
7086 let mut active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| {
7087 active_diagnostics
7088 .primary_range
7089 .to_offset(&buffer)
7090 .to_inclusive()
7091 });
7092 let mut search_start = if let Some(active_primary_range) = active_primary_range.as_ref() {
7093 if active_primary_range.contains(&selection.head()) {
7094 *active_primary_range.end()
7095 } else {
7096 selection.head()
7097 }
7098 } else {
7099 selection.head()
7100 };
7101
7102 loop {
7103 let mut diagnostics = if direction == Direction::Prev {
7104 buffer.diagnostics_in_range::<_, usize>(0..search_start, true)
7105 } else {
7106 buffer.diagnostics_in_range::<_, usize>(search_start..buffer.len(), false)
7107 };
7108 let group = diagnostics.find_map(|entry| {
7109 if entry.diagnostic.is_primary
7110 && entry.diagnostic.severity <= DiagnosticSeverity::WARNING
7111 && !entry.range.is_empty()
7112 && Some(entry.range.end) != active_primary_range.as_ref().map(|r| *r.end())
7113 && !entry.range.contains(&search_start)
7114 {
7115 Some((entry.range, entry.diagnostic.group_id))
7116 } else {
7117 None
7118 }
7119 });
7120
7121 if let Some((primary_range, group_id)) = group {
7122 if self.activate_diagnostics(group_id, cx) {
7123 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7124 s.select(vec![Selection {
7125 id: selection.id,
7126 start: primary_range.start,
7127 end: primary_range.start,
7128 reversed: false,
7129 goal: SelectionGoal::None,
7130 }]);
7131 });
7132 }
7133 break;
7134 } else {
7135 // Cycle around to the start of the buffer, potentially moving back to the start of
7136 // the currently active diagnostic.
7137 active_primary_range.take();
7138 if direction == Direction::Prev {
7139 if search_start == buffer.len() {
7140 break;
7141 } else {
7142 search_start = buffer.len();
7143 }
7144 } else if search_start == 0 {
7145 break;
7146 } else {
7147 search_start = 0;
7148 }
7149 }
7150 }
7151 }
7152
7153 fn go_to_hunk(&mut self, _: &GoToHunk, cx: &mut ViewContext<Self>) {
7154 let snapshot = self
7155 .display_map
7156 .update(cx, |display_map, cx| display_map.snapshot(cx));
7157 let selection = self.selections.newest::<Point>(cx);
7158
7159 if !self.seek_in_direction(
7160 &snapshot,
7161 selection.head(),
7162 false,
7163 snapshot
7164 .buffer_snapshot
7165 .git_diff_hunks_in_range((selection.head().row + 1)..u32::MAX),
7166 cx,
7167 ) {
7168 let wrapped_point = Point::zero();
7169 self.seek_in_direction(
7170 &snapshot,
7171 wrapped_point,
7172 true,
7173 snapshot
7174 .buffer_snapshot
7175 .git_diff_hunks_in_range((wrapped_point.row + 1)..u32::MAX),
7176 cx,
7177 );
7178 }
7179 }
7180
7181 fn go_to_prev_hunk(&mut self, _: &GoToPrevHunk, cx: &mut ViewContext<Self>) {
7182 let snapshot = self
7183 .display_map
7184 .update(cx, |display_map, cx| display_map.snapshot(cx));
7185 let selection = self.selections.newest::<Point>(cx);
7186
7187 if !self.seek_in_direction(
7188 &snapshot,
7189 selection.head(),
7190 false,
7191 snapshot
7192 .buffer_snapshot
7193 .git_diff_hunks_in_range_rev(0..selection.head().row),
7194 cx,
7195 ) {
7196 let wrapped_point = snapshot.buffer_snapshot.max_point();
7197 self.seek_in_direction(
7198 &snapshot,
7199 wrapped_point,
7200 true,
7201 snapshot
7202 .buffer_snapshot
7203 .git_diff_hunks_in_range_rev(0..wrapped_point.row),
7204 cx,
7205 );
7206 }
7207 }
7208
7209 fn seek_in_direction(
7210 &mut self,
7211 snapshot: &DisplaySnapshot,
7212 initial_point: Point,
7213 is_wrapped: bool,
7214 hunks: impl Iterator<Item = DiffHunk<u32>>,
7215 cx: &mut ViewContext<Editor>,
7216 ) -> bool {
7217 let display_point = initial_point.to_display_point(snapshot);
7218 let mut hunks = hunks
7219 .map(|hunk| diff_hunk_to_display(hunk, &snapshot))
7220 .filter(|hunk| {
7221 if is_wrapped {
7222 true
7223 } else {
7224 !hunk.contains_display_row(display_point.row())
7225 }
7226 })
7227 .dedup();
7228
7229 if let Some(hunk) = hunks.next() {
7230 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7231 let row = hunk.start_display_row();
7232 let point = DisplayPoint::new(row, 0);
7233 s.select_display_ranges([point..point]);
7234 });
7235
7236 true
7237 } else {
7238 false
7239 }
7240 }
7241
7242 pub fn go_to_definition(&mut self, _: &GoToDefinition, cx: &mut ViewContext<Self>) {
7243 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, cx);
7244 }
7245
7246 pub fn go_to_type_definition(&mut self, _: &GoToTypeDefinition, cx: &mut ViewContext<Self>) {
7247 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, cx);
7248 }
7249
7250 pub fn go_to_definition_split(&mut self, _: &GoToDefinitionSplit, cx: &mut ViewContext<Self>) {
7251 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, cx);
7252 }
7253
7254 pub fn go_to_type_definition_split(
7255 &mut self,
7256 _: &GoToTypeDefinitionSplit,
7257 cx: &mut ViewContext<Self>,
7258 ) {
7259 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, cx);
7260 }
7261
7262 fn go_to_definition_of_kind(
7263 &mut self,
7264 kind: GotoDefinitionKind,
7265 split: bool,
7266 cx: &mut ViewContext<Self>,
7267 ) {
7268 let Some(workspace) = self.workspace() else {
7269 return;
7270 };
7271 let buffer = self.buffer.read(cx);
7272 let head = self.selections.newest::<usize>(cx).head();
7273 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
7274 text_anchor
7275 } else {
7276 return;
7277 };
7278
7279 let project = workspace.read(cx).project().clone();
7280 let definitions = project.update(cx, |project, cx| match kind {
7281 GotoDefinitionKind::Symbol => project.definition(&buffer, head, cx),
7282 GotoDefinitionKind::Type => project.type_definition(&buffer, head, cx),
7283 });
7284
7285 cx.spawn(|editor, mut cx| async move {
7286 let definitions = definitions.await?;
7287 editor.update(&mut cx, |editor, cx| {
7288 editor.navigate_to_definitions(
7289 definitions
7290 .into_iter()
7291 .map(GoToDefinitionLink::Text)
7292 .collect(),
7293 split,
7294 cx,
7295 );
7296 })?;
7297 Ok::<(), anyhow::Error>(())
7298 })
7299 .detach_and_log_err(cx);
7300 }
7301
7302 pub fn navigate_to_definitions(
7303 &mut self,
7304 mut definitions: Vec<GoToDefinitionLink>,
7305 split: bool,
7306 cx: &mut ViewContext<Editor>,
7307 ) {
7308 let Some(workspace) = self.workspace() else {
7309 return;
7310 };
7311 let pane = workspace.read(cx).active_pane().clone();
7312 // If there is one definition, just open it directly
7313 if definitions.len() == 1 {
7314 let definition = definitions.pop().unwrap();
7315 let target_task = match definition {
7316 GoToDefinitionLink::Text(link) => Task::Ready(Some(Ok(Some(link.target)))),
7317 GoToDefinitionLink::InlayHint(lsp_location, server_id) => {
7318 self.compute_target_location(lsp_location, server_id, cx)
7319 }
7320 };
7321 cx.spawn(|editor, mut cx| async move {
7322 let target = target_task.await.context("target resolution task")?;
7323 if let Some(target) = target {
7324 editor.update(&mut cx, |editor, cx| {
7325 let range = target.range.to_offset(target.buffer.read(cx));
7326 let range = editor.range_for_match(&range);
7327 if Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
7328 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
7329 s.select_ranges([range]);
7330 });
7331 } else {
7332 cx.window_context().defer(move |cx| {
7333 let target_editor: View<Self> =
7334 workspace.update(cx, |workspace, cx| {
7335 if split {
7336 workspace.split_project_item(target.buffer.clone(), cx)
7337 } else {
7338 workspace.open_project_item(target.buffer.clone(), cx)
7339 }
7340 });
7341 target_editor.update(cx, |target_editor, cx| {
7342 // When selecting a definition in a different buffer, disable the nav history
7343 // to avoid creating a history entry at the previous cursor location.
7344 pane.update(cx, |pane, _| pane.disable_history());
7345 target_editor.change_selections(
7346 Some(Autoscroll::fit()),
7347 cx,
7348 |s| {
7349 s.select_ranges([range]);
7350 },
7351 );
7352 pane.update(cx, |pane, _| pane.enable_history());
7353 });
7354 });
7355 }
7356 })
7357 } else {
7358 Ok(())
7359 }
7360 })
7361 .detach_and_log_err(cx);
7362 } else if !definitions.is_empty() {
7363 let replica_id = self.replica_id(cx);
7364 cx.spawn(|editor, mut cx| async move {
7365 let (title, location_tasks) = editor
7366 .update(&mut cx, |editor, cx| {
7367 let title = definitions
7368 .iter()
7369 .find_map(|definition| match definition {
7370 GoToDefinitionLink::Text(link) => {
7371 link.origin.as_ref().map(|origin| {
7372 let buffer = origin.buffer.read(cx);
7373 format!(
7374 "Definitions for {}",
7375 buffer
7376 .text_for_range(origin.range.clone())
7377 .collect::<String>()
7378 )
7379 })
7380 }
7381 GoToDefinitionLink::InlayHint(_, _) => None,
7382 })
7383 .unwrap_or("Definitions".to_string());
7384 let location_tasks = definitions
7385 .into_iter()
7386 .map(|definition| match definition {
7387 GoToDefinitionLink::Text(link) => {
7388 Task::Ready(Some(Ok(Some(link.target))))
7389 }
7390 GoToDefinitionLink::InlayHint(lsp_location, server_id) => {
7391 editor.compute_target_location(lsp_location, server_id, cx)
7392 }
7393 })
7394 .collect::<Vec<_>>();
7395 (title, location_tasks)
7396 })
7397 .context("location tasks preparation")?;
7398
7399 let locations = futures::future::join_all(location_tasks)
7400 .await
7401 .into_iter()
7402 .filter_map(|location| location.transpose())
7403 .collect::<Result<_>>()
7404 .context("location tasks")?;
7405 workspace
7406 .update(&mut cx, |workspace, cx| {
7407 Self::open_locations_in_multibuffer(
7408 workspace, locations, replica_id, title, split, cx,
7409 )
7410 })
7411 .ok();
7412
7413 anyhow::Ok(())
7414 })
7415 .detach_and_log_err(cx);
7416 }
7417 }
7418
7419 fn compute_target_location(
7420 &self,
7421 lsp_location: lsp::Location,
7422 server_id: LanguageServerId,
7423 cx: &mut ViewContext<Editor>,
7424 ) -> Task<anyhow::Result<Option<Location>>> {
7425 let Some(project) = self.project.clone() else {
7426 return Task::Ready(Some(Ok(None)));
7427 };
7428
7429 cx.spawn(move |editor, mut cx| async move {
7430 let location_task = editor.update(&mut cx, |editor, cx| {
7431 project.update(cx, |project, cx| {
7432 let language_server_name =
7433 editor.buffer.read(cx).as_singleton().and_then(|buffer| {
7434 project
7435 .language_server_for_buffer(buffer.read(cx), server_id, cx)
7436 .map(|(_, lsp_adapter)| {
7437 LanguageServerName(Arc::from(lsp_adapter.name()))
7438 })
7439 });
7440 language_server_name.map(|language_server_name| {
7441 project.open_local_buffer_via_lsp(
7442 lsp_location.uri.clone(),
7443 server_id,
7444 language_server_name,
7445 cx,
7446 )
7447 })
7448 })
7449 })?;
7450 let location = match location_task {
7451 Some(task) => Some({
7452 let target_buffer_handle = task.await.context("open local buffer")?;
7453 let range = target_buffer_handle.update(&mut cx, |target_buffer, _| {
7454 let target_start = target_buffer
7455 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
7456 let target_end = target_buffer
7457 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
7458 target_buffer.anchor_after(target_start)
7459 ..target_buffer.anchor_before(target_end)
7460 })?;
7461 Location {
7462 buffer: target_buffer_handle,
7463 range,
7464 }
7465 }),
7466 None => None,
7467 };
7468 Ok(location)
7469 })
7470 }
7471
7472 pub fn find_all_references(
7473 &mut self,
7474 _: &FindAllReferences,
7475 cx: &mut ViewContext<Self>,
7476 ) -> Option<Task<Result<()>>> {
7477 let buffer = self.buffer.read(cx);
7478 let head = self.selections.newest::<usize>(cx).head();
7479 let (buffer, head) = buffer.text_anchor_for_position(head, cx)?;
7480 let replica_id = self.replica_id(cx);
7481
7482 let workspace = self.workspace()?;
7483 let project = workspace.read(cx).project().clone();
7484 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
7485 Some(cx.spawn(|_, mut cx| async move {
7486 let locations = references.await?;
7487 if locations.is_empty() {
7488 return Ok(());
7489 }
7490
7491 workspace.update(&mut cx, |workspace, cx| {
7492 let title = locations
7493 .first()
7494 .as_ref()
7495 .map(|location| {
7496 let buffer = location.buffer.read(cx);
7497 format!(
7498 "References to `{}`",
7499 buffer
7500 .text_for_range(location.range.clone())
7501 .collect::<String>()
7502 )
7503 })
7504 .unwrap();
7505 Self::open_locations_in_multibuffer(
7506 workspace, locations, replica_id, title, false, cx,
7507 );
7508 })?;
7509
7510 Ok(())
7511 }))
7512 }
7513
7514 /// Opens a multibuffer with the given project locations in it
7515 pub fn open_locations_in_multibuffer(
7516 workspace: &mut Workspace,
7517 mut locations: Vec<Location>,
7518 replica_id: ReplicaId,
7519 title: String,
7520 split: bool,
7521 cx: &mut ViewContext<Workspace>,
7522 ) {
7523 // If there are multiple definitions, open them in a multibuffer
7524 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
7525 let mut locations = locations.into_iter().peekable();
7526 let mut ranges_to_highlight = Vec::new();
7527 let capability = workspace.project().read(cx).capability();
7528
7529 let excerpt_buffer = cx.new_model(|cx| {
7530 let mut multibuffer = MultiBuffer::new(replica_id, capability);
7531 while let Some(location) = locations.next() {
7532 let buffer = location.buffer.read(cx);
7533 let mut ranges_for_buffer = Vec::new();
7534 let range = location.range.to_offset(buffer);
7535 ranges_for_buffer.push(range.clone());
7536
7537 while let Some(next_location) = locations.peek() {
7538 if next_location.buffer == location.buffer {
7539 ranges_for_buffer.push(next_location.range.to_offset(buffer));
7540 locations.next();
7541 } else {
7542 break;
7543 }
7544 }
7545
7546 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
7547 ranges_to_highlight.extend(multibuffer.push_excerpts_with_context_lines(
7548 location.buffer.clone(),
7549 ranges_for_buffer,
7550 1,
7551 cx,
7552 ))
7553 }
7554
7555 multibuffer.with_title(title)
7556 });
7557
7558 let editor = cx.new_view(|cx| {
7559 Editor::for_multibuffer(excerpt_buffer, Some(workspace.project().clone()), cx)
7560 });
7561 editor.update(cx, |editor, cx| {
7562 editor.highlight_background::<Self>(
7563 ranges_to_highlight,
7564 |theme| theme.editor_highlighted_line_background,
7565 cx,
7566 );
7567 });
7568 if split {
7569 workspace.split_item(SplitDirection::Right, Box::new(editor), cx);
7570 } else {
7571 workspace.add_item(Box::new(editor), cx);
7572 }
7573 }
7574
7575 pub fn rename(&mut self, _: &Rename, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
7576 use language::ToOffset as _;
7577
7578 let project = self.project.clone()?;
7579 let selection = self.selections.newest_anchor().clone();
7580 let (cursor_buffer, cursor_buffer_position) = self
7581 .buffer
7582 .read(cx)
7583 .text_anchor_for_position(selection.head(), cx)?;
7584 let (tail_buffer, _) = self
7585 .buffer
7586 .read(cx)
7587 .text_anchor_for_position(selection.tail(), cx)?;
7588 if tail_buffer != cursor_buffer {
7589 return None;
7590 }
7591
7592 let snapshot = cursor_buffer.read(cx).snapshot();
7593 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
7594 let prepare_rename = project.update(cx, |project, cx| {
7595 project.prepare_rename(cursor_buffer, cursor_buffer_offset, cx)
7596 });
7597
7598 Some(cx.spawn(|this, mut cx| async move {
7599 let rename_range = if let Some(range) = prepare_rename.await? {
7600 Some(range)
7601 } else {
7602 this.update(&mut cx, |this, cx| {
7603 let buffer = this.buffer.read(cx).snapshot(cx);
7604 let mut buffer_highlights = this
7605 .document_highlights_for_position(selection.head(), &buffer)
7606 .filter(|highlight| {
7607 highlight.start.excerpt_id == selection.head().excerpt_id
7608 && highlight.end.excerpt_id == selection.head().excerpt_id
7609 });
7610 buffer_highlights
7611 .next()
7612 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
7613 })?
7614 };
7615 if let Some(rename_range) = rename_range {
7616 let rename_buffer_range = rename_range.to_offset(&snapshot);
7617 let cursor_offset_in_rename_range =
7618 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
7619
7620 this.update(&mut cx, |this, cx| {
7621 this.take_rename(false, cx);
7622 let buffer = this.buffer.read(cx).read(cx);
7623 let cursor_offset = selection.head().to_offset(&buffer);
7624 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
7625 let rename_end = rename_start + rename_buffer_range.len();
7626 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
7627 let mut old_highlight_id = None;
7628 let old_name: Arc<str> = buffer
7629 .chunks(rename_start..rename_end, true)
7630 .map(|chunk| {
7631 if old_highlight_id.is_none() {
7632 old_highlight_id = chunk.syntax_highlight_id;
7633 }
7634 chunk.text
7635 })
7636 .collect::<String>()
7637 .into();
7638
7639 drop(buffer);
7640
7641 // Position the selection in the rename editor so that it matches the current selection.
7642 this.show_local_selections = false;
7643 let rename_editor = cx.new_view(|cx| {
7644 let mut editor = Editor::single_line(cx);
7645 editor.buffer.update(cx, |buffer, cx| {
7646 buffer.edit([(0..0, old_name.clone())], None, cx)
7647 });
7648 editor.select_all(&SelectAll, cx);
7649 editor
7650 });
7651
7652 let ranges = this
7653 .clear_background_highlights::<DocumentHighlightWrite>(cx)
7654 .into_iter()
7655 .flat_map(|(_, ranges)| ranges.into_iter())
7656 .chain(
7657 this.clear_background_highlights::<DocumentHighlightRead>(cx)
7658 .into_iter()
7659 .flat_map(|(_, ranges)| ranges.into_iter()),
7660 )
7661 .collect();
7662
7663 this.highlight_text::<Rename>(
7664 ranges,
7665 HighlightStyle {
7666 fade_out: Some(0.6),
7667 ..Default::default()
7668 },
7669 cx,
7670 );
7671 let rename_focus_handle = rename_editor.focus_handle(cx);
7672 cx.focus(&rename_focus_handle);
7673 let block_id = this.insert_blocks(
7674 [BlockProperties {
7675 style: BlockStyle::Flex,
7676 position: range.start.clone(),
7677 height: 1,
7678 render: Arc::new({
7679 let rename_editor = rename_editor.clone();
7680 move |cx: &mut BlockContext| {
7681 let mut text_style = cx.editor_style.text.clone();
7682 if let Some(highlight_style) = old_highlight_id
7683 .and_then(|h| h.style(&cx.editor_style.syntax))
7684 {
7685 text_style = text_style.highlight(highlight_style);
7686 }
7687 div()
7688 .pl(cx.anchor_x)
7689 .child(EditorElement::new(
7690 &rename_editor,
7691 EditorStyle {
7692 background: cx.theme().system().transparent,
7693 local_player: cx.editor_style.local_player,
7694 text: text_style,
7695 scrollbar_width: cx.editor_style.scrollbar_width,
7696 syntax: cx.editor_style.syntax.clone(),
7697 status: cx.editor_style.status.clone(),
7698 inlays_style: HighlightStyle {
7699 color: Some(cx.theme().status().hint),
7700 font_weight: Some(FontWeight::BOLD),
7701 ..HighlightStyle::default()
7702 },
7703 suggestions_style: HighlightStyle {
7704 color: Some(cx.theme().status().predictive),
7705 ..HighlightStyle::default()
7706 },
7707 },
7708 ))
7709 .into_any_element()
7710 }
7711 }),
7712 disposition: BlockDisposition::Below,
7713 }],
7714 Some(Autoscroll::fit()),
7715 cx,
7716 )[0];
7717 this.pending_rename = Some(RenameState {
7718 range,
7719 old_name,
7720 editor: rename_editor,
7721 block_id,
7722 });
7723 })?;
7724 }
7725
7726 Ok(())
7727 }))
7728 }
7729
7730 pub fn confirm_rename(
7731 &mut self,
7732 _: &ConfirmRename,
7733 cx: &mut ViewContext<Self>,
7734 ) -> Option<Task<Result<()>>> {
7735 let rename = self.take_rename(false, cx)?;
7736 let workspace = self.workspace()?;
7737 let (start_buffer, start) = self
7738 .buffer
7739 .read(cx)
7740 .text_anchor_for_position(rename.range.start.clone(), cx)?;
7741 let (end_buffer, end) = self
7742 .buffer
7743 .read(cx)
7744 .text_anchor_for_position(rename.range.end.clone(), cx)?;
7745 if start_buffer != end_buffer {
7746 return None;
7747 }
7748
7749 let buffer = start_buffer;
7750 let range = start..end;
7751 let old_name = rename.old_name;
7752 let new_name = rename.editor.read(cx).text(cx);
7753
7754 let rename = workspace
7755 .read(cx)
7756 .project()
7757 .clone()
7758 .update(cx, |project, cx| {
7759 project.perform_rename(buffer.clone(), range.start, new_name.clone(), true, cx)
7760 });
7761 let workspace = workspace.downgrade();
7762
7763 Some(cx.spawn(|editor, mut cx| async move {
7764 let project_transaction = rename.await?;
7765 Self::open_project_transaction(
7766 &editor,
7767 workspace,
7768 project_transaction,
7769 format!("Rename: {} → {}", old_name, new_name),
7770 cx.clone(),
7771 )
7772 .await?;
7773
7774 editor.update(&mut cx, |editor, cx| {
7775 editor.refresh_document_highlights(cx);
7776 })?;
7777 Ok(())
7778 }))
7779 }
7780
7781 fn take_rename(
7782 &mut self,
7783 moving_cursor: bool,
7784 cx: &mut ViewContext<Self>,
7785 ) -> Option<RenameState> {
7786 let rename = self.pending_rename.take()?;
7787 if rename.editor.focus_handle(cx).is_focused(cx) {
7788 cx.focus(&self.focus_handle);
7789 }
7790
7791 self.remove_blocks(
7792 [rename.block_id].into_iter().collect(),
7793 Some(Autoscroll::fit()),
7794 cx,
7795 );
7796 self.clear_highlights::<Rename>(cx);
7797 self.show_local_selections = true;
7798
7799 if moving_cursor {
7800 let rename_editor = rename.editor.read(cx);
7801 let cursor_in_rename_editor = rename_editor.selections.newest::<usize>(cx).head();
7802
7803 // Update the selection to match the position of the selection inside
7804 // the rename editor.
7805 let snapshot = self.buffer.read(cx).read(cx);
7806 let rename_range = rename.range.to_offset(&snapshot);
7807 let cursor_in_editor = snapshot
7808 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
7809 .min(rename_range.end);
7810 drop(snapshot);
7811
7812 self.change_selections(None, cx, |s| {
7813 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
7814 });
7815 } else {
7816 self.refresh_document_highlights(cx);
7817 }
7818
7819 Some(rename)
7820 }
7821
7822 #[cfg(any(test, feature = "test-support"))]
7823 pub fn pending_rename(&self) -> Option<&RenameState> {
7824 self.pending_rename.as_ref()
7825 }
7826
7827 fn format(&mut self, _: &Format, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
7828 let project = match &self.project {
7829 Some(project) => project.clone(),
7830 None => return None,
7831 };
7832
7833 Some(self.perform_format(project, FormatTrigger::Manual, cx))
7834 }
7835
7836 fn perform_format(
7837 &mut self,
7838 project: Model<Project>,
7839 trigger: FormatTrigger,
7840 cx: &mut ViewContext<Self>,
7841 ) -> Task<Result<()>> {
7842 let buffer = self.buffer().clone();
7843 let buffers = buffer.read(cx).all_buffers();
7844
7845 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
7846 let format = project.update(cx, |project, cx| project.format(buffers, true, trigger, cx));
7847
7848 cx.spawn(|_, mut cx| async move {
7849 let transaction = futures::select_biased! {
7850 _ = timeout => {
7851 log::warn!("timed out waiting for formatting");
7852 None
7853 }
7854 transaction = format.log_err().fuse() => transaction,
7855 };
7856
7857 buffer
7858 .update(&mut cx, |buffer, cx| {
7859 if let Some(transaction) = transaction {
7860 if !buffer.is_singleton() {
7861 buffer.push_transaction(&transaction.0, cx);
7862 }
7863 }
7864
7865 cx.notify();
7866 })
7867 .ok();
7868
7869 Ok(())
7870 })
7871 }
7872
7873 fn restart_language_server(&mut self, _: &RestartLanguageServer, cx: &mut ViewContext<Self>) {
7874 if let Some(project) = self.project.clone() {
7875 self.buffer.update(cx, |multi_buffer, cx| {
7876 project.update(cx, |project, cx| {
7877 project.restart_language_servers_for_buffers(multi_buffer.all_buffers(), cx);
7878 });
7879 })
7880 }
7881 }
7882
7883 fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) {
7884 cx.show_character_palette();
7885 }
7886
7887 fn refresh_active_diagnostics(&mut self, cx: &mut ViewContext<Editor>) {
7888 if let Some(active_diagnostics) = self.active_diagnostics.as_mut() {
7889 let buffer = self.buffer.read(cx).snapshot(cx);
7890 let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer);
7891 let is_valid = buffer
7892 .diagnostics_in_range::<_, usize>(active_diagnostics.primary_range.clone(), false)
7893 .any(|entry| {
7894 entry.diagnostic.is_primary
7895 && !entry.range.is_empty()
7896 && entry.range.start == primary_range_start
7897 && entry.diagnostic.message == active_diagnostics.primary_message
7898 });
7899
7900 if is_valid != active_diagnostics.is_valid {
7901 active_diagnostics.is_valid = is_valid;
7902 let mut new_styles = HashMap::default();
7903 for (block_id, diagnostic) in &active_diagnostics.blocks {
7904 new_styles.insert(
7905 *block_id,
7906 diagnostic_block_renderer(diagnostic.clone(), is_valid),
7907 );
7908 }
7909 self.display_map
7910 .update(cx, |display_map, _| display_map.replace_blocks(new_styles));
7911 }
7912 }
7913 }
7914
7915 fn activate_diagnostics(&mut self, group_id: usize, cx: &mut ViewContext<Self>) -> bool {
7916 self.dismiss_diagnostics(cx);
7917 self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
7918 let buffer = self.buffer.read(cx).snapshot(cx);
7919
7920 let mut primary_range = None;
7921 let mut primary_message = None;
7922 let mut group_end = Point::zero();
7923 let diagnostic_group = buffer
7924 .diagnostic_group::<Point>(group_id)
7925 .map(|entry| {
7926 if entry.range.end > group_end {
7927 group_end = entry.range.end;
7928 }
7929 if entry.diagnostic.is_primary {
7930 primary_range = Some(entry.range.clone());
7931 primary_message = Some(entry.diagnostic.message.clone());
7932 }
7933 entry
7934 })
7935 .collect::<Vec<_>>();
7936 let primary_range = primary_range?;
7937 let primary_message = primary_message?;
7938 let primary_range =
7939 buffer.anchor_after(primary_range.start)..buffer.anchor_before(primary_range.end);
7940
7941 let blocks = display_map
7942 .insert_blocks(
7943 diagnostic_group.iter().map(|entry| {
7944 let diagnostic = entry.diagnostic.clone();
7945 let message_height = diagnostic.message.lines().count() as u8;
7946 BlockProperties {
7947 style: BlockStyle::Fixed,
7948 position: buffer.anchor_after(entry.range.start),
7949 height: message_height,
7950 render: diagnostic_block_renderer(diagnostic, true),
7951 disposition: BlockDisposition::Below,
7952 }
7953 }),
7954 cx,
7955 )
7956 .into_iter()
7957 .zip(diagnostic_group.into_iter().map(|entry| entry.diagnostic))
7958 .collect();
7959
7960 Some(ActiveDiagnosticGroup {
7961 primary_range,
7962 primary_message,
7963 blocks,
7964 is_valid: true,
7965 })
7966 });
7967 self.active_diagnostics.is_some()
7968 }
7969
7970 fn dismiss_diagnostics(&mut self, cx: &mut ViewContext<Self>) {
7971 if let Some(active_diagnostic_group) = self.active_diagnostics.take() {
7972 self.display_map.update(cx, |display_map, cx| {
7973 display_map.remove_blocks(active_diagnostic_group.blocks.into_keys().collect(), cx);
7974 });
7975 cx.notify();
7976 }
7977 }
7978
7979 pub fn set_selections_from_remote(
7980 &mut self,
7981 selections: Vec<Selection<Anchor>>,
7982 pending_selection: Option<Selection<Anchor>>,
7983 cx: &mut ViewContext<Self>,
7984 ) {
7985 let old_cursor_position = self.selections.newest_anchor().head();
7986 self.selections.change_with(cx, |s| {
7987 s.select_anchors(selections);
7988 if let Some(pending_selection) = pending_selection {
7989 s.set_pending(pending_selection, SelectMode::Character);
7990 } else {
7991 s.clear_pending();
7992 }
7993 });
7994 self.selections_did_change(false, &old_cursor_position, cx);
7995 }
7996
7997 fn push_to_selection_history(&mut self) {
7998 self.selection_history.push(SelectionHistoryEntry {
7999 selections: self.selections.disjoint_anchors(),
8000 select_next_state: self.select_next_state.clone(),
8001 select_prev_state: self.select_prev_state.clone(),
8002 add_selections_state: self.add_selections_state.clone(),
8003 });
8004 }
8005
8006 pub fn transact(
8007 &mut self,
8008 cx: &mut ViewContext<Self>,
8009 update: impl FnOnce(&mut Self, &mut ViewContext<Self>),
8010 ) -> Option<TransactionId> {
8011 self.start_transaction_at(Instant::now(), cx);
8012 update(self, cx);
8013 self.end_transaction_at(Instant::now(), cx)
8014 }
8015
8016 fn start_transaction_at(&mut self, now: Instant, cx: &mut ViewContext<Self>) {
8017 self.end_selection(cx);
8018 if let Some(tx_id) = self
8019 .buffer
8020 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
8021 {
8022 self.selection_history
8023 .insert_transaction(tx_id, self.selections.disjoint_anchors());
8024 }
8025 }
8026
8027 fn end_transaction_at(
8028 &mut self,
8029 now: Instant,
8030 cx: &mut ViewContext<Self>,
8031 ) -> Option<TransactionId> {
8032 if let Some(tx_id) = self
8033 .buffer
8034 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
8035 {
8036 if let Some((_, end_selections)) = self.selection_history.transaction_mut(tx_id) {
8037 *end_selections = Some(self.selections.disjoint_anchors());
8038 } else {
8039 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
8040 }
8041
8042 cx.emit(EditorEvent::Edited);
8043 Some(tx_id)
8044 } else {
8045 None
8046 }
8047 }
8048
8049 pub fn fold(&mut self, _: &Fold, cx: &mut ViewContext<Self>) {
8050 let mut fold_ranges = Vec::new();
8051
8052 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8053
8054 let selections = self.selections.all_adjusted(cx);
8055 for selection in selections {
8056 let range = selection.range().sorted();
8057 let buffer_start_row = range.start.row;
8058
8059 for row in (0..=range.end.row).rev() {
8060 let fold_range = display_map.foldable_range(row);
8061
8062 if let Some(fold_range) = fold_range {
8063 if fold_range.end.row >= buffer_start_row {
8064 fold_ranges.push(fold_range);
8065 if row <= range.start.row {
8066 break;
8067 }
8068 }
8069 }
8070 }
8071 }
8072
8073 self.fold_ranges(fold_ranges, true, cx);
8074 }
8075
8076 pub fn fold_at(&mut self, fold_at: &FoldAt, cx: &mut ViewContext<Self>) {
8077 let buffer_row = fold_at.buffer_row;
8078 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8079
8080 if let Some(fold_range) = display_map.foldable_range(buffer_row) {
8081 let autoscroll = self
8082 .selections
8083 .all::<Point>(cx)
8084 .iter()
8085 .any(|selection| fold_range.overlaps(&selection.range()));
8086
8087 self.fold_ranges(std::iter::once(fold_range), autoscroll, cx);
8088 }
8089 }
8090
8091 pub fn unfold_lines(&mut self, _: &UnfoldLines, cx: &mut ViewContext<Self>) {
8092 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8093 let buffer = &display_map.buffer_snapshot;
8094 let selections = self.selections.all::<Point>(cx);
8095 let ranges = selections
8096 .iter()
8097 .map(|s| {
8098 let range = s.display_range(&display_map).sorted();
8099 let mut start = range.start.to_point(&display_map);
8100 let mut end = range.end.to_point(&display_map);
8101 start.column = 0;
8102 end.column = buffer.line_len(end.row);
8103 start..end
8104 })
8105 .collect::<Vec<_>>();
8106
8107 self.unfold_ranges(ranges, true, true, cx);
8108 }
8109
8110 pub fn unfold_at(&mut self, unfold_at: &UnfoldAt, cx: &mut ViewContext<Self>) {
8111 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8112
8113 let intersection_range = Point::new(unfold_at.buffer_row, 0)
8114 ..Point::new(
8115 unfold_at.buffer_row,
8116 display_map.buffer_snapshot.line_len(unfold_at.buffer_row),
8117 );
8118
8119 let autoscroll = self
8120 .selections
8121 .all::<Point>(cx)
8122 .iter()
8123 .any(|selection| selection.range().overlaps(&intersection_range));
8124
8125 self.unfold_ranges(std::iter::once(intersection_range), true, autoscroll, cx)
8126 }
8127
8128 pub fn fold_selected_ranges(&mut self, _: &FoldSelectedRanges, cx: &mut ViewContext<Self>) {
8129 let selections = self.selections.all::<Point>(cx);
8130 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8131 let line_mode = self.selections.line_mode;
8132 let ranges = selections.into_iter().map(|s| {
8133 if line_mode {
8134 let start = Point::new(s.start.row, 0);
8135 let end = Point::new(s.end.row, display_map.buffer_snapshot.line_len(s.end.row));
8136 start..end
8137 } else {
8138 s.start..s.end
8139 }
8140 });
8141 self.fold_ranges(ranges, true, cx);
8142 }
8143
8144 pub fn fold_ranges<T: ToOffset + Clone>(
8145 &mut self,
8146 ranges: impl IntoIterator<Item = Range<T>>,
8147 auto_scroll: bool,
8148 cx: &mut ViewContext<Self>,
8149 ) {
8150 let mut ranges = ranges.into_iter().peekable();
8151 if ranges.peek().is_some() {
8152 self.display_map.update(cx, |map, cx| map.fold(ranges, cx));
8153
8154 if auto_scroll {
8155 self.request_autoscroll(Autoscroll::fit(), cx);
8156 }
8157
8158 cx.notify();
8159 }
8160 }
8161
8162 pub fn unfold_ranges<T: ToOffset + Clone>(
8163 &mut self,
8164 ranges: impl IntoIterator<Item = Range<T>>,
8165 inclusive: bool,
8166 auto_scroll: bool,
8167 cx: &mut ViewContext<Self>,
8168 ) {
8169 let mut ranges = ranges.into_iter().peekable();
8170 if ranges.peek().is_some() {
8171 self.display_map
8172 .update(cx, |map, cx| map.unfold(ranges, inclusive, cx));
8173 if auto_scroll {
8174 self.request_autoscroll(Autoscroll::fit(), cx);
8175 }
8176
8177 cx.notify();
8178 }
8179 }
8180
8181 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut ViewContext<Self>) {
8182 if hovered != self.gutter_hovered {
8183 self.gutter_hovered = hovered;
8184 cx.notify();
8185 }
8186 }
8187
8188 pub fn insert_blocks(
8189 &mut self,
8190 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
8191 autoscroll: Option<Autoscroll>,
8192 cx: &mut ViewContext<Self>,
8193 ) -> Vec<BlockId> {
8194 let blocks = self
8195 .display_map
8196 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
8197 if let Some(autoscroll) = autoscroll {
8198 self.request_autoscroll(autoscroll, cx);
8199 }
8200 blocks
8201 }
8202
8203 pub fn replace_blocks(
8204 &mut self,
8205 blocks: HashMap<BlockId, RenderBlock>,
8206 autoscroll: Option<Autoscroll>,
8207 cx: &mut ViewContext<Self>,
8208 ) {
8209 self.display_map
8210 .update(cx, |display_map, _| display_map.replace_blocks(blocks));
8211 if let Some(autoscroll) = autoscroll {
8212 self.request_autoscroll(autoscroll, cx);
8213 }
8214 }
8215
8216 pub fn remove_blocks(
8217 &mut self,
8218 block_ids: HashSet<BlockId>,
8219 autoscroll: Option<Autoscroll>,
8220 cx: &mut ViewContext<Self>,
8221 ) {
8222 self.display_map.update(cx, |display_map, cx| {
8223 display_map.remove_blocks(block_ids, cx)
8224 });
8225 if let Some(autoscroll) = autoscroll {
8226 self.request_autoscroll(autoscroll, cx);
8227 }
8228 }
8229
8230 pub fn longest_row(&self, cx: &mut AppContext) -> u32 {
8231 self.display_map
8232 .update(cx, |map, cx| map.snapshot(cx))
8233 .longest_row()
8234 }
8235
8236 pub fn max_point(&self, cx: &mut AppContext) -> DisplayPoint {
8237 self.display_map
8238 .update(cx, |map, cx| map.snapshot(cx))
8239 .max_point()
8240 }
8241
8242 pub fn text(&self, cx: &AppContext) -> String {
8243 self.buffer.read(cx).read(cx).text()
8244 }
8245
8246 pub fn text_option(&self, cx: &AppContext) -> Option<String> {
8247 let text = self.text(cx);
8248 let text = text.trim();
8249
8250 if text.is_empty() {
8251 return None;
8252 }
8253
8254 Some(text.to_string())
8255 }
8256
8257 pub fn set_text(&mut self, text: impl Into<Arc<str>>, cx: &mut ViewContext<Self>) {
8258 self.transact(cx, |this, cx| {
8259 this.buffer
8260 .read(cx)
8261 .as_singleton()
8262 .expect("you can only call set_text on editors for singleton buffers")
8263 .update(cx, |buffer, cx| buffer.set_text(text, cx));
8264 });
8265 }
8266
8267 pub fn display_text(&self, cx: &mut AppContext) -> String {
8268 self.display_map
8269 .update(cx, |map, cx| map.snapshot(cx))
8270 .text()
8271 }
8272
8273 pub fn wrap_guides(&self, cx: &AppContext) -> SmallVec<[(usize, bool); 2]> {
8274 let mut wrap_guides = smallvec::smallvec![];
8275
8276 if self.show_wrap_guides == Some(false) {
8277 return wrap_guides;
8278 }
8279
8280 let settings = self.buffer.read(cx).settings_at(0, cx);
8281 if settings.show_wrap_guides {
8282 if let SoftWrap::Column(soft_wrap) = self.soft_wrap_mode(cx) {
8283 wrap_guides.push((soft_wrap as usize, true));
8284 }
8285 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
8286 }
8287
8288 wrap_guides
8289 }
8290
8291 pub fn soft_wrap_mode(&self, cx: &AppContext) -> SoftWrap {
8292 let settings = self.buffer.read(cx).settings_at(0, cx);
8293 let mode = self
8294 .soft_wrap_mode_override
8295 .unwrap_or_else(|| settings.soft_wrap);
8296 match mode {
8297 language_settings::SoftWrap::None => SoftWrap::None,
8298 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
8299 language_settings::SoftWrap::PreferredLineLength => {
8300 SoftWrap::Column(settings.preferred_line_length)
8301 }
8302 }
8303 }
8304
8305 pub fn set_soft_wrap_mode(
8306 &mut self,
8307 mode: language_settings::SoftWrap,
8308 cx: &mut ViewContext<Self>,
8309 ) {
8310 self.soft_wrap_mode_override = Some(mode);
8311 cx.notify();
8312 }
8313
8314 pub fn set_style(&mut self, style: EditorStyle, cx: &mut ViewContext<Self>) {
8315 let rem_size = cx.rem_size();
8316 self.display_map.update(cx, |map, cx| {
8317 map.set_font(
8318 style.text.font(),
8319 style.text.font_size.to_pixels(rem_size),
8320 cx,
8321 )
8322 });
8323 self.style = Some(style);
8324 }
8325
8326 #[cfg(any(test, feature = "test-support"))]
8327 pub fn style(&self) -> Option<&EditorStyle> {
8328 self.style.as_ref()
8329 }
8330
8331 // Called by the element. This method is not designed to be called outside of the editor
8332 // element's layout code because it does not notify when rewrapping is computed synchronously.
8333 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut AppContext) -> bool {
8334 self.display_map
8335 .update(cx, |map, cx| map.set_wrap_width(width, cx))
8336 }
8337
8338 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, cx: &mut ViewContext<Self>) {
8339 if self.soft_wrap_mode_override.is_some() {
8340 self.soft_wrap_mode_override.take();
8341 } else {
8342 let soft_wrap = match self.soft_wrap_mode(cx) {
8343 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
8344 SoftWrap::EditorWidth | SoftWrap::Column(_) => language_settings::SoftWrap::None,
8345 };
8346 self.soft_wrap_mode_override = Some(soft_wrap);
8347 }
8348 cx.notify();
8349 }
8350
8351 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut ViewContext<Self>) {
8352 self.show_gutter = show_gutter;
8353 cx.notify();
8354 }
8355
8356 pub fn set_show_wrap_guides(&mut self, show_gutter: bool, cx: &mut ViewContext<Self>) {
8357 self.show_wrap_guides = Some(show_gutter);
8358 cx.notify();
8359 }
8360
8361 pub fn reveal_in_finder(&mut self, _: &RevealInFinder, cx: &mut ViewContext<Self>) {
8362 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
8363 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
8364 cx.reveal_path(&file.abs_path(cx));
8365 }
8366 }
8367 }
8368
8369 pub fn copy_path(&mut self, _: &CopyPath, cx: &mut ViewContext<Self>) {
8370 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
8371 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
8372 if let Some(path) = file.abs_path(cx).to_str() {
8373 cx.write_to_clipboard(ClipboardItem::new(path.to_string()));
8374 }
8375 }
8376 }
8377 }
8378
8379 pub fn copy_relative_path(&mut self, _: &CopyRelativePath, cx: &mut ViewContext<Self>) {
8380 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
8381 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
8382 if let Some(path) = file.path().to_str() {
8383 cx.write_to_clipboard(ClipboardItem::new(path.to_string()));
8384 }
8385 }
8386 }
8387 }
8388
8389 pub fn highlight_rows(&mut self, rows: Option<Range<u32>>) {
8390 self.highlighted_rows = rows;
8391 }
8392
8393 pub fn highlighted_rows(&self) -> Option<Range<u32>> {
8394 self.highlighted_rows.clone()
8395 }
8396
8397 pub fn highlight_background<T: 'static>(
8398 &mut self,
8399 ranges: Vec<Range<Anchor>>,
8400 color_fetcher: fn(&ThemeColors) -> Hsla,
8401 cx: &mut ViewContext<Self>,
8402 ) {
8403 self.background_highlights
8404 .insert(TypeId::of::<T>(), (color_fetcher, ranges));
8405 cx.notify();
8406 }
8407
8408 pub fn highlight_inlay_background<T: 'static>(
8409 &mut self,
8410 ranges: Vec<InlayHighlight>,
8411 color_fetcher: fn(&ThemeColors) -> Hsla,
8412 cx: &mut ViewContext<Self>,
8413 ) {
8414 // TODO: no actual highlights happen for inlays currently, find a way to do that
8415 self.inlay_background_highlights
8416 .insert(Some(TypeId::of::<T>()), (color_fetcher, ranges));
8417 cx.notify();
8418 }
8419
8420 pub fn clear_background_highlights<T: 'static>(
8421 &mut self,
8422 cx: &mut ViewContext<Self>,
8423 ) -> Option<BackgroundHighlight> {
8424 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>());
8425 let inlay_highlights = self
8426 .inlay_background_highlights
8427 .remove(&Some(TypeId::of::<T>()));
8428 if text_highlights.is_some() || inlay_highlights.is_some() {
8429 cx.notify();
8430 }
8431 text_highlights
8432 }
8433
8434 #[cfg(feature = "test-support")]
8435 pub fn all_text_background_highlights(
8436 &mut self,
8437 cx: &mut ViewContext<Self>,
8438 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
8439 let snapshot = self.snapshot(cx);
8440 let buffer = &snapshot.buffer_snapshot;
8441 let start = buffer.anchor_before(0);
8442 let end = buffer.anchor_after(buffer.len());
8443 let theme = cx.theme().colors();
8444 self.background_highlights_in_range(start..end, &snapshot, theme)
8445 }
8446
8447 fn document_highlights_for_position<'a>(
8448 &'a self,
8449 position: Anchor,
8450 buffer: &'a MultiBufferSnapshot,
8451 ) -> impl 'a + Iterator<Item = &Range<Anchor>> {
8452 let read_highlights = self
8453 .background_highlights
8454 .get(&TypeId::of::<DocumentHighlightRead>())
8455 .map(|h| &h.1);
8456 let write_highlights = self
8457 .background_highlights
8458 .get(&TypeId::of::<DocumentHighlightWrite>())
8459 .map(|h| &h.1);
8460 let left_position = position.bias_left(buffer);
8461 let right_position = position.bias_right(buffer);
8462 read_highlights
8463 .into_iter()
8464 .chain(write_highlights)
8465 .flat_map(move |ranges| {
8466 let start_ix = match ranges.binary_search_by(|probe| {
8467 let cmp = probe.end.cmp(&left_position, buffer);
8468 if cmp.is_ge() {
8469 Ordering::Greater
8470 } else {
8471 Ordering::Less
8472 }
8473 }) {
8474 Ok(i) | Err(i) => i,
8475 };
8476
8477 let right_position = right_position.clone();
8478 ranges[start_ix..]
8479 .iter()
8480 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
8481 })
8482 }
8483
8484 pub fn has_background_highlights<T: 'static>(&self) -> bool {
8485 self.background_highlights
8486 .get(&TypeId::of::<T>())
8487 .map_or(false, |(_, highlights)| !highlights.is_empty())
8488 }
8489
8490 pub fn background_highlights_in_range(
8491 &self,
8492 search_range: Range<Anchor>,
8493 display_snapshot: &DisplaySnapshot,
8494 theme: &ThemeColors,
8495 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
8496 let mut results = Vec::new();
8497 for (color_fetcher, ranges) in self.background_highlights.values() {
8498 let color = color_fetcher(theme);
8499 let start_ix = match ranges.binary_search_by(|probe| {
8500 let cmp = probe
8501 .end
8502 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
8503 if cmp.is_gt() {
8504 Ordering::Greater
8505 } else {
8506 Ordering::Less
8507 }
8508 }) {
8509 Ok(i) | Err(i) => i,
8510 };
8511 for range in &ranges[start_ix..] {
8512 if range
8513 .start
8514 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
8515 .is_ge()
8516 {
8517 break;
8518 }
8519
8520 let start = range.start.to_display_point(&display_snapshot);
8521 let end = range.end.to_display_point(&display_snapshot);
8522 results.push((start..end, color))
8523 }
8524 }
8525 results
8526 }
8527
8528 pub fn background_highlight_row_ranges<T: 'static>(
8529 &self,
8530 search_range: Range<Anchor>,
8531 display_snapshot: &DisplaySnapshot,
8532 count: usize,
8533 ) -> Vec<RangeInclusive<DisplayPoint>> {
8534 let mut results = Vec::new();
8535 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
8536 return vec![];
8537 };
8538
8539 let start_ix = match ranges.binary_search_by(|probe| {
8540 let cmp = probe
8541 .end
8542 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
8543 if cmp.is_gt() {
8544 Ordering::Greater
8545 } else {
8546 Ordering::Less
8547 }
8548 }) {
8549 Ok(i) | Err(i) => i,
8550 };
8551 let mut push_region = |start: Option<Point>, end: Option<Point>| {
8552 if let (Some(start_display), Some(end_display)) = (start, end) {
8553 results.push(
8554 start_display.to_display_point(display_snapshot)
8555 ..=end_display.to_display_point(display_snapshot),
8556 );
8557 }
8558 };
8559 let mut start_row: Option<Point> = None;
8560 let mut end_row: Option<Point> = None;
8561 if ranges.len() > count {
8562 return Vec::new();
8563 }
8564 for range in &ranges[start_ix..] {
8565 if range
8566 .start
8567 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
8568 .is_ge()
8569 {
8570 break;
8571 }
8572 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
8573 if let Some(current_row) = &end_row {
8574 if end.row == current_row.row {
8575 continue;
8576 }
8577 }
8578 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
8579 if start_row.is_none() {
8580 assert_eq!(end_row, None);
8581 start_row = Some(start);
8582 end_row = Some(end);
8583 continue;
8584 }
8585 if let Some(current_end) = end_row.as_mut() {
8586 if start.row > current_end.row + 1 {
8587 push_region(start_row, end_row);
8588 start_row = Some(start);
8589 end_row = Some(end);
8590 } else {
8591 // Merge two hunks.
8592 *current_end = end;
8593 }
8594 } else {
8595 unreachable!();
8596 }
8597 }
8598 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
8599 push_region(start_row, end_row);
8600 results
8601 }
8602
8603 pub fn highlight_text<T: 'static>(
8604 &mut self,
8605 ranges: Vec<Range<Anchor>>,
8606 style: HighlightStyle,
8607 cx: &mut ViewContext<Self>,
8608 ) {
8609 self.display_map.update(cx, |map, _| {
8610 map.highlight_text(TypeId::of::<T>(), ranges, style)
8611 });
8612 cx.notify();
8613 }
8614
8615 pub fn highlight_inlays<T: 'static>(
8616 &mut self,
8617 highlights: Vec<InlayHighlight>,
8618 style: HighlightStyle,
8619 cx: &mut ViewContext<Self>,
8620 ) {
8621 self.display_map.update(cx, |map, _| {
8622 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
8623 });
8624 cx.notify();
8625 }
8626
8627 pub fn text_highlights<'a, T: 'static>(
8628 &'a self,
8629 cx: &'a AppContext,
8630 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
8631 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
8632 }
8633
8634 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut ViewContext<Self>) {
8635 let cleared = self
8636 .display_map
8637 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
8638 if cleared {
8639 cx.notify();
8640 }
8641 }
8642
8643 pub fn show_local_cursors(&self, cx: &WindowContext) -> bool {
8644 (self.read_only(cx) || self.blink_manager.read(cx).visible())
8645 && self.focus_handle.is_focused(cx)
8646 }
8647
8648 fn on_buffer_changed(&mut self, _: Model<MultiBuffer>, cx: &mut ViewContext<Self>) {
8649 cx.notify();
8650 }
8651
8652 fn on_buffer_event(
8653 &mut self,
8654 multibuffer: Model<MultiBuffer>,
8655 event: &multi_buffer::Event,
8656 cx: &mut ViewContext<Self>,
8657 ) {
8658 match event {
8659 multi_buffer::Event::Edited {
8660 sigleton_buffer_edited,
8661 } => {
8662 self.refresh_active_diagnostics(cx);
8663 self.refresh_code_actions(cx);
8664 if self.has_active_copilot_suggestion(cx) {
8665 self.update_visible_copilot_suggestion(cx);
8666 }
8667 cx.emit(EditorEvent::BufferEdited);
8668 cx.emit(SearchEvent::MatchesInvalidated);
8669
8670 if *sigleton_buffer_edited {
8671 if let Some(project) = &self.project {
8672 let project = project.read(cx);
8673 let languages_affected = multibuffer
8674 .read(cx)
8675 .all_buffers()
8676 .into_iter()
8677 .filter_map(|buffer| {
8678 let buffer = buffer.read(cx);
8679 let language = buffer.language()?;
8680 if project.is_local()
8681 && project.language_servers_for_buffer(buffer, cx).count() == 0
8682 {
8683 None
8684 } else {
8685 Some(language)
8686 }
8687 })
8688 .cloned()
8689 .collect::<HashSet<_>>();
8690 if !languages_affected.is_empty() {
8691 self.refresh_inlay_hints(
8692 InlayHintRefreshReason::BufferEdited(languages_affected),
8693 cx,
8694 );
8695 }
8696 }
8697 }
8698
8699 let Some(project) = &self.project else { return };
8700 let telemetry = project.read(cx).client().telemetry().clone();
8701 telemetry.log_edit_event("editor");
8702 }
8703 multi_buffer::Event::ExcerptsAdded {
8704 buffer,
8705 predecessor,
8706 excerpts,
8707 } => {
8708 cx.emit(EditorEvent::ExcerptsAdded {
8709 buffer: buffer.clone(),
8710 predecessor: *predecessor,
8711 excerpts: excerpts.clone(),
8712 });
8713 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
8714 }
8715 multi_buffer::Event::ExcerptsRemoved { ids } => {
8716 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
8717 cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
8718 }
8719 multi_buffer::Event::Reparsed => cx.emit(EditorEvent::Reparsed),
8720 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
8721 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
8722 multi_buffer::Event::FileHandleChanged | multi_buffer::Event::Reloaded => {
8723 cx.emit(EditorEvent::TitleChanged)
8724 }
8725 multi_buffer::Event::DiffBaseChanged => cx.emit(EditorEvent::DiffBaseChanged),
8726 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
8727 multi_buffer::Event::DiagnosticsUpdated => {
8728 self.refresh_active_diagnostics(cx);
8729 }
8730 _ => {}
8731 };
8732 }
8733
8734 fn on_display_map_changed(&mut self, _: Model<DisplayMap>, cx: &mut ViewContext<Self>) {
8735 cx.notify();
8736 }
8737
8738 fn settings_changed(&mut self, cx: &mut ViewContext<Self>) {
8739 self.refresh_copilot_suggestions(true, cx);
8740 self.refresh_inlay_hints(
8741 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
8742 self.selections.newest_anchor().head(),
8743 &self.buffer.read(cx).snapshot(cx),
8744 cx,
8745 )),
8746 cx,
8747 );
8748 cx.notify();
8749 }
8750
8751 pub fn set_searchable(&mut self, searchable: bool) {
8752 self.searchable = searchable;
8753 }
8754
8755 pub fn searchable(&self) -> bool {
8756 self.searchable
8757 }
8758
8759 fn open_excerpts(&mut self, _: &OpenExcerpts, cx: &mut ViewContext<Self>) {
8760 let buffer = self.buffer.read(cx);
8761 if buffer.is_singleton() {
8762 cx.propagate();
8763 return;
8764 }
8765
8766 let Some(workspace) = self.workspace() else {
8767 cx.propagate();
8768 return;
8769 };
8770
8771 let mut new_selections_by_buffer = HashMap::default();
8772 for selection in self.selections.all::<usize>(cx) {
8773 for (buffer, mut range, _) in
8774 buffer.range_to_buffer_ranges(selection.start..selection.end, cx)
8775 {
8776 if selection.reversed {
8777 mem::swap(&mut range.start, &mut range.end);
8778 }
8779 new_selections_by_buffer
8780 .entry(buffer)
8781 .or_insert(Vec::new())
8782 .push(range)
8783 }
8784 }
8785
8786 self.push_to_nav_history(self.selections.newest_anchor().head(), None, cx);
8787
8788 // We defer the pane interaction because we ourselves are a workspace item
8789 // and activating a new item causes the pane to call a method on us reentrantly,
8790 // which panics if we're on the stack.
8791 cx.window_context().defer(move |cx| {
8792 workspace.update(cx, |workspace, cx| {
8793 let pane = workspace.active_pane().clone();
8794 pane.update(cx, |pane, _| pane.disable_history());
8795
8796 for (buffer, ranges) in new_selections_by_buffer.into_iter() {
8797 let editor = workspace.open_project_item::<Self>(buffer, cx);
8798 editor.update(cx, |editor, cx| {
8799 editor.change_selections(Some(Autoscroll::newest()), cx, |s| {
8800 s.select_ranges(ranges);
8801 });
8802 });
8803 }
8804
8805 pane.update(cx, |pane, _| pane.enable_history());
8806 })
8807 });
8808 }
8809
8810 fn jump(
8811 &mut self,
8812 path: ProjectPath,
8813 position: Point,
8814 anchor: language::Anchor,
8815 cx: &mut ViewContext<Self>,
8816 ) {
8817 let workspace = self.workspace();
8818 cx.spawn(|_, mut cx| async move {
8819 let workspace = workspace.ok_or_else(|| anyhow!("cannot jump without workspace"))?;
8820 let editor = workspace.update(&mut cx, |workspace, cx| {
8821 workspace.open_path(path, None, true, cx)
8822 })?;
8823 let editor = editor
8824 .await?
8825 .downcast::<Editor>()
8826 .ok_or_else(|| anyhow!("opened item was not an editor"))?
8827 .downgrade();
8828 editor.update(&mut cx, |editor, cx| {
8829 let buffer = editor
8830 .buffer()
8831 .read(cx)
8832 .as_singleton()
8833 .ok_or_else(|| anyhow!("cannot jump in a multi-buffer"))?;
8834 let buffer = buffer.read(cx);
8835 let cursor = if buffer.can_resolve(&anchor) {
8836 language::ToPoint::to_point(&anchor, buffer)
8837 } else {
8838 buffer.clip_point(position, Bias::Left)
8839 };
8840
8841 let nav_history = editor.nav_history.take();
8842 editor.change_selections(Some(Autoscroll::newest()), cx, |s| {
8843 s.select_ranges([cursor..cursor]);
8844 });
8845 editor.nav_history = nav_history;
8846
8847 anyhow::Ok(())
8848 })??;
8849
8850 anyhow::Ok(())
8851 })
8852 .detach_and_log_err(cx);
8853 }
8854
8855 fn marked_text_ranges(&self, cx: &AppContext) -> Option<Vec<Range<OffsetUtf16>>> {
8856 let snapshot = self.buffer.read(cx).read(cx);
8857 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
8858 Some(
8859 ranges
8860 .iter()
8861 .map(move |range| {
8862 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
8863 })
8864 .collect(),
8865 )
8866 }
8867
8868 fn selection_replacement_ranges(
8869 &self,
8870 range: Range<OffsetUtf16>,
8871 cx: &AppContext,
8872 ) -> Vec<Range<OffsetUtf16>> {
8873 let selections = self.selections.all::<OffsetUtf16>(cx);
8874 let newest_selection = selections
8875 .iter()
8876 .max_by_key(|selection| selection.id)
8877 .unwrap();
8878 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
8879 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
8880 let snapshot = self.buffer.read(cx).read(cx);
8881 selections
8882 .into_iter()
8883 .map(|mut selection| {
8884 selection.start.0 =
8885 (selection.start.0 as isize).saturating_add(start_delta) as usize;
8886 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
8887 snapshot.clip_offset_utf16(selection.start, Bias::Left)
8888 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
8889 })
8890 .collect()
8891 }
8892
8893 fn report_copilot_event(
8894 &self,
8895 suggestion_id: Option<String>,
8896 suggestion_accepted: bool,
8897 cx: &AppContext,
8898 ) {
8899 let Some(project) = &self.project else { return };
8900
8901 // If None, we are either getting suggestions in a new, unsaved file, or in a file without an extension
8902 let file_extension = self
8903 .buffer
8904 .read(cx)
8905 .as_singleton()
8906 .and_then(|b| b.read(cx).file())
8907 .and_then(|file| Path::new(file.file_name(cx)).extension())
8908 .and_then(|e| e.to_str())
8909 .map(|a| a.to_string());
8910
8911 let telemetry = project.read(cx).client().telemetry().clone();
8912
8913 telemetry.report_copilot_event(suggestion_id, suggestion_accepted, file_extension)
8914 }
8915
8916 #[cfg(any(test, feature = "test-support"))]
8917 fn report_editor_event(
8918 &self,
8919 _operation: &'static str,
8920 _file_extension: Option<String>,
8921 _cx: &AppContext,
8922 ) {
8923 }
8924
8925 #[cfg(not(any(test, feature = "test-support")))]
8926 fn report_editor_event(
8927 &self,
8928 operation: &'static str,
8929 file_extension: Option<String>,
8930 cx: &AppContext,
8931 ) {
8932 let Some(project) = &self.project else { return };
8933
8934 // If None, we are in a file without an extension
8935 let file = self
8936 .buffer
8937 .read(cx)
8938 .as_singleton()
8939 .and_then(|b| b.read(cx).file());
8940 let file_extension = file_extension.or(file
8941 .as_ref()
8942 .and_then(|file| Path::new(file.file_name(cx)).extension())
8943 .and_then(|e| e.to_str())
8944 .map(|a| a.to_string()));
8945
8946 let vim_mode = cx
8947 .global::<SettingsStore>()
8948 .raw_user_settings()
8949 .get("vim_mode")
8950 == Some(&serde_json::Value::Bool(true));
8951 let copilot_enabled = all_language_settings(file, cx).copilot_enabled(None, None);
8952 let copilot_enabled_for_language = self
8953 .buffer
8954 .read(cx)
8955 .settings_at(0, cx)
8956 .show_copilot_suggestions;
8957
8958 let telemetry = project.read(cx).client().telemetry().clone();
8959 telemetry.report_editor_event(
8960 file_extension,
8961 vim_mode,
8962 operation,
8963 copilot_enabled,
8964 copilot_enabled_for_language,
8965 )
8966 }
8967
8968 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
8969 /// with each line being an array of {text, highlight} objects.
8970 fn copy_highlight_json(&mut self, _: &CopyHighlightJson, cx: &mut ViewContext<Self>) {
8971 let Some(buffer) = self.buffer.read(cx).as_singleton() else {
8972 return;
8973 };
8974
8975 #[derive(Serialize)]
8976 struct Chunk<'a> {
8977 text: String,
8978 highlight: Option<&'a str>,
8979 }
8980
8981 let snapshot = buffer.read(cx).snapshot();
8982 let range = self
8983 .selected_text_range(cx)
8984 .and_then(|selected_range| {
8985 if selected_range.is_empty() {
8986 None
8987 } else {
8988 Some(selected_range)
8989 }
8990 })
8991 .unwrap_or_else(|| 0..snapshot.len());
8992
8993 let chunks = snapshot.chunks(range, true);
8994 let mut lines = Vec::new();
8995 let mut line: VecDeque<Chunk> = VecDeque::new();
8996
8997 let Some(style) = self.style.as_ref() else {
8998 return;
8999 };
9000
9001 for chunk in chunks {
9002 let highlight = chunk
9003 .syntax_highlight_id
9004 .and_then(|id| id.name(&style.syntax));
9005 let mut chunk_lines = chunk.text.split("\n").peekable();
9006 while let Some(text) = chunk_lines.next() {
9007 let mut merged_with_last_token = false;
9008 if let Some(last_token) = line.back_mut() {
9009 if last_token.highlight == highlight {
9010 last_token.text.push_str(text);
9011 merged_with_last_token = true;
9012 }
9013 }
9014
9015 if !merged_with_last_token {
9016 line.push_back(Chunk {
9017 text: text.into(),
9018 highlight,
9019 });
9020 }
9021
9022 if chunk_lines.peek().is_some() {
9023 if line.len() > 1 && line.front().unwrap().text.is_empty() {
9024 line.pop_front();
9025 }
9026 if line.len() > 1 && line.back().unwrap().text.is_empty() {
9027 line.pop_back();
9028 }
9029
9030 lines.push(mem::take(&mut line));
9031 }
9032 }
9033 }
9034
9035 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
9036 return;
9037 };
9038 cx.write_to_clipboard(ClipboardItem::new(lines));
9039 }
9040
9041 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
9042 &self.inlay_hint_cache
9043 }
9044
9045 pub fn replay_insert_event(
9046 &mut self,
9047 text: &str,
9048 relative_utf16_range: Option<Range<isize>>,
9049 cx: &mut ViewContext<Self>,
9050 ) {
9051 if !self.input_enabled {
9052 cx.emit(EditorEvent::InputIgnored { text: text.into() });
9053 return;
9054 }
9055 if let Some(relative_utf16_range) = relative_utf16_range {
9056 let selections = self.selections.all::<OffsetUtf16>(cx);
9057 self.change_selections(None, cx, |s| {
9058 let new_ranges = selections.into_iter().map(|range| {
9059 let start = OffsetUtf16(
9060 range
9061 .head()
9062 .0
9063 .saturating_add_signed(relative_utf16_range.start),
9064 );
9065 let end = OffsetUtf16(
9066 range
9067 .head()
9068 .0
9069 .saturating_add_signed(relative_utf16_range.end),
9070 );
9071 start..end
9072 });
9073 s.select_ranges(new_ranges);
9074 });
9075 }
9076
9077 self.handle_input(text, cx);
9078 }
9079
9080 pub fn supports_inlay_hints(&self, cx: &AppContext) -> bool {
9081 let Some(project) = self.project.as_ref() else {
9082 return false;
9083 };
9084 let project = project.read(cx);
9085
9086 let mut supports = false;
9087 self.buffer().read(cx).for_each_buffer(|buffer| {
9088 if !supports {
9089 supports = project
9090 .language_servers_for_buffer(buffer.read(cx), cx)
9091 .any(
9092 |(_, server)| match server.capabilities().inlay_hint_provider {
9093 Some(lsp::OneOf::Left(enabled)) => enabled,
9094 Some(lsp::OneOf::Right(_)) => true,
9095 None => false,
9096 },
9097 )
9098 }
9099 });
9100 supports
9101 }
9102
9103 pub fn focus(&self, cx: &mut WindowContext) {
9104 cx.focus(&self.focus_handle)
9105 }
9106
9107 pub fn is_focused(&self, cx: &WindowContext) -> bool {
9108 self.focus_handle.is_focused(cx)
9109 }
9110
9111 fn handle_focus(&mut self, cx: &mut ViewContext<Self>) {
9112 cx.emit(EditorEvent::Focused);
9113
9114 if let Some(rename) = self.pending_rename.as_ref() {
9115 let rename_editor_focus_handle = rename.editor.read(cx).focus_handle.clone();
9116 cx.focus(&rename_editor_focus_handle);
9117 } else {
9118 self.blink_manager.update(cx, BlinkManager::enable);
9119 self.buffer.update(cx, |buffer, cx| {
9120 buffer.finalize_last_transaction(cx);
9121 if self.leader_peer_id.is_none() {
9122 buffer.set_active_selections(
9123 &self.selections.disjoint_anchors(),
9124 self.selections.line_mode,
9125 self.cursor_shape,
9126 cx,
9127 );
9128 }
9129 });
9130 }
9131 }
9132
9133 pub fn handle_blur(&mut self, cx: &mut ViewContext<Self>) {
9134 self.blink_manager.update(cx, BlinkManager::disable);
9135 self.buffer
9136 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
9137 self.hide_context_menu(cx);
9138 hide_hover(self, cx);
9139 cx.emit(EditorEvent::Blurred);
9140 cx.notify();
9141 }
9142
9143 pub fn register_action<A: Action>(
9144 &mut self,
9145 listener: impl Fn(&A, &mut WindowContext) + 'static,
9146 ) -> &mut Self {
9147 let listener = Arc::new(listener);
9148
9149 self.editor_actions.push(Box::new(move |cx| {
9150 let _view = cx.view().clone();
9151 let cx = cx.window_context();
9152 let listener = listener.clone();
9153 cx.on_action(TypeId::of::<A>(), move |action, phase, cx| {
9154 let action = action.downcast_ref().unwrap();
9155 if phase == DispatchPhase::Bubble {
9156 listener(action, cx)
9157 }
9158 })
9159 }));
9160 self
9161 }
9162}
9163
9164pub trait CollaborationHub {
9165 fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap<PeerId, Collaborator>;
9166 fn user_participant_indices<'a>(
9167 &self,
9168 cx: &'a AppContext,
9169 ) -> &'a HashMap<u64, ParticipantIndex>;
9170}
9171
9172impl CollaborationHub for Model<Project> {
9173 fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap<PeerId, Collaborator> {
9174 self.read(cx).collaborators()
9175 }
9176
9177 fn user_participant_indices<'a>(
9178 &self,
9179 cx: &'a AppContext,
9180 ) -> &'a HashMap<u64, ParticipantIndex> {
9181 self.read(cx).user_store().read(cx).participant_indices()
9182 }
9183}
9184
9185fn inlay_hint_settings(
9186 location: Anchor,
9187 snapshot: &MultiBufferSnapshot,
9188 cx: &mut ViewContext<'_, Editor>,
9189) -> InlayHintSettings {
9190 let file = snapshot.file_at(location);
9191 let language = snapshot.language_at(location);
9192 let settings = all_language_settings(file, cx);
9193 settings
9194 .language(language.map(|l| l.name()).as_deref())
9195 .inlay_hints
9196}
9197
9198fn consume_contiguous_rows(
9199 contiguous_row_selections: &mut Vec<Selection<Point>>,
9200 selection: &Selection<Point>,
9201 display_map: &DisplaySnapshot,
9202 selections: &mut std::iter::Peekable<std::slice::Iter<Selection<Point>>>,
9203) -> (u32, u32) {
9204 contiguous_row_selections.push(selection.clone());
9205 let start_row = selection.start.row;
9206 let mut end_row = ending_row(selection, display_map);
9207
9208 while let Some(next_selection) = selections.peek() {
9209 if next_selection.start.row <= end_row {
9210 end_row = ending_row(next_selection, display_map);
9211 contiguous_row_selections.push(selections.next().unwrap().clone());
9212 } else {
9213 break;
9214 }
9215 }
9216 (start_row, end_row)
9217}
9218
9219fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> u32 {
9220 if next_selection.end.column > 0 || next_selection.is_empty() {
9221 display_map.next_line_boundary(next_selection.end).0.row + 1
9222 } else {
9223 next_selection.end.row
9224 }
9225}
9226
9227impl EditorSnapshot {
9228 pub fn remote_selections_in_range<'a>(
9229 &'a self,
9230 range: &'a Range<Anchor>,
9231 collaboration_hub: &dyn CollaborationHub,
9232 cx: &'a AppContext,
9233 ) -> impl 'a + Iterator<Item = RemoteSelection> {
9234 let participant_indices = collaboration_hub.user_participant_indices(cx);
9235 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
9236 let collaborators_by_replica_id = collaborators_by_peer_id
9237 .iter()
9238 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
9239 .collect::<HashMap<_, _>>();
9240 self.buffer_snapshot
9241 .remote_selections_in_range(range)
9242 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
9243 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
9244 let participant_index = participant_indices.get(&collaborator.user_id).copied();
9245 Some(RemoteSelection {
9246 replica_id,
9247 selection,
9248 cursor_shape,
9249 line_mode,
9250 participant_index,
9251 peer_id: collaborator.peer_id,
9252 })
9253 })
9254 }
9255
9256 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
9257 self.display_snapshot.buffer_snapshot.language_at(position)
9258 }
9259
9260 pub fn is_focused(&self) -> bool {
9261 self.is_focused
9262 }
9263
9264 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
9265 self.placeholder_text.as_ref()
9266 }
9267
9268 pub fn scroll_position(&self) -> gpui::Point<f32> {
9269 self.scroll_anchor.scroll_position(&self.display_snapshot)
9270 }
9271}
9272
9273impl Deref for EditorSnapshot {
9274 type Target = DisplaySnapshot;
9275
9276 fn deref(&self) -> &Self::Target {
9277 &self.display_snapshot
9278 }
9279}
9280
9281#[derive(Clone, Debug, PartialEq, Eq)]
9282pub enum EditorEvent {
9283 InputIgnored {
9284 text: Arc<str>,
9285 },
9286 InputHandled {
9287 utf16_range_to_replace: Option<Range<isize>>,
9288 text: Arc<str>,
9289 },
9290 ExcerptsAdded {
9291 buffer: Model<Buffer>,
9292 predecessor: ExcerptId,
9293 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
9294 },
9295 ExcerptsRemoved {
9296 ids: Vec<ExcerptId>,
9297 },
9298 BufferEdited,
9299 Edited,
9300 Reparsed,
9301 Focused,
9302 Blurred,
9303 DirtyChanged,
9304 Saved,
9305 TitleChanged,
9306 DiffBaseChanged,
9307 SelectionsChanged {
9308 local: bool,
9309 },
9310 ScrollPositionChanged {
9311 local: bool,
9312 autoscroll: bool,
9313 },
9314 Closed,
9315}
9316
9317impl EventEmitter<EditorEvent> for Editor {}
9318
9319impl FocusableView for Editor {
9320 fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
9321 self.focus_handle.clone()
9322 }
9323}
9324
9325impl Render for Editor {
9326 fn render<'a>(&mut self, cx: &mut ViewContext<'a, Self>) -> impl IntoElement {
9327 let settings = ThemeSettings::get_global(cx);
9328 let text_style = match self.mode {
9329 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
9330 color: cx.theme().colors().editor_foreground,
9331 font_family: settings.ui_font.family.clone(),
9332 font_features: settings.ui_font.features,
9333 font_size: rems(0.875).into(),
9334 font_weight: FontWeight::NORMAL,
9335 font_style: FontStyle::Normal,
9336 line_height: relative(settings.buffer_line_height.value()),
9337 background_color: None,
9338 underline: None,
9339 white_space: WhiteSpace::Normal,
9340 },
9341
9342 EditorMode::Full => TextStyle {
9343 color: cx.theme().colors().editor_foreground,
9344 font_family: settings.buffer_font.family.clone(),
9345 font_features: settings.buffer_font.features,
9346 font_size: settings.buffer_font_size(cx).into(),
9347 font_weight: FontWeight::NORMAL,
9348 font_style: FontStyle::Normal,
9349 line_height: relative(settings.buffer_line_height.value()),
9350 background_color: None,
9351 underline: None,
9352 white_space: WhiteSpace::Normal,
9353 },
9354 };
9355
9356 let background = match self.mode {
9357 EditorMode::SingleLine => cx.theme().system().transparent,
9358 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
9359 EditorMode::Full => cx.theme().colors().editor_background,
9360 };
9361
9362 EditorElement::new(
9363 cx.view(),
9364 EditorStyle {
9365 background,
9366 local_player: cx.theme().players().local(),
9367 text: text_style,
9368 scrollbar_width: px(12.),
9369 syntax: cx.theme().syntax().clone(),
9370 status: cx.theme().status().clone(),
9371 inlays_style: HighlightStyle {
9372 color: Some(cx.theme().status().hint),
9373 font_weight: Some(FontWeight::BOLD),
9374 ..HighlightStyle::default()
9375 },
9376 suggestions_style: HighlightStyle {
9377 color: Some(cx.theme().status().predictive),
9378 ..HighlightStyle::default()
9379 },
9380 },
9381 )
9382 }
9383}
9384
9385impl InputHandler for Editor {
9386 fn text_for_range(
9387 &mut self,
9388 range_utf16: Range<usize>,
9389 cx: &mut ViewContext<Self>,
9390 ) -> Option<String> {
9391 Some(
9392 self.buffer
9393 .read(cx)
9394 .read(cx)
9395 .text_for_range(OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end))
9396 .collect(),
9397 )
9398 }
9399
9400 fn selected_text_range(&mut self, cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
9401 // Prevent the IME menu from appearing when holding down an alphabetic key
9402 // while input is disabled.
9403 if !self.input_enabled {
9404 return None;
9405 }
9406
9407 let range = self.selections.newest::<OffsetUtf16>(cx).range();
9408 Some(range.start.0..range.end.0)
9409 }
9410
9411 fn marked_text_range(&self, cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
9412 let snapshot = self.buffer.read(cx).read(cx);
9413 let range = self.text_highlights::<InputComposition>(cx)?.1.get(0)?;
9414 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
9415 }
9416
9417 fn unmark_text(&mut self, cx: &mut ViewContext<Self>) {
9418 self.clear_highlights::<InputComposition>(cx);
9419 self.ime_transaction.take();
9420 }
9421
9422 fn replace_text_in_range(
9423 &mut self,
9424 range_utf16: Option<Range<usize>>,
9425 text: &str,
9426 cx: &mut ViewContext<Self>,
9427 ) {
9428 if !self.input_enabled {
9429 cx.emit(EditorEvent::InputIgnored { text: text.into() });
9430 return;
9431 }
9432
9433 self.transact(cx, |this, cx| {
9434 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
9435 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
9436 Some(this.selection_replacement_ranges(range_utf16, cx))
9437 } else {
9438 this.marked_text_ranges(cx)
9439 };
9440
9441 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
9442 let newest_selection_id = this.selections.newest_anchor().id;
9443 this.selections
9444 .all::<OffsetUtf16>(cx)
9445 .iter()
9446 .zip(ranges_to_replace.iter())
9447 .find_map(|(selection, range)| {
9448 if selection.id == newest_selection_id {
9449 Some(
9450 (range.start.0 as isize - selection.head().0 as isize)
9451 ..(range.end.0 as isize - selection.head().0 as isize),
9452 )
9453 } else {
9454 None
9455 }
9456 })
9457 });
9458
9459 cx.emit(EditorEvent::InputHandled {
9460 utf16_range_to_replace: range_to_replace,
9461 text: text.into(),
9462 });
9463
9464 if let Some(new_selected_ranges) = new_selected_ranges {
9465 this.change_selections(None, cx, |selections| {
9466 selections.select_ranges(new_selected_ranges)
9467 });
9468 }
9469
9470 this.handle_input(text, cx);
9471 });
9472
9473 if let Some(transaction) = self.ime_transaction {
9474 self.buffer.update(cx, |buffer, cx| {
9475 buffer.group_until_transaction(transaction, cx);
9476 });
9477 }
9478
9479 self.unmark_text(cx);
9480 }
9481
9482 fn replace_and_mark_text_in_range(
9483 &mut self,
9484 range_utf16: Option<Range<usize>>,
9485 text: &str,
9486 new_selected_range_utf16: Option<Range<usize>>,
9487 cx: &mut ViewContext<Self>,
9488 ) {
9489 if !self.input_enabled {
9490 cx.emit(EditorEvent::InputIgnored { text: text.into() });
9491 return;
9492 }
9493
9494 let transaction = self.transact(cx, |this, cx| {
9495 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
9496 let snapshot = this.buffer.read(cx).read(cx);
9497 if let Some(relative_range_utf16) = range_utf16.as_ref() {
9498 for marked_range in &mut marked_ranges {
9499 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
9500 marked_range.start.0 += relative_range_utf16.start;
9501 marked_range.start =
9502 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
9503 marked_range.end =
9504 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
9505 }
9506 }
9507 Some(marked_ranges)
9508 } else if let Some(range_utf16) = range_utf16 {
9509 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
9510 Some(this.selection_replacement_ranges(range_utf16, cx))
9511 } else {
9512 None
9513 };
9514
9515 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
9516 let newest_selection_id = this.selections.newest_anchor().id;
9517 this.selections
9518 .all::<OffsetUtf16>(cx)
9519 .iter()
9520 .zip(ranges_to_replace.iter())
9521 .find_map(|(selection, range)| {
9522 if selection.id == newest_selection_id {
9523 Some(
9524 (range.start.0 as isize - selection.head().0 as isize)
9525 ..(range.end.0 as isize - selection.head().0 as isize),
9526 )
9527 } else {
9528 None
9529 }
9530 })
9531 });
9532
9533 cx.emit(EditorEvent::InputHandled {
9534 utf16_range_to_replace: range_to_replace,
9535 text: text.into(),
9536 });
9537
9538 if let Some(ranges) = ranges_to_replace {
9539 this.change_selections(None, cx, |s| s.select_ranges(ranges));
9540 }
9541
9542 let marked_ranges = {
9543 let snapshot = this.buffer.read(cx).read(cx);
9544 this.selections
9545 .disjoint_anchors()
9546 .iter()
9547 .map(|selection| {
9548 selection.start.bias_left(&*snapshot)..selection.end.bias_right(&*snapshot)
9549 })
9550 .collect::<Vec<_>>()
9551 };
9552
9553 if text.is_empty() {
9554 this.unmark_text(cx);
9555 } else {
9556 this.highlight_text::<InputComposition>(
9557 marked_ranges.clone(),
9558 HighlightStyle::default(), // todo!() this.style(cx).composition_mark,
9559 cx,
9560 );
9561 }
9562
9563 this.handle_input(text, cx);
9564
9565 if let Some(new_selected_range) = new_selected_range_utf16 {
9566 let snapshot = this.buffer.read(cx).read(cx);
9567 let new_selected_ranges = marked_ranges
9568 .into_iter()
9569 .map(|marked_range| {
9570 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
9571 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
9572 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
9573 snapshot.clip_offset_utf16(new_start, Bias::Left)
9574 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
9575 })
9576 .collect::<Vec<_>>();
9577
9578 drop(snapshot);
9579 this.change_selections(None, cx, |selections| {
9580 selections.select_ranges(new_selected_ranges)
9581 });
9582 }
9583 });
9584
9585 self.ime_transaction = self.ime_transaction.or(transaction);
9586 if let Some(transaction) = self.ime_transaction {
9587 self.buffer.update(cx, |buffer, cx| {
9588 buffer.group_until_transaction(transaction, cx);
9589 });
9590 }
9591
9592 if self.text_highlights::<InputComposition>(cx).is_none() {
9593 self.ime_transaction.take();
9594 }
9595 }
9596
9597 fn bounds_for_range(
9598 &mut self,
9599 range_utf16: Range<usize>,
9600 element_bounds: gpui::Bounds<Pixels>,
9601 cx: &mut ViewContext<Self>,
9602 ) -> Option<gpui::Bounds<Pixels>> {
9603 let text_layout_details = self.text_layout_details(cx);
9604 let style = &text_layout_details.editor_style;
9605 let font_id = cx.text_system().resolve_font(&style.text.font());
9606 let font_size = style.text.font_size.to_pixels(cx.rem_size());
9607 let line_height = style.text.line_height_in_pixels(cx.rem_size());
9608 let em_width = cx
9609 .text_system()
9610 .typographic_bounds(font_id, font_size, 'm')
9611 .unwrap()
9612 .size
9613 .width;
9614
9615 let snapshot = self.snapshot(cx);
9616 let scroll_position = snapshot.scroll_position();
9617 let scroll_left = scroll_position.x * em_width;
9618
9619 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
9620 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
9621 + self.gutter_width;
9622 let y = line_height * (start.row() as f32 - scroll_position.y);
9623
9624 Some(Bounds {
9625 origin: element_bounds.origin + point(x, y),
9626 size: size(em_width, line_height),
9627 })
9628 }
9629}
9630
9631trait SelectionExt {
9632 fn offset_range(&self, buffer: &MultiBufferSnapshot) -> Range<usize>;
9633 fn point_range(&self, buffer: &MultiBufferSnapshot) -> Range<Point>;
9634 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
9635 fn spanned_rows(&self, include_end_if_at_line_start: bool, map: &DisplaySnapshot)
9636 -> Range<u32>;
9637}
9638
9639impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
9640 fn point_range(&self, buffer: &MultiBufferSnapshot) -> Range<Point> {
9641 let start = self.start.to_point(buffer);
9642 let end = self.end.to_point(buffer);
9643 if self.reversed {
9644 end..start
9645 } else {
9646 start..end
9647 }
9648 }
9649
9650 fn offset_range(&self, buffer: &MultiBufferSnapshot) -> Range<usize> {
9651 let start = self.start.to_offset(buffer);
9652 let end = self.end.to_offset(buffer);
9653 if self.reversed {
9654 end..start
9655 } else {
9656 start..end
9657 }
9658 }
9659
9660 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
9661 let start = self
9662 .start
9663 .to_point(&map.buffer_snapshot)
9664 .to_display_point(map);
9665 let end = self
9666 .end
9667 .to_point(&map.buffer_snapshot)
9668 .to_display_point(map);
9669 if self.reversed {
9670 end..start
9671 } else {
9672 start..end
9673 }
9674 }
9675
9676 fn spanned_rows(
9677 &self,
9678 include_end_if_at_line_start: bool,
9679 map: &DisplaySnapshot,
9680 ) -> Range<u32> {
9681 let start = self.start.to_point(&map.buffer_snapshot);
9682 let mut end = self.end.to_point(&map.buffer_snapshot);
9683 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
9684 end.row -= 1;
9685 }
9686
9687 let buffer_start = map.prev_line_boundary(start).0;
9688 let buffer_end = map.next_line_boundary(end).0;
9689 buffer_start.row..buffer_end.row + 1
9690 }
9691}
9692
9693impl<T: InvalidationRegion> InvalidationStack<T> {
9694 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
9695 where
9696 S: Clone + ToOffset,
9697 {
9698 while let Some(region) = self.last() {
9699 let all_selections_inside_invalidation_ranges =
9700 if selections.len() == region.ranges().len() {
9701 selections
9702 .iter()
9703 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
9704 .all(|(selection, invalidation_range)| {
9705 let head = selection.head().to_offset(buffer);
9706 invalidation_range.start <= head && invalidation_range.end >= head
9707 })
9708 } else {
9709 false
9710 };
9711
9712 if all_selections_inside_invalidation_ranges {
9713 break;
9714 } else {
9715 self.pop();
9716 }
9717 }
9718 }
9719}
9720
9721impl<T> Default for InvalidationStack<T> {
9722 fn default() -> Self {
9723 Self(Default::default())
9724 }
9725}
9726
9727impl<T> Deref for InvalidationStack<T> {
9728 type Target = Vec<T>;
9729
9730 fn deref(&self) -> &Self::Target {
9731 &self.0
9732 }
9733}
9734
9735impl<T> DerefMut for InvalidationStack<T> {
9736 fn deref_mut(&mut self) -> &mut Self::Target {
9737 &mut self.0
9738 }
9739}
9740
9741impl InvalidationRegion for SnippetState {
9742 fn ranges(&self) -> &[Range<Anchor>] {
9743 &self.ranges[self.active_index]
9744 }
9745}
9746
9747pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> RenderBlock {
9748 let (text_without_backticks, code_ranges) = highlight_diagnostic_message(&diagnostic);
9749
9750 Arc::new(move |cx: &mut BlockContext| {
9751 let color = Some(cx.theme().colors().text_accent);
9752 let group_id: SharedString = cx.block_id.to_string().into();
9753 // TODO: Nate: We should tint the background of the block with the severity color
9754 // We need to extend the theme before we can do this
9755 h_flex()
9756 .id(cx.block_id)
9757 .group(group_id.clone())
9758 .relative()
9759 .pl(cx.anchor_x)
9760 .size_full()
9761 .gap_2()
9762 .child(
9763 StyledText::new(text_without_backticks.clone()).with_highlights(
9764 &cx.text_style(),
9765 code_ranges.iter().map(|range| {
9766 (
9767 range.clone(),
9768 HighlightStyle {
9769 color,
9770 ..Default::default()
9771 },
9772 )
9773 }),
9774 ),
9775 )
9776 .child(
9777 IconButton::new(("copy-block", cx.block_id), IconName::Copy)
9778 .icon_color(Color::Muted)
9779 .size(ButtonSize::Compact)
9780 .style(ButtonStyle::Transparent)
9781 .visible_on_hover(group_id)
9782 .on_click(cx.listener({
9783 let message = diagnostic.message.clone();
9784 move |_, _, cx| cx.write_to_clipboard(ClipboardItem::new(message.clone()))
9785 }))
9786 .tooltip(|cx| Tooltip::text("Copy diagnostic message", cx)),
9787 )
9788 .into_any_element()
9789 })
9790}
9791
9792pub fn highlight_diagnostic_message(diagnostic: &Diagnostic) -> (SharedString, Vec<Range<usize>>) {
9793 let mut text_without_backticks = String::new();
9794 let mut code_ranges = Vec::new();
9795
9796 if let Some(source) = &diagnostic.source {
9797 text_without_backticks.push_str(&source);
9798 code_ranges.push(0..source.len());
9799 text_without_backticks.push_str(": ");
9800 }
9801
9802 let mut prev_offset = 0;
9803 let mut in_code_block = false;
9804 for (ix, _) in diagnostic
9805 .message
9806 .match_indices('`')
9807 .chain([(diagnostic.message.len(), "")])
9808 {
9809 let prev_len = text_without_backticks.len();
9810 text_without_backticks.push_str(&diagnostic.message[prev_offset..ix]);
9811 prev_offset = ix + 1;
9812 if in_code_block {
9813 code_ranges.push(prev_len..text_without_backticks.len());
9814 in_code_block = false;
9815 } else {
9816 in_code_block = true;
9817 }
9818 }
9819
9820 (text_without_backticks.into(), code_ranges)
9821}
9822
9823pub fn diagnostic_style(severity: DiagnosticSeverity, valid: bool, colors: &StatusColors) -> Hsla {
9824 match (severity, valid) {
9825 (DiagnosticSeverity::ERROR, true) => colors.error,
9826 (DiagnosticSeverity::ERROR, false) => colors.error,
9827 (DiagnosticSeverity::WARNING, true) => colors.warning,
9828 (DiagnosticSeverity::WARNING, false) => colors.warning,
9829 (DiagnosticSeverity::INFORMATION, true) => colors.info,
9830 (DiagnosticSeverity::INFORMATION, false) => colors.info,
9831 (DiagnosticSeverity::HINT, true) => colors.info,
9832 (DiagnosticSeverity::HINT, false) => colors.info,
9833 _ => colors.ignored,
9834 }
9835}
9836
9837pub fn styled_runs_for_code_label<'a>(
9838 label: &'a CodeLabel,
9839 syntax_theme: &'a theme::SyntaxTheme,
9840) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
9841 let fade_out = HighlightStyle {
9842 fade_out: Some(0.35),
9843 ..Default::default()
9844 };
9845
9846 let mut prev_end = label.filter_range.end;
9847 label
9848 .runs
9849 .iter()
9850 .enumerate()
9851 .flat_map(move |(ix, (range, highlight_id))| {
9852 let style = if let Some(style) = highlight_id.style(syntax_theme) {
9853 style
9854 } else {
9855 return Default::default();
9856 };
9857 let mut muted_style = style;
9858 muted_style.highlight(fade_out);
9859
9860 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
9861 if range.start >= label.filter_range.end {
9862 if range.start > prev_end {
9863 runs.push((prev_end..range.start, fade_out));
9864 }
9865 runs.push((range.clone(), muted_style));
9866 } else if range.end <= label.filter_range.end {
9867 runs.push((range.clone(), style));
9868 } else {
9869 runs.push((range.start..label.filter_range.end, style));
9870 runs.push((label.filter_range.end..range.end, muted_style));
9871 }
9872 prev_end = cmp::max(prev_end, range.end);
9873
9874 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
9875 runs.push((prev_end..label.text.len(), fade_out));
9876 }
9877
9878 runs
9879 })
9880}
9881
9882pub fn split_words<'a>(text: &'a str) -> impl std::iter::Iterator<Item = &'a str> + 'a {
9883 let mut index = 0;
9884 let mut codepoints = text.char_indices().peekable();
9885
9886 std::iter::from_fn(move || {
9887 let start_index = index;
9888 while let Some((new_index, codepoint)) = codepoints.next() {
9889 index = new_index + codepoint.len_utf8();
9890 let current_upper = codepoint.is_uppercase();
9891 let next_upper = codepoints
9892 .peek()
9893 .map(|(_, c)| c.is_uppercase())
9894 .unwrap_or(false);
9895
9896 if !current_upper && next_upper {
9897 return Some(&text[start_index..index]);
9898 }
9899 }
9900
9901 index = text.len();
9902 if start_index < text.len() {
9903 return Some(&text[start_index..]);
9904 }
9905 None
9906 })
9907 .flat_map(|word| word.split_inclusive('_'))
9908 .flat_map(|word| word.split_inclusive('-'))
9909}
9910
9911trait RangeToAnchorExt {
9912 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
9913}
9914
9915impl<T: ToOffset> RangeToAnchorExt for Range<T> {
9916 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
9917 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
9918 }
9919}