5e9b5d3cfb
Audio tab and note embeds with waveform, live spectrum (own FFT), seek, speed, loop and volume through ffplay/ffmpeg; code and text files open in an editable source editor with syntax highlighting, a Language chip and a banner to install, pick or ignore a grammar; ico decoding through the largest entry; info card as a clean grid; draw tools: arrows and lines clamped and clipped to the picture with a head, inline text tool, hotkeys; Activity tab with unread count and hotkey; calendar title shows a link icon and a pointing hand; explorer trash button works on press; right panel tab pills shrink so they never cover the collapse button; math: unknown command keeps a parser tooltip; test files grouped into tests/ folders (12-file rule); flaky search-order test waits for the rows. Built by Claude Sonnet and MiniMax M3.1 Flash; the whole tree verified with cargo xtask ci (fmt, clippy -D warnings, 4422 tests, layers, tree, i18n) on a copy of the working tree. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
105 lines
4.4 KiB
Rust
105 lines
4.4 KiB
Rust
//! The language controls of a source file's tab, driven headless: the hint banner for a file type no
|
|
//! grammar fits, Ignore, the chip and its picker, and that a choice is remembered and highlights.
|
|
|
|
use egui_kittest::kittest::Queryable;
|
|
use notescraft_themes::builtin;
|
|
use notescraft_ui_egui::code_view::language::{self, Action, Prefs};
|
|
use notescraft_ui_egui::theme::Tokens;
|
|
|
|
/// What the tab draws and what the user did.
|
|
struct Tab {
|
|
path: &'static str,
|
|
tokens: Tokens,
|
|
prefs: Prefs,
|
|
warmed: bool,
|
|
}
|
|
|
|
/// The header chip and, when the type needs a hint, the banner, as the tab draws them.
|
|
fn tab(path: &'static str) -> egui_kittest::Harness<'static, Tab> {
|
|
let tokens = Tokens::new(builtin("catppuccin-mocha").expect("the theme is there"));
|
|
let state = Tab { path, tokens, prefs: Prefs::default(), warmed: false };
|
|
egui_kittest::Harness::builder().with_size(egui::Vec2::new(900.0, 500.0)).with_max_steps(200).build_ui_state(
|
|
|ui, state: &mut Tab| {
|
|
if !state.warmed {
|
|
notescraft_ui_egui::fonts::install(ui.ctx());
|
|
state.warmed = true;
|
|
return;
|
|
}
|
|
if let Some(action) = language::take_action(ui.ctx()) {
|
|
language::apply(&mut state.prefs, action);
|
|
}
|
|
let resolved = language::resolve(state.path, "", &state.prefs.chosen);
|
|
language::set_current(ui.ctx(), state.path, resolved.as_deref());
|
|
let row = egui::Rect::from_min_size(ui.cursor().min, egui::vec2(220.0, 28.0));
|
|
ui.allocate_rect(row, egui::Sense::hover());
|
|
notescraft_ui_egui::code_view::chip(ui, &state.tokens, row, state.path);
|
|
if language::needs_hint(state.path, resolved.as_deref(), &state.prefs) {
|
|
notescraft_ui_egui::code_view::banner(ui, &state.tokens, state.path);
|
|
}
|
|
},
|
|
state,
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn the_banner_shows_for_an_unknown_type_and_not_for_a_known_one() {
|
|
let mut unknown = tab("data/file.xyz");
|
|
unknown.run();
|
|
unknown.run();
|
|
assert!(unknown.query_by_label("No syntax highlighting found for .xyz").is_some());
|
|
assert!(unknown.query_by_label("Install a grammar").is_some());
|
|
assert!(unknown.query_by_label("Use another language").is_some());
|
|
assert!(unknown.query_by_label("Language: Plain text").is_some(), "the chip is always there");
|
|
let mut known = tab("ar/publish/publish.css");
|
|
known.run();
|
|
known.run();
|
|
assert!(known.query_by_label_contains("No syntax highlighting").is_none());
|
|
assert!(known.query_by_label_contains("Language: CSS").is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn ignore_is_remembered_and_the_chip_stays() {
|
|
let mut harness = tab("data/file.xyz");
|
|
harness.run();
|
|
harness.run();
|
|
harness.get_by_label("Ignore").click();
|
|
harness.run();
|
|
harness.run();
|
|
assert!(harness.state().prefs.ignored.contains(&"xyz".to_owned()));
|
|
assert!(harness.query_by_label_contains("No syntax highlighting").is_none());
|
|
assert!(harness.query_by_label("Language: Plain text").is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn the_chip_picks_a_language_that_is_remembered_and_highlights() {
|
|
let mut harness = tab("data/file.xyz");
|
|
harness.run();
|
|
harness.run();
|
|
harness.get_by_label("Language: Plain text").click();
|
|
harness.run();
|
|
harness.run();
|
|
harness.event(egui::Event::Text("rust".to_owned()));
|
|
harness.run();
|
|
harness.run();
|
|
harness.get_by_label("Rust").click();
|
|
for _ in 0..4 {
|
|
harness.run();
|
|
}
|
|
assert_eq!(harness.state().prefs.chosen.get("xyz").map(String::as_str), Some("Rust"));
|
|
assert!(harness.query_by_label_contains("No syntax highlighting").is_none());
|
|
assert!(harness.query_by_label("Language: Rust").is_some());
|
|
let chosen = &harness.state().prefs.chosen;
|
|
assert!(language::resolve("other/b.xyz", "", chosen).is_some(), "the type is remembered for every file of it");
|
|
// The remembered grammar colours the text.
|
|
assert!(notescraft_syntax::Highlighter::new("Rust", "fn main() {}\n").is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn a_choice_changes_with_the_chip_at_any_time() {
|
|
let mut prefs = Prefs::default();
|
|
language::apply(&mut prefs, Action::Choose { key: "css".to_owned(), language: "Python".to_owned() });
|
|
assert_eq!(language::resolve("a.css", "", &prefs.chosen).as_deref(), Some("Python"));
|
|
language::apply(&mut prefs, Action::Choose { key: "css".to_owned(), language: "CSS".to_owned() });
|
|
assert_eq!(language::resolve("a.css", "", &prefs.chosen).as_deref(), Some("CSS"));
|
|
}
|