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