minor README tweaks + rust progress

This commit is contained in:
kuberwastaken 2026-04-01 03:31:03 +05:30
parent c99507ca1e
commit 45f7ac9071
9 changed files with 628 additions and 25 deletions

View file

@ -57,6 +57,9 @@ pub fn parse_keystroke(s: &str) -> Option<ParsedKeystroke> {
for part in s.split('+') {
let part = part.trim();
if part.is_empty() {
continue;
}
match part {
"ctrl" | "control" => ctrl = true,
"alt" | "opt" | "option" => alt = true,
@ -164,6 +167,18 @@ pub struct UserKeybindings {
pub bindings: Vec<UserBinding>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct JsonKeybindingConfig {
#[serde(default)]
bindings: Vec<JsonKeybindingBlock>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct JsonKeybindingBlock {
context: String,
bindings: HashMap<String, Option<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserBinding {
pub chord: String, // e.g. "ctrl+k ctrl+d"
@ -172,10 +187,16 @@ pub struct UserBinding {
}
impl UserKeybindings {
pub fn from_json_str(content: &str) -> Self {
serde_json::from_str(content)
.or_else(|_| Self::from_block_config(content))
.unwrap_or_default()
}
pub fn load(config_dir: &Path) -> Self {
let path = config_dir.join("keybindings.json");
if let Ok(content) = std::fs::read_to_string(&path) {
serde_json::from_str(&content).unwrap_or_default()
Self::from_json_str(&content)
} else {
Self::default()
}
@ -187,6 +208,23 @@ impl UserKeybindings {
std::fs::write(path, json)?;
Ok(())
}
fn from_block_config(content: &str) -> Result<Self, serde_json::Error> {
let config: JsonKeybindingConfig = serde_json::from_str(content)?;
let bindings = config
.bindings
.into_iter()
.flat_map(|block| {
let context = block.context;
block.bindings.into_iter().map(move |(chord, action)| UserBinding {
chord,
action,
context: Some(context.clone()),
})
})
.collect();
Ok(Self { bindings })
}
}
/// Resolved keybindings (defaults merged with user overrides)
@ -420,4 +458,28 @@ mod tests {
let user = UserKeybindings::default();
assert!(user.bindings.is_empty());
}
#[test]
fn test_user_keybindings_supports_ts_block_format() {
let user = UserKeybindings::from_json_str(
r#"{
"bindings": [
{
"context": "Chat",
"bindings": {
"ctrl+g": "chat:externalEditor",
"space": null
}
}
]
}"#,
);
assert_eq!(user.bindings.len(), 2);
assert_eq!(user.bindings[0].context.as_deref(), Some("Chat"));
assert_eq!(user.bindings[0].chord, "ctrl+g");
assert_eq!(user.bindings[0].action.as_deref(), Some("chat:externalEditor"));
assert_eq!(user.bindings[1].chord, "space");
assert_eq!(user.bindings[1].action, None);
}
}

View file

@ -411,6 +411,8 @@ pub mod config {
pub max_tokens: Option<u32>,
pub permission_mode: PermissionMode,
pub theme: Theme,
#[serde(default)]
pub output_style: Option<String>,
pub auto_compact: bool,
pub compact_threshold: f32,
pub verbose: bool,
@ -424,6 +426,8 @@ pub mod config {
pub append_system_prompt: Option<String>,
pub disable_claude_mds: bool,
pub project_dir: Option<PathBuf>,
#[serde(default)]
pub workspace_paths: Vec<PathBuf>,
/// Event hooks: map of event → list of hook commands.
#[serde(default)]
pub hooks: HashMap<HookEvent, Vec<HookEntry>>,
@ -518,6 +522,14 @@ pub mod config {
}
}
/// Resolve the effective output style for system-prompt assembly.
pub fn effective_output_style(&self) -> crate::system_prompt::OutputStyle {
self.output_style
.as_deref()
.map(crate::system_prompt::OutputStyle::from_str)
.unwrap_or_default()
}
/// Resolve the API key from the config, then from `ANTHROPIC_API_KEY`.
pub fn resolve_api_key(&self) -> Option<String> {
self.api_key
@ -633,6 +645,28 @@ pub mod config {
tokio::fs::write(&path, content).await?;
Ok(())
}
/// Synchronous variant used by pre-session commands.
pub fn load_sync() -> anyhow::Result<Self> {
let path = Self::global_settings_path();
if path.exists() {
let content = std::fs::read_to_string(&path)?;
Ok(serde_json::from_str(&content).unwrap_or_default())
} else {
Ok(Self::default())
}
}
/// Synchronous variant used by pre-session commands.
pub fn save_sync(&self) -> anyhow::Result<()> {
let path = Self::global_settings_path();
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let content = serde_json::to_string_pretty(self)?;
std::fs::write(&path, content)?;
Ok(())
}
}
}

View file

@ -218,7 +218,7 @@ pub fn format_memory_manifest(memories: &[MemoryFileMeta]) -> String {
match &m.description {
Some(desc) => format!("- {}{} ({}): {}", tag, m.filename, ts, desc),
None => format!("- {}{} ({})", tag, m.filename, ts),
None => format!("- {}{}", tag, m.filename),
}
})
.collect::<Vec<_>>()
@ -361,8 +361,7 @@ pub fn auto_memory_path(project_root: &Path) -> PathBuf {
/// Sanitize an arbitrary string into a directory-name-safe component.
/// Matches `sanitizePath` used inside `getAutoMemPath` in `paths.ts`.
pub fn sanitize_path_component(s: &str) -> String {
let sanitized: String = s
.chars()
s.chars()
.map(|c| {
if c.is_alphanumeric() || c == '-' || c == '_' || c == '.' {
c
@ -370,8 +369,7 @@ pub fn sanitize_path_component(s: &str) -> String {
'_'
}
})
.collect();
sanitized.trim_matches('_').to_string()
.collect()
}
/// Whether the auto-memory system is enabled for this session.