mod display;
mod edit;
mod error;
mod file;
mod open;
pub use display::BufferDisplayPath;
pub use edit::{
BufferEdit, BufferEditError, BufferEditKind, BufferEditReport, BufferEditShape,
DEFAULT_UNDO_RECORD_LIMIT, DEFAULT_UNDO_RETAINED_BYTES, EditTransaction, EditTransactionShape,
UndoRecord, UndoRecordShape, UndoStack,
};
pub use error::{BufferOpenError, BufferWriteError};
pub use file::{BufferFile, BufferWriteReport};
pub use open::{launch_file_argument, open_launch_buffer};
#[cfg(test)]
mod tests {
use super::{BufferDisplayPath, BufferFile, BufferWriteReport, launch_file_argument};
use crate::{fs_utils::FilesystemConfig, text_stream::TextByteStream};
use std::{
ffi::OsString,
path::PathBuf,
time::{SystemTime, UNIX_EPOCH},
};
fn missing_parent_file_path(name: &str) -> PathBuf {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system time should be after unix epoch")
.as_nanos();
std::env::temp_dir()
.join(format!("alma-missing-parent-{name}-{nanos}"))
.join("buffer.txt")
}
#[test]
fn launch_argument_uses_first_user_argument() {
assert_eq!(
launch_file_argument([OsString::from("alma"), OsString::from("file.txt")]),
Some(OsString::from("alma"))
);
assert_eq!(
launch_file_argument([OsString::from("file.txt")]),
Some(OsString::from("file.txt"))
);
assert_eq!(launch_file_argument([]), None);
}
#[test]
fn buffer_tracks_saved_revision() {
let mut stream = TextByteStream::new("hello");
let buffer = BufferFile::scratch(stream.revision());
assert!(!buffer.is_modified(&stream));
stream.replace_all("goodbye");
assert!(buffer.is_modified(&stream));
}
#[test]
fn failed_write_does_not_advance_saved_revision() {
let mut stream = TextByteStream::new("hello");
let mut buffer = BufferFile::backed_by(missing_parent_file_path("write-error"), 0);
stream.replace_all("dirty");
assert!(buffer.is_modified(&stream));
let filesystem_config =
FilesystemConfig::discover().expect("filesystem config should be discovered");
assert!(buffer.write(&stream, &filesystem_config).is_err());
assert!(buffer.is_modified(&stream));
}
#[test]
fn write_report_matches_vim_shape() {
let report = BufferWriteReport::new(
BufferDisplayPath::from_sanitized("note.txt"),
"one\ntwo\n",
8,
);
assert_eq!(report.status_text(), "\"note.txt\" 2L, 8B written");
}
#[test]
fn display_path_prefers_workspace_relative_text() {
let root = std::env::temp_dir();
let config = FilesystemConfig::from_workspace_root(&root)
.expect("temporary directory should be a workspace root");
let path = config.workspace_root.join("nested/note.txt");
let display_path = BufferDisplayPath::from_resolved_path(&path, &config);
assert_eq!(display_path.as_str(), "nested/note.txt");
assert!(
!display_path
.as_str()
.contains(config.workspace_root.to_string_lossy().as_ref())
);
}
#[test]
fn display_path_escapes_status_control_characters() {
let root = std::env::temp_dir();
let config = FilesystemConfig::from_workspace_root(&root)
.expect("temporary directory should be a workspace root");
let path = config
.workspace_root
.join("bad\nname\tquote\"bidi\u{202E}.txt");
let display_path = BufferDisplayPath::from_resolved_path(&path, &config);
assert_eq!(
display_path.as_str(),
"bad\\nname\\tquote\\\"bidi\\u{202E}.txt"
);
assert!(!display_path.as_str().contains('\n'));
assert!(!display_path.as_str().contains('\t'));
assert!(!display_path.as_str().contains('\u{202E}'));
}
}