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