From 101d36cd45ba2fa5216f94a75ee07f41f4dbc11d Mon Sep 17 00:00:00 2001 From: Joscha Date: Sun, 30 Apr 2023 22:12:21 +0200 Subject: [PATCH] Provide list of key groups in config crate This also fixes the f1 menu not displaying the room group. --- cove-config/src/keys.rs | 27 ++++++++++++++++++++++- cove-input/src/lib.rs | 26 +++++++++++++++++++++- cove-macro/src/document.rs | 4 ++-- cove-macro/src/key_group.rs | 17 +++++++++++--- cove-macro/src/util.rs | 25 ++++++++++----------- cove/src/ui/key_bindings.rs | 44 ++++++++++++------------------------- 6 files changed, 93 insertions(+), 50 deletions(-) diff --git a/cove-config/src/keys.rs b/cove-config/src/keys.rs index b9372d5..3ecbb5b 100644 --- a/cove-config/src/keys.rs +++ b/cove-config/src/keys.rs @@ -1,4 +1,4 @@ -use cove_input::{KeyBinding, KeyGroup}; +use cove_input::{KeyBinding, KeyGroup, KeyGroupInfo}; use serde::Deserialize; use crate::doc::Document; @@ -110,6 +110,7 @@ default_bindings! { } #[derive(Debug, Deserialize, Document, KeyGroup)] +/// General. pub struct General { /// Quit cove. #[serde(default = "default::general::exit")] @@ -133,6 +134,7 @@ pub struct General { } #[derive(Debug, Deserialize, Document, KeyGroup)] +/// Scrolling. pub struct Scroll { /// Scroll up one line. #[serde(default = "default::scroll::up_line")] @@ -158,6 +160,7 @@ pub struct Scroll { } #[derive(Debug, Deserialize, Document, KeyGroup)] +/// Cursor movement. pub struct Cursor { /// Move up. #[serde(default = "default::cursor::up")] @@ -174,6 +177,7 @@ pub struct Cursor { } #[derive(Debug, Deserialize, Document, KeyGroup)] +/// Editor cursor movement. pub struct EditorCursor { /// Move left. #[serde(default = "default::editor_cursor::left")] @@ -202,6 +206,7 @@ pub struct EditorCursor { } #[derive(Debug, Deserialize, Document, KeyGroup)] +/// Editor actions. pub struct EditorAction { /// Delete before cursor. #[serde(default = "default::editor_action::backspace")] @@ -229,6 +234,7 @@ pub struct Editor { } #[derive(Debug, Deserialize, Document, KeyGroup)] +/// Room list actions. pub struct RoomsAction { /// Connect to selected room. #[serde(default = "default::rooms_action::connect")] @@ -267,6 +273,7 @@ pub struct Rooms { } #[derive(Debug, Deserialize, Document, KeyGroup)] +/// Room actions. pub struct RoomAction { /// Authenticate. #[serde(default = "default::room_action::authenticate")] @@ -293,6 +300,7 @@ pub struct Room { } #[derive(Debug, Deserialize, Document, KeyGroup)] +/// Tree cursor movement. pub struct TreeCursor { /// Move to above sibling. #[serde(default = "default::tree_cursor::to_above_sibling")] @@ -322,6 +330,7 @@ pub struct TreeCursor { } #[derive(Debug, Deserialize, Document, KeyGroup)] +/// Tree actions. pub struct TreeAction { /// Reply to message, inline if possible. #[serde(default = "default::tree_action::reply")] @@ -393,3 +402,19 @@ pub struct Keys { #[document(no_default)] pub tree: Tree, } + +impl Keys { + pub fn groups(&self) -> Vec> { + vec![ + KeyGroupInfo::new("general", &self.general), + KeyGroupInfo::new("scroll", &self.scroll), + KeyGroupInfo::new("cursor", &self.cursor), + KeyGroupInfo::new("editor.cursor", &self.editor.cursor), + KeyGroupInfo::new("editor.action", &self.editor.action), + KeyGroupInfo::new("rooms.action", &self.rooms.action), + KeyGroupInfo::new("room.action", &self.room.action), + KeyGroupInfo::new("tree.cursor", &self.tree.cursor), + KeyGroupInfo::new("tree.action", &self.tree.action), + ] + } +} diff --git a/cove-input/src/lib.rs b/cove-input/src/lib.rs index fe578fd..b42fdcb 100644 --- a/cove-input/src/lib.rs +++ b/cove-input/src/lib.rs @@ -10,9 +10,33 @@ use toss::{Frame, Terminal, WidthDb}; pub use crate::keys::*; +pub struct KeyBindingInfo<'a> { + pub name: &'static str, + pub binding: &'a KeyBinding, + pub description: &'static str, +} + /// A group of related key bindings. pub trait KeyGroup { - fn bindings(&self) -> Vec<(&KeyBinding, &'static str)>; + const DESCRIPTION: &'static str; + + fn bindings(&self) -> Vec>; +} + +pub struct KeyGroupInfo<'a> { + pub name: &'static str, + pub description: &'static str, + pub bindings: Vec>, +} + +impl<'a> KeyGroupInfo<'a> { + pub fn new(name: &'static str, group: &'a G) -> Self { + Self { + name, + description: G::DESCRIPTION, + bindings: group.bindings(), + } + } } pub struct InputEvent<'a> { diff --git a/cove-macro/src/document.rs b/cove-macro/src/document.rs index ddf13c8..e8e248e 100644 --- a/cove-macro/src/document.rs +++ b/cove-macro/src/document.rs @@ -16,12 +16,12 @@ struct FieldInfo { impl FieldInfo { fn initialize_from_field(&mut self, field: &Field) -> syn::Result<()> { - let docstring = util::docstring(field)?; + let docstring = util::docstring(&field.attrs)?; if !docstring.is_empty() { self.description = Some(docstring); } - for arg in util::attribute_arguments(field, "document")? { + for arg in util::attribute_arguments(&field.attrs, "document")? { if arg.path.is_ident("metavar") { // Parse `#[document(metavar = "bla")]` if let Some(metavar) = arg.value.and_then(util::into_litstr) { diff --git a/cove-macro/src/key_group.rs b/cove-macro/src/key_group.rs index 254f660..bc7bdea 100644 --- a/cove-macro/src/key_group.rs +++ b/cove-macro/src/key_group.rs @@ -19,11 +19,16 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result { return util::bail(input.span(), "must be a struct"); }; + let docstring = util::docstring(&input.attrs)?; + let description = docstring.strip_suffix('.').unwrap_or(&docstring); + let mut bindings = vec![]; let mut defaults = vec![]; for field in &data.fields { if let Some(field_ident) = &field.ident { - let docstring = util::docstring(field)?; + let field_name = field_ident.to_string(); + + let docstring = util::docstring(&field.attrs)?; let description = decapitalize(&docstring); let description = description.strip_suffix('.').unwrap_or(&description); @@ -34,7 +39,11 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result { let default_value = default.value(); bindings.push(quote! { - (&self.#field_ident, #description) + ::cove_input::KeyBindingInfo { + name: #field_name, + binding: &self.#field_ident, + description: #description + } }); defaults.push(quote! { @@ -46,7 +55,9 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result { let ident = input.ident; Ok(quote! { impl ::cove_input::KeyGroup for #ident { - fn bindings(&self) -> Vec<(&::cove_input::KeyBinding, &'static str)> { + const DESCRIPTION: &'static str = #description; + + fn bindings(&self) -> Vec<::cove_input::KeyBindingInfo<'_>> { vec![ #( #bindings, )* ] diff --git a/cove-macro/src/util.rs b/cove-macro/src/util.rs index e56174a..b7bf62a 100644 --- a/cove-macro/src/util.rs +++ b/cove-macro/src/util.rs @@ -2,7 +2,7 @@ use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::parse::Parse; use syn::punctuated::Punctuated; -use syn::{Expr, ExprLit, ExprPath, Field, Lit, LitStr, Path, Token, Type}; +use syn::{Attribute, Expr, ExprLit, ExprPath, Field, Lit, LitStr, Path, Token, Type}; pub fn bail(span: Span, message: &str) -> syn::Result { Err(syn::Error::new(span, message)) @@ -28,14 +28,10 @@ pub fn into_litstr(expr: Expr) -> Option { /// Given a struct field, this finds all attributes like `#[doc = "bla"]`, /// unindents, concatenates and returns them. -pub fn docstring(field: &Field) -> syn::Result { +pub fn docstring(attributes: &[Attribute]) -> syn::Result { let mut lines = vec![]; - for attr in field - .attrs - .iter() - .filter(|attr| attr.path().is_ident("doc")) - { + for attr in attributes.iter().filter(|attr| attr.path().is_ident("doc")) { if let Some(lit) = litstr(&attr.meta.require_name_value()?.value) { let value = lit.value(); let value = value @@ -70,16 +66,19 @@ impl Parse for AttributeArgument { /// Given a struct field, this finds all arguments of the form `#[path(key)]` /// and `#[path(key = value)]`. Multiple arguments may be specified in a single /// annotation, e.g. `#[foo(bar, baz = true)]`. -pub fn attribute_arguments(field: &Field, path: &str) -> syn::Result> { - let mut attrs = vec![]; +pub fn attribute_arguments( + attributes: &[Attribute], + path: &str, +) -> syn::Result> { + let mut attr_args = vec![]; - for attr in field.attrs.iter().filter(|attr| attr.path().is_ident(path)) { + for attr in attributes.iter().filter(|attr| attr.path().is_ident(path)) { let args = attr.parse_args_with(Punctuated::::parse_terminated)?; - attrs.extend(args); + attr_args.extend(args); } - Ok(attrs) + Ok(attr_args) } pub enum SerdeDefault { @@ -102,7 +101,7 @@ impl SerdeDefault { /// Find `#[serde(default)]` or `#[serde(default = "bla")]`. pub fn serde_default(field: &Field) -> syn::Result> { - for arg in attribute_arguments(field, "serde")? { + for arg in attribute_arguments(&field.attrs, "serde")? { if arg.path.is_ident("default") { if let Some(value) = arg.value { if let Some(path) = into_litstr(value) { diff --git a/cove/src/ui/key_bindings.rs b/cove/src/ui/key_bindings.rs index a4de6da..679c929 100644 --- a/cove/src/ui/key_bindings.rs +++ b/cove/src/ui/key_bindings.rs @@ -3,7 +3,7 @@ use std::convert::Infallible; use cove_config::{Config, Keys}; -use cove_input::{InputEvent, KeyBinding, KeyGroup}; +use cove_input::{InputEvent, KeyBinding, KeyBindingInfo, KeyGroupInfo}; use crossterm::style::Stylize; use toss::widgets::{Either2, Join2, Padding, Text}; use toss::{Style, Styled, Widget, WidgetExt}; @@ -41,16 +41,16 @@ fn render_title(builder: &mut Builder, title: &str) { builder.add_unsel(Text::new(Styled::new(title, style)).first2()); } -fn render_binding(builder: &mut Builder, binding: &KeyBinding, description: &str) { +fn render_binding_info(builder: &mut Builder, binding_info: KeyBindingInfo<'_>) { builder.add_unsel( Join2::horizontal( - Text::new(description) + Text::new(binding_info.description) .with_wrap(false) .padding() .with_right(2) .with_stretch(true) .segment(), - Text::new(format_binding(binding)) + Text::new(format_binding(binding_info.binding)) .with_wrap(false) .segment() .with_fixed(true), @@ -59,9 +59,10 @@ fn render_binding(builder: &mut Builder, binding: &KeyBinding, description: &str ) } -fn render_group(builder: &mut Builder, group: &G) { - for (binding, description) in group.bindings() { - render_binding(builder, binding, description); +fn render_group_info(builder: &mut Builder, group_info: KeyGroupInfo<'_>) { + render_title(builder, group_info.description); + for binding_info in group_info.bindings { + render_binding_info(builder, binding_info); } } @@ -71,29 +72,12 @@ pub fn widget<'a>( ) -> impl Widget + 'a { let mut list_builder = ListBuilder::new(); - render_title(&mut list_builder, "General"); - render_group(&mut list_builder, &config.keys.general); - render_empty(&mut list_builder); - render_title(&mut list_builder, "Scrolling"); - render_group(&mut list_builder, &config.keys.scroll); - render_empty(&mut list_builder); - render_title(&mut list_builder, "Cursor movement"); - render_group(&mut list_builder, &config.keys.cursor); - render_empty(&mut list_builder); - render_title(&mut list_builder, "Editor cursor movement"); - render_group(&mut list_builder, &config.keys.editor.cursor); - render_empty(&mut list_builder); - render_title(&mut list_builder, "Editor actions"); - render_group(&mut list_builder, &config.keys.editor.action); - render_empty(&mut list_builder); - render_title(&mut list_builder, "Room list actions"); - render_group(&mut list_builder, &config.keys.rooms.action); - render_empty(&mut list_builder); - render_title(&mut list_builder, "Tree cursor movement"); - render_group(&mut list_builder, &config.keys.tree.cursor); - render_empty(&mut list_builder); - render_title(&mut list_builder, "Tree actions"); - render_group(&mut list_builder, &config.keys.tree.action); + for group_info in config.keys.groups() { + if !list_builder.is_empty() { + render_empty(&mut list_builder); + } + render_group_info(&mut list_builder, group_info); + } Popup::new(list_builder.build(list), "Key bindings") }