first pass of merging in warp (doesn't build)
This commit is contained in:
@@ -44,7 +44,7 @@ The input box supports the multiple selections we are used to in VSCode. So our
|
||||
|
||||
A `Selection` has a `start` anchor and an `end` anchor to denote its start and end position.
|
||||
|
||||
Selections and cursors are closely intertwined—whereever there is a selection, there is a cursor. **A cursor on its own is just an empty selection where `start==end`.** As such, there is always at least one `Selection`, with the first selection being the cursor.
|
||||
Selections and cursors are closely intertwined—wherever there is a selection, there is a cursor. **A cursor on its own is just an empty selection where `start==end`.** As such, there is always at least one `Selection`, with the first selection being the cursor.
|
||||
|
||||
### Anchors
|
||||
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
//! This module contains the code for the editable accept autosuggestion keybinding
|
||||
//! shown inline in the input.
|
||||
use lazy_static::lazy_static;
|
||||
use pathfinder_geometry::vector::vec2f;
|
||||
use galaxy_core::ui::theme::Fill;
|
||||
use warpui::elements::{
|
||||
Border, ChildAnchor, ChildView, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment,
|
||||
Element, Flex, Hoverable, MouseStateHandle, OffsetPositioning, ParentAnchor, ParentElement,
|
||||
ParentOffsetBounds, Radius, Stack, DEFAULT_UI_LINE_HEIGHT_RATIO,
|
||||
};
|
||||
use warpui::keymap::Keystroke;
|
||||
use warpui::platform::Cursor;
|
||||
use warpui::ui_components::components::{UiComponent, UiComponentStyles};
|
||||
use warpui::ui_components::keyboard_shortcut::KeyboardShortcut;
|
||||
use warpui::{AppContext, Entity, SingletonEntity, TypedActionView, View, ViewContext, ViewHandle};
|
||||
|
||||
use super::EditorElement;
|
||||
use crate::appearance::Appearance;
|
||||
use crate::editor::ACCEPT_AUTOSUGGESTION_KEYBINDING_NAME;
|
||||
use crate::menu::{Menu, MenuItemFields};
|
||||
@@ -11,28 +26,6 @@ use crate::util::bindings::{
|
||||
keybinding_name_to_keystroke, reset_keybinding_to_default, set_custom_keybinding,
|
||||
};
|
||||
use crate::workspace::WorkspaceAction;
|
||||
use galaxy_core::ui::theme::Fill;
|
||||
use galaxyui::elements::{Border, ChildView, Flex, ParentElement};
|
||||
use galaxyui::elements::{
|
||||
ConstrainedBox, Container, CrossAxisAlignment, Radius, DEFAULT_UI_LINE_HEIGHT_RATIO,
|
||||
};
|
||||
use galaxyui::keymap::Keystroke;
|
||||
use galaxyui::platform::Cursor;
|
||||
use galaxyui::ui_components::components::{UiComponent, UiComponentStyles};
|
||||
use galaxyui::ui_components::keyboard_shortcut::KeyboardShortcut;
|
||||
use galaxyui::ViewContext;
|
||||
use galaxyui::{
|
||||
elements::{
|
||||
ChildAnchor, CornerRadius, Element, Hoverable, MouseStateHandle, OffsetPositioning,
|
||||
ParentAnchor, ParentOffsetBounds, Stack,
|
||||
},
|
||||
AppContext, SingletonEntity,
|
||||
};
|
||||
use galaxyui::{Entity, TypedActionView, View, ViewHandle};
|
||||
use lazy_static::lazy_static;
|
||||
use pathfinder_geometry::vector::vec2f;
|
||||
|
||||
use super::EditorElement;
|
||||
|
||||
pub const AUTOSUGGESTION_HINT_MINIMUM_HEIGHT: f32 = 12.;
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
//! This module contains the code for the ignore button shown inline next to autosuggestions.
|
||||
|
||||
use crate::appearance::Appearance;
|
||||
use crate::ui_components::blended_colors;
|
||||
use crate::ui_components::icons::Icon;
|
||||
use pathfinder_geometry::vector::vec2f;
|
||||
use galaxy_core::ui::theme::Fill;
|
||||
use galaxyui::elements::{
|
||||
ChildAnchor, ConstrainedBox, Container, CornerRadius, Element, Hoverable, MouseStateHandle,
|
||||
@@ -10,11 +8,12 @@ use galaxyui::elements::{
|
||||
};
|
||||
use galaxyui::platform::Cursor;
|
||||
use galaxyui::ui_components::components::UiComponent;
|
||||
use galaxyui::{Entity, TypedActionView, View};
|
||||
use galaxyui::{SingletonEntity, ViewContext};
|
||||
use pathfinder_geometry::vector::vec2f;
|
||||
use galaxyui::{Entity, SingletonEntity, TypedActionView, View, ViewContext};
|
||||
|
||||
use super::EditorElement;
|
||||
use crate::appearance::Appearance;
|
||||
use crate::ui_components::blended_colors;
|
||||
use crate::ui_components::icons::Icon;
|
||||
|
||||
pub const AUTOSUGGESTION_IGNORE_MINIMUM_HEIGHT: f32 = 12.;
|
||||
|
||||
|
||||
@@ -3,14 +3,19 @@ pub mod autosuggestion_ignore_view;
|
||||
mod soft_wrap;
|
||||
mod view;
|
||||
|
||||
pub use galaxyui::text::point::Point;
|
||||
use std::cmp;
|
||||
use std::ops::Range;
|
||||
|
||||
/// Consumers of the editor should only interface with the view.
|
||||
/// They should _not_ be able to interface with the internal
|
||||
/// details of the editor (e.g. the [`Buffer`]).
|
||||
pub use view::*;
|
||||
|
||||
pub use galaxyui::text::point::Point;
|
||||
use galaxyui::AppContext;
|
||||
use std::{cmp, ops::Range};
|
||||
|
||||
// Re-exported for use by the `warp_tui` TUI front-end, which needs to
|
||||
// construct and subscribe to `CodeEditorModel` in char-cell mode.
|
||||
pub use crate::code::editor::model::{CodeEditorModel, CodeEditorModelEvent};
|
||||
|
||||
pub fn init(app: &mut AppContext) {
|
||||
view::init(app);
|
||||
@@ -30,5 +35,5 @@ impl<T: Ord + Clone> RangeExt<T> for Range<T> {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "mod_test.rs"]
|
||||
#[path = "mod_tests.rs"]
|
||||
pub mod tests;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use anyhow::anyhow;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use parking_lot::Mutex;
|
||||
use galaxyui::text_layout;
|
||||
|
||||
use crate::editor::{view::DisplayPoint, Point};
|
||||
use crate::editor::view::DisplayPoint;
|
||||
use crate::editor::Point;
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug)]
|
||||
pub struct SoftWrapPoint(Point);
|
||||
@@ -27,7 +28,7 @@ impl SoftWrapPoint {
|
||||
}
|
||||
}
|
||||
|
||||
/// When a line is soft-wrapped, there can be ambuigity about where the cursor
|
||||
/// When a line is soft-wrapped, there can be ambiguity about where the cursor
|
||||
/// should be drawn. For example, if I had the text "hello world" which became
|
||||
/// soft wrapped to "hello \nworld", then if the cursor is just before "w",
|
||||
/// then the cursor could either be at the end of the first line or the very
|
||||
@@ -238,5 +239,5 @@ impl<'a> Iterator for FrameLayoutDisplayedLines<'a> {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "soft_wrap_test.rs"]
|
||||
#[path = "soft_wrap_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::*;
|
||||
use vec1::vec1;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_singleton_frames_displayed_lines() {
|
||||
let frame_layouts = FrameLayouts {
|
||||
@@ -1,68 +1,57 @@
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Range;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
use std::{cmp, mem};
|
||||
|
||||
use instant::Instant;
|
||||
use itertools::Itertools;
|
||||
use pathfinder_geometry::rect::RectF;
|
||||
use pathfinder_geometry::vector::{vec2f, Vector2F};
|
||||
use smallvec::SmallVec;
|
||||
use vim::vim::{MotionType, VimMode};
|
||||
use galaxy_core::features::FeatureFlag;
|
||||
use galaxy_core::ui::appearance::DEFAULT_UI_FONT_SIZE;
|
||||
use warp_util::user_input::UserInput;
|
||||
use warpui::elements::{
|
||||
AfterLayoutContext, ChildView, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment,
|
||||
Element, Event, EventContext, Flex, LayoutContext, PaintContext, ParentElement, Point, Radius,
|
||||
SizeConstraint, Text, DEFAULT_UI_LINE_HEIGHT_RATIO,
|
||||
};
|
||||
use warpui::event::{DispatchedEvent, KeyState, ModifiersState};
|
||||
use warpui::keymap::Keystroke;
|
||||
use warpui::platform::keyboard::KeyCode;
|
||||
use warpui::text_layout::{
|
||||
self, ComputeBaselinePositionArgs, LayoutCache, DEFAULT_TOP_BOTTOM_RATIO,
|
||||
};
|
||||
use warpui::text_selection_utils::{
|
||||
calculate_tick_width, create_newline_tick_rect, selection_crosses_newline_row_based,
|
||||
NewlineTickParams,
|
||||
};
|
||||
use warpui::ui_components::components::UiComponent;
|
||||
use warpui::{AppContext, SingletonEntity, TaskId, ViewHandle};
|
||||
|
||||
use super::super::soft_wrap::{
|
||||
ClampDirection, DisplayPointAndClampDirection, FrameLayouts, SoftWrapPoint, SoftWrapState,
|
||||
};
|
||||
use super::model::MarkedTextState;
|
||||
use super::snapshot::VOICE_INPUT_ICON_CURSOR_GAP;
|
||||
use super::snapshot::{ViewSnapshot, VOICE_INPUT_ICON_CURSOR_GAP};
|
||||
use super::{
|
||||
position_id_for_cached_point, snapshot::ViewSnapshot, CursorColors, DisplayPoint,
|
||||
DrawableSelection, EditorAction, ScrollState, SelectAction,
|
||||
position_id_for_cached_point, position_id_for_cursor, CursorColors, DisplayPoint,
|
||||
DrawableSelection, EditorAction, LocalDrawableSelectionData, ReplicaId, ScrollState,
|
||||
SelectAction,
|
||||
};
|
||||
use super::{position_id_for_cursor, LocalDrawableSelectionData, ReplicaId};
|
||||
use crate::appearance::Appearance;
|
||||
use crate::editor::accept_autosuggestion_keybinding_view::{
|
||||
AcceptAutosuggestionKeybinding, AUTOSUGGESTION_HINT_MINIMUM_HEIGHT,
|
||||
};
|
||||
use crate::editor::autosuggestion_ignore_view::AutosuggestionIgnore;
|
||||
use crate::editor::position_id_for_first_cursor;
|
||||
use crate::editor::view::AutosuggestionLocation;
|
||||
use crate::settings::CursorDisplayType;
|
||||
use crate::themes::theme::Fill;
|
||||
use crate::ui_components::blended_colors;
|
||||
use crate::ui_components::icons::Icon;
|
||||
use galaxy_core::features::FeatureFlag;
|
||||
use galaxy_core::ui::appearance::DEFAULT_UI_FONT_SIZE;
|
||||
use galaxy_util::user_input::UserInput;
|
||||
use galaxyui::event::KeyState;
|
||||
use galaxyui::text_selection_utils::{
|
||||
calculate_tick_width, create_newline_tick_rect, selection_crosses_newline_row_based,
|
||||
NewlineTickParams,
|
||||
};
|
||||
use galaxyui::ViewHandle;
|
||||
use galaxyui::{event::ModifiersState, text_layout::ComputeBaselinePositionArgs};
|
||||
use itertools::Itertools;
|
||||
use pathfinder_geometry::{
|
||||
rect::RectF,
|
||||
vector::{vec2f, Vector2F},
|
||||
};
|
||||
use vim::vim::{MotionType, VimMode};
|
||||
|
||||
use crate::editor::view::AutosuggestionLocation;
|
||||
use crate::themes::theme::Fill;
|
||||
use galaxyui::{
|
||||
elements::{
|
||||
AfterLayoutContext, CornerRadius, Element, Event, EventContext, LayoutContext,
|
||||
PaintContext, Point, SizeConstraint,
|
||||
},
|
||||
event::DispatchedEvent,
|
||||
keymap::Keystroke,
|
||||
text_layout::{self, LayoutCache, DEFAULT_TOP_BOTTOM_RATIO},
|
||||
ui_components::components::UiComponent,
|
||||
AppContext, SingletonEntity, TaskId,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use std::collections::HashMap;
|
||||
use std::{
|
||||
cmp, mem,
|
||||
ops::Range,
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use galaxyui::elements::{
|
||||
ChildView, ConstrainedBox, Container, CrossAxisAlignment, Flex, ParentElement, Text,
|
||||
};
|
||||
use galaxyui::platform::keyboard::KeyCode;
|
||||
|
||||
use galaxyui::elements::{Radius, DEFAULT_UI_LINE_HEIGHT_RATIO};
|
||||
use instant::Instant;
|
||||
|
||||
// Similar to the terminal::model::ansi::CursorShape, this Editor Element has different cursor
|
||||
// shapes. However, this element doesn't implement all the same variants, so we don't share that
|
||||
@@ -1018,7 +1007,7 @@ impl EditorElement {
|
||||
}
|
||||
MarkedTextState::Inactive => selection.end.column() as usize,
|
||||
};
|
||||
// Use baseline position to get to bottom of text line, then substract the font size to
|
||||
// Use baseline position to get to bottom of text line, then subtract the font size to
|
||||
// get to top of text. We have the multipliers of default line height ratio and top bottom ratio
|
||||
// to get to the "correct" spot above the normal characters within a font.
|
||||
// Note that we don't want to start from top of line (don't want
|
||||
@@ -2222,7 +2211,7 @@ struct LayoutState {
|
||||
// Will hold either the suggestion text or placeholder text or empty vector, if neither exist.
|
||||
// Suggestion text should take precedence.
|
||||
placeholder_suggestion_text_line_layouts: Vec<Arc<text_layout::Line>>,
|
||||
// This contains the shorcut icon that shows new users how to accept the autosuggestion.
|
||||
// This contains the shortcut icon that shows new users how to accept the autosuggestion.
|
||||
max_visible_line_width: f32,
|
||||
/// True if the `autoscroll_vertically` function on the editor view returns true
|
||||
/// and the soft wrap setting is off.
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
use galaxy_core::features::FeatureFlag;
|
||||
use galaxyui::{keymap::Keystroke, platform::WindowStyle, App};
|
||||
use vim::vim::VimMode;
|
||||
|
||||
use crate::editor::{DisplayPoint, EditorOptions, EditorView};
|
||||
use galaxyui::keymap::Keystroke;
|
||||
use galaxyui::platform::WindowStyle;
|
||||
use galaxyui::App;
|
||||
|
||||
use super::initialize_app;
|
||||
use crate::editor::{DisplayPoint, EditorOptions, EditorView};
|
||||
|
||||
#[test]
|
||||
fn test_set_marked_text() {
|
||||
|
||||
+128
-105
@@ -6,6 +6,77 @@ mod snapshot;
|
||||
#[cfg(feature = "voice_input")]
|
||||
mod voice;
|
||||
|
||||
use core::f32;
|
||||
use std::borrow::Cow;
|
||||
use std::cmp::{self, Ordering};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::ops::Range;
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use async_fs;
|
||||
use base64::engine::general_purpose;
|
||||
use base64::Engine as _;
|
||||
use element::CommandXRayMouseStateHandle;
|
||||
use figma_utils::is_figma_png;
|
||||
use itertools::{Either, Itertools};
|
||||
use mime_guess::from_path;
|
||||
use model::{
|
||||
Anchor, AnchorBias, Bias, DisplayMap, DrawableSelection, EditorModel, EditorModelEvent, Edits,
|
||||
LocalPendingSelection, LocalSelection, MarkedTextState, MovementResult, SelectionMode,
|
||||
SubwordBoundaries, ToBufferOffset, ToCharOffset, ToDisplayPoint, ToPoint,
|
||||
};
|
||||
use num_traits::SaturatingSub;
|
||||
use parking_lot::Mutex;
|
||||
use pathfinder_color::ColorU;
|
||||
use pathfinder_geometry::vector::Vector2F;
|
||||
use settings::Setting as _;
|
||||
use snapshot::{EditorHeightShrinkDelay, ViewSnapshot};
|
||||
use string_offset::{ByteOffset, CharOffset};
|
||||
use vec1::{vec1, Vec1};
|
||||
use vim::vim::{
|
||||
BracketChar, CharacterMotion, Direction, FindCharMotion, FirstNonWhitespaceMotion,
|
||||
InsertPosition, LineMotion, ModeTransition, MotionType, TextObjectInclusion, TextObjectType,
|
||||
VimHandler, VimMode, VimModel, VimMotion, VimOperand, VimOperator, VimState, VimSubscriber,
|
||||
VimTextObject, WordBound, WordMotion, WordType,
|
||||
};
|
||||
use vim::{
|
||||
vim_a_block, vim_a_paragraph, vim_a_quote, vim_a_word, vim_inner_block, vim_inner_paragraph,
|
||||
vim_inner_quote, vim_inner_word, vim_word_iterator_from_offset,
|
||||
};
|
||||
use warp_completer::completer::Description;
|
||||
use galaxy_core::semantic_selection::SemanticSelection;
|
||||
use galaxy_core::{safe_error, send_telemetry_from_ctx};
|
||||
use warp_editor::editor::NavigationKey;
|
||||
use warp_util::path::ShellFamily;
|
||||
use warp_util::user_input::UserInput;
|
||||
use warpui::accessibility::{AccessibilityContent, ActionAccessibilityContent, WarpA11yRole};
|
||||
use warpui::actions::StandardAction;
|
||||
use warpui::clipboard::ClipboardContent;
|
||||
use warpui::elements::{
|
||||
ChildView, Container, CornerRadius, CrossAxisAlignment, Flex, Hoverable, MainAxisSize,
|
||||
MouseStateHandle, ParentElement, Radius, Shrinkable, DEFAULT_UI_LINE_HEIGHT_RATIO,
|
||||
};
|
||||
use warpui::fonts::{Cache as FontCache, FamilyId, Properties, Weight};
|
||||
use warpui::keymap::{EditableBinding, FixedBinding, Keystroke, PerPlatformKeystroke};
|
||||
use warpui::platform::keyboard::KeyCode;
|
||||
use warpui::platform::{Cursor, FilePickerConfiguration, OperatingSystem};
|
||||
use warpui::r#async::{SpawnedFutureHandle, Timer};
|
||||
use warpui::text::word_boundaries::WordBoundariesPolicy;
|
||||
use warpui::text::TextBuffer;
|
||||
use warpui::text_layout::TextStyle;
|
||||
use warpui::ui_components::button::ButtonTooltipPosition;
|
||||
use warpui::ui_components::components::{Coords, UiComponent, UiComponentStyles};
|
||||
use warpui::windowing::WindowManager;
|
||||
use warpui::{
|
||||
elements, windowing, AppContext, BlurContext, CursorInfo, Element, Entity, EntityId,
|
||||
FocusContext, ModelAsRef, ModelContext, ModelHandle, SingletonEntity, TypedActionView, View,
|
||||
ViewContext, ViewHandle, WindowId,
|
||||
};
|
||||
/// The editor interfaces that we publicly expose to consumers.
|
||||
/// This should be a very limited set; if you need to add something here,
|
||||
/// you should carefully consider if it leaks the internal details of the editor.
|
||||
@@ -21,124 +92,45 @@ pub use {
|
||||
use self::model::{LocalSelections, Selection, UpdateBufferOption};
|
||||
use super::soft_wrap::{ClampDirection, DisplayPointAndClampDirection};
|
||||
use super::Point;
|
||||
#[cfg(feature = "voice_input")]
|
||||
use crate::view_components::FeaturePopup;
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
use element::CommandXRayMouseStateHandle;
|
||||
use figma_utils::is_figma_png;
|
||||
use galaxy_core::{safe_error, send_telemetry_from_ctx};
|
||||
use galaxy_util::{path::ShellFamily, user_input::UserInput};
|
||||
use galaxyui::platform::keyboard::KeyCode;
|
||||
use galaxyui::ui_components::button::ButtonTooltipPosition;
|
||||
use galaxyui::ui_components::components::{Coords, UiComponent, UiComponentStyles};
|
||||
use galaxyui::{elements, ViewHandle};
|
||||
use itertools::{Either, Itertools};
|
||||
use mime_guess::from_path;
|
||||
use model::{
|
||||
Anchor, AnchorBias, Bias, DisplayMap, DrawableSelection, LocalPendingSelection, LocalSelection,
|
||||
MarkedTextState, MovementResult, SelectionMode, SubwordBoundaries, ToBufferOffset,
|
||||
ToCharOffset, ToDisplayPoint, ToPoint,
|
||||
};
|
||||
use model::{EditorModel, EditorModelEvent, Edits};
|
||||
use pathfinder_color::ColorU;
|
||||
use settings::Setting as _;
|
||||
use snapshot::{EditorHeightShrinkDelay, ViewSnapshot};
|
||||
use vec1::{vec1, Vec1};
|
||||
|
||||
use crate::ai::agent::ImageContext;
|
||||
use crate::ai::blocklist::{BlocklistAIContextModel, PendingAttachment, PendingFile};
|
||||
use crate::ai::blocklist::{BlocklistAIContextModel, InputType, PendingAttachment, PendingFile};
|
||||
use crate::ai::predict::next_command_model::{NextCommandModel, NextCommandSuggestionState};
|
||||
use crate::appearance::Appearance;
|
||||
use crate::channel::{Channel, ChannelState};
|
||||
use crate::editor::accept_autosuggestion_keybinding_view::AcceptAutosuggestionKeybinding;
|
||||
use crate::editor::autosuggestion_ignore_view::{AutosuggestionIgnore, AutosuggestionIgnoreEvent};
|
||||
use crate::editor::RangeExt;
|
||||
use crate::features::FeatureFlag;
|
||||
use crate::search::ai_context_menu::mixer::AIContextMenuSearchableAction;
|
||||
use crate::search::ai_context_menu::view::{
|
||||
AIContextMenu, AIContextMenuCategory, AIContextMenuEvent,
|
||||
};
|
||||
use crate::server::telemetry::TelemetryEvent;
|
||||
use crate::settings_view::flags;
|
||||
use crate::suggestions::ignored_suggestions_model::{IgnoredSuggestionsModel, SuggestionType};
|
||||
use crate::ui_components::buttons::icon_button;
|
||||
use crate::ui_components::icons;
|
||||
use crate::view_components::DismissibleToast;
|
||||
use crate::vim_registers::{RegisterContent, VimRegisters};
|
||||
use crate::workspace::ToastStack;
|
||||
use crate::{ai::blocklist::InputType, settings::AISettings};
|
||||
|
||||
use crate::editor::RangeExt;
|
||||
use crate::features::FeatureFlag;
|
||||
#[cfg(feature = "voice_input")]
|
||||
use crate::settings::AISettingsChangedEvent;
|
||||
use crate::settings::{AppEditorSettings, CursorBlink};
|
||||
use crate::settings::{
|
||||
AppEditorSettingsChangedEvent, CursorDisplayType, InputSettings, SelectionSettings,
|
||||
AISettings, AppEditorSettings, AppEditorSettingsChangedEvent, CursorBlink, CursorDisplayType,
|
||||
InputSettings, SelectionSettings,
|
||||
};
|
||||
use crate::settings_view::flags;
|
||||
use crate::suggestions::ignored_suggestions_model::{IgnoredSuggestionsModel, SuggestionType};
|
||||
use crate::terminal::grid_size_util::grid_cell_dimensions;
|
||||
use crate::terminal::model::block::BlockId;
|
||||
use crate::themes::theme::Fill;
|
||||
use crate::ui_components::avatar::{Avatar, AvatarContent};
|
||||
use crate::ui_components::buttons::icon_button;
|
||||
use crate::ui_components::icons;
|
||||
use crate::util::bindings::{cmd_or_ctrl_shift, keybinding_name_to_keystroke, CustomAction};
|
||||
use crate::util::clipboard::clipboard_content_with_escaped_paths;
|
||||
use crate::util::color::{ContrastingColor, MinimumAllowedContrast};
|
||||
use crate::util::image::{resize_image, MAX_IMAGE_COUNT_FOR_QUERY, MAX_IMAGE_SIZE_BYTES};
|
||||
use crate::util::merge_ranges;
|
||||
use crate::{workspace::Workspace, BlocklistAIHistoryModel};
|
||||
use anyhow::Result;
|
||||
use core::f32;
|
||||
use galaxy_core::semantic_selection::SemanticSelection;
|
||||
use std::path::Path;
|
||||
use vim::vim::{
|
||||
BracketChar, CharacterMotion, Direction, FindCharMotion, FirstNonWhitespaceMotion,
|
||||
InsertPosition, LineMotion, ModeTransition, MotionType, TextObjectInclusion, TextObjectType,
|
||||
VimHandler, VimMode, VimModel, VimMotion, VimOperand, VimOperator, VimState, VimSubscriber,
|
||||
VimTextObject, WordBound, WordMotion, WordType,
|
||||
};
|
||||
use vim::{
|
||||
vim_a_block, vim_a_paragraph, vim_a_quote, vim_a_word, vim_inner_block, vim_inner_paragraph,
|
||||
vim_inner_quote, vim_inner_word, vim_word_iterator_from_offset,
|
||||
};
|
||||
|
||||
use num_traits::SaturatingSub;
|
||||
use parking_lot::Mutex;
|
||||
use pathfinder_geometry::vector::Vector2F;
|
||||
|
||||
use async_fs;
|
||||
use galaxy_completer::completer::Description;
|
||||
use galaxy_editor::editor::NavigationKey;
|
||||
use galaxyui::actions::StandardAction;
|
||||
use galaxyui::clipboard::ClipboardContent;
|
||||
use galaxyui::elements::{
|
||||
ChildView, Container, CornerRadius, CrossAxisAlignment, Flex, Hoverable, MainAxisSize,
|
||||
ParentElement, Shrinkable, DEFAULT_UI_LINE_HEIGHT_RATIO,
|
||||
};
|
||||
use galaxyui::elements::{MouseStateHandle, Radius};
|
||||
use galaxyui::fonts::{FamilyId, Properties, Weight};
|
||||
use galaxyui::keymap::{Keystroke, PerPlatformKeystroke};
|
||||
use galaxyui::platform::{Cursor, FilePickerConfiguration, OperatingSystem};
|
||||
use galaxyui::r#async::{SpawnedFutureHandle, Timer};
|
||||
use galaxyui::text::word_boundaries::WordBoundariesPolicy;
|
||||
use galaxyui::text::TextBuffer;
|
||||
use galaxyui::text_layout::TextStyle;
|
||||
use galaxyui::windowing::WindowManager;
|
||||
use galaxyui::{
|
||||
accessibility::{AccessibilityContent, ActionAccessibilityContent, GalaxyA11yRole},
|
||||
fonts::Cache as FontCache,
|
||||
keymap::{EditableBinding, FixedBinding},
|
||||
AppContext, Element, Entity, ModelAsRef, ModelHandle, View, ViewContext, WindowId,
|
||||
};
|
||||
use galaxyui::{windowing, BlurContext, EntityId, FocusContext};
|
||||
use galaxyui::{CursorInfo, ModelContext, SingletonEntity, TypedActionView};
|
||||
use std::collections::HashMap;
|
||||
use std::{borrow::Cow, rc::Rc};
|
||||
use std::{
|
||||
cmp::{self, Ordering},
|
||||
fmt,
|
||||
ops::Range,
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use string_offset::{ByteOffset, CharOffset};
|
||||
use crate::view_components::DismissibleToast;
|
||||
#[cfg(feature = "voice_input")]
|
||||
use crate::view_components::FeaturePopup;
|
||||
use crate::vim_registers::{RegisterContent, VimRegisters};
|
||||
use crate::workspace::{ToastStack, Workspace};
|
||||
use crate::BlocklistAIHistoryModel;
|
||||
|
||||
const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
|
||||
const DEFAULT_TAB_SIZE: usize = 4;
|
||||
@@ -371,12 +363,16 @@ pub fn init(ctx: &mut AppContext) {
|
||||
FixedBinding::new(
|
||||
"pageup",
|
||||
EditorAction::PageUp,
|
||||
id!("EditorView") & !id!("IMEOpen"),
|
||||
id!("EditorView")
|
||||
& !id!("IMEOpen")
|
||||
& !id!(flags::TERMINAL_INPUT_PAGE_KEYS_HANDLED_BY_INPUT),
|
||||
),
|
||||
FixedBinding::new(
|
||||
"pagedown",
|
||||
EditorAction::PageDown,
|
||||
id!("EditorView") & !id!("IMEOpen"),
|
||||
id!("EditorView")
|
||||
& !id!("IMEOpen")
|
||||
& !id!(flags::TERMINAL_INPUT_PAGE_KEYS_HANDLED_BY_INPUT),
|
||||
),
|
||||
// Some editable bindings currently have more than 1 action.
|
||||
// Below's the list of those.
|
||||
@@ -3239,6 +3235,23 @@ impl EditorView {
|
||||
ctx.emit(Event::BufferReinitialized);
|
||||
}
|
||||
|
||||
/// Exits the ephemeral loading state created by `set_buffer_text_ignoring_undo`
|
||||
/// without touching the CRDT buffer or emitting any `UpdatePeers` operations.
|
||||
/// The editor switches back to displaying the regular collaborative buffer.
|
||||
pub fn exit_ephemeral_loading_state(&mut self, ctx: &mut ViewContext<Self>) {
|
||||
self.editor_model.update(ctx, |model, ctx| {
|
||||
model.exit_ephemeral_loading_state(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
/// Shows an empty display-only ephemeral overlay for immediate visual feedback.
|
||||
/// See [`EditorModel::show_display_only_empty_buffer`] for the full contract.
|
||||
pub fn show_display_only_empty_buffer(&mut self, ctx: &mut ViewContext<Self>) {
|
||||
self.editor_model.update(ctx, |model, ctx| {
|
||||
model.show_display_only_empty_buffer(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn register_remote_peer(
|
||||
&mut self,
|
||||
replica_id: ReplicaId,
|
||||
@@ -3613,6 +3626,16 @@ impl EditorView {
|
||||
self.autogrow = autogrow;
|
||||
}
|
||||
|
||||
/// Replaces the editor's enter-key settings at runtime (effective next keystroke).
|
||||
pub fn set_enter_settings(&mut self, settings: EnterSettings) {
|
||||
self.enter_settings = settings;
|
||||
}
|
||||
|
||||
/// Returns the current enter-key settings (for tests asserting applied settings).
|
||||
pub fn enter_settings(&self) -> EnterSettings {
|
||||
self.enter_settings.clone()
|
||||
}
|
||||
|
||||
/// Clears the transient editor-height shrink-delay state.
|
||||
///
|
||||
/// The shrink-delay is useful when height briefly drops during autosuggestion churn, but
|
||||
@@ -4073,7 +4096,7 @@ impl EditorView {
|
||||
|
||||
/// This method is triggered by the initial double-click selection (not a drag). The way the
|
||||
/// selection range expands depends on user settings stored in SemanticSelection. Smart-select may
|
||||
/// be enabled, and if not, the word-breaking characters may have been overriden.
|
||||
/// be enabled, and if not, the word-breaking characters may have been overridden.
|
||||
pub fn select_word(&mut self, position: &DisplayPoint, ctx: &mut ViewContext<Self>) {
|
||||
let position = *position;
|
||||
|
||||
@@ -4236,7 +4259,7 @@ impl EditorView {
|
||||
.first_selection(ctx)
|
||||
.head()
|
||||
.to_byte_offset(buffer)
|
||||
.expect("Selection must be convertable to byte offset")
|
||||
.expect("Selection must be convertible to byte offset")
|
||||
}
|
||||
|
||||
/// Finds the start byte of the token under the given point (the offset at
|
||||
@@ -6346,12 +6369,12 @@ impl EditorView {
|
||||
selection
|
||||
.head()
|
||||
.to_point(buffer)
|
||||
.expect("Selection head must be convertable to a Point")
|
||||
.expect("Selection head must be convertible to a Point")
|
||||
} else {
|
||||
selection
|
||||
.end()
|
||||
.to_point(buffer)
|
||||
.expect("Selection end must be convertable to a Point")
|
||||
.expect("Selection end must be convertible to a Point")
|
||||
};
|
||||
if let Ok(mut boundaries) =
|
||||
buffer.subword_ends_from_offset_exclusive(end_position)
|
||||
@@ -6361,7 +6384,7 @@ impl EditorView {
|
||||
.anchor_before(
|
||||
subword_start
|
||||
.to_display_point(map, ctx)
|
||||
.expect("Subword start must be convertable to a DisplayPoint"),
|
||||
.expect("Subword start must be convertible to a DisplayPoint"),
|
||||
Bias::Right,
|
||||
ctx,
|
||||
)
|
||||
@@ -6394,12 +6417,12 @@ impl EditorView {
|
||||
selection
|
||||
.head()
|
||||
.to_point(buffer)
|
||||
.expect("Selection head must be convertable to a Point")
|
||||
.expect("Selection head must be convertible to a Point")
|
||||
} else {
|
||||
selection
|
||||
.start()
|
||||
.to_point(buffer)
|
||||
.expect("Selection start must be convertable to a Point")
|
||||
.expect("Selection start must be convertible to a Point")
|
||||
};
|
||||
if let Ok(mut boundaries) =
|
||||
buffer.subword_backward_starts_from_offset_exclusive(start_position)
|
||||
@@ -6409,7 +6432,7 @@ impl EditorView {
|
||||
.anchor_before(
|
||||
subword_start
|
||||
.to_display_point(map, ctx)
|
||||
.expect("Subword start must be convertable to a DisplayPoint"),
|
||||
.expect("Subword start must be convertible to a DisplayPoint"),
|
||||
Bias::Left,
|
||||
ctx,
|
||||
)
|
||||
@@ -8770,5 +8793,5 @@ pub fn position_id_for_first_cursor(editor_view_id: EntityId) -> String {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "mod_test.rs"]
|
||||
#[path = "mod_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
use anyhow::Error;
|
||||
use itertools::Itertools;
|
||||
use pathfinder_geometry::vector::vec2f;
|
||||
use settings::ToggleableSetting;
|
||||
use unindent::Unindent;
|
||||
use warpui::color::ColorU;
|
||||
use warpui::platform::WindowStyle;
|
||||
use warpui::text_layout::TextFrame;
|
||||
use warpui::windowing::WindowManager;
|
||||
use warpui::{AddSingletonModel, App, UpdateModel, UpdateView};
|
||||
|
||||
use super::*;
|
||||
use crate::auth::AuthStateProvider;
|
||||
use crate::editor::soft_wrap::FrameLayouts;
|
||||
@@ -11,16 +22,6 @@ use crate::test_util::settings::initialize_settings_for_tests;
|
||||
use crate::workspace::sync_inputs::SyncedInputState;
|
||||
use crate::workspace::ToastStack;
|
||||
use crate::workspaces::user_workspaces::UserWorkspaces;
|
||||
use anyhow::Error;
|
||||
use galaxyui::color::ColorU;
|
||||
use galaxyui::platform::WindowStyle;
|
||||
use galaxyui::text_layout::TextFrame;
|
||||
use galaxyui::windowing::WindowManager;
|
||||
use galaxyui::{AddSingletonModel, App, UpdateModel, UpdateView};
|
||||
use itertools::Itertools;
|
||||
use pathfinder_geometry::vector::vec2f;
|
||||
use settings::ToggleableSetting;
|
||||
use unindent::Unindent;
|
||||
|
||||
impl EditorView {
|
||||
fn selected_ranges(&self, app: &AppContext) -> Vec<Range<DisplayPoint>> {
|
||||
@@ -2316,7 +2317,6 @@ fn test_partial_autosuggestion() -> Result<()> {
|
||||
|
||||
#[test]
|
||||
fn test_placeholder_text() {
|
||||
use galaxyui::text_layout::LayoutCache;
|
||||
|
||||
App::test((), |mut app| async move {
|
||||
initialize_app(&mut app);
|
||||
@@ -3772,7 +3772,7 @@ fn test_add_cursor() {
|
||||
.soft_wrap_state()
|
||||
.update(frame_layouts);
|
||||
|
||||
// Add cursors (mimicing cursors added via mouse).
|
||||
// Add cursors (mimicking cursors added via mouse).
|
||||
let existing_cursors = vec![
|
||||
DisplayPoint::new(1, 1)..DisplayPoint::new(1, 2),
|
||||
DisplayPoint::new(2, 2)..DisplayPoint::new(2, 3),
|
||||
@@ -4520,7 +4520,7 @@ fn test_drag_and_drop_files_applies_path_transformer() {
|
||||
});
|
||||
}
|
||||
|
||||
#[path = "vim_handler_test.rs"]
|
||||
#[path = "vim_handler_tests.rs"]
|
||||
mod vim_handler_tests;
|
||||
|
||||
#[path = "marked_text_tests.rs"]
|
||||
@@ -1,11 +1,13 @@
|
||||
use super::{time, Buffer};
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Ordering;
|
||||
use std::ops::Range;
|
||||
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use string_offset::CharOffset;
|
||||
use time::Lamport;
|
||||
|
||||
use super::{time, Buffer};
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Hash, Serialize, Deserialize)]
|
||||
pub enum Anchor {
|
||||
Start,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use super::time::ReplicaId;
|
||||
use super::Operation;
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
/// An operation queue to defer buffer edits
|
||||
/// that cannot yet be applied.
|
||||
#[cfg_attr(test, derive(Clone))]
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use crate::editor::view::model::buffer::time::ReplicaId;
|
||||
use crate::editor::view::model::buffer::EditOperation;
|
||||
use itertools::Itertools;
|
||||
use string_offset::CharOffset;
|
||||
|
||||
use super::super::time::{Global, Lamport};
|
||||
use super::{DeferredOperations, Operation};
|
||||
use itertools::Itertools;
|
||||
use string_offset::CharOffset;
|
||||
use crate::editor::view::model::buffer::time::ReplicaId;
|
||||
use crate::editor::view::model::buffer::EditOperation;
|
||||
|
||||
fn edit_operation(lamport: Lamport) -> Operation {
|
||||
Operation::Edit(EditOperation {
|
||||
|
||||
@@ -7,6 +7,30 @@ mod text;
|
||||
mod time;
|
||||
mod undo;
|
||||
|
||||
use std::cmp::{self};
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
use std::iter::{self, Iterator};
|
||||
use std::ops::{AddAssign, Range};
|
||||
use std::rc::Rc;
|
||||
use std::str;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use itertools::Itertools;
|
||||
use lazy_static::lazy_static;
|
||||
#[cfg(test)]
|
||||
use rand::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use string_offset::{ByteOffset, CharOffset};
|
||||
use sum_tree::{self, Cursor, FilterCursor, SeekBias, SumTree};
|
||||
use time::{Global, Lamport};
|
||||
use undo::{LocalUndoStack, UndoHistory};
|
||||
use vec1::{vec1, Vec1};
|
||||
use warpui::color::ColorU;
|
||||
use warpui::text::point::Point;
|
||||
use warpui::text::words::is_default_word_boundary;
|
||||
use warpui::text::{BufferIndex, TextBuffer};
|
||||
use warpui::text_layout::TextStyle;
|
||||
use warpui::{Entity, ModelContext};
|
||||
/// The public interfaces that we expose to the model.
|
||||
/// This should be a very limited set of APIs and should
|
||||
/// not expose the internal details of the buffer.
|
||||
@@ -19,36 +43,10 @@ pub use {
|
||||
};
|
||||
|
||||
use super::selections::{
|
||||
AsSelection, MarkedTextState, RemoteSelection, RemoteSelections, Selection,
|
||||
AsSelection, LocalSelections, MarkedTextState, RemoteSelection, RemoteSelections, Selection,
|
||||
};
|
||||
use super::EditorSnapshot;
|
||||
use super::{selections::LocalSelections, LocalSelection};
|
||||
use super::{EditorSnapshot, LocalSelection};
|
||||
use crate::editor::{CursorColors, PlainTextEditorViewAction};
|
||||
use anyhow::{anyhow, Result};
|
||||
use galaxyui::color::ColorU;
|
||||
use galaxyui::text::{point::Point, words::is_default_word_boundary, BufferIndex, TextBuffer};
|
||||
use galaxyui::text_layout::TextStyle;
|
||||
use galaxyui::{Entity, ModelContext};
|
||||
use itertools::Itertools;
|
||||
use lazy_static::lazy_static;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeSet;
|
||||
use std::rc::Rc;
|
||||
use std::{
|
||||
cmp::{self},
|
||||
collections::HashMap,
|
||||
iter::{self, Iterator},
|
||||
ops::{AddAssign, Range},
|
||||
str,
|
||||
};
|
||||
use string_offset::{ByteOffset, CharOffset};
|
||||
use sum_tree::{self, Cursor, FilterCursor, SeekBias, SumTree};
|
||||
use time::{Global, Lamport};
|
||||
use undo::{LocalUndoStack, UndoHistory};
|
||||
use vec1::{vec1, Vec1};
|
||||
|
||||
#[cfg(test)]
|
||||
use rand::prelude::*;
|
||||
|
||||
#[cfg_attr(test, derive(Clone))]
|
||||
pub struct Buffer {
|
||||
@@ -1544,9 +1542,10 @@ impl Buffer {
|
||||
|
||||
#[cfg(test)]
|
||||
pub(super) fn selections_for_replica(&self, replica: ReplicaId) -> Vec<Range<CharOffset>> {
|
||||
use crate::editor::RangeExt;
|
||||
use itertools::Either;
|
||||
|
||||
use crate::editor::RangeExt;
|
||||
|
||||
let selections = if replica == self.replica_id() {
|
||||
Either::Left(
|
||||
self.local_selections
|
||||
@@ -2506,7 +2505,7 @@ impl Buffer {
|
||||
// the correct offset. For example a fragment with the text "foo" starting at 0
|
||||
// would be spliced into "fo" if the start of the range was 2.
|
||||
if range.start > *chars_to_fragment_start {
|
||||
// Note that the current_fragment is ovewritten to be the latter part of the splice
|
||||
// Note that the current_fragment is overwritten to be the latter part of the splice
|
||||
// whereas the spliced_fragment contains the earlier part of the splice.
|
||||
// The spliced fragment should not be styled.
|
||||
let spliced_fragment = Self::splice_fragment_at_char_offset(
|
||||
@@ -3892,5 +3891,5 @@ pub enum RangesWhenEditing {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "mod_test.rs"]
|
||||
#[path = "mod_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
+11
-9
@@ -2,20 +2,22 @@
|
||||
// which is at odds with this clippy rule.
|
||||
#![allow(clippy::single_range_in_vec_init)]
|
||||
|
||||
use crate::editor::{soft_wrap::ClampDirection, tests::RandomCharIter};
|
||||
use async_channel::Receiver;
|
||||
use test::Network;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashSet;
|
||||
use std::pin::{pin, Pin};
|
||||
|
||||
use super::*;
|
||||
use async_channel::Receiver;
|
||||
use enclose::enclose;
|
||||
use futures::StreamExt;
|
||||
use galaxyui::{color::ColorU, App, ModelHandle};
|
||||
use rand::prelude::StdRng;
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::HashSet,
|
||||
pin::{pin, Pin},
|
||||
};
|
||||
use test::Network;
|
||||
use galaxyui::color::ColorU;
|
||||
use galaxyui::{App, ModelHandle};
|
||||
|
||||
use super::*;
|
||||
use crate::editor::soft_wrap::ClampDirection;
|
||||
use crate::editor::tests::RandomCharIter;
|
||||
|
||||
fn visible_text_styles(buffer: &Buffer) -> Vec<Option<TextStyle>> {
|
||||
buffer
|
||||
@@ -1,10 +1,12 @@
|
||||
use super::{CharOffset, Point};
|
||||
use galaxyui::text::{
|
||||
word_boundaries::WordBoundariesApproach, words::is_subword_boundary_char, TextBuffer,
|
||||
};
|
||||
use itertools::Either;
|
||||
use std::iter::Peekable;
|
||||
|
||||
use itertools::Either;
|
||||
use galaxyui::text::word_boundaries::WordBoundariesApproach;
|
||||
use galaxyui::text::words::is_subword_boundary_char;
|
||||
use galaxyui::text::TextBuffer;
|
||||
|
||||
use super::{CharOffset, Point};
|
||||
|
||||
pub struct SubwordBoundaries<'a, T: TextBuffer + ?Sized> {
|
||||
offset: CharOffset,
|
||||
chars: Peekable<Either<T::Chars<'a>, T::CharsReverse<'a>>>,
|
||||
@@ -266,7 +268,7 @@ impl<T: TextBuffer + ?Sized> Iterator for SubwordBoundaries<'_, T> {
|
||||
/// Storage for characters from the buffer, used by the `SubwordBoundaries`
|
||||
/// iterator to find the start and end of subwords.
|
||||
struct CharWindow {
|
||||
/// A store of characters retreived from the `chars` iterator.
|
||||
/// A store of characters retrieved from the `chars` iterator.
|
||||
///
|
||||
/// `char_window[0]`: character at the current offset.
|
||||
///
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::super::Buffer;
|
||||
use galaxyui::text::point::Point;
|
||||
|
||||
use super::super::Buffer;
|
||||
|
||||
#[test]
|
||||
fn test_subword_boundaries_forward_starts() {
|
||||
let mut buffer: Buffer;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use rand::Rng;
|
||||
|
||||
/// Test utilities for testing the buffer.
|
||||
use super::time::ReplicaId;
|
||||
use rand::Rng;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) struct Network<T: Clone> {
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
use std::cmp;
|
||||
use std::fmt::{self, Debug};
|
||||
use std::ops::{Bound, Index, Range, RangeBounds};
|
||||
use std::rc::Rc;
|
||||
|
||||
use arrayvec::ArrayVec;
|
||||
use galaxyui::text::point::Point;
|
||||
use galaxyui::text_layout::TextStyle;
|
||||
use num_traits::SaturatingSub;
|
||||
use std::{
|
||||
cmp,
|
||||
fmt::{self, Debug},
|
||||
ops::{Bound, Index, Range, RangeBounds},
|
||||
rc::Rc,
|
||||
};
|
||||
use string_offset::{ByteOffset, CharOffset};
|
||||
use sum_tree::{self, SeekBias, SumTree};
|
||||
|
||||
@@ -390,5 +389,5 @@ impl Text {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "text_test.rs"]
|
||||
#[path = "text_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
+2
-1
@@ -1,7 +1,8 @@
|
||||
use super::*;
|
||||
use std::collections::HashSet;
|
||||
use std::iter::FromIterator;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_basic() {
|
||||
let text = Text::from(String::from("ab\ncd€\nfghij\nkl¢m"));
|
||||
@@ -1,7 +1,8 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::{self, Ordering};
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
const BASE_REPLICA_ID: &str = "0";
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
use std::{
|
||||
collections::{BTreeSet, HashMap},
|
||||
time::Duration,
|
||||
};
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
use std::time::Duration;
|
||||
|
||||
use bounded_vec_deque::BoundedVecDeque;
|
||||
use instant::Instant;
|
||||
|
||||
use super::time::{Global, Lamport, LamportValue};
|
||||
use crate::editor::view::{model::LocalSelections, PlainTextEditorViewAction};
|
||||
use crate::editor::view::model::LocalSelections;
|
||||
use crate::editor::view::PlainTextEditorViewAction;
|
||||
|
||||
/// The maximum time we will batch consecutive edits for the same [`Action`].
|
||||
/// The "batch" here is not to be confused with the [`Buffer`]'s notion
|
||||
@@ -269,5 +268,5 @@ impl PlainTextEditorViewAction {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "undo_test.rs"]
|
||||
#[path = "undo_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
+7
-12
@@ -1,17 +1,12 @@
|
||||
use super::{LocalUndoStack, UndoHistory};
|
||||
use crate::editor::{
|
||||
view::model::{
|
||||
buffer::{
|
||||
time::{Global, Lamport, LamportValue},
|
||||
undo::UNDO_REDO_BATCH_TIMER,
|
||||
ReplicaId,
|
||||
},
|
||||
Anchor, LocalSelection, LocalSelections,
|
||||
},
|
||||
PlainTextEditorViewAction,
|
||||
};
|
||||
use vec1::vec1;
|
||||
|
||||
use super::{LocalUndoStack, UndoHistory};
|
||||
use crate::editor::view::model::buffer::time::{Global, Lamport, LamportValue};
|
||||
use crate::editor::view::model::buffer::undo::UNDO_REDO_BATCH_TIMER;
|
||||
use crate::editor::view::model::buffer::ReplicaId;
|
||||
use crate::editor::view::model::{Anchor, LocalSelection, LocalSelections};
|
||||
use crate::editor::PlainTextEditorViewAction;
|
||||
|
||||
fn local_selections(start: Anchor, end: Anchor) -> LocalSelections {
|
||||
LocalSelections {
|
||||
pending: None,
|
||||
@@ -1,17 +1,15 @@
|
||||
use std::cmp::{self, Ordering};
|
||||
use std::iter::Take;
|
||||
use std::ops::Range;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use string_offset::CharOffset;
|
||||
use sum_tree::{self, Cursor, Dimension, SeekBias, SumTree};
|
||||
|
||||
use super::super::buffer::{AnchorRangeExt, TextSummary};
|
||||
use super::buffer::StylizedChar;
|
||||
use super::{buffer, Anchor, Buffer, DisplayPoint, Edit, Point, ToCharOffset};
|
||||
use crate::util::extensions::SliceExt as _;
|
||||
use anyhow::{anyhow, Result};
|
||||
use galaxyui::text_layout::TextStyle;
|
||||
use galaxyui::{AppContext, ModelHandle};
|
||||
use std::{
|
||||
cmp::{self, Ordering},
|
||||
iter::Take,
|
||||
ops::Range,
|
||||
};
|
||||
use string_offset::CharOffset;
|
||||
use sum_tree::{self, Cursor, Dimension, SeekBias, SumTree};
|
||||
|
||||
pub struct FoldMap {
|
||||
buffer: ModelHandle<Buffer>,
|
||||
@@ -509,5 +507,5 @@ impl<'a> Dimension<'a, TransformSummary> for CharOffset {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "fold_map_test.rs"]
|
||||
#[path = "fold_map_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
+5
-3
@@ -1,8 +1,9 @@
|
||||
use tests::buffer::RangesWhenEditing;
|
||||
use warpui::App;
|
||||
|
||||
use super::*;
|
||||
use crate::editor::tests::{sample_text, RandomCharIter};
|
||||
use crate::editor::EditOrigin;
|
||||
use galaxyui::App;
|
||||
use tests::buffer::RangesWhenEditing;
|
||||
|
||||
#[test]
|
||||
fn test_basic_folds() -> Result<()> {
|
||||
@@ -125,9 +126,10 @@ fn test_merging_folds_via_edit() -> Result<()> {
|
||||
|
||||
#[test]
|
||||
fn test_random_folds() -> Result<()> {
|
||||
use super::super::buffer::ToPoint;
|
||||
use rand::prelude::*;
|
||||
|
||||
use super::super::buffer::ToPoint;
|
||||
|
||||
for seed in 0..100 {
|
||||
println!("{seed:?}");
|
||||
let mut rng = StdRng::seed_from_u64(seed);
|
||||
@@ -1,16 +1,16 @@
|
||||
mod fold_map;
|
||||
|
||||
use super::buffer::{self, Anchor, Buffer, Edit, StylizedChar, ToCharOffset, ToPoint};
|
||||
use crate::editor::soft_wrap::{self, DisplayPointAndClampDirection, SoftWrapPoint, SoftWrapState};
|
||||
use std::cmp;
|
||||
use std::ops::Range;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
pub use fold_map::BufferRows;
|
||||
use fold_map::FoldMap;
|
||||
use galaxyui::text::point::Point;
|
||||
use galaxyui::{AppContext, Entity, ModelContext, ModelHandle};
|
||||
use std::cmp;
|
||||
use std::ops::Range;
|
||||
use string_offset::CharOffset;
|
||||
|
||||
use super::buffer::{self, Anchor, Buffer, Edit, StylizedChar, ToCharOffset, ToPoint};
|
||||
use crate::editor::soft_wrap::{self, DisplayPointAndClampDirection, SoftWrapPoint, SoftWrapState};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum Bias {
|
||||
Left,
|
||||
@@ -327,7 +327,12 @@ impl DisplayMap {
|
||||
self.fold_map.apply_edits(edits, ctx)
|
||||
}
|
||||
|
||||
fn handle_buffer_event(&mut self, event: &buffer::Event, ctx: &mut ModelContext<Self>) {
|
||||
fn handle_buffer_event(
|
||||
&mut self,
|
||||
_: ModelHandle<Buffer>,
|
||||
event: &buffer::Event,
|
||||
ctx: &mut ModelContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
buffer::Event::Edited { edits, .. } => self.apply_edits(edits, ctx).unwrap(),
|
||||
buffer::Event::StylesUpdated
|
||||
@@ -512,5 +517,5 @@ pub fn collapse_tabs(
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "mod_test.rs"]
|
||||
#[path = "mod_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
+4
-4
@@ -1,10 +1,10 @@
|
||||
use crate::editor::tests::sample_text;
|
||||
|
||||
use super::*;
|
||||
use crate::editor::EditOrigin;
|
||||
use anyhow::Error;
|
||||
use galaxyui::App;
|
||||
|
||||
use super::*;
|
||||
use crate::editor::tests::sample_text;
|
||||
use crate::editor::EditOrigin;
|
||||
|
||||
#[test]
|
||||
fn test_chars_at() -> Result<()> {
|
||||
App::test((), |mut app| async move {
|
||||
@@ -2,68 +2,50 @@ mod buffer;
|
||||
mod display_map;
|
||||
mod selections;
|
||||
|
||||
use self::buffer::Peer;
|
||||
use std::cmp::{self};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::mem;
|
||||
use std::ops::Range;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub use {
|
||||
buffer::{
|
||||
Anchor, AnchorBias, Chars, EditOrigin, Operation as CrdtOperation, PeerSelectionData,
|
||||
ReplicaId, SubwordBoundaries, TextRun, TextStyleOperation, ToBufferOffset, ToCharOffset,
|
||||
ToPoint,
|
||||
},
|
||||
display_map::{Bias, DisplayMap, DisplayPoint, MovementResult, ToDisplayPoint},
|
||||
selections::{
|
||||
DrawableSelection, LocalDrawableSelectionData, LocalPendingSelection, LocalSelection,
|
||||
LocalSelections, MarkedTextState, RemoteDrawableSelectionData, SelectAction, Selection,
|
||||
SelectionMode,
|
||||
},
|
||||
};
|
||||
|
||||
use std::{
|
||||
cmp::{self},
|
||||
collections::{HashMap, HashSet},
|
||||
mem,
|
||||
ops::Range,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use galaxyui::{
|
||||
accessibility::{AccessibilityContent, GalaxyA11yRole},
|
||||
text_layout::TextStyle,
|
||||
AppContext, Entity, ModelAsRef, ModelContext, ModelHandle,
|
||||
};
|
||||
use galaxyui::{
|
||||
text::{point::Point, word_boundaries::WordBoundariesPolicy, TextBuffer},
|
||||
SingletonEntity,
|
||||
pub use buffer::{
|
||||
Anchor, AnchorBias, Chars, EditOrigin, Operation as CrdtOperation, PeerSelectionData,
|
||||
ReplicaId, SubwordBoundaries, TextRun, TextStyleOperation, ToBufferOffset, ToCharOffset,
|
||||
ToPoint,
|
||||
};
|
||||
use buffer::{Buffer, Text};
|
||||
pub use display_map::{Bias, DisplayMap, DisplayPoint, MovementResult, ToDisplayPoint};
|
||||
use itertools::FoldWhile::{Continue, Done};
|
||||
use itertools::Itertools;
|
||||
use lazy_static::lazy_static;
|
||||
use num_traits::SaturatingSub;
|
||||
pub use selections::{
|
||||
DrawableSelection, LocalDrawableSelectionData, LocalPendingSelection, LocalSelection,
|
||||
LocalSelections, MarkedTextState, RemoteDrawableSelectionData, SelectAction, Selection,
|
||||
SelectionMode,
|
||||
};
|
||||
use string_offset::{ByteOffset, CharOffset};
|
||||
use vec1::{vec1, Vec1};
|
||||
|
||||
use crate::{editor::RangeExt, vim_registers::VimRegisters};
|
||||
|
||||
use vim::{
|
||||
find_next_paragraph_end, find_previous_paragraph_start,
|
||||
vim::{
|
||||
BracketChar, CharacterMotion, Direction, FindCharMotion, FirstNonWhitespaceMotion,
|
||||
LineMotion, MotionType, TextObjectInclusion, TextObjectType, VimOperator, WordBound,
|
||||
WordMotion,
|
||||
},
|
||||
vim_a_paragraph, vim_inner_paragraph,
|
||||
use vim::vim::{
|
||||
BracketChar, CharacterMotion, Direction, FindCharMotion, FirstNonWhitespaceMotion, LineMotion,
|
||||
MotionType, TextObjectInclusion, TextObjectType, VimOperator, WordBound, WordMotion,
|
||||
};
|
||||
use vim::{
|
||||
vim_a_block, vim_a_quote, vim_a_word, vim_find_char_on_line, vim_find_matching_bracket,
|
||||
vim_inner_block, vim_inner_quote, vim_inner_word, vim_word_iterator_from_offset,
|
||||
find_next_paragraph_end, find_previous_paragraph_start, vim_a_block, vim_a_paragraph,
|
||||
vim_a_quote, vim_a_word, vim_find_char_on_line, vim_find_matching_bracket, vim_inner_block,
|
||||
vim_inner_paragraph, vim_inner_quote, vim_inner_word, vim_word_iterator_from_offset,
|
||||
};
|
||||
use warpui::accessibility::{AccessibilityContent, WarpA11yRole};
|
||||
use warpui::text::point::Point;
|
||||
use warpui::text::word_boundaries::WordBoundariesPolicy;
|
||||
use warpui::text::TextBuffer;
|
||||
use warpui::text_layout::TextStyle;
|
||||
use warpui::{AppContext, Entity, ModelAsRef, ModelContext, ModelHandle, SingletonEntity};
|
||||
|
||||
use buffer::{Buffer, Text};
|
||||
|
||||
use self::buffer::Peer;
|
||||
use super::{movement, PlainTextEditorViewAction, SelectionInsertion, ValidInputType};
|
||||
|
||||
use itertools::{
|
||||
FoldWhile::{Continue, Done},
|
||||
Itertools,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use crate::editor::RangeExt;
|
||||
use crate::vim_registers::VimRegisters;
|
||||
|
||||
lazy_static! {
|
||||
static ref AUTOCOMPLETE_SYMBOLS: HashMap<&'static str, &'static str> = HashMap::from([
|
||||
@@ -365,6 +347,14 @@ struct BufferAndDisplayMaps {
|
||||
/// A buffer and display map dedicated for ephemeral edits (see [`UpdateBufferOption::IsEphemeral`]).
|
||||
/// If [`Some`], then the ephemeral buffer is active.
|
||||
ephemeral: Option<(ModelHandle<Buffer>, ModelHandle<DisplayMap>)>,
|
||||
|
||||
/// When `true`, the active ephemeral buffer is "display-only": it exists purely for
|
||||
/// visual feedback and its content must NOT be applied to the regular buffer when the
|
||||
/// ephemeral is exited (materialized). On materialization the ephemeral is simply
|
||||
/// discarded and the edit proceeds directly on the regular buffer without any
|
||||
/// content-restoration step. This avoids generating spurious CRDT delete operations
|
||||
/// that would corrupt the shared collaborative state.
|
||||
ephemeral_is_display_only: bool,
|
||||
}
|
||||
|
||||
impl BufferAndDisplayMaps {
|
||||
@@ -389,9 +379,11 @@ impl BufferAndDisplayMaps {
|
||||
/// Deactivates any ephemeral state.
|
||||
fn deactivate_ephemeral_state(&mut self) {
|
||||
self.ephemeral.take();
|
||||
self.ephemeral_is_display_only = false;
|
||||
}
|
||||
|
||||
/// Activates a new ephemeral state.
|
||||
/// Activates a new regular ephemeral state whose content will be applied
|
||||
/// to the regular buffer when the ephemeral is exited (materialized).
|
||||
fn activate_new_ephemeral_state(&mut self, ctx: &mut ModelContext<EditorModel>) {
|
||||
let tab_size = self.regular.1.as_ref(ctx).tab_size();
|
||||
let ephemeral_buffer = ctx.add_model(|_| Buffer::new(""));
|
||||
@@ -406,6 +398,15 @@ impl BufferAndDisplayMaps {
|
||||
EditorModel::handle_display_map_event,
|
||||
);
|
||||
self.ephemeral = Some((ephemeral_buffer, ephemeral_display_map));
|
||||
self.ephemeral_is_display_only = false;
|
||||
}
|
||||
|
||||
/// Activates a display-only ephemeral state. When this ephemeral is materialized
|
||||
/// (exited by a non-ephemeral edit), its content is discarded rather than applied
|
||||
/// to the regular buffer, preventing spurious CRDT operations.
|
||||
fn activate_display_only_ephemeral_state(&mut self, ctx: &mut ModelContext<EditorModel>) {
|
||||
self.activate_new_ephemeral_state(ctx);
|
||||
self.ephemeral_is_display_only = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -556,6 +557,7 @@ impl EditorModel {
|
||||
buffer_and_display_map: BufferAndDisplayMaps {
|
||||
regular,
|
||||
ephemeral: None,
|
||||
ephemeral_is_display_only: false,
|
||||
},
|
||||
vim_visual_tails: vec![],
|
||||
consecutive_autocomplete_insertion_edits_counter: 0,
|
||||
@@ -621,6 +623,31 @@ impl EditorModel {
|
||||
self.buffer_and_display_map.deactivate_ephemeral_state();
|
||||
}
|
||||
|
||||
/// Exits an ephemeral loading state (created by `set_buffer_text_ignoring_undo`)
|
||||
/// without touching the CRDT buffer or generating any `UpdatePeers` operations.
|
||||
/// After this call the editor displays the regular collaborative buffer, allowing
|
||||
/// any pending remote delete operations to become visible.
|
||||
pub fn exit_ephemeral_loading_state(&mut self, ctx: &mut ModelContext<Self>) {
|
||||
if self.is_ephemeral() {
|
||||
self.buffer_and_display_map.deactivate_ephemeral_state();
|
||||
ctx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
/// Shows an empty buffer as a display-only ephemeral overlay for immediate visual
|
||||
/// feedback, without touching the regular CRDT buffer or emitting `UpdatePeers` ops.
|
||||
///
|
||||
/// When the viewer next makes an edit (materializing the ephemeral), the empty content
|
||||
/// is **discarded** rather than applied to the regular buffer — so no spurious CRDT
|
||||
/// delete ops are generated for whatever is currently in the regular buffer (e.g.
|
||||
/// another viewer's concurrent edits). The edit instead proceeds directly on the
|
||||
/// regular buffer as-is.
|
||||
pub fn show_display_only_empty_buffer(&mut self, ctx: &mut ModelContext<Self>) {
|
||||
self.buffer_and_display_map
|
||||
.activate_display_only_ephemeral_state(ctx);
|
||||
ctx.notify();
|
||||
}
|
||||
|
||||
fn refresh_batch_version(&mut self, ctx: &mut ModelContext<Self>) {
|
||||
self.buffer_handle().update(ctx, |buffer, _| {
|
||||
buffer.refresh_version_on_edits_and_selection_changes_batch()
|
||||
@@ -696,15 +723,26 @@ impl EditorModel {
|
||||
// 2) we star the the batch on the correct (regular vs. ephemeral) buffer
|
||||
let restore_from_snapshot = if can_edit && edit.is_ephemeral() {
|
||||
let snapshot = self.as_snapshot(ctx);
|
||||
let vim_visual_tail_offsets = self.vim_visual_tail_offsets(ctx);
|
||||
self.buffer_and_display_map
|
||||
.activate_new_ephemeral_state(ctx);
|
||||
Some(snapshot)
|
||||
Some((snapshot, vim_visual_tail_offsets))
|
||||
} else if can_edit && self.is_ephemeral() && edit.update_buffer.is_some() {
|
||||
// We're materializing an ephemeral edit, so snapshot the ephemeral buffer
|
||||
// so that we can apply it to the regular buffer.
|
||||
let snapshot = self.as_snapshot(ctx);
|
||||
self.buffer_and_display_map.deactivate_ephemeral_state();
|
||||
Some(snapshot)
|
||||
if self.buffer_and_display_map.ephemeral_is_display_only {
|
||||
// Display-only ephemeral: discard the ephemeral content entirely and
|
||||
// proceed directly on the regular buffer. Do NOT snapshot-and-restore,
|
||||
// which would generate spurious CRDT delete ops for whatever the regular
|
||||
// buffer currently contains (e.g. another viewer's concurrent edits).
|
||||
self.buffer_and_display_map.deactivate_ephemeral_state();
|
||||
None
|
||||
} else {
|
||||
// Regular ephemeral (history picker, model selector, etc.): snapshot the
|
||||
// ephemeral buffer so its content can be applied to the regular buffer.
|
||||
let snapshot = self.as_snapshot(ctx);
|
||||
let vim_visual_tail_offsets = self.vim_visual_tail_offsets(ctx);
|
||||
self.buffer_and_display_map.deactivate_ephemeral_state();
|
||||
Some((snapshot, vim_visual_tail_offsets))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -720,8 +758,8 @@ impl EditorModel {
|
||||
// right buffer state. For example, if we tried to select
|
||||
// without having restored the snapshot, we would be selecting
|
||||
// on the wrong underlying buffer.
|
||||
if let Some(snapshot) = restore_from_snapshot {
|
||||
self.restore_from_snapshot(snapshot, ctx);
|
||||
if let Some((snapshot, vim_visual_tail_offsets)) = restore_from_snapshot {
|
||||
self.restore_from_snapshot(snapshot, vim_visual_tail_offsets, ctx);
|
||||
// Refresh batch version here as we already recorded edits for the snapshot
|
||||
// restoration.
|
||||
self.refresh_batch_version(ctx);
|
||||
@@ -789,7 +827,17 @@ impl EditorModel {
|
||||
if let Err(e) = buffer.apply_ops(operations, ctx) {
|
||||
log::warn!("Failed to apply remote edits to buffer: {e}");
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// If a display-only empty ephemeral is showing (optimistic clear after sending
|
||||
// an agent prompt), exit it now that a real CRDT update has arrived. This makes
|
||||
// the actual collaborative buffer state immediately visible to the viewer, whether
|
||||
// that's an empty buffer from the sharer's delete ops or another participant's
|
||||
// concurrent edits.
|
||||
if self.buffer_and_display_map.ephemeral_is_display_only {
|
||||
self.buffer_and_display_map.deactivate_ephemeral_state();
|
||||
ctx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn interaction_state(&self) -> InteractionState {
|
||||
@@ -1069,7 +1117,7 @@ impl EditorModel {
|
||||
&& self.all_cursors_next_character_matches_char(
|
||||
text.chars()
|
||||
.next()
|
||||
.expect("Autocompleted symobl should have at least one character"),
|
||||
.expect("Autocompleted symbol should have at least one character"),
|
||||
ctx,
|
||||
)
|
||||
{
|
||||
@@ -1283,8 +1331,21 @@ impl EditorModel {
|
||||
);
|
||||
}
|
||||
|
||||
fn vim_visual_tail_offsets<C: ModelAsRef>(&self, ctx: &C) -> Vec<CharOffset> {
|
||||
let buffer = self.buffer(ctx);
|
||||
self.vim_visual_tails
|
||||
.iter()
|
||||
.filter_map(|tail| tail.to_char_offset(buffer).ok())
|
||||
.collect()
|
||||
}
|
||||
|
||||
// Used for restoring the buffer from a snapshot.
|
||||
fn restore_from_snapshot(&mut self, snapshot: EditorSnapshot, ctx: &mut ModelContext<Self>) {
|
||||
fn restore_from_snapshot(
|
||||
&mut self,
|
||||
snapshot: EditorSnapshot,
|
||||
vim_visual_tail_offsets: Vec<CharOffset>,
|
||||
ctx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let version = self.buffer_handle().as_ref(ctx).versions();
|
||||
self.clear_selections(ctx);
|
||||
self.clear_buffer(ctx);
|
||||
@@ -1359,6 +1420,15 @@ impl EditorModel {
|
||||
}]
|
||||
};
|
||||
self.change_selections(new_selections, ctx);
|
||||
|
||||
let new_visual_tails: Vec<Anchor> = {
|
||||
let buffer = self.buffer(ctx);
|
||||
vim_visual_tail_offsets
|
||||
.iter()
|
||||
.filter_map(|offset| buffer.anchor_before(*offset).ok())
|
||||
.collect()
|
||||
};
|
||||
self.vim_visual_tails = new_visual_tails;
|
||||
}
|
||||
|
||||
pub fn selection_insertion_index(&self, start: &Anchor, app: &AppContext) -> usize {
|
||||
@@ -2942,7 +3012,12 @@ impl EditorModel {
|
||||
|
||||
/// The private interface.
|
||||
impl EditorModel {
|
||||
fn handle_buffer_event(&mut self, event: &buffer::Event, ctx: &mut ModelContext<Self>) {
|
||||
fn handle_buffer_event(
|
||||
&mut self,
|
||||
_: ModelHandle<Buffer>,
|
||||
event: &buffer::Event,
|
||||
ctx: &mut ModelContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
buffer::Event::Edited { edit_origin, .. } => ctx.emit(EditorModelEvent::Edited {
|
||||
edit_origin: *edit_origin,
|
||||
@@ -2957,17 +3032,19 @@ impl EditorModel {
|
||||
|
||||
fn handle_buffer_event_for_non_collaborative_editor(
|
||||
&mut self,
|
||||
handle: ModelHandle<Buffer>,
|
||||
event: &buffer::Event,
|
||||
ctx: &mut ModelContext<Self>,
|
||||
) {
|
||||
// For non-collaborative editors, we don't care about fanning out updates to peers.
|
||||
if !matches!(event, buffer::Event::UpdatePeers { .. }) {
|
||||
self.handle_buffer_event(event, ctx);
|
||||
self.handle_buffer_event(handle, event, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_display_map_event(
|
||||
&mut self,
|
||||
_: ModelHandle<DisplayMap>,
|
||||
event: &display_map::Event,
|
||||
ctx: &mut ModelContext<Self>,
|
||||
) {
|
||||
@@ -3114,7 +3191,7 @@ impl EditorModel {
|
||||
selection.set_end(
|
||||
buffer
|
||||
.anchor_before(point)
|
||||
.expect("valid point should be vaild anchor"),
|
||||
.expect("valid point should be valid anchor"),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3221,5 +3298,5 @@ impl EditorModel {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "mod_test.rs"]
|
||||
#[path = "mod_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use galaxyui::{text_layout::TextStyle, App};
|
||||
use string_offset::{ByteOffset, CharOffset};
|
||||
|
||||
use crate::editor::{EditorSnapshot, PlainTextEditorViewAction, TextRun, ValidInputType};
|
||||
use vec1::vec1;
|
||||
use galaxyui::text_layout::TextStyle;
|
||||
use galaxyui::App;
|
||||
|
||||
use super::{EditOrigin, EditorModel, Edits, InteractionState, UpdateBufferOption};
|
||||
use vec1::vec1;
|
||||
use crate::editor::{EditorSnapshot, PlainTextEditorViewAction, TextRun, ValidInputType};
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
@@ -635,7 +636,7 @@ fn test_restoring_invalid_selections() {
|
||||
ByteOffset::from(0)..ByteOffset::from(3),
|
||||
)],
|
||||
};
|
||||
model.restore_from_snapshot(snapshot, ctx);
|
||||
model.restore_from_snapshot(snapshot, Vec::new(), ctx);
|
||||
},
|
||||
),
|
||||
)
|
||||
@@ -1,4 +1,6 @@
|
||||
use std::{cmp::Ordering, mem, ops::Range};
|
||||
use std::cmp::Ordering;
|
||||
use std::mem;
|
||||
use std::ops::Range;
|
||||
|
||||
use galaxyui::text::point::Point;
|
||||
use galaxyui::AppContext;
|
||||
@@ -7,18 +9,12 @@ use serde::{Deserialize, Serialize};
|
||||
use string_offset::{ByteOffset, CharOffset};
|
||||
use vec1::Vec1;
|
||||
|
||||
use super::{
|
||||
buffer::{Anchor, Buffer, LamportValue, ToBufferOffset, ToCharOffset, ToPoint},
|
||||
display_map::{DisplayMap, ToDisplayPoint},
|
||||
DisplayPoint, ReplicaId,
|
||||
};
|
||||
use crate::{
|
||||
editor::{
|
||||
soft_wrap::{ClampDirection, DisplayPointAndClampDirection},
|
||||
CursorColors, RangeExt,
|
||||
},
|
||||
ui_components::avatar::Avatar,
|
||||
};
|
||||
use super::buffer::{Anchor, Buffer, LamportValue, ToBufferOffset, ToCharOffset, ToPoint};
|
||||
use super::display_map::{DisplayMap, ToDisplayPoint};
|
||||
use super::{DisplayPoint, ReplicaId};
|
||||
use crate::editor::soft_wrap::{ClampDirection, DisplayPointAndClampDirection};
|
||||
use crate::editor::{CursorColors, RangeExt};
|
||||
use crate::ui_components::avatar::Avatar;
|
||||
|
||||
/// This type encapsulates enough information about a selection to be able to
|
||||
/// draw it. Compared to the `Selection` type, the points are converted based on
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use super::{DisplayMap, DisplayPoint};
|
||||
use anyhow::Result;
|
||||
use galaxyui::AppContext;
|
||||
|
||||
use super::{DisplayMap, DisplayPoint};
|
||||
|
||||
pub fn left(
|
||||
map: &DisplayMap,
|
||||
mut point: DisplayPoint,
|
||||
|
||||
@@ -1,49 +1,38 @@
|
||||
use core::f32;
|
||||
use std::borrow::Cow;
|
||||
use std::cmp::{self};
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use instant::Instant;
|
||||
use parking_lot::Mutex;
|
||||
use pathfinder_geometry::vector::{vec2f, Vector2F};
|
||||
use rayon::prelude::*;
|
||||
use string_offset::ByteOffset;
|
||||
use warp_completer::completer::Description;
|
||||
use warpui::fonts::{Cache as FontCache, FamilyId, Properties};
|
||||
use warpui::platform::LineStyle;
|
||||
use warpui::text::point::Point;
|
||||
use warpui::text_layout::{
|
||||
self, default_compute_baseline_position_fn, ClipConfig, ComputeBaselinePositionFn, LayoutCache,
|
||||
StyleAndFont, TextAlignment, TextStyle, DEFAULT_TOP_BOTTOM_RATIO,
|
||||
};
|
||||
use warpui::{AppContext, EntityId, ModelHandle};
|
||||
|
||||
use super::model::EditorModel;
|
||||
use super::{
|
||||
AutosuggestionLocation, AutosuggestionState, AutosuggestionType,
|
||||
BaselinePositionComputationMethod, Bias, DisplayPoint, DrawableSelection, ScrollState,
|
||||
ToBufferOffset, ToDisplayPoint,
|
||||
ToBufferOffset, ToCharOffset, ToDisplayPoint, ToPoint,
|
||||
};
|
||||
use super::{ToCharOffset, ToPoint};
|
||||
use crate::editor::soft_wrap::FrameLayouts;
|
||||
#[cfg(feature = "voice_input")]
|
||||
use crate::editor::view::voice::VoiceInputState;
|
||||
|
||||
use crate::editor::soft_wrap::FrameLayouts;
|
||||
use crate::terminal::grid_size_util::grid_compute_baseline_position_fn;
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use pathfinder_geometry::vector::{vec2f, Vector2F};
|
||||
|
||||
use anyhow::Result;
|
||||
use core::f32;
|
||||
use galaxy_completer::completer::Description;
|
||||
use galaxyui::text::point::Point;
|
||||
use instant::Instant;
|
||||
use rayon::prelude::*;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
use std::{
|
||||
cmp::{self},
|
||||
ops::Range,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use string_offset::ByteOffset;
|
||||
|
||||
use galaxyui::fonts::{FamilyId, Properties};
|
||||
use galaxyui::platform::LineStyle;
|
||||
use galaxyui::text_layout::{
|
||||
default_compute_baseline_position_fn, ClipConfig, ComputeBaselinePositionFn, StyleAndFont,
|
||||
TextAlignment, TextStyle, DEFAULT_TOP_BOTTOM_RATIO,
|
||||
};
|
||||
use galaxyui::EntityId;
|
||||
use galaxyui::{
|
||||
fonts::Cache as FontCache,
|
||||
text_layout::{self, LayoutCache},
|
||||
AppContext, ModelHandle,
|
||||
};
|
||||
|
||||
/// Ratio to calculate font size of cursor avatar.
|
||||
/// Found experimentally to scale the best proportionally with
|
||||
/// current font size and the avatar's size.
|
||||
|
||||
+43
-10
@@ -1,10 +1,10 @@
|
||||
use itertools::Itertools;
|
||||
use unindent::Unindent;
|
||||
use galaxyui::platform::WindowStyle;
|
||||
use galaxyui::{App, EntityIdSet, ViewHandle};
|
||||
|
||||
use super::*;
|
||||
use crate::editor::EditorView;
|
||||
use galaxyui::platform::WindowStyle;
|
||||
use galaxyui::{App, ViewHandle};
|
||||
use itertools::Itertools;
|
||||
use std::collections::HashSet;
|
||||
use unindent::Unindent;
|
||||
|
||||
/// Helper function for testing vim mode commands.
|
||||
/// This creates an editor with the given content and enters Vim Normal mode,
|
||||
@@ -296,7 +296,7 @@ fn test_vim_number_repeat_line_motion() {
|
||||
let window_id = app.read(|ctx| editor.window_id(ctx));
|
||||
let mut presenter = galaxyui::presenter::Presenter::new(window_id);
|
||||
|
||||
let mut updated = HashSet::new();
|
||||
let mut updated = EntityIdSet::default();
|
||||
updated.insert(app.root_view_id(window_id).unwrap());
|
||||
let invalidation = galaxyui::WindowInvalidation {
|
||||
updated,
|
||||
@@ -376,7 +376,7 @@ fn test_vim_number_repeat_character_motion() {
|
||||
let window_id = app.read(|ctx| editor.window_id(ctx));
|
||||
let mut presenter = galaxyui::presenter::Presenter::new(window_id);
|
||||
|
||||
let mut updated = HashSet::new();
|
||||
let mut updated = EntityIdSet::default();
|
||||
updated.insert(app.root_view_id(window_id).unwrap());
|
||||
let invalidation = galaxyui::WindowInvalidation {
|
||||
updated,
|
||||
@@ -2395,7 +2395,7 @@ fn test_vim_begin_line_above() {
|
||||
let window_id = app.read(|ctx| editor.window_id(ctx));
|
||||
let mut presenter = galaxyui::presenter::Presenter::new(window_id);
|
||||
|
||||
let mut updated = HashSet::new();
|
||||
let mut updated = EntityIdSet::default();
|
||||
updated.insert(app.root_view_id(window_id).unwrap());
|
||||
let invalidation = galaxyui::WindowInvalidation {
|
||||
updated,
|
||||
@@ -7127,6 +7127,39 @@ fn test_vim_visual_mode_paste() {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vim_visual_mode_paste_after_history_recall() {
|
||||
App::test((), |mut app| async move {
|
||||
initialize_app(&mut app);
|
||||
|
||||
let editor = add_editor_vim_normal_mode("echo foo bar", &mut app);
|
||||
|
||||
// Yank "foo" into the unnamed register.
|
||||
editor.update(&mut app, |view, ctx| {
|
||||
view.vim_user_insert("wve", ctx);
|
||||
view.vim_user_insert("y", ctx);
|
||||
});
|
||||
|
||||
// Simulate scrolling up the command history (e.g. pressing "k" in normal mode), which
|
||||
// replaces the editor buffer with a previously run command via an ephemeral edit.
|
||||
editor.update(&mut app, |view, ctx| {
|
||||
view.set_buffer_text_ignoring_undo("echo xxx bar", ctx);
|
||||
});
|
||||
|
||||
// Select "xxx" in the recalled command and paste "foo" over it. The selected range should
|
||||
// be replaced, not appended to.
|
||||
editor.update(&mut app, |view, ctx| {
|
||||
view.vim_user_insert("0wve", ctx);
|
||||
view.vim_user_insert("p", ctx);
|
||||
});
|
||||
|
||||
editor.read(&app, |view, ctx| {
|
||||
assert_eq!(view.buffer_text(ctx), "echo foo bar");
|
||||
assert_eq!(view.vim_mode(ctx), Some(VimMode::Normal));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vim_unnamed_system_clipboard() {
|
||||
App::test((), |mut app| async move {
|
||||
@@ -7625,7 +7658,7 @@ fn test_vim_visual_selection_with_newlines() {
|
||||
// Ensure layout so vertical motions (j/k) use real geometry for goal columns.
|
||||
let window_id = app.read(|ctx| editor.window_id(ctx));
|
||||
let mut presenter = galaxyui::presenter::Presenter::new(window_id);
|
||||
let mut updated = std::collections::HashSet::new();
|
||||
let mut updated = EntityIdSet::default();
|
||||
updated.insert(app.root_view_id(window_id).unwrap());
|
||||
let invalidation = galaxyui::WindowInvalidation {
|
||||
updated,
|
||||
@@ -7673,7 +7706,7 @@ fn test_vim_visual_selection_with_newlines() {
|
||||
// Re-layout for new content
|
||||
let window_id = app.read(|ctx| editor.window_id(ctx));
|
||||
let mut presenter = galaxyui::presenter::Presenter::new(window_id);
|
||||
let mut updated = std::collections::HashSet::new();
|
||||
let mut updated = EntityIdSet::default();
|
||||
updated.insert(app.root_view_id(window_id).unwrap());
|
||||
let invalidation = galaxyui::WindowInvalidation {
|
||||
updated,
|
||||
@@ -1,4 +1,16 @@
|
||||
use super::{EditorAction, EditorView, VoiceTranscriptionOptions};
|
||||
use settings::Setting as _;
|
||||
use voice_input::{StartListeningError, VoiceInput, VoiceSessionResult};
|
||||
use galaxy_core::send_telemetry_from_ctx;
|
||||
use galaxy_core::ui::theme::color::internal_colors;
|
||||
use galaxy_core::ui::theme::AnsiColorIdentifier;
|
||||
use warpui::elements::{Container, CornerRadius, Icon, Radius};
|
||||
use warpui::platform::Cursor;
|
||||
use warpui::r#async::SpawnedFutureHandle;
|
||||
use warpui::ui_components::button::ButtonTooltipPosition;
|
||||
use warpui::ui_components::components::{Coords, UiComponent, UiComponentStyles};
|
||||
use warpui::{elements, AppContext, Element, SingletonEntity, ViewContext, ViewHandle};
|
||||
|
||||
use super::{EditorAction, EditorView, VoiceTranscriber, VoiceTranscriptionOptions};
|
||||
use crate::ai::blocklist::InputType;
|
||||
use crate::appearance::Appearance;
|
||||
use crate::editor::EditorElement;
|
||||
@@ -11,21 +23,6 @@ use crate::ui_components::icons;
|
||||
use crate::view_components::{FeaturePopup, NewFeaturePopupLabel};
|
||||
use crate::workspace::ToastStack;
|
||||
use crate::workspaces::user_workspaces::UserWorkspaces;
|
||||
use galaxy_core::send_telemetry_from_ctx;
|
||||
use galaxy_core::ui::theme::color::internal_colors;
|
||||
use galaxy_core::ui::theme::AnsiColorIdentifier;
|
||||
use galaxyui::elements;
|
||||
use galaxyui::elements::{Container, CornerRadius, Icon, Radius};
|
||||
use galaxyui::platform::Cursor;
|
||||
use galaxyui::r#async::SpawnedFutureHandle;
|
||||
use galaxyui::ui_components::button::ButtonTooltipPosition;
|
||||
use galaxyui::ui_components::components::{Coords, UiComponent, UiComponentStyles};
|
||||
use galaxyui::ViewHandle;
|
||||
use galaxyui::{AppContext, Element, SingletonEntity, ViewContext};
|
||||
use settings::Setting as _;
|
||||
use voice_input::{StartListeningError, VoiceInput, VoiceSessionResult};
|
||||
|
||||
use super::VoiceTranscriber;
|
||||
|
||||
const MICROPHONE_ACCESS_ERROR_ID: &str = "MICROPHONE_ACCESS_ERROR";
|
||||
const NUM_TIMES_TO_SHOW_VOICE_NEW_FEATURE_POPUP: usize = 4;
|
||||
|
||||
Reference in New Issue
Block a user