diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eb9b6d1f7cb25cb689d624bb361529cde4251f79..60dc4c1f528a34ae2e74fc237650ab7a21516fb8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: uses: actions/checkout@v3 with: clean: false - submodules: 'recursive' + submodules: "recursive" - name: cargo fmt run: cargo fmt --all -- --check @@ -56,13 +56,13 @@ jobs: - name: Install Node uses: actions/setup-node@v3 with: - node-version: '18' + node-version: "18" - name: Checkout repo uses: actions/checkout@v3 with: clean: false - submodules: 'recursive' + submodules: "recursive" - name: Limit target directory size run: script/clear-target-dir-if-larger-than 70 @@ -79,9 +79,6 @@ jobs: - name: Build other binaries run: cargo build --workspace --bins --all-features - - name: Generate license file - run: script/generate-licenses - bundle: name: Bundle app runs-on: @@ -106,13 +103,13 @@ jobs: - name: Install Node uses: actions/setup-node@v3 with: - node-version: '18' + node-version: "18" - name: Checkout repo uses: actions/checkout@v3 with: clean: false - submodules: 'recursive' + submodules: "recursive" - name: Limit target directory size run: script/clear-target-dir-if-larger-than 70 diff --git a/Cargo.lock b/Cargo.lock index 7d17b48cc789efac41ef9cdbf34fbba2d76582e4..fcbf5a0f958537cd5f1318474e913c8c14ac8800 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4988,6 +4988,7 @@ name = "menu2" version = "0.1.0" dependencies = [ "gpui2", + "serde", ] [[package]] @@ -6023,6 +6024,23 @@ dependencies = [ "workspace", ] +[[package]] +name = "picker2" +version = "0.1.0" +dependencies = [ + "ctor", + "editor2", + "env_logger 0.9.3", + "gpui2", + "menu2", + "parking_lot 0.11.2", + "serde_json", + "settings2", + "theme2", + "util", + "workspace2", +] + [[package]] name = "pico-args" version = "0.4.2" @@ -8539,9 +8557,14 @@ dependencies = [ "backtrace-on-stack-overflow", "chrono", "clap 4.4.4", + "editor2", + "fuzzy2", "gpui2", "itertools 0.11.0", + "language2", "log", + "menu2", + "picker2", "rust-embed", "serde", "settings2", @@ -9005,6 +9028,7 @@ dependencies = [ "fs2", "gpui2", "indexmap 1.9.3", + "itertools 0.11.0", "parking_lot 0.11.2", "refineable", "schemars", @@ -11077,7 +11101,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.112.0" +version = "0.113.0" dependencies = [ "activity_indicator", "ai", diff --git a/Cargo.toml b/Cargo.toml index 01d6c50643642cd49e702b3c7b5237323e31b37f..81aff80c90fa51386b44f71a7a5babcef32cc993 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,6 +68,7 @@ members = [ "crates/notifications", "crates/outline", "crates/picker", + "crates/picker2", "crates/plugin", "crates/plugin_macros", "crates/plugin_runtime", diff --git a/crates/client2/src/client2.rs b/crates/client2/src/client2.rs index 4a1c5f321c375a097a89252830bd17c572d0c648..93ec7f329bfab51f11d3689904cbc4d5edb62071 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 ac52bf156d66fe52c1e1625b56195555a4db5a21..9e82823b9bcaee3ebeee7edf9c03a3d0a9486fb5 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 10831995ff303d72d6687926d4b6b2a485721b72..4361ed53d0948b738cfd2739c268ebd40c21965e 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, hsla, px, relative, rems, AnyElement, AppContext, BackgroundExecutor, Context, - DispatchContext, Div, Element, Entity, EventEmitter, FocusHandle, FontFeatures, FontStyle, - FontWeight, Hsla, Model, Pixels, Render, Styled, Subscription, Task, TextStyle, View, - ViewContext, VisualContext, WeakView, WindowContext, + action, actions, div, px, relative, rems, AnyElement, AppContext, BackgroundExecutor, + ClipboardItem, Context, DispatchContext, Div, Element, Entity, EventEmitter, FocusHandle, + FontFeatures, 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,12 +54,13 @@ 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}; use movement::TextLayoutDetails; +use multi_buffer::ToOffsetUtf16; pub use multi_buffer::{ Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint, @@ -63,26 +68,30 @@ 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, }; -use selections_collection::{MutableSelectionsCollection, SelectionsCollection}; +use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection}; 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}, }; pub use sum_tree::Bias; use sum_tree::TreeMap; -use text::Rope; +use text::{OffsetUtf16, Rope}; use theme::{ ActiveTheme, DiagnosticStyle, PlayerColor, SyntaxTheme, Theme, ThemeColors, ThemeSettings, }; @@ -164,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), @@ -261,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, - Newline, - NewlineAbove, - NewlineBelow, - GoToDiagnostic, - GoToPrevDiagnostic, - GoToHunk, - GoToPrevHunk, - Indent, - Outdent, DeleteLine, - DeleteToPreviousWordStart, - DeleteToPreviousSubwordStart, - DeleteToNextWordEnd, - DeleteToNextSubwordEnd, DeleteToBeginningOfLine, DeleteToEndOfLine, - CutToEndOfLine, + DeleteToNextSubwordEnd, + DeleteToNextWordEnd, + DeleteToPreviousSubwordStart, + DeleteToPreviousWordStart, DuplicateLine, - MoveLineUp, - MoveLineDown, + FindAllReferences, + Fold, + FoldSelectedRanges, + Format, + GoToDefinition, + GoToDefinitionSplit, + GoToDiagnostic, + GoToHunk, + GoToPrevDiagnostic, + GoToPrevHunk, + GoToTypeDefinition, + GoToTypeDefinitionSplit, + HalfPageDown, + HalfPageUp, + Hover, + Indent, JoinLines, - SortLinesCaseSensitive, - SortLinesCaseInsensitive, - ReverseLines, - ShuffleLines, - ConvertToUpperCase, - ConvertToLowerCase, - ConvertToTitleCase, - ConvertToSnakeCase, - ConvertToKebabCase, - ConvertToUpperCamelCase, - ConvertToLowerCamelCase, - Transpose, - Cut, - Copy, - Paste, - Undo, - Redo, - MoveUp, - PageUp, + LineDown, + LineUp, MoveDown, - PageDown, MoveLeft, + MoveLineDown, + MoveLineUp, MoveRight, - MoveToPreviousWordStart, - MoveToPreviousSubwordStart, - MoveToNextWordEnd, - MoveToNextSubwordEnd, + MoveToBeginning, MoveToBeginningOfLine, + MoveToEnclosingBracket, + MoveToEnd, MoveToEndOfLine, - MoveToStartOfParagraph, MoveToEndOfParagraph, - MoveToBeginning, - MoveToEnd, - SelectUp, + MoveToNextSubwordEnd, + MoveToNextWordEnd, + MoveToPreviousSubwordStart, + MoveToPreviousWordStart, + MoveToStartOfParagraph, + MoveUp, + Newline, + NewlineAbove, + NewlineBelow, + NextScreen, + OpenExcerpts, + Outdent, + PageDown, + 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, + ToggleInlayHints, + ToggleSoftWrap, + Transpose, + Undo, UndoSelection, - RedoSelection, - FindAllReferences, - Rename, - ConfirmRename, - Fold, UnfoldLines, - FoldSelectedRanges, - ShowCompletions, - OpenExcerpts, - RestartLanguageServer, - Hover, - Format, - ToggleSoftWrap, - ToggleInlayHints, - RevealInFinder, - CopyPath, - CopyRelativePath, - CopyHighlightJson, - ContextMenuFirst, - ContextMenuPrev, - ContextMenuNext, - ContextMenuLast, ); // impl_actions!( @@ -447,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); @@ -532,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); @@ -988,7 +998,7 @@ impl CompletionsMenu { project: Option>, cx: &mut ViewContext, ) { - todo!("implementation below "); + // todo!("implementation below "); } // ) { // let settings = EditorSettings::get_global(cx); @@ -1155,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, @@ -1199,7 +1209,7 @@ impl CompletionsMenu { completion: lsp::CompletionItem, language_registry: Arc, ) { - todo!() + // todo!() // let can_resolve = server // .capabilities() // .completion_provider @@ -2383,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); @@ -2734,490 +2744,490 @@ 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(); + pub fn handle_input(&mut self, text: &str, cx: &mut ViewContext) { + let text: Arc = text.into(); - // if self.read_only { - // return; - // } + if self.read_only { + return; + } - // let selections = self.selections.all_adjusted(cx); - // let mut brace_inserted = false; - // let mut edits = Vec::new(); - // let mut new_selections = Vec::with_capacity(selections.len()); - // let mut new_autoclose_regions = Vec::new(); - // let snapshot = self.buffer.read(cx).read(cx); + let selections = self.selections.all_adjusted(cx); + let mut brace_inserted = false; + let mut edits = Vec::new(); + let mut new_selections = Vec::with_capacity(selections.len()); + let mut new_autoclose_regions = Vec::new(); + let snapshot = self.buffer.read(cx).read(cx); - // for (selection, autoclose_region) in - // self.selections_with_autoclose_regions(selections, &snapshot) - // { - // if let Some(scope) = snapshot.language_scope_at(selection.head()) { - // // Determine if the inserted text matches the opening or closing - // // bracket of any of this language's bracket pairs. - // let mut bracket_pair = None; - // let mut is_bracket_pair_start = false; - // if !text.is_empty() { - // // `text` can be empty when an user is using IME (e.g. Chinese Wubi Simplified) - // // and they are removing the character that triggered IME popup. - // for (pair, enabled) in scope.brackets() { - // if enabled && pair.close && pair.start.ends_with(text.as_ref()) { - // bracket_pair = Some(pair.clone()); - // is_bracket_pair_start = true; - // break; - // } else if pair.end.as_str() == text.as_ref() { - // bracket_pair = Some(pair.clone()); - // break; - // } - // } - // } + for (selection, autoclose_region) in + self.selections_with_autoclose_regions(selections, &snapshot) + { + if let Some(scope) = snapshot.language_scope_at(selection.head()) { + // Determine if the inserted text matches the opening or closing + // bracket of any of this language's bracket pairs. + let mut bracket_pair = None; + let mut is_bracket_pair_start = false; + if !text.is_empty() { + // `text` can be empty when an user is using IME (e.g. Chinese Wubi Simplified) + // and they are removing the character that triggered IME popup. + for (pair, enabled) in scope.brackets() { + if enabled && pair.close && pair.start.ends_with(text.as_ref()) { + bracket_pair = Some(pair.clone()); + is_bracket_pair_start = true; + break; + } else if pair.end.as_str() == text.as_ref() { + bracket_pair = Some(pair.clone()); + break; + } + } + } - // if let Some(bracket_pair) = bracket_pair { - // if selection.is_empty() { - // if is_bracket_pair_start { - // let prefix_len = bracket_pair.start.len() - text.len(); - - // // If the inserted text is a suffix of an opening bracket and the - // // selection is preceded by the rest of the opening bracket, then - // // insert the closing bracket. - // let following_text_allows_autoclose = snapshot - // .chars_at(selection.start) - // .next() - // .map_or(true, |c| scope.should_autoclose_before(c)); - // let preceding_text_matches_prefix = prefix_len == 0 - // || (selection.start.column >= (prefix_len as u32) - // && snapshot.contains_str_at( - // Point::new( - // selection.start.row, - // selection.start.column - (prefix_len as u32), - // ), - // &bracket_pair.start[..prefix_len], - // )); - // if following_text_allows_autoclose && preceding_text_matches_prefix { - // let anchor = snapshot.anchor_before(selection.end); - // new_selections.push((selection.map(|_| anchor), text.len())); - // new_autoclose_regions.push(( - // anchor, - // text.len(), - // selection.id, - // bracket_pair.clone(), - // )); - // edits.push(( - // selection.range(), - // format!("{}{}", text, bracket_pair.end).into(), - // )); - // brace_inserted = true; - // continue; - // } - // } + if let Some(bracket_pair) = bracket_pair { + if selection.is_empty() { + if is_bracket_pair_start { + let prefix_len = bracket_pair.start.len() - text.len(); + + // If the inserted text is a suffix of an opening bracket and the + // selection is preceded by the rest of the opening bracket, then + // insert the closing bracket. + let following_text_allows_autoclose = snapshot + .chars_at(selection.start) + .next() + .map_or(true, |c| scope.should_autoclose_before(c)); + let preceding_text_matches_prefix = prefix_len == 0 + || (selection.start.column >= (prefix_len as u32) + && snapshot.contains_str_at( + Point::new( + selection.start.row, + selection.start.column - (prefix_len as u32), + ), + &bracket_pair.start[..prefix_len], + )); + if following_text_allows_autoclose && preceding_text_matches_prefix { + let anchor = snapshot.anchor_before(selection.end); + new_selections.push((selection.map(|_| anchor), text.len())); + new_autoclose_regions.push(( + anchor, + text.len(), + selection.id, + bracket_pair.clone(), + )); + edits.push(( + selection.range(), + format!("{}{}", text, bracket_pair.end).into(), + )); + brace_inserted = true; + continue; + } + } - // if let Some(region) = autoclose_region { - // // If the selection is followed by an auto-inserted closing bracket, - // // then don't insert that closing bracket again; just move the selection - // // past the closing bracket. - // let should_skip = selection.end == region.range.end.to_point(&snapshot) - // && text.as_ref() == region.pair.end.as_str(); - // if should_skip { - // let anchor = snapshot.anchor_after(selection.end); - // new_selections - // .push((selection.map(|_| anchor), region.pair.end.len())); - // continue; - // } - // } - // } - // // If an opening bracket is 1 character long and is typed while - // // text is selected, then surround that text with the bracket pair. - // else if is_bracket_pair_start && bracket_pair.start.chars().count() == 1 { - // edits.push((selection.start..selection.start, text.clone())); - // edits.push(( - // selection.end..selection.end, - // bracket_pair.end.as_str().into(), - // )); - // brace_inserted = true; - // new_selections.push(( - // Selection { - // id: selection.id, - // start: snapshot.anchor_after(selection.start), - // end: snapshot.anchor_before(selection.end), - // reversed: selection.reversed, - // goal: selection.goal, - // }, - // 0, - // )); - // continue; - // } - // } - // } + if let Some(region) = autoclose_region { + // If the selection is followed by an auto-inserted closing bracket, + // then don't insert that closing bracket again; just move the selection + // past the closing bracket. + let should_skip = selection.end == region.range.end.to_point(&snapshot) + && text.as_ref() == region.pair.end.as_str(); + if should_skip { + let anchor = snapshot.anchor_after(selection.end); + new_selections + .push((selection.map(|_| anchor), region.pair.end.len())); + continue; + } + } + } + // If an opening bracket is 1 character long and is typed while + // text is selected, then surround that text with the bracket pair. + else if is_bracket_pair_start && bracket_pair.start.chars().count() == 1 { + edits.push((selection.start..selection.start, text.clone())); + edits.push(( + selection.end..selection.end, + bracket_pair.end.as_str().into(), + )); + brace_inserted = true; + new_selections.push(( + Selection { + id: selection.id, + start: snapshot.anchor_after(selection.start), + end: snapshot.anchor_before(selection.end), + reversed: selection.reversed, + goal: selection.goal, + }, + 0, + )); + continue; + } + } + } - // // If not handling any auto-close operation, then just replace the selected - // // text with the given input and move the selection to the end of the - // // newly inserted text. - // let anchor = snapshot.anchor_after(selection.end); - // new_selections.push((selection.map(|_| anchor), 0)); - // edits.push((selection.start..selection.end, text.clone())); - // } + // If not handling any auto-close operation, then just replace the selected + // text with the given input and move the selection to the end of the + // newly inserted text. + let anchor = snapshot.anchor_after(selection.end); + new_selections.push((selection.map(|_| anchor), 0)); + edits.push((selection.start..selection.end, text.clone())); + } - // drop(snapshot); - // self.transact(cx, |this, cx| { - // this.buffer.update(cx, |buffer, cx| { - // buffer.edit(edits, this.autoindent_mode.clone(), cx); - // }); + drop(snapshot); + self.transact(cx, |this, cx| { + this.buffer.update(cx, |buffer, cx| { + buffer.edit(edits, this.autoindent_mode.clone(), cx); + }); - // let new_anchor_selections = new_selections.iter().map(|e| &e.0); - // let new_selection_deltas = new_selections.iter().map(|e| e.1); - // let snapshot = this.buffer.read(cx).read(cx); - // let new_selections = resolve_multiple::(new_anchor_selections, &snapshot) - // .zip(new_selection_deltas) - // .map(|(selection, delta)| Selection { - // id: selection.id, - // start: selection.start + delta, - // end: selection.end + delta, - // reversed: selection.reversed, - // goal: SelectionGoal::None, - // }) - // .collect::>(); - - // let mut i = 0; - // for (position, delta, selection_id, pair) in new_autoclose_regions { - // let position = position.to_offset(&snapshot) + delta; - // let start = snapshot.anchor_before(position); - // let end = snapshot.anchor_after(position); - // while let Some(existing_state) = this.autoclose_regions.get(i) { - // match existing_state.range.start.cmp(&start, &snapshot) { - // Ordering::Less => i += 1, - // Ordering::Greater => break, - // Ordering::Equal => match end.cmp(&existing_state.range.end, &snapshot) { - // Ordering::Less => i += 1, - // Ordering::Equal => break, - // Ordering::Greater => break, - // }, - // } - // } - // this.autoclose_regions.insert( - // i, - // AutocloseRegion { - // selection_id, - // range: start..end, - // pair, - // }, - // ); - // } + let new_anchor_selections = new_selections.iter().map(|e| &e.0); + let new_selection_deltas = new_selections.iter().map(|e| e.1); + let snapshot = this.buffer.read(cx).read(cx); + let new_selections = resolve_multiple::(new_anchor_selections, &snapshot) + .zip(new_selection_deltas) + .map(|(selection, delta)| Selection { + id: selection.id, + start: selection.start + delta, + end: selection.end + delta, + reversed: selection.reversed, + goal: SelectionGoal::None, + }) + .collect::>(); - // drop(snapshot); - // let had_active_copilot_suggestion = this.has_active_copilot_suggestion(cx); - // this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections)); + let mut i = 0; + for (position, delta, selection_id, pair) in new_autoclose_regions { + let position = position.to_offset(&snapshot) + delta; + let start = snapshot.anchor_before(position); + let end = snapshot.anchor_after(position); + while let Some(existing_state) = this.autoclose_regions.get(i) { + match existing_state.range.start.cmp(&start, &snapshot) { + Ordering::Less => i += 1, + Ordering::Greater => break, + Ordering::Equal => match end.cmp(&existing_state.range.end, &snapshot) { + Ordering::Less => i += 1, + Ordering::Equal => break, + Ordering::Greater => break, + }, + } + } + this.autoclose_regions.insert( + i, + AutocloseRegion { + selection_id, + range: start..end, + pair, + }, + ); + } - // if !brace_inserted && EditorSettings>(cx).use_on_type_format { - // if let Some(on_type_format_task) = - // this.trigger_on_type_formatting(text.to_string(), cx) - // { - // on_type_format_task.detach_and_log_err(cx); - // } - // } + drop(snapshot); + let had_active_copilot_suggestion = this.has_active_copilot_suggestion(cx); + this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections)); - // if had_active_copilot_suggestion { - // this.refresh_copilot_suggestions(true, cx); - // if !this.has_active_copilot_suggestion(cx) { - // this.trigger_completion_on_input(&text, cx); - // } - // } else { - // this.trigger_completion_on_input(&text, cx); - // this.refresh_copilot_suggestions(true, cx); - // } - // }); - // } + if !brace_inserted && EditorSettings::get_global(cx).use_on_type_format { + if let Some(on_type_format_task) = + this.trigger_on_type_formatting(text.to_string(), cx) + { + on_type_format_task.detach_and_log_err(cx); + } + } - // 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 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) - // }; + if had_active_copilot_suggestion { + this.refresh_copilot_suggestions(true, cx); + if !this.has_active_copilot_suggestion(cx) { + this.trigger_completion_on_input(&text, cx); + } + } else { + this.trigger_completion_on_input(&text, cx); + this.refresh_copilot_suggestions(true, cx); + } + }); + } - // 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); - // } + 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 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) + }; + + 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, @@ -3259,83 +3269,83 @@ impl Editor { }); } - // fn trigger_completion_on_input(&mut self, text: &str, cx: &mut ViewContext) { - // if !EditorSettings>(cx).show_completions_on_input { - // return; - // } + fn trigger_completion_on_input(&mut self, text: &str, cx: &mut ViewContext) { + if !EditorSettings::get_global(cx).show_completions_on_input { + return; + } - // let selection = self.selections.newest_anchor(); - // if self - // .buffer - // .read(cx) - // .is_completion_trigger(selection.head(), text, cx) - // { - // self.show_completions(&ShowCompletions, cx); - // } else { - // self.hide_context_menu(cx); - // } - // } + let selection = self.selections.newest_anchor(); + if self + .buffer + .read(cx) + .is_completion_trigger(selection.head(), text, cx) + { + self.show_completions(&ShowCompletions, cx); + } else { + self.hide_context_menu(cx); + } + } - // /// 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 - // /// regions to avoid repeated comparisons. - // fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>( - // &'a self, - // selections: impl IntoIterator>, - // buffer: &'a MultiBufferSnapshot, - // ) -> impl Iterator, Option<&'a AutocloseRegion>)> { - // let mut i = 0; - // let mut regions = self.autoclose_regions.as_slice(); - // selections.into_iter().map(move |selection| { - // let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer); - - // let mut enclosing = None; - // while let Some(pair_state) = regions.get(i) { - // if pair_state.range.end.to_offset(buffer) < range.start { - // regions = ®ions[i + 1..]; - // i = 0; - // } else if pair_state.range.start.to_offset(buffer) > range.end { - // break; - // } else { - // if pair_state.selection_id == selection.id { - // enclosing = Some(pair_state); - // } - // i += 1; - // } - // } + /// Iterate the given selections, and for each one, find the smallest surrounding + /// autoclose region. This uses the ordering of the selections and the autoclose + /// regions to avoid repeated comparisons. + fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>( + &'a self, + selections: impl IntoIterator>, + buffer: &'a MultiBufferSnapshot, + ) -> impl Iterator, Option<&'a AutocloseRegion>)> { + let mut i = 0; + let mut regions = self.autoclose_regions.as_slice(); + selections.into_iter().map(move |selection| { + let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer); + + let mut enclosing = None; + while let Some(pair_state) = regions.get(i) { + if pair_state.range.end.to_offset(buffer) < range.start { + regions = ®ions[i + 1..]; + i = 0; + } else if pair_state.range.start.to_offset(buffer) > range.end { + break; + } else { + if pair_state.selection_id == selection.id { + enclosing = Some(pair_state); + } + i += 1; + } + } - // (selection.clone(), enclosing) - // }) - // } + (selection.clone(), enclosing) + }) + } /// Remove any autoclose regions that no longer contain their selection. fn invalidate_autoclose_regions( @@ -3377,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 { @@ -3537,51 +3545,51 @@ impl Editor { cx.notify(); } - // fn trigger_on_type_formatting( - // &self, - // input: String, - // cx: &mut ViewContext, - // ) -> Option>> { - // if input.len() != 1 { - // return None; - // } + fn trigger_on_type_formatting( + &self, + input: String, + cx: &mut ViewContext, + ) -> Option>> { + if input.len() != 1 { + return None; + } - // let project = self.project.as_ref()?; - // let position = self.selections.newest_anchor().head(); - // let (buffer, buffer_position) = self - // .buffer - // .read(cx) - // .text_anchor_for_position(position.clone(), cx)?; - - // // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances, - // // hence we do LSP request & edit on host side only — add formats to host's history. - // let push_to_lsp_host_history = true; - // // If this is not the host, append its history with new edits. - // let push_to_client_history = project.read(cx).is_remote(); - - // let on_type_formatting = project.update(cx, |project, cx| { - // project.on_type_format( - // buffer.clone(), - // buffer_position, - // input, - // push_to_lsp_host_history, - // cx, - // ) - // }); - // Some(cx.spawn(|editor, mut cx| async move { - // if let Some(transaction) = on_type_formatting.await? { - // if push_to_client_history { - // buffer.update(&mut cx, |buffer, _| { - // buffer.push_transaction(transaction, Instant::now()); - // }); - // } - // editor.update(&mut cx, |editor, cx| { - // editor.refresh_document_highlights(cx); - // })?; - // } - // Ok(()) - // })) - // } + let project = self.project.as_ref()?; + let position = self.selections.newest_anchor().head(); + let (buffer, buffer_position) = self + .buffer + .read(cx) + .text_anchor_for_position(position.clone(), cx)?; + + // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances, + // hence we do LSP request & edit on host side only — add formats to host's history. + let push_to_lsp_host_history = true; + // If this is not the host, append its history with new edits. + let push_to_client_history = project.read(cx).is_remote(); + + let on_type_formatting = project.update(cx, |project, cx| { + project.on_type_format( + buffer.clone(), + buffer_position, + input, + push_to_lsp_host_history, + cx, + ) + }); + Some(cx.spawn(|editor, mut cx| async move { + if let Some(transaction) = on_type_formatting.await? { + if push_to_client_history { + buffer.update(&mut cx, |buffer, _| { + buffer.push_transaction(transaction, Instant::now()); + }); + } + editor.update(&mut cx, |editor, cx| { + editor.refresh_document_highlights(cx); + })?; + } + Ok(()) + })) + } fn show_completions(&mut self, _: &ShowCompletions, cx: &mut ViewContext) { if self.pending_rename.is_some() { @@ -4225,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(); } } } @@ -4241,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(); } } } @@ -4499,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, - // ); + 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::>() - // }); + 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, - // }); - // } + 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(()) - // } + 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_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_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; - // } - // } + 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 - // } + false + } - // pub fn clear(&mut self, cx: &mut ViewContext) { - // self.transact(cx, |this, cx| { - // this.select_all(&SelectAll, cx); - // this.insert("", cx); - // }); - // } + 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, - // ), - // ); - // } - // } + 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); - // } - // } - // } + 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); - // }); - // } + 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 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; - // } + pub fn tab_prev(&mut self, _: &TabPrev, cx: &mut ViewContext) { + if self.move_to_prev_snippet_tabstop(cx) { + return; + } - // self.outdent(&Outdent, cx); - // } + self.outdent(&Outdent, cx); + } - // pub fn tab(&mut self, _: &Tab, cx: &mut ViewContext) { - // if self.move_to_next_snippet_tabstop(cx) { - // return; - // } + 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; + 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 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; - // } - // } + // 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; - // } + // 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; - // } + // 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); - // }); - // } + 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; + 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); - // } + 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)); - // }); - // } + 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; + 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; - // } + // 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; - // } - // } + // 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 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::(), - // )); + 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; - // } - // } + // 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 - // } - // } + 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; - // } - // } + 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); - // } - // } - // } - // } + 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)); - // }); - // } + 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; - // } - // } + 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 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())); + 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); - // } + 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(); + 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); - // }); - // }); - // } + 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 - // }; + 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); - // } + 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); - // } + 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 { - // "" - // }; + 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.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) - // }); - // }); - // } + 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_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 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 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())) - // } + 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, - // ); + 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 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" - // ); + let mut edits = Vec::new(); - // 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, - // }); - // } + 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(); - // self.transact(cx, |this, cx| { - // this.buffer.update(cx, |buffer, cx| { - // buffer.edit(edits, None, cx); - // }); + while let Some(selection) = selections.next() { + let (start_row, end_row) = consume_contiguous_rows( + &mut contiguous_row_selections, + selection, + &display_map, + &mut selections, + ); - // this.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.select(new_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" + ); - // this.request_autoscroll(Autoscroll::fit(), cx); - // }); - // } + 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, + }); + } - // pub fn convert_to_upper_case(&mut self, _: &ConvertToUpperCase, cx: &mut ViewContext) { - // self.manipulate_text(cx, |text| text.to_uppercase()) - // } + self.transact(cx, |this, cx| { + this.buffer.update(cx, |buffer, cx| { + buffer.edit(edits, None, cx); + }); - // pub fn convert_to_lower_case(&mut self, _: &ConvertToLowerCase, cx: &mut ViewContext) { - // self.manipulate_text(cx, |text| text.to_lowercase()) - // } + this.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select(new_selections); + }); - // 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") - // }) - // } + this.request_autoscroll(Autoscroll::fit(), cx); + }); + } - // 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_upper_case(&mut self, _: &ConvertToUpperCase, cx: &mut ViewContext) { + self.manipulate_text(cx, |text| text.to_uppercase()) + } - // 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_lower_case(&mut self, _: &ConvertToLowerCase, cx: &mut ViewContext) { + self.manipulate_text(cx, |text| text.to_lowercase()) + } - // 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_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_lower_camel_case( - // &mut self, - // _: &ConvertToLowerCamelCase, - // cx: &mut ViewContext, - // ) { - // self.manipulate_text(cx, |text| text.to_case(Case::Camel)) - // } + pub fn convert_to_snake_case(&mut self, _: &ConvertToSnakeCase, cx: &mut ViewContext) { + self.manipulate_text(cx, |text| text.to_case(Case::Snake)) + } - // 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); + pub fn convert_to_kebab_case(&mut self, _: &ConvertToKebabCase, cx: &mut ViewContext) { + self.manipulate_text(cx, |text| text.to_case(Case::Kebab)) + } - // let mut new_selections = Vec::new(); - // let mut edits = Vec::new(); - // let mut selection_adjustment = 0i32; + 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") + }) + } - // for selection in self.selections.all::(cx) { - // let selection_is_empty = selection.is_empty(); + pub fn convert_to_lower_camel_case( + &mut self, + _: &ConvertToLowerCamelCase, + cx: &mut ViewContext, + ) { + self.manipulate_text(cx, |text| text.to_case(Case::Camel)) + } - // 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) - // }; + 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 text = buffer.text_for_range(start..end).collect::(); - // let old_length = text.len() as i32; - // let text = callback(&text); + let mut new_selections = Vec::new(); + let mut edits = Vec::new(); + let mut selection_adjustment = 0i32; - // 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 - // }); + for selection in self.selections.all::(cx) { + let selection_is_empty = selection.is_empty(); - // selection_adjustment += old_length - text.len() as i32; + 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) + }; - // edits.push((start..end, text)); - // } + let text = buffer.text_for_range(start..end).collect::(); + let old_length = text.len() as i32; + let text = callback(&text); - // self.transact(cx, |this, cx| { - // this.buffer.update(cx, |buffer, cx| { - // buffer.edit(edits, None, cx); - // }); + 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 + }); - // this.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.select(new_selections); - // }); + selection_adjustment += old_length - text.len() as i32; - // this.request_autoscroll(Autoscroll::fit(), cx); - // }); - // } + edits.push((start..end, text)); + } - // 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; - // } - // } + self.transact(cx, |this, cx| { + this.buffer.update(cx, |buffer, cx| { + buffer.edit(edits, None, cx); + }); - // // 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)); - // } + this.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select(new_selections); + }); - // self.transact(cx, |this, cx| { - // this.buffer.update(cx, |buffer, cx| { - // buffer.edit(edits, None, cx); - // }); + this.request_autoscroll(Autoscroll::fit(), cx); + }); + } - // 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; + } + } - // 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, - // ); + // 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)); + } - // // 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); - // } - // } - // } + self.transact(cx, |this, cx| { + this.buffer.update(cx, |buffer, cx| { + buffer.edit(edits, None, cx); + }); - // // If we didn't move line(s), preserve the existing selections - // new_selections.append(&mut contiguous_row_selections); - // } + this.request_autoscroll(Autoscroll::fit(), cx); + }); + } - // 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_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, + ); - // 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 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); + } + } + } - // // 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); + } - // // 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); + }) + }); + } - // 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, + ); - // 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; - // } + // 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); + } + } + } - // 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 we didn't move line(s), preserve the existing selections + new_selections.append(&mut contiguous_row_selections); + } - // if transpose_offset == 0 { - // return; - // } + 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)); + }); + } - // *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 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; + } - // 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, - // }); - // } - // } + 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); + } - // 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)); - // }); - // } + if transpose_offset == 0 { + return; + } - // 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(); + *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); + }); + }); + } - // 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, - // }); - // } - // } + 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, + }); + } + } - // cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections)); - // } + 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 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(..); - // } + 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(); - // 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 - // } + 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, + }); + } + } - // // 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() - // }; + cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections)); + } - // edits.push((range, to_insert)); - // original_indent_columns.extend(original_indent_column); - // } - // drop(snapshot); + 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(..); + } - // buffer.edit( - // edits, - // Some(AutoindentMode::Block { - // original_indent_columns, - // }), - // cx, - // ); - // }); + 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 + } - // let selections = this.selections.all::(cx); - // this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections)); - // } else { - // this.insert(&clipboard_text, cx); - // } - // } - // }); - // } + // 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() + }; - // 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); - // } - // } + edits.push((range, to_insert)); + original_indent_columns.extend(original_indent_column); + } + drop(snapshot); - // 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); - // } - // } + buffer.edit( + edits, + Some(AutoindentMode::Block { + original_indent_columns, + }), + cx, + ); + }); - // pub fn finalize_last_transaction(&mut self, cx: &mut ViewContext) { - // self.buffer - // .update(cx, |buffer, cx| buffer.finalize_last_transaction(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| { @@ -5732,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| { @@ -5752,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() { @@ -5788,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); @@ -5867,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() { @@ -5952,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, @@ -6376,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 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 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 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 add_selection_above(&mut self, _: &AddSelectionAbove, cx: &mut ViewContext) { - // self.add_selection(true, cx); - // } + 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 add_selection_below(&mut self, _: &AddSelectionBelow, cx: &mut ViewContext) { - // self.add_selection(false, cx); - // } + 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); + }); + } - // 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); - // } - // } + pub fn add_selection_above(&mut self, _: &AddSelectionAbove, cx: &mut ViewContext) { + self.add_selection(true, cx); + } - // if above { - // stack.reverse(); - // } + pub fn add_selection_below(&mut self, _: &AddSelectionBelow, cx: &mut ViewContext) { + self.add_selection(false, cx); + } - // AddSelectionsState { above, stack } - // }); + 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); + } + } - // 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() - // }; + if above { + stack.reverse(); + } - // '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); + AddSelectionsState { above, stack } + }); - // start_x.min(end_x)..start_x.max(end_x) - // }; + 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() + }; - // while row != end_row { - // if above { - // row -= 1; - // } else { - // row += 1; - // } + '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) + }; - // 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); - // } + while row != end_row { + if above { + row -= 1; + } else { + row += 1; + } - // continue 'outer; - // } - // } - // } + 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); + } - // new_selections.push(selection); - // } - // } else { - // new_selections = selections; - // new_selections.retain(|s| s.id != last_added_selection); - // state.stack.pop(); - // } + continue 'outer; + } + } + } - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.select(new_selections); - // }); - // if state.stack.len() > 1 { - // self.add_selections_state = Some(state); - // } - // } + new_selections.push(selection); + } + } else { + new_selections = selections; + new_selections.retain(|s| s.id != last_added_selection); + state.stack.pop(); + } - // 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()); - // }); - // } + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select(new_selections); + }); + if state.stack.len() > 1 { + self.add_selections_state = Some(state); + } + } - // 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)), - // ); + 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()); + }); + } - // 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; - // } - // } - // } + 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)), + ); - // 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; - // } - // } + 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); - // 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(()) - // } + 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; + } + } + } - // 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)); + 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; + } + } - // loop { - // self.select_next_match_internal(&display_map, action.replace_newest, None, cx)?; + 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(()) + } - // if self - // .select_next_state - // .as_ref() - // .map(|selection_state| selection_state.done) - // .unwrap_or(true) - // { - // break; - // } - // } + 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)); - // Ok(()) - // } + loop { + self.select_next_match_internal(&display_map, action.replace_newest, None, cx)?; - // 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(()) - // } + if self + .select_next_state + .as_ref() + .map(|selection_state| selection_state.done) + .unwrap_or(true) + { + break; + } + } - // 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; - // } - // } + Ok(()) + } - // 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; - // } - // } + 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(()) + } - // 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 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; + } + } - // 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 - // } - // } + 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; + } + } - // 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(); + 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(()) + } - // let leading_space_len = if suffix_start_column > 0 - // && line_end_bytes.next() == Some(b' ') - // && comment_suffix_has_leading_space - // { - // 1 - // } else { - // 0 - // }; + 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 + } + } - // // 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 - // } - // } + 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 + }; - // // 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; - // }; + // 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 + } + } - // selection_edit_ranges.clear(); + // 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; + }; - // // 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); + selection_edit_ranges.clear(); - // if start_row > end_row { - // continue; - // } + // 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 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; - // } + if 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 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; + } - // 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(' '), - // ); + 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 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; - // } - // } + 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(' '), + ); - // drop(snapshot); - // this.buffer.update(cx, |buffer, cx| { - // buffer.edit(edits, None, cx); - // }); + 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; + } + } - // // 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.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)); + 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) - // }) - // }); - // } - // }); - // } + 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(); + 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; - // } - // } + 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::>(); + 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; - // } + 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 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; - // }; + 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; - // } + 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; - // } + // 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() - // } - // }, - // ); - // } + 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); - // } - // }) - // }); - // } + 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 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; + } - // 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_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) + } - // 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); - // 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; - // } - // } + // 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() - // }; + 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 - // } - // }); + 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; - // } - // } - // } - // } + 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_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 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(); + 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]); - // }); + 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 - // } - // } + true + } else { + false + } + } pub fn go_to_definition(&mut self, _: &GoToDefinition, cx: &mut ViewContext) { self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, cx); @@ -7725,139 +7732,138 @@ impl Editor { } } - // 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 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) - // }); + // 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 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); - // 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))); + // 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; // } - // editor.buffer.update(cx, |buffer, cx| { - // buffer.edit([(0..0, old_name.clone())], None, cx) - // }); - // editor.select_all(&SelectAll, cx); - // editor + // 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(); + // 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, - // }); - // })?; - // } + // 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, @@ -7946,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, @@ -7990,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() { @@ -8032,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); - - // 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(); - - // 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 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 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() + } + + 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 { @@ -8134,15 +8140,14 @@ impl Editor { } fn start_transaction_at(&mut self, now: Instant, cx: &mut ViewContext) { - todo!() - // self.end_selection(cx); - // if let Some(tx_id) = self - // .buffer - // .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx)) - // { - // self.selection_history - // .insert_transaction(tx_id, self.selections.disjoint_anchors()); - // } + self.end_selection(cx); + if let Some(tx_id) = self + .buffer + .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx)) + { + self.selection_history + .insert_transaction(tx_id, self.selections.disjoint_anchors()); + } } fn end_transaction_at( @@ -8150,118 +8155,117 @@ impl Editor { now: Instant, cx: &mut ViewContext, ) -> Option { - todo!() - // if let Some(tx_id) = self - // .buffer - // .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx)) - // { - // if let Some((_, end_selections)) = self.selection_history.transaction_mut(tx_id) { - // *end_selections = Some(self.selections.disjoint_anchors()); - // } else { - // error!("unexpectedly ended a transaction that wasn't started by this editor"); - // } - - // cx.emit(Event::Edited); - // Some(tx_id) - // } else { - // None - // } + if let Some(tx_id) = self + .buffer + .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx)) + { + if let Some((_, end_selections)) = self.selection_history.transaction_mut(tx_id) { + *end_selections = Some(self.selections.disjoint_anchors()); + } else { + log::error!("unexpectedly ended a transaction that wasn't started by this editor"); + } + + cx.emit(Event::Edited); + Some(tx_id) + } else { + None + } } - // 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, @@ -8300,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, @@ -8351,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![]; @@ -8429,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; @@ -8499,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, @@ -8613,111 +8615,111 @@ 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, - // ranges: Vec>, - // style: HighlightStyle, - // cx: &mut ViewContext, - // ) { - // self.display_map.update(cx, |map, _| { - // map.highlight_text(TypeId::of::(), ranges, style) - // }); - // cx.notify(); - // } + pub fn highlight_text( + &mut self, + ranges: Vec>, + style: HighlightStyle, + cx: &mut ViewContext, + ) { + self.display_map.update(cx, |map, _| { + map.highlight_text(TypeId::of::(), ranges, 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 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, - // cx: &'a AppContext, - // ) -> Option<(HighlightStyle, &'a [Range])> { - // self.display_map.read(cx).text_highlights(TypeId::of::()) - // } + pub fn text_highlights<'a, T: 'static>( + &'a self, + cx: &'a AppContext, + ) -> Option<(HighlightStyle, &'a [Range])> { + self.display_map.read(cx).text_highlights(TypeId::of::()) + } pub fn clear_highlights(&mut self, cx: &mut ViewContext) { let cleared = self @@ -8934,43 +8936,43 @@ impl Editor { // .detach_and_log_err(cx); // } - // fn marked_text_ranges(&self, cx: &AppContext) -> Option>> { - // let snapshot = self.buffer.read(cx).read(cx); - // let (_, ranges) = self.text_highlights::(cx)?; - // Some( - // ranges - // .iter() - // .map(move |range| { - // range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot) - // }) - // .collect(), - // ) - // } + fn marked_text_ranges(&self, cx: &AppContext) -> Option>> { + let snapshot = self.buffer.read(cx).read(cx); + let (_, ranges) = self.text_highlights::(cx)?; + Some( + ranges + .iter() + .map(move |range| { + range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot) + }) + .collect(), + ) + } - // fn selection_replacement_ranges( - // &self, - // range: Range, - // cx: &AppContext, - // ) -> Vec> { - // let selections = self.selections.all::(cx); - // let newest_selection = selections - // .iter() - // .max_by_key(|selection| selection.id) - // .unwrap(); - // let start_delta = range.start.0 as isize - newest_selection.start.0 as isize; - // let end_delta = range.end.0 as isize - newest_selection.end.0 as isize; - // let snapshot = self.buffer.read(cx).read(cx); - // selections - // .into_iter() - // .map(|mut selection| { - // selection.start.0 = - // (selection.start.0 as isize).saturating_add(start_delta) as usize; - // selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize; - // snapshot.clip_offset_utf16(selection.start, Bias::Left) - // ..snapshot.clip_offset_utf16(selection.end, Bias::Right) - // }) - // .collect() - // } + fn selection_replacement_ranges( + &self, + range: Range, + cx: &AppContext, + ) -> Vec> { + let selections = self.selections.all::(cx); + let newest_selection = selections + .iter() + .max_by_key(|selection| selection.id) + .unwrap(); + let start_delta = range.start.0 as isize - newest_selection.start.0 as isize; + let end_delta = range.end.0 as isize - newest_selection.end.0 as isize; + let snapshot = self.buffer.read(cx).read(cx); + selections + .into_iter() + .map(|mut selection| { + selection.start.0 = + (selection.start.0 as isize).saturating_add(start_delta) as usize; + selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize; + snapshot.clip_offset_utf16(selection.start, Bias::Left) + ..snapshot.clip_offset_utf16(selection.end, Bias::Right) + }) + .collect() + } fn report_copilot_event( &self, @@ -9055,138 +9057,142 @@ 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 chunks = snapshot.chunks(range, true); - // let mut lines = Vec::new(); - // let mut line: VecDeque = VecDeque::new(); - - // let theme = &theme::current(cx).editor.syntax; - - // 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; - // } - // } + 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()); - // if !merged_with_last_token { - // line.push_back(Chunk { - // text: text.into(), - // highlight, - // }); - // } + let chunks = snapshot.chunks(range, true); + let mut lines = Vec::new(); + let mut line: VecDeque = VecDeque::new(); - // 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(); - // } + let Some(style) = self.style.as_ref() else { + return; + }; - // lines.push(mem::take(&mut line)); - // } - // } - // } + 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; + } + } - // let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else { - // return; - // }; - // cx.write_to_clipboard(ClipboardItem::new(lines)); - // } + if !merged_with_last_token { + line.push_back(Chunk { + text: text.into(), + highlight, + }); + } - // pub fn inlay_hint_cache(&self) -> &InlayHintCache { - // &self.inlay_hint_cache - // } + 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(); + } - // 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); - // }); - // } + lines.push(mem::take(&mut line)); + } + } + } - // self.handle_input(text, cx); - // } + let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else { + return; + }; + cx.write_to_clipboard(ClipboardItem::new(lines)); + } - // 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 - // } + 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); + }); + } + + 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); + + 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 + } - fn focus(&self, cx: &mut WindowContext) { + pub fn focus(&self, cx: &mut WindowContext) { cx.focus(&self.focus_handle) } } @@ -9509,214 +9515,229 @@ impl Render for Editor { // false // } -// -// fn text_for_range(&self, range_utf16: Range, cx: &AppContext) -> Option { -// Some( -// self.buffer -// .read(cx) -// .read(cx) -// .text_for_range(OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end)) -// .collect(), -// ) -// } -// fn selected_text_range(&self, cx: &AppContext) -> Option> { -// // Prevent the IME menu from appearing when holding down an alphabetic key -// // while input is disabled. -// if !self.input_enabled { -// return None; -// } +impl InputHandler for Editor { + fn text_for_range( + &self, + range_utf16: Range, + cx: &mut ViewContext, + ) -> Option { + Some( + self.buffer + .read(cx) + .read(cx) + .text_for_range(OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end)) + .collect(), + ) + } + + fn selected_text_range(&self, cx: &mut ViewContext) -> Option> { + // Prevent the IME menu from appearing when holding down an alphabetic key + // while input is disabled. + if !self.input_enabled { + return None; + } -// let range = self.selections.newest::(cx).range(); -// Some(range.start.0..range.end.0) -// } + let range = self.selections.newest::(cx).range(); + Some(range.start.0..range.end.0) + } -// fn marked_text_range(&self, cx: &AppContext) -> Option> { -// let snapshot = self.buffer.read(cx).read(cx); -// let range = self.text_highlights::(cx)?.1.get(0)?; -// Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0) -// } + fn marked_text_range(&self, cx: &mut ViewContext) -> Option> { + let snapshot = self.buffer.read(cx).read(cx); + let range = self.text_highlights::(cx)?.1.get(0)?; + Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0) + } -// fn unmark_text(&mut self, cx: &mut ViewContext) { -// self.clear_highlights::(cx); -// self.ime_transaction.take(); -// } + fn unmark_text(&mut self, cx: &mut ViewContext) { + self.clear_highlights::(cx); + self.ime_transaction.take(); + } -// fn replace_text_in_range( -// &mut self, -// range_utf16: Option>, -// text: &str, -// cx: &mut ViewContext, -// ) { -// if !self.input_enabled { -// cx.emit(Event::InputIgnored { text: text.into() }); -// return; -// } + fn replace_text_in_range( + &mut self, + range_utf16: Option>, + text: &str, + cx: &mut ViewContext, + ) { + if !self.input_enabled { + cx.emit(Event::InputIgnored { text: text.into() }); + return; + } -// self.transact(cx, |this, cx| { -// let new_selected_ranges = if let Some(range_utf16) = range_utf16 { -// let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end); -// Some(this.selection_replacement_ranges(range_utf16, cx)) -// } else { -// this.marked_text_ranges(cx) -// }; + self.transact(cx, |this, cx| { + let new_selected_ranges = if let Some(range_utf16) = range_utf16 { + let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end); + Some(this.selection_replacement_ranges(range_utf16, cx)) + } else { + this.marked_text_ranges(cx) + }; -// let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| { -// let newest_selection_id = this.selections.newest_anchor().id; -// this.selections -// .all::(cx) -// .iter() -// .zip(ranges_to_replace.iter()) -// .find_map(|(selection, range)| { -// if selection.id == newest_selection_id { -// Some( -// (range.start.0 as isize - selection.head().0 as isize) -// ..(range.end.0 as isize - selection.head().0 as isize), -// ) -// } else { -// None -// } -// }) -// }); + let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| { + let newest_selection_id = this.selections.newest_anchor().id; + this.selections + .all::(cx) + .iter() + .zip(ranges_to_replace.iter()) + .find_map(|(selection, range)| { + if selection.id == newest_selection_id { + Some( + (range.start.0 as isize - selection.head().0 as isize) + ..(range.end.0 as isize - selection.head().0 as isize), + ) + } else { + None + } + }) + }); -// cx.emit(Event::InputHandled { -// utf16_range_to_replace: range_to_replace, -// text: text.into(), -// }); + cx.emit(Event::InputHandled { + utf16_range_to_replace: range_to_replace, + text: text.into(), + }); -// if let Some(new_selected_ranges) = new_selected_ranges { -// this.change_selections(None, cx, |selections| { -// selections.select_ranges(new_selected_ranges) -// }); -// } + if let Some(new_selected_ranges) = new_selected_ranges { + this.change_selections(None, cx, |selections| { + selections.select_ranges(new_selected_ranges) + }); + } -// this.handle_input(text, cx); -// }); + this.handle_input(text, cx); + }); -// if let Some(transaction) = self.ime_transaction { -// self.buffer.update(cx, |buffer, cx| { -// buffer.group_until_transaction(transaction, cx); -// }); -// } + if let Some(transaction) = self.ime_transaction { + self.buffer.update(cx, |buffer, cx| { + buffer.group_until_transaction(transaction, cx); + }); + } -// self.unmark_text(cx); -// } + self.unmark_text(cx); + } -// fn replace_and_mark_text_in_range( -// &mut self, -// range_utf16: Option>, -// text: &str, -// new_selected_range_utf16: Option>, -// cx: &mut ViewContext, -// ) { -// if !self.input_enabled { -// cx.emit(Event::InputIgnored { text: text.into() }); -// return; -// } + fn replace_and_mark_text_in_range( + &mut self, + range_utf16: Option>, + text: &str, + new_selected_range_utf16: Option>, + cx: &mut ViewContext, + ) { + if !self.input_enabled { + cx.emit(Event::InputIgnored { text: text.into() }); + return; + } -// let transaction = self.transact(cx, |this, cx| { -// let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) { -// let snapshot = this.buffer.read(cx).read(cx); -// if let Some(relative_range_utf16) = range_utf16.as_ref() { -// for marked_range in &mut marked_ranges { -// marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end; -// marked_range.start.0 += relative_range_utf16.start; -// marked_range.start = -// snapshot.clip_offset_utf16(marked_range.start, Bias::Left); -// marked_range.end = -// snapshot.clip_offset_utf16(marked_range.end, Bias::Right); -// } -// } -// Some(marked_ranges) -// } else if let Some(range_utf16) = range_utf16 { -// let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end); -// Some(this.selection_replacement_ranges(range_utf16, cx)) -// } else { -// None -// }; + let transaction = self.transact(cx, |this, cx| { + let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) { + let snapshot = this.buffer.read(cx).read(cx); + if let Some(relative_range_utf16) = range_utf16.as_ref() { + for marked_range in &mut marked_ranges { + marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end; + marked_range.start.0 += relative_range_utf16.start; + marked_range.start = + snapshot.clip_offset_utf16(marked_range.start, Bias::Left); + marked_range.end = + snapshot.clip_offset_utf16(marked_range.end, Bias::Right); + } + } + Some(marked_ranges) + } else if let Some(range_utf16) = range_utf16 { + let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end); + Some(this.selection_replacement_ranges(range_utf16, cx)) + } else { + None + }; -// let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| { -// let newest_selection_id = this.selections.newest_anchor().id; -// this.selections -// .all::(cx) -// .iter() -// .zip(ranges_to_replace.iter()) -// .find_map(|(selection, range)| { -// if selection.id == newest_selection_id { -// Some( -// (range.start.0 as isize - selection.head().0 as isize) -// ..(range.end.0 as isize - selection.head().0 as isize), -// ) -// } else { -// None -// } -// }) -// }); + let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| { + let newest_selection_id = this.selections.newest_anchor().id; + this.selections + .all::(cx) + .iter() + .zip(ranges_to_replace.iter()) + .find_map(|(selection, range)| { + if selection.id == newest_selection_id { + Some( + (range.start.0 as isize - selection.head().0 as isize) + ..(range.end.0 as isize - selection.head().0 as isize), + ) + } else { + None + } + }) + }); -// cx.emit(Event::InputHandled { -// utf16_range_to_replace: range_to_replace, -// text: text.into(), -// }); + cx.emit(Event::InputHandled { + utf16_range_to_replace: range_to_replace, + text: text.into(), + }); -// if let Some(ranges) = ranges_to_replace { -// this.change_selections(None, cx, |s| s.select_ranges(ranges)); -// } + if let Some(ranges) = ranges_to_replace { + this.change_selections(None, cx, |s| s.select_ranges(ranges)); + } -// let marked_ranges = { -// let snapshot = this.buffer.read(cx).read(cx); -// this.selections -// .disjoint_anchors() -// .iter() -// .map(|selection| { -// selection.start.bias_left(&*snapshot)..selection.end.bias_right(&*snapshot) -// }) -// .collect::>() -// }; + let marked_ranges = { + let snapshot = this.buffer.read(cx).read(cx); + this.selections + .disjoint_anchors() + .iter() + .map(|selection| { + selection.start.bias_left(&*snapshot)..selection.end.bias_right(&*snapshot) + }) + .collect::>() + }; -// if text.is_empty() { -// this.unmark_text(cx); -// } else { -// this.highlight_text::( -// marked_ranges.clone(), -// this.style(cx).composition_mark, -// cx, -// ); -// } + if text.is_empty() { + this.unmark_text(cx); + } else { + this.highlight_text::( + marked_ranges.clone(), + HighlightStyle::default(), // todo!() this.style(cx).composition_mark, + cx, + ); + } -// this.handle_input(text, cx); - -// if let Some(new_selected_range) = new_selected_range_utf16 { -// let snapshot = this.buffer.read(cx).read(cx); -// let new_selected_ranges = marked_ranges -// .into_iter() -// .map(|marked_range| { -// let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0; -// let new_start = OffsetUtf16(new_selected_range.start + insertion_start); -// let new_end = OffsetUtf16(new_selected_range.end + insertion_start); -// snapshot.clip_offset_utf16(new_start, Bias::Left) -// ..snapshot.clip_offset_utf16(new_end, Bias::Right) -// }) -// .collect::>(); - -// drop(snapshot); -// this.change_selections(None, cx, |selections| { -// selections.select_ranges(new_selected_ranges) -// }); -// } -// }); + this.handle_input(text, cx); -// self.ime_transaction = self.ime_transaction.or(transaction); -// if let Some(transaction) = self.ime_transaction { -// self.buffer.update(cx, |buffer, cx| { -// buffer.group_until_transaction(transaction, cx); -// }); -// } + if let Some(new_selected_range) = new_selected_range_utf16 { + let snapshot = this.buffer.read(cx).read(cx); + let new_selected_ranges = marked_ranges + .into_iter() + .map(|marked_range| { + let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0; + let new_start = OffsetUtf16(new_selected_range.start + insertion_start); + let new_end = OffsetUtf16(new_selected_range.end + insertion_start); + snapshot.clip_offset_utf16(new_start, Bias::Left) + ..snapshot.clip_offset_utf16(new_end, Bias::Right) + }) + .collect::>(); -// if self.text_highlights::(cx).is_none() { -// self.ime_transaction.take(); -// } -// } -// } + drop(snapshot); + this.change_selections(None, cx, |selections| { + selections.select_ranges(new_selected_ranges) + }); + } + }); + + self.ime_transaction = self.ime_transaction.or(transaction); + if let Some(transaction) = self.ime_transaction { + self.buffer.update(cx, |buffer, cx| { + buffer.group_until_transaction(transaction, cx); + }); + } + + if self.text_highlights::(cx).is_none() { + self.ime_transaction.take(); + } + } + + fn bounds_for_range( + &self, + range_utf16: Range, + cx: &mut ViewContext, + ) -> Option> { + // todo!() + // See how we did it before: `rect_for_range` + None + } +} // fn build_style( // settings: &ThemeSettings, diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 5af5d97e03ceed35569acbb950e0f75d3fceb7c6..04b8494a888bcc8333de24effa8edf675ba02866 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -2,17 +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, BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchContext, DispatchPhase, - Edges, Element, ElementId, Entity, Hsla, KeyDownEvent, KeyListener, KeyMatch, Line, Pixels, - ScrollWheelEvent, ShapedGlyph, Size, StatefulInteraction, Style, TextRun, TextStyle, - TextSystem, ViewContext, WindowContext, + 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; @@ -31,6 +39,7 @@ use std::{ }; use sum_tree::Bias; use theme::{ActiveTheme, PlayerColor}; +use util::ResultExt; use workspace::item::Item; enum FoldMarkers {} @@ -104,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; - // } - - // 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() - // } - // } - // }), - // ); - - // 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, - // ); - // }, - // ), - // ) - // } + 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; - // 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; - // } + 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; + } - // 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, - // ); - // } + 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, + ); + } - // true - // } + true + } // fn mouse_right_down( // editor: &mut Editor, @@ -313,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(); - - // 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); - // } - - // return true; - // } - - // end_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(); - // 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 - // }; + if end_selection { + editor.select(SelectPhase::End, cx); + } - // update_go_to_definition_link( - // editor, - // point.map(GoToDefinitionTrigger::Text), - // cmd, - // shift, - // 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); + } - // if editor.has_pending_selection() { - // let mut scroll_delta = gpui::Point::::zero(); + return true; + } - // 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)) - // } + end_selection + } - // 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, - // )) - // } + 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); + } - // let point_for_position = position_map.point_for_position(text_bounds, position); + 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); + } - // 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 - // } - // } + 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, + ); + } - // 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); - // } + 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); + + // 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); + + 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, + ); + } + } - // 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, @@ -2337,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)] @@ -2556,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(), |_| {}) + }); }) }); } @@ -2609,30 +2493,27 @@ impl Element for EditorElement { cx: &mut gpui::ViewContext, ) { let layout = self.compute_layout(editor, cx, bounds); + let gutter_bounds = Bounds { + origin: bounds.origin, + size: layout.gutter_size, + }; + let text_bounds = Bounds { + origin: gutter_bounds.upper_right(), + size: layout.text_size, + }; - 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(); - } - } - }); + 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); @@ -3339,7 +3220,7 @@ impl PositionMap { let previous_valid = self.snapshot.clip_point(exact_unclipped, Bias::Left); let next_valid = self.snapshot.clip_point(exact_unclipped, Bias::Right); - let column_overshoot_after_line_end = (x_overshoot_after_line_end / self.em_advance).into(); + let column_overshoot_after_line_end = (x_overshoot_after_line_end / self.em_advance) as u32; *exact_unclipped.column_mut() += column_overshoot_after_line_end; PointForPosition { previous_valid, @@ -3661,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)] @@ -4112,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 9eddd259af022d03da94a0ceec83b052c1ef684c..5c8f403d4f7d768d764f5665f45e31863b00e733 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 9db4061e5096ac3f4b3a221a3bc22f45114b26d2..d36762f3955e126200d7025002d543e9744bce79 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 d5aeb853bbf7a1210387b8c7c9184d4a2f43192c..52e7498f970c8c325ae6133529da0fe98443d4d2 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; - // } - - // 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); - // } - - // /// 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 let Some(visible_lines) = self.visible_line_count() { - // if newest_head.row() < screen_top.row() + visible_lines as u32 { - // return Ordering::Equal; - // } - // } - - // Ordering::Greater - // } + 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; + } + + 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); + + 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; + } + } + + 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 5ea502c456b0c2f73f24dc05833f9864ee97f90d..9a1e54f5e48bc301bd29756b92e20885f2466117 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 2762fd369ea2bf16e687fb61a41d4811973b86fb..23c5a75809719fc918a8716d17e949538fcc1c2c 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); - - // 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); - - // 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 - // } - // } + 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 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.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 fc0b7432c7b3eed48135e2551d7be5710168bae2..bb705427b1cb2898da3309545408cb6f81c76bad 100644 --- a/crates/go_to_line2/src/go_to_line.rs +++ b/crates/go_to_line2/src/go_to_line.rs @@ -12,7 +12,6 @@ use workspace::ModalRegistry; actions!(Toggle, Cancel, Confirm); pub fn init(cx: &mut AppContext) { - cx.register_action_type::(); cx.global_mut::() .register_modal(Toggle, |workspace, cx| { let editor = workspace diff --git a/crates/gpui2/src/action.rs b/crates/gpui2/src/action.rs index 90f312f502ca60d011c7c40c1dac60a1a5427bef..4d89ba1826e03a75a71b2fbd27018b511d784a02 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 63d2143a6726773be7508c77c72bdd6b1052fe3d..79f80f474d41bc6f6cdaeeb34a6797d348b674b3 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>; @@ -154,7 +153,7 @@ type ReleaseListener = Box; // } pub struct AppContext { - this: Weak, + pub(crate) this: Weak, pub(crate) platform: Rc, app_metadata: AppMetadata, text_system: Arc, @@ -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(), @@ -522,7 +519,7 @@ impl AppContext { window_handle .update(self, |_, cx| { if cx.window.focus == focused { - let mut listeners = mem::take(&mut cx.window.focus_listeners); + let mut listeners = mem::take(&mut cx.window.current_frame.focus_listeners); let focused = focused .map(|id| FocusHandle::for_id(id, &cx.window.focus_handles).unwrap()); let blurred = cx @@ -538,8 +535,8 @@ impl AppContext { } } - listeners.extend(cx.window.focus_listeners.drain(..)); - cx.window.focus_listeners = listeners; + listeners.extend(cx.window.current_frame.focus_listeners.drain(..)); + cx.window.current_frame.focus_listeners = listeners; } }) .ok(); @@ -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/app/entity_map.rs b/crates/gpui2/src/app/entity_map.rs index e626f8c409d2a80ac9f7ac2116741855f7e5beb3..588091c7a0bb1e3027b3a81cc0be2fa55f98e0f3 100644 --- a/crates/gpui2/src/app/entity_map.rs +++ b/crates/gpui2/src/app/entity_map.rs @@ -13,6 +13,7 @@ use std::{ atomic::{AtomicUsize, Ordering::SeqCst}, Arc, Weak, }, + thread::panicking, }; slotmap::new_key_type! { pub struct EntityId; } @@ -140,9 +141,8 @@ impl<'a, T: 'static> core::ops::DerefMut for Lease<'a, T> { impl<'a, T> Drop for Lease<'a, T> { fn drop(&mut self) { - if self.entity.is_some() { - // We don't panic here, because other panics can cause us to drop the lease without ending it cleanly. - log::error!("Leases must be ended with EntityMap::end_lease") + if self.entity.is_some() && !panicking() { + panic!("Leases must be ended with EntityMap::end_lease") } } } diff --git a/crates/gpui2/src/color.rs b/crates/gpui2/src/color.rs index 77f8ebdf41cad82bd16a36380814d8bf1c946cd1..3ae247962b13071c0e7e711778f6d1c9b60dc9df 100644 --- a/crates/gpui2/src/color.rs +++ b/crates/gpui2/src/color.rs @@ -203,6 +203,15 @@ pub fn red() -> Hsla { } } +pub fn blue() -> Hsla { + Hsla { + h: 0.6, + s: 1., + l: 0.5, + a: 1., + } +} + impl Hsla { /// Returns true if the HSLA color is fully transparent, false otherwise. pub fn is_transparent(&self) -> bool { diff --git a/crates/gpui2/src/elements.rs b/crates/gpui2/src/elements.rs index 83c27b8a3b1a88e6dee517c485f5f99ea1df7a93..eb061f7d34c8e77c1a366123818b253d33620c7a 100644 --- a/crates/gpui2/src/elements.rs +++ b/crates/gpui2/src/elements.rs @@ -2,8 +2,10 @@ mod div; mod img; mod svg; mod text; +mod uniform_list; pub use div::*; pub use img::*; pub use svg::*; pub use text::*; +pub use uniform_list::*; diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index 061ec095f8917517c38c7c4ce71d3e95ff03d7f6..d88a4119b7232838042ebe53f984b9468e1309c0 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -1,28 +1,28 @@ use crate::{ point, AnyElement, BorrowWindow, Bounds, Component, Element, ElementFocus, ElementId, - ElementInteraction, FocusDisabled, FocusEnabled, FocusHandle, FocusListeners, Focusable, + ElementInteractivity, FocusDisabled, FocusEnabled, FocusHandle, FocusListeners, Focusable, GlobalElementId, GroupBounds, InteractiveElementState, LayoutId, Overflow, ParentElement, - Pixels, Point, SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction, - StatelessInteractive, Style, StyleRefinement, Styled, ViewContext, Visibility, + Pixels, Point, SharedString, StatefulInteractive, StatefulInteractivity, StatelessInteractive, + StatelessInteractivity, Style, StyleRefinement, Styled, ViewContext, Visibility, }; use refineable::Refineable; use smallvec::SmallVec; pub struct Div< V: 'static, - I: ElementInteraction = StatelessInteraction, + I: ElementInteractivity = StatelessInteractivity, F: ElementFocus = FocusDisabled, > { - interaction: I, + interactivity: I, focus: F, children: SmallVec<[AnyElement; 2]>, group: Option, base_style: StyleRefinement, } -pub fn div() -> Div, FocusDisabled> { +pub fn div() -> Div, FocusDisabled> { Div { - interaction: StatelessInteraction::default(), + interactivity: StatelessInteractivity::default(), focus: FocusDisabled, children: SmallVec::new(), group: None, @@ -30,14 +30,14 @@ pub fn div() -> Div, FocusDisabled> { } } -impl Div, F> +impl Div, F> where V: 'static, F: ElementFocus, { - pub fn id(self, id: impl Into) -> Div, F> { + pub fn id(self, id: impl Into) -> Div, F> { Div { - interaction: id.into().into(), + interactivity: StatefulInteractivity::new(id.into(), self.interactivity), focus: self.focus, children: self.children, group: self.group, @@ -48,7 +48,7 @@ where impl Div where - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { pub fn group(mut self, group: impl Into) -> Self { @@ -98,16 +98,20 @@ where let mut computed_style = Style::default(); computed_style.refine(&self.base_style); self.focus.refine_style(&mut computed_style, cx); - self.interaction - .refine_style(&mut computed_style, bounds, &element_state.interactive, cx); + self.interactivity.refine_style( + &mut computed_style, + bounds, + &element_state.interactive, + cx, + ); computed_style } } -impl Div, FocusDisabled> { - pub fn focusable(self) -> Div, FocusEnabled> { +impl Div, FocusDisabled> { + pub fn focusable(self) -> Div, FocusEnabled> { Div { - interaction: self.interaction, + interactivity: self.interactivity, focus: FocusEnabled::new(), children: self.children, group: self.group, @@ -118,9 +122,9 @@ impl Div, FocusDisabled> { pub fn track_focus( self, handle: &FocusHandle, - ) -> Div, FocusEnabled> { + ) -> Div, FocusEnabled> { Div { - interaction: self.interaction, + interactivity: self.interactivity, focus: FocusEnabled::tracked(handle), children: self.children, group: self.group, @@ -145,13 +149,13 @@ impl Div, FocusDisabled> { } } -impl Div, FocusDisabled> { +impl Div, FocusDisabled> { pub fn track_focus( self, handle: &FocusHandle, - ) -> Div, FocusEnabled> { + ) -> Div, FocusEnabled> { Div { - interaction: self.interaction.into_stateful(handle), + interactivity: self.interactivity.into_stateful(handle), focus: handle.clone().into(), children: self.children, group: self.group, @@ -163,7 +167,7 @@ impl Div, FocusDisabled> { impl Focusable for Div> where V: 'static, - I: ElementInteraction, + I: ElementInteractivity, { fn focus_listeners(&mut self) -> &mut FocusListeners { &mut self.focus.focus_listeners @@ -191,13 +195,13 @@ pub struct DivState { impl Element for Div where - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { type ElementState = DivState; fn id(&self) -> Option { - self.interaction + self.interactivity .as_stateful() .map(|identified| identified.id.clone()) } @@ -209,7 +213,7 @@ where cx: &mut ViewContext, ) -> Self::ElementState { let mut element_state = element_state.unwrap_or_default(); - self.interaction.initialize(cx, |cx| { + self.interactivity.initialize(cx, |cx| { self.focus .initialize(element_state.focus_handle.take(), cx, |focus_handle, cx| { element_state.focus_handle = focus_handle; @@ -281,11 +285,11 @@ where (child_max - child_min).into() }; - cx.stack(z_index, |cx| { - cx.stack(0, |cx| { + cx.with_z_index(z_index, |cx| { + cx.with_z_index(0, |cx| { style.paint(bounds, cx); this.focus.paint(bounds, cx); - this.interaction.paint( + this.interactivity.paint( bounds, content_size, style.overflow, @@ -293,7 +297,7 @@ where cx, ); }); - cx.stack(1, |cx| { + cx.with_z_index(1, |cx| { style.apply_text_style(cx, |cx| { style.apply_overflow(bounds, cx, |cx| { let scroll_offset = element_state.interactive.scroll_offset(); @@ -316,7 +320,7 @@ where impl Component for Div where - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { fn render(self) -> AnyElement { @@ -326,7 +330,7 @@ where impl ParentElement for Div where - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { @@ -336,7 +340,7 @@ where impl Styled for Div where - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { fn style(&mut self) -> &mut StyleRefinement { @@ -346,19 +350,19 @@ where impl StatelessInteractive for Div where - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { - fn stateless_interaction(&mut self) -> &mut StatelessInteraction { - self.interaction.as_stateless_mut() + fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity { + self.interactivity.as_stateless_mut() } } -impl StatefulInteractive for Div, F> +impl StatefulInteractive for Div, F> where F: ElementFocus, { - fn stateful_interaction(&mut self) -> &mut StatefulInteraction { - &mut self.interaction + fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity { + &mut self.interactivity } } diff --git a/crates/gpui2/src/elements/img.rs b/crates/gpui2/src/elements/img.rs index a35436d74ec9f668e4081eb9a9c0f1ea4168a914..638665d4141d4a19362b2812932048a8ad46c348 100644 --- a/crates/gpui2/src/elements/img.rs +++ b/crates/gpui2/src/elements/img.rs @@ -1,15 +1,15 @@ use crate::{ div, AnyElement, BorrowWindow, Bounds, Component, Div, DivState, Element, ElementFocus, - ElementId, ElementInteraction, FocusDisabled, FocusEnabled, FocusListeners, Focusable, - LayoutId, Pixels, SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction, - StatelessInteractive, StyleRefinement, Styled, ViewContext, + ElementId, ElementInteractivity, FocusDisabled, FocusEnabled, FocusListeners, Focusable, + LayoutId, Pixels, SharedString, StatefulInteractive, StatefulInteractivity, + StatelessInteractive, StatelessInteractivity, StyleRefinement, Styled, ViewContext, }; use futures::FutureExt; use util::ResultExt; pub struct Img< V: 'static, - I: ElementInteraction = StatelessInteraction, + I: ElementInteractivity = StatelessInteractivity, F: ElementFocus = FocusDisabled, > { base: Div, @@ -17,7 +17,7 @@ pub struct Img< grayscale: bool, } -pub fn img() -> Img, FocusDisabled> { +pub fn img() -> Img, FocusDisabled> { Img { base: div(), uri: None, @@ -28,7 +28,7 @@ pub fn img() -> Img, FocusDisabled> { impl Img where V: 'static, - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { pub fn uri(mut self, uri: impl Into) -> Self { @@ -42,11 +42,11 @@ where } } -impl Img, F> +impl Img, F> where F: ElementFocus, { - pub fn id(self, id: impl Into) -> Img, F> { + pub fn id(self, id: impl Into) -> Img, F> { Img { base: self.base.id(id), uri: self.uri, @@ -57,7 +57,7 @@ where impl Component for Img where - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { fn render(self) -> AnyElement { @@ -67,7 +67,7 @@ where impl Element for Img where - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { type ElementState = DivState; @@ -101,7 +101,7 @@ where element_state: &mut Self::ElementState, cx: &mut ViewContext, ) { - cx.stack(0, |cx| { + cx.with_z_index(0, |cx| { self.base.paint(bounds, view, element_state, cx); }); @@ -118,7 +118,7 @@ where .and_then(ResultExt::log_err) { let corner_radii = corner_radii.to_pixels(bounds.size, cx.rem_size()); - cx.stack(1, |cx| { + cx.with_z_index(1, |cx| { cx.paint_image(bounds, corner_radii, data, self.grayscale) .log_err() }); @@ -136,7 +136,7 @@ where impl Styled for Img where - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { fn style(&mut self) -> &mut StyleRefinement { @@ -146,27 +146,27 @@ where impl StatelessInteractive for Img where - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { - fn stateless_interaction(&mut self) -> &mut StatelessInteraction { - self.base.stateless_interaction() + fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity { + self.base.stateless_interactivity() } } -impl StatefulInteractive for Img, F> +impl StatefulInteractive for Img, F> where F: ElementFocus, { - fn stateful_interaction(&mut self) -> &mut StatefulInteraction { - self.base.stateful_interaction() + fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity { + self.base.stateful_interactivity() } } impl Focusable for Img> where V: 'static, - I: ElementInteraction, + I: ElementInteractivity, { fn focus_listeners(&mut self) -> &mut FocusListeners { self.base.focus_listeners() diff --git a/crates/gpui2/src/elements/svg.rs b/crates/gpui2/src/elements/svg.rs index 7db4c5cf6db86d96bf154c9a4f243c630b0a3fe5..8e2ba9d8a1ba944f7cd82ca583e30e53de147c23 100644 --- a/crates/gpui2/src/elements/svg.rs +++ b/crates/gpui2/src/elements/svg.rs @@ -1,21 +1,21 @@ use crate::{ div, AnyElement, Bounds, Component, Div, DivState, Element, ElementFocus, ElementId, - ElementInteraction, FocusDisabled, FocusEnabled, FocusListeners, Focusable, LayoutId, Pixels, - SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction, - StatelessInteractive, StyleRefinement, Styled, ViewContext, + ElementInteractivity, FocusDisabled, FocusEnabled, FocusListeners, Focusable, LayoutId, Pixels, + SharedString, StatefulInteractive, StatefulInteractivity, StatelessInteractive, + StatelessInteractivity, StyleRefinement, Styled, ViewContext, }; use util::ResultExt; pub struct Svg< V: 'static, - I: ElementInteraction = StatelessInteraction, + I: ElementInteractivity = StatelessInteractivity, F: ElementFocus = FocusDisabled, > { base: Div, path: Option, } -pub fn svg() -> Svg, FocusDisabled> { +pub fn svg() -> Svg, FocusDisabled> { Svg { base: div(), path: None, @@ -24,7 +24,7 @@ pub fn svg() -> Svg, FocusDisabled> { impl Svg where - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { pub fn path(mut self, path: impl Into) -> Self { @@ -33,11 +33,11 @@ where } } -impl Svg, F> +impl Svg, F> where F: ElementFocus, { - pub fn id(self, id: impl Into) -> Svg, F> { + pub fn id(self, id: impl Into) -> Svg, F> { Svg { base: self.base.id(id), path: self.path, @@ -47,7 +47,7 @@ where impl Component for Svg where - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { fn render(self) -> AnyElement { @@ -57,7 +57,7 @@ where impl Element for Svg where - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { type ElementState = DivState; @@ -107,7 +107,7 @@ where impl Styled for Svg where - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { fn style(&mut self) -> &mut StyleRefinement { @@ -117,27 +117,27 @@ where impl StatelessInteractive for Svg where - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { - fn stateless_interaction(&mut self) -> &mut StatelessInteraction { - self.base.stateless_interaction() + fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity { + self.base.stateless_interactivity() } } -impl StatefulInteractive for Svg, F> +impl StatefulInteractive for Svg, F> where V: 'static, F: ElementFocus, { - fn stateful_interaction(&mut self) -> &mut StatefulInteraction { - self.base.stateful_interaction() + fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity { + self.base.stateful_interactivity() } } impl Focusable for Svg> where - I: ElementInteraction, + I: ElementInteractivity, { fn focus_listeners(&mut self) -> &mut FocusListeners { self.base.focus_listeners() diff --git a/crates/gpui2/src/elements/text.rs b/crates/gpui2/src/elements/text.rs index 76c587d22f3e641c248f38e79a96b2651aa7ab43..e258d3e7dc6dba8c7d7c625981b7ef340c1dc96c 100644 --- a/crates/gpui2/src/elements/text.rs +++ b/crates/gpui2/src/elements/text.rs @@ -127,6 +127,7 @@ impl Element for Text { let element_state = element_state .as_ref() .expect("measurement has not been performed"); + let line_height = element_state.line_height; let mut line_origin = bounds.origin; for line in &element_state.lines { diff --git a/crates/gpui2/src/elements/uniform_list.rs b/crates/gpui2/src/elements/uniform_list.rs new file mode 100644 index 0000000000000000000000000000000000000000..e1160227637c8374fa47e922a0fabb509308ec1e --- /dev/null +++ b/crates/gpui2/src/elements/uniform_list.rs @@ -0,0 +1,244 @@ +use crate::{ + point, px, AnyElement, AvailableSpace, BorrowWindow, Bounds, Component, Element, ElementId, + ElementInteractivity, InteractiveElementState, LayoutId, Pixels, Point, Size, + StatefulInteractive, StatefulInteractivity, StatelessInteractive, StatelessInteractivity, + StyleRefinement, Styled, ViewContext, +}; +use parking_lot::Mutex; +use smallvec::SmallVec; +use std::{cmp, ops::Range, sync::Arc}; +use taffy::style::Overflow; + +pub fn uniform_list( + id: Id, + item_count: usize, + f: impl 'static + Fn(&mut V, Range, &mut ViewContext) -> SmallVec<[C; 64]>, +) -> UniformList +where + Id: Into, + V: 'static, + C: Component, +{ + let id = id.into(); + UniformList { + id: id.clone(), + style: Default::default(), + item_count, + render_items: Box::new(move |view, visible_range, cx| { + f(view, visible_range, cx) + .into_iter() + .map(|component| component.render()) + .collect() + }), + interactivity: StatefulInteractivity::new(id, StatelessInteractivity::default()), + scroll_handle: None, + } +} + +pub struct UniformList { + id: ElementId, + style: StyleRefinement, + item_count: usize, + render_items: Box< + dyn for<'a> Fn( + &'a mut V, + Range, + &'a mut ViewContext, + ) -> SmallVec<[AnyElement; 64]>, + >, + interactivity: StatefulInteractivity, + scroll_handle: Option, +} + +#[derive(Clone)] +pub struct UniformListScrollHandle(Arc>>); + +#[derive(Clone, Debug)] +struct ScrollHandleState { + item_height: Pixels, + list_height: Pixels, + scroll_offset: Arc>>, +} + +impl UniformListScrollHandle { + pub fn new() -> Self { + Self(Arc::new(Mutex::new(None))) + } + + pub fn scroll_to_item(&self, ix: usize) { + if let Some(state) = &*self.0.lock() { + let mut scroll_offset = state.scroll_offset.lock(); + let item_top = state.item_height * ix; + let item_bottom = item_top + state.item_height; + let scroll_top = -scroll_offset.y; + if item_top < scroll_top { + scroll_offset.y = -item_top; + } else if item_bottom > scroll_top + state.list_height { + scroll_offset.y = -(item_bottom - state.list_height); + } + } + } +} + +impl Styled for UniformList { + fn style(&mut self) -> &mut StyleRefinement { + &mut self.style + } +} + +impl Element for UniformList { + type ElementState = InteractiveElementState; + + fn id(&self) -> Option { + Some(self.id.clone()) + } + + fn initialize( + &mut self, + _: &mut V, + element_state: Option, + _: &mut ViewContext, + ) -> Self::ElementState { + element_state.unwrap_or_default() + } + + fn layout( + &mut self, + _view_state: &mut V, + _element_state: &mut Self::ElementState, + cx: &mut ViewContext, + ) -> LayoutId { + cx.request_layout(&self.computed_style(), None) + } + + fn paint( + &mut self, + bounds: crate::Bounds, + view_state: &mut V, + element_state: &mut Self::ElementState, + cx: &mut ViewContext, + ) { + let style = self.computed_style(); + style.paint(bounds, cx); + + let border = style.border_widths.to_pixels(cx.rem_size()); + let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size()); + + let padded_bounds = Bounds::from_corners( + bounds.origin + point(border.left + padding.left, border.top + padding.top), + bounds.lower_right() + - point(border.right + padding.right, border.bottom + padding.bottom), + ); + + cx.with_z_index(style.z_index.unwrap_or(0), |cx| { + let content_size; + if self.item_count > 0 { + let item_height = self.measure_item_height(view_state, padded_bounds, cx); + if let Some(scroll_handle) = self.scroll_handle.clone() { + scroll_handle.0.lock().replace(ScrollHandleState { + item_height, + list_height: padded_bounds.size.height, + scroll_offset: element_state.track_scroll_offset(), + }); + } + let visible_item_count = if item_height > px(0.) { + (padded_bounds.size.height / item_height).ceil() as usize + 1 + } else { + 0 + }; + let scroll_offset = element_state + .scroll_offset() + .map_or((0.0).into(), |offset| offset.y); + let first_visible_element_ix = (-scroll_offset / item_height).floor() as usize; + let visible_range = first_visible_element_ix + ..cmp::min( + first_visible_element_ix + visible_item_count, + self.item_count, + ); + + let mut items = (self.render_items)(view_state, visible_range.clone(), cx); + + content_size = Size { + width: padded_bounds.size.width, + height: item_height * self.item_count, + }; + + cx.with_z_index(1, |cx| { + for (item, ix) in items.iter_mut().zip(visible_range) { + item.initialize(view_state, cx); + + let layout_id = item.layout(view_state, cx); + cx.compute_layout( + layout_id, + Size { + width: AvailableSpace::Definite(bounds.size.width), + height: AvailableSpace::Definite(item_height), + }, + ); + let offset = + padded_bounds.origin + point(px(0.), item_height * ix + scroll_offset); + cx.with_element_offset(Some(offset), |cx| item.paint(view_state, cx)) + } + }); + } else { + content_size = Size { + width: bounds.size.width, + height: px(0.), + }; + } + + let overflow = point(style.overflow.x, Overflow::Scroll); + + cx.with_z_index(0, |cx| { + self.interactivity + .paint(bounds, content_size, overflow, element_state, cx); + }); + }) + } +} + +impl UniformList { + fn measure_item_height( + &self, + view_state: &mut V, + list_bounds: Bounds, + cx: &mut ViewContext, + ) -> Pixels { + let mut items = (self.render_items)(view_state, 0..1, cx); + debug_assert!(items.len() == 1); + let mut item_to_measure = items.pop().unwrap(); + item_to_measure.initialize(view_state, cx); + let layout_id = item_to_measure.layout(view_state, cx); + cx.compute_layout( + layout_id, + Size { + width: AvailableSpace::Definite(list_bounds.size.width), + height: AvailableSpace::MinContent, + }, + ); + cx.layout_bounds(layout_id).size.height + } + + pub fn track_scroll(mut self, handle: UniformListScrollHandle) -> Self { + self.scroll_handle = Some(handle); + self + } +} + +impl StatelessInteractive for UniformList { + fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity { + self.interactivity.as_stateless_mut() + } +} + +impl StatefulInteractive for UniformList { + fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity { + &mut self.interactivity + } +} + +impl Component for UniformList { + fn render(self) -> AnyElement { + AnyElement::new(self) + } +} diff --git a/crates/gpui2/src/executor.rs b/crates/gpui2/src/executor.rs index b6fd5b23180bed5bf852ae99e79a6cb071ac7174..b7e3610283f5ec58fbd61a6d977c02e55efedaaf 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/geometry.rs b/crates/gpui2/src/geometry.rs index e16635be362f6bc59370805a6c93413da50a7c64..c5209239aea6ed84bc43d715d52b6b1e0a895c7c 100644 --- a/crates/gpui2/src/geometry.rs +++ b/crates/gpui2/src/geometry.rs @@ -267,6 +267,24 @@ impl From> for Size { } } +impl From> for Size { + fn from(size: Size) -> Self { + Size { + width: size.width.into(), + height: size.height.into(), + } + } +} + +impl From> for Size { + fn from(size: Size) -> Self { + Size { + width: size.width.into(), + height: size.height.into(), + } + } +} + impl Size { pub fn full() -> Self { Self { @@ -558,6 +576,15 @@ impl Edges { left: px(0.).into(), } } + + pub fn to_pixels(&self, parent_size: Size, rem_size: Pixels) -> Edges { + Edges { + top: self.top.to_pixels(parent_size.height, rem_size), + right: self.right.to_pixels(parent_size.width, rem_size), + bottom: self.bottom.to_pixels(parent_size.height, rem_size), + left: self.left.to_pixels(parent_size.width, rem_size), + } + } } impl Edges { @@ -689,16 +716,16 @@ impl Copy for Corners where T: Copy + Clone + Default + Debug {} pub struct Pixels(pub(crate) f32); impl std::ops::Div for Pixels { - type Output = Self; + type Output = f32; fn div(self, rhs: Self) -> Self::Output { - Self(self.0 / rhs.0) + self.0 / rhs.0 } } impl std::ops::DivAssign for Pixels { fn div_assign(&mut self, rhs: Self) { - self.0 /= rhs.0; + *self = Self(self.0 / rhs.0); } } @@ -750,14 +777,6 @@ impl Pixels { pub const ZERO: Pixels = Pixels(0.0); pub const MAX: Pixels = Pixels(f32::MAX); - pub fn as_usize(&self) -> usize { - self.0 as usize - } - - pub fn as_isize(&self) -> isize { - self.0 as isize - } - pub fn floor(&self) -> Self { Self(self.0.floor()) } diff --git a/crates/gpui2/src/gpui2.rs b/crates/gpui2/src/gpui2.rs index 348da17356dd3f48d1a507cfcb0c8d19a4413d2b..8f3dc6c314c0542eb47fc7213fcf766399d33a3f 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/crates/gpui2/src/gpui2.rs @@ -24,6 +24,7 @@ mod text_system; mod util; mod view; mod window; +mod window_input_handler; mod private { /// A mechanism for restricting implementations of a trait to only those in GPUI. @@ -36,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::*; @@ -64,6 +66,7 @@ pub use text_system::*; pub use util::arc_cow::ArcCow; pub use view::*; pub use window::*; +pub use window_input_handler::*; use derive_more::{Deref, DerefMut}; use std::{ diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index 84beb56ef8c68c44824e7d00a2a7b218ae689722..a546c1b40b9cbb073f3539133c29b1714fffd951 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -25,13 +25,13 @@ const TOOLTIP_DELAY: Duration = Duration::from_millis(500); const TOOLTIP_OFFSET: Point = Point::new(px(10.0), px(8.0)); pub trait StatelessInteractive: Element { - fn stateless_interaction(&mut self) -> &mut StatelessInteraction; + fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity; fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self where Self: Sized, { - self.stateless_interaction().hover_style = f(StyleRefinement::default()); + self.stateless_interactivity().hover_style = f(StyleRefinement::default()); self } @@ -43,7 +43,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interaction().group_hover_style = Some(GroupStyle { + self.stateless_interactivity().group_hover_style = Some(GroupStyle { group: group_name.into(), style: f(StyleRefinement::default()), }); @@ -58,7 +58,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interaction() + self.stateless_interactivity() .mouse_down_listeners .push(Box::new(move |view, event, bounds, phase, cx| { if phase == DispatchPhase::Bubble @@ -79,7 +79,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interaction() + self.stateless_interactivity() .mouse_up_listeners .push(Box::new(move |view, event, bounds, phase, cx| { if phase == DispatchPhase::Bubble @@ -100,7 +100,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interaction() + self.stateless_interactivity() .mouse_down_listeners .push(Box::new(move |view, event, bounds, phase, cx| { if phase == DispatchPhase::Capture @@ -121,7 +121,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interaction() + self.stateless_interactivity() .mouse_up_listeners .push(Box::new(move |view, event, bounds, phase, cx| { if phase == DispatchPhase::Capture @@ -141,7 +141,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interaction() + self.stateless_interactivity() .mouse_move_listeners .push(Box::new(move |view, event, bounds, phase, cx| { if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { @@ -158,7 +158,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interaction() + self.stateless_interactivity() .scroll_wheel_listeners .push(Box::new(move |view, event, bounds, phase, cx| { if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { @@ -174,23 +174,48 @@ pub trait StatelessInteractive: Element { C: TryInto, C::Error: Debug, { - self.stateless_interaction().dispatch_context = + self.stateless_interactivity().dispatch_context = context.try_into().expect("invalid dispatch context"); self } + /// Capture the given action, fires during the capture phase + fn capture_action( + mut self, + listener: impl Fn(&mut V, &A, &mut ViewContext) + 'static, + ) -> Self + where + Self: Sized, + { + self.stateless_interactivity().key_listeners.push(( + TypeId::of::(), + Box::new(move |view, action, _dipatch_context, phase, cx| { + let action = action.downcast_ref().unwrap(); + if phase == DispatchPhase::Capture { + listener(view, action, cx) + } + None + }), + )); + self + } + + /// Add a listener for the given action, fires during the bubble event phase fn on_action( mut self, - listener: impl Fn(&mut V, &A, DispatchPhase, &mut ViewContext) + 'static, + listener: impl Fn(&mut V, &A, &mut ViewContext) + 'static, ) -> Self where Self: Sized, { - self.stateless_interaction().key_listeners.push(( + self.stateless_interactivity().key_listeners.push(( TypeId::of::(), - Box::new(move |view, event, _, phase, cx| { - let event = event.downcast_ref().unwrap(); - listener(view, event, phase, cx); + Box::new(move |view, action, _dispatch_context, phase, cx| { + let action = action.downcast_ref().unwrap(); + if phase == DispatchPhase::Bubble { + listener(view, action, cx) + } + None }), )); @@ -204,7 +229,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interaction().key_listeners.push(( + self.stateless_interactivity().key_listeners.push(( TypeId::of::(), Box::new(move |view, event, _, phase, cx| { let event = event.downcast_ref().unwrap(); @@ -222,7 +247,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interaction().key_listeners.push(( + self.stateless_interactivity().key_listeners.push(( TypeId::of::(), Box::new(move |view, event, _, phase, cx| { let event = event.downcast_ref().unwrap(); @@ -237,7 +262,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interaction() + self.stateless_interactivity() .drag_over_styles .push((TypeId::of::(), f(StyleRefinement::default()))); self @@ -251,7 +276,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interaction().group_drag_over_styles.push(( + self.stateless_interactivity().group_drag_over_styles.push(( TypeId::of::(), GroupStyle { group: group_name.into(), @@ -268,7 +293,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interaction().drop_listeners.push(( + self.stateless_interactivity().drop_listeners.push(( TypeId::of::(), Box::new(move |view, dragged_view, cx| { listener(view, dragged_view.downcast().unwrap(), cx); @@ -279,13 +304,13 @@ pub trait StatelessInteractive: Element { } pub trait StatefulInteractive: StatelessInteractive { - fn stateful_interaction(&mut self) -> &mut StatefulInteraction; + fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity; fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self where Self: Sized, { - self.stateful_interaction().active_style = f(StyleRefinement::default()); + self.stateful_interactivity().active_style = f(StyleRefinement::default()); self } @@ -297,7 +322,7 @@ pub trait StatefulInteractive: StatelessInteractive { where Self: Sized, { - self.stateful_interaction().group_active_style = Some(GroupStyle { + self.stateful_interactivity().group_active_style = Some(GroupStyle { group: group_name.into(), style: f(StyleRefinement::default()), }); @@ -311,7 +336,7 @@ pub trait StatefulInteractive: StatelessInteractive { where Self: Sized, { - self.stateful_interaction() + self.stateful_interactivity() .click_listeners .push(Box::new(move |view, event, cx| listener(view, event, cx))); self @@ -326,10 +351,10 @@ pub trait StatefulInteractive: StatelessInteractive { W: 'static + Render, { debug_assert!( - self.stateful_interaction().drag_listener.is_none(), + self.stateful_interactivity().drag_listener.is_none(), "calling on_drag more than once on the same element is not supported" ); - self.stateful_interaction().drag_listener = + self.stateful_interactivity().drag_listener = Some(Box::new(move |view_state, cursor_offset, cx| AnyDrag { view: listener(view_state, cx).into(), cursor_offset, @@ -342,10 +367,10 @@ pub trait StatefulInteractive: StatelessInteractive { Self: Sized, { debug_assert!( - self.stateful_interaction().hover_listener.is_none(), + self.stateful_interactivity().hover_listener.is_none(), "calling on_hover more than once on the same element is not supported" ); - self.stateful_interaction().hover_listener = Some(Box::new(listener)); + self.stateful_interactivity().hover_listener = Some(Box::new(listener)); self } @@ -358,10 +383,10 @@ pub trait StatefulInteractive: StatelessInteractive { W: 'static + Render, { debug_assert!( - self.stateful_interaction().tooltip_builder.is_none(), + self.stateful_interactivity().tooltip_builder.is_none(), "calling tooltip more than once on the same element is not supported" ); - self.stateful_interaction().tooltip_builder = Some(Arc::new(move |view_state, cx| { + self.stateful_interactivity().tooltip_builder = Some(Arc::new(move |view_state, cx| { build_tooltip(view_state, cx).into() })); @@ -369,11 +394,11 @@ pub trait StatefulInteractive: StatelessInteractive { } } -pub trait ElementInteraction: 'static { - fn as_stateless(&self) -> &StatelessInteraction; - fn as_stateless_mut(&mut self) -> &mut StatelessInteraction; - fn as_stateful(&self) -> Option<&StatefulInteraction>; - fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteraction>; +pub trait ElementInteractivity: 'static { + fn as_stateless(&self) -> &StatelessInteractivity; + fn as_stateless_mut(&mut self) -> &mut StatelessInteractivity; + fn as_stateful(&self) -> Option<&StatefulInteractivity>; + fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity>; fn initialize( &mut self, @@ -382,6 +407,8 @@ pub trait ElementInteraction: 'static { ) -> R { if let Some(stateful) = self.as_stateful_mut() { cx.with_element_id(stateful.id.clone(), |global_id, cx| { + // In addition to any key down/up listeners registered directly on the element, + // we also add a key listener to match actions from the keymap. stateful.key_listeners.push(( TypeId::of::(), Box::new(move |_, key_down, context, phase, cx| { @@ -736,11 +763,11 @@ pub trait ElementInteraction: 'static { } #[derive(Deref, DerefMut)] -pub struct StatefulInteraction { +pub struct StatefulInteractivity { pub id: ElementId, #[deref] #[deref_mut] - stateless: StatelessInteraction, + stateless: StatelessInteractivity, click_listeners: SmallVec<[ClickListener; 2]>, active_style: StyleRefinement, group_active_style: Option, @@ -749,42 +776,42 @@ pub struct StatefulInteraction { tooltip_builder: Option>, } -impl ElementInteraction for StatefulInteraction { - fn as_stateful(&self) -> Option<&StatefulInteraction> { +impl StatefulInteractivity { + pub fn new(id: ElementId, stateless: StatelessInteractivity) -> Self { + Self { + id, + stateless, + click_listeners: SmallVec::new(), + active_style: StyleRefinement::default(), + group_active_style: None, + drag_listener: None, + hover_listener: None, + tooltip_builder: None, + } + } +} + +impl ElementInteractivity for StatefulInteractivity { + fn as_stateful(&self) -> Option<&StatefulInteractivity> { Some(self) } - fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteraction> { + fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity> { Some(self) } - fn as_stateless(&self) -> &StatelessInteraction { + fn as_stateless(&self) -> &StatelessInteractivity { &self.stateless } - fn as_stateless_mut(&mut self) -> &mut StatelessInteraction { + fn as_stateless_mut(&mut self) -> &mut StatelessInteractivity { &mut self.stateless } } -impl From for StatefulInteraction { - fn from(id: ElementId) -> Self { - Self { - id, - stateless: StatelessInteraction::default(), - click_listeners: SmallVec::new(), - drag_listener: None, - hover_listener: None, - tooltip_builder: None, - active_style: StyleRefinement::default(), - group_active_style: None, - } - } -} - type DropListener = dyn Fn(&mut V, AnyView, &mut ViewContext) + 'static; -pub struct StatelessInteraction { +pub struct StatelessInteractivity { pub dispatch_context: DispatchContext, pub mouse_down_listeners: SmallVec<[MouseDownListener; 2]>, pub mouse_up_listeners: SmallVec<[MouseUpListener; 2]>, @@ -798,9 +825,9 @@ pub struct StatelessInteraction { drop_listeners: SmallVec<[(TypeId, Box>); 2]>, } -impl StatelessInteraction { - pub fn into_stateful(self, id: impl Into) -> StatefulInteraction { - StatefulInteraction { +impl StatelessInteractivity { + pub fn into_stateful(self, id: impl Into) -> StatefulInteractivity { + StatefulInteractivity { id: id.into(), stateless: self, click_listeners: SmallVec::new(), @@ -876,9 +903,15 @@ impl InteractiveElementState { .as_ref() .map(|offset| offset.lock().clone()) } + + pub fn track_scroll_offset(&mut self) -> Arc>> { + self.scroll_offset + .get_or_insert_with(|| Arc::new(Mutex::new(Default::default()))) + .clone() + } } -impl Default for StatelessInteraction { +impl Default for StatelessInteractivity { fn default() -> Self { Self { dispatch_context: DispatchContext::default(), @@ -896,20 +929,20 @@ impl Default for StatelessInteraction { } } -impl ElementInteraction for StatelessInteraction { - fn as_stateful(&self) -> Option<&StatefulInteraction> { +impl ElementInteractivity for StatelessInteractivity { + fn as_stateful(&self) -> Option<&StatefulInteractivity> { None } - fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteraction> { + fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity> { None } - fn as_stateless(&self) -> &StatelessInteraction { + fn as_stateless(&self) -> &StatelessInteractivity { self } - fn as_stateless_mut(&mut self) -> &mut StatelessInteraction { + fn as_stateless_mut(&mut self) -> &mut StatelessInteractivity { self } } @@ -1236,7 +1269,7 @@ pub type KeyListener = Box< mod test { use crate::{ self as gpui, div, Div, FocusHandle, KeyBinding, Keystroke, ParentElement, Render, - StatefulInteraction, StatelessInteractive, TestAppContext, VisualContext, + StatefulInteractivity, StatelessInteractive, TestAppContext, VisualContext, }; struct TestView { @@ -1248,19 +1281,13 @@ mod test { actions!(TestAction); impl Render for TestView { - type Element = Div>; + type Element = Div>; fn render(&mut self, _: &mut gpui::ViewContext) -> Self::Element { div().id("testview").child( div() - .on_key_down(|this: &mut TestView, _, _, _| { - dbg!("ola!"); - this.saw_key_down = true - }) - .on_action(|this: &mut TestView, _: &TestAction, _, _| { - dbg!("ola!"); - this.saw_action = true - }) + .on_key_down(|this: &mut TestView, _, _, _| this.saw_key_down = true) + .on_action(|this: &mut TestView, _: &TestAction, _| this.saw_action = true) .track_focus(&self.focus_handle), ) } diff --git a/crates/gpui2/src/platform/mac/metal_renderer.rs b/crates/gpui2/src/platform/mac/metal_renderer.rs index 7bb80640beb76f1fa1eb0dd8bc991314d9aa043b..0631c75de5e222e43f780f29fb9844f5e96c099c 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/platform/mac/shaders.metal b/crates/gpui2/src/platform/mac/shaders.metal index 0a3a2b2129de9355b657905adee1fc39f6007ff5..4def1c33b85a430d051376322dca47de93b9e70c 100644 --- a/crates/gpui2/src/platform/mac/shaders.metal +++ b/crates/gpui2/src/platform/mac/shaders.metal @@ -5,10 +5,11 @@ using namespace metal; float4 hsla_to_rgba(Hsla hsla); float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds, - Bounds_ScaledPixels clip_bounds, constant Size_DevicePixels *viewport_size); float2 to_tile_position(float2 unit_vertex, AtlasTile tile, constant Size_DevicePixels *atlas_size); +float4 distance_from_clip_rect(float2 unit_vertex, Bounds_ScaledPixels bounds, + Bounds_ScaledPixels clip_bounds); float quad_sdf(float2 point, Bounds_ScaledPixels bounds, Corners_ScaledPixels corner_radii); float gaussian(float x, float sigma); @@ -21,6 +22,14 @@ struct QuadVertexOutput { float4 background_color [[flat]]; float4 border_color [[flat]]; uint quad_id [[flat]]; + float clip_distance [[clip_distance]][4]; +}; + +struct QuadFragmentInput { + float4 position [[position]]; + float4 background_color [[flat]]; + float4 border_color [[flat]]; + uint quad_id [[flat]]; }; vertex QuadVertexOutput quad_vertex(uint unit_vertex_id [[vertex_id]], @@ -33,15 +42,21 @@ vertex QuadVertexOutput quad_vertex(uint unit_vertex_id [[vertex_id]], [[buffer(QuadInputIndex_ViewportSize)]]) { float2 unit_vertex = unit_vertices[unit_vertex_id]; Quad quad = quads[quad_id]; - float4 device_position = to_device_position( - unit_vertex, quad.bounds, quad.content_mask.bounds, viewport_size); + float4 device_position = + to_device_position(unit_vertex, quad.bounds, viewport_size); + float4 clip_distance = distance_from_clip_rect(unit_vertex, quad.bounds, + quad.content_mask.bounds); float4 background_color = hsla_to_rgba(quad.background); float4 border_color = hsla_to_rgba(quad.border_color); - return QuadVertexOutput{device_position, background_color, border_color, - quad_id}; + return QuadVertexOutput{ + device_position, + background_color, + border_color, + quad_id, + {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}}; } -fragment float4 quad_fragment(QuadVertexOutput input [[stage_in]], +fragment float4 quad_fragment(QuadFragmentInput input [[stage_in]], constant Quad *quads [[buffer(QuadInputIndex_Quads)]]) { Quad quad = quads[input.quad_id]; @@ -117,6 +132,13 @@ struct ShadowVertexOutput { float4 position [[position]]; float4 color [[flat]]; uint shadow_id [[flat]]; + float clip_distance [[clip_distance]][4]; +}; + +struct ShadowFragmentInput { + float4 position [[position]]; + float4 color [[flat]]; + uint shadow_id [[flat]]; }; vertex ShadowVertexOutput shadow_vertex( @@ -137,18 +159,20 @@ vertex ShadowVertexOutput shadow_vertex( bounds.size.width += 2. * margin; bounds.size.height += 2. * margin; - float4 device_position = to_device_position( - unit_vertex, bounds, shadow.content_mask.bounds, viewport_size); + float4 device_position = + to_device_position(unit_vertex, bounds, viewport_size); + float4 clip_distance = + distance_from_clip_rect(unit_vertex, bounds, shadow.content_mask.bounds); float4 color = hsla_to_rgba(shadow.color); return ShadowVertexOutput{ device_position, color, shadow_id, - }; + {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}}; } -fragment float4 shadow_fragment(ShadowVertexOutput input [[stage_in]], +fragment float4 shadow_fragment(ShadowFragmentInput input [[stage_in]], constant Shadow *shadows [[buffer(ShadowInputIndex_Shadows)]]) { Shadow shadow = shadows[input.shadow_id]; @@ -197,6 +221,13 @@ struct UnderlineVertexOutput { float4 position [[position]]; float4 color [[flat]]; uint underline_id [[flat]]; + float clip_distance [[clip_distance]][4]; +}; + +struct UnderlineFragmentInput { + float4 position [[position]]; + float4 color [[flat]]; + uint underline_id [[flat]]; }; vertex UnderlineVertexOutput underline_vertex( @@ -208,13 +239,18 @@ vertex UnderlineVertexOutput underline_vertex( float2 unit_vertex = unit_vertices[unit_vertex_id]; Underline underline = underlines[underline_id]; float4 device_position = - to_device_position(unit_vertex, underline.bounds, - underline.content_mask.bounds, viewport_size); + to_device_position(unit_vertex, underline.bounds, viewport_size); + float4 clip_distance = distance_from_clip_rect(unit_vertex, underline.bounds, + underline.content_mask.bounds); float4 color = hsla_to_rgba(underline.color); - return UnderlineVertexOutput{device_position, color, underline_id}; + return UnderlineVertexOutput{ + device_position, + color, + underline_id, + {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}}; } -fragment float4 underline_fragment(UnderlineVertexOutput input [[stage_in]], +fragment float4 underline_fragment(UnderlineFragmentInput input [[stage_in]], constant Underline *underlines [[buffer(UnderlineInputIndex_Underlines)]]) { Underline underline = underlines[input.underline_id]; @@ -244,7 +280,13 @@ struct MonochromeSpriteVertexOutput { float4 position [[position]]; float2 tile_position; float4 color [[flat]]; - uint sprite_id [[flat]]; + float clip_distance [[clip_distance]][4]; +}; + +struct MonochromeSpriteFragmentInput { + float4 position [[position]]; + float2 tile_position; + float4 color [[flat]]; }; vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex( @@ -255,32 +297,31 @@ vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex( [[buffer(SpriteInputIndex_ViewportSize)]], constant Size_DevicePixels *atlas_size [[buffer(SpriteInputIndex_AtlasTextureSize)]]) { - float2 unit_vertex = unit_vertices[unit_vertex_id]; MonochromeSprite sprite = sprites[sprite_id]; - // Don't apply content mask at the vertex level because we don't have time - // to make sampling from the texture match the mask. - float4 device_position = to_device_position(unit_vertex, sprite.bounds, - sprite.bounds, viewport_size); + float4 device_position = + to_device_position(unit_vertex, sprite.bounds, viewport_size); + float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds, + sprite.content_mask.bounds); float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size); float4 color = hsla_to_rgba(sprite.color); - return MonochromeSpriteVertexOutput{device_position, tile_position, color, - sprite_id}; + return MonochromeSpriteVertexOutput{ + device_position, + tile_position, + color, + {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}}; } fragment float4 monochrome_sprite_fragment( - MonochromeSpriteVertexOutput input [[stage_in]], + MonochromeSpriteFragmentInput input [[stage_in]], constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]], texture2d atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) { - MonochromeSprite sprite = sprites[input.sprite_id]; constexpr sampler atlas_texture_sampler(mag_filter::linear, min_filter::linear); float4 sample = atlas_texture.sample(atlas_texture_sampler, input.tile_position); - float clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds, - Corners_ScaledPixels{0., 0., 0., 0.}); float4 color = input.color; - color.a *= sample.a * saturate(0.5 - clip_distance); + color.a *= sample.a; return color; } @@ -288,6 +329,13 @@ struct PolychromeSpriteVertexOutput { float4 position [[position]]; float2 tile_position; uint sprite_id [[flat]]; + float clip_distance [[clip_distance]][4]; +}; + +struct PolychromeSpriteFragmentInput { + float4 position [[position]]; + float2 tile_position; + uint sprite_id [[flat]]; }; vertex PolychromeSpriteVertexOutput polychrome_sprite_vertex( @@ -301,17 +349,20 @@ vertex PolychromeSpriteVertexOutput polychrome_sprite_vertex( float2 unit_vertex = unit_vertices[unit_vertex_id]; PolychromeSprite sprite = sprites[sprite_id]; - // Don't apply content mask at the vertex level because we don't have time - // to make sampling from the texture match the mask. - float4 device_position = to_device_position(unit_vertex, sprite.bounds, - sprite.bounds, viewport_size); + float4 device_position = + to_device_position(unit_vertex, sprite.bounds, viewport_size); + float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds, + sprite.content_mask.bounds); float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size); - return PolychromeSpriteVertexOutput{device_position, tile_position, - sprite_id}; + return PolychromeSpriteVertexOutput{ + device_position, + tile_position, + sprite_id, + {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}}; } fragment float4 polychrome_sprite_fragment( - PolychromeSpriteVertexOutput input [[stage_in]], + PolychromeSpriteFragmentInput input [[stage_in]], constant PolychromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]], texture2d atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) { PolychromeSprite sprite = sprites[input.sprite_id]; @@ -319,11 +370,8 @@ fragment float4 polychrome_sprite_fragment( min_filter::linear); float4 sample = atlas_texture.sample(atlas_texture_sampler, input.tile_position); - float quad_distance = + float distance = quad_sdf(input.position.xy, sprite.bounds, sprite.corner_radii); - float clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds, - Corners_ScaledPixels{0., 0., 0., 0.}); - float distance = max(quad_distance, clip_distance); float4 color = sample; if (sprite.grayscale) { @@ -385,7 +433,6 @@ struct PathSpriteVertexOutput { float4 position [[position]]; float2 tile_position; float4 color [[flat]]; - uint sprite_id [[flat]]; }; vertex PathSpriteVertexOutput path_sprite_vertex( @@ -401,19 +448,17 @@ vertex PathSpriteVertexOutput path_sprite_vertex( PathSprite sprite = sprites[sprite_id]; // Don't apply content mask because it was already accounted for when // rasterizing the path. - float4 device_position = to_device_position(unit_vertex, sprite.bounds, - sprite.bounds, viewport_size); + float4 device_position = + to_device_position(unit_vertex, sprite.bounds, viewport_size); float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size); float4 color = hsla_to_rgba(sprite.color); - return PathSpriteVertexOutput{device_position, tile_position, color, - sprite_id}; + return PathSpriteVertexOutput{device_position, tile_position, color}; } fragment float4 path_sprite_fragment( PathSpriteVertexOutput input [[stage_in]], constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]], texture2d atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) { - PathSprite sprite = sprites[input.sprite_id]; constexpr sampler atlas_texture_sampler(mag_filter::linear, min_filter::linear); float4 sample = @@ -473,16 +518,10 @@ float4 hsla_to_rgba(Hsla hsla) { } float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds, - Bounds_ScaledPixels clip_bounds, constant Size_DevicePixels *input_viewport_size) { float2 position = unit_vertex * float2(bounds.size.width, bounds.size.height) + float2(bounds.origin.x, bounds.origin.y); - position.x = max(clip_bounds.origin.x, position.x); - position.x = min(clip_bounds.origin.x + clip_bounds.size.width, position.x); - position.y = max(clip_bounds.origin.y, position.y); - position.y = min(clip_bounds.origin.y + clip_bounds.size.height, position.y); - float2 viewport_size = float2((float)input_viewport_size->width, (float)input_viewport_size->height); float2 device_position = @@ -551,3 +590,14 @@ float blur_along_x(float x, float y, float sigma, float corner, 0.5 + 0.5 * erf((x + float2(-curved, curved)) * (sqrt(0.5) / sigma)); return integral.y - integral.x; } + +float4 distance_from_clip_rect(float2 unit_vertex, Bounds_ScaledPixels bounds, + Bounds_ScaledPixels clip_bounds) { + float2 position = + unit_vertex * float2(bounds.size.width, bounds.size.height) + + float2(bounds.origin.x, bounds.origin.y); + return float4(position.x - clip_bounds.origin.x, + clip_bounds.origin.x + clip_bounds.size.width - position.x, + position.y - clip_bounds.origin.y, + clip_bounds.origin.y + clip_bounds.size.height - position.y); +} diff --git a/crates/gpui2/src/scene.rs b/crates/gpui2/src/scene.rs index c176479a67f7f51ae47ae7efada8ee04102dee38..87e89adfa0e15d18512d2a1a748ea5a212a48489 100644 --- a/crates/gpui2/src/scene.rs +++ b/crates/gpui2/src/scene.rs @@ -27,8 +27,8 @@ pub(crate) struct SceneBuilder { polychrome_sprites: Vec, } -impl SceneBuilder { - pub fn new() -> SceneBuilder { +impl Default for SceneBuilder { + fn default() -> Self { SceneBuilder { layers_by_order: BTreeMap::new(), splitter: BspSplitter::new(), @@ -40,7 +40,9 @@ impl SceneBuilder { polychrome_sprites: Vec::new(), } } +} +impl SceneBuilder { pub fn build(&mut self) -> Scene { // Map each layer id to a float between 0. and 1., with 1. closer to the viewer. let mut layer_z_values = vec![0.; self.layers_by_order.len()]; diff --git a/crates/gpui2/src/style.rs b/crates/gpui2/src/style.rs index 5de173c2d4f729e90f9ba691bccb02bab664214a..6d3b31c1f7a2817cbf45b6b8c4221d045f6992ca 100644 --- a/crates/gpui2/src/style.rs +++ b/crates/gpui2/src/style.rs @@ -281,7 +281,7 @@ impl Style { pub fn paint(&self, bounds: Bounds, cx: &mut ViewContext) { let rem_size = cx.rem_size(); - cx.stack(0, |cx| { + cx.with_z_index(0, |cx| { cx.paint_shadows( bounds, self.corner_radii.to_pixels(bounds.size, rem_size), @@ -291,7 +291,7 @@ impl Style { let background_color = self.background.as_ref().and_then(Fill::color); if background_color.is_some() || self.is_border_visible() { - cx.stack(1, |cx| { + cx.with_z_index(1, |cx| { cx.paint_quad( bounds, self.corner_radii.to_pixels(bounds.size, rem_size), diff --git a/crates/gpui2/src/styled.rs b/crates/gpui2/src/styled.rs index 2bf3006d419261eb087da3a79ea9d461d1ca1837..3f1fa843f99abb689ca609940dd2cbc55fb16dbd 100644 --- a/crates/gpui2/src/styled.rs +++ b/crates/gpui2/src/styled.rs @@ -1,14 +1,19 @@ use crate::{ self as gpui, hsla, point, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle, DefiniteLength, Display, Fill, FlexDirection, Hsla, JustifyContent, Length, Position, - SharedString, StyleRefinement, Visibility, + SharedString, Style, StyleRefinement, Visibility, }; use crate::{BoxShadow, TextStyleRefinement}; +use refineable::Refineable; use smallvec::{smallvec, SmallVec}; pub trait Styled { fn style(&mut self) -> &mut StyleRefinement; + fn computed_style(&mut self) -> Style { + Style::default().refined(self.style().clone()) + } + gpui2_macros::style_helpers!(); /// Sets the size of the element to the full width and height. diff --git a/crates/gpui2/src/text_system/line.rs b/crates/gpui2/src/text_system/line.rs index ad70a79bb1e411fdaa4b0fddc0de39bdbf96e7af..707274ad33f26ce2212512c97df5527bd420ead7 100644 --- a/crates/gpui2/src/text_system/line.rs +++ b/crates/gpui2/src/text_system/line.rs @@ -74,7 +74,6 @@ impl Line { glyph_origin.y += line_height; } prev_glyph_position = glyph.position; - let glyph_origin = glyph_origin + baseline_offset; let mut finished_underline: Option<(Point, UnderlineStyle)> = None; if glyph.index >= run_end { @@ -125,14 +124,14 @@ impl Line { if max_glyph_bounds.intersects(&content_mask.bounds) { if glyph.is_emoji { cx.paint_emoji( - glyph_origin, + glyph_origin + baseline_offset, run.font_id, glyph.id, self.layout.layout.font_size, )?; } else { cx.paint_glyph( - glyph_origin, + glyph_origin + baseline_offset, run.font_id, glyph.id, self.layout.layout.font_size, diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 5b0349c7d48ff9a84478b624205b65c9c1be2c75..1474165742627ced402bb81d6fab5c15f0caa887 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -2,13 +2,14 @@ use crate::{ px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, DevicePixels, DispatchContext, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId, - GlobalElementId, GlyphId, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, - KeyMatcher, Keystroke, LayoutId, Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, - MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, - PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, - RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, - Style, SubscriberSet, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, - VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, + GlobalElementId, GlyphId, Hsla, ImageData, InputEvent, InputHandler, IsZero, KeyListener, + KeyMatch, KeyMatcher, Keystroke, LayoutId, Model, ModelContext, Modifiers, MonochromeSprite, + MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, + PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, PromptLevel, + Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, + SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, Subscription, + TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakView, + WindowBounds, WindowInputHandler, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Result}; use collections::HashMap; @@ -131,7 +132,12 @@ impl FocusHandle { if self.id == ancestor_id { return true; } else { - ancestor = cx.window.focus_parents_by_child.get(&ancestor_id).copied(); + ancestor = cx + .window + .current_frame + .focus_parents_by_child + .get(&ancestor_id) + .copied(); } } false @@ -174,34 +180,39 @@ pub struct Window { pub(crate) layout_engine: TaffyLayoutEngine, pub(crate) root_view: Option, pub(crate) element_id_stack: GlobalElementId, - prev_frame_element_states: HashMap, - element_states: HashMap, - prev_frame_key_matchers: HashMap, - key_matchers: HashMap, - z_index_stack: StackingOrder, - content_mask_stack: Vec>, - element_offset_stack: Vec>, - mouse_listeners: HashMap>, - key_dispatch_stack: Vec, - freeze_key_dispatch_stack: bool, - focus_stack: Vec, - focus_parents_by_child: HashMap, - pub(crate) focus_listeners: Vec, + pub(crate) previous_frame: Frame, + pub(crate) current_frame: Frame, pub(crate) focus_handles: Arc>>, default_prevented: bool, mouse_position: Point, requested_cursor_style: Option, + requested_input_handler: Option>, scale_factor: f32, bounds: WindowBounds, bounds_observers: SubscriberSet<(), AnyObserver>, active: bool, activation_observers: SubscriberSet<(), AnyObserver>, - pub(crate) scene_builder: SceneBuilder, pub(crate) dirty: bool, pub(crate) last_blur: Option>, pub(crate) focus: Option, } +#[derive(Default)] +pub(crate) struct Frame { + element_states: HashMap, + key_matchers: HashMap, + mouse_listeners: HashMap>, + pub(crate) focus_listeners: Vec, + key_dispatch_stack: Vec, + freeze_key_dispatch_stack: bool, + focus_parents_by_child: HashMap, + pub(crate) scene_builder: SceneBuilder, + z_index_stack: StackingOrder, + content_mask_stack: Vec>, + element_offset_stack: Vec>, + focus_stack: Vec, +} + impl Window { pub(crate) fn new( handle: AnyWindowHandle, @@ -253,7 +264,7 @@ impl Window { handle .update(&mut cx, |_, cx| cx.dispatch_event(event)) .log_err() - .unwrap_or(true) + .unwrap_or(false) }) }); @@ -268,29 +279,18 @@ impl Window { layout_engine: TaffyLayoutEngine::new(), root_view: None, element_id_stack: GlobalElementId::default(), - prev_frame_element_states: HashMap::default(), - element_states: HashMap::default(), - prev_frame_key_matchers: HashMap::default(), - key_matchers: HashMap::default(), - z_index_stack: StackingOrder(SmallVec::new()), - content_mask_stack: Vec::new(), - element_offset_stack: Vec::new(), - mouse_listeners: HashMap::default(), - key_dispatch_stack: Vec::new(), - freeze_key_dispatch_stack: false, - focus_stack: Vec::new(), - focus_parents_by_child: HashMap::default(), - focus_listeners: Vec::new(), + previous_frame: Frame::default(), + current_frame: Frame::default(), focus_handles: Arc::new(RwLock::new(SlotMap::with_key())), default_prevented: true, mouse_position, requested_cursor_style: None, + requested_input_handler: None, scale_factor, bounds, bounds_observers: SubscriberSet::new(), active: false, activation_observers: SubscriberSet::new(), - scene_builder: SceneBuilder::new(), dirty: true, last_blur: None, focus: None, @@ -560,6 +560,12 @@ impl<'a> WindowContext<'a> { .request_measured_layout(style, rem_size, measure) } + pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size) { + self.window + .layout_engine + .compute_layout(layout_id, available_space) + } + /// Obtain the bounds computed for the given LayoutId relative to the window. This method should not /// be invoked until the paint phase begins, and will usually be invoked by GPUI itself automatically /// in order to pass your element its `Bounds` automatically. @@ -605,6 +611,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. @@ -654,8 +664,9 @@ impl<'a> WindowContext<'a> { &mut self, handler: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static, ) { - let order = self.window.z_index_stack.clone(); + let order = self.window.current_frame.z_index_stack.clone(); self.window + .current_frame .mouse_listeners .entry(TypeId::of::()) .or_default() @@ -679,9 +690,9 @@ impl<'a> WindowContext<'a> { /// Called during painting to invoke the given closure in a new stacking context. The given /// z-index is interpreted relative to the previous call to `stack`. pub fn stack(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R { - self.window.z_index_stack.push(z_index); + self.window.current_frame.z_index_stack.push(z_index); let result = f(self); - self.window.z_index_stack.pop(); + self.window.current_frame.z_index_stack.pop(); result } @@ -699,8 +710,8 @@ impl<'a> WindowContext<'a> { let mut shadow_bounds = bounds; shadow_bounds.origin += shadow.offset; shadow_bounds.dilate(shadow.spread_radius); - window.scene_builder.insert( - &window.z_index_stack, + window.current_frame.scene_builder.insert( + &window.current_frame.z_index_stack, Shadow { order: 0, bounds: shadow_bounds.scale(scale_factor), @@ -727,8 +738,8 @@ impl<'a> WindowContext<'a> { let content_mask = self.content_mask(); let window = &mut *self.window; - window.scene_builder.insert( - &window.z_index_stack, + window.current_frame.scene_builder.insert( + &window.current_frame.z_index_stack, Quad { order: 0, bounds: bounds.scale(scale_factor), @@ -748,9 +759,10 @@ impl<'a> WindowContext<'a> { path.content_mask = content_mask; path.color = color.into(); let window = &mut *self.window; - window - .scene_builder - .insert(&window.z_index_stack, path.scale(scale_factor)); + window.current_frame.scene_builder.insert( + &window.current_frame.z_index_stack, + path.scale(scale_factor), + ); } /// Paint an underline into the scene for the current frame at the current z-index. @@ -772,8 +784,8 @@ impl<'a> WindowContext<'a> { }; let content_mask = self.content_mask(); let window = &mut *self.window; - window.scene_builder.insert( - &window.z_index_stack, + window.current_frame.scene_builder.insert( + &window.current_frame.z_index_stack, Underline { order: 0, bounds: bounds.scale(scale_factor), @@ -787,6 +799,7 @@ impl<'a> WindowContext<'a> { } /// Paint a monochrome (non-emoji) glyph into the scene for the current frame at the current z-index. + /// The y component of the origin is the baseline of the glyph. pub fn paint_glyph( &mut self, origin: Point, @@ -825,8 +838,8 @@ impl<'a> WindowContext<'a> { }; let content_mask = self.content_mask().scale(scale_factor); let window = &mut *self.window; - window.scene_builder.insert( - &window.z_index_stack, + window.current_frame.scene_builder.insert( + &window.current_frame.z_index_stack, MonochromeSprite { order: 0, bounds, @@ -840,6 +853,7 @@ impl<'a> WindowContext<'a> { } /// Paint an emoji glyph into the scene for the current frame at the current z-index. + /// The y component of the origin is the baseline of the glyph. pub fn paint_emoji( &mut self, origin: Point, @@ -875,8 +889,8 @@ impl<'a> WindowContext<'a> { let content_mask = self.content_mask().scale(scale_factor); let window = &mut *self.window; - window.scene_builder.insert( - &window.z_index_stack, + window.current_frame.scene_builder.insert( + &window.current_frame.z_index_stack, PolychromeSprite { order: 0, bounds, @@ -917,8 +931,8 @@ impl<'a> WindowContext<'a> { let content_mask = self.content_mask().scale(scale_factor); let window = &mut *self.window; - window.scene_builder.insert( - &window.z_index_stack, + window.current_frame.scene_builder.insert( + &window.current_frame.z_index_stack, MonochromeSprite { order: 0, bounds, @@ -953,8 +967,8 @@ impl<'a> WindowContext<'a> { let corner_radii = corner_radii.scale(scale_factor); let window = &mut *self.window; - window.scene_builder.insert( - &window.z_index_stack, + window.current_frame.scene_builder.insert( + &window.current_frame.z_index_stack, PolychromeSprite { order: 0, bounds, @@ -999,7 +1013,7 @@ impl<'a> WindowContext<'a> { } self.window.root_view = Some(root_view); - let scene = self.window.scene_builder.build(); + let scene = self.window.current_frame.scene_builder.build(); self.window.platform_window.draw(scene); let cursor_style = self @@ -1008,43 +1022,28 @@ impl<'a> WindowContext<'a> { .take() .unwrap_or(CursorStyle::Arrow); self.platform.set_cursor_style(cursor_style); + if let Some(handler) = self.window.requested_input_handler.take() { + self.window.platform_window.set_input_handler(handler); + } self.window.dirty = false; } + /// Rotate the current frame and the previous frame, then clear the current frame. + /// We repopulate all state in the current frame during each paint. fn start_frame(&mut self) { self.text_system().start_frame(); let window = &mut *self.window; - - // Move the current frame element states to the previous frame. - // The new empty element states map will be populated for any element states we - // reference during the upcoming frame. - mem::swap( - &mut window.element_states, - &mut window.prev_frame_element_states, - ); - window.element_states.clear(); - - // Make the current key matchers the previous, and then clear the current. - // An empty key matcher map will be created for every identified element in the - // upcoming frame. - mem::swap( - &mut window.key_matchers, - &mut window.prev_frame_key_matchers, - ); - window.key_matchers.clear(); - - // Clear mouse event listeners, because elements add new element listeners - // when the upcoming frame is painted. - window.mouse_listeners.values_mut().for_each(Vec::clear); - - // Clear focus state, because we determine what is focused when the new elements - // in the upcoming frame are initialized. - window.focus_listeners.clear(); - window.key_dispatch_stack.clear(); - window.focus_parents_by_child.clear(); - window.freeze_key_dispatch_stack = false; + mem::swap(&mut window.previous_frame, &mut window.current_frame); + let frame = &mut window.current_frame; + frame.element_states.clear(); + frame.key_matchers.clear(); + frame.mouse_listeners.values_mut().for_each(Vec::clear); + frame.focus_listeners.clear(); + frame.key_dispatch_stack.clear(); + frame.focus_parents_by_child.clear(); + frame.freeze_key_dispatch_stack = false; } /// Dispatch a mouse or keyboard event on the window. @@ -1108,6 +1107,7 @@ impl<'a> WindowContext<'a> { if let Some(any_mouse_event) = event.mouse_event() { if let Some(mut handlers) = self .window + .current_frame .mouse_listeners .remove(&any_mouse_event.type_id()) { @@ -1142,17 +1142,19 @@ impl<'a> WindowContext<'a> { // Just in case any handlers added new handlers, which is weird, but possible. handlers.extend( self.window + .current_frame .mouse_listeners .get_mut(&any_mouse_event.type_id()) .into_iter() .flat_map(|handlers| handlers.drain(..)), ); self.window + .current_frame .mouse_listeners .insert(any_mouse_event.type_id(), handlers); } } else if let Some(any_key_event) = event.keyboard_event() { - let key_dispatch_stack = mem::take(&mut self.window.key_dispatch_stack); + let key_dispatch_stack = mem::take(&mut self.window.current_frame.key_dispatch_stack); let key_event_type = any_key_event.type_id(); let mut context_stack = SmallVec::<[&DispatchContext; 16]>::new(); @@ -1212,10 +1214,10 @@ impl<'a> WindowContext<'a> { } drop(context_stack); - self.window.key_dispatch_stack = key_dispatch_stack; + self.window.current_frame.key_dispatch_stack = key_dispatch_stack; } - true + !self.app.propagate_event } /// Attempt to map a keystroke to an action based on the keymap. @@ -1227,13 +1229,14 @@ impl<'a> WindowContext<'a> { ) -> KeyMatch { let key_match = self .window + .current_frame .key_matchers .get_mut(element_id) .unwrap() .match_keystroke(keystroke, context_stack); if key_match.is_some() { - for matcher in self.window.key_matchers.values_mut() { + for matcher in self.window.current_frame.key_matchers.values_mut() { matcher.clear_pending(); } } @@ -1493,11 +1496,12 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { window.element_id_stack.push(id.into()); let global_id = window.element_id_stack.clone(); - if window.key_matchers.get(&global_id).is_none() { - window.key_matchers.insert( + if window.current_frame.key_matchers.get(&global_id).is_none() { + window.current_frame.key_matchers.insert( global_id.clone(), window - .prev_frame_key_matchers + .previous_frame + .key_matchers .remove(&global_id) .unwrap_or_else(|| KeyMatcher::new(keymap)), ); @@ -1517,9 +1521,12 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { f: impl FnOnce(&mut Self) -> R, ) -> R { let mask = mask.intersect(&self.content_mask()); - self.window_mut().content_mask_stack.push(mask); + self.window_mut() + .current_frame + .content_mask_stack + .push(mask); let result = f(self); - self.window_mut().content_mask_stack.pop(); + self.window_mut().current_frame.content_mask_stack.pop(); result } @@ -1535,15 +1542,19 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { }; let offset = self.element_offset() + offset; - self.window_mut().element_offset_stack.push(offset); + self.window_mut() + .current_frame + .element_offset_stack + .push(offset); let result = f(self); - self.window_mut().element_offset_stack.pop(); + self.window_mut().current_frame.element_offset_stack.pop(); result } /// Obtain the current element offset. fn element_offset(&self) -> Point { self.window() + .current_frame .element_offset_stack .last() .copied() @@ -1565,9 +1576,15 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { self.with_element_id(id, |global_id, cx| { if let Some(any) = cx .window_mut() + .current_frame .element_states .remove(&global_id) - .or_else(|| cx.window_mut().prev_frame_element_states.remove(&global_id)) + .or_else(|| { + cx.window_mut() + .previous_frame + .element_states + .remove(&global_id) + }) { // Using the extra inner option to avoid needing to reallocate a new box. let mut state_box = any @@ -1578,11 +1595,15 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { .expect("element state is already on the stack"); let (result, state) = f(Some(state), cx); state_box.replace(state); - cx.window_mut().element_states.insert(global_id, state_box); + cx.window_mut() + .current_frame + .element_states + .insert(global_id, state_box); result } else { let (result, state) = f(None, cx); cx.window_mut() + .current_frame .element_states .insert(global_id, Box::new(Some(state))); result @@ -1610,6 +1631,7 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { /// Obtain the current content mask. fn content_mask(&self) -> ContentMask { self.window() + .current_frame .content_mask_stack .last() .cloned() @@ -1693,10 +1715,10 @@ impl<'a, V: 'static> ViewContext<'a, V> { &mut self.window_cx } - pub fn stack(&mut self, order: u32, f: impl FnOnce(&mut Self) -> R) -> R { - self.window.z_index_stack.push(order); + pub fn with_z_index(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R { + self.window.current_frame.z_index_stack.push(z_index); let result = f(self); - self.window.z_index_stack.pop(); + self.window.current_frame.z_index_stack.pop(); result } @@ -1851,11 +1873,14 @@ impl<'a, V: 'static> ViewContext<'a, V> { listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext) + 'static, ) { let handle = self.view().downgrade(); - self.window.focus_listeners.push(Box::new(move |event, cx| { - handle - .update(cx, |view, cx| listener(view, event, cx)) - .log_err(); - })); + self.window + .current_frame + .focus_listeners + .push(Box::new(move |event, cx| { + handle + .update(cx, |view, cx| listener(view, event, cx)) + .log_err(); + })); } pub fn with_key_listeners( @@ -1863,8 +1888,8 @@ impl<'a, V: 'static> ViewContext<'a, V> { key_listeners: impl IntoIterator)>, f: impl FnOnce(&mut Self) -> R, ) -> R { - let old_stack_len = self.window.key_dispatch_stack.len(); - if !self.window.freeze_key_dispatch_stack { + let old_stack_len = self.window.current_frame.key_dispatch_stack.len(); + if !self.window.current_frame.freeze_key_dispatch_stack { for (event_type, listener) in key_listeners { let handle = self.view().downgrade(); let listener = Box::new( @@ -1880,19 +1905,22 @@ impl<'a, V: 'static> ViewContext<'a, V> { .flatten() }, ); - self.window - .key_dispatch_stack - .push(KeyDispatchStackFrame::Listener { + self.window.current_frame.key_dispatch_stack.push( + KeyDispatchStackFrame::Listener { event_type, listener, - }); + }, + ); } } let result = f(self); - if !self.window.freeze_key_dispatch_stack { - self.window.key_dispatch_stack.truncate(old_stack_len); + if !self.window.current_frame.freeze_key_dispatch_stack { + self.window + .current_frame + .key_dispatch_stack + .truncate(old_stack_len); } result @@ -1907,16 +1935,17 @@ impl<'a, V: 'static> ViewContext<'a, V> { return f(self); } - if !self.window.freeze_key_dispatch_stack { + if !self.window.current_frame.freeze_key_dispatch_stack { self.window + .current_frame .key_dispatch_stack .push(KeyDispatchStackFrame::Context(context)); } let result = f(self); - if !self.window.freeze_key_dispatch_stack { - self.window.key_dispatch_stack.pop(); + if !self.window.previous_frame.freeze_key_dispatch_stack { + self.window.previous_frame.key_dispatch_stack.pop(); } result @@ -1927,20 +1956,21 @@ impl<'a, V: 'static> ViewContext<'a, V> { focus_handle: FocusHandle, f: impl FnOnce(&mut Self) -> R, ) -> R { - if let Some(parent_focus_id) = self.window.focus_stack.last().copied() { + if let Some(parent_focus_id) = self.window.current_frame.focus_stack.last().copied() { self.window + .current_frame .focus_parents_by_child .insert(focus_handle.id, parent_focus_id); } - self.window.focus_stack.push(focus_handle.id); + self.window.current_frame.focus_stack.push(focus_handle.id); if Some(focus_handle.id) == self.window.focus { - self.window.freeze_key_dispatch_stack = true; + self.window.current_frame.freeze_key_dispatch_stack = true; } let result = f(self); - self.window.focus_stack.pop(); + self.window.current_frame.focus_stack.pop(); result } @@ -1995,6 +2025,19 @@ impl<'a, V: 'static> ViewContext<'a, V> { } } +impl ViewContext<'_, V> +where + V: InputHandler + 'static, +{ + pub fn handle_text_input(&mut self) { + self.window.requested_input_handler = Some(Box::new(WindowInputHandler { + cx: self.app.this.clone(), + window: self.window_handle(), + handler: self.view().downgrade(), + })); + } +} + impl ViewContext<'_, V> where V: EventEmitter, diff --git a/crates/gpui2/src/window_input_handler.rs b/crates/gpui2/src/window_input_handler.rs new file mode 100644 index 0000000000000000000000000000000000000000..caae5838ce46171a1584b272d7e916d100e13881 --- /dev/null +++ b/crates/gpui2/src/window_input_handler.rs @@ -0,0 +1,89 @@ +use crate::{AnyWindowHandle, AppCell, Context, PlatformInputHandler, ViewContext, WeakView}; +use std::{ops::Range, rc::Weak}; + +pub struct WindowInputHandler +where + V: InputHandler, +{ + pub cx: Weak, + pub window: AnyWindowHandle, + pub handler: WeakView, +} + +impl PlatformInputHandler for WindowInputHandler { + fn selected_text_range(&self) -> Option> { + self.update(|view, cx| view.selected_text_range(cx)) + .flatten() + } + + fn marked_text_range(&self) -> Option> { + self.update(|view, cx| view.marked_text_range(cx)).flatten() + } + + fn text_for_range(&self, range_utf16: std::ops::Range) -> Option { + self.update(|view, cx| view.text_for_range(range_utf16, cx)) + .flatten() + } + + fn replace_text_in_range( + &mut self, + replacement_range: Option>, + text: &str, + ) { + self.update(|view, cx| view.replace_text_in_range(replacement_range, text, cx)); + } + + fn replace_and_mark_text_in_range( + &mut self, + range_utf16: Option>, + new_text: &str, + new_selected_range: Option>, + ) { + self.update(|view, cx| { + view.replace_and_mark_text_in_range(range_utf16, new_text, new_selected_range, cx) + }); + } + + fn unmark_text(&mut self) { + self.update(|view, cx| view.unmark_text(cx)); + } + + fn bounds_for_range(&self, range_utf16: std::ops::Range) -> Option> { + self.update(|view, cx| view.bounds_for_range(range_utf16, cx)) + .flatten() + } +} + +impl WindowInputHandler { + fn update(&self, f: impl FnOnce(&mut V, &mut ViewContext) -> T) -> Option { + let cx = self.cx.upgrade()?; + let mut cx = cx.borrow_mut(); + cx.update_window(self.window, |_, cx| self.handler.update(cx, f).ok()) + .ok()? + } +} + +pub trait InputHandler: Sized { + fn text_for_range(&self, range: Range, cx: &mut ViewContext) -> Option; + fn selected_text_range(&self, cx: &mut ViewContext) -> Option>; + fn marked_text_range(&self, cx: &mut ViewContext) -> Option>; + fn unmark_text(&mut self, cx: &mut ViewContext); + fn replace_text_in_range( + &mut self, + range: Option>, + text: &str, + cx: &mut ViewContext, + ); + fn replace_and_mark_text_in_range( + &mut self, + range: Option>, + new_text: &str, + new_selected_range: Option>, + cx: &mut ViewContext, + ); + fn bounds_for_range( + &self, + range_utf16: std::ops::Range, + cx: &mut ViewContext, + ) -> Option>; +} diff --git a/crates/gpui2_macros/src/action.rs b/crates/gpui2_macros/src/action.rs new file mode 100644 index 0000000000000000000000000000000000000000..66302f3fc0b6ee519e349376ca65fda8b2e4a783 --- /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 2e0c0547f79e29f1e3bcc5bfcc43cfed516f7df9..80b67e1a12393d76d1ecffc5a342dc8eaefacc40 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 0000000000000000000000000000000000000000..68c39ad9bd7347be7d01e139e37b5a14c8c05d92 --- /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/menu2/Cargo.toml b/crates/menu2/Cargo.toml index 9bf61db82c6c01d30c12e9c01844c3db464df40f..0585f988858279552ce09a6abde7addd99c387fb 100644 --- a/crates/menu2/Cargo.toml +++ b/crates/menu2/Cargo.toml @@ -10,3 +10,4 @@ doctest = false [dependencies] gpui = { package = "gpui2", path = "../gpui2" } +serde = { workspace = true } diff --git a/crates/menu2/src/menu2.rs b/crates/menu2/src/menu2.rs index decd4aca22be0bda9c9c5ceae87346213e9d4392..e5e8242f37ecc92090c08718f55763a94426a0f9 100644 --- a/crates/menu2/src/menu2.rs +++ b/crates/menu2/src/menu2.rs @@ -1,25 +1,12 @@ -// todo!(use actions! macro) - -#[derive(Clone, Debug, Default, PartialEq)] -pub struct Cancel; - -#[derive(Clone, Debug, Default, PartialEq)] -pub struct Confirm; - -#[derive(Clone, Debug, Default, PartialEq)] -pub struct SecondaryConfirm; - -#[derive(Clone, Debug, Default, PartialEq)] -pub struct SelectPrev; - -#[derive(Clone, Debug, Default, PartialEq)] -pub struct SelectNext; - -#[derive(Clone, Debug, Default, PartialEq)] -pub struct SelectFirst; - -#[derive(Clone, Debug, Default, PartialEq)] -pub struct SelectLast; - -#[derive(Clone, Debug, Default, PartialEq)] -pub struct ShowContextMenu; +use gpui::actions; + +actions!( + Cancel, + Confirm, + SecondaryConfirm, + SelectPrev, + SelectNext, + SelectFirst, + SelectLast, + ShowContextMenu +); diff --git a/crates/picker2/Cargo.toml b/crates/picker2/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..8d88c25366fa52bcaa20018503608d3dd86ebbf2 --- /dev/null +++ b/crates/picker2/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "picker2" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/picker2.rs" +doctest = false + +[dependencies] +editor = { package = "editor2", path = "../editor2" } +gpui = { package = "gpui2", path = "../gpui2" } +menu = { package = "menu2", path = "../menu2" } +settings = { package = "settings2", path = "../settings2" } +util = { path = "../util" } +theme = { package = "theme2", path = "../theme2" } +workspace = { package = "workspace2", path = "../workspace2" } + +parking_lot.workspace = true + +[dev-dependencies] +editor = { package = "editor2", path = "../editor2", features = ["test-support"] } +gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } +serde_json.workspace = true +workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] } +ctor.workspace = true +env_logger.workspace = true diff --git a/crates/picker2/src/picker2.rs b/crates/picker2/src/picker2.rs new file mode 100644 index 0000000000000000000000000000000000000000..075cf10ff6811eeb2ce8f8a82f31f9799c0a4a00 --- /dev/null +++ b/crates/picker2/src/picker2.rs @@ -0,0 +1,163 @@ +use editor::Editor; +use gpui::{ + div, uniform_list, Component, Div, FocusEnabled, ParentElement, Render, StatefulInteractivity, + StatelessInteractive, Styled, Task, UniformListScrollHandle, View, ViewContext, VisualContext, + WindowContext, +}; +use std::cmp; + +pub struct Picker { + pub delegate: D, + scroll_handle: UniformListScrollHandle, + editor: View, + pending_update_matches: Option>>, +} + +pub trait PickerDelegate: Sized + 'static { + type ListItem: Component>; + + fn match_count(&self) -> usize; + fn selected_index(&self) -> usize; + fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext>); + + // fn placeholder_text(&self) -> Arc; + fn update_matches(&mut self, query: String, cx: &mut ViewContext>) -> Task<()>; + + fn confirm(&mut self, secondary: bool, cx: &mut ViewContext>); + fn dismissed(&mut self, cx: &mut ViewContext>); + + fn render_match( + &self, + ix: usize, + selected: bool, + cx: &mut ViewContext>, + ) -> Self::ListItem; +} + +impl Picker { + pub fn new(delegate: D, cx: &mut ViewContext) -> Self { + let editor = cx.build_view(|cx| Editor::single_line(cx)); + cx.subscribe(&editor, Self::on_input_editor_event).detach(); + Self { + delegate, + scroll_handle: UniformListScrollHandle::new(), + pending_update_matches: None, + editor, + } + } + + pub fn focus(&self, cx: &mut WindowContext) { + self.editor.update(cx, |editor, cx| editor.focus(cx)); + } + + fn select_next(&mut self, _: &menu::SelectNext, cx: &mut ViewContext) { + let count = self.delegate.match_count(); + if count > 0 { + let index = self.delegate.selected_index(); + let ix = cmp::min(index + 1, count - 1); + self.delegate.set_selected_index(ix, cx); + self.scroll_handle.scroll_to_item(ix); + } + } + + fn select_prev(&mut self, _: &menu::SelectPrev, cx: &mut ViewContext) { + let count = self.delegate.match_count(); + if count > 0 { + let index = self.delegate.selected_index(); + let ix = index.saturating_sub(1); + self.delegate.set_selected_index(ix, cx); + self.scroll_handle.scroll_to_item(ix); + } + } + + fn select_first(&mut self, _: &menu::SelectFirst, cx: &mut ViewContext) { + let count = self.delegate.match_count(); + if count > 0 { + self.delegate.set_selected_index(0, cx); + self.scroll_handle.scroll_to_item(0); + } + } + + fn select_last(&mut self, _: &menu::SelectLast, cx: &mut ViewContext) { + let count = self.delegate.match_count(); + if count > 0 { + self.delegate.set_selected_index(count - 1, cx); + self.scroll_handle.scroll_to_item(count - 1); + } + } + + fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext) { + self.delegate.dismissed(cx); + } + + fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext) { + self.delegate.confirm(false, cx); + } + + fn secondary_confirm(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext) { + self.delegate.confirm(true, cx); + } + + fn on_input_editor_event( + &mut self, + _: View, + event: &editor::Event, + cx: &mut ViewContext, + ) { + if let editor::Event::BufferEdited = event { + let query = self.editor.read(cx).text(cx); + self.update_matches(query, cx); + } + } + + pub fn update_matches(&mut self, query: String, cx: &mut ViewContext) { + let update = self.delegate.update_matches(query, cx); + self.matches_updated(cx); + self.pending_update_matches = Some(cx.spawn(|this, mut cx| async move { + update.await; + this.update(&mut cx, |this, cx| { + this.matches_updated(cx); + }) + .ok() + })); + } + + fn matches_updated(&mut self, cx: &mut ViewContext) { + let index = self.delegate.selected_index(); + self.scroll_handle.scroll_to_item(index); + self.pending_update_matches = None; + cx.notify(); + } +} + +impl Render for Picker { + type Element = Div, FocusEnabled>; + + fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { + div() + .context("picker") + .id("picker-container") + .focusable() + .size_full() + .on_action(Self::select_next) + .on_action(Self::select_prev) + .on_action(Self::select_first) + .on_action(Self::select_last) + .on_action(Self::cancel) + .on_action(Self::confirm) + .on_action(Self::secondary_confirm) + .child(self.editor.clone()) + .child( + uniform_list("candidates", self.delegate.match_count(), { + move |this: &mut Self, visible_range, cx| { + let selected_ix = this.delegate.selected_index(); + visible_range + .map(|ix| this.delegate.render_match(ix, ix == selected_ix, cx)) + .collect() + } + }) + .track_scroll(self.scroll_handle.clone()) + .size_full(), + ) + } +} diff --git a/crates/settings2/src/keymap_file.rs b/crates/settings2/src/keymap_file.rs index 93635935cbd746b7e6fc3d96294e5895ee5c01e4..9f279864ee739243c9c475395aef6d228582fe0a 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/Cargo.toml b/crates/storybook2/Cargo.toml index 1f3a0b33cc2254671069d8e8c1f0fb0e453db9ae..7c6776c9309fd68352b7e18b846af8cd4e8fbdae 100644 --- a/crates/storybook2/Cargo.toml +++ b/crates/storybook2/Cargo.toml @@ -13,9 +13,12 @@ anyhow.workspace = true # TODO: Remove after diagnosing stack overflow. backtrace-on-stack-overflow = "0.3.0" clap = { version = "4.4", features = ["derive", "string"] } +editor = { package = "editor2", path = "../editor2" } chrono = "0.4" +fuzzy = { package = "fuzzy2", path = "../fuzzy2" } gpui = { package = "gpui2", path = "../gpui2" } itertools = "0.11.0" +language = { package = "language2", path = "../language2" } log.workspace = true rust-embed.workspace = true serde.workspace = true @@ -25,8 +28,10 @@ smallvec.workspace = true strum = { version = "0.25.0", features = ["derive"] } theme = { path = "../theme" } theme2 = { path = "../theme2" } +menu = { package = "menu2", path = "../menu2" } ui = { package = "ui2", path = "../ui2", features = ["stories"] } util = { path = "../util" } +picker = { package = "picker2", path = "../picker2" } [dev-dependencies] gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } diff --git a/crates/storybook2/src/stories.rs b/crates/storybook2/src/stories.rs index 3d8a332fb93b16ae8480acd32e97bbd4a0c5ab25..2620e68d6c102600637f41551e5d6f65b96451e1 100644 --- a/crates/storybook2/src/stories.rs +++ b/crates/storybook2/src/stories.rs @@ -1,6 +1,7 @@ mod colors; mod focus; mod kitchen_sink; +mod picker; mod scroll; mod text; mod z_index; @@ -8,6 +9,7 @@ mod z_index; pub use colors::*; pub use focus::*; pub use kitchen_sink::*; +pub use picker::*; pub use scroll::*; pub use text::*; pub use z_index::*; diff --git a/crates/storybook2/src/stories/focus.rs b/crates/storybook2/src/stories/focus.rs index 0f580d0cbf171ef32b2c83465ad8601785304227..984ee421db3b357545de400547bd5057365610da 100644 --- a/crates/storybook2/src/stories/focus.rs +++ b/crates/storybook2/src/stories/focus.rs @@ -1,6 +1,6 @@ use gpui::{ actions, div, Div, FocusEnabled, Focusable, KeyBinding, ParentElement, Render, - StatefulInteraction, StatelessInteractive, Styled, View, VisualContext, WindowContext, + StatefulInteractivity, StatelessInteractive, Styled, View, VisualContext, WindowContext, }; use theme2::ActiveTheme; @@ -15,24 +15,22 @@ 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 {}) } } impl Render for FocusStory { - type Element = Div, FocusEnabled>; + type Element = Div, FocusEnabled>; fn render(&mut self, cx: &mut gpui::ViewContext) -> Self::Element { let theme = cx.theme(); - let color_1 = theme.styles.git.created; - let color_2 = theme.styles.git.modified; - let color_3 = theme.styles.git.deleted; - let color_4 = theme.styles.git.conflict; - let color_5 = theme.styles.git.ignored; - let color_6 = theme.styles.git.renamed; + let color_1 = theme.status().created; + let color_2 = theme.status().modified; + let color_3 = theme.status().deleted; + let color_4 = theme.status().conflict; + let color_5 = theme.status().ignored; + let color_6 = theme.status().renamed; let child_1 = cx.focus_handle(); let child_2 = cx.focus_handle(); @@ -40,20 +38,18 @@ impl Render for FocusStory { .id("parent") .focusable() .context("parent") - .on_action(|_, action: &ActionA, phase, cx| { - println!("Action A dispatched on parent during {:?}", phase); + .on_action(|_, action: &ActionA, cx| { + println!("Action A dispatched on parent during"); }) - .on_action(|_, action: &ActionB, phase, cx| { - println!("Action B dispatched on parent during {:?}", phase); + .on_action(|_, action: &ActionB, cx| { + println!("Action B dispatched on parent during"); }) .on_focus(|_, _, _| println!("Parent focused")) .on_blur(|_, _, _| println!("Parent blurred")) .on_focus_in(|_, _, _| println!("Parent focus_in")) .on_focus_out(|_, _, _| println!("Parent focus_out")) - .on_key_down(|_, event, phase, _| { - println!("Key down on parent {:?} {:?}", phase, event) - }) - .on_key_up(|_, event, phase, _| println!("Key up on parent {:?} {:?}", phase, event)) + .on_key_down(|_, event, phase, _| println!("Key down on parent {:?}", event)) + .on_key_up(|_, event, phase, _| println!("Key up on parent {:?}", event)) .size_full() .bg(color_1) .focus(|style| style.bg(color_2)) @@ -62,8 +58,8 @@ impl Render for FocusStory { div() .track_focus(&child_1) .context("child-1") - .on_action(|_, action: &ActionB, phase, cx| { - println!("Action B dispatched on child 1 during {:?}", phase); + .on_action(|_, action: &ActionB, cx| { + println!("Action B dispatched on child 1 during"); }) .w_full() .h_6() @@ -74,20 +70,16 @@ impl Render for FocusStory { .on_blur(|_, _, _| println!("Child 1 blurred")) .on_focus_in(|_, _, _| println!("Child 1 focus_in")) .on_focus_out(|_, _, _| println!("Child 1 focus_out")) - .on_key_down(|_, event, phase, _| { - println!("Key down on child 1 {:?} {:?}", phase, event) - }) - .on_key_up(|_, event, phase, _| { - println!("Key up on child 1 {:?} {:?}", phase, event) - }) + .on_key_down(|_, event, phase, _| println!("Key down on child 1 {:?}", event)) + .on_key_up(|_, event, phase, _| println!("Key up on child 1 {:?}", event)) .child("Child 1"), ) .child( div() .track_focus(&child_2) .context("child-2") - .on_action(|_, action: &ActionC, phase, cx| { - println!("Action C dispatched on child 2 during {:?}", phase); + .on_action(|_, action: &ActionC, cx| { + println!("Action C dispatched on child 2 during"); }) .w_full() .h_6() @@ -96,12 +88,8 @@ impl Render for FocusStory { .on_blur(|_, _, _| println!("Child 2 blurred")) .on_focus_in(|_, _, _| println!("Child 2 focus_in")) .on_focus_out(|_, _, _| println!("Child 2 focus_out")) - .on_key_down(|_, event, phase, _| { - println!("Key down on child 2 {:?} {:?}", phase, event) - }) - .on_key_up(|_, event, phase, _| { - println!("Key up on child 2 {:?} {:?}", phase, event) - }) + .on_key_down(|_, event, phase, _| println!("Key down on child 2 {:?}", event)) + .on_key_up(|_, event, phase, _| println!("Key up on child 2 {:?}", event)) .child("Child 2"), ) } diff --git a/crates/storybook2/src/stories/kitchen_sink.rs b/crates/storybook2/src/stories/kitchen_sink.rs index 54d6f2a3a967cc3f6e5a29be97e36291c45bdeff..6831ae27220ca4691f5f8ac371c0f43e6dd7b013 100644 --- a/crates/storybook2/src/stories/kitchen_sink.rs +++ b/crates/storybook2/src/stories/kitchen_sink.rs @@ -1,5 +1,5 @@ use crate::{story::Story, story_selector::ComponentStory}; -use gpui::{Div, Render, StatefulInteraction, View, VisualContext}; +use gpui::{Div, Render, StatefulInteractivity, View, VisualContext}; use strum::IntoEnumIterator; use ui::prelude::*; @@ -12,7 +12,7 @@ impl KitchenSinkStory { } impl Render for KitchenSinkStory { - type Element = Div>; + type Element = Div>; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { let component_stories = ComponentStory::iter() diff --git a/crates/storybook2/src/stories/picker.rs b/crates/storybook2/src/stories/picker.rs new file mode 100644 index 0000000000000000000000000000000000000000..82a010e6b30e820681b903232b74c35cad6b8584 --- /dev/null +++ b/crates/storybook2/src/stories/picker.rs @@ -0,0 +1,214 @@ +use std::sync::Arc; + +use fuzzy::StringMatchCandidate; +use gpui::{ + div, Component, Div, KeyBinding, ParentElement, Render, StatelessInteractive, Styled, Task, + View, VisualContext, WindowContext, +}; +use picker::{Picker, PickerDelegate}; +use theme2::ActiveTheme; + +pub struct PickerStory { + picker: View>, +} + +struct Delegate { + candidates: Arc<[StringMatchCandidate]>, + matches: Vec, + selected_ix: usize, +} + +impl Delegate { + fn new(strings: &[&str]) -> Self { + Self { + candidates: strings + .iter() + .copied() + .enumerate() + .map(|(id, string)| StringMatchCandidate { + id, + char_bag: string.into(), + string: string.into(), + }) + .collect(), + matches: vec![], + selected_ix: 0, + } + } +} + +impl PickerDelegate for Delegate { + type ListItem = Div>; + + fn match_count(&self) -> usize { + self.candidates.len() + } + + fn render_match( + &self, + ix: usize, + selected: bool, + cx: &mut gpui::ViewContext>, + ) -> Self::ListItem { + let colors = cx.theme().colors(); + let Some(candidate_ix) = self.matches.get(ix) else { + return div(); + }; + let candidate = self.candidates[*candidate_ix].string.clone(); + + div() + .text_color(colors.text) + .when(selected, |s| { + s.border_l_10().border_color(colors.terminal_ansi_yellow) + }) + .hover(|style| { + style + .bg(colors.element_active) + .text_color(colors.text_accent) + }) + .child(candidate) + } + + fn selected_index(&self) -> usize { + self.selected_ix + } + + fn set_selected_index(&mut self, ix: usize, cx: &mut gpui::ViewContext>) { + self.selected_ix = ix; + cx.notify(); + } + + fn confirm(&mut self, secondary: bool, cx: &mut gpui::ViewContext>) { + let candidate_ix = self.matches[self.selected_ix]; + let candidate = self.candidates[candidate_ix].string.clone(); + + if secondary { + eprintln!("Secondary confirmed {}", candidate) + } else { + eprintln!("Confirmed {}", candidate) + } + } + + fn dismissed(&mut self, cx: &mut gpui::ViewContext>) { + cx.quit(); + } + + fn update_matches( + &mut self, + query: String, + cx: &mut gpui::ViewContext>, + ) -> Task<()> { + let candidates = self.candidates.clone(); + self.matches = cx + .background_executor() + .block(fuzzy::match_strings( + &candidates, + &query, + true, + 100, + &Default::default(), + cx.background_executor().clone(), + )) + .into_iter() + .map(|r| r.candidate_id) + .collect(); + self.selected_ix = 0; + Task::ready(()) + } +} + +impl PickerStory { + pub fn new(cx: &mut WindowContext) -> View { + cx.build_view(|cx| { + cx.bind_keys([ + KeyBinding::new("up", menu::SelectPrev, Some("picker")), + KeyBinding::new("pageup", menu::SelectFirst, Some("picker")), + KeyBinding::new("shift-pageup", menu::SelectFirst, Some("picker")), + KeyBinding::new("ctrl-p", menu::SelectPrev, Some("picker")), + KeyBinding::new("down", menu::SelectNext, Some("picker")), + KeyBinding::new("pagedown", menu::SelectLast, Some("picker")), + KeyBinding::new("shift-pagedown", menu::SelectFirst, Some("picker")), + KeyBinding::new("ctrl-n", menu::SelectNext, Some("picker")), + KeyBinding::new("cmd-up", menu::SelectFirst, Some("picker")), + KeyBinding::new("cmd-down", menu::SelectLast, Some("picker")), + KeyBinding::new("enter", menu::Confirm, Some("picker")), + KeyBinding::new("ctrl-enter", menu::ShowContextMenu, Some("picker")), + KeyBinding::new("cmd-enter", menu::SecondaryConfirm, Some("picker")), + KeyBinding::new("escape", menu::Cancel, Some("picker")), + KeyBinding::new("ctrl-c", menu::Cancel, Some("picker")), + ]); + + PickerStory { + picker: cx.build_view(|cx| { + let mut delegate = Delegate::new(&[ + "Baguette (France)", + "Baklava (Turkey)", + "Beef Wellington (UK)", + "Biryani (India)", + "Borscht (Ukraine)", + "Bratwurst (Germany)", + "Bulgogi (Korea)", + "Burrito (USA)", + "Ceviche (Peru)", + "Chicken Tikka Masala (India)", + "Churrasco (Brazil)", + "Couscous (North Africa)", + "Croissant (France)", + "Dim Sum (China)", + "Empanada (Argentina)", + "Fajitas (Mexico)", + "Falafel (Middle East)", + "Feijoada (Brazil)", + "Fish and Chips (UK)", + "Fondue (Switzerland)", + "Goulash (Hungary)", + "Haggis (Scotland)", + "Kebab (Middle East)", + "Kimchi (Korea)", + "Lasagna (Italy)", + "Maple Syrup Pancakes (Canada)", + "Moussaka (Greece)", + "Pad Thai (Thailand)", + "Paella (Spain)", + "Pancakes (USA)", + "Pasta Carbonara (Italy)", + "Pavlova (Australia)", + "Peking Duck (China)", + "Pho (Vietnam)", + "Pierogi (Poland)", + "Pizza (Italy)", + "Poutine (Canada)", + "Pretzel (Germany)", + "Ramen (Japan)", + "Rendang (Indonesia)", + "Sashimi (Japan)", + "Satay (Indonesia)", + "Shepherd's Pie (Ireland)", + "Sushi (Japan)", + "Tacos (Mexico)", + "Tandoori Chicken (India)", + "Tortilla (Spain)", + "Tzatziki (Greece)", + "Wiener Schnitzel (Austria)", + ]); + delegate.update_matches("".into(), cx).detach(); + + let picker = Picker::new(delegate, cx); + picker.focus(cx); + picker + }), + } + }) + } +} + +impl Render for PickerStory { + type Element = Div; + + fn render(&mut self, cx: &mut gpui::ViewContext) -> Self::Element { + div() + .bg(cx.theme().styles.colors.background) + .size_full() + .child(self.picker.clone()) + } +} diff --git a/crates/storybook2/src/stories/scroll.rs b/crates/storybook2/src/stories/scroll.rs index cdb48603e093f62e4ef5ee5968af068798e3bce9..296dc50cb491a1def608c050f65cf7ff33cedb2b 100644 --- a/crates/storybook2/src/stories/scroll.rs +++ b/crates/storybook2/src/stories/scroll.rs @@ -1,5 +1,5 @@ use gpui::{ - div, px, Component, Div, ParentElement, Render, SharedString, StatefulInteraction, Styled, + div, px, Component, Div, ParentElement, Render, SharedString, StatefulInteractivity, Styled, View, VisualContext, WindowContext, }; use theme2::ActiveTheme; @@ -13,12 +13,12 @@ impl ScrollStory { } impl Render for ScrollStory { - type Element = Div>; + type Element = Div>; fn render(&mut self, cx: &mut gpui::ViewContext) -> Self::Element { let theme = cx.theme(); - let color_1 = theme.styles.git.created; - let color_2 = theme.styles.git.modified; + let color_1 = theme.status().created; + let color_2 = theme.status().modified; div() .id("parent") diff --git a/crates/storybook2/src/story_selector.rs b/crates/storybook2/src/story_selector.rs index f59208ccb8794948dd93afa99155d707ea91ad0d..040bd75189104aeb8d1a5437e96b7a3935d9efcc 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, @@ -51,6 +52,7 @@ pub enum ComponentStory { TrafficLights, Workspace, ZIndex, + Picker, } impl ComponentStory { @@ -79,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(), @@ -94,6 +97,7 @@ impl ComponentStory { Self::TrafficLights => cx.build_view(|_| ui::TrafficLightsStory).into(), Self::Workspace => ui::WorkspaceStory::view(cx).into(), Self::ZIndex => cx.build_view(|_| ZIndexStory).into(), + Self::Picker => PickerStory::new(cx).into(), } } } diff --git a/crates/storybook2/src/storybook2.rs b/crates/storybook2/src/storybook2.rs index c8849c134278ebe36d583c34a14b6e8961f4885b..f0ba124162d546bcdbdc1fce12e986364f950dad 100644 --- a/crates/storybook2/src/storybook2.rs +++ b/crates/storybook2/src/storybook2.rs @@ -72,6 +72,8 @@ fn main() { ThemeSettings::override_global(theme_settings, cx); ui::settings::init(cx); + language::init(cx); + editor::init(cx); let window = cx.open_window( WindowOptions { diff --git a/crates/terminal2/src/mappings/mouse.rs b/crates/terminal2/src/mappings/mouse.rs index eac6ad17ff257384396c4c639dd6f7ea39acea5f..edced3156faf90cdf9dc227ee646f0e4cccb68e3 100644 --- a/crates/terminal2/src/mappings/mouse.rs +++ b/crates/terminal2/src/mappings/mouse.rs @@ -186,9 +186,9 @@ pub fn mouse_side( } pub fn grid_point(pos: Point, cur_size: TerminalSize, display_offset: usize) -> AlacPoint { - let col = GridCol((pos.x / cur_size.cell_width).as_usize()); + let col = GridCol((cur_size.cell_width / pos.x) as usize); let col = min(col, cur_size.last_column()); - let line = (pos.y / cur_size.line_height).as_isize() as i32; + let line = (cur_size.line_height / pos.y) as i32; let line = min(line, cur_size.bottommost_line().0); AlacPoint::new(GridLine(line - display_offset as i32), col) } diff --git a/crates/terminal2/src/terminal2.rs b/crates/terminal2/src/terminal2.rs index ba5c4815f29253152c355c1ba4d998e54c2fc9e5..3d06b488123016c19102e8d911d6b24568124ab1 100644 --- a/crates/terminal2/src/terminal2.rs +++ b/crates/terminal2/src/terminal2.rs @@ -1121,8 +1121,7 @@ impl Terminal { None => return, }; - let scroll_lines = - (scroll_delta / self.last_content.size.line_height).as_isize() as i32; + let scroll_lines = (scroll_delta / self.last_content.size.line_height) as i32; self.events .push_back(InternalEvent::Scroll(AlacScroll::Delta(scroll_lines))); @@ -1280,11 +1279,11 @@ impl Terminal { } /* Calculate the appropriate scroll lines */ TouchPhase::Moved => { - let old_offset = (self.scroll_px / line_height).as_isize() as i32; + let old_offset = (self.scroll_px / line_height) as i32; self.scroll_px += e.delta.pixel_delta(line_height).y * scroll_multiplier; - let new_offset = (self.scroll_px / line_height).as_isize() as i32; + let new_offset = (self.scroll_px / line_height) as i32; // Whenever we hit the edges, reset our stored scroll to 0 // so we can respond to changes in direction quickly @@ -1396,9 +1395,9 @@ fn all_search_matches<'a, T>( } fn content_index_for_mouse(pos: Point, size: &TerminalSize) -> usize { - let col = (pos.x / size.cell_width()).round().as_usize(); + let col = (pos.x / size.cell_width()).round() as usize; let clamped_col = min(col, size.columns() - 1); - let row = (pos.y / size.line_height()).round().as_usize(); + let row = (pos.y / size.line_height()).round() as usize; let clamped_row = min(row, size.screen_lines() - 1); clamped_row * size.columns() + clamped_col } diff --git a/crates/theme2/Cargo.toml b/crates/theme2/Cargo.toml index d57c22ede7830fccdce6ce26cd1bcc10fb88923c..45ba4587bab8d5953b39469c5e2382d11b38db57 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 8ee4b5fd47830d062105c85959827cb37c2d7bc1..83af983d04486ddcb0d03ecfc1d35c034079259e 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 { @@ -56,17 +29,6 @@ pub struct StatusColors { pub warning: Hsla, } -#[derive(Refineable, Clone, Debug)] -#[refineable(debug)] -pub struct GitStatusColors { - pub conflict: Hsla, - pub created: Hsla, - pub deleted: Hsla, - pub ignored: Hsla, - pub modified: Hsla, - pub renamed: Hsla, -} - #[derive(Refineable, Clone, Debug)] #[refineable(debug, deserialize)] pub struct ThemeColors { @@ -287,7 +249,6 @@ pub struct ThemeStyles { #[refineable] pub colors: ThemeColors, pub status: StatusColors, - pub git: GitStatusColors, pub player: PlayerColors, pub syntax: Arc, } diff --git a/crates/theme2/src/default_colors.rs b/crates/theme2/src/default_colors.rs index 7252e829723d7dce36bceff4ecd882a46bff97e4..ec57538e8f710e813ebcbb3ee5e7cb3083ccd895 100644 --- a/crates/theme2/src/default_colors.rs +++ b/crates/theme2/src/default_colors.rs @@ -2,12 +2,104 @@ use std::num::ParseIntError; use gpui::{hsla, Hsla, Rgba}; -use crate::{ - colors::{GitStatusColors, PlayerColor, PlayerColors, StatusColors, SystemColors, ThemeColors}, - scale::{ColorScaleSet, ColorScales}, - syntax::SyntaxTheme, - ColorScale, -}; +use crate::colors::{StatusColors, SystemColors, ThemeColors}; +use crate::scale::{ColorScaleSet, ColorScales}; +use crate::syntax::SyntaxTheme; +use crate::{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,61 +119,21 @@ 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(), } } } -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(), - } - } -} - -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 3c9634c9898062db06b3431e6d495df7cb8ae682..40fb7df7cfd85d1ed33edcd16ad562af4519ee5d 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::{StatusColors, SystemColors, ThemeColors, ThemeStyles}, + default_color_scales, Appearance, PlayerColors, SyntaxTheme, Theme, ThemeFamily, }; fn zed_pro_daylight() -> Theme { @@ -14,8 +14,7 @@ fn zed_pro_daylight() -> Theme { system: SystemColors::default(), colors: ThemeColors::default_light(), status: StatusColors::default(), - git: GitStatusColors::default(), - player: PlayerColors::default(), + player: PlayerColors::default_light(), syntax: Arc::new(SyntaxTheme::default_light()), }, } @@ -30,7 +29,6 @@ pub(crate) fn zed_pro_moonlight() -> Theme { system: SystemColors::default(), colors: ThemeColors::default_dark(), status: StatusColors::default(), - git: GitStatusColors::default(), player: PlayerColors::default(), syntax: Arc::new(SyntaxTheme::default_dark()), }, diff --git a/crates/theme2/src/players.rs b/crates/theme2/src/players.rs new file mode 100644 index 0000000000000000000000000000000000000000..0e36ff594732c67ac7f1fd455e849aa259084bc7 --- /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/registry.rs b/crates/theme2/src/registry.rs index c6e195e757c8b349d98b105615d97aea40cbde3f..decec8b5dada0ef19250f15de5de100ce57d47c2 100644 --- a/crates/theme2/src/registry.rs +++ b/crates/theme2/src/registry.rs @@ -6,8 +6,8 @@ use gpui::SharedString; use refineable::Refineable; use crate::{ - zed_pro_family, Appearance, GitStatusColors, PlayerColors, StatusColors, SyntaxTheme, - SystemColors, Theme, ThemeColors, ThemeFamily, ThemeStyles, UserTheme, UserThemeFamily, + zed_pro_family, Appearance, PlayerColors, StatusColors, SyntaxTheme, SystemColors, Theme, + ThemeColors, ThemeFamily, ThemeStyles, UserTheme, UserThemeFamily, }; pub struct ThemeRegistry { @@ -50,7 +50,6 @@ impl ThemeRegistry { system: SystemColors::default(), colors: theme_colors, status: StatusColors::default(), - git: GitStatusColors::default(), player: PlayerColors::default(), syntax: match user_theme.appearance { Appearance::Light => Arc::new(SyntaxTheme::default_light()), diff --git a/crates/theme2/src/story.rs b/crates/theme2/src/story.rs new file mode 100644 index 0000000000000000000000000000000000000000..8b3754b59e9799f00cc60f3cb3f45fb84fcd0cff --- /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 33a977f695b945d197fcf2b4bf62ce53b322f156..74b5a4269ec3432e165d76105197e788595b365e 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::*; @@ -93,19 +95,13 @@ impl Theme { &self.styles.status } - /// Returns the [`GitStatusColors`] for the theme. - #[inline(always)] - pub fn git(&self) -> &GitStatusColors { - &self.styles.git - } - /// Returns the color for the syntax node with the given name. #[inline(always)] pub fn syntax_color(&self, name: &str) -> Hsla { self.syntax().color(name) } - /// Returns the [`StatusColors`] for the theme. + /// Returns the [`DiagnosticStyle`] for the theme. #[inline(always)] pub fn diagnostic_style(&self) -> DiagnosticStyle { DiagnosticStyle { @@ -126,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/theme_importer/src/theme_printer.rs b/crates/theme_importer/src/theme_printer.rs index 0f20a8d60f93620fc990f815d3248b36452df641..49caf7bfd0be145329efb2e2bfca23fab77f15bd 100644 --- a/crates/theme_importer/src/theme_printer.rs +++ b/crates/theme_importer/src/theme_printer.rs @@ -2,8 +2,8 @@ use std::fmt::{self, Debug}; use gpui::{Hsla, Rgba}; use theme::{ - Appearance, GitStatusColors, PlayerColor, PlayerColors, StatusColors, SyntaxTheme, - SystemColors, ThemeColorsRefinement, UserTheme, UserThemeFamily, UserThemeStylesRefinement, + Appearance, PlayerColor, PlayerColors, StatusColors, SyntaxTheme, SystemColors, + ThemeColorsRefinement, UserTheme, UserThemeFamily, UserThemeStylesRefinement, }; struct RawSyntaxPrinter<'a>(&'a str); @@ -270,21 +270,6 @@ impl<'a> Debug for StatusColorsPrinter<'a> { } } -pub struct GitStatusColorsPrinter<'a>(&'a GitStatusColors); - -impl<'a> Debug for GitStatusColorsPrinter<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("GitStatusColors") - .field("conflict", &HslaPrinter(self.0.conflict)) - .field("created", &HslaPrinter(self.0.created)) - .field("deleted", &HslaPrinter(self.0.deleted)) - .field("ignored", &HslaPrinter(self.0.ignored)) - .field("modified", &HslaPrinter(self.0.modified)) - .field("renamed", &HslaPrinter(self.0.renamed)) - .finish() - } -} - pub struct PlayerColorsPrinter<'a>(&'a PlayerColors); impl<'a> Debug for PlayerColorsPrinter<'a> { diff --git a/crates/ui2/src/components/checkbox.rs b/crates/ui2/src/components/checkbox.rs index 9f7c10a10433eef59249e3ab0df408b94a8a2aff..20dad747124ca2a652679324f04590dbf9d3f05d 100644 --- a/crates/ui2/src/components/checkbox.rs +++ b/crates/ui2/src/components/checkbox.rs @@ -128,7 +128,7 @@ impl Checkbox { // click area for the checkbox. .size_5() // Because we've enlarged the click area, we need to create a - // `group` to pass down interaction events to the checkbox. + // `group` to pass down interactivity events to the checkbox. .group(group_id.clone()) .child( div() @@ -148,7 +148,7 @@ impl Checkbox { .bg(bg_color) .border() .border_color(border_color) - // We only want the interaction states to fire when we + // We only want the interactivity states to fire when we // are in a checkbox that isn't disabled. .when(!self.disabled, |this| { // Here instead of `hover()` we use `group_hover()` diff --git a/crates/ui2/src/prelude.rs b/crates/ui2/src/prelude.rs index c942f0aa3b883141a6178533c83e45c2a3a9f48c..3f179210eb3fc5c8329ea3cde110f2f5b33e7ee0 100644 --- a/crates/ui2/src/prelude.rs +++ b/crates/ui2/src/prelude.rs @@ -46,12 +46,12 @@ pub enum GitStatus { impl GitStatus { pub fn hsla(&self, cx: &WindowContext) -> Hsla { match self { - Self::None => cx.theme().styles.system.transparent, - Self::Created => cx.theme().styles.git.created, - Self::Modified => cx.theme().styles.git.modified, - Self::Deleted => cx.theme().styles.git.deleted, - Self::Conflict => cx.theme().styles.git.conflict, - Self::Renamed => cx.theme().styles.git.renamed, + Self::None => cx.theme().system().transparent, + Self::Created => cx.theme().status().created, + Self::Modified => cx.theme().status().modified, + Self::Deleted => cx.theme().status().deleted, + Self::Conflict => cx.theme().status().conflict, + Self::Renamed => cx.theme().status().renamed, } } } diff --git a/crates/workspace2/src/modal_layer.rs b/crates/workspace2/src/modal_layer.rs index c69354f87b54bf0ad005c2a6247ad9b9b263254e..694b4e7ffe1067870a46c0c0ad48dca1d85aec7c 100644 --- a/crates/workspace2/src/modal_layer.rs +++ b/crates/workspace2/src/modal_layer.rs @@ -1,13 +1,11 @@ -use std::{any::TypeId, sync::Arc}; - +use crate::Workspace; use gpui::{ - div, px, AnyView, AppContext, Component, DispatchPhase, Div, ParentElement, Render, - StatelessInteractive, Styled, View, ViewContext, + div, px, AnyView, AppContext, Component, Div, ParentElement, Render, StatelessInteractive, + Styled, View, ViewContext, }; +use std::{any::TypeId, sync::Arc}; use ui::v_stack; -use crate::Workspace; - pub struct ModalRegistry { registered_modals: Vec<(TypeId, Box) -> Div>)>, } @@ -43,14 +41,7 @@ impl ModalRegistry { let build_view = build_view.clone(); div.on_action( - move |workspace: &mut Workspace, - event: &A, - phase: DispatchPhase, - cx: &mut ViewContext| { - if phase == DispatchPhase::Capture { - return; - } - + move |workspace: &mut Workspace, event: &A, cx: &mut ViewContext| { let Some(new_modal) = (build_view)(workspace, cx) else { return; }; diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index ee6134e9e4a2df4297f9d5bf190a401a92b4dc1b..42751ed632740bcb95cd08f7658db0837eb05afb 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -2694,7 +2694,7 @@ impl Workspace { .any(|item| item.has_conflict(cx) || item.is_dirty(cx)); if is_edited != self.window_edited { self.window_edited = is_edited; - todo!() + // todo!() // cx.set_window_edited(self.window_edited) } } diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index c9012a3a143a95718ab59eeb10d2afd60210b3c6..bee8d0bc429b1b9a70626ef811ae6ef8926adedf 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.112.0" +version = "0.113.0" publish = false [lib] diff --git a/crates/zed/src/languages/racket/highlights.scm b/crates/zed/src/languages/racket/highlights.scm index 2c0caf89357cfbe8f966bffbbc712272b3c1e59d..3caf1d88e97dd0fcdc2e08d6ef866ad74c436a31 100644 --- a/crates/zed/src/languages/racket/highlights.scm +++ b/crates/zed/src/languages/racket/highlights.scm @@ -37,4 +37,3 @@ ((symbol) @comment (#match? @comment "^#[cC][iIsS]$")) - diff --git a/crates/zed2/src/languages/json.rs b/crates/zed2/src/languages/json.rs index 63f909ae2a2e264ea672dee48e305ba1be82e066..cf9b33d9683cadd9f5b27c41104d215becd1110d 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( diff --git a/crates/zed2/src/languages/racket/highlights.scm b/crates/zed2/src/languages/racket/highlights.scm index 2c0caf89357cfbe8f966bffbbc712272b3c1e59d..3caf1d88e97dd0fcdc2e08d6ef866ad74c436a31 100644 --- a/crates/zed2/src/languages/racket/highlights.scm +++ b/crates/zed2/src/languages/racket/highlights.scm @@ -37,4 +37,3 @@ ((symbol) @comment (#match? @comment "^#[cC][iIsS]$")) - diff --git a/script/get-preview-channel-changes b/script/get-preview-channel-changes index 5a0be3ed6669233bb50e001320259f09c626c288..7206134200b9df7c2804a37ca2afe06cde5c12ca 100755 --- a/script/get-preview-channel-changes +++ b/script/get-preview-channel-changes @@ -2,7 +2,7 @@ const { execFileSync } = require("child_process"); const { GITHUB_ACCESS_TOKEN } = process.env; -const PR_REGEX = /#\d+/ // Ex: matches on #4241 +const PR_REGEX = /#\d+/; // Ex: matches on #4241 const FIXES_REGEX = /(fixes|closes|completes) (.+[/#]\d+.*)$/im; main(); @@ -12,7 +12,7 @@ async function main() { const [newTag, oldTag] = execFileSync( "git", ["tag", "--sort", "-committerdate"], - { encoding: "utf8" } + { encoding: "utf8" }, ) .split("\n") .filter((t) => t.startsWith("v") && t.endsWith("-pre")); @@ -22,13 +22,22 @@ async function main() { let hasProtocolChanges = false; try { - execFileSync("git", ["diff", oldTag, newTag, "--exit-code", "--", "crates/rpc"]).status != 0; + execFileSync("git", [ + "diff", + oldTag, + newTag, + "--exit-code", + "--", + "crates/rpc", + ]).status != 0; } catch (error) { hasProtocolChanges = true; } if (hasProtocolChanges) { - console.warn("\033[31;1;4mRPC protocol changes, server should be re-deployed\033[0m\n"); + console.warn( + "\033[31;1;4mRPC protocol changes, server should be re-deployed\033[0m\n", + ); } else { console.log("No RPC protocol changes\n"); } @@ -37,10 +46,14 @@ async function main() { const pullRequestNumbers = getPullRequestNumbers(oldTag, newTag); // Get the PRs that were cherry-picked between main and the old tag. - const existingPullRequestNumbers = new Set(getPullRequestNumbers("main", oldTag)); + const existingPullRequestNumbers = new Set( + getPullRequestNumbers("main", oldTag), + ); // Filter out those existing PRs from the set of new PRs. - const newPullRequestNumbers = pullRequestNumbers.filter(number => !existingPullRequestNumbers.has(number)); + const newPullRequestNumbers = pullRequestNumbers.filter( + (number) => !existingPullRequestNumbers.has(number), + ); // Fetch the pull requests from the GitHub API. console.log("Merged Pull requests:"); @@ -56,6 +69,16 @@ async function main() { // Print the pull request title and URL. const pullRequest = await response.json(); + const releaseNotesHeader = /^\s*(?:Release )?Notes\s*:(.+)/ims; + + let releaseNotes = pullRequest.body || ""; + const captures = releaseNotesHeader.exec(releaseNotes); + const notes = captures ? captures[1] : "MISSING"; + const skippableNoteRegex = /^\s*-?\s*n\/?a\s*/ims; + + if (skippableNoteRegex.exec(notes) != null) { + continue; + } console.log("*", pullRequest.title); console.log(" PR URL: ", webURL); @@ -66,38 +89,30 @@ async function main() { console.log(" Issue URL: ", fixedIssueURL); } - let releaseNotes = (pullRequest.body || "").split("Release Notes:")[1]; - - if (releaseNotes) { - releaseNotes = releaseNotes.trim().split("\n") - console.log(" Release Notes:"); + releaseNotes = notes.trim().split("\n"); + console.log(" Release Notes:"); - for (const line of releaseNotes) { - console.log(` ${line}`); - } + for (const line of releaseNotes) { + console.log(` ${line}`); } - console.log() + console.log(); } } function getPullRequestNumbers(oldTag, newTag) { const pullRequestNumbers = execFileSync( "git", - [ - "log", - `${oldTag}..${newTag}`, - "--oneline" - ], - { encoding: "utf8" } + ["log", `${oldTag}..${newTag}`, "--oneline"], + { encoding: "utf8" }, ) .split("\n") - .filter(line => line.length > 0) - .map(line => { + .filter((line) => line.length > 0) + .map((line) => { const match = line.match(/#(\d+)/); return match ? match[1] : null; }) - .filter(line => line); + .filter((line) => line); return pullRequestNumbers; }