use super::*;
#[derive(Debug, PartialEq, Eq)]
pub struct PanelSession {
pub tabs: Vec<PathBuf>,
pub active: usize,
}
impl PanelSession {
pub fn dir(&self) -> &std::path::Path {
&self.tabs[self.active]
}
}
pub fn load_session() -> Option<(PanelSession, PanelSession)> {
let text = fs::read_to_string(config_path("session")?).ok()?;
parse_session(&text)
}
pub fn save_session(left: &PanelSession, right: &PanelSession) -> io::Result<()> {
let mut text = format!("left = {}\nright = {}\n", escape_path(left.dir()), escape_path(right.dir()));
for (side, panel) in [("left", left), ("right", right)] {
for tab in &panel.tabs {
text.push_str(&format!("{side}_tab = {}\n", escape_path(tab)));
}
text.push_str(&format!("{side}_tab_active = {}\n", panel.active));
}
write_config_file("session", &text)
}
pub fn load_bookmarks() -> Vec<PathBuf> {
config_path("bookmarks").and_then(|path| fs::read_to_string(path).ok()).map(|text| parse_bookmarks(&text)).unwrap_or_default()
}
pub fn save_bookmarks(bookmarks: &[PathBuf]) -> io::Result<()> {
let text: String = bookmarks.iter().map(|path| format!("bookmark = {}\n", escape_path(path))).collect();
write_config_file("bookmarks", &text)
}
pub(super) fn parse_bookmarks(text: &str) -> Vec<PathBuf> {
key_values(text).filter(|&(key, value)| key == "bookmark" && !value.is_empty()).map(|(_, value)| unescape_path(value)).collect()
}
pub(super) fn escape_path(path: &std::path::Path) -> String {
let escaped = |bytes: &[u8]| bytes.iter().map(|byte| format!("%{byte:02X}")).collect::<String>();
let mut out = String::new();
for chunk in path.as_os_str().as_encoded_bytes().utf8_chunks() {
for ch in chunk.valid().chars() {
if ch == '%' || ch.is_control() {
out.push_str(&escaped(ch.encode_utf8(&mut [0; 4]).as_bytes()));
} else {
out.push(ch);
}
}
out.push_str(&escaped(chunk.invalid()));
}
let start = out.len() - out.trim_start().len();
let end = start + out.trim().len();
format!("{}{}{}", escaped(&out.as_bytes()[..start]), &out[start..end], escaped(&out.as_bytes()[end..]))
}
pub(super) fn unescape_path(text: &str) -> PathBuf {
let bytes = text.as_bytes();
let mut out = Vec::with_capacity(bytes.len());
let mut index = 0;
while index < bytes.len() {
let hex = bytes.get(index + 1..index + 3).and_then(|pair| std::str::from_utf8(pair).ok());
match (bytes[index], hex.and_then(|pair| u8::from_str_radix(pair, 16).ok())) {
(b'%', Some(byte)) => {
out.push(byte);
index += 3;
}
(byte, _) => {
out.push(byte);
index += 1;
}
}
}
path_from_bytes(out)
}
#[cfg(unix)]
fn path_from_bytes(bytes: Vec<u8>) -> PathBuf {
use std::os::unix::ffi::OsStringExt;
PathBuf::from(std::ffi::OsString::from_vec(bytes))
}
#[cfg(not(unix))]
fn path_from_bytes(bytes: Vec<u8>) -> PathBuf {
PathBuf::from(String::from_utf8_lossy(&bytes).into_owned())
}
pub(super) fn parse_session(text: &str) -> Option<(PanelSession, PanelSession)> {
let (mut left, mut right) = (None, None);
let (mut left_tabs, mut right_tabs) = (Vec::new(), Vec::new());
let (mut left_active, mut right_active) = (0, 0);
for (key, value) in key_values(text) {
match key {
"left" if !value.is_empty() => left = Some(unescape_path(value)),
"right" if !value.is_empty() => right = Some(unescape_path(value)),
"left_tab" if !value.is_empty() => left_tabs.push(unescape_path(value)),
"right_tab" if !value.is_empty() => right_tabs.push(unescape_path(value)),
"left_tab_active" => left_active = value.parse().unwrap_or(0),
"right_tab_active" => right_active = value.parse().unwrap_or(0),
_ => {}
}
}
let panel = |dir: PathBuf, tabs: Vec<PathBuf>, active: usize| {
if tabs.is_empty() {
PanelSession { tabs: vec![dir], active: 0 }
} else {
let active = active.min(tabs.len() - 1);
PanelSession { tabs, active }
}
};
Some((panel(left?, left_tabs, left_active), panel(right?, right_tabs, right_active)))
}