feat: add editor component

This commit is contained in:
2026-11-04 14:01:54 +07:00
parent 50da1d4a9d
commit 503b105bad
13 changed files with 1021 additions and 9 deletions
+1
View File
@@ -10,6 +10,7 @@ gpui_platform = { workspace = true }
reqwest_client = { workspace = true }
gpui-native-core = { workspace = true, features = ["full"] }
gpui-native-data = { workspace = true, features = ["full"] }
gpui-native-editor = { workspace = true, features = ["full"] }
gpui-native-layout = { workspace = true }
gpui-native-icons = { workspace = true }
gpui-native-theme = { workspace = true }
+179
View File
@@ -40,6 +40,8 @@ use gpui_native_data::list::{List, ListItem};
use gpui_native_data::sidebar::{Sidebar, SidebarEntry};
use gpui_native_data::tabs::{Tab, Tabs, TabsVariant};
use gpui_native_data::tree::{Tree, TreeItem};
use gpui_native_editor::code_view::CodeViewState;
use gpui_native_editor::highlight::Language;
use gpui_native_icons::{IconAssets, IconRole, icon};
use gpui_native_layout::dock::{Dock, DockSidebar, DockState, PanelDescriptor, SplitDirection};
use gpui_native_layout::resize_handle::ResizeHandle;
@@ -82,6 +84,7 @@ const CATALOG: &[(&str, &[(&str, &str)])] = &[
("tabs", "Tabs"),
],
),
("Editor", &[("code-view", "Code view")]),
(
"Feedback",
&[
@@ -623,6 +626,12 @@ impl Gallery {
}),
cx,
);
dock.register_panel(
PanelDescriptor::new("code-view", "Code view", |_, cx| {
cx.new(CodeViewDemo::new).into()
}),
cx,
);
dock.register_panel(
PanelDescriptor::new("sidebar", "Sidebar", |_, cx| {
cx.new(|_| SidebarDemo { selected: None }).into()
@@ -2736,6 +2745,176 @@ impl Render for ScrollbarDemo {
}
}
/// The code view demo's Rust sample.
const RUST_SAMPLE: &str = r#"// The palette is the theme's semantic ramps.
struct Application {
name: String,
ports: Vec<u16>,
}
impl Application {
/// Binds every port, one by one.
fn bind_all(&self) -> Result<(), String> {
for port in &self.ports {
if port > &1024 {
continue;
}
return Err(format!("port {} is privileged", port));
}
Ok(())
}
}
fn main() {
let app = Application {
name: "signed".to_string(),
ports: vec![8080, 443, 9090],
};
println!("{} binds on {:?}", app.name, app.ports);
app.bind_all().unwrap_or_else(|err| eprintln!("{}", err));
}
"#;
/// The code view demo's TypeScript sample.
const TYPESCRIPT_SAMPLE: &str = r#"// A relay flips from stale to fresh as commits land.
interface Relay {
name: string;
commits: number;
fresh: boolean;
}
const STALE_AFTER_MS = 60000;
export function refresh(relays: Relay[]): Relay[] {
return relays.map((relay) => ({
...relay,
fresh: Date.now() - STALE_AFTER_MS > 0,
}));
}
console.log(refresh([]).length); // 0
"#;
/// The code view demo's sample choice.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
enum CodeSample {
#[default]
Rust,
TypeScript,
/// A generated file with thousands of lines, to test scroll performance.
Large,
}
/// The line count of the large-file sample.
const LARGE_SAMPLE_LINES: usize = 5000;
/// Generates a deterministic pseudo-Rust file of roughly `lines` lines, so
/// scrolling the code view can be exercised on realistic content.
fn large_sample(lines: usize) -> String {
let mut source = String::with_capacity(lines * 48);
let mut index = 0usize;
while source.lines().count() < lines {
let i = index;
source.push_str(&format!(
"// Section {i}: generated for the scroll performance test.\n"
));
source.push_str(&format!("struct Section{i} {{\n"));
source.push_str(" id: u64,\n");
source.push_str(" label: String,\n");
source.push_str(" scores: Vec<f64>,\n");
source.push_str("}\n\n");
source.push_str(&format!("impl Section{i} {{\n"));
source.push_str(" /// A one-line summary of this section.\n");
source.push_str(" fn describe(&self) -> String {\n");
source.push_str(&format!(
" format!(\"section {i}: {{}} at {{:?}}\", self.label, self.scores)\n"
));
source.push_str(" }\n");
source.push_str("}\n\n");
index += 1;
}
source
}
struct CodeViewDemo {
code: Entity<CodeViewState>,
sample: CodeSample,
large_source: SharedString,
_subscription: gpui::Subscription,
}
impl CodeViewDemo {
fn new(cx: &mut Context<Self>) -> Self {
let code = cx.new(|_| CodeViewState::new("code-view-demo", RUST_SAMPLE, Language::Rust));
// The panel is a cached view in the dock: it re-renders only when
// the code state it renders is notified.
let subscription = cx.observe(&code, |_, _, cx| cx.notify());
Self {
code,
sample: CodeSample::Rust,
large_source: large_sample(LARGE_SAMPLE_LINES).into(),
_subscription: subscription,
}
}
}
impl Render for CodeViewDemo {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let colors = cx.theme().colors;
let radius = cx.theme().radius;
let view = cx.entity();
let line_count = self.code.read(cx).code().lines.len();
ScrollArea::new("code-view-demo").size_full().p_4().child(
v_flex()
.gap_3()
.child(demo_section(
"language",
colors,
Segmented::new("code-view-demo-sample", self.sample)
.options(vec![
(CodeSample::Rust, "Rust"),
(CodeSample::TypeScript, "TypeScript"),
(CodeSample::Large, "Large file"),
])
.on_change(move |next, _, cx| {
view.update(cx, |demo, cx| {
demo.sample = next;
let (source, language) = match next {
CodeSample::Rust => (RUST_SAMPLE.into(), Language::Rust),
CodeSample::TypeScript => {
(TYPESCRIPT_SAMPLE.into(), Language::TypeScript)
}
CodeSample::Large => {
(demo.large_source.clone(), Language::Rust)
}
};
demo.code
.update(cx, |code, cx| code.set_source(source, language, cx));
});
}),
))
.child(demo_section(
"source",
colors,
div()
.h(px(480.))
.rounded(radius.control)
.border_1()
.border_color(colors.border)
.overflow_hidden()
.child(self.code.clone()),
))
.child(
div()
.text_xs()
.text_color(colors.muted_foreground)
.child(format!("{line_count} lines — scroll to test performance")),
),
)
}
}
struct SidebarDemo {
selected: Option<usize>,
}