use serde::{Deserialize, Serialize};
use tatara_lisp::DeriveTataraDomain;
#[derive(DeriveTataraDomain, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[tatara(keyword = "defmark")]
pub struct MarkSpec {
pub name: String,
#[serde(default)]
pub file: String,
#[serde(default)]
pub line: u32,
#[serde(default)]
pub column: u32,
#[serde(default)]
pub kind: String,
#[serde(default)]
pub description: String,
}
pub const KNOWN_KINDS: &[&str] = &["jump", "anchor", "glance"];
#[must_use]
pub fn is_known_kind(name: &str) -> bool {
name.is_empty() || KNOWN_KINDS.iter().any(|k| *k == name)
}
impl MarkSpec {
#[must_use]
pub fn is_vim_single_letter(&self) -> bool {
let Some(rest) = self.name.strip_prefix('\'') else {
return false;
};
let mut chars = rest.chars();
let (first, next) = (chars.next(), chars.next());
match (first, next) {
(Some(c), None) if c.is_ascii_alphanumeric() => true,
_ => false,
}
}
#[must_use]
pub fn effective_kind(&self) -> &str {
crate::strutil::default_if_empty(&self.kind, "jump")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn known_kind_accepts_empty_and_canonicals() {
assert!(is_known_kind(""));
for k in KNOWN_KINDS {
assert!(is_known_kind(k));
}
assert!(!is_known_kind("laser"));
}
#[test]
fn vim_single_letter_classifier() {
let cases: &[(&str, bool)] = &[
("'A", true),
("'a", true),
("'0", true),
("'9", true),
("'AA", false), ("''", false), ("A", false), ("bug-notes", false),
("", false),
];
for (name, want) in cases {
let m = MarkSpec {
name: (*name).to_string(),
..Default::default()
};
assert_eq!(m.is_vim_single_letter(), *want, "name {name}");
}
}
#[test]
fn effective_kind_defaults_jump() {
let m = MarkSpec {
name: "x".into(),
..Default::default()
};
assert_eq!(m.effective_kind(), "jump");
let m = MarkSpec {
name: "x".into(),
kind: "anchor".into(),
..Default::default()
};
assert_eq!(m.effective_kind(), "anchor");
}
}
impl Default for MarkSpec {
fn default() -> Self {
Self {
name: String::new(),
file: String::new(),
line: 0,
column: 0,
kind: String::new(),
description: String::new(),
}
}
}