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 codespan_reporting::diagnostic::Severity;
use eww_shared_util::AttrName;
use eww_shared_util::{AttrName, Spanned};
use gdk::prelude::Cast;
use gtk::{
prelude::{BoxExt, ContainerExt, WidgetExt, WidgetExtManual},
Orientation,
};
use itertools::Itertools;
use simplexpr::SimplExpr;
use std::{collections::HashMap, rc::Rc};
use maplit::hashmap;
use simplexpr::{dynval::DynVal, SimplExpr};
use std::{cell::RefCell, collections::HashMap, rc::Rc};
use yuck::{
config::{widget_definition::WidgetDefinition, widget_use::WidgetUse},
config::{
widget_definition::WidgetDefinition,
widget_use::{BasicWidgetUse, ChildrenWidgetUse, LoopWidgetUse, WidgetUse},
},
gen_diagnostic,
};
@ -28,7 +32,7 @@ use super::widget_definitions::{resolve_orientable_attrs, resolve_range_attrs, r
pub struct BuilderArgs<'a> {
pub calling_scope: ScopeIndex,
pub widget_use: WidgetUse,
pub widget_use: BasicWidgetUse,
pub scope_graph: &'a mut ScopeGraph,
pub unhandled_attrs: Vec<AttrName>,
pub widget_defs: Rc<HashMap<String, WidgetDefinition>>,
@ -45,7 +49,26 @@ pub fn build_gtk_widget(
graph: &mut ScopeGraph,
widget_defs: Rc<HashMap<String, WidgetDefinition>>,
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>>,
) -> Result<gtk::Widget> {
if let Some(custom_widget) = widget_defs.clone().get(&widget_use.name) {
@ -100,7 +123,7 @@ fn build_builtin_gtk_widget(
graph: &mut ScopeGraph,
widget_defs: Rc<HashMap<String, WidgetDefinition>>,
calling_scope: ScopeIndex,
widget_use: WidgetUse,
widget_use: BasicWidgetUse,
custom_widget_invocation: Option<Rc<CustomWidgetInvocation>>,
) -> Result<gtk::Widget> {
let mut bargs = BuilderArgs {
@ -160,25 +183,93 @@ fn populate_widget_children(
custom_widget_invocation: Option<Rc<CustomWidgetInvocation>>,
) -> Result<()> {
for child in widget_use_children {
if child.name == "children" {
let custom_widget_invocation = custom_widget_invocation.clone().context("Not in a custom widget invocation")?;
match child {
WidgetUse::Children(child) => {
build_children_special_widget(
tree,
widget_defs.clone(),
calling_scope,
child,
gtk_container,
custom_widget_invocation,
custom_widget_invocation.clone().context("Not in a custom widget invocation")?,
)?;
} else {
}
WidgetUse::Loop(child) => {
build_loop_special_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(())
}
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`].
/// 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
@ -187,13 +278,12 @@ fn build_children_special_widget(
tree: &mut ScopeGraph,
widget_defs: Rc<HashMap<String, WidgetDefinition>>,
calling_scope: ScopeIndex,
mut widget_use: WidgetUse,
widget_use: ChildrenWidgetUse,
gtk_container: &gtk::Container,
custom_widget_invocation: Rc<CustomWidgetInvocation>,
) -> Result<()> {
assert_eq!(&widget_use.name, "children");
if let Some(nth) = widget_use.attrs.ast_optional::<SimplExpr>("nth")? {
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.
// This should be a custom gtk::Bin subclass,..
let child_container = gtk::Box::new(Orientation::Horizontal, 0);
child_container.set_homogeneous(true);
@ -250,7 +340,7 @@ pub struct CustomWidgetInvocation {
}
/// 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 {
Err(DiagError::new(gen_diagnostic! {
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>> {
if self.0.is_empty() {
Ok(Vec::new())

View file

@ -7,7 +7,11 @@ use crate::{
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};
#[derive(Debug, thiserror::Error)]
@ -71,6 +75,7 @@ pub fn validate_variables_in_widget_use(
widget: &WidgetUse,
is_in_definition: bool,
) -> Result<(), ValidationError> {
if let WidgetUse::Basic(widget) = widget {
let matching_definition = defs.get(&widget.name);
if let Some(matching_def) = matching_definition {
let missing_arg = matching_def
@ -86,7 +91,6 @@ pub fn validate_variables_in_widget_use(
});
}
}
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();
@ -103,6 +107,21 @@ pub fn validate_variables_in_widget_use(
for child in widget.children.iter() {
let _ = validate_variables_in_widget_use(defs, variables, child, is_in_definition)?;
}
} else if let WidgetUse::Loop(widget) = widget {
let unknown_var = widget
.elements_expr
.var_refs_with_span()
.iter()
.cloned()
.map(|(span, var_ref)| (span, var_ref.clone()))
.find(|(_, var_ref)| var_ref != &widget.element_name && !variables.contains(var_ref));
if let Some((span, var)) = unknown_var {
return Err(ValidationError::UnknownVariable { span, name: var, in_definition: is_in_definition });
}
let mut variables = variables.clone();
variables.insert(widget.element_name.clone());
let _ = validate_variables_in_widget_use(defs, &variables, &widget.body, is_in_definition)?;
}
Ok(())
}

View file

@ -4,15 +4,41 @@ use simplexpr::SimplExpr;
use crate::{
config::attributes::AttrEntry,
error::{AstError, AstResult},
parser::{ast::Ast, ast_iterator::AstIterator, from_ast::FromAst},
error::{AstError, AstResult, AstResultExt, FormFormatError},
parser::{
ast::Ast,
ast_iterator::AstIterator,
from_ast::{FromAst, FromAstElementContent},
},
};
use eww_shared_util::{AttrName, Span, Spanned, VarName};
use super::attributes::Attributes;
#[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 attrs: Attributes,
pub children: Vec<WidgetUse>,
@ -20,39 +46,79 @@ pub struct WidgetUse {
pub name_span: Span,
}
impl WidgetUse {
impl BasicWidgetUse {
pub fn children_span(&self) -> Span {
if self.children.is_empty() {
self.span.point_span_at_end().shifted(-1)
} 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 {
fn from_ast(e: Ast) -> AstResult<Self> {
let span = e.span();
if let Ok(value) = e.clone().as_simplexpr() {
Ok(label_from_simplexpr(value, span))
Ok(WidgetUse::Basic(label_from_simplexpr(value, span)))
} else {
let mut iter = e.try_ast_iter()?;
let (name_span, name) = iter.expect_symbol()?;
let attrs = iter.expect_key_values()?;
let children = iter.map(WidgetUse::from_ast).collect::<AstResult<Vec<_>>>()?;
Ok(Self { name, attrs, children, span, name_span })
match name.as_ref() {
LoopWidgetUse::ELEMENT_NAME => Ok(WidgetUse::Loop(LoopWidgetUse::from_tail(span, iter)?)),
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 span(&self) -> Span {
self.span
}
}
fn label_from_simplexpr(value: SimplExpr, span: Span) -> WidgetUse {
WidgetUse {
fn label_from_simplexpr(value: SimplExpr, span: Span) -> BasicWidgetUse {
BasicWidgetUse {
name: "label".to_string(),
name_span: span.point_span(),
attrs: Attributes::new(
@ -68,3 +134,15 @@ fn label_from_simplexpr(value: SimplExpr, span: Span) -> WidgetUse {
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")]
WidgetDefMultipleChildren(Span),
#[error("Expected 'in' in this position, but got '{}'", .1)]
ExpectedInInForLoop(Span, String),
}
impl Spanned for FormFormatError {
fn span(&self) -> Span {
match self {
FormFormatError::WidgetDefArglistMissing(span) => *span,
FormFormatError::WidgetDefMultipleChildren(span) => *span,
FormFormatError::WidgetDefArglistMissing(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\
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

@ -67,6 +67,20 @@ impl<I: Iterator<Item = Ast>> AstIterator<I> {
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<()> {
if let Some(next) = self.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
"(box (button 'foo') (button 'bar'))")
; then, inside your widget, use:
; Then, inside your widget, use:
(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!
## 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
As time passes, your configuration might grow larger and larger. Luckily, you can easily split up your configuration into multiple files!