Support indentation and {layout} in formats

timeout_old
Tassilo Horn 3 years ago
parent 1c1b155e95
commit 69aa2597c8
  1. 15
      NEWS.md
  2. 12
      src/cmds.rs
  3. 22
      src/config.rs
  4. 83
      src/tree.rs
  5. 1
      src/util.rs

@ -1,3 +1,18 @@
swayr v0.10.0
=============
- The `con` module which enhances the sway IPC container tree structure has
been replaced by `tree` which achieves the same job but is not restricted to
only handle workspaces, and windows.
- Formats such as `format.workspace_format`, and `format.window_format` can now
include a `{indent}` placeholder which will be replaced with N times the new
`format.indent` value. N is the depth in the shown menu input, e.g., with
`swayr switch-workspace-or-window` the indent level for workspaces is 0, and
1 for windows.
- The `format.workspace_format` may now include a `{layout}` placeholder which
is replaced with the current container's layout.
swayr v0.9.0 swayr v0.9.0
============ ============

@ -167,9 +167,15 @@ pub struct ExecSwayrCmdArgs<'a> {
impl DisplayFormat for SwayrCommand { impl DisplayFormat for SwayrCommand {
fn format_for_display(&self, _: &cfg::Config) -> std::string::String { fn format_for_display(&self, _: &cfg::Config) -> std::string::String {
// TODO: Add a format to Config // TODO: It would be very nice if the display format was exactly like
// the swayr invocation in the shell. Can that somehow be retrieved
// from clap?
format!("{:?}", self) format!("{:?}", self)
} }
fn get_indent_level(&self) -> usize {
0
}
} }
fn always_true(_x: &t::DisplayNode) -> bool { fn always_true(_x: &t::DisplayNode) -> bool {
@ -867,6 +873,10 @@ impl DisplayFormat for SwaymsgCmd<'_> {
fn format_for_display(&self, _: &cfg::Config) -> std::string::String { fn format_for_display(&self, _: &cfg::Config) -> std::string::String {
self.cmd.join(" ") self.cmd.join(" ")
} }
fn get_indent_level(&self) -> usize {
0
}
} }
pub fn exec_swaymsg_command() { pub fn exec_swaymsg_command() {

@ -87,6 +87,14 @@ impl Config {
// .expect("No format.container_format defined.") // .expect("No format.container_format defined.")
// } // }
pub fn get_format_indent(&self) -> String {
self.format
.as_ref()
.and_then(|f| f.indent.clone())
.or_else(|| Format::default().indent)
.expect("No format.indent defined.")
}
pub fn get_format_urgency_start(&self) -> String { pub fn get_format_urgency_start(&self) -> String {
self.format self.format
.as_ref() .as_ref()
@ -156,6 +164,7 @@ pub struct Format {
window_format: Option<String>, window_format: Option<String>,
workspace_format: Option<String>, workspace_format: Option<String>,
//container_format: Option<String>, //container_format: Option<String>,
indent: Option<String>,
urgency_start: Option<String>, urgency_start: Option<String>,
urgency_end: Option<String>, urgency_end: Option<String>,
html_escape: Option<bool>, html_escape: Option<bool>,
@ -196,6 +205,7 @@ impl Default for Menu {
"--insensitive".to_string(), "--insensitive".to_string(),
"--cache-file=/dev/null".to_string(), "--cache-file=/dev/null".to_string(),
"--parse-search".to_string(), "--parse-search".to_string(),
"--height=40%".to_string(),
"--prompt={prompt}".to_string(), "--prompt={prompt}".to_string(),
]), ]),
} }
@ -206,13 +216,14 @@ impl Default for Format {
fn default() -> Self { fn default() -> Self {
Format { Format {
window_format: Some( window_format: Some(
"{urgency_start}<b>{title}</b>{urgency_end} \ "img:{app_icon}:text:{indent}<i>{app_name}</i> \
<i>{app_name}</i> on workspace {workspace_name} \ {urgency_start}<b>{title}</b>{urgency_end} {layout} \
on workspace {workspace_name} {marks} \
<span alpha=\"20000\">({id})</span>" <span alpha=\"20000\">({id})</span>"
.to_string(), .to_string(),
), ),
workspace_format: Some( workspace_format: Some(
"<b>Workspace {name}</b> \ "{indent}<b>Workspace {name}</b> {layout} \
<span alpha=\"20000\">({id})</span>" <span alpha=\"20000\">({id})</span>"
.to_string(), .to_string(),
), ),
@ -221,6 +232,7 @@ impl Default for Format {
// <span alpha=\"20000\">({id})</span>" // <span alpha=\"20000\">({id})</span>"
// .to_string(), // .to_string(),
// ), // ),
indent: Some(" ".to_string()),
html_escape: Some(true), html_escape: Some(true),
urgency_start: Some( urgency_start: Some(
"<span background=\"darkred\" foreground=\"yellow\">" "<span background=\"darkred\" foreground=\"yellow\">"
@ -229,7 +241,10 @@ impl Default for Format {
urgency_end: Some("</span>".to_string()), urgency_end: Some("</span>".to_string()),
icon_dirs: Some(vec![ icon_dirs: Some(vec![
"/usr/share/icons/hicolor/scalable/apps".to_string(), "/usr/share/icons/hicolor/scalable/apps".to_string(),
"/usr/share/icons/hicolor/64x64/apps".to_string(),
"/usr/share/icons/hicolor/48x48/apps".to_string(), "/usr/share/icons/hicolor/48x48/apps".to_string(),
"/usr/share/icons/Adwaita/64x64/apps".to_string(),
"/usr/share/icons/Adwaita/48x48/apps".to_string(),
"/usr/share/pixmaps".to_string(), "/usr/share/pixmaps".to_string(),
]), ]),
fallback_icon: None, fallback_icon: None,
@ -240,6 +255,7 @@ impl Default for Format {
impl Default for Layout { impl Default for Layout {
fn default() -> Layout { fn default() -> Layout {
let resolution_min_width_vec = vec![ let resolution_min_width_vec = vec![
[800, 400],
[1024, 500], [1024, 500],
[1280, 600], [1280, 600],
[1400, 680], [1400, 680],

@ -151,7 +151,7 @@ impl NodeMethods for s::Node {
} }
/// Extra properties gathered by swayrd for windows and workspaces. /// Extra properties gathered by swayrd for windows and workspaces.
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Copy, Clone, Debug, Deserialize, Serialize)]
pub struct ExtraProps { pub struct ExtraProps {
/// Milliseconds since UNIX epoch. /// Milliseconds since UNIX epoch.
pub last_focus_time: u128, pub last_focus_time: u128,
@ -165,9 +165,17 @@ pub struct Tree<'a> {
extra_props: &'a HashMap<i64, ExtraProps>, extra_props: &'a HashMap<i64, ExtraProps>,
} }
#[derive(Copy, Clone, PartialEq, Eq)]
enum IndentLevel {
Fixed(u32),
WorkspacesZeroWindowsOne,
TreeDepth(u8),
}
pub struct DisplayNode<'a> { pub struct DisplayNode<'a> {
pub node: &'a s::Node, pub node: &'a s::Node,
pub tree: &'a Tree<'a>, pub tree: &'a Tree<'a>,
indent_level: IndentLevel,
} }
impl<'a> Tree<'a> { impl<'a> Tree<'a> {
@ -226,11 +234,16 @@ impl<'a> Tree<'a> {
self.sorted_nodes_of_type_1(self.root, t) self.sorted_nodes_of_type_1(self.root, t)
} }
fn as_display_nodes(&self, v: Vec<&'a s::Node>) -> Vec<DisplayNode> { fn as_display_nodes(
&self,
v: Vec<&'a s::Node>,
indent_level: IndentLevel,
) -> Vec<DisplayNode> {
v.iter() v.iter()
.map(|n| DisplayNode { .map(|node| DisplayNode {
node: n, node,
tree: self, tree: self,
indent_level,
}) })
.collect() .collect()
} }
@ -245,13 +258,13 @@ impl<'a> Tree<'a> {
pub fn get_workspaces(&self) -> Vec<DisplayNode> { pub fn get_workspaces(&self) -> Vec<DisplayNode> {
let mut v = self.sorted_nodes_of_type(Type::Workspace); let mut v = self.sorted_nodes_of_type(Type::Workspace);
v.rotate_left(1); v.rotate_left(1);
self.as_display_nodes(v) self.as_display_nodes(v, IndentLevel::Fixed(0))
} }
pub fn get_windows(&self) -> Vec<DisplayNode> { pub fn get_windows(&self) -> Vec<DisplayNode> {
let mut v = self.sorted_nodes_of_type(Type::Window); let mut v = self.sorted_nodes_of_type(Type::Window);
v.rotate_left(1); v.rotate_left(1);
self.as_display_nodes(v) self.as_display_nodes(v, IndentLevel::Fixed(0))
} }
pub fn get_workspaces_and_windows(&self) -> Vec<DisplayNode> { pub fn get_workspaces_and_windows(&self) -> Vec<DisplayNode> {
@ -274,7 +287,7 @@ impl<'a> Tree<'a> {
v.rotate_left(1); v.rotate_left(1);
} }
self.as_display_nodes(v) self.as_display_nodes(v, IndentLevel::WorkspacesZeroWindowsOne)
} }
pub fn is_child_of_tiled_container(&self, id: i64) -> bool { pub fn is_child_of_tiled_container(&self, id: i64) -> bool {
@ -351,27 +364,27 @@ fn maybe_html_escape(do_it: bool, text: &str) -> String {
impl DisplayFormat for DisplayNode<'_> { impl DisplayFormat for DisplayNode<'_> {
fn format_for_display(&self, cfg: &config::Config) -> String { fn format_for_display(&self, cfg: &config::Config) -> String {
let indent = cfg.get_format_indent();
let html_escape = cfg.get_format_html_escape();
match self.node.get_type() { match self.node.get_type() {
Type::Root => String::from("Cannot format Root"), Type::Root => String::from("Cannot format Root"),
Type::Output => String::from("Cannot format Output"), Type::Output => String::from("Cannot format Output"),
Type::Workspace => { Type::Workspace => cfg
let fmt = cfg.get_format_workspace_format(); .get_format_workspace_format()
let html_escape = cfg.get_format_html_escape(); .replace("{indent}", &indent.repeat(self.get_indent_level()))
.replace("{layout}", format!("{:?}", self.node.layout).as_str())
fmt.replace("{id}", format!("{}", self.node.id).as_str()) .replace("{id}", format!("{}", self.node.id).as_str())
.replace( .replace(
"{name}", "{name}",
&maybe_html_escape(html_escape, self.node.get_name()), &maybe_html_escape(html_escape, self.node.get_name()),
) ),
}
Type::Container => { Type::Container => {
todo!("DisplayFormat for Container not yet implemented") todo!("DisplayFormat for Container not yet implemented")
} }
Type::Window => { Type::Window => {
let window_format = cfg.get_format_window_format();
let urgency_start = cfg.get_format_urgency_start(); let urgency_start = cfg.get_format_urgency_start();
let urgency_end = cfg.get_format_urgency_end(); let urgency_end = cfg.get_format_urgency_end();
let html_escape = cfg.get_format_html_escape();
let icon_dirs = cfg.get_format_icon_dirs(); let icon_dirs = cfg.get_format_icon_dirs();
// fallback_icon has no default value. // fallback_icon has no default value.
let fallback_icon: Option<Box<std::path::Path>> = let fallback_icon: Option<Box<std::path::Path>> =
@ -386,7 +399,15 @@ impl DisplayFormat for DisplayNode<'_> {
let app_name_no_version = let app_name_no_version =
APP_NAME_AND_VERSION_RX.replace(app_name, "$1"); APP_NAME_AND_VERSION_RX.replace(app_name, "$1");
window_format cfg.get_format_window_format()
.replace(
"{indent}",
&indent.repeat(self.get_indent_level()),
)
.replace(
"{layout}",
format!("{:?}", self.node.layout).as_str(),
)
.replace("{id}", format!("{}", self.node.id).as_str()) .replace("{id}", format!("{}", self.node.id).as_str())
.replace( .replace(
"{urgency_start}", "{urgency_start}",
@ -454,4 +475,26 @@ impl DisplayFormat for DisplayNode<'_> {
} }
} }
} }
fn get_indent_level(&self) -> usize {
match self.indent_level {
IndentLevel::Fixed(level) => level as usize,
IndentLevel::WorkspacesZeroWindowsOne => {
match self.node.get_type(){
Type::Workspace => 0,
Type::Window => 1,
_ => panic!("Only Workspaces and Windows expected. File a bug report!")
}
}
IndentLevel::TreeDepth(offset) => {
let mut depth: usize = 0;
let mut node = self.node;
while let Some(p) = self.tree.get_parent_node(node.id) {
depth += 1;
node = p;
}
depth - offset as usize
}
}
}
} }

@ -229,6 +229,7 @@ fn test_icon_stuff() {
pub trait DisplayFormat { pub trait DisplayFormat {
fn format_for_display(&self, config: &cfg::Config) -> String; fn format_for_display(&self, config: &cfg::Config) -> String;
fn get_indent_level(&self) -> usize;
} }
pub fn select_from_menu<'a, 'b, TS>( pub fn select_from_menu<'a, 'b, TS>(

Loading…
Cancel
Save