kak_ui/
lib.rs

1//! Provides a high-level wrapper around [kakoune's JSON-RPC UI](https://github.com/mawww/kakoune/blob/master/doc/json_ui.asciidoc).
2//! This crate doesn't have any opinions on how you choose to communicate with kakoune or how you choose to deserialize/serialize JSON
3//! as long as it is supported by serde.
4//!
5//! The main types to look at here are [`IncomingRequest`] and [`OutgoingRequest`].
6//!
7//! # Examples
8//!
9//! Basic usage:
10//!
11//!```rust
12//! use std::io::{BufRead, BufReader};
13//! use std::process::{Command, Child, Stdio};
14//! use kak_ui::{IncomingRequest, OutgoingRequest};
15//!
16//! let kak_child_process = Command::new("kak")
17//!     .args(&["-ui", "json"])
18//!     .stdout(Stdio::piped())
19//!     .stdin(Stdio::piped())
20//!     .spawn()
21//!     .unwrap();
22//!
23//! let incoming_request: IncomingRequest = serde_json::from_str(
24//!     &BufReader::new(kak_child_process.stdout.unwrap())
25//!         .lines()
26//!         .next()
27//!         .unwrap()
28//!         .unwrap(),
29//! )
30//! .unwrap();
31//!
32//! let outgoing_request = OutgoingRequest::Keys(vec!["<esc>:q<ret>".to_string()]);
33//! serde_json::to_writer(kak_child_process.stdin.unwrap(), &outgoing_request).unwrap();
34//!```
35
36// TODO: Add links to kakoune docs
37
38use serde::{Deserialize, Deserializer, Serialize, Serializer};
39use std::collections::HashMap;
40
41/// A color in kakoune. Currently, this is a newtype wrapper around String.
42#[derive(Debug, Clone, Deserialize)]
43pub struct KakColor(String);
44
45/// An attribute in kakoune
46#[derive(Debug, Clone, Deserialize)]
47#[serde(rename_all = "snake_case")]
48pub enum KakAttribute {
49    Underline,
50    Reverse,
51    Blink,
52    Bold,
53    Dim,
54    Italic,
55    FinalFg,
56    FinalBg,
57    FinalAttr,
58}
59
60/// A kakoune face
61#[derive(Debug, Clone, Deserialize)]
62pub struct KakFace {
63    pub fg: KakColor,
64    pub bg: KakColor,
65    pub attributes: Vec<KakAttribute>,
66}
67
68/// A kakoune atom
69#[derive(Debug, Clone, Deserialize)]
70pub struct KakAtom {
71    pub face: KakFace,
72    pub contents: String,
73}
74
75/// A [`Vec`] of [`KakAtom`]
76pub type KakLine = Vec<KakAtom>;
77
78/// A coordinate in kakoune
79#[derive(Debug, Clone, Deserialize)]
80pub struct KakCoord {
81    pub line: u32,
82    pub column: u32,
83}
84
85/// A incoming request. Recieve this from kakoune's stdout
86#[derive(Debug, Clone)]
87pub enum IncomingRequest {
88    Draw {
89        lines: Vec<KakLine>,
90        default_face: KakFace,
91        padding_face: KakFace,
92    },
93    DrawStatus {
94        status_line: KakLine,
95        mode_line: KakLine,
96        default_face: KakFace,
97    },
98    MenuShow {
99        items: Vec<KakLine>,
100        anchor: KakCoord,
101        selected_item_face: KakFace,
102        menu_face: KakFace,
103        style: String,
104    },
105    MenuSelect {
106        selected: u32,
107    },
108    MenuHide,
109    InfoShow {
110        title: KakLine,
111        content: Vec<KakLine>,
112        anchor: KakCoord,
113        face: KakFace,
114        style: String,
115    },
116    InfoHide,
117    SetCursor {
118        mode: String,
119        coord: KakCoord,
120    },
121    SetUiOptions {
122        options: HashMap<String, String>,
123    },
124    Refresh {
125        force: bool,
126    },
127}
128
129#[derive(Debug, Clone, Deserialize)]
130#[serde(rename_all = "snake_case")]
131#[serde(tag = "method", content = "params")]
132enum RawIncomingRequest {
133    Draw(Vec<KakLine>, KakFace, KakFace),
134    DrawStatus(KakLine, KakLine, KakFace),
135    MenuShow(Vec<KakLine>, KakCoord, KakFace, KakFace, String),
136    MenuSelect((u32,)),
137    MenuHide([(); 0]),
138    InfoShow(KakLine, Vec<KakLine>, KakCoord, KakFace, String),
139    InfoHide([(); 0]),
140    SetCursor(String, KakCoord),
141    SetUiOptions((HashMap<String, String>,)),
142    Refresh((bool,)),
143}
144
145impl<'de> Deserialize<'de> for IncomingRequest {
146    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
147    where
148        D: Deserializer<'de>,
149    {
150        Ok(<JsonRpc<RawIncomingRequest>>::deserialize(deserializer)?
151            .inner
152            .into())
153    }
154}
155
156impl From<RawIncomingRequest> for IncomingRequest {
157    fn from(raw_request: RawIncomingRequest) -> Self {
158        type Raw = RawIncomingRequest;
159        type Processed = IncomingRequest;
160        match raw_request {
161            Raw::Draw(a, b, c) => Processed::Draw {
162                lines: a,
163                default_face: b,
164                padding_face: c,
165            },
166            Raw::DrawStatus(a, b, c) => Processed::DrawStatus {
167                status_line: a,
168                mode_line: b,
169                default_face: c,
170            },
171            Raw::MenuShow(a, b, c, d, e) => Processed::MenuShow {
172                items: a,
173                anchor: b,
174                selected_item_face: c,
175                menu_face: d,
176                style: e,
177            },
178            Raw::MenuSelect((a,)) => Processed::MenuSelect { selected: a },
179            Raw::MenuHide(_) => Processed::MenuHide,
180            Raw::InfoShow(a, b, c, d, e) => Processed::InfoShow {
181                title: a,
182                content: b,
183                anchor: c,
184                face: d,
185                style: e,
186            },
187            Raw::InfoHide(_) => Processed::InfoHide,
188            Raw::SetCursor(a, b) => Processed::SetCursor { mode: a, coord: b },
189            Raw::SetUiOptions((a,)) => Processed::SetUiOptions { options: a },
190            Raw::Refresh((a,)) => Processed::Refresh { force: a },
191        }
192    }
193}
194
195/// A outgoing request. Input this to kakoune via stdin.
196#[derive(Debug, Clone)]
197pub enum OutgoingRequest {
198    Keys(Vec<String>),
199    Resize {
200        rows: u32,
201        columns: u32,
202    },
203    Scroll {
204        amount: u32,
205    },
206    MouseMove {
207        line: u32,
208        column: u32,
209    },
210    MousePress {
211        button: String,
212        line: u32,
213        column: u32,
214    },
215    MouseRelease {
216        button: String,
217        line: u32,
218        column: u32,
219    },
220    MenuSelect {
221        index: u32,
222    },
223}
224
225#[derive(Debug, Clone, Serialize)]
226#[serde(rename_all = "snake_case")]
227#[serde(tag = "method", content = "params")]
228enum RawOutgoingRequest {
229    Keys(Vec<String>),
230    Resize(u32, u32),
231    Scroll((u32,)),
232    MouseMove(u32, u32),
233    MousePress(String, u32, u32),
234    MouseRelease(String, u32, u32),
235    MenuSelect((u32,)),
236}
237
238impl Serialize for OutgoingRequest {
239    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
240    where
241        S: Serializer,
242    {
243        JsonRpc::new(RawOutgoingRequest::from(self.clone())).serialize(serializer)
244    }
245}
246
247impl From<OutgoingRequest> for RawOutgoingRequest {
248    fn from(request: OutgoingRequest) -> Self {
249        type Raw = RawOutgoingRequest;
250        type Processed = OutgoingRequest;
251        match request {
252            Processed::Keys(vec) => Raw::Keys(vec),
253            Processed::Resize {
254                rows: a,
255                columns: b,
256            } => Raw::Resize(a, b),
257            Processed::Scroll { amount: a } => Raw::Scroll((a,)),
258            Processed::MouseMove { line: a, column: b } => Raw::MouseMove(a, b),
259            Processed::MousePress {
260                button: a,
261                line: b,
262                column: c,
263            } => Raw::MousePress(a, b, c),
264            Processed::MouseRelease {
265                button: a,
266                line: b,
267                column: c,
268            } => Raw::MouseRelease(a, b, c),
269            Processed::MenuSelect { index: a } => Raw::MenuSelect((a,)),
270        }
271    }
272}
273
274#[derive(Deserialize, Serialize)]
275struct JsonRpc<T> {
276    jsonrpc: String,
277    #[serde(flatten)]
278    pub inner: T,
279}
280
281impl<T> JsonRpc<T> {
282    fn new(inner: T) -> Self {
283        Self {
284            jsonrpc: "2.0".to_string(),
285            inner,
286        }
287    }
288}