Recreate repository due to Git object corruption (all files preserved)
This commit is contained in:
734
INPUT_PIPELINE_MIGRATION.md
Normal file
734
INPUT_PIPELINE_MIGRATION.md
Normal file
@@ -0,0 +1,734 @@
|
||||
# Input Pipeline Migration Guide
|
||||
|
||||
## Goal
|
||||
Migrate `komp_ac_client/src/input_pipeline/` to a generalized `no_std` compatible API in `src/input_pipeline/` that can be used by any TUI application.
|
||||
|
||||
## Current State (komp_ac_client)
|
||||
|
||||
Files in `komp_ac_client/src/input_pipeline/`:
|
||||
- `key_chord.rs` - Uses crossterm::event::{KeyCode, KeyModifiers}
|
||||
- `sequence.rs` - Uses std::time::{Duration, Instant}
|
||||
- `registry.rs` - Uses std::collections::HashMap
|
||||
- `pipeline.rs` - Pure logic
|
||||
- `response.rs` - Pure types
|
||||
|
||||
Dependencies to remove:
|
||||
- `crossterm::event` - Replace with custom types
|
||||
- `std::time` - Replace with alloc-based solution or make optional
|
||||
- `std::collections` - Use alloc or custom structures
|
||||
|
||||
## Target Structure
|
||||
|
||||
```
|
||||
src/input_pipeline/
|
||||
├── mod.rs # Routing only
|
||||
├── key.rs # KeyCode, KeyModifiers enums (no_std)
|
||||
├── chord.rs # KeyChord type (no_std)
|
||||
├── sequence.rs # KeySequence type (no_std)
|
||||
├── key_map.rs # KeyMap: Chord -> Action mapping (no_std)
|
||||
├── key_registry.rs # Registry for storing key bindings (alloc)
|
||||
├── sequence_tracker.rs # Track incomplete sequences (optional with std feature)
|
||||
├── pipeline.rs # Main pipeline logic (no_std)
|
||||
└── response.rs # Response types (no_std)
|
||||
```
|
||||
|
||||
## Step 1: Core Types (no_std)
|
||||
|
||||
### `src/input_pipeline/key.rs`
|
||||
|
||||
Define backend-agnostic KeyCode and KeyModifiers:
|
||||
|
||||
```rust
|
||||
// path_from_the_root: src/input_pipeline/key.rs
|
||||
|
||||
use core::fmt;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum KeyCode {
|
||||
Char(char),
|
||||
Enter,
|
||||
Tab,
|
||||
Esc,
|
||||
Backspace,
|
||||
Delete,
|
||||
Home,
|
||||
End,
|
||||
PageUp,
|
||||
PageDown,
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
F(u8),
|
||||
Null,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
|
||||
pub struct KeyModifiers {
|
||||
pub control: bool,
|
||||
pub alt: bool,
|
||||
pub shift: bool,
|
||||
}
|
||||
|
||||
impl KeyModifiers {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
control: false,
|
||||
alt: false,
|
||||
shift: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn with_control(mut self) -> Self {
|
||||
self.control = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub const fn with_alt(mut self) -> Self {
|
||||
self.alt = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub const fn with_shift(mut self) -> Self {
|
||||
self.shift = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub const fn is_empty(&self) -> bool {
|
||||
!self.control && !self.alt && !self.shift
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `src/input_pipeline/chord.rs`
|
||||
|
||||
Define KeyChord using custom types:
|
||||
|
||||
```rust
|
||||
// path_from_the_root: src/input_pipeline/chord.rs
|
||||
|
||||
use super::key::{KeyCode, KeyModifiers};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct KeyChord {
|
||||
pub code: KeyCode,
|
||||
pub modifiers: KeyModifiers,
|
||||
}
|
||||
|
||||
impl KeyChord {
|
||||
pub const fn new(code: KeyCode, modifiers: KeyModifiers) -> Self {
|
||||
Self { code, modifiers }
|
||||
}
|
||||
|
||||
pub const fn char(c: char) -> Self {
|
||||
Self {
|
||||
code: KeyCode::Char(c),
|
||||
modifiers: KeyModifiers::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display_string(&self) -> alloc::string::String {
|
||||
let mut out = alloc::string::String::new();
|
||||
if self.modifiers.control {
|
||||
out.push_str("Ctrl+");
|
||||
}
|
||||
if self.modifiers.alt {
|
||||
out.push_str("Alt+");
|
||||
}
|
||||
if self.modifiers.shift {
|
||||
out.push_str("Shift+");
|
||||
}
|
||||
match self.code {
|
||||
KeyCode::Char(c) => out.push(c),
|
||||
KeyCode::Enter => out.push_str("Enter"),
|
||||
KeyCode::Tab => out.push_str("Tab"),
|
||||
KeyCode::Esc => out.push_str("Esc"),
|
||||
KeyCode::Backspace => out.push_str("Backspace"),
|
||||
KeyCode::Delete => out.push_str("Delete"),
|
||||
KeyCode::Up => out.push_str("Up"),
|
||||
KeyCode::Down => out.push_str("Down"),
|
||||
KeyCode::Left => out.push_str("Left"),
|
||||
KeyCode::Right => out.push_str("Right"),
|
||||
KeyCode::F(n) => {
|
||||
out.push('F');
|
||||
out.push(char::from_digit(n as u32, 10).unwrap_or('0'));
|
||||
}
|
||||
KeyCode::Home => out.push_str("Home"),
|
||||
KeyCode::End => out.push_str("End"),
|
||||
KeyCode::PageUp => out.push_str("PageUp"),
|
||||
KeyCode::PageDown => out.push_str("PageDown"),
|
||||
KeyCode::Null => out.push_str("Null"),
|
||||
}
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KeyCode> for KeyChord {
|
||||
fn from(code: KeyCode) -> Self {
|
||||
Self {
|
||||
code,
|
||||
modifiers: KeyModifiers::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Step 2: Sequence Types (no_std)
|
||||
|
||||
### `src/input_pipeline/sequence.rs`
|
||||
|
||||
```rust
|
||||
// path_from_the_root: src/input_pipeline/sequence.rs
|
||||
|
||||
use super::chord::KeyChord;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct KeySequence {
|
||||
chords: alloc::vec::Vec<KeyChord>,
|
||||
}
|
||||
|
||||
impl KeySequence {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
chords: alloc::vec::Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_chords(chords: impl IntoIterator<Item = KeyChord>) -> Self {
|
||||
Self {
|
||||
chords: chords.into_iter().collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, chord: KeyChord) {
|
||||
self.chords.push(chord);
|
||||
}
|
||||
|
||||
pub fn chords(&self) -> &[KeyChord] {
|
||||
&self.chords
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.chords.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.chords.is_empty()
|
||||
}
|
||||
|
||||
pub fn starts_with(&self, other: &KeySequence) -> bool {
|
||||
self.chords.starts_with(other.chords())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `src/input_pipeline/key_map.rs`
|
||||
|
||||
```rust
|
||||
// path_from_the_root: src/input_pipeline/key_map.rs
|
||||
|
||||
use super::chord::KeyChord;
|
||||
use super::sequence::KeySequence;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum KeyMapEntry<Action> {
|
||||
Chord(KeyChord, Action),
|
||||
Sequence(KeySequence, Action),
|
||||
}
|
||||
|
||||
impl<Action: Clone> KeyMapEntry<Action> {
|
||||
pub fn chord(chord: KeyChord, action: Action) -> Self {
|
||||
Self::Chord(chord, action)
|
||||
}
|
||||
|
||||
pub fn sequence(sequence: KeySequence, action: Action) -> Self {
|
||||
Self::Sequence(sequence, action)
|
||||
}
|
||||
|
||||
pub fn action(&self) -> &Action {
|
||||
match self {
|
||||
Self::Chord(_, action) | Self::Sequence(_, action) => action,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Step 3: Response Types (no_std)
|
||||
|
||||
### `src/input_pipeline/response.rs`
|
||||
|
||||
```rust
|
||||
// path_from_the_root: src/input_pipeline/response.rs
|
||||
|
||||
use super::chord::KeyChord;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum PipelineResponse<Action> {
|
||||
Execute(Action),
|
||||
Type(KeyChord),
|
||||
Wait(alloc::vec::Vec<InputHint<Action>>),
|
||||
Cancel,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct InputHint<Action> {
|
||||
pub chord: KeyChord,
|
||||
pub action: Action,
|
||||
}
|
||||
```
|
||||
|
||||
## Step 4: Key Registry (alloc)
|
||||
|
||||
### `src/input_pipeline/key_registry.rs`
|
||||
|
||||
```rust
|
||||
// path_from_the_root: src/input_pipeline/key_registry.rs
|
||||
|
||||
use super::chord::KeyChord;
|
||||
use super::sequence::KeySequence;
|
||||
use super::key_map::KeyMapEntry;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct KeyRegistry<Action> {
|
||||
chords: alloc::collections::HashMap<KeyChord, Action>,
|
||||
sequences: alloc::vec::Vec<(KeySequence, Action)>,
|
||||
}
|
||||
|
||||
impl<Action: Clone> KeyRegistry<Action> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
chords: alloc::collections::HashMap::new(),
|
||||
sequences: alloc::vec::Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_chord(&mut self, chord: KeyChord, action: Action) {
|
||||
self.chords.insert(chord, action);
|
||||
}
|
||||
|
||||
pub fn register_sequence(&mut self, sequence: KeySequence, action: Action) {
|
||||
self.sequences.push((sequence, action));
|
||||
}
|
||||
|
||||
pub fn get_chord(&self, chord: &KeyChord) -> Option<&Action> {
|
||||
self.chords.get(chord)
|
||||
}
|
||||
|
||||
pub fn find_sequences_starting_with(
|
||||
&self,
|
||||
prefix: &KeySequence,
|
||||
) -> alloc::vec::Vec<&KeySequence> {
|
||||
self.sequences
|
||||
.iter()
|
||||
.filter(|(seq, _)| seq.starts_with(prefix))
|
||||
.map(|(seq, _)| seq)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_sequence(&self, sequence: &KeySequence) -> Option<&Action> {
|
||||
self.sequences
|
||||
.iter()
|
||||
.find(|(seq, _)| seq == sequence)
|
||||
.map(|(_, action)| action)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Action: Clone> Default for KeyRegistry<Action> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Step 5: Sequence Tracker (optional, with std feature)
|
||||
|
||||
### `src/input_pipeline/sequence_tracker.rs`
|
||||
|
||||
```rust
|
||||
// path_from_the_root: src/input_pipeline/sequence_tracker.rs
|
||||
|
||||
use super::sequence::KeySequence;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SequenceTracker {
|
||||
current: KeySequence,
|
||||
#[cfg(feature = "std")]
|
||||
last_input: Option<std::time::Instant>,
|
||||
#[cfg(feature = "std")]
|
||||
timeout: std::time::Duration,
|
||||
}
|
||||
|
||||
impl SequenceTracker {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
current: KeySequence::new(),
|
||||
#[cfg(feature = "std")]
|
||||
last_input: None,
|
||||
#[cfg(feature = "std")]
|
||||
timeout: std::time::Duration::from_millis(1000),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub fn with_timeout(timeout_ms: u64) -> Self {
|
||||
Self {
|
||||
current: KeySequence::new(),
|
||||
last_input: None,
|
||||
timeout: std::time::Duration::from_millis(timeout_ms),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.current = KeySequence::new();
|
||||
#[cfg(feature = "std")]
|
||||
{
|
||||
self.last_input = None;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(&mut self, chord: KeyChord) {
|
||||
self.current.push(chord);
|
||||
#[cfg(feature = "std")]
|
||||
{
|
||||
self.last_input = Some(std::time::Instant::now());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub fn is_expired(&self) -> bool {
|
||||
match self.last_input {
|
||||
None => false,
|
||||
Some(last) => last.elapsed() > self.timeout,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
pub fn is_expired(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn current(&self) -> &KeySequence {
|
||||
&self.current
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.current.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SequenceTracker {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Step 6: Pipeline Logic (no_std)
|
||||
|
||||
### `src/input_pipeline/pipeline.rs`
|
||||
|
||||
```rust
|
||||
// path_from_the_root: src/input_pipeline/pipeline.rs
|
||||
|
||||
use super::chord::KeyChord;
|
||||
use super::key_registry::KeyRegistry;
|
||||
use super::sequence::KeySequence;
|
||||
use super::response::{PipelineResponse, InputHint};
|
||||
use super::sequence_tracker::SequenceTracker;
|
||||
|
||||
pub struct KeyPipeline<Action: Clone> {
|
||||
registry: KeyRegistry<Action>,
|
||||
#[cfg(feature = "std")]
|
||||
tracker: SequenceTracker,
|
||||
}
|
||||
|
||||
impl<Action: Clone> KeyPipeline<Action> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
registry: KeyRegistry::new(),
|
||||
#[cfg(feature = "std")]
|
||||
tracker: SequenceTracker::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_chord(&mut self, chord: KeyChord, action: Action) {
|
||||
self.registry.register_chord(chord, action);
|
||||
}
|
||||
|
||||
pub fn register_sequence(&mut self, sequence: KeySequence, action: Action) {
|
||||
self.registry.register_sequence(sequence, action);
|
||||
}
|
||||
|
||||
pub fn process(&mut self, chord: KeyChord) -> PipelineResponse<Action> {
|
||||
#[cfg(feature = "std")]
|
||||
{
|
||||
if self.tracker.is_expired() {
|
||||
self.tracker.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
if !self.tracker.is_empty() {
|
||||
self.tracker.add(chord);
|
||||
let current = self.tracker.current();
|
||||
|
||||
if let Some(action) = self.registry.get_sequence(current) {
|
||||
self.tracker.reset();
|
||||
return PipelineResponse::Execute(action.clone());
|
||||
}
|
||||
|
||||
let matching = self.registry.find_sequences_starting_with(current);
|
||||
if matching.is_empty() {
|
||||
self.tracker.reset();
|
||||
PipelineResponse::Cancel
|
||||
} else {
|
||||
let hints: alloc::vec::Vec<InputHint<Action>> = matching
|
||||
.into_iter()
|
||||
.filter_map(|seq| {
|
||||
if seq.len() > current.len() {
|
||||
let next_chord = seq.chords()[current.len()];
|
||||
self.registry.get_sequence(seq)
|
||||
.map(|action| InputHint {
|
||||
chord: next_chord,
|
||||
action: action.clone(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
PipelineResponse::Wait(hints)
|
||||
}
|
||||
} else {
|
||||
if let Some(action) = self.registry.get_chord(&chord) {
|
||||
PipelineResponse::Execute(action.clone())
|
||||
} else {
|
||||
let one_chord_seq = KeySequence::from_chords([chord]);
|
||||
let matching = self.registry.find_sequences_starting_with(&one_chord_seq);
|
||||
|
||||
if !matching.is_empty() {
|
||||
#[cfg(feature = "std")]
|
||||
{
|
||||
self.tracker.add(chord);
|
||||
let hints: alloc::vec::Vec<InputHint<Action>> = matching
|
||||
.into_iter()
|
||||
.filter_map(|seq| {
|
||||
if seq.len() > 1 {
|
||||
let next_chord = seq.chords()[1];
|
||||
self.registry.get_sequence(seq)
|
||||
.map(|action| InputHint {
|
||||
chord: next_chord,
|
||||
action: action.clone(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
PipelineResponse::Wait(hints)
|
||||
}
|
||||
#[cfg(not(feature = "std"))]
|
||||
{
|
||||
PipelineResponse::Cancel
|
||||
}
|
||||
} else {
|
||||
PipelineResponse::Type(chord)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Action: Clone> Default for KeyPipeline<Action> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Step 7: Module Routing
|
||||
|
||||
### `src/input_pipeline/mod.rs`
|
||||
|
||||
```rust
|
||||
// path_from_the_root: src/input_pipeline/mod.rs
|
||||
|
||||
pub mod key;
|
||||
pub mod chord;
|
||||
pub mod sequence;
|
||||
pub mod key_map;
|
||||
pub mod key_registry;
|
||||
pub mod sequence_tracker;
|
||||
pub mod pipeline;
|
||||
pub mod response;
|
||||
|
||||
pub use key::{KeyCode, KeyModifiers};
|
||||
pub use chord::KeyChord;
|
||||
pub use sequence::KeySequence;
|
||||
pub use key_map::KeyMapEntry;
|
||||
pub use key_registry::KeyRegistry;
|
||||
pub use sequence_tracker::SequenceTracker;
|
||||
pub use pipeline::KeyPipeline;
|
||||
pub use response::{PipelineResponse, InputHint};
|
||||
```
|
||||
|
||||
## Step 8: Update lib.rs
|
||||
|
||||
```rust
|
||||
// path_from_the_root: src/lib.rs
|
||||
|
||||
#![no_std]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
pub mod input_pipeline;
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::input_pipeline::*;
|
||||
}
|
||||
```
|
||||
|
||||
## Step 9: Update Cargo.toml
|
||||
|
||||
```toml
|
||||
[package]
|
||||
name = "tui_orchestrator"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = []
|
||||
alloc = []
|
||||
|
||||
[dependencies]
|
||||
|
||||
[dev-dependencies]
|
||||
```
|
||||
|
||||
## Step 10: Tests
|
||||
|
||||
Create `tests/input_pipeline/` directory with tests for each module:
|
||||
|
||||
```
|
||||
tests/input_pipeline/
|
||||
├── key_tests.rs
|
||||
├── chord_tests.rs
|
||||
├── sequence_tests.rs
|
||||
├── registry_tests.rs
|
||||
└── pipeline_tests.rs
|
||||
```
|
||||
|
||||
Example test file:
|
||||
|
||||
```rust
|
||||
// path_from_the_root: tests/input_pipeline/chord_tests.rs
|
||||
|
||||
use tui_orchestrator::input_pipeline::{KeyChord, KeyCode, KeyModifiers};
|
||||
|
||||
#[test]
|
||||
fn test_chord_creation() {
|
||||
let chord = KeyChord::new(
|
||||
KeyCode::Char('a'),
|
||||
KeyModifiers::new().with_control(),
|
||||
);
|
||||
assert_eq!(chord.code, KeyCode::Char('a'));
|
||||
assert!(chord.modifiers.control);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chord_display() {
|
||||
let chord = KeyChord::new(
|
||||
KeyCode::Char('a'),
|
||||
KeyModifiers::new().with_control().with_shift(),
|
||||
);
|
||||
let display = chord.display_string();
|
||||
assert!(display.contains("Ctrl+"));
|
||||
assert!(display.contains("Shift+"));
|
||||
assert!(display.contains('a'));
|
||||
}
|
||||
```
|
||||
|
||||
## Integration with komp_ac_client
|
||||
|
||||
After migration, update `komp_ac_client/Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
tui_orchestrator = { path = "..", features = ["std"] }
|
||||
```
|
||||
|
||||
Then in `komp_ac_client/src/input_pipeline/mod.rs`, replace with:
|
||||
|
||||
```rust
|
||||
// path_from_the_root: komp_ac_client/src/input_pipeline/mod.rs
|
||||
|
||||
pub use tui_orchestrator::input_pipeline::*;
|
||||
|
||||
// Add crossterm conversion trait
|
||||
use crossterm::event::{KeyCode, KeyModifiers as CrosstermModifiers};
|
||||
|
||||
impl From<&crossterm::event::KeyEvent> for KeyChord {
|
||||
fn from(event: &crossterm::event::KeyEvent) -> Self {
|
||||
let code = match event.code {
|
||||
KeyCode::Char(c) => KeyCode::Char(c),
|
||||
KeyCode::Enter => KeyCode::Enter,
|
||||
KeyCode::Tab => KeyCode::Tab,
|
||||
KeyCode::Esc => KeyCode::Esc,
|
||||
KeyCode::Backspace => KeyCode::Backspace,
|
||||
KeyCode::Delete => KeyCode::Delete,
|
||||
KeyCode::Home => KeyCode::Home,
|
||||
KeyCode::End => KeyCode::End,
|
||||
KeyCode::PageUp => KeyCode::PageUp,
|
||||
KeyCode::PageDown => KeyCode::PageDown,
|
||||
KeyCode::Up => KeyCode::Up,
|
||||
KeyCode::Down => KeyCode::Down,
|
||||
KeyCode::Left => KeyCode::Left,
|
||||
KeyCode::Right => KeyCode::Right,
|
||||
KeyCode::F(n) => KeyCode::F(n),
|
||||
KeyCode::Null => KeyCode::Null,
|
||||
};
|
||||
|
||||
let modifiers = KeyModifiers {
|
||||
control: event.modifiers.contains(CrosstermModifiers::CONTROL),
|
||||
alt: event.modifiers.contains(CrosstermModifiers::ALT),
|
||||
shift: event.modifiers.contains(CrosstermModifiers::SHIFT),
|
||||
};
|
||||
|
||||
KeyChord::new(code, modifiers)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Delete the migrated files from `komp_ac_client/src/input_pipeline/`.
|
||||
|
||||
## Benefits
|
||||
|
||||
1. **no_std compatible** - Works on embedded systems and WASM
|
||||
2. **Backend agnostic** - No crossterm/ratatui dependency
|
||||
3. **General purpose** - Any TUI can use this API
|
||||
4. **Type safe** - Strong typing for key codes and modifiers
|
||||
5. **Testable** - Pure functions, easy to unit test
|
||||
6. **Flexible** - Applications define their own Action types
|
||||
|
||||
## Migration Checklist
|
||||
|
||||
- [ ] Create `src/input_pipeline/key.rs`
|
||||
- [ ] Create `src/input_pipeline/chord.rs`
|
||||
- [ ] Create `src/input_pipeline/sequence.rs`
|
||||
- [ ] Create `src/input_pipeline/key_map.rs`
|
||||
- [ ] Create `src/input_pipeline/key_registry.rs`
|
||||
- [ ] Create `src/input_pipeline/sequence_tracker.rs`
|
||||
- [ ] Create `src/input_pipeline/pipeline.rs`
|
||||
- [ ] Create `src/input_pipeline/response.rs`
|
||||
- [ ] Create `src/input_pipeline/mod.rs`
|
||||
- [ ] Update `src/lib.rs`
|
||||
- [ ] Update `src/key/mod.rs` (remove old placeholders or convert)
|
||||
- [ ] Create tests in `tests/input_pipeline/`
|
||||
- [ ] Run tests: `cargo test --no-default-features`
|
||||
- [ ] Run tests with std: `cargo test`
|
||||
- [ ] Update komp_ac_client to use library
|
||||
- [ ] Delete migrated files from komp_ac_client
|
||||
- [ ] Run komp_ac_client tests
|
||||
Reference in New Issue
Block a user