file can now be used as standalone mode
This commit is contained in:
parent
f558131233
commit
d0b526fb9d
7 changed files with 189 additions and 104 deletions
48
Cargo.lock
generated
48
Cargo.lock
generated
|
|
@ -436,6 +436,12 @@ dependencies = [
|
|||
"rustc_version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
|
|
@ -1026,9 +1032,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "f79496a5651c8d57cd033c5add8ca7ee4e3d5f7587a4777484640d9cb60392d9"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"nom",
|
||||
"nom 1.2.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.7"
|
||||
|
|
@ -1055,6 +1067,16 @@ version = "1.2.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5b8c256fd9471521bcb84c3cdba98921497f1a331cbc15b8030fc63b82050ce"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
|
|
@ -1121,6 +1143,16 @@ version = "1.0.15"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
|
||||
dependencies = [
|
||||
"fixedbitset",
|
||||
"indexmap 2.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.11.3"
|
||||
|
|
@ -1555,6 +1587,19 @@ dependencies = [
|
|||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree_magic_mini"
|
||||
version = "3.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aac5e8971f245c3389a5a76e648bfc80803ae066a1243a75db0064d7c1129d63"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"memchr",
|
||||
"nom 7.1.3",
|
||||
"once_cell",
|
||||
"petgraph",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
|
|
@ -1755,6 +1800,7 @@ dependencies = [
|
|||
"strsim 0.11.1",
|
||||
"thiserror",
|
||||
"toml",
|
||||
"tree_magic_mini",
|
||||
"which",
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -43,3 +43,4 @@ strsim = "0.11.1"
|
|||
dirs = "6.0.0"
|
||||
which = "7.0.3"
|
||||
meval = "0.2.0"
|
||||
tree_magic_mini = "3.1.6"
|
||||
|
|
|
|||
14
README.md
14
README.md
|
|
@ -1,8 +1,12 @@
|
|||
# Worf
|
||||
# Worf - Wayland Optimized Run Facilitator
|
||||
|
||||
Worf is yet another style launcher, heavily inspired by wofi, rofi and walker.
|
||||
Worf is written in Rust on top of GTK4.
|
||||
|
||||
It aims to be a drop in replacement for wofi in most part, so it is (almost) compatible with its
|
||||
configuration and css files. See below for differences
|
||||
|
||||
|
||||
Worf is yet another dmenu style launcher, heavily inspired by wofi but written in Rust on top of GTK4.
|
||||
It supports a lot of things the same way wofi does, so migrating to worf is easy, but things I did not
|
||||
deemed necessary where dropped from worf. See breaking changes section for details.
|
||||
|
||||
## Setup
|
||||
|
||||
|
|
@ -37,7 +41,7 @@ layerrule = blur, worf
|
|||
|
||||
## Dropped arguments
|
||||
* `mode`, use show
|
||||
* `D`, arguments are the same as config in worf, no need to have have this flag.
|
||||
* `D`, arguments are the same as config in worf, no need to have this flag.
|
||||
|
||||
### Dropped configuration options
|
||||
* stylesheet -> use style instead
|
||||
|
|
|
|||
|
|
@ -64,6 +64,9 @@ pub enum Mode {
|
|||
|
||||
/// tries to determine automatically what to do
|
||||
Auto,
|
||||
|
||||
/// use worf as file browser
|
||||
File,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
|
@ -80,6 +83,7 @@ impl FromStr for Mode {
|
|||
"run" => Ok(Mode::Run),
|
||||
"drun" => Ok(Mode::Drun),
|
||||
"dmenu" => Ok(Mode::Dmenu),
|
||||
"file" => Ok(Mode::File),
|
||||
"auto" => Ok(Mode::Auto),
|
||||
_ => Err(ArgsError::InvalidParameter(
|
||||
format!("{s} is not a valid argument show this, see help for details").to_owned(),
|
||||
|
|
@ -128,21 +132,16 @@ pub struct Config {
|
|||
#[clap(long = "height")]
|
||||
pub height: Option<String>,
|
||||
|
||||
/// Defines which prompt is used. Default is selected 'show'
|
||||
#[clap(short = 'p', long = "prompt")]
|
||||
pub prompt: Option<String>,
|
||||
|
||||
#[clap(short = 'x', long = "xoffset")]
|
||||
pub xoffset: Option<i32>,
|
||||
|
||||
#[clap(long = "x")]
|
||||
pub x: Option<i32>,
|
||||
|
||||
#[clap(short = 'y', long = "yoffset")]
|
||||
pub yoffset: Option<i32>,
|
||||
|
||||
#[clap(long = "y")]
|
||||
pub y: Option<i32>,
|
||||
|
||||
/// If true a normal window instead of a layer shell will be used
|
||||
#[serde(default = "default_normal_window")]
|
||||
#[clap(short = 'n', long = "normal-window")]
|
||||
|
|
@ -315,9 +314,7 @@ impl Default for Config {
|
|||
height: default_height(),
|
||||
prompt: None,
|
||||
xoffset: None,
|
||||
x: None,
|
||||
yoffset: None,
|
||||
y: None,
|
||||
normal_window: default_normal_window(),
|
||||
allow_images: None,
|
||||
allow_markup: None,
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ fn build_ui<T, P>(
|
|||
}
|
||||
|
||||
/// todo make this configurable
|
||||
window.set_anchor(Edge::Top, true);
|
||||
//window.set_anchor(Edge::Top, true);
|
||||
|
||||
let outer_box = gtk4::Box::new(config.orientation.unwrap().into(), 0);
|
||||
outer_box.set_widget_name("outer-box");
|
||||
|
|
@ -944,7 +944,7 @@ fn percent_or_absolute(value: Option<&String>, base_value: i32) -> Option<i32> {
|
|||
|
||||
// highly unlikely that we are dealing with > i64 items
|
||||
#[allow(clippy::cast_possible_wrap)]
|
||||
pub fn initialize_sort_scores<T: std::clone::Clone>(items: &mut [MenuItem<T>]) {
|
||||
pub fn sort_menu_items_alphabetically_honor_initial_score<T: std::clone::Clone>(items: &mut [MenuItem<T>]) {
|
||||
let mut regular_score = items.len() as i64;
|
||||
items.sort_by(|l, r| l.label.cmp(&r.label));
|
||||
|
||||
|
|
|
|||
208
src/lib/mode.rs
208
src/lib/mode.rs
|
|
@ -124,7 +124,7 @@ impl<T: Clone> DRunProvider<T> {
|
|||
entries.push(entry);
|
||||
}
|
||||
|
||||
gui::initialize_sort_scores(&mut entries);
|
||||
gui::sort_menu_items_alphabetically_honor_initial_score(&mut entries);
|
||||
|
||||
DRunProvider {
|
||||
items: entries,
|
||||
|
|
@ -134,7 +134,7 @@ impl<T: Clone> DRunProvider<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: std::clone::Clone> ItemProvider<T> for DRunProvider<T> {
|
||||
impl<T: Clone> ItemProvider<T> for DRunProvider<T> {
|
||||
fn get_elements(&mut self, _: Option<&str>) -> Vec<MenuItem<T>> {
|
||||
self.items.clone()
|
||||
}
|
||||
|
|
@ -144,51 +144,80 @@ impl<T: std::clone::Clone> ItemProvider<T> for DRunProvider<T> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
enum AutoRunType {
|
||||
Math,
|
||||
DRun,
|
||||
File,
|
||||
Ssh,
|
||||
WebSearch,
|
||||
Emoji,
|
||||
Run,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AutoItemProvider {
|
||||
drun_provider: DRunProvider<AutoRunType>,
|
||||
last_result: Option<Vec<MenuItem<AutoRunType>>>,
|
||||
struct FileItemProvider<T: std::clone::Clone> {
|
||||
last_result: Option<Vec<MenuItem<T>>>,
|
||||
menu_item_data: T,
|
||||
}
|
||||
|
||||
impl AutoItemProvider {
|
||||
fn new() -> Self {
|
||||
AutoItemProvider {
|
||||
drun_provider: DRunProvider::new(AutoRunType::DRun),
|
||||
|
||||
impl<T: Clone> FileItemProvider<T> {
|
||||
fn new(menu_item_data: T) -> Self {
|
||||
FileItemProvider {
|
||||
last_result: None,
|
||||
menu_item_data,
|
||||
}
|
||||
}
|
||||
|
||||
fn auto_run_handle_files(&mut self, trimmed_search: &str) -> Vec<MenuItem<AutoRunType>> {
|
||||
let folder_icon = "inode-directory";
|
||||
fn resolve_icon_for_name(&self, path: PathBuf) -> String {
|
||||
let result = tree_magic_mini::from_filepath(&path);
|
||||
if let Some(result) = result {
|
||||
if result.starts_with("image") {
|
||||
"image-x-generic".to_owned()
|
||||
} else if result.starts_with("inode") {
|
||||
return result.replace("/", "-");
|
||||
} else if result.starts_with("text") {
|
||||
if result.contains("plain") {
|
||||
"text-x-generic".to_owned()
|
||||
} else if result.contains("python") {
|
||||
"text-x-script".to_owned()
|
||||
} else if result.contains("html") {
|
||||
return "text-html".to_owned();
|
||||
} else {
|
||||
"text-x-generic".to_owned()
|
||||
}
|
||||
} else if result.starts_with("application") {
|
||||
if result.contains("octet") {
|
||||
"application-x-executable".to_owned()
|
||||
} else if result.contains("tar")
|
||||
|| result.contains("lz")
|
||||
|| result.contains("zip")
|
||||
|| result.contains("7z")
|
||||
|| result.contains("xz")
|
||||
{
|
||||
"package-x-generic".to_owned()
|
||||
} else {
|
||||
return "text-html".to_owned();
|
||||
}
|
||||
} else {
|
||||
log::debug!("unsupported mime type {result}");
|
||||
return "application-x-generic".to_owned();
|
||||
}
|
||||
} else {
|
||||
"image-not-found".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let path = config::expand_path(trimmed_search);
|
||||
let mut items: Vec<MenuItem<AutoRunType>> = Vec::new();
|
||||
impl<T: Clone> ItemProvider<T> for FileItemProvider<T> {
|
||||
fn get_elements(&mut self, search: Option<&str>) -> Vec<MenuItem<T>> {
|
||||
let default_path = if let Some(home) = dirs::home_dir() {
|
||||
home.display().to_string()
|
||||
} else {
|
||||
"/".to_string()
|
||||
};
|
||||
|
||||
let mut trimmed_search = search.unwrap_or(&default_path).to_owned();
|
||||
if !trimmed_search.starts_with("/") && !trimmed_search.starts_with("~") {
|
||||
trimmed_search = format!("{default_path}/{trimmed_search}");
|
||||
}
|
||||
|
||||
let path = expand_path(&trimmed_search);
|
||||
let mut items: Vec<MenuItem<T>> = Vec::new();
|
||||
|
||||
if !path.exists() {
|
||||
if let Some(last) = &self.last_result {
|
||||
if !last.is_empty()
|
||||
&& last.first().is_some_and(|l| {
|
||||
l.as_ref()
|
||||
.data
|
||||
.as_ref()
|
||||
.is_some_and(|t| t == &AutoRunType::File)
|
||||
})
|
||||
{
|
||||
return last.clone();
|
||||
}
|
||||
}
|
||||
|
||||
return vec![];
|
||||
}
|
||||
|
|
@ -210,17 +239,13 @@ impl AutoItemProvider {
|
|||
items.push({
|
||||
MenuItem {
|
||||
label: path_str.clone(),
|
||||
icon_path: if entry.path().is_dir() {
|
||||
Some(folder_icon.to_owned())
|
||||
} else {
|
||||
Some(resolve_icon_for_name(entry.path()))
|
||||
},
|
||||
icon_path: Some(self.resolve_icon_for_name(entry.path())),
|
||||
action: Some(format!("xdg-open {path_str}")),
|
||||
sub_elements: vec![],
|
||||
working_dir: None,
|
||||
initial_sort_score: 0,
|
||||
search_sort_score: 0.0,
|
||||
data: Some(AutoRunType::File),
|
||||
data: Some(self.menu_item_data.clone()),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -229,67 +254,54 @@ impl AutoItemProvider {
|
|||
items.push({
|
||||
MenuItem {
|
||||
label: trimmed_search.to_owned(),
|
||||
icon_path: Some(resolve_icon_for_name(PathBuf::from(trimmed_search))),
|
||||
icon_path: Some(self.resolve_icon_for_name(PathBuf::from(&trimmed_search))),
|
||||
action: Some(format!("xdg-open {trimmed_search}")),
|
||||
sub_elements: vec![],
|
||||
working_dir: None,
|
||||
initial_sort_score: 0,
|
||||
search_sort_score: 0.0,
|
||||
data: Some(AutoRunType::File),
|
||||
data: Some(self.menu_item_data.clone()),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
gui::sort_menu_items_alphabetically_honor_initial_score(&mut items);
|
||||
|
||||
self.last_result = Some(items.clone());
|
||||
items
|
||||
}
|
||||
|
||||
fn get_sub_elements(&mut self, _: &MenuItem<T>) -> Option<Vec<MenuItem<T>>> {
|
||||
self.last_result.clone()
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_icon_for_name(path: PathBuf) -> String {
|
||||
// todo use https://docs.rs/tree_magic_mini/latest/tree_magic_mini/ instead
|
||||
if let Ok(metadata) = fs::symlink_metadata(&path) {
|
||||
if metadata.file_type().is_symlink() {
|
||||
return "inode-symlink".to_owned();
|
||||
} else if metadata.is_dir() {
|
||||
return "inode-directory".to_owned();
|
||||
} else if metadata.permissions().mode() & 0o111 != 0 {
|
||||
return "application-x-executable".to_owned();
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
enum AutoRunType {
|
||||
Math,
|
||||
DRun,
|
||||
File,
|
||||
Ssh,
|
||||
WebSearch,
|
||||
Emoji,
|
||||
Run,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AutoItemProvider {
|
||||
drun_provider: DRunProvider<AutoRunType>,
|
||||
file_provider: FileItemProvider<AutoRunType>,
|
||||
last_result: Option<Vec<MenuItem<AutoRunType>>>,
|
||||
}
|
||||
|
||||
impl AutoItemProvider {
|
||||
fn new() -> Self {
|
||||
AutoItemProvider {
|
||||
drun_provider: DRunProvider::new(AutoRunType::DRun),
|
||||
file_provider: FileItemProvider::new(AutoRunType::File),
|
||||
last_result: None,
|
||||
}
|
||||
}
|
||||
|
||||
let file_name = path
|
||||
.file_name()
|
||||
.and_then(|f| f.to_str())
|
||||
.unwrap_or("")
|
||||
.to_lowercase();
|
||||
|
||||
let extension = path
|
||||
.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.unwrap_or("")
|
||||
.to_lowercase();
|
||||
|
||||
match extension.as_str() {
|
||||
"sh" | "py" | "rb" | "pl" | "bash" => "text-x-script".to_owned(),
|
||||
"c" | "cpp" | "rs" | "java" | "js" | "h" | "hpp" => "text-x-generic".to_owned(),
|
||||
"txt" | "md" | "log" => "text-x-generic".to_owned(),
|
||||
"html" | "htm" => "text-html".to_owned(),
|
||||
"jpg" | "jpeg" | "png" | "gif" | "svg" | "webp" => "image-x-generic".to_owned(),
|
||||
"mp3" | "wav" | "ogg" => "audio-x-generic".to_owned(),
|
||||
"mp4" | "mkv" | "avi" => "video-x-generic".to_owned(),
|
||||
"ttf" | "otf" | "woff" => "font-x-generic".to_owned(),
|
||||
"zip" | "tar" | "gz" | "xz" | "7z" | "lz4" => "package-x-generic".to_owned(),
|
||||
"deb" | "rpm" | "apk" => "x-package-repository".to_owned(),
|
||||
"odt" => "x-office-document".to_owned(),
|
||||
"ott" => "x-office-document-template".to_owned(),
|
||||
"ods" => "x-office-spreadsheet".to_owned(),
|
||||
"ots" => "x-office-spreadsheet-template".to_owned(),
|
||||
"odp" => "x-office-presentation".to_owned(),
|
||||
"otp" => "x-office-presentation-template".to_owned(),
|
||||
"odg" => "x-office-drawing".to_owned(),
|
||||
"vcf" => "x-office-addressbook".to_owned(),
|
||||
_ => "application-x-generic".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_math_functions_or_starts_with_number(input: &str) -> bool {
|
||||
|
|
@ -320,7 +332,7 @@ impl ItemProvider<AutoRunType> for AutoItemProvider {
|
|||
let item = MenuItem {
|
||||
label: result,
|
||||
icon_path: None,
|
||||
action: None,
|
||||
action: Some(trimmed_search.to_owned()),
|
||||
sub_elements: vec![],
|
||||
working_dir: None,
|
||||
initial_sort_score: 0,
|
||||
|
|
@ -333,7 +345,7 @@ impl ItemProvider<AutoRunType> for AutoItemProvider {
|
|||
|| trimmed_search.starts_with("/")
|
||||
|| trimmed_search.starts_with("~")
|
||||
{
|
||||
self.auto_run_handle_files(trimmed_search)
|
||||
self.file_provider.get_elements(search_opt)
|
||||
} else {
|
||||
return self.drun_provider.get_elements(search_opt);
|
||||
}
|
||||
|
|
@ -414,6 +426,28 @@ pub fn auto(config: &mut Config) -> anyhow::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn file(config: &mut Config) -> Result<(), String> {
|
||||
let provider = FileItemProvider::new("".to_owned());
|
||||
if config.prompt.is_none() {
|
||||
config.prompt = Some("file".to_owned());
|
||||
}
|
||||
|
||||
// todo ues a arc instead of cloning the config
|
||||
let selection_result = gui::show(config.clone(), provider);
|
||||
match selection_result {
|
||||
Ok(s) => {
|
||||
if let Some(action) = s.action {
|
||||
spawn_fork(&action, s.working_dir.as_ref()).map_err(|e| e.to_string())?;
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
log::error!("No item selected");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_drun_cache_and_run<T: Clone>(
|
||||
cache_path: Option<PathBuf>,
|
||||
cache: &mut HashMap<String, i64>,
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@ fn main() -> anyhow::Result<()> {
|
|||
Mode::Dmenu => {
|
||||
todo!("dmenu not implemented")
|
||||
}
|
||||
Mode::File => {
|
||||
mode::file(&mut config).map_err(|e| anyhow!(e))?;
|
||||
}
|
||||
Mode::Auto => {
|
||||
mode::auto(&mut config)?;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue