Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/scripts/release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env bash

set -euxo pipefail

TARGET_BRANCH="${1:-main}"

# An overview of how release-please works:
#
# It only runs against the target remote branch of the repository. Any changes made locally will
# not trigger a release.
#
# By default, only commits start with "feat:", "fix:", or "deps:" will trigger a new release.
release-please release-pr \
--token "$(gh auth token)" \
--repo-url rezigned/keymap-rs \
--config-file .github/prerelease-please-config.json \
--manifest-file .github/.release-please-manifest.json \
--target-branch "${TARGET_BRANCH}" \
--debug \
--dry-run
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ on:
branches: ["main"]
paths-ignore:
- '**.md'
- '.github/scripts/**'
pull_request:
branches: ["main"]
paths-ignore:
- '**.md'
- '.github/scripts/**'

env:
CARGO_TERM_COLOR: always
Expand Down
10 changes: 5 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,6 @@ name = "crossterm_derived_config"
path = "examples/crossterm/derived_config.rs"
required-features = ["crossterm", "derive"]

[[example]]
name = "crossterm_modes"
path = "examples/crossterm/modes.rs"
required-features = ["crossterm", "derive"]

[[example]]
name = "termion"
required-features = ["termion"]
Expand All @@ -78,3 +73,8 @@ required-features = ["termion", "derive"]
name = "termion_derived_config"
path = "examples/termion/derived_config.rs"
required-features = ["termion", "derive"]

[[example]]
name = "modes"
path = "examples/modes.rs"
required-features = ["crossterm", "derive"]
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ match config.get(&key) {

### 2. Using External Configuration

You can also load keymaps from external files (e.g., `config.toml`). This is useful for user-configurable keybindings.
Keymaps can also be loaded from external files (e.g., `config.toml`). This is useful for user-configurable keybindings.

**Example `config.toml`:**

Expand All @@ -108,7 +108,7 @@ Jump = { keys = ["j", "up"], description = "Jump with 'j' or up arrow!" }
Quit = { keys = ["@any"], description = "Quit on any key press." }
```

You have two ways to load this configuration:
This configuration can be loaded in two ways:

#### `Config<T>`: Load from File Only

Expand Down Expand Up @@ -143,7 +143,7 @@ let config: DerivedConfig<Action> = toml::from_str(config_str).unwrap();

### 3. Compile-Time Validation

The `keymap_derive` macro validates all key strings at **compile time**, so you get immediate feedback on invalid syntax.
The `keymap_derive` macro validates all key strings at **compile time**, providing immediate feedback on invalid syntax.

**Invalid Key Example:**

Expand All @@ -170,7 +170,7 @@ error: Invalid key "enter2": Parse error at position 5: expect end of input, fou

### 4. Direct Key Parsing

You can also parse key strings directly into a `KeyMap` or a backend-specific key event.
Key strings can also be parsed directly into a `KeyMap` or a backend-specific key event.

```rust
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
Expand Down
56 changes: 0 additions & 56 deletions examples/crossterm/modes.rs

This file was deleted.

41 changes: 38 additions & 3 deletions examples/crossterm/utils.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
use std::io;

#[cfg(feature = "crossterm")]
use crossterm::{cursor, execute, style::Print};
use crossterm::{
event::{read, Event, KeyEvent},
terminal::{disable_raw_mode, enable_raw_mode},
};

#[allow(unused)]
#[allow(dead_code)]
#[cfg(feature = "crossterm")]
pub(crate) fn output<T: std::fmt::Display>() -> impl FnMut(T) -> std::io::Result<()> {
use std::fmt::Display;

use crossterm::terminal::{Clear, ClearType};

let mut stdout = std::io::stdout();
Expand All @@ -19,5 +23,36 @@ pub(crate) fn output<T: std::fmt::Display>() -> impl FnMut(T) -> std::io::Result
}
}

#[allow(dead_code)]
pub(crate) fn print(s: &str) -> bool {
println!("{s}\r");
false
}

#[allow(dead_code)]
pub(crate) fn quit(s: &str) -> bool {
println!("{s}\r");
true
}

#[allow(dead_code)]
pub(crate) fn run<F>(mut f: F) -> io::Result<()>
where
F: FnMut(KeyEvent) -> bool,
{
enable_raw_mode()?;

loop {
if let Event::Key(key) = read()? {
let quit = f(key);
if quit {
break;
}
}
}

disable_raw_mode()
}

#[allow(unused)]
fn main() {}
84 changes: 84 additions & 0 deletions examples/modes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use std::collections::HashMap;

use keymap::DerivedConfig;
use serde::Deserialize;

use crate::crossterm_utils::{print, quit, run};

#[path = "./crossterm/utils.rs"]
mod crossterm_utils;

#[derive(keymap::KeyMap, Deserialize, Debug, Hash, Eq, PartialEq)]
enum HomeAction {
#[key("esc")]
Quit,
#[key("e")]
Edit,
}

#[derive(keymap::KeyMap, Deserialize, Debug, Hash, Eq, PartialEq)]
enum EditAction {
#[key("esc")]
Exit,
}

#[derive(Deserialize, Debug)]
#[serde(untagged)]
enum Actions {
Home(DerivedConfig<HomeAction>),
Edit(DerivedConfig<EditAction>),
}

type Modes = HashMap<String, Actions>;

#[allow(unused)]
pub(crate) const CONFIG: &str = r#"
[home]
Quit = { keys = ["esc", "q"], description = "Quit the app" }
Edit = { keys = ["e"], description = "Enter edit mode" }

[edit]
Exit = { keys = ["esc", "q"], description = "Exit edit mode" }
"#;

fn main() -> std::io::Result<()> {
let modes: Modes = toml::from_str(CONFIG).unwrap();
let mut mode = "home";

println!("mode: {mode}\r");

run(move |key| match mode {
"home" => {
let Some(Actions::Home(config)) = modes.get(mode) else {
return false;
};

match config.get(&key) {
Some(action) => match action {
HomeAction::Quit => quit("quit!"),
HomeAction::Edit => {
mode = "edit";
print("enter edit mode!")
}
},
None => print(&format!("{}", key.code)),
}
}
"edit" => {
let Some(Actions::Edit(config)) = modes.get(mode) else {
return false;
};

match config.get(&key) {
Some(action) => match action {
EditAction::Exit => {
mode = "home";
print("exit edit mode!")
}
},
None => print(&format!("{}", key.code)),
}
}
_ => unreachable!(),
})
}
Loading