use std::cell::RefCell; use std::rc::Rc; use gpui::{ AppContext as _, Context, Entity, IntoElement, ParentElement as _, Render, Styled as _, TestAppContext, VisualTestContext, Window, div, }; use gpui_native_editor::code_view::CodeViewState; use gpui_native_editor::highlight::{HighlightedCode, Language, Role, highlight}; fn spans_with_role(code: &HighlightedCode, role: Role) -> Vec<&str> { code.lines .iter() .flat_map(|line| line.spans.iter()) .filter(|span| span.role == role) .map(|span| span.text.as_ref()) .collect() } #[test] fn a_rust_snippet_gets_its_roles_from_the_grammar() { let source = "// a comment\nfn main() {\n let greeting = \"hello\";\n}\n"; let code = highlight(source, Language::Rust); assert_eq!(code.lines.len(), 5, "a trailing newline ends an empty line"); assert!(spans_with_role(&code, Role::Comment).contains(&"// a comment")); assert!( spans_with_role(&code, Role::Keyword) .iter() .any(|text| text.contains("fn")) ); assert!( spans_with_role(&code, Role::Keyword) .iter() .any(|text| text.contains("let")) ); assert!(spans_with_role(&code, Role::Function).contains(&"main")); assert!( spans_with_role(&code, Role::String) .iter() .any(|text| text.contains("hello")) ); } #[test] fn a_capture_spanning_newlines_styles_both_lines() { let source = "let text = \"a\nb\";\n"; let code = highlight(source, Language::Rust); assert!( code.lines[0] .spans .iter() .any(|span| span.role == Role::String && span.text.contains("\"a")), "the string opens on the first line" ); assert!( code.lines[1] .spans .iter() .any(|span| span.role == Role::String && span.text.contains("b\"")), "and continues on the second" ); } #[test] fn set_source_rehighlights_with_the_new_language() { let mut cx = TestAppContext::single(); let state = cx.new(|_| CodeViewState::new("test-code", "fn main() {}\n", Language::Rust)); cx.update(|cx| { state.update(cx, |state, cx| { state.set_source("def main():\n pass\n", Language::Python, cx); }); }); let code = cx.update(|cx| state.read(cx).code().clone()); assert!( spans_with_role(&code, Role::Keyword) .iter() .any(|text| text.contains("def")), "python's def highlights as a keyword" ); assert!(spans_with_role(&code, Role::Function).contains(&"main")); } struct Host { code: Entity, } impl Render for Host { fn render(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { div().size_full().child(self.code.clone()) } } fn code_view_window( line_count: usize, ) -> (TestAppContext, VisualTestContext, Entity) { let mut cx = TestAppContext::single(); cx.update(gpui_native_theme::Theme::init); let source: String = (0..line_count) .map(|line| format!("fn line_{line}() {{}}\n")) .collect(); let state = CodeViewState::new("test-code", source, Language::Rust); let state_cell: Rc>>> = Rc::new(RefCell::new(None)); let cell = state_cell.clone(); let window = cx.add_window(move |_, cx| { let code = cx.new(|_| state); cell.borrow_mut().replace(code.clone()); Host { code } }); let state = state_cell.borrow().clone().expect("state captured"); let mut visual = VisualTestContext::from_window(window.into(), &cx); visual.update(|window, cx| { window.draw(cx).clear(cx); }); (cx, visual, state) } #[test] fn rows_render_virtualized_and_scroll() { let (_cx, mut visual, state) = code_view_window(80); assert!( visual.debug_bounds("test-code-row-0").is_some(), "the first row renders" ); assert!( visual.debug_bounds("test-code-row-79").is_none(), "rows past the viewport never render" ); state.update_in(&mut visual, |state, _window, _cx| state.scroll_to_line(79)); visual.update(|window, cx| { window.draw(cx).clear(cx); }); assert!( visual.debug_bounds("test-code-row-79").is_some(), "scrolling brings the last row in" ); assert!( visual.debug_bounds("test-code-row-0").is_none(), "the first row scrolled out" ); }