rofi_faye/
lib.rs

1// Copyright (c) 2024 fawn
2// SPDX-License-Identifier: Apache-2.0
3
4use copypasta_ext::{
5    copypasta::ClipboardProvider, display::DisplayServer, osc52::Osc52ClipboardContext,
6};
7use faye::prelude::{Context as FayeContext, Parser};
8
9rofi_mode::export_mode!(Mode<'_>);
10
11const DEFAULT_MESSAGE: &str = "faye 0.6.1";
12
13struct Mode<'rofi> {
14    #[allow(dead_code)]
15    api: rofi_mode::Api<'rofi>,
16    faye: FayeContext,
17    last_input: String,
18    entries: Vec<Entry>,
19}
20
21impl<'rofi> rofi_mode::Mode<'rofi> for Mode<'rofi> {
22    const NAME: &'static str = "faye\0";
23
24    fn init(api: rofi_mode::Api<'rofi>) -> Result<Self, ()> {
25        Ok(Self {
26            api,
27            faye: FayeContext::default(),
28            last_input: DEFAULT_MESSAGE.to_owned(),
29            entries: vec![Entry::new(
30                String::from("Add to history"),
31                String::from("Add to history"),
32            )],
33        })
34    }
35
36    fn entries(&mut self) -> usize {
37        self.entries.len()
38    }
39
40    fn entry_content(&self, line: usize) -> rofi_mode::String {
41        (&self.entries[line].output).into()
42    }
43
44    fn entry_icon(&mut self, _line: usize, _height: u32) -> Option<rofi_mode::cairo::Surface> {
45        None
46    }
47
48    fn react(
49        &mut self,
50        event: rofi_mode::Event,
51        input: &mut rofi_mode::String,
52    ) -> rofi_mode::Action {
53        match event {
54            rofi_mode::Event::Cancel { selected: _ } => return rofi_mode::Action::Exit,
55            rofi_mode::Event::Ok { selected, .. } => {
56                if !self.is_history_button(selected) {
57                    self.copy(selected);
58                    println!(
59                        "{} => {}",
60                        self.entries[selected].input, self.entries[selected].output
61                    );
62                    return rofi_mode::Action::Exit;
63                }
64
65                if !self.is_init() {
66                    if let Some(Ok(res)) = self.eval() {
67                        self.entries.push(Entry::new(input.into(), res));
68                    }
69                }
70            }
71            rofi_mode::Event::DeleteEntry { selected } => {
72                if !self.is_history_button(selected) {
73                    self.entries.remove(selected);
74                };
75            }
76            rofi_mode::Event::Complete {
77                selected: Some(selected),
78            } => {
79                input.clear();
80                input.push_str(&self.entries[selected].output);
81            }
82            rofi_mode::Event::Complete { .. }
83            | rofi_mode::Event::CustomCommand { .. }
84            | rofi_mode::Event::CustomInput { .. } => {}
85        }
86        rofi_mode::Action::Reload
87    }
88
89    fn matches(&self, _line: usize, _matcher: rofi_mode::Matcher<'_>) -> bool {
90        true
91    }
92
93    fn message(&mut self) -> rofi_mode::String {
94        match self.eval() {
95            Some(Ok(res)) => rofi_mode::format!("Result: <b>{} => {res}</b>", self.last_input,),
96            Some(Err(err)) => rofi_mode::format!("<span foreground='#ee9598'>Error: {err}</span>"),
97            None => (&self.last_input).into(),
98        }
99    }
100
101    fn preprocess_input(&mut self, input: &str) -> rofi_mode::String {
102        self.last_input = input.to_string();
103        unsafe { rofi_plugin_sys::view::reload() };
104        input.into()
105    }
106}
107
108impl Mode<'_> {
109    fn copy(&mut self, selected: usize) {
110        if let Some(mut ctx) = DisplayServer::select().try_context() {
111            ctx.set_contents((&self.entries[selected].output).into())
112                .unwrap();
113        } else {
114            let mut ctx = Osc52ClipboardContext::new().unwrap();
115            ctx.set_contents((&self.entries[selected].output).into())
116                .unwrap();
117        }
118    }
119
120    fn eval(&mut self) -> Option<Result<String, String>> {
121        if self.is_init() {
122            return None;
123        }
124
125        // let mut faye_ctx = FayeContext::default();
126        let mut parser = Parser::new(&self.last_input);
127
128        let ast = match parser.parse() {
129            Ok(ast) => ast,
130            Err(err) => return Some(Err(err.to_string())),
131        };
132
133        let mut res = vec![];
134
135        for node in ast {
136            match self.faye.eval(&node) {
137                Ok(expr) => res.push(expr),
138                Err(err) => return Some(Err(err.to_string())),
139            }
140        }
141
142        Some(Ok(res
143            .iter()
144            .map(ToString::to_string)
145            .collect::<Vec<_>>()
146            .join(" ")))
147    }
148
149    fn is_init(&self) -> bool {
150        self.last_input.eq(DEFAULT_MESSAGE)
151    }
152
153    fn is_history_button(&self, selected: usize) -> bool {
154        selected.eq(&0)
155    }
156}
157
158struct Entry {
159    input: String,
160    output: String,
161}
162
163impl Entry {
164    fn new(input: String, output: String) -> Self {
165        Self { input, output }
166    }
167}