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