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