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