mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-11-14 21:04:04 +01:00
feat(wm): promote
The first tile of a layout is its largest, and the place for the window being worked on. `komorebic promote` moves the focused window there, with the windows that were before it each moving one tile on; `promote-swap` swaps it with the window that has the first tile, and from the first tile swaps it back with the window it was last swapped with; `promote-window left|right|up|down` promotes the window on that side of the focused one; and `promote-focus` gives the focus to the first tile and moves nothing. The grid layout has no largest tile, so the four do nothing there. The vm-commands sweep gains the four.
This commit is contained in:
1 parent
95eb4d5039
commit
554d77aed9
17 files changed
+419
-6
No files matched your search
@@ -138,8 +138,9 @@ one in the layout, or to the next one in the workspace's order, and `move` and
|
||||
again. `change-layout`, `cycle-layout` and `flip-layout` tile the workspace in
|
||||
another layout, `layout-ratios` and `scrolling-layout-columns` tune it,
|
||||
`toggle-tiling` stops it moving windows and starts it again,
|
||||
`preselect-direction` reserves a tile for the next window, and `resize-edge`
|
||||
and `resize-axis` move the edges of the focused tile. The scrolling layout
|
||||
`preselect-direction` reserves a tile for the next window, `resize-edge` and
|
||||
`resize-axis` move the edges of the focused tile, and `promote` brings the
|
||||
focused window to the largest tile. The scrolling layout
|
||||
is a strip of columns that runs past the edges of the monitor and scrolls to
|
||||
the window that has the focus.
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
```text
|
||||
Promote the user focus to the largest window container
|
||||
|
||||
Usage: promote-focus
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
Print help
|
||||
|
||||
```
|
||||
@@ -0,0 +1,10 @@
|
||||
```text
|
||||
Promote the focused window to the largest tile by swapping container indices with the largest tile
|
||||
|
||||
Usage: promote-swap
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
Print help
|
||||
|
||||
```
|
||||
@@ -0,0 +1,14 @@
|
||||
```text
|
||||
Promote the window in the specified direction
|
||||
|
||||
Usage: promote-window <OPERATION_DIRECTION>
|
||||
|
||||
Arguments:
|
||||
<OPERATION_DIRECTION>
|
||||
[possible values: left, right, up, down]
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
Print help
|
||||
|
||||
```
|
||||
@@ -0,0 +1,10 @@
|
||||
```text
|
||||
Promote the focused window to the largest tile via container removal and re-insertion
|
||||
|
||||
Usage: promote
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
Print help
|
||||
|
||||
```
|
||||
@@ -285,6 +285,15 @@ focused container has changed, which scrolls the strip to it. KWin keeps part of
|
||||
resizes on an output, so the bridge asks again for the position of a window KWin pulled back
|
||||
within a second of the command, three times at most ([`bridge.js`]'s `settle`).
|
||||
|
||||
`Promote` moves the focused container to index 0, the largest tile of every layout but the grid,
|
||||
through `LockableSequence`, so the containers before it move one index on
|
||||
([`WindowManager::promote_container_to_front`]). `PromoteSwap` swaps it with the container at
|
||||
index 0 and records the index it came from in [`Workspace::promotion_swap_container_idx`], so
|
||||
that from index 0 it swaps back ([`WindowManager::promote_container_swap`]). `PromoteFocus`
|
||||
focuses the container at index 0 ([`WindowManager::promote_focus_to_front`]), and
|
||||
`PromoteWindow` focuses the container in a direction and promotes it. On the grid layout they
|
||||
change nothing.
|
||||
|
||||
`ResizeWindowEdge` and `ResizeWindowAxis` resize the focused container
|
||||
([`WindowManager::resize_window`]). A resize is an adjustment of the container's tile, kept in
|
||||
[`Workspace::resize_dimensions`], one per container, which the layout applies when it calculates
|
||||
@@ -352,8 +361,8 @@ module's unit.
|
||||
changes its containers.
|
||||
- [`window_manager`](../../komorebi/src/window_manager.rs): the [`WindowManager`] and the
|
||||
operations on it (tile the workspace in front, tile every monitor, take a minimized
|
||||
window out, move the focus, move the focused container, change the layout, resize the
|
||||
focused container).
|
||||
window out, move the focus, move the focused container, change the layout, resize or
|
||||
promote the focused container).
|
||||
- [`desktop`](../../komorebi/src/desktop.rs): loads the monitors and the open windows from
|
||||
the backend.
|
||||
- [`window_manager_event`](../../komorebi/src/window_manager_event.rs) and
|
||||
@@ -553,7 +562,8 @@ A window is shown, and ends up on a tile:
|
||||
commands against the fake;
|
||||
[`komorebi/tests/layout_options.rs`](../../komorebi/tests/layout_options.rs) and
|
||||
[`komorebi/tests/scrolling.rs`](../../komorebi/tests/scrolling.rs), the layout options and the
|
||||
scrolling layout; [`komorebi/tests/resize.rs`](../../komorebi/tests/resize.rs), resizing.
|
||||
scrolling layout; [`komorebi/tests/resize.rs`](../../komorebi/tests/resize.rs), resizing;
|
||||
[`komorebi/tests/promote.rs`](../../komorebi/tests/promote.rs), promoting.
|
||||
- [`nix/tests/vm/suites/scrolling`](../../nix/tests/vm/suites/scrolling/test.py): the scrolling
|
||||
strip in a real KWin, with its columns out of view off the screen.
|
||||
- [`nix/tests/vm/suites/commands`](../../nix/tests/vm/suites/commands/test.py): every layout,
|
||||
@@ -593,6 +603,10 @@ A window is shown, and ends up on a tile:
|
||||
[`Workspace::layout_options`]: ../../komorebi/src/workspace.rs
|
||||
[`Workspace::resize_dimensions`]: ../../komorebi/src/workspace.rs
|
||||
[`WindowManager::resize_window`]: ../../komorebi/src/window_manager.rs
|
||||
[`WindowManager::promote_container_to_front`]: ../../komorebi/src/window_manager.rs
|
||||
[`WindowManager::promote_container_swap`]: ../../komorebi/src/window_manager.rs
|
||||
[`WindowManager::promote_focus_to_front`]: ../../komorebi/src/window_manager.rs
|
||||
[`Workspace::promotion_swap_container_idx`]: ../../komorebi/src/workspace.rs
|
||||
[`WindowManager::resize_delta`]: ../../komorebi/src/window_manager.rs
|
||||
[`Workspace::latest_layout`]: ../../komorebi/src/workspace.rs
|
||||
[`LayoutOptions`]: ../../komorebi/src/core/mod.rs
|
||||
|
||||
@@ -33,6 +33,11 @@ komorebic cycle-focus next
|
||||
komorebic cycle-focus previous
|
||||
```
|
||||
|
||||
## The largest tile
|
||||
|
||||
[`komorebic promote-focus`](../cli/promote-focus.md) focuses the window on the first tile of the
|
||||
layout, which is its largest ([Moving windows](moving-windows.md#to-the-largest-tile)).
|
||||
|
||||
## The pointer
|
||||
|
||||
By default the pointer moves to the middle of the window that is given the focus. komorebi asks
|
||||
|
||||
@@ -32,6 +32,29 @@ komorebic cycle-move next
|
||||
komorebic cycle-move previous
|
||||
```
|
||||
|
||||
## To the largest tile
|
||||
|
||||
The first tile of a layout is the largest one: the left half in BSP and the vertical stack, the
|
||||
top half in the horizontal stack, the middle of the ultrawide vertical stack and the right half
|
||||
in the right main vertical stack. In the columns, rows and scrolling layouts it is the first
|
||||
column or row. The grid layout is left alone: these commands do nothing there.
|
||||
|
||||
[`komorebic promote`](../cli/promote.md) moves the focused window to the first tile, and the
|
||||
windows that were before it each move one tile on.
|
||||
[`komorebic promote-swap`](../cli/promote-swap.md) swaps the focused window with the window on
|
||||
the first tile instead, and from the first tile swaps it back with the window it was last
|
||||
swapped with. [`komorebic promote-window`](../cli/promote-window.md) promotes the window on one
|
||||
side of the focused one.
|
||||
|
||||
```sh
|
||||
komorebic promote
|
||||
komorebic promote-swap
|
||||
komorebic promote-window left
|
||||
```
|
||||
|
||||
[`komorebic promote-focus`](../cli/promote-focus.md) gives the focus to the window on the first
|
||||
tile and moves nothing.
|
||||
|
||||
## The pointer
|
||||
|
||||
The pointer follows the window that moved, unless
|
||||
|
||||
@@ -348,6 +348,10 @@ pub enum SocketMessage {
|
||||
CycleMoveWindow(CycleDirection),
|
||||
ResizeWindowEdge(OperationDirection, Sizing),
|
||||
ResizeWindowAxis(Axis, Sizing),
|
||||
Promote,
|
||||
PromoteSwap,
|
||||
PromoteFocus,
|
||||
PromoteWindow(OperationDirection),
|
||||
// Current Workspace Commands
|
||||
ChangeLayout(DefaultLayout),
|
||||
CycleLayout(CycleDirection),
|
||||
|
||||
@@ -20,6 +20,11 @@
|
||||
//! of it, or with the next or the previous one, and tile the workspace again
|
||||
//! ([`WindowManager::move_container_in_direction`],
|
||||
//! [`WindowManager::move_container_in_cycle_direction`]).
|
||||
//! - **Promote** moves the focused container to the largest tile, **PromoteSwap** swaps it with
|
||||
//! the container there (and back), **PromoteFocus** gives that container the focus, and
|
||||
//! **PromoteWindow** promotes the container on one side of the focused one
|
||||
//! ([`WindowManager::promote_container_to_front`], [`WindowManager::promote_container_swap`],
|
||||
//! [`WindowManager::promote_focus_to_front`]).
|
||||
//! - **ChangeLayout**, **CycleLayout** and **FlipLayout** give the focused workspace another
|
||||
//! layout, or mirror it, and **ToggleTiling** turns its tiling off and on
|
||||
//! ([`WindowManager::change_workspace_layout_default`], [`WindowManager::cycle_layout`],
|
||||
@@ -138,7 +143,7 @@ impl WindowManager {
|
||||
/// [`WindowManager::move_container_in_direction`] and
|
||||
/// [`WindowManager::move_container_in_cycle_direction`] give.
|
||||
/// - There is no focused workspace to change the layout or the tiling of, to preselect a
|
||||
/// place in, or to resize a container of, or it cannot be tiled again.
|
||||
/// place in, or to resize or promote a container of, or it cannot be tiled again.
|
||||
/// - The file of `QuickSave` or `Save` cannot be written, or the file of `QuickLoad` or
|
||||
/// `Load` cannot be read or is not a list of resize adjustments.
|
||||
/// - The window manager fails to retile, for one of the reasons
|
||||
@@ -159,6 +164,13 @@ impl WindowManager {
|
||||
let initial_state = State::from(self.as_ref());
|
||||
|
||||
match message {
|
||||
SocketMessage::Promote => self.promote_container_to_front()?,
|
||||
SocketMessage::PromoteSwap => self.promote_container_swap()?,
|
||||
SocketMessage::PromoteFocus => self.promote_focus_to_front()?,
|
||||
SocketMessage::PromoteWindow(direction) => {
|
||||
self.focus_container_in_direction(direction)?;
|
||||
self.promote_container_to_front()?
|
||||
}
|
||||
SocketMessage::FocusWindow(direction) => {
|
||||
self.focus_container_in_direction(direction)?;
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@ impl From<&WindowManager> for State {
|
||||
tile: workspace.tile,
|
||||
globals: workspace.globals,
|
||||
preselected_container_idx: None,
|
||||
promotion_swap_container_idx: None,
|
||||
})
|
||||
.collect::<VecDeque<_>>();
|
||||
ws.focus(monitor.workspaces.focused_idx());
|
||||
|
||||
@@ -606,6 +606,92 @@ impl WindowManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Moves the focused container to the first index of the focused workspace, which is the
|
||||
/// largest tile, and tiles the workspace again: the containers before it each move one index
|
||||
/// on
|
||||
///
|
||||
/// The grid layout has no largest tile, and nothing changes there.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// There is no focused workspace, it has no containers, or it cannot be tiled again.
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn promote_container_to_front(&mut self) -> eyre::Result<()> {
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
|
||||
if matches!(workspace.layout, Layout::Default(DefaultLayout::Grid)) {
|
||||
tracing::debug!("ignoring promote command for grid layout");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
tracing::info!("promoting container");
|
||||
|
||||
workspace.promote_container()?;
|
||||
self.update_focused_workspace(self.mouse_follows_focus, true)
|
||||
}
|
||||
|
||||
/// Swaps the focused container with the one at the first index, the largest tile, and tiles
|
||||
/// the workspace again; when the focused container is the first already, swaps it back with
|
||||
/// the container it was swapped with last
|
||||
///
|
||||
/// The grid layout has no largest tile, and nothing changes there.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// There is no focused workspace, or it cannot be tiled again.
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn promote_container_swap(&mut self) -> eyre::Result<()> {
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
let focused_container_idx = workspace.focused_container_idx();
|
||||
|
||||
let primary_idx = match workspace.layout {
|
||||
Layout::Default(_) => 0,
|
||||
};
|
||||
|
||||
if matches!(workspace.layout, Layout::Default(DefaultLayout::Grid)) {
|
||||
tracing::debug!("ignoring promote-swap command for grid layout");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let primary_tile_is_focused = focused_container_idx == primary_idx;
|
||||
|
||||
if primary_tile_is_focused && let Some(swap_idx) = workspace.promotion_swap_container_idx {
|
||||
workspace.swap_containers(focused_container_idx, swap_idx);
|
||||
} else {
|
||||
workspace.promotion_swap_container_idx = Some(focused_container_idx);
|
||||
workspace.swap_containers(focused_container_idx, primary_idx);
|
||||
}
|
||||
|
||||
self.update_focused_workspace(self.mouse_follows_focus, true)
|
||||
}
|
||||
|
||||
/// Gives the focus to the container at the first index, the largest tile, without moving
|
||||
/// anything
|
||||
///
|
||||
/// The grid layout has no largest tile, and nothing changes there.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// There is no focused workspace, or the backend refuses to focus the window.
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn promote_focus_to_front(&mut self) -> eyre::Result<()> {
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
|
||||
if matches!(workspace.layout, Layout::Default(DefaultLayout::Grid)) {
|
||||
tracing::info!("ignoring promote focus command for grid layout");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
tracing::info!("promoting focus");
|
||||
|
||||
let target_idx = match workspace.layout {
|
||||
Layout::Default(_) => 0,
|
||||
};
|
||||
|
||||
workspace.focus_container(target_idx);
|
||||
self.update_focused_workspace(self.mouse_follows_focus, true)
|
||||
}
|
||||
|
||||
/// Mirrors the layout of the focused workspace along `layout_flip`, on top of the way it is
|
||||
/// mirrored already, and tiles the workspace again
|
||||
///
|
||||
|
||||
@@ -39,6 +39,8 @@ pub struct Workspace {
|
||||
pub globals: WorkspaceGlobals,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub preselected_container_idx: Option<usize>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub promotion_swap_container_idx: Option<usize>,
|
||||
}
|
||||
|
||||
impl_ring_elements!(Workspace, Container);
|
||||
@@ -57,6 +59,7 @@ impl Default for Workspace {
|
||||
globals: Default::default(),
|
||||
tile: true,
|
||||
preselected_container_idx: None,
|
||||
promotion_swap_container_idx: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -340,6 +343,31 @@ impl Workspace {
|
||||
container
|
||||
}
|
||||
|
||||
/// Moves the focused container to the first index, the largest tile in every layout but the
|
||||
/// grid, and keeps the focus on it
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// The workspace has no containers.
|
||||
pub fn promote_container(&mut self) -> eyre::Result<()> {
|
||||
let focused_idx = self.focused_container_idx();
|
||||
let container = self
|
||||
.containers_mut()
|
||||
.remove_respecting_locks(focused_idx)
|
||||
.ok_or_eyre("there is no container")?;
|
||||
|
||||
let primary_idx = match &self.layout {
|
||||
Layout::Default(_) => 0,
|
||||
};
|
||||
|
||||
let insertion_idx = self
|
||||
.containers_mut()
|
||||
.insert_respecting_locks(primary_idx, container);
|
||||
self.focus_container(insertion_idx);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_container_to_back(&mut self, container: Container) {
|
||||
self.containers_mut().push_back(container);
|
||||
self.focus_last_container();
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
//! Bringing a window to the largest tile with `promote`, `promote-swap`, `promote-window`, and the
|
||||
//! focus to it with `promote-focus`, against an in-memory desktop: the messages go in through
|
||||
//! [`WindowManager::process_command`], and what comes out is the order of the containers, where
|
||||
//! the backend is asked to put each window, and which window has the focus.
|
||||
//!
|
||||
//! Window ids are in the 24_000s.
|
||||
|
||||
use komorebi::backend::fake::Command;
|
||||
use komorebi::backend::fake::FakeBackend;
|
||||
use komorebi::backend::with_backend;
|
||||
use komorebi::core::DefaultLayout;
|
||||
use komorebi::core::OperationDirection;
|
||||
use komorebi::core::SocketMessage;
|
||||
use komorebi::window::Window;
|
||||
use komorebi::window_manager::WindowManager;
|
||||
use komorebi::window_manager_event::KWinNotification;
|
||||
use komorebi::window_manager_event::SystemNotification;
|
||||
use komorebi::window_manager_event::WindowManagerEvent;
|
||||
use std::sync::Arc;
|
||||
|
||||
fn added(id: u32) -> WindowManagerEvent {
|
||||
WindowManagerEvent::Show(
|
||||
SystemNotification::KWin(KWinNotification::WindowAdded),
|
||||
Window::from(id),
|
||||
)
|
||||
}
|
||||
|
||||
/// A window manager over a fake desktop with one 1920x1080 monitor in `layout`, and a window for
|
||||
/// each of `ids`, opened in that order. The last one has the focus, and the commands it took to
|
||||
/// get there are cleared.
|
||||
fn desktop_with(layout: DefaultLayout, ids: &[u32]) -> (Arc<FakeBackend>, WindowManager) {
|
||||
let fake = Arc::new(FakeBackend::with_one_monitor());
|
||||
let (_sender, receiver) = crossbeam_channel::bounded(1);
|
||||
// unix socket paths are limited to ~104 bytes on macOS, which the temp dir alone can exceed
|
||||
static NEXT: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
|
||||
let socket = std::path::PathBuf::from(format!(
|
||||
"/tmp/kmp-{}-{}.sock",
|
||||
std::process::id(),
|
||||
NEXT.fetch_add(1, std::sync::atomic::Ordering::SeqCst)
|
||||
));
|
||||
|
||||
let mut wm = with_backend(fake.clone(), || {
|
||||
let mut wm = WindowManager::new(receiver, Some(socket)).unwrap();
|
||||
wm.init().unwrap();
|
||||
wm
|
||||
});
|
||||
send(&fake, &mut wm, SocketMessage::ChangeLayout(layout));
|
||||
for &id in ids {
|
||||
fake.open_window(id, "foot", "foot", &format!("terminal {id}"));
|
||||
with_backend(fake.clone(), || wm.process_event(added(id))).unwrap();
|
||||
}
|
||||
|
||||
fake.clear_commands();
|
||||
(fake, wm)
|
||||
}
|
||||
|
||||
fn send(fake: &Arc<FakeBackend>, wm: &mut WindowManager, message: SocketMessage) {
|
||||
with_backend(fake.clone(), || wm.process_command(message, Vec::new())).unwrap();
|
||||
}
|
||||
|
||||
/// The window of each container of the focused workspace, in order
|
||||
fn order(wm: &WindowManager) -> Vec<u32> {
|
||||
wm.focused_workspace()
|
||||
.unwrap()
|
||||
.containers()
|
||||
.iter()
|
||||
.map(|container| container.focused_window().unwrap().id)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn focused(wm: &WindowManager) -> u32 {
|
||||
wm.focused_window().unwrap().id
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn promote_moves_the_focused_window_to_the_largest_tile_and_the_others_along() {
|
||||
let (fake, mut wm) = desktop_with(DefaultLayout::BSP, &[24_001, 24_002, 24_003]);
|
||||
let largest = fake.rect_of(24_001);
|
||||
|
||||
send(&fake, &mut wm, SocketMessage::Promote);
|
||||
|
||||
assert_eq!(order(&wm), [24_003, 24_001, 24_002]);
|
||||
assert_eq!(focused(&wm), 24_003);
|
||||
assert_eq!(fake.rect_of(24_003), largest);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn promote_swap_swaps_with_the_largest_tile_and_swaps_back() {
|
||||
let (fake, mut wm) = desktop_with(DefaultLayout::BSP, &[24_011, 24_012, 24_013]);
|
||||
|
||||
send(&fake, &mut wm, SocketMessage::PromoteSwap);
|
||||
|
||||
assert_eq!(order(&wm), [24_013, 24_012, 24_011]);
|
||||
assert_eq!(focused(&wm), 24_013);
|
||||
|
||||
send(&fake, &mut wm, SocketMessage::PromoteSwap);
|
||||
|
||||
assert_eq!(
|
||||
order(&wm),
|
||||
[24_011, 24_012, 24_013],
|
||||
"the largest tile is swapped back"
|
||||
);
|
||||
assert_eq!(focused(&wm), 24_013);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn promote_focus_gives_the_focus_to_the_largest_tile_and_moves_nothing() {
|
||||
let ids = [24_021, 24_022, 24_023];
|
||||
let (fake, mut wm) = desktop_with(DefaultLayout::BSP, &ids);
|
||||
let tiles: Vec<_> = ids.iter().map(|&id| fake.rect_of(id)).collect();
|
||||
|
||||
send(&fake, &mut wm, SocketMessage::PromoteFocus);
|
||||
|
||||
assert_eq!(focused(&wm), 24_021);
|
||||
assert_eq!(order(&wm), ids);
|
||||
assert_eq!(
|
||||
ids.iter().map(|&id| fake.rect_of(id)).collect::<Vec<_>>(),
|
||||
tiles
|
||||
);
|
||||
assert!(
|
||||
fake.commands().contains(&Command::Focus(24_021, true)),
|
||||
"{:?}",
|
||||
fake.commands()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn promote_window_promotes_the_window_on_that_side() {
|
||||
let (fake, mut wm) = desktop_with(DefaultLayout::Columns, &[24_031, 24_032, 24_033]);
|
||||
|
||||
send(
|
||||
&fake,
|
||||
&mut wm,
|
||||
SocketMessage::PromoteWindow(OperationDirection::Left),
|
||||
);
|
||||
|
||||
assert_eq!(order(&wm), [24_032, 24_031, 24_033]);
|
||||
assert_eq!(focused(&wm), 24_032);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nothing_is_promoted_in_the_grid_layout() {
|
||||
let ids = [24_041, 24_042, 24_043];
|
||||
let (fake, mut wm) = desktop_with(DefaultLayout::Grid, &ids);
|
||||
|
||||
send(&fake, &mut wm, SocketMessage::Promote);
|
||||
send(&fake, &mut wm, SocketMessage::PromoteSwap);
|
||||
send(&fake, &mut wm, SocketMessage::PromoteFocus);
|
||||
|
||||
assert_eq!(order(&wm), ids);
|
||||
assert_eq!(focused(&wm), 24_043);
|
||||
assert!(fake.commands().is_empty(), "{:?}", fake.commands());
|
||||
}
|
||||
@@ -14,6 +14,8 @@
|
||||
//! workspace, `scrolling-layout-columns` and `layout-ratios` tune it, `toggle-tiling` turns its
|
||||
//! tiling off and on, and `preselect-direction` and `cancel-preselect` reserve the place of the
|
||||
//! next window and give it up.
|
||||
//! - `promote`, `promote-swap`, `promote-focus` and `promote-window` bring a window to the largest
|
||||
//! tile, or the focus to it.
|
||||
//! - `resize-edge` and `resize-axis` resize the focused window by the `resize-delta`, and
|
||||
//! `quick-save-resize`, `quick-load-resize`, `save-resize` and `load-resize` save the sizes of
|
||||
//! the tiles of the focused workspace and load them back.
|
||||
@@ -95,6 +97,7 @@ gen_enum_subcommand_args! {
|
||||
// WatchConfiguration: BooleanState,
|
||||
MouseFollowsFocus: BooleanState,
|
||||
Query: StateQuery,
|
||||
PromoteWindow: OperationDirection,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
@@ -289,6 +292,14 @@ enum SubCommand {
|
||||
/// Flip the layout on the focused workspace
|
||||
#[clap(arg_required_else_help = true)]
|
||||
FlipLayout(FlipLayout),
|
||||
/// Promote the focused window to the largest tile via container removal and re-insertion
|
||||
Promote,
|
||||
/// Promote the focused window to the largest tile by swapping container indices with the largest tile
|
||||
PromoteSwap,
|
||||
/// Promote the user focus to the largest window container
|
||||
PromoteFocus,
|
||||
/// Promote the window in the specified direction
|
||||
PromoteWindow(PromoteWindow),
|
||||
/// Force the retiling of all managed windows
|
||||
Retile,
|
||||
/// Toggle window tiling on the focused workspace
|
||||
@@ -451,6 +462,18 @@ fn main() -> eyre::Result<()> {
|
||||
SubCommand::Retile => {
|
||||
send_message(&SocketMessage::Retile)?;
|
||||
}
|
||||
SubCommand::Promote => {
|
||||
send_message(&SocketMessage::Promote)?;
|
||||
}
|
||||
SubCommand::PromoteSwap => {
|
||||
send_message(&SocketMessage::PromoteSwap)?;
|
||||
}
|
||||
SubCommand::PromoteFocus => {
|
||||
send_message(&SocketMessage::PromoteFocus)?;
|
||||
}
|
||||
SubCommand::PromoteWindow(args) => {
|
||||
send_message(&SocketMessage::PromoteWindow(args.operation_direction))?;
|
||||
}
|
||||
SubCommand::ToggleTiling => {
|
||||
send_message(&SocketMessage::ToggleTiling)?;
|
||||
}
|
||||
|
||||
@@ -284,6 +284,17 @@ fn the_resize_subcommands_send_what_they_are_given() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_promote_subcommands_send_their_message() {
|
||||
sends(&["promote"], r#"{"type":"Promote"}"#);
|
||||
sends(&["promote-swap"], r#"{"type":"PromoteSwap"}"#);
|
||||
sends(&["promote-focus"], r#"{"type":"PromoteFocus"}"#);
|
||||
sends(
|
||||
&["promote-window", "right"],
|
||||
r#"{"type":"PromoteWindow","content":"Right"}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mouse_follows_focus_is_set_with_enable_and_disable_or_toggled() {
|
||||
sends(
|
||||
@@ -355,6 +366,10 @@ fn docgen_writes_the_help_of_every_subcommand_in_a_text_fence() {
|
||||
"mouse-follows-focus.md",
|
||||
"move.md",
|
||||
"preselect-direction.md",
|
||||
"promote-focus.md",
|
||||
"promote-swap.md",
|
||||
"promote-window.md",
|
||||
"promote.md",
|
||||
"query.md",
|
||||
"quick-load-resize.md",
|
||||
"quick-save-resize.md",
|
||||
|
||||
@@ -165,6 +165,10 @@ resize-axis horizontal increase
|
||||
resize-axis vertical decrease
|
||||
resize-delta 50
|
||||
resize-delta 100
|
||||
promote
|
||||
promote-swap
|
||||
promote-focus
|
||||
promote-window left
|
||||
retile
|
||||
flip-layout horizontal
|
||||
flip-layout vertical
|
||||
|
||||
Reference in new issue
Block a user