weechat/hooks/
completion.rs1use libc::{c_char, c_int};
2use std::{borrow::Cow, ffi::CStr, os::raw::c_void, ptr};
3
4use weechat_sys::{
5 t_gui_buffer, t_gui_completion, t_weechat_plugin, WEECHAT_RC_ERROR, WEECHAT_RC_OK,
6};
7
8use crate::{buffer::Buffer, hooks::Hook, LossyCString, Weechat};
9
10pub struct Completion {
12 weechat_ptr: *mut t_weechat_plugin,
13 ptr: *mut t_gui_completion,
14}
15
16pub trait CompletionCallback {
20 fn callback(
34 &mut self,
35 weechat: &Weechat,
36 buffer: &Buffer,
37 completion_name: Cow<str>,
38 completion: &Completion,
39 ) -> Result<(), ()>;
40}
41
42impl<T: FnMut(&Weechat, &Buffer, Cow<str>, &Completion) -> Result<(), ()> + 'static>
43 CompletionCallback for T
44{
45 fn callback(
46 &mut self,
47 weechat: &Weechat,
48 buffer: &Buffer,
49 completion_name: Cow<str>,
50 completion: &Completion,
51 ) -> Result<(), ()> {
52 self(weechat, buffer, completion_name, completion)
53 }
54}
55
56#[derive(Clone, Copy)]
58pub enum CompletionPosition {
59 Sorted,
61 Beginning,
63 End,
65}
66
67impl CompletionPosition {
68 pub(crate) fn value(&self) -> &str {
69 match self {
70 CompletionPosition::Sorted => "sort",
71 CompletionPosition::Beginning => "beginning",
72 CompletionPosition::End => "end",
73 }
74 }
75}
76
77impl Completion {
78 pub(crate) fn from_raw(
79 weechat: *mut t_weechat_plugin,
80 completion: *mut t_gui_completion,
81 ) -> Completion {
82 Completion {
83 weechat_ptr: weechat,
84 ptr: completion,
85 }
86 }
87
88 pub fn add(&self, word: &str) {
90 self.add_with_options(word, false, CompletionPosition::Sorted)
91 }
92
93 pub fn base_command(&self) -> Cow<str> {
95 self.get_string("base_command")
96 }
97
98 pub fn base_word(&self) -> Cow<str> {
100 self.get_string("base_word")
101 }
102
103 pub fn arguments(&self) -> Cow<str> {
105 self.get_string("args")
106 }
107
108 fn get_string(&self, property_name: &str) -> Cow<str> {
109 let weechat = Weechat::from_ptr(self.weechat_ptr);
110
111 let get_string = weechat.get().hook_completion_get_string.unwrap();
112
113 let property_name = LossyCString::new(property_name);
114
115 unsafe {
116 let ret = get_string(self.ptr, property_name.as_ptr());
117 CStr::from_ptr(ret).to_string_lossy()
118 }
119 }
120
121 pub fn add_with_options(&self, word: &str, is_nick: bool, position: CompletionPosition) {
132 let weechat = Weechat::from_ptr(self.weechat_ptr);
133
134 let hook_completion_list_add = weechat.get().hook_completion_list_add.unwrap();
135
136 let word = LossyCString::new(word);
137 let method = LossyCString::new(position.value());
138
139 unsafe {
140 hook_completion_list_add(self.ptr, word.as_ptr(), is_nick as i32, method.as_ptr());
141 }
142 }
143}
144
145pub struct CompletionHook {
147 _hook: Hook,
148 _hook_data: Box<CompletionHookData>,
149}
150
151struct CompletionHookData {
152 #[allow(clippy::type_complexity)]
153 callback: Box<dyn CompletionCallback>,
154 weechat_ptr: *mut t_weechat_plugin,
155}
156
157impl CompletionHook {
158 pub fn new(
168 completion_item: &str,
169 description: &str,
170 callback: impl CompletionCallback + 'static,
171 ) -> Result<CompletionHook, ()> {
172 unsafe extern "C" fn c_hook_cb(
173 pointer: *const c_void,
174 _data: *mut c_void,
175 completion_item: *const c_char,
176 buffer: *mut t_gui_buffer,
177 completion: *mut t_gui_completion,
178 ) -> c_int {
179 let hook_data: &mut CompletionHookData = { &mut *(pointer as *mut CompletionHookData) };
180 let cb = &mut hook_data.callback;
181 let weechat = Weechat::from_ptr(hook_data.weechat_ptr);
182 let buffer = weechat.buffer_from_ptr(buffer);
183
184 let completion_item = CStr::from_ptr(completion_item).to_string_lossy();
185
186 let ret = cb.callback(
187 &weechat,
188 &buffer,
189 completion_item,
190 &Completion::from_raw(hook_data.weechat_ptr, completion),
191 );
192
193 if let Ok(()) = ret {
194 WEECHAT_RC_OK
195 } else {
196 WEECHAT_RC_ERROR
197 }
198 }
199
200 Weechat::check_thread();
201 let weechat = unsafe { Weechat::weechat() };
202
203 let data = Box::new(CompletionHookData {
204 callback: Box::new(callback),
205 weechat_ptr: weechat.ptr,
206 });
207
208 let data_ref = Box::leak(data);
209 let hook_completion = weechat.get().hook_completion.unwrap();
210
211 let completion_item = LossyCString::new(completion_item);
212 let description = LossyCString::new(description);
213
214 let hook_ptr = unsafe {
215 hook_completion(
216 weechat.ptr,
217 completion_item.as_ptr(),
218 description.as_ptr(),
219 Some(c_hook_cb),
220 data_ref as *const _ as *const c_void,
221 ptr::null_mut(),
222 )
223 };
224
225 let hook_data = unsafe { Box::from_raw(data_ref) };
226
227 if hook_ptr.is_null() {
228 return Err(());
229 }
230
231 let hook = Hook {
232 ptr: hook_ptr,
233 weechat_ptr: weechat.ptr,
234 };
235
236 Ok(CompletionHook {
237 _hook: hook,
238 _hook_data: hook_data,
239 })
240 }
241}