summary refs log tree commit diff
diff options
context:
space:
mode:
authorVika <vika@fireburn.ru>2025-03-11 14:14:06 +0300
committerVika <vika@fireburn.ru>2025-04-09 20:20:19 +0300
commit936ed2d5d00199c545a8cdac763909cc63be096b (patch)
treec171aafafe26fc648d64ea03481bfd3994bb4eb5
parent2224f5557a3f2d522a82de27cee73234fa856298 (diff)
downloadbowl-936ed2d5d00199c545a8cdac763909cc63be096b.tar.zst
libspelling support
While libspelling is rather primitive (i.e. doesn't support mixing languages),
it's better than nothing to detect simple spelling mistakes. Let's use it.
-rw-r--r--Cargo.lock66
-rw-r--r--Cargo.toml2
-rw-r--r--default.nix3
-rw-r--r--src/components/post_editor.rs24
-rw-r--r--src/main.rs3
5 files changed, 93 insertions, 5 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 58cb500..4074e7a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -123,6 +123,7 @@ dependencies = [
  "kittybox-util",
  "libadwaita",
  "libsecret",
+ "libspelling",
  "log",
  "microformats",
  "relm4",
@@ -132,6 +133,7 @@ dependencies = [
  "serde_json",
  "serde_urlencoded",
  "soup3",
+ "sourceview5",
  "thiserror",
  "tokio",
  "tracing",
@@ -1050,6 +1052,35 @@ dependencies = [
 ]
 
 [[package]]
+name = "libspelling"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cbd36b794de5725e0b2be4cc90c57c5e3c7a5a3e5c317436e9e667305274c34"
+dependencies = [
+ "gio",
+ "glib",
+ "gtk4",
+ "libc",
+ "libspelling-sys",
+ "sourceview5",
+]
+
+[[package]]
+name = "libspelling-sys"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d2ec120461981daf9d0c5a8b0bc55ebf350292280e93fd6d063895593754484"
+dependencies = [
+ "gio-sys",
+ "glib-sys",
+ "gobject-sys",
+ "gtk4-sys",
+ "libc",
+ "sourceview5-sys",
+ "system-deps",
+]
+
+[[package]]
 name = "litemap"
 version = "0.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1714,6 +1745,41 @@ dependencies = [
 ]
 
 [[package]]
+name = "sourceview5"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0e07d99b15f12767aa1c84870c45667f42bf24fd6a989dc70088e32854ef56e"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "gdk-pixbuf",
+ "gdk4",
+ "gio",
+ "glib",
+ "gtk4",
+ "libc",
+ "pango",
+ "sourceview5-sys",
+]
+
+[[package]]
+name = "sourceview5-sys"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a3759467713554a8063faa380237ee2c753e89026bbe1b8e9611d991cb106ff"
+dependencies = [
+ "gdk-pixbuf-sys",
+ "gdk4-sys",
+ "gio-sys",
+ "glib-sys",
+ "gobject-sys",
+ "gtk4-sys",
+ "libc",
+ "pango-sys",
+ "system-deps",
+]
+
+[[package]]
 name = "spin"
 version = "0.9.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 7b76f8c..2b6d3eb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,6 +19,7 @@ gettext-rs = { version = "=0.7.0", features = ["gettext-system"] }
 gio = { version = "0.20.1", features = ["v2_80"] }
 glib = { version = "0.20.1", features = ["log"] }
 gtk = { version = "0.9.0", package = "gtk4", features = ["gnome_46", "v4_14"] }
+sourceview5 = { version = "0.9.1" }
 kittybox-indieauth = { git = "https://git.vikanezrimaya.xyz/kittybox", version = "0.2.0" }
 kittybox-util = { git = "https://git.vikanezrimaya.xyz/kittybox", version = "0.3.0" }
 libsecret = { version = "0.7.0", features = ["v0_21_2"] }
@@ -30,6 +31,7 @@ serde = { version = "1.0.208", features = ["derive"] }
 serde_json = "1.0.125"
 serde_urlencoded = "0.7.1"
 soup3 = "0.7.0"
+spelling = { version = "0.3.0", package = "libspelling" }
 thiserror = "1.0.63"
 tokio = { version = "1.39.3", features = ["full", "tracing"] }
 tracing = { version = "0.1.40", features = ["log"] }
diff --git a/default.nix b/default.nix
index de622f9..ebd0f28 100644
--- a/default.nix
+++ b/default.nix
@@ -2,6 +2,7 @@
 , pkg-config, wrapGAppsHook4, meson, ninja, gettext
 , desktop-file-utils
 , gtk4, libadwaita, libpanel, libsoup_3, libsecret
+, libspelling, gtksourceview5
 , librsvg, glib-networking
 
 , withLLMEnhancements ? true
@@ -26,7 +27,7 @@ let
     strictDeps = true;
 
     buildInputs = [
-      gtk4 libadwaita libsoup_3 libsecret
+      gtk4 libadwaita libsoup_3 libsecret libspelling gtksourceview5
       librsvg glib-networking
       gettext
     ];
diff --git a/src/components/post_editor.rs b/src/components/post_editor.rs
index 863e515..d08685a 100644
--- a/src/components/post_editor.rs
+++ b/src/components/post_editor.rs
@@ -88,10 +88,12 @@ impl Post {
 pub(crate) struct PostEditor<E> {
     #[no_eq] smart_summary_busy_guard: Option<gtk::gio::ApplicationBusyGuard>,
     sending: bool,
+    #[do_not_track] #[allow(dead_code)] spell_checker: spelling::Checker,
+    #[do_not_track] spelling_adapter: spelling::TextBufferAdapter,
 
     #[do_not_track] name_buffer: gtk::EntryBuffer,
     #[do_not_track] summary_buffer: gtk::EntryBuffer,
-    #[do_not_track] content_buffer: gtk::TextBuffer,
+    #[do_not_track] content_buffer: sourceview5::Buffer,
 
     #[do_not_track] pending_tag_buffer: gtk::EntryBuffer,
     #[do_not_track] tags: relm4::factory::FactoryVecDeque<TagPill>,
@@ -241,6 +243,9 @@ impl<E: std::error::Error + std::fmt::Debug + Send + 'static> Component for Post
                             #[name = "content_textarea"]
                             gtk::TextView {
                                 set_buffer: Some(&model.content_buffer),
+                                set_extra_menu: Some(&model.spelling_adapter.menu_model()),
+                                insert_action_group: ("spelling", Some(&model.spelling_adapter)),
+
                                 set_hexpand: true,
                                 #[iterate]
                                 add_css_class: &["frame", "view"],
@@ -340,15 +345,25 @@ impl<E: std::error::Error + std::fmt::Debug + Send + 'static> Component for Post
         #[cfg(feature = "smart-summary")]
         let (http, init) = init;
 
+        let spell_checker = spelling::Checker::default();
+        let content_buffer = Default::default();
+
         let mut model = Self {
             smart_summary_busy_guard: None,
             sending: false,
 
+            spelling_adapter: spelling::TextBufferAdapter::new(
+                &content_buffer,
+                &spell_checker
+            ),
+            spell_checker,
+
             name_buffer: gtk::EntryBuffer::default(),
             summary_buffer: gtk::EntryBuffer::default(),
-            content_buffer: gtk::TextBuffer::default(),
-            pending_tag_buffer: gtk::EntryBuffer::default(),
+            content_buffer,
 
+
+            pending_tag_buffer: gtk::EntryBuffer::default(),
             tags: FactoryVecDeque::builder()
                 .launch({
                     let listbox = gtk::Box::default();
@@ -383,10 +398,11 @@ impl<E: std::error::Error + std::fmt::Debug + Send + 'static> Component for Post
         let visibility_model = adw::EnumListModel::new(Visibility::static_type());
 
         let widgets = view_output!();
-
         #[cfg(feature = "smart-summary")]
         widgets.summary_field.append(model.smart_summary.widget());
 
+        model.spelling_adapter.set_enabled(true);
+
         widgets.visibility_selector.set_expression(Some(
             gtk::ClosureExpression::new::<String>(
                 [] as [gtk::Expression; 0],
diff --git a/src/main.rs b/src/main.rs
index 9f531ea..989516a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -16,6 +16,9 @@ fn main() {
 
     relm4_icons::initialize_icons(bowl::icons::GRESOURCE_BYTES, bowl::icons::RESOURCE_PREFIX);
 
+    sourceview5::init();
+    spelling::init();
+
     let app = relm4::RelmApp::new(bowl::APPLICATION_ID);
     relm4::set_global_css("/* CSS for Bowl */
 .tag-pill button {