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