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