first pass of merging in warp (doesn't build)

This commit is contained in:
Ryan Ward
2026-07-01 16:08:58 -05:00
parent 2f64909469
commit 4770ac06b5
3662 changed files with 414574 additions and 89772 deletions
+1 -1
View File
@@ -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.;
+5 -6
View File
@@ -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.;
+9 -4
View File
@@ -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;
+6 -5
View File
@@ -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 {
+41 -52
View File
@@ -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.
+4 -2
View File
@@ -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
View File
@@ -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"]
+5 -3
View File
@@ -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 {
+30 -31
View File
@@ -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;
@@ -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;
+4 -2
View File
@@ -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> {
+6 -7
View File
@@ -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;
@@ -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"));
+2 -1
View File
@@ -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";
+5 -6
View File
@@ -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;
@@ -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;
@@ -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);
+13 -8
View File
@@ -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;
@@ -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 {
+145 -68
View File
@@ -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);
},
),
)
+9 -13
View File
@@ -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
+2 -1
View File
@@ -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,
+26 -37
View File
@@ -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.
@@ -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,
+13 -16
View File
@@ -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;