Implement loop widget (#350)
This commit is contained in:
parent
eec0358324
commit
6b7fa5d55c
8 changed files with 302 additions and 76 deletions
|
@ -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: >k::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: >k::Container,
|
gtk_container: >k::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: >k::Container, widget_use: &WidgetUse) -> Result<(), DiagError> {
|
fn validate_container_children_count(container: >k::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,
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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(())
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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!
|
||||||
|
|
Loading…
Add table
Reference in a new issue