1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
use knuffel::errors::DecodeError;
use crate::LayoutPart;
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
pub struct Workspace {
#[knuffel(argument)]
pub name: WorkspaceName,
#[knuffel(child, unwrap(argument))]
pub open_on_output: Option<String>,
#[knuffel(child)]
pub layout: Option<WorkspaceLayoutPart>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WorkspaceName(pub String);
#[derive(Debug, Clone, PartialEq)]
pub struct WorkspaceLayoutPart(pub LayoutPart);
impl<S: knuffel::traits::ErrorSpan> knuffel::Decode<S> for WorkspaceLayoutPart {
fn decode_node(
node: &knuffel::ast::SpannedNode<S>,
ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> {
for child in node.children() {
let name = &**child.node_name;
// Check for disallowed properties.
//
// - empty-workspace-above-first is a monitor-level concept.
// - insert-hint customization could make sense for workspaces, however currently it is
// also handled at the monitor level (since insert hints in-between workspaces are a
// monitor-level concept), so for now this config option would do nothing.
if matches!(name, "empty-workspace-above-first" | "insert-hint") {
ctx.emit_error(DecodeError::unexpected(
child,
"node",
format!("node `{name}` is not allowed inside `workspace.layout`"),
));
}
}
LayoutPart::decode_node(node, ctx).map(Self)
}
}
impl<S: knuffel::traits::ErrorSpan> knuffel::DecodeScalar<S> for WorkspaceName {
fn type_check(
type_name: &Option<knuffel::span::Spanned<knuffel::ast::TypeName, S>>,
ctx: &mut knuffel::decode::Context<S>,
) {
if let Some(type_name) = &type_name {
ctx.emit_error(DecodeError::unexpected(
type_name,
"type name",
"no type name expected for this node",
));
}
}
fn raw_decode(
val: &knuffel::span::Spanned<knuffel::ast::Literal, S>,
ctx: &mut knuffel::decode::Context<S>,
) -> Result<WorkspaceName, DecodeError<S>> {
#[derive(Debug)]
struct WorkspaceNameSet(Vec<String>);
match &**val {
knuffel::ast::Literal::String(ref s) => {
let mut name_set: Vec<String> = match ctx.get::<WorkspaceNameSet>() {
Some(h) => h.0.clone(),
None => Vec::new(),
};
if name_set.iter().any(|name| name.eq_ignore_ascii_case(s)) {
ctx.emit_error(DecodeError::unexpected(
val,
"named workspace",
format!("duplicate named workspace: {s}"),
));
return Ok(Self(String::new()));
}
name_set.push(s.to_string());
ctx.set(WorkspaceNameSet(name_set));
Ok(Self(s.clone().into()))
}
_ => {
ctx.emit_error(DecodeError::unsupported(
val,
"workspace names must be strings",
));
Ok(Self(String::new()))
}
}
}
}
|