@@ -412,6 +412,37 @@ where
412
412
wss. into_iter ( )
413
413
}
414
414
415
+ fn focus_adjacent_workspace ( & mut self , tags : Vec < String > ) {
416
+ let cur_tag = self . current_tag ( ) ;
417
+ let mut it = tags. iter ( ) . skip_while ( |t| * t != cur_tag) ;
418
+ // We should never be without at least our focused tag as a Screen requires a Workspace to
419
+ // be present on it, but handling things this way means we avoid having any unwrapping or
420
+ // raw indexing into vecs.
421
+ if let Some ( new_tag) = it. nth ( 1 ) . or ( tags. first ( ) ) {
422
+ self . focus_tag ( new_tag)
423
+ }
424
+ }
425
+
426
+ /// Move focus to the [Workspace] after the currently focused workspace as defined by their
427
+ /// position in [StackSet::ordered_workspaces].
428
+ ///
429
+ /// As with the behaviour of [StackSet::focus_tag], if the next tag is on another screen then
430
+ /// focus will move to that screen rather than pulling the workspace to the active screen.
431
+ pub fn focus_next_workspace ( & mut self ) {
432
+ self . focus_adjacent_workspace ( self . ordered_tags ( ) )
433
+ }
434
+
435
+ /// Move focus to the [Workspace] before the currently focused workspace as defined by their
436
+ /// position in [StackSet::ordered_workspaces].
437
+ ///
438
+ /// As with the behaviour of [StackSet::focus_tag], if the next tag is on another screen then
439
+ /// focus will move to that screen rather than pulling the workspace to the active screen.
440
+ pub fn focus_previous_workspace ( & mut self ) {
441
+ let mut tags = self . ordered_tags ( ) ;
442
+ tags. reverse ( ) ;
443
+ self . focus_adjacent_workspace ( tags)
444
+ }
445
+
415
446
/// Find the tag of the [Workspace] currently displayed on [Screen] `index`.
416
447
///
417
448
/// Returns [None] if the index is out of bounds
@@ -1210,6 +1241,28 @@ pub mod tests {
1210
1241
assert_eq ! ( s. previous_tag, "PREVIOUS" ) ;
1211
1242
}
1212
1243
1244
+ #[ test_case( "2" , true , "3" ; "forward non-wrapping" ) ]
1245
+ #[ test_case( "5" , true , "1" ; "forward wrapping" ) ]
1246
+ #[ test_case( "3" , false , "2" ; "backward non-wrapping" ) ]
1247
+ #[ test_case( "1" , false , "5" ; "backward wrapping" ) ]
1248
+ #[ test]
1249
+ fn focus_next_prev_workspace_identifies_the_correct_tag (
1250
+ initial_tag : & str ,
1251
+ next : bool ,
1252
+ expected_tag : & str ,
1253
+ ) {
1254
+ let mut s = test_stack_set ( 5 , 3 ) ;
1255
+ s. focus_tag ( initial_tag) ;
1256
+
1257
+ if next {
1258
+ s. focus_next_workspace ( ) ;
1259
+ } else {
1260
+ s. focus_previous_workspace ( ) ;
1261
+ }
1262
+
1263
+ assert_eq ! ( s. current_tag( ) , expected_tag) ;
1264
+ }
1265
+
1213
1266
#[ test]
1214
1267
fn floating_layer_clients_hold_focus ( ) {
1215
1268
let mut s = test_stack_set ( 5 , 3 ) ;
@@ -1461,4 +1514,28 @@ mod quickcheck_tests {
1461
1514
1462
1515
s. current_client ( ) == Some ( & c)
1463
1516
}
1517
+
1518
+ #[ quickcheck]
1519
+ fn focus_next_workspace_always_changes_workspace ( mut s : StackSet < Xid > ) -> bool {
1520
+ if s. ordered_tags ( ) . len ( ) == 1 {
1521
+ return true ; // need at least two tags to cycle
1522
+ } ;
1523
+
1524
+ let current_tag = s. current_tag ( ) . to_string ( ) ;
1525
+ s. focus_next_workspace ( ) ;
1526
+
1527
+ s. current_tag ( ) != current_tag
1528
+ }
1529
+
1530
+ #[ quickcheck]
1531
+ fn focus_previous_workspace_always_changes_workspace ( mut s : StackSet < Xid > ) -> bool {
1532
+ if s. ordered_tags ( ) . len ( ) == 1 {
1533
+ return true ; // need at least two tags to cycle
1534
+ } ;
1535
+
1536
+ let current_tag = s. current_tag ( ) . to_string ( ) ;
1537
+ s. focus_previous_workspace ( ) ;
1538
+
1539
+ s. current_tag ( ) != current_tag
1540
+ }
1464
1541
}
0 commit comments