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, DiagnosticStyle, PlayerColor, 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 diagnostic_style: DiagnosticStyle,
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 diagnostic_style: cx
7666 .editor_style
7667 .diagnostic_style
7668 .clone(),
7669 // todo!("what about the rest of the highlight style parts for inlays and suggestions?")
7670 inlays_style: HighlightStyle {
7671 color: Some(cx.theme().status().hint),
7672 font_weight: Some(FontWeight::BOLD),
7673 ..HighlightStyle::default()
7674 },
7675 suggestions_style: HighlightStyle {
7676 color: Some(cx.theme().status().predictive),
7677 ..HighlightStyle::default()
7678 },
7679 },
7680 ))
7681 .into_any_element()
7682 }
7683 }),
7684 disposition: BlockDisposition::Below,
7685 }],
7686 Some(Autoscroll::fit()),
7687 cx,
7688 )[0];
7689 this.pending_rename = Some(RenameState {
7690 range,
7691 old_name,
7692 editor: rename_editor,
7693 block_id,
7694 });
7695 })?;
7696 }
7697
7698 Ok(())
7699 }))
7700 }
7701
7702 pub fn confirm_rename(
7703 &mut self,
7704 _: &ConfirmRename,
7705 cx: &mut ViewContext<Self>,
7706 ) -> Option<Task<Result<()>>> {
7707 let rename = self.take_rename(false, cx)?;
7708 let workspace = self.workspace()?;
7709 let (start_buffer, start) = self
7710 .buffer
7711 .read(cx)
7712 .text_anchor_for_position(rename.range.start.clone(), cx)?;
7713 let (end_buffer, end) = self
7714 .buffer
7715 .read(cx)
7716 .text_anchor_for_position(rename.range.end.clone(), cx)?;
7717 if start_buffer != end_buffer {
7718 return None;
7719 }
7720
7721 let buffer = start_buffer;
7722 let range = start..end;
7723 let old_name = rename.old_name;
7724 let new_name = rename.editor.read(cx).text(cx);
7725
7726 let rename = workspace
7727 .read(cx)
7728 .project()
7729 .clone()
7730 .update(cx, |project, cx| {
7731 project.perform_rename(buffer.clone(), range.start, new_name.clone(), true, cx)
7732 });
7733 let workspace = workspace.downgrade();
7734
7735 Some(cx.spawn(|editor, mut cx| async move {
7736 let project_transaction = rename.await?;
7737 Self::open_project_transaction(
7738 &editor,
7739 workspace,
7740 project_transaction,
7741 format!("Rename: {} → {}", old_name, new_name),
7742 cx.clone(),
7743 )
7744 .await?;
7745
7746 editor.update(&mut cx, |editor, cx| {
7747 editor.refresh_document_highlights(cx);
7748 })?;
7749 Ok(())
7750 }))
7751 }
7752
7753 fn take_rename(
7754 &mut self,
7755 moving_cursor: bool,
7756 cx: &mut ViewContext<Self>,
7757 ) -> Option<RenameState> {
7758 let rename = self.pending_rename.take()?;
7759 if rename.editor.focus_handle(cx).is_focused(cx) {
7760 cx.focus(&self.focus_handle);
7761 }
7762
7763 self.remove_blocks(
7764 [rename.block_id].into_iter().collect(),
7765 Some(Autoscroll::fit()),
7766 cx,
7767 );
7768 self.clear_highlights::<Rename>(cx);
7769 self.show_local_selections = true;
7770
7771 if moving_cursor {
7772 let rename_editor = rename.editor.read(cx);
7773 let cursor_in_rename_editor = rename_editor.selections.newest::<usize>(cx).head();
7774
7775 // Update the selection to match the position of the selection inside
7776 // the rename editor.
7777 let snapshot = self.buffer.read(cx).read(cx);
7778 let rename_range = rename.range.to_offset(&snapshot);
7779 let cursor_in_editor = snapshot
7780 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
7781 .min(rename_range.end);
7782 drop(snapshot);
7783
7784 self.change_selections(None, cx, |s| {
7785 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
7786 });
7787 } else {
7788 self.refresh_document_highlights(cx);
7789 }
7790
7791 Some(rename)
7792 }
7793
7794 #[cfg(any(test, feature = "test-support"))]
7795 pub fn pending_rename(&self) -> Option<&RenameState> {
7796 self.pending_rename.as_ref()
7797 }
7798
7799 fn format(&mut self, _: &Format, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
7800 let project = match &self.project {
7801 Some(project) => project.clone(),
7802 None => return None,
7803 };
7804
7805 Some(self.perform_format(project, FormatTrigger::Manual, cx))
7806 }
7807
7808 fn perform_format(
7809 &mut self,
7810 project: Model<Project>,
7811 trigger: FormatTrigger,
7812 cx: &mut ViewContext<Self>,
7813 ) -> Task<Result<()>> {
7814 let buffer = self.buffer().clone();
7815 let buffers = buffer.read(cx).all_buffers();
7816
7817 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
7818 let format = project.update(cx, |project, cx| project.format(buffers, true, trigger, cx));
7819
7820 cx.spawn(|_, mut cx| async move {
7821 let transaction = futures::select_biased! {
7822 _ = timeout => {
7823 log::warn!("timed out waiting for formatting");
7824 None
7825 }
7826 transaction = format.log_err().fuse() => transaction,
7827 };
7828
7829 buffer
7830 .update(&mut cx, |buffer, cx| {
7831 if let Some(transaction) = transaction {
7832 if !buffer.is_singleton() {
7833 buffer.push_transaction(&transaction.0, cx);
7834 }
7835 }
7836
7837 cx.notify();
7838 })
7839 .ok();
7840
7841 Ok(())
7842 })
7843 }
7844
7845 fn restart_language_server(&mut self, _: &RestartLanguageServer, cx: &mut ViewContext<Self>) {
7846 if let Some(project) = self.project.clone() {
7847 self.buffer.update(cx, |multi_buffer, cx| {
7848 project.update(cx, |project, cx| {
7849 project.restart_language_servers_for_buffers(multi_buffer.all_buffers(), cx);
7850 });
7851 })
7852 }
7853 }
7854
7855 fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) {
7856 cx.show_character_palette();
7857 }
7858
7859 fn refresh_active_diagnostics(&mut self, cx: &mut ViewContext<Editor>) {
7860 if let Some(active_diagnostics) = self.active_diagnostics.as_mut() {
7861 let buffer = self.buffer.read(cx).snapshot(cx);
7862 let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer);
7863 let is_valid = buffer
7864 .diagnostics_in_range::<_, usize>(active_diagnostics.primary_range.clone(), false)
7865 .any(|entry| {
7866 entry.diagnostic.is_primary
7867 && !entry.range.is_empty()
7868 && entry.range.start == primary_range_start
7869 && entry.diagnostic.message == active_diagnostics.primary_message
7870 });
7871
7872 if is_valid != active_diagnostics.is_valid {
7873 active_diagnostics.is_valid = is_valid;
7874 let mut new_styles = HashMap::default();
7875 for (block_id, diagnostic) in &active_diagnostics.blocks {
7876 new_styles.insert(
7877 *block_id,
7878 diagnostic_block_renderer(diagnostic.clone(), is_valid),
7879 );
7880 }
7881 self.display_map
7882 .update(cx, |display_map, _| display_map.replace_blocks(new_styles));
7883 }
7884 }
7885 }
7886
7887 fn activate_diagnostics(&mut self, group_id: usize, cx: &mut ViewContext<Self>) -> bool {
7888 self.dismiss_diagnostics(cx);
7889 self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
7890 let buffer = self.buffer.read(cx).snapshot(cx);
7891
7892 let mut primary_range = None;
7893 let mut primary_message = None;
7894 let mut group_end = Point::zero();
7895 let diagnostic_group = buffer
7896 .diagnostic_group::<Point>(group_id)
7897 .map(|entry| {
7898 if entry.range.end > group_end {
7899 group_end = entry.range.end;
7900 }
7901 if entry.diagnostic.is_primary {
7902 primary_range = Some(entry.range.clone());
7903 primary_message = Some(entry.diagnostic.message.clone());
7904 }
7905 entry
7906 })
7907 .collect::<Vec<_>>();
7908 let primary_range = primary_range?;
7909 let primary_message = primary_message?;
7910 let primary_range =
7911 buffer.anchor_after(primary_range.start)..buffer.anchor_before(primary_range.end);
7912
7913 let blocks = display_map
7914 .insert_blocks(
7915 diagnostic_group.iter().map(|entry| {
7916 let diagnostic = entry.diagnostic.clone();
7917 let message_height = diagnostic.message.lines().count() as u8;
7918 BlockProperties {
7919 style: BlockStyle::Fixed,
7920 position: buffer.anchor_after(entry.range.start),
7921 height: message_height,
7922 render: diagnostic_block_renderer(diagnostic, true),
7923 disposition: BlockDisposition::Below,
7924 }
7925 }),
7926 cx,
7927 )
7928 .into_iter()
7929 .zip(diagnostic_group.into_iter().map(|entry| entry.diagnostic))
7930 .collect();
7931
7932 Some(ActiveDiagnosticGroup {
7933 primary_range,
7934 primary_message,
7935 blocks,
7936 is_valid: true,
7937 })
7938 });
7939 self.active_diagnostics.is_some()
7940 }
7941
7942 fn dismiss_diagnostics(&mut self, cx: &mut ViewContext<Self>) {
7943 if let Some(active_diagnostic_group) = self.active_diagnostics.take() {
7944 self.display_map.update(cx, |display_map, cx| {
7945 display_map.remove_blocks(active_diagnostic_group.blocks.into_keys().collect(), cx);
7946 });
7947 cx.notify();
7948 }
7949 }
7950
7951 pub fn set_selections_from_remote(
7952 &mut self,
7953 selections: Vec<Selection<Anchor>>,
7954 pending_selection: Option<Selection<Anchor>>,
7955 cx: &mut ViewContext<Self>,
7956 ) {
7957 let old_cursor_position = self.selections.newest_anchor().head();
7958 self.selections.change_with(cx, |s| {
7959 s.select_anchors(selections);
7960 if let Some(pending_selection) = pending_selection {
7961 s.set_pending(pending_selection, SelectMode::Character);
7962 } else {
7963 s.clear_pending();
7964 }
7965 });
7966 self.selections_did_change(false, &old_cursor_position, cx);
7967 }
7968
7969 fn push_to_selection_history(&mut self) {
7970 self.selection_history.push(SelectionHistoryEntry {
7971 selections: self.selections.disjoint_anchors(),
7972 select_next_state: self.select_next_state.clone(),
7973 select_prev_state: self.select_prev_state.clone(),
7974 add_selections_state: self.add_selections_state.clone(),
7975 });
7976 }
7977
7978 pub fn transact(
7979 &mut self,
7980 cx: &mut ViewContext<Self>,
7981 update: impl FnOnce(&mut Self, &mut ViewContext<Self>),
7982 ) -> Option<TransactionId> {
7983 self.start_transaction_at(Instant::now(), cx);
7984 update(self, cx);
7985 self.end_transaction_at(Instant::now(), cx)
7986 }
7987
7988 fn start_transaction_at(&mut self, now: Instant, cx: &mut ViewContext<Self>) {
7989 self.end_selection(cx);
7990 if let Some(tx_id) = self
7991 .buffer
7992 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
7993 {
7994 self.selection_history
7995 .insert_transaction(tx_id, self.selections.disjoint_anchors());
7996 }
7997 }
7998
7999 fn end_transaction_at(
8000 &mut self,
8001 now: Instant,
8002 cx: &mut ViewContext<Self>,
8003 ) -> Option<TransactionId> {
8004 if let Some(tx_id) = self
8005 .buffer
8006 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
8007 {
8008 if let Some((_, end_selections)) = self.selection_history.transaction_mut(tx_id) {
8009 *end_selections = Some(self.selections.disjoint_anchors());
8010 } else {
8011 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
8012 }
8013
8014 cx.emit(EditorEvent::Edited);
8015 Some(tx_id)
8016 } else {
8017 None
8018 }
8019 }
8020
8021 pub fn fold(&mut self, _: &Fold, cx: &mut ViewContext<Self>) {
8022 let mut fold_ranges = Vec::new();
8023
8024 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8025
8026 let selections = self.selections.all_adjusted(cx);
8027 for selection in selections {
8028 let range = selection.range().sorted();
8029 let buffer_start_row = range.start.row;
8030
8031 for row in (0..=range.end.row).rev() {
8032 let fold_range = display_map.foldable_range(row);
8033
8034 if let Some(fold_range) = fold_range {
8035 if fold_range.end.row >= buffer_start_row {
8036 fold_ranges.push(fold_range);
8037 if row <= range.start.row {
8038 break;
8039 }
8040 }
8041 }
8042 }
8043 }
8044
8045 self.fold_ranges(fold_ranges, true, cx);
8046 }
8047
8048 pub fn fold_at(&mut self, fold_at: &FoldAt, cx: &mut ViewContext<Self>) {
8049 let buffer_row = fold_at.buffer_row;
8050 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8051
8052 if let Some(fold_range) = display_map.foldable_range(buffer_row) {
8053 let autoscroll = self
8054 .selections
8055 .all::<Point>(cx)
8056 .iter()
8057 .any(|selection| fold_range.overlaps(&selection.range()));
8058
8059 self.fold_ranges(std::iter::once(fold_range), autoscroll, cx);
8060 }
8061 }
8062
8063 pub fn unfold_lines(&mut self, _: &UnfoldLines, cx: &mut ViewContext<Self>) {
8064 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8065 let buffer = &display_map.buffer_snapshot;
8066 let selections = self.selections.all::<Point>(cx);
8067 let ranges = selections
8068 .iter()
8069 .map(|s| {
8070 let range = s.display_range(&display_map).sorted();
8071 let mut start = range.start.to_point(&display_map);
8072 let mut end = range.end.to_point(&display_map);
8073 start.column = 0;
8074 end.column = buffer.line_len(end.row);
8075 start..end
8076 })
8077 .collect::<Vec<_>>();
8078
8079 self.unfold_ranges(ranges, true, true, cx);
8080 }
8081
8082 pub fn unfold_at(&mut self, unfold_at: &UnfoldAt, cx: &mut ViewContext<Self>) {
8083 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8084
8085 let intersection_range = Point::new(unfold_at.buffer_row, 0)
8086 ..Point::new(
8087 unfold_at.buffer_row,
8088 display_map.buffer_snapshot.line_len(unfold_at.buffer_row),
8089 );
8090
8091 let autoscroll = self
8092 .selections
8093 .all::<Point>(cx)
8094 .iter()
8095 .any(|selection| selection.range().overlaps(&intersection_range));
8096
8097 self.unfold_ranges(std::iter::once(intersection_range), true, autoscroll, cx)
8098 }
8099
8100 pub fn fold_selected_ranges(&mut self, _: &FoldSelectedRanges, cx: &mut ViewContext<Self>) {
8101 let selections = self.selections.all::<Point>(cx);
8102 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8103 let line_mode = self.selections.line_mode;
8104 let ranges = selections.into_iter().map(|s| {
8105 if line_mode {
8106 let start = Point::new(s.start.row, 0);
8107 let end = Point::new(s.end.row, display_map.buffer_snapshot.line_len(s.end.row));
8108 start..end
8109 } else {
8110 s.start..s.end
8111 }
8112 });
8113 self.fold_ranges(ranges, true, cx);
8114 }
8115
8116 pub fn fold_ranges<T: ToOffset + Clone>(
8117 &mut self,
8118 ranges: impl IntoIterator<Item = Range<T>>,
8119 auto_scroll: bool,
8120 cx: &mut ViewContext<Self>,
8121 ) {
8122 let mut ranges = ranges.into_iter().peekable();
8123 if ranges.peek().is_some() {
8124 self.display_map.update(cx, |map, cx| map.fold(ranges, cx));
8125
8126 if auto_scroll {
8127 self.request_autoscroll(Autoscroll::fit(), cx);
8128 }
8129
8130 cx.notify();
8131 }
8132 }
8133
8134 pub fn unfold_ranges<T: ToOffset + Clone>(
8135 &mut self,
8136 ranges: impl IntoIterator<Item = Range<T>>,
8137 inclusive: bool,
8138 auto_scroll: bool,
8139 cx: &mut ViewContext<Self>,
8140 ) {
8141 let mut ranges = ranges.into_iter().peekable();
8142 if ranges.peek().is_some() {
8143 self.display_map
8144 .update(cx, |map, cx| map.unfold(ranges, inclusive, cx));
8145 if auto_scroll {
8146 self.request_autoscroll(Autoscroll::fit(), cx);
8147 }
8148
8149 cx.notify();
8150 }
8151 }
8152
8153 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut ViewContext<Self>) {
8154 if hovered != self.gutter_hovered {
8155 self.gutter_hovered = hovered;
8156 cx.notify();
8157 }
8158 }
8159
8160 pub fn insert_blocks(
8161 &mut self,
8162 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
8163 autoscroll: Option<Autoscroll>,
8164 cx: &mut ViewContext<Self>,
8165 ) -> Vec<BlockId> {
8166 let blocks = self
8167 .display_map
8168 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
8169 if let Some(autoscroll) = autoscroll {
8170 self.request_autoscroll(autoscroll, cx);
8171 }
8172 blocks
8173 }
8174
8175 pub fn replace_blocks(
8176 &mut self,
8177 blocks: HashMap<BlockId, RenderBlock>,
8178 autoscroll: Option<Autoscroll>,
8179 cx: &mut ViewContext<Self>,
8180 ) {
8181 self.display_map
8182 .update(cx, |display_map, _| display_map.replace_blocks(blocks));
8183 if let Some(autoscroll) = autoscroll {
8184 self.request_autoscroll(autoscroll, cx);
8185 }
8186 }
8187
8188 pub fn remove_blocks(
8189 &mut self,
8190 block_ids: HashSet<BlockId>,
8191 autoscroll: Option<Autoscroll>,
8192 cx: &mut ViewContext<Self>,
8193 ) {
8194 self.display_map.update(cx, |display_map, cx| {
8195 display_map.remove_blocks(block_ids, cx)
8196 });
8197 if let Some(autoscroll) = autoscroll {
8198 self.request_autoscroll(autoscroll, cx);
8199 }
8200 }
8201
8202 pub fn longest_row(&self, cx: &mut AppContext) -> u32 {
8203 self.display_map
8204 .update(cx, |map, cx| map.snapshot(cx))
8205 .longest_row()
8206 }
8207
8208 pub fn max_point(&self, cx: &mut AppContext) -> DisplayPoint {
8209 self.display_map
8210 .update(cx, |map, cx| map.snapshot(cx))
8211 .max_point()
8212 }
8213
8214 pub fn text(&self, cx: &AppContext) -> String {
8215 self.buffer.read(cx).read(cx).text()
8216 }
8217
8218 pub fn text_option(&self, cx: &AppContext) -> Option<String> {
8219 let text = self.text(cx);
8220 let text = text.trim();
8221
8222 if text.is_empty() {
8223 return None;
8224 }
8225
8226 Some(text.to_string())
8227 }
8228
8229 pub fn set_text(&mut self, text: impl Into<Arc<str>>, cx: &mut ViewContext<Self>) {
8230 self.transact(cx, |this, cx| {
8231 this.buffer
8232 .read(cx)
8233 .as_singleton()
8234 .expect("you can only call set_text on editors for singleton buffers")
8235 .update(cx, |buffer, cx| buffer.set_text(text, cx));
8236 });
8237 }
8238
8239 pub fn display_text(&self, cx: &mut AppContext) -> String {
8240 self.display_map
8241 .update(cx, |map, cx| map.snapshot(cx))
8242 .text()
8243 }
8244
8245 pub fn wrap_guides(&self, cx: &AppContext) -> SmallVec<[(usize, bool); 2]> {
8246 let mut wrap_guides = smallvec::smallvec![];
8247
8248 if self.show_wrap_guides == Some(false) {
8249 return wrap_guides;
8250 }
8251
8252 let settings = self.buffer.read(cx).settings_at(0, cx);
8253 if settings.show_wrap_guides {
8254 if let SoftWrap::Column(soft_wrap) = self.soft_wrap_mode(cx) {
8255 wrap_guides.push((soft_wrap as usize, true));
8256 }
8257 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
8258 }
8259
8260 wrap_guides
8261 }
8262
8263 pub fn soft_wrap_mode(&self, cx: &AppContext) -> SoftWrap {
8264 let settings = self.buffer.read(cx).settings_at(0, cx);
8265 let mode = self
8266 .soft_wrap_mode_override
8267 .unwrap_or_else(|| settings.soft_wrap);
8268 match mode {
8269 language_settings::SoftWrap::None => SoftWrap::None,
8270 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
8271 language_settings::SoftWrap::PreferredLineLength => {
8272 SoftWrap::Column(settings.preferred_line_length)
8273 }
8274 }
8275 }
8276
8277 pub fn set_soft_wrap_mode(
8278 &mut self,
8279 mode: language_settings::SoftWrap,
8280 cx: &mut ViewContext<Self>,
8281 ) {
8282 self.soft_wrap_mode_override = Some(mode);
8283 cx.notify();
8284 }
8285
8286 pub fn set_style(&mut self, style: EditorStyle, cx: &mut ViewContext<Self>) {
8287 let rem_size = cx.rem_size();
8288 self.display_map.update(cx, |map, cx| {
8289 map.set_font(
8290 style.text.font(),
8291 style.text.font_size.to_pixels(rem_size),
8292 cx,
8293 )
8294 });
8295 self.style = Some(style);
8296 }
8297
8298 #[cfg(any(test, feature = "test-support"))]
8299 pub fn style(&self) -> Option<&EditorStyle> {
8300 self.style.as_ref()
8301 }
8302
8303 // Called by the element. This method is not designed to be called outside of the editor
8304 // element's layout code because it does not notify when rewrapping is computed synchronously.
8305 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut AppContext) -> bool {
8306 self.display_map
8307 .update(cx, |map, cx| map.set_wrap_width(width, cx))
8308 }
8309
8310 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, cx: &mut ViewContext<Self>) {
8311 if self.soft_wrap_mode_override.is_some() {
8312 self.soft_wrap_mode_override.take();
8313 } else {
8314 let soft_wrap = match self.soft_wrap_mode(cx) {
8315 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
8316 SoftWrap::EditorWidth | SoftWrap::Column(_) => language_settings::SoftWrap::None,
8317 };
8318 self.soft_wrap_mode_override = Some(soft_wrap);
8319 }
8320 cx.notify();
8321 }
8322
8323 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut ViewContext<Self>) {
8324 self.show_gutter = show_gutter;
8325 cx.notify();
8326 }
8327
8328 pub fn set_show_wrap_guides(&mut self, show_gutter: bool, cx: &mut ViewContext<Self>) {
8329 self.show_wrap_guides = Some(show_gutter);
8330 cx.notify();
8331 }
8332
8333 pub fn reveal_in_finder(&mut self, _: &RevealInFinder, cx: &mut ViewContext<Self>) {
8334 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
8335 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
8336 cx.reveal_path(&file.abs_path(cx));
8337 }
8338 }
8339 }
8340
8341 pub fn copy_path(&mut self, _: &CopyPath, cx: &mut ViewContext<Self>) {
8342 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
8343 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
8344 if let Some(path) = file.abs_path(cx).to_str() {
8345 cx.write_to_clipboard(ClipboardItem::new(path.to_string()));
8346 }
8347 }
8348 }
8349 }
8350
8351 pub fn copy_relative_path(&mut self, _: &CopyRelativePath, cx: &mut ViewContext<Self>) {
8352 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
8353 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
8354 if let Some(path) = file.path().to_str() {
8355 cx.write_to_clipboard(ClipboardItem::new(path.to_string()));
8356 }
8357 }
8358 }
8359 }
8360
8361 pub fn highlight_rows(&mut self, rows: Option<Range<u32>>) {
8362 self.highlighted_rows = rows;
8363 }
8364
8365 pub fn highlighted_rows(&self) -> Option<Range<u32>> {
8366 self.highlighted_rows.clone()
8367 }
8368
8369 pub fn highlight_background<T: 'static>(
8370 &mut self,
8371 ranges: Vec<Range<Anchor>>,
8372 color_fetcher: fn(&ThemeColors) -> Hsla,
8373 cx: &mut ViewContext<Self>,
8374 ) {
8375 self.background_highlights
8376 .insert(TypeId::of::<T>(), (color_fetcher, ranges));
8377 cx.notify();
8378 }
8379
8380 pub fn highlight_inlay_background<T: 'static>(
8381 &mut self,
8382 ranges: Vec<InlayHighlight>,
8383 color_fetcher: fn(&ThemeColors) -> Hsla,
8384 cx: &mut ViewContext<Self>,
8385 ) {
8386 // TODO: no actual highlights happen for inlays currently, find a way to do that
8387 self.inlay_background_highlights
8388 .insert(Some(TypeId::of::<T>()), (color_fetcher, ranges));
8389 cx.notify();
8390 }
8391
8392 pub fn clear_background_highlights<T: 'static>(
8393 &mut self,
8394 cx: &mut ViewContext<Self>,
8395 ) -> Option<BackgroundHighlight> {
8396 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>());
8397 let inlay_highlights = self
8398 .inlay_background_highlights
8399 .remove(&Some(TypeId::of::<T>()));
8400 if text_highlights.is_some() || inlay_highlights.is_some() {
8401 cx.notify();
8402 }
8403 text_highlights
8404 }
8405
8406 #[cfg(feature = "test-support")]
8407 pub fn all_text_background_highlights(
8408 &mut self,
8409 cx: &mut ViewContext<Self>,
8410 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
8411 let snapshot = self.snapshot(cx);
8412 let buffer = &snapshot.buffer_snapshot;
8413 let start = buffer.anchor_before(0);
8414 let end = buffer.anchor_after(buffer.len());
8415 let theme = cx.theme().colors();
8416 self.background_highlights_in_range(start..end, &snapshot, theme)
8417 }
8418
8419 fn document_highlights_for_position<'a>(
8420 &'a self,
8421 position: Anchor,
8422 buffer: &'a MultiBufferSnapshot,
8423 ) -> impl 'a + Iterator<Item = &Range<Anchor>> {
8424 let read_highlights = self
8425 .background_highlights
8426 .get(&TypeId::of::<DocumentHighlightRead>())
8427 .map(|h| &h.1);
8428 let write_highlights = self
8429 .background_highlights
8430 .get(&TypeId::of::<DocumentHighlightWrite>())
8431 .map(|h| &h.1);
8432 let left_position = position.bias_left(buffer);
8433 let right_position = position.bias_right(buffer);
8434 read_highlights
8435 .into_iter()
8436 .chain(write_highlights)
8437 .flat_map(move |ranges| {
8438 let start_ix = match ranges.binary_search_by(|probe| {
8439 let cmp = probe.end.cmp(&left_position, buffer);
8440 if cmp.is_ge() {
8441 Ordering::Greater
8442 } else {
8443 Ordering::Less
8444 }
8445 }) {
8446 Ok(i) | Err(i) => i,
8447 };
8448
8449 let right_position = right_position.clone();
8450 ranges[start_ix..]
8451 .iter()
8452 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
8453 })
8454 }
8455
8456 pub fn background_highlights_in_range(
8457 &self,
8458 search_range: Range<Anchor>,
8459 display_snapshot: &DisplaySnapshot,
8460 theme: &ThemeColors,
8461 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
8462 let mut results = Vec::new();
8463 for (color_fetcher, ranges) in self.background_highlights.values() {
8464 let color = color_fetcher(theme);
8465 let start_ix = match ranges.binary_search_by(|probe| {
8466 let cmp = probe
8467 .end
8468 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
8469 if cmp.is_gt() {
8470 Ordering::Greater
8471 } else {
8472 Ordering::Less
8473 }
8474 }) {
8475 Ok(i) | Err(i) => i,
8476 };
8477 for range in &ranges[start_ix..] {
8478 if range
8479 .start
8480 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
8481 .is_ge()
8482 {
8483 break;
8484 }
8485
8486 let start = range.start.to_display_point(&display_snapshot);
8487 let end = range.end.to_display_point(&display_snapshot);
8488 results.push((start..end, color))
8489 }
8490 }
8491 results
8492 }
8493
8494 pub fn background_highlight_row_ranges<T: 'static>(
8495 &self,
8496 search_range: Range<Anchor>,
8497 display_snapshot: &DisplaySnapshot,
8498 count: usize,
8499 ) -> Vec<RangeInclusive<DisplayPoint>> {
8500 let mut results = Vec::new();
8501 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
8502 return vec![];
8503 };
8504
8505 let start_ix = match ranges.binary_search_by(|probe| {
8506 let cmp = probe
8507 .end
8508 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
8509 if cmp.is_gt() {
8510 Ordering::Greater
8511 } else {
8512 Ordering::Less
8513 }
8514 }) {
8515 Ok(i) | Err(i) => i,
8516 };
8517 let mut push_region = |start: Option<Point>, end: Option<Point>| {
8518 if let (Some(start_display), Some(end_display)) = (start, end) {
8519 results.push(
8520 start_display.to_display_point(display_snapshot)
8521 ..=end_display.to_display_point(display_snapshot),
8522 );
8523 }
8524 };
8525 let mut start_row: Option<Point> = None;
8526 let mut end_row: Option<Point> = None;
8527 if ranges.len() > count {
8528 return Vec::new();
8529 }
8530 for range in &ranges[start_ix..] {
8531 if range
8532 .start
8533 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
8534 .is_ge()
8535 {
8536 break;
8537 }
8538 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
8539 if let Some(current_row) = &end_row {
8540 if end.row == current_row.row {
8541 continue;
8542 }
8543 }
8544 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
8545 if start_row.is_none() {
8546 assert_eq!(end_row, None);
8547 start_row = Some(start);
8548 end_row = Some(end);
8549 continue;
8550 }
8551 if let Some(current_end) = end_row.as_mut() {
8552 if start.row > current_end.row + 1 {
8553 push_region(start_row, end_row);
8554 start_row = Some(start);
8555 end_row = Some(end);
8556 } else {
8557 // Merge two hunks.
8558 *current_end = end;
8559 }
8560 } else {
8561 unreachable!();
8562 }
8563 }
8564 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
8565 push_region(start_row, end_row);
8566 results
8567 }
8568
8569 pub fn highlight_text<T: 'static>(
8570 &mut self,
8571 ranges: Vec<Range<Anchor>>,
8572 style: HighlightStyle,
8573 cx: &mut ViewContext<Self>,
8574 ) {
8575 self.display_map.update(cx, |map, _| {
8576 map.highlight_text(TypeId::of::<T>(), ranges, style)
8577 });
8578 cx.notify();
8579 }
8580
8581 pub fn highlight_inlays<T: 'static>(
8582 &mut self,
8583 highlights: Vec<InlayHighlight>,
8584 style: HighlightStyle,
8585 cx: &mut ViewContext<Self>,
8586 ) {
8587 self.display_map.update(cx, |map, _| {
8588 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
8589 });
8590 cx.notify();
8591 }
8592
8593 pub fn text_highlights<'a, T: 'static>(
8594 &'a self,
8595 cx: &'a AppContext,
8596 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
8597 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
8598 }
8599
8600 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut ViewContext<Self>) {
8601 let cleared = self
8602 .display_map
8603 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
8604 if cleared {
8605 cx.notify();
8606 }
8607 }
8608
8609 pub fn show_local_cursors(&self, cx: &WindowContext) -> bool {
8610 self.blink_manager.read(cx).visible() && self.focus_handle.is_focused(cx)
8611 }
8612
8613 fn on_buffer_changed(&mut self, _: Model<MultiBuffer>, cx: &mut ViewContext<Self>) {
8614 cx.notify();
8615 }
8616
8617 fn on_buffer_event(
8618 &mut self,
8619 multibuffer: Model<MultiBuffer>,
8620 event: &multi_buffer::Event,
8621 cx: &mut ViewContext<Self>,
8622 ) {
8623 match event {
8624 multi_buffer::Event::Edited {
8625 sigleton_buffer_edited,
8626 } => {
8627 self.refresh_active_diagnostics(cx);
8628 self.refresh_code_actions(cx);
8629 if self.has_active_copilot_suggestion(cx) {
8630 self.update_visible_copilot_suggestion(cx);
8631 }
8632 cx.emit(EditorEvent::BufferEdited);
8633 cx.emit(SearchEvent::MatchesInvalidated);
8634
8635 if *sigleton_buffer_edited {
8636 if let Some(project) = &self.project {
8637 let project = project.read(cx);
8638 let languages_affected = multibuffer
8639 .read(cx)
8640 .all_buffers()
8641 .into_iter()
8642 .filter_map(|buffer| {
8643 let buffer = buffer.read(cx);
8644 let language = buffer.language()?;
8645 if project.is_local()
8646 && project.language_servers_for_buffer(buffer, cx).count() == 0
8647 {
8648 None
8649 } else {
8650 Some(language)
8651 }
8652 })
8653 .cloned()
8654 .collect::<HashSet<_>>();
8655 if !languages_affected.is_empty() {
8656 self.refresh_inlay_hints(
8657 InlayHintRefreshReason::BufferEdited(languages_affected),
8658 cx,
8659 );
8660 }
8661 }
8662 }
8663 }
8664 multi_buffer::Event::ExcerptsAdded {
8665 buffer,
8666 predecessor,
8667 excerpts,
8668 } => {
8669 cx.emit(EditorEvent::ExcerptsAdded {
8670 buffer: buffer.clone(),
8671 predecessor: *predecessor,
8672 excerpts: excerpts.clone(),
8673 });
8674 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
8675 }
8676 multi_buffer::Event::ExcerptsRemoved { ids } => {
8677 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
8678 cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
8679 }
8680 multi_buffer::Event::Reparsed => cx.emit(EditorEvent::Reparsed),
8681 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
8682 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
8683 multi_buffer::Event::FileHandleChanged | multi_buffer::Event::Reloaded => {
8684 cx.emit(EditorEvent::TitleChanged)
8685 }
8686 multi_buffer::Event::DiffBaseChanged => cx.emit(EditorEvent::DiffBaseChanged),
8687 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
8688 multi_buffer::Event::DiagnosticsUpdated => {
8689 self.refresh_active_diagnostics(cx);
8690 }
8691 _ => {}
8692 };
8693 }
8694
8695 fn on_display_map_changed(&mut self, _: Model<DisplayMap>, cx: &mut ViewContext<Self>) {
8696 cx.notify();
8697 }
8698
8699 fn settings_changed(&mut self, cx: &mut ViewContext<Self>) {
8700 self.refresh_copilot_suggestions(true, cx);
8701 self.refresh_inlay_hints(
8702 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
8703 self.selections.newest_anchor().head(),
8704 &self.buffer.read(cx).snapshot(cx),
8705 cx,
8706 )),
8707 cx,
8708 );
8709 }
8710
8711 pub fn set_searchable(&mut self, searchable: bool) {
8712 self.searchable = searchable;
8713 }
8714
8715 pub fn searchable(&self) -> bool {
8716 self.searchable
8717 }
8718
8719 fn open_excerpts(&mut self, _: &OpenExcerpts, cx: &mut ViewContext<Self>) {
8720 let buffer = self.buffer.read(cx);
8721 if buffer.is_singleton() {
8722 cx.propagate();
8723 return;
8724 }
8725
8726 let Some(workspace) = self.workspace() else {
8727 cx.propagate();
8728 return;
8729 };
8730
8731 let mut new_selections_by_buffer = HashMap::default();
8732 for selection in self.selections.all::<usize>(cx) {
8733 for (buffer, mut range, _) in
8734 buffer.range_to_buffer_ranges(selection.start..selection.end, cx)
8735 {
8736 if selection.reversed {
8737 mem::swap(&mut range.start, &mut range.end);
8738 }
8739 new_selections_by_buffer
8740 .entry(buffer)
8741 .or_insert(Vec::new())
8742 .push(range)
8743 }
8744 }
8745
8746 self.push_to_nav_history(self.selections.newest_anchor().head(), None, cx);
8747
8748 // We defer the pane interaction because we ourselves are a workspace item
8749 // and activating a new item causes the pane to call a method on us reentrantly,
8750 // which panics if we're on the stack.
8751 cx.window_context().defer(move |cx| {
8752 workspace.update(cx, |workspace, cx| {
8753 let pane = workspace.active_pane().clone();
8754 pane.update(cx, |pane, _| pane.disable_history());
8755
8756 for (buffer, ranges) in new_selections_by_buffer.into_iter() {
8757 let editor = workspace.open_project_item::<Self>(buffer, cx);
8758 editor.update(cx, |editor, cx| {
8759 editor.change_selections(Some(Autoscroll::newest()), cx, |s| {
8760 s.select_ranges(ranges);
8761 });
8762 });
8763 }
8764
8765 pane.update(cx, |pane, _| pane.enable_history());
8766 })
8767 });
8768 }
8769
8770 fn jump(
8771 &mut self,
8772 path: ProjectPath,
8773 position: Point,
8774 anchor: language::Anchor,
8775 cx: &mut ViewContext<Self>,
8776 ) {
8777 let workspace = self.workspace();
8778 cx.spawn(|_, mut cx| async move {
8779 let workspace = workspace.ok_or_else(|| anyhow!("cannot jump without workspace"))?;
8780 let editor = workspace.update(&mut cx, |workspace, cx| {
8781 workspace.open_path(path, None, true, cx)
8782 })?;
8783 let editor = editor
8784 .await?
8785 .downcast::<Editor>()
8786 .ok_or_else(|| anyhow!("opened item was not an editor"))?
8787 .downgrade();
8788 editor.update(&mut cx, |editor, cx| {
8789 let buffer = editor
8790 .buffer()
8791 .read(cx)
8792 .as_singleton()
8793 .ok_or_else(|| anyhow!("cannot jump in a multi-buffer"))?;
8794 let buffer = buffer.read(cx);
8795 let cursor = if buffer.can_resolve(&anchor) {
8796 language::ToPoint::to_point(&anchor, buffer)
8797 } else {
8798 buffer.clip_point(position, Bias::Left)
8799 };
8800
8801 let nav_history = editor.nav_history.take();
8802 editor.change_selections(Some(Autoscroll::newest()), cx, |s| {
8803 s.select_ranges([cursor..cursor]);
8804 });
8805 editor.nav_history = nav_history;
8806
8807 anyhow::Ok(())
8808 })??;
8809
8810 anyhow::Ok(())
8811 })
8812 .detach_and_log_err(cx);
8813 }
8814
8815 fn marked_text_ranges(&self, cx: &AppContext) -> Option<Vec<Range<OffsetUtf16>>> {
8816 let snapshot = self.buffer.read(cx).read(cx);
8817 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
8818 Some(
8819 ranges
8820 .iter()
8821 .map(move |range| {
8822 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
8823 })
8824 .collect(),
8825 )
8826 }
8827
8828 fn selection_replacement_ranges(
8829 &self,
8830 range: Range<OffsetUtf16>,
8831 cx: &AppContext,
8832 ) -> Vec<Range<OffsetUtf16>> {
8833 let selections = self.selections.all::<OffsetUtf16>(cx);
8834 let newest_selection = selections
8835 .iter()
8836 .max_by_key(|selection| selection.id)
8837 .unwrap();
8838 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
8839 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
8840 let snapshot = self.buffer.read(cx).read(cx);
8841 selections
8842 .into_iter()
8843 .map(|mut selection| {
8844 selection.start.0 =
8845 (selection.start.0 as isize).saturating_add(start_delta) as usize;
8846 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
8847 snapshot.clip_offset_utf16(selection.start, Bias::Left)
8848 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
8849 })
8850 .collect()
8851 }
8852
8853 fn report_copilot_event(
8854 &self,
8855 suggestion_id: Option<String>,
8856 suggestion_accepted: bool,
8857 cx: &AppContext,
8858 ) {
8859 let Some(project) = &self.project else { return };
8860
8861 // If None, we are either getting suggestions in a new, unsaved file, or in a file without an extension
8862 let file_extension = self
8863 .buffer
8864 .read(cx)
8865 .as_singleton()
8866 .and_then(|b| b.read(cx).file())
8867 .and_then(|file| Path::new(file.file_name(cx)).extension())
8868 .and_then(|e| e.to_str())
8869 .map(|a| a.to_string());
8870
8871 let telemetry = project.read(cx).client().telemetry().clone();
8872 let telemetry_settings = *TelemetrySettings::get_global(cx);
8873
8874 telemetry.report_copilot_event(
8875 telemetry_settings,
8876 suggestion_id,
8877 suggestion_accepted,
8878 file_extension,
8879 )
8880 }
8881
8882 #[cfg(any(test, feature = "test-support"))]
8883 fn report_editor_event(
8884 &self,
8885 _operation: &'static str,
8886 _file_extension: Option<String>,
8887 _cx: &AppContext,
8888 ) {
8889 }
8890
8891 #[cfg(not(any(test, feature = "test-support")))]
8892 fn report_editor_event(
8893 &self,
8894 operation: &'static str,
8895 file_extension: Option<String>,
8896 cx: &AppContext,
8897 ) {
8898 let Some(project) = &self.project else { return };
8899
8900 // If None, we are in a file without an extension
8901 let file = self
8902 .buffer
8903 .read(cx)
8904 .as_singleton()
8905 .and_then(|b| b.read(cx).file());
8906 let file_extension = file_extension.or(file
8907 .as_ref()
8908 .and_then(|file| Path::new(file.file_name(cx)).extension())
8909 .and_then(|e| e.to_str())
8910 .map(|a| a.to_string()));
8911
8912 let vim_mode = cx
8913 .global::<SettingsStore>()
8914 .raw_user_settings()
8915 .get("vim_mode")
8916 == Some(&serde_json::Value::Bool(true));
8917 let telemetry_settings = *TelemetrySettings::get_global(cx);
8918 let copilot_enabled = all_language_settings(file, cx).copilot_enabled(None, None);
8919 let copilot_enabled_for_language = self
8920 .buffer
8921 .read(cx)
8922 .settings_at(0, cx)
8923 .show_copilot_suggestions;
8924
8925 let telemetry = project.read(cx).client().telemetry().clone();
8926 telemetry.report_editor_event(
8927 telemetry_settings,
8928 file_extension,
8929 vim_mode,
8930 operation,
8931 copilot_enabled,
8932 copilot_enabled_for_language,
8933 )
8934 }
8935
8936 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
8937 /// with each line being an array of {text, highlight} objects.
8938 fn copy_highlight_json(&mut self, _: &CopyHighlightJson, cx: &mut ViewContext<Self>) {
8939 let Some(buffer) = self.buffer.read(cx).as_singleton() else {
8940 return;
8941 };
8942
8943 #[derive(Serialize)]
8944 struct Chunk<'a> {
8945 text: String,
8946 highlight: Option<&'a str>,
8947 }
8948
8949 let snapshot = buffer.read(cx).snapshot();
8950 let range = self
8951 .selected_text_range(cx)
8952 .and_then(|selected_range| {
8953 if selected_range.is_empty() {
8954 None
8955 } else {
8956 Some(selected_range)
8957 }
8958 })
8959 .unwrap_or_else(|| 0..snapshot.len());
8960
8961 let chunks = snapshot.chunks(range, true);
8962 let mut lines = Vec::new();
8963 let mut line: VecDeque<Chunk> = VecDeque::new();
8964
8965 let Some(style) = self.style.as_ref() else {
8966 return;
8967 };
8968
8969 for chunk in chunks {
8970 let highlight = chunk
8971 .syntax_highlight_id
8972 .and_then(|id| id.name(&style.syntax));
8973 let mut chunk_lines = chunk.text.split("\n").peekable();
8974 while let Some(text) = chunk_lines.next() {
8975 let mut merged_with_last_token = false;
8976 if let Some(last_token) = line.back_mut() {
8977 if last_token.highlight == highlight {
8978 last_token.text.push_str(text);
8979 merged_with_last_token = true;
8980 }
8981 }
8982
8983 if !merged_with_last_token {
8984 line.push_back(Chunk {
8985 text: text.into(),
8986 highlight,
8987 });
8988 }
8989
8990 if chunk_lines.peek().is_some() {
8991 if line.len() > 1 && line.front().unwrap().text.is_empty() {
8992 line.pop_front();
8993 }
8994 if line.len() > 1 && line.back().unwrap().text.is_empty() {
8995 line.pop_back();
8996 }
8997
8998 lines.push(mem::take(&mut line));
8999 }
9000 }
9001 }
9002
9003 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
9004 return;
9005 };
9006 cx.write_to_clipboard(ClipboardItem::new(lines));
9007 }
9008
9009 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
9010 &self.inlay_hint_cache
9011 }
9012
9013 pub fn replay_insert_event(
9014 &mut self,
9015 text: &str,
9016 relative_utf16_range: Option<Range<isize>>,
9017 cx: &mut ViewContext<Self>,
9018 ) {
9019 if !self.input_enabled {
9020 cx.emit(EditorEvent::InputIgnored { text: text.into() });
9021 return;
9022 }
9023 if let Some(relative_utf16_range) = relative_utf16_range {
9024 let selections = self.selections.all::<OffsetUtf16>(cx);
9025 self.change_selections(None, cx, |s| {
9026 let new_ranges = selections.into_iter().map(|range| {
9027 let start = OffsetUtf16(
9028 range
9029 .head()
9030 .0
9031 .saturating_add_signed(relative_utf16_range.start),
9032 );
9033 let end = OffsetUtf16(
9034 range
9035 .head()
9036 .0
9037 .saturating_add_signed(relative_utf16_range.end),
9038 );
9039 start..end
9040 });
9041 s.select_ranges(new_ranges);
9042 });
9043 }
9044
9045 self.handle_input(text, cx);
9046 }
9047
9048 pub fn supports_inlay_hints(&self, cx: &AppContext) -> bool {
9049 let Some(project) = self.project.as_ref() else {
9050 return false;
9051 };
9052 let project = project.read(cx);
9053
9054 let mut supports = false;
9055 self.buffer().read(cx).for_each_buffer(|buffer| {
9056 if !supports {
9057 supports = project
9058 .language_servers_for_buffer(buffer.read(cx), cx)
9059 .any(
9060 |(_, server)| match server.capabilities().inlay_hint_provider {
9061 Some(lsp::OneOf::Left(enabled)) => enabled,
9062 Some(lsp::OneOf::Right(_)) => true,
9063 None => false,
9064 },
9065 )
9066 }
9067 });
9068 supports
9069 }
9070
9071 pub fn focus(&self, cx: &mut WindowContext) {
9072 cx.focus(&self.focus_handle)
9073 }
9074
9075 pub fn is_focused(&self, cx: &WindowContext) -> bool {
9076 self.focus_handle.is_focused(cx)
9077 }
9078
9079 fn handle_focus(&mut self, cx: &mut ViewContext<Self>) {
9080 cx.emit(EditorEvent::Focused);
9081
9082 if let Some(rename) = self.pending_rename.as_ref() {
9083 let rename_editor_focus_handle = rename.editor.read(cx).focus_handle.clone();
9084 cx.focus(&rename_editor_focus_handle);
9085 } else {
9086 self.blink_manager.update(cx, BlinkManager::enable);
9087 self.buffer.update(cx, |buffer, cx| {
9088 buffer.finalize_last_transaction(cx);
9089 if self.leader_peer_id.is_none() {
9090 buffer.set_active_selections(
9091 &self.selections.disjoint_anchors(),
9092 self.selections.line_mode,
9093 self.cursor_shape,
9094 cx,
9095 );
9096 }
9097 });
9098 }
9099 }
9100
9101 pub fn handle_blur(&mut self, cx: &mut ViewContext<Self>) {
9102 self.blink_manager.update(cx, BlinkManager::disable);
9103 self.buffer
9104 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
9105 self.hide_context_menu(cx);
9106 hide_hover(self, cx);
9107 cx.emit(EditorEvent::Blurred);
9108 cx.notify();
9109 }
9110
9111 pub fn register_action<A: Action>(
9112 &mut self,
9113 listener: impl Fn(&A, &mut WindowContext) + 'static,
9114 ) -> &mut Self {
9115 let listener = Arc::new(listener);
9116
9117 self.editor_actions.push(Box::new(move |cx| {
9118 let _view = cx.view().clone();
9119 let cx = cx.window_context();
9120 let listener = listener.clone();
9121 cx.on_action(TypeId::of::<A>(), move |action, phase, cx| {
9122 let action = action.downcast_ref().unwrap();
9123 if phase == DispatchPhase::Bubble {
9124 listener(action, cx)
9125 }
9126 })
9127 }));
9128 self
9129 }
9130}
9131
9132pub trait CollaborationHub {
9133 fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap<PeerId, Collaborator>;
9134 fn user_participant_indices<'a>(
9135 &self,
9136 cx: &'a AppContext,
9137 ) -> &'a HashMap<u64, ParticipantIndex>;
9138}
9139
9140impl CollaborationHub for Model<Project> {
9141 fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap<PeerId, Collaborator> {
9142 self.read(cx).collaborators()
9143 }
9144
9145 fn user_participant_indices<'a>(
9146 &self,
9147 cx: &'a AppContext,
9148 ) -> &'a HashMap<u64, ParticipantIndex> {
9149 self.read(cx).user_store().read(cx).participant_indices()
9150 }
9151}
9152
9153fn inlay_hint_settings(
9154 location: Anchor,
9155 snapshot: &MultiBufferSnapshot,
9156 cx: &mut ViewContext<'_, Editor>,
9157) -> InlayHintSettings {
9158 let file = snapshot.file_at(location);
9159 let language = snapshot.language_at(location);
9160 let settings = all_language_settings(file, cx);
9161 settings
9162 .language(language.map(|l| l.name()).as_deref())
9163 .inlay_hints
9164}
9165
9166fn consume_contiguous_rows(
9167 contiguous_row_selections: &mut Vec<Selection<Point>>,
9168 selection: &Selection<Point>,
9169 display_map: &DisplaySnapshot,
9170 selections: &mut std::iter::Peekable<std::slice::Iter<Selection<Point>>>,
9171) -> (u32, u32) {
9172 contiguous_row_selections.push(selection.clone());
9173 let start_row = selection.start.row;
9174 let mut end_row = ending_row(selection, display_map);
9175
9176 while let Some(next_selection) = selections.peek() {
9177 if next_selection.start.row <= end_row {
9178 end_row = ending_row(next_selection, display_map);
9179 contiguous_row_selections.push(selections.next().unwrap().clone());
9180 } else {
9181 break;
9182 }
9183 }
9184 (start_row, end_row)
9185}
9186
9187fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> u32 {
9188 if next_selection.end.column > 0 || next_selection.is_empty() {
9189 display_map.next_line_boundary(next_selection.end).0.row + 1
9190 } else {
9191 next_selection.end.row
9192 }
9193}
9194
9195impl EditorSnapshot {
9196 pub fn remote_selections_in_range<'a>(
9197 &'a self,
9198 range: &'a Range<Anchor>,
9199 collaboration_hub: &dyn CollaborationHub,
9200 cx: &'a AppContext,
9201 ) -> impl 'a + Iterator<Item = RemoteSelection> {
9202 let participant_indices = collaboration_hub.user_participant_indices(cx);
9203 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
9204 let collaborators_by_replica_id = collaborators_by_peer_id
9205 .iter()
9206 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
9207 .collect::<HashMap<_, _>>();
9208 self.buffer_snapshot
9209 .remote_selections_in_range(range)
9210 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
9211 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
9212 let participant_index = participant_indices.get(&collaborator.user_id).copied();
9213 Some(RemoteSelection {
9214 replica_id,
9215 selection,
9216 cursor_shape,
9217 line_mode,
9218 participant_index,
9219 peer_id: collaborator.peer_id,
9220 })
9221 })
9222 }
9223
9224 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
9225 self.display_snapshot.buffer_snapshot.language_at(position)
9226 }
9227
9228 pub fn is_focused(&self) -> bool {
9229 self.is_focused
9230 }
9231
9232 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
9233 self.placeholder_text.as_ref()
9234 }
9235
9236 pub fn scroll_position(&self) -> gpui::Point<f32> {
9237 self.scroll_anchor.scroll_position(&self.display_snapshot)
9238 }
9239}
9240
9241impl Deref for EditorSnapshot {
9242 type Target = DisplaySnapshot;
9243
9244 fn deref(&self) -> &Self::Target {
9245 &self.display_snapshot
9246 }
9247}
9248
9249#[derive(Clone, Debug, PartialEq, Eq)]
9250pub enum EditorEvent {
9251 InputIgnored {
9252 text: Arc<str>,
9253 },
9254 InputHandled {
9255 utf16_range_to_replace: Option<Range<isize>>,
9256 text: Arc<str>,
9257 },
9258 ExcerptsAdded {
9259 buffer: Model<Buffer>,
9260 predecessor: ExcerptId,
9261 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
9262 },
9263 ExcerptsRemoved {
9264 ids: Vec<ExcerptId>,
9265 },
9266 BufferEdited,
9267 Edited,
9268 Reparsed,
9269 Focused,
9270 Blurred,
9271 DirtyChanged,
9272 Saved,
9273 TitleChanged,
9274 DiffBaseChanged,
9275 SelectionsChanged {
9276 local: bool,
9277 },
9278 ScrollPositionChanged {
9279 local: bool,
9280 autoscroll: bool,
9281 },
9282 Closed,
9283}
9284
9285impl EventEmitter<EditorEvent> for Editor {}
9286
9287impl FocusableView for Editor {
9288 fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
9289 self.focus_handle.clone()
9290 }
9291}
9292
9293impl Render for Editor {
9294 fn render<'a>(&mut self, cx: &mut ViewContext<'a, Self>) -> impl IntoElement {
9295 let settings = ThemeSettings::get_global(cx);
9296 let text_style = match self.mode {
9297 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
9298 color: cx.theme().colors().editor_foreground,
9299 font_family: settings.ui_font.family.clone(),
9300 font_features: settings.ui_font.features,
9301 font_size: rems(0.875).into(),
9302 font_weight: FontWeight::NORMAL,
9303 font_style: FontStyle::Normal,
9304 line_height: relative(settings.buffer_line_height.value()),
9305 background_color: None,
9306 underline: None,
9307 white_space: WhiteSpace::Normal,
9308 },
9309
9310 EditorMode::Full => TextStyle {
9311 color: cx.theme().colors().editor_foreground,
9312 font_family: settings.buffer_font.family.clone(),
9313 font_features: settings.buffer_font.features,
9314 font_size: settings.buffer_font_size(cx).into(),
9315 font_weight: FontWeight::NORMAL,
9316 font_style: FontStyle::Normal,
9317 line_height: relative(settings.buffer_line_height.value()),
9318 background_color: None,
9319 underline: None,
9320 white_space: WhiteSpace::Normal,
9321 },
9322 };
9323
9324 let background = match self.mode {
9325 EditorMode::SingleLine => cx.theme().system().transparent,
9326 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
9327 EditorMode::Full => cx.theme().colors().editor_background,
9328 };
9329
9330 EditorElement::new(
9331 cx.view(),
9332 EditorStyle {
9333 background,
9334 local_player: cx.theme().players().local(),
9335 text: text_style,
9336 scrollbar_width: px(12.),
9337 syntax: cx.theme().syntax().clone(),
9338 diagnostic_style: cx.theme().diagnostic_style(),
9339 // todo!("what about the rest of the highlight style parts?")
9340 inlays_style: HighlightStyle {
9341 color: Some(cx.theme().status().hint),
9342 font_weight: Some(FontWeight::BOLD),
9343 ..HighlightStyle::default()
9344 },
9345 suggestions_style: HighlightStyle {
9346 color: Some(cx.theme().status().predictive),
9347 ..HighlightStyle::default()
9348 },
9349 },
9350 )
9351 }
9352}
9353
9354impl InputHandler for Editor {
9355 fn text_for_range(
9356 &mut self,
9357 range_utf16: Range<usize>,
9358 cx: &mut ViewContext<Self>,
9359 ) -> Option<String> {
9360 Some(
9361 self.buffer
9362 .read(cx)
9363 .read(cx)
9364 .text_for_range(OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end))
9365 .collect(),
9366 )
9367 }
9368
9369 fn selected_text_range(&mut self, cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
9370 // Prevent the IME menu from appearing when holding down an alphabetic key
9371 // while input is disabled.
9372 if !self.input_enabled {
9373 return None;
9374 }
9375
9376 let range = self.selections.newest::<OffsetUtf16>(cx).range();
9377 Some(range.start.0..range.end.0)
9378 }
9379
9380 fn marked_text_range(&self, cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
9381 let snapshot = self.buffer.read(cx).read(cx);
9382 let range = self.text_highlights::<InputComposition>(cx)?.1.get(0)?;
9383 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
9384 }
9385
9386 fn unmark_text(&mut self, cx: &mut ViewContext<Self>) {
9387 self.clear_highlights::<InputComposition>(cx);
9388 self.ime_transaction.take();
9389 }
9390
9391 fn replace_text_in_range(
9392 &mut self,
9393 range_utf16: Option<Range<usize>>,
9394 text: &str,
9395 cx: &mut ViewContext<Self>,
9396 ) {
9397 if !self.input_enabled {
9398 cx.emit(EditorEvent::InputIgnored { text: text.into() });
9399 return;
9400 }
9401
9402 self.transact(cx, |this, cx| {
9403 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
9404 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
9405 Some(this.selection_replacement_ranges(range_utf16, cx))
9406 } else {
9407 this.marked_text_ranges(cx)
9408 };
9409
9410 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
9411 let newest_selection_id = this.selections.newest_anchor().id;
9412 this.selections
9413 .all::<OffsetUtf16>(cx)
9414 .iter()
9415 .zip(ranges_to_replace.iter())
9416 .find_map(|(selection, range)| {
9417 if selection.id == newest_selection_id {
9418 Some(
9419 (range.start.0 as isize - selection.head().0 as isize)
9420 ..(range.end.0 as isize - selection.head().0 as isize),
9421 )
9422 } else {
9423 None
9424 }
9425 })
9426 });
9427
9428 cx.emit(EditorEvent::InputHandled {
9429 utf16_range_to_replace: range_to_replace,
9430 text: text.into(),
9431 });
9432
9433 if let Some(new_selected_ranges) = new_selected_ranges {
9434 this.change_selections(None, cx, |selections| {
9435 selections.select_ranges(new_selected_ranges)
9436 });
9437 }
9438
9439 this.handle_input(text, cx);
9440 });
9441
9442 if let Some(transaction) = self.ime_transaction {
9443 self.buffer.update(cx, |buffer, cx| {
9444 buffer.group_until_transaction(transaction, cx);
9445 });
9446 }
9447
9448 self.unmark_text(cx);
9449 }
9450
9451 fn replace_and_mark_text_in_range(
9452 &mut self,
9453 range_utf16: Option<Range<usize>>,
9454 text: &str,
9455 new_selected_range_utf16: Option<Range<usize>>,
9456 cx: &mut ViewContext<Self>,
9457 ) {
9458 if !self.input_enabled {
9459 cx.emit(EditorEvent::InputIgnored { text: text.into() });
9460 return;
9461 }
9462
9463 let transaction = self.transact(cx, |this, cx| {
9464 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
9465 let snapshot = this.buffer.read(cx).read(cx);
9466 if let Some(relative_range_utf16) = range_utf16.as_ref() {
9467 for marked_range in &mut marked_ranges {
9468 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
9469 marked_range.start.0 += relative_range_utf16.start;
9470 marked_range.start =
9471 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
9472 marked_range.end =
9473 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
9474 }
9475 }
9476 Some(marked_ranges)
9477 } else if let Some(range_utf16) = range_utf16 {
9478 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
9479 Some(this.selection_replacement_ranges(range_utf16, cx))
9480 } else {
9481 None
9482 };
9483
9484 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
9485 let newest_selection_id = this.selections.newest_anchor().id;
9486 this.selections
9487 .all::<OffsetUtf16>(cx)
9488 .iter()
9489 .zip(ranges_to_replace.iter())
9490 .find_map(|(selection, range)| {
9491 if selection.id == newest_selection_id {
9492 Some(
9493 (range.start.0 as isize - selection.head().0 as isize)
9494 ..(range.end.0 as isize - selection.head().0 as isize),
9495 )
9496 } else {
9497 None
9498 }
9499 })
9500 });
9501
9502 cx.emit(EditorEvent::InputHandled {
9503 utf16_range_to_replace: range_to_replace,
9504 text: text.into(),
9505 });
9506
9507 if let Some(ranges) = ranges_to_replace {
9508 this.change_selections(None, cx, |s| s.select_ranges(ranges));
9509 }
9510
9511 let marked_ranges = {
9512 let snapshot = this.buffer.read(cx).read(cx);
9513 this.selections
9514 .disjoint_anchors()
9515 .iter()
9516 .map(|selection| {
9517 selection.start.bias_left(&*snapshot)..selection.end.bias_right(&*snapshot)
9518 })
9519 .collect::<Vec<_>>()
9520 };
9521
9522 if text.is_empty() {
9523 this.unmark_text(cx);
9524 } else {
9525 this.highlight_text::<InputComposition>(
9526 marked_ranges.clone(),
9527 HighlightStyle::default(), // todo!() this.style(cx).composition_mark,
9528 cx,
9529 );
9530 }
9531
9532 this.handle_input(text, cx);
9533
9534 if let Some(new_selected_range) = new_selected_range_utf16 {
9535 let snapshot = this.buffer.read(cx).read(cx);
9536 let new_selected_ranges = marked_ranges
9537 .into_iter()
9538 .map(|marked_range| {
9539 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
9540 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
9541 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
9542 snapshot.clip_offset_utf16(new_start, Bias::Left)
9543 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
9544 })
9545 .collect::<Vec<_>>();
9546
9547 drop(snapshot);
9548 this.change_selections(None, cx, |selections| {
9549 selections.select_ranges(new_selected_ranges)
9550 });
9551 }
9552 });
9553
9554 self.ime_transaction = self.ime_transaction.or(transaction);
9555 if let Some(transaction) = self.ime_transaction {
9556 self.buffer.update(cx, |buffer, cx| {
9557 buffer.group_until_transaction(transaction, cx);
9558 });
9559 }
9560
9561 if self.text_highlights::<InputComposition>(cx).is_none() {
9562 self.ime_transaction.take();
9563 }
9564 }
9565
9566 fn bounds_for_range(
9567 &mut self,
9568 range_utf16: Range<usize>,
9569 element_bounds: gpui::Bounds<Pixels>,
9570 cx: &mut ViewContext<Self>,
9571 ) -> Option<gpui::Bounds<Pixels>> {
9572 let text_layout_details = self.text_layout_details(cx);
9573 let style = &text_layout_details.editor_style;
9574 let font_id = cx.text_system().font_id(&style.text.font()).unwrap();
9575 let font_size = style.text.font_size.to_pixels(cx.rem_size());
9576 let line_height = style.text.line_height_in_pixels(cx.rem_size());
9577 let em_width = cx
9578 .text_system()
9579 .typographic_bounds(font_id, font_size, 'm')
9580 .unwrap()
9581 .size
9582 .width;
9583
9584 let snapshot = self.snapshot(cx);
9585 let scroll_position = snapshot.scroll_position();
9586 let scroll_left = scroll_position.x * em_width;
9587
9588 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
9589 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
9590 + self.gutter_width;
9591 let y = line_height * (start.row() as f32 - scroll_position.y);
9592
9593 Some(Bounds {
9594 origin: element_bounds.origin + point(x, y),
9595 size: size(em_width, line_height),
9596 })
9597 }
9598}
9599
9600trait SelectionExt {
9601 fn offset_range(&self, buffer: &MultiBufferSnapshot) -> Range<usize>;
9602 fn point_range(&self, buffer: &MultiBufferSnapshot) -> Range<Point>;
9603 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
9604 fn spanned_rows(&self, include_end_if_at_line_start: bool, map: &DisplaySnapshot)
9605 -> Range<u32>;
9606}
9607
9608impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
9609 fn point_range(&self, buffer: &MultiBufferSnapshot) -> Range<Point> {
9610 let start = self.start.to_point(buffer);
9611 let end = self.end.to_point(buffer);
9612 if self.reversed {
9613 end..start
9614 } else {
9615 start..end
9616 }
9617 }
9618
9619 fn offset_range(&self, buffer: &MultiBufferSnapshot) -> Range<usize> {
9620 let start = self.start.to_offset(buffer);
9621 let end = self.end.to_offset(buffer);
9622 if self.reversed {
9623 end..start
9624 } else {
9625 start..end
9626 }
9627 }
9628
9629 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
9630 let start = self
9631 .start
9632 .to_point(&map.buffer_snapshot)
9633 .to_display_point(map);
9634 let end = self
9635 .end
9636 .to_point(&map.buffer_snapshot)
9637 .to_display_point(map);
9638 if self.reversed {
9639 end..start
9640 } else {
9641 start..end
9642 }
9643 }
9644
9645 fn spanned_rows(
9646 &self,
9647 include_end_if_at_line_start: bool,
9648 map: &DisplaySnapshot,
9649 ) -> Range<u32> {
9650 let start = self.start.to_point(&map.buffer_snapshot);
9651 let mut end = self.end.to_point(&map.buffer_snapshot);
9652 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
9653 end.row -= 1;
9654 }
9655
9656 let buffer_start = map.prev_line_boundary(start).0;
9657 let buffer_end = map.next_line_boundary(end).0;
9658 buffer_start.row..buffer_end.row + 1
9659 }
9660}
9661
9662impl<T: InvalidationRegion> InvalidationStack<T> {
9663 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
9664 where
9665 S: Clone + ToOffset,
9666 {
9667 while let Some(region) = self.last() {
9668 let all_selections_inside_invalidation_ranges =
9669 if selections.len() == region.ranges().len() {
9670 selections
9671 .iter()
9672 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
9673 .all(|(selection, invalidation_range)| {
9674 let head = selection.head().to_offset(buffer);
9675 invalidation_range.start <= head && invalidation_range.end >= head
9676 })
9677 } else {
9678 false
9679 };
9680
9681 if all_selections_inside_invalidation_ranges {
9682 break;
9683 } else {
9684 self.pop();
9685 }
9686 }
9687 }
9688}
9689
9690impl<T> Default for InvalidationStack<T> {
9691 fn default() -> Self {
9692 Self(Default::default())
9693 }
9694}
9695
9696impl<T> Deref for InvalidationStack<T> {
9697 type Target = Vec<T>;
9698
9699 fn deref(&self) -> &Self::Target {
9700 &self.0
9701 }
9702}
9703
9704impl<T> DerefMut for InvalidationStack<T> {
9705 fn deref_mut(&mut self) -> &mut Self::Target {
9706 &mut self.0
9707 }
9708}
9709
9710impl InvalidationRegion for SnippetState {
9711 fn ranges(&self) -> &[Range<Anchor>] {
9712 &self.ranges[self.active_index]
9713 }
9714}
9715
9716pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> RenderBlock {
9717 let (text_without_backticks, code_ranges) = highlight_diagnostic_message(&diagnostic);
9718
9719 Arc::new(move |cx: &mut BlockContext| {
9720 let color = Some(cx.theme().colors().text_accent);
9721 let group_id: SharedString = cx.block_id.to_string().into();
9722 // TODO: Nate: We should tint the background of the block with the severity color
9723 // We need to extend the theme before we can do this
9724 h_stack()
9725 .id(cx.block_id)
9726 .group(group_id.clone())
9727 .relative()
9728 .pl(cx.anchor_x)
9729 .size_full()
9730 .gap_2()
9731 .child(
9732 StyledText::new(text_without_backticks.clone()).with_highlights(
9733 &cx.text_style(),
9734 code_ranges.iter().map(|range| {
9735 (
9736 range.clone(),
9737 HighlightStyle {
9738 color,
9739 ..Default::default()
9740 },
9741 )
9742 }),
9743 ),
9744 )
9745 .child(
9746 IconButton::new(("copy-block", cx.block_id), Icon::Copy)
9747 .icon_color(Color::Muted)
9748 .size(ButtonSize::Compact)
9749 .style(ButtonStyle::Transparent)
9750 .visible_on_hover(group_id)
9751 .on_click(cx.listener({
9752 let message = diagnostic.message.clone();
9753 move |_, _, cx| cx.write_to_clipboard(ClipboardItem::new(message.clone()))
9754 }))
9755 .tooltip(|cx| Tooltip::text("Copy diagnostic message", cx)),
9756 )
9757 .into_any_element()
9758 })
9759}
9760
9761pub fn highlight_diagnostic_message(diagnostic: &Diagnostic) -> (SharedString, Vec<Range<usize>>) {
9762 let mut text_without_backticks = String::new();
9763 let mut code_ranges = Vec::new();
9764
9765 if let Some(source) = &diagnostic.source {
9766 text_without_backticks.push_str(&source);
9767 code_ranges.push(0..source.len());
9768 text_without_backticks.push_str(": ");
9769 }
9770
9771 let mut prev_offset = 0;
9772 let mut in_code_block = false;
9773 for (ix, _) in diagnostic
9774 .message
9775 .match_indices('`')
9776 .chain([(diagnostic.message.len(), "")])
9777 {
9778 let prev_len = text_without_backticks.len();
9779 text_without_backticks.push_str(&diagnostic.message[prev_offset..ix]);
9780 prev_offset = ix + 1;
9781 if in_code_block {
9782 code_ranges.push(prev_len..text_without_backticks.len());
9783 in_code_block = false;
9784 } else {
9785 in_code_block = true;
9786 }
9787 }
9788
9789 (text_without_backticks.into(), code_ranges)
9790}
9791
9792pub fn diagnostic_style(
9793 severity: DiagnosticSeverity,
9794 valid: bool,
9795 style: &DiagnosticStyle,
9796) -> Hsla {
9797 match (severity, valid) {
9798 (DiagnosticSeverity::ERROR, true) => style.error,
9799 (DiagnosticSeverity::ERROR, false) => style.error,
9800 (DiagnosticSeverity::WARNING, true) => style.warning,
9801 (DiagnosticSeverity::WARNING, false) => style.warning,
9802 (DiagnosticSeverity::INFORMATION, true) => style.info,
9803 (DiagnosticSeverity::INFORMATION, false) => style.info,
9804 (DiagnosticSeverity::HINT, true) => style.info,
9805 (DiagnosticSeverity::HINT, false) => style.info,
9806 _ => style.ignored,
9807 }
9808}
9809
9810pub fn styled_runs_for_code_label<'a>(
9811 label: &'a CodeLabel,
9812 syntax_theme: &'a theme::SyntaxTheme,
9813) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
9814 let fade_out = HighlightStyle {
9815 fade_out: Some(0.35),
9816 ..Default::default()
9817 };
9818
9819 let mut prev_end = label.filter_range.end;
9820 label
9821 .runs
9822 .iter()
9823 .enumerate()
9824 .flat_map(move |(ix, (range, highlight_id))| {
9825 let style = if let Some(style) = highlight_id.style(syntax_theme) {
9826 style
9827 } else {
9828 return Default::default();
9829 };
9830 let mut muted_style = style;
9831 muted_style.highlight(fade_out);
9832
9833 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
9834 if range.start >= label.filter_range.end {
9835 if range.start > prev_end {
9836 runs.push((prev_end..range.start, fade_out));
9837 }
9838 runs.push((range.clone(), muted_style));
9839 } else if range.end <= label.filter_range.end {
9840 runs.push((range.clone(), style));
9841 } else {
9842 runs.push((range.start..label.filter_range.end, style));
9843 runs.push((label.filter_range.end..range.end, muted_style));
9844 }
9845 prev_end = cmp::max(prev_end, range.end);
9846
9847 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
9848 runs.push((prev_end..label.text.len(), fade_out));
9849 }
9850
9851 runs
9852 })
9853}
9854
9855pub fn split_words<'a>(text: &'a str) -> impl std::iter::Iterator<Item = &'a str> + 'a {
9856 let mut index = 0;
9857 let mut codepoints = text.char_indices().peekable();
9858
9859 std::iter::from_fn(move || {
9860 let start_index = index;
9861 while let Some((new_index, codepoint)) = codepoints.next() {
9862 index = new_index + codepoint.len_utf8();
9863 let current_upper = codepoint.is_uppercase();
9864 let next_upper = codepoints
9865 .peek()
9866 .map(|(_, c)| c.is_uppercase())
9867 .unwrap_or(false);
9868
9869 if !current_upper && next_upper {
9870 return Some(&text[start_index..index]);
9871 }
9872 }
9873
9874 index = text.len();
9875 if start_index < text.len() {
9876 return Some(&text[start_index..]);
9877 }
9878 None
9879 })
9880 .flat_map(|word| word.split_inclusive('_'))
9881 .flat_map(|word| word.split_inclusive('-'))
9882}
9883
9884trait RangeToAnchorExt {
9885 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
9886}
9887
9888impl<T: ToOffset> RangeToAnchorExt for Range<T> {
9889 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
9890 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
9891 }
9892}