Skip to main content

weechat/hooks/
completion.rs

1use 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
10/// A handle to a completion item.
11pub struct Completion {
12    weechat_ptr: *mut t_weechat_plugin,
13    ptr: *mut t_gui_completion,
14}
15
16/// Trait for the completion callback.
17///
18/// A blanket implementation for pure `FnMut` functions exists, if data needs to
19pub trait CompletionCallback {
20    /// Callback that will be called if when a completion is requested.
21    ///
22    /// # Arguments
23    ///
24    /// * `weechat` - A Weechat context.
25    ///
26    /// * `buffer` - The currently active buffer that requested the completion
27    /// to run.
28    ///
29    /// * `completion_name` - The name of the completion.
30    ///
31    /// * `completion` - The completion object that should be populated with
32    /// completion words by the callback.
33    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/// The positions an entry can be added to a completion list.
57#[derive(Clone, Copy)]
58pub enum CompletionPosition {
59    /// Insert the item in a way that keeps the list sorted.
60    Sorted,
61    /// Insert the item at the beginning of the list.
62    Beginning,
63    /// Insert the item at the end of the list.
64    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    /// Add a word for completion, keeping the list sorted.
89    pub fn add(&self, word: &str) {
90        self.add_with_options(word, false, CompletionPosition::Sorted)
91    }
92
93    /// Get the command used in the completion.
94    pub fn base_command(&self) -> Cow<str> {
95        self.get_string("base_command")
96    }
97
98    /// Get the word that is being completed.
99    pub fn base_word(&self) -> Cow<str> {
100        self.get_string("base_word")
101    }
102
103    /// Get the command arguments including the base word.
104    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    /// Add a word to the completion giving the position and wether the word is
122    /// a nick.
123    ///
124    /// # Arguments
125    ///
126    /// * `word` - The word that should be added to the completion.
127    ///
128    /// * `is_nick` - Set if the word is a nick.
129    ///
130    /// * `position` - Set the position where the nick should be added to.
131    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
145/// Hook for a completion item, the hook is removed when the object is dropped.
146pub 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    /// Create a new completion
159    ///
160    /// * `name` - The name of the new completion. After this is created the
161    ///     can be used as `%(name)` when creating commands.
162    ///
163    /// * `description` - The description of the new completion.
164    ///
165    /// * `callback` - A function that will be called when the completion is
166    ///     used, the callback must populate the words for the completion.
167    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}