Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add flag to jump to next empty workspace #69

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ Options:
-p, --previous Go to the previous workspace instead of the next
-m, --move Move the active window to the dispatched workspace
-n, --no-empty Don't create empty workspaces in the given direction
-e, --empty Go to the next empty workspace
-k, --keep-special Don't auto-close special workspaces when switching workspaces
-c, --cycle Cycle between workspaces instead of creating new ones
-v, --verbose Print debugging information
Expand Down
12 changes: 10 additions & 2 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ struct Cli {
#[arg(short = 'N', long, default_value_t = false, hide = true)]
no_empty_after: bool,

/// Go to the next empty workspace
///
/// This flag will cause hyprnome to advance to the next empty workspace,
/// as if on the last occupied workspace.
#[arg(short, long, default_value_t = false)]
empty: bool,

/// Don't auto-close special workspaces when switching workspaces
///
/// With --move:
Expand Down Expand Up @@ -127,17 +134,18 @@ pub fn log(text: &str) {
}

/// Gets an ID to dispatch based on the current workspace state and cli options
pub fn get_options() -> [bool; 7] {
pub fn get_options() -> [bool; 8] {
let Cli {
_move,
keep_special,
previous,
no_empty,
no_empty_before,
no_empty_after,
empty,
cycle,
..
} = Cli::parse();

[_move, keep_special, previous, no_empty, no_empty_before, no_empty_after, cycle]
[_move, keep_special, previous, no_empty, no_empty_before, no_empty_after, empty, cycle]
}
27 changes: 17 additions & 10 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub struct WorkspaceState {
no_empty_before: bool,
no_empty_after: bool,
previous: bool,
empty: bool,
cycle: bool,
}

Expand All @@ -34,29 +35,30 @@ impl WorkspaceState {
no_empty_before: false,
no_empty_after: false,
previous: false,
empty: false,
cycle: false,
}
}

/// Gets the previous workspace on a monitor, or try to choose the next left-most empty workspace
///
/// 1) If we're the first workspace on a monitor:
/// 1) If we're the first workspace on a monitor or the previous empty workspace was requested:
/// 1.1) If we're at the lowest possible id 1 OR the user doesn't want empty workspaces OR the user wants to cycle:
/// 1.1.1) If the user wants to cycle, go to the last workspace. Otherwise return the current id
/// 1.2) Otherwise, return the first unoccupied workspace before the current id
/// 1.2.1) If all other workspaces before are occupied, return the current id instead
/// 2) Otherwise, since there are workspaces before on the same monitor, select the one before.
#[must_use]
pub fn get_previous_id(&self) -> i32 {
if self.monitor_ids[0] == self.current_id {
if self.monitor_ids[0] == self.current_id || self.empty {
if self.monitor_ids[0] == 1 || self.no_empty_before || self.cycle {
if self.cycle {
self.monitor_ids[self.monitor_ids.len() - 1]
} else {
self.current_id
self.monitor_ids[0]
}
} else {
let mut i = self.current_id - 1;
let mut i = self.monitor_ids[0] - 1;

while i > 0 {
if !self.occupied_ids.contains(&i) {
Expand All @@ -66,7 +68,7 @@ impl WorkspaceState {
i -= 1;
}

self.current_id
self.monitor_ids[0]
}
} else {
self.monitor_ids[self.monitor_ids.iter().position(|&x| x == self.current_id).unwrap() - 1]
Expand All @@ -75,23 +77,23 @@ impl WorkspaceState {

/// Gets the next workspace on a monitor, or try to choose the next right-most empty workspace
///
/// 1) If we're the last workspace on a monitor:
/// 1) If we're the last workspace on a monitor or the next empty workspace was requested:
/// 1.1) If we're at the MAX OR the user doesn't want empty workspaces OR the user wants to cycle:
/// 1.1.1) If the user wants to cycle, go to the first workspace. Otherwise return the current id
/// 1.2) Otherwise, return the first unoccupied workspace after the current id
/// 1.2.1) If all other workspaces after are occupied, return the current id instead
/// 2) Otherwise, since there are workspaces after on the same monitor, select the one after.
#[must_use]
pub fn get_next_id(&self) -> i32 {
if self.monitor_ids[self.monitor_ids.len() - 1] == self.current_id {
if self.monitor_ids[self.monitor_ids.len() - 1] == self.current_id || self.empty {
if self.monitor_ids[self.monitor_ids.len() - 1] == i32::MAX || self.no_empty_after || self.cycle {
if self.cycle {
self.monitor_ids[0]
} else {
self.current_id
self.monitor_ids[self.monitor_ids.len() - 1]
}
} else {
let mut i = self.current_id + 1;
let mut i = self.monitor_ids[self.monitor_ids.len() - 1] + 1;

while i < i32::MAX {
if !self.occupied_ids.contains(&i) {
Expand All @@ -104,7 +106,7 @@ impl WorkspaceState {
if !self.occupied_ids.contains(&i) {
i
} else {
self.current_id
self.monitor_ids[self.monitor_ids.len() - 1]
}
}
} else {
Expand All @@ -127,6 +129,11 @@ impl WorkspaceState {
self.previous = previous;
}

/// Sets `empty`
pub fn set_empty(&mut self, empty: bool) {
self.empty = empty;
}

/// Sets `cycle`
pub fn set_cycle(&mut self, cycle: bool) {
self.cycle = cycle;
Expand Down
3 changes: 2 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ mod hyprland;
///
/// Specific features are abstracted into lib to make things testable.
fn main() {
let [_move, keep_special, previous, no_empty, no_empty_before, no_empty_after, cycle] = cli::get_options();
let [_move, keep_special, previous, no_empty, no_empty_before, no_empty_after, empty, cycle] = cli::get_options();

if let Ok(mut state) = hyprland::get_state() {
state.set_no_empty_before(no_empty || no_empty_before);
state.set_no_empty_after(no_empty || no_empty_after);
state.set_previous(previous);
state.set_empty(empty);
state.set_cycle(cycle);

cli::log(&format!("{state}"));
Expand Down
21 changes: 21 additions & 0 deletions tests/get_next_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,24 @@ fn cycle() {

assert_eq!(state.get_next_id(), 500, "should return the first workspace on monitor if last workspace on monitor");
}

#[test]
fn empty() {
let current_id = 500;
let monitor_ids = [500, 502].to_vec();
let occupied_ids = [500, 502].to_vec();
let mut state = WorkspaceState::new(current_id, monitor_ids, occupied_ids);

state.set_empty(true);

assert_eq!(state.get_next_id(), 503, "should return next empty workspace if requested");

let current_id = 500;
let monitor_ids = [500, 502].to_vec();
let occupied_ids = [500, 502, 503, 504].to_vec();
let mut state = WorkspaceState::new(current_id, monitor_ids, occupied_ids);

state.set_empty(true);

assert_eq!(state.get_next_id(), 505, "should return next empty workspace if requested with occupied workspaces on other monitors");
}
21 changes: 21 additions & 0 deletions tests/get_previous_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,24 @@ fn cycle() {

assert_eq!(state.get_previous_id(), 504, "should return the last workspace on monitor if first workspace on monitor");
}

#[test]
fn empty() {
let current_id = 501;
let monitor_ids = [500, 501].to_vec();
let occupied_ids = [500, 501].to_vec();
let mut state = WorkspaceState::new(current_id, monitor_ids, occupied_ids);

state.set_empty(true);

assert_eq!(state.get_previous_id(), 499, "should return previous empty workspace if requested");

let current_id = 501;
let monitor_ids = [500, 501].to_vec();
let occupied_ids = [499, 500, 501].to_vec();
let mut state = WorkspaceState::new(current_id, monitor_ids, occupied_ids);

state.set_empty(true);

assert_eq!(state.get_previous_id(), 498, "should return previous empty workspace if requested with occupied workspaces on other monitors");
}