first pass of merging in warp (doesn't build)
This commit is contained in:
@@ -1,12 +1,29 @@
|
||||
use std::cell::RefCell;
|
||||
|
||||
use pathfinder_color::ColorU;
|
||||
use pathfinder_geometry::vector::Vector2F;
|
||||
use galaxy_core::ui::appearance::Appearance;
|
||||
use galaxy_core::ui::theme::Fill;
|
||||
use warp_editor::render::element::VerticalExpansionBehavior;
|
||||
use warpui::elements::{
|
||||
Border, ChildView, Clipped, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, Flex,
|
||||
MainAxisAlignment, MainAxisSize, ParentElement, Radius, Shrinkable, Text,
|
||||
};
|
||||
use warpui::keymap::Keystroke;
|
||||
use warpui::text_layout::ClipConfig;
|
||||
use warpui::units::Pixels;
|
||||
use warpui::{
|
||||
AppContext, Element, Entity, FocusContext, ModelHandle, SingletonEntity, TypedActionView, View,
|
||||
ViewContext, ViewHandle,
|
||||
};
|
||||
|
||||
use crate::code::editor::comments::{EditorCommentsModel, PendingCommentEvent};
|
||||
use crate::code::editor::line::EditorLineLocation;
|
||||
use crate::code_review::comments::{CommentId, CommentOrigin};
|
||||
use crate::editor::InteractionState;
|
||||
use crate::notebooks::editor::{
|
||||
model::NotebooksEditorModel,
|
||||
rich_text_styles,
|
||||
view::{EditorViewEvent, RichTextEditorConfig, RichTextEditorView},
|
||||
};
|
||||
use crate::notebooks::editor::model::NotebooksEditorModel;
|
||||
use crate::notebooks::editor::rich_text_styles;
|
||||
use crate::notebooks::editor::view::{EditorViewEvent, RichTextEditorConfig, RichTextEditorView};
|
||||
use crate::notebooks::link::{NotebookLinks, SessionSource};
|
||||
use crate::settings::FontSettings;
|
||||
use crate::ui_components::blended_colors;
|
||||
@@ -14,22 +31,6 @@ use crate::ui_components::icons::Icon;
|
||||
use crate::view_components::action_button::{
|
||||
ActionButton, ButtonSize, DangerNakedTheme, KeystrokeSource, NakedTheme, PrimaryTheme,
|
||||
};
|
||||
use galaxy_core::ui::{appearance::Appearance, theme::Fill};
|
||||
use galaxy_editor::render::element::VerticalExpansionBehavior;
|
||||
use galaxyui::{
|
||||
elements::{
|
||||
Border, ChildView, Clipped, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment,
|
||||
Flex, MainAxisAlignment, MainAxisSize, ParentElement, Radius, Shrinkable, Text,
|
||||
},
|
||||
keymap::Keystroke,
|
||||
text_layout::ClipConfig,
|
||||
units::Pixels,
|
||||
AppContext, Element, Entity, FocusContext, ModelHandle, SingletonEntity, TypedActionView, View,
|
||||
ViewContext, ViewHandle,
|
||||
};
|
||||
use pathfinder_color::ColorU;
|
||||
use pathfinder_geometry::vector::Vector2F;
|
||||
use std::cell::RefCell;
|
||||
|
||||
/// Default width of the comment editor, in pixels.
|
||||
pub(crate) const DEFAULT_COMMENT_MAX_WIDTH: f32 = 750.0;
|
||||
@@ -165,7 +166,10 @@ impl CommentEditor {
|
||||
let save_button = ctx.add_typed_action_view(|ctx| {
|
||||
ActionButton::new("Comment", PrimaryTheme)
|
||||
.with_keybinding(
|
||||
KeystrokeSource::Fixed(Keystroke::parse("cmdorctrl-enter").unwrap_or_default()),
|
||||
KeystrokeSource::Fixed(
|
||||
Keystroke::parse(crate::code_review::CODE_REVIEW_SUBMIT_KEYSTROKE)
|
||||
.unwrap_or_default(),
|
||||
),
|
||||
ctx,
|
||||
)
|
||||
.on_click(|ctx| {
|
||||
@@ -423,8 +427,8 @@ impl View for CommentEditor {
|
||||
)
|
||||
.with_child(
|
||||
Container::new(footer_row)
|
||||
.with_vertical_padding(8.)
|
||||
.with_horizontal_padding(8.)
|
||||
.with_vertical_padding(4.)
|
||||
.with_horizontal_padding(4.)
|
||||
.with_border(Border::top(1.).with_border_fill(border_color))
|
||||
.finish(),
|
||||
)
|
||||
|
||||
@@ -1,31 +1,26 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use galaxyui::{
|
||||
platform::WindowStyle, presenter::ChildView, App, Element, Entity, TypedActionView, View,
|
||||
ViewHandle, WindowId,
|
||||
};
|
||||
|
||||
use super::{create_editable_comment_markdown_editor, create_readonly_comment_markdown_editor};
|
||||
use crate::notebooks::editor::view::RichTextEditorView;
|
||||
use repo_metadata::repositories::DetectedRepositories;
|
||||
use repo_metadata::RepoMetadataModel;
|
||||
use warpui::platform::WindowStyle;
|
||||
use warpui::presenter::ChildView;
|
||||
use warpui::{App, Element, Entity, TypedActionView, View, ViewHandle, WindowId};
|
||||
|
||||
use crate::{
|
||||
appearance::Appearance,
|
||||
auth::AuthStateProvider,
|
||||
cloud_object::model::persistence::CloudModel,
|
||||
notebooks::{
|
||||
editor::keys::NotebookKeybindings,
|
||||
link::{NotebookLinks, SessionSource},
|
||||
},
|
||||
search::files::model::FileSearchModel,
|
||||
server::server_api::{team::MockTeamClient, workspace::MockWorkspaceClient},
|
||||
settings_view::keybindings::KeybindingChangedNotifier,
|
||||
terminal::keys::TerminalKeybindings,
|
||||
test_util::settings::initialize_settings_for_tests,
|
||||
workspace::ActiveSession,
|
||||
GlobalResourceHandles, GlobalResourceHandlesProvider, UserWorkspaces,
|
||||
};
|
||||
use super::{create_editable_comment_markdown_editor, create_readonly_comment_markdown_editor};
|
||||
use crate::appearance::Appearance;
|
||||
use crate::auth::AuthStateProvider;
|
||||
use crate::cloud_object::model::persistence::CloudModel;
|
||||
use crate::notebooks::editor::keys::NotebookKeybindings;
|
||||
use crate::notebooks::editor::view::RichTextEditorView;
|
||||
use crate::notebooks::link::{NotebookLinks, SessionSource};
|
||||
use crate::search::files::model::FileSearchModel;
|
||||
use crate::server::server_api::team::MockTeamClient;
|
||||
use crate::server::server_api::workspace::MockWorkspaceClient;
|
||||
use crate::settings_view::keybindings::KeybindingChangedNotifier;
|
||||
use crate::terminal::keys::TerminalKeybindings;
|
||||
use crate::test_util::settings::initialize_settings_for_tests;
|
||||
use crate::workspace::ActiveSession;
|
||||
use crate::{GlobalResourceHandles, GlobalResourceHandlesProvider, UserWorkspaces};
|
||||
|
||||
struct TestView {
|
||||
editor: ViewHandle<RichTextEditorView>,
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#![cfg_attr(target_family = "wasm", allow(dead_code, unused_imports))]
|
||||
// Adding this file level gate as some of the code around editability is not used in WASM yet.
|
||||
|
||||
use std::{collections::HashMap, ops::Range, rc::Rc, sync::Arc};
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Range;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use futures::stream::AbortHandle;
|
||||
use galaxy_core::ui::theme::Fill;
|
||||
@@ -16,14 +19,16 @@ use pathfinder_color::ColorU;
|
||||
use rangemap::RangeMap;
|
||||
use similar::{ChangeTag, DiffOp, TextDiff};
|
||||
use string_offset::CharOffset;
|
||||
use galaxy_core::ui::theme::{AnsiColorIdentifier, Fill};
|
||||
use galaxy_editor::content::edit::TemporaryBlock;
|
||||
use galaxy_editor::content::version::BufferVersion;
|
||||
use galaxy_editor::multiline::{AnyMultilineString, MultilineStr, MultilineString, LF};
|
||||
use galaxy_editor::render::model::{Decoration, LineCount, LineDecoration};
|
||||
|
||||
use super::super::DiffResult;
|
||||
|
||||
use crate::{
|
||||
appearance::Appearance,
|
||||
code::editor::{line::EditorLineLocation, line_iterator::LineIterator},
|
||||
};
|
||||
use galaxy_core::ui::theme::AnsiColorIdentifier;
|
||||
use crate::appearance::Appearance;
|
||||
use crate::code::editor::line::EditorLineLocation;
|
||||
use crate::code::editor::line_iterator::LineIterator;
|
||||
|
||||
const OVERLAY_ALPHA: u8 = 56;
|
||||
const INLINE_OVERLAY_ALPHA: u8 = 71;
|
||||
|
||||
@@ -4,9 +4,8 @@ use galaxy_editor::multiline::{MultilineStr, MultilineString};
|
||||
use rangemap::RangeMap;
|
||||
use unindent::Unindent as _;
|
||||
|
||||
use crate::code::editor::diff::ChangeType;
|
||||
|
||||
use super::DiffModel;
|
||||
use crate::code::editor::diff::ChangeType;
|
||||
|
||||
#[test]
|
||||
fn test_diff_generation() {
|
||||
@@ -76,7 +75,6 @@ fn test_diff_generation() {
|
||||
|
||||
#[test]
|
||||
fn test_reverse_action() {
|
||||
use galaxyui::App;
|
||||
App::test((), |_| async move {
|
||||
let mut diff_model = DiffModel::new();
|
||||
diff_model.set_base(MultilineString::apply(
|
||||
@@ -109,7 +107,6 @@ fn test_reverse_action() {
|
||||
|
||||
#[test]
|
||||
fn test_reverse_action_replaced_newlines() {
|
||||
use galaxyui::App;
|
||||
App::test((), |_| async move {
|
||||
let mut diff_model = DiffModel::new();
|
||||
let base_text = r"
|
||||
@@ -192,7 +189,6 @@ fn test_reverse_action_replaced_newlines() {
|
||||
|
||||
#[test]
|
||||
fn test_reverse_action_replaced_text() {
|
||||
use galaxyui::App;
|
||||
App::test((), |_| async move {
|
||||
let mut diff_model = DiffModel::new();
|
||||
let base_text = r"
|
||||
@@ -269,7 +265,6 @@ fn test_reverse_action_replaced_text() {
|
||||
|
||||
#[test]
|
||||
fn test_reverse_action_deleted_lines() {
|
||||
use galaxyui::App;
|
||||
App::test((), |_| async move {
|
||||
let mut diff_model = DiffModel::new();
|
||||
let base_text = r"
|
||||
@@ -353,7 +348,6 @@ fn test_reverse_action_deleted_lines() {
|
||||
|
||||
#[test]
|
||||
fn test_diff_count_before_line() {
|
||||
use galaxyui::App;
|
||||
App::test((), |_| async move {
|
||||
let mut diff_model = DiffModel::new();
|
||||
diff_model.set_base(
|
||||
@@ -373,7 +367,6 @@ fn test_diff_count_before_line() {
|
||||
|
||||
#[test]
|
||||
fn test_unified_diff() {
|
||||
use galaxyui::App;
|
||||
App::test((), |_| async move {
|
||||
let diff = DiffModel::retrieve_unified_diff_internal(
|
||||
MultilineStr::try_new("Hello World\nThis is the second line.\nThis is the third.")
|
||||
@@ -395,7 +388,6 @@ fn test_unified_diff() {
|
||||
/// to produce duplicate deletion and insertion hunks for what is logically a replacement.
|
||||
#[test]
|
||||
fn test_coalesce_replacements() {
|
||||
use galaxyui::App;
|
||||
App::test((), |_| async move {
|
||||
let mut diff_model = DiffModel::new();
|
||||
let base_text = r"
|
||||
|
||||
@@ -1,46 +1,37 @@
|
||||
mod gutter_button;
|
||||
use std::ops::Range;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use gutter_button::{AddAsContextButton, CommentButton, RevertHunkButton};
|
||||
|
||||
use std::{
|
||||
ops::Range,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
},
|
||||
use parking_lot::Mutex;
|
||||
use pathfinder_color::ColorU;
|
||||
use pathfinder_geometry::rect::RectF;
|
||||
use pathfinder_geometry::vector::{vec2f, Vector2F};
|
||||
use galaxy_core::features::FeatureFlag;
|
||||
use galaxy_core::ui::appearance::Appearance;
|
||||
use galaxy_core::ui::theme::color::internal_colors;
|
||||
use galaxy_core::ui::theme::Fill;
|
||||
use galaxy_editor::editor::EditorView;
|
||||
use galaxy_editor::render::element::lens_element::RichTextElementLens;
|
||||
use galaxy_editor::render::element::{RenderableBlock, RichTextElement, VerticalExpansionBehavior};
|
||||
use galaxy_editor::render::model::{
|
||||
gutter_expansion_button_types, BlockLocation, ExpansionType, LineCount, RenderState,
|
||||
};
|
||||
|
||||
use galaxy_core::ui::{
|
||||
appearance::Appearance,
|
||||
theme::{color::internal_colors, Fill},
|
||||
};
|
||||
use galaxy_editor::{
|
||||
editor::EditorView,
|
||||
render::{
|
||||
element::{
|
||||
lens_element::RichTextElementLens, RenderableBlock, RichTextElement,
|
||||
VerticalExpansionBehavior,
|
||||
},
|
||||
model::{
|
||||
gutter_expansion_button_types, BlockLocation, ExpansionType, LineCount, RenderState,
|
||||
},
|
||||
},
|
||||
use galaxyui::elements::new_scrollable::{NewScrollableElement, ScrollableAxis};
|
||||
use galaxyui::elements::{
|
||||
Align, Axis, Border, ChildAnchor, ConstrainedBox, Container, CornerRadius, Empty, F32Ext, Flex,
|
||||
Hoverable, MainAxisSize, MouseStateHandle, OffsetPositioning, ParentAnchor, ParentElement,
|
||||
ParentOffsetBounds, Point, Radius, ScrollData, Stack, Text, ZIndex,
|
||||
};
|
||||
use galaxyui::event::DispatchedEvent;
|
||||
use galaxyui::fonts::FamilyId;
|
||||
use galaxyui::ui_components::components::UiComponent;
|
||||
use galaxyui::units::{IntoPixels, Pixels};
|
||||
use galaxyui::{
|
||||
elements::{
|
||||
new_scrollable::{NewScrollableElement, ScrollableAxis},
|
||||
Align, Axis, Border, ChildAnchor, ConstrainedBox, Container, CornerRadius, Empty, F32Ext,
|
||||
Flex, MainAxisSize, OffsetPositioning, ParentAnchor, ParentElement, ParentOffsetBounds,
|
||||
Point, Radius, ScrollData, Stack, Text, ZIndex,
|
||||
},
|
||||
event::DispatchedEvent,
|
||||
fonts::FamilyId,
|
||||
ui_components::components::UiComponent,
|
||||
units::{IntoPixels, Pixels},
|
||||
AfterLayoutContext, AppContext, ClipBounds, Element, Event, EventContext, LayoutContext,
|
||||
ModelHandle, PaintContext, SingletonEntity, SizeConstraint,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use pathfinder_color::ColorU;
|
||||
use pathfinder_geometry::{
|
||||
rect::RectF,
|
||||
vector::{vec2f, Vector2F},
|
||||
@@ -49,15 +40,10 @@ use pathfinder_geometry::{
|
||||
use super::diff::{DiffHunkDisplay, DiffStatus};
|
||||
use super::model::DiffNavigationState;
|
||||
use crate::code::editor::element::gutter_button::GutterButton;
|
||||
use crate::{
|
||||
code::editor::{
|
||||
line::EditorLineLocation,
|
||||
view::{CodeEditorViewAction, SavedComment},
|
||||
},
|
||||
view_components::action_button::{ActionButtonTheme, SecondaryTheme},
|
||||
};
|
||||
use galaxy_core::features::FeatureFlag;
|
||||
use galaxyui::elements::{Hoverable, MouseStateHandle};
|
||||
use crate::code::editor::line::EditorLineLocation;
|
||||
use crate::code::editor::view::{CodeEditorViewAction, SavedComment};
|
||||
use crate::settings::CodeEditorLineNumberMode;
|
||||
use crate::view_components::action_button::{ActionButtonTheme, SecondaryTheme};
|
||||
|
||||
pub const GUTTER_WIDTH: f32 = 94.;
|
||||
const VERTICAL_DIFF_HUNK_INDICATOR_WIDTH: f32 = 3.;
|
||||
@@ -351,7 +337,7 @@ impl GutterRange {
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum GutterHoverTarget {
|
||||
// The entire line covered by the gutter is cosidered the hover target.
|
||||
// The entire line covered by the gutter is considered the hover target.
|
||||
Line,
|
||||
// Only the gutter element itself is considered the hover target.
|
||||
GutterElement,
|
||||
@@ -367,6 +353,28 @@ pub struct LineNumberConfig {
|
||||
pub text_color: ColorU,
|
||||
pub highlight_text_color: ColorU,
|
||||
pub starting_line_number: Option<usize>,
|
||||
pub mode: CodeEditorLineNumberMode,
|
||||
pub active_line_number: Option<LineCount>,
|
||||
pub active_cursor_is_visible: bool,
|
||||
}
|
||||
impl LineNumberConfig {
|
||||
pub fn absolute_line_number(&self, line_count: LineCount) -> usize {
|
||||
line_count.as_usize() + self.starting_line_number.unwrap_or(1)
|
||||
}
|
||||
|
||||
pub fn display_line_number(&self, line_count: LineCount) -> usize {
|
||||
if self.mode == CodeEditorLineNumberMode::Relative {
|
||||
if let Some(active_line_number) = self.active_line_number {
|
||||
if active_line_number != line_count {
|
||||
return active_line_number
|
||||
.as_usize()
|
||||
.abs_diff(line_count.as_usize());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.absolute_line_number(line_count)
|
||||
}
|
||||
}
|
||||
|
||||
struct CommentBox {
|
||||
@@ -567,6 +575,21 @@ impl<V: EditorView> EditorWrapper<V> {
|
||||
.cloned()
|
||||
}
|
||||
|
||||
fn should_display_relative_line_number(&self) -> bool {
|
||||
let Some(line_number_config) = &self.line_number_config else {
|
||||
return false;
|
||||
};
|
||||
if line_number_config.mode != CodeEditorLineNumberMode::Relative
|
||||
|| line_number_config.active_line_number.is_none()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Relative numbers follow the cursor: only show them when a cursor is
|
||||
// actually drawn (editor focused and editable).
|
||||
line_number_config.active_cursor_is_visible
|
||||
}
|
||||
|
||||
/// Returning **no** gutter means the gutter shouldn't be rendered at all.
|
||||
/// Returning an **empty** gutter means the gutter should be rendered with no contents.
|
||||
fn gutter_elements(&self, app: &AppContext) -> Option<Vec<GutterElement>> {
|
||||
@@ -602,8 +625,11 @@ impl<V: EditorView> EditorWrapper<V> {
|
||||
let diff_hunk = self.diff_status.diff_hunk(line_count, appearance);
|
||||
let is_removal = matches!(diff_hunk, Some(DiffHunkDisplay::Remove(_)));
|
||||
|
||||
let current_line =
|
||||
line_count.as_usize() + line_number_config.starting_line_number.unwrap_or(1);
|
||||
let current_line = if self.should_display_relative_line_number() {
|
||||
line_number_config.display_line_number(line_count)
|
||||
} else {
|
||||
line_number_config.absolute_line_number(line_count)
|
||||
};
|
||||
|
||||
// If the block is temporary, don't render line number.
|
||||
// Currently, all temporary blocks are removal hunks, either from a deleted section,
|
||||
@@ -1662,3 +1688,7 @@ impl<V: EditorView> NewScrollableElement for EditorWrapper<V> {
|
||||
ScrollableAxis::Both
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "element_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
use crate::view_components::action_button::{
|
||||
ActionButtonTheme, DisabledSecondaryTheme, SecondaryTheme,
|
||||
};
|
||||
use galaxy_core::ui::appearance::Appearance;
|
||||
use galaxy_core::ui::color::contrast::MinimumAllowedContrast;
|
||||
use galaxy_core::ui::color::ContrastingColor;
|
||||
@@ -9,6 +6,10 @@ use galaxy_core::ui::theme::Fill;
|
||||
use galaxy_core::ui::Icon;
|
||||
use galaxyui::elements::MouseState;
|
||||
|
||||
use crate::view_components::action_button::{
|
||||
ActionButtonTheme, DisabledSecondaryTheme, SecondaryTheme,
|
||||
};
|
||||
|
||||
/// A button rendered within the gutter of the editor.
|
||||
pub(super) trait GutterButton {
|
||||
/// The icon color for the gutter.
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
use super::*;
|
||||
fn config(
|
||||
mode: CodeEditorLineNumberMode,
|
||||
starting_line_number: Option<usize>,
|
||||
active_line_number: Option<LineCount>,
|
||||
) -> LineNumberConfig {
|
||||
LineNumberConfig {
|
||||
font_family: FamilyId(0),
|
||||
font_size: 0.,
|
||||
text_color: ColorU::transparent_black(),
|
||||
highlight_text_color: ColorU::transparent_black(),
|
||||
starting_line_number,
|
||||
mode,
|
||||
active_line_number,
|
||||
active_cursor_is_visible: true,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn absolute_line_numbers_default_to_one_based_values() {
|
||||
let config = config(CodeEditorLineNumberMode::Absolute, None, None);
|
||||
|
||||
assert_eq!(config.absolute_line_number(LineCount::from(0)), 1);
|
||||
assert_eq!(config.absolute_line_number(LineCount::from(4)), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn absolute_line_numbers_honor_starting_line_number() {
|
||||
let config = config(CodeEditorLineNumberMode::Absolute, Some(10), None);
|
||||
|
||||
assert_eq!(config.absolute_line_number(LineCount::from(0)), 10);
|
||||
assert_eq!(config.absolute_line_number(LineCount::from(4)), 14);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn relative_line_numbers_show_absolute_value_on_active_line() {
|
||||
let config = config(
|
||||
CodeEditorLineNumberMode::Relative,
|
||||
None,
|
||||
Some(LineCount::from(4)),
|
||||
);
|
||||
assert_eq!(config.display_line_number(LineCount::from(4)), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn relative_line_numbers_show_distance_above_and_below_active_line() {
|
||||
let config = config(
|
||||
CodeEditorLineNumberMode::Relative,
|
||||
None,
|
||||
Some(LineCount::from(5)),
|
||||
);
|
||||
assert_eq!(config.display_line_number(LineCount::from(2)), 3);
|
||||
assert_eq!(config.display_line_number(LineCount::from(8)), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn relative_line_numbers_fall_back_to_absolute_without_active_line() {
|
||||
let config = config(CodeEditorLineNumberMode::Relative, None, None);
|
||||
assert_eq!(config.display_line_number(LineCount::from(4)), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn relative_line_numbers_use_starting_line_number_for_active_line_only() {
|
||||
let config = config(
|
||||
CodeEditorLineNumberMode::Relative,
|
||||
Some(10),
|
||||
Some(LineCount::from(4)),
|
||||
);
|
||||
assert_eq!(config.display_line_number(LineCount::from(4)), 14);
|
||||
assert_eq!(config.display_line_number(LineCount::from(1)), 3);
|
||||
}
|
||||
@@ -10,10 +10,10 @@ use galaxy_editor::render::layout::TextLayout;
|
||||
use pathfinder_geometry::vector::{vec2f, Vector2F};
|
||||
use serde_yaml::Mapping;
|
||||
use uuid::Uuid;
|
||||
|
||||
use galaxy_editor::render::model::viewport::ViewportItem;
|
||||
use galaxy_editor::render::model::{
|
||||
viewport::ViewportItem, BlockSpacing, EmbeddedItem, EmbeddedItemHTMLRepresentation,
|
||||
EmbeddedItemRichFormat, LaidOutEmbeddedItem, RenderState,
|
||||
BlockSpacing, EmbeddedItem, EmbeddedItemHTMLRepresentation, EmbeddedItemRichFormat,
|
||||
LaidOutEmbeddedItem, RenderState,
|
||||
};
|
||||
use galaxyui::event::DispatchedEvent;
|
||||
use galaxyui::units::Pixels;
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use serde_yaml::{Mapping, Value};
|
||||
use warp_editor::content::markdown::MarkdownStyle;
|
||||
use warpui::{EntityId, WindowId};
|
||||
|
||||
use super::{
|
||||
comment_embedded_item_conversion, EmbeddedCommentSpace, EmbeddedItem as _,
|
||||
COMMENT_ID_MAPPING_KEY, ENTITY_ID_MAPPING_KEY, WINDOW_ID_MAPPING_KEY,
|
||||
};
|
||||
use crate::code_review::comments::CommentId;
|
||||
use galaxy_editor::content::markdown::MarkdownStyle;
|
||||
use galaxyui::{EntityId, WindowId};
|
||||
use serde_yaml::{Mapping, Value};
|
||||
|
||||
#[test]
|
||||
fn test_comment_embedded_item_conversion_valid_input() {
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
#![cfg_attr(target_family = "wasm", allow(dead_code, unused_imports))]
|
||||
// Adding this file level gate as some of the code around editability is not used in WASM yet.
|
||||
|
||||
use pathfinder_color::ColorU;
|
||||
use warp_editor::editor::NavigationKey;
|
||||
use warp_editor::search::{SearchEvent, Searcher};
|
||||
pub use warpui::accessibility::{AccessibilityContent, WarpA11yRole};
|
||||
use warpui::elements::{
|
||||
Align, Border, ChildAnchor, Clipped, ConstrainedBox, Container, CornerRadius,
|
||||
CrossAxisAlignment, DropShadow, Element, Flex, Hoverable, MainAxisAlignment, MouseStateHandle,
|
||||
OffsetPositioning, ParentAnchor, ParentOffsetBounds, Radius, Rect, SavePosition, Shrinkable,
|
||||
Text,
|
||||
};
|
||||
pub use warpui::elements::{ParentElement as _, Stack};
|
||||
pub use warpui::geometry::vector::vec2f;
|
||||
use warpui::keymap::EditableBinding;
|
||||
use warpui::presenter::ChildView;
|
||||
use warpui::ui_components::components::UiComponent;
|
||||
pub use warpui::AppContext;
|
||||
use warpui::{
|
||||
Entity, FocusContext, ModelHandle, SingletonEntity, TypedActionView, View, ViewContext,
|
||||
ViewHandle,
|
||||
};
|
||||
|
||||
use crate::appearance::Appearance;
|
||||
use crate::editor::{
|
||||
EditorView, Event as EditorEvent, InteractionState, PropagateAndNoOpNavigationKeys,
|
||||
SingleLineEditorOptions, TextOptions,
|
||||
};
|
||||
use crate::features::FeatureFlag;
|
||||
use crate::send_telemetry_from_ctx;
|
||||
use crate::server::telemetry::{FindOption, TelemetryEvent};
|
||||
use crate::settings::AppEditorSettings;
|
||||
use crate::themes::theme::Fill;
|
||||
use crate::ui_components::{blended_colors, icons::Icon};
|
||||
use crate::ui_components::blended_colors;
|
||||
use crate::ui_components::icons::Icon;
|
||||
use crate::view_components::action_button::{ActionButton, DisabledSecondaryTheme, SecondaryTheme};
|
||||
use crate::view_components::find::FindDirection;
|
||||
use crate::{features::FeatureFlag, settings::AppEditorSettings};
|
||||
use galaxy_editor::editor::NavigationKey;
|
||||
use galaxy_editor::search::{SearchEvent, Searcher};
|
||||
use galaxyui::elements::MainAxisAlignment;
|
||||
use galaxyui::elements::{ChildAnchor, OffsetPositioning, Radius, SavePosition, Shrinkable};
|
||||
use galaxyui::keymap::EditableBinding;
|
||||
use galaxyui::ui_components::components::UiComponent;
|
||||
pub use galaxyui::{
|
||||
accessibility::{AccessibilityContent, GalaxyA11yRole},
|
||||
elements::{ParentElement as _, Stack},
|
||||
geometry::vector::vec2f,
|
||||
AppContext,
|
||||
};
|
||||
use galaxyui::{
|
||||
elements::{
|
||||
Align, Border, Clipped, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment,
|
||||
DropShadow, Element, Flex, Hoverable, MouseStateHandle, ParentAnchor, ParentOffsetBounds,
|
||||
Rect, Text,
|
||||
},
|
||||
Entity, SingletonEntity, TypedActionView, View,
|
||||
};
|
||||
use galaxyui::{presenter::ChildView, ViewContext, ViewHandle};
|
||||
use galaxyui::{FocusContext, ModelHandle};
|
||||
use pathfinder_color::ColorU;
|
||||
|
||||
pub const FIND_BAR_WIDTH: f32 = 500.;
|
||||
const ICON_PADDING: f32 = 4.;
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
#![cfg_attr(target_family = "wasm", allow(dead_code, unused_imports))]
|
||||
|
||||
use warpui::elements::{
|
||||
Align, Border, ChildView, ConstrainedBox, Container, CornerRadius, DropShadow, Flex,
|
||||
ParentElement, Radius, Text,
|
||||
};
|
||||
use warpui::{
|
||||
AppContext, Element, Entity, FocusContext, SingletonEntity, TypedActionView, View, ViewContext,
|
||||
ViewHandle,
|
||||
};
|
||||
|
||||
use crate::appearance::Appearance;
|
||||
use crate::code::editor::find::view::{FIND_BAR_PADDING, FIND_EDITOR_BORDER_RADIUS};
|
||||
use crate::editor::{
|
||||
EditorView, Event as EditorEvent, InteractionState, PropagateAndNoOpNavigationKeys,
|
||||
SingleLineEditorOptions, TextOptions,
|
||||
};
|
||||
use galaxyui::{
|
||||
elements::{
|
||||
Align, Border, ChildView, ConstrainedBox, Container, CornerRadius, DropShadow, Flex,
|
||||
ParentElement, Radius, Text,
|
||||
},
|
||||
AppContext, Element, Entity, FocusContext, SingletonEntity, TypedActionView, View, ViewContext,
|
||||
ViewHandle,
|
||||
};
|
||||
|
||||
const GOTO_LINE_WIDTH: f32 = 300.;
|
||||
const GOTO_LINE_LABEL_FONT_SIZE: f32 = 12.;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use galaxy_editor::render::model::{LineCount, RenderLineLocation};
|
||||
use std::ops::Range;
|
||||
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EditorLineLocation {
|
||||
Collapsed {
|
||||
|
||||
@@ -15,8 +15,7 @@ pub mod scroll;
|
||||
pub mod view;
|
||||
|
||||
pub use comment_editor::{CommentEditor, CommentEditorEvent};
|
||||
pub use comments::EditorCommentsModel;
|
||||
pub use comments::EditorReviewComment;
|
||||
pub use comments::{EditorCommentsModel, EditorReviewComment};
|
||||
pub(crate) use diff::{add_color, remove_color};
|
||||
pub use element::GutterHoverTarget;
|
||||
pub use nav_bar::NavBarBehavior;
|
||||
|
||||
+228
-76
@@ -1,22 +1,6 @@
|
||||
#![cfg_attr(target_family = "wasm", allow(dead_code, unused_imports))]
|
||||
// Adding this file level gate as some of the code around editability is not used in WASM yet.
|
||||
|
||||
use crate::code::editor::line_iterator::LineIterator;
|
||||
use crate::code_review::CodeReviewTelemetryEvent;
|
||||
use galaxy_core::platform::SessionPlatform;
|
||||
use galaxy_core::send_telemetry_from_ctx;
|
||||
use galaxy_core::ui::theme::Fill;
|
||||
use galaxy_editor::content::anchor::Anchor;
|
||||
use galaxy_editor::content::edit::EditDelta;
|
||||
use galaxy_editor::content::find::{SearchConfig, SearchResults};
|
||||
use galaxy_editor::content::selection_model::BufferSelectionModel;
|
||||
use galaxy_editor::content::version::BufferVersion;
|
||||
use galaxy_editor::multiline::{AnyMultilineString, MultilineString, LF};
|
||||
use galaxy_editor::render::model::{AutoScrollMode, LineCount, StyleUpdateAction};
|
||||
use galaxy_editor::selection::TextDirection;
|
||||
use galaxyui::units::{IntoPixels, Pixels};
|
||||
use num_traits::SaturatingSub;
|
||||
use rangemap::{RangeMap, RangeSet};
|
||||
use std::future::Future;
|
||||
use std::ops::Range;
|
||||
use std::path::Path;
|
||||
@@ -24,42 +8,12 @@ use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::{cmp, mem};
|
||||
|
||||
use crate::util::link_detection::get_word_range_at_offset;
|
||||
use crate::{
|
||||
appearance::Appearance, editor::InteractionState, notebooks::editor::model::word_unit,
|
||||
themes::theme::AnsiColorIdentifier,
|
||||
};
|
||||
|
||||
use ai::diff_validation::DiffDelta;
|
||||
use galaxy_core::semantic_selection::SemanticSelection;
|
||||
use galaxy_editor::content::buffer::{ShouldAutoscroll, VimInsertPoint};
|
||||
use galaxy_editor::{
|
||||
content::{
|
||||
buffer::{
|
||||
AutoScrollBehavior, Buffer, BufferEditAction, BufferEvent, BufferSelectAction,
|
||||
EditOrigin, InitialBufferState, SelectionOffsets, ToBufferCharOffset, ToBufferPoint,
|
||||
},
|
||||
hidden_lines_model::HiddenLinesModel,
|
||||
text::{BufferBlockStyle, IndentBehavior, IndentUnit},
|
||||
},
|
||||
decoration::DecorationLayer,
|
||||
editor::TextDecoration,
|
||||
model::{CoreEditorModel, PlainTextEditorModel},
|
||||
render::model::{
|
||||
BlockItem, Decoration, LineDecoration, RenderEvent, RenderLineLocation, RenderState,
|
||||
RichTextStyles, UpdateDecorationAfterLayout, WidthSetting,
|
||||
},
|
||||
selection::{SelectionMode, SelectionModel, TextUnit},
|
||||
};
|
||||
use galaxyui::elements::{
|
||||
AnchorPair, OffsetPositioning, OffsetType, PositionedElementOffsetBounds, PositioningAxis,
|
||||
XAxisAnchor, YAxisAnchor,
|
||||
};
|
||||
use galaxyui::text::{point::Point, TextBuffer};
|
||||
use galaxyui::{AppContext, Entity, ModelContext, ModelHandle, SingletonEntity};
|
||||
use itertools::Itertools;
|
||||
use languages::{language_by_filename, language_by_name, Language};
|
||||
use languages::{language_by_filename, language_by_local_filename, language_by_name, Language};
|
||||
use line_ending::LineEnding;
|
||||
use num_traits::SaturatingSub;
|
||||
use rangemap::{RangeMap, RangeSet};
|
||||
use string_offset::CharOffset;
|
||||
use syntax_tree::{ColorMap, DecorationStateEvent, SyntaxTreeState};
|
||||
use vec1::{vec1, Vec1};
|
||||
@@ -73,6 +27,41 @@ use vim::{
|
||||
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 galaxy_core::platform::SessionPlatform;
|
||||
use galaxy_core::semantic_selection::SemanticSelection;
|
||||
use galaxy_core::ui::theme::Fill;
|
||||
use galaxy_editor::content::anchor::Anchor;
|
||||
use galaxy_editor::content::buffer::{
|
||||
AutoScrollBehavior, Buffer, BufferEditAction, BufferEvent, BufferSelectAction, EditOrigin,
|
||||
InitialBufferState, SelectionOffsets, ShouldAutoscroll, ToBufferCharOffset, ToBufferPoint,
|
||||
VimInsertPoint,
|
||||
};
|
||||
use galaxy_editor::content::edit::EditDelta;
|
||||
use galaxy_editor::content::find::{SearchConfig, SearchResults};
|
||||
use galaxy_editor::content::hidden_lines_model::HiddenLinesModel;
|
||||
use galaxy_editor::content::selection_model::BufferSelectionModel;
|
||||
use galaxy_editor::content::text::{BufferBlockStyle, IndentBehavior, IndentUnit};
|
||||
use galaxy_editor::content::version::BufferVersion;
|
||||
use galaxy_editor::decoration::DecorationLayer;
|
||||
use galaxy_editor::editor::TextDecoration;
|
||||
use galaxy_editor::model::{CoreEditorModel, PlainTextEditorModel};
|
||||
use galaxy_editor::multiline::{AnyMultilineString, MultilineString, LF};
|
||||
use galaxy_editor::render::model::{
|
||||
AutoScrollMode, BlockItem, BlockSpacings, BrokenLinkStyle, CheckBoxStyle, ColumnUnit,
|
||||
Decoration, HorizontalRuleStyle, InlineCodeStyle, LineCount, LineDecoration, ParagraphStyles,
|
||||
RenderEvent, RenderLineLocation, RenderState, RichTextStyles, StyleUpdateAction, TableStyle,
|
||||
UpdateDecorationAfterLayout, WidthSetting,
|
||||
};
|
||||
use galaxy_editor::selection::{SelectionMode, SelectionModel, TextDirection, TextUnit};
|
||||
use galaxy_util::standardized_path::StandardizedPath;
|
||||
use galaxyui::elements::{
|
||||
AnchorPair, OffsetPositioning, OffsetType, PositionedElementOffsetBounds, PositioningAxis,
|
||||
XAxisAnchor, YAxisAnchor,
|
||||
};
|
||||
use galaxyui::text::point::Point;
|
||||
use galaxyui::text::TextBuffer;
|
||||
use galaxyui::units::{IntoPixels, Pixels};
|
||||
use galaxyui::{AppContext, Entity, ModelContext, ModelHandle, SingletonEntity};
|
||||
|
||||
use super::super::DiffResult;
|
||||
use super::comments::{EditorCommentsModel, PendingComment, PendingCommentEvent};
|
||||
@@ -80,7 +69,13 @@ use super::diff::{
|
||||
add_inline_overlay_color, DiffModel, DiffModelEvent, DiffStatus, RenderableDiffHunk,
|
||||
};
|
||||
use super::line::EditorLineLocation;
|
||||
use crate::appearance::Appearance;
|
||||
use crate::code::editor::line_iterator::LineIterator;
|
||||
use crate::code_review::comments::{CommentId, CommentOrigin, LineDiffContent};
|
||||
use crate::editor::InteractionState;
|
||||
use crate::notebooks::editor::model::word_unit;
|
||||
use crate::themes::theme::AnsiColorIdentifier;
|
||||
use crate::util::link_detection::get_word_range_at_offset;
|
||||
|
||||
/// An opaque handle to a stable line in the editor content, suitable for scroll
|
||||
/// position preservation. Contains an internal anchor that tracks through
|
||||
@@ -261,7 +256,7 @@ impl DelayRendering {
|
||||
model.render_state.update(ctx, move |render_state, _| {
|
||||
let should_autoscroll = self.should_autoscroll;
|
||||
for (delta, content_version) in self.edits {
|
||||
render_state.add_pending_edit(delta.clone(), content_version);
|
||||
render_state.add_pending_edit(delta, content_version);
|
||||
}
|
||||
match should_autoscroll {
|
||||
ShouldAutoscroll::Yes => render_state.request_autoscroll(),
|
||||
@@ -338,7 +333,73 @@ impl CodeEditorModel {
|
||||
content.update(ctx, |buffer, _| {
|
||||
buffer.set_session_platform(session_platform);
|
||||
});
|
||||
ctx.subscribe_to_model(&content, |me, event, ctx| {
|
||||
|
||||
Self::from_content(
|
||||
content,
|
||||
true, // show_current_line_highlights
|
||||
lazy_layout, // lazy_layout_enabled
|
||||
false, // lazy_layout_initialized
|
||||
ctx,
|
||||
|hidden_lines, ctx| {
|
||||
ctx.add_model(|ctx| {
|
||||
RenderState::new(text_styles, lazy_layout, Some(hidden_lines.clone()), ctx)
|
||||
.with_width_setting(WidthSetting::InfiniteWidth)
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Constructs a `CodeEditorModel` in TUI char-cell mode.
|
||||
///
|
||||
/// Identical to `new` but creates the `RenderState` with
|
||||
/// [`LayoutMode::CharCell`] so all soft-wrap positions use monospace
|
||||
/// character-count arithmetic rather than font-aware pixel layout.
|
||||
/// `TuiEditorModel` (in `warp_tui`) is a type alias for this type;
|
||||
/// constructing via this method is what gives the TUI editor all of
|
||||
/// `CodeEditorModel`'s features (vim, syntax, diff, hidden lines) for free
|
||||
/// while sharing no GUI-rendering infrastructure.
|
||||
///
|
||||
/// Like `new`, this reads syntax-highlight colors from the `Appearance`
|
||||
/// singleton, so callers must register `Appearance` (a real one for the
|
||||
/// runtime, `Appearance::mock()` for tests) before constructing the model.
|
||||
pub fn new_tui(terminal_width: u16, ctx: &mut ModelContext<Self>) -> Self {
|
||||
let content = ctx.add_model(|_| Buffer::new(Box::new(|_, _| IndentBehavior::Ignore)));
|
||||
|
||||
Self::from_content(
|
||||
content,
|
||||
false, // show_current_line_highlights: no GPU rendering in TUI
|
||||
false, // lazy_layout_enabled: no lazy layout in TUI
|
||||
true, // lazy_layout_initialized: no lazy layout in TUI
|
||||
ctx,
|
||||
|_hidden_lines, ctx| {
|
||||
// CharCell layout never consults `RichTextStyles`, so pass a stub.
|
||||
ctx.add_model(|ctx| {
|
||||
RenderState::new_tui(terminal_width, Self::tui_stub_text_styles(), ctx)
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Shared construction for [`Self::new`] and [`Self::new_tui`]. The two modes
|
||||
/// differ only in how the backing `content` buffer and the `RenderState` are
|
||||
/// built (GUI pixel layout vs. TUI char-cell layout) plus a few flags; all
|
||||
/// other sub-models (selection, syntax tree, diff, hidden lines, comments)
|
||||
/// and event subscriptions are identical and wired up here.
|
||||
///
|
||||
/// `build_render_state` receives the freshly-created `hidden_lines` handle so
|
||||
/// the GUI path can attach it; the TUI path ignores it.
|
||||
fn from_content(
|
||||
content: ModelHandle<Buffer>,
|
||||
show_current_line_highlights: bool,
|
||||
lazy_layout_enabled: bool,
|
||||
lazy_layout_initialized: bool,
|
||||
ctx: &mut ModelContext<Self>,
|
||||
build_render_state: impl FnOnce(
|
||||
&ModelHandle<HiddenLinesModel>,
|
||||
&mut ModelContext<Self>,
|
||||
) -> ModelHandle<RenderState>,
|
||||
) -> Self {
|
||||
ctx.subscribe_to_model(&content, |me, _, event, ctx| {
|
||||
me.handle_content_model_event(event, ctx);
|
||||
});
|
||||
|
||||
@@ -349,23 +410,20 @@ impl CodeEditorModel {
|
||||
let buffer_handle = content.downgrade();
|
||||
let syntax_tree =
|
||||
ctx.add_model(|_ctx| SyntaxTreeState::new(buffer_handle, buffer_version, color_map));
|
||||
ctx.subscribe_to_model(&syntax_tree, |me, event, ctx| {
|
||||
ctx.subscribe_to_model(&syntax_tree, |me, _, event, ctx| {
|
||||
me.handle_syntax_tree_model_event(event, ctx);
|
||||
});
|
||||
|
||||
let diff = ctx.add_model(|_ctx| DiffModel::new());
|
||||
ctx.subscribe_to_model(&diff, |me, event, ctx| {
|
||||
ctx.subscribe_to_model(&diff, |me, _, event, ctx| {
|
||||
me.handle_diff_model_event(event, ctx);
|
||||
});
|
||||
|
||||
let hidden_lines =
|
||||
ctx.add_model(|_| HiddenLinesModel::new(content.clone(), selection_model.clone()));
|
||||
|
||||
let render_state = ctx.add_model(|ctx| {
|
||||
RenderState::new(text_styles, lazy_layout, Some(hidden_lines.clone()), ctx)
|
||||
.with_width_setting(WidthSetting::InfiniteWidth)
|
||||
});
|
||||
ctx.subscribe_to_model(&render_state, |me, event, ctx| {
|
||||
let render_state = build_render_state(&hidden_lines, ctx);
|
||||
ctx.subscribe_to_model(&render_state, |me, _, event, ctx| {
|
||||
me.handle_render_state_model_event(event, ctx);
|
||||
});
|
||||
let selection = ctx.add_model(|ctx| {
|
||||
@@ -394,17 +452,96 @@ impl CodeEditorModel {
|
||||
hidden_lines,
|
||||
diff_navigation_state: DiffNavigationState::Collapsed,
|
||||
interaction_state: InteractionState::Editable,
|
||||
show_current_line_highlights: true,
|
||||
show_current_line_highlights,
|
||||
delay_rendering: None,
|
||||
vim_visual_tails: vec![],
|
||||
hovered_symbol_range: None,
|
||||
hide_lines_outside_of_active_diff: None,
|
||||
lazy_layout_enabled: lazy_layout,
|
||||
lazy_layout_initialized: false,
|
||||
lazy_layout_enabled,
|
||||
lazy_layout_initialized,
|
||||
pending_syntax_tree_bootstrap: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// A minimal [`RichTextStyles`] for the TUI char-cell editor.
|
||||
///
|
||||
/// `RenderState::new_tui` stores styles only for API compatibility and never
|
||||
/// uses them for char-cell layout, so these values are placeholders. This
|
||||
/// lives here (the caller of `RenderState::new_tui`) rather than in the core
|
||||
/// editor crate so the editor API doesn't carry a TUI-specific stub.
|
||||
fn tui_stub_text_styles() -> RichTextStyles {
|
||||
use warpui::elements::{Border, Fill};
|
||||
use warpui::fonts::{FamilyId, Weight};
|
||||
|
||||
const TRANSPARENT: warpui::color::ColorU = warpui::color::ColorU {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 0,
|
||||
};
|
||||
let paragraph = |fixed_width_tab_size| ParagraphStyles {
|
||||
font_family: FamilyId(0),
|
||||
font_size: 10.,
|
||||
font_weight: Weight::Normal,
|
||||
line_height_ratio: 1.,
|
||||
text_color: TRANSPARENT,
|
||||
baseline_ratio: 0.7,
|
||||
fixed_width_tab_size,
|
||||
};
|
||||
RichTextStyles {
|
||||
base_text: paragraph(None),
|
||||
code_text: paragraph(Some(4)),
|
||||
code_background: Fill::None,
|
||||
embedding_background: Fill::None,
|
||||
embedding_text: paragraph(None),
|
||||
code_border: Border::new(0.),
|
||||
placeholder_color: TRANSPARENT,
|
||||
selection_fill: Fill::None,
|
||||
cursor_fill: Fill::None,
|
||||
inline_code_style: InlineCodeStyle {
|
||||
font_family: FamilyId(0),
|
||||
background: TRANSPARENT,
|
||||
font_color: TRANSPARENT,
|
||||
},
|
||||
check_box_style: CheckBoxStyle {
|
||||
border_width: 0.,
|
||||
border_color: TRANSPARENT,
|
||||
icon_path: "",
|
||||
background: TRANSPARENT,
|
||||
hover_background: TRANSPARENT,
|
||||
},
|
||||
horizontal_rule_style: HorizontalRuleStyle {
|
||||
rule_height: 0.,
|
||||
color: TRANSPARENT,
|
||||
},
|
||||
broken_link_style: BrokenLinkStyle {
|
||||
icon_path: "",
|
||||
icon_color: TRANSPARENT,
|
||||
},
|
||||
block_spacings: BlockSpacings::default(),
|
||||
minimum_paragraph_height: None,
|
||||
show_placeholder_text_on_empty_block: false,
|
||||
cursor_width: 0.,
|
||||
highlight_urls: false,
|
||||
table_style: TableStyle {
|
||||
border_color: TRANSPARENT,
|
||||
header_background: TRANSPARENT,
|
||||
cell_background: TRANSPARENT,
|
||||
alternate_row_background: None,
|
||||
text_color: TRANSPARENT,
|
||||
header_text_color: TRANSPARENT,
|
||||
scrollbar_nonactive_thumb_color: TRANSPARENT,
|
||||
scrollbar_active_thumb_color: TRANSPARENT,
|
||||
font_family: FamilyId(0),
|
||||
font_size: 10.,
|
||||
cell_padding: 0.,
|
||||
outer_border: false,
|
||||
column_dividers: false,
|
||||
row_dividers: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn should_defer_syntax_tree_parsing(&self) -> bool {
|
||||
self.lazy_layout_enabled && !self.lazy_layout_initialized
|
||||
}
|
||||
@@ -1151,7 +1288,11 @@ impl CodeEditorModel {
|
||||
}
|
||||
|
||||
/// Set the language of the syntax map based on the file path.
|
||||
pub fn set_language_with_path(&mut self, path: &Path, ctx: &mut ModelContext<Self>) {
|
||||
pub fn set_language_with_path(
|
||||
&mut self,
|
||||
path: &StandardizedPath,
|
||||
ctx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let language = language_by_filename(path);
|
||||
|
||||
if let Some(language) = language {
|
||||
@@ -1159,6 +1300,15 @@ impl CodeEditorModel {
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the language of the syntax map based on the local filesystem path.
|
||||
pub fn set_language_with_local_path(&mut self, path: &Path, ctx: &mut ModelContext<Self>) {
|
||||
let language = language_by_local_filename(path);
|
||||
|
||||
if let Some(language) = language {
|
||||
self.set_language(language, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_language_with_name(&mut self, name: &str, ctx: &mut ModelContext<Self>) {
|
||||
let language = language_by_name(name);
|
||||
if let Some(language) = language {
|
||||
@@ -1581,8 +1731,6 @@ impl CodeEditorModel {
|
||||
fn update_cursor_line_highlights(&self, ctx: &mut ModelContext<CodeEditorModel>) {
|
||||
let selection_model = self.selection_model.as_ref(ctx);
|
||||
|
||||
let overlay = Appearance::as_ref(ctx).theme().surface_2();
|
||||
|
||||
let highlight_line = if self.diff_nav_is_active() {
|
||||
// We don't show current line highlights during diff navigation so we don't need
|
||||
// to update the `RenderState`. This lets us keep the line decorations we set
|
||||
@@ -1591,6 +1739,7 @@ impl CodeEditorModel {
|
||||
} else if selection_model.all_single_cursors() && self.show_current_line_highlights {
|
||||
// When diff is not expanded, the only source of line decoration is highlights
|
||||
// from the active cursor, e.g. the current line highlight.
|
||||
let overlay = Appearance::as_ref(ctx).theme().surface_2();
|
||||
Some(
|
||||
selection_model
|
||||
.selected_lines(ctx)
|
||||
@@ -2302,7 +2451,7 @@ impl CodeEditorModel {
|
||||
self.vim_set_selections_preserving_goal_xs(new_selections, AutoScrollBehavior::None, ctx);
|
||||
}
|
||||
|
||||
/// Horziontal cursor movement for vim in the code editor.
|
||||
/// Horizontal cursor movement for vim in the code editor.
|
||||
/// Separate from the model's `move_left` and `move_right` functions to allow for stopping at
|
||||
/// line boundaries and vim-specific selection logic.
|
||||
pub fn vim_move_horizontal_by_offset(
|
||||
@@ -2428,7 +2577,7 @@ impl CodeEditorModel {
|
||||
if let Some(existing) = self.selection().as_ref(ctx).goal_xs.as_ref() {
|
||||
existing
|
||||
.iter()
|
||||
.map(|px| px.as_f32().round() as u32)
|
||||
.map(|col| col.as_pixels().as_f32().round() as u32)
|
||||
.collect()
|
||||
} else {
|
||||
current_selections
|
||||
@@ -2469,10 +2618,11 @@ impl CodeEditorModel {
|
||||
if let Ok(new_selections) = Vec1::try_from_vec(new_selections_vec) {
|
||||
self.vim_set_selections(new_selections, AutoScrollBehavior::Selection, ctx);
|
||||
|
||||
// Update goal_xs to the desired columns (stored as pixels for consistency with SelectionModel)
|
||||
// Update goal_xs to the desired columns (stored as ColumnUnit::Pixels for
|
||||
// consistency with the GUI SelectionModel pixel path)
|
||||
let goal_pixels: Vec<_> = goal_cols
|
||||
.into_iter()
|
||||
.map(|c| (c as usize).into_pixels())
|
||||
.map(|c| ColumnUnit::Pixels((c as usize).into_pixels()))
|
||||
.collect();
|
||||
self.selection().update(ctx, |selection, _| {
|
||||
selection.goal_xs = Vec1::try_from_vec(goal_pixels).ok();
|
||||
@@ -3642,11 +3792,19 @@ impl CoreEditorModel for CodeEditorModel {
|
||||
buffer_version: BufferVersion,
|
||||
ctx: &mut ModelContext<Self::T>,
|
||||
) {
|
||||
// Synchronously convert hidden range anchors into offsets for the given version. This allows the render model
|
||||
// to accurately hide line ranges based on the corresponding incoming buffer state.
|
||||
// Synchronously convert hidden range anchors into offsets for the given version. This allows
|
||||
// the render model to accurately hide line ranges based on the corresponding incoming buffer state.
|
||||
self.hidden_lines.update(ctx, |hidden_lines_model, ctx| {
|
||||
hidden_lines_model.materialize_hidden_range_offsets(buffer_version, ctx);
|
||||
});
|
||||
// In TUI char-cell mode the async font-shaping pipeline is bypassed entirely (the
|
||||
// LayoutAction::BufferEdit arm is a no-op for CharCell). We must therefore refresh the
|
||||
// char-cell line index synchronously here so that offset_to_softwrap_point, max_line,
|
||||
// and all cursor-positioning queries see up-to-date data in the same frame.
|
||||
if let Some(char_cell) = self.render_state.as_ref(ctx).char_cell() {
|
||||
let text = self.content.as_ref(ctx).text().into_string();
|
||||
char_cell.update_text(&text);
|
||||
}
|
||||
}
|
||||
|
||||
fn content(&self) -> &ModelHandle<Buffer> {
|
||||
@@ -3817,9 +3975,6 @@ impl CoreEditorModel for CodeEditorModel {
|
||||
|
||||
impl CodeEditorModel {
|
||||
pub fn open_comment_line(&mut self, line: &EditorLineLocation, ctx: &mut ModelContext<Self>) {
|
||||
// Telemetry: comment editor opened for a new inline review comment.
|
||||
send_telemetry_from_ctx!(CodeReviewTelemetryEvent::CommentEditorOpened, ctx);
|
||||
|
||||
self.comments.update(ctx, |comments, ctx| {
|
||||
comments.pending_comment = PendingComment::Open { line: line.clone() };
|
||||
ctx.emit(PendingCommentEvent::NewPendingComment(line.clone()));
|
||||
@@ -3834,9 +3989,6 @@ impl CodeEditorModel {
|
||||
origin: &CommentOrigin,
|
||||
ctx: &mut ModelContext<Self>,
|
||||
) {
|
||||
// Telemetry: comment editor opened for editing an existing inline review comment.
|
||||
send_telemetry_from_ctx!(CodeReviewTelemetryEvent::CommentEditorOpened, ctx);
|
||||
|
||||
self.comments.update(ctx, |comments, ctx| {
|
||||
comments.pending_comment = PendingComment::Open { line: line.clone() };
|
||||
ctx.emit(PendingCommentEvent::ReopenPendingComment {
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
use futures::channel::oneshot;
|
||||
use galaxy_editor::content::buffer::{InitialBufferState, SelectionOffsets};
|
||||
use galaxy_editor::multiline::MultilineString;
|
||||
use galaxy_util::content_version::ContentVersion;
|
||||
use galaxyui::App;
|
||||
use std::path::Path;
|
||||
|
||||
use futures::channel::oneshot;
|
||||
use vec1::vec1;
|
||||
|
||||
use crate::{
|
||||
code::editor::line::EditorLineLocation, code::editor::view::code_text_styles,
|
||||
settings::FontSettings, test_util::settings::initialize_settings_for_tests,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
use crate::code::editor::line::EditorLineLocation;
|
||||
use crate::code::editor::view::code_text_styles;
|
||||
use crate::settings::FontSettings;
|
||||
use crate::test_util::settings::initialize_settings_for_tests;
|
||||
|
||||
fn initialize_deps(app: &mut App) {
|
||||
app.add_singleton_model(|_| Appearance::mock());
|
||||
@@ -24,7 +20,7 @@ fn mock_model(app: &mut App, text: &str, version: ContentVersion) -> ModelHandle
|
||||
let mut model = CodeEditorModel::new(styles, None, false, None, ctx);
|
||||
let state = InitialBufferState::plain_text(text).with_version(version);
|
||||
model.reset_content(state, ctx);
|
||||
model.set_language_with_path(Path::new("test.rs"), ctx);
|
||||
model.set_language_with_local_path(Path::new("/test.rs"), ctx);
|
||||
model
|
||||
})
|
||||
}
|
||||
@@ -40,7 +36,7 @@ fn mock_model_with_diff(
|
||||
let mut model = CodeEditorModel::new(styles, None, false, None, ctx);
|
||||
let state = InitialBufferState::plain_text(current_text).with_version(version);
|
||||
model.reset_content(state, ctx);
|
||||
model.set_language_with_path(Path::new("test.rs"), ctx);
|
||||
model.set_language_with_local_path(Path::new("/test.rs"), ctx);
|
||||
|
||||
// Set up diff model with base text
|
||||
model.diff().update(ctx, |diff, _| {
|
||||
|
||||
@@ -1,31 +1,27 @@
|
||||
#![cfg_attr(target_family = "wasm", allow(dead_code, unused_imports))]
|
||||
// Adding this file level gate as some of the code around editability is not used in WASM yet.
|
||||
|
||||
use galaxy_core::ui::{appearance::Appearance, theme::Fill};
|
||||
use galaxy_core::ui::appearance::Appearance;
|
||||
use galaxy_core::ui::theme::Fill;
|
||||
use galaxy_editor::model::CoreEditorModel;
|
||||
use galaxyui::elements::{
|
||||
Align, Border, ConstrainedBox, Container, CrossAxisAlignment, Flex, MouseStateHandle,
|
||||
ParentElement, Shrinkable,
|
||||
};
|
||||
use galaxyui::presenter::ChildView;
|
||||
use galaxyui::ui_components::button::ButtonVariant;
|
||||
use galaxyui::ui_components::components::{UiComponent, UiComponentStyles};
|
||||
use galaxyui::units::IntoPixels;
|
||||
use galaxyui::{
|
||||
elements::{
|
||||
Align, Border, ConstrainedBox, Container, CrossAxisAlignment, Flex, MouseStateHandle,
|
||||
ParentElement, Shrinkable,
|
||||
},
|
||||
presenter::ChildView,
|
||||
ui_components::{
|
||||
button::ButtonVariant,
|
||||
components::{UiComponent, UiComponentStyles},
|
||||
},
|
||||
units::IntoPixels,
|
||||
AppContext, Element, Entity, ModelHandle, SingletonEntity, TypedActionView, View, ViewContext,
|
||||
ViewHandle,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
editor::InteractionState,
|
||||
ui_components::icons::Icon,
|
||||
view_components::action_button::{ActionButton, ButtonSize, NakedTheme},
|
||||
view_components::find::FIND_BAR_PADDING,
|
||||
};
|
||||
|
||||
use super::model::{CodeEditorModel, CodeEditorModelEvent};
|
||||
use crate::editor::InteractionState;
|
||||
use crate::ui_components::icons::Icon;
|
||||
use crate::view_components::action_button::{ActionButton, ButtonSize, NakedTheme};
|
||||
use crate::view_components::find::FIND_BAR_PADDING;
|
||||
|
||||
const NAV_BAR_HEIGHT: f32 = 40.;
|
||||
const NAV_BAR_ICON_SIZE: f32 = 16.;
|
||||
|
||||
+140
-79
@@ -1,88 +1,85 @@
|
||||
#![cfg_attr(target_family = "wasm", allow(dead_code, unused_imports))]
|
||||
// Adding this file level gate as some of the code around editability is not used in WASM yet.
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::Debug;
|
||||
use std::ops::Range;
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::code::editor::{
|
||||
comment_editor::{CommentEditor, CommentEditorEvent},
|
||||
comments::PendingComment,
|
||||
diff::DiffStatus,
|
||||
element::{
|
||||
AddAsContextButton, CommentButton, EditorWrapper, EditorWrapperStateHandle,
|
||||
GutterHoverTarget, GutterRange, InnerEditor, LineNumberConfig, RevertHunkButton,
|
||||
},
|
||||
find::view::{CodeEditorFind as Find, Event as FindViewEvent},
|
||||
goto_line::view::{Event as GoToLineEvent, GoToLineView},
|
||||
line::EditorLineLocation,
|
||||
model::{CodeEditorModel, CodeEditorModelEvent, HoverableLink, LineBound, StableEditorLine},
|
||||
nav_bar::{NavBar, NavBarBehavior, NavBarEvent},
|
||||
scroll::{ScrollPosition, ScrollTrigger, ScrollWheelBehavior},
|
||||
};
|
||||
use crate::code::{
|
||||
editor::EditorReviewComment, DiffResult, NoopCommentEditorProvider,
|
||||
NoopFindReferencesCardProvider, ShowCommentEditorProvider, ShowFindReferencesCardProvider,
|
||||
};
|
||||
use crate::{
|
||||
appearance::Appearance,
|
||||
code_review::comments::{CommentId, CommentOrigin},
|
||||
editor::InteractionState,
|
||||
features::FeatureFlag,
|
||||
notebooks::editor::rich_text_styles,
|
||||
settings::{AppEditorSettings, FontSettings},
|
||||
view_components::find::FindDirection,
|
||||
};
|
||||
use ai::diff_validation::DiffDelta;
|
||||
use galaxy_core::platform::SessionPlatform;
|
||||
use galaxy_editor::{
|
||||
content::{
|
||||
buffer::{
|
||||
Buffer, BufferEditAction, EditOrigin, InitialBufferState, ToBufferCharOffset as _,
|
||||
ToBufferPoint,
|
||||
},
|
||||
text::IndentUnit,
|
||||
version::BufferVersion,
|
||||
},
|
||||
model::{CoreEditorModel, PlainTextEditorModel},
|
||||
multiline::AnyMultilineString,
|
||||
render::{
|
||||
element::{
|
||||
lens_element::RichTextElementLens, DisplayOptions, DisplayStateHandle, RichTextElement,
|
||||
VerticalExpansionBehavior,
|
||||
},
|
||||
model::{
|
||||
AutoScrollMode, BlockSpacing, Decoration, ExpansionType, LineCount, ParagraphStyles,
|
||||
RichTextStyles, CODE_EDITOR_HIDDEN_SECTION_EXPANSION_LINES,
|
||||
},
|
||||
},
|
||||
search::{SearchEvent, Searcher, MATCH_FILL, SELECTED_MATCH_FILL},
|
||||
};
|
||||
use galaxy_util::content_version::ContentVersion;
|
||||
use galaxyui::{
|
||||
elements::{
|
||||
new_scrollable::{
|
||||
AxisConfiguration, DualAxisConfig, NewScrollableElement, ScrollableAppearance,
|
||||
},
|
||||
ChildAnchor, ChildView, Dismiss, Fill, Flex, Margin, MouseStateHandle, NewScrollable,
|
||||
OffsetPositioning, Padding, ParentAnchor, ParentElement, ParentOffsetBounds,
|
||||
ScrollStateHandle, Shrinkable, Stack,
|
||||
},
|
||||
event::ModifiersState,
|
||||
keymap::Keystroke,
|
||||
platform::Cursor,
|
||||
prelude::RectF,
|
||||
text::point::Point,
|
||||
units::Pixels,
|
||||
AppContext, BlurContext, Element, Entity, FocusContext, ModelHandle, SingletonEntity, View,
|
||||
ViewContext, ViewHandle, WeakViewHandle, WindowId,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use num_traits::SaturatingSub;
|
||||
use pathfinder_geometry::vector::vec2f;
|
||||
use std::fmt::Debug;
|
||||
use std::rc::Rc;
|
||||
use std::{collections::HashMap, ops::Range};
|
||||
use std::{collections::HashSet, path::Path};
|
||||
use settings::Setting as _;
|
||||
use string_offset::CharOffset;
|
||||
use vec1::{vec1, Vec1};
|
||||
use vim::vim::{Direction, InsertPosition, VimMode, VimModel, VimState, VimSubscriber};
|
||||
use galaxy_core::platform::SessionPlatform;
|
||||
use galaxy_editor::content::buffer::{
|
||||
Buffer, BufferEditAction, EditOrigin, InitialBufferState, ToBufferCharOffset as _,
|
||||
ToBufferPoint,
|
||||
};
|
||||
use galaxy_editor::content::text::IndentUnit;
|
||||
use galaxy_editor::content::version::BufferVersion;
|
||||
use galaxy_editor::model::{CoreEditorModel, PlainTextEditorModel};
|
||||
use galaxy_editor::multiline::AnyMultilineString;
|
||||
use galaxy_editor::render::element::lens_element::RichTextElementLens;
|
||||
use galaxy_editor::render::element::{
|
||||
DisplayOptions, DisplayStateHandle, RichTextElement, VerticalExpansionBehavior,
|
||||
};
|
||||
use galaxy_editor::render::model::{
|
||||
AutoScrollMode, BlockSpacing, Decoration, ExpansionType, LineCount, ParagraphStyles,
|
||||
RichTextStyles, CODE_EDITOR_HIDDEN_SECTION_EXPANSION_LINES,
|
||||
};
|
||||
use galaxy_editor::search::{SearchEvent, Searcher, MATCH_FILL, SELECTED_MATCH_FILL};
|
||||
use galaxy_util::content_version::ContentVersion;
|
||||
use galaxy_util::standardized_path::StandardizedPath;
|
||||
use galaxyui::elements::new_scrollable::{
|
||||
AxisConfiguration, DualAxisConfig, NewScrollableElement, ScrollableAppearance,
|
||||
};
|
||||
use galaxyui::elements::{
|
||||
ChildAnchor, ChildView, Dismiss, Fill, Flex, Margin, MouseStateHandle, NewScrollable,
|
||||
OffsetPositioning, Padding, ParentAnchor, ParentElement, ParentOffsetBounds, ScrollStateHandle,
|
||||
Shrinkable, Stack,
|
||||
};
|
||||
use galaxyui::event::ModifiersState;
|
||||
use galaxyui::keymap::Keystroke;
|
||||
use galaxyui::platform::Cursor;
|
||||
use galaxyui::prelude::RectF;
|
||||
use galaxyui::text::point::Point;
|
||||
use galaxyui::units::Pixels;
|
||||
use galaxyui::{
|
||||
AppContext, BlurContext, CursorInfo, Element, Entity, FocusContext, ModelHandle,
|
||||
SingletonEntity, View, ViewContext, ViewHandle, WeakViewHandle, WindowId,
|
||||
};
|
||||
|
||||
use crate::appearance::Appearance;
|
||||
use crate::code::editor::comment_editor::{CommentEditor, CommentEditorEvent};
|
||||
use crate::code::editor::comments::PendingComment;
|
||||
use crate::code::editor::diff::DiffStatus;
|
||||
use crate::code::editor::element::{
|
||||
AddAsContextButton, CommentButton, EditorWrapper, EditorWrapperStateHandle, GutterHoverTarget,
|
||||
GutterRange, InnerEditor, LineNumberConfig, RevertHunkButton,
|
||||
};
|
||||
use crate::code::editor::find::view::{CodeEditorFind as Find, Event as FindViewEvent};
|
||||
use crate::code::editor::goto_line::view::{Event as GoToLineEvent, GoToLineView};
|
||||
use crate::code::editor::line::EditorLineLocation;
|
||||
use crate::code::editor::model::{
|
||||
CodeEditorModel, CodeEditorModelEvent, HoverableLink, LineBound, StableEditorLine,
|
||||
};
|
||||
use crate::code::editor::nav_bar::{NavBar, NavBarBehavior, NavBarEvent};
|
||||
use crate::code::editor::scroll::{ScrollPosition, ScrollTrigger, ScrollWheelBehavior};
|
||||
use crate::code::editor::EditorReviewComment;
|
||||
use crate::code::{
|
||||
DiffResult, NoopCommentEditorProvider, NoopFindReferencesCardProvider,
|
||||
ShowCommentEditorProvider, ShowFindReferencesCardProvider,
|
||||
};
|
||||
use crate::code_review::comments::{CommentId, CommentOrigin};
|
||||
use crate::editor::InteractionState;
|
||||
use crate::features::FeatureFlag;
|
||||
use crate::notebooks::editor::rich_text_styles;
|
||||
use crate::settings::{AppEditorSettings, CodeEditorLineNumberMode, FontSettings};
|
||||
use crate::view_components::find::FindDirection;
|
||||
|
||||
mod actions;
|
||||
pub use actions::init;
|
||||
@@ -123,6 +120,8 @@ pub enum CodeEditorEvent {
|
||||
},
|
||||
/// Emitted when a diff hunk is reverted
|
||||
DiffReverted,
|
||||
/// Emitted when the inline comment editor is opened.
|
||||
CommentEditorOpened,
|
||||
HiddenSectionExpanded,
|
||||
/// Emitted when a comment is saved. This gets propagated up so that it
|
||||
/// can be augmented with the file and repo paths and saved to the comment model.
|
||||
@@ -318,6 +317,10 @@ impl CodeEditorView {
|
||||
ctx.subscribe_to_model(&font_settings_handle, |me, _, _, ctx| {
|
||||
me.handle_appearance_or_font_change(ctx);
|
||||
});
|
||||
let app_editor_settings_handle = AppEditorSettings::handle(ctx);
|
||||
ctx.subscribe_to_model(&app_editor_settings_handle, |_, _, _, ctx| {
|
||||
ctx.notify();
|
||||
});
|
||||
|
||||
let model = ctx.add_model(|ctx| {
|
||||
CodeEditorModel::new(
|
||||
@@ -1215,18 +1218,32 @@ impl CodeEditorView {
|
||||
let appearance = Appearance::as_ref(ctx);
|
||||
let theme = appearance.theme();
|
||||
if self.display_options.show_line_numbers {
|
||||
let editor_settings = AppEditorSettings::as_ref(ctx);
|
||||
Some(LineNumberConfig {
|
||||
font_family: appearance.monospace_font_family(),
|
||||
font_size: appearance.monospace_font_size(),
|
||||
text_color: theme.sub_text_color(theme.background()).into(),
|
||||
highlight_text_color: theme.main_text_color(theme.background()).into(),
|
||||
starting_line_number: self.display_options.starting_line_number,
|
||||
mode: *editor_settings.code_editor_line_number_mode.value(),
|
||||
active_line_number: self.active_cursor_line_for_line_numbers(ctx),
|
||||
active_cursor_is_visible: self.is_focused(ctx) && self.is_editable(ctx),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn active_cursor_line_for_line_numbers(&self, ctx: &AppContext) -> Option<LineCount> {
|
||||
let model = self.model.as_ref(ctx);
|
||||
let selection = *model.selections(ctx).first();
|
||||
let buffer = model.content().as_ref(ctx);
|
||||
let point = selection.head.to_buffer_point(buffer);
|
||||
// `LineCount`s used by render blocks are zero-based, while buffer points report rows using
|
||||
// the editor's one-based convention.
|
||||
Some(LineCount::from(point.row.saturating_sub(1) as usize))
|
||||
}
|
||||
|
||||
fn run_find(&mut self, query: &str, ctx: &mut ViewContext<Self>) {
|
||||
self.searcher.update(ctx, |searcher, ctx| {
|
||||
searcher.set_query(query.to_string(), ctx);
|
||||
@@ -1252,6 +1269,14 @@ impl CodeEditorView {
|
||||
self.reset_for_editing_change();
|
||||
self.vim_maybe_enforce_cursor_line_cap(ctx);
|
||||
ctx.emit(CodeEditorEvent::SelectionChanged);
|
||||
if *AppEditorSettings::as_ref(ctx)
|
||||
.code_editor_line_number_mode
|
||||
.value()
|
||||
== CodeEditorLineNumberMode::Relative
|
||||
{
|
||||
// Repaint relative line-number gutters when the cursor origin changes.
|
||||
ctx.notify();
|
||||
}
|
||||
}
|
||||
CodeEditorModelEvent::ContentChanged { origin } => {
|
||||
if origin.from_user() {
|
||||
@@ -1426,12 +1451,18 @@ impl CodeEditorView {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_language_with_path(&mut self, path: &Path, ctx: &mut ViewContext<Self>) {
|
||||
pub fn set_language_with_path(&mut self, path: &StandardizedPath, ctx: &mut ViewContext<Self>) {
|
||||
self.model.update(ctx, |model, ctx| {
|
||||
model.set_language_with_path(path, ctx);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_language_with_local_path(&mut self, path: &Path, ctx: &mut ViewContext<Self>) {
|
||||
self.model.update(ctx, |model, ctx| {
|
||||
model.set_language_with_local_path(path, ctx);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_language_with_name(&mut self, name: &str, ctx: &mut ViewContext<Self>) {
|
||||
self.model.update(ctx, |model, ctx| {
|
||||
model.set_language_with_name(name, ctx);
|
||||
@@ -2040,7 +2071,7 @@ impl CodeEditorView {
|
||||
}
|
||||
}
|
||||
|
||||
// If the character is opening autcomplete symbol, we want to autocomplete it with a closing symbol.
|
||||
// If the character is opening autocomplete symbol, we want to autocomplete it with a closing symbol.
|
||||
if let Some(close) = AUTOCOMPLETE_SYMBOLS.get(&first_char) {
|
||||
self.model.update(ctx, |model, ctx| {
|
||||
model.autocomplete_symbol(first_char, *close, ctx);
|
||||
@@ -2126,6 +2157,7 @@ impl CodeEditorView {
|
||||
self.model.update(ctx, |editor_model, ctx| {
|
||||
editor_model.reopen_comment_line(id, location, comment_text, origin, ctx);
|
||||
});
|
||||
ctx.emit(CodeEditorEvent::CommentEditorOpened);
|
||||
|
||||
ctx.notify();
|
||||
}
|
||||
@@ -2356,6 +2388,18 @@ impl View for CodeEditorView {
|
||||
}
|
||||
}
|
||||
|
||||
fn active_cursor_position(&self, ctx: &ViewContext<Self>) -> Option<CursorInfo> {
|
||||
let render_state = self.model.as_ref(ctx).render_state().as_ref(ctx);
|
||||
let cursor_id = render_state.saved_positions().cursor_id();
|
||||
let font_size = render_state.styles().base_text.font_size;
|
||||
|
||||
ctx.element_position_by_id(cursor_id.as_str())
|
||||
.map(|position| CursorInfo {
|
||||
position,
|
||||
font_size,
|
||||
})
|
||||
}
|
||||
|
||||
fn on_blur(&mut self, blur_ctx: &BlurContext, ctx: &mut ViewContext<Self>) {
|
||||
if blur_ctx.is_self_blurred() {
|
||||
ctx.notify();
|
||||
@@ -2370,8 +2414,14 @@ impl View for CodeEditorView {
|
||||
}
|
||||
if let Some(vim_mode) = self.vim_mode(app) {
|
||||
context.set.insert("Vim");
|
||||
if vim_mode == VimMode::Normal {
|
||||
context.set.insert("VimNormalMode");
|
||||
match vim_mode {
|
||||
VimMode::Normal => {
|
||||
context.set.insert("VimNormalMode");
|
||||
}
|
||||
VimMode::Visual(_) => {
|
||||
context.set.insert("VimVisualMode");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if self.find_bar.is_some() {
|
||||
@@ -2422,6 +2472,17 @@ impl CodeEditorView {
|
||||
};
|
||||
self.handle_goto_line_event(&event, ctx);
|
||||
}
|
||||
|
||||
pub fn displayed_line_number_for_test(
|
||||
&self,
|
||||
one_based_line_number: usize,
|
||||
ctx: &AppContext,
|
||||
) -> Option<usize> {
|
||||
let line_number_config = self.line_number_config(ctx)?;
|
||||
let line_count = LineCount::from(one_based_line_number.checked_sub(1)?);
|
||||
|
||||
Some(line_number_config.display_line_number(line_count))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,42 +1,36 @@
|
||||
#![cfg_attr(target_family = "wasm", allow(dead_code, unused_imports))]
|
||||
// Adding this file level gate as some of the code around editability is not used in WASM yet.
|
||||
|
||||
use crate::code::editor::{
|
||||
line::EditorLineLocation,
|
||||
model::CodeEditorModel,
|
||||
view::{CodeEditorEvent, CodeEditorView, VimMode},
|
||||
};
|
||||
use crate::{
|
||||
cmd_or_ctrl_shift, code_review::comments::CommentId,
|
||||
code_review::telemetry_event::CodeReviewTelemetryEvent, editor::InteractionState,
|
||||
features::FeatureFlag, notebooks::editor::model::word_unit, send_telemetry_from_ctx,
|
||||
util::bindings::CustomAction,
|
||||
};
|
||||
use galaxy_editor::{
|
||||
content::version::BufferVersion,
|
||||
editor::{EmbeddedItemModel, RunnableCommandModel, TextDecoration},
|
||||
model::{CoreEditorModel, PlainTextEditorModel},
|
||||
render::{
|
||||
element::RichTextAction,
|
||||
model::{ExpansionType, LineCount, Location},
|
||||
},
|
||||
selection::{TextDirection, TextUnit},
|
||||
};
|
||||
use galaxy_util::user_input::UserInput;
|
||||
use galaxyui::{
|
||||
actions::StandardAction,
|
||||
elements::Axis,
|
||||
event::ModifiersState,
|
||||
keymap::{EditableBinding, FixedBinding, Keystroke, PerPlatformKeystroke},
|
||||
units::Pixels,
|
||||
AppContext, TypedActionView, ViewContext, WeakViewHandle,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use rangemap::RangeSet;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::Debug;
|
||||
use std::ops::Range;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use rangemap::RangeSet;
|
||||
use string_offset::CharOffset;
|
||||
use galaxy_editor::content::version::BufferVersion;
|
||||
use galaxy_editor::editor::{EmbeddedItemModel, RunnableCommandModel, TextDecoration};
|
||||
use galaxy_editor::model::{CoreEditorModel, PlainTextEditorModel};
|
||||
use galaxy_editor::render::element::RichTextAction;
|
||||
use galaxy_editor::render::model::{ExpansionType, LineCount, Location};
|
||||
use galaxy_editor::selection::{TextDirection, TextUnit};
|
||||
use galaxy_util::user_input::UserInput;
|
||||
use galaxyui::actions::StandardAction;
|
||||
use galaxyui::elements::Axis;
|
||||
use galaxyui::event::ModifiersState;
|
||||
use galaxyui::keymap::{EditableBinding, FixedBinding, Keystroke, PerPlatformKeystroke};
|
||||
use galaxyui::units::Pixels;
|
||||
use galaxyui::{AppContext, TypedActionView, ViewContext, WeakViewHandle};
|
||||
|
||||
use crate::cmd_or_ctrl_shift;
|
||||
use crate::code::editor::line::EditorLineLocation;
|
||||
use crate::code::editor::model::CodeEditorModel;
|
||||
use crate::code::editor::view::{CodeEditorEvent, CodeEditorView, VimMode};
|
||||
use crate::code_review::comments::CommentId;
|
||||
use crate::editor::InteractionState;
|
||||
use crate::features::FeatureFlag;
|
||||
use crate::notebooks::editor::model::word_unit;
|
||||
use crate::util::bindings::CustomAction;
|
||||
|
||||
/// Limit the keybindings that conflict with the Agent Mode embedded editor.
|
||||
const NON_EDITABLE_KEYMAP_CONTEXT: &str = "NonEditableKeymapContext";
|
||||
@@ -508,8 +502,24 @@ pub fn init(app: &mut AppContext) {
|
||||
.with_context_predicate(text_entry.clone())
|
||||
.with_key_binding("cmdorctrl-/"),
|
||||
EditableBinding::new("editor_view:delete", "Delete", CodeEditorViewAction::Delete)
|
||||
.with_context_predicate(text_entry.clone())
|
||||
.with_context_predicate(
|
||||
text_entry.clone() & !id!("VimNormalMode") & !id!("VimVisualMode"),
|
||||
)
|
||||
.with_key_binding("ctrl-d"),
|
||||
EditableBinding::new(
|
||||
"editor_view:vim_scroll_half_page_down",
|
||||
"Scroll down half a page (vim)",
|
||||
CodeEditorViewAction::ScrollHalfPageDown,
|
||||
)
|
||||
.with_context_predicate(text_entry.clone() & (id!("VimNormalMode") | id!("VimVisualMode")))
|
||||
.with_key_binding("ctrl-d"),
|
||||
EditableBinding::new(
|
||||
"editor_view:vim_scroll_half_page_up",
|
||||
"Scroll up half a page (vim)",
|
||||
CodeEditorViewAction::ScrollHalfPageUp,
|
||||
)
|
||||
.with_context_predicate(text_entry.clone() & (id!("VimNormalMode") | id!("VimVisualMode")))
|
||||
.with_key_binding("ctrl-u"),
|
||||
EditableBinding::new(
|
||||
"editor_view:cut_word_left",
|
||||
"Cut word left",
|
||||
@@ -616,6 +626,8 @@ pub enum CodeEditorViewAction {
|
||||
ToggleComment,
|
||||
ScrollVertical(Pixels),
|
||||
ScrollHorizontal(Pixels),
|
||||
ScrollHalfPageDown,
|
||||
ScrollHalfPageUp,
|
||||
SelectUp,
|
||||
SelectDown,
|
||||
SelectLeft,
|
||||
@@ -753,6 +765,8 @@ impl CodeEditorViewAction {
|
||||
Self::WindowsCtrlC => true,
|
||||
Self::ScrollVertical(_)
|
||||
| Self::ScrollHorizontal(_)
|
||||
| Self::ScrollHalfPageDown
|
||||
| Self::ScrollHalfPageUp
|
||||
| Self::SelectUp
|
||||
| Self::SelectDown
|
||||
| Self::SelectLeft
|
||||
@@ -867,7 +881,7 @@ impl TypedActionView for CodeEditorView {
|
||||
match self.vim_mode(ctx) {
|
||||
Some(VimMode::Visual(_)) => {
|
||||
// In Vim Visual mode, if we get a ToggleComment request via the keyboard
|
||||
// shorcut (cmd+/), simulate `gc` to the VimModel so that we correctly
|
||||
// shortcut (cmd+/), simulate `gc` to the VimModel so that we correctly
|
||||
// calculate the current visual selections, apply the toggle, and exit to
|
||||
// normal mode.
|
||||
self.vim_user_insert("gc", ctx);
|
||||
@@ -889,6 +903,12 @@ impl TypedActionView for CodeEditorView {
|
||||
render_state.scroll_horizontal(*delta, ctx);
|
||||
})
|
||||
}),
|
||||
ScrollHalfPageDown => {
|
||||
self.vim_keystroke(&Keystroke::parse("ctrl-d").expect("ctrl-d parses"), ctx)
|
||||
}
|
||||
ScrollHalfPageUp => {
|
||||
self.vim_keystroke(&Keystroke::parse("ctrl-u").expect("ctrl-u parses"), ctx)
|
||||
}
|
||||
SelectUp => self.model.update(ctx, |model, ctx| {
|
||||
model.select_up(ctx);
|
||||
}),
|
||||
@@ -1067,8 +1087,6 @@ impl TypedActionView for CodeEditorView {
|
||||
}
|
||||
RevertDiffHunk { line_range } => {
|
||||
if FeatureFlag::RevertDiffHunk.is_enabled() {
|
||||
send_telemetry_from_ctx!(CodeReviewTelemetryEvent::RevertHunkClicked, ctx);
|
||||
|
||||
// Convert line range to diff hunk index and revert it
|
||||
let hunk_index = self
|
||||
.model
|
||||
@@ -1093,6 +1111,7 @@ impl TypedActionView for CodeEditorView {
|
||||
self.model.update(ctx, |model: &mut CodeEditorModel, ctx| {
|
||||
model.open_comment_line(line_info, ctx);
|
||||
});
|
||||
ctx.emit(CodeEditorEvent::CommentEditorOpened);
|
||||
|
||||
ctx.focus(&self.active_comment_editor);
|
||||
ctx.notify();
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
use galaxy_core::ui::appearance::Appearance;
|
||||
use galaxy_editor::render::element::VerticalExpansionBehavior;
|
||||
use galaxyui::{
|
||||
elements::{new_scrollable::ScrollableAppearance, ScrollbarWidth},
|
||||
platform::WindowStyle,
|
||||
App, TypedActionView, ViewHandle, WindowId,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
cloud_object::model::persistence::CloudModel,
|
||||
editor::InteractionState,
|
||||
notebooks::editor::keys::NotebookKeybindings,
|
||||
server::server_api::{team::MockTeamClient, workspace::MockWorkspaceClient},
|
||||
settings_view::keybindings::KeybindingChangedNotifier,
|
||||
test_util::settings::initialize_settings_for_tests,
|
||||
vim_registers::VimRegisters,
|
||||
workspace::{sync_inputs::SyncedInputState, ActiveSession},
|
||||
workspaces::user_workspaces::UserWorkspaces,
|
||||
AuthStateProvider,
|
||||
};
|
||||
use galaxy_core::ui::appearance::Appearance;
|
||||
use galaxy_editor::render::element::VerticalExpansionBehavior;
|
||||
use galaxy_util::user_input::UserInput;
|
||||
use galaxyui::elements::new_scrollable::ScrollableAppearance;
|
||||
use galaxyui::elements::ScrollbarWidth;
|
||||
use galaxyui::platform::WindowStyle;
|
||||
use galaxyui::{App, TypedActionView, ViewHandle, WindowId};
|
||||
|
||||
use super::{CodeEditorRenderOptions, CodeEditorView, CodeEditorViewAction};
|
||||
use galaxy_util::user_input::UserInput;
|
||||
use crate::cloud_object::model::persistence::CloudModel;
|
||||
use crate::editor::InteractionState;
|
||||
use crate::notebooks::editor::keys::NotebookKeybindings;
|
||||
use crate::server::server_api::team::MockTeamClient;
|
||||
use crate::server::server_api::workspace::MockWorkspaceClient;
|
||||
use crate::settings_view::keybindings::KeybindingChangedNotifier;
|
||||
use crate::test_util::settings::initialize_settings_for_tests;
|
||||
use crate::vim_registers::VimRegisters;
|
||||
use crate::workspace::sync_inputs::SyncedInputState;
|
||||
use crate::workspace::ActiveSession;
|
||||
use crate::workspaces::user_workspaces::UserWorkspaces;
|
||||
use crate::AuthStateProvider;
|
||||
|
||||
fn initialize_editor(app: &mut App) -> (WindowId, ViewHandle<CodeEditorView>) {
|
||||
initialize_settings_for_tests(app);
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
use super::{CodeEditorEvent, CodeEditorView};
|
||||
use crate::code::editor::{
|
||||
find::view::Event as FindViewEvent,
|
||||
model::{CaseTransform, CodeEditorModel, LineBound},
|
||||
};
|
||||
use crate::{
|
||||
view_components::find::FindDirection,
|
||||
vim_registers::{RegisterContent, VimRegisters},
|
||||
};
|
||||
use galaxy_editor::{
|
||||
content::buffer::{
|
||||
AutoScrollBehavior, BufferEditAction, EditOrigin, SelectionOffsets,
|
||||
ToBufferCharOffset as _, VimInsertPoint,
|
||||
},
|
||||
model::{CoreEditorModel, PlainTextEditorModel},
|
||||
selection::{TextDirection, TextUnit},
|
||||
};
|
||||
use galaxyui::{text::point::Point, SingletonEntity, ViewContext};
|
||||
use vim::vim::{
|
||||
BracketChar, CharacterMotion, Direction, FindCharMotion, FirstNonWhitespaceMotion,
|
||||
InsertPosition, LineMotion, ModeTransition, MotionType, TextObjectType, VimHandler, VimMode,
|
||||
VimMotion, VimOperand, VimOperator, VimTextObject, WordMotion,
|
||||
};
|
||||
use galaxy_editor::content::buffer::{
|
||||
AutoScrollBehavior, BufferEditAction, EditOrigin, SelectionOffsets, ToBufferCharOffset as _,
|
||||
VimInsertPoint,
|
||||
};
|
||||
use galaxy_editor::model::{CoreEditorModel, PlainTextEditorModel};
|
||||
use galaxy_editor::render::model::AutoScrollMode;
|
||||
use galaxy_editor::selection::{TextDirection, TextUnit};
|
||||
use galaxyui::text::point::Point;
|
||||
use galaxyui::units::IntoPixels;
|
||||
use galaxyui::{SingletonEntity, ViewContext};
|
||||
|
||||
use super::{CodeEditorEvent, CodeEditorView};
|
||||
use crate::code::editor::find::view::Event as FindViewEvent;
|
||||
use crate::code::editor::model::{CaseTransform, CodeEditorModel, LineBound};
|
||||
use crate::view_components::find::FindDirection;
|
||||
use crate::vim_registers::{RegisterContent, VimRegisters};
|
||||
|
||||
impl VimHandler for CodeEditorView {
|
||||
fn insert_char(&mut self, c: char, ctx: &mut ViewContext<Self>) {
|
||||
@@ -921,6 +919,62 @@ impl VimHandler for CodeEditorView {
|
||||
fn show_hover(&mut self, ctx: &mut ViewContext<Self>) {
|
||||
ctx.emit(CodeEditorEvent::VimShowHover);
|
||||
}
|
||||
|
||||
fn center_cursor_vertically(&mut self, ctx: &mut ViewContext<Self>) {
|
||||
let cursor_offset = self
|
||||
.model
|
||||
.as_ref(ctx)
|
||||
.buffer_selection_model()
|
||||
.as_ref(ctx)
|
||||
.first_selection_head();
|
||||
self.model
|
||||
.as_ref(ctx)
|
||||
.render_state()
|
||||
.clone()
|
||||
.update(ctx, |render_state, _ctx| {
|
||||
render_state.request_autoscroll_to(AutoScrollMode::PositionOffsetInViewportCenter(
|
||||
cursor_offset,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
fn scroll_half_page_down(&mut self, count: u32, ctx: &mut ViewContext<Self>) {
|
||||
self.scroll_half_page(count, TextDirection::Forwards, ctx);
|
||||
}
|
||||
|
||||
fn scroll_half_page_up(&mut self, count: u32, ctx: &mut ViewContext<Self>) {
|
||||
self.scroll_half_page(count, TextDirection::Backwards, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
impl CodeEditorView {
|
||||
/// Implements `<C-d>` and `<C-u>`. Without a count, scrolls by half the
|
||||
/// viewport; with a count > 1, scrolls by that many lines (matching vim's
|
||||
/// `n<C-d>` / `n<C-u>` behavior).
|
||||
fn scroll_half_page(
|
||||
&mut self,
|
||||
count: u32,
|
||||
direction: TextDirection,
|
||||
ctx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let model = self.model.as_ref(ctx);
|
||||
let lines = if count > 1 {
|
||||
count as usize
|
||||
} else {
|
||||
(model.lines_in_viewport(ctx) / 2).max(1)
|
||||
};
|
||||
let signed_lines = match direction {
|
||||
TextDirection::Forwards => -(lines as f32),
|
||||
TextDirection::Backwards => lines as f32,
|
||||
};
|
||||
let scroll_pixels = (signed_lines * model.line_height(ctx)).into_pixels();
|
||||
self.model.update(ctx, |model, ctx| {
|
||||
model.vim_move_vertical_by_offset(lines as u32, direction, false, ctx);
|
||||
model.render_state().update(ctx, |render_state, ctx| {
|
||||
render_state.scroll(scroll_pixels, ctx);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Like [`str::trim_end_matches`] except that it only trims up to a single instance.
|
||||
|
||||
@@ -1,33 +1,35 @@
|
||||
use crate::auth::AuthStateProvider;
|
||||
use crate::cloud_object::model::persistence::CloudModel;
|
||||
use crate::code::editor::view::CodeEditorRenderOptions;
|
||||
use crate::notebooks::editor::keys::NotebookKeybindings;
|
||||
use crate::workspace::ActiveSession;
|
||||
use crate::{
|
||||
code::editor::view::{CodeEditorView, CodeEditorViewAction},
|
||||
server::server_api::{team::MockTeamClient, workspace::MockWorkspaceClient},
|
||||
settings::AppEditorSettings,
|
||||
settings_view::keybindings::KeybindingChangedNotifier,
|
||||
test_util::settings::initialize_settings_for_tests,
|
||||
vim_registers::VimRegisters,
|
||||
workspace::sync_inputs::SyncedInputState,
|
||||
workspaces::user_workspaces::UserWorkspaces,
|
||||
};
|
||||
use galaxy_core::{features::FeatureFlag, settings::Setting, ui::appearance::Appearance};
|
||||
use galaxy_editor::model::CoreEditorModel;
|
||||
use galaxy_editor::{
|
||||
content::buffer::{InitialBufferState, ToBufferCharOffset, ToBufferPoint},
|
||||
render::element::VerticalExpansionBehavior,
|
||||
};
|
||||
use galaxy_util::user_input::UserInput;
|
||||
use galaxyui::text::point::Point;
|
||||
use galaxyui::{
|
||||
keymap::Keystroke, platform::WindowStyle, App, SingletonEntity, TypedActionView, UpdateModel,
|
||||
ViewHandle,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
use pathfinder_geometry::vector::Vector2F;
|
||||
use unindent::Unindent;
|
||||
use vim::vim::{MotionType, VimMode};
|
||||
use galaxy_core::features::FeatureFlag;
|
||||
use galaxy_core::settings::Setting;
|
||||
use galaxy_core::ui::appearance::Appearance;
|
||||
use galaxy_editor::content::buffer::{InitialBufferState, ToBufferCharOffset, ToBufferPoint};
|
||||
use galaxy_editor::model::CoreEditorModel;
|
||||
use galaxy_editor::render::element::VerticalExpansionBehavior;
|
||||
use galaxy_editor::render::model::viewport::SizeInfo;
|
||||
use galaxy_util::user_input::UserInput;
|
||||
use galaxyui::keymap::Keystroke;
|
||||
use galaxyui::platform::WindowStyle;
|
||||
use galaxyui::text::point::Point;
|
||||
use galaxyui::units::IntoPixels;
|
||||
use galaxyui::{App, SingletonEntity, TypedActionView, UpdateModel, ViewHandle};
|
||||
|
||||
use crate::auth::AuthStateProvider;
|
||||
use crate::cloud_object::model::persistence::CloudModel;
|
||||
use crate::code::editor::view::{CodeEditorRenderOptions, CodeEditorView, CodeEditorViewAction};
|
||||
use crate::notebooks::editor::keys::NotebookKeybindings;
|
||||
use crate::server::server_api::team::MockTeamClient;
|
||||
use crate::server::server_api::workspace::MockWorkspaceClient;
|
||||
use crate::settings::AppEditorSettings;
|
||||
use crate::settings_view::keybindings::KeybindingChangedNotifier;
|
||||
use crate::test_util::settings::initialize_settings_for_tests;
|
||||
use crate::vim_registers::VimRegisters;
|
||||
use crate::workspace::sync_inputs::SyncedInputState;
|
||||
use crate::workspace::ActiveSession;
|
||||
use crate::workspaces::user_workspaces::UserWorkspaces;
|
||||
|
||||
// Await render/layout completion for a CodeEditorView in tests.
|
||||
async fn layout_editor_view(app: &mut App, editor: &ViewHandle<CodeEditorView>) {
|
||||
@@ -143,6 +145,37 @@ fn set_cursor_position(editor: &ViewHandle<CodeEditorView>, row: usize, col: usi
|
||||
});
|
||||
}
|
||||
|
||||
/// Set the viewport to exactly `lines` rows tall. Returns the line height in pixels.
|
||||
fn set_viewport_lines(editor: &ViewHandle<CodeEditorView>, lines: usize, app: &mut App) -> f32 {
|
||||
let (line_height, render_state) = editor.read(app, |view, ctx| {
|
||||
let model = view.model.as_ref(ctx);
|
||||
(model.line_height(ctx), model.render_state().clone())
|
||||
});
|
||||
render_state.update(app, |render_state, ctx| {
|
||||
render_state.set_viewport_size(
|
||||
SizeInfo {
|
||||
viewport_size: Vector2F::new(800.0, line_height * lines as f32),
|
||||
needs_layout: false,
|
||||
},
|
||||
ctx,
|
||||
);
|
||||
});
|
||||
line_height
|
||||
}
|
||||
|
||||
/// Read the current vertical scroll position.
|
||||
fn scroll_top(editor: &ViewHandle<CodeEditorView>, app: &App) -> f32 {
|
||||
editor.read(app, |view, ctx| {
|
||||
view.model
|
||||
.as_ref(ctx)
|
||||
.render_state()
|
||||
.as_ref(ctx)
|
||||
.viewport()
|
||||
.scroll_top()
|
||||
.as_f32()
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_code_editor_vim_basic_mode_switching() {
|
||||
let _feature_flag_guard = FeatureFlag::VimCodeEditor.override_enabled(true);
|
||||
@@ -1436,3 +1469,266 @@ fn test_vim_visual_linewise_delete_first_line_does_not_panic() {
|
||||
assert_eq!(buffer_text(&editor, &app), "bbb\nccc");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vim_zz_in_normal_mode_preserves_cursor_and_mode() {
|
||||
let _feature_flag_guard = FeatureFlag::VimCodeEditor.override_enabled(true);
|
||||
|
||||
App::test((), |mut app| async move {
|
||||
initialize_code_editor_app(&mut app);
|
||||
let editor = add_code_editor(
|
||||
"line 1
|
||||
line 2
|
||||
line 3
|
||||
line 4
|
||||
line 5",
|
||||
&mut app,
|
||||
);
|
||||
|
||||
layout_editor_view(&mut app, &editor).await;
|
||||
|
||||
// Place cursor on line 3, then center it. zz scrolls but should not move
|
||||
// the cursor or change the mode.
|
||||
set_cursor_position(&editor, 3, 0, &mut app);
|
||||
assert_eq!(cursor_position(&editor, &app), (3, 0));
|
||||
assert_eq!(vim_mode(&editor, &app), Some(VimMode::Normal));
|
||||
|
||||
vim_user_insert(&editor, "zz", &mut app);
|
||||
|
||||
assert_eq!(cursor_position(&editor, &app), (3, 0));
|
||||
assert_eq!(vim_mode(&editor, &app), Some(VimMode::Normal));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vim_zz_in_visual_mode_preserves_cursor_and_mode() {
|
||||
let _feature_flag_guard = FeatureFlag::VimCodeEditor.override_enabled(true);
|
||||
|
||||
App::test((), |mut app| async move {
|
||||
initialize_code_editor_app(&mut app);
|
||||
let editor = add_code_editor(
|
||||
"line 1
|
||||
line 2
|
||||
line 3
|
||||
line 4
|
||||
line 5",
|
||||
&mut app,
|
||||
);
|
||||
|
||||
layout_editor_view(&mut app, &editor).await;
|
||||
|
||||
set_cursor_position(&editor, 3, 0, &mut app);
|
||||
vim_user_insert(&editor, "v", &mut app);
|
||||
assert_eq!(
|
||||
vim_mode(&editor, &app),
|
||||
Some(VimMode::Visual(MotionType::Charwise))
|
||||
);
|
||||
|
||||
vim_user_insert(&editor, "zz", &mut app);
|
||||
|
||||
assert_eq!(cursor_position(&editor, &app), (3, 0));
|
||||
assert_eq!(
|
||||
vim_mode(&editor, &app),
|
||||
Some(VimMode::Visual(MotionType::Charwise))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vim_z_followed_by_non_z_clears_pending() {
|
||||
let _feature_flag_guard = FeatureFlag::VimCodeEditor.override_enabled(true);
|
||||
|
||||
App::test((), |mut app| async move {
|
||||
initialize_code_editor_app(&mut app);
|
||||
let editor = add_code_editor(
|
||||
"line 1
|
||||
line 2
|
||||
line 3",
|
||||
&mut app,
|
||||
);
|
||||
|
||||
layout_editor_view(&mut app, &editor).await;
|
||||
|
||||
// `z` followed by an unrecognized char should clear the pending action.
|
||||
// After that, a subsequent `j` should move the cursor down one line as normal.
|
||||
set_cursor_position(&editor, 1, 0, &mut app);
|
||||
vim_user_insert(&editor, "z", &mut app);
|
||||
vim_user_insert(&editor, "x", &mut app);
|
||||
assert_eq!(cursor_position(&editor, &app), (1, 0));
|
||||
|
||||
vim_user_insert(&editor, "j", &mut app);
|
||||
assert_eq!(cursor_position(&editor, &app), (2, 0));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vim_ctrl_d_scrolls_half_page_down() {
|
||||
let _feature_flag_guard = FeatureFlag::VimCodeEditor.override_enabled(true);
|
||||
|
||||
App::test((), |mut app| async move {
|
||||
initialize_code_editor_app(&mut app);
|
||||
let buffer: String = (1..=200).map(|i| format!("line {}\n", i)).collect();
|
||||
let editor = add_code_editor(buffer.as_str(), &mut app);
|
||||
|
||||
layout_editor_view(&mut app, &editor).await;
|
||||
|
||||
// 20 visible lines → half page = 10 lines.
|
||||
let line_height = set_viewport_lines(&editor, 20, &mut app);
|
||||
let half_page = 10;
|
||||
|
||||
set_cursor_position(&editor, 1, 0, &mut app);
|
||||
let (start_row, _) = cursor_position(&editor, &app);
|
||||
let start_scroll = scroll_top(&editor, &app);
|
||||
|
||||
editor.update(&mut app, |view, ctx| {
|
||||
view.vim_keystroke(&Keystroke::parse("ctrl-d").unwrap(), ctx);
|
||||
});
|
||||
|
||||
let (after_row, _) = cursor_position(&editor, &app);
|
||||
let after_scroll = scroll_top(&editor, &app);
|
||||
assert_eq!(after_row, start_row + half_page);
|
||||
assert!(
|
||||
(after_scroll - start_scroll - half_page as f32 * line_height).abs() < 0.5,
|
||||
"scroll_top should advance by half_page * line_height \
|
||||
(start={start_scroll}, after={after_scroll}, line_height={line_height})",
|
||||
);
|
||||
assert_eq!(vim_mode(&editor, &app), Some(VimMode::Normal));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vim_ctrl_u_scrolls_half_page_up() {
|
||||
let _feature_flag_guard = FeatureFlag::VimCodeEditor.override_enabled(true);
|
||||
|
||||
App::test((), |mut app| async move {
|
||||
initialize_code_editor_app(&mut app);
|
||||
let buffer: String = (1..=200).map(|i| format!("line {}\n", i)).collect();
|
||||
let editor = add_code_editor(buffer.as_str(), &mut app);
|
||||
|
||||
layout_editor_view(&mut app, &editor).await;
|
||||
|
||||
// 20 visible lines → half page = 10 lines.
|
||||
let line_height = set_viewport_lines(&editor, 20, &mut app);
|
||||
let half_page = 10;
|
||||
|
||||
// Start near the bottom and scroll down so we have room to scroll up.
|
||||
set_cursor_position(&editor, 100, 0, &mut app);
|
||||
let render_state = editor.read(&app, |view, ctx| {
|
||||
view.model.as_ref(ctx).render_state().clone()
|
||||
});
|
||||
render_state.update(&mut app, |render_state, ctx| {
|
||||
render_state.scroll(-(50.0 * line_height).into_pixels(), ctx);
|
||||
});
|
||||
|
||||
let (start_row, _) = cursor_position(&editor, &app);
|
||||
let start_scroll = scroll_top(&editor, &app);
|
||||
|
||||
editor.update(&mut app, |view, ctx| {
|
||||
view.vim_keystroke(&Keystroke::parse("ctrl-u").unwrap(), ctx);
|
||||
});
|
||||
|
||||
let (after_row, _) = cursor_position(&editor, &app);
|
||||
let after_scroll = scroll_top(&editor, &app);
|
||||
assert_eq!(after_row, start_row - half_page);
|
||||
assert!(
|
||||
(start_scroll - after_scroll - half_page as f32 * line_height).abs() < 0.5,
|
||||
"scroll_top should retreat by half_page * line_height \
|
||||
(start={start_scroll}, after={after_scroll}, line_height={line_height})",
|
||||
);
|
||||
assert_eq!(vim_mode(&editor, &app), Some(VimMode::Normal));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vim_ctrl_d_with_count_scrolls_n_lines() {
|
||||
let _feature_flag_guard = FeatureFlag::VimCodeEditor.override_enabled(true);
|
||||
|
||||
App::test((), |mut app| async move {
|
||||
initialize_code_editor_app(&mut app);
|
||||
let buffer: String = (1..=200).map(|i| format!("line {}\n", i)).collect();
|
||||
let editor = add_code_editor(buffer.as_str(), &mut app);
|
||||
|
||||
layout_editor_view(&mut app, &editor).await;
|
||||
|
||||
// Viewport half page would be 10, but `5<C-d>` should scroll by 5 lines,
|
||||
// not 5 * half_page.
|
||||
set_viewport_lines(&editor, 20, &mut app);
|
||||
|
||||
set_cursor_position(&editor, 1, 0, &mut app);
|
||||
let (start_row, _) = cursor_position(&editor, &app);
|
||||
|
||||
vim_user_insert(&editor, "5", &mut app);
|
||||
editor.update(&mut app, |view, ctx| {
|
||||
view.vim_keystroke(&Keystroke::parse("ctrl-d").unwrap(), ctx);
|
||||
});
|
||||
|
||||
let (after_row, _) = cursor_position(&editor, &app);
|
||||
assert_eq!(after_row, start_row + 5);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vim_ctrl_d_consumes_pending_count() {
|
||||
let _feature_flag_guard = FeatureFlag::VimCodeEditor.override_enabled(true);
|
||||
|
||||
App::test((), |mut app| async move {
|
||||
initialize_code_editor_app(&mut app);
|
||||
// Use a buffer big enough that scrolling won't max the cursor at the bottom.
|
||||
let buffer: String = (1..=200)
|
||||
.map(|i| format!("line {}\n", i))
|
||||
.collect::<String>();
|
||||
let editor = add_code_editor(buffer.as_str(), &mut app);
|
||||
|
||||
layout_editor_view(&mut app, &editor).await;
|
||||
|
||||
// After `2<C-d>`, the pending count of 2 must be consumed by ctrl-d. A
|
||||
// following `j` should move the cursor down exactly 1 line, not 2.
|
||||
set_cursor_position(&editor, 1, 0, &mut app);
|
||||
vim_user_insert(&editor, "2", &mut app);
|
||||
editor.update(&mut app, |view, ctx| {
|
||||
view.vim_keystroke(&Keystroke::parse("ctrl-d").unwrap(), ctx);
|
||||
});
|
||||
let (after_scroll_row, _) = cursor_position(&editor, &app);
|
||||
vim_user_insert(&editor, "j", &mut app);
|
||||
let (after_j_row, _) = cursor_position(&editor, &app);
|
||||
assert_eq!(
|
||||
after_j_row,
|
||||
after_scroll_row + 1,
|
||||
"j after `2<C-d>` should move down 1, not 2 (after_scroll_row={}, after_j_row={})",
|
||||
after_scroll_row,
|
||||
after_j_row
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vim_ctrl_d_clears_pending_operator() {
|
||||
let _feature_flag_guard = FeatureFlag::VimCodeEditor.override_enabled(true);
|
||||
|
||||
App::test((), |mut app| async move {
|
||||
initialize_code_editor_app(&mut app);
|
||||
let editor = add_code_editor(
|
||||
"alpha bravo charlie
|
||||
delta echo foxtrot
|
||||
golf hotel india",
|
||||
&mut app,
|
||||
);
|
||||
|
||||
layout_editor_view(&mut app, &editor).await;
|
||||
|
||||
// After `d<C-d>`, the pending `d` operator must be cleared. A following
|
||||
// `w` should move forward by word, not delete a word.
|
||||
set_cursor_position(&editor, 1, 0, &mut app);
|
||||
let original = buffer_text(&editor, &app);
|
||||
vim_user_insert(&editor, "d", &mut app);
|
||||
editor.update(&mut app, |view, ctx| {
|
||||
view.vim_keystroke(&Keystroke::parse("ctrl-d").unwrap(), ctx);
|
||||
});
|
||||
vim_user_insert(&editor, "w", &mut app);
|
||||
assert_eq!(
|
||||
buffer_text(&editor, &app),
|
||||
original,
|
||||
"w after `d<C-d>` should not delete (pending d should be cleared)"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user