Provide list of key groups in config crate

This also fixes the f1 menu not displaying the room group.
This commit is contained in:
Joscha 2023-04-30 22:12:21 +02:00
parent 48279e879a
commit 101d36cd45
6 changed files with 93 additions and 50 deletions

View file

@ -1,4 +1,4 @@
use cove_input::{KeyBinding, KeyGroup}; use cove_input::{KeyBinding, KeyGroup, KeyGroupInfo};
use serde::Deserialize; use serde::Deserialize;
use crate::doc::Document; use crate::doc::Document;
@ -110,6 +110,7 @@ default_bindings! {
} }
#[derive(Debug, Deserialize, Document, KeyGroup)] #[derive(Debug, Deserialize, Document, KeyGroup)]
/// General.
pub struct General { pub struct General {
/// Quit cove. /// Quit cove.
#[serde(default = "default::general::exit")] #[serde(default = "default::general::exit")]
@ -133,6 +134,7 @@ pub struct General {
} }
#[derive(Debug, Deserialize, Document, KeyGroup)] #[derive(Debug, Deserialize, Document, KeyGroup)]
/// Scrolling.
pub struct Scroll { pub struct Scroll {
/// Scroll up one line. /// Scroll up one line.
#[serde(default = "default::scroll::up_line")] #[serde(default = "default::scroll::up_line")]
@ -158,6 +160,7 @@ pub struct Scroll {
} }
#[derive(Debug, Deserialize, Document, KeyGroup)] #[derive(Debug, Deserialize, Document, KeyGroup)]
/// Cursor movement.
pub struct Cursor { pub struct Cursor {
/// Move up. /// Move up.
#[serde(default = "default::cursor::up")] #[serde(default = "default::cursor::up")]
@ -174,6 +177,7 @@ pub struct Cursor {
} }
#[derive(Debug, Deserialize, Document, KeyGroup)] #[derive(Debug, Deserialize, Document, KeyGroup)]
/// Editor cursor movement.
pub struct EditorCursor { pub struct EditorCursor {
/// Move left. /// Move left.
#[serde(default = "default::editor_cursor::left")] #[serde(default = "default::editor_cursor::left")]
@ -202,6 +206,7 @@ pub struct EditorCursor {
} }
#[derive(Debug, Deserialize, Document, KeyGroup)] #[derive(Debug, Deserialize, Document, KeyGroup)]
/// Editor actions.
pub struct EditorAction { pub struct EditorAction {
/// Delete before cursor. /// Delete before cursor.
#[serde(default = "default::editor_action::backspace")] #[serde(default = "default::editor_action::backspace")]
@ -229,6 +234,7 @@ pub struct Editor {
} }
#[derive(Debug, Deserialize, Document, KeyGroup)] #[derive(Debug, Deserialize, Document, KeyGroup)]
/// Room list actions.
pub struct RoomsAction { pub struct RoomsAction {
/// Connect to selected room. /// Connect to selected room.
#[serde(default = "default::rooms_action::connect")] #[serde(default = "default::rooms_action::connect")]
@ -267,6 +273,7 @@ pub struct Rooms {
} }
#[derive(Debug, Deserialize, Document, KeyGroup)] #[derive(Debug, Deserialize, Document, KeyGroup)]
/// Room actions.
pub struct RoomAction { pub struct RoomAction {
/// Authenticate. /// Authenticate.
#[serde(default = "default::room_action::authenticate")] #[serde(default = "default::room_action::authenticate")]
@ -293,6 +300,7 @@ pub struct Room {
} }
#[derive(Debug, Deserialize, Document, KeyGroup)] #[derive(Debug, Deserialize, Document, KeyGroup)]
/// Tree cursor movement.
pub struct TreeCursor { pub struct TreeCursor {
/// Move to above sibling. /// Move to above sibling.
#[serde(default = "default::tree_cursor::to_above_sibling")] #[serde(default = "default::tree_cursor::to_above_sibling")]
@ -322,6 +330,7 @@ pub struct TreeCursor {
} }
#[derive(Debug, Deserialize, Document, KeyGroup)] #[derive(Debug, Deserialize, Document, KeyGroup)]
/// Tree actions.
pub struct TreeAction { pub struct TreeAction {
/// Reply to message, inline if possible. /// Reply to message, inline if possible.
#[serde(default = "default::tree_action::reply")] #[serde(default = "default::tree_action::reply")]
@ -393,3 +402,19 @@ pub struct Keys {
#[document(no_default)] #[document(no_default)]
pub tree: Tree, pub tree: Tree,
} }
impl Keys {
pub fn groups(&self) -> Vec<KeyGroupInfo<'_>> {
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),
]
}
}

View file

@ -10,9 +10,33 @@ use toss::{Frame, Terminal, WidthDb};
pub use crate::keys::*; 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. /// A group of related key bindings.
pub trait KeyGroup { pub trait KeyGroup {
fn bindings(&self) -> Vec<(&KeyBinding, &'static str)>; const DESCRIPTION: &'static str;
fn bindings(&self) -> Vec<KeyBindingInfo<'_>>;
}
pub struct KeyGroupInfo<'a> {
pub name: &'static str,
pub description: &'static str,
pub bindings: Vec<KeyBindingInfo<'a>>,
}
impl<'a> KeyGroupInfo<'a> {
pub fn new<G: KeyGroup>(name: &'static str, group: &'a G) -> Self {
Self {
name,
description: G::DESCRIPTION,
bindings: group.bindings(),
}
}
} }
pub struct InputEvent<'a> { pub struct InputEvent<'a> {

View file

@ -16,12 +16,12 @@ struct FieldInfo {
impl FieldInfo { impl FieldInfo {
fn initialize_from_field(&mut self, field: &Field) -> syn::Result<()> { 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() { if !docstring.is_empty() {
self.description = Some(docstring); 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") { if arg.path.is_ident("metavar") {
// Parse `#[document(metavar = "bla")]` // Parse `#[document(metavar = "bla")]`
if let Some(metavar) = arg.value.and_then(util::into_litstr) { if let Some(metavar) = arg.value.and_then(util::into_litstr) {

View file

@ -19,11 +19,16 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> {
return util::bail(input.span(), "must be a struct"); 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 bindings = vec![];
let mut defaults = vec![]; let mut defaults = vec![];
for field in &data.fields { for field in &data.fields {
if let Some(field_ident) = &field.ident { 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 = decapitalize(&docstring);
let description = description.strip_suffix('.').unwrap_or(&description); let description = description.strip_suffix('.').unwrap_or(&description);
@ -34,7 +39,11 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> {
let default_value = default.value(); let default_value = default.value();
bindings.push(quote! { bindings.push(quote! {
(&self.#field_ident, #description) ::cove_input::KeyBindingInfo {
name: #field_name,
binding: &self.#field_ident,
description: #description
}
}); });
defaults.push(quote! { defaults.push(quote! {
@ -46,7 +55,9 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> {
let ident = input.ident; let ident = input.ident;
Ok(quote! { Ok(quote! {
impl ::cove_input::KeyGroup for #ident { 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![ vec![
#( #bindings, )* #( #bindings, )*
] ]

View file

@ -2,7 +2,7 @@ use proc_macro2::{Span, TokenStream};
use quote::quote; use quote::quote;
use syn::parse::Parse; use syn::parse::Parse;
use syn::punctuated::Punctuated; 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<T>(span: Span, message: &str) -> syn::Result<T> { pub fn bail<T>(span: Span, message: &str) -> syn::Result<T> {
Err(syn::Error::new(span, message)) Err(syn::Error::new(span, message))
@ -28,14 +28,10 @@ pub fn into_litstr(expr: Expr) -> Option<LitStr> {
/// Given a struct field, this finds all attributes like `#[doc = "bla"]`, /// Given a struct field, this finds all attributes like `#[doc = "bla"]`,
/// unindents, concatenates and returns them. /// unindents, concatenates and returns them.
pub fn docstring(field: &Field) -> syn::Result<String> { pub fn docstring(attributes: &[Attribute]) -> syn::Result<String> {
let mut lines = vec![]; let mut lines = vec![];
for attr in field for attr in attributes.iter().filter(|attr| attr.path().is_ident("doc")) {
.attrs
.iter()
.filter(|attr| attr.path().is_ident("doc"))
{
if let Some(lit) = litstr(&attr.meta.require_name_value()?.value) { if let Some(lit) = litstr(&attr.meta.require_name_value()?.value) {
let value = lit.value(); let value = lit.value();
let value = 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)]` /// 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 /// and `#[path(key = value)]`. Multiple arguments may be specified in a single
/// annotation, e.g. `#[foo(bar, baz = true)]`. /// annotation, e.g. `#[foo(bar, baz = true)]`.
pub fn attribute_arguments(field: &Field, path: &str) -> syn::Result<Vec<AttributeArgument>> { pub fn attribute_arguments(
let mut attrs = vec![]; attributes: &[Attribute],
path: &str,
) -> syn::Result<Vec<AttributeArgument>> {
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 = let args =
attr.parse_args_with(Punctuated::<AttributeArgument, Token![,]>::parse_terminated)?; attr.parse_args_with(Punctuated::<AttributeArgument, Token![,]>::parse_terminated)?;
attrs.extend(args); attr_args.extend(args);
} }
Ok(attrs) Ok(attr_args)
} }
pub enum SerdeDefault { pub enum SerdeDefault {
@ -102,7 +101,7 @@ impl SerdeDefault {
/// Find `#[serde(default)]` or `#[serde(default = "bla")]`. /// Find `#[serde(default)]` or `#[serde(default = "bla")]`.
pub fn serde_default(field: &Field) -> syn::Result<Option<SerdeDefault>> { pub fn serde_default(field: &Field) -> syn::Result<Option<SerdeDefault>> {
for arg in attribute_arguments(field, "serde")? { for arg in attribute_arguments(&field.attrs, "serde")? {
if arg.path.is_ident("default") { if arg.path.is_ident("default") {
if let Some(value) = arg.value { if let Some(value) = arg.value {
if let Some(path) = into_litstr(value) { if let Some(path) = into_litstr(value) {

View file

@ -3,7 +3,7 @@
use std::convert::Infallible; use std::convert::Infallible;
use cove_config::{Config, Keys}; use cove_config::{Config, Keys};
use cove_input::{InputEvent, KeyBinding, KeyGroup}; use cove_input::{InputEvent, KeyBinding, KeyBindingInfo, KeyGroupInfo};
use crossterm::style::Stylize; use crossterm::style::Stylize;
use toss::widgets::{Either2, Join2, Padding, Text}; use toss::widgets::{Either2, Join2, Padding, Text};
use toss::{Style, Styled, Widget, WidgetExt}; 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()); 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( builder.add_unsel(
Join2::horizontal( Join2::horizontal(
Text::new(description) Text::new(binding_info.description)
.with_wrap(false) .with_wrap(false)
.padding() .padding()
.with_right(2) .with_right(2)
.with_stretch(true) .with_stretch(true)
.segment(), .segment(),
Text::new(format_binding(binding)) Text::new(format_binding(binding_info.binding))
.with_wrap(false) .with_wrap(false)
.segment() .segment()
.with_fixed(true), .with_fixed(true),
@ -59,9 +59,10 @@ fn render_binding(builder: &mut Builder, binding: &KeyBinding, description: &str
) )
} }
fn render_group<G: KeyGroup>(builder: &mut Builder, group: &G) { fn render_group_info(builder: &mut Builder, group_info: KeyGroupInfo<'_>) {
for (binding, description) in group.bindings() { render_title(builder, group_info.description);
render_binding(builder, binding, description); for binding_info in group_info.bindings {
render_binding_info(builder, binding_info);
} }
} }
@ -71,29 +72,12 @@ pub fn widget<'a>(
) -> impl Widget<UiError> + 'a { ) -> impl Widget<UiError> + 'a {
let mut list_builder = ListBuilder::new(); let mut list_builder = ListBuilder::new();
render_title(&mut list_builder, "General"); for group_info in config.keys.groups() {
render_group(&mut list_builder, &config.keys.general); if !list_builder.is_empty() {
render_empty(&mut list_builder); render_empty(&mut list_builder);
render_title(&mut list_builder, "Scrolling"); }
render_group(&mut list_builder, &config.keys.scroll); render_group_info(&mut list_builder, group_info);
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);
Popup::new(list_builder.build(list), "Key bindings") Popup::new(list_builder.build(list), "Key bindings")
} }