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