Migrate all remaining collapsible_if patterns to Rust 2024 let-chain syntax across the entire codebase. This modernizes conditional logic by replacing nested if statements with single-level expressions using the && operator with let patterns. Changes: - storage.rs: 2 let-chain conversions (database dir creation, legacy archiving) - session.rs: 3 let-chain conversions (empty content check, ledger dir creation, consent flow) - ollama.rs: 8 let-chain conversions (socket parsing, cloud validation, model caching, capabilities) - main.rs: 2 let-chain conversions (API key validation, provider enablement) - owlen-tui: ~50 let-chain conversions across app/mod.rs, chat_app.rs, ui.rs, highlight.rs, and state modules Test fixes: - prompt_server.rs: Add missing .await on async RemoteMcpClient::new_with_config - presets.rs, prompt_server.rs: Add missing rpc_timeout_secs field to McpServerConfig - file_write.rs: Update error assertion to accept new "escapes workspace boundary" message Verification: - cargo build --all: ✅ succeeds - cargo clippy --all -- -D clippy::collapsible_if: ✅ zero warnings - cargo test --all: ✅ 109+ tests pass Net result: -46 lines of code, improved readability and maintainability. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
163 lines
5.0 KiB
Rust
163 lines
5.0 KiB
Rust
use once_cell::sync::Lazy;
|
|
use ratatui::style::{Color as TuiColor, Modifier, Style as TuiStyle};
|
|
use std::path::{Path, PathBuf};
|
|
use syntect::easy::HighlightLines;
|
|
use syntect::highlighting::{FontStyle, Style as SynStyle, Theme, ThemeSet};
|
|
use syntect::parsing::{SyntaxReference, SyntaxSet};
|
|
|
|
static SYNTAX_SET: Lazy<SyntaxSet> = Lazy::new(SyntaxSet::load_defaults_newlines);
|
|
static THEME_SET: Lazy<ThemeSet> = Lazy::new(ThemeSet::load_defaults);
|
|
static THEME: Lazy<Theme> = Lazy::new(|| {
|
|
THEME_SET
|
|
.themes
|
|
.get("base16-ocean.dark")
|
|
.cloned()
|
|
.or_else(|| THEME_SET.themes.values().next().cloned())
|
|
.unwrap_or_default()
|
|
});
|
|
|
|
fn select_syntax(path_hint: Option<&Path>) -> &'static SyntaxReference {
|
|
if let Some(path) = path_hint
|
|
&& let Ok(Some(syntax)) = SYNTAX_SET.find_syntax_for_file(path)
|
|
{
|
|
return syntax;
|
|
}
|
|
if let Some(path) = path_hint
|
|
&& let Some(ext) = path.extension().and_then(|ext| ext.to_str())
|
|
&& let Some(syntax) = SYNTAX_SET.find_syntax_by_extension(ext)
|
|
{
|
|
return syntax;
|
|
}
|
|
if let Some(path) = path_hint
|
|
&& let Some(name) = path.file_name().and_then(|name| name.to_str())
|
|
&& let Some(syntax) = SYNTAX_SET.find_syntax_by_token(name)
|
|
{
|
|
return syntax;
|
|
}
|
|
|
|
SYNTAX_SET.find_syntax_plain_text()
|
|
}
|
|
|
|
fn select_syntax_for_language(language: Option<&str>) -> &'static SyntaxReference {
|
|
let token = language
|
|
.map(|lang| lang.trim().to_ascii_lowercase())
|
|
.filter(|lang| !lang.is_empty());
|
|
|
|
if let Some(token) = token {
|
|
let mut attempts: Vec<&str> = vec![token.as_str()];
|
|
match token.as_str() {
|
|
"c++" => attempts.extend(["cpp", "c"]),
|
|
"c#" | "cs" => attempts.extend(["csharp", "cs"]),
|
|
"shell" => attempts.extend(["bash", "sh"]),
|
|
"typescript" | "ts" => attempts.extend(["typescript", "ts", "tsx"]),
|
|
"javascript" | "js" => attempts.extend(["javascript", "js", "jsx"]),
|
|
"py" => attempts.push("python"),
|
|
"rs" => attempts.push("rust"),
|
|
"yml" => attempts.push("yaml"),
|
|
other => {
|
|
if let Some(stripped) = other.strip_prefix('.') {
|
|
attempts.push(stripped);
|
|
}
|
|
}
|
|
}
|
|
|
|
for candidate in attempts {
|
|
if let Some(syntax) = SYNTAX_SET.find_syntax_by_token(candidate) {
|
|
return syntax;
|
|
}
|
|
if let Some(syntax) = SYNTAX_SET.find_syntax_by_extension(candidate) {
|
|
return syntax;
|
|
}
|
|
}
|
|
}
|
|
|
|
SYNTAX_SET.find_syntax_plain_text()
|
|
}
|
|
|
|
fn path_hint_from_components(absolute: Option<&Path>, display: Option<&str>) -> Option<PathBuf> {
|
|
if let Some(abs) = absolute {
|
|
return Some(abs.to_path_buf());
|
|
}
|
|
display.map(PathBuf::from)
|
|
}
|
|
|
|
fn style_from_syntect(style: SynStyle) -> TuiStyle {
|
|
let mut tui_style = TuiStyle::default().fg(TuiColor::Rgb(
|
|
style.foreground.r,
|
|
style.foreground.g,
|
|
style.foreground.b,
|
|
));
|
|
|
|
let mut modifiers = Modifier::empty();
|
|
if style.font_style.contains(FontStyle::BOLD) {
|
|
modifiers |= Modifier::BOLD;
|
|
}
|
|
if style.font_style.contains(FontStyle::ITALIC) {
|
|
modifiers |= Modifier::ITALIC;
|
|
}
|
|
if style.font_style.contains(FontStyle::UNDERLINE) {
|
|
modifiers |= Modifier::UNDERLINED;
|
|
}
|
|
|
|
if !modifiers.is_empty() {
|
|
tui_style = tui_style.add_modifier(modifiers);
|
|
}
|
|
|
|
tui_style
|
|
}
|
|
|
|
pub fn build_highlighter(
|
|
absolute: Option<&Path>,
|
|
display: Option<&str>,
|
|
) -> HighlightLines<'static> {
|
|
let hint_path = path_hint_from_components(absolute, display);
|
|
let syntax = select_syntax(hint_path.as_deref());
|
|
HighlightLines::new(syntax, &THEME)
|
|
}
|
|
|
|
pub fn highlight_line(
|
|
highlighter: &mut HighlightLines<'static>,
|
|
line: &str,
|
|
) -> Vec<(TuiStyle, String)> {
|
|
let mut segments = Vec::new();
|
|
match highlighter.highlight_line(line, &SYNTAX_SET) {
|
|
Ok(result) => {
|
|
for (style, piece) in result {
|
|
let tui_style = style_from_syntect(style);
|
|
segments.push((tui_style, piece.to_string()));
|
|
}
|
|
}
|
|
Err(_) => {
|
|
segments.push((TuiStyle::default(), line.to_string()));
|
|
}
|
|
}
|
|
|
|
if segments.is_empty() {
|
|
segments.push((TuiStyle::default(), String::new()));
|
|
}
|
|
|
|
segments
|
|
}
|
|
|
|
pub fn build_highlighter_for_language(language: Option<&str>) -> HighlightLines<'static> {
|
|
let syntax = select_syntax_for_language(language);
|
|
HighlightLines::new(syntax, &THEME)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn rust_highlighting_produces_colored_segment() {
|
|
let mut highlighter = build_highlighter_for_language(Some("rust"));
|
|
let segments = highlight_line(&mut highlighter, "fn main() {}");
|
|
assert!(
|
|
segments
|
|
.iter()
|
|
.any(|(style, text)| style.fg.is_some() && !text.trim().is_empty()),
|
|
"Expected at least one colored segment"
|
|
);
|
|
}
|
|
}
|