Implement loop widget (#350)

This commit is contained in:
ElKowar 2022-04-23 12:54:48 +02:00 committed by GitHub
parent eec0358324
commit 6b7fa5d55c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 302 additions and 76 deletions

View file

@ -1,16 +1,20 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use codespan_reporting::diagnostic::Severity; use codespan_reporting::diagnostic::Severity;
use eww_shared_util::AttrName; use eww_shared_util::{AttrName, Spanned};
use gdk::prelude::Cast; use gdk::prelude::Cast;
use gtk::{ use gtk::{
prelude::{BoxExt, ContainerExt, WidgetExt, WidgetExtManual}, prelude::{BoxExt, ContainerExt, WidgetExt, WidgetExtManual},
Orientation, Orientation,
}; };
use itertools::Itertools; use itertools::Itertools;
use simplexpr::SimplExpr; use maplit::hashmap;
use std::{collections::HashMap, rc::Rc}; use simplexpr::{dynval::DynVal, SimplExpr};
use std::{cell::RefCell, collections::HashMap, rc::Rc};
use yuck::{ use yuck::{
config::{widget_definition::WidgetDefinition, widget_use::WidgetUse}, config::{
widget_definition::WidgetDefinition,
widget_use::{BasicWidgetUse, ChildrenWidgetUse, LoopWidgetUse, WidgetUse},
},
gen_diagnostic, gen_diagnostic,
}; };
@ -28,7 +32,7 @@ use super::widget_definitions::{resolve_orientable_attrs, resolve_range_attrs, r
pub struct BuilderArgs<'a> { pub struct BuilderArgs<'a> {
pub calling_scope: ScopeIndex, pub calling_scope: ScopeIndex,
pub widget_use: WidgetUse, pub widget_use: BasicWidgetUse,
pub scope_graph: &'a mut ScopeGraph, pub scope_graph: &'a mut ScopeGraph,
pub unhandled_attrs: Vec<AttrName>, pub unhandled_attrs: Vec<AttrName>,
pub widget_defs: Rc<HashMap<String, WidgetDefinition>>, pub widget_defs: Rc<HashMap<String, WidgetDefinition>>,
@ -45,7 +49,26 @@ pub fn build_gtk_widget(
graph: &mut ScopeGraph, graph: &mut ScopeGraph,
widget_defs: Rc<HashMap<String, WidgetDefinition>>, widget_defs: Rc<HashMap<String, WidgetDefinition>>,
calling_scope: ScopeIndex, calling_scope: ScopeIndex,
mut widget_use: WidgetUse, widget_use: WidgetUse,
custom_widget_invocation: Option<Rc<CustomWidgetInvocation>>,
) -> Result<gtk::Widget> {
match widget_use {
WidgetUse::Basic(widget_use) => {
build_basic_gtk_widget(graph, widget_defs, calling_scope, widget_use, custom_widget_invocation)
}
WidgetUse::Loop(_) | WidgetUse::Children(_) => Err(anyhow::anyhow!(DiagError::new(gen_diagnostic! {
msg = "This widget can only be used as a child of some container widget such as box",
label = widget_use.span(),
note = "Hint: try wrapping this in a `box`"
}))),
}
}
fn build_basic_gtk_widget(
graph: &mut ScopeGraph,
widget_defs: Rc<HashMap<String, WidgetDefinition>>,
calling_scope: ScopeIndex,
mut widget_use: BasicWidgetUse,
custom_widget_invocation: Option<Rc<CustomWidgetInvocation>>, custom_widget_invocation: Option<Rc<CustomWidgetInvocation>>,
) -> Result<gtk::Widget> { ) -> Result<gtk::Widget> {
if let Some(custom_widget) = widget_defs.clone().get(&widget_use.name) { if let Some(custom_widget) = widget_defs.clone().get(&widget_use.name) {
@ -100,7 +123,7 @@ fn build_builtin_gtk_widget(
graph: &mut ScopeGraph, graph: &mut ScopeGraph,
widget_defs: Rc<HashMap<String, WidgetDefinition>>, widget_defs: Rc<HashMap<String, WidgetDefinition>>,
calling_scope: ScopeIndex, calling_scope: ScopeIndex,
widget_use: WidgetUse, widget_use: BasicWidgetUse,
custom_widget_invocation: Option<Rc<CustomWidgetInvocation>>, custom_widget_invocation: Option<Rc<CustomWidgetInvocation>>,
) -> Result<gtk::Widget> { ) -> Result<gtk::Widget> {
let mut bargs = BuilderArgs { let mut bargs = BuilderArgs {
@ -160,25 +183,93 @@ fn populate_widget_children(
custom_widget_invocation: Option<Rc<CustomWidgetInvocation>>, custom_widget_invocation: Option<Rc<CustomWidgetInvocation>>,
) -> Result<()> { ) -> Result<()> {
for child in widget_use_children { for child in widget_use_children {
if child.name == "children" { match child {
let custom_widget_invocation = custom_widget_invocation.clone().context("Not in a custom widget invocation")?; WidgetUse::Children(child) => {
build_children_special_widget( build_children_special_widget(
tree, tree,
widget_defs.clone(), widget_defs.clone(),
calling_scope, calling_scope,
child, child,
gtk_container, gtk_container,
custom_widget_invocation, custom_widget_invocation.clone().context("Not in a custom widget invocation")?,
)?; )?;
} else { }
let child_widget = WidgetUse::Loop(child) => {
build_gtk_widget(tree, widget_defs.clone(), calling_scope, child, custom_widget_invocation.clone())?; build_loop_special_widget(
gtk_container.add(&child_widget); tree,
widget_defs.clone(),
calling_scope,
child,
gtk_container,
custom_widget_invocation.clone(),
)?;
}
_ => {
let child_widget =
build_gtk_widget(tree, widget_defs.clone(), calling_scope, child, custom_widget_invocation.clone())?;
gtk_container.add(&child_widget);
}
} }
} }
Ok(()) Ok(())
} }
fn build_loop_special_widget(
tree: &mut ScopeGraph,
widget_defs: Rc<HashMap<String, WidgetDefinition>>,
calling_scope: ScopeIndex,
widget_use: LoopWidgetUse,
gtk_container: &gtk::Container,
custom_widget_invocation: Option<Rc<CustomWidgetInvocation>>,
) -> Result<()> {
tree.register_listener(
calling_scope,
Listener {
needed_variables: widget_use.elements_expr.collect_var_refs(),
f: Box::new({
let custom_widget_invocation = custom_widget_invocation.clone();
let widget_defs = widget_defs.clone();
let elements_expr = widget_use.elements_expr.clone();
let elements_expr_span = widget_use.elements_expr_span.clone();
let element_name = widget_use.element_name.clone();
let body: WidgetUse = widget_use.body.as_ref().clone();
let created_children = Rc::new(RefCell::new(Vec::<gtk::Widget>::new()));
let gtk_container = gtk_container.clone();
move |tree, values| {
let elements_value = elements_expr
.eval(&values)?
.as_json_value()?
.as_array()
.context("Not an array value")?
.into_iter()
.map(DynVal::from)
.collect_vec();
let mut created_children = created_children.borrow_mut();
for old_child in created_children.drain(..) {
unsafe { old_child.destroy() };
}
for element in elements_value {
let scope = tree.register_new_scope(
format!("for {} = {}", element_name.0, element),
Some(calling_scope),
calling_scope,
hashmap! {
element_name.clone().into() => SimplExpr::Literal(DynVal(element.0, elements_expr_span))
},
)?;
let new_child_widget =
build_gtk_widget(tree, widget_defs.clone(), scope, body.clone(), custom_widget_invocation.clone())?;
gtk_container.add(&new_child_widget);
created_children.push(new_child_widget);
}
Ok(())
}
}),
},
)
}
/// Handle an invocation of the special `children` [`WidgetUse`]. /// Handle an invocation of the special `children` [`WidgetUse`].
/// This widget expands to multiple other widgets, thus we require the `gtk_container` we should expand the widgets into. /// This widget expands to multiple other widgets, thus we require the `gtk_container` we should expand the widgets into.
/// The `custom_widget_invocation` will be used here to evaluate the provided children in their /// The `custom_widget_invocation` will be used here to evaluate the provided children in their
@ -187,13 +278,12 @@ fn build_children_special_widget(
tree: &mut ScopeGraph, tree: &mut ScopeGraph,
widget_defs: Rc<HashMap<String, WidgetDefinition>>, widget_defs: Rc<HashMap<String, WidgetDefinition>>,
calling_scope: ScopeIndex, calling_scope: ScopeIndex,
mut widget_use: WidgetUse, widget_use: ChildrenWidgetUse,
gtk_container: &gtk::Container, gtk_container: &gtk::Container,
custom_widget_invocation: Rc<CustomWidgetInvocation>, custom_widget_invocation: Rc<CustomWidgetInvocation>,
) -> Result<()> { ) -> Result<()> {
assert_eq!(&widget_use.name, "children"); if let Some(nth) = widget_use.nth_expr {
// TODORW this might not be necessary, if I can keep a copy of the widget I can destroy it directly, no need to go through the container.
if let Some(nth) = widget_use.attrs.ast_optional::<SimplExpr>("nth")? {
// This should be a custom gtk::Bin subclass,.. // This should be a custom gtk::Bin subclass,..
let child_container = gtk::Box::new(Orientation::Horizontal, 0); let child_container = gtk::Box::new(Orientation::Horizontal, 0);
child_container.set_homogeneous(true); child_container.set_homogeneous(true);
@ -250,7 +340,7 @@ pub struct CustomWidgetInvocation {
} }
/// Make sure that [`gtk::Bin`] widgets only get a single child. /// Make sure that [`gtk::Bin`] widgets only get a single child.
fn validate_container_children_count(container: &gtk::Container, widget_use: &WidgetUse) -> Result<(), DiagError> { fn validate_container_children_count(container: &gtk::Container, widget_use: &BasicWidgetUse) -> Result<(), DiagError> {
if container.dynamic_cast_ref::<gtk::Bin>().is_some() && widget_use.children.len() > 1 { if container.dynamic_cast_ref::<gtk::Bin>().is_some() && widget_use.children.len() > 1 {
Err(DiagError::new(gen_diagnostic! { Err(DiagError::new(gen_diagnostic! {
kind = Severity::Error, kind = Severity::Error,

View file

@ -185,6 +185,7 @@ impl DynVal {
} }
} }
// TODO this should return Result<Vec<DynVal>> and use json parsing
pub fn as_vec(&self) -> Result<Vec<String>> { pub fn as_vec(&self) -> Result<Vec<String>> {
if self.0.is_empty() { if self.0.is_empty() {
Ok(Vec::new()) Ok(Vec::new())

View file

@ -7,7 +7,11 @@ use crate::{
parser::{ast::Ast, ast_iterator::AstIterator, from_ast::FromAst}, parser::{ast::Ast, ast_iterator::AstIterator, from_ast::FromAst},
}; };
use super::{widget_definition::WidgetDefinition, widget_use::WidgetUse, Config}; use super::{
widget_definition::WidgetDefinition,
widget_use::{BasicWidgetUse, WidgetUse},
Config,
};
use eww_shared_util::{AttrName, Span, Spanned, VarName}; use eww_shared_util::{AttrName, Span, Spanned, VarName};
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
@ -71,37 +75,52 @@ pub fn validate_variables_in_widget_use(
widget: &WidgetUse, widget: &WidgetUse,
is_in_definition: bool, is_in_definition: bool,
) -> Result<(), ValidationError> { ) -> Result<(), ValidationError> {
let matching_definition = defs.get(&widget.name); if let WidgetUse::Basic(widget) = widget {
if let Some(matching_def) = matching_definition { let matching_definition = defs.get(&widget.name);
let missing_arg = matching_def if let Some(matching_def) = matching_definition {
.expected_args let missing_arg = matching_def
.iter() .expected_args
.find(|expected| !expected.optional && !widget.attrs.attrs.contains_key(&expected.name)); .iter()
if let Some(missing_arg) = missing_arg { .find(|expected| !expected.optional && !widget.attrs.attrs.contains_key(&expected.name));
return Err(ValidationError::MissingAttr { if let Some(missing_arg) = missing_arg {
widget_name: widget.name.clone(), return Err(ValidationError::MissingAttr {
arg_name: missing_arg.name.clone(), widget_name: widget.name.clone(),
arg_list_span: Some(matching_def.args_span), arg_name: missing_arg.name.clone(),
use_span: widget.attrs.span, arg_list_span: Some(matching_def.args_span),
}); use_span: widget.attrs.span,
});
}
}
let values = widget.attrs.attrs.values();
let unknown_var = values.filter_map(|value| value.value.as_simplexpr().ok()).find_map(|expr: SimplExpr| {
let span = expr.span();
expr.var_refs_with_span()
.iter()
.cloned()
.map(|(span, var_ref)| (span, var_ref.clone()))
.find(|(_, var_ref)| !variables.contains(var_ref))
});
if let Some((span, var)) = unknown_var {
return Err(ValidationError::UnknownVariable { span, name: var, in_definition: is_in_definition });
} }
}
let values = widget.attrs.attrs.values(); for child in widget.children.iter() {
let unknown_var = values.filter_map(|value| value.value.as_simplexpr().ok()).find_map(|expr: SimplExpr| { let _ = validate_variables_in_widget_use(defs, variables, child, is_in_definition)?;
let span = expr.span(); }
expr.var_refs_with_span() } else if let WidgetUse::Loop(widget) = widget {
let unknown_var = widget
.elements_expr
.var_refs_with_span()
.iter() .iter()
.cloned() .cloned()
.map(|(span, var_ref)| (span, var_ref.clone())) .map(|(span, var_ref)| (span, var_ref.clone()))
.find(|(_, var_ref)| !variables.contains(var_ref)) .find(|(_, var_ref)| var_ref != &widget.element_name && !variables.contains(var_ref));
}); if let Some((span, var)) = unknown_var {
if let Some((span, var)) = unknown_var { return Err(ValidationError::UnknownVariable { span, name: var, in_definition: is_in_definition });
return Err(ValidationError::UnknownVariable { span, name: var, in_definition: is_in_definition }); }
} let mut variables = variables.clone();
variables.insert(widget.element_name.clone());
for child in widget.children.iter() { let _ = validate_variables_in_widget_use(defs, &variables, &widget.body, is_in_definition)?;
let _ = validate_variables_in_widget_use(defs, variables, child, is_in_definition)?;
} }
Ok(()) Ok(())

View file

@ -4,15 +4,41 @@ use simplexpr::SimplExpr;
use crate::{ use crate::{
config::attributes::AttrEntry, config::attributes::AttrEntry,
error::{AstError, AstResult}, error::{AstError, AstResult, AstResultExt, FormFormatError},
parser::{ast::Ast, ast_iterator::AstIterator, from_ast::FromAst}, parser::{
ast::Ast,
ast_iterator::AstIterator,
from_ast::{FromAst, FromAstElementContent},
},
}; };
use eww_shared_util::{AttrName, Span, Spanned, VarName}; use eww_shared_util::{AttrName, Span, Spanned, VarName};
use super::attributes::Attributes; use super::attributes::Attributes;
#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)] #[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)]
pub struct WidgetUse { pub enum WidgetUse {
Basic(BasicWidgetUse),
Loop(LoopWidgetUse),
Children(ChildrenWidgetUse),
}
#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)]
pub struct LoopWidgetUse {
pub element_name: VarName,
pub elements_expr: SimplExpr,
pub elements_expr_span: Span,
pub body: Box<WidgetUse>,
pub span: Span,
}
#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)]
pub struct ChildrenWidgetUse {
pub span: Span,
pub nth_expr: Option<SimplExpr>,
}
#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)]
pub struct BasicWidgetUse {
pub name: String, pub name: String,
pub attrs: Attributes, pub attrs: Attributes,
pub children: Vec<WidgetUse>, pub children: Vec<WidgetUse>,
@ -20,39 +46,79 @@ pub struct WidgetUse {
pub name_span: Span, pub name_span: Span,
} }
impl WidgetUse { impl BasicWidgetUse {
pub fn children_span(&self) -> Span { pub fn children_span(&self) -> Span {
if self.children.is_empty() { if self.children.is_empty() {
self.span.point_span_at_end().shifted(-1) self.span.point_span_at_end().shifted(-1)
} else { } else {
self.children.first().unwrap().span.to(self.children.last().unwrap().span) self.children.first().unwrap().span().to(self.children.last().unwrap().span())
} }
} }
fn from_iter<I: Iterator<Item = Ast>>(
span: Span,
name: String,
name_span: Span,
mut iter: AstIterator<I>,
) -> AstResult<Self> {
let attrs = iter.expect_key_values()?;
let children = iter.map(WidgetUse::from_ast).collect::<AstResult<Vec<_>>>()?;
Ok(Self { name, attrs, children, span, name_span })
}
}
impl FromAstElementContent for LoopWidgetUse {
const ELEMENT_NAME: &'static str = "for";
fn from_tail<I: Iterator<Item = Ast>>(span: Span, mut iter: AstIterator<I>) -> AstResult<Self> {
let (element_name_span, element_name) = iter.expect_symbol()?;
let (in_string_span, in_string) = iter.expect_symbol()?;
if in_string != "in" {
return Err(AstError::FormFormatError(FormFormatError::ExpectedInInForLoop(in_string_span, in_string)));
}
let (elements_span, elements_expr) = iter.expect_simplexpr()?;
let body = iter.expect_any().note("Expected a loop body").and_then(WidgetUse::from_ast)?;
iter.expect_done()?;
Ok(Self {
element_name: VarName(element_name),
elements_expr,
body: Box::new(body),
span,
elements_expr_span: elements_span,
})
}
}
impl FromAstElementContent for ChildrenWidgetUse {
const ELEMENT_NAME: &'static str = "children";
fn from_tail<I: Iterator<Item = Ast>>(span: Span, mut iter: AstIterator<I>) -> AstResult<Self> {
let mut attrs = iter.expect_key_values()?;
let nth_expr = attrs.ast_optional("nth")?;
iter.expect_done()?;
Ok(Self { span, nth_expr })
}
} }
impl FromAst for WidgetUse { impl FromAst for WidgetUse {
fn from_ast(e: Ast) -> AstResult<Self> { fn from_ast(e: Ast) -> AstResult<Self> {
let span = e.span(); let span = e.span();
if let Ok(value) = e.clone().as_simplexpr() { if let Ok(value) = e.clone().as_simplexpr() {
Ok(label_from_simplexpr(value, span)) Ok(WidgetUse::Basic(label_from_simplexpr(value, span)))
} else { } else {
let mut iter = e.try_ast_iter()?; let mut iter = e.try_ast_iter()?;
let (name_span, name) = iter.expect_symbol()?; let (name_span, name) = iter.expect_symbol()?;
let attrs = iter.expect_key_values()?; match name.as_ref() {
let children = iter.map(WidgetUse::from_ast).collect::<AstResult<Vec<_>>>()?; LoopWidgetUse::ELEMENT_NAME => Ok(WidgetUse::Loop(LoopWidgetUse::from_tail(span, iter)?)),
Ok(Self { name, attrs, children, span, name_span }) ChildrenWidgetUse::ELEMENT_NAME => Ok(WidgetUse::Children(ChildrenWidgetUse::from_tail(span, iter)?)),
_ => Ok(WidgetUse::Basic(BasicWidgetUse::from_iter(span, name, name_span, iter)?)),
}
} }
} }
} }
impl Spanned for WidgetUse { fn label_from_simplexpr(value: SimplExpr, span: Span) -> BasicWidgetUse {
fn span(&self) -> Span { BasicWidgetUse {
self.span
}
}
fn label_from_simplexpr(value: SimplExpr, span: Span) -> WidgetUse {
WidgetUse {
name: "label".to_string(), name: "label".to_string(),
name_span: span.point_span(), name_span: span.point_span(),
attrs: Attributes::new( attrs: Attributes::new(
@ -68,3 +134,15 @@ fn label_from_simplexpr(value: SimplExpr, span: Span) -> WidgetUse {
span, span,
} }
} }
macro_rules! impl_spanned {
($($super:ident => $name:ident),*) => {
$(impl Spanned for $name { fn span(&self) -> Span { self.span } })*
impl Spanned for WidgetUse {
fn span(&self) -> Span {
match self { $(WidgetUse::$super(widget) => widget.span),* }
}
}
}
}
impl_spanned!(Basic => BasicWidgetUse, Loop => LoopWidgetUse, Children => ChildrenWidgetUse);

View file

@ -170,13 +170,17 @@ pub enum FormFormatError {
#[error("Widget definition has more than one child widget")] #[error("Widget definition has more than one child widget")]
WidgetDefMultipleChildren(Span), WidgetDefMultipleChildren(Span),
#[error("Expected 'in' in this position, but got '{}'", .1)]
ExpectedInInForLoop(Span, String),
} }
impl Spanned for FormFormatError { impl Spanned for FormFormatError {
fn span(&self) -> Span { fn span(&self) -> Span {
match self { match self {
FormFormatError::WidgetDefArglistMissing(span) => *span, FormFormatError::WidgetDefArglistMissing(span)
FormFormatError::WidgetDefMultipleChildren(span) => *span, | FormFormatError::WidgetDefMultipleChildren(span)
| FormFormatError::ExpectedInInForLoop(span, _) => *span,
} }
} }
} }

View file

@ -298,6 +298,10 @@ impl ToDiagnostic for FormFormatError {
To include multiple elements, wrap these elements in a single container widget such as `box`.\n\ To include multiple elements, wrap these elements in a single container widget such as `box`.\n\
This is necessary as eww can't know how you want these elements to be layed out otherwise." This is necessary as eww can't know how you want these elements to be layed out otherwise."
}, },
FormFormatError::ExpectedInInForLoop(span, got) => gen_diagnostic! {
msg = self,
label = span,
},
} }
} }
} }

View file

@ -40,9 +40,9 @@ macro_rules! return_or_put_back {
impl<I: Iterator<Item = Ast>> AstIterator<I> { impl<I: Iterator<Item = Ast>> AstIterator<I> {
return_or_put_back! { return_or_put_back! {
fn expect_symbol -> AstType::Symbol, (Span, String) = Ast::Symbol(span, x) => (span, x) fn expect_symbol -> AstType::Symbol, (Span, String) = Ast::Symbol(span, x) => (span, x)
fn expect_list -> AstType::List, (Span, Vec<Ast>) = Ast::List(span, x) => (span, x) fn expect_list -> AstType::List, (Span, Vec<Ast>) = Ast::List(span, x) => (span, x)
fn expect_array -> AstType::Array, (Span, Vec<Ast>) = Ast::Array(span, x) => (span, x) fn expect_array -> AstType::Array, (Span, Vec<Ast>) = Ast::Array(span, x) => (span, x)
} }
pub fn expect_literal(&mut self) -> AstResult<(Span, DynVal)> { pub fn expect_literal(&mut self) -> AstResult<(Span, DynVal)> {
@ -67,6 +67,20 @@ impl<I: Iterator<Item = Ast>> AstIterator<I> {
self.next().or_missing(self.remaining_span.point_span()) self.next().or_missing(self.remaining_span.point_span())
} }
pub fn expect_simplexpr(&mut self) -> AstResult<(Span, SimplExpr)> {
let expr_type = AstType::SimplExpr;
match self.expect_any()? {
Ast::SimplExpr(span, expr) => Ok((span, expr)),
Ast::Symbol(span, var) => Ok((span, SimplExpr::VarRef(span, VarName(var)))),
other => {
let span = other.span();
let actual_type = other.expr_type();
self.put_back(other);
Err(AstError::WrongExprType(span, expr_type, actual_type))
}
}
}
pub fn expect_done(&mut self) -> AstResult<()> { pub fn expect_done(&mut self) -> AstResult<()> {
if let Some(next) = self.next() { if let Some(next) = self.next() {
self.put_back(next); self.put_back(next);

View file

@ -239,7 +239,7 @@ For this, you can make use of one of eww's most powerful features: the `literal`
(defvar variable_containing_yuck (defvar variable_containing_yuck
"(box (button 'foo') (button 'bar'))") "(box (button 'foo') (button 'bar'))")
; then, inside your widget, use: ; Then, inside your widget, use:
(literal :content variable_containing_yuck) (literal :content variable_containing_yuck)
``` ```
@ -248,6 +248,22 @@ Eww then reads the provided value and renders the resulting widget. Whenever it
Note that this is not all that efficient. Make sure to only use `literal` when necessary! Note that this is not all that efficient. Make sure to only use `literal` when necessary!
## Generating a list of widgets from JSON using `for`
If you want to display a list of values, you can use the `for`-Element to fill a container with a list of elements generated from a JSON-array.
```lisp
(defvar my-json "[1, 2, 3]")
; Then, inside your widget, you can use
(box
(for entry in my-json
(button :onclick "notify-send 'click' 'button ${entry}'"
entry)))
```
This can be useful in many situations, for example when generating a workspace list from a JSON representation of your workspaces.
In many cases, this can be used instead of `literal`, and should most likely be preferred in those cases.
## Splitting up your configuration ## Splitting up your configuration
As time passes, your configuration might grow larger and larger. Luckily, you can easily split up your configuration into multiple files! As time passes, your configuration might grow larger and larger. Luckily, you can easily split up your configuration into multiple files!