use libc::{c_char, c_int};
use std::{borrow::Cow, ffi::CStr, os::raw::c_void, ptr};
use weechat_sys::{
t_gui_buffer, t_gui_completion, t_weechat_plugin, WEECHAT_RC_ERROR, WEECHAT_RC_OK,
};
use crate::{buffer::Buffer, hooks::Hook, LossyCString, Weechat};
pub struct Completion {
weechat_ptr: *mut t_weechat_plugin,
ptr: *mut t_gui_completion,
}
pub trait CompletionCallback {
fn callback(
&mut self,
weechat: &Weechat,
buffer: &Buffer,
completion_name: Cow<str>,
completion: &Completion,
) -> Result<(), ()>;
}
impl<T: FnMut(&Weechat, &Buffer, Cow<str>, &Completion) -> Result<(), ()> + 'static>
CompletionCallback for T
{
fn callback(
&mut self,
weechat: &Weechat,
buffer: &Buffer,
completion_name: Cow<str>,
completion: &Completion,
) -> Result<(), ()> {
self(weechat, buffer, completion_name, completion)
}
}
#[derive(Clone, Copy)]
pub enum CompletionPosition {
Sorted,
Beginning,
End,
}
impl CompletionPosition {
pub(crate) fn value(&self) -> &str {
match self {
CompletionPosition::Sorted => "sort",
CompletionPosition::Beginning => "beginning",
CompletionPosition::End => "end",
}
}
}
impl Completion {
pub(crate) fn from_raw(
weechat: *mut t_weechat_plugin,
completion: *mut t_gui_completion,
) -> Completion {
Completion {
weechat_ptr: weechat,
ptr: completion,
}
}
pub fn add(&self, word: &str) {
self.add_with_options(word, false, CompletionPosition::Sorted)
}
pub fn base_command(&self) -> Cow<str> {
self.get_string("base_command")
}
pub fn base_word(&self) -> Cow<str> {
self.get_string("base_word")
}
pub fn arguments(&self) -> Cow<str> {
self.get_string("args")
}
fn get_string(&self, property_name: &str) -> Cow<str> {
let weechat = Weechat::from_ptr(self.weechat_ptr);
let get_string = weechat.get().hook_completion_get_string.unwrap();
let property_name = LossyCString::new(property_name);
unsafe {
let ret = get_string(self.ptr, property_name.as_ptr());
CStr::from_ptr(ret).to_string_lossy()
}
}
pub fn add_with_options(&self, word: &str, is_nick: bool, position: CompletionPosition) {
let weechat = Weechat::from_ptr(self.weechat_ptr);
let hook_completion_list_add = weechat.get().hook_completion_list_add.unwrap();
let word = LossyCString::new(word);
let method = LossyCString::new(position.value());
unsafe {
hook_completion_list_add(self.ptr, word.as_ptr(), is_nick as i32, method.as_ptr());
}
}
}
pub struct CompletionHook {
_hook: Hook,
_hook_data: Box<CompletionHookData>,
}
struct CompletionHookData {
#[allow(clippy::type_complexity)]
callback: Box<dyn CompletionCallback>,
weechat_ptr: *mut t_weechat_plugin,
}
impl CompletionHook {
pub fn new(
completion_item: &str,
description: &str,
callback: impl CompletionCallback + 'static,
) -> Result<CompletionHook, ()> {
unsafe extern "C" fn c_hook_cb(
pointer: *const c_void,
_data: *mut c_void,
completion_item: *const c_char,
buffer: *mut t_gui_buffer,
completion: *mut t_gui_completion,
) -> c_int {
let hook_data: &mut CompletionHookData = { &mut *(pointer as *mut CompletionHookData) };
let cb = &mut hook_data.callback;
let weechat = Weechat::from_ptr(hook_data.weechat_ptr);
let buffer = weechat.buffer_from_ptr(buffer);
let completion_item = CStr::from_ptr(completion_item).to_string_lossy();
let ret = cb.callback(
&weechat,
&buffer,
completion_item,
&Completion::from_raw(hook_data.weechat_ptr, completion),
);
if let Ok(()) = ret {
WEECHAT_RC_OK
} else {
WEECHAT_RC_ERROR
}
}
Weechat::check_thread();
let weechat = unsafe { Weechat::weechat() };
let data = Box::new(CompletionHookData {
callback: Box::new(callback),
weechat_ptr: weechat.ptr,
});
let data_ref = Box::leak(data);
let hook_completion = weechat.get().hook_completion.unwrap();
let completion_item = LossyCString::new(completion_item);
let description = LossyCString::new(description);
let hook_ptr = unsafe {
hook_completion(
weechat.ptr,
completion_item.as_ptr(),
description.as_ptr(),
Some(c_hook_cb),
data_ref as *const _ as *const c_void,
ptr::null_mut(),
)
};
let hook_data = unsafe { Box::from_raw(data_ref) };
if hook_ptr.is_null() {
return Err(());
}
let hook = Hook {
ptr: hook_ptr,
weechat_ptr: weechat.ptr,
};
Ok(CompletionHook {
_hook: hook,
_hook_data: hook_data,
})
}
}