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