feat: add editor component
This commit is contained in:
@@ -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 }
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user