dotfiles/bin/sway_tree.rs

250 lines
7.3 KiB
Rust
Raw Normal View History

2023-12-23 20:13:47 -07:00
// Quick and dirty tree representation of sway's tree string
//
// Input looks something like:
// H[T[H[S[Alacritty baobab] T[ArchWiki]]] T[chromium-browser Alacritty]]
//
// This can be found, in the case of one workspace, at
// swaymsg -t get_tree | jq '.nodes[1].nodes[0].representation'
//
// Which can then be piped to this script
// swaymsg -t get_tree | jq '.nodes[1].nodes[0].representation' | sway_tree 2>/dev/null
//
// For the example string above, the output will be
// └─┬(Horizontal)
// ├─┬(Tabbed)
// | └─┬(Horizontal)
// | ├─┬(Stacked)
// | | ├───> [Alacritty]
// | | └───> [Baobab]
// | └─┬(Tabbed)
// | └───> [ArchWiki]
// └─┬(Tabbed)
// ├───> [Chromium-browser]
// └───> [Alacritty]
2023-12-23 20:13:57 -07:00
use std::io::{self, BufRead, BufReader};
use std::process::{Command, Stdio};
use std::cmp::min;
2023-12-23 20:13:47 -07:00
fn main() {
let sway_tree = get_tree().expect("Failed to get sway tree");
2023-12-23 20:13:57 -07:00
for (i, line) in sway_tree.iter().enumerate() {
2023-12-23 20:13:57 -07:00
println!("Workspace {} :: {}", i+1, line);
println!("{}", ParserTree::from(line).format());
2023-12-23 20:13:57 -07:00
}
}
2023-12-23 20:13:47 -07:00
fn get_tree() -> io::Result<Vec<String>> {
2023-12-23 20:13:57 -07:00
let swaymsg = Command::new("swaymsg")
.arg("-t")
.arg("get_tree")
.stdout(Stdio::piped())
.spawn()?;
let swaymsg_out = swaymsg.stdout.expect("Swaymsg didn't return window tree");
2023-12-23 20:13:57 -07:00
let repr: Vec<String> = BufReader::new(swaymsg_out)
.lines()
.map(|l| l.unwrap().trim().to_string())
.filter(|l| &l[..min(16, l.len())] == "\"representation\"")
.map(|l| l[19..l.len()-1].to_string())
.collect();
2023-12-23 20:13:57 -07:00
Ok(repr)
2023-12-23 20:13:47 -07:00
}
#[derive(Debug)]
pub enum State {
ReadingTitle(String),
ExitContainer,
EnterContainer,
}
#[derive(Debug)]
pub enum Layout {
Horizontal,
Vertical,
Tabbed,
Stacked,
}
#[derive(Debug)]
pub enum ParserTree {
Container(Layout, Vec<ParserTree>),
Leaf(String),
Empty,
}
impl ParserTree {
pub fn new() -> Self {
ParserTree::Empty
}
pub fn from(str_rep: &str) -> Self {
parse_sway_tree(str_rep)
}
pub fn new_leaf(title: String) -> Self {
ParserTree::Leaf(title)
}
pub fn container(layout: Layout, nodes: Vec<Self>) -> Self {
Self::Container(layout, nodes)
}
// Formats a tree view of parsed tree
pub fn format(self) -> String {
self.format_recursive(vec![false], true)
}
fn format_recursive(self, mut is_branched: Vec<bool>, is_last: bool) -> String {
let indent_size = 2;
let mut indent = (0..((is_branched.len() - 1) * indent_size)).map(|i| {
if i % indent_size == 0 && is_branched[i / indent_size + 1] {
'|'
} else {
' '
}
}).collect::<String>();
indent += match is_last {
true => "└─",
false => "├─",
};
return match self {
Self::Container(layout, children) => {
indent.push(if children.is_empty() { '─' } else { '┬' });
let mut container = indent;
match layout {
Layout::Horizontal => container.push_str("(Horizontal)\n"),
Layout::Vertical => container.push_str("(Vertical)\n"),
Layout::Tabbed => container.push_str("(Tabbed)\n"),
Layout::Stacked => container.push_str("(Stacked)\n"),
}
let last_i = children.len() - 1;
is_branched.push(!is_last);
for (i, child) in children.into_iter().enumerate() {
container += &child.format_recursive(is_branched.clone(), i == last_i);
}
container
}
Self::Leaf(title) => {
let mut cs = title.chars();
let title_cased = match cs.next() {
None => String::from("Untitled"),
Some(f) => f.to_uppercase().collect::<String>() + cs.as_str(),
};
format!("{}> {}\n", indent, title_cased)
}
Self::Empty => format!("{} - <>\n", indent),
}
}
}
fn parse_sway_tree(rep: &str) -> ParserTree {
let mut state = State::EnterContainer;
let characters: Vec<char> = rep.chars().collect();
let mut container_stack: Vec<(Layout, Vec<ParserTree>)> = Vec::new();
let mut c: char;
let mut i = 0;
loop {
c = characters[i];
match state {
State::ReadingTitle(ref mut title) => {
if c == ']' {
let leaf = ParserTree::new_leaf(title.clone());
let mut container = container_stack.pop().unwrap();
container.1.push(leaf);
let child = ParserTree::container(container.0, container.1);
if !container_stack.is_empty() {
let parent = container_stack.last_mut().unwrap();
parent.1.push(child);
} else {
return child
}
2023-12-23 20:13:57 -07:00
//eprintln!("PushLeaf: {:?}", &container_stack);
2023-12-23 20:13:47 -07:00
state = State::ExitContainer;
} else if c == ' ' {
let leaf = ParserTree::new_leaf(title.clone());
let container = container_stack.last_mut().unwrap();
container.1.push(leaf);
2023-12-23 20:13:57 -07:00
//eprintln!("PushLeaf: {:?}", &container_stack);
2023-12-23 20:13:47 -07:00
state = State::EnterContainer;
} else {
title.push(c);
}
i += 1;
}
State::EnterContainer => {
let c1 = *characters.get(i + 1).unwrap_or(&'\0');
if (c == 'H' || c == 'V' || c == 'T' || c == 'S') && c1 == '[' {
container_stack.push(init_container(c));
2023-12-23 20:13:57 -07:00
//eprintln!("AppendChild: {:?}", &container_stack);
2023-12-23 20:13:47 -07:00
i += 2;
} else {
state = State::ReadingTitle("".to_string());
}
}
State::ExitContainer => {
if c == ']' {
2023-12-23 20:13:57 -07:00
//eprintln!("AppendParent:{:?}", &container_stack);
2023-12-23 20:13:47 -07:00
let container = container_stack.pop().unwrap();
let child = ParserTree::container(container.0, container.1);
if container_stack.len() == 0 {
return child
} else {
let parent = container_stack.last_mut().unwrap();
parent.1.push(child);
}
i += 1;
} else {
2023-12-23 20:13:57 -07:00
//eprintln!("NextChild: {:?}", &container_stack);
2023-12-23 20:13:47 -07:00
state = State::EnterContainer;
i += 1;
}
}
}
}
}
// Creates new parts for a container
fn init_container(layout_char: char) -> (Layout, Vec<ParserTree>) {
match layout_char {
'H' => (Layout::Horizontal, vec![]),
'V' => (Layout::Vertical, vec![]),
'T' => (Layout::Tabbed, vec![]),
'S' => (Layout::Stacked, vec![]),
_ => unreachable!(),
}
}