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