diff --git a/Cargo.lock b/Cargo.lock index 580de35aa0..8db022476c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9028,6 +9028,7 @@ dependencies = [ "fs2", "gpui2", "indexmap 1.9.3", + "itertools 0.11.0", "parking_lot 0.11.2", "refineable", "schemars", diff --git a/crates/client2/src/client2.rs b/crates/client2/src/client2.rs index 4a1c5f321c..93ec7f329b 100644 --- a/crates/client2/src/client2.rs +++ b/crates/client2/src/client2.rs @@ -80,7 +80,6 @@ pub fn init(client: &Arc, cx: &mut AppContext) { init_settings(cx); let client = Arc::downgrade(client); - cx.register_action_type::(); cx.on_action({ let client = client.clone(); move |_: &SignIn, cx| { @@ -93,7 +92,6 @@ pub fn init(client: &Arc, cx: &mut AppContext) { } }); - cx.register_action_type::(); cx.on_action({ let client = client.clone(); move |_: &SignOut, cx| { @@ -106,7 +104,6 @@ pub fn init(client: &Arc, cx: &mut AppContext) { } }); - cx.register_action_type::(); cx.on_action({ let client = client.clone(); move |_: &Reconnect, cx| { diff --git a/crates/copilot2/src/copilot2.rs b/crates/copilot2/src/copilot2.rs index ac52bf156d..9e82823b9b 100644 --- a/crates/copilot2/src/copilot2.rs +++ b/crates/copilot2/src/copilot2.rs @@ -7,8 +7,8 @@ use async_tar::Archive; use collections::{HashMap, HashSet}; use futures::{channel::oneshot, future::Shared, Future, FutureExt, TryFutureExt}; use gpui::{ - AppContext, AsyncAppContext, Context, Entity, EntityId, EventEmitter, Model, ModelContext, - Task, WeakModel, + actions, AppContext, AsyncAppContext, Context, Entity, EntityId, EventEmitter, Model, + ModelContext, Task, WeakModel, }; use language::{ language_settings::{all_language_settings, language_settings}, @@ -34,19 +34,11 @@ use util::{ // todo!() // const COPILOT_AUTH_NAMESPACE: &'static str = "copilot_auth"; -// actions!(copilot_auth, [SignIn, SignOut]); +actions!(SignIn, SignOut); // todo!() // const COPILOT_NAMESPACE: &'static str = "copilot"; -// actions!( -// copilot, -// [Suggest, NextSuggestion, PreviousSuggestion, Reinstall] -// ); -// -pub struct Suggest; -pub struct NextSuggestion; -pub struct PreviousSuggestion; -pub struct Reinstall; +actions!(Suggest, NextSuggestion, PreviousSuggestion, Reinstall); pub fn init( new_server_id: LanguageServerId, diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index 4d58335783..ea76a7b57d 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -20,12 +20,14 @@ pub mod selections_collection; mod editor_tests; #[cfg(any(test, feature = "test-support"))] pub mod test; +use ::git::diff::DiffHunk; use aho_corasick::AhoCorasick; use anyhow::{Context as _, Result}; use blink_manager::BlinkManager; use client::{ClickhouseEvent, Client, Collaborator, ParticipantIndex, TelemetrySettings}; use clock::ReplicaId; -use collections::{BTreeMap, HashMap, HashSet, VecDeque}; +use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque}; +use convert_case::{Case, Casing}; use copilot::Copilot; pub use display_map::DisplayPoint; use display_map::*; @@ -35,11 +37,13 @@ pub use element::{ }; use futures::FutureExt; use fuzzy::{StringMatch, StringMatchCandidate}; +use git::diff_hunk_to_display; use gpui::{ - actions, div, px, relative, AnyElement, AppContext, BackgroundExecutor, Context, - DispatchContext, Div, Element, Entity, EventEmitter, FocusHandle, FontStyle, FontWeight, - HighlightStyle, Hsla, InputHandler, Model, Pixels, PlatformInputHandler, Render, Styled, - Subscription, Task, TextStyle, View, ViewContext, VisualContext, WeakView, WindowContext, + action, actions, div, px, relative, AnyElement, AppContext, BackgroundExecutor, ClipboardItem, + Context, DispatchContext, Div, Element, Entity, EventEmitter, FocusHandle, FontStyle, + FontWeight, HighlightStyle, Hsla, InputHandler, Model, Pixels, PlatformInputHandler, Render, + Styled, Subscription, Task, TextStyle, View, ViewContext, VisualContext, WeakView, + WindowContext, }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; @@ -50,8 +54,8 @@ pub use language::{char_kind, CharKind}; use language::{ language_settings::{self, all_language_settings, InlayHintSettings}, point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, Completion, CursorShape, - Diagnostic, Language, LanguageRegistry, LanguageServerName, OffsetRangeExt, Point, Selection, - SelectionGoal, TransactionId, + Diagnostic, IndentKind, IndentSize, Language, LanguageRegistry, LanguageServerName, + OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId, }; use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState}; use lsp::{DiagnosticSeverity, Documentation, LanguageServerId}; @@ -64,6 +68,7 @@ pub use multi_buffer::{ use ordered_float::OrderedFloat; use parking_lot::RwLock; use project::{FormatTrigger, Location, Project}; +use rand::prelude::*; use rpc::proto::*; use scroll::{ autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide, @@ -72,11 +77,14 @@ use selections_collection::{resolve_multiple, MutableSelectionsCollection, Selec use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use smallvec::SmallVec; +use snippet::Snippet; use std::{ any::TypeId, borrow::Cow, cmp::{self, Ordering, Reverse}, - ops::{ControlFlow, Deref, DerefMut, Range}, + mem, + num::NonZeroU32, + ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive}, path::Path, sync::Arc, time::{Duration, Instant}, @@ -165,87 +173,82 @@ pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2); // // .with_soft_wrap(true) // } -#[derive(Clone, Deserialize, PartialEq, Default)] +#[action] pub struct SelectNext { #[serde(default)] pub replace_newest: bool, } -#[derive(Clone, Deserialize, PartialEq, Default)] +#[action] pub struct SelectPrevious { #[serde(default)] pub replace_newest: bool, } -#[derive(Clone, Deserialize, PartialEq, Default)] +#[action] pub struct SelectAllMatches { #[serde(default)] pub replace_newest: bool, } -#[derive(Clone, Deserialize, PartialEq)] +#[action] pub struct SelectToBeginningOfLine { #[serde(default)] stop_at_soft_wraps: bool, } -#[derive(Clone, Default, Deserialize, PartialEq)] +#[action] pub struct MovePageUp { #[serde(default)] center_cursor: bool, } -#[derive(Clone, Default, Deserialize, PartialEq)] +#[action] pub struct MovePageDown { #[serde(default)] center_cursor: bool, } -#[derive(Clone, Deserialize, PartialEq)] +#[action] pub struct SelectToEndOfLine { #[serde(default)] stop_at_soft_wraps: bool, } -#[derive(Clone, Deserialize, PartialEq)] +#[action] pub struct ToggleCodeActions { #[serde(default)] pub deployed_from_indicator: bool, } -#[derive(Clone, Default, Deserialize, PartialEq)] +#[action] pub struct ConfirmCompletion { #[serde(default)] pub item_ix: Option, } -#[derive(Clone, Default, Deserialize, PartialEq)] +#[action] pub struct ConfirmCodeAction { #[serde(default)] pub item_ix: Option, } -#[derive(Clone, Default, Deserialize, PartialEq)] +#[action] pub struct ToggleComments { #[serde(default)] pub advance_downwards: bool, } -#[derive(Clone, Default, Deserialize, PartialEq)] +#[action] pub struct FoldAt { pub buffer_row: u32, } -#[derive(Clone, Default, Deserialize, PartialEq)] +#[action] pub struct UnfoldAt { pub buffer_row: u32, } -#[derive(Clone, Default, Deserialize, PartialEq)] -pub struct GutterHover { - pub hovered: bool, -} - #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum InlayId { Suggestion(usize), @@ -262,113 +265,121 @@ impl InlayId { } actions!( - Cancel, + AddSelectionAbove, + AddSelectionBelow, Backspace, + Cancel, + ConfirmRename, + ContextMenuFirst, + ContextMenuLast, + ContextMenuNext, + ContextMenuPrev, + ConvertToKebabCase, + ConvertToLowerCamelCase, + ConvertToLowerCase, + ConvertToSnakeCase, + ConvertToTitleCase, + ConvertToUpperCamelCase, + ConvertToUpperCase, + Copy, + CopyHighlightJson, + CopyPath, + CopyRelativePath, + Cut, + CutToEndOfLine, Delete, + DeleteLine, + DeleteToBeginningOfLine, + DeleteToEndOfLine, + DeleteToNextSubwordEnd, + DeleteToNextWordEnd, + DeleteToPreviousSubwordStart, + DeleteToPreviousWordStart, + DuplicateLine, + FindAllReferences, + Fold, + FoldSelectedRanges, + Format, + GoToDefinition, + GoToDefinitionSplit, + GoToDiagnostic, + GoToHunk, + GoToPrevDiagnostic, + GoToPrevHunk, + GoToTypeDefinition, + GoToTypeDefinitionSplit, + HalfPageDown, + HalfPageUp, + Hover, + Indent, + JoinLines, + LineDown, + LineUp, + MoveDown, + MoveLeft, + MoveLineDown, + MoveLineUp, + MoveRight, + MoveToBeginning, + MoveToBeginningOfLine, + MoveToEnclosingBracket, + MoveToEnd, + MoveToEndOfLine, + MoveToEndOfParagraph, + MoveToNextSubwordEnd, + MoveToNextWordEnd, + MoveToPreviousSubwordStart, + MoveToPreviousWordStart, + MoveToStartOfParagraph, + MoveUp, Newline, NewlineAbove, NewlineBelow, - GoToDiagnostic, - GoToPrevDiagnostic, - GoToHunk, - GoToPrevHunk, - Indent, + NextScreen, + OpenExcerpts, Outdent, - DeleteLine, - DeleteToPreviousWordStart, - DeleteToPreviousSubwordStart, - DeleteToNextWordEnd, - DeleteToNextSubwordEnd, - DeleteToBeginningOfLine, - DeleteToEndOfLine, - CutToEndOfLine, - DuplicateLine, - MoveLineUp, - MoveLineDown, - JoinLines, - SortLinesCaseSensitive, - SortLinesCaseInsensitive, - ReverseLines, - ShuffleLines, - ConvertToUpperCase, - ConvertToLowerCase, - ConvertToTitleCase, - ConvertToSnakeCase, - ConvertToKebabCase, - ConvertToUpperCamelCase, - ConvertToLowerCamelCase, - Transpose, - Cut, - Copy, - Paste, - Undo, - Redo, - MoveUp, - PageUp, - MoveDown, PageDown, - MoveLeft, - MoveRight, - MoveToPreviousWordStart, - MoveToPreviousSubwordStart, - MoveToNextWordEnd, - MoveToNextSubwordEnd, - MoveToBeginningOfLine, - MoveToEndOfLine, - MoveToStartOfParagraph, - MoveToEndOfParagraph, - MoveToBeginning, - MoveToEnd, - SelectUp, + PageUp, + Paste, + Redo, + RedoSelection, + Rename, + RestartLanguageServer, + RevealInFinder, + ReverseLines, + ScrollCursorBottom, + ScrollCursorCenter, + ScrollCursorTop, + SelectAll, SelectDown, + SelectLargerSyntaxNode, SelectLeft, + SelectLine, SelectRight, - SelectToPreviousWordStart, - SelectToPreviousSubwordStart, - SelectToNextWordEnd, - SelectToNextSubwordEnd, - SelectToStartOfParagraph, - SelectToEndOfParagraph, + SelectSmallerSyntaxNode, SelectToBeginning, SelectToEnd, - SelectAll, - SelectLine, + SelectToEndOfParagraph, + SelectToNextSubwordEnd, + SelectToNextWordEnd, + SelectToPreviousSubwordStart, + SelectToPreviousWordStart, + SelectToStartOfParagraph, + SelectUp, + ShowCharacterPalette, + ShowCompletions, + ShuffleLines, + SortLinesCaseInsensitive, + SortLinesCaseSensitive, SplitSelectionIntoLines, - AddSelectionAbove, - AddSelectionBelow, Tab, TabPrev, - ShowCharacterPalette, - SelectLargerSyntaxNode, - SelectSmallerSyntaxNode, - GoToDefinition, - GoToDefinitionSplit, - GoToTypeDefinition, - GoToTypeDefinitionSplit, - MoveToEnclosingBracket, - UndoSelection, - RedoSelection, - FindAllReferences, - Rename, - ConfirmRename, - Fold, - UnfoldLines, - FoldSelectedRanges, - ShowCompletions, - OpenExcerpts, - RestartLanguageServer, - Hover, - Format, - ToggleSoftWrap, ToggleInlayHints, - RevealInFinder, - CopyPath, - CopyRelativePath, - CopyHighlightJson, - ContextMenuFirst, - ContextMenuPrev, - ContextMenuNext, - ContextMenuLast, + ToggleSoftWrap, + Transpose, + Undo, + UndoSelection, + UnfoldLines, ); // impl_actions!( @@ -448,13 +459,12 @@ pub fn init(cx: &mut AppContext) { // cx.register_action_type(Editor::paste); // cx.register_action_type(Editor::undo); // cx.register_action_type(Editor::redo); - cx.register_action_type::(); // cx.register_action_type(Editor::move_page_up); - cx.register_action_type::(); + // cx.register_action_type::(); // cx.register_action_type(Editor::move_page_down); // cx.register_action_type(Editor::next_screen); - cx.register_action_type::(); - cx.register_action_type::(); + // cx.register_action_type::(); + // cx.register_action_type::(); // cx.register_action_type(Editor::move_to_previous_word_start); // cx.register_action_type(Editor::move_to_previous_subword_start); // cx.register_action_type(Editor::move_to_next_word_end); @@ -533,7 +543,6 @@ pub fn init(cx: &mut AppContext) { // cx.register_action_type(Editor::context_menu_last); hover_popover::init(cx); - scroll::actions::init(cx); workspace::register_project_item::(cx); workspace::register_followable_item::(cx); @@ -989,7 +998,7 @@ impl CompletionsMenu { project: Option>, cx: &mut ViewContext, ) { - todo!("implementation below "); + // todo!("implementation below "); } // ) { // let settings = EditorSettings::get_global(cx); @@ -1156,7 +1165,7 @@ impl CompletionsMenu { client: Arc, language_registry: Arc, ) { - todo!() + // todo!() // let request = proto::ResolveCompletionDocumentation { // project_id, // language_server_id: server_id.0 as u64, @@ -1200,7 +1209,7 @@ impl CompletionsMenu { completion: lsp::CompletionItem, language_registry: Arc, ) { - todo!() + // todo!() // let can_resolve = server // .capabilities() // .completion_provider @@ -2384,45 +2393,45 @@ impl Editor { .update(cx, |buffer, cx| buffer.edit(edits, None, cx)); } - // pub fn edit_with_autoindent(&mut self, edits: I, cx: &mut ViewContext) - // where - // I: IntoIterator, T)>, - // S: ToOffset, - // T: Into>, - // { - // if self.read_only { - // return; - // } + pub fn edit_with_autoindent(&mut self, edits: I, cx: &mut ViewContext) + where + I: IntoIterator, T)>, + S: ToOffset, + T: Into>, + { + if self.read_only { + return; + } - // self.buffer.update(cx, |buffer, cx| { - // buffer.edit(edits, self.autoindent_mode.clone(), cx) - // }); - // } + self.buffer.update(cx, |buffer, cx| { + buffer.edit(edits, self.autoindent_mode.clone(), cx) + }); + } - // pub fn edit_with_block_indent( - // &mut self, - // edits: I, - // original_indent_columns: Vec, - // cx: &mut ViewContext, - // ) where - // I: IntoIterator, T)>, - // S: ToOffset, - // T: Into>, - // { - // if self.read_only { - // return; - // } + pub fn edit_with_block_indent( + &mut self, + edits: I, + original_indent_columns: Vec, + cx: &mut ViewContext, + ) where + I: IntoIterator, T)>, + S: ToOffset, + T: Into>, + { + if self.read_only { + return; + } - // self.buffer.update(cx, |buffer, cx| { - // buffer.edit( - // edits, - // Some(AutoindentMode::Block { - // original_indent_columns, - // }), - // cx, - // ) - // }); - // } + self.buffer.update(cx, |buffer, cx| { + buffer.edit( + edits, + Some(AutoindentMode::Block { + original_indent_columns, + }), + cx, + ) + }); + } fn select(&mut self, phase: SelectPhase, cx: &mut ViewContext) { self.hide_context_menu(cx); @@ -2735,40 +2744,40 @@ impl Editor { self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some() } - // pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext) { - // if self.take_rename(false, cx).is_some() { - // return; - // } + pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext) { + if self.take_rename(false, cx).is_some() { + return; + } - // if hide_hover(self, cx) { - // return; - // } + if hide_hover(self, cx) { + return; + } - // if self.hide_context_menu(cx).is_some() { - // return; - // } + if self.hide_context_menu(cx).is_some() { + return; + } - // if self.discard_copilot_suggestion(cx) { - // return; - // } + if self.discard_copilot_suggestion(cx) { + return; + } - // if self.snippet_stack.pop().is_some() { - // return; - // } + if self.snippet_stack.pop().is_some() { + return; + } - // if self.mode == EditorMode::Full { - // if self.active_diagnostics.is_some() { - // self.dismiss_diagnostics(cx); - // return; - // } + if self.mode == EditorMode::Full { + if self.active_diagnostics.is_some() { + self.dismiss_diagnostics(cx); + return; + } - // if self.change_selections(Some(Autoscroll::fit()), cx, |s| s.try_cancel()) { - // return; - // } - // } + if self.change_selections(Some(Autoscroll::fit()), cx, |s| s.try_cancel()) { + return; + } + } - // cx.propagate(); - // } + cx.propagate(); + } pub fn handle_input(&mut self, text: &str, cx: &mut ViewContext) { let text: Arc = text.into(); @@ -2962,263 +2971,263 @@ impl Editor { }); } - // pub fn newline(&mut self, _: &Newline, cx: &mut ViewContext) { - // self.transact(cx, |this, cx| { - // let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = { - // let selections = this.selections.all::(cx); - // let multi_buffer = this.buffer.read(cx); - // let buffer = multi_buffer.snapshot(cx); - // selections - // .iter() - // .map(|selection| { - // let start_point = selection.start.to_point(&buffer); - // let mut indent = buffer.indent_size_for_line(start_point.row); - // indent.len = cmp::min(indent.len, start_point.column); - // let start = selection.start; - // let end = selection.end; - // let is_cursor = start == end; - // let language_scope = buffer.language_scope_at(start); - // let (comment_delimiter, insert_extra_newline) = if let Some(language) = - // &language_scope - // { - // let leading_whitespace_len = buffer - // .reversed_chars_at(start) - // .take_while(|c| c.is_whitespace() && *c != '\n') - // .map(|c| c.len_utf8()) - // .sum::(); + pub fn newline(&mut self, _: &Newline, cx: &mut ViewContext) { + self.transact(cx, |this, cx| { + let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = { + let selections = this.selections.all::(cx); + let multi_buffer = this.buffer.read(cx); + let buffer = multi_buffer.snapshot(cx); + selections + .iter() + .map(|selection| { + let start_point = selection.start.to_point(&buffer); + let mut indent = buffer.indent_size_for_line(start_point.row); + indent.len = cmp::min(indent.len, start_point.column); + let start = selection.start; + let end = selection.end; + let is_cursor = start == end; + let language_scope = buffer.language_scope_at(start); + let (comment_delimiter, insert_extra_newline) = if let Some(language) = + &language_scope + { + let leading_whitespace_len = buffer + .reversed_chars_at(start) + .take_while(|c| c.is_whitespace() && *c != '\n') + .map(|c| c.len_utf8()) + .sum::(); - // let trailing_whitespace_len = buffer - // .chars_at(end) - // .take_while(|c| c.is_whitespace() && *c != '\n') - // .map(|c| c.len_utf8()) - // .sum::(); + let trailing_whitespace_len = buffer + .chars_at(end) + .take_while(|c| c.is_whitespace() && *c != '\n') + .map(|c| c.len_utf8()) + .sum::(); - // let insert_extra_newline = - // language.brackets().any(|(pair, enabled)| { - // let pair_start = pair.start.trim_end(); - // let pair_end = pair.end.trim_start(); + let insert_extra_newline = + language.brackets().any(|(pair, enabled)| { + let pair_start = pair.start.trim_end(); + let pair_end = pair.end.trim_start(); - // enabled - // && pair.newline - // && buffer.contains_str_at( - // end + trailing_whitespace_len, - // pair_end, - // ) - // && buffer.contains_str_at( - // (start - leading_whitespace_len) - // .saturating_sub(pair_start.len()), - // pair_start, - // ) - // }); - // // Comment extension on newline is allowed only for cursor selections - // let comment_delimiter = language.line_comment_prefix().filter(|_| { - // let is_comment_extension_enabled = - // multi_buffer.settings_at(0, cx).extend_comment_on_newline; - // is_cursor && is_comment_extension_enabled - // }); - // let comment_delimiter = if let Some(delimiter) = comment_delimiter { - // buffer - // .buffer_line_for_row(start_point.row) - // .is_some_and(|(snapshot, range)| { - // let mut index_of_first_non_whitespace = 0; - // let line_starts_with_comment = snapshot - // .chars_for_range(range) - // .skip_while(|c| { - // let should_skip = c.is_whitespace(); - // if should_skip { - // index_of_first_non_whitespace += 1; - // } - // should_skip - // }) - // .take(delimiter.len()) - // .eq(delimiter.chars()); - // let cursor_is_placed_after_comment_marker = - // index_of_first_non_whitespace + delimiter.len() - // <= start_point.column as usize; - // line_starts_with_comment - // && cursor_is_placed_after_comment_marker - // }) - // .then(|| delimiter.clone()) - // } else { - // None - // }; - // (comment_delimiter, insert_extra_newline) - // } else { - // (None, false) - // }; + enabled + && pair.newline + && buffer.contains_str_at( + end + trailing_whitespace_len, + pair_end, + ) + && buffer.contains_str_at( + (start - leading_whitespace_len) + .saturating_sub(pair_start.len()), + pair_start, + ) + }); + // Comment extension on newline is allowed only for cursor selections + let comment_delimiter = language.line_comment_prefix().filter(|_| { + let is_comment_extension_enabled = + multi_buffer.settings_at(0, cx).extend_comment_on_newline; + is_cursor && is_comment_extension_enabled + }); + let comment_delimiter = if let Some(delimiter) = comment_delimiter { + buffer + .buffer_line_for_row(start_point.row) + .is_some_and(|(snapshot, range)| { + let mut index_of_first_non_whitespace = 0; + let line_starts_with_comment = snapshot + .chars_for_range(range) + .skip_while(|c| { + let should_skip = c.is_whitespace(); + if should_skip { + index_of_first_non_whitespace += 1; + } + should_skip + }) + .take(delimiter.len()) + .eq(delimiter.chars()); + let cursor_is_placed_after_comment_marker = + index_of_first_non_whitespace + delimiter.len() + <= start_point.column as usize; + line_starts_with_comment + && cursor_is_placed_after_comment_marker + }) + .then(|| delimiter.clone()) + } else { + None + }; + (comment_delimiter, insert_extra_newline) + } else { + (None, false) + }; - // let capacity_for_delimiter = comment_delimiter - // .as_deref() - // .map(str::len) - // .unwrap_or_default(); - // let mut new_text = - // String::with_capacity(1 + capacity_for_delimiter + indent.len as usize); - // new_text.push_str("\n"); - // new_text.extend(indent.chars()); - // if let Some(delimiter) = &comment_delimiter { - // new_text.push_str(&delimiter); - // } - // if insert_extra_newline { - // new_text = new_text.repeat(2); - // } + let capacity_for_delimiter = comment_delimiter + .as_deref() + .map(str::len) + .unwrap_or_default(); + let mut new_text = + String::with_capacity(1 + capacity_for_delimiter + indent.len as usize); + new_text.push_str("\n"); + new_text.extend(indent.chars()); + if let Some(delimiter) = &comment_delimiter { + new_text.push_str(&delimiter); + } + if insert_extra_newline { + new_text = new_text.repeat(2); + } - // let anchor = buffer.anchor_after(end); - // let new_selection = selection.map(|_| anchor); - // ( - // (start..end, new_text), - // (insert_extra_newline, new_selection), - // ) - // }) - // .unzip() - // }; + let anchor = buffer.anchor_after(end); + let new_selection = selection.map(|_| anchor); + ( + (start..end, new_text), + (insert_extra_newline, new_selection), + ) + }) + .unzip() + }; - // this.edit_with_autoindent(edits, cx); - // let buffer = this.buffer.read(cx).snapshot(cx); - // let new_selections = selection_fixup_info - // .into_iter() - // .map(|(extra_newline_inserted, new_selection)| { - // let mut cursor = new_selection.end.to_point(&buffer); - // if extra_newline_inserted { - // cursor.row -= 1; - // cursor.column = buffer.line_len(cursor.row); - // } - // new_selection.map(|_| cursor) - // }) - // .collect(); + this.edit_with_autoindent(edits, cx); + let buffer = this.buffer.read(cx).snapshot(cx); + let new_selections = selection_fixup_info + .into_iter() + .map(|(extra_newline_inserted, new_selection)| { + let mut cursor = new_selection.end.to_point(&buffer); + if extra_newline_inserted { + cursor.row -= 1; + cursor.column = buffer.line_len(cursor.row); + } + new_selection.map(|_| cursor) + }) + .collect(); - // this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections)); - // this.refresh_copilot_suggestions(true, cx); - // }); - // } + this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections)); + this.refresh_copilot_suggestions(true, cx); + }); + } - // pub fn newline_above(&mut self, _: &NewlineAbove, cx: &mut ViewContext) { - // let buffer = self.buffer.read(cx); - // let snapshot = buffer.snapshot(cx); + pub fn newline_above(&mut self, _: &NewlineAbove, cx: &mut ViewContext) { + let buffer = self.buffer.read(cx); + let snapshot = buffer.snapshot(cx); - // let mut edits = Vec::new(); - // let mut rows = Vec::new(); - // let mut rows_inserted = 0; + let mut edits = Vec::new(); + let mut rows = Vec::new(); + let mut rows_inserted = 0; - // for selection in self.selections.all_adjusted(cx) { - // let cursor = selection.head(); - // let row = cursor.row; + for selection in self.selections.all_adjusted(cx) { + let cursor = selection.head(); + let row = cursor.row; - // let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left); + let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left); - // let newline = "\n".to_string(); - // edits.push((start_of_line..start_of_line, newline)); + let newline = "\n".to_string(); + edits.push((start_of_line..start_of_line, newline)); - // rows.push(row + rows_inserted); - // rows_inserted += 1; - // } + rows.push(row + rows_inserted); + rows_inserted += 1; + } - // self.transact(cx, |editor, cx| { - // editor.edit(edits, cx); + self.transact(cx, |editor, cx| { + editor.edit(edits, cx); - // editor.change_selections(Some(Autoscroll::fit()), cx, |s| { - // let mut index = 0; - // s.move_cursors_with(|map, _, _| { - // let row = rows[index]; - // index += 1; + editor.change_selections(Some(Autoscroll::fit()), cx, |s| { + let mut index = 0; + s.move_cursors_with(|map, _, _| { + let row = rows[index]; + index += 1; - // let point = Point::new(row, 0); - // let boundary = map.next_line_boundary(point).1; - // let clipped = map.clip_point(boundary, Bias::Left); + let point = Point::new(row, 0); + let boundary = map.next_line_boundary(point).1; + let clipped = map.clip_point(boundary, Bias::Left); - // (clipped, SelectionGoal::None) - // }); - // }); + (clipped, SelectionGoal::None) + }); + }); - // let mut indent_edits = Vec::new(); - // let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx); - // for row in rows { - // let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx); - // for (row, indent) in indents { - // if indent.len == 0 { - // continue; - // } + let mut indent_edits = Vec::new(); + let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx); + for row in rows { + let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx); + for (row, indent) in indents { + if indent.len == 0 { + continue; + } - // let text = match indent.kind { - // IndentKind::Space => " ".repeat(indent.len as usize), - // IndentKind::Tab => "\t".repeat(indent.len as usize), - // }; - // let point = Point::new(row, 0); - // indent_edits.push((point..point, text)); - // } - // } - // editor.edit(indent_edits, cx); - // }); - // } + let text = match indent.kind { + IndentKind::Space => " ".repeat(indent.len as usize), + IndentKind::Tab => "\t".repeat(indent.len as usize), + }; + let point = Point::new(row, 0); + indent_edits.push((point..point, text)); + } + } + editor.edit(indent_edits, cx); + }); + } - // pub fn newline_below(&mut self, _: &NewlineBelow, cx: &mut ViewContext) { - // let buffer = self.buffer.read(cx); - // let snapshot = buffer.snapshot(cx); + pub fn newline_below(&mut self, _: &NewlineBelow, cx: &mut ViewContext) { + let buffer = self.buffer.read(cx); + let snapshot = buffer.snapshot(cx); - // let mut edits = Vec::new(); - // let mut rows = Vec::new(); - // let mut rows_inserted = 0; + let mut edits = Vec::new(); + let mut rows = Vec::new(); + let mut rows_inserted = 0; - // for selection in self.selections.all_adjusted(cx) { - // let cursor = selection.head(); - // let row = cursor.row; + for selection in self.selections.all_adjusted(cx) { + let cursor = selection.head(); + let row = cursor.row; - // let point = Point::new(row + 1, 0); - // let start_of_line = snapshot.clip_point(point, Bias::Left); + let point = Point::new(row + 1, 0); + let start_of_line = snapshot.clip_point(point, Bias::Left); - // let newline = "\n".to_string(); - // edits.push((start_of_line..start_of_line, newline)); + let newline = "\n".to_string(); + edits.push((start_of_line..start_of_line, newline)); - // rows_inserted += 1; - // rows.push(row + rows_inserted); - // } + rows_inserted += 1; + rows.push(row + rows_inserted); + } - // self.transact(cx, |editor, cx| { - // editor.edit(edits, cx); + self.transact(cx, |editor, cx| { + editor.edit(edits, cx); - // editor.change_selections(Some(Autoscroll::fit()), cx, |s| { - // let mut index = 0; - // s.move_cursors_with(|map, _, _| { - // let row = rows[index]; - // index += 1; + editor.change_selections(Some(Autoscroll::fit()), cx, |s| { + let mut index = 0; + s.move_cursors_with(|map, _, _| { + let row = rows[index]; + index += 1; - // let point = Point::new(row, 0); - // let boundary = map.next_line_boundary(point).1; - // let clipped = map.clip_point(boundary, Bias::Left); + let point = Point::new(row, 0); + let boundary = map.next_line_boundary(point).1; + let clipped = map.clip_point(boundary, Bias::Left); - // (clipped, SelectionGoal::None) - // }); - // }); + (clipped, SelectionGoal::None) + }); + }); - // let mut indent_edits = Vec::new(); - // let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx); - // for row in rows { - // let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx); - // for (row, indent) in indents { - // if indent.len == 0 { - // continue; - // } + let mut indent_edits = Vec::new(); + let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx); + for row in rows { + let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx); + for (row, indent) in indents { + if indent.len == 0 { + continue; + } - // let text = match indent.kind { - // IndentKind::Space => " ".repeat(indent.len as usize), - // IndentKind::Tab => "\t".repeat(indent.len as usize), - // }; - // let point = Point::new(row, 0); - // indent_edits.push((point..point, text)); - // } - // } - // editor.edit(indent_edits, cx); - // }); - // } + let text = match indent.kind { + IndentKind::Space => " ".repeat(indent.len as usize), + IndentKind::Tab => "\t".repeat(indent.len as usize), + }; + let point = Point::new(row, 0); + indent_edits.push((point..point, text)); + } + } + editor.edit(indent_edits, cx); + }); + } - // pub fn insert(&mut self, text: &str, cx: &mut ViewContext) { - // self.insert_with_autoindent_mode( - // text, - // Some(AutoindentMode::Block { - // original_indent_columns: Vec::new(), - // }), - // cx, - // ); - // } + pub fn insert(&mut self, text: &str, cx: &mut ViewContext) { + self.insert_with_autoindent_mode( + text, + Some(AutoindentMode::Block { + original_indent_columns: Vec::new(), + }), + cx, + ); + } fn insert_with_autoindent_mode( &mut self, @@ -3277,34 +3286,34 @@ impl Editor { } } - // /// If any empty selections is touching the start of its innermost containing autoclose - // /// region, expand it to select the brackets. - // fn select_autoclose_pair(&mut self, cx: &mut ViewContext) { - // let selections = self.selections.all::(cx); - // let buffer = self.buffer.read(cx).read(cx); - // let mut new_selections = Vec::new(); - // for (mut selection, region) in self.selections_with_autoclose_regions(selections, &buffer) { - // if let (Some(region), true) = (region, selection.is_empty()) { - // let mut range = region.range.to_offset(&buffer); - // if selection.start == range.start { - // if range.start >= region.pair.start.len() { - // range.start -= region.pair.start.len(); - // if buffer.contains_str_at(range.start, ®ion.pair.start) { - // if buffer.contains_str_at(range.end, ®ion.pair.end) { - // range.end += region.pair.end.len(); - // selection.start = range.start; - // selection.end = range.end; - // } - // } - // } - // } - // } - // new_selections.push(selection); - // } + /// If any empty selections is touching the start of its innermost containing autoclose + /// region, expand it to select the brackets. + fn select_autoclose_pair(&mut self, cx: &mut ViewContext) { + let selections = self.selections.all::(cx); + let buffer = self.buffer.read(cx).read(cx); + let mut new_selections = Vec::new(); + for (mut selection, region) in self.selections_with_autoclose_regions(selections, &buffer) { + if let (Some(region), true) = (region, selection.is_empty()) { + let mut range = region.range.to_offset(&buffer); + if selection.start == range.start { + if range.start >= region.pair.start.len() { + range.start -= region.pair.start.len(); + if buffer.contains_str_at(range.start, ®ion.pair.start) { + if buffer.contains_str_at(range.end, ®ion.pair.end) { + range.end += region.pair.end.len(); + selection.start = range.start; + selection.end = range.end; + } + } + } + } + } + new_selections.push(selection); + } - // drop(buffer); - // self.change_selections(None, cx, |selections| selections.select(new_selections)); - // } + drop(buffer); + self.change_selections(None, cx, |selections| selections.select(new_selections)); + } /// Iterate the given selections, and for each one, find the smallest surrounding /// autoclose region. This uses the ordering of the selections and the autoclose @@ -3378,18 +3387,16 @@ impl Editor { } } - // pub fn toggle_inlay_hints(&mut self, _: &ToggleInlayHints, cx: &mut ViewContext) { - // todo!(); - // // self.refresh_inlay_hints( - // // InlayHintRefreshReason::Toggle(!self.inlay_hint_cache.enabled), - // // cx, - // // ); - // } + pub fn toggle_inlay_hints(&mut self, _: &ToggleInlayHints, cx: &mut ViewContext) { + self.refresh_inlay_hints( + InlayHintRefreshReason::Toggle(!self.inlay_hint_cache.enabled), + cx, + ); + } - // pub fn inlay_hints_enabled(&self) -> bool { - // todo!(); - // self.inlay_hint_cache.enabled - // } + pub fn inlay_hints_enabled(&self) -> bool { + self.inlay_hint_cache.enabled + } fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut ViewContext) { if self.project.is_none() || self.mode != EditorMode::Full { @@ -4226,8 +4233,7 @@ impl Editor { } else { let is_copilot_disabled = self.refresh_copilot_suggestions(false, cx).is_none(); if is_copilot_disabled { - todo!(); - // cx.propagate(); + cx.propagate(); } } } @@ -4242,8 +4248,7 @@ impl Editor { } else { let is_copilot_disabled = self.refresh_copilot_suggestions(false, cx).is_none(); if is_copilot_disabled { - todo!(); - // cx.propagate(); + cx.propagate(); } } } @@ -4500,1224 +4505,1224 @@ impl Editor { context_menu } - // pub fn insert_snippet( - // &mut self, - // insertion_ranges: &[Range], - // snippet: Snippet, - // cx: &mut ViewContext, - // ) -> Result<()> { - // let tabstops = self.buffer.update(cx, |buffer, cx| { - // let snippet_text: Arc = snippet.text.clone().into(); - // buffer.edit( - // insertion_ranges - // .iter() - // .cloned() - // .map(|range| (range, snippet_text.clone())), - // Some(AutoindentMode::EachLine), - // cx, - // ); - - // let snapshot = &*buffer.read(cx); - // let snippet = &snippet; - // snippet - // .tabstops - // .iter() - // .map(|tabstop| { - // let mut tabstop_ranges = tabstop - // .iter() - // .flat_map(|tabstop_range| { - // let mut delta = 0_isize; - // insertion_ranges.iter().map(move |insertion_range| { - // let insertion_start = insertion_range.start as isize + delta; - // delta += - // snippet.text.len() as isize - insertion_range.len() as isize; - - // let start = snapshot.anchor_before( - // (insertion_start + tabstop_range.start) as usize, - // ); - // let end = snapshot - // .anchor_after((insertion_start + tabstop_range.end) as usize); - // start..end - // }) - // }) - // .collect::>(); - // tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot)); - // tabstop_ranges - // }) - // .collect::>() - // }); - - // if let Some(tabstop) = tabstops.first() { - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.select_ranges(tabstop.iter().cloned()); - // }); - // self.snippet_stack.push(SnippetState { - // active_index: 0, - // ranges: tabstops, - // }); - // } - - // Ok(()) - // } - - // pub fn move_to_next_snippet_tabstop(&mut self, cx: &mut ViewContext) -> bool { - // self.move_to_snippet_tabstop(Bias::Right, cx) - // } - - // pub fn move_to_prev_snippet_tabstop(&mut self, cx: &mut ViewContext) -> bool { - // self.move_to_snippet_tabstop(Bias::Left, cx) - // } - - // pub fn move_to_snippet_tabstop(&mut self, bias: Bias, cx: &mut ViewContext) -> bool { - // if let Some(mut snippet) = self.snippet_stack.pop() { - // match bias { - // Bias::Left => { - // if snippet.active_index > 0 { - // snippet.active_index -= 1; - // } else { - // self.snippet_stack.push(snippet); - // return false; - // } - // } - // Bias::Right => { - // if snippet.active_index + 1 < snippet.ranges.len() { - // snippet.active_index += 1; - // } else { - // self.snippet_stack.push(snippet); - // return false; - // } - // } - // } - // if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) { - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.select_anchor_ranges(current_ranges.iter().cloned()) - // }); - // // If snippet state is not at the last tabstop, push it back on the stack - // if snippet.active_index + 1 < snippet.ranges.len() { - // self.snippet_stack.push(snippet); - // } - // return true; - // } - // } - - // false - // } - - // pub fn clear(&mut self, cx: &mut ViewContext) { - // self.transact(cx, |this, cx| { - // this.select_all(&SelectAll, cx); - // this.insert("", cx); - // }); - // } - - // pub fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext) { - // self.transact(cx, |this, cx| { - // this.select_autoclose_pair(cx); - // let mut selections = this.selections.all::(cx); - // if !this.selections.line_mode { - // let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx)); - // for selection in &mut selections { - // if selection.is_empty() { - // let old_head = selection.head(); - // let mut new_head = - // movement::left(&display_map, old_head.to_display_point(&display_map)) - // .to_point(&display_map); - // if let Some((buffer, line_buffer_range)) = display_map - // .buffer_snapshot - // .buffer_line_for_row(old_head.row) - // { - // let indent_size = - // buffer.indent_size_for_line(line_buffer_range.start.row); - // let indent_len = match indent_size.kind { - // IndentKind::Space => { - // buffer.settings_at(line_buffer_range.start, cx).tab_size - // } - // IndentKind::Tab => NonZeroU32::new(1).unwrap(), - // }; - // if old_head.column <= indent_size.len && old_head.column > 0 { - // let indent_len = indent_len.get(); - // new_head = cmp::min( - // new_head, - // Point::new( - // old_head.row, - // ((old_head.column - 1) / indent_len) * indent_len, - // ), - // ); - // } - // } - - // selection.set_head(new_head, SelectionGoal::None); - // } - // } - // } - - // this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections)); - // this.insert("", cx); - // this.refresh_copilot_suggestions(true, cx); - // }); - // } - - // pub fn delete(&mut self, _: &Delete, cx: &mut ViewContext) { - // self.transact(cx, |this, cx| { - // this.change_selections(Some(Autoscroll::fit()), cx, |s| { - // let line_mode = s.line_mode; - // s.move_with(|map, selection| { - // if selection.is_empty() && !line_mode { - // let cursor = movement::right(map, selection.head()); - // selection.end = cursor; - // selection.reversed = true; - // selection.goal = SelectionGoal::None; - // } - // }) - // }); - // this.insert("", cx); - // this.refresh_copilot_suggestions(true, cx); - // }); - // } - - // pub fn tab_prev(&mut self, _: &TabPrev, cx: &mut ViewContext) { - // if self.move_to_prev_snippet_tabstop(cx) { - // return; - // } - - // self.outdent(&Outdent, cx); - // } - - // pub fn tab(&mut self, _: &Tab, cx: &mut ViewContext) { - // if self.move_to_next_snippet_tabstop(cx) { - // return; - // } - - // let mut selections = self.selections.all_adjusted(cx); - // let buffer = self.buffer.read(cx); - // let snapshot = buffer.snapshot(cx); - // let rows_iter = selections.iter().map(|s| s.head().row); - // let suggested_indents = snapshot.suggested_indents(rows_iter, cx); - - // let mut edits = Vec::new(); - // let mut prev_edited_row = 0; - // let mut row_delta = 0; - // for selection in &mut selections { - // if selection.start.row != prev_edited_row { - // row_delta = 0; - // } - // prev_edited_row = selection.end.row; - - // // If the selection is non-empty, then increase the indentation of the selected lines. - // if !selection.is_empty() { - // row_delta = - // Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx); - // continue; - // } - - // // If the selection is empty and the cursor is in the leading whitespace before the - // // suggested indentation, then auto-indent the line. - // let cursor = selection.head(); - // let current_indent = snapshot.indent_size_for_line(cursor.row); - // if let Some(suggested_indent) = suggested_indents.get(&cursor.row).copied() { - // if cursor.column < suggested_indent.len - // && cursor.column <= current_indent.len - // && current_indent.len <= suggested_indent.len - // { - // selection.start = Point::new(cursor.row, suggested_indent.len); - // selection.end = selection.start; - // if row_delta == 0 { - // edits.extend(Buffer::edit_for_indent_size_adjustment( - // cursor.row, - // current_indent, - // suggested_indent, - // )); - // row_delta = suggested_indent.len - current_indent.len; - // } - // continue; - // } - // } - - // // Accept copilot suggestion if there is only one selection and the cursor is not - // // in the leading whitespace. - // if self.selections.count() == 1 - // && cursor.column >= current_indent.len - // && self.has_active_copilot_suggestion(cx) - // { - // self.accept_copilot_suggestion(cx); - // return; - // } - - // // Otherwise, insert a hard or soft tab. - // let settings = buffer.settings_at(cursor, cx); - // let tab_size = if settings.hard_tabs { - // IndentSize::tab() - // } else { - // let tab_size = settings.tab_size.get(); - // let char_column = snapshot - // .text_for_range(Point::new(cursor.row, 0)..cursor) - // .flat_map(str::chars) - // .count() - // + row_delta as usize; - // let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size); - // IndentSize::spaces(chars_to_next_tab_stop) - // }; - // selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len); - // selection.end = selection.start; - // edits.push((cursor..cursor, tab_size.chars().collect::())); - // row_delta += tab_size.len; - // } - - // self.transact(cx, |this, cx| { - // this.buffer.update(cx, |b, cx| b.edit(edits, None, cx)); - // this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections)); - // this.refresh_copilot_suggestions(true, cx); - // }); - // } - - // pub fn indent(&mut self, _: &Indent, cx: &mut ViewContext) { - // let mut selections = self.selections.all::(cx); - // let mut prev_edited_row = 0; - // let mut row_delta = 0; - // let mut edits = Vec::new(); - // let buffer = self.buffer.read(cx); - // let snapshot = buffer.snapshot(cx); - // for selection in &mut selections { - // if selection.start.row != prev_edited_row { - // row_delta = 0; - // } - // prev_edited_row = selection.end.row; - - // row_delta = - // Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx); - // } - - // self.transact(cx, |this, cx| { - // this.buffer.update(cx, |b, cx| b.edit(edits, None, cx)); - // this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections)); - // }); - // } - - // fn indent_selection( - // buffer: &MultiBuffer, - // snapshot: &MultiBufferSnapshot, - // selection: &mut Selection, - // edits: &mut Vec<(Range, String)>, - // delta_for_start_row: u32, - // cx: &AppContext, - // ) -> u32 { - // let settings = buffer.settings_at(selection.start, cx); - // let tab_size = settings.tab_size.get(); - // let indent_kind = if settings.hard_tabs { - // IndentKind::Tab - // } else { - // IndentKind::Space - // }; - // let mut start_row = selection.start.row; - // let mut end_row = selection.end.row + 1; - - // // If a selection ends at the beginning of a line, don't indent - // // that last line. - // if selection.end.column == 0 { - // end_row -= 1; - // } - - // // Avoid re-indenting a row that has already been indented by a - // // previous selection, but still update this selection's column - // // to reflect that indentation. - // if delta_for_start_row > 0 { - // start_row += 1; - // selection.start.column += delta_for_start_row; - // if selection.end.row == selection.start.row { - // selection.end.column += delta_for_start_row; - // } - // } - - // let mut delta_for_end_row = 0; - // for row in start_row..end_row { - // let current_indent = snapshot.indent_size_for_line(row); - // let indent_delta = match (current_indent.kind, indent_kind) { - // (IndentKind::Space, IndentKind::Space) => { - // let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size); - // IndentSize::spaces(columns_to_next_tab_stop) - // } - // (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size), - // (_, IndentKind::Tab) => IndentSize::tab(), - // }; - - // let row_start = Point::new(row, 0); - // edits.push(( - // row_start..row_start, - // indent_delta.chars().collect::(), - // )); - - // // Update this selection's endpoints to reflect the indentation. - // if row == selection.start.row { - // selection.start.column += indent_delta.len; - // } - // if row == selection.end.row { - // selection.end.column += indent_delta.len; - // delta_for_end_row = indent_delta.len; - // } - // } - - // if selection.start.row == selection.end.row { - // delta_for_start_row + delta_for_end_row - // } else { - // delta_for_end_row - // } - // } - - // pub fn outdent(&mut self, _: &Outdent, cx: &mut ViewContext) { - // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - // let selections = self.selections.all::(cx); - // let mut deletion_ranges = Vec::new(); - // let mut last_outdent = None; - // { - // let buffer = self.buffer.read(cx); - // let snapshot = buffer.snapshot(cx); - // for selection in &selections { - // let settings = buffer.settings_at(selection.start, cx); - // let tab_size = settings.tab_size.get(); - // let mut rows = selection.spanned_rows(false, &display_map); - - // // Avoid re-outdenting a row that has already been outdented by a - // // previous selection. - // if let Some(last_row) = last_outdent { - // if last_row == rows.start { - // rows.start += 1; - // } - // } - - // for row in rows { - // let indent_size = snapshot.indent_size_for_line(row); - // if indent_size.len > 0 { - // let deletion_len = match indent_size.kind { - // IndentKind::Space => { - // let columns_to_prev_tab_stop = indent_size.len % tab_size; - // if columns_to_prev_tab_stop == 0 { - // tab_size - // } else { - // columns_to_prev_tab_stop - // } - // } - // IndentKind::Tab => 1, - // }; - // deletion_ranges.push(Point::new(row, 0)..Point::new(row, deletion_len)); - // last_outdent = Some(row); - // } - // } - // } - // } - - // self.transact(cx, |this, cx| { - // this.buffer.update(cx, |buffer, cx| { - // let empty_str: Arc = "".into(); - // buffer.edit( - // deletion_ranges - // .into_iter() - // .map(|range| (range, empty_str.clone())), - // None, - // cx, - // ); - // }); - // let selections = this.selections.all::(cx); - // this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections)); - // }); - // } - - // pub fn delete_line(&mut self, _: &DeleteLine, cx: &mut ViewContext) { - // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - // let selections = self.selections.all::(cx); - - // let mut new_cursors = Vec::new(); - // let mut edit_ranges = Vec::new(); - // let mut selections = selections.iter().peekable(); - // while let Some(selection) = selections.next() { - // let mut rows = selection.spanned_rows(false, &display_map); - // let goal_display_column = selection.head().to_display_point(&display_map).column(); - - // // Accumulate contiguous regions of rows that we want to delete. - // while let Some(next_selection) = selections.peek() { - // let next_rows = next_selection.spanned_rows(false, &display_map); - // if next_rows.start <= rows.end { - // rows.end = next_rows.end; - // selections.next().unwrap(); - // } else { - // break; - // } - // } - - // let buffer = &display_map.buffer_snapshot; - // let mut edit_start = Point::new(rows.start, 0).to_offset(buffer); - // let edit_end; - // let cursor_buffer_row; - // if buffer.max_point().row >= rows.end { - // // If there's a line after the range, delete the \n from the end of the row range - // // and position the cursor on the next line. - // edit_end = Point::new(rows.end, 0).to_offset(buffer); - // cursor_buffer_row = rows.end; - // } else { - // // If there isn't a line after the range, delete the \n from the line before the - // // start of the row range and position the cursor there. - // edit_start = edit_start.saturating_sub(1); - // edit_end = buffer.len(); - // cursor_buffer_row = rows.start.saturating_sub(1); - // } - - // let mut cursor = Point::new(cursor_buffer_row, 0).to_display_point(&display_map); - // *cursor.column_mut() = - // cmp::min(goal_display_column, display_map.line_len(cursor.row())); - - // new_cursors.push(( - // selection.id, - // buffer.anchor_after(cursor.to_point(&display_map)), - // )); - // edit_ranges.push(edit_start..edit_end); - // } - - // self.transact(cx, |this, cx| { - // let buffer = this.buffer.update(cx, |buffer, cx| { - // let empty_str: Arc = "".into(); - // buffer.edit( - // edit_ranges - // .into_iter() - // .map(|range| (range, empty_str.clone())), - // None, - // cx, - // ); - // buffer.snapshot(cx) - // }); - // let new_selections = new_cursors - // .into_iter() - // .map(|(id, cursor)| { - // let cursor = cursor.to_point(&buffer); - // Selection { - // id, - // start: cursor, - // end: cursor, - // reversed: false, - // goal: SelectionGoal::None, - // } - // }) - // .collect(); - - // this.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.select(new_selections); - // }); - // }); - // } - - // pub fn join_lines(&mut self, _: &JoinLines, cx: &mut ViewContext) { - // let mut row_ranges = Vec::>::new(); - // for selection in self.selections.all::(cx) { - // let start = selection.start.row; - // let end = if selection.start.row == selection.end.row { - // selection.start.row + 1 - // } else { - // selection.end.row - // }; - - // if let Some(last_row_range) = row_ranges.last_mut() { - // if start <= last_row_range.end { - // last_row_range.end = end; - // continue; - // } - // } - // row_ranges.push(start..end); - // } - - // let snapshot = self.buffer.read(cx).snapshot(cx); - // let mut cursor_positions = Vec::new(); - // for row_range in &row_ranges { - // let anchor = snapshot.anchor_before(Point::new( - // row_range.end - 1, - // snapshot.line_len(row_range.end - 1), - // )); - // cursor_positions.push(anchor.clone()..anchor); - // } - - // self.transact(cx, |this, cx| { - // for row_range in row_ranges.into_iter().rev() { - // for row in row_range.rev() { - // let end_of_line = Point::new(row, snapshot.line_len(row)); - // let indent = snapshot.indent_size_for_line(row + 1); - // let start_of_next_line = Point::new(row + 1, indent.len); - - // let replace = if snapshot.line_len(row + 1) > indent.len { - // " " - // } else { - // "" - // }; - - // this.buffer.update(cx, |buffer, cx| { - // buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx) - // }); - // } - // } - - // this.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.select_anchor_ranges(cursor_positions) - // }); - // }); - // } - - // pub fn sort_lines_case_sensitive( - // &mut self, - // _: &SortLinesCaseSensitive, - // cx: &mut ViewContext, - // ) { - // self.manipulate_lines(cx, |lines| lines.sort()) - // } - - // pub fn sort_lines_case_insensitive( - // &mut self, - // _: &SortLinesCaseInsensitive, - // cx: &mut ViewContext, - // ) { - // self.manipulate_lines(cx, |lines| lines.sort_by_key(|line| line.to_lowercase())) - // } - - // pub fn reverse_lines(&mut self, _: &ReverseLines, cx: &mut ViewContext) { - // self.manipulate_lines(cx, |lines| lines.reverse()) - // } - - // pub fn shuffle_lines(&mut self, _: &ShuffleLines, cx: &mut ViewContext) { - // self.manipulate_lines(cx, |lines| lines.shuffle(&mut thread_rng())) - // } - - // fn manipulate_lines(&mut self, cx: &mut ViewContext, mut callback: Fn) - // where - // Fn: FnMut(&mut [&str]), - // { - // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - // let buffer = self.buffer.read(cx).snapshot(cx); - - // let mut edits = Vec::new(); - - // let selections = self.selections.all::(cx); - // let mut selections = selections.iter().peekable(); - // let mut contiguous_row_selections = Vec::new(); - // let mut new_selections = Vec::new(); - - // while let Some(selection) = selections.next() { - // let (start_row, end_row) = consume_contiguous_rows( - // &mut contiguous_row_selections, - // selection, - // &display_map, - // &mut selections, - // ); - - // let start_point = Point::new(start_row, 0); - // let end_point = Point::new(end_row - 1, buffer.line_len(end_row - 1)); - // let text = buffer - // .text_for_range(start_point..end_point) - // .collect::(); - // let mut lines = text.split("\n").collect_vec(); - - // let lines_len = lines.len(); - // callback(&mut lines); - - // // This is a current limitation with selections. - // // If we wanted to support removing or adding lines, we'd need to fix the logic associated with selections. - // debug_assert!( - // lines.len() == lines_len, - // "callback should not change the number of lines" - // ); - - // edits.push((start_point..end_point, lines.join("\n"))); - // let start_anchor = buffer.anchor_after(start_point); - // let end_anchor = buffer.anchor_before(end_point); - - // // Make selection and push - // new_selections.push(Selection { - // id: selection.id, - // start: start_anchor.to_offset(&buffer), - // end: end_anchor.to_offset(&buffer), - // goal: SelectionGoal::None, - // reversed: selection.reversed, - // }); - // } - - // self.transact(cx, |this, cx| { - // this.buffer.update(cx, |buffer, cx| { - // buffer.edit(edits, None, cx); - // }); - - // this.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.select(new_selections); - // }); - - // this.request_autoscroll(Autoscroll::fit(), cx); - // }); - // } - - // pub fn convert_to_upper_case(&mut self, _: &ConvertToUpperCase, cx: &mut ViewContext) { - // self.manipulate_text(cx, |text| text.to_uppercase()) - // } - - // pub fn convert_to_lower_case(&mut self, _: &ConvertToLowerCase, cx: &mut ViewContext) { - // self.manipulate_text(cx, |text| text.to_lowercase()) - // } - - // pub fn convert_to_title_case(&mut self, _: &ConvertToTitleCase, cx: &mut ViewContext) { - // self.manipulate_text(cx, |text| { - // // Hack to get around the fact that to_case crate doesn't support '\n' as a word boundary - // // https://github.com/rutrum/convert-case/issues/16 - // text.split("\n") - // .map(|line| line.to_case(Case::Title)) - // .join("\n") - // }) - // } - - // pub fn convert_to_snake_case(&mut self, _: &ConvertToSnakeCase, cx: &mut ViewContext) { - // self.manipulate_text(cx, |text| text.to_case(Case::Snake)) - // } - - // pub fn convert_to_kebab_case(&mut self, _: &ConvertToKebabCase, cx: &mut ViewContext) { - // self.manipulate_text(cx, |text| text.to_case(Case::Kebab)) - // } - - // pub fn convert_to_upper_camel_case( - // &mut self, - // _: &ConvertToUpperCamelCase, - // cx: &mut ViewContext, - // ) { - // self.manipulate_text(cx, |text| { - // // Hack to get around the fact that to_case crate doesn't support '\n' as a word boundary - // // https://github.com/rutrum/convert-case/issues/16 - // text.split("\n") - // .map(|line| line.to_case(Case::UpperCamel)) - // .join("\n") - // }) - // } - - // pub fn convert_to_lower_camel_case( - // &mut self, - // _: &ConvertToLowerCamelCase, - // cx: &mut ViewContext, - // ) { - // self.manipulate_text(cx, |text| text.to_case(Case::Camel)) - // } - - // fn manipulate_text(&mut self, cx: &mut ViewContext, mut callback: Fn) - // where - // Fn: FnMut(&str) -> String, - // { - // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - // let buffer = self.buffer.read(cx).snapshot(cx); - - // let mut new_selections = Vec::new(); - // let mut edits = Vec::new(); - // let mut selection_adjustment = 0i32; - - // for selection in self.selections.all::(cx) { - // let selection_is_empty = selection.is_empty(); - - // let (start, end) = if selection_is_empty { - // let word_range = movement::surrounding_word( - // &display_map, - // selection.start.to_display_point(&display_map), - // ); - // let start = word_range.start.to_offset(&display_map, Bias::Left); - // let end = word_range.end.to_offset(&display_map, Bias::Left); - // (start, end) - // } else { - // (selection.start, selection.end) - // }; - - // let text = buffer.text_for_range(start..end).collect::(); - // let old_length = text.len() as i32; - // let text = callback(&text); - - // new_selections.push(Selection { - // start: (start as i32 - selection_adjustment) as usize, - // end: ((start + text.len()) as i32 - selection_adjustment) as usize, - // goal: SelectionGoal::None, - // ..selection - // }); - - // selection_adjustment += old_length - text.len() as i32; - - // edits.push((start..end, text)); - // } - - // self.transact(cx, |this, cx| { - // this.buffer.update(cx, |buffer, cx| { - // buffer.edit(edits, None, cx); - // }); - - // this.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.select(new_selections); - // }); - - // this.request_autoscroll(Autoscroll::fit(), cx); - // }); - // } - - // pub fn duplicate_line(&mut self, _: &DuplicateLine, cx: &mut ViewContext) { - // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - // let buffer = &display_map.buffer_snapshot; - // let selections = self.selections.all::(cx); - - // let mut edits = Vec::new(); - // let mut selections_iter = selections.iter().peekable(); - // while let Some(selection) = selections_iter.next() { - // // Avoid duplicating the same lines twice. - // let mut rows = selection.spanned_rows(false, &display_map); - - // while let Some(next_selection) = selections_iter.peek() { - // let next_rows = next_selection.spanned_rows(false, &display_map); - // if next_rows.start < rows.end { - // rows.end = next_rows.end; - // selections_iter.next().unwrap(); - // } else { - // break; - // } - // } - - // // Copy the text from the selected row region and splice it at the start of the region. - // let start = Point::new(rows.start, 0); - // let end = Point::new(rows.end - 1, buffer.line_len(rows.end - 1)); - // let text = buffer - // .text_for_range(start..end) - // .chain(Some("\n")) - // .collect::(); - // edits.push((start..start, text)); - // } - - // self.transact(cx, |this, cx| { - // this.buffer.update(cx, |buffer, cx| { - // buffer.edit(edits, None, cx); - // }); - - // this.request_autoscroll(Autoscroll::fit(), cx); - // }); - // } - - // pub fn move_line_up(&mut self, _: &MoveLineUp, cx: &mut ViewContext) { - // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - // let buffer = self.buffer.read(cx).snapshot(cx); - - // let mut edits = Vec::new(); - // let mut unfold_ranges = Vec::new(); - // let mut refold_ranges = Vec::new(); - - // let selections = self.selections.all::(cx); - // let mut selections = selections.iter().peekable(); - // let mut contiguous_row_selections = Vec::new(); - // let mut new_selections = Vec::new(); - - // while let Some(selection) = selections.next() { - // // Find all the selections that span a contiguous row range - // let (start_row, end_row) = consume_contiguous_rows( - // &mut contiguous_row_selections, - // selection, - // &display_map, - // &mut selections, - // ); - - // // Move the text spanned by the row range to be before the line preceding the row range - // if start_row > 0 { - // let range_to_move = Point::new(start_row - 1, buffer.line_len(start_row - 1)) - // ..Point::new(end_row - 1, buffer.line_len(end_row - 1)); - // let insertion_point = display_map - // .prev_line_boundary(Point::new(start_row - 1, 0)) - // .0; - - // // Don't move lines across excerpts - // if buffer - // .excerpt_boundaries_in_range(( - // Bound::Excluded(insertion_point), - // Bound::Included(range_to_move.end), - // )) - // .next() - // .is_none() - // { - // let text = buffer - // .text_for_range(range_to_move.clone()) - // .flat_map(|s| s.chars()) - // .skip(1) - // .chain(['\n']) - // .collect::(); - - // edits.push(( - // buffer.anchor_after(range_to_move.start) - // ..buffer.anchor_before(range_to_move.end), - // String::new(), - // )); - // let insertion_anchor = buffer.anchor_after(insertion_point); - // edits.push((insertion_anchor..insertion_anchor, text)); - - // let row_delta = range_to_move.start.row - insertion_point.row + 1; - - // // Move selections up - // new_selections.extend(contiguous_row_selections.drain(..).map( - // |mut selection| { - // selection.start.row -= row_delta; - // selection.end.row -= row_delta; - // selection - // }, - // )); - - // // Move folds up - // unfold_ranges.push(range_to_move.clone()); - // for fold in display_map.folds_in_range( - // buffer.anchor_before(range_to_move.start) - // ..buffer.anchor_after(range_to_move.end), - // ) { - // let mut start = fold.start.to_point(&buffer); - // let mut end = fold.end.to_point(&buffer); - // start.row -= row_delta; - // end.row -= row_delta; - // refold_ranges.push(start..end); - // } - // } - // } - - // // If we didn't move line(s), preserve the existing selections - // new_selections.append(&mut contiguous_row_selections); - // } - - // self.transact(cx, |this, cx| { - // this.unfold_ranges(unfold_ranges, true, true, cx); - // this.buffer.update(cx, |buffer, cx| { - // for (range, text) in edits { - // buffer.edit([(range, text)], None, cx); - // } - // }); - // this.fold_ranges(refold_ranges, true, cx); - // this.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.select(new_selections); - // }) - // }); - // } - - // pub fn move_line_down(&mut self, _: &MoveLineDown, cx: &mut ViewContext) { - // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - // let buffer = self.buffer.read(cx).snapshot(cx); - - // let mut edits = Vec::new(); - // let mut unfold_ranges = Vec::new(); - // let mut refold_ranges = Vec::new(); - - // let selections = self.selections.all::(cx); - // let mut selections = selections.iter().peekable(); - // let mut contiguous_row_selections = Vec::new(); - // let mut new_selections = Vec::new(); - - // while let Some(selection) = selections.next() { - // // Find all the selections that span a contiguous row range - // let (start_row, end_row) = consume_contiguous_rows( - // &mut contiguous_row_selections, - // selection, - // &display_map, - // &mut selections, - // ); - - // // Move the text spanned by the row range to be after the last line of the row range - // if end_row <= buffer.max_point().row { - // let range_to_move = Point::new(start_row, 0)..Point::new(end_row, 0); - // let insertion_point = display_map.next_line_boundary(Point::new(end_row, 0)).0; - - // // Don't move lines across excerpt boundaries - // if buffer - // .excerpt_boundaries_in_range(( - // Bound::Excluded(range_to_move.start), - // Bound::Included(insertion_point), - // )) - // .next() - // .is_none() - // { - // let mut text = String::from("\n"); - // text.extend(buffer.text_for_range(range_to_move.clone())); - // text.pop(); // Drop trailing newline - // edits.push(( - // buffer.anchor_after(range_to_move.start) - // ..buffer.anchor_before(range_to_move.end), - // String::new(), - // )); - // let insertion_anchor = buffer.anchor_after(insertion_point); - // edits.push((insertion_anchor..insertion_anchor, text)); - - // let row_delta = insertion_point.row - range_to_move.end.row + 1; - - // // Move selections down - // new_selections.extend(contiguous_row_selections.drain(..).map( - // |mut selection| { - // selection.start.row += row_delta; - // selection.end.row += row_delta; - // selection - // }, - // )); - - // // Move folds down - // unfold_ranges.push(range_to_move.clone()); - // for fold in display_map.folds_in_range( - // buffer.anchor_before(range_to_move.start) - // ..buffer.anchor_after(range_to_move.end), - // ) { - // let mut start = fold.start.to_point(&buffer); - // let mut end = fold.end.to_point(&buffer); - // start.row += row_delta; - // end.row += row_delta; - // refold_ranges.push(start..end); - // } - // } - // } - - // // If we didn't move line(s), preserve the existing selections - // new_selections.append(&mut contiguous_row_selections); - // } - - // self.transact(cx, |this, cx| { - // this.unfold_ranges(unfold_ranges, true, true, cx); - // this.buffer.update(cx, |buffer, cx| { - // for (range, text) in edits { - // buffer.edit([(range, text)], None, cx); - // } - // }); - // this.fold_ranges(refold_ranges, true, cx); - // this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections)); - // }); - // } - - // pub fn transpose(&mut self, _: &Transpose, cx: &mut ViewContext) { - // let text_layout_details = &self.text_layout_details(cx); - // self.transact(cx, |this, cx| { - // let edits = this.change_selections(Some(Autoscroll::fit()), cx, |s| { - // let mut edits: Vec<(Range, String)> = Default::default(); - // let line_mode = s.line_mode; - // s.move_with(|display_map, selection| { - // if !selection.is_empty() || line_mode { - // return; - // } - - // let mut head = selection.head(); - // let mut transpose_offset = head.to_offset(display_map, Bias::Right); - // if head.column() == display_map.line_len(head.row()) { - // transpose_offset = display_map - // .buffer_snapshot - // .clip_offset(transpose_offset.saturating_sub(1), Bias::Left); - // } - - // if transpose_offset == 0 { - // return; - // } - - // *head.column_mut() += 1; - // head = display_map.clip_point(head, Bias::Right); - // let goal = SelectionGoal::HorizontalPosition( - // display_map.x_for_point(head, &text_layout_details), - // ); - // selection.collapse_to(head, goal); - - // let transpose_start = display_map - // .buffer_snapshot - // .clip_offset(transpose_offset.saturating_sub(1), Bias::Left); - // if edits.last().map_or(true, |e| e.0.end <= transpose_start) { - // let transpose_end = display_map - // .buffer_snapshot - // .clip_offset(transpose_offset + 1, Bias::Right); - // if let Some(ch) = - // display_map.buffer_snapshot.chars_at(transpose_start).next() - // { - // edits.push((transpose_start..transpose_offset, String::new())); - // edits.push((transpose_end..transpose_end, ch.to_string())); - // } - // } - // }); - // edits - // }); - // this.buffer - // .update(cx, |buffer, cx| buffer.edit(edits, None, cx)); - // let selections = this.selections.all::(cx); - // this.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.select(selections); - // }); - // }); - // } - - // pub fn cut(&mut self, _: &Cut, cx: &mut ViewContext) { - // let mut text = String::new(); - // let buffer = self.buffer.read(cx).snapshot(cx); - // let mut selections = self.selections.all::(cx); - // let mut clipboard_selections = Vec::with_capacity(selections.len()); - // { - // let max_point = buffer.max_point(); - // let mut is_first = true; - // for selection in &mut selections { - // let is_entire_line = selection.is_empty() || self.selections.line_mode; - // if is_entire_line { - // selection.start = Point::new(selection.start.row, 0); - // selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0)); - // selection.goal = SelectionGoal::None; - // } - // if is_first { - // is_first = false; - // } else { - // text += "\n"; - // } - // let mut len = 0; - // for chunk in buffer.text_for_range(selection.start..selection.end) { - // text.push_str(chunk); - // len += chunk.len(); - // } - // clipboard_selections.push(ClipboardSelection { - // len, - // is_entire_line, - // first_line_indent: buffer.indent_size_for_line(selection.start.row).len, - // }); - // } - // } - - // self.transact(cx, |this, cx| { - // this.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.select(selections); - // }); - // this.insert("", cx); - // cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections)); - // }); - // } - - // pub fn copy(&mut self, _: &Copy, cx: &mut ViewContext) { - // let selections = self.selections.all::(cx); - // let buffer = self.buffer.read(cx).read(cx); - // let mut text = String::new(); - - // let mut clipboard_selections = Vec::with_capacity(selections.len()); - // { - // let max_point = buffer.max_point(); - // let mut is_first = true; - // for selection in selections.iter() { - // let mut start = selection.start; - // let mut end = selection.end; - // let is_entire_line = selection.is_empty() || self.selections.line_mode; - // if is_entire_line { - // start = Point::new(start.row, 0); - // end = cmp::min(max_point, Point::new(end.row + 1, 0)); - // } - // if is_first { - // is_first = false; - // } else { - // text += "\n"; - // } - // let mut len = 0; - // for chunk in buffer.text_for_range(start..end) { - // text.push_str(chunk); - // len += chunk.len(); - // } - // clipboard_selections.push(ClipboardSelection { - // len, - // is_entire_line, - // first_line_indent: buffer.indent_size_for_line(start.row).len, - // }); - // } - // } - - // cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections)); - // } - - // pub fn paste(&mut self, _: &Paste, cx: &mut ViewContext) { - // self.transact(cx, |this, cx| { - // if let Some(item) = cx.read_from_clipboard() { - // let clipboard_text = Cow::Borrowed(item.text()); - // if let Some(mut clipboard_selections) = item.metadata::>() { - // let old_selections = this.selections.all::(cx); - // let all_selections_were_entire_line = - // clipboard_selections.iter().all(|s| s.is_entire_line); - // let first_selection_indent_column = - // clipboard_selections.first().map(|s| s.first_line_indent); - // if clipboard_selections.len() != old_selections.len() { - // clipboard_selections.drain(..); - // } - - // this.buffer.update(cx, |buffer, cx| { - // let snapshot = buffer.read(cx); - // let mut start_offset = 0; - // let mut edits = Vec::new(); - // let mut original_indent_columns = Vec::new(); - // let line_mode = this.selections.line_mode; - // for (ix, selection) in old_selections.iter().enumerate() { - // let to_insert; - // let entire_line; - // let original_indent_column; - // if let Some(clipboard_selection) = clipboard_selections.get(ix) { - // let end_offset = start_offset + clipboard_selection.len; - // to_insert = &clipboard_text[start_offset..end_offset]; - // entire_line = clipboard_selection.is_entire_line; - // start_offset = end_offset + 1; - // original_indent_column = - // Some(clipboard_selection.first_line_indent); - // } else { - // to_insert = clipboard_text.as_str(); - // entire_line = all_selections_were_entire_line; - // original_indent_column = first_selection_indent_column - // } - - // // If the corresponding selection was empty when this slice of the - // // clipboard text was written, then the entire line containing the - // // selection was copied. If this selection is also currently empty, - // // then paste the line before the current line of the buffer. - // let range = if selection.is_empty() && !line_mode && entire_line { - // let column = selection.start.to_point(&snapshot).column as usize; - // let line_start = selection.start - column; - // line_start..line_start - // } else { - // selection.range() - // }; - - // edits.push((range, to_insert)); - // original_indent_columns.extend(original_indent_column); - // } - // drop(snapshot); - - // buffer.edit( - // edits, - // Some(AutoindentMode::Block { - // original_indent_columns, - // }), - // cx, - // ); - // }); - - // let selections = this.selections.all::(cx); - // this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections)); - // } else { - // this.insert(&clipboard_text, cx); - // } - // } - // }); - // } - - // pub fn undo(&mut self, _: &Undo, cx: &mut ViewContext) { - // if let Some(tx_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) { - // if let Some((selections, _)) = self.selection_history.transaction(tx_id).cloned() { - // self.change_selections(None, cx, |s| { - // s.select_anchors(selections.to_vec()); - // }); - // } - // self.request_autoscroll(Autoscroll::fit(), cx); - // self.unmark_text(cx); - // self.refresh_copilot_suggestions(true, cx); - // cx.emit(Event::Edited); - // } - // } - - // pub fn redo(&mut self, _: &Redo, cx: &mut ViewContext) { - // if let Some(tx_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) { - // if let Some((_, Some(selections))) = self.selection_history.transaction(tx_id).cloned() - // { - // self.change_selections(None, cx, |s| { - // s.select_anchors(selections.to_vec()); - // }); - // } - // self.request_autoscroll(Autoscroll::fit(), cx); - // self.unmark_text(cx); - // self.refresh_copilot_suggestions(true, cx); - // cx.emit(Event::Edited); - // } - // } - - // pub fn finalize_last_transaction(&mut self, cx: &mut ViewContext) { - // self.buffer - // .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx)); - // } + pub fn insert_snippet( + &mut self, + insertion_ranges: &[Range], + snippet: Snippet, + cx: &mut ViewContext, + ) -> Result<()> { + let tabstops = self.buffer.update(cx, |buffer, cx| { + let snippet_text: Arc = snippet.text.clone().into(); + buffer.edit( + insertion_ranges + .iter() + .cloned() + .map(|range| (range, snippet_text.clone())), + Some(AutoindentMode::EachLine), + cx, + ); + + let snapshot = &*buffer.read(cx); + let snippet = &snippet; + snippet + .tabstops + .iter() + .map(|tabstop| { + let mut tabstop_ranges = tabstop + .iter() + .flat_map(|tabstop_range| { + let mut delta = 0_isize; + insertion_ranges.iter().map(move |insertion_range| { + let insertion_start = insertion_range.start as isize + delta; + delta += + snippet.text.len() as isize - insertion_range.len() as isize; + + let start = snapshot.anchor_before( + (insertion_start + tabstop_range.start) as usize, + ); + let end = snapshot + .anchor_after((insertion_start + tabstop_range.end) as usize); + start..end + }) + }) + .collect::>(); + tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot)); + tabstop_ranges + }) + .collect::>() + }); + + if let Some(tabstop) = tabstops.first() { + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select_ranges(tabstop.iter().cloned()); + }); + self.snippet_stack.push(SnippetState { + active_index: 0, + ranges: tabstops, + }); + } + + Ok(()) + } + + pub fn move_to_next_snippet_tabstop(&mut self, cx: &mut ViewContext) -> bool { + self.move_to_snippet_tabstop(Bias::Right, cx) + } + + pub fn move_to_prev_snippet_tabstop(&mut self, cx: &mut ViewContext) -> bool { + self.move_to_snippet_tabstop(Bias::Left, cx) + } + + pub fn move_to_snippet_tabstop(&mut self, bias: Bias, cx: &mut ViewContext) -> bool { + if let Some(mut snippet) = self.snippet_stack.pop() { + match bias { + Bias::Left => { + if snippet.active_index > 0 { + snippet.active_index -= 1; + } else { + self.snippet_stack.push(snippet); + return false; + } + } + Bias::Right => { + if snippet.active_index + 1 < snippet.ranges.len() { + snippet.active_index += 1; + } else { + self.snippet_stack.push(snippet); + return false; + } + } + } + if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) { + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select_anchor_ranges(current_ranges.iter().cloned()) + }); + // If snippet state is not at the last tabstop, push it back on the stack + if snippet.active_index + 1 < snippet.ranges.len() { + self.snippet_stack.push(snippet); + } + return true; + } + } + + false + } + + pub fn clear(&mut self, cx: &mut ViewContext) { + self.transact(cx, |this, cx| { + this.select_all(&SelectAll, cx); + this.insert("", cx); + }); + } + + pub fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext) { + self.transact(cx, |this, cx| { + this.select_autoclose_pair(cx); + let mut selections = this.selections.all::(cx); + if !this.selections.line_mode { + let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx)); + for selection in &mut selections { + if selection.is_empty() { + let old_head = selection.head(); + let mut new_head = + movement::left(&display_map, old_head.to_display_point(&display_map)) + .to_point(&display_map); + if let Some((buffer, line_buffer_range)) = display_map + .buffer_snapshot + .buffer_line_for_row(old_head.row) + { + let indent_size = + buffer.indent_size_for_line(line_buffer_range.start.row); + let indent_len = match indent_size.kind { + IndentKind::Space => { + buffer.settings_at(line_buffer_range.start, cx).tab_size + } + IndentKind::Tab => NonZeroU32::new(1).unwrap(), + }; + if old_head.column <= indent_size.len && old_head.column > 0 { + let indent_len = indent_len.get(); + new_head = cmp::min( + new_head, + Point::new( + old_head.row, + ((old_head.column - 1) / indent_len) * indent_len, + ), + ); + } + } + + selection.set_head(new_head, SelectionGoal::None); + } + } + } + + this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections)); + this.insert("", cx); + this.refresh_copilot_suggestions(true, cx); + }); + } + + pub fn delete(&mut self, _: &Delete, cx: &mut ViewContext) { + self.transact(cx, |this, cx| { + this.change_selections(Some(Autoscroll::fit()), cx, |s| { + let line_mode = s.line_mode; + s.move_with(|map, selection| { + if selection.is_empty() && !line_mode { + let cursor = movement::right(map, selection.head()); + selection.end = cursor; + selection.reversed = true; + selection.goal = SelectionGoal::None; + } + }) + }); + this.insert("", cx); + this.refresh_copilot_suggestions(true, cx); + }); + } + + pub fn tab_prev(&mut self, _: &TabPrev, cx: &mut ViewContext) { + if self.move_to_prev_snippet_tabstop(cx) { + return; + } + + self.outdent(&Outdent, cx); + } + + pub fn tab(&mut self, _: &Tab, cx: &mut ViewContext) { + if self.move_to_next_snippet_tabstop(cx) { + return; + } + + let mut selections = self.selections.all_adjusted(cx); + let buffer = self.buffer.read(cx); + let snapshot = buffer.snapshot(cx); + let rows_iter = selections.iter().map(|s| s.head().row); + let suggested_indents = snapshot.suggested_indents(rows_iter, cx); + + let mut edits = Vec::new(); + let mut prev_edited_row = 0; + let mut row_delta = 0; + for selection in &mut selections { + if selection.start.row != prev_edited_row { + row_delta = 0; + } + prev_edited_row = selection.end.row; + + // If the selection is non-empty, then increase the indentation of the selected lines. + if !selection.is_empty() { + row_delta = + Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx); + continue; + } + + // If the selection is empty and the cursor is in the leading whitespace before the + // suggested indentation, then auto-indent the line. + let cursor = selection.head(); + let current_indent = snapshot.indent_size_for_line(cursor.row); + if let Some(suggested_indent) = suggested_indents.get(&cursor.row).copied() { + if cursor.column < suggested_indent.len + && cursor.column <= current_indent.len + && current_indent.len <= suggested_indent.len + { + selection.start = Point::new(cursor.row, suggested_indent.len); + selection.end = selection.start; + if row_delta == 0 { + edits.extend(Buffer::edit_for_indent_size_adjustment( + cursor.row, + current_indent, + suggested_indent, + )); + row_delta = suggested_indent.len - current_indent.len; + } + continue; + } + } + + // Accept copilot suggestion if there is only one selection and the cursor is not + // in the leading whitespace. + if self.selections.count() == 1 + && cursor.column >= current_indent.len + && self.has_active_copilot_suggestion(cx) + { + self.accept_copilot_suggestion(cx); + return; + } + + // Otherwise, insert a hard or soft tab. + let settings = buffer.settings_at(cursor, cx); + let tab_size = if settings.hard_tabs { + IndentSize::tab() + } else { + let tab_size = settings.tab_size.get(); + let char_column = snapshot + .text_for_range(Point::new(cursor.row, 0)..cursor) + .flat_map(str::chars) + .count() + + row_delta as usize; + let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size); + IndentSize::spaces(chars_to_next_tab_stop) + }; + selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len); + selection.end = selection.start; + edits.push((cursor..cursor, tab_size.chars().collect::())); + row_delta += tab_size.len; + } + + self.transact(cx, |this, cx| { + this.buffer.update(cx, |b, cx| b.edit(edits, None, cx)); + this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections)); + this.refresh_copilot_suggestions(true, cx); + }); + } + + pub fn indent(&mut self, _: &Indent, cx: &mut ViewContext) { + let mut selections = self.selections.all::(cx); + let mut prev_edited_row = 0; + let mut row_delta = 0; + let mut edits = Vec::new(); + let buffer = self.buffer.read(cx); + let snapshot = buffer.snapshot(cx); + for selection in &mut selections { + if selection.start.row != prev_edited_row { + row_delta = 0; + } + prev_edited_row = selection.end.row; + + row_delta = + Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx); + } + + self.transact(cx, |this, cx| { + this.buffer.update(cx, |b, cx| b.edit(edits, None, cx)); + this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections)); + }); + } + + fn indent_selection( + buffer: &MultiBuffer, + snapshot: &MultiBufferSnapshot, + selection: &mut Selection, + edits: &mut Vec<(Range, String)>, + delta_for_start_row: u32, + cx: &AppContext, + ) -> u32 { + let settings = buffer.settings_at(selection.start, cx); + let tab_size = settings.tab_size.get(); + let indent_kind = if settings.hard_tabs { + IndentKind::Tab + } else { + IndentKind::Space + }; + let mut start_row = selection.start.row; + let mut end_row = selection.end.row + 1; + + // If a selection ends at the beginning of a line, don't indent + // that last line. + if selection.end.column == 0 { + end_row -= 1; + } + + // Avoid re-indenting a row that has already been indented by a + // previous selection, but still update this selection's column + // to reflect that indentation. + if delta_for_start_row > 0 { + start_row += 1; + selection.start.column += delta_for_start_row; + if selection.end.row == selection.start.row { + selection.end.column += delta_for_start_row; + } + } + + let mut delta_for_end_row = 0; + for row in start_row..end_row { + let current_indent = snapshot.indent_size_for_line(row); + let indent_delta = match (current_indent.kind, indent_kind) { + (IndentKind::Space, IndentKind::Space) => { + let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size); + IndentSize::spaces(columns_to_next_tab_stop) + } + (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size), + (_, IndentKind::Tab) => IndentSize::tab(), + }; + + let row_start = Point::new(row, 0); + edits.push(( + row_start..row_start, + indent_delta.chars().collect::(), + )); + + // Update this selection's endpoints to reflect the indentation. + if row == selection.start.row { + selection.start.column += indent_delta.len; + } + if row == selection.end.row { + selection.end.column += indent_delta.len; + delta_for_end_row = indent_delta.len; + } + } + + if selection.start.row == selection.end.row { + delta_for_start_row + delta_for_end_row + } else { + delta_for_end_row + } + } + + pub fn outdent(&mut self, _: &Outdent, cx: &mut ViewContext) { + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let selections = self.selections.all::(cx); + let mut deletion_ranges = Vec::new(); + let mut last_outdent = None; + { + let buffer = self.buffer.read(cx); + let snapshot = buffer.snapshot(cx); + for selection in &selections { + let settings = buffer.settings_at(selection.start, cx); + let tab_size = settings.tab_size.get(); + let mut rows = selection.spanned_rows(false, &display_map); + + // Avoid re-outdenting a row that has already been outdented by a + // previous selection. + if let Some(last_row) = last_outdent { + if last_row == rows.start { + rows.start += 1; + } + } + + for row in rows { + let indent_size = snapshot.indent_size_for_line(row); + if indent_size.len > 0 { + let deletion_len = match indent_size.kind { + IndentKind::Space => { + let columns_to_prev_tab_stop = indent_size.len % tab_size; + if columns_to_prev_tab_stop == 0 { + tab_size + } else { + columns_to_prev_tab_stop + } + } + IndentKind::Tab => 1, + }; + deletion_ranges.push(Point::new(row, 0)..Point::new(row, deletion_len)); + last_outdent = Some(row); + } + } + } + } + + self.transact(cx, |this, cx| { + this.buffer.update(cx, |buffer, cx| { + let empty_str: Arc = "".into(); + buffer.edit( + deletion_ranges + .into_iter() + .map(|range| (range, empty_str.clone())), + None, + cx, + ); + }); + let selections = this.selections.all::(cx); + this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections)); + }); + } + + pub fn delete_line(&mut self, _: &DeleteLine, cx: &mut ViewContext) { + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let selections = self.selections.all::(cx); + + let mut new_cursors = Vec::new(); + let mut edit_ranges = Vec::new(); + let mut selections = selections.iter().peekable(); + while let Some(selection) = selections.next() { + let mut rows = selection.spanned_rows(false, &display_map); + let goal_display_column = selection.head().to_display_point(&display_map).column(); + + // Accumulate contiguous regions of rows that we want to delete. + while let Some(next_selection) = selections.peek() { + let next_rows = next_selection.spanned_rows(false, &display_map); + if next_rows.start <= rows.end { + rows.end = next_rows.end; + selections.next().unwrap(); + } else { + break; + } + } + + let buffer = &display_map.buffer_snapshot; + let mut edit_start = Point::new(rows.start, 0).to_offset(buffer); + let edit_end; + let cursor_buffer_row; + if buffer.max_point().row >= rows.end { + // If there's a line after the range, delete the \n from the end of the row range + // and position the cursor on the next line. + edit_end = Point::new(rows.end, 0).to_offset(buffer); + cursor_buffer_row = rows.end; + } else { + // If there isn't a line after the range, delete the \n from the line before the + // start of the row range and position the cursor there. + edit_start = edit_start.saturating_sub(1); + edit_end = buffer.len(); + cursor_buffer_row = rows.start.saturating_sub(1); + } + + let mut cursor = Point::new(cursor_buffer_row, 0).to_display_point(&display_map); + *cursor.column_mut() = + cmp::min(goal_display_column, display_map.line_len(cursor.row())); + + new_cursors.push(( + selection.id, + buffer.anchor_after(cursor.to_point(&display_map)), + )); + edit_ranges.push(edit_start..edit_end); + } + + self.transact(cx, |this, cx| { + let buffer = this.buffer.update(cx, |buffer, cx| { + let empty_str: Arc = "".into(); + buffer.edit( + edit_ranges + .into_iter() + .map(|range| (range, empty_str.clone())), + None, + cx, + ); + buffer.snapshot(cx) + }); + let new_selections = new_cursors + .into_iter() + .map(|(id, cursor)| { + let cursor = cursor.to_point(&buffer); + Selection { + id, + start: cursor, + end: cursor, + reversed: false, + goal: SelectionGoal::None, + } + }) + .collect(); + + this.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select(new_selections); + }); + }); + } + + pub fn join_lines(&mut self, _: &JoinLines, cx: &mut ViewContext) { + let mut row_ranges = Vec::>::new(); + for selection in self.selections.all::(cx) { + let start = selection.start.row; + let end = if selection.start.row == selection.end.row { + selection.start.row + 1 + } else { + selection.end.row + }; + + if let Some(last_row_range) = row_ranges.last_mut() { + if start <= last_row_range.end { + last_row_range.end = end; + continue; + } + } + row_ranges.push(start..end); + } + + let snapshot = self.buffer.read(cx).snapshot(cx); + let mut cursor_positions = Vec::new(); + for row_range in &row_ranges { + let anchor = snapshot.anchor_before(Point::new( + row_range.end - 1, + snapshot.line_len(row_range.end - 1), + )); + cursor_positions.push(anchor.clone()..anchor); + } + + self.transact(cx, |this, cx| { + for row_range in row_ranges.into_iter().rev() { + for row in row_range.rev() { + let end_of_line = Point::new(row, snapshot.line_len(row)); + let indent = snapshot.indent_size_for_line(row + 1); + let start_of_next_line = Point::new(row + 1, indent.len); + + let replace = if snapshot.line_len(row + 1) > indent.len { + " " + } else { + "" + }; + + this.buffer.update(cx, |buffer, cx| { + buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx) + }); + } + } + + this.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select_anchor_ranges(cursor_positions) + }); + }); + } + + pub fn sort_lines_case_sensitive( + &mut self, + _: &SortLinesCaseSensitive, + cx: &mut ViewContext, + ) { + self.manipulate_lines(cx, |lines| lines.sort()) + } + + pub fn sort_lines_case_insensitive( + &mut self, + _: &SortLinesCaseInsensitive, + cx: &mut ViewContext, + ) { + self.manipulate_lines(cx, |lines| lines.sort_by_key(|line| line.to_lowercase())) + } + + pub fn reverse_lines(&mut self, _: &ReverseLines, cx: &mut ViewContext) { + self.manipulate_lines(cx, |lines| lines.reverse()) + } + + pub fn shuffle_lines(&mut self, _: &ShuffleLines, cx: &mut ViewContext) { + self.manipulate_lines(cx, |lines| lines.shuffle(&mut thread_rng())) + } + + fn manipulate_lines(&mut self, cx: &mut ViewContext, mut callback: Fn) + where + Fn: FnMut(&mut [&str]), + { + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let buffer = self.buffer.read(cx).snapshot(cx); + + let mut edits = Vec::new(); + + let selections = self.selections.all::(cx); + let mut selections = selections.iter().peekable(); + let mut contiguous_row_selections = Vec::new(); + let mut new_selections = Vec::new(); + + while let Some(selection) = selections.next() { + let (start_row, end_row) = consume_contiguous_rows( + &mut contiguous_row_selections, + selection, + &display_map, + &mut selections, + ); + + let start_point = Point::new(start_row, 0); + let end_point = Point::new(end_row - 1, buffer.line_len(end_row - 1)); + let text = buffer + .text_for_range(start_point..end_point) + .collect::(); + let mut lines = text.split("\n").collect_vec(); + + let lines_len = lines.len(); + callback(&mut lines); + + // This is a current limitation with selections. + // If we wanted to support removing or adding lines, we'd need to fix the logic associated with selections. + debug_assert!( + lines.len() == lines_len, + "callback should not change the number of lines" + ); + + edits.push((start_point..end_point, lines.join("\n"))); + let start_anchor = buffer.anchor_after(start_point); + let end_anchor = buffer.anchor_before(end_point); + + // Make selection and push + new_selections.push(Selection { + id: selection.id, + start: start_anchor.to_offset(&buffer), + end: end_anchor.to_offset(&buffer), + goal: SelectionGoal::None, + reversed: selection.reversed, + }); + } + + self.transact(cx, |this, cx| { + this.buffer.update(cx, |buffer, cx| { + buffer.edit(edits, None, cx); + }); + + this.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select(new_selections); + }); + + this.request_autoscroll(Autoscroll::fit(), cx); + }); + } + + pub fn convert_to_upper_case(&mut self, _: &ConvertToUpperCase, cx: &mut ViewContext) { + self.manipulate_text(cx, |text| text.to_uppercase()) + } + + pub fn convert_to_lower_case(&mut self, _: &ConvertToLowerCase, cx: &mut ViewContext) { + self.manipulate_text(cx, |text| text.to_lowercase()) + } + + pub fn convert_to_title_case(&mut self, _: &ConvertToTitleCase, cx: &mut ViewContext) { + self.manipulate_text(cx, |text| { + // Hack to get around the fact that to_case crate doesn't support '\n' as a word boundary + // https://github.com/rutrum/convert-case/issues/16 + text.split("\n") + .map(|line| line.to_case(Case::Title)) + .join("\n") + }) + } + + pub fn convert_to_snake_case(&mut self, _: &ConvertToSnakeCase, cx: &mut ViewContext) { + self.manipulate_text(cx, |text| text.to_case(Case::Snake)) + } + + pub fn convert_to_kebab_case(&mut self, _: &ConvertToKebabCase, cx: &mut ViewContext) { + self.manipulate_text(cx, |text| text.to_case(Case::Kebab)) + } + + pub fn convert_to_upper_camel_case( + &mut self, + _: &ConvertToUpperCamelCase, + cx: &mut ViewContext, + ) { + self.manipulate_text(cx, |text| { + // Hack to get around the fact that to_case crate doesn't support '\n' as a word boundary + // https://github.com/rutrum/convert-case/issues/16 + text.split("\n") + .map(|line| line.to_case(Case::UpperCamel)) + .join("\n") + }) + } + + pub fn convert_to_lower_camel_case( + &mut self, + _: &ConvertToLowerCamelCase, + cx: &mut ViewContext, + ) { + self.manipulate_text(cx, |text| text.to_case(Case::Camel)) + } + + fn manipulate_text(&mut self, cx: &mut ViewContext, mut callback: Fn) + where + Fn: FnMut(&str) -> String, + { + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let buffer = self.buffer.read(cx).snapshot(cx); + + let mut new_selections = Vec::new(); + let mut edits = Vec::new(); + let mut selection_adjustment = 0i32; + + for selection in self.selections.all::(cx) { + let selection_is_empty = selection.is_empty(); + + let (start, end) = if selection_is_empty { + let word_range = movement::surrounding_word( + &display_map, + selection.start.to_display_point(&display_map), + ); + let start = word_range.start.to_offset(&display_map, Bias::Left); + let end = word_range.end.to_offset(&display_map, Bias::Left); + (start, end) + } else { + (selection.start, selection.end) + }; + + let text = buffer.text_for_range(start..end).collect::(); + let old_length = text.len() as i32; + let text = callback(&text); + + new_selections.push(Selection { + start: (start as i32 - selection_adjustment) as usize, + end: ((start + text.len()) as i32 - selection_adjustment) as usize, + goal: SelectionGoal::None, + ..selection + }); + + selection_adjustment += old_length - text.len() as i32; + + edits.push((start..end, text)); + } + + self.transact(cx, |this, cx| { + this.buffer.update(cx, |buffer, cx| { + buffer.edit(edits, None, cx); + }); + + this.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select(new_selections); + }); + + this.request_autoscroll(Autoscroll::fit(), cx); + }); + } + + pub fn duplicate_line(&mut self, _: &DuplicateLine, cx: &mut ViewContext) { + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let buffer = &display_map.buffer_snapshot; + let selections = self.selections.all::(cx); + + let mut edits = Vec::new(); + let mut selections_iter = selections.iter().peekable(); + while let Some(selection) = selections_iter.next() { + // Avoid duplicating the same lines twice. + let mut rows = selection.spanned_rows(false, &display_map); + + while let Some(next_selection) = selections_iter.peek() { + let next_rows = next_selection.spanned_rows(false, &display_map); + if next_rows.start < rows.end { + rows.end = next_rows.end; + selections_iter.next().unwrap(); + } else { + break; + } + } + + // Copy the text from the selected row region and splice it at the start of the region. + let start = Point::new(rows.start, 0); + let end = Point::new(rows.end - 1, buffer.line_len(rows.end - 1)); + let text = buffer + .text_for_range(start..end) + .chain(Some("\n")) + .collect::(); + edits.push((start..start, text)); + } + + self.transact(cx, |this, cx| { + this.buffer.update(cx, |buffer, cx| { + buffer.edit(edits, None, cx); + }); + + this.request_autoscroll(Autoscroll::fit(), cx); + }); + } + + pub fn move_line_up(&mut self, _: &MoveLineUp, cx: &mut ViewContext) { + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let buffer = self.buffer.read(cx).snapshot(cx); + + let mut edits = Vec::new(); + let mut unfold_ranges = Vec::new(); + let mut refold_ranges = Vec::new(); + + let selections = self.selections.all::(cx); + let mut selections = selections.iter().peekable(); + let mut contiguous_row_selections = Vec::new(); + let mut new_selections = Vec::new(); + + while let Some(selection) = selections.next() { + // Find all the selections that span a contiguous row range + let (start_row, end_row) = consume_contiguous_rows( + &mut contiguous_row_selections, + selection, + &display_map, + &mut selections, + ); + + // Move the text spanned by the row range to be before the line preceding the row range + if start_row > 0 { + let range_to_move = Point::new(start_row - 1, buffer.line_len(start_row - 1)) + ..Point::new(end_row - 1, buffer.line_len(end_row - 1)); + let insertion_point = display_map + .prev_line_boundary(Point::new(start_row - 1, 0)) + .0; + + // Don't move lines across excerpts + if buffer + .excerpt_boundaries_in_range(( + Bound::Excluded(insertion_point), + Bound::Included(range_to_move.end), + )) + .next() + .is_none() + { + let text = buffer + .text_for_range(range_to_move.clone()) + .flat_map(|s| s.chars()) + .skip(1) + .chain(['\n']) + .collect::(); + + edits.push(( + buffer.anchor_after(range_to_move.start) + ..buffer.anchor_before(range_to_move.end), + String::new(), + )); + let insertion_anchor = buffer.anchor_after(insertion_point); + edits.push((insertion_anchor..insertion_anchor, text)); + + let row_delta = range_to_move.start.row - insertion_point.row + 1; + + // Move selections up + new_selections.extend(contiguous_row_selections.drain(..).map( + |mut selection| { + selection.start.row -= row_delta; + selection.end.row -= row_delta; + selection + }, + )); + + // Move folds up + unfold_ranges.push(range_to_move.clone()); + for fold in display_map.folds_in_range( + buffer.anchor_before(range_to_move.start) + ..buffer.anchor_after(range_to_move.end), + ) { + let mut start = fold.start.to_point(&buffer); + let mut end = fold.end.to_point(&buffer); + start.row -= row_delta; + end.row -= row_delta; + refold_ranges.push(start..end); + } + } + } + + // If we didn't move line(s), preserve the existing selections + new_selections.append(&mut contiguous_row_selections); + } + + self.transact(cx, |this, cx| { + this.unfold_ranges(unfold_ranges, true, true, cx); + this.buffer.update(cx, |buffer, cx| { + for (range, text) in edits { + buffer.edit([(range, text)], None, cx); + } + }); + this.fold_ranges(refold_ranges, true, cx); + this.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select(new_selections); + }) + }); + } + + pub fn move_line_down(&mut self, _: &MoveLineDown, cx: &mut ViewContext) { + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let buffer = self.buffer.read(cx).snapshot(cx); + + let mut edits = Vec::new(); + let mut unfold_ranges = Vec::new(); + let mut refold_ranges = Vec::new(); + + let selections = self.selections.all::(cx); + let mut selections = selections.iter().peekable(); + let mut contiguous_row_selections = Vec::new(); + let mut new_selections = Vec::new(); + + while let Some(selection) = selections.next() { + // Find all the selections that span a contiguous row range + let (start_row, end_row) = consume_contiguous_rows( + &mut contiguous_row_selections, + selection, + &display_map, + &mut selections, + ); + + // Move the text spanned by the row range to be after the last line of the row range + if end_row <= buffer.max_point().row { + let range_to_move = Point::new(start_row, 0)..Point::new(end_row, 0); + let insertion_point = display_map.next_line_boundary(Point::new(end_row, 0)).0; + + // Don't move lines across excerpt boundaries + if buffer + .excerpt_boundaries_in_range(( + Bound::Excluded(range_to_move.start), + Bound::Included(insertion_point), + )) + .next() + .is_none() + { + let mut text = String::from("\n"); + text.extend(buffer.text_for_range(range_to_move.clone())); + text.pop(); // Drop trailing newline + edits.push(( + buffer.anchor_after(range_to_move.start) + ..buffer.anchor_before(range_to_move.end), + String::new(), + )); + let insertion_anchor = buffer.anchor_after(insertion_point); + edits.push((insertion_anchor..insertion_anchor, text)); + + let row_delta = insertion_point.row - range_to_move.end.row + 1; + + // Move selections down + new_selections.extend(contiguous_row_selections.drain(..).map( + |mut selection| { + selection.start.row += row_delta; + selection.end.row += row_delta; + selection + }, + )); + + // Move folds down + unfold_ranges.push(range_to_move.clone()); + for fold in display_map.folds_in_range( + buffer.anchor_before(range_to_move.start) + ..buffer.anchor_after(range_to_move.end), + ) { + let mut start = fold.start.to_point(&buffer); + let mut end = fold.end.to_point(&buffer); + start.row += row_delta; + end.row += row_delta; + refold_ranges.push(start..end); + } + } + } + + // If we didn't move line(s), preserve the existing selections + new_selections.append(&mut contiguous_row_selections); + } + + self.transact(cx, |this, cx| { + this.unfold_ranges(unfold_ranges, true, true, cx); + this.buffer.update(cx, |buffer, cx| { + for (range, text) in edits { + buffer.edit([(range, text)], None, cx); + } + }); + this.fold_ranges(refold_ranges, true, cx); + this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections)); + }); + } + + pub fn transpose(&mut self, _: &Transpose, cx: &mut ViewContext) { + let text_layout_details = &self.text_layout_details(cx); + self.transact(cx, |this, cx| { + let edits = this.change_selections(Some(Autoscroll::fit()), cx, |s| { + let mut edits: Vec<(Range, String)> = Default::default(); + let line_mode = s.line_mode; + s.move_with(|display_map, selection| { + if !selection.is_empty() || line_mode { + return; + } + + let mut head = selection.head(); + let mut transpose_offset = head.to_offset(display_map, Bias::Right); + if head.column() == display_map.line_len(head.row()) { + transpose_offset = display_map + .buffer_snapshot + .clip_offset(transpose_offset.saturating_sub(1), Bias::Left); + } + + if transpose_offset == 0 { + return; + } + + *head.column_mut() += 1; + head = display_map.clip_point(head, Bias::Right); + let goal = SelectionGoal::HorizontalPosition( + display_map.x_for_point(head, &text_layout_details).into(), + ); + selection.collapse_to(head, goal); + + let transpose_start = display_map + .buffer_snapshot + .clip_offset(transpose_offset.saturating_sub(1), Bias::Left); + if edits.last().map_or(true, |e| e.0.end <= transpose_start) { + let transpose_end = display_map + .buffer_snapshot + .clip_offset(transpose_offset + 1, Bias::Right); + if let Some(ch) = + display_map.buffer_snapshot.chars_at(transpose_start).next() + { + edits.push((transpose_start..transpose_offset, String::new())); + edits.push((transpose_end..transpose_end, ch.to_string())); + } + } + }); + edits + }); + this.buffer + .update(cx, |buffer, cx| buffer.edit(edits, None, cx)); + let selections = this.selections.all::(cx); + this.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select(selections); + }); + }); + } + + pub fn cut(&mut self, _: &Cut, cx: &mut ViewContext) { + let mut text = String::new(); + let buffer = self.buffer.read(cx).snapshot(cx); + let mut selections = self.selections.all::(cx); + let mut clipboard_selections = Vec::with_capacity(selections.len()); + { + let max_point = buffer.max_point(); + let mut is_first = true; + for selection in &mut selections { + let is_entire_line = selection.is_empty() || self.selections.line_mode; + if is_entire_line { + selection.start = Point::new(selection.start.row, 0); + selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0)); + selection.goal = SelectionGoal::None; + } + if is_first { + is_first = false; + } else { + text += "\n"; + } + let mut len = 0; + for chunk in buffer.text_for_range(selection.start..selection.end) { + text.push_str(chunk); + len += chunk.len(); + } + clipboard_selections.push(ClipboardSelection { + len, + is_entire_line, + first_line_indent: buffer.indent_size_for_line(selection.start.row).len, + }); + } + } + + self.transact(cx, |this, cx| { + this.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select(selections); + }); + this.insert("", cx); + cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections)); + }); + } + + pub fn copy(&mut self, _: &Copy, cx: &mut ViewContext) { + let selections = self.selections.all::(cx); + let buffer = self.buffer.read(cx).read(cx); + let mut text = String::new(); + + let mut clipboard_selections = Vec::with_capacity(selections.len()); + { + let max_point = buffer.max_point(); + let mut is_first = true; + for selection in selections.iter() { + let mut start = selection.start; + let mut end = selection.end; + let is_entire_line = selection.is_empty() || self.selections.line_mode; + if is_entire_line { + start = Point::new(start.row, 0); + end = cmp::min(max_point, Point::new(end.row + 1, 0)); + } + if is_first { + is_first = false; + } else { + text += "\n"; + } + let mut len = 0; + for chunk in buffer.text_for_range(start..end) { + text.push_str(chunk); + len += chunk.len(); + } + clipboard_selections.push(ClipboardSelection { + len, + is_entire_line, + first_line_indent: buffer.indent_size_for_line(start.row).len, + }); + } + } + + cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections)); + } + + pub fn paste(&mut self, _: &Paste, cx: &mut ViewContext) { + self.transact(cx, |this, cx| { + if let Some(item) = cx.read_from_clipboard() { + let clipboard_text = Cow::Borrowed(item.text()); + if let Some(mut clipboard_selections) = item.metadata::>() { + let old_selections = this.selections.all::(cx); + let all_selections_were_entire_line = + clipboard_selections.iter().all(|s| s.is_entire_line); + let first_selection_indent_column = + clipboard_selections.first().map(|s| s.first_line_indent); + if clipboard_selections.len() != old_selections.len() { + clipboard_selections.drain(..); + } + + this.buffer.update(cx, |buffer, cx| { + let snapshot = buffer.read(cx); + let mut start_offset = 0; + let mut edits = Vec::new(); + let mut original_indent_columns = Vec::new(); + let line_mode = this.selections.line_mode; + for (ix, selection) in old_selections.iter().enumerate() { + let to_insert; + let entire_line; + let original_indent_column; + if let Some(clipboard_selection) = clipboard_selections.get(ix) { + let end_offset = start_offset + clipboard_selection.len; + to_insert = &clipboard_text[start_offset..end_offset]; + entire_line = clipboard_selection.is_entire_line; + start_offset = end_offset + 1; + original_indent_column = + Some(clipboard_selection.first_line_indent); + } else { + to_insert = clipboard_text.as_str(); + entire_line = all_selections_were_entire_line; + original_indent_column = first_selection_indent_column + } + + // If the corresponding selection was empty when this slice of the + // clipboard text was written, then the entire line containing the + // selection was copied. If this selection is also currently empty, + // then paste the line before the current line of the buffer. + let range = if selection.is_empty() && !line_mode && entire_line { + let column = selection.start.to_point(&snapshot).column as usize; + let line_start = selection.start - column; + line_start..line_start + } else { + selection.range() + }; + + edits.push((range, to_insert)); + original_indent_columns.extend(original_indent_column); + } + drop(snapshot); + + buffer.edit( + edits, + Some(AutoindentMode::Block { + original_indent_columns, + }), + cx, + ); + }); + + let selections = this.selections.all::(cx); + this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections)); + } else { + this.insert(&clipboard_text, cx); + } + } + }); + } + + pub fn undo(&mut self, _: &Undo, cx: &mut ViewContext) { + if let Some(tx_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) { + if let Some((selections, _)) = self.selection_history.transaction(tx_id).cloned() { + self.change_selections(None, cx, |s| { + s.select_anchors(selections.to_vec()); + }); + } + self.request_autoscroll(Autoscroll::fit(), cx); + self.unmark_text(cx); + self.refresh_copilot_suggestions(true, cx); + cx.emit(Event::Edited); + } + } + + pub fn redo(&mut self, _: &Redo, cx: &mut ViewContext) { + if let Some(tx_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) { + if let Some((_, Some(selections))) = self.selection_history.transaction(tx_id).cloned() + { + self.change_selections(None, cx, |s| { + s.select_anchors(selections.to_vec()); + }); + } + self.request_autoscroll(Autoscroll::fit(), cx); + self.unmark_text(cx); + self.refresh_copilot_suggestions(true, cx); + cx.emit(Event::Edited); + } + } + + pub fn finalize_last_transaction(&mut self, cx: &mut ViewContext) { + self.buffer + .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx)); + } pub fn move_left(&mut self, _: &MoveLeft, cx: &mut ViewContext) { self.change_selections(Some(Autoscroll::fit()), cx, |s| { @@ -5733,11 +5738,11 @@ impl Editor { }) } - // pub fn select_left(&mut self, _: &SelectLeft, cx: &mut ViewContext) { - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None)); - // }) - // } + pub fn select_left(&mut self, _: &SelectLeft, cx: &mut ViewContext) { + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None)); + }) + } pub fn move_right(&mut self, _: &MoveRight, cx: &mut ViewContext) { self.change_selections(Some(Autoscroll::fit()), cx, |s| { @@ -5753,11 +5758,11 @@ impl Editor { }) } - // pub fn select_right(&mut self, _: &SelectRight, cx: &mut ViewContext) { - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None)); - // }) - // } + pub fn select_right(&mut self, _: &SelectRight, cx: &mut ViewContext) { + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None)); + }) + } pub fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext) { if self.take_rename(true, cx).is_some() { @@ -5789,57 +5794,57 @@ impl Editor { }) } - // pub fn move_page_up(&mut self, action: &MovePageUp, cx: &mut ViewContext) { - // if self.take_rename(true, cx).is_some() { - // return; - // } + pub fn move_page_up(&mut self, action: &MovePageUp, cx: &mut ViewContext) { + if self.take_rename(true, cx).is_some() { + return; + } - // if matches!(self.mode, EditorMode::SingleLine) { - // cx.propagate(); - // return; - // } + if matches!(self.mode, EditorMode::SingleLine) { + cx.propagate(); + return; + } - // let row_count = if let Some(row_count) = self.visible_line_count() { - // row_count as u32 - 1 - // } else { - // return; - // }; + let row_count = if let Some(row_count) = self.visible_line_count() { + row_count as u32 - 1 + } else { + return; + }; - // let autoscroll = if action.center_cursor { - // Autoscroll::center() - // } else { - // Autoscroll::fit() - // }; + let autoscroll = if action.center_cursor { + Autoscroll::center() + } else { + Autoscroll::fit() + }; - // let text_layout_details = &self.text_layout_details(cx); + let text_layout_details = &self.text_layout_details(cx); - // self.change_selections(Some(autoscroll), cx, |s| { - // let line_mode = s.line_mode; - // s.move_with(|map, selection| { - // if !selection.is_empty() && !line_mode { - // selection.goal = SelectionGoal::None; - // } - // let (cursor, goal) = movement::up_by_rows( - // map, - // selection.end, - // row_count, - // selection.goal, - // false, - // &text_layout_details, - // ); - // selection.collapse_to(cursor, goal); - // }); - // }); - // } + self.change_selections(Some(autoscroll), cx, |s| { + let line_mode = s.line_mode; + s.move_with(|map, selection| { + if !selection.is_empty() && !line_mode { + selection.goal = SelectionGoal::None; + } + let (cursor, goal) = movement::up_by_rows( + map, + selection.end, + row_count, + selection.goal, + false, + &text_layout_details, + ); + selection.collapse_to(cursor, goal); + }); + }); + } - // pub fn select_up(&mut self, _: &SelectUp, cx: &mut ViewContext) { - // let text_layout_details = &self.text_layout_details(cx); - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.move_heads_with(|map, head, goal| { - // movement::up(map, head, goal, false, &text_layout_details) - // }) - // }) - // } + pub fn select_up(&mut self, _: &SelectUp, cx: &mut ViewContext) { + let text_layout_details = &self.text_layout_details(cx); + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_heads_with(|map, head, goal| { + movement::up(map, head, goal, false, &text_layout_details) + }) + }) + } pub fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext) { self.take_rename(true, cx); @@ -5868,66 +5873,66 @@ impl Editor { }); } - // pub fn move_page_down(&mut self, action: &MovePageDown, cx: &mut ViewContext) { - // if self.take_rename(true, cx).is_some() { - // return; - // } + pub fn move_page_down(&mut self, action: &MovePageDown, cx: &mut ViewContext) { + if self.take_rename(true, cx).is_some() { + return; + } - // if self - // .context_menu - // .write() - // .as_mut() - // .map(|menu| menu.select_last(self.project.as_ref(), cx)) - // .unwrap_or(false) - // { - // return; - // } + if self + .context_menu + .write() + .as_mut() + .map(|menu| menu.select_last(self.project.as_ref(), cx)) + .unwrap_or(false) + { + return; + } - // if matches!(self.mode, EditorMode::SingleLine) { - // cx.propagate(); - // return; - // } + if matches!(self.mode, EditorMode::SingleLine) { + cx.propagate(); + return; + } - // let row_count = if let Some(row_count) = self.visible_line_count() { - // row_count as u32 - 1 - // } else { - // return; - // }; + let row_count = if let Some(row_count) = self.visible_line_count() { + row_count as u32 - 1 + } else { + return; + }; - // let autoscroll = if action.center_cursor { - // Autoscroll::center() - // } else { - // Autoscroll::fit() - // }; + let autoscroll = if action.center_cursor { + Autoscroll::center() + } else { + Autoscroll::fit() + }; - // let text_layout_details = &self.text_layout_details(cx); - // self.change_selections(Some(autoscroll), cx, |s| { - // let line_mode = s.line_mode; - // s.move_with(|map, selection| { - // if !selection.is_empty() && !line_mode { - // selection.goal = SelectionGoal::None; - // } - // let (cursor, goal) = movement::down_by_rows( - // map, - // selection.end, - // row_count, - // selection.goal, - // false, - // &text_layout_details, - // ); - // selection.collapse_to(cursor, goal); - // }); - // }); - // } + let text_layout_details = &self.text_layout_details(cx); + self.change_selections(Some(autoscroll), cx, |s| { + let line_mode = s.line_mode; + s.move_with(|map, selection| { + if !selection.is_empty() && !line_mode { + selection.goal = SelectionGoal::None; + } + let (cursor, goal) = movement::down_by_rows( + map, + selection.end, + row_count, + selection.goal, + false, + &text_layout_details, + ); + selection.collapse_to(cursor, goal); + }); + }); + } - // pub fn select_down(&mut self, _: &SelectDown, cx: &mut ViewContext) { - // let text_layout_details = &self.text_layout_details(cx); - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.move_heads_with(|map, head, goal| { - // movement::down(map, head, goal, false, &text_layout_details) - // }) - // }); - // } + pub fn select_down(&mut self, _: &SelectDown, cx: &mut ViewContext) { + let text_layout_details = &self.text_layout_details(cx); + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_heads_with(|map, head, goal| { + movement::down(map, head, goal, false, &text_layout_details) + }) + }); + } // pub fn context_menu_first(&mut self, _: &ContextMenuFirst, cx: &mut ViewContext) { // if let Some(context_menu) = self.context_menu.write().as_mut() { @@ -5953,397 +5958,397 @@ impl Editor { // } // } - // pub fn move_to_previous_word_start( - // &mut self, - // _: &MoveToPreviousWordStart, - // cx: &mut ViewContext, - // ) { - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.move_cursors_with(|map, head, _| { - // ( - // movement::previous_word_start(map, head), - // SelectionGoal::None, - // ) - // }); - // }) - // } + pub fn move_to_previous_word_start( + &mut self, + _: &MoveToPreviousWordStart, + cx: &mut ViewContext, + ) { + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_cursors_with(|map, head, _| { + ( + movement::previous_word_start(map, head), + SelectionGoal::None, + ) + }); + }) + } - // pub fn move_to_previous_subword_start( - // &mut self, - // _: &MoveToPreviousSubwordStart, - // cx: &mut ViewContext, - // ) { - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.move_cursors_with(|map, head, _| { - // ( - // movement::previous_subword_start(map, head), - // SelectionGoal::None, - // ) - // }); - // }) - // } + pub fn move_to_previous_subword_start( + &mut self, + _: &MoveToPreviousSubwordStart, + cx: &mut ViewContext, + ) { + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_cursors_with(|map, head, _| { + ( + movement::previous_subword_start(map, head), + SelectionGoal::None, + ) + }); + }) + } - // pub fn select_to_previous_word_start( - // &mut self, - // _: &SelectToPreviousWordStart, - // cx: &mut ViewContext, - // ) { - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.move_heads_with(|map, head, _| { - // ( - // movement::previous_word_start(map, head), - // SelectionGoal::None, - // ) - // }); - // }) - // } + pub fn select_to_previous_word_start( + &mut self, + _: &SelectToPreviousWordStart, + cx: &mut ViewContext, + ) { + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_heads_with(|map, head, _| { + ( + movement::previous_word_start(map, head), + SelectionGoal::None, + ) + }); + }) + } - // pub fn select_to_previous_subword_start( - // &mut self, - // _: &SelectToPreviousSubwordStart, - // cx: &mut ViewContext, - // ) { - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.move_heads_with(|map, head, _| { - // ( - // movement::previous_subword_start(map, head), - // SelectionGoal::None, - // ) - // }); - // }) - // } + pub fn select_to_previous_subword_start( + &mut self, + _: &SelectToPreviousSubwordStart, + cx: &mut ViewContext, + ) { + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_heads_with(|map, head, _| { + ( + movement::previous_subword_start(map, head), + SelectionGoal::None, + ) + }); + }) + } - // pub fn delete_to_previous_word_start( - // &mut self, - // _: &DeleteToPreviousWordStart, - // cx: &mut ViewContext, - // ) { - // self.transact(cx, |this, cx| { - // this.select_autoclose_pair(cx); - // this.change_selections(Some(Autoscroll::fit()), cx, |s| { - // let line_mode = s.line_mode; - // s.move_with(|map, selection| { - // if selection.is_empty() && !line_mode { - // let cursor = movement::previous_word_start(map, selection.head()); - // selection.set_head(cursor, SelectionGoal::None); - // } - // }); - // }); - // this.insert("", cx); - // }); - // } + pub fn delete_to_previous_word_start( + &mut self, + _: &DeleteToPreviousWordStart, + cx: &mut ViewContext, + ) { + self.transact(cx, |this, cx| { + this.select_autoclose_pair(cx); + this.change_selections(Some(Autoscroll::fit()), cx, |s| { + let line_mode = s.line_mode; + s.move_with(|map, selection| { + if selection.is_empty() && !line_mode { + let cursor = movement::previous_word_start(map, selection.head()); + selection.set_head(cursor, SelectionGoal::None); + } + }); + }); + this.insert("", cx); + }); + } - // pub fn delete_to_previous_subword_start( - // &mut self, - // _: &DeleteToPreviousSubwordStart, - // cx: &mut ViewContext, - // ) { - // self.transact(cx, |this, cx| { - // this.select_autoclose_pair(cx); - // this.change_selections(Some(Autoscroll::fit()), cx, |s| { - // let line_mode = s.line_mode; - // s.move_with(|map, selection| { - // if selection.is_empty() && !line_mode { - // let cursor = movement::previous_subword_start(map, selection.head()); - // selection.set_head(cursor, SelectionGoal::None); - // } - // }); - // }); - // this.insert("", cx); - // }); - // } + pub fn delete_to_previous_subword_start( + &mut self, + _: &DeleteToPreviousSubwordStart, + cx: &mut ViewContext, + ) { + self.transact(cx, |this, cx| { + this.select_autoclose_pair(cx); + this.change_selections(Some(Autoscroll::fit()), cx, |s| { + let line_mode = s.line_mode; + s.move_with(|map, selection| { + if selection.is_empty() && !line_mode { + let cursor = movement::previous_subword_start(map, selection.head()); + selection.set_head(cursor, SelectionGoal::None); + } + }); + }); + this.insert("", cx); + }); + } - // pub fn move_to_next_word_end(&mut self, _: &MoveToNextWordEnd, cx: &mut ViewContext) { - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.move_cursors_with(|map, head, _| { - // (movement::next_word_end(map, head), SelectionGoal::None) - // }); - // }) - // } + pub fn move_to_next_word_end(&mut self, _: &MoveToNextWordEnd, cx: &mut ViewContext) { + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_cursors_with(|map, head, _| { + (movement::next_word_end(map, head), SelectionGoal::None) + }); + }) + } - // pub fn move_to_next_subword_end( - // &mut self, - // _: &MoveToNextSubwordEnd, - // cx: &mut ViewContext, - // ) { - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.move_cursors_with(|map, head, _| { - // (movement::next_subword_end(map, head), SelectionGoal::None) - // }); - // }) - // } + pub fn move_to_next_subword_end( + &mut self, + _: &MoveToNextSubwordEnd, + cx: &mut ViewContext, + ) { + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_cursors_with(|map, head, _| { + (movement::next_subword_end(map, head), SelectionGoal::None) + }); + }) + } - // pub fn select_to_next_word_end(&mut self, _: &SelectToNextWordEnd, cx: &mut ViewContext) { - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.move_heads_with(|map, head, _| { - // (movement::next_word_end(map, head), SelectionGoal::None) - // }); - // }) - // } + pub fn select_to_next_word_end(&mut self, _: &SelectToNextWordEnd, cx: &mut ViewContext) { + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_heads_with(|map, head, _| { + (movement::next_word_end(map, head), SelectionGoal::None) + }); + }) + } - // pub fn select_to_next_subword_end( - // &mut self, - // _: &SelectToNextSubwordEnd, - // cx: &mut ViewContext, - // ) { - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.move_heads_with(|map, head, _| { - // (movement::next_subword_end(map, head), SelectionGoal::None) - // }); - // }) - // } + pub fn select_to_next_subword_end( + &mut self, + _: &SelectToNextSubwordEnd, + cx: &mut ViewContext, + ) { + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_heads_with(|map, head, _| { + (movement::next_subword_end(map, head), SelectionGoal::None) + }); + }) + } - // pub fn delete_to_next_word_end(&mut self, _: &DeleteToNextWordEnd, cx: &mut ViewContext) { - // self.transact(cx, |this, cx| { - // this.change_selections(Some(Autoscroll::fit()), cx, |s| { - // let line_mode = s.line_mode; - // s.move_with(|map, selection| { - // if selection.is_empty() && !line_mode { - // let cursor = movement::next_word_end(map, selection.head()); - // selection.set_head(cursor, SelectionGoal::None); - // } - // }); - // }); - // this.insert("", cx); - // }); - // } + pub fn delete_to_next_word_end(&mut self, _: &DeleteToNextWordEnd, cx: &mut ViewContext) { + self.transact(cx, |this, cx| { + this.change_selections(Some(Autoscroll::fit()), cx, |s| { + let line_mode = s.line_mode; + s.move_with(|map, selection| { + if selection.is_empty() && !line_mode { + let cursor = movement::next_word_end(map, selection.head()); + selection.set_head(cursor, SelectionGoal::None); + } + }); + }); + this.insert("", cx); + }); + } - // pub fn delete_to_next_subword_end( - // &mut self, - // _: &DeleteToNextSubwordEnd, - // cx: &mut ViewContext, - // ) { - // self.transact(cx, |this, cx| { - // this.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.move_with(|map, selection| { - // if selection.is_empty() { - // let cursor = movement::next_subword_end(map, selection.head()); - // selection.set_head(cursor, SelectionGoal::None); - // } - // }); - // }); - // this.insert("", cx); - // }); - // } + pub fn delete_to_next_subword_end( + &mut self, + _: &DeleteToNextSubwordEnd, + cx: &mut ViewContext, + ) { + self.transact(cx, |this, cx| { + this.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_with(|map, selection| { + if selection.is_empty() { + let cursor = movement::next_subword_end(map, selection.head()); + selection.set_head(cursor, SelectionGoal::None); + } + }); + }); + this.insert("", cx); + }); + } - // pub fn move_to_beginning_of_line( - // &mut self, - // _: &MoveToBeginningOfLine, - // cx: &mut ViewContext, - // ) { - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.move_cursors_with(|map, head, _| { - // ( - // movement::indented_line_beginning(map, head, true), - // SelectionGoal::None, - // ) - // }); - // }) - // } + pub fn move_to_beginning_of_line( + &mut self, + _: &MoveToBeginningOfLine, + cx: &mut ViewContext, + ) { + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_cursors_with(|map, head, _| { + ( + movement::indented_line_beginning(map, head, true), + SelectionGoal::None, + ) + }); + }) + } - // pub fn select_to_beginning_of_line( - // &mut self, - // action: &SelectToBeginningOfLine, - // cx: &mut ViewContext, - // ) { - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.move_heads_with(|map, head, _| { - // ( - // movement::indented_line_beginning(map, head, action.stop_at_soft_wraps), - // SelectionGoal::None, - // ) - // }); - // }); - // } + pub fn select_to_beginning_of_line( + &mut self, + action: &SelectToBeginningOfLine, + cx: &mut ViewContext, + ) { + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_heads_with(|map, head, _| { + ( + movement::indented_line_beginning(map, head, action.stop_at_soft_wraps), + SelectionGoal::None, + ) + }); + }); + } - // pub fn delete_to_beginning_of_line( - // &mut self, - // _: &DeleteToBeginningOfLine, - // cx: &mut ViewContext, - // ) { - // self.transact(cx, |this, cx| { - // this.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.move_with(|_, selection| { - // selection.reversed = true; - // }); - // }); + pub fn delete_to_beginning_of_line( + &mut self, + _: &DeleteToBeginningOfLine, + cx: &mut ViewContext, + ) { + self.transact(cx, |this, cx| { + this.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_with(|_, selection| { + selection.reversed = true; + }); + }); - // this.select_to_beginning_of_line( - // &SelectToBeginningOfLine { - // stop_at_soft_wraps: false, - // }, - // cx, - // ); - // this.backspace(&Backspace, cx); - // }); - // } + this.select_to_beginning_of_line( + &SelectToBeginningOfLine { + stop_at_soft_wraps: false, + }, + cx, + ); + this.backspace(&Backspace, cx); + }); + } - // pub fn move_to_end_of_line(&mut self, _: &MoveToEndOfLine, cx: &mut ViewContext) { - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.move_cursors_with(|map, head, _| { - // (movement::line_end(map, head, true), SelectionGoal::None) - // }); - // }) - // } + pub fn move_to_end_of_line(&mut self, _: &MoveToEndOfLine, cx: &mut ViewContext) { + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_cursors_with(|map, head, _| { + (movement::line_end(map, head, true), SelectionGoal::None) + }); + }) + } - // pub fn select_to_end_of_line( - // &mut self, - // action: &SelectToEndOfLine, - // cx: &mut ViewContext, - // ) { - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.move_heads_with(|map, head, _| { - // ( - // movement::line_end(map, head, action.stop_at_soft_wraps), - // SelectionGoal::None, - // ) - // }); - // }) - // } + pub fn select_to_end_of_line( + &mut self, + action: &SelectToEndOfLine, + cx: &mut ViewContext, + ) { + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_heads_with(|map, head, _| { + ( + movement::line_end(map, head, action.stop_at_soft_wraps), + SelectionGoal::None, + ) + }); + }) + } - // pub fn delete_to_end_of_line(&mut self, _: &DeleteToEndOfLine, cx: &mut ViewContext) { - // self.transact(cx, |this, cx| { - // this.select_to_end_of_line( - // &SelectToEndOfLine { - // stop_at_soft_wraps: false, - // }, - // cx, - // ); - // this.delete(&Delete, cx); - // }); - // } + pub fn delete_to_end_of_line(&mut self, _: &DeleteToEndOfLine, cx: &mut ViewContext) { + self.transact(cx, |this, cx| { + this.select_to_end_of_line( + &SelectToEndOfLine { + stop_at_soft_wraps: false, + }, + cx, + ); + this.delete(&Delete, cx); + }); + } - // pub fn cut_to_end_of_line(&mut self, _: &CutToEndOfLine, cx: &mut ViewContext) { - // self.transact(cx, |this, cx| { - // this.select_to_end_of_line( - // &SelectToEndOfLine { - // stop_at_soft_wraps: false, - // }, - // cx, - // ); - // this.cut(&Cut, cx); - // }); - // } + pub fn cut_to_end_of_line(&mut self, _: &CutToEndOfLine, cx: &mut ViewContext) { + self.transact(cx, |this, cx| { + this.select_to_end_of_line( + &SelectToEndOfLine { + stop_at_soft_wraps: false, + }, + cx, + ); + this.cut(&Cut, cx); + }); + } - // pub fn move_to_start_of_paragraph( - // &mut self, - // _: &MoveToStartOfParagraph, - // cx: &mut ViewContext, - // ) { - // if matches!(self.mode, EditorMode::SingleLine) { - // cx.propagate(); - // return; - // } + pub fn move_to_start_of_paragraph( + &mut self, + _: &MoveToStartOfParagraph, + cx: &mut ViewContext, + ) { + if matches!(self.mode, EditorMode::SingleLine) { + cx.propagate(); + return; + } - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.move_with(|map, selection| { - // selection.collapse_to( - // movement::start_of_paragraph(map, selection.head(), 1), - // SelectionGoal::None, - // ) - // }); - // }) - // } + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_with(|map, selection| { + selection.collapse_to( + movement::start_of_paragraph(map, selection.head(), 1), + SelectionGoal::None, + ) + }); + }) + } - // pub fn move_to_end_of_paragraph( - // &mut self, - // _: &MoveToEndOfParagraph, - // cx: &mut ViewContext, - // ) { - // if matches!(self.mode, EditorMode::SingleLine) { - // cx.propagate(); - // return; - // } + pub fn move_to_end_of_paragraph( + &mut self, + _: &MoveToEndOfParagraph, + cx: &mut ViewContext, + ) { + if matches!(self.mode, EditorMode::SingleLine) { + cx.propagate(); + return; + } - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.move_with(|map, selection| { - // selection.collapse_to( - // movement::end_of_paragraph(map, selection.head(), 1), - // SelectionGoal::None, - // ) - // }); - // }) - // } + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_with(|map, selection| { + selection.collapse_to( + movement::end_of_paragraph(map, selection.head(), 1), + SelectionGoal::None, + ) + }); + }) + } - // pub fn select_to_start_of_paragraph( - // &mut self, - // _: &SelectToStartOfParagraph, - // cx: &mut ViewContext, - // ) { - // if matches!(self.mode, EditorMode::SingleLine) { - // cx.propagate(); - // return; - // } + pub fn select_to_start_of_paragraph( + &mut self, + _: &SelectToStartOfParagraph, + cx: &mut ViewContext, + ) { + if matches!(self.mode, EditorMode::SingleLine) { + cx.propagate(); + return; + } - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.move_heads_with(|map, head, _| { - // ( - // movement::start_of_paragraph(map, head, 1), - // SelectionGoal::None, - // ) - // }); - // }) - // } + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_heads_with(|map, head, _| { + ( + movement::start_of_paragraph(map, head, 1), + SelectionGoal::None, + ) + }); + }) + } - // pub fn select_to_end_of_paragraph( - // &mut self, - // _: &SelectToEndOfParagraph, - // cx: &mut ViewContext, - // ) { - // if matches!(self.mode, EditorMode::SingleLine) { - // cx.propagate(); - // return; - // } + pub fn select_to_end_of_paragraph( + &mut self, + _: &SelectToEndOfParagraph, + cx: &mut ViewContext, + ) { + if matches!(self.mode, EditorMode::SingleLine) { + cx.propagate(); + return; + } - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.move_heads_with(|map, head, _| { - // ( - // movement::end_of_paragraph(map, head, 1), - // SelectionGoal::None, - // ) - // }); - // }) - // } + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_heads_with(|map, head, _| { + ( + movement::end_of_paragraph(map, head, 1), + SelectionGoal::None, + ) + }); + }) + } - // pub fn move_to_beginning(&mut self, _: &MoveToBeginning, cx: &mut ViewContext) { - // if matches!(self.mode, EditorMode::SingleLine) { - // cx.propagate(); - // return; - // } + pub fn move_to_beginning(&mut self, _: &MoveToBeginning, cx: &mut ViewContext) { + if matches!(self.mode, EditorMode::SingleLine) { + cx.propagate(); + return; + } - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.select_ranges(vec![0..0]); - // }); - // } + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select_ranges(vec![0..0]); + }); + } - // pub fn select_to_beginning(&mut self, _: &SelectToBeginning, cx: &mut ViewContext) { - // let mut selection = self.selections.last::(cx); - // selection.set_head(Point::zero(), SelectionGoal::None); + pub fn select_to_beginning(&mut self, _: &SelectToBeginning, cx: &mut ViewContext) { + let mut selection = self.selections.last::(cx); + selection.set_head(Point::zero(), SelectionGoal::None); - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.select(vec![selection]); - // }); - // } + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select(vec![selection]); + }); + } - // pub fn move_to_end(&mut self, _: &MoveToEnd, cx: &mut ViewContext) { - // if matches!(self.mode, EditorMode::SingleLine) { - // cx.propagate(); - // return; - // } + pub fn move_to_end(&mut self, _: &MoveToEnd, cx: &mut ViewContext) { + if matches!(self.mode, EditorMode::SingleLine) { + cx.propagate(); + return; + } - // let cursor = self.buffer.read(cx).read(cx).len(); - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.select_ranges(vec![cursor..cursor]) - // }); - // } + let cursor = self.buffer.read(cx).read(cx).len(); + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select_ranges(vec![cursor..cursor]) + }); + } - // pub fn set_nav_history(&mut self, nav_history: Option) { - // self.nav_history = nav_history; - // } + pub fn set_nav_history(&mut self, nav_history: Option) { + self.nav_history = nav_history; + } - // pub fn nav_history(&self) -> Option<&ItemNavHistory> { - // self.nav_history.as_ref() - // } + pub fn nav_history(&self) -> Option<&ItemNavHistory> { + self.nav_history.as_ref() + } fn push_to_nav_history( &mut self, @@ -6377,1018 +6382,1019 @@ impl Editor { } } - // pub fn select_to_end(&mut self, _: &SelectToEnd, cx: &mut ViewContext) { - // let buffer = self.buffer.read(cx).snapshot(cx); - // let mut selection = self.selections.first::(cx); - // selection.set_head(buffer.len(), SelectionGoal::None); - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.select(vec![selection]); - // }); - // } - - // pub fn select_all(&mut self, _: &SelectAll, cx: &mut ViewContext) { - // let end = self.buffer.read(cx).read(cx).len(); - // self.change_selections(None, cx, |s| { - // s.select_ranges(vec![0..end]); - // }); - // } - - // pub fn select_line(&mut self, _: &SelectLine, cx: &mut ViewContext) { - // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - // let mut selections = self.selections.all::(cx); - // let max_point = display_map.buffer_snapshot.max_point(); - // for selection in &mut selections { - // let rows = selection.spanned_rows(true, &display_map); - // selection.start = Point::new(rows.start, 0); - // selection.end = cmp::min(max_point, Point::new(rows.end, 0)); - // selection.reversed = false; - // } - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.select(selections); - // }); - // } - - // pub fn split_selection_into_lines( - // &mut self, - // _: &SplitSelectionIntoLines, - // cx: &mut ViewContext, - // ) { - // let mut to_unfold = Vec::new(); - // let mut new_selection_ranges = Vec::new(); - // { - // let selections = self.selections.all::(cx); - // let buffer = self.buffer.read(cx).read(cx); - // for selection in selections { - // for row in selection.start.row..selection.end.row { - // let cursor = Point::new(row, buffer.line_len(row)); - // new_selection_ranges.push(cursor..cursor); - // } - // new_selection_ranges.push(selection.end..selection.end); - // to_unfold.push(selection.start..selection.end); - // } - // } - // self.unfold_ranges(to_unfold, true, true, cx); - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.select_ranges(new_selection_ranges); - // }); - // } - - // pub fn add_selection_above(&mut self, _: &AddSelectionAbove, cx: &mut ViewContext) { - // self.add_selection(true, cx); - // } - - // pub fn add_selection_below(&mut self, _: &AddSelectionBelow, cx: &mut ViewContext) { - // self.add_selection(false, cx); - // } - - // fn add_selection(&mut self, above: bool, cx: &mut ViewContext) { - // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - // let mut selections = self.selections.all::(cx); - // let text_layout_details = self.text_layout_details(cx); - // let mut state = self.add_selections_state.take().unwrap_or_else(|| { - // let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone(); - // let range = oldest_selection.display_range(&display_map).sorted(); - - // let start_x = display_map.x_for_point(range.start, &text_layout_details); - // let end_x = display_map.x_for_point(range.end, &text_layout_details); - // let positions = start_x.min(end_x)..start_x.max(end_x); - - // selections.clear(); - // let mut stack = Vec::new(); - // for row in range.start.row()..=range.end.row() { - // if let Some(selection) = self.selections.build_columnar_selection( - // &display_map, - // row, - // &positions, - // oldest_selection.reversed, - // &text_layout_details, - // ) { - // stack.push(selection.id); - // selections.push(selection); - // } - // } - - // if above { - // stack.reverse(); - // } - - // AddSelectionsState { above, stack } - // }); - - // let last_added_selection = *state.stack.last().unwrap(); - // let mut new_selections = Vec::new(); - // if above == state.above { - // let end_row = if above { - // 0 - // } else { - // display_map.max_point().row() - // }; - - // 'outer: for selection in selections { - // if selection.id == last_added_selection { - // let range = selection.display_range(&display_map).sorted(); - // debug_assert_eq!(range.start.row(), range.end.row()); - // let mut row = range.start.row(); - // let positions = if let SelectionGoal::HorizontalRange { start, end } = - // selection.goal - // { - // start..end - // } else { - // let start_x = display_map.x_for_point(range.start, &text_layout_details); - // let end_x = display_map.x_for_point(range.end, &text_layout_details); - - // start_x.min(end_x)..start_x.max(end_x) - // }; - - // while row != end_row { - // if above { - // row -= 1; - // } else { - // row += 1; - // } - - // if let Some(new_selection) = self.selections.build_columnar_selection( - // &display_map, - // row, - // &positions, - // selection.reversed, - // &text_layout_details, - // ) { - // state.stack.push(new_selection.id); - // if above { - // new_selections.push(new_selection); - // new_selections.push(selection); - // } else { - // new_selections.push(selection); - // new_selections.push(new_selection); - // } - - // continue 'outer; - // } - // } - // } - - // new_selections.push(selection); - // } - // } else { - // new_selections = selections; - // new_selections.retain(|s| s.id != last_added_selection); - // state.stack.pop(); - // } - - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.select(new_selections); - // }); - // if state.stack.len() > 1 { - // self.add_selections_state = Some(state); - // } - // } - - // pub fn select_next_match_internal( - // &mut self, - // display_map: &DisplaySnapshot, - // replace_newest: bool, - // autoscroll: Option, - // cx: &mut ViewContext, - // ) -> Result<()> { - // fn select_next_match_ranges( - // this: &mut Editor, - // range: Range, - // replace_newest: bool, - // auto_scroll: Option, - // cx: &mut ViewContext, - // ) { - // this.unfold_ranges([range.clone()], false, true, cx); - // this.change_selections(auto_scroll, cx, |s| { - // if replace_newest { - // s.delete(s.newest_anchor().id); - // } - // s.insert_range(range.clone()); - // }); - // } - - // let buffer = &display_map.buffer_snapshot; - // let mut selections = self.selections.all::(cx); - // if let Some(mut select_next_state) = self.select_next_state.take() { - // let query = &select_next_state.query; - // if !select_next_state.done { - // let first_selection = selections.iter().min_by_key(|s| s.id).unwrap(); - // let last_selection = selections.iter().max_by_key(|s| s.id).unwrap(); - // let mut next_selected_range = None; - - // let bytes_after_last_selection = - // buffer.bytes_in_range(last_selection.end..buffer.len()); - // let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start); - // let query_matches = query - // .stream_find_iter(bytes_after_last_selection) - // .map(|result| (last_selection.end, result)) - // .chain( - // query - // .stream_find_iter(bytes_before_first_selection) - // .map(|result| (0, result)), - // ); - - // for (start_offset, query_match) in query_matches { - // let query_match = query_match.unwrap(); // can only fail due to I/O - // let offset_range = - // start_offset + query_match.start()..start_offset + query_match.end(); - // let display_range = offset_range.start.to_display_point(&display_map) - // ..offset_range.end.to_display_point(&display_map); - - // if !select_next_state.wordwise - // || (!movement::is_inside_word(&display_map, display_range.start) - // && !movement::is_inside_word(&display_map, display_range.end)) - // { - // if selections - // .iter() - // .find(|selection| selection.range().overlaps(&offset_range)) - // .is_none() - // { - // next_selected_range = Some(offset_range); - // break; - // } - // } - // } - - // if let Some(next_selected_range) = next_selected_range { - // select_next_match_ranges( - // self, - // next_selected_range, - // replace_newest, - // autoscroll, - // cx, - // ); - // } else { - // select_next_state.done = true; - // } - // } - - // self.select_next_state = Some(select_next_state); - // } else if selections.len() == 1 { - // let selection = selections.last_mut().unwrap(); - // if selection.start == selection.end { - // let word_range = movement::surrounding_word( - // &display_map, - // selection.start.to_display_point(&display_map), - // ); - // selection.start = word_range.start.to_offset(&display_map, Bias::Left); - // selection.end = word_range.end.to_offset(&display_map, Bias::Left); - // selection.goal = SelectionGoal::None; - // selection.reversed = false; - - // let query = buffer - // .text_for_range(selection.start..selection.end) - // .collect::(); - - // let is_empty = query.is_empty(); - // let select_state = SelectNextState { - // query: AhoCorasick::new(&[query])?, - // wordwise: true, - // done: is_empty, - // }; - // select_next_match_ranges( - // self, - // selection.start..selection.end, - // replace_newest, - // autoscroll, - // cx, - // ); - // self.select_next_state = Some(select_state); - // } else { - // let query = buffer - // .text_for_range(selection.start..selection.end) - // .collect::(); - // self.select_next_state = Some(SelectNextState { - // query: AhoCorasick::new(&[query])?, - // wordwise: false, - // done: false, - // }); - // self.select_next_match_internal(display_map, replace_newest, autoscroll, cx)?; - // } - // } - // Ok(()) - // } - - // pub fn select_all_matches( - // &mut self, - // action: &SelectAllMatches, - // cx: &mut ViewContext, - // ) -> Result<()> { - // self.push_to_selection_history(); - // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - - // loop { - // self.select_next_match_internal(&display_map, action.replace_newest, None, cx)?; - - // if self - // .select_next_state - // .as_ref() - // .map(|selection_state| selection_state.done) - // .unwrap_or(true) - // { - // break; - // } - // } - - // Ok(()) - // } - - // pub fn select_next(&mut self, action: &SelectNext, cx: &mut ViewContext) -> Result<()> { - // self.push_to_selection_history(); - // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - // self.select_next_match_internal( - // &display_map, - // action.replace_newest, - // Some(Autoscroll::newest()), - // cx, - // )?; - // Ok(()) - // } - - // pub fn select_previous( - // &mut self, - // action: &SelectPrevious, - // cx: &mut ViewContext, - // ) -> Result<()> { - // self.push_to_selection_history(); - // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - // let buffer = &display_map.buffer_snapshot; - // let mut selections = self.selections.all::(cx); - // if let Some(mut select_prev_state) = self.select_prev_state.take() { - // let query = &select_prev_state.query; - // if !select_prev_state.done { - // let first_selection = selections.iter().min_by_key(|s| s.id).unwrap(); - // let last_selection = selections.iter().max_by_key(|s| s.id).unwrap(); - // let mut next_selected_range = None; - // // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer. - // let bytes_before_last_selection = - // buffer.reversed_bytes_in_range(0..last_selection.start); - // let bytes_after_first_selection = - // buffer.reversed_bytes_in_range(first_selection.end..buffer.len()); - // let query_matches = query - // .stream_find_iter(bytes_before_last_selection) - // .map(|result| (last_selection.start, result)) - // .chain( - // query - // .stream_find_iter(bytes_after_first_selection) - // .map(|result| (buffer.len(), result)), - // ); - // for (end_offset, query_match) in query_matches { - // let query_match = query_match.unwrap(); // can only fail due to I/O - // let offset_range = - // end_offset - query_match.end()..end_offset - query_match.start(); - // let display_range = offset_range.start.to_display_point(&display_map) - // ..offset_range.end.to_display_point(&display_map); - - // if !select_prev_state.wordwise - // || (!movement::is_inside_word(&display_map, display_range.start) - // && !movement::is_inside_word(&display_map, display_range.end)) - // { - // next_selected_range = Some(offset_range); - // break; - // } - // } - - // if let Some(next_selected_range) = next_selected_range { - // self.unfold_ranges([next_selected_range.clone()], false, true, cx); - // self.change_selections(Some(Autoscroll::newest()), cx, |s| { - // if action.replace_newest { - // s.delete(s.newest_anchor().id); - // } - // s.insert_range(next_selected_range); - // }); - // } else { - // select_prev_state.done = true; - // } - // } - - // self.select_prev_state = Some(select_prev_state); - // } else if selections.len() == 1 { - // let selection = selections.last_mut().unwrap(); - // if selection.start == selection.end { - // let word_range = movement::surrounding_word( - // &display_map, - // selection.start.to_display_point(&display_map), - // ); - // selection.start = word_range.start.to_offset(&display_map, Bias::Left); - // selection.end = word_range.end.to_offset(&display_map, Bias::Left); - // selection.goal = SelectionGoal::None; - // selection.reversed = false; - - // let query = buffer - // .text_for_range(selection.start..selection.end) - // .collect::(); - // let query = query.chars().rev().collect::(); - // let select_state = SelectNextState { - // query: AhoCorasick::new(&[query])?, - // wordwise: true, - // done: false, - // }; - // self.unfold_ranges([selection.start..selection.end], false, true, cx); - // self.change_selections(Some(Autoscroll::newest()), cx, |s| { - // s.select(selections); - // }); - // self.select_prev_state = Some(select_state); - // } else { - // let query = buffer - // .text_for_range(selection.start..selection.end) - // .collect::(); - // let query = query.chars().rev().collect::(); - // self.select_prev_state = Some(SelectNextState { - // query: AhoCorasick::new(&[query])?, - // wordwise: false, - // done: false, - // }); - // self.select_previous(action, cx)?; - // } - // } - // Ok(()) - // } - - // pub fn toggle_comments(&mut self, action: &ToggleComments, cx: &mut ViewContext) { - // let text_layout_details = &self.text_layout_details(cx); - // self.transact(cx, |this, cx| { - // let mut selections = this.selections.all::(cx); - // let mut edits = Vec::new(); - // let mut selection_edit_ranges = Vec::new(); - // let mut last_toggled_row = None; - // let snapshot = this.buffer.read(cx).read(cx); - // let empty_str: Arc = "".into(); - // let mut suffixes_inserted = Vec::new(); - - // fn comment_prefix_range( - // snapshot: &MultiBufferSnapshot, - // row: u32, - // comment_prefix: &str, - // comment_prefix_whitespace: &str, - // ) -> Range { - // let start = Point::new(row, snapshot.indent_size_for_line(row).len); - - // let mut line_bytes = snapshot - // .bytes_in_range(start..snapshot.max_point()) - // .flatten() - // .copied(); - - // // If this line currently begins with the line comment prefix, then record - // // the range containing the prefix. - // if line_bytes - // .by_ref() - // .take(comment_prefix.len()) - // .eq(comment_prefix.bytes()) - // { - // // Include any whitespace that matches the comment prefix. - // let matching_whitespace_len = line_bytes - // .zip(comment_prefix_whitespace.bytes()) - // .take_while(|(a, b)| a == b) - // .count() as u32; - // let end = Point::new( - // start.row, - // start.column + comment_prefix.len() as u32 + matching_whitespace_len, - // ); - // start..end - // } else { - // start..start - // } - // } - - // fn comment_suffix_range( - // snapshot: &MultiBufferSnapshot, - // row: u32, - // comment_suffix: &str, - // comment_suffix_has_leading_space: bool, - // ) -> Range { - // let end = Point::new(row, snapshot.line_len(row)); - // let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32); - - // let mut line_end_bytes = snapshot - // .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end) - // .flatten() - // .copied(); - - // let leading_space_len = if suffix_start_column > 0 - // && line_end_bytes.next() == Some(b' ') - // && comment_suffix_has_leading_space - // { - // 1 - // } else { - // 0 - // }; - - // // If this line currently begins with the line comment prefix, then record - // // the range containing the prefix. - // if line_end_bytes.by_ref().eq(comment_suffix.bytes()) { - // let start = Point::new(end.row, suffix_start_column - leading_space_len); - // start..end - // } else { - // end..end - // } - // } - - // // TODO: Handle selections that cross excerpts - // for selection in &mut selections { - // let start_column = snapshot.indent_size_for_line(selection.start.row).len; - // let language = if let Some(language) = - // snapshot.language_scope_at(Point::new(selection.start.row, start_column)) - // { - // language - // } else { - // continue; - // }; - - // selection_edit_ranges.clear(); - - // // If multiple selections contain a given row, avoid processing that - // // row more than once. - // let mut start_row = selection.start.row; - // if last_toggled_row == Some(start_row) { - // start_row += 1; - // } - // let end_row = - // if selection.end.row > selection.start.row && selection.end.column == 0 { - // selection.end.row - 1 - // } else { - // selection.end.row - // }; - // last_toggled_row = Some(end_row); - - // if start_row > end_row { - // continue; - // } - - // // If the language has line comments, toggle those. - // if let Some(full_comment_prefix) = language.line_comment_prefix() { - // // Split the comment prefix's trailing whitespace into a separate string, - // // as that portion won't be used for detecting if a line is a comment. - // let comment_prefix = full_comment_prefix.trim_end_matches(' '); - // let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..]; - // let mut all_selection_lines_are_comments = true; - - // for row in start_row..=end_row { - // if snapshot.is_line_blank(row) && start_row < end_row { - // continue; - // } - - // let prefix_range = comment_prefix_range( - // snapshot.deref(), - // row, - // comment_prefix, - // comment_prefix_whitespace, - // ); - // if prefix_range.is_empty() { - // all_selection_lines_are_comments = false; - // } - // selection_edit_ranges.push(prefix_range); - // } - - // if all_selection_lines_are_comments { - // edits.extend( - // selection_edit_ranges - // .iter() - // .cloned() - // .map(|range| (range, empty_str.clone())), - // ); - // } else { - // let min_column = selection_edit_ranges - // .iter() - // .map(|r| r.start.column) - // .min() - // .unwrap_or(0); - // edits.extend(selection_edit_ranges.iter().map(|range| { - // let position = Point::new(range.start.row, min_column); - // (position..position, full_comment_prefix.clone()) - // })); - // } - // } else if let Some((full_comment_prefix, comment_suffix)) = - // language.block_comment_delimiters() - // { - // let comment_prefix = full_comment_prefix.trim_end_matches(' '); - // let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..]; - // let prefix_range = comment_prefix_range( - // snapshot.deref(), - // start_row, - // comment_prefix, - // comment_prefix_whitespace, - // ); - // let suffix_range = comment_suffix_range( - // snapshot.deref(), - // end_row, - // comment_suffix.trim_start_matches(' '), - // comment_suffix.starts_with(' '), - // ); - - // if prefix_range.is_empty() || suffix_range.is_empty() { - // edits.push(( - // prefix_range.start..prefix_range.start, - // full_comment_prefix.clone(), - // )); - // edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone())); - // suffixes_inserted.push((end_row, comment_suffix.len())); - // } else { - // edits.push((prefix_range, empty_str.clone())); - // edits.push((suffix_range, empty_str.clone())); - // } - // } else { - // continue; - // } - // } - - // drop(snapshot); - // this.buffer.update(cx, |buffer, cx| { - // buffer.edit(edits, None, cx); - // }); - - // // Adjust selections so that they end before any comment suffixes that - // // were inserted. - // let mut suffixes_inserted = suffixes_inserted.into_iter().peekable(); - // let mut selections = this.selections.all::(cx); - // let snapshot = this.buffer.read(cx).read(cx); - // for selection in &mut selections { - // while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() { - // match row.cmp(&selection.end.row) { - // Ordering::Less => { - // suffixes_inserted.next(); - // continue; - // } - // Ordering::Greater => break, - // Ordering::Equal => { - // if selection.end.column == snapshot.line_len(row) { - // if selection.is_empty() { - // selection.start.column -= suffix_len as u32; - // } - // selection.end.column -= suffix_len as u32; - // } - // break; - // } - // } - // } - // } - - // drop(snapshot); - // this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections)); - - // let selections = this.selections.all::(cx); - // let selections_on_single_row = selections.windows(2).all(|selections| { - // selections[0].start.row == selections[1].start.row - // && selections[0].end.row == selections[1].end.row - // && selections[0].start.row == selections[0].end.row - // }); - // let selections_selecting = selections - // .iter() - // .any(|selection| selection.start != selection.end); - // let advance_downwards = action.advance_downwards - // && selections_on_single_row - // && !selections_selecting - // && this.mode != EditorMode::SingleLine; - - // if advance_downwards { - // let snapshot = this.buffer.read(cx).snapshot(cx); - - // this.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.move_cursors_with(|display_snapshot, display_point, _| { - // let mut point = display_point.to_point(display_snapshot); - // point.row += 1; - // point = snapshot.clip_point(point, Bias::Left); - // let display_point = point.to_display_point(display_snapshot); - // let goal = SelectionGoal::HorizontalPosition( - // display_snapshot.x_for_point(display_point, &text_layout_details), - // ); - // (display_point, goal) - // }) - // }); - // } - // }); - // } - - // pub fn select_larger_syntax_node( - // &mut self, - // _: &SelectLargerSyntaxNode, - // cx: &mut ViewContext, - // ) { - // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - // let buffer = self.buffer.read(cx).snapshot(cx); - // let old_selections = self.selections.all::(cx).into_boxed_slice(); - - // let mut stack = mem::take(&mut self.select_larger_syntax_node_stack); - // let mut selected_larger_node = false; - // let new_selections = old_selections - // .iter() - // .map(|selection| { - // let old_range = selection.start..selection.end; - // let mut new_range = old_range.clone(); - // while let Some(containing_range) = - // buffer.range_for_syntax_ancestor(new_range.clone()) - // { - // new_range = containing_range; - // if !display_map.intersects_fold(new_range.start) - // && !display_map.intersects_fold(new_range.end) - // { - // break; - // } - // } - - // selected_larger_node |= new_range != old_range; - // Selection { - // id: selection.id, - // start: new_range.start, - // end: new_range.end, - // goal: SelectionGoal::None, - // reversed: selection.reversed, - // } - // }) - // .collect::>(); - - // if selected_larger_node { - // stack.push(old_selections); - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.select(new_selections); - // }); - // } - // self.select_larger_syntax_node_stack = stack; - // } - - // pub fn select_smaller_syntax_node( - // &mut self, - // _: &SelectSmallerSyntaxNode, - // cx: &mut ViewContext, - // ) { - // let mut stack = mem::take(&mut self.select_larger_syntax_node_stack); - // if let Some(selections) = stack.pop() { - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.select(selections.to_vec()); - // }); - // } - // self.select_larger_syntax_node_stack = stack; - // } - - // pub fn move_to_enclosing_bracket( - // &mut self, - // _: &MoveToEnclosingBracket, - // cx: &mut ViewContext, - // ) { - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.move_offsets_with(|snapshot, selection| { - // let Some(enclosing_bracket_ranges) = - // snapshot.enclosing_bracket_ranges(selection.start..selection.end) - // else { - // return; - // }; - - // let mut best_length = usize::MAX; - // let mut best_inside = false; - // let mut best_in_bracket_range = false; - // let mut best_destination = None; - // for (open, close) in enclosing_bracket_ranges { - // let close = close.to_inclusive(); - // let length = close.end() - open.start; - // let inside = selection.start >= open.end && selection.end <= *close.start(); - // let in_bracket_range = open.to_inclusive().contains(&selection.head()) - // || close.contains(&selection.head()); - - // // If best is next to a bracket and current isn't, skip - // if !in_bracket_range && best_in_bracket_range { - // continue; - // } - - // // Prefer smaller lengths unless best is inside and current isn't - // if length > best_length && (best_inside || !inside) { - // continue; - // } - - // best_length = length; - // best_inside = inside; - // best_in_bracket_range = in_bracket_range; - // best_destination = Some( - // if close.contains(&selection.start) && close.contains(&selection.end) { - // if inside { - // open.end - // } else { - // open.start - // } - // } else { - // if inside { - // *close.start() - // } else { - // *close.end() - // } - // }, - // ); - // } - - // if let Some(destination) = best_destination { - // selection.collapse_to(destination, SelectionGoal::None); - // } - // }) - // }); - // } - - // pub fn undo_selection(&mut self, _: &UndoSelection, cx: &mut ViewContext) { - // self.end_selection(cx); - // self.selection_history.mode = SelectionHistoryMode::Undoing; - // if let Some(entry) = self.selection_history.undo_stack.pop_back() { - // self.change_selections(None, cx, |s| s.select_anchors(entry.selections.to_vec())); - // self.select_next_state = entry.select_next_state; - // self.select_prev_state = entry.select_prev_state; - // self.add_selections_state = entry.add_selections_state; - // self.request_autoscroll(Autoscroll::newest(), cx); - // } - // self.selection_history.mode = SelectionHistoryMode::Normal; - // } - - // pub fn redo_selection(&mut self, _: &RedoSelection, cx: &mut ViewContext) { - // self.end_selection(cx); - // self.selection_history.mode = SelectionHistoryMode::Redoing; - // if let Some(entry) = self.selection_history.redo_stack.pop_back() { - // self.change_selections(None, cx, |s| s.select_anchors(entry.selections.to_vec())); - // self.select_next_state = entry.select_next_state; - // self.select_prev_state = entry.select_prev_state; - // self.add_selections_state = entry.add_selections_state; - // self.request_autoscroll(Autoscroll::newest(), cx); - // } - // self.selection_history.mode = SelectionHistoryMode::Normal; - // } - - // fn go_to_diagnostic(&mut self, _: &GoToDiagnostic, cx: &mut ViewContext) { - // self.go_to_diagnostic_impl(Direction::Next, cx) - // } - - // fn go_to_prev_diagnostic(&mut self, _: &GoToPrevDiagnostic, cx: &mut ViewContext) { - // self.go_to_diagnostic_impl(Direction::Prev, cx) - // } - - // pub fn go_to_diagnostic_impl(&mut self, direction: Direction, cx: &mut ViewContext) { - // let buffer = self.buffer.read(cx).snapshot(cx); - // let selection = self.selections.newest::(cx); - - // // If there is an active Diagnostic Popover. Jump to it's diagnostic instead. - // if direction == Direction::Next { - // if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() { - // let (group_id, jump_to) = popover.activation_info(); - // if self.activate_diagnostics(group_id, cx) { - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // let mut new_selection = s.newest_anchor().clone(); - // new_selection.collapse_to(jump_to, SelectionGoal::None); - // s.select_anchors(vec![new_selection.clone()]); - // }); - // } - // return; - // } - // } - - // let mut active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| { - // active_diagnostics - // .primary_range - // .to_offset(&buffer) - // .to_inclusive() - // }); - // let mut search_start = if let Some(active_primary_range) = active_primary_range.as_ref() { - // if active_primary_range.contains(&selection.head()) { - // *active_primary_range.end() - // } else { - // selection.head() - // } - // } else { - // selection.head() - // }; - - // loop { - // let mut diagnostics = if direction == Direction::Prev { - // buffer.diagnostics_in_range::<_, usize>(0..search_start, true) - // } else { - // buffer.diagnostics_in_range::<_, usize>(search_start..buffer.len(), false) - // }; - // let group = diagnostics.find_map(|entry| { - // if entry.diagnostic.is_primary - // && entry.diagnostic.severity <= DiagnosticSeverity::WARNING - // && !entry.range.is_empty() - // && Some(entry.range.end) != active_primary_range.as_ref().map(|r| *r.end()) - // && !entry.range.contains(&search_start) - // { - // Some((entry.range, entry.diagnostic.group_id)) - // } else { - // None - // } - // }); - - // if let Some((primary_range, group_id)) = group { - // if self.activate_diagnostics(group_id, cx) { - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.select(vec![Selection { - // id: selection.id, - // start: primary_range.start, - // end: primary_range.start, - // reversed: false, - // goal: SelectionGoal::None, - // }]); - // }); - // } - // break; - // } else { - // // Cycle around to the start of the buffer, potentially moving back to the start of - // // the currently active diagnostic. - // active_primary_range.take(); - // if direction == Direction::Prev { - // if search_start == buffer.len() { - // break; - // } else { - // search_start = buffer.len(); - // } - // } else if search_start == 0 { - // break; - // } else { - // search_start = 0; - // } - // } - // } - // } - - // fn go_to_hunk(&mut self, _: &GoToHunk, cx: &mut ViewContext) { - // let snapshot = self - // .display_map - // .update(cx, |display_map, cx| display_map.snapshot(cx)); - // let selection = self.selections.newest::(cx); - - // if !self.seek_in_direction( - // &snapshot, - // selection.head(), - // false, - // snapshot - // .buffer_snapshot - // .git_diff_hunks_in_range((selection.head().row + 1)..u32::MAX), - // cx, - // ) { - // let wrapped_point = Point::zero(); - // self.seek_in_direction( - // &snapshot, - // wrapped_point, - // true, - // snapshot - // .buffer_snapshot - // .git_diff_hunks_in_range((wrapped_point.row + 1)..u32::MAX), - // cx, - // ); - // } - // } - - // fn go_to_prev_hunk(&mut self, _: &GoToPrevHunk, cx: &mut ViewContext) { - // let snapshot = self - // .display_map - // .update(cx, |display_map, cx| display_map.snapshot(cx)); - // let selection = self.selections.newest::(cx); - - // if !self.seek_in_direction( - // &snapshot, - // selection.head(), - // false, - // snapshot - // .buffer_snapshot - // .git_diff_hunks_in_range_rev(0..selection.head().row), - // cx, - // ) { - // let wrapped_point = snapshot.buffer_snapshot.max_point(); - // self.seek_in_direction( - // &snapshot, - // wrapped_point, - // true, - // snapshot - // .buffer_snapshot - // .git_diff_hunks_in_range_rev(0..wrapped_point.row), - // cx, - // ); - // } - // } - - // fn seek_in_direction( - // &mut self, - // snapshot: &DisplaySnapshot, - // initial_point: Point, - // is_wrapped: bool, - // hunks: impl Iterator>, - // cx: &mut ViewContext, - // ) -> bool { - // let display_point = initial_point.to_display_point(snapshot); - // let mut hunks = hunks - // .map(|hunk| diff_hunk_to_display(hunk, &snapshot)) - // .filter(|hunk| { - // if is_wrapped { - // true - // } else { - // !hunk.contains_display_row(display_point.row()) - // } - // }) - // .dedup(); - - // if let Some(hunk) = hunks.next() { - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // let row = hunk.start_display_row(); - // let point = DisplayPoint::new(row, 0); - // s.select_display_ranges([point..point]); - // }); - - // true - // } else { - // false - // } - // } + pub fn select_to_end(&mut self, _: &SelectToEnd, cx: &mut ViewContext) { + let buffer = self.buffer.read(cx).snapshot(cx); + let mut selection = self.selections.first::(cx); + selection.set_head(buffer.len(), SelectionGoal::None); + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select(vec![selection]); + }); + } + + pub fn select_all(&mut self, _: &SelectAll, cx: &mut ViewContext) { + let end = self.buffer.read(cx).read(cx).len(); + self.change_selections(None, cx, |s| { + s.select_ranges(vec![0..end]); + }); + } + + pub fn select_line(&mut self, _: &SelectLine, cx: &mut ViewContext) { + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let mut selections = self.selections.all::(cx); + let max_point = display_map.buffer_snapshot.max_point(); + for selection in &mut selections { + let rows = selection.spanned_rows(true, &display_map); + selection.start = Point::new(rows.start, 0); + selection.end = cmp::min(max_point, Point::new(rows.end, 0)); + selection.reversed = false; + } + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select(selections); + }); + } + + pub fn split_selection_into_lines( + &mut self, + _: &SplitSelectionIntoLines, + cx: &mut ViewContext, + ) { + let mut to_unfold = Vec::new(); + let mut new_selection_ranges = Vec::new(); + { + let selections = self.selections.all::(cx); + let buffer = self.buffer.read(cx).read(cx); + for selection in selections { + for row in selection.start.row..selection.end.row { + let cursor = Point::new(row, buffer.line_len(row)); + new_selection_ranges.push(cursor..cursor); + } + new_selection_ranges.push(selection.end..selection.end); + to_unfold.push(selection.start..selection.end); + } + } + self.unfold_ranges(to_unfold, true, true, cx); + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select_ranges(new_selection_ranges); + }); + } + + pub fn add_selection_above(&mut self, _: &AddSelectionAbove, cx: &mut ViewContext) { + self.add_selection(true, cx); + } + + pub fn add_selection_below(&mut self, _: &AddSelectionBelow, cx: &mut ViewContext) { + self.add_selection(false, cx); + } + + fn add_selection(&mut self, above: bool, cx: &mut ViewContext) { + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let mut selections = self.selections.all::(cx); + let text_layout_details = self.text_layout_details(cx); + let mut state = self.add_selections_state.take().unwrap_or_else(|| { + let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone(); + let range = oldest_selection.display_range(&display_map).sorted(); + + let start_x = display_map.x_for_point(range.start, &text_layout_details); + let end_x = display_map.x_for_point(range.end, &text_layout_details); + let positions = start_x.min(end_x)..start_x.max(end_x); + + selections.clear(); + let mut stack = Vec::new(); + for row in range.start.row()..=range.end.row() { + if let Some(selection) = self.selections.build_columnar_selection( + &display_map, + row, + &positions, + oldest_selection.reversed, + &text_layout_details, + ) { + stack.push(selection.id); + selections.push(selection); + } + } + + if above { + stack.reverse(); + } + + AddSelectionsState { above, stack } + }); + + let last_added_selection = *state.stack.last().unwrap(); + let mut new_selections = Vec::new(); + if above == state.above { + let end_row = if above { + 0 + } else { + display_map.max_point().row() + }; + + 'outer: for selection in selections { + if selection.id == last_added_selection { + let range = selection.display_range(&display_map).sorted(); + debug_assert_eq!(range.start.row(), range.end.row()); + let mut row = range.start.row(); + let positions = if let SelectionGoal::HorizontalRange { start, end } = + selection.goal + { + px(start)..px(end) + } else { + let start_x = display_map.x_for_point(range.start, &text_layout_details); + let end_x = display_map.x_for_point(range.end, &text_layout_details); + start_x.min(end_x)..start_x.max(end_x) + }; + + while row != end_row { + if above { + row -= 1; + } else { + row += 1; + } + + if let Some(new_selection) = self.selections.build_columnar_selection( + &display_map, + row, + &positions, + selection.reversed, + &text_layout_details, + ) { + state.stack.push(new_selection.id); + if above { + new_selections.push(new_selection); + new_selections.push(selection); + } else { + new_selections.push(selection); + new_selections.push(new_selection); + } + + continue 'outer; + } + } + } + + new_selections.push(selection); + } + } else { + new_selections = selections; + new_selections.retain(|s| s.id != last_added_selection); + state.stack.pop(); + } + + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select(new_selections); + }); + if state.stack.len() > 1 { + self.add_selections_state = Some(state); + } + } + + pub fn select_next_match_internal( + &mut self, + display_map: &DisplaySnapshot, + replace_newest: bool, + autoscroll: Option, + cx: &mut ViewContext, + ) -> Result<()> { + fn select_next_match_ranges( + this: &mut Editor, + range: Range, + replace_newest: bool, + auto_scroll: Option, + cx: &mut ViewContext, + ) { + this.unfold_ranges([range.clone()], false, true, cx); + this.change_selections(auto_scroll, cx, |s| { + if replace_newest { + s.delete(s.newest_anchor().id); + } + s.insert_range(range.clone()); + }); + } + + let buffer = &display_map.buffer_snapshot; + let mut selections = self.selections.all::(cx); + if let Some(mut select_next_state) = self.select_next_state.take() { + let query = &select_next_state.query; + if !select_next_state.done { + let first_selection = selections.iter().min_by_key(|s| s.id).unwrap(); + let last_selection = selections.iter().max_by_key(|s| s.id).unwrap(); + let mut next_selected_range = None; + + let bytes_after_last_selection = + buffer.bytes_in_range(last_selection.end..buffer.len()); + let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start); + let query_matches = query + .stream_find_iter(bytes_after_last_selection) + .map(|result| (last_selection.end, result)) + .chain( + query + .stream_find_iter(bytes_before_first_selection) + .map(|result| (0, result)), + ); + + for (start_offset, query_match) in query_matches { + let query_match = query_match.unwrap(); // can only fail due to I/O + let offset_range = + start_offset + query_match.start()..start_offset + query_match.end(); + let display_range = offset_range.start.to_display_point(&display_map) + ..offset_range.end.to_display_point(&display_map); + + if !select_next_state.wordwise + || (!movement::is_inside_word(&display_map, display_range.start) + && !movement::is_inside_word(&display_map, display_range.end)) + { + if selections + .iter() + .find(|selection| selection.range().overlaps(&offset_range)) + .is_none() + { + next_selected_range = Some(offset_range); + break; + } + } + } + + if let Some(next_selected_range) = next_selected_range { + select_next_match_ranges( + self, + next_selected_range, + replace_newest, + autoscroll, + cx, + ); + } else { + select_next_state.done = true; + } + } + + self.select_next_state = Some(select_next_state); + } else if selections.len() == 1 { + let selection = selections.last_mut().unwrap(); + if selection.start == selection.end { + let word_range = movement::surrounding_word( + &display_map, + selection.start.to_display_point(&display_map), + ); + selection.start = word_range.start.to_offset(&display_map, Bias::Left); + selection.end = word_range.end.to_offset(&display_map, Bias::Left); + selection.goal = SelectionGoal::None; + selection.reversed = false; + + let query = buffer + .text_for_range(selection.start..selection.end) + .collect::(); + + let is_empty = query.is_empty(); + let select_state = SelectNextState { + query: AhoCorasick::new(&[query])?, + wordwise: true, + done: is_empty, + }; + select_next_match_ranges( + self, + selection.start..selection.end, + replace_newest, + autoscroll, + cx, + ); + self.select_next_state = Some(select_state); + } else { + let query = buffer + .text_for_range(selection.start..selection.end) + .collect::(); + self.select_next_state = Some(SelectNextState { + query: AhoCorasick::new(&[query])?, + wordwise: false, + done: false, + }); + self.select_next_match_internal(display_map, replace_newest, autoscroll, cx)?; + } + } + Ok(()) + } + + pub fn select_all_matches( + &mut self, + action: &SelectAllMatches, + cx: &mut ViewContext, + ) -> Result<()> { + self.push_to_selection_history(); + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + + loop { + self.select_next_match_internal(&display_map, action.replace_newest, None, cx)?; + + if self + .select_next_state + .as_ref() + .map(|selection_state| selection_state.done) + .unwrap_or(true) + { + break; + } + } + + Ok(()) + } + + pub fn select_next(&mut self, action: &SelectNext, cx: &mut ViewContext) -> Result<()> { + self.push_to_selection_history(); + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + self.select_next_match_internal( + &display_map, + action.replace_newest, + Some(Autoscroll::newest()), + cx, + )?; + Ok(()) + } + + pub fn select_previous( + &mut self, + action: &SelectPrevious, + cx: &mut ViewContext, + ) -> Result<()> { + self.push_to_selection_history(); + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let buffer = &display_map.buffer_snapshot; + let mut selections = self.selections.all::(cx); + if let Some(mut select_prev_state) = self.select_prev_state.take() { + let query = &select_prev_state.query; + if !select_prev_state.done { + let first_selection = selections.iter().min_by_key(|s| s.id).unwrap(); + let last_selection = selections.iter().max_by_key(|s| s.id).unwrap(); + let mut next_selected_range = None; + // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer. + let bytes_before_last_selection = + buffer.reversed_bytes_in_range(0..last_selection.start); + let bytes_after_first_selection = + buffer.reversed_bytes_in_range(first_selection.end..buffer.len()); + let query_matches = query + .stream_find_iter(bytes_before_last_selection) + .map(|result| (last_selection.start, result)) + .chain( + query + .stream_find_iter(bytes_after_first_selection) + .map(|result| (buffer.len(), result)), + ); + for (end_offset, query_match) in query_matches { + let query_match = query_match.unwrap(); // can only fail due to I/O + let offset_range = + end_offset - query_match.end()..end_offset - query_match.start(); + let display_range = offset_range.start.to_display_point(&display_map) + ..offset_range.end.to_display_point(&display_map); + + if !select_prev_state.wordwise + || (!movement::is_inside_word(&display_map, display_range.start) + && !movement::is_inside_word(&display_map, display_range.end)) + { + next_selected_range = Some(offset_range); + break; + } + } + + if let Some(next_selected_range) = next_selected_range { + self.unfold_ranges([next_selected_range.clone()], false, true, cx); + self.change_selections(Some(Autoscroll::newest()), cx, |s| { + if action.replace_newest { + s.delete(s.newest_anchor().id); + } + s.insert_range(next_selected_range); + }); + } else { + select_prev_state.done = true; + } + } + + self.select_prev_state = Some(select_prev_state); + } else if selections.len() == 1 { + let selection = selections.last_mut().unwrap(); + if selection.start == selection.end { + let word_range = movement::surrounding_word( + &display_map, + selection.start.to_display_point(&display_map), + ); + selection.start = word_range.start.to_offset(&display_map, Bias::Left); + selection.end = word_range.end.to_offset(&display_map, Bias::Left); + selection.goal = SelectionGoal::None; + selection.reversed = false; + + let query = buffer + .text_for_range(selection.start..selection.end) + .collect::(); + let query = query.chars().rev().collect::(); + let select_state = SelectNextState { + query: AhoCorasick::new(&[query])?, + wordwise: true, + done: false, + }; + self.unfold_ranges([selection.start..selection.end], false, true, cx); + self.change_selections(Some(Autoscroll::newest()), cx, |s| { + s.select(selections); + }); + self.select_prev_state = Some(select_state); + } else { + let query = buffer + .text_for_range(selection.start..selection.end) + .collect::(); + let query = query.chars().rev().collect::(); + self.select_prev_state = Some(SelectNextState { + query: AhoCorasick::new(&[query])?, + wordwise: false, + done: false, + }); + self.select_previous(action, cx)?; + } + } + Ok(()) + } + + pub fn toggle_comments(&mut self, action: &ToggleComments, cx: &mut ViewContext) { + let text_layout_details = &self.text_layout_details(cx); + self.transact(cx, |this, cx| { + let mut selections = this.selections.all::(cx); + let mut edits = Vec::new(); + let mut selection_edit_ranges = Vec::new(); + let mut last_toggled_row = None; + let snapshot = this.buffer.read(cx).read(cx); + let empty_str: Arc = "".into(); + let mut suffixes_inserted = Vec::new(); + + fn comment_prefix_range( + snapshot: &MultiBufferSnapshot, + row: u32, + comment_prefix: &str, + comment_prefix_whitespace: &str, + ) -> Range { + let start = Point::new(row, snapshot.indent_size_for_line(row).len); + + let mut line_bytes = snapshot + .bytes_in_range(start..snapshot.max_point()) + .flatten() + .copied(); + + // If this line currently begins with the line comment prefix, then record + // the range containing the prefix. + if line_bytes + .by_ref() + .take(comment_prefix.len()) + .eq(comment_prefix.bytes()) + { + // Include any whitespace that matches the comment prefix. + let matching_whitespace_len = line_bytes + .zip(comment_prefix_whitespace.bytes()) + .take_while(|(a, b)| a == b) + .count() as u32; + let end = Point::new( + start.row, + start.column + comment_prefix.len() as u32 + matching_whitespace_len, + ); + start..end + } else { + start..start + } + } + + fn comment_suffix_range( + snapshot: &MultiBufferSnapshot, + row: u32, + comment_suffix: &str, + comment_suffix_has_leading_space: bool, + ) -> Range { + let end = Point::new(row, snapshot.line_len(row)); + let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32); + + let mut line_end_bytes = snapshot + .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end) + .flatten() + .copied(); + + let leading_space_len = if suffix_start_column > 0 + && line_end_bytes.next() == Some(b' ') + && comment_suffix_has_leading_space + { + 1 + } else { + 0 + }; + + // If this line currently begins with the line comment prefix, then record + // the range containing the prefix. + if line_end_bytes.by_ref().eq(comment_suffix.bytes()) { + let start = Point::new(end.row, suffix_start_column - leading_space_len); + start..end + } else { + end..end + } + } + + // TODO: Handle selections that cross excerpts + for selection in &mut selections { + let start_column = snapshot.indent_size_for_line(selection.start.row).len; + let language = if let Some(language) = + snapshot.language_scope_at(Point::new(selection.start.row, start_column)) + { + language + } else { + continue; + }; + + selection_edit_ranges.clear(); + + // If multiple selections contain a given row, avoid processing that + // row more than once. + let mut start_row = selection.start.row; + if last_toggled_row == Some(start_row) { + start_row += 1; + } + let end_row = + if selection.end.row > selection.start.row && selection.end.column == 0 { + selection.end.row - 1 + } else { + selection.end.row + }; + last_toggled_row = Some(end_row); + + if start_row > end_row { + continue; + } + + // If the language has line comments, toggle those. + if let Some(full_comment_prefix) = language.line_comment_prefix() { + // Split the comment prefix's trailing whitespace into a separate string, + // as that portion won't be used for detecting if a line is a comment. + let comment_prefix = full_comment_prefix.trim_end_matches(' '); + let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..]; + let mut all_selection_lines_are_comments = true; + + for row in start_row..=end_row { + if snapshot.is_line_blank(row) && start_row < end_row { + continue; + } + + let prefix_range = comment_prefix_range( + snapshot.deref(), + row, + comment_prefix, + comment_prefix_whitespace, + ); + if prefix_range.is_empty() { + all_selection_lines_are_comments = false; + } + selection_edit_ranges.push(prefix_range); + } + + if all_selection_lines_are_comments { + edits.extend( + selection_edit_ranges + .iter() + .cloned() + .map(|range| (range, empty_str.clone())), + ); + } else { + let min_column = selection_edit_ranges + .iter() + .map(|r| r.start.column) + .min() + .unwrap_or(0); + edits.extend(selection_edit_ranges.iter().map(|range| { + let position = Point::new(range.start.row, min_column); + (position..position, full_comment_prefix.clone()) + })); + } + } else if let Some((full_comment_prefix, comment_suffix)) = + language.block_comment_delimiters() + { + let comment_prefix = full_comment_prefix.trim_end_matches(' '); + let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..]; + let prefix_range = comment_prefix_range( + snapshot.deref(), + start_row, + comment_prefix, + comment_prefix_whitespace, + ); + let suffix_range = comment_suffix_range( + snapshot.deref(), + end_row, + comment_suffix.trim_start_matches(' '), + comment_suffix.starts_with(' '), + ); + + if prefix_range.is_empty() || suffix_range.is_empty() { + edits.push(( + prefix_range.start..prefix_range.start, + full_comment_prefix.clone(), + )); + edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone())); + suffixes_inserted.push((end_row, comment_suffix.len())); + } else { + edits.push((prefix_range, empty_str.clone())); + edits.push((suffix_range, empty_str.clone())); + } + } else { + continue; + } + } + + drop(snapshot); + this.buffer.update(cx, |buffer, cx| { + buffer.edit(edits, None, cx); + }); + + // Adjust selections so that they end before any comment suffixes that + // were inserted. + let mut suffixes_inserted = suffixes_inserted.into_iter().peekable(); + let mut selections = this.selections.all::(cx); + let snapshot = this.buffer.read(cx).read(cx); + for selection in &mut selections { + while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() { + match row.cmp(&selection.end.row) { + Ordering::Less => { + suffixes_inserted.next(); + continue; + } + Ordering::Greater => break, + Ordering::Equal => { + if selection.end.column == snapshot.line_len(row) { + if selection.is_empty() { + selection.start.column -= suffix_len as u32; + } + selection.end.column -= suffix_len as u32; + } + break; + } + } + } + } + + drop(snapshot); + this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections)); + + let selections = this.selections.all::(cx); + let selections_on_single_row = selections.windows(2).all(|selections| { + selections[0].start.row == selections[1].start.row + && selections[0].end.row == selections[1].end.row + && selections[0].start.row == selections[0].end.row + }); + let selections_selecting = selections + .iter() + .any(|selection| selection.start != selection.end); + let advance_downwards = action.advance_downwards + && selections_on_single_row + && !selections_selecting + && this.mode != EditorMode::SingleLine; + + if advance_downwards { + let snapshot = this.buffer.read(cx).snapshot(cx); + + this.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_cursors_with(|display_snapshot, display_point, _| { + let mut point = display_point.to_point(display_snapshot); + point.row += 1; + point = snapshot.clip_point(point, Bias::Left); + let display_point = point.to_display_point(display_snapshot); + let goal = SelectionGoal::HorizontalPosition( + display_snapshot + .x_for_point(display_point, &text_layout_details) + .into(), + ); + (display_point, goal) + }) + }); + } + }); + } + + pub fn select_larger_syntax_node( + &mut self, + _: &SelectLargerSyntaxNode, + cx: &mut ViewContext, + ) { + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let buffer = self.buffer.read(cx).snapshot(cx); + let old_selections = self.selections.all::(cx).into_boxed_slice(); + + let mut stack = mem::take(&mut self.select_larger_syntax_node_stack); + let mut selected_larger_node = false; + let new_selections = old_selections + .iter() + .map(|selection| { + let old_range = selection.start..selection.end; + let mut new_range = old_range.clone(); + while let Some(containing_range) = + buffer.range_for_syntax_ancestor(new_range.clone()) + { + new_range = containing_range; + if !display_map.intersects_fold(new_range.start) + && !display_map.intersects_fold(new_range.end) + { + break; + } + } + + selected_larger_node |= new_range != old_range; + Selection { + id: selection.id, + start: new_range.start, + end: new_range.end, + goal: SelectionGoal::None, + reversed: selection.reversed, + } + }) + .collect::>(); + + if selected_larger_node { + stack.push(old_selections); + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select(new_selections); + }); + } + self.select_larger_syntax_node_stack = stack; + } + + pub fn select_smaller_syntax_node( + &mut self, + _: &SelectSmallerSyntaxNode, + cx: &mut ViewContext, + ) { + let mut stack = mem::take(&mut self.select_larger_syntax_node_stack); + if let Some(selections) = stack.pop() { + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select(selections.to_vec()); + }); + } + self.select_larger_syntax_node_stack = stack; + } + + pub fn move_to_enclosing_bracket( + &mut self, + _: &MoveToEnclosingBracket, + cx: &mut ViewContext, + ) { + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_offsets_with(|snapshot, selection| { + let Some(enclosing_bracket_ranges) = + snapshot.enclosing_bracket_ranges(selection.start..selection.end) + else { + return; + }; + + let mut best_length = usize::MAX; + let mut best_inside = false; + let mut best_in_bracket_range = false; + let mut best_destination = None; + for (open, close) in enclosing_bracket_ranges { + let close = close.to_inclusive(); + let length = close.end() - open.start; + let inside = selection.start >= open.end && selection.end <= *close.start(); + let in_bracket_range = open.to_inclusive().contains(&selection.head()) + || close.contains(&selection.head()); + + // If best is next to a bracket and current isn't, skip + if !in_bracket_range && best_in_bracket_range { + continue; + } + + // Prefer smaller lengths unless best is inside and current isn't + if length > best_length && (best_inside || !inside) { + continue; + } + + best_length = length; + best_inside = inside; + best_in_bracket_range = in_bracket_range; + best_destination = Some( + if close.contains(&selection.start) && close.contains(&selection.end) { + if inside { + open.end + } else { + open.start + } + } else { + if inside { + *close.start() + } else { + *close.end() + } + }, + ); + } + + if let Some(destination) = best_destination { + selection.collapse_to(destination, SelectionGoal::None); + } + }) + }); + } + + pub fn undo_selection(&mut self, _: &UndoSelection, cx: &mut ViewContext) { + self.end_selection(cx); + self.selection_history.mode = SelectionHistoryMode::Undoing; + if let Some(entry) = self.selection_history.undo_stack.pop_back() { + self.change_selections(None, cx, |s| s.select_anchors(entry.selections.to_vec())); + self.select_next_state = entry.select_next_state; + self.select_prev_state = entry.select_prev_state; + self.add_selections_state = entry.add_selections_state; + self.request_autoscroll(Autoscroll::newest(), cx); + } + self.selection_history.mode = SelectionHistoryMode::Normal; + } + + pub fn redo_selection(&mut self, _: &RedoSelection, cx: &mut ViewContext) { + self.end_selection(cx); + self.selection_history.mode = SelectionHistoryMode::Redoing; + if let Some(entry) = self.selection_history.redo_stack.pop_back() { + self.change_selections(None, cx, |s| s.select_anchors(entry.selections.to_vec())); + self.select_next_state = entry.select_next_state; + self.select_prev_state = entry.select_prev_state; + self.add_selections_state = entry.add_selections_state; + self.request_autoscroll(Autoscroll::newest(), cx); + } + self.selection_history.mode = SelectionHistoryMode::Normal; + } + + fn go_to_diagnostic(&mut self, _: &GoToDiagnostic, cx: &mut ViewContext) { + self.go_to_diagnostic_impl(Direction::Next, cx) + } + + fn go_to_prev_diagnostic(&mut self, _: &GoToPrevDiagnostic, cx: &mut ViewContext) { + self.go_to_diagnostic_impl(Direction::Prev, cx) + } + + pub fn go_to_diagnostic_impl(&mut self, direction: Direction, cx: &mut ViewContext) { + let buffer = self.buffer.read(cx).snapshot(cx); + let selection = self.selections.newest::(cx); + + // If there is an active Diagnostic Popover. Jump to it's diagnostic instead. + if direction == Direction::Next { + if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() { + let (group_id, jump_to) = popover.activation_info(); + if self.activate_diagnostics(group_id, cx) { + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + let mut new_selection = s.newest_anchor().clone(); + new_selection.collapse_to(jump_to, SelectionGoal::None); + s.select_anchors(vec![new_selection.clone()]); + }); + } + return; + } + } + + let mut active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| { + active_diagnostics + .primary_range + .to_offset(&buffer) + .to_inclusive() + }); + let mut search_start = if let Some(active_primary_range) = active_primary_range.as_ref() { + if active_primary_range.contains(&selection.head()) { + *active_primary_range.end() + } else { + selection.head() + } + } else { + selection.head() + }; + + loop { + let mut diagnostics = if direction == Direction::Prev { + buffer.diagnostics_in_range::<_, usize>(0..search_start, true) + } else { + buffer.diagnostics_in_range::<_, usize>(search_start..buffer.len(), false) + }; + let group = diagnostics.find_map(|entry| { + if entry.diagnostic.is_primary + && entry.diagnostic.severity <= DiagnosticSeverity::WARNING + && !entry.range.is_empty() + && Some(entry.range.end) != active_primary_range.as_ref().map(|r| *r.end()) + && !entry.range.contains(&search_start) + { + Some((entry.range, entry.diagnostic.group_id)) + } else { + None + } + }); + + if let Some((primary_range, group_id)) = group { + if self.activate_diagnostics(group_id, cx) { + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select(vec![Selection { + id: selection.id, + start: primary_range.start, + end: primary_range.start, + reversed: false, + goal: SelectionGoal::None, + }]); + }); + } + break; + } else { + // Cycle around to the start of the buffer, potentially moving back to the start of + // the currently active diagnostic. + active_primary_range.take(); + if direction == Direction::Prev { + if search_start == buffer.len() { + break; + } else { + search_start = buffer.len(); + } + } else if search_start == 0 { + break; + } else { + search_start = 0; + } + } + } + } + + fn go_to_hunk(&mut self, _: &GoToHunk, cx: &mut ViewContext) { + let snapshot = self + .display_map + .update(cx, |display_map, cx| display_map.snapshot(cx)); + let selection = self.selections.newest::(cx); + + if !self.seek_in_direction( + &snapshot, + selection.head(), + false, + snapshot + .buffer_snapshot + .git_diff_hunks_in_range((selection.head().row + 1)..u32::MAX), + cx, + ) { + let wrapped_point = Point::zero(); + self.seek_in_direction( + &snapshot, + wrapped_point, + true, + snapshot + .buffer_snapshot + .git_diff_hunks_in_range((wrapped_point.row + 1)..u32::MAX), + cx, + ); + } + } + + fn go_to_prev_hunk(&mut self, _: &GoToPrevHunk, cx: &mut ViewContext) { + let snapshot = self + .display_map + .update(cx, |display_map, cx| display_map.snapshot(cx)); + let selection = self.selections.newest::(cx); + + if !self.seek_in_direction( + &snapshot, + selection.head(), + false, + snapshot + .buffer_snapshot + .git_diff_hunks_in_range_rev(0..selection.head().row), + cx, + ) { + let wrapped_point = snapshot.buffer_snapshot.max_point(); + self.seek_in_direction( + &snapshot, + wrapped_point, + true, + snapshot + .buffer_snapshot + .git_diff_hunks_in_range_rev(0..wrapped_point.row), + cx, + ); + } + } + + fn seek_in_direction( + &mut self, + snapshot: &DisplaySnapshot, + initial_point: Point, + is_wrapped: bool, + hunks: impl Iterator>, + cx: &mut ViewContext, + ) -> bool { + let display_point = initial_point.to_display_point(snapshot); + let mut hunks = hunks + .map(|hunk| diff_hunk_to_display(hunk, &snapshot)) + .filter(|hunk| { + if is_wrapped { + true + } else { + !hunk.contains_display_row(display_point.row()) + } + }) + .dedup(); + + if let Some(hunk) = hunks.next() { + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + let row = hunk.start_display_row(); + let point = DisplayPoint::new(row, 0); + s.select_display_ranges([point..point]); + }); + + true + } else { + false + } + } pub fn go_to_definition(&mut self, _: &GoToDefinition, cx: &mut ViewContext) { self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, cx); @@ -7726,139 +7732,138 @@ impl Editor { } } - // pub fn rename(&mut self, _: &Rename, cx: &mut ViewContext) -> Option>> { - // use language::ToOffset as _; + // pub fn rename(&mut self, _: &Rename, cx: &mut ViewContext) -> Option>> { + // use language::ToOffset as _; - // let project = self.project.clone()?; - // let selection = self.selections.newest_anchor().clone(); - // let (cursor_buffer, cursor_buffer_position) = self - // .buffer - // .read(cx) - // .text_anchor_for_position(selection.head(), cx)?; - // let (tail_buffer, _) = self - // .buffer - // .read(cx) - // .text_anchor_for_position(selection.tail(), cx)?; - // if tail_buffer != cursor_buffer { - // return None; + // let project = self.project.clone()?; + // let selection = self.selections.newest_anchor().clone(); + // let (cursor_buffer, cursor_buffer_position) = self + // .buffer + // .read(cx) + // .text_anchor_for_position(selection.head(), cx)?; + // let (tail_buffer, _) = self + // .buffer + // .read(cx) + // .text_anchor_for_position(selection.tail(), cx)?; + // if tail_buffer != cursor_buffer { + // return None; + // } + + // let snapshot = cursor_buffer.read(cx).snapshot(); + // let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot); + // let prepare_rename = project.update(cx, |project, cx| { + // project.prepare_rename(cursor_buffer, cursor_buffer_offset, cx) + // }); + + // Some(cx.spawn(|this, mut cx| async move { + // let rename_range = if let Some(range) = prepare_rename.await? { + // Some(range) + // } else { + // this.update(&mut cx, |this, cx| { + // let buffer = this.buffer.read(cx).snapshot(cx); + // let mut buffer_highlights = this + // .document_highlights_for_position(selection.head(), &buffer) + // .filter(|highlight| { + // highlight.start.excerpt_id == selection.head().excerpt_id + // && highlight.end.excerpt_id == selection.head().excerpt_id + // }); + // buffer_highlights + // .next() + // .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor) + // })? + // }; + // if let Some(rename_range) = rename_range { + // let rename_buffer_range = rename_range.to_offset(&snapshot); + // let cursor_offset_in_rename_range = + // cursor_buffer_offset.saturating_sub(rename_buffer_range.start); + + // this.update(&mut cx, |this, cx| { + // this.take_rename(false, cx); + // let buffer = this.buffer.read(cx).read(cx); + // let cursor_offset = selection.head().to_offset(&buffer); + // let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range); + // let rename_end = rename_start + rename_buffer_range.len(); + // let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end); + // let mut old_highlight_id = None; + // let old_name: Arc = buffer + // .chunks(rename_start..rename_end, true) + // .map(|chunk| { + // if old_highlight_id.is_none() { + // old_highlight_id = chunk.syntax_highlight_id; + // } + // chunk.text + // }) + // .collect::() + // .into(); + + // drop(buffer); + + // // Position the selection in the rename editor so that it matches the current selection. + // this.show_local_selections = false; + // let rename_editor = cx.build_view(|cx| { + // let mut editor = Editor::single_line(cx); + // if let Some(old_highlight_id) = old_highlight_id { + // editor.override_text_style = + // Some(Box::new(move |style| old_highlight_id.style(&style.syntax))); + // } + // editor.buffer.update(cx, |buffer, cx| { + // buffer.edit([(0..0, old_name.clone())], None, cx) + // }); + // editor.select_all(&SelectAll, cx); + // editor + // }); + + // let ranges = this + // .clear_background_highlights::(cx) + // .into_iter() + // .flat_map(|(_, ranges)| ranges.into_iter()) + // .chain( + // this.clear_background_highlights::(cx) + // .into_iter() + // .flat_map(|(_, ranges)| ranges.into_iter()), + // ) + // .collect(); + + // this.highlight_text::( + // ranges, + // HighlightStyle { + // fade_out: Some(style.rename_fade), + // ..Default::default() + // }, + // cx, + // ); + // cx.focus(&rename_editor); + // let block_id = this.insert_blocks( + // [BlockProperties { + // style: BlockStyle::Flex, + // position: range.start.clone(), + // height: 1, + // render: Arc::new({ + // let editor = rename_editor.clone(); + // move |cx: &mut BlockContext| { + // ChildView::new(&editor, cx) + // .contained() + // .with_padding_left(cx.anchor_x) + // .into_any() + // } + // }), + // disposition: BlockDisposition::Below, + // }], + // Some(Autoscroll::fit()), + // cx, + // )[0]; + // this.pending_rename = Some(RenameState { + // range, + // old_name, + // editor: rename_editor, + // block_id, + // }); + // })?; // } - // let snapshot = cursor_buffer.read(cx).snapshot(); - // let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot); - // let prepare_rename = project.update(cx, |project, cx| { - // project.prepare_rename(cursor_buffer, cursor_buffer_offset, cx) - // }); - - // Some(cx.spawn(|this, mut cx| async move { - // let rename_range = if let Some(range) = prepare_rename.await? { - // Some(range) - // } else { - // this.update(&mut cx, |this, cx| { - // let buffer = this.buffer.read(cx).snapshot(cx); - // let mut buffer_highlights = this - // .document_highlights_for_position(selection.head(), &buffer) - // .filter(|highlight| { - // highlight.start.excerpt_id == selection.head().excerpt_id - // && highlight.end.excerpt_id == selection.head().excerpt_id - // }); - // buffer_highlights - // .next() - // .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor) - // })? - // }; - // if let Some(rename_range) = rename_range { - // let rename_buffer_range = rename_range.to_offset(&snapshot); - // let cursor_offset_in_rename_range = - // cursor_buffer_offset.saturating_sub(rename_buffer_range.start); - - // this.update(&mut cx, |this, cx| { - // this.take_rename(false, cx); - // let style = this.style(cx); - // let buffer = this.buffer.read(cx).read(cx); - // let cursor_offset = selection.head().to_offset(&buffer); - // let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range); - // let rename_end = rename_start + rename_buffer_range.len(); - // let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end); - // let mut old_highlight_id = None; - // let old_name: Arc = buffer - // .chunks(rename_start..rename_end, true) - // .map(|chunk| { - // if old_highlight_id.is_none() { - // old_highlight_id = chunk.syntax_highlight_id; - // } - // chunk.text - // }) - // .collect::() - // .into(); - - // drop(buffer); - - // // Position the selection in the rename editor so that it matches the current selection. - // this.show_local_selections = false; - // let rename_editor = cx.add_view(|cx| { - // let mut editor = Editor::single_line(None, cx); - // if let Some(old_highlight_id) = old_highlight_id { - // editor.override_text_style = - // Some(Box::new(move |style| old_highlight_id.style(&style.syntax))); - // } - // editor.buffer.update(cx, |buffer, cx| { - // buffer.edit([(0..0, old_name.clone())], None, cx) - // }); - // editor.select_all(&SelectAll, cx); - // editor - // }); - - // let ranges = this - // .clear_background_highlights::(cx) - // .into_iter() - // .flat_map(|(_, ranges)| ranges.into_iter()) - // .chain( - // this.clear_background_highlights::(cx) - // .into_iter() - // .flat_map(|(_, ranges)| ranges.into_iter()), - // ) - // .collect(); - - // this.highlight_text::( - // ranges, - // HighlightStyle { - // fade_out: Some(style.rename_fade), - // ..Default::default() - // }, - // cx, - // ); - // cx.focus(&rename_editor); - // let block_id = this.insert_blocks( - // [BlockProperties { - // style: BlockStyle::Flex, - // position: range.start.clone(), - // height: 1, - // render: Arc::new({ - // let editor = rename_editor.clone(); - // move |cx: &mut BlockContext| { - // ChildView::new(&editor, cx) - // .contained() - // .with_padding_left(cx.anchor_x) - // .into_any() - // } - // }), - // disposition: BlockDisposition::Below, - // }], - // Some(Autoscroll::fit()), - // cx, - // )[0]; - // this.pending_rename = Some(RenameState { - // range, - // old_name, - // editor: rename_editor, - // block_id, - // }); - // })?; - // } - - // Ok(()) - // })) - // } + // Ok(()) + // })) + // } // pub fn confirm_rename( // workspace: &mut Workspace, @@ -7947,14 +7952,14 @@ impl Editor { self.pending_rename.as_ref() } - // fn format(&mut self, _: &Format, cx: &mut ViewContext) -> Option>> { - // let project = match &self.project { - // Some(project) => project.clone(), - // None => return None, - // }; + fn format(&mut self, _: &Format, cx: &mut ViewContext) -> Option>> { + let project = match &self.project { + Some(project) => project.clone(), + None => return None, + }; - // Some(self.perform_format(project, FormatTrigger::Manual, cx)) - // } + Some(self.perform_format(project, FormatTrigger::Manual, cx)) + } fn perform_format( &mut self, @@ -7991,19 +7996,19 @@ impl Editor { }) } - // fn restart_language_server(&mut self, _: &RestartLanguageServer, cx: &mut ViewContext) { - // if let Some(project) = self.project.clone() { - // self.buffer.update(cx, |multi_buffer, cx| { - // project.update(cx, |project, cx| { - // project.restart_language_servers_for_buffers(multi_buffer.all_buffers(), cx); - // }); - // }) - // } - // } + fn restart_language_server(&mut self, _: &RestartLanguageServer, cx: &mut ViewContext) { + if let Some(project) = self.project.clone() { + self.buffer.update(cx, |multi_buffer, cx| { + project.update(cx, |project, cx| { + project.restart_language_servers_for_buffers(multi_buffer.all_buffers(), cx); + }); + }) + } + } - // fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext) { - // cx.show_character_palette(); - // } + fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext) { + cx.show_character_palette(); + } fn refresh_active_diagnostics(&mut self, cx: &mut ViewContext) { if let Some(active_diagnostics) = self.active_diagnostics.as_mut() { @@ -8033,87 +8038,87 @@ impl Editor { } } - // fn activate_diagnostics(&mut self, group_id: usize, cx: &mut ViewContext) -> bool { - // self.dismiss_diagnostics(cx); - // self.active_diagnostics = self.display_map.update(cx, |display_map, cx| { - // let buffer = self.buffer.read(cx).snapshot(cx); + fn activate_diagnostics(&mut self, group_id: usize, cx: &mut ViewContext) -> bool { + self.dismiss_diagnostics(cx); + self.active_diagnostics = self.display_map.update(cx, |display_map, cx| { + let buffer = self.buffer.read(cx).snapshot(cx); - // let mut primary_range = None; - // let mut primary_message = None; - // let mut group_end = Point::zero(); - // let diagnostic_group = buffer - // .diagnostic_group::(group_id) - // .map(|entry| { - // if entry.range.end > group_end { - // group_end = entry.range.end; - // } - // if entry.diagnostic.is_primary { - // primary_range = Some(entry.range.clone()); - // primary_message = Some(entry.diagnostic.message.clone()); - // } - // entry - // }) - // .collect::>(); - // let primary_range = primary_range?; - // let primary_message = primary_message?; - // let primary_range = - // buffer.anchor_after(primary_range.start)..buffer.anchor_before(primary_range.end); + let mut primary_range = None; + let mut primary_message = None; + let mut group_end = Point::zero(); + let diagnostic_group = buffer + .diagnostic_group::(group_id) + .map(|entry| { + if entry.range.end > group_end { + group_end = entry.range.end; + } + if entry.diagnostic.is_primary { + primary_range = Some(entry.range.clone()); + primary_message = Some(entry.diagnostic.message.clone()); + } + entry + }) + .collect::>(); + let primary_range = primary_range?; + let primary_message = primary_message?; + let primary_range = + buffer.anchor_after(primary_range.start)..buffer.anchor_before(primary_range.end); - // let blocks = display_map - // .insert_blocks( - // diagnostic_group.iter().map(|entry| { - // let diagnostic = entry.diagnostic.clone(); - // let message_height = diagnostic.message.lines().count() as u8; - // BlockProperties { - // style: BlockStyle::Fixed, - // position: buffer.anchor_after(entry.range.start), - // height: message_height, - // render: diagnostic_block_renderer(diagnostic, true), - // disposition: BlockDisposition::Below, - // } - // }), - // cx, - // ) - // .into_iter() - // .zip(diagnostic_group.into_iter().map(|entry| entry.diagnostic)) - // .collect(); + let blocks = display_map + .insert_blocks( + diagnostic_group.iter().map(|entry| { + let diagnostic = entry.diagnostic.clone(); + let message_height = diagnostic.message.lines().count() as u8; + BlockProperties { + style: BlockStyle::Fixed, + position: buffer.anchor_after(entry.range.start), + height: message_height, + render: diagnostic_block_renderer(diagnostic, true), + disposition: BlockDisposition::Below, + } + }), + cx, + ) + .into_iter() + .zip(diagnostic_group.into_iter().map(|entry| entry.diagnostic)) + .collect(); - // Some(ActiveDiagnosticGroup { - // primary_range, - // primary_message, - // blocks, - // is_valid: true, - // }) - // }); - // self.active_diagnostics.is_some() - // } + Some(ActiveDiagnosticGroup { + primary_range, + primary_message, + blocks, + is_valid: true, + }) + }); + self.active_diagnostics.is_some() + } - // fn dismiss_diagnostics(&mut self, cx: &mut ViewContext) { - // if let Some(active_diagnostic_group) = self.active_diagnostics.take() { - // self.display_map.update(cx, |display_map, cx| { - // display_map.remove_blocks(active_diagnostic_group.blocks.into_keys().collect(), cx); - // }); - // cx.notify(); - // } - // } + fn dismiss_diagnostics(&mut self, cx: &mut ViewContext) { + if let Some(active_diagnostic_group) = self.active_diagnostics.take() { + self.display_map.update(cx, |display_map, cx| { + display_map.remove_blocks(active_diagnostic_group.blocks.into_keys().collect(), cx); + }); + cx.notify(); + } + } - // pub fn set_selections_from_remote( - // &mut self, - // selections: Vec>, - // pending_selection: Option>, - // cx: &mut ViewContext, - // ) { - // let old_cursor_position = self.selections.newest_anchor().head(); - // self.selections.change_with(cx, |s| { - // s.select_anchors(selections); - // if let Some(pending_selection) = pending_selection { - // s.set_pending(pending_selection, SelectMode::Character); - // } else { - // s.clear_pending(); - // } - // }); - // self.selections_did_change(false, &old_cursor_position, cx); - // } + pub fn set_selections_from_remote( + &mut self, + selections: Vec>, + pending_selection: Option>, + cx: &mut ViewContext, + ) { + let old_cursor_position = self.selections.newest_anchor().head(); + self.selections.change_with(cx, |s| { + s.select_anchors(selections); + if let Some(pending_selection) = pending_selection { + s.set_pending(pending_selection, SelectMode::Character); + } else { + s.clear_pending(); + } + }); + self.selections_did_change(false, &old_cursor_position, cx); + } fn push_to_selection_history(&mut self) { self.selection_history.push(SelectionHistoryEntry { @@ -8167,100 +8172,100 @@ impl Editor { } } - // pub fn fold(&mut self, _: &Fold, cx: &mut ViewContext) { - // let mut fold_ranges = Vec::new(); + pub fn fold(&mut self, _: &Fold, cx: &mut ViewContext) { + let mut fold_ranges = Vec::new(); - // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - // let selections = self.selections.all_adjusted(cx); - // for selection in selections { - // let range = selection.range().sorted(); - // let buffer_start_row = range.start.row; + let selections = self.selections.all_adjusted(cx); + for selection in selections { + let range = selection.range().sorted(); + let buffer_start_row = range.start.row; - // for row in (0..=range.end.row).rev() { - // let fold_range = display_map.foldable_range(row); + for row in (0..=range.end.row).rev() { + let fold_range = display_map.foldable_range(row); - // if let Some(fold_range) = fold_range { - // if fold_range.end.row >= buffer_start_row { - // fold_ranges.push(fold_range); - // if row <= range.start.row { - // break; - // } - // } - // } - // } - // } + if let Some(fold_range) = fold_range { + if fold_range.end.row >= buffer_start_row { + fold_ranges.push(fold_range); + if row <= range.start.row { + break; + } + } + } + } + } - // self.fold_ranges(fold_ranges, true, cx); - // } + self.fold_ranges(fold_ranges, true, cx); + } - // pub fn fold_at(&mut self, fold_at: &FoldAt, cx: &mut ViewContext) { - // let buffer_row = fold_at.buffer_row; - // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + pub fn fold_at(&mut self, fold_at: &FoldAt, cx: &mut ViewContext) { + let buffer_row = fold_at.buffer_row; + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - // if let Some(fold_range) = display_map.foldable_range(buffer_row) { - // let autoscroll = self - // .selections - // .all::(cx) - // .iter() - // .any(|selection| fold_range.overlaps(&selection.range())); + if let Some(fold_range) = display_map.foldable_range(buffer_row) { + let autoscroll = self + .selections + .all::(cx) + .iter() + .any(|selection| fold_range.overlaps(&selection.range())); - // self.fold_ranges(std::iter::once(fold_range), autoscroll, cx); - // } - // } + self.fold_ranges(std::iter::once(fold_range), autoscroll, cx); + } + } - // pub fn unfold_lines(&mut self, _: &UnfoldLines, cx: &mut ViewContext) { - // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - // let buffer = &display_map.buffer_snapshot; - // let selections = self.selections.all::(cx); - // let ranges = selections - // .iter() - // .map(|s| { - // let range = s.display_range(&display_map).sorted(); - // let mut start = range.start.to_point(&display_map); - // let mut end = range.end.to_point(&display_map); - // start.column = 0; - // end.column = buffer.line_len(end.row); - // start..end - // }) - // .collect::>(); + pub fn unfold_lines(&mut self, _: &UnfoldLines, cx: &mut ViewContext) { + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let buffer = &display_map.buffer_snapshot; + let selections = self.selections.all::(cx); + let ranges = selections + .iter() + .map(|s| { + let range = s.display_range(&display_map).sorted(); + let mut start = range.start.to_point(&display_map); + let mut end = range.end.to_point(&display_map); + start.column = 0; + end.column = buffer.line_len(end.row); + start..end + }) + .collect::>(); - // self.unfold_ranges(ranges, true, true, cx); - // } + self.unfold_ranges(ranges, true, true, cx); + } - // pub fn unfold_at(&mut self, unfold_at: &UnfoldAt, cx: &mut ViewContext) { - // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + pub fn unfold_at(&mut self, unfold_at: &UnfoldAt, cx: &mut ViewContext) { + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - // let intersection_range = Point::new(unfold_at.buffer_row, 0) - // ..Point::new( - // unfold_at.buffer_row, - // display_map.buffer_snapshot.line_len(unfold_at.buffer_row), - // ); + let intersection_range = Point::new(unfold_at.buffer_row, 0) + ..Point::new( + unfold_at.buffer_row, + display_map.buffer_snapshot.line_len(unfold_at.buffer_row), + ); - // let autoscroll = self - // .selections - // .all::(cx) - // .iter() - // .any(|selection| selection.range().overlaps(&intersection_range)); + let autoscroll = self + .selections + .all::(cx) + .iter() + .any(|selection| selection.range().overlaps(&intersection_range)); - // self.unfold_ranges(std::iter::once(intersection_range), true, autoscroll, cx) - // } + self.unfold_ranges(std::iter::once(intersection_range), true, autoscroll, cx) + } - // pub fn fold_selected_ranges(&mut self, _: &FoldSelectedRanges, cx: &mut ViewContext) { - // let selections = self.selections.all::(cx); - // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - // let line_mode = self.selections.line_mode; - // let ranges = selections.into_iter().map(|s| { - // if line_mode { - // let start = Point::new(s.start.row, 0); - // let end = Point::new(s.end.row, display_map.buffer_snapshot.line_len(s.end.row)); - // start..end - // } else { - // s.start..s.end - // } - // }); - // self.fold_ranges(ranges, true, cx); - // } + pub fn fold_selected_ranges(&mut self, _: &FoldSelectedRanges, cx: &mut ViewContext) { + let selections = self.selections.all::(cx); + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let line_mode = self.selections.line_mode; + let ranges = selections.into_iter().map(|s| { + if line_mode { + let start = Point::new(s.start.row, 0); + let end = Point::new(s.end.row, display_map.buffer_snapshot.line_len(s.end.row)); + start..end + } else { + s.start..s.end + } + }); + self.fold_ranges(ranges, true, cx); + } pub fn fold_ranges( &mut self, @@ -8299,42 +8304,40 @@ impl Editor { } } - // pub fn gutter_hover( - // &mut self, - // GutterHover { hovered }: &GutterHover, - // cx: &mut ViewContext, - // ) { - // self.gutter_hovered = *hovered; - // cx.notify(); - // } + pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut ViewContext) { + if hovered != self.gutter_hovered { + self.gutter_hovered = hovered; + cx.notify(); + } + } - // pub fn insert_blocks( - // &mut self, - // blocks: impl IntoIterator>, - // autoscroll: Option, - // cx: &mut ViewContext, - // ) -> Vec { - // let blocks = self - // .display_map - // .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx)); - // if let Some(autoscroll) = autoscroll { - // self.request_autoscroll(autoscroll, cx); - // } - // blocks - // } + pub fn insert_blocks( + &mut self, + blocks: impl IntoIterator>, + autoscroll: Option, + cx: &mut ViewContext, + ) -> Vec { + let blocks = self + .display_map + .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx)); + if let Some(autoscroll) = autoscroll { + self.request_autoscroll(autoscroll, cx); + } + blocks + } - // pub fn replace_blocks( - // &mut self, - // blocks: HashMap, - // autoscroll: Option, - // cx: &mut ViewContext, - // ) { - // self.display_map - // .update(cx, |display_map, _| display_map.replace_blocks(blocks)); - // if let Some(autoscroll) = autoscroll { - // self.request_autoscroll(autoscroll, cx); - // } - // } + pub fn replace_blocks( + &mut self, + blocks: HashMap, + autoscroll: Option, + cx: &mut ViewContext, + ) { + self.display_map + .update(cx, |display_map, _| display_map.replace_blocks(blocks)); + if let Some(autoscroll) = autoscroll { + self.request_autoscroll(autoscroll, cx); + } + } pub fn remove_blocks( &mut self, @@ -8350,37 +8353,37 @@ impl Editor { } } - // pub fn longest_row(&self, cx: &mut AppContext) -> u32 { - // self.display_map - // .update(cx, |map, cx| map.snapshot(cx)) - // .longest_row() - // } + pub fn longest_row(&self, cx: &mut AppContext) -> u32 { + self.display_map + .update(cx, |map, cx| map.snapshot(cx)) + .longest_row() + } - // pub fn max_point(&self, cx: &mut AppContext) -> DisplayPoint { - // self.display_map - // .update(cx, |map, cx| map.snapshot(cx)) - // .max_point() - // } + pub fn max_point(&self, cx: &mut AppContext) -> DisplayPoint { + self.display_map + .update(cx, |map, cx| map.snapshot(cx)) + .max_point() + } pub fn text(&self, cx: &AppContext) -> String { self.buffer.read(cx).read(cx).text() } - // pub fn set_text(&mut self, text: impl Into>, cx: &mut ViewContext) { - // self.transact(cx, |this, cx| { - // this.buffer - // .read(cx) - // .as_singleton() - // .expect("you can only call set_text on editors for singleton buffers") - // .update(cx, |buffer, cx| buffer.set_text(text, cx)); - // }); - // } + pub fn set_text(&mut self, text: impl Into>, cx: &mut ViewContext) { + self.transact(cx, |this, cx| { + this.buffer + .read(cx) + .as_singleton() + .expect("you can only call set_text on editors for singleton buffers") + .update(cx, |buffer, cx| buffer.set_text(text, cx)); + }); + } - // pub fn display_text(&self, cx: &mut AppContext) -> String { - // self.display_map - // .update(cx, |map, cx| map.snapshot(cx)) - // .text() - // } + pub fn display_text(&self, cx: &mut AppContext) -> String { + self.display_map + .update(cx, |map, cx| map.snapshot(cx)) + .text() + } pub fn wrap_guides(&self, cx: &AppContext) -> SmallVec<[(usize, bool); 2]> { let mut wrap_guides = smallvec::smallvec![]; @@ -8428,56 +8431,56 @@ impl Editor { .update(cx, |map, cx| map.set_wrap_width(width, cx)) } - // pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, cx: &mut ViewContext) { - // if self.soft_wrap_mode_override.is_some() { - // self.soft_wrap_mode_override.take(); - // } else { - // let soft_wrap = match self.soft_wrap_mode(cx) { - // SoftWrap::None => language_settings::SoftWrap::EditorWidth, - // SoftWrap::EditorWidth | SoftWrap::Column(_) => language_settings::SoftWrap::None, - // }; - // self.soft_wrap_mode_override = Some(soft_wrap); - // } - // cx.notify(); - // } + pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, cx: &mut ViewContext) { + if self.soft_wrap_mode_override.is_some() { + self.soft_wrap_mode_override.take(); + } else { + let soft_wrap = match self.soft_wrap_mode(cx) { + SoftWrap::None => language_settings::SoftWrap::EditorWidth, + SoftWrap::EditorWidth | SoftWrap::Column(_) => language_settings::SoftWrap::None, + }; + self.soft_wrap_mode_override = Some(soft_wrap); + } + cx.notify(); + } - // pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut ViewContext) { - // self.show_gutter = show_gutter; - // cx.notify(); - // } + pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut ViewContext) { + self.show_gutter = show_gutter; + cx.notify(); + } - // pub fn set_show_wrap_guides(&mut self, show_gutter: bool, cx: &mut ViewContext) { - // self.show_wrap_guides = Some(show_gutter); - // cx.notify(); - // } + pub fn set_show_wrap_guides(&mut self, show_gutter: bool, cx: &mut ViewContext) { + self.show_wrap_guides = Some(show_gutter); + cx.notify(); + } - // pub fn reveal_in_finder(&mut self, _: &RevealInFinder, cx: &mut ViewContext) { - // if let Some(buffer) = self.buffer().read(cx).as_singleton() { - // if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) { - // cx.reveal_path(&file.abs_path(cx)); - // } - // } - // } + pub fn reveal_in_finder(&mut self, _: &RevealInFinder, cx: &mut ViewContext) { + if let Some(buffer) = self.buffer().read(cx).as_singleton() { + if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) { + cx.reveal_path(&file.abs_path(cx)); + } + } + } - // pub fn copy_path(&mut self, _: &CopyPath, cx: &mut ViewContext) { - // if let Some(buffer) = self.buffer().read(cx).as_singleton() { - // if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) { - // if let Some(path) = file.abs_path(cx).to_str() { - // cx.write_to_clipboard(ClipboardItem::new(path.to_string())); - // } - // } - // } - // } + pub fn copy_path(&mut self, _: &CopyPath, cx: &mut ViewContext) { + if let Some(buffer) = self.buffer().read(cx).as_singleton() { + if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) { + if let Some(path) = file.abs_path(cx).to_str() { + cx.write_to_clipboard(ClipboardItem::new(path.to_string())); + } + } + } + } - // pub fn copy_relative_path(&mut self, _: &CopyRelativePath, cx: &mut ViewContext) { - // if let Some(buffer) = self.buffer().read(cx).as_singleton() { - // if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) { - // if let Some(path) = file.path().to_str() { - // cx.write_to_clipboard(ClipboardItem::new(path.to_string())); - // } - // } - // } - // } + pub fn copy_relative_path(&mut self, _: &CopyRelativePath, cx: &mut ViewContext) { + if let Some(buffer) = self.buffer().read(cx).as_singleton() { + if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) { + if let Some(path) = file.path().to_str() { + cx.write_to_clipboard(ClipboardItem::new(path.to_string())); + } + } + } + } pub fn highlight_rows(&mut self, rows: Option>) { self.highlighted_rows = rows; @@ -8498,81 +8501,81 @@ impl Editor { cx.notify(); } - // pub fn highlight_inlay_background( - // &mut self, - // ranges: Vec, - // color_fetcher: fn(&Theme) -> Color, - // cx: &mut ViewContext, - // ) { - // // TODO: no actual highlights happen for inlays currently, find a way to do that - // self.inlay_background_highlights - // .insert(Some(TypeId::of::()), (color_fetcher, ranges)); - // cx.notify(); - // } + pub fn highlight_inlay_background( + &mut self, + ranges: Vec, + color_fetcher: fn(&ThemeColors) -> Hsla, + cx: &mut ViewContext, + ) { + // TODO: no actual highlights happen for inlays currently, find a way to do that + self.inlay_background_highlights + .insert(Some(TypeId::of::()), (color_fetcher, ranges)); + cx.notify(); + } - // pub fn clear_background_highlights( - // &mut self, - // cx: &mut ViewContext, - // ) -> Option { - // let text_highlights = self.background_highlights.remove(&TypeId::of::()); - // let inlay_highlights = self - // .inlay_background_highlights - // .remove(&Some(TypeId::of::())); - // if text_highlights.is_some() || inlay_highlights.is_some() { - // cx.notify(); - // } - // text_highlights - // } + pub fn clear_background_highlights( + &mut self, + cx: &mut ViewContext, + ) -> Option { + let text_highlights = self.background_highlights.remove(&TypeId::of::()); + let inlay_highlights = self + .inlay_background_highlights + .remove(&Some(TypeId::of::())); + if text_highlights.is_some() || inlay_highlights.is_some() { + cx.notify(); + } + text_highlights + } - // #[cfg(feature = "test-support")] - // pub fn all_text_background_highlights( - // &mut self, - // cx: &mut ViewContext, - // ) -> Vec<(Range, Color)> { - // let snapshot = self.snapshot(cx); - // let buffer = &snapshot.buffer_snapshot; - // let start = buffer.anchor_before(0); - // let end = buffer.anchor_after(buffer.len()); - // let theme = theme::current(cx); - // self.background_highlights_in_range(start..end, &snapshot, theme.as_ref()) - // } + #[cfg(feature = "test-support")] + pub fn all_text_background_highlights( + &mut self, + cx: &mut ViewContext, + ) -> Vec<(Range, Hsla)> { + let snapshot = self.snapshot(cx); + let buffer = &snapshot.buffer_snapshot; + let start = buffer.anchor_before(0); + let end = buffer.anchor_after(buffer.len()); + let theme = cx.theme().colors(); + self.background_highlights_in_range(start..end, &snapshot, theme) + } - // fn document_highlights_for_position<'a>( - // &'a self, - // position: Anchor, - // buffer: &'a MultiBufferSnapshot, - // ) -> impl 'a + Iterator> { - // let read_highlights = self - // .background_highlights - // .get(&TypeId::of::()) - // .map(|h| &h.1); - // let write_highlights = self - // .background_highlights - // .get(&TypeId::of::()) - // .map(|h| &h.1); - // let left_position = position.bias_left(buffer); - // let right_position = position.bias_right(buffer); - // read_highlights - // .into_iter() - // .chain(write_highlights) - // .flat_map(move |ranges| { - // let start_ix = match ranges.binary_search_by(|probe| { - // let cmp = probe.end.cmp(&left_position, buffer); - // if cmp.is_ge() { - // Ordering::Greater - // } else { - // Ordering::Less - // } - // }) { - // Ok(i) | Err(i) => i, - // }; + fn document_highlights_for_position<'a>( + &'a self, + position: Anchor, + buffer: &'a MultiBufferSnapshot, + ) -> impl 'a + Iterator> { + let read_highlights = self + .background_highlights + .get(&TypeId::of::()) + .map(|h| &h.1); + let write_highlights = self + .background_highlights + .get(&TypeId::of::()) + .map(|h| &h.1); + let left_position = position.bias_left(buffer); + let right_position = position.bias_right(buffer); + read_highlights + .into_iter() + .chain(write_highlights) + .flat_map(move |ranges| { + let start_ix = match ranges.binary_search_by(|probe| { + let cmp = probe.end.cmp(&left_position, buffer); + if cmp.is_ge() { + Ordering::Greater + } else { + Ordering::Less + } + }) { + Ok(i) | Err(i) => i, + }; - // let right_position = right_position.clone(); - // ranges[start_ix..] - // .iter() - // .take_while(move |range| range.start.cmp(&right_position, buffer).is_le()) - // }) - // } + let right_position = right_position.clone(); + ranges[start_ix..] + .iter() + .take_while(move |range| range.start.cmp(&right_position, buffer).is_le()) + }) + } pub fn background_highlights_in_range( &self, @@ -8612,80 +8615,80 @@ impl Editor { results } - // pub fn background_highlight_row_ranges( - // &self, - // search_range: Range, - // display_snapshot: &DisplaySnapshot, - // count: usize, - // ) -> Vec> { - // let mut results = Vec::new(); - // let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::()) else { - // return vec![]; - // }; + pub fn background_highlight_row_ranges( + &self, + search_range: Range, + display_snapshot: &DisplaySnapshot, + count: usize, + ) -> Vec> { + let mut results = Vec::new(); + let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::()) else { + return vec![]; + }; - // let start_ix = match ranges.binary_search_by(|probe| { - // let cmp = probe - // .end - // .cmp(&search_range.start, &display_snapshot.buffer_snapshot); - // if cmp.is_gt() { - // Ordering::Greater - // } else { - // Ordering::Less - // } - // }) { - // Ok(i) | Err(i) => i, - // }; - // let mut push_region = |start: Option, end: Option| { - // if let (Some(start_display), Some(end_display)) = (start, end) { - // results.push( - // start_display.to_display_point(display_snapshot) - // ..=end_display.to_display_point(display_snapshot), - // ); - // } - // }; - // let mut start_row: Option = None; - // let mut end_row: Option = None; - // if ranges.len() > count { - // return Vec::new(); - // } - // for range in &ranges[start_ix..] { - // if range - // .start - // .cmp(&search_range.end, &display_snapshot.buffer_snapshot) - // .is_ge() - // { - // break; - // } - // let end = range.end.to_point(&display_snapshot.buffer_snapshot); - // if let Some(current_row) = &end_row { - // if end.row == current_row.row { - // continue; - // } - // } - // let start = range.start.to_point(&display_snapshot.buffer_snapshot); - // if start_row.is_none() { - // assert_eq!(end_row, None); - // start_row = Some(start); - // end_row = Some(end); - // continue; - // } - // if let Some(current_end) = end_row.as_mut() { - // if start.row > current_end.row + 1 { - // push_region(start_row, end_row); - // start_row = Some(start); - // end_row = Some(end); - // } else { - // // Merge two hunks. - // *current_end = end; - // } - // } else { - // unreachable!(); - // } - // } - // // We might still have a hunk that was not rendered (if there was a search hit on the last line) - // push_region(start_row, end_row); - // results - // } + let start_ix = match ranges.binary_search_by(|probe| { + let cmp = probe + .end + .cmp(&search_range.start, &display_snapshot.buffer_snapshot); + if cmp.is_gt() { + Ordering::Greater + } else { + Ordering::Less + } + }) { + Ok(i) | Err(i) => i, + }; + let mut push_region = |start: Option, end: Option| { + if let (Some(start_display), Some(end_display)) = (start, end) { + results.push( + start_display.to_display_point(display_snapshot) + ..=end_display.to_display_point(display_snapshot), + ); + } + }; + let mut start_row: Option = None; + let mut end_row: Option = None; + if ranges.len() > count { + return Vec::new(); + } + for range in &ranges[start_ix..] { + if range + .start + .cmp(&search_range.end, &display_snapshot.buffer_snapshot) + .is_ge() + { + break; + } + let end = range.end.to_point(&display_snapshot.buffer_snapshot); + if let Some(current_row) = &end_row { + if end.row == current_row.row { + continue; + } + } + let start = range.start.to_point(&display_snapshot.buffer_snapshot); + if start_row.is_none() { + assert_eq!(end_row, None); + start_row = Some(start); + end_row = Some(end); + continue; + } + if let Some(current_end) = end_row.as_mut() { + if start.row > current_end.row + 1 { + push_region(start_row, end_row); + start_row = Some(start); + end_row = Some(end); + } else { + // Merge two hunks. + *current_end = end; + } + } else { + unreachable!(); + } + } + // We might still have a hunk that was not rendered (if there was a search hit on the last line) + push_region(start_row, end_row); + results + } pub fn highlight_text( &mut self, @@ -8699,17 +8702,17 @@ impl Editor { cx.notify(); } - // pub fn highlight_inlays( - // &mut self, - // highlights: Vec, - // style: HighlightStyle, - // cx: &mut ViewContext, - // ) { - // self.display_map.update(cx, |map, _| { - // map.highlight_inlays(TypeId::of::(), highlights, style) - // }); - // cx.notify(); - // } + pub fn highlight_inlays( + &mut self, + highlights: Vec, + style: HighlightStyle, + cx: &mut ViewContext, + ) { + self.display_map.update(cx, |map, _| { + map.highlight_inlays(TypeId::of::(), highlights, style) + }); + cx.notify(); + } pub fn text_highlights<'a, T: 'static>( &'a self, @@ -9054,136 +9057,140 @@ impl Editor { telemetry.report_clickhouse_event(event, telemetry_settings) } - // /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines, - // /// with each line being an array of {text, highlight} objects. - // fn copy_highlight_json(&mut self, _: &CopyHighlightJson, cx: &mut ViewContext) { - // let Some(buffer) = self.buffer.read(cx).as_singleton() else { - // return; - // }; + /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines, + /// with each line being an array of {text, highlight} objects. + fn copy_highlight_json(&mut self, _: &CopyHighlightJson, cx: &mut ViewContext) { + let Some(buffer) = self.buffer.read(cx).as_singleton() else { + return; + }; - // #[derive(Serialize)] - // struct Chunk<'a> { - // text: String, - // highlight: Option<&'a str>, - // } + #[derive(Serialize)] + struct Chunk<'a> { + text: String, + highlight: Option<&'a str>, + } - // let snapshot = buffer.read(cx).snapshot(); - // let range = self - // .selected_text_range(cx) - // .and_then(|selected_range| { - // if selected_range.is_empty() { - // None - // } else { - // Some(selected_range) - // } - // }) - // .unwrap_or_else(|| 0..snapshot.len()); + let snapshot = buffer.read(cx).snapshot(); + let range = self + .selected_text_range(cx) + .and_then(|selected_range| { + if selected_range.is_empty() { + None + } else { + Some(selected_range) + } + }) + .unwrap_or_else(|| 0..snapshot.len()); - // let chunks = snapshot.chunks(range, true); - // let mut lines = Vec::new(); - // let mut line: VecDeque = VecDeque::new(); + let chunks = snapshot.chunks(range, true); + let mut lines = Vec::new(); + let mut line: VecDeque = VecDeque::new(); - // let theme = &theme::current(cx).editor.syntax; + let Some(style) = self.style.as_ref() else { + return; + }; - // for chunk in chunks { - // let highlight = chunk.syntax_highlight_id.and_then(|id| id.name(theme)); - // let mut chunk_lines = chunk.text.split("\n").peekable(); - // while let Some(text) = chunk_lines.next() { - // let mut merged_with_last_token = false; - // if let Some(last_token) = line.back_mut() { - // if last_token.highlight == highlight { - // last_token.text.push_str(text); - // merged_with_last_token = true; - // } - // } + for chunk in chunks { + let highlight = chunk + .syntax_highlight_id + .and_then(|id| id.name(&style.syntax)); + let mut chunk_lines = chunk.text.split("\n").peekable(); + while let Some(text) = chunk_lines.next() { + let mut merged_with_last_token = false; + if let Some(last_token) = line.back_mut() { + if last_token.highlight == highlight { + last_token.text.push_str(text); + merged_with_last_token = true; + } + } - // if !merged_with_last_token { - // line.push_back(Chunk { - // text: text.into(), - // highlight, - // }); - // } + if !merged_with_last_token { + line.push_back(Chunk { + text: text.into(), + highlight, + }); + } - // if chunk_lines.peek().is_some() { - // if line.len() > 1 && line.front().unwrap().text.is_empty() { - // line.pop_front(); - // } - // if line.len() > 1 && line.back().unwrap().text.is_empty() { - // line.pop_back(); - // } + if chunk_lines.peek().is_some() { + if line.len() > 1 && line.front().unwrap().text.is_empty() { + line.pop_front(); + } + if line.len() > 1 && line.back().unwrap().text.is_empty() { + line.pop_back(); + } - // lines.push(mem::take(&mut line)); - // } - // } - // } + lines.push(mem::take(&mut line)); + } + } + } - // let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else { - // return; - // }; - // cx.write_to_clipboard(ClipboardItem::new(lines)); - // } + let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else { + return; + }; + cx.write_to_clipboard(ClipboardItem::new(lines)); + } - // pub fn inlay_hint_cache(&self) -> &InlayHintCache { - // &self.inlay_hint_cache - // } + pub fn inlay_hint_cache(&self) -> &InlayHintCache { + &self.inlay_hint_cache + } - // pub fn replay_insert_event( - // &mut self, - // text: &str, - // relative_utf16_range: Option>, - // cx: &mut ViewContext, - // ) { - // if !self.input_enabled { - // cx.emit(Event::InputIgnored { text: text.into() }); - // return; - // } - // if let Some(relative_utf16_range) = relative_utf16_range { - // let selections = self.selections.all::(cx); - // self.change_selections(None, cx, |s| { - // let new_ranges = selections.into_iter().map(|range| { - // let start = OffsetUtf16( - // range - // .head() - // .0 - // .saturating_add_signed(relative_utf16_range.start), - // ); - // let end = OffsetUtf16( - // range - // .head() - // .0 - // .saturating_add_signed(relative_utf16_range.end), - // ); - // start..end - // }); - // s.select_ranges(new_ranges); - // }); - // } + pub fn replay_insert_event( + &mut self, + text: &str, + relative_utf16_range: Option>, + cx: &mut ViewContext, + ) { + if !self.input_enabled { + cx.emit(Event::InputIgnored { text: text.into() }); + return; + } + if let Some(relative_utf16_range) = relative_utf16_range { + let selections = self.selections.all::(cx); + self.change_selections(None, cx, |s| { + let new_ranges = selections.into_iter().map(|range| { + let start = OffsetUtf16( + range + .head() + .0 + .saturating_add_signed(relative_utf16_range.start), + ); + let end = OffsetUtf16( + range + .head() + .0 + .saturating_add_signed(relative_utf16_range.end), + ); + start..end + }); + s.select_ranges(new_ranges); + }); + } - // self.handle_input(text, cx); - // } + self.handle_input(text, cx); + } - // pub fn supports_inlay_hints(&self, cx: &AppContext) -> bool { - // let Some(project) = self.project.as_ref() else { - // return false; - // }; - // let project = project.read(cx); + pub fn supports_inlay_hints(&self, cx: &AppContext) -> bool { + let Some(project) = self.project.as_ref() else { + return false; + }; + let project = project.read(cx); - // let mut supports = false; - // self.buffer().read(cx).for_each_buffer(|buffer| { - // if !supports { - // supports = project - // .language_servers_for_buffer(buffer.read(cx), cx) - // .any( - // |(_, server)| match server.capabilities().inlay_hint_provider { - // Some(lsp::OneOf::Left(enabled)) => enabled, - // Some(lsp::OneOf::Right(_)) => true, - // None => false, - // }, - // ) - // } - // }); - // supports - // } + let mut supports = false; + self.buffer().read(cx).for_each_buffer(|buffer| { + if !supports { + supports = project + .language_servers_for_buffer(buffer.read(cx), cx) + .any( + |(_, server)| match server.capabilities().inlay_hint_provider { + Some(lsp::OneOf::Left(enabled)) => enabled, + Some(lsp::OneOf::Right(_)) => true, + None => false, + }, + ) + } + }); + supports + } pub fn focus(&self, cx: &mut WindowContext) { cx.focus(&self.focus_handle) diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index cf4f68d23a..04b8494a88 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -2,16 +2,25 @@ use crate::{ display_map::{BlockStyle, DisplaySnapshot, FoldStatus, HighlightedChunk, ToDisplayPoint}, editor_settings::ShowScrollbar, git::{diff_hunk_to_display, DisplayDiffHunk}, + hover_popover::hover_at, + link_go_to_definition::{ + go_to_fetched_definition, go_to_fetched_type_definition, update_go_to_definition_link, + update_inlay_link_and_hover_points, GoToDefinitionTrigger, + }, + scroll::scroll_amount::ScrollAmount, CursorShape, DisplayPoint, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle, - MoveDown, Point, Selection, SoftWrap, ToPoint, MAX_LINE_LEN, + HalfPageDown, HalfPageUp, LineDown, LineUp, MoveDown, PageDown, PageUp, Point, SelectPhase, + Selection, SoftWrap, ToPoint, MAX_LINE_LEN, }; use anyhow::Result; use collections::{BTreeMap, HashMap}; use gpui::{ - black, hsla, point, px, relative, size, transparent_black, Action, AnyElement, BorrowWindow, - Bounds, ContentMask, Corners, DispatchContext, DispatchPhase, Edges, Element, ElementId, - Entity, Hsla, KeyDownEvent, KeyListener, KeyMatch, Line, Pixels, ScrollWheelEvent, ShapedGlyph, - Size, StatefulInteractivity, Style, TextRun, TextStyle, TextSystem, ViewContext, WindowContext, + black, hsla, point, px, relative, size, transparent_black, Action, AnyElement, + BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchContext, DispatchPhase, + Edges, Element, ElementId, Entity, GlobalElementId, Hsla, KeyDownEvent, KeyListener, KeyMatch, + Line, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, + ScrollWheelEvent, ShapedGlyph, Size, Style, TextRun, TextStyle, TextSystem, ViewContext, + WindowContext, }; use itertools::Itertools; use language::language_settings::ShowWhitespaceSetting; @@ -30,6 +39,7 @@ use std::{ }; use sum_tree::Bias; use theme::{ActiveTheme, PlayerColor}; +use util::ResultExt; use workspace::item::Item; enum FoldMarkers {} @@ -103,194 +113,54 @@ impl EditorElement { Self { style } } - // fn attach_mouse_handlers( - // position_map: &Arc, - // has_popovers: bool, - // visible_bounds: Bounds, - // text_bounds: Bounds, - // gutter_bounds: Bounds, - // bounds: Bounds, - // cx: &mut ViewContext, - // ) { - // enum EditorElementMouseHandlers {} - // let view_id = cx.view_id(); - // cx.scene().push_mouse_region( - // MouseRegion::new::(view_id, view_id, visible_bounds) - // .on_down(MouseButton::Left, { - // let position_map = position_map.clone(); - // move |event, editor, cx| { - // if !Self::mouse_down( - // editor, - // event.platform_event, - // position_map.as_ref(), - // text_bounds, - // gutter_bounds, - // cx, - // ) { - // cx.propagate_event(); - // } - // } - // }) - // .on_down(MouseButton::Right, { - // let position_map = position_map.clone(); - // move |event, editor, cx| { - // if !Self::mouse_right_down( - // editor, - // event.position, - // position_map.as_ref(), - // text_bounds, - // cx, - // ) { - // cx.propagate_event(); - // } - // } - // }) - // .on_up(MouseButton::Left, { - // let position_map = position_map.clone(); - // move |event, editor, cx| { - // if !Self::mouse_up( - // editor, - // event.position, - // event.cmd, - // event.shift, - // event.alt, - // position_map.as_ref(), - // text_bounds, - // cx, - // ) { - // cx.propagate_event() - // } - // } - // }) - // .on_drag(MouseButton::Left, { - // let position_map = position_map.clone(); - // move |event, editor, cx| { - // if event.end { - // return; - // } + fn mouse_down( + editor: &mut Editor, + event: &MouseDownEvent, + position_map: &PositionMap, + text_bounds: Bounds, + gutter_bounds: Bounds, + cx: &mut ViewContext, + ) -> bool { + let mut click_count = event.click_count; + let modifiers = event.modifiers; - // if !Self::mouse_dragged( - // editor, - // event.platform_event, - // position_map.as_ref(), - // text_bounds, - // cx, - // ) { - // cx.propagate_event() - // } - // } - // }) - // .on_move({ - // let position_map = position_map.clone(); - // move |event, editor, cx| { - // if !Self::mouse_moved( - // editor, - // event.platform_event, - // &position_map, - // text_bounds, - // cx, - // ) { - // cx.propagate_event() - // } - // } - // }) - // .on_move_out(move |_, editor: &mut Editor, cx| { - // if has_popovers { - // hide_hover(editor, cx); - // } - // }) - // .on_scroll({ - // let position_map = position_map.clone(); - // move |event, editor, cx| { - // if !Self::scroll( - // editor, - // event.position, - // *event.delta.raw(), - // event.delta.precise(), - // &position_map, - // bounds, - // cx, - // ) { - // cx.propagate_event() - // } - // } - // }), - // ); + if gutter_bounds.contains_point(&event.position) { + click_count = 3; // Simulate triple-click when clicking the gutter to select lines + } else if !text_bounds.contains_point(&event.position) { + return false; + } - // enum GutterHandlers {} - // let view_id = cx.view_id(); - // let region_id = cx.view_id() + 1; - // cx.scene().push_mouse_region( - // MouseRegion::new::(view_id, region_id, gutter_bounds).on_hover( - // |hover, editor: &mut Editor, cx| { - // editor.gutter_hover( - // &GutterHover { - // hovered: hover.started, - // }, - // cx, - // ); - // }, - // ), - // ) - // } + let point_for_position = position_map.point_for_position(text_bounds, event.position); + let position = point_for_position.previous_valid; + if modifiers.shift && modifiers.alt { + editor.select( + SelectPhase::BeginColumnar { + position, + goal_column: point_for_position.exact_unclipped.column(), + }, + cx, + ); + } else if modifiers.shift && !modifiers.control && !modifiers.alt && !modifiers.command { + editor.select( + SelectPhase::Extend { + position, + click_count, + }, + cx, + ); + } else { + editor.select( + SelectPhase::Begin { + position, + add: modifiers.alt, + click_count, + }, + cx, + ); + } - // fn mouse_down( - // editor: &mut Editor, - // MouseButtonEvent { - // position, - // modifiers: - // Modifiers { - // shift, - // ctrl, - // alt, - // cmd, - // .. - // }, - // mut click_count, - // .. - // }: MouseButtonEvent, - // position_map: &PositionMap, - // text_bounds: Bounds, - // gutter_bounds: Bounds, - // cx: &mut EventContext, - // ) -> bool { - // if gutter_bounds.contains_point(position) { - // click_count = 3; // Simulate triple-click when clicking the gutter to select lines - // } else if !text_bounds.contains_point(position) { - // return false; - // } - - // let point_for_position = position_map.point_for_position(text_bounds, position); - // let position = point_for_position.previous_valid; - // if shift && alt { - // editor.select( - // SelectPhase::BeginColumnar { - // position, - // goal_column: point_for_position.exact_unclipped.column(), - // }, - // cx, - // ); - // } else if shift && !ctrl && !alt && !cmd { - // editor.select( - // SelectPhase::Extend { - // position, - // click_count, - // }, - // cx, - // ); - // } else { - // editor.select( - // SelectPhase::Begin { - // position, - // add: alt, - // click_count, - // }, - // cx, - // ); - // } - - // true - // } + true + } // fn mouse_right_down( // editor: &mut Editor, @@ -312,157 +182,120 @@ impl EditorElement { // true // } - // fn mouse_up( - // editor: &mut Editor, - // position: gpui::Point, - // cmd: bool, - // shift: bool, - // alt: bool, - // position_map: &PositionMap, - // text_bounds: Bounds, - // cx: &mut EventContext, - // ) -> bool { - // let end_selection = editor.has_pending_selection(); - // let pending_nonempty_selections = editor.has_pending_nonempty_selection(); + fn mouse_up( + editor: &mut Editor, + event: &MouseUpEvent, + position_map: &PositionMap, + text_bounds: Bounds, + cx: &mut ViewContext, + ) -> bool { + let end_selection = editor.has_pending_selection(); + let pending_nonempty_selections = editor.has_pending_nonempty_selection(); - // if end_selection { - // editor.select(SelectPhase::End, cx); - // } + if end_selection { + editor.select(SelectPhase::End, cx); + } - // if !pending_nonempty_selections && cmd && text_bounds.contains_point(position) { - // let point = position_map.point_for_position(text_bounds, position); - // let could_be_inlay = point.as_valid().is_none(); - // if shift || could_be_inlay { - // go_to_fetched_type_definition(editor, point, alt, cx); - // } else { - // go_to_fetched_definition(editor, point, alt, cx); - // } + if !pending_nonempty_selections + && event.modifiers.command + && text_bounds.contains_point(&event.position) + { + let point = position_map.point_for_position(text_bounds, event.position); + let could_be_inlay = point.as_valid().is_none(); + let split = event.modifiers.alt; + if event.modifiers.shift || could_be_inlay { + go_to_fetched_type_definition(editor, point, split, cx); + } else { + go_to_fetched_definition(editor, point, split, cx); + } - // return true; - // } + return true; + } - // end_selection - // } + end_selection + } - // fn mouse_dragged( - // editor: &mut Editor, - // MouseMovedEvent { - // modifiers: Modifiers { cmd, shift, .. }, - // position, - // .. - // }: MouseMovedEvent, - // position_map: &PositionMap, - // text_bounds: Bounds, - // cx: &mut EventContext, - // ) -> bool { - // // This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed - // // Don't trigger hover popover if mouse is hovering over context menu - // let point = if text_bounds.contains_point(position) { - // position_map - // .point_for_position(text_bounds, position) - // .as_valid() - // } else { - // None - // }; + fn mouse_moved( + editor: &mut Editor, + event: &MouseMoveEvent, + position_map: &PositionMap, + text_bounds: Bounds, + gutter_bounds: Bounds, + cx: &mut ViewContext, + ) -> bool { + let modifiers = event.modifiers; + if editor.has_pending_selection() && event.pressed_button == Some(MouseButton::Left) { + let point_for_position = position_map.point_for_position(text_bounds, event.position); + let mut scroll_delta = gpui::Point::::zero(); + let vertical_margin = position_map.line_height.min(text_bounds.size.height / 3.0); + let top = text_bounds.origin.y + vertical_margin; + let bottom = text_bounds.lower_left().y - vertical_margin; + if event.position.y < top { + scroll_delta.y = -scale_vertical_mouse_autoscroll_delta(top - event.position.y); + } + if event.position.y > bottom { + scroll_delta.y = scale_vertical_mouse_autoscroll_delta(event.position.y - bottom); + } - // update_go_to_definition_link( - // editor, - // point.map(GoToDefinitionTrigger::Text), - // cmd, - // shift, - // cx, - // ); + let horizontal_margin = position_map.line_height.min(text_bounds.size.width / 3.0); + let left = text_bounds.origin.x + horizontal_margin; + let right = text_bounds.upper_right().x - horizontal_margin; + if event.position.x < left { + scroll_delta.x = -scale_horizontal_mouse_autoscroll_delta(left - event.position.x); + } + if event.position.x > right { + scroll_delta.x = scale_horizontal_mouse_autoscroll_delta(event.position.x - right); + } - // if editor.has_pending_selection() { - // let mut scroll_delta = gpui::Point::::zero(); + editor.select( + SelectPhase::Update { + position: point_for_position.previous_valid, + goal_column: point_for_position.exact_unclipped.column(), + scroll_position: (position_map.snapshot.scroll_position() + scroll_delta) + .clamp(&gpui::Point::zero(), &position_map.scroll_max), + }, + cx, + ); + } - // let vertical_margin = position_map.line_height.min(text_bounds.height() / 3.0); - // let top = text_bounds.origin.y + vertical_margin; - // let bottom = text_bounds.lower_left().y - vertical_margin; - // if position.y < top { - // scroll_delta.set_y(-scale_vertical_mouse_autoscroll_delta(top - position.y)) - // } - // if position.y > bottom { - // scroll_delta.set_y(scale_vertical_mouse_autoscroll_delta(position.y - bottom)) - // } + let text_hovered = text_bounds.contains_point(&event.position); + let gutter_hovered = gutter_bounds.contains_point(&event.position); + editor.set_gutter_hovered(gutter_hovered, cx); - // let horizontal_margin = position_map.line_height.min(text_bounds.width() / 3.0); - // let left = text_bounds.origin.x + horizontal_margin; - // let right = text_bounds.upper_right().x - horizontal_margin; - // if position.x < left { - // scroll_delta.set_x(-scale_horizontal_mouse_autoscroll_delta( - // left - position.x, - // )) - // } - // if position.x > right { - // scroll_delta.set_x(scale_horizontal_mouse_autoscroll_delta( - // position.x - right, - // )) - // } + // Don't trigger hover popover if mouse is hovering over context menu + if text_hovered { + let point_for_position = position_map.point_for_position(text_bounds, event.position); - // let point_for_position = position_map.point_for_position(text_bounds, position); + match point_for_position.as_valid() { + Some(point) => { + update_go_to_definition_link( + editor, + Some(GoToDefinitionTrigger::Text(point)), + modifiers.command, + modifiers.shift, + cx, + ); + hover_at(editor, Some(point), cx); + } + None => { + update_inlay_link_and_hover_points( + &position_map.snapshot, + point_for_position, + editor, + modifiers.command, + modifiers.shift, + cx, + ); + } + } - // editor.select( - // SelectPhase::Update { - // position: point_for_position.previous_valid, - // goal_column: point_for_position.exact_unclipped.column(), - // scroll_position: (position_map.snapshot.scroll_position() + scroll_delta) - // .clamp(gpui::Point::::zero(), position_map.scroll_max), - // }, - // cx, - // ); - // hover_at(editor, point, cx); - // true - // } else { - // hover_at(editor, point, cx); - // false - // } - // } - - // fn mouse_moved( - // editor: &mut Editor, - // MouseMovedEvent { - // modifiers: Modifiers { shift, cmd, .. }, - // position, - // .. - // }: MouseMovedEvent, - // position_map: &PositionMap, - // text_bounds: Bounds, - // cx: &mut ViewContext, - // ) -> bool { - // // This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed - // // Don't trigger hover popover if mouse is hovering over context menu - // if text_bounds.contains_point(position) { - // let point_for_position = position_map.point_for_position(text_bounds, position); - // match point_for_position.as_valid() { - // Some(point) => { - // update_go_to_definition_link( - // editor, - // Some(GoToDefinitionTrigger::Text(point)), - // cmd, - // shift, - // cx, - // ); - // hover_at(editor, Some(point), cx); - // } - // None => { - // update_inlay_link_and_hover_points( - // &position_map.snapshot, - // point_for_position, - // editor, - // cmd, - // shift, - // cx, - // ); - // } - // } - // } else { - // update_go_to_definition_link(editor, None, cmd, shift, cx); - // hover_at(editor, None, cx); - // } - - // true - // } + true + } else { + update_go_to_definition_link(editor, None, modifiers.command, modifiers.shift, cx); + hover_at(editor, None, cx); + gutter_hovered + } + } fn scroll( editor: &mut Editor, @@ -2336,6 +2169,79 @@ impl EditorElement { // blocks, // ) // } + + fn paint_mouse_listeners( + &mut self, + bounds: Bounds, + gutter_bounds: Bounds, + text_bounds: Bounds, + position_map: &Arc, + cx: &mut ViewContext, + ) { + cx.on_mouse_event({ + let position_map = position_map.clone(); + move |editor, event: &ScrollWheelEvent, phase, cx| { + if phase != DispatchPhase::Bubble { + return; + } + + if Self::scroll(editor, event, &position_map, bounds, cx) { + cx.stop_propagation(); + } + } + }); + cx.on_mouse_event({ + let position_map = position_map.clone(); + move |editor, event: &MouseDownEvent, phase, cx| { + if phase != DispatchPhase::Bubble { + return; + } + + if Self::mouse_down(editor, event, &position_map, text_bounds, gutter_bounds, cx) { + cx.stop_propagation() + } + } + }); + cx.on_mouse_event({ + let position_map = position_map.clone(); + move |editor, event: &MouseUpEvent, phase, cx| { + if phase != DispatchPhase::Bubble { + return; + } + + if Self::mouse_up(editor, event, &position_map, text_bounds, cx) { + cx.stop_propagation() + } + } + }); + // todo!() + // on_down(MouseButton::Right, { + // let position_map = position_map.clone(); + // move |event, editor, cx| { + // if !Self::mouse_right_down( + // editor, + // event.position, + // position_map.as_ref(), + // text_bounds, + // cx, + // ) { + // cx.propagate_event(); + // } + // } + // }); + cx.on_mouse_event({ + let position_map = position_map.clone(); + move |editor, event: &MouseMoveEvent, phase, cx| { + if phase != DispatchPhase::Bubble { + return; + } + + if Self::mouse_moved(editor, event, &position_map, text_bounds, gutter_bounds, cx) { + cx.stop_propagation() + } + } + }); + } } #[derive(Debug)] @@ -2555,30 +2461,9 @@ impl Element for EditorElement { let dispatch_context = editor.dispatch_context(cx); cx.with_element_id(cx.view().entity_id(), |global_id, cx| { cx.with_key_dispatch_context(dispatch_context, |cx| { - cx.with_key_listeners( - [ - build_action_listener(Editor::move_left), - build_action_listener(Editor::move_right), - build_action_listener(Editor::move_down), - build_action_listener(Editor::move_up), - build_key_listener( - move |editor, key_down: &KeyDownEvent, dispatch_context, phase, cx| { - if phase == DispatchPhase::Bubble { - if let KeyMatch::Some(action) = cx.match_keystroke( - &global_id, - &key_down.keystroke, - dispatch_context, - ) { - return Some(action); - } - } - - None - }, - ), - ], - |cx| cx.with_focus(editor.focus_handle.clone(), |_| {}), - ); + cx.with_key_listeners(build_key_listeners(global_id), |cx| { + cx.with_focus(editor.focus_handle.clone(), |_| {}) + }); }) }); } @@ -2608,34 +2493,27 @@ impl Element for EditorElement { cx: &mut gpui::ViewContext, ) { let layout = self.compute_layout(editor, cx, bounds); - - cx.on_mouse_event({ - let position_map = layout.position_map.clone(); - move |editor, event: &ScrollWheelEvent, phase, cx| { - if phase != DispatchPhase::Bubble { - return; - } - - if Self::scroll(editor, event, &position_map, bounds, cx) { - cx.stop_propagation(); - } - } - }); + let gutter_bounds = Bounds { + origin: bounds.origin, + size: layout.gutter_size, + }; + let text_bounds = Bounds { + origin: gutter_bounds.upper_right(), + size: layout.text_size, + }; if editor.focus_handle.is_focused(cx) { cx.handle_text_input(); } cx.with_content_mask(ContentMask { bounds }, |cx| { - let gutter_bounds = Bounds { - origin: bounds.origin, - size: layout.gutter_size, - }; - let text_bounds = Bounds { - origin: gutter_bounds.upper_right(), - size: layout.text_size, - }; - + self.paint_mouse_listeners( + bounds, + gutter_bounds, + text_bounds, + &layout.position_map, + cx, + ); self.paint_background(gutter_bounds, text_bounds, &layout, cx); if layout.gutter_size.width > Pixels::ZERO { self.paint_gutter(gutter_bounds, &layout, editor, cx); @@ -3664,12 +3542,12 @@ impl HighlightedRange { // bounds.into_iter() // } -pub fn scale_vertical_mouse_autoscroll_delta(delta: f32) -> f32 { - delta.powf(1.5) / 100.0 +pub fn scale_vertical_mouse_autoscroll_delta(delta: Pixels) -> f32 { + (delta.pow(1.5) / 100.0).into() } -fn scale_horizontal_mouse_autoscroll_delta(delta: f32) -> f32 { - delta.powf(1.2) / 300.0 +fn scale_horizontal_mouse_autoscroll_delta(delta: Pixels) -> f32 { + (delta.pow(1.2) / 300.0).into() } // #[cfg(test)] @@ -4115,6 +3993,178 @@ fn scale_horizontal_mouse_autoscroll_delta(delta: f32) -> f32 { // } // } +fn build_key_listeners( + global_element_id: GlobalElementId, +) -> impl IntoIterator)> { + [ + build_action_listener(Editor::move_left), + build_action_listener(Editor::move_right), + build_action_listener(Editor::move_down), + build_action_listener(Editor::move_up), + // build_action_listener(Editor::new_file), todo!() + // build_action_listener(Editor::new_file_in_direction), todo!() + build_action_listener(Editor::cancel), + build_action_listener(Editor::newline), + build_action_listener(Editor::newline_above), + build_action_listener(Editor::newline_below), + build_action_listener(Editor::backspace), + build_action_listener(Editor::delete), + build_action_listener(Editor::tab), + build_action_listener(Editor::tab_prev), + build_action_listener(Editor::indent), + build_action_listener(Editor::outdent), + build_action_listener(Editor::delete_line), + build_action_listener(Editor::join_lines), + build_action_listener(Editor::sort_lines_case_sensitive), + build_action_listener(Editor::sort_lines_case_insensitive), + build_action_listener(Editor::reverse_lines), + build_action_listener(Editor::shuffle_lines), + build_action_listener(Editor::convert_to_upper_case), + build_action_listener(Editor::convert_to_lower_case), + build_action_listener(Editor::convert_to_title_case), + build_action_listener(Editor::convert_to_snake_case), + build_action_listener(Editor::convert_to_kebab_case), + build_action_listener(Editor::convert_to_upper_camel_case), + build_action_listener(Editor::convert_to_lower_camel_case), + build_action_listener(Editor::delete_to_previous_word_start), + build_action_listener(Editor::delete_to_previous_subword_start), + build_action_listener(Editor::delete_to_next_word_end), + build_action_listener(Editor::delete_to_next_subword_end), + build_action_listener(Editor::delete_to_beginning_of_line), + build_action_listener(Editor::delete_to_end_of_line), + build_action_listener(Editor::cut_to_end_of_line), + build_action_listener(Editor::duplicate_line), + build_action_listener(Editor::move_line_up), + build_action_listener(Editor::move_line_down), + build_action_listener(Editor::transpose), + build_action_listener(Editor::cut), + build_action_listener(Editor::copy), + build_action_listener(Editor::paste), + build_action_listener(Editor::undo), + build_action_listener(Editor::redo), + build_action_listener(Editor::move_page_up), + build_action_listener(Editor::move_page_down), + build_action_listener(Editor::next_screen), + build_action_listener(Editor::scroll_cursor_top), + build_action_listener(Editor::scroll_cursor_center), + build_action_listener(Editor::scroll_cursor_bottom), + build_action_listener(|editor, _: &LineDown, cx| { + editor.scroll_screen(&ScrollAmount::Line(1.), cx) + }), + build_action_listener(|editor, _: &LineUp, cx| { + editor.scroll_screen(&ScrollAmount::Line(-1.), cx) + }), + build_action_listener(|editor, _: &HalfPageDown, cx| { + editor.scroll_screen(&ScrollAmount::Page(0.5), cx) + }), + build_action_listener(|editor, _: &HalfPageUp, cx| { + editor.scroll_screen(&ScrollAmount::Page(-0.5), cx) + }), + build_action_listener(|editor, _: &PageDown, cx| { + editor.scroll_screen(&ScrollAmount::Page(1.), cx) + }), + build_action_listener(|editor, _: &PageUp, cx| { + editor.scroll_screen(&ScrollAmount::Page(-1.), cx) + }), + build_action_listener(Editor::move_to_previous_word_start), + build_action_listener(Editor::move_to_previous_subword_start), + build_action_listener(Editor::move_to_next_word_end), + build_action_listener(Editor::move_to_next_subword_end), + build_action_listener(Editor::move_to_beginning_of_line), + build_action_listener(Editor::move_to_end_of_line), + build_action_listener(Editor::move_to_start_of_paragraph), + build_action_listener(Editor::move_to_end_of_paragraph), + build_action_listener(Editor::move_to_beginning), + build_action_listener(Editor::move_to_end), + build_action_listener(Editor::select_up), + build_action_listener(Editor::select_down), + build_action_listener(Editor::select_left), + build_action_listener(Editor::select_right), + build_action_listener(Editor::select_to_previous_word_start), + build_action_listener(Editor::select_to_previous_subword_start), + build_action_listener(Editor::select_to_next_word_end), + build_action_listener(Editor::select_to_next_subword_end), + build_action_listener(Editor::select_to_beginning_of_line), + build_action_listener(Editor::select_to_end_of_line), + build_action_listener(Editor::select_to_start_of_paragraph), + build_action_listener(Editor::select_to_end_of_paragraph), + build_action_listener(Editor::select_to_beginning), + build_action_listener(Editor::select_to_end), + build_action_listener(Editor::select_all), + build_action_listener(|editor, action, cx| { + editor.select_all_matches(action, cx).log_err(); + }), + build_action_listener(Editor::select_line), + build_action_listener(Editor::split_selection_into_lines), + build_action_listener(Editor::add_selection_above), + build_action_listener(Editor::add_selection_below), + build_action_listener(|editor, action, cx| { + editor.select_next(action, cx).log_err(); + }), + build_action_listener(|editor, action, cx| { + editor.select_previous(action, cx).log_err(); + }), + build_action_listener(Editor::toggle_comments), + build_action_listener(Editor::select_larger_syntax_node), + build_action_listener(Editor::select_smaller_syntax_node), + build_action_listener(Editor::move_to_enclosing_bracket), + build_action_listener(Editor::undo_selection), + build_action_listener(Editor::redo_selection), + build_action_listener(Editor::go_to_diagnostic), + build_action_listener(Editor::go_to_prev_diagnostic), + build_action_listener(Editor::go_to_hunk), + build_action_listener(Editor::go_to_prev_hunk), + build_action_listener(Editor::go_to_definition), + build_action_listener(Editor::go_to_definition_split), + build_action_listener(Editor::go_to_type_definition), + build_action_listener(Editor::go_to_type_definition_split), + build_action_listener(Editor::fold), + build_action_listener(Editor::fold_at), + build_action_listener(Editor::unfold_lines), + build_action_listener(Editor::unfold_at), + build_action_listener(Editor::fold_selected_ranges), + build_action_listener(Editor::show_completions), + // build_action_listener(Editor::toggle_code_actions), todo!() + // build_action_listener(Editor::open_excerpts), todo!() + build_action_listener(Editor::toggle_soft_wrap), + build_action_listener(Editor::toggle_inlay_hints), + build_action_listener(Editor::reveal_in_finder), + build_action_listener(Editor::copy_path), + build_action_listener(Editor::copy_relative_path), + build_action_listener(Editor::copy_highlight_json), + build_action_listener(|editor, action, cx| { + editor + .format(action, cx) + .map(|task| task.detach_and_log_err(cx)); + }), + build_action_listener(Editor::restart_language_server), + build_action_listener(Editor::show_character_palette), + // build_action_listener(Editor::confirm_completion), todo!() + // build_action_listener(Editor::confirm_code_action), todo!() + // build_action_listener(Editor::rename), todo!() + // build_action_listener(Editor::confirm_rename), todo!() + // build_action_listener(Editor::find_all_references), todo!() + build_action_listener(Editor::next_copilot_suggestion), + build_action_listener(Editor::previous_copilot_suggestion), + build_action_listener(Editor::copilot_suggest), + build_key_listener( + move |editor, key_down: &KeyDownEvent, dispatch_context, phase, cx| { + if phase == DispatchPhase::Bubble { + if let KeyMatch::Some(action) = cx.match_keystroke( + &global_element_id, + &key_down.keystroke, + dispatch_context, + ) { + return Some(action); + } + } + + None + }, + ), + ] +} + fn build_key_listener( listener: impl Fn( &mut Editor, diff --git a/crates/editor2/src/hover_popover.rs b/crates/editor2/src/hover_popover.rs index 9eddd259af..5c8f403d4f 100644 --- a/crates/editor2/src/hover_popover.rs +++ b/crates/editor2/src/hover_popover.rs @@ -144,8 +144,7 @@ pub fn hide_hover(editor: &mut Editor, cx: &mut ViewContext) -> bool { editor.hover_state.info_task = None; editor.hover_state.triggered_from = None; - // todo!() - // editor.clear_background_highlights::(cx); + editor.clear_background_highlights::(cx); if did_hide { cx.notify(); @@ -325,23 +324,22 @@ fn show_hover( }; this.update(&mut cx, |this, cx| { - todo!(); - // if let Some(symbol_range) = hover_popover - // .as_ref() - // .and_then(|hover_popover| hover_popover.symbol_range.as_text_range()) - // { - // // Highlight the selected symbol using a background highlight - // this.highlight_background::( - // vec![symbol_range], - // |theme| theme.editor.hover_popover.highlight, - // cx, - // ); - // } else { - // this.clear_background_highlights::(cx); - // } - // - // this.hover_state.info_popover = hover_popover; - // cx.notify(); + if let Some(symbol_range) = hover_popover + .as_ref() + .and_then(|hover_popover| hover_popover.symbol_range.as_text_range()) + { + // Highlight the selected symbol using a background highlight + this.highlight_background::( + vec![symbol_range], + |theme| theme.element_hover, // todo! update theme + cx, + ); + } else { + this.clear_background_highlights::(cx); + } + + this.hover_state.info_popover = hover_popover; + cx.notify(); })?; Ok::<_, anyhow::Error>(()) diff --git a/crates/editor2/src/link_go_to_definition.rs b/crates/editor2/src/link_go_to_definition.rs index 9db4061e50..d36762f395 100644 --- a/crates/editor2/src/link_go_to_definition.rs +++ b/crates/editor2/src/link_go_to_definition.rs @@ -171,173 +171,170 @@ pub fn update_inlay_link_and_hover_points( shift_held: bool, cx: &mut ViewContext<'_, Editor>, ) { - todo!("old implementation below") -} -// ) { -// let hovered_offset = if point_for_position.column_overshoot_after_line_end == 0 { -// Some(snapshot.display_point_to_inlay_offset(point_for_position.exact_unclipped, Bias::Left)) -// } else { -// None -// }; -// let mut go_to_definition_updated = false; -// let mut hover_updated = false; -// if let Some(hovered_offset) = hovered_offset { -// let buffer_snapshot = editor.buffer().read(cx).snapshot(cx); -// let previous_valid_anchor = buffer_snapshot.anchor_at( -// point_for_position.previous_valid.to_point(snapshot), -// Bias::Left, -// ); -// let next_valid_anchor = buffer_snapshot.anchor_at( -// point_for_position.next_valid.to_point(snapshot), -// Bias::Right, -// ); -// if let Some(hovered_hint) = editor -// .visible_inlay_hints(cx) -// .into_iter() -// .skip_while(|hint| { -// hint.position -// .cmp(&previous_valid_anchor, &buffer_snapshot) -// .is_lt() -// }) -// .take_while(|hint| { -// hint.position -// .cmp(&next_valid_anchor, &buffer_snapshot) -// .is_le() -// }) -// .max_by_key(|hint| hint.id) -// { -// let inlay_hint_cache = editor.inlay_hint_cache(); -// let excerpt_id = previous_valid_anchor.excerpt_id; -// if let Some(cached_hint) = inlay_hint_cache.hint_by_id(excerpt_id, hovered_hint.id) { -// match cached_hint.resolve_state { -// ResolveState::CanResolve(_, _) => { -// if let Some(buffer_id) = previous_valid_anchor.buffer_id { -// inlay_hint_cache.spawn_hint_resolve( -// buffer_id, -// excerpt_id, -// hovered_hint.id, -// cx, -// ); -// } -// } -// ResolveState::Resolved => { -// let mut extra_shift_left = 0; -// let mut extra_shift_right = 0; -// if cached_hint.padding_left { -// extra_shift_left += 1; -// extra_shift_right += 1; -// } -// if cached_hint.padding_right { -// extra_shift_right += 1; -// } -// match cached_hint.label { -// project::InlayHintLabel::String(_) => { -// if let Some(tooltip) = cached_hint.tooltip { -// hover_popover::hover_at_inlay( -// editor, -// InlayHover { -// excerpt: excerpt_id, -// tooltip: match tooltip { -// InlayHintTooltip::String(text) => HoverBlock { -// text, -// kind: HoverBlockKind::PlainText, -// }, -// InlayHintTooltip::MarkupContent(content) => { -// HoverBlock { -// text: content.value, -// kind: content.kind, -// } -// } -// }, -// range: InlayHighlight { -// inlay: hovered_hint.id, -// inlay_position: hovered_hint.position, -// range: extra_shift_left -// ..hovered_hint.text.len() + extra_shift_right, -// }, -// }, -// cx, -// ); -// hover_updated = true; -// } -// } -// project::InlayHintLabel::LabelParts(label_parts) => { -// let hint_start = -// snapshot.anchor_to_inlay_offset(hovered_hint.position); -// if let Some((hovered_hint_part, part_range)) = -// hover_popover::find_hovered_hint_part( -// label_parts, -// hint_start, -// hovered_offset, -// ) -// { -// let highlight_start = -// (part_range.start - hint_start).0 + extra_shift_left; -// let highlight_end = -// (part_range.end - hint_start).0 + extra_shift_right; -// let highlight = InlayHighlight { -// inlay: hovered_hint.id, -// inlay_position: hovered_hint.position, -// range: highlight_start..highlight_end, -// }; -// if let Some(tooltip) = hovered_hint_part.tooltip { -// hover_popover::hover_at_inlay( -// editor, -// InlayHover { -// excerpt: excerpt_id, -// tooltip: match tooltip { -// InlayHintLabelPartTooltip::String(text) => { -// HoverBlock { -// text, -// kind: HoverBlockKind::PlainText, -// } -// } -// InlayHintLabelPartTooltip::MarkupContent( -// content, -// ) => HoverBlock { -// text: content.value, -// kind: content.kind, -// }, -// }, -// range: highlight.clone(), -// }, -// cx, -// ); -// hover_updated = true; -// } -// if let Some((language_server_id, location)) = -// hovered_hint_part.location -// { -// go_to_definition_updated = true; -// update_go_to_definition_link( -// editor, -// Some(GoToDefinitionTrigger::InlayHint( -// highlight, -// location, -// language_server_id, -// )), -// cmd_held, -// shift_held, -// cx, -// ); -// } -// } -// } -// }; -// } -// ResolveState::Resolving => {} -// } -// } -// } -// } + let hovered_offset = if point_for_position.column_overshoot_after_line_end == 0 { + Some(snapshot.display_point_to_inlay_offset(point_for_position.exact_unclipped, Bias::Left)) + } else { + None + }; + let mut go_to_definition_updated = false; + let mut hover_updated = false; + if let Some(hovered_offset) = hovered_offset { + let buffer_snapshot = editor.buffer().read(cx).snapshot(cx); + let previous_valid_anchor = buffer_snapshot.anchor_at( + point_for_position.previous_valid.to_point(snapshot), + Bias::Left, + ); + let next_valid_anchor = buffer_snapshot.anchor_at( + point_for_position.next_valid.to_point(snapshot), + Bias::Right, + ); + if let Some(hovered_hint) = editor + .visible_inlay_hints(cx) + .into_iter() + .skip_while(|hint| { + hint.position + .cmp(&previous_valid_anchor, &buffer_snapshot) + .is_lt() + }) + .take_while(|hint| { + hint.position + .cmp(&next_valid_anchor, &buffer_snapshot) + .is_le() + }) + .max_by_key(|hint| hint.id) + { + let inlay_hint_cache = editor.inlay_hint_cache(); + let excerpt_id = previous_valid_anchor.excerpt_id; + if let Some(cached_hint) = inlay_hint_cache.hint_by_id(excerpt_id, hovered_hint.id) { + match cached_hint.resolve_state { + ResolveState::CanResolve(_, _) => { + if let Some(buffer_id) = previous_valid_anchor.buffer_id { + inlay_hint_cache.spawn_hint_resolve( + buffer_id, + excerpt_id, + hovered_hint.id, + cx, + ); + } + } + ResolveState::Resolved => { + let mut extra_shift_left = 0; + let mut extra_shift_right = 0; + if cached_hint.padding_left { + extra_shift_left += 1; + extra_shift_right += 1; + } + if cached_hint.padding_right { + extra_shift_right += 1; + } + match cached_hint.label { + project::InlayHintLabel::String(_) => { + if let Some(tooltip) = cached_hint.tooltip { + hover_popover::hover_at_inlay( + editor, + InlayHover { + excerpt: excerpt_id, + tooltip: match tooltip { + InlayHintTooltip::String(text) => HoverBlock { + text, + kind: HoverBlockKind::PlainText, + }, + InlayHintTooltip::MarkupContent(content) => { + HoverBlock { + text: content.value, + kind: content.kind, + } + } + }, + range: InlayHighlight { + inlay: hovered_hint.id, + inlay_position: hovered_hint.position, + range: extra_shift_left + ..hovered_hint.text.len() + extra_shift_right, + }, + }, + cx, + ); + hover_updated = true; + } + } + project::InlayHintLabel::LabelParts(label_parts) => { + let hint_start = + snapshot.anchor_to_inlay_offset(hovered_hint.position); + if let Some((hovered_hint_part, part_range)) = + hover_popover::find_hovered_hint_part( + label_parts, + hint_start, + hovered_offset, + ) + { + let highlight_start = + (part_range.start - hint_start).0 + extra_shift_left; + let highlight_end = + (part_range.end - hint_start).0 + extra_shift_right; + let highlight = InlayHighlight { + inlay: hovered_hint.id, + inlay_position: hovered_hint.position, + range: highlight_start..highlight_end, + }; + if let Some(tooltip) = hovered_hint_part.tooltip { + hover_popover::hover_at_inlay( + editor, + InlayHover { + excerpt: excerpt_id, + tooltip: match tooltip { + InlayHintLabelPartTooltip::String(text) => { + HoverBlock { + text, + kind: HoverBlockKind::PlainText, + } + } + InlayHintLabelPartTooltip::MarkupContent( + content, + ) => HoverBlock { + text: content.value, + kind: content.kind, + }, + }, + range: highlight.clone(), + }, + cx, + ); + hover_updated = true; + } + if let Some((language_server_id, location)) = + hovered_hint_part.location + { + go_to_definition_updated = true; + update_go_to_definition_link( + editor, + Some(GoToDefinitionTrigger::InlayHint( + highlight, + location, + language_server_id, + )), + cmd_held, + shift_held, + cx, + ); + } + } + } + }; + } + ResolveState::Resolving => {} + } + } + } + } -// if !go_to_definition_updated { -// update_go_to_definition_link(editor, None, cmd_held, shift_held, cx); -// } -// if !hover_updated { -// hover_popover::hover_at(editor, None, cx); -// } -// } + if !go_to_definition_updated { + update_go_to_definition_link(editor, None, cmd_held, shift_held, cx); + } + if !hover_updated { + hover_popover::hover_at(editor, None, cx); + } +} #[derive(Debug, Clone, Copy, PartialEq)] pub enum LinkDefinitionKind { diff --git a/crates/editor2/src/scroll.rs b/crates/editor2/src/scroll.rs index d5aeb853bb..52e7498f97 100644 --- a/crates/editor2/src/scroll.rs +++ b/crates/editor2/src/scroll.rs @@ -288,16 +288,15 @@ impl ScrollManager { } } -// todo!() impl Editor { - // pub fn vertical_scroll_margin(&mut self) -> usize { - // self.scroll_manager.vertical_scroll_margin as usize - // } + pub fn vertical_scroll_margin(&mut self) -> usize { + self.scroll_manager.vertical_scroll_margin as usize + } - // pub fn set_vertical_scroll_margin(&mut self, margin_rows: usize, cx: &mut ViewContext) { - // self.scroll_manager.vertical_scroll_margin = margin_rows as f32; - // cx.notify(); - // } + pub fn set_vertical_scroll_margin(&mut self, margin_rows: usize, cx: &mut ViewContext) { + self.scroll_manager.vertical_scroll_margin = margin_rows as f32; + cx.notify(); + } pub fn visible_line_count(&self) -> Option { self.scroll_manager.visible_line_count @@ -349,11 +348,9 @@ impl Editor { self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx); } - pub fn scroll_position(&self, cx: &mut ViewContext) -> gpui::Point { + pub fn scroll_position(&self, cx: &mut ViewContext) -> gpui::Point { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - // todo!() Should `self.scroll_manager.anchor.scroll_position()` return `Pixels`? - // self.scroll_manager.anchor.scroll_position(&display_map) - todo!() + self.scroll_manager.anchor.scroll_position(&display_map) } pub fn set_scroll_anchor(&mut self, scroll_anchor: ScrollAnchor, cx: &mut ViewContext) { @@ -382,50 +379,50 @@ impl Editor { .set_anchor(scroll_anchor, top_row, false, false, workspace_id, cx); } - // pub fn scroll_screen(&mut self, amount: &ScrollAmount, cx: &mut ViewContext) { - // if matches!(self.mode, EditorMode::SingleLine) { - // cx.propagate_action(); - // return; - // } + pub fn scroll_screen(&mut self, amount: &ScrollAmount, cx: &mut ViewContext) { + if matches!(self.mode, EditorMode::SingleLine) { + cx.propagate(); + return; + } - // if self.take_rename(true, cx).is_some() { - // return; - // } + if self.take_rename(true, cx).is_some() { + return; + } - // let cur_position = self.scroll_position(cx); - // let new_pos = cur_position + point(0., amount.lines(self)); - // self.set_scroll_position(new_pos, cx); - // } + let cur_position = self.scroll_position(cx); + let new_pos = cur_position + point(0., amount.lines(self)); + self.set_scroll_position(new_pos, cx); + } - // /// Returns an ordering. The newest selection is: - // /// Ordering::Equal => on screen - // /// Ordering::Less => above the screen - // /// Ordering::Greater => below the screen - // pub fn newest_selection_on_screen(&self, cx: &mut AppContext) -> Ordering { - // let snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - // let newest_head = self - // .selections - // .newest_anchor() - // .head() - // .to_display_point(&snapshot); - // let screen_top = self - // .scroll_manager - // .anchor - // .anchor - // .to_display_point(&snapshot); + /// Returns an ordering. The newest selection is: + /// Ordering::Equal => on screen + /// Ordering::Less => above the screen + /// Ordering::Greater => below the screen + pub fn newest_selection_on_screen(&self, cx: &mut AppContext) -> Ordering { + let snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let newest_head = self + .selections + .newest_anchor() + .head() + .to_display_point(&snapshot); + let screen_top = self + .scroll_manager + .anchor + .anchor + .to_display_point(&snapshot); - // if screen_top > newest_head { - // return Ordering::Less; - // } + if screen_top > newest_head { + return Ordering::Less; + } - // if let Some(visible_lines) = self.visible_line_count() { - // if newest_head.row() < screen_top.row() + visible_lines as u32 { - // return Ordering::Equal; - // } - // } + if let Some(visible_lines) = self.visible_line_count() { + if newest_head.row() < screen_top.row() + visible_lines as u32 { + return Ordering::Equal; + } + } - // Ordering::Greater - // } + Ordering::Greater + } pub fn read_scroll_position_from_db( &mut self, diff --git a/crates/editor2/src/scroll/actions.rs b/crates/editor2/src/scroll/actions.rs index 5ea502c456..9a1e54f5e4 100644 --- a/crates/editor2/src/scroll/actions.rs +++ b/crates/editor2/src/scroll/actions.rs @@ -1,66 +1,27 @@ use super::Axis; -use crate::Editor; -use gpui::{AppContext, Point, ViewContext}; - -// actions!( -// editor, -// [ -// LineDown, -// LineUp, -// HalfPageDown, -// HalfPageUp, -// PageDown, -// PageUp, -// NextScreen, -// ScrollCursorTop, -// ScrollCursorCenter, -// ScrollCursorBottom, -// ] -// ); - -pub fn init(cx: &mut AppContext) { - // todo!() - // cx.add_action(Editor::next_screen); - // cx.add_action(Editor::scroll_cursor_top); - // cx.add_action(Editor::scroll_cursor_center); - // cx.add_action(Editor::scroll_cursor_bottom); - // cx.add_action(|this: &mut Editor, _: &LineDown, cx| { - // this.scroll_screen(&ScrollAmount::Line(1.), cx) - // }); - // cx.add_action(|this: &mut Editor, _: &LineUp, cx| { - // this.scroll_screen(&ScrollAmount::Line(-1.), cx) - // }); - // cx.add_action(|this: &mut Editor, _: &HalfPageDown, cx| { - // this.scroll_screen(&ScrollAmount::Page(0.5), cx) - // }); - // cx.add_action(|this: &mut Editor, _: &HalfPageUp, cx| { - // this.scroll_screen(&ScrollAmount::Page(-0.5), cx) - // }); - // cx.add_action(|this: &mut Editor, _: &PageDown, cx| { - // this.scroll_screen(&ScrollAmount::Page(1.), cx) - // }); - // cx.add_action(|this: &mut Editor, _: &PageUp, cx| { - // this.scroll_screen(&ScrollAmount::Page(-1.), cx) - // }); -} +use crate::{ + Autoscroll, Bias, Editor, EditorMode, NextScreen, ScrollAnchor, ScrollCursorBottom, + ScrollCursorCenter, ScrollCursorTop, +}; +use gpui::{actions, AppContext, Point, ViewContext}; impl Editor { - // pub fn next_screen(&mut self, _: &NextScreen, cx: &mut ViewContext) -> Option<()> { - // if self.take_rename(true, cx).is_some() { - // return None; - // } + pub fn next_screen(&mut self, _: &NextScreen, cx: &mut ViewContext) { + if self.take_rename(true, cx).is_some() { + return; + } - // if self.mouse_context_menu.read(cx).visible() { - // return None; - // } + // todo!() + // if self.mouse_context_menu.read(cx).visible() { + // return None; + // } - // if matches!(self.mode, EditorMode::SingleLine) { - // cx.propagate_action(); - // return None; - // } - // self.request_autoscroll(Autoscroll::Next, cx); - // Some(()) - // } + if matches!(self.mode, EditorMode::SingleLine) { + cx.propagate(); + return; + } + self.request_autoscroll(Autoscroll::Next, cx); + } pub fn scroll( &mut self, @@ -72,79 +33,71 @@ impl Editor { self.set_scroll_position(scroll_position, cx); } - // fn scroll_cursor_top(editor: &mut Editor, _: &ScrollCursorTop, cx: &mut ViewContext) { - // let snapshot = editor.snapshot(cx).display_snapshot; - // let scroll_margin_rows = editor.vertical_scroll_margin() as u32; + pub fn scroll_cursor_top(&mut self, _: &ScrollCursorTop, cx: &mut ViewContext) { + let snapshot = self.snapshot(cx).display_snapshot; + let scroll_margin_rows = self.vertical_scroll_margin() as u32; - // let mut new_screen_top = editor.selections.newest_display(cx).head(); - // *new_screen_top.row_mut() = new_screen_top.row().saturating_sub(scroll_margin_rows); - // *new_screen_top.column_mut() = 0; - // let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left); - // let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top); + let mut new_screen_top = self.selections.newest_display(cx).head(); + *new_screen_top.row_mut() = new_screen_top.row().saturating_sub(scroll_margin_rows); + *new_screen_top.column_mut() = 0; + let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left); + let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top); - // editor.set_scroll_anchor( - // ScrollAnchor { - // anchor: new_anchor, - // offset: Default::default(), - // }, - // cx, - // ) - // } + self.set_scroll_anchor( + ScrollAnchor { + anchor: new_anchor, + offset: Default::default(), + }, + cx, + ) + } - // fn scroll_cursor_center( - // editor: &mut Editor, - // _: &ScrollCursorCenter, - // cx: &mut ViewContext, - // ) { - // let snapshot = editor.snapshot(cx).display_snapshot; - // let visible_rows = if let Some(visible_rows) = editor.visible_line_count() { - // visible_rows as u32 - // } else { - // return; - // }; + pub fn scroll_cursor_center(&mut self, _: &ScrollCursorCenter, cx: &mut ViewContext) { + let snapshot = self.snapshot(cx).display_snapshot; + let visible_rows = if let Some(visible_rows) = self.visible_line_count() { + visible_rows as u32 + } else { + return; + }; - // let mut new_screen_top = editor.selections.newest_display(cx).head(); - // *new_screen_top.row_mut() = new_screen_top.row().saturating_sub(visible_rows / 2); - // *new_screen_top.column_mut() = 0; - // let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left); - // let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top); + let mut new_screen_top = self.selections.newest_display(cx).head(); + *new_screen_top.row_mut() = new_screen_top.row().saturating_sub(visible_rows / 2); + *new_screen_top.column_mut() = 0; + let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left); + let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top); - // editor.set_scroll_anchor( - // ScrollAnchor { - // anchor: new_anchor, - // offset: Default::default(), - // }, - // cx, - // ) - // } + self.set_scroll_anchor( + ScrollAnchor { + anchor: new_anchor, + offset: Default::default(), + }, + cx, + ) + } - // fn scroll_cursor_bottom( - // editor: &mut Editor, - // _: &ScrollCursorBottom, - // cx: &mut ViewContext, - // ) { - // let snapshot = editor.snapshot(cx).display_snapshot; - // let scroll_margin_rows = editor.vertical_scroll_margin() as u32; - // let visible_rows = if let Some(visible_rows) = editor.visible_line_count() { - // visible_rows as u32 - // } else { - // return; - // }; + pub fn scroll_cursor_bottom(&mut self, _: &ScrollCursorBottom, cx: &mut ViewContext) { + let snapshot = self.snapshot(cx).display_snapshot; + let scroll_margin_rows = self.vertical_scroll_margin() as u32; + let visible_rows = if let Some(visible_rows) = self.visible_line_count() { + visible_rows as u32 + } else { + return; + }; - // let mut new_screen_top = editor.selections.newest_display(cx).head(); - // *new_screen_top.row_mut() = new_screen_top - // .row() - // .saturating_sub(visible_rows.saturating_sub(scroll_margin_rows)); - // *new_screen_top.column_mut() = 0; - // let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left); - // let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top); + let mut new_screen_top = self.selections.newest_display(cx).head(); + *new_screen_top.row_mut() = new_screen_top + .row() + .saturating_sub(visible_rows.saturating_sub(scroll_margin_rows)); + *new_screen_top.column_mut() = 0; + let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left); + let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top); - // editor.set_scroll_anchor( - // ScrollAnchor { - // anchor: new_anchor, - // offset: Default::default(), - // }, - // cx, - // ) - // } + self.set_scroll_anchor( + ScrollAnchor { + anchor: new_anchor, + offset: Default::default(), + }, + cx, + ) + } } diff --git a/crates/editor2/src/selections_collection.rs b/crates/editor2/src/selections_collection.rs index 2762fd369e..23c5a75809 100644 --- a/crates/editor2/src/selections_collection.rs +++ b/crates/editor2/src/selections_collection.rs @@ -302,39 +302,39 @@ impl SelectionsCollection { .collect() } - // pub fn build_columnar_selection( - // &mut self, - // display_map: &DisplaySnapshot, - // row: u32, - // positions: &Range, - // reversed: bool, - // text_layout_details: &TextLayoutDetails, - // ) -> Option> { - // let is_empty = positions.start == positions.end; - // let line_len = display_map.line_len(row); + pub fn build_columnar_selection( + &mut self, + display_map: &DisplaySnapshot, + row: u32, + positions: &Range, + reversed: bool, + text_layout_details: &TextLayoutDetails, + ) -> Option> { + let is_empty = positions.start == positions.end; + let line_len = display_map.line_len(row); - // let layed_out_line = display_map.lay_out_line_for_row(row, &text_layout_details); + let layed_out_line = display_map.lay_out_line_for_row(row, &text_layout_details); - // let start_col = layed_out_line.closest_index_for_x(positions.start) as u32; - // if start_col < line_len || (is_empty && positions.start == layed_out_line.width()) { - // let start = DisplayPoint::new(row, start_col); - // let end_col = layed_out_line.closest_index_for_x(positions.end) as u32; - // let end = DisplayPoint::new(row, end_col); + let start_col = layed_out_line.closest_index_for_x(positions.start) as u32; + if start_col < line_len || (is_empty && positions.start == layed_out_line.width) { + let start = DisplayPoint::new(row, start_col); + let end_col = layed_out_line.closest_index_for_x(positions.end) as u32; + let end = DisplayPoint::new(row, end_col); - // Some(Selection { - // id: post_inc(&mut self.next_selection_id), - // start: start.to_point(display_map), - // end: end.to_point(display_map), - // reversed, - // goal: SelectionGoal::HorizontalRange { - // start: positions.start, - // end: positions.end, - // }, - // }) - // } else { - // None - // } - // } + Some(Selection { + id: post_inc(&mut self.next_selection_id), + start: start.to_point(display_map), + end: end.to_point(display_map), + reversed, + goal: SelectionGoal::HorizontalRange { + start: positions.start.into(), + end: positions.end.into(), + }, + }) + } else { + None + } + } pub(crate) fn change_with( &mut self, diff --git a/crates/go_to_line2/src/go_to_line.rs b/crates/go_to_line2/src/go_to_line.rs index 13d283ecff..b8b91b1646 100644 --- a/crates/go_to_line2/src/go_to_line.rs +++ b/crates/go_to_line2/src/go_to_line.rs @@ -4,7 +4,6 @@ use workspace::ModalRegistry; actions!(Toggle); pub fn init(cx: &mut AppContext) { - cx.register_action_type::(); cx.global_mut::() .register_modal(Toggle, |_, cx| { // if let Some(editor) = workspace diff --git a/crates/gpui2/src/action.rs b/crates/gpui2/src/action.rs index 90f312f502..4d89ba1826 100644 --- a/crates/gpui2/src/action.rs +++ b/crates/gpui2/src/action.rs @@ -1,9 +1,54 @@ use crate::SharedString; use anyhow::{anyhow, Context, Result}; use collections::{HashMap, HashSet}; +use lazy_static::lazy_static; +use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard}; use serde::Deserialize; use std::any::{type_name, Any}; +/// Actions are used to implement keyboard-driven UI. +/// When you declare an action, you can bind keys to the action in the keymap and +/// listeners for that action in the element tree. +/// +/// To declare a list of simple actions, you can use the actions! macro, which defines a simple unit struct +/// action for each listed action name. +/// ```rust +/// actions!(MoveUp, MoveDown, MoveLeft, MoveRight, Newline); +/// ``` +/// More complex data types can also be actions. If you annotate your type with the `#[action]` proc macro, +/// it will automatically +/// ``` +/// #[action] +/// pub struct SelectNext { +/// pub replace_newest: bool, +/// } +/// +/// Any type A that satisfies the following bounds is automatically an action: +/// +/// ``` +/// A: for<'a> Deserialize<'a> + PartialEq + Clone + Default + std::fmt::Debug + 'static, +/// ``` +/// +/// The `#[action]` annotation will derive these implementations for your struct automatically. If you +/// want to control them manually, you can use the lower-level `#[register_action]` macro, which only +/// generates the code needed to register your action before `main`. Then you'll need to implement all +/// the traits manually. +/// +/// ``` +/// #[gpui::register_action] +/// #[derive(gpui::serde::Deserialize, std::cmp::PartialEq, std::clone::Clone, std::fmt::Debug)] +/// pub struct Paste { +/// pub content: SharedString, +/// } +/// +/// impl std::default::Default for Paste { +/// fn default() -> Self { +/// Self { +/// content: SharedString::from("🍝"), +/// } +/// } +/// } +/// ``` pub trait Action: std::fmt::Debug + 'static { fn qualified_name() -> SharedString where @@ -17,32 +62,7 @@ pub trait Action: std::fmt::Debug + 'static { fn as_any(&self) -> &dyn Any; } -// actions defines structs that can be used as actions. -#[macro_export] -macro_rules! actions { - () => {}; - - ( $name:ident ) => { - #[derive(::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, ::std::cmp::PartialEq, $crate::serde::Deserialize)] - pub struct $name; - }; - - ( $name:ident { $($token:tt)* } ) => { - #[derive(::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, ::std::cmp::PartialEq, $crate::serde::Deserialize)] - pub struct $name { $($token)* } - }; - - ( $name:ident, $($rest:tt)* ) => { - actions!($name); - actions!($($rest)*); - }; - - ( $name:ident { $($token:tt)* }, $($rest:tt)* ) => { - actions!($name { $($token)* }); - actions!($($rest)*); - }; -} - +// Types become actions by satisfying a list of trait bounds. impl Action for A where A: for<'a> Deserialize<'a> + PartialEq + Clone + Default + std::fmt::Debug + 'static, @@ -80,6 +100,61 @@ where } } +type ActionBuilder = fn(json: Option) -> anyhow::Result>; + +lazy_static! { + static ref ACTION_REGISTRY: RwLock = RwLock::default(); +} + +#[derive(Default)] +struct ActionRegistry { + builders_by_name: HashMap, + all_names: Vec, // So we can return a static slice. +} + +/// Register an action type to allow it to be referenced in keymaps. +pub fn register_action() { + let name = A::qualified_name(); + let mut lock = ACTION_REGISTRY.write(); + lock.builders_by_name.insert(name.clone(), A::build); + lock.all_names.push(name); +} + +/// Construct an action based on its name and optional JSON parameters sourced from the keymap. +pub fn build_action(name: &str, params: Option) -> Result> { + let lock = ACTION_REGISTRY.read(); + let build_action = lock + .builders_by_name + .get(name) + .ok_or_else(|| anyhow!("no action type registered for {}", name))?; + (build_action)(params) +} + +pub fn all_action_names() -> MappedRwLockReadGuard<'static, [SharedString]> { + let lock = ACTION_REGISTRY.read(); + RwLockReadGuard::map(lock, |registry: &ActionRegistry| { + registry.all_names.as_slice() + }) +} + +/// Defines unit structs that can be used as actions. +/// To use more complex data types as actions, annotate your type with the #[action] macro. +#[macro_export] +macro_rules! actions { + () => {}; + + ( $name:ident ) => { + #[gpui::register_action] + #[derive(::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, ::std::cmp::PartialEq, $crate::serde::Deserialize)] + pub struct $name; + }; + + ( $name:ident, $($rest:tt)* ) => { + actions!($name); + actions!($($rest)*); + }; +} + #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct DispatchContext { set: HashSet, @@ -317,22 +392,23 @@ fn skip_whitespace(source: &str) -> &str { #[cfg(test)] mod tests { use super::*; + use crate as gpui; use DispatchContextPredicate::*; #[test] fn test_actions_definition() { { - actions!(A, B { field: i32 }, C, D, E, F {}, G); + actions!(A, B, C, D, E, F, G); } { actions!( A, - B { field: i32 }, + B, C, D, E, - F {}, + F, G, // Don't wrap, test the trailing comma ); } diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 42d34e0c20..f2ac3a91cf 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -17,9 +17,9 @@ use crate::{ current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId, Entity, FocusEvent, FocusHandle, FocusId, ForegroundExecutor, KeyBinding, Keymap, LayoutId, - PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, SharedString, - SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, - View, Window, WindowContext, WindowHandle, WindowId, + PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, SubscriberSet, + Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, Window, + WindowContext, WindowHandle, WindowId, }; use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, VecDeque}; @@ -140,7 +140,6 @@ impl App { } } -type ActionBuilder = fn(json: Option) -> anyhow::Result>; pub(crate) type FrameCallback = Box; type Handler = Box bool + 'static>; type Listener = Box bool + 'static>; @@ -176,7 +175,6 @@ pub struct AppContext { pub(crate) keymap: Arc>, pub(crate) global_action_listeners: HashMap>>, - action_builders: HashMap, pending_effects: VecDeque, pub(crate) pending_notifications: HashSet, pub(crate) pending_global_notifications: HashSet, @@ -234,7 +232,6 @@ impl AppContext { windows: SlotMap::with_key(), keymap: Arc::new(Mutex::new(Keymap::default())), global_action_listeners: HashMap::default(), - action_builders: HashMap::default(), pending_effects: VecDeque::new(), pending_notifications: HashSet::default(), pending_global_notifications: HashSet::default(), @@ -695,10 +692,6 @@ impl AppContext { ) } - pub fn all_action_names<'a>(&'a self) -> impl Iterator + 'a { - self.action_builders.keys().cloned() - } - /// Move the global of the given type to the stack. pub(crate) fn lease_global(&mut self) -> GlobalLease { GlobalLease::new( @@ -761,24 +754,6 @@ impl AppContext { })); } - /// Register an action type to allow it to be referenced in keymaps. - pub fn register_action_type(&mut self) { - self.action_builders.insert(A::qualified_name(), A::build); - } - - /// Construct an action based on its name and parameters. - pub fn build_action( - &mut self, - name: &str, - params: Option, - ) -> Result> { - let build = self - .action_builders - .get(name) - .ok_or_else(|| anyhow!("no action type registered for {}", name))?; - (build)(params) - } - /// Event handlers propagate events by default. Call this method to stop dispatching to /// event handlers with a lower z-index (mouse) or higher in the tree (keyboard). This is /// the opposite of [propagate]. It's also possible to cancel a call to [propagate] by diff --git a/crates/gpui2/src/executor.rs b/crates/gpui2/src/executor.rs index b6fd5b2318..b7e3610283 100644 --- a/crates/gpui2/src/executor.rs +++ b/crates/gpui2/src/executor.rs @@ -68,7 +68,8 @@ impl Future for Task { } } } - +type AnyLocalFuture = Pin>>; +type AnyFuture = Pin>>; impl BackgroundExecutor { pub fn new(dispatcher: Arc) -> Self { Self { dispatcher } @@ -81,10 +82,16 @@ impl BackgroundExecutor { R: Send + 'static, { let dispatcher = self.dispatcher.clone(); - let (runnable, task) = - async_task::spawn(future, move |runnable| dispatcher.dispatch(runnable)); - runnable.schedule(); - Task::Spawned(task) + fn inner( + dispatcher: Arc, + future: AnyFuture, + ) -> Task { + let (runnable, task) = + async_task::spawn(future, move |runnable| dispatcher.dispatch(runnable)); + runnable.schedule(); + Task::Spawned(task) + } + inner::(dispatcher, Box::pin(future)) } #[cfg(any(test, feature = "test-support"))] @@ -243,11 +250,17 @@ impl ForegroundExecutor { R: 'static, { let dispatcher = self.dispatcher.clone(); - let (runnable, task) = async_task::spawn_local(future, move |runnable| { - dispatcher.dispatch_on_main_thread(runnable) - }); - runnable.schedule(); - Task::Spawned(task) + fn inner( + dispatcher: Arc, + future: AnyLocalFuture, + ) -> Task { + let (runnable, task) = async_task::spawn_local(future, move |runnable| { + dispatcher.dispatch_on_main_thread(runnable) + }); + runnable.schedule(); + Task::Spawned(task) + } + inner::(dispatcher, Box::pin(future)) } } diff --git a/crates/gpui2/src/gpui2.rs b/crates/gpui2/src/gpui2.rs index e59b196a91..8f3dc6c314 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/crates/gpui2/src/gpui2.rs @@ -37,6 +37,7 @@ pub use anyhow::Result; pub use app::*; pub use assets::*; pub use color::*; +pub use ctor::ctor; pub use element::*; pub use elements::*; pub use executor::*; diff --git a/crates/gpui2/src/platform/mac/metal_renderer.rs b/crates/gpui2/src/platform/mac/metal_renderer.rs index 7bb80640be..0631c75de5 100644 --- a/crates/gpui2/src/platform/mac/metal_renderer.rs +++ b/crates/gpui2/src/platform/mac/metal_renderer.rs @@ -87,7 +87,7 @@ impl MetalRenderer { MTLResourceOptions::StorageModeManaged, ); - let paths_rasterization_pipeline_state = build_pipeline_state( + let paths_rasterization_pipeline_state = build_path_rasterization_pipeline_state( &device, &library, "paths_rasterization", @@ -823,7 +823,40 @@ fn build_pipeline_state( color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One); color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha); color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One); - descriptor.set_depth_attachment_pixel_format(MTLPixelFormat::Invalid); + + device + .new_render_pipeline_state(&descriptor) + .expect("could not create render pipeline state") +} + +fn build_path_rasterization_pipeline_state( + device: &metal::DeviceRef, + library: &metal::LibraryRef, + label: &str, + vertex_fn_name: &str, + fragment_fn_name: &str, + pixel_format: metal::MTLPixelFormat, +) -> metal::RenderPipelineState { + let vertex_fn = library + .get_function(vertex_fn_name, None) + .expect("error locating vertex function"); + let fragment_fn = library + .get_function(fragment_fn_name, None) + .expect("error locating fragment function"); + + let descriptor = metal::RenderPipelineDescriptor::new(); + descriptor.set_label(label); + descriptor.set_vertex_function(Some(vertex_fn.as_ref())); + descriptor.set_fragment_function(Some(fragment_fn.as_ref())); + let color_attachment = descriptor.color_attachments().object_at(0).unwrap(); + color_attachment.set_pixel_format(pixel_format); + color_attachment.set_blending_enabled(true); + color_attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add); + color_attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add); + color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::One); + color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One); + color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::One); + color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One); device .new_render_pipeline_state(&descriptor) diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index a7c8748354..e55f0152d8 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -614,6 +614,10 @@ impl<'a> WindowContext<'a> { .find(|display| display.id() == self.window.display_id) } + pub fn show_character_palette(&self) { + self.window.platform_window.show_character_palette(); + } + /// The scale factor of the display associated with the window. For example, it could /// return 2.0 for a "retina" display, indicating that each logical pixel should actually /// be rendered as two pixels on screen. diff --git a/crates/gpui2_macros/src/action.rs b/crates/gpui2_macros/src/action.rs new file mode 100644 index 0000000000..66302f3fc0 --- /dev/null +++ b/crates/gpui2_macros/src/action.rs @@ -0,0 +1,55 @@ +// Input: +// +// #[action] +// struct Foo { +// bar: String, +// } + +// Output: +// +// #[gpui::register_action] +// #[derive(gpui::serde::Deserialize, std::cmp::PartialEq, std::clone::Clone, std::default::Default, std::fmt::Debug)] +// struct Foo { +// bar: String, +// } + +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, DeriveInput}; + +pub fn action(_attr: TokenStream, item: TokenStream) -> TokenStream { + let input = parse_macro_input!(item as DeriveInput); + let name = &input.ident; + let attrs = input + .attrs + .into_iter() + .filter(|attr| !attr.path.is_ident("action")) + .collect::>(); + + let attributes = quote! { + #[gpui::register_action] + #[derive(gpui::serde::Deserialize, std::cmp::PartialEq, std::clone::Clone, std::default::Default, std::fmt::Debug)] + #(#attrs)* + }; + let visibility = input.vis; + + let output = match input.data { + syn::Data::Struct(ref struct_data) => { + let fields = &struct_data.fields; + quote! { + #attributes + #visibility struct #name #fields + } + } + syn::Data::Enum(ref enum_data) => { + let variants = &enum_data.variants; + quote! { + #attributes + #visibility enum #name { #variants } + } + } + _ => panic!("Expected a struct or an enum."), + }; + + TokenStream::from(output) +} diff --git a/crates/gpui2_macros/src/gpui2_macros.rs b/crates/gpui2_macros/src/gpui2_macros.rs index 2e0c0547f7..80b67e1a12 100644 --- a/crates/gpui2_macros/src/gpui2_macros.rs +++ b/crates/gpui2_macros/src/gpui2_macros.rs @@ -1,14 +1,26 @@ -use proc_macro::TokenStream; - +mod action; mod derive_component; +mod register_action; mod style_helpers; mod test; +use proc_macro::TokenStream; + #[proc_macro] pub fn style_helpers(args: TokenStream) -> TokenStream { style_helpers::style_helpers(args) } +#[proc_macro_attribute] +pub fn action(attr: TokenStream, item: TokenStream) -> TokenStream { + action::action(attr, item) +} + +#[proc_macro_attribute] +pub fn register_action(attr: TokenStream, item: TokenStream) -> TokenStream { + register_action::register_action(attr, item) +} + #[proc_macro_derive(Component, attributes(component))] pub fn derive_component(input: TokenStream) -> TokenStream { derive_component::derive_component(input) diff --git a/crates/gpui2_macros/src/register_action.rs b/crates/gpui2_macros/src/register_action.rs new file mode 100644 index 0000000000..68c39ad9bd --- /dev/null +++ b/crates/gpui2_macros/src/register_action.rs @@ -0,0 +1,33 @@ +// Input: +// +// struct FooBar {} + +// Output: +// +// struct FooBar {} +// +// #[allow(non_snake_case)] +// #[gpui2::ctor] +// fn register_foobar_builder() { +// gpui2::register_action_builder::() +// } +use proc_macro::TokenStream; +use quote::{format_ident, quote}; +use syn::{parse_macro_input, DeriveInput}; + +pub fn register_action(_attr: TokenStream, item: TokenStream) -> TokenStream { + let input = parse_macro_input!(item as DeriveInput); + let type_name = &input.ident; + let ctor_fn_name = format_ident!("register_{}_builder", type_name.to_string().to_lowercase()); + + let expanded = quote! { + #input + #[allow(non_snake_case)] + #[gpui::ctor] + fn #ctor_fn_name() { + gpui::register_action::<#type_name>() + } + }; + + TokenStream::from(expanded) +} diff --git a/crates/settings2/src/keymap_file.rs b/crates/settings2/src/keymap_file.rs index 93635935cb..9f279864ee 100644 --- a/crates/settings2/src/keymap_file.rs +++ b/crates/settings2/src/keymap_file.rs @@ -73,9 +73,9 @@ impl KeymapFile { "Expected first item in array to be a string." ))); }; - cx.build_action(&name, Some(data)) + gpui::build_action(&name, Some(data)) } - Value::String(name) => cx.build_action(&name, None), + Value::String(name) => gpui::build_action(&name, None), Value::Null => Ok(no_action()), _ => { return Some(Err(anyhow!("Expected two-element array, got {action:?}"))) diff --git a/crates/storybook2/src/stories/focus.rs b/crates/storybook2/src/stories/focus.rs index cae68c21e8..4302a2cc50 100644 --- a/crates/storybook2/src/stories/focus.rs +++ b/crates/storybook2/src/stories/focus.rs @@ -15,8 +15,6 @@ impl FocusStory { KeyBinding::new("cmd-a", ActionB, Some("child-1")), KeyBinding::new("cmd-c", ActionC, None), ]); - cx.register_action_type::(); - cx.register_action_type::(); cx.build_view(move |cx| Self {}) } diff --git a/crates/storybook2/src/story_selector.rs b/crates/storybook2/src/story_selector.rs index 766c0dd51a..040bd75189 100644 --- a/crates/storybook2/src/story_selector.rs +++ b/crates/storybook2/src/story_selector.rs @@ -38,6 +38,7 @@ pub enum ComponentStory { Palette, Panel, ProjectPanel, + Players, RecentProjects, Scroll, Tab, @@ -80,6 +81,7 @@ impl ComponentStory { Self::MultiBuffer => cx.build_view(|_| ui::MultiBufferStory).into(), Self::NotificationsPanel => cx.build_view(|cx| ui::NotificationsPanelStory).into(), Self::Palette => cx.build_view(|cx| ui::PaletteStory).into(), + Self::Players => cx.build_view(|_| theme2::PlayerStory).into(), Self::Panel => cx.build_view(|cx| ui::PanelStory).into(), Self::ProjectPanel => cx.build_view(|_| ui::ProjectPanelStory).into(), Self::RecentProjects => cx.build_view(|_| ui::RecentProjectsStory).into(), diff --git a/crates/theme2/Cargo.toml b/crates/theme2/Cargo.toml index d57c22ede7..45ba4587ba 100644 --- a/crates/theme2/Cargo.toml +++ b/crates/theme2/Cargo.toml @@ -5,6 +5,8 @@ edition = "2021" publish = false [features] +default = ["stories"] +stories = ["dep:itertools"] test-support = [ "gpui/test-support", "fs/test-support", @@ -30,6 +32,7 @@ settings = { package = "settings2", path = "../settings2" } toml.workspace = true uuid.workspace = true util = { path = "../util" } +itertools = { version = "0.11.0", optional = true } [dev-dependencies] gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } diff --git a/crates/theme2/src/colors.rs b/crates/theme2/src/colors.rs index 8ee4b5fd47..8e3db63537 100644 --- a/crates/theme2/src/colors.rs +++ b/crates/theme2/src/colors.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use gpui::Hsla; use refineable::Refineable; -use crate::SyntaxTheme; +use crate::{PlayerColors, SyntaxTheme}; #[derive(Clone)] pub struct SystemColors { @@ -13,33 +13,6 @@ pub struct SystemColors { pub mac_os_traffic_light_green: Hsla, } -#[derive(Debug, Clone, Copy)] -pub struct PlayerColor { - pub cursor: Hsla, - pub background: Hsla, - pub selection: Hsla, -} - -#[derive(Clone)] -pub struct PlayerColors(pub Vec); - -impl PlayerColors { - pub fn local(&self) -> PlayerColor { - // todo!("use a valid color"); - *self.0.first().unwrap() - } - - pub fn absent(&self) -> PlayerColor { - // todo!("use a valid color"); - *self.0.last().unwrap() - } - - pub fn color_for_participant(&self, participant_index: u32) -> PlayerColor { - let len = self.0.len() - 1; - self.0[(participant_index as usize % len) + 1] - } -} - #[derive(Refineable, Clone, Debug)] #[refineable(debug)] pub struct StatusColors { diff --git a/crates/theme2/src/default_colors.rs b/crates/theme2/src/default_colors.rs index 7252e82972..3a626205f9 100644 --- a/crates/theme2/src/default_colors.rs +++ b/crates/theme2/src/default_colors.rs @@ -3,12 +3,106 @@ use std::num::ParseIntError; use gpui::{hsla, Hsla, Rgba}; use crate::{ - colors::{GitStatusColors, PlayerColor, PlayerColors, StatusColors, SystemColors, ThemeColors}, + colors::{GitStatusColors, StatusColors, SystemColors, ThemeColors}, scale::{ColorScaleSet, ColorScales}, syntax::SyntaxTheme, - ColorScale, + ColorScale, PlayerColor, PlayerColors, }; +impl Default for PlayerColors { + fn default() -> Self { + Self(vec![ + PlayerColor { + cursor: blue().dark().step_9(), + background: blue().dark().step_5(), + selection: blue().dark().step_3(), + }, + PlayerColor { + cursor: orange().dark().step_9(), + background: orange().dark().step_5(), + selection: orange().dark().step_3(), + }, + PlayerColor { + cursor: pink().dark().step_9(), + background: pink().dark().step_5(), + selection: pink().dark().step_3(), + }, + PlayerColor { + cursor: lime().dark().step_9(), + background: lime().dark().step_5(), + selection: lime().dark().step_3(), + }, + PlayerColor { + cursor: purple().dark().step_9(), + background: purple().dark().step_5(), + selection: purple().dark().step_3(), + }, + PlayerColor { + cursor: amber().dark().step_9(), + background: amber().dark().step_5(), + selection: amber().dark().step_3(), + }, + PlayerColor { + cursor: jade().dark().step_9(), + background: jade().dark().step_5(), + selection: jade().dark().step_3(), + }, + PlayerColor { + cursor: red().dark().step_9(), + background: red().dark().step_5(), + selection: red().dark().step_3(), + }, + ]) + } +} + +impl PlayerColors { + pub fn default_light() -> Self { + Self(vec![ + PlayerColor { + cursor: blue().light().step_9(), + background: blue().light().step_4(), + selection: blue().light().step_3(), + }, + PlayerColor { + cursor: orange().light().step_9(), + background: orange().light().step_4(), + selection: orange().light().step_3(), + }, + PlayerColor { + cursor: pink().light().step_9(), + background: pink().light().step_4(), + selection: pink().light().step_3(), + }, + PlayerColor { + cursor: lime().light().step_9(), + background: lime().light().step_4(), + selection: lime().light().step_3(), + }, + PlayerColor { + cursor: purple().light().step_9(), + background: purple().light().step_4(), + selection: purple().light().step_3(), + }, + PlayerColor { + cursor: amber().light().step_9(), + background: amber().light().step_4(), + selection: amber().light().step_3(), + }, + PlayerColor { + cursor: jade().light().step_9(), + background: jade().light().step_4(), + selection: jade().light().step_3(), + }, + PlayerColor { + cursor: red().light().step_9(), + background: red().light().step_4(), + selection: red().light().step_3(), + }, + ]) + } +} + fn neutral() -> ColorScaleSet { slate() } @@ -27,17 +121,17 @@ impl Default for SystemColors { impl Default for StatusColors { fn default() -> Self { Self { - conflict: red().dark().step_11(), - created: grass().dark().step_11(), - deleted: red().dark().step_11(), - error: red().dark().step_11(), - hidden: neutral().dark().step_11(), - ignored: neutral().dark().step_11(), - info: blue().dark().step_11(), - modified: yellow().dark().step_11(), - renamed: blue().dark().step_11(), - success: grass().dark().step_11(), - warning: yellow().dark().step_11(), + conflict: red().dark().step_9(), + created: grass().dark().step_9(), + deleted: red().dark().step_9(), + error: red().dark().step_9(), + hidden: neutral().dark().step_9(), + ignored: neutral().dark().step_9(), + info: blue().dark().step_9(), + modified: yellow().dark().step_9(), + renamed: blue().dark().step_9(), + success: grass().dark().step_9(), + warning: yellow().dark().step_9(), } } } @@ -45,43 +139,16 @@ impl Default for StatusColors { impl Default for GitStatusColors { fn default() -> Self { Self { - conflict: orange().dark().step_11(), - created: grass().dark().step_11(), - deleted: red().dark().step_11(), - ignored: neutral().dark().step_11(), - modified: yellow().dark().step_11(), - renamed: blue().dark().step_11(), + conflict: orange().dark().step_9(), + created: grass().dark().step_9(), + deleted: red().dark().step_9(), + ignored: neutral().dark().step_9(), + modified: yellow().dark().step_9(), + renamed: blue().dark().step_9(), } } } -impl Default for PlayerColors { - fn default() -> Self { - Self(vec![ - PlayerColor { - cursor: hsla(0.0, 0.0, 0.0, 1.0), - background: hsla(0.0, 0.0, 0.0, 1.0), - selection: hsla(0.0, 0.0, 0.0, 1.0), - }, - PlayerColor { - cursor: hsla(0.0, 0.0, 0.0, 1.0), - background: hsla(0.0, 0.0, 0.0, 1.0), - selection: hsla(0.0, 0.0, 0.0, 1.0), - }, - PlayerColor { - cursor: hsla(0.0, 0.0, 0.0, 1.0), - background: hsla(0.0, 0.0, 0.0, 1.0), - selection: hsla(0.0, 0.0, 0.0, 1.0), - }, - PlayerColor { - cursor: hsla(0.0, 0.0, 0.0, 1.0), - background: hsla(0.0, 0.0, 0.0, 1.0), - selection: hsla(0.0, 0.0, 0.0, 1.0), - }, - ]) - } -} - impl SyntaxTheme { pub fn default_light() -> Self { Self { diff --git a/crates/theme2/src/default_theme.rs b/crates/theme2/src/default_theme.rs index 3c9634c989..a0947e47c3 100644 --- a/crates/theme2/src/default_theme.rs +++ b/crates/theme2/src/default_theme.rs @@ -1,8 +1,8 @@ use std::sync::Arc; use crate::{ - colors::{GitStatusColors, PlayerColors, StatusColors, SystemColors, ThemeColors, ThemeStyles}, - default_color_scales, Appearance, SyntaxTheme, Theme, ThemeFamily, + colors::{GitStatusColors, StatusColors, SystemColors, ThemeColors, ThemeStyles}, + default_color_scales, Appearance, PlayerColors, SyntaxTheme, Theme, ThemeFamily, }; fn zed_pro_daylight() -> Theme { @@ -15,7 +15,7 @@ fn zed_pro_daylight() -> Theme { colors: ThemeColors::default_light(), status: StatusColors::default(), git: GitStatusColors::default(), - player: PlayerColors::default(), + player: PlayerColors::default_light(), syntax: Arc::new(SyntaxTheme::default_light()), }, } diff --git a/crates/theme2/src/players.rs b/crates/theme2/src/players.rs new file mode 100644 index 0000000000..0e36ff5947 --- /dev/null +++ b/crates/theme2/src/players.rs @@ -0,0 +1,170 @@ +use gpui::Hsla; + +#[derive(Debug, Clone, Copy)] +pub struct PlayerColor { + pub cursor: Hsla, + pub background: Hsla, + pub selection: Hsla, +} + +/// A collection of colors that are used to color players in the editor. +/// +/// The first color is always the local player's color, usually a blue. +/// +/// The rest of the default colors crisscross back and forth on the +/// color wheel so that the colors are as distinct as possible. +#[derive(Clone)] +pub struct PlayerColors(pub Vec); + +impl PlayerColors { + pub fn local(&self) -> PlayerColor { + // todo!("use a valid color"); + *self.0.first().unwrap() + } + + pub fn absent(&self) -> PlayerColor { + // todo!("use a valid color"); + *self.0.last().unwrap() + } + + pub fn color_for_participant(&self, participant_index: u32) -> PlayerColor { + let len = self.0.len() - 1; + self.0[(participant_index as usize % len) + 1] + } +} + +#[cfg(feature = "stories")] +pub use stories::*; + +#[cfg(feature = "stories")] +mod stories { + use super::*; + use crate::{ActiveTheme, Story}; + use gpui::{div, img, px, Div, ParentElement, Render, Styled, ViewContext}; + + pub struct PlayerStory; + + impl Render for PlayerStory { + type Element = Div; + + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + Story::container(cx).child( + div() + .flex() + .flex_col() + .gap_4() + .child(Story::title_for::<_, PlayerColors>(cx)) + .child(Story::label(cx, "Player Colors")) + .child( + div() + .flex() + .flex_col() + .gap_1() + .child( + div().flex().gap_1().children( + cx.theme().players().0.clone().iter_mut().map(|player| { + div().w_8().h_8().rounded_md().bg(player.cursor) + }), + ), + ) + .child(div().flex().gap_1().children( + cx.theme().players().0.clone().iter_mut().map(|player| { + div().w_8().h_8().rounded_md().bg(player.background) + }), + )) + .child(div().flex().gap_1().children( + cx.theme().players().0.clone().iter_mut().map(|player| { + div().w_8().h_8().rounded_md().bg(player.selection) + }), + )), + ) + .child(Story::label(cx, "Avatar Rings")) + .child(div().flex().gap_1().children( + cx.theme().players().0.clone().iter_mut().map(|player| { + div() + .my_1() + .rounded_full() + .border_2() + .border_color(player.cursor) + .child( + img() + .rounded_full() + .uri("https://avatars.githubusercontent.com/u/1714999?v=4") + .size_6() + .bg(gpui::red()), + ) + }), + )) + .child(Story::label(cx, "Player Backgrounds")) + .child(div().flex().gap_1().children( + cx.theme().players().0.clone().iter_mut().map(|player| { + div() + .my_1() + .rounded_xl() + .flex() + .items_center() + .h_8() + .py_0p5() + .px_1p5() + .bg(player.background) + .child( + div().relative().neg_mx_1().rounded_full().z_index(3) + .border_2() + .border_color(player.background) + .size(px(28.)) + .child( + img() + .rounded_full() + .uri("https://avatars.githubusercontent.com/u/1714999?v=4") + .size(px(24.)) + .bg(gpui::red()), + ), + ).child( + div().relative().neg_mx_1().rounded_full().z_index(2) + .border_2() + .border_color(player.background) + .size(px(28.)) + .child( + img() + .rounded_full() + .uri("https://avatars.githubusercontent.com/u/1714999?v=4") + .size(px(24.)) + .bg(gpui::red()), + ), + ).child( + div().relative().neg_mx_1().rounded_full().z_index(1) + .border_2() + .border_color(player.background) + .size(px(28.)) + .child( + img() + .rounded_full() + .uri("https://avatars.githubusercontent.com/u/1714999?v=4") + .size(px(24.)) + .bg(gpui::red()), + ), + ) + }), + )) + .child(Story::label(cx, "Player Selections")) + .child(div().flex().flex_col().gap_px().children( + cx.theme().players().0.clone().iter_mut().map(|player| { + div() + .flex() + .child( + div() + .flex() + .flex_none() + .rounded_sm() + .px_0p5() + .text_color(cx.theme().colors().text) + .bg(player.selection) + .child("The brown fox jumped over the lazy dog."), + ) + .child(div().flex_1()) + }), + )), + ) + } + } +} diff --git a/crates/theme2/src/story.rs b/crates/theme2/src/story.rs new file mode 100644 index 0000000000..8b3754b59e --- /dev/null +++ b/crates/theme2/src/story.rs @@ -0,0 +1,38 @@ +use gpui::{div, Component, Div, ParentElement, Styled, ViewContext}; + +use crate::ActiveTheme; + +pub struct Story {} + +impl Story { + pub fn container(cx: &mut ViewContext) -> Div { + div() + .size_full() + .flex() + .flex_col() + .pt_2() + .px_4() + .font("Zed Mono") + .bg(cx.theme().colors().background) + } + + pub fn title(cx: &mut ViewContext, title: &str) -> impl Component { + div() + .text_xl() + .text_color(cx.theme().colors().text) + .child(title.to_owned()) + } + + pub fn title_for(cx: &mut ViewContext) -> impl Component { + Self::title(cx, std::any::type_name::()) + } + + pub fn label(cx: &mut ViewContext, label: &str) -> impl Component { + div() + .mt_4() + .mb_2() + .text_xs() + .text_color(cx.theme().colors().text) + .child(label.to_owned()) + } +} diff --git a/crates/theme2/src/theme2.rs b/crates/theme2/src/theme2.rs index 88db3c55f4..9019eba07a 100644 --- a/crates/theme2/src/theme2.rs +++ b/crates/theme2/src/theme2.rs @@ -1,6 +1,7 @@ mod colors; mod default_colors; mod default_theme; +mod players; mod registry; mod scale; mod settings; @@ -14,6 +15,7 @@ use ::settings::Settings; pub use colors::*; pub use default_colors::*; pub use default_theme::*; +pub use players::*; pub use registry::*; pub use scale::*; pub use settings::*; @@ -120,3 +122,8 @@ pub struct DiagnosticStyle { pub hint: Hsla, pub ignored: Hsla, } + +#[cfg(feature = "stories")] +mod story; +#[cfg(feature = "stories")] +pub use story::*; diff --git a/crates/zed2/src/languages/json.rs b/crates/zed2/src/languages/json.rs index 63f909ae2a..cf9b33d968 100644 --- a/crates/zed2/src/languages/json.rs +++ b/crates/zed2/src/languages/json.rs @@ -107,7 +107,7 @@ impl LspAdapter for JsonLspAdapter { &self, cx: &mut AppContext, ) -> BoxFuture<'static, serde_json::Value> { - let action_names = cx.all_action_names().collect::>(); + let action_names = gpui::all_action_names(); let staff_mode = cx.is_staff(); let language_names = &self.languages.language_names(); let settings_schema = cx.global::().json_schema(