1use 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 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}