1use std::num::ParseFloatError;
2use std::result;
3use std::sync::Arc;
4
5use neovim_lib::neovim_api::Tabpage;
6use neovim_lib::{UiOption, Value};
7
8use crate::shell;
9use crate::ui::UiMutex;
10
11use rmpv;
12use crate::value::ValueMapExt;
13
14use super::handler::NvimHandler;
15use super::repaint_mode::RepaintMode;
16
17macro_rules! try_str {
18 ($exp:expr) => {
19 $exp.as_str()
20 .ok_or_else(|| "Can't convert argument to string".to_owned())?
21 };
22}
23
24macro_rules! try_int {
25 ($expr:expr) => {
26 $expr
27 .as_i64()
28 .ok_or_else(|| "Can't convert argument to int".to_owned())?
29 };
30}
31
32macro_rules! try_uint {
33 ($exp:expr) => {
34 $exp.as_u64()
35 .ok_or_else(|| "Can't convert argument to u64".to_owned())?
36 };
37}
38
39macro_rules! try_bool {
40 ($exp:expr) => {
41 $exp.as_bool()
42 .ok_or_else(|| "Can't convert argument to bool".to_owned())?
43 };
44}
45
46macro_rules! map_array {
47 ($arg:expr, $err:expr, | $item:ident | $exp:expr) => {
48 $arg.as_array().ok_or_else(|| $err).and_then(|items| {
49 items
50 .iter()
51 .map(|$item| $exp)
52 .collect::<Result<Vec<_>, _>>()
53 })
54 };
55 ($arg:expr, $err:expr, | $item:ident | { $exp:expr }) => {
56 $arg.as_array().ok_or_else(|| $err).and_then(|items| {
57 items
58 .iter()
59 .map(|$item| $exp)
60 .collect::<Result<Vec<_>, _>>()
61 })
62 };
63}
64
65macro_rules! try_arg {
66 ($value:expr,val_ref) => {
67 &$value
68 };
69 ($value:expr,val) => {
70 $value
71 };
72 ($value:expr,bool) => {
73 try_bool!($value)
74 };
75 ($value:expr,uint) => {
76 try_uint!($value)
77 };
78 ($value:expr,int) => {
79 try_int!($value)
80 };
81 ($value:expr,float) => {
82 try_float!($value)
83 };
84 ($value:expr,str) => {
85 match $value {
86 Value::String(s) => {
87 if let Some(s) = s.into_str() {
88 Ok(s)
89 } else {
90 Err("Can't convert to utf8 string".to_owned())
91 }
92 }
93 _ => Err("Can't convert to string".to_owned()),
94 }?
95 };
96 ($value:expr,ext) => {
97 rmpv::ext::from_value($value).map_err(|e| e.to_string())?
98 };
99}
100
101macro_rules! call {
102 ($s:ident -> $c:ident ($args:ident : $($arg_type:ident),+ )) => (
103 {
104 let mut iter = $args.into_iter();
105 $s.$c($(
106 try_arg!(iter.next()
107 .ok_or_else(|| format!("No such argument for {}", stringify!($c)))?,
108 $arg_type
109 )
110 ),+ )
111 }
112 )
113}
114
115pub enum NvimCommand {
116 ToggleSidebar,
117 ShowProjectView,
118 Transparency(f64, f64),
119 PreferDarkTheme(bool),
120}
121
122pub fn call_gui_event(
123 ui: &mut shell::State,
124 method: &str,
125 args: Vec<Value>,
126) -> result::Result<(), String> {
127 match method {
128 "Font" => call!(ui->set_font(args: str)),
129 "FontFeatures" => call!(ui->set_font_features(args: str)),
130 "Linespace" => call!(ui->set_line_space(args: str)),
131 "Clipboard" => match try_str!(args[0]) {
132 "Set" => match try_str!(args[1]) {
133 "*" => ui.clipboard_primary_set(try_str!(args[2])),
134 _ => ui.clipboard_clipboard_set(try_str!(args[2])),
135 },
136 opt => error!("Unknown option {}", opt),
137 },
138 "Option" => match try_str!(args[0]) {
139 "Popupmenu" => ui
140 .nvim()
141 .ok_or_else(|| "Nvim not initialized".to_owned())
142 .and_then(|mut nvim| {
143 nvim.set_option(UiOption::ExtPopupmenu(try_uint!(args[1]) == 1))
144 .map_err(|e| e.to_string())
145 })?,
146 "Tabline" => ui
147 .nvim()
148 .ok_or_else(|| "Nvim not initialized".to_owned())
149 .and_then(|mut nvim| {
150 nvim.set_option(UiOption::ExtTabline(try_uint!(args[1]) == 1))
151 .map_err(|e| e.to_string())
152 })?,
153 "Cmdline" => ui
154 .nvim()
155 .ok_or_else(|| "Nvim not initialized".to_owned())
156 .and_then(|mut nvim| {
157 nvim.set_option(UiOption::ExtCmdline(try_uint!(args[1]) == 1))
158 .map_err(|e| e.to_string())?;
159 nvim.set_option(UiOption::ExtWildmenu(try_uint!(args[1]) == 1))
160 .map_err(|e| e.to_string())
161 })?,
162 opt => error!("Unknown option {}", opt),
163 },
164 "Command" => {
165 match try_str!(args[0]) {
166 "ToggleSidebar" => ui.on_command(NvimCommand::ToggleSidebar),
167 "ShowProjectView" => ui.on_command(NvimCommand::ShowProjectView),
168 "Transparency" => ui.on_command(NvimCommand::Transparency(
169 try_str!(args.get(1).cloned().unwrap_or_else(|| "1.0".into()))
170 .parse()
171 .map_err(|e: ParseFloatError| e.to_string())?,
172 try_str!(args.get(2).cloned().unwrap_or_else(|| "1.0".into()))
173 .parse()
174 .map_err(|e: ParseFloatError| e.to_string())?,
175 )),
176 "PreferDarkTheme" => {
177 let prefer_dark_theme =
178 match try_str!(args.get(1).cloned().unwrap_or_else(|| Value::from("off"))) {
179 "on" => true,
180 _ => false,
181 };
182
183 ui.on_command(NvimCommand::PreferDarkTheme(prefer_dark_theme))
184 }
185 "SetCursorBlink" => {
186 let blink_count =
187 match try_str!(args.get(1).cloned().unwrap_or_else(|| Value::from(-1)))
188 .parse::<i32>()
189 {
190 Ok(val) => val,
191 Err(_) => -1,
192 };
193 ui.set_cursor_blink(blink_count);
194 }
195 _ => error!("Unknown command"),
196 };
197 }
198 _ => return Err(format!("Unsupported event {}({:?})", method, args)),
199 }
200 Ok(())
201}
202
203pub fn call_gui_request(
204 ui: &Arc<UiMutex<shell::State>>,
205 method: &str,
206 args: &Vec<Value>,
207) -> result::Result<Value, Value> {
208 match method {
209 "Clipboard" => {
210 match try_str!(args[0]) {
211 "Get" => {
212 let clipboard = {
216 let ui = &mut ui.borrow_mut();
217 match try_str!(args[1]) {
218 "*" => ui.clipboard_primary.clone(),
219 _ => ui.clipboard_clipboard.clone(),
220 }
221 };
222 let t = clipboard.wait_for_text().unwrap_or_else(|| String::new().into());
223 Ok(Value::Array(
224 t.split('\n').map(|s| s.into()).collect::<Vec<Value>>(),
225 ))
226 }
227 opt => {
228 error!("Unknown option {}", opt);
229 Err(Value::Nil)
230 }
231 }
232 }
233 _ => Err(Value::String(
234 format!("Unsupported request {}({:?})", method, args).into(),
235 )),
236 }
237}
238
239pub fn call(
240 ui: &mut shell::State,
241 method: &str,
242 args: Vec<Value>,
243) -> result::Result<RepaintMode, String> {
244 let repaint_mode = match method {
245 "grid_line" => call!(ui->grid_line(args: uint, uint, uint, ext)),
246 "grid_clear" => call!(ui->grid_clear(args: uint)),
247 "grid_destroy" => call!(ui->grid_destroy(args: uint)),
248 "grid_cursor_goto" => call!(ui->grid_cursor_goto(args: uint, uint, uint)),
249 "grid_scroll" => call!(ui->grid_scroll(args: uint, uint, uint, uint, uint, int, int)),
250 "grid_resize" => call!(ui->grid_resize(args: uint, uint, uint)),
251 "default_colors_set" => call!(ui->default_colors_set(args: int, int, int, int, int)),
252 "hl_attr_define" => call!(ui->hl_attr_define(args: uint, ext, val_ref, ext)),
253 "mode_change" => call!(ui->on_mode_change(args: str, uint)),
254 "mouse_on" => ui.on_mouse(true),
255 "mouse_off" => ui.on_mouse(false),
256 "busy_start" => ui.on_busy(true),
257 "busy_stop" => ui.on_busy(false),
258 "popupmenu_show" => {
259 let menu_items = map_array!(args[0], "Error get menu list array", |item| map_array!(
260 item,
261 "Error get menu item array",
262 |col| col.as_str().ok_or("Error get menu column")
263 ))?;
264
265 ui.popupmenu_show(
266 &CompleteItem::map(&menu_items),
267 try_int!(args[1]),
268 try_uint!(args[2]),
269 try_uint!(args[3]),
270 )
271 }
272 "popupmenu_hide" => ui.popupmenu_hide(),
273 "popupmenu_select" => call!(ui->popupmenu_select(args: int)),
274 "tabline_update" => {
275 let tabs_out = map_array!(args[1], "Error get tabline list".to_owned(), |tab| tab
276 .as_map()
277 .ok_or_else(|| "Error get map for tab".to_owned())
278 .and_then(|tab_map| tab_map.to_attrs_map())
279 .map(|tab_attrs| {
280 let name_attr = tab_attrs
281 .get("name")
282 .and_then(|n| n.as_str().map(|s| s.to_owned()));
283 let tab_attr = tab_attrs
284 .get("tab")
285 .map(|&tab_id| Tabpage::new(tab_id.clone()))
286 .unwrap();
287
288 (tab_attr, name_attr)
289 }))?;
290 ui.tabline_update(Tabpage::new(args[0].clone()), tabs_out)
291 }
292 "mode_info_set" => call!(ui->mode_info_set(args: bool, ext)),
293 "option_set" => call!(ui->option_set(args: str, val)),
294 "cmdline_show" => call!(ui->cmdline_show(args: ext, uint, str, str, uint, uint)),
295 "cmdline_block_show" => call!(ui->cmdline_block_show(args: ext)),
296 "cmdline_block_append" => call!(ui->cmdline_block_append(args: ext)),
297 "cmdline_hide" => call!(ui->cmdline_hide(args: uint)),
298 "cmdline_block_hide" => ui.cmdline_block_hide(),
299 "cmdline_pos" => call!(ui->cmdline_pos(args: uint, uint)),
300 "cmdline_special_char" => call!(ui->cmdline_special_char(args: str, bool, uint)),
301 "wildmenu_show" => call!(ui->wildmenu_show(args: ext)),
302 "wildmenu_hide" => ui.wildmenu_hide(),
303 "wildmenu_select" => call!(ui->wildmenu_select(args: int)),
304 _ => {
305 warn!("Event {}({:?})", method, args);
306 RepaintMode::Nothing
307 }
308 };
309
310 Ok(repaint_mode)
311}
312
313pub fn remove_or_delay_uneeded_events(handler: &NvimHandler, params: &mut Vec<Value>) {
322 let mut show_popup_finded = false;
323 let mut to_remove = Vec::new();
324 let mut delayed_hide_event = None;
325
326 for (idx, val) in params.iter().enumerate().rev() {
327 if let Some(args) = val.as_array() {
328 match args[0].as_str() {
329 Some("popupmenu_show") => {
330 show_popup_finded = true;
331 handler.remove_scheduled_redraw_event();
332 }
333 Some("popupmenu_hide") if !show_popup_finded && delayed_hide_event.is_none() => {
334 to_remove.push(idx);
335 delayed_hide_event = Some(idx);
336 handler.remove_scheduled_redraw_event();
337 }
338 Some("popupmenu_hide") => {
339 to_remove.push(idx);
340 }
341 _ => (),
342 }
343 }
344 }
345
346 to_remove.iter().for_each(|&idx| {
347 let ev = params.remove(idx);
348 if let Some(delayed_hide_event_idx) = delayed_hide_event {
349 if delayed_hide_event_idx == idx {
350 handler.schedule_redraw_event(ev);
351 }
352 }
353 });
354}
355
356pub struct CompleteItem<'a> {
357 pub word: &'a str,
358 pub kind: &'a str,
359 pub menu: &'a str,
360 pub info: &'a str,
361}
362
363impl<'a> CompleteItem<'a> {
364 fn map(menu: &'a [Vec<&str>]) -> Vec<Self> {
365 menu.iter()
366 .map(|menu| CompleteItem {
367 word: menu[0],
368 kind: menu[1],
369 menu: menu[2],
370 info: menu[3],
371 })
372 .collect()
373 }
374}