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