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