diff --git a/Cargo.lock b/Cargo.lock index 463d119..b435889 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,12 +8,12 @@ dependencies = [ [[package]] name = "atk-sys" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -50,25 +50,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "cairo-rs" -version = "0.3.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "c_vec 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cairo-sys-rs 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "glib 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cairo-sys-rs 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "cairo-sys-rs" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -118,103 +117,106 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "gdk" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cairo-rs 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cairo-sys-rs 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gdk-pixbuf 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gdk-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gio 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "glib 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cairo-rs 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cairo-sys-rs 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gdk-pixbuf 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gdk-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gio 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", - "pango 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pango 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "gdk-pixbuf" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "gdk-pixbuf-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "glib 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gdk-pixbuf-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gio 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gio-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "gdk-pixbuf-sys" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gio-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gio-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "gdk-sys" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cairo-sys-rs 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gdk-pixbuf-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gio-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cairo-sys-rs 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gdk-pixbuf-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gio-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", - "pango-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pango-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "gio" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gio-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "glib 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gio-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "gio-sys" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "glib" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "glib-sys" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -224,52 +226,53 @@ dependencies = [ [[package]] name = "gobject-sys" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "gtk" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cairo-rs 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cairo-sys-rs 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gdk 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gdk-pixbuf 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gdk-pixbuf-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gdk-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gio 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gio-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "glib 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gtk-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cairo-rs 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cairo-sys-rs 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gdk 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gdk-pixbuf 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gdk-pixbuf-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gdk-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gio 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gio-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gtk-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", - "pango 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pango 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "gtk-sys" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "atk-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atk-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cairo-sys-rs 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gdk-pixbuf-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gdk-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gio-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cairo-sys-rs 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gdk-pixbuf-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gdk-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gio-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", - "pango-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pango-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -364,25 +367,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "nvim-gtk" version = "0.2.0" dependencies = [ - "cairo-rs 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cairo-rs 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "gdk 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gdk-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gio 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "glib 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gtk 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gtk-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gdk 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gdk-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gio 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gtk 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gtk-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "htmlescape 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "neovim-lib 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "pango 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pango-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pangocairo 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "pangocairo-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pango 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pango-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pangocairo 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pangocairo-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)", "phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "rmpv 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", @@ -392,59 +398,64 @@ dependencies = [ [[package]] name = "pango" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "glib 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", - "pango-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pango-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "pango-sys" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "pangocairo" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cairo-rs 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cairo-sys-rs 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "glib 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cairo-rs 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cairo-sys-rs 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", - "pango 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pango-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pangocairo-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pango 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pango-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pangocairo-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "pangocairo-sys" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cairo-sys-rs 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cairo-sys-rs 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", - "pango-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pango-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "percent-encoding" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "phf" version = "0.7.21" @@ -702,11 +713,6 @@ name = "void" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "winapi" version = "0.3.4" @@ -736,31 +742,31 @@ dependencies = [ [metadata] "checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4" -"checksum atk-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "33a67fd81e1922dddc335887516f2f5254534e89c9d39fa89bca5d79bd150d34" +"checksum atk-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8dc233521f7bffd3042c31082ea71bd08820abf44bac938fb36591e20f76f39" "checksum atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8352656fd42c30a0c3c89d26dea01e3b77c0ab2af18230835c15e2e13cd51859" "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" "checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf" "checksum byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "652805b7e73fada9d85e9a6682a4abd490cb52d96aeecc12e33a0de34dfd0d23" "checksum c_vec 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6237ac5a4b1e81c213c24c6437964c61e646df910a914b4ab1487b46df20bd13" -"checksum cairo-rs 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b6b5695f59fd036fe5741bc5a4eb20c78fbe42256e3b08a2af26bbcbe8070bf3" -"checksum cairo-sys-rs 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7c6e18fecaeac51809db57f45f4553cc0975225a7eb435a7a7e91e5e8113a84d" +"checksum cairo-rs 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a110f269c2fd382df5fe8bd46dfa5f1b83608aa717fecb6e7a28c08c202f0e13" +"checksum cairo-sys-rs 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0395175ecba60accac076a02c31d143b9dcd9d5eb5316d7163a3273803b765c7" "checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" "checksum chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7c20ebe0b2b08b0aeddba49c609fe7957ba2e33449882cb186a180bc60682fa9" "checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab" "checksum env_logger 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f15f0b172cb4f52ed5dbf47f774a387cd2315d1bf7894ab5af9b083ae27efa5a" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" -"checksum gdk 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e51db95be6565011bcd5cd99f9b17fdd585001057a999b21e09f1e8c28deb9" -"checksum gdk-pixbuf 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "16160d212ae91abe9f3324c3fb233929ba322dde63585d15cda3336f8c529ed1" -"checksum gdk-pixbuf-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "798f97101eea8180da363d0e80e07ec7ec6d1809306601c0100c1de5bc8b4f52" -"checksum gdk-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4ee916f5f25c5f4b21bd9dcb12a216ae697406940ff9476358c308a8ececada" -"checksum gio 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "84ba5a2beb559059a0c9c2bd3681743cdede8d9a36c775840bca800333b22867" -"checksum gio-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a303bbf7a5e75ab3b627117ff10e495d1b9e97e1d68966285ac2b1f6270091bc" -"checksum glib 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9b0452824cc63066940f01adc721804919f0b76cdba3cfab977b00b87f16d4a" -"checksum glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9693049613ff52b93013cc3d2590366d8e530366d288438724b73f6c7dc4be8" -"checksum gobject-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60d507c87a71b1143c66ed21a969be9b99a76df234b342d733e787e6c9c7d7c2" -"checksum gtk 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0847c507e52c1feaede13ef56fb4847742438602655449d5f1f782e8633f146f" -"checksum gtk-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "905fcfbaaad1b44ec0b4bba9e4d527d728284c62bc2ba41fccedace2b096766f" +"checksum gdk 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dd30051ff3d908ff2fc7e5776ffe1c699821e043809f294c3a61004f11d6c3a9" +"checksum gdk-pixbuf 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c2d2199eba47ebcb9977ce28179649bdd59305ef465c4e6f9b65aaa41c24e6b5" +"checksum gdk-pixbuf-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df6a3b73e04fafc07f5ebc083f1096a773412e627828e1103a55e921f81187d8" +"checksum gdk-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3162ff940526ddff71bf1f630facee6b5e05d282d125ba0c4c803842819b80c3" +"checksum gio 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "071fd4e5592b39fdc31318e32d2497842501f539bcedb60c75cd365946642adc" +"checksum gio-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2a57872499171d279f8577ce83837da4cae62b08dd32892236ed67ab7ea61030" +"checksum glib 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5e0be1b1432e227bcd1a9b28db9dc1474a7e7fd4227e08e16f35304f32d09b61" +"checksum glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "615bef979b5838526aee99241afc80cfb2e34a8735d4bcb8ec6072598c18a408" +"checksum gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "70409d6405db8b1591602fcd0cbe8af52cd9976dd39194442b4c149ba343f86d" +"checksum gtk 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "414f3522f550a0b4f65e089f00ffcd3987dab8b0be284cb979aa7f6a03d60516" +"checksum gtk-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d9554cf5b3a85a13fb39258c65b04b262989c1d7a758f8f555b77a478621a91" "checksum htmlescape 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163" "checksum itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8324a32baf01e2ae060e9de58ed0bc2320c9a2833491ee36cd3b4c414de4db8c" "checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" @@ -773,10 +779,11 @@ dependencies = [ "checksum num-iter 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "4b226df12c5a59b63569dd57fafb926d91b385dfce33d8074a412411b689d593" "checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" "checksum num-traits 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e7de20f146db9d920c45ee8ed8f71681fd9ade71909b48c3acbd766aa504cf10" -"checksum pango 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e81c404ab81ea7ea2fc2431a0a7672507b80e4b8bf4b41eac3fc83cc665104e" -"checksum pango-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34f34a1be107fe16abb2744e0e206bee4b3b07460b5fddd3009a6aaf60bd69ab" -"checksum pangocairo 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "41a8620ece55098d741bacf4d3aa52398f85ce83cfe0d8f670fa11de88f52c40" -"checksum pangocairo-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e34ec010b38a2a6dafdf3d86ffe1251a0ae759208ec85f78c66c0445481bd5a8" +"checksum pango 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "45374801e224373c3c0393cd48073c81093494c8735721e81d1dbaa4096b2767" +"checksum pango-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94039b3921a4af4058a3e4335e5d15099101f298a92f5afc40bab3a3027594a1" +"checksum pangocairo 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "db4130d0e0567b177178c9568466277ff167cb43a245b2881e203509ea5fbd84" +"checksum pangocairo-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "decc9523cd5bbba49a67ed8d2ef7dfca957f4760bf420a1ea4a82634da26381c" +"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" "checksum phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "cb325642290f28ee14d8c6201159949a872f220c62af6e110a56ea914fbe42fc" "checksum phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "d62594c0bb54c464f633175d502038177e90309daf2e0158be42ed5f023ce88f" "checksum phf_generator 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "6b07ffcc532ccc85e3afc45865469bf5d9e4ef5bfcf9622e3cfe80c2d275ec03" @@ -809,7 +816,6 @@ dependencies = [ "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" -"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index e6847aa..345a294 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,17 +5,17 @@ authors = ["daa84 "] build = "build.rs" [dependencies] -cairo-rs = "0.3" -pango = "0.3" -pango-sys = "0.5" -pangocairo = "0.4" -pangocairo-sys = "0.6" -glib = "0.4" -glib-sys = "0.5" -gdk = "0.7" -gdk-sys = "0.5" -gio = "0.3" -gobject-sys = "0.5" +cairo-rs = "0.4" +pango = "0.4" +pango-sys = "0.6" +pangocairo = "0.5" +pangocairo-sys = "0.7" +glib = "0.5" +glib-sys = "0.6" +gdk = "0.8" +gdk-sys = "0.6" +gio = "0.4" +gobject-sys = "0.6" #gdk = { git = 'https://github.com/gtk-rs/gdk' } #gdk-sys = { git = 'https://github.com/gtk-rs/sys' } #glib = { git = 'https://github.com/gtk-rs/glib' } @@ -32,6 +32,9 @@ log = "0.4" env_logger = "0.5" htmlescape = "0.3" rmpv = "0.4" +percent-encoding = "1.0" +regex = "0.2" +lazy_static = "1.0" serde = "1.0" serde_derive = "1.0" @@ -45,12 +48,12 @@ serde_json = "1.0" phf_codegen = "0.7" [dependencies.gtk] -version = "0.3" +version = "0.4" features = ["v3_22"] #git = "https://github.com/gtk-rs/gtk" [dependencies.gtk-sys] -version = "0.5" +version = "0.6" features = ["v3_22"] #git = 'https://github.com/gtk-rs/sys' diff --git a/resources/side-panel.ui b/resources/side-panel.ui new file mode 100644 index 0000000..2e595cb --- /dev/null +++ b/resources/side-panel.ui @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + True + False + + + True + False + filebrowser.cd + Go To Directory + + + + + True + False + + + + + True + False + filebrowser.reload + Reload + + + + + True + False + Show Hidden Files + + + + + + + + + + + + + + + + + 150 + False + vertical + + + False + False + 6 + dir_list_model + 1 + + + 6 + + + 1 + + + + + 6 + end + + + 0 + + + + + False + True + 0 + + + + + False + + + False + file_browser_tree_store + False + False + 20 + True + + + + + + autosize + + + 6 + + + 3 + + + + + + 0 + + + + + + + + + True + True + 1 + + + + + diff --git a/screenshots/neovimgtk-screen.png b/screenshots/neovimgtk-screen.png index 05f745c..cc21790 100644 Binary files a/screenshots/neovimgtk-screen.png and b/screenshots/neovimgtk-screen.png differ diff --git a/src/file_browser.rs b/src/file_browser.rs new file mode 100644 index 0000000..9865296 --- /dev/null +++ b/src/file_browser.rs @@ -0,0 +1,551 @@ +use std::cell::RefCell; +use std::cmp::Ordering; +use std::io; +use std::fs; +use std::fs::DirEntry; +use std::path::{Component, Path, PathBuf}; +use std::rc::Rc; +use std::ops::Deref; + +use gio; +use gio::prelude::*; +use gtk; +use gtk::MenuExt; +use gtk::prelude::*; + +use neovim_lib::{NeovimApi, NeovimApiAsync}; + +use misc::escape_filename; +use nvim::{ErrorReport, NeovimClient, NeovimRef}; +use shell; + +const ICON_FOLDER_CLOSED: &str = "folder-symbolic"; +const ICON_FOLDER_OPEN: &str = "folder-open-symbolic"; +const ICON_FILE: &str = "text-x-generic-symbolic"; + +struct Components { + dir_list_model: gtk::TreeStore, + dir_list: gtk::ComboBox, + context_menu: gtk::Menu, + show_hidden_checkbox: gtk::CheckMenuItem, + cd_action: gio::SimpleAction, +} + +struct State { + current_dir: String, + show_hidden: bool, + selected_path: Option, +} + +pub struct FileBrowserWidget { + store: gtk::TreeStore, + tree: gtk::TreeView, + widget: gtk::Box, + nvim: Option>, + comps: Components, + state: Rc>, +} + +impl Deref for FileBrowserWidget { + type Target = gtk::Box; + + fn deref(&self) -> >k::Box { + &self.widget + } +} + +#[derive(Copy, Clone, Debug)] +enum FileType { + File, + Dir, +} + +#[allow(dead_code)] +enum Column { + Filename, + Path, + FileType, + IconName, +} + +impl FileBrowserWidget { + pub fn new() -> Self { + let builder = gtk::Builder::new_from_string(include_str!("../resources/side-panel.ui")); + let widget: gtk::Box = builder.get_object("file_browser").unwrap(); + let tree: gtk::TreeView = builder.get_object("file_browser_tree_view").unwrap(); + let store: gtk::TreeStore = builder.get_object("file_browser_tree_store").unwrap(); + let dir_list_model: gtk::TreeStore = builder.get_object("dir_list_model").unwrap(); + let dir_list: gtk::ComboBox = builder.get_object("dir_list").unwrap(); + let context_menu: gtk::Menu = builder.get_object("file_browser_context_menu").unwrap(); + let show_hidden_checkbox: gtk::CheckMenuItem = builder + .get_object("file_browser_show_hidden_checkbox") + .unwrap(); + + let file_browser = FileBrowserWidget { + store, + tree, + widget, + nvim: None, + comps: Components { + dir_list_model, + dir_list, + context_menu, + show_hidden_checkbox, + cd_action: gio::SimpleAction::new("cd", None), + }, + state: Rc::new(RefCell::new(State { + current_dir: "".to_owned(), + show_hidden: false, + selected_path: None, + })), + }; + file_browser + } + + fn nvim(&self) -> Option { + self.nvim.as_ref().unwrap().nvim() + } + + pub fn init(&mut self, shell_state: &shell::State) { + // Initialize values. + let nvim = shell_state.nvim_clone(); + self.nvim = Some(nvim); + if let Some(dir) = get_current_dir(&mut self.nvim().unwrap()) { + update_dir_list(&dir, &self.comps.dir_list_model, &self.comps.dir_list); + self.state.borrow_mut().current_dir = dir; + } + + // Populate tree. + tree_reload(&self.store, &self.state.borrow()); + + let store = &self.store; + let state_ref = &self.state; + self.tree.connect_test_expand_row(clone!(store, state_ref => move |_, iter, _| { + store.set(&iter, &[Column::IconName as u32], &[&ICON_FOLDER_OPEN]); + // We cannot recursively populate all directories. Instead, we have prepared a single + // empty child entry for all non-empty directories, so the row will be expandable. Now, + // when a directory is expanded, populate its children. + let state = state_ref.borrow(); + if let Some(child) = store.iter_children(iter) { + let filename = store.get_value(&child, Column::Filename as i32); + if filename.get::<&str>().is_none() { + store.remove(&child); + let dir_value = store.get_value(&iter, Column::Path as i32); + if let Some(dir) = dir_value.get() { + populate_tree_nodes(&store, &state, dir, Some(iter)); + } + } else { + // This directory is already populated, i.e. it has been expanded and collapsed + // again. Rows further down the tree might have been silently collapsed without + // getting an event. Update their folder icon. + let mut tree_path = store.get_path(&child).unwrap(); + while let Some(iter) = store.get_iter(&tree_path) { + tree_path.next(); + let file_type = store + .get_value(&iter, Column::FileType as i32) + .get::(); + if file_type == Some(FileType::Dir as u8) { + store.set(&iter, &[Column::IconName as u32], &[&ICON_FOLDER_CLOSED]); + } + } + } + } + Inhibit(false) + })); + + self.tree.connect_row_collapsed(clone!(store => move |_, iter, _| { + store.set(&iter, &[Column::IconName as u32], &[&ICON_FOLDER_CLOSED]); + })); + + // Further initialization. + self.init_actions(); + self.init_subscriptions(shell_state); + self.connect_events(); + } + + fn init_actions(&self) { + let actions = gio::SimpleActionGroup::new(); + + let store = &self.store; + let state_ref = &self.state; + let nvim_ref = self.nvim.as_ref().unwrap(); + + let reload_action = gio::SimpleAction::new("reload", None); + reload_action.connect_activate(clone!(store, state_ref => move |_, _| { + tree_reload(&store, &state_ref.borrow()); + })); + actions.add_action(&reload_action); + + let cd_action = &self.comps.cd_action; + cd_action.connect_activate(clone!(state_ref, nvim_ref => move |_, _| { + let mut nvim = nvim_ref.nvim().unwrap(); + if let Some(ref path) = state_ref.borrow().selected_path { + nvim.set_current_dir(&path).report_err(); + } + })); + actions.add_action(cd_action); + + self.comps + .context_menu + .insert_action_group("filebrowser", &actions); + } + + fn init_subscriptions(&self, shell_state: &shell::State) { + // Always set the current working directory as the root of the file browser. + let store = &self.store; + let state_ref = &self.state; + let dir_list_model = &self.comps.dir_list_model; + let dir_list = &self.comps.dir_list; + shell_state.subscribe( + "DirChanged", + &["getcwd()"], + clone!(store, state_ref, dir_list_model, dir_list => move |args| { + let dir = args.into_iter().next().unwrap(); + let mut state = state_ref.borrow_mut(); + if dir != *state.current_dir { + update_dir_list(&dir, &dir_list_model, &dir_list); + state.current_dir = dir; + tree_reload(&store, &state); + } + }), + ); + + // Reveal the file of an entered buffer in the file browser and select the entry. + let tree = &self.tree; + let subscription = shell_state.subscribe( + "BufEnter", + &["getcwd()", "expand('%:p')"], + clone!(tree, store => move |args| { + let mut args_iter = args.into_iter(); + let dir = args_iter.next().unwrap(); + let file_path = args_iter.next().unwrap(); + let could_reveal = + if let Ok(rel_path) = Path::new(&file_path).strip_prefix(&Path::new(&dir)) { + reveal_path_in_tree(&store, &tree, &rel_path) + } else { + false + }; + if !could_reveal { + tree.get_selection().unselect_all(); + } + }), + ); + shell_state.run_now(&subscription); + } + + fn connect_events(&self) { + // Open file / go to dir, when user clicks on an entry. + let store = &self.store; + let state_ref = &self.state; + let nvim_ref = self.nvim.as_ref().unwrap(); + self.tree.connect_row_activated(clone!(store, state_ref, nvim_ref => move |tree, path, _| { + let iter = store.get_iter(path).unwrap(); + let file_type = store + .get_value(&iter, Column::FileType as i32) + .get::() + .unwrap(); + let file_path = store + .get_value(&iter, Column::Path as i32) + .get::() + .unwrap(); + if file_type == FileType::Dir as u8 { + let expanded = tree.row_expanded(path); + if expanded { + tree.collapse_row(path); + } else { + tree.expand_row(path, false); + } + } else { + // FileType::File + let cwd = &state_ref.borrow().current_dir; + let cwd = Path::new(cwd); + let file_path = if let Some(rel_path) = Path::new(&file_path) + .strip_prefix(&cwd) + .ok() + .and_then(|p| p.to_str()) + { + rel_path + } else { + &file_path + }; + let file_path = escape_filename(file_path); + nvim_ref.nvim().unwrap().command_async(&format!(":e {}", file_path)) + .cb(|r| r.report_err()) + .call(); + } + })); + + // Connect directory list. + let nvim_ref = self.nvim.as_ref().unwrap(); + self.comps.dir_list.connect_changed(clone!(nvim_ref => move |dir_list| { + if let Some(iter) = dir_list.get_active_iter() { + let model = dir_list.get_model().unwrap(); + if let Some(dir) = model.get_value(&iter, 2).get::<&str>() { + let mut nvim = nvim_ref.nvim().unwrap(); + nvim.set_current_dir(dir).report_err(); + } + } + })); + + let store = &self.store; + let state_ref = &self.state; + let context_menu = &self.comps.context_menu; + let cd_action = &self.comps.cd_action; + self.tree.connect_button_press_event( + clone!(store, state_ref, context_menu, cd_action => move |tree, ev_btn| { + // Open context menu on right click. + if ev_btn.get_button() == 3 { + context_menu.popup_at_pointer(&**ev_btn); + let (pos_x, pos_y) = ev_btn.get_position(); + let iter = tree + .get_path_at_pos(pos_x as i32, pos_y as i32) + .and_then(|(path, _, _, _)| path) + .and_then(|path| store.get_iter(&path)); + let file_type = iter + .as_ref() + .and_then(|iter| { + store + .get_value(&iter, Column::FileType as i32) + .get::() + }); + // Enable the "Go To Directory" action only if the user clicked on a folder. + cd_action.set_enabled(file_type == Some(FileType::Dir as u8)); + let path = iter + .and_then(|iter| { + store + .get_value(&iter, Column::Path as i32) + .get::() + }); + state_ref.borrow_mut().selected_path = path; + } + Inhibit(false) + }), + ); + + // Show / hide hidden files when corresponding menu item is toggled. + self.comps.show_hidden_checkbox.connect_toggled(clone!(state_ref, store => move |ev| { + let mut state = state_ref.borrow_mut(); + state.show_hidden = ev.get_active(); + tree_reload(&store, &state); + })); + } +} + +/// Compare function for dir entries. +/// +/// Sorts directories above files. +fn cmp_dirs_first(lhs: &DirEntry, rhs: &DirEntry) -> io::Result { + let lhs_metadata = fs::metadata(lhs.path())?; + let rhs_metadata = fs::metadata(rhs.path())?; + if lhs_metadata.file_type() == rhs_metadata.file_type() { + Ok(lhs.path().cmp(&rhs.path())) + } else { + if lhs_metadata.is_dir() { + Ok(Ordering::Less) + } else { + Ok(Ordering::Greater) + } + } +} + +/// Clears an repopulate the entire tree. +fn tree_reload(store: >k::TreeStore, state: &State) { + let dir = &state.current_dir; + store.clear(); + populate_tree_nodes(store, state, dir, None); +} + +/// Updates the dirctory list on top of the file browser. +/// +/// The list represents the path the the current working directory. If the new cwd is a parent of +/// the old one, the list is kept and only the active entry is updated. Otherwise, the list is +/// replaced with the new path and the last entry is marked active. +fn update_dir_list(dir: &str, dir_list_model: >k::TreeStore, dir_list: >k::ComboBox) { + // The current working directory path. + let complete_path = Path::new(dir); + let mut path = PathBuf::new(); + let mut components = complete_path.components(); + let mut next = components.next(); + + // Iterator over existing dir_list model. + let mut dir_list_iter = dir_list_model.get_iter_first(); + + // Whether existing entries up to the current position of dir_list_iter are a prefix of the + // new current working directory path. + let mut is_prefix = true; + + // Iterate over components of the cwd. Simultaneously move dir_list_iter forward. + while let Some(dir) = next { + next = components.next(); + let dir_name = &*dir.as_os_str().to_string_lossy(); + // Assemble path up to current component. + path.push(Path::new(&dir)); + let path_str = path.to_str().unwrap_or_else(|| { + error!( + "Could not convert path to string: {}\n + Directory chooser will not work for that entry.", + path.to_string_lossy() + ); + "" + }); + // Use the current entry of dir_list, if any, otherwise append a new one. + let current_iter = dir_list_iter.unwrap_or_else(|| dir_list_model.append(None)); + // Check if the current entry is still part of the new cwd. + if is_prefix && dir_list_model.get_value(¤t_iter, 0).get::<&str>() != Some(&dir_name) + { + is_prefix = false; + } + if next.is_some() { + // Update dir_list entry. + dir_list_model.set( + ¤t_iter, + &[0, 1, 2], + &[&dir_name, &ICON_FOLDER_CLOSED, &path_str], + ); + } else { + // We reached the last component of the new cwd path. Set the active entry of dir_list + // to this one. + dir_list_model.set( + ¤t_iter, + &[0, 1, 2], + &[&dir_name, &ICON_FOLDER_OPEN, &path_str], + ); + dir_list.set_active_iter(¤t_iter); + }; + // Advance dir_list_iter. + dir_list_iter = if dir_list_model.iter_next(¤t_iter) { + Some(current_iter) + } else { + None + } + } + // We updated the dir list to the point of the current working directory. + if let Some(iter) = dir_list_iter { + if is_prefix { + // If we didn't change any entries to this point and the list contains further entries, + // the remaining ones are subdirectories of the cwd and we keep them. + loop { + dir_list_model.set(&iter, &[1], &[&ICON_FOLDER_CLOSED]); + if !dir_list_model.iter_next(&iter) { + break; + } + } + } else { + // If we needed to change entries, the following ones are not directories under the + // cwd and we clear them. + while dir_list_model.remove(&iter) {} + } + } +} + +/// Populates one level, i.e. one directory of the file browser tree. +fn populate_tree_nodes( + store: >k::TreeStore, + state: &State, + dir: &str, + parent: Option<>k::TreeIter>, +) { + let path = Path::new(dir); + let read_dir = match path.read_dir() { + Ok(read_dir) => read_dir, + Err(err) => { + error!("Couldn't populate tree: {}", err); + return; + } + }; + let iter = read_dir.filter_map(Result::ok); + let mut entries: Vec = if state.show_hidden { + iter.collect() + } else { + iter.filter(|entry| !entry.file_name().to_string_lossy().starts_with(".")) + .filter(|entry| !entry.file_name().to_string_lossy().ends_with("~")) + .collect() + }; + entries.sort_unstable_by(|lhs, rhs| cmp_dirs_first(lhs, rhs).unwrap_or(Ordering::Equal)); + for entry in entries { + let path = if let Some(path) = entry.path().to_str() { + path.to_owned() + } else { + // Skip paths that contain invalid unicode. + continue; + }; + let filename = entry.file_name().to_str().unwrap().to_owned(); + let file_type = if let Ok(metadata) = fs::metadata(entry.path()) { + let file_type = metadata.file_type(); + if file_type.is_dir() { + FileType::Dir + } else if file_type.is_file() { + FileType::File + } else { + continue; + } + } else { + // In case of invalid symlinks, we cannot obtain metadata. + continue; + }; + let icon = match file_type { + FileType::Dir => ICON_FOLDER_CLOSED, + FileType::File => ICON_FILE, + }; + // When we get until here, we want to show the entry. Append it to the tree. + let iter = store.append(parent); + store.set( + &iter, + &[0, 1, 2, 3], + &[&filename, &path, &(file_type as u8), &icon], + ); + // For directories, check whether the directory is empty. If not, append a single empty + // entry, so the expand arrow is shown. Its contents are dynamically populated when + // expanded (see `init`). + if let FileType::Dir = file_type { + let not_empty = if let Ok(mut dir) = entry.path().read_dir() { + dir.next().is_some() + } else { + false + }; + if not_empty { + let iter = store.append(&iter); + store.set(&iter, &[], &[]); + } + } + } +} + +fn get_current_dir(nvim: &mut NeovimRef) -> Option { + match nvim.eval("getcwd()") { + Ok(cwd) => cwd.as_str().map(|s| s.to_owned()), + Err(err) => { + error!("Couldn't get cwd: {}", err); + None + } + } +} + +/// Reveals and selects the given file in the file browser. +/// +/// Returns `true` if the file could be successfully revealed. +fn reveal_path_in_tree(store: >k::TreeStore, tree: >k::TreeView, rel_file_path: &Path) -> bool { + let mut tree_path = gtk::TreePath::new(); + 'components: for component in rel_file_path.components() { + if let Component::Normal(component) = component { + tree_path.down(); + while let Some(iter) = store.get_iter(&tree_path) { + let entry_value = store.get_value(&iter, Column::Filename as i32); + let entry = entry_value.get::<&str>().unwrap(); + if component == entry { + tree.expand_row(&tree_path, false); + continue 'components; + } + tree_path.next(); + } + return false; + } else { + return false; + } + } + if tree_path.get_depth() == 0 { + return false; + } + tree.set_cursor(&tree_path, None, false); + true +} diff --git a/src/main.rs b/src/main.rs index 9699b80..4003ead 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,14 +11,18 @@ extern crate gtk; extern crate gtk_sys; extern crate htmlescape; #[macro_use] +extern crate lazy_static; +#[macro_use] extern crate log; extern crate neovim_lib; extern crate pango; extern crate pango_cairo_sys; extern crate pango_sys; extern crate pangocairo; +extern crate percent_encoding; extern crate phf; extern crate rmpv; +extern crate regex; extern crate serde; #[macro_use] @@ -50,6 +54,9 @@ mod popup_menu; mod project; mod tabline; mod error; +mod file_browser; +mod subscriptions; +mod misc; use std::env; use std::time::Duration; @@ -98,21 +105,23 @@ fn main() { } fn open(app: >k::Application, files: &[gio::File], _: &str) { - for f in files { - let mut ui = Ui::new(ShellOptions::new( - nvim_bin_path(std::env::args()), - f.get_path().and_then(|p| p.to_str().map(str::to_owned)), - nvim_timeout(std::env::args()), - )); + let files_list: Vec = files + .into_iter() + .filter_map(|f| f.get_path()?.to_str().map(str::to_owned)) + .collect(); + let mut ui = Ui::new(ShellOptions::new( + nvim_bin_path(std::env::args()), + files_list, + nvim_timeout(std::env::args()), + )); - ui.init(app, !nvim_disable_win_state(std::env::args())); - } + ui.init(app, !nvim_disable_win_state(std::env::args())); } fn activate(app: >k::Application) { let mut ui = Ui::new(ShellOptions::new( nvim_bin_path(std::env::args()), - None, + Vec::new(), nvim_timeout(std::env::args()), )); diff --git a/src/misc.rs b/src/misc.rs new file mode 100644 index 0000000..2c3c768 --- /dev/null +++ b/src/misc.rs @@ -0,0 +1,38 @@ +use std::borrow::Cow; + +use regex::Regex; +use percent_encoding::percent_decode; + +/// Escape special ASCII characters with a backslash. +pub fn escape_filename<'t>(filename: &'t str) -> Cow<'t, str> { + lazy_static! { + static ref SPECIAL_CHARS: Regex = if cfg!(target_os = "windows") { + // On Windows, don't escape `:` and `\`, as these are valid components of the path. + Regex::new(r"[[:ascii:]&&[^0-9a-zA-Z._:\\-]]").unwrap() + } else { + // Similarly, don't escape `/` on other platforms. + Regex::new(r"[[:ascii:]&&[^0-9a-zA-Z._/-]]").unwrap() + }; + } + SPECIAL_CHARS.replace_all(&*filename, r"\$0") +} + +/// Decode a file URI. +/// +/// - On UNIX: `file:///path/to/a%20file.ext` -> `/path/to/a file.ext` +/// - On Windows: `file:///C:/path/to/a%20file.ext` -> `C:\path\to\a file.ext` +pub fn decode_uri(uri: &str) -> Option { + let path = match uri.split_at(8) { + ("file:///", path) => path, + _ => return None, + }; + let path = percent_decode(path.as_bytes()).decode_utf8().ok()?; + if cfg!(target_os = "windows") { + lazy_static! { + static ref SLASH: Regex = Regex::new(r"/").unwrap(); + } + Some(String::from(SLASH.replace_all(&*path, r"\"))) + } else { + Some("/".to_owned() + &path) + } +} diff --git a/src/nvim/handler.rs b/src/nvim/handler.rs index 699c6a9..3718dc1 100644 --- a/src/nvim/handler.rs +++ b/src/nvim/handler.rs @@ -83,6 +83,12 @@ impl NvimHandler { error!("Unsupported event {:?}", params); } } + "subscription" => { + self.safe_call(move |ui| { + let ui = &ui.borrow(); + ui.notify(params) + }); + } _ => { error!("Notification {}({:?})", method, params); } @@ -130,7 +136,7 @@ impl NvimHandler { } } } - + fn safe_call(&self, cb: F) where F: FnOnce(&Arc>) -> result::Result<(), String> + 'static + Send, diff --git a/src/nvim/mod.rs b/src/nvim/mod.rs index d9f8b7a..f1ec552 100644 --- a/src/nvim/mod.rs +++ b/src/nvim/mod.rs @@ -20,8 +20,9 @@ use std::result; use std::sync::Arc; use std::time::Duration; -use neovim_lib::{Neovim, NeovimApi, Session, UiAttachOptions}; +use neovim_lib::{Neovim, NeovimApi, NeovimApiAsync, Session, UiAttachOptions}; +use misc::escape_filename; use ui::UiMutex; use shell; use nvim_config::NvimConfig; @@ -138,7 +139,7 @@ pub fn start( pub fn post_start_init( nvim: NeovimClientAsync, - open_path: Option<&String>, + open_paths: Vec, cols: u64, rows: u64, ) -> result::Result<(), NvimInitError> { @@ -159,11 +160,18 @@ pub fn post_start_init( .command("runtime! ginit.vim") .map_err(NvimInitError::new_post_init)?; - if let Some(path) = open_path { + if !open_paths.is_empty() { + let command = open_paths + .iter() + .fold(":ar".to_owned(), |command, filename| { + let filename = escape_filename(filename); + command + " " + &filename + }); nvim.borrow() .unwrap() - .command(&format!("e {}", path)) - .map_err(NvimInitError::new_post_init)?; + .command_async(&command) + .cb(|r| r.report_err()) + .call(); } Ok(()) diff --git a/src/plug_manager/vim_plug.rs b/src/plug_manager/vim_plug.rs index e30e5b0..3bff9a6 100644 --- a/src/plug_manager/vim_plug.rs +++ b/src/plug_manager/vim_plug.rs @@ -1,6 +1,6 @@ use std::rc::Rc; -use neovim_lib::NeovimApi; +use neovim_lib::{NeovimApi, NeovimApiAsync}; use nvim::{NeovimClient, ErrorReport, NeovimRef}; use value::ValueMapExt; @@ -84,7 +84,9 @@ impl Manager { pub fn reload(&self, path: &str) { if let Some(mut nvim) = self.nvim() { - nvim.command(&format!("source {}", path)).report_err(); + nvim.command_async(&format!("source {}", path)) + .cb(|r| r.report_err()) + .call() } } } diff --git a/src/project.rs b/src/project.rs index 8ce293e..6423fa8 100644 --- a/src/project.rs +++ b/src/project.rs @@ -59,7 +59,7 @@ pub struct Projects { } impl Projects { - pub fn new(ref_widget: >k::ToolButton, shell: Rc>) -> Rc> { + pub fn new(ref_widget: >k::Button, shell: Rc>) -> Rc> { let projects = Projects { shell, popup: Popover::new(Some(ref_widget)), diff --git a/src/shell.rs b/src/shell.rs index 62ec42a..444487f 100644 --- a/src/shell.rs +++ b/src/shell.rs @@ -19,6 +19,7 @@ use pangocairo; use neovim_lib::{Neovim, NeovimApi, NeovimApiAsync, Value}; use neovim_lib::neovim_api::Tabpage; +use misc::{decode_uri, escape_filename}; use settings::{FontSource, Settings}; use ui_model::{Attrs, ModelRect, UiModel}; use color::{Color, ColorModel, COLOR_BLACK, COLOR_RED, COLOR_WHITE}; @@ -35,6 +36,7 @@ use error; use mode; use render; use render::CellMetrics; +use subscriptions::{SubscriptionHandle, Subscriptions}; const DEFAULT_FONT_NAME: &str = "DejaVu Sans Mono 12"; pub const MINIMUM_SUPPORTED_NVIM_VERSION: &str = "0.2.2"; @@ -96,6 +98,8 @@ pub struct State { detach_cb: Option>>, nvim_started_cb: Option>>, + + subscriptions: RefCell, } impl State { @@ -135,6 +139,8 @@ impl State { detach_cb: None, nvim_started_cb: None, + + subscriptions: RefCell::new(Subscriptions::new()), } } @@ -197,13 +203,17 @@ impl State { pub fn open_file(&self, path: &str) { if let Some(mut nvim) = self.nvim() { - nvim.command(&format!("e {}", path)).report_err(); + nvim.command_async(&format!("e {}", path)) + .cb(|r| r.report_err()) + .call(); } } pub fn cd(&self, path: &str) { if let Some(mut nvim) = self.nvim() { - nvim.command(&format!("cd {}", path)).report_err(); + nvim.command_async(&format!("cd {}", path)) + .cb(|r| r.report_err()) + .call(); } } @@ -360,6 +370,31 @@ impl State { fn max_popup_width(&self) -> i32 { self.drawing_area.get_allocated_width() - 20 } + + pub fn subscribe(&self, event_name: &str, args: &[&str], cb: F) -> SubscriptionHandle + where + F: Fn(Vec) + 'static, + { + self.subscriptions + .borrow_mut() + .subscribe(event_name, args, cb) + } + + pub fn set_autocmds(&self) { + self.subscriptions + .borrow() + .set_autocmds(&mut self.nvim().unwrap()); + } + + pub fn notify(&self, params: Vec) -> Result<(), String> { + self.subscriptions.borrow().notify(params) + } + + pub fn run_now(&self, handle: &SubscriptionHandle) { + self.subscriptions + .borrow() + .run_now(handle, &mut self.nvim().unwrap()); + } } pub struct UiState { @@ -379,19 +414,19 @@ impl UiState { #[derive(Clone)] pub struct ShellOptions { nvim_bin_path: Option, - open_path: Option, + open_paths: Vec, timeout: Option, } impl ShellOptions { pub fn new( nvim_bin_path: Option, - open_path: Option, + open_paths: Vec, timeout: Option, ) -> Self { ShellOptions { nvim_bin_path, - open_path, + open_paths, timeout, } } @@ -565,6 +600,29 @@ impl Shell { state.drawing_area.connect_size_allocate(move |_, _| { init_nvim(&ref_state); }); + + let ref_state = self.state.clone(); + let targets = vec![ + gtk::TargetEntry::new("text/uri-list", gtk::TargetFlags::OTHER_APP, 0), + ]; + state + .drawing_area + .drag_dest_set(gtk::DestDefaults::ALL, &targets, gdk::DragAction::COPY); + state + .drawing_area + .connect_drag_data_received(move |_, _, _, _, s, _, _| { + let uris = s.get_uris(); + let command = uris.iter().filter_map(|uri| decode_uri(uri)).fold( + ":ar".to_owned(), + |command, filename| { + let filename = escape_filename(&filename); + command + " " + &filename + }, + ); + let state = ref_state.borrow_mut(); + let mut nvim = state.nvim().unwrap(); + nvim.command_async(&command).cb(|r| r.report_err()).call() + }); } #[cfg(unix)] @@ -602,7 +660,16 @@ impl Shell { let nvim = state.nvim(); if let Some(mut nvim) = nvim { - nvim.command(":wa").report_err(); + nvim.command_async(":wa").cb(|r| r.report_err()).call(); + } + } + + pub fn new_tab(&self) { + let state = self.state.borrow(); + + let nvim = state.nvim(); + if let Some(mut nvim) = nvim { + nvim.command_async(":tabe").cb(|r| r.report_err()).call(); } } @@ -852,9 +919,7 @@ fn init_nvim_async( }); // attach ui - if let Err(err) = - nvim::post_start_init(nvim, options.open_path.as_ref(), cols as u64, rows as u64) - { + if let Err(err) = nvim::post_start_init(nvim, options.open_paths, cols as u64, rows as u64) { show_nvim_init_error(&err, state_arc.clone()); } else { set_nvim_initialized(state_arc); diff --git a/src/subscriptions.rs b/src/subscriptions.rs new file mode 100644 index 0000000..50db097 --- /dev/null +++ b/src/subscriptions.rs @@ -0,0 +1,156 @@ +use std::collections::HashMap; + +use neovim_lib::{NeovimApi, NeovimApiAsync, Value}; + +use nvim::{ErrorReport, NeovimRef}; + +/// A subscription to a Neovim autocmd event. +struct Subscription { + /// A callback to be executed each time the event triggers. + cb: Box) + 'static>, + /// A list of expressions which will be evaluated when the event triggers. The result is passed + /// to the callback. + args: Vec, +} + +/// A map of all registered subscriptions. +pub struct Subscriptions(HashMap>); + +/// A handle to identify a `Subscription` within the `Subscriptions` map. +/// +/// Can be used to trigger the subscription manually even when the event was not triggered. +/// +/// Could be used in the future to suspend individual subscriptions. +#[derive(Debug)] +pub struct SubscriptionHandle { + event_name: String, + index: usize, +} + +impl Subscriptions { + pub fn new() -> Self { + Subscriptions(HashMap::new()) + } + + /// Subscribe to a Neovim autocmd event. + /// + /// Subscriptions are not active immediately but only after `set_autocmds` is called. At the + /// moment, all calls to `subscribe` must be made before calling `set_autocmds`. + /// + /// This function is wrapped by `shell::State`. + /// + /// # Arguments: + /// + /// - `event_name`: The event to register. + /// See `:help autocmd-events` for a list of supported event names. Event names can be + /// comma-separated. + /// + /// - `args`: A list of expressions to be evaluated when the event triggers. + /// Expressions are evaluated using Vimscript. The results are passed to the callback as a + /// list of Strings. + /// This is especially useful as `Neovim::eval` is synchronous and might block if called from + /// the callback function; so always use the `args` mechanism instead. + /// + /// - `cb`: The callback function. + /// This will be called each time the event triggers or when `run_now` is called. + /// It is passed a vector with the results of the evaluated expressions given with `args`. + /// + /// # Example + /// + /// Call a function each time a buffer is entered or the current working directory is changed. + /// Pass the current buffer name and directory to the callback. + /// ``` + /// let my_subscription = shell.state.borrow() + /// .subscribe("BufEnter,DirChanged", &["expand(@%)", "getcwd()"], move |args| { + /// let filename = &args[0]; + /// let dir = &args[1]; + /// // do stuff + /// }); + /// ``` + pub fn subscribe(&mut self, event_name: &str, args: &[&str], cb: F) -> SubscriptionHandle + where + F: Fn(Vec) + 'static, + { + let entry = self.0.entry(event_name.to_owned()).or_insert(Vec::new()); + let index = entry.len(); + entry.push(Subscription { + cb: Box::new(cb), + args: args.into_iter().map(|&s| s.to_owned()).collect(), + }); + SubscriptionHandle { + event_name: event_name.to_owned(), + index, + } + } + + /// Register all subscriptions with Neovim. + /// + /// This function is wrapped by `shell::State`. + pub fn set_autocmds(&self, nvim: &mut NeovimRef) { + for (event_name, subscriptions) in &self.0 { + for (i, subscription) in subscriptions.iter().enumerate() { + let args = subscription + .args + .iter() + .fold("".to_owned(), |acc, arg| acc + ", " + &arg); + nvim.command_async(&format!( + "au {} * call rpcnotify(1, 'subscription', '{}', {} {})", + event_name, event_name, i, args, + )).cb(|r| r.report_err()) + .call(); + } + } + } + + /// Trigger given event. + fn on_notify(&self, event_name: &str, index: usize, args: Vec) { + if let Some(subscription) = self.0.get(event_name).and_then(|v| v.get(index)) { + (*subscription.cb)(args); + } + } + + /// Wrapper around `on_notify` for easy calling with a `neovim_lib::Handler` implementation. + /// + /// This function is wrapped by `shell::State`. + pub fn notify(&self, params: Vec) -> Result<(), String> { + let mut params_iter = params.into_iter(); + let ev_name = params_iter.next(); + let ev_name = ev_name + .as_ref() + .and_then(Value::as_str) + .ok_or("Error reading event name")?; + let index = params_iter + .next() + .and_then(|i| i.as_u64()) + .ok_or("Error reading index")? as usize; + let args = params_iter + .map(|arg| arg.as_str().map(|s| s.to_owned())) + .collect::>>() + .ok_or("Error reading args")?; + self.on_notify(ev_name, index, args); + Ok(()) + } + + /// Manually trigger the given subscription. + /// + /// The `nvim` instance is needed to evaluate the `args` expressions. + /// + /// This function is wrapped by `shell::State`. + pub fn run_now(&self, handle: &SubscriptionHandle, nvim: &mut NeovimRef) { + let subscription = &self.0.get(&handle.event_name).unwrap()[handle.index]; + let args = subscription + .args + .iter() + .map(|arg| nvim.eval(arg)) + .map(|res| { + res.ok() + .and_then(|val| val.as_str().map(|s: &str| s.to_owned())) + }) + .collect::>>(); + if let Some(args) = args { + self.on_notify(&handle.event_name, handle.index, args); + } else { + error!("Error manually running {:?}", handle); + } + } +} diff --git a/src/sys/pango/mod.rs b/src/sys/pango/mod.rs index 881bfd9..c0a008d 100644 --- a/src/sys/pango/mod.rs +++ b/src/sys/pango/mod.rs @@ -10,7 +10,6 @@ use std::ptr; use pango; use pango_sys; -use glib_ffi; use glib::translate::*; @@ -23,8 +22,7 @@ pub fn pango_itemize( cached_iter: Option<&mut AttrIterator>, ) -> Vec { unsafe { - //FromGlibPtrContainer::from_glib_full(pango_sys::pango_itemize( - from_glib_full_as_vec(pango_sys::pango_itemize( + FromGlibPtrContainer::from_glib_full(pango_sys::pango_itemize( context.to_glib_none().0, text.as_ptr() as *const i8, start_index as i32, @@ -37,12 +35,6 @@ pub fn pango_itemize( } } - -unsafe fn from_glib_full_as_vec(ptr: *mut glib_ffi::GList) -> Vec { - let num = glib_ffi::g_list_length(ptr) as usize; - FromGlibContainer::from_glib_full_num(ptr, num) -} - pub fn pango_shape( text: &str, offset: usize, diff --git a/src/tabline.rs b/src/tabline.rs index b09fbc7..20f4d57 100644 --- a/src/tabline.rs +++ b/src/tabline.rs @@ -10,7 +10,7 @@ use glib::signal; use pango; -use neovim_lib::NeovimApi; +use neovim_lib::{NeovimApi, NeovimApiAsync}; use neovim_lib::neovim_api::Tabpage; use nvim; @@ -39,6 +39,14 @@ impl State { } } } + + fn close_tab(&self, idx: u32) { + if let Some(mut nvim) = self.nvim.as_ref().unwrap().nvim() { + nvim.command_async(&format!(":tabc {}", idx + 1)) + .cb(|r| r.report_err()) + .call(); + } + } } pub struct Tabline { @@ -55,6 +63,7 @@ impl Tabline { tabs.set_scrollable(true); tabs.set_show_border(false); tabs.set_border_width(0); + tabs.set_hexpand(true); tabs.hide(); let state = Rc::new(RefCell::new(State::new())); @@ -113,7 +122,33 @@ impl Tabline { let title = gtk::Label::new(None); title.set_ellipsize(pango::EllipsizeMode::Middle); title.set_width_chars(25); - self.tabs.append_page(&empty, Some(&title)); + let close_btn = gtk::Button::new_from_icon_name( + "window-close-symbolic", + gtk::IconSize::Menu.into(), + ); + close_btn.set_relief(gtk::ReliefStyle::None); + close_btn.get_style_context().unwrap().add_class("small-button"); + close_btn.set_focus_on_click(false); + let label_box = gtk::Box::new(gtk::Orientation::Horizontal, 0); + label_box.pack_start(&title, true, false, 0); + label_box.pack_start(&close_btn, false, false, 0); + title.show(); + close_btn.show(); + self.tabs.append_page(&empty, Some(&label_box)); + + let tabs = self.tabs.clone(); + let state_ref = Rc::clone(&self.state); + close_btn.connect_clicked(move |btn| { + let current_label = btn + .get_parent().unwrap(); + for i in 0..tabs.get_n_pages() { + let page = tabs.get_nth_page(Some(i)).unwrap(); + let label = tabs.get_tab_label(&page).unwrap(); + if label == current_label { + state_ref.borrow().close_tab(i); + } + } + }); } } else if count > tabs.len() { for _ in tabs.len()..count { @@ -126,6 +161,12 @@ impl Tabline { let tab_label = self.tabs .get_tab_label(&tab_child.unwrap()) .unwrap() + .downcast::() + .unwrap() + .get_children() + .into_iter() + .next() + .unwrap() .downcast::() .unwrap(); tab_label.set_text(tab.1.as_ref().unwrap_or(&"??".to_owned())); diff --git a/src/ui.rs b/src/ui.rs index fd77e4b..a01706f 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,13 +1,13 @@ use std::cell::{Ref, RefCell, RefMut}; use std::{env, thread}; +use std::path::Path; use std::rc::Rc; use std::sync::Arc; use gdk; use gtk; -use gtk_sys; use gtk::prelude::*; -use gtk::{AboutDialog, ApplicationWindow, HeaderBar, Image, SettingsExt, ToolButton}; +use gtk::{AboutDialog, ApplicationWindow, Button, HeaderBar, Orientation, Paned, SettingsExt}; use gio::prelude::*; use gio::{Menu, MenuExt, MenuItem, SimpleAction}; use toml; @@ -17,6 +17,8 @@ use shell::{self, Shell, ShellOptions}; use shell_dlg; use project::Projects; use plug_manager; +use file_browser::FileBrowserWidget; +use subscriptions::SubscriptionHandle; macro_rules! clone { (@param _) => ( _ ); @@ -37,6 +39,7 @@ macro_rules! clone { const DEFAULT_WIDTH: i32 = 800; const DEFAULT_HEIGHT: i32 = 600; +const DEFAULT_SIDEBAR_WIDTH: i32 = 200; pub struct Ui { initialized: bool, @@ -45,21 +48,30 @@ pub struct Ui { shell: Rc>, projects: Rc>, plug_manager: Arc>, + file_browser: Arc>, } pub struct Components { window: Option, window_state: WindowState, - open_btn: ToolButton, + open_btn: Button, } impl Components { fn new() -> Components { - let save_image = - Image::new_from_icon_name("document-open", gtk_sys::GTK_ICON_SIZE_SMALL_TOOLBAR as i32); - + let open_btn = Button::new(); + let open_btn_box = gtk::Box::new(gtk::Orientation::Horizontal, 3); + open_btn_box.pack_start(>k::Label::new("Open"), false, false, 3); + open_btn_box.pack_start( + >k::Image::new_from_icon_name("pan-down-symbolic", gtk::IconSize::Menu.into()), + false, + false, + 3, + ); + open_btn.add(&open_btn_box); + open_btn.set_can_focus(false); Components { - open_btn: ToolButton::new(Some(&save_image), "Open"), + open_btn, window: None, window_state: WindowState::load(), } @@ -79,6 +91,7 @@ impl Ui { let plug_manager = plug_manager::Manager::new(); let plug_manager = Arc::new(UiMutex::new(plug_manager)); + let file_browser = Arc::new(UiMutex::new(FileBrowserWidget::new())); let comps = Arc::new(UiMutex::new(Components::new())); let settings = Rc::new(RefCell::new(Settings::new())); let shell = Rc::new(RefCell::new(Shell::new(settings.clone(), options))); @@ -93,6 +106,7 @@ impl Ui { settings, projects, plug_manager, + file_browser, } } @@ -106,6 +120,7 @@ impl Ui { settings.init(); let window = ApplicationWindow::new(app); + let main = Paned::new(Orientation::Horizontal); { // initialize window from comps @@ -126,48 +141,6 @@ impl Ui { } } - // Client side decorations including the toolbar are disabled via NVIM_GTK_NO_HEADERBAR=1 - let use_header_bar = env::var("NVIM_GTK_NO_HEADERBAR") - .map(|opt| opt.trim() != "1") - .unwrap_or(true); - - if app.prefers_app_menu() || use_header_bar { - self.create_main_menu(app, &window); - } - - if use_header_bar { - let header_bar = HeaderBar::new(); - - let projects = self.projects.clone(); - header_bar.pack_start(&comps.open_btn); - comps - .open_btn - .connect_clicked(move |_| projects.borrow_mut().show()); - - let save_image = Image::new_from_icon_name( - "document-save", - gtk_sys::GTK_ICON_SIZE_SMALL_TOOLBAR as i32, - ); - let save_btn = ToolButton::new(Some(&save_image), "Save"); - - let shell = self.shell.clone(); - save_btn.connect_clicked(move |_| shell.borrow_mut().edit_save_all()); - header_bar.pack_start(&save_btn); - - let paste_image = Image::new_from_icon_name( - "edit-paste", - gtk_sys::GTK_ICON_SIZE_SMALL_TOOLBAR as i32, - ); - let paste_btn = ToolButton::new(Some(&paste_image), "Paste"); - let shell = self.shell.clone(); - paste_btn.connect_clicked(move |_| shell.borrow_mut().edit_paste()); - header_bar.pack_start(&paste_btn); - - header_bar.set_show_close_button(true); - - window.set_titlebar(Some(&header_bar)); - } - if restore_win_state { if comps.window_state.is_maximized { window.maximize(); @@ -177,15 +150,51 @@ impl Ui { comps.window_state.current_width, comps.window_state.current_height, ); + + main.set_position(comps.window_state.sidebar_width); } else { window.set_default_size(DEFAULT_WIDTH, DEFAULT_HEIGHT); + main.set_position(DEFAULT_SIDEBAR_WIDTH); } } + // Client side decorations including the toolbar are disabled via NVIM_GTK_NO_HEADERBAR=1 + let use_header_bar = env::var("NVIM_GTK_NO_HEADERBAR") + .map(|opt| opt.trim() != "1") + .unwrap_or(true); + + if app.prefers_app_menu() || use_header_bar { + self.create_main_menu(app, &window); + } + + let update_subtitle = if use_header_bar { + Some(self.create_header_bar()) + } else { + None + }; + + let show_sidebar_action = + SimpleAction::new_stateful("show-sidebar", None, &false.to_variant()); + let file_browser_ref = self.file_browser.clone(); let comps_ref = self.comps.clone(); - window.connect_size_allocate(move |window, _| { - gtk_window_size_allocate(window, &mut *comps_ref.borrow_mut()) + show_sidebar_action.connect_change_state(move |action, value| { + if let Some(ref value) = *value { + action.set_state(value); + let is_active = value.get::().unwrap(); + file_browser_ref.borrow().set_visible(is_active); + comps_ref.borrow_mut().window_state.show_sidebar = is_active; + } }); + app.add_action(&show_sidebar_action); + + let comps_ref = self.comps.clone(); + window.connect_size_allocate(clone!(main => move |window, _| { + gtk_window_size_allocate( + window, + &mut *comps_ref.borrow_mut(), + &main, + ); + })); let comps_ref = self.comps.clone(); window.connect_window_state_event(move |_, event| { @@ -199,10 +208,44 @@ impl Ui { }); let shell = self.shell.borrow(); - window.add(&**shell); + let file_browser = self.file_browser.borrow(); + main.pack1(&**file_browser, false, false); + main.pack2(&**shell, true, false); + + window.add(&main); window.show_all(); - window.set_title("NeovimGtk"); + + if restore_win_state { + // Hide sidebar, if it wasn't shown last time. + // Has to be done after show_all(), so it won't be shown again. + let show_sidebar = self.comps.borrow().window_state.show_sidebar; + show_sidebar_action.change_state(&show_sidebar.to_variant()); + } + + let comps_ref = self.comps.clone(); + let update_title = shell.state.borrow().subscribe( + "BufEnter,DirChanged", + &["expand('%:p')", "getcwd()"], + move |args| { + let comps = comps_ref.borrow(); + let window = comps.window.as_ref().unwrap(); + let file_path = &args[0]; + let dir = Path::new(&args[1]); + let filename = if file_path.is_empty() { + "[No Name]" + } else if let Some(rel_path) = Path::new(&file_path) + .strip_prefix(&dir) + .ok() + .and_then(|p| p.to_str()) + { + rel_path + } else { + &file_path + }; + window.set_title(filename); + }, + ); let comps_ref = self.comps.clone(); let shell_ref = self.shell.clone(); @@ -220,14 +263,71 @@ impl Ui { })); let state_ref = self.shell.borrow().state.clone(); + let file_browser_ref = self.file_browser.clone(); let plug_manager_ref = self.plug_manager.clone(); shell.set_nvim_started_cb(Some(move || { + let state = state_ref.borrow(); plug_manager_ref .borrow_mut() .init_nvim_client(state_ref.borrow().nvim_clone()); + file_browser_ref.borrow_mut().init(&state); + state.set_autocmds(); + state.run_now(&update_title); + if let Some(ref update_subtitle) = update_subtitle { + state.run_now(&update_subtitle); + } })); } + fn create_header_bar(&self) -> SubscriptionHandle { + let header_bar = HeaderBar::new(); + let comps = self.comps.borrow(); + let window = comps.window.as_ref().unwrap(); + + let projects = self.projects.clone(); + header_bar.pack_start(&comps.open_btn); + comps + .open_btn + .connect_clicked(move |_| projects.borrow_mut().show()); + + let new_tab_btn = + Button::new_from_icon_name("tab-new-symbolic", gtk::IconSize::SmallToolbar.into()); + let shell_ref = Rc::clone(&self.shell); + new_tab_btn.connect_clicked(move |_| shell_ref.borrow_mut().new_tab()); + new_tab_btn.set_can_focus(false); + new_tab_btn.set_tooltip_text("Open a new tab"); + header_bar.pack_start(&new_tab_btn); + + let paste_btn = + Button::new_from_icon_name("edit-paste-symbolic", gtk::IconSize::SmallToolbar.into()); + let shell = self.shell.clone(); + paste_btn.connect_clicked(move |_| shell.borrow_mut().edit_paste()); + paste_btn.set_can_focus(false); + paste_btn.set_tooltip_text("Paste from clipboard"); + header_bar.pack_end(&paste_btn); + + let save_btn = Button::new_with_label("Save All"); + let shell = self.shell.clone(); + save_btn.connect_clicked(move |_| shell.borrow_mut().edit_save_all()); + save_btn.set_can_focus(false); + header_bar.pack_end(&save_btn); + + header_bar.set_show_close_button(true); + + window.set_titlebar(Some(&header_bar)); + + let shell = self.shell.borrow(); + let update_subtitle = shell.state.borrow().subscribe( + "DirChanged", + &["getcwd()"], + move |args| { + header_bar.set_subtitle(&*args[0]); + }, + ); + + update_subtitle + } + fn create_main_menu(&self, app: >k::Application, window: >k::ApplicationWindow) { let plug_manager = self.plug_manager.clone(); @@ -237,6 +337,10 @@ impl Ui { section.append_item(&MenuItem::new("New Window", "app.new-window")); menu.append_section(None, §ion); + let section = Menu::new(); + section.append_item(&MenuItem::new("Sidebar", "app.show-sidebar")); + menu.append_section(None, §ion); + let section = Menu::new(); section.append_item(&MenuItem::new("Plugins", "app.Plugins")); section.append_item(&MenuItem::new("About", "app.HelpAbout")); @@ -293,12 +397,19 @@ fn gtk_delete(comps: &UiMutex, shell: &RefCell) -> Inhibit { }) } -fn gtk_window_size_allocate(app_window: >k::ApplicationWindow, comps: &mut Components) { +fn gtk_window_size_allocate( + app_window: >k::ApplicationWindow, + comps: &mut Components, + main: &Paned, +) { if !app_window.is_maximized() { let (current_width, current_height) = app_window.get_size(); comps.window_state.current_width = current_width; comps.window_state.current_height = current_height; } + if comps.window_state.show_sidebar { + comps.window_state.sidebar_width = main.get_position(); + } } fn gtk_window_state_event(event: &gdk::EventWindowState, comps: &mut Components) { @@ -312,6 +423,8 @@ struct WindowState { current_width: i32, current_height: i32, is_maximized: bool, + show_sidebar: bool, + sidebar_width: i32, } impl WindowState { @@ -320,6 +433,8 @@ impl WindowState { current_width: DEFAULT_WIDTH, current_height: DEFAULT_HEIGHT, is_maximized: false, + show_sidebar: false, + sidebar_width: DEFAULT_SIDEBAR_WIDTH, } } }