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