Ely Office: six offline-first desktop apps in Rust and GPUI
ElyDoc, ElyTable, ElyShow (LibreOfficeKit), ElyPDF (MuPDF), ElyTree (Git), ElyMark (Milkdown and CodeMirror in CEF). ElyMail and ElyAgent follow.
This commit is contained in:
817 files changed
+143101
No files matched your search
@@ -0,0 +1,115 @@
|
||||
//! An opened folder's Markdown files, and searching them (MARK-05, MARK-10).
|
||||
|
||||
use std::{
|
||||
io,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
/// An opened folder and its Markdown files.
|
||||
pub struct Folder {
|
||||
pub root: PathBuf,
|
||||
pub files: Vec<String>,
|
||||
}
|
||||
|
||||
/// Hits a search keeps; it looks no further.
|
||||
pub const MOST_HITS: usize = 500;
|
||||
|
||||
fn markdown(name: &str) -> bool {
|
||||
let name = name.to_lowercase();
|
||||
name.ends_with(".md") || name.ends_with(".markdown")
|
||||
}
|
||||
|
||||
/// Markdown files under `root`; no links, no dot names.
|
||||
pub fn markdown_under(root: &Path) -> io::Result<Vec<String>> {
|
||||
let mut found = Vec::new();
|
||||
let mut folders = vec![(root.to_path_buf(), String::new())];
|
||||
while let Some((dir, prefix)) = folders.pop() {
|
||||
for entry in std::fs::read_dir(&dir)? {
|
||||
let entry = entry?;
|
||||
let name = entry.file_name().to_string_lossy().into_owned();
|
||||
if name.starts_with('.') {
|
||||
continue;
|
||||
}
|
||||
let kind = entry.file_type()?;
|
||||
let path = format!("{prefix}{name}");
|
||||
if kind.is_dir() {
|
||||
folders.push((entry.path(), format!("{path}/")));
|
||||
} else if kind.is_file() && markdown(&name) {
|
||||
found.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
found.sort_by_key(|path| path.to_lowercase());
|
||||
Ok(found)
|
||||
}
|
||||
|
||||
/// One line that holds the words.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Hit {
|
||||
pub file: PathBuf,
|
||||
/// Counted from one.
|
||||
pub line: usize,
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
/// Lines of `text` holding `query`, any case.
|
||||
pub fn lines_with(file: &Path, text: &str, query: &str, hits: &mut Vec<Hit>) {
|
||||
let query = query.to_lowercase();
|
||||
for (at, line) in text.lines().enumerate() {
|
||||
if hits.len() == MOST_HITS {
|
||||
return;
|
||||
}
|
||||
if line.to_lowercase().contains(&query) {
|
||||
let shown: String = line.trim().chars().take(160).collect();
|
||||
hits.push(Hit {
|
||||
file: file.to_path_buf(),
|
||||
line: at + 1,
|
||||
text: shown,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn markdown_files_list_without_hidden_or_other_files() {
|
||||
let dir = tempfile::tempdir().expect("a temp dir");
|
||||
let root = dir.path();
|
||||
std::fs::create_dir_all(root.join("notes/deep")).expect("makes");
|
||||
std::fs::create_dir_all(root.join(".git")).expect("makes");
|
||||
for file in [
|
||||
"b.md",
|
||||
"A.markdown",
|
||||
"notes/deep/c.MD",
|
||||
"notes/skip.txt",
|
||||
".git/x.md",
|
||||
".hidden.md",
|
||||
] {
|
||||
std::fs::write(root.join(file), "x").expect("writes");
|
||||
}
|
||||
#[cfg(unix)]
|
||||
std::os::unix::fs::symlink(root.join("notes"), root.join("loop")).expect("links");
|
||||
let found = super::markdown_under(root).expect("lists");
|
||||
assert_eq!(found, ["A.markdown", "b.md", "notes/deep/c.MD"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lines_with_the_words_any_case() {
|
||||
let mut hits = Vec::new();
|
||||
super::lines_with(
|
||||
Path::new("a.md"),
|
||||
"# Title\nsome Words here\nnone\nWORDS",
|
||||
"words",
|
||||
&mut hits,
|
||||
);
|
||||
let lines: Vec<_> = hits
|
||||
.iter()
|
||||
.map(|hit| (hit.line, hit.text.as_str()))
|
||||
.collect();
|
||||
assert_eq!(lines, [(2, "some Words here"), (4, "WORDS")]);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user