diff --git a/.gitignore b/.gitignore index 026c70b..6458cd9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +/.idea test/bin/ target/ doc/ diff --git a/Cargo.toml b/Cargo.toml index cd69e04..bb56618 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ features = ["dox", "embed-lgpl-docs"] [dependencies] libc = "0.2" bitflags = "1.0" +once_cell = "1.0" [dependencies.gtk-source-sys] path = "./sourceview-sys" @@ -91,3 +92,9 @@ git = "https://github.com/gtk-rs/gio" [dev-dependencies] gir-format-check = "^0.1" + +[dev-dependencies.gtk] +git = "https://github.com/gtk-rs/gtk" + +[dev-dependencies.glib] +git = "https://github.com/gtk-rs/glib" \ No newline at end of file diff --git a/examples/autocomplete_words.rs b/examples/autocomplete_words.rs new file mode 100644 index 0000000..23add56 --- /dev/null +++ b/examples/autocomplete_words.rs @@ -0,0 +1,30 @@ +extern crate gio; +extern crate gtk; +extern crate sourceview; +use gio::prelude::*; +use gtk::prelude::*; +use sourceview::prelude::*; + +use std::env; + +fn main() { + let uiapp = gtk::Application::new(None, gio::ApplicationFlags::FLAGS_NONE) + .expect("Application::new failed"); + uiapp.connect_activate(|app| { + let win = gtk::ApplicationWindow::new(app); + win.set_default_size(320, 200); + win.set_title("Basic example"); + + let sourceview = sourceview::View::new(); + let completion = sourceview::CompletionWords::new(Some("Words"), None); + completion.register(&sourceview.get_buffer().expect("Sourceview has no buffer.")); + sourceview + .get_completion() + .expect("Sourceview has no completion") + .add_provider(&completion); + win.add(&sourceview); + + win.show_all(); + }); + uiapp.run(&env::args().collect::>()); +} diff --git a/examples/custom_autocompletion_subclass.rs b/examples/custom_autocompletion_subclass.rs new file mode 100644 index 0000000..8c72d08 --- /dev/null +++ b/examples/custom_autocompletion_subclass.rs @@ -0,0 +1,168 @@ +extern crate gio; +extern crate glib; +extern crate gtk; +extern crate sourceview; + +use gio::prelude::*; +use glib::glib_object_wrapper; +use glib::subclass::types::ObjectSubclass; +use glib::translate::*; +use gtk::prelude::*; +use sourceview::prelude::*; +use sourceview::subclass::CompletionProviderImpl; + +use glib::subclass::object::ObjectImpl; +use glib::GString; +use sourceview::{CompletionInfo, CompletionProvider}; +use std::env; + +fn main() { + let uiapp = gtk::Application::new(None, gio::ApplicationFlags::FLAGS_NONE) + .expect("Application::new failed"); + uiapp.connect_activate(|app| { + let win = gtk::ApplicationWindow::new(app); + win.set_default_size(320, 200); + win.set_title("Basic example"); + + let sourceview = sourceview::View::new(); + let completion = CustomAutocomplete::new(); + let _ = sourceview + .get_completion() + .expect("Sourceview has no completion") + .add_provider(&completion); + win.add(&sourceview); + + win.show_all(); + }); + uiapp.run(&env::args().collect::>()); +} + +glib::glib_wrapper! { + pub struct CustomAutocomplete(Object, glib::subclass::simple::ClassStruct, CustomAutocompleteClass>) @implements crate::CompletionProvider; + + match fn { + get_type => || imp::CustomAutocomplete::get_type().to_glib(), + } +} + +impl CustomAutocomplete { + fn new() -> CustomAutocomplete { + glib::Object::new(Self::static_type(), &[]) + .expect("Failed to create CustomAutocomplete instance.") + .downcast() + .expect("Created CustomAutocomplete is of wrong type.") + } +} + +mod imp { + use super::*; + use sourceview::CompletionProposal; + + pub struct CustomAutocomplete; + + impl ObjectSubclass for CustomAutocomplete { + const NAME: &'static str = "CustomAutocomplete"; + type ParentType = glib::Object; + type Instance = glib::subclass::simple::InstanceStruct; + type Class = glib::subclass::simple::ClassStruct; + + glib::glib_object_subclass!(); + + fn type_init(type_: &mut glib::subclass::InitializingType) { + type_.add_interface::(); + } + + fn new() -> Self { + CustomAutocomplete + } + } + + impl ObjectImpl for CustomAutocomplete { + glib::glib_object_impl!(); + } + + impl CompletionProviderImpl for CustomAutocomplete { + fn get_name(&self, _obj: &CompletionProvider) -> GString { + GString::from("CustomAutocomplete") + } + + fn get_icon(&self, _obj: &CompletionProvider) -> Option { + None + } + + fn get_icon_name(&self, _obj: &CompletionProvider) -> Option { + None + } + + fn get_gicon(&self, _obg: &CompletionProvider) -> Option { + None + } + + fn populate(&self, obg: &CompletionProvider, context: &sourceview::CompletionContext) { + for i in 1..10 { + let item = sourceview::CompletionItem::new( + &format!("Test {}", i), + &format!("Test {};", i), + None, + Some("This is a test proposal"), + ); + context.add_proposals(obg, &[item.upcast::()], i == 9); + } + } + + fn get_activation(&self, _obg: &CompletionProvider) -> sourceview::CompletionActivation { + sourceview::CompletionActivation::all() + } + + fn provide_match( + &self, + _obg: &CompletionProvider, + _context: &sourceview::CompletionContext, + ) -> bool { + true + } + + fn get_info_widget( + &self, + _obg: &CompletionProvider, + _proposal: &sourceview::CompletionProposal, + ) -> Option { + None + } + + fn update_info( + &self, + _obg: &CompletionProvider, + _proposal: &sourceview::CompletionProposal, + _info: &CompletionInfo, + ) { + } + + fn get_start_iter( + &self, + _obg: &CompletionProvider, + _context: &sourceview::CompletionContext, + _proposal: &sourceview::CompletionProposal, + _iter: >k::TextIter, + ) -> bool { + false + } + + fn activate_proposal( + &self, + _obg: &CompletionProvider, + _proposal: &sourceview::CompletionProposal, + _iter: >k::TextIter, + ) -> bool { + false + } + + fn get_interactive_delay(&self, _obj: &CompletionProvider) -> i32 { + 0 + } + + fn get_priority(&self, _obg: &CompletionProvider) -> i32 { + 1 + } + } +} diff --git a/examples/plain_sourceview.rs b/examples/plain_sourceview.rs new file mode 100644 index 0000000..3cb1dce --- /dev/null +++ b/examples/plain_sourceview.rs @@ -0,0 +1,23 @@ +extern crate gio; +extern crate gtk; +extern crate sourceview; +use gio::prelude::*; +use gtk::prelude::*; + +use std::env; + +fn main() { + let uiapp = gtk::Application::new(None, gio::ApplicationFlags::FLAGS_NONE) + .expect("Application::new failed"); + uiapp.connect_activate(|app| { + let win = gtk::ApplicationWindow::new(app); + win.set_default_size(320, 200); + win.set_title("Basic example"); + + let sourceview = sourceview::View::new(); + win.add(&sourceview); + + win.show_all(); + }); + uiapp.run(&env::args().collect::>()); +} diff --git a/src/lib.rs b/src/lib.rs index 66c0862..4a89a46 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,7 @@ extern crate gobject_sys; extern crate gtk; extern crate gtk_source_sys; extern crate gtk_sys; +extern crate once_cell; extern crate pango; extern crate libc; @@ -56,6 +57,8 @@ pub mod prelude { pub use file_saver::FileSaverExtManual; } +pub mod subclass; + #[macro_use] mod rt; diff --git a/src/subclass/completion_provider.rs b/src/subclass/completion_provider.rs new file mode 100644 index 0000000..2bc99ee --- /dev/null +++ b/src/subclass/completion_provider.rs @@ -0,0 +1,343 @@ +use crate::CompletionActivation; +use crate::CompletionContext; +use crate::CompletionInfo; +use crate::CompletionProposal; +use crate::CompletionProvider; +use glib::prelude::*; +use glib::subclass::prelude::*; +use glib::translate::*; +use once_cell::sync::Lazy; + +pub trait CompletionProviderImpl: ObjectImpl + Send + 'static { + fn get_name(&self, obj: &CompletionProvider) -> glib::GString; + fn get_icon(&self, obj: &CompletionProvider) -> Option; + fn get_icon_name(&self, obj: &CompletionProvider) -> Option; + fn get_gicon(&self, obg: &CompletionProvider) -> Option; + fn populate(&self, obg: &CompletionProvider, context: &CompletionContext); + fn get_activation(&self, obg: &CompletionProvider) -> CompletionActivation; + fn provide_match(&self, obg: &CompletionProvider, context: &CompletionContext) -> bool; + fn get_info_widget( + &self, + obg: &CompletionProvider, + proposal: &CompletionProposal, + ) -> Option; + fn update_info( + &self, + obg: &CompletionProvider, + proposal: &CompletionProposal, + info: &CompletionInfo, + ); + fn get_start_iter( + &self, + obg: &CompletionProvider, + context: &CompletionContext, + proposal: &CompletionProposal, + iter: >k::TextIter, + ) -> bool; + fn activate_proposal( + &self, + obg: &CompletionProvider, + proposal: &CompletionProposal, + iter: >k::TextIter, + ) -> bool; + fn get_interactive_delay(&self, obj: &CompletionProvider) -> i32; + fn get_priority(&self, obg: &CompletionProvider) -> i32; +} + +unsafe impl IsImplementable for CompletionProvider { + unsafe extern "C" fn interface_init( + iface: glib::glib_sys::gpointer, + _iface_data: glib::glib_sys::gpointer, + ) { + let iface = &mut *(iface as *mut gtk_source_sys::GtkSourceCompletionProviderIface); + iface.get_name = Some(completion_provider_get_name::); + iface.get_icon = Some(completion_provider_get_icon::); + iface.get_icon_name = Some(completion_provider_get_icon_name::); + iface.get_gicon = Some(completion_provider_get_gicon::); + iface.populate = Some(completion_provider_populate::); + iface.get_activation = Some(completion_provider_get_activation::); + iface.match_ = Some(completion_provider_match::); + iface.get_info_widget = Some(completion_provider_get_info_widget::); + iface.update_info = Some(completion_provider_update_info::); + iface.get_start_iter = Some(completion_provider_get_start_iter::); + iface.activate_proposal = Some(completion_provider_activate_proposal::); + iface.get_interactive_delay = Some(completion_provider_get_interactive_delay::); + iface.get_priority = Some(completion_provider_get_priority::); + } +} + +unsafe extern "C" fn completion_provider_get_name( + completion_provider: *mut gtk_source_sys::GtkSourceCompletionProvider, +) -> *const libc::c_char { + let instance = &*(completion_provider as *mut T::Instance); + let imp = instance.get_impl(); + imp.get_name(&from_glib_borrow(completion_provider)) + .to_glib_full() +} + +static COMPLETION_PROVIDER_ICON_QUARK: Lazy = + Lazy::new(|| glib::Quark::from_string("gtk-sourceview-subclass-icon")); + +unsafe extern "C" fn completion_provider_get_icon( + completion_provider: *mut gtk_source_sys::GtkSourceCompletionProvider, +) -> *mut gdk_pixbuf_sys::GdkPixbuf { + let instance = &*(completion_provider as *mut T::Instance); + let imp = instance.get_impl(); + + let ret = imp.get_icon(&from_glib_borrow(completion_provider)); + let ret_ptr = match ret { + Some(ref ret) => ret.as_ptr(), + None => std::ptr::null_mut(), + }; + + let old_ptr = gobject_sys::g_object_get_qdata( + completion_provider as *mut _, + COMPLETION_PROVIDER_ICON_QUARK.to_glib(), + ); + if !old_ptr.is_null() { + assert_eq!(old_ptr as *mut _, ret_ptr, "Did not return same icon again"); + } + + if let Some(_) = ret { + gobject_sys::g_object_set_qdata_full( + completion_provider as *mut _, + COMPLETION_PROVIDER_ICON_QUARK.to_glib(), + gobject_sys::g_object_ref(ret_ptr as *mut _) as *mut _, + Some(unref), + ); + } + + ret.to_glib_none().0 +} + +static COMPLETION_PROVIDER_ICON_NAME_QUARK: Lazy = + Lazy::new(|| glib::Quark::from_string("gtk-sourceview-subclass-icon-name")); + +unsafe extern "C" fn completion_provider_get_icon_name< + T: ObjectSubclass + CompletionProviderImpl, +>( + completion_provider: *mut gtk_source_sys::GtkSourceCompletionProvider, +) -> *const libc::c_char { + let instance = &*(completion_provider as *mut T::Instance); + let imp = instance.get_impl(); + + let ret = imp.get_icon_name(&from_glib_borrow(completion_provider)); + let ret_ptr = match ret { + Some(ref ret) => ret.as_ptr(), + None => std::ptr::null(), + }; + + let old_ptr = gobject_sys::g_object_get_qdata( + completion_provider as *mut _, + COMPLETION_PROVIDER_ICON_NAME_QUARK.to_glib(), + ); + if !old_ptr.is_null() { + assert_eq!( + old_ptr as *const _, ret_ptr, + "Did not return same icon name again" + ); + } + + if let Some(_) = ret { + gobject_sys::g_object_set_qdata_full( + completion_provider as *mut _, + COMPLETION_PROVIDER_ICON_NAME_QUARK.to_glib(), + gobject_sys::g_object_ref(ret_ptr as *mut _) as *mut _, + Some(unref), + ); + } + + ret.to_glib_none().0 +} + +static COMPLETION_PROVIDER_GICON_QUARK: Lazy = + Lazy::new(|| glib::Quark::from_string("gtk-sourceview-subclass-gicon")); + +unsafe extern "C" fn completion_provider_get_gicon( + completion_provider: *mut gtk_source_sys::GtkSourceCompletionProvider, +) -> *mut gio_sys::GIcon { + let instance = &*(completion_provider as *mut T::Instance); + let imp = instance.get_impl(); + + let ret = imp.get_gicon(&from_glib_borrow(completion_provider)); + let ret_ptr = match ret { + Some(ref ret) => ret.as_ptr(), + None => std::ptr::null_mut(), + }; + + let old_ptr = gobject_sys::g_object_get_qdata( + completion_provider as *mut _, + COMPLETION_PROVIDER_GICON_QUARK.to_glib(), + ); + if !old_ptr.is_null() { + assert_eq!( + old_ptr as *mut _, ret_ptr, + "Did not return same gicon again" + ); + } + + if let Some(_) = ret { + gobject_sys::g_object_set_qdata_full( + completion_provider as *mut _, + COMPLETION_PROVIDER_GICON_QUARK.to_glib(), + gobject_sys::g_object_ref(ret_ptr as *mut _) as *mut _, + Some(unref), + ); + } + + ret.to_glib_none().0 +} + +unsafe extern "C" fn completion_provider_populate( + completion_provider: *mut gtk_source_sys::GtkSourceCompletionProvider, + context: *mut gtk_source_sys::GtkSourceCompletionContext, +) { + let instance = &*(completion_provider as *mut T::Instance); + let imp = instance.get_impl(); + imp.populate( + &from_glib_borrow(completion_provider), + &from_glib_borrow(context), + ) +} + +unsafe extern "C" fn completion_provider_get_activation< + T: ObjectSubclass + CompletionProviderImpl, +>( + completion_provider: *mut gtk_source_sys::GtkSourceCompletionProvider, +) -> gtk_source_sys::GtkSourceCompletionActivation { + let instance = &*(completion_provider as *mut T::Instance); + let imp = instance.get_impl(); + imp.get_activation(&from_glib_borrow(completion_provider)) + .to_glib() +} + +unsafe extern "C" fn completion_provider_match( + completion_provider: *mut gtk_source_sys::GtkSourceCompletionProvider, + context: *mut gtk_source_sys::GtkSourceCompletionContext, +) -> glib_sys::gboolean { + let instance = &*(completion_provider as *mut T::Instance); + let imp = instance.get_impl(); + imp.provide_match( + &from_glib_borrow(completion_provider), + &from_glib_borrow(context), + ) + .to_glib() +} + +static COMPLETION_PROVIDER_INFO_WIDGET: Lazy = + Lazy::new(|| glib::Quark::from_string("gtk-sourceview-subclass-info-widget")); + +unsafe extern "C" fn completion_provider_get_info_widget< + T: ObjectSubclass + CompletionProviderImpl, +>( + completion_provider: *mut gtk_source_sys::GtkSourceCompletionProvider, + proposal: *mut gtk_source_sys::GtkSourceCompletionProposal, +) -> *mut gtk_sys::GtkWidget { + let instance = &*(completion_provider as *mut T::Instance); + let imp = instance.get_impl(); + + let ret = imp.get_info_widget( + &from_glib_borrow(completion_provider), + &from_glib_borrow(proposal), + ); + let ret_ptr = match ret { + Some(ref ret) => ret.as_ptr(), + None => std::ptr::null_mut(), + }; + + let old_ptr = gobject_sys::g_object_get_qdata( + proposal as *mut _, + COMPLETION_PROVIDER_INFO_WIDGET.to_glib(), + ); + if !old_ptr.is_null() { + assert_eq!( + old_ptr as *mut _, ret_ptr, + "Did not return same info widget again" + ); + } + + if let Some(_) = ret { + gobject_sys::g_object_set_qdata_full( + proposal as *mut _, + COMPLETION_PROVIDER_INFO_WIDGET.to_glib(), + gobject_sys::g_object_ref(ret_ptr as *mut _) as *mut _, + Some(unref), + ); + } + + ret.to_glib_none().0 +} + +unsafe extern "C" fn completion_provider_update_info( + completion_provider: *mut gtk_source_sys::GtkSourceCompletionProvider, + proposal: *mut gtk_source_sys::GtkSourceCompletionProposal, + completion_info: *mut gtk_source_sys::GtkSourceCompletionInfo, +) { + let instance = &*(completion_provider as *mut T::Instance); + let imp = instance.get_impl(); + imp.update_info( + &from_glib_borrow(completion_provider), + &from_glib_borrow(proposal), + &from_glib_borrow(completion_info), + ) +} + +unsafe extern "C" fn completion_provider_get_start_iter< + T: ObjectSubclass + CompletionProviderImpl, +>( + completion_provider: *mut gtk_source_sys::GtkSourceCompletionProvider, + context: *mut gtk_source_sys::GtkSourceCompletionContext, + proposal: *mut gtk_source_sys::GtkSourceCompletionProposal, + iter: *mut gtk_sys::GtkTextIter, +) -> glib_sys::gboolean { + let instance = &*(completion_provider as *mut T::Instance); + let imp = instance.get_impl(); + imp.get_start_iter( + &from_glib_borrow(completion_provider), + &from_glib_borrow(context), + &from_glib_borrow(proposal), + &from_glib_borrow(iter), + ) + .to_glib() +} + +unsafe extern "C" fn completion_provider_activate_proposal< + T: ObjectSubclass + CompletionProviderImpl, +>( + completion_provider: *mut gtk_source_sys::GtkSourceCompletionProvider, + proposal: *mut gtk_source_sys::GtkSourceCompletionProposal, + iter: *mut gtk_sys::GtkTextIter, +) -> glib_sys::gboolean { + let instance = &*(completion_provider as *mut T::Instance); + let imp = instance.get_impl(); + imp.activate_proposal( + &from_glib_borrow(completion_provider), + &from_glib_borrow(proposal), + &from_glib_borrow(iter), + ) + .to_glib() +} + +unsafe extern "C" fn completion_provider_get_interactive_delay< + T: ObjectSubclass + CompletionProviderImpl, +>( + completion_provider: *mut gtk_source_sys::GtkSourceCompletionProvider, +) -> libc::c_int { + let instance = &*(completion_provider as *mut T::Instance); + let imp = instance.get_impl(); + imp.get_interactive_delay(&from_glib_borrow(completion_provider)) +} + +unsafe extern "C" fn completion_provider_get_priority< + T: ObjectSubclass + CompletionProviderImpl, +>( + completion_provider: *mut gtk_source_sys::GtkSourceCompletionProvider, +) -> libc::c_int { + let instance = &*(completion_provider as *mut T::Instance); + let imp = instance.get_impl(); + imp.get_priority(&from_glib_borrow(completion_provider)) +} + +unsafe extern "C" fn unref(ptr: glib_sys::gpointer) { + gobject_sys::g_object_unref(ptr as *mut _); +} diff --git a/src/subclass/mod.rs b/src/subclass/mod.rs new file mode 100644 index 0000000..88e6965 --- /dev/null +++ b/src/subclass/mod.rs @@ -0,0 +1,2 @@ +mod completion_provider; +pub use self::completion_provider::*;