improve formatting w/ nightly toolchain

nightly rustformat allows some
advanced formatting for strings and
imports.
Add a pipeline steps that validates through this.
This commit is contained in:
Alexander Mohr 2025-07-26 17:38:22 +02:00
parent 997b87f607
commit 0fdee55fbe
26 changed files with 271 additions and 216 deletions

View file

@ -13,8 +13,47 @@ env:
CARGO_TERM_COLOR: always
jobs:
build:
format_and_clippy_nightly_toolchain:
runs-on : ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: false
- name: Install ubuntu dependencies
run: |
sudo apt update
sudo apt install -y librust-gdk4-sys-dev \
libglib2.0-dev libgtk-layer-shell-dev libgtk-layer-shell0 gir1.2-gtklayershell-0.1 \
libgtk-4-dev gobject-introspection libgirepository1.0-dev gtk-doc-tools python3 valac \
git cmake gcc meson ninja-build
- name: Install gt4k layer shell
run: |
git clone https://github.com/wmww/gtk4-layer-shell
cd gtk4-layer-shell
meson setup -Dexamples=true -Ddocs=true -Dtests=true build
ninja -C build
sudo ninja -C build install
sudo ldconfig
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@nightly
- name: Install format
run: rustup component add rustfmt
- name: Check format with nightly rules
run: cargo +nightly fmt -- --check --config error_on_unformatted=true,error_on_line_overflow=true,format_strings=true,group_imports=StdExternalCrate,imports_granularity=Crate
- name: install clippy
run: rustup component add clippy --toolchain nightly
- name: run clippy nightly
run: cargo +nightly clippy --all-targets -- -D warnings
build:
runs-on: ubuntu-latest
steps:
@ -38,11 +77,19 @@ jobs:
sudo ninja -C build install
sudo ldconfig
- name: Formatting
- name: Install taplo toml toolkit
run: cargo install --locked taplo-cli
- name: Toml Formatting
run: taplo fmt --check --diff
- name: Rust Formatting
run: cargo fmt --all -- --check
- name: Clippy warnings
run: cargo clippy -- -D warnings
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test -- --show-output

View file

@ -1,9 +1,9 @@
[workspace]
members = [
"worf",
"examples/worf-warden",
"examples/worf-hyprswitch",
"examples/worf-hyprspace",
"worf",
"examples/worf-warden",
"examples/worf-hyprswitch",
"examples/worf-hyprspace",
]
resolver = "3"

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2024"
[dependencies]
worf = {path = "../../worf"}
worf = { path = "../../worf" }
env_logger = "0.11.8"
hyprland = "0.4.0-beta.2"
clap = "4.5.40"

View file

@ -385,10 +385,10 @@ fn workspace_from_selection<'a>(
action: Option<Action>,
max_id: i32,
) -> Result<(WorkspaceIdentifierWithSpecial<'a>, i32, bool), String> {
if let Some(action) = action {
if let Some(ws) = action.workspace {
return Ok((WorkspaceIdentifierWithSpecial::Id(ws.id), ws.id, false));
}
if let Some(action) = action
&& let Some(ws) = action.workspace
{
return Ok((WorkspaceIdentifierWithSpecial::Id(ws.id), ws.id, false));
}
find_first_free_workspace_id(max_id)
.map(|id| (WorkspaceIdentifierWithSpecial::Id(id), id, true))

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2024"
[dependencies]
worf = {path = "../../worf"}
worf = { path = "../../worf" }
env_logger = "0.11.8"
hyprland = "0.4.0-beta.2"
sysinfo = "0.35.2"

View file

@ -161,10 +161,10 @@ fn main() -> Result<(), String> {
.map_err(|e| e.to_string())?;
let update_cache = thread::spawn(move || {
windows.iter().for_each(|item| {
if let Some(window) = &item.data {
if let Some(icon) = &window.icon {
cache.insert(window.process.clone(), icon.clone());
}
if let Some(window) = &item.data
&& let Some(icon) = &window.icon
{
cache.insert(window.process.clone(), icon.clone());
}
});
let updated_toml = toml::to_string(&cache);

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2024"
[dependencies]
worf = {path = "../../worf"}
worf = { path = "../../worf" }
env_logger = "0.11.8"
log = "0.4.27"
serde = { version = "1.0.219", features = ["derive"] }

View file

@ -1,5 +1,3 @@
use clap::Parser;
use serde::{Deserialize, Serialize};
use std::{
collections::HashMap,
env,
@ -8,6 +6,9 @@ use std::{
thread::sleep,
time::Duration,
};
use clap::Parser;
use serde::{Deserialize, Serialize};
use worf::{
config::{self, Config, CustomKeyHintLocation, Key},
desktop::{copy_to_clipboard, spawn_fork},

View file

@ -1,6 +1,6 @@
image_size=28
allow_images=true
term="kitty -e"
insensitive=true
key_detection_type="Code"
lines_size_factor=1.9
image_size = 28
allow_images = true
term = "kitty -e"
insensitive = true
key_detection_type = "Code"
lines_size_factor = 1.9

View file

@ -1,7 +1,7 @@
image_size=28
allow_images=true
term="kitty -e"
insensitive=true
key_detection_type="Code"
blurred_background=false
lines_size_factor=1.9
image_size = 28
allow_images = true
term = "kitty -e"
insensitive = true
key_detection_type = "Code"
blurred_background = false
lines_size_factor = 1.9

View file

@ -1,7 +1,7 @@
image_size=28
allow_images=true
term="kitty -e"
insensitive=true
key_detection_type="Code"
blurred_background=false
lines_size_factor=1.9
image_size = 28
allow_images = true
term = "kitty -e"
insensitive = true
key_detection_type = "Code"
blurred_background = false
lines_size_factor = 1.9

View file

@ -1,5 +1,5 @@
image_size=28
allow_images=true
term="kitty -e"
insensitive=true
key_detection_type="Code"
image_size = 28
allow_images = true
term = "kitty -e"
insensitive = true
key_detection_type = "Code"

View file

@ -1,7 +1,7 @@
image_size=28
allow_images=true
term="kitty -e"
insensitive=true
key_detection_type="Code"
blurred_background=false
lines_size_factor=1.9
image_size = 28
allow_images = true
term = "kitty -e"
insensitive = true
key_detection_type = "Code"
blurred_background = false
lines_size_factor = 1.9

View file

@ -1,7 +1,6 @@
image_size=28
allow_images=true
term="kitty -e"
insensitive=true
key_detection_type="Code"
image_size = 28
allow_images = true
term = "kitty -e"
insensitive = true
key_detection_type = "Code"
#blurred_background=false

View file

@ -1,10 +1,10 @@
image_size=0
columns=999
allow_images=false
orientation="Horizontal"
row_box_orientation="Horizontal"
content_halign="Start"
width="100%"
hide_scroll=true
location=["Top"]
lines=1
image_size = 0
columns = 999
allow_images = false
orientation = "Horizontal"
row_box_orientation = "Horizontal"
content_halign = "Start"
width = "100%"
hide_scroll = true
location = ["Top"]
lines = 1

View file

@ -1,9 +1,9 @@
image_size=64
columns=6
orientation="Vertical"
row_box_orientation="Vertical"
content_halign="Center"
height="70%"
width="60%"
valign="Start"
emoji_hide_label=true
image_size = 64
columns = 6
orientation = "Vertical"
row_box_orientation = "Vertical"
content_halign = "Center"
height = "70%"
width = "60%"
valign = "Start"
emoji_hide_label = true

View file

@ -1,12 +1,12 @@
image_size=64
columns=4
orientation="Vertical"
row_box_orientation="Vertical"
content_halign="Center"
height="105%"
width="100%"
valign="Start"
blurred_background=true
line_max_chars=32
key_detection_type="Code"
term="kitty -e"
image_size = 64
columns = 4
orientation = "Vertical"
row_box_orientation = "Vertical"
content_halign = "Center"
height = "105%"
width = "100%"
valign = "Start"
blurred_background = true
line_max_chars = 32
key_detection_type = "Code"
term = "kitty -e"

View file

@ -1,9 +1,9 @@
image_size=28
allow_images=false
term="kitty -e"
insensitive=true
key_detection_type="Code"
blurred_background=false
location=["Top"]
lines=3
matching="None"
image_size = 28
allow_images = false
term = "kitty -e"
insensitive = true
key_detection_type = "Code"
blurred_background = false
location = ["Top"]
lines = 3
matching = "None"

View file

@ -1,5 +1,5 @@
image_size=48
columns=1
height="60%"
width="70%"
valign="Start"
image_size = 48
columns = 1
height = "60%"
width = "70%"
valign = "Start"

View file

@ -50,6 +50,6 @@ rayon = "1.10.0"
nix = { version = "0.30.0", features = ["process"] }
emoji = "0.2.1"
wl-clipboard-rs = "0.9.2"
notify-rust="4.11.7"
notify-rust = "4.11.7"
thiserror = "2.0.12"
urlencoding = "2.1.3"

View file

@ -1,8 +1,7 @@
use std::{env, fs, path::PathBuf, str::FromStr};
use clap::{Parser, ValueEnum};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use serde_json::Value;
use crate::Error;
@ -462,7 +461,9 @@ pub struct Config {
/// set where the window is displayed.
/// can be used to anchor a window to an edge by
/// setting top,left for example
#[clap(short = 'l', long = "location", value_delimiter = ',', value_parser = clap::builder::ValueParser::new(Anchor::from_str)
#[clap(
short = 'l', long = "location",
value_delimiter = ',', value_parser = clap::builder::ValueParser::new(Anchor::from_str)
)]
location: Option<Vec<Anchor>>,
@ -1050,10 +1051,10 @@ pub fn expand_path(input: &str) -> PathBuf {
let mut path = input.to_string();
// Expand ~ to home directory
if path.starts_with('~') {
if let Some(home_dir) = dirs::home_dir() {
path = path.replacen('~', home_dir.to_str().unwrap_or(""), 1);
}
if path.starts_with('~')
&& let Some(home_dir) = dirs::home_dir()
{
path = path.replacen('~', home_dir.to_str().unwrap_or(""), 1);
}
// Expand $VAR style environment variables

View file

@ -1277,21 +1277,21 @@ where
drop(menu_rows);
if let Some(item) = item {
if let Err(e) = handle_selected_item(ui, meta, None, Some(item), None) {
log::error!("failed to handle selected item {e}");
}
if let Some(item) = item
&& let Err(e) = handle_selected_item(ui, meta, None, Some(item), None)
{
log::error!("failed to handle selected item {e}");
}
} else {
drop(menu_rows);
}
if meta.config.read().unwrap().dynamic_lines() {
if let Some(geometry) = get_monitor_geometry(ui.window.surface().as_ref()) {
let height =
calculate_dynamic_lines_window_height(&meta.config.read().unwrap(), ui, geometry);
ui.window.set_height_request(height);
}
if meta.config.read().unwrap().dynamic_lines()
&& let Some(geometry) = get_monitor_geometry(ui.window.surface().as_ref())
{
let height =
calculate_dynamic_lines_window_height(&meta.config.read().unwrap(), ui, geometry);
ui.window.set_height_request(height);
}
}
@ -1310,45 +1310,43 @@ fn handle_key_expand<T>(ui: &Rc<UiElements<T>>, meta: &Rc<MetaData<T>>) -> Propa
where
T: Clone + Send + 'static,
{
if let Some(fb) = ui.main_box.selected_children().first() {
if let Some(child) = fb.child() {
let expander = child.downcast::<Expander>().ok();
if let Some(expander) = expander {
expander.set_expanded(true);
} else {
let data = {
let lock = ui.menu_rows.read().unwrap();
let menu_item = lock.get(fb);
menu_item.map(|menu_item| {
(
meta.item_provider
.lock()
.unwrap()
.get_sub_elements(menu_item),
menu_item.clone(),
)
})
};
if let Some(fb) = ui.main_box.selected_children().first()
&& let Some(child) = fb.child()
{
let expander = child.downcast::<Expander>().ok();
if let Some(expander) = expander {
expander.set_expanded(true);
} else {
let data = {
let lock = ui.menu_rows.read().unwrap();
let menu_item = lock.get(fb);
menu_item.map(|menu_item| {
(
meta.item_provider
.lock()
.unwrap()
.get_sub_elements(menu_item),
menu_item.clone(),
)
})
};
if let Some((provider_data, menu_item)) = data {
if let Some(items) = provider_data.items {
build_ui_from_menu_items(ui, meta, items);
let query = match meta.expand_mode {
ExpandMode::Verbatim => menu_item.label.clone(),
ExpandMode::WithSpace => format!("{} ", menu_item.label.clone()),
};
if let Some((provider_data, menu_item)) = data {
if let Some(items) = provider_data.items {
build_ui_from_menu_items(ui, meta, items);
let query = match meta.expand_mode {
ExpandMode::Verbatim => menu_item.label.clone(),
ExpandMode::WithSpace => format!("{} ", menu_item.label.clone()),
};
set_search_text(ui, meta, &query);
if let Ok(new_pos) = i32::try_from(query.len() + 1) {
ui.search.set_position(new_pos);
}
update_view(ui, meta, &query);
} else if let Err(e) =
handle_selected_item(ui, meta, None, Some(menu_item), None)
{
log::error!("{e}");
set_search_text(ui, meta, &query);
if let Ok(new_pos) = i32::try_from(query.len() + 1) {
ui.search.set_position(new_pos);
}
update_view(ui, meta, &query);
} else if let Err(e) = handle_selected_item(ui, meta, None, Some(menu_item), None) {
log::error!("{e}");
}
}
}
@ -1360,12 +1358,11 @@ fn handle_key_copy<T>(ui: &Rc<UiElements<T>>, meta: &Rc<MetaData<T>>) -> Propaga
where
T: Clone + Send + 'static,
{
if let Some(item) = get_selected_item(ui) {
if let Some(action) = item.action {
if let Err(e) = desktop::copy_to_clipboard(action, None) {
log::error!("failed to copy to clipboard: {e}");
}
}
if let Some(item) = get_selected_item(ui)
&& let Some(action) = item.action
&& let Err(e) = desktop::copy_to_clipboard(action, None)
{
log::error!("failed to copy to clipboard: {e}");
}
if let Err(e) = meta.selected_sender.send(Err(Error::NoSelection)) {
log::error!("failed to send message {e}");
@ -1445,11 +1442,11 @@ fn window_show_resize<T: Clone + 'static>(config: &Config, ui: &Rc<UiElements<T>
return;
};
if !config.blurred_background_fullscreen() {
if let Some(background) = &ui.background {
background.set_height_request(geometry.height());
background.set_width_request(geometry.width());
}
if !config.blurred_background_fullscreen()
&& let Some(background) = &ui.background
{
background.set_height_request(geometry.height());
background.set_width_request(geometry.width());
}
// Calculate target width from config, return early if not set
@ -1541,7 +1538,8 @@ fn calculate_row_height<T: Clone + 'static>(
};
log::debug!(
"heights: scroll {scroll_height}, window {window_height}, keys {height_box}, height {height:?}, lines {lines:?}"
"heights: scroll {scroll_height}, window {window_height}, keys {height_box}, height \
{height:?}, lines {lines:?}"
);
height_box
@ -1574,10 +1572,10 @@ where
if let Some(s) = ui.main_box.selected_children().into_iter().next() {
let list_items = ui.menu_rows.read().unwrap();
let item = list_items.get(&s);
if let Some(selected_item) = item {
if selected_item.visible {
return Some(selected_item.clone());
}
if let Some(selected_item) = item
&& selected_item.visible
{
return Some(selected_item.clone());
}
}
@ -1728,16 +1726,15 @@ fn create_menu_row<T: Clone + 'static + Send>(
label.set_max_width_chars(max_width_chars);
}
if let Some(max_len) = meta.config.read().unwrap().line_max_chars() {
if let Some(text) = label_text.as_ref() {
if text.chars().count() > max_len {
let end = text
.char_indices()
.nth(max_len)
.map_or(text.len(), |(idx, _)| idx);
label.set_text(&format!("{}...", &text[..end]));
}
}
if let Some(max_len) = meta.config.read().unwrap().line_max_chars()
&& let Some(text) = label_text.as_ref()
&& text.chars().count() > max_len
{
let end = text
.char_indices()
.nth(max_len)
.map_or(text.len(), |(idx, _)| idx);
label.set_text(&format!("{}...", &text[..end]));
}
row_box.append(&label);
@ -1772,16 +1769,16 @@ fn create_menu_row<T: Clone + 'static + Send>(
};
click.connect_pressed(move |_gesture, n_press, _x, _y| {
if n_press == presses {
if let Err(e) = handle_selected_item(
if n_press == presses
&& let Err(e) = handle_selected_item(
&click_ui,
&click_meta,
None,
Some(element_clone.clone()),
None,
) {
log::error!("{e}");
}
)
{
log::error!("{e}");
}
});
row.add_controller(click);
@ -1963,10 +1960,10 @@ fn find_visible_child<T: Clone>(
for i in range {
let i_32 = i.try_into().unwrap_or(i32::MAX);
if let Some(child) = flow_box.child_at_index(i_32) {
if child.is_visible() {
return Some(child);
}
if let Some(child) = flow_box.child_at_index(i_32)
&& child.is_visible()
{
return Some(child);
}
}

View file

@ -60,10 +60,10 @@ impl AutoItemProvider {
.is_some_and(|t| t != &AutoRunType::Auto)
{
let mut data = self.drun.get_elements(None);
if let Some(items) = data.items.as_mut() {
if let Some(mut ssh) = self.ssh.get_elements(None).items {
items.append(&mut ssh);
}
if let Some(items) = data.items.as_mut()
&& let Some(mut ssh) = self.ssh.get_elements(None).items
{
items.append(&mut ssh);
}
self.last_mode = Some(AutoRunType::Auto);
@ -77,7 +77,18 @@ impl AutoItemProvider {
fn contains_math_functions_or_starts_with_number(input: &str) -> bool {
// Regex for function names (word boundaries to match whole words)
static MATH_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"\b(sqrt|abs|exp|ln|sin|cos|tan|asin|acos|atan|atan2|sinh|cosh|tanh|asinh|acosh|atanh|floor|ceil|round|signum|min|max|pi|e|0x|0b|\||&|<<|>>|\^)\b").unwrap()
Regex::new(
r"\b(
sqrt|abs|exp|ln|sin|cos|tan|
asin|acos|atan|atan2|
sinh|cosh|tanh|asinh|acosh|atanh|
floor|ceil|round|signum|min|max|
pi|e|
0x|0b|
\||&|<<|>>|\^
)\b",
)
.unwrap()
});
// Regex for strings that start with a number (including decimals)

View file

@ -7,7 +7,6 @@ use std::{
use freedesktop_file_parser::EntryType;
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use crate::{
Error,
@ -20,12 +19,6 @@ use crate::{
modes::load_cache,
};
#[derive(Debug, Deserialize, Serialize, Clone)]
struct DRunCache {
desktop_entry: String,
run_count: usize,
}
#[derive(Clone)]
pub(crate) struct DRunProvider<T: Clone> {
items: Option<Vec<MenuItem<T>>>,
@ -78,8 +71,7 @@ impl<T: Clone + Send + Sync> DRunProvider<T> {
let entries: Vec<MenuItem<T>> = find_desktop_files()
.into_par_iter()
.filter(|file| {
!file.entry.no_display.unwrap_or(false)
&& !file.entry.hidden.unwrap_or(false)
!file.entry.no_display.unwrap_or(false) && !file.entry.hidden.unwrap_or(false)
})
.filter_map(|file| {
let name = lookup_name_with_locale(
@ -89,7 +81,11 @@ impl<T: Clone + Send + Sync> DRunProvider<T> {
)?;
let (action, working_dir, in_terminal) = match &file.entry.entry_type {
EntryType::Application(app) => (app.exec.clone(), app.path.clone(), app.terminal.unwrap_or(false)),
EntryType::Application(app) => (
app.exec.clone(),
app.path.clone(),
app.terminal.unwrap_or(false),
),
_ => return None,
};
@ -104,7 +100,10 @@ impl<T: Clone + Send + Sync> DRunProvider<T> {
.unwrap_or(false);
if !cmd_exists {
log::warn!("Skipping desktop entry for {name:?} because action {action:?} does not exist");
log::warn!(
"Skipping desktop entry for {name:?} because action {action:?} does not \
exist"
);
return None;
}
@ -142,8 +141,8 @@ impl<T: Clone + Send + Sync> DRunProvider<T> {
.or(icon.clone())
.unwrap_or("application-x-executable".to_string());
let action = self.get_action(in_terminal, action.exec.clone(), &action_name);
let action =
self.get_action(in_terminal, action.exec.clone(), &action_name);
entry.sub_elements.push(MenuItem::new(
action_name,

View file

@ -137,12 +137,11 @@ impl<T: Clone> ItemProvider<T> for FileItemProvider<T> {
if let Some(mut path_str) =
entry.path().to_str().map(std::string::ToString::to_string)
{
if trimmed_search.starts_with('~') {
if let Some(home_dir) = dirs::home_dir() {
if let Some(home_str) = home_dir.to_str() {
path_str = path_str.replace(home_str, "~");
}
}
if trimmed_search.starts_with('~')
&& let Some(home_dir) = dirs::home_dir()
&& let Some(home_str) = home_dir.to_str()
{
path_str = path_str.replace(home_str, "~");
}
if entry.path().is_dir() {

View file

@ -162,7 +162,8 @@ fn tokenize(expr: &str) -> Result<VecDeque<Token>, String> {
i += 1;
}
'0'..='9' | '.' => {
// Only insert implicit multiplication if the last token is ')' and the last token in tokens is not already an operator (except ')')
// Only insert implicit multiplication if the last token is ')'
// and the last token in tokens is not already an operator (except ')')
if let Some(Token::Op(')')) = last_token {
if let Some(Token::Op(op)) = tokens.back() {
if *op == ')' {