Merge branch 'master' into fix-async-startup

This commit is contained in:
daa 2017-11-06 22:05:11 +03:00
commit 25cdf3ef8f
20 changed files with 1454 additions and 110 deletions

25
Cargo.lock generated
View File

@ -22,6 +22,7 @@ dependencies = [
"phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"toml 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -106,6 +107,11 @@ name = "custom_derive"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "dtoa"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "env_logger"
version = "0.4.3"
@ -277,6 +283,11 @@ name = "htmlescape"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "itoa"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "lazy_static"
version = "0.2.8"
@ -496,6 +507,17 @@ dependencies = [
"synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde_json"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "siphasher"
version = "0.2.2"
@ -585,6 +607,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de"
"checksum conv 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299"
"checksum custom_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9"
"checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab"
"checksum env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3ddf21e73e016298f5cb37d6ef8e8da8e39f91f9ec8b0df44b7deb16a9f8cd5b"
"checksum gdk 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f30018ecbbb1e6f1d59c4024ec08675850744b799abc5420be0629ac9ba0abd2"
"checksum gdk-pixbuf 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "caf05dab73febcc6e90abaff8f24cfe1cf1bd2222cd648ddfe337bf3b994489f"
@ -598,6 +621,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum gtk 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce17f98e7dcdc9d06b3a5f7621d796a24937c04953481205b1be267c5a02697a"
"checksum gtk-sys 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "854b56ce6d6b05945f7735651482835c5ac1f8582142ce67306726259a3dafb0"
"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 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3b37545ab726dd833ec6420aaba8231c5b320814b9029ad585555d2a03e94fbf"
"checksum libc 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)" = "8a014d9226c2cc402676fbe9ea2e15dd5222cd1dd57f576b5b283178c944a264"
"checksum log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "880f77541efa6e5cc74e76910c9884d9859683118839d6a1dc3b11e63512565b"
@ -624,6 +648,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum serde 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "f7726f29ddf9731b17ff113c461e362c381d9d69433f79de4f3dd572488823e9"
"checksum serde_derive 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cf823e706be268e73e7747b147aa31c8f633ab4ba31f115efb57e5047c3a76dd"
"checksum serde_derive_internals 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)" = "37aee4e0da52d801acfbc0cc219eb1eda7142112339726e427926a6f6ee65d3a"
"checksum serde_json 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ae1e67ce320daa7e494c578e34d4b00689f23bb94512fe0ca0dfaf02ea53fb67"
"checksum siphasher 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0df90a788073e8d0235a67e50441d47db7c8ad9debd91cbf43736a2a92d36537"
"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"

View File

@ -34,6 +34,7 @@ htmlescape = "0.3"
serde = "1.0"
serde_derive = "1.0"
toml = "0.4"
serde_json = "1.0"
#[dependencies.neovim-lib]
#git = "https://github.com/daa84/neovim-lib"

View File

@ -36,7 +36,8 @@ git clone https://aur.archlinux.org/neovim-gtk-git.git
cd neovim-gtk-git
makepkg -si
```
## flatpak
Flatpak package available [here](https://github.com/daa84/neovim-gtk-flatpak)
# Build
## Linux

View File

@ -37,6 +37,7 @@ fn main() {
.entry("Return", "\"CR\"")
.entry("Escape", "\"Esc\"")
.entry("Delete", "\"Del\"")
.entry("Insert", "\"Insert\"")
.entry("Page_Up", "\"PageUp\"")
.entry("Page_Down", "\"PageDown\"")
.entry("Enter", "\"CR\"")

View File

@ -89,15 +89,21 @@ impl Cursor {
}
pub fn reset_state(&mut self) {
self.start();
if self.state.borrow().anim_phase != AnimPhase::Busy {
self.start();
}
}
pub fn enter_focus(&mut self) {
self.start();
if self.state.borrow().anim_phase != AnimPhase::Busy {
self.start();
}
}
pub fn leave_focus(&mut self) {
self.state.borrow_mut().reset_to(AnimPhase::NoFocus);
if self.state.borrow().anim_phase != AnimPhase::Busy {
self.state.borrow_mut().reset_to(AnimPhase::NoFocus);
}
}
pub fn busy_on(&mut self) {

33
src/dirs.rs Normal file
View File

@ -0,0 +1,33 @@
use std;
use std::path::PathBuf;
pub fn get_app_config_dir_create() -> Result<PathBuf, String> {
let config_dir = get_app_config_dir()?;
std::fs::create_dir_all(&config_dir).map_err(
|e| format!("{}", e),
)?;
Ok(config_dir)
}
pub fn get_app_config_dir() -> Result<PathBuf, String> {
let mut config_dir = get_xdg_config_dir()?;
config_dir.push("nvim-gtk");
Ok(config_dir)
}
fn get_xdg_config_dir() -> Result<PathBuf, String> {
if let Ok(config_path) = std::env::var("XDG_CONFIG_HOME") {
return Ok(PathBuf::from(config_path));
}
let mut home_dir = std::env::home_dir().ok_or(
"Impossible to get your home dir!",
)?;
home_dir.push(".config");
Ok(home_dir)
}

View File

@ -18,18 +18,23 @@ extern crate log;
extern crate env_logger;
extern crate htmlescape;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate toml;
extern crate serde_json;
mod sys;
mod nvim_config;
mod dirs;
mod color;
mod value;
mod mode;
mod ui_model;
#[macro_use]
mod ui;
mod plug_manager;
mod nvim;
mod render;
mod shell;
@ -71,6 +76,8 @@ fn main() {
app.connect_open(open);
}
gtk::Window::set_default_icon_name("org.daa.NeovimGtk");
let args: Vec<String> = env::args().collect();
let argv: Vec<&str> = args.iter()
.filter(|a| !a.starts_with(BIN_PATH_ARG))

View File

@ -21,6 +21,7 @@ use neovim_lib::{Neovim, NeovimApi, Session, UiAttachOptions, CallError};
use ui::UiMutex;
use shell;
use nvim_config::NvimConfig;
#[derive(Debug)]
pub struct NvimInitError {
@ -107,6 +108,12 @@ pub fn start(
cmd.arg("--cmd").arg("let &rtp.=',runtime'");
}
if let Some(nvim_config) = NvimConfig::config_path() {
if let Some(path) = nvim_config.to_str() {
cmd.arg("--cmd").arg(format!("source {}", path));
}
}
let session = Session::new_child_cmd(&mut cmd);
let session = match session {
@ -149,16 +156,23 @@ pub fn post_start_init(
}
pub trait ErrorReport {
pub trait ErrorReport<T> {
fn report_err(&self, nvim: &mut NeovimApi);
fn ok_and_report(&self, nvim: &mut NeovimApi) -> Option<&T>;
}
impl<T> ErrorReport for result::Result<T, CallError> {
impl<T> ErrorReport<T> for result::Result<T, CallError> {
fn report_err(&self, _: &mut NeovimApi) {
if let Err(ref err) = *self {
println!("{}", err);
//nvim.report_error(&err_msg).expect("Error report error :)");
}
}
fn ok_and_report(&self, nvim: &mut NeovimApi) -> Option<&T> {
self.report_err(nvim);
self.as_ref().ok()
}
}

68
src/nvim_config.rs Normal file
View File

@ -0,0 +1,68 @@
use std::path::PathBuf;
use std::fs::{remove_file, OpenOptions};
use std::io::Write;
use dirs;
use plug_manager;
#[derive(Clone)]
pub struct NvimConfig {
plug_config: Option<plug_manager::PlugManagerConfigSource>,
}
impl NvimConfig {
const CONFIG_PATH: &'static str = "settings.vim";
pub fn new(plug_config: Option<plug_manager::PlugManagerConfigSource>) -> Self {
NvimConfig { plug_config }
}
pub fn generate_config(&self) -> Option<PathBuf> {
if self.plug_config.is_some() {
match self.write_file() {
Err(err) => {
error!("{}", err);
None
}
Ok(file) => Some(file),
}
} else {
NvimConfig::config_path().map(remove_file);
None
}
}
pub fn config_path() -> Option<PathBuf> {
if let Ok(mut path) = dirs::get_app_config_dir() {
path.push(NvimConfig::CONFIG_PATH);
if path.is_file() {
return Some(path);
}
}
None
}
fn write_file(&self) -> Result<PathBuf, String> {
let mut config_dir = dirs::get_app_config_dir_create()?;
config_dir.push(NvimConfig::CONFIG_PATH);
let mut file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(&config_dir)
.map_err(|e| format!("{}", e))?;
let content = &self.plug_config.as_ref().unwrap().source;
if !content.is_empty() {
debug!("{}", content);
file.write_all(content.as_bytes()).map_err(
|e| format!("{}", e),
)?;
}
file.sync_all().map_err(|e| format!("{}", e))?;
Ok(config_dir)
}
}

118
src/plug_manager/manager.rs Normal file
View File

@ -0,0 +1,118 @@
use std::rc::Rc;
use std::cell::RefCell;
use super::vim_plug;
use super::store::{Store, PlugInfo};
use nvim::NeovimClient;
pub struct Manager {
pub vim_plug: vim_plug::Manager,
pub store: Store,
pub plug_manage_state: PlugManageState,
}
impl Manager {
pub fn new() -> Self {
let (plug_manage_state, store) = if Store::is_config_exists() {
(PlugManageState::NvimGtk, Store::load())
} else {
(PlugManageState::Unknown, Store::empty())
};
Manager {
vim_plug: vim_plug::Manager::new(),
plug_manage_state,
store,
}
}
pub fn generate_config(&self) -> Option<PlugManagerConfigSource> {
if self.store.is_enabled() {
Some(PlugManagerConfigSource::new(&self.store))
} else {
None
}
}
pub fn init_nvim_client(&mut self, nvim: Rc<RefCell<NeovimClient>>) {
self.vim_plug.initialize(nvim);
}
pub fn reload_store(&mut self) {
match self.plug_manage_state {
PlugManageState::Unknown => {
if self.vim_plug.is_loaded() {
self.store = Store::load_from_plug(&self.vim_plug);
self.plug_manage_state = PlugManageState::VimPlug;
} else {
self.store = Store::empty();
}
}
PlugManageState::NvimGtk => {
if Store::is_config_exists() {
self.store = Store::load();
} else {
self.store = Store::empty();
}
}
PlugManageState::VimPlug => {
if Store::is_config_exists() {
self.store = Store::load();
self.plug_manage_state = PlugManageState::NvimGtk;
} else {
self.store = Store::empty();
}
}
}
if let PlugManageState::Unknown = self.plug_manage_state {
if self.vim_plug.is_loaded() {
self.store = Store::load_from_plug(&self.vim_plug);
self.plug_manage_state = PlugManageState::VimPlug;
}
}
}
pub fn save(&self) {
self.store.save();
}
pub fn clear_removed(&mut self) {
self.store.clear_removed();
}
pub fn add_plug(&mut self, plug: PlugInfo) -> bool {
self.store.add_plug(plug)
}
pub fn move_item(&mut self, idx: usize, offset: i32) {
self.store.move_item(idx, offset);
}
}
pub enum PlugManageState {
NvimGtk,
VimPlug,
Unknown,
}
#[derive(Clone)]
pub struct PlugManagerConfigSource {
pub source: String,
}
impl PlugManagerConfigSource {
pub fn new(store: &Store) -> Self {
let mut builder = "call plug#begin()\n".to_owned();
for plug in store.get_plugs() {
if !plug.removed {
builder += &format!("Plug '{}', {{ 'as': '{}' }}\n", plug.get_plug_path(), plug.name);
}
}
builder += "call plug#end()\n";
PlugManagerConfigSource { source: builder }
}
}

9
src/plug_manager/mod.rs Normal file
View File

@ -0,0 +1,9 @@
mod ui;
mod vim_plug;
mod store;
mod manager;
mod plugin_settings_dlg;
mod vimawesome;
pub use self::ui::Ui;
pub use self::manager::{Manager, PlugManagerConfigSource};

View File

@ -0,0 +1,114 @@
use gtk;
use gtk::prelude::*;
use super::store;
pub struct Builder<'a> {
title: &'a str,
}
impl<'a> Builder<'a> {
pub fn new(title: &'a str) -> Self {
Builder { title }
}
pub fn show<F: IsA<gtk::Window>>(&self, parent: &F) -> Option<store::PlugInfo> {
let dlg = gtk::Dialog::new_with_buttons(
Some(self.title),
Some(parent),
gtk::DIALOG_USE_HEADER_BAR | gtk::DIALOG_DESTROY_WITH_PARENT,
&[
("Cancel", gtk::ResponseType::Cancel.into()),
("Ok", gtk::ResponseType::Ok.into()),
],
);
let content = dlg.get_content_area();
let border = gtk::Box::new(gtk::Orientation::Horizontal, 0);
border.set_border_width(12);
let list = gtk::ListBox::new();
list.set_selection_mode(gtk::SelectionMode::None);
let path = gtk::Box::new(gtk::Orientation::Horizontal, 5);
path.set_border_width(5);
let path_lbl = gtk::Label::new("Repo");
let path_e = gtk::Entry::new();
path_e.set_placeholder_text("user_name/repo_name");
path.pack_start(&path_lbl, true, true, 0);
path.pack_end(&path_e, false, true, 0);
list.add(&path);
let name = gtk::Box::new(gtk::Orientation::Horizontal, 5);
name.set_border_width(5);
let name_lbl = gtk::Label::new("Name");
let name_e = gtk::Entry::new();
name.pack_start(&name_lbl, true, true, 0);
name.pack_end(&name_e, false, true, 0);
list.add(&name);
border.pack_start(&list, true, true, 0);
content.add(&border);
content.show_all();
path_e.connect_changed(clone!(name_e => move |p| {
if let Some(name) = p.get_text().and_then(|t| extract_name(&t)) {
name_e.set_text(&name);
}
}));
let ok: i32 = gtk::ResponseType::Ok.into();
let res = if dlg.run() == ok {
path_e.get_text().map(|path| {
let name = name_e
.get_text()
.and_then(|name| if name.trim().is_empty() {
None
} else {
Some(name)
})
.or_else(|| extract_name(&path))
.unwrap_or_else(|| path.clone());
store::PlugInfo::new(name.to_owned(), path.to_owned())
})
} else {
None
};
dlg.destroy();
res
}
}
fn extract_name(path: &str) -> Option<String> {
if let Some(idx) = path.rfind(|c| c == '/' || c == '\\') {
if idx < path.len() - 1 {
let path = path.trim_right_matches(".git");
Some(path[idx + 1..].to_owned())
} else {
None
}
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_name() {
assert_eq!(
Some("plugin_name".to_owned()),
extract_name("http://github.com/somebody/plugin_name.git")
);
}
}

155
src/plug_manager/store.rs Normal file
View File

@ -0,0 +1,155 @@
use toml;
use settings::SettingsLoader;
use super::vim_plug;
pub struct Store {
settings: Settings,
}
impl Store {
pub fn is_config_exists() -> bool {
Settings::is_file_exists()
}
pub fn is_enabled(&self) -> bool {
self.settings.enabled
}
pub fn load() -> Self {
Store { settings: Settings::load() }
}
pub fn empty() -> Self {
Store { settings: Settings::empty() }
}
pub fn load_from_plug(vim_plug: &vim_plug::Manager) -> Self {
let settings = match vim_plug.get_plugs() {
Err(msg) => {
error!("{}", msg);
Settings::empty()
}
Ok(plugs) => {
let plugs = plugs
.iter()
.map(|vpi| PlugInfo::new(vpi.name.to_owned(), vpi.uri.to_owned()))
.collect();
Settings::new(plugs)
}
};
Store { settings }
}
pub fn get_plugs(&self) -> &[PlugInfo] {
&self.settings.plugs
}
pub fn set_enabled(&mut self, enabled: bool) {
self.settings.enabled = enabled;
}
pub fn clear_removed(&mut self) {
self.settings.plugs.retain(|p| !p.removed);
}
pub fn save(&self) {
self.settings.save();
}
pub fn remove_plug(&mut self, idx: usize) {
self.settings.plugs[idx].removed = true;
}
pub fn restore_plug(&mut self, idx: usize) {
self.settings.plugs[idx].removed = false;
}
pub fn add_plug(&mut self, plug: PlugInfo) -> bool {
let path = plug.get_plug_path();
if self.settings.plugs.iter().any(|p| p.get_plug_path() == path || p.name == plug.name) {
return false;
}
self.settings.plugs.push(plug);
true
}
pub fn plugs_count(&self) -> usize {
self.settings.plugs.len()
}
pub fn move_item(&mut self, idx: usize, offset: i32) {
let plug = self.settings.plugs.remove(idx);
self.settings.plugs.insert((idx as i32 + offset) as usize, plug);
}
}
#[derive(Serialize, Deserialize)]
struct Settings {
enabled: bool,
plugs: Vec<PlugInfo>,
}
impl Settings {
fn new(plugs: Vec<PlugInfo>) -> Self {
Settings { plugs, enabled: false }
}
}
impl SettingsLoader for Settings {
const SETTINGS_FILE: &'static str = "plugs.toml";
fn empty() -> Self {
Settings { plugs: vec![], enabled: false }
}
fn from_str(s: &str) -> Result<Self, String> {
toml::from_str(&s).map_err(|e| format!("{}", e))
}
}
#[derive(Serialize, Deserialize)]
pub struct PlugInfo {
pub name: String,
pub url: String,
pub removed: bool,
}
impl PlugInfo {
pub fn new(name: String, url: String) -> Self {
PlugInfo {
name,
url,
removed: false,
}
}
pub fn get_plug_path(&self) -> String {
if self.url.contains("github.com") {
let mut path_comps: Vec<&str> = self.url
.trim_right_matches(".git")
.rsplit('/')
.take(2)
.collect();
path_comps.reverse();
path_comps.join("/")
} else {
self.url.clone()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_plug_path() {
let plug = PlugInfo::new(
"rust.vim".to_owned(),
"https://git::@github.com/rust-lang/rust.vim.git".to_owned(),
);
assert_eq!("rust-lang/rust.vim".to_owned(), plug.get_plug_path());
}
}

441
src/plug_manager/ui.rs Normal file
View File

@ -0,0 +1,441 @@
use std::sync::Arc;
use std::rc::Rc;
use std::cell::RefCell;
use std::ops::Deref;
use ui::UiMutex;
use gtk;
use gtk::prelude::*;
use gtk_sys;
use super::manager;
use super::store::{Store, PlugInfo};
use super::plugin_settings_dlg;
use super::vimawesome;
use nvim_config::NvimConfig;
pub struct Ui<'a> {
manager: &'a Arc<UiMutex<manager::Manager>>,
}
impl<'a> Ui<'a> {
pub fn new(manager: &'a Arc<UiMutex<manager::Manager>>) -> Ui<'a> {
manager.borrow_mut().reload_store();
Ui { manager }
}
pub fn show<T: IsA<gtk::Window>>(&mut self, parent: &T) {
let dlg = gtk::Dialog::new_with_buttons(
Some("Plug"),
Some(parent),
gtk::DIALOG_DESTROY_WITH_PARENT,
&[
("Cancel", gtk::ResponseType::Cancel.into()),
("Ok", gtk::ResponseType::Ok.into()),
],
);
dlg.set_default_size(800, 600);
let content = dlg.get_content_area();
let header_bar = gtk::HeaderBar::new();
let add_plug_btn = gtk::Button::new_with_label("Add..");
add_plug_btn.get_style_context().map(|c| c.add_class("suggested-action"));
header_bar.pack_end(&add_plug_btn);
let enable_swc = gtk::Switch::new();
enable_swc.set_valign(gtk::Align::Center);
enable_swc.show();
header_bar.pack_end(&enable_swc);
header_bar.set_title("Plug");
header_bar.set_show_close_button(true);
header_bar.show();
dlg.set_titlebar(&header_bar);
let pages = SettingsPages::new(
clone!(add_plug_btn => move |row_name| if row_name == "plugins" {
add_plug_btn.show();
} else {
add_plug_btn.hide();
}),
);
enable_swc.set_state(self.manager.borrow().store.is_enabled());
let plugins = gtk::Box::new(gtk::Orientation::Vertical, 3);
let plugs_panel = self.fill_plugin_list(&plugins, &self.manager.borrow().store);
add_vimawesome_tab(&pages, &self.manager, &plugs_panel);
let plugins_lbl = gtk::Label::new("Plugins");
pages.add_page(&plugins_lbl, &plugins, "plugins");
add_help_tab(
&pages,
&format!(
"NeovimGtk plugin manager is a GUI for vim-plug.\n\
It can load plugins from vim-plug configuration if vim-plug sarted and NeovimGtk manager settings is empty.\n\
When enabled it generate and load vim-plug as simple vim file at startup before init.vim is processed.\n\
So <b>after</b> enabling this manager <b>you must disable vim-plug</b> configuration in init.vim.\n\
This manager currently only manage vim-plug configuration and do not any actions on plugin management.\n\
So you must call all vim-plug (PlugInstall, PlugUpdate, PlugClean) commands manually.\n\
Current configuration source is <b>{}</b>",
match self.manager.borrow().plug_manage_state {
manager::PlugManageState::NvimGtk => "NeovimGtk config file",
manager::PlugManageState::VimPlug => "loaded from vim-plug",
manager::PlugManageState::Unknown => "Unknown",
}
),
);
let manager_ref = self.manager.clone();
enable_swc.connect_state_set(move |_, state| {
manager_ref.borrow_mut().store.set_enabled(state);
Inhibit(false)
});
let manager_ref = self.manager.clone();
add_plug_btn.connect_clicked(clone!(dlg => move |_| {
show_add_plug_dlg(&dlg, &manager_ref, &plugs_panel);
}));
content.pack_start(&*pages, true, true, 0);
content.show_all();
let ok: i32 = gtk::ResponseType::Ok.into();
if dlg.run() == ok {
let mut manager = self.manager.borrow_mut();
manager.clear_removed();
manager.save();
if let Some(config_path) = NvimConfig::new(manager.generate_config()).generate_config() {
if let Some(path) = config_path.to_str() {
manager.vim_plug.reload(path);
}
}
}
dlg.destroy();
}
fn fill_plugin_list(&self, panel: &gtk::Box, store: &Store) -> gtk::ListBox {
let scroll = gtk::ScrolledWindow::new(None, None);
scroll.get_style_context().map(|c| c.add_class("view"));
let plugs_panel = gtk::ListBox::new();
for (idx, plug_info) in store.get_plugs().iter().enumerate() {
let row = create_plug_row(idx, plug_info, &self.manager);
plugs_panel.add(&row);
}
scroll.add(&plugs_panel);
panel.pack_start(&scroll, true, true, 5);
panel.pack_start(
&create_up_down_btns(&plugs_panel, &self.manager),
false,
true,
0,
);
plugs_panel
}
}
fn create_up_down_btns(
plugs_panel: &gtk::ListBox,
manager: &Arc<UiMutex<manager::Manager>>,
) -> gtk::Box {
let buttons_panel = gtk::Box::new(gtk::Orientation::Horizontal, 5);
let up_btn = gtk::Button::new_from_icon_name("go-up-symbolic", gtk_sys::GTK_ICON_SIZE_BUTTON as i32);
let down_btn = gtk::Button::new_from_icon_name("go-down-symbolic", gtk_sys::GTK_ICON_SIZE_BUTTON as i32);
up_btn.connect_clicked(clone!(plugs_panel, manager => move |_| {
if let Some(row) = plugs_panel.get_selected_row() {
let idx = row.get_index();
if idx > 0 {
plugs_panel.unselect_row(&row);
plugs_panel.remove(&row);
plugs_panel.insert(&row, idx - 1);
plugs_panel.select_row(&row);
manager.borrow_mut().move_item(idx as usize, -1);
}
}
}));
down_btn.connect_clicked(clone!(plugs_panel, manager => move |_| {
if let Some(row) = plugs_panel.get_selected_row() {
let idx = row.get_index();
let mut manager = manager.borrow_mut();
if idx >= 0 && idx < manager.store.plugs_count() as i32 {
plugs_panel.unselect_row(&row);
plugs_panel.remove(&row);
plugs_panel.insert(&row, idx + 1);
plugs_panel.select_row(&row);
manager.move_item(idx as usize, 1);
}
}
}));
buttons_panel.pack_start(&up_btn, false, true, 0);
buttons_panel.pack_start(&down_btn, false, true, 0);
buttons_panel.set_halign(gtk::Align::Center);
buttons_panel
}
fn populate_get_plugins(
query: Option<String>,
get_plugins: &gtk::Box,
manager: Arc<UiMutex<manager::Manager>>,
plugs_panel: gtk::ListBox,
) {
let plugs_panel = UiMutex::new(plugs_panel);
let get_plugins = UiMutex::new(get_plugins.clone());
vimawesome::call(query, move |res| {
let panel = get_plugins.borrow();
for child in panel.get_children() {
panel.remove(&child);
}
match res {
Ok(list) => {
let result = vimawesome::build_result_panel(&list, move |new_plug| {
add_plugin(&manager, &*plugs_panel.borrow(), new_plug);
});
panel.pack_start(&result, true, true, 0);
}
Err(e) => {
panel.pack_start(&gtk::Label::new(format!("{}", e).as_str()), false, true, 0);
error!("{}", e)
}
}
});
}
fn create_plug_row(
plug_idx: usize,
plug_info: &PlugInfo,
manager: &Arc<UiMutex<manager::Manager>>,
) -> gtk::ListBoxRow {
let row = gtk::ListBoxRow::new();
let row_container = gtk::Box::new(gtk::Orientation::Vertical, 5);
row_container.set_border_width(5);
let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 5);
let label_box = create_plug_label(plug_info);
let button_box = gtk::Box::new(gtk::Orientation::Horizontal, 0);
button_box.set_halign(gtk::Align::End);
let exists_button_box = gtk::Box::new(gtk::Orientation::Horizontal, 5);
let remove_btn = gtk::Button::new_with_label("Remove");
exists_button_box.pack_start(&remove_btn, false, true, 0);
let undo_btn = gtk::Button::new_with_label("Undo");
row_container.pack_start(&hbox, true, true, 0);
hbox.pack_start(&label_box, true, true, 0);
button_box.pack_start(&exists_button_box, false, true, 0);
hbox.pack_start(&button_box, false, true, 0);
row.add(&row_container);
remove_btn.connect_clicked(
clone!(manager, label_box, button_box, exists_button_box, undo_btn => move |_| {
label_box.set_sensitive(false);
button_box.remove(&exists_button_box);
button_box.pack_start(&undo_btn, false, true, 0);
button_box.show_all();
manager.borrow_mut().store.remove_plug(plug_idx);
}),
);
undo_btn.connect_clicked(
clone!(manager, label_box, button_box, exists_button_box, undo_btn => move |_| {
label_box.set_sensitive(true);
button_box.remove(&undo_btn);
button_box.pack_start(&exists_button_box, false, true, 0);
button_box.show_all();
manager.borrow_mut().store.restore_plug(plug_idx);
}),
);
row
}
fn show_add_plug_dlg<F: IsA<gtk::Window>>(
parent: &F,
manager: &Arc<UiMutex<manager::Manager>>,
plugs_panel: &gtk::ListBox,
) {
if let Some(new_plugin) = plugin_settings_dlg::Builder::new("Add plugin").show(parent) {
add_plugin(manager, plugs_panel, new_plugin);
}
}
fn add_plugin(
manager: &Arc<UiMutex<manager::Manager>>,
plugs_panel: &gtk::ListBox,
new_plugin: PlugInfo,
) -> bool {
let row = create_plug_row(manager.borrow().store.plugs_count(), &new_plugin, manager);
if manager.borrow_mut().add_plug(new_plugin) {
row.show_all();
plugs_panel.add(&row);
true
} else {
let dlg = gtk::MessageDialog::new(
None::<&gtk::Window>,
gtk::DialogFlags::empty(),
gtk::MessageType::Error,
gtk::ButtonsType::Ok,
"Plugin with this name or path already exists",
);
dlg.run();
dlg.destroy();
false
}
}
fn create_plug_label(plug_info: &PlugInfo) -> gtk::Box {
let label_box = gtk::Box::new(gtk::Orientation::Vertical, 5);
let name_lbl = gtk::Label::new(None);
name_lbl.set_markup(&format!("<b>{}</b>", plug_info.name));
name_lbl.set_halign(gtk::Align::Start);
let url_lbl = gtk::Label::new(Some(plug_info.get_plug_path().as_str()));
url_lbl.set_halign(gtk::Align::Start);
label_box.pack_start(&name_lbl, true, true, 0);
label_box.pack_start(&url_lbl, true, true, 0);
label_box
}
fn add_vimawesome_tab(
pages: &SettingsPages,
manager: &Arc<UiMutex<manager::Manager>>,
plugs_panel: &gtk::ListBox,
) {
let get_plugins = gtk::Box::new(gtk::Orientation::Vertical, 0);
let spinner = gtk::Spinner::new();
let get_plugins_lbl = gtk::Label::new("Get Plugins");
pages.add_page(&get_plugins_lbl, &get_plugins, "get_plugins");
let list_panel = gtk::Box::new(gtk::Orientation::Vertical, 0);
let link_button = gtk::Label::new(None);
link_button.set_markup(
"Plugins are taken from: <a href=\"https://vimawesome.com\">https://vimawesome.com</a>",
);
let search_entry = gtk::SearchEntry::new();
get_plugins.pack_start(&link_button, false, true, 10);
get_plugins.pack_start(&search_entry, false, true, 5);
get_plugins.pack_start(&list_panel, true, true, 0);
list_panel.pack_start(&spinner, true, true, 0);
spinner.start();
search_entry.connect_activate(clone!(list_panel, manager, plugs_panel => move |se| {
let spinner = gtk::Spinner::new();
list_panel.pack_start(&spinner, false, true, 5);
spinner.show();
spinner.start();
populate_get_plugins(se.get_text(), &list_panel, manager.clone(), plugs_panel.clone());
}));
gtk::idle_add(clone!(manager, plugs_panel => move || {
populate_get_plugins(None, &list_panel, manager.clone(), plugs_panel.clone());
Continue(false)
}));
}
fn add_help_tab(pages: &SettingsPages, markup: &str) {
let help = gtk::Box::new(gtk::Orientation::Vertical, 3);
let label = gtk::Label::new(None);
label.set_markup(markup);
help.pack_start(&label, true, false, 0);
let help_lbl = gtk::Label::new("Help");
pages.add_page(&help_lbl, &help, "help");
}
struct SettingsPages {
categories: gtk::ListBox,
stack: gtk::Stack,
content: gtk::Box,
rows: Rc<RefCell<Vec<(gtk::ListBoxRow, &'static str)>>>,
}
impl SettingsPages {
pub fn new<F: Fn(&str) + 'static>(row_selected: F) -> Self {
let content = gtk::Box::new(gtk::Orientation::Horizontal, 5);
let categories = gtk::ListBox::new();
categories.get_style_context().map(|c| c.add_class("view"));
let stack = gtk::Stack::new();
stack.set_transition_type(gtk::StackTransitionType::Crossfade);
let rows: Rc<RefCell<Vec<(gtk::ListBoxRow, &'static str)>>> =
Rc::new(RefCell::new(Vec::new()));
content.pack_start(&categories, false, true, 0);
content.pack_start(&stack, true, true, 0);
categories.connect_row_selected(
clone!(stack, rows => move |_, row| if let &Some(ref row) = row {
if let Some(ref r) = rows.borrow().iter().find(|r| r.0 == *row) {
if let Some(child) = stack.get_child_by_name(&r.1) {
stack.set_visible_child(&child);
row_selected(&r.1);
}
}
}),
);
SettingsPages {
categories,
stack,
content,
rows,
}
}
fn add_page<W: gtk::IsA<gtk::Widget>>(
&self,
label: &gtk::Label,
widget: &W,
name: &'static str,
) {
let row = gtk::ListBoxRow::new();
let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 0);
hbox.set_border_width(12);
hbox.pack_start(label, false, true, 0);
row.add(&hbox);
self.categories.add(&row);
self.stack.add_named(widget, name);
self.rows.borrow_mut().push((row, name));
}
}
impl Deref for SettingsPages {
type Target = gtk::Box;
fn deref(&self) -> &gtk::Box {
&self.content
}
}

View File

@ -0,0 +1,109 @@
use std::rc::Rc;
use std::cell::{RefCell, RefMut};
use neovim_lib::{Neovim, NeovimApi};
use nvim::{NeovimClient, ErrorReport};
use value::ValueMapExt;
pub struct Manager {
nvim: Option<Rc<RefCell<NeovimClient>>>,
}
impl Manager {
pub fn new() -> Self {
Manager { nvim: None }
}
pub fn initialize(&mut self, nvim: Rc<RefCell<NeovimClient>>) {
self.nvim = Some(nvim);
}
fn nvim(&self) -> Option<RefMut<Neovim>> {
let nvim_client = self.nvim.as_ref().unwrap();
if nvim_client.borrow().is_initialized() {
Some(RefMut::map(nvim_client.borrow_mut(), |n| n.nvim_mut()))
} else {
None
}
}
pub fn get_plugs(&self) -> Result<Box<[VimPlugInfo]>, String> {
if let Some(mut nvim) = self.nvim() {
let g_plugs = nvim.eval("g:plugs").map_err(|e| {
format!("Can't retrive g:plugs map: {}", e)
})?;
let plugs_map = g_plugs
.as_map()
.ok_or("Can't retrive g:plugs map".to_owned())?
.to_attrs_map()?;
let g_plugs_order = nvim.eval("g:plugs_order").map_err(|e| format!("{}", e))?;
let order_arr = g_plugs_order.as_array().ok_or(
"Can't find g:plugs_order array"
.to_owned(),
)?;
let plugs_info: Vec<VimPlugInfo> = order_arr
.iter()
.map(|n| n.as_str())
.filter_map(|name| if let Some(name) = name {
plugs_map
.get(name)
.and_then(|desc| desc.as_map())
.and_then(|desc| desc.to_attrs_map().ok())
.and_then(|desc| {
let uri = desc.get("uri").and_then(|uri| uri.as_str());
if let Some(uri) = uri {
Some(VimPlugInfo::new(name.to_owned(), uri.to_owned()))
} else {
None
}
})
} else {
None
})
.collect();
Ok(plugs_info.into_boxed_slice())
} else {
Err("Nvim not initialized".to_owned())
}
}
pub fn is_loaded(&self) -> bool {
if let Some(mut nvim) = self.nvim() {
let loaded_plug = nvim.eval("exists('g:loaded_plug')");
loaded_plug
.ok_and_report(&mut *nvim)
.and_then(|loaded_plug| loaded_plug.as_i64())
.map_or(false, |loaded_plug| if loaded_plug > 0 {
true
} else {
false
})
} else {
false
}
}
pub fn reload(&self, path: &str) {
if let Some(mut nvim) = self.nvim() {
nvim.command(&format!("source {}", path)).report_err(&mut *nvim);
}
}
}
#[derive(Debug)]
pub struct VimPlugInfo {
pub name: String,
pub uri: String,
}
impl VimPlugInfo {
pub fn new(name: String, uri: String) -> Self {
VimPlugInfo { name, uri }
}
}

View File

@ -0,0 +1,162 @@
use std::io;
use std::thread;
use std::rc::Rc;
use std::process::{Command, Stdio};
use serde_json;
use gtk;
use gtk::prelude::*;
use glib;
use super::store::PlugInfo;
pub fn call<F>(query: Option<String>, cb: F)
where
F: FnOnce(io::Result<DescriptionList>) + Send + 'static,
{
thread::spawn(move || {
let mut result = Some(request(query.as_ref().map(|s| s.as_ref())));
let mut cb = Some(cb);
glib::idle_add(move || {
cb.take().unwrap()(result.take().unwrap());
Continue(false)
})
});
}
fn request(query: Option<&str>) -> io::Result<DescriptionList> {
let child = Command::new("curl")
.arg("-s")
.arg(format!(
"https://vimawesome.com/api/plugins?query={}&page=1",
query.unwrap_or("")
))
.stdout(Stdio::piped())
.spawn()?;
let out = child.wait_with_output()?;
if out.status.success() {
if out.stdout.is_empty() {
Ok(DescriptionList::empty())
} else {
let description_list: DescriptionList = serde_json::from_slice(&out.stdout).map_err(|e| {
io::Error::new(io::ErrorKind::Other, e)
})?;
Ok(description_list)
}
} else {
Err(io::Error::new(
io::ErrorKind::Other,
format!(
"curl exit with error:\n{}",
match out.status.code() {
Some(code) => format!("Exited with status code: {}", code),
None => "Process terminated by signal".to_owned(),
}
),
))
}
}
pub fn build_result_panel<F: Fn(PlugInfo) + 'static>(
list: &DescriptionList,
add_cb: F,
) -> gtk::ScrolledWindow {
let scroll = gtk::ScrolledWindow::new(None, None);
scroll.get_style_context().map(|c| c.add_class("view"));
let panel = gtk::ListBox::new();
let cb_ref = Rc::new(add_cb);
for plug in list.plugins.iter() {
let row = create_plug_row(plug, cb_ref.clone());
panel.add(&row);
}
scroll.add(&panel);
scroll.show_all();
scroll
}
fn create_plug_row<F: Fn(PlugInfo) + 'static>(
plug: &Description,
add_cb: Rc<F>,
) -> gtk::ListBoxRow {
let row = gtk::ListBoxRow::new();
let row_container = gtk::Box::new(gtk::Orientation::Vertical, 5);
row_container.set_border_width(5);
let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 5);
let label_box = create_plug_label(plug);
let button_box = gtk::Box::new(gtk::Orientation::Horizontal, 0);
button_box.set_halign(gtk::Align::End);
let add_btn = gtk::Button::new_with_label("Install");
button_box.pack_start(&add_btn, false, true, 0);
row_container.pack_start(&hbox, true, true, 0);
hbox.pack_start(&label_box, true, true, 0);
hbox.pack_start(&button_box, false, true, 0);
row.add(&row_container);
add_btn.connect_clicked(clone!(plug => move |btn| {
if let Some(ref github_url) = plug.github_url {
btn.set_sensitive(false);
add_cb(PlugInfo::new(plug.name.clone(), github_url.clone()));
}
}));
row
}
fn create_plug_label(plug: &Description) -> gtk::Box {
let label_box = gtk::Box::new(gtk::Orientation::Vertical, 5);
let name_lbl = gtk::Label::new(None);
name_lbl.set_markup(&format!(
"<b>{}</b> by {}",
plug.name,
plug.author.as_ref().map(|s| s.as_ref()).unwrap_or(
"unknown",
)
));
name_lbl.set_halign(gtk::Align::Start);
let url_lbl = gtk::Label::new(None);
if let Some(url) = plug.github_url.as_ref() {
url_lbl.set_markup(&format!("<a href=\"{}\">{}</a>", url, url));
}
url_lbl.set_halign(gtk::Align::Start);
label_box.pack_start(&name_lbl, true, true, 0);
label_box.pack_start(&url_lbl, true, true, 0);
label_box
}
#[derive(Deserialize, Debug)]
pub struct DescriptionList {
pub plugins: Box<[Description]>,
}
impl DescriptionList {
fn empty() -> DescriptionList {
DescriptionList {
plugins: Box::new([]),
}
}
}
#[derive(Deserialize, Debug, Clone)]
pub struct Description {
pub name: String,
pub github_url: Option<String>,
pub author: Option<String>,
pub github_stars: Option<i64>,
}

View File

@ -532,14 +532,9 @@ impl Entry {
// ----- Store / Load settings
//
use std::path::PathBuf;
use std::fs::File;
use std::io::prelude::*;
use std;
use settings::SettingsLoader;
use toml;
const PROJECTS_SETTINGS_FILE: &str = "projects.toml";
#[derive(Serialize, Deserialize)]
struct ProjectSettings {
projects: Vec<ProjectEntrySettings>,
@ -564,87 +559,21 @@ impl ProjectEntrySettings {
}
}
impl ProjectSettings {
fn new(projects: Vec<ProjectEntrySettings>) -> ProjectSettings {
ProjectSettings { projects }
}
impl SettingsLoader for ProjectSettings {
const SETTINGS_FILE: &'static str = "projects.toml";
fn empty() -> ProjectSettings {
ProjectSettings { projects: vec![] }
}
fn load_from_file(path: &Path) -> Result<ProjectSettings, String> {
if path.exists() {
let mut file = File::open(path).map_err(|e| format!("{}", e))?;
let mut contents = String::new();
file.read_to_string(&mut contents)
.map_err(|e| format!("{}", e))?;
toml::from_str(&contents).map_err(|e| format!("{}", e))
} else {
Ok(ProjectSettings::empty())
}
}
fn load_err() -> Result<ProjectSettings, String> {
let mut toml_path = get_app_config_dir_create()?;
toml_path.push(PROJECTS_SETTINGS_FILE);
ProjectSettings::load_from_file(&toml_path)
}
fn load() -> ProjectSettings {
match ProjectSettings::load_err() {
Ok(settings) => settings,
Err(e) => {
println!("{}", e);
ProjectSettings::empty()
}
}
}
fn save_err(&self) -> Result<(), String> {
let mut toml_path = get_app_config_dir_create()?;
toml_path.push(PROJECTS_SETTINGS_FILE);
let mut file = File::create(toml_path).map_err(|e| format!("{}", e))?;
let contents = toml::to_vec(self).map_err(|e| format!("{}", e))?;
file.write_all(&contents).map_err(|e| format!("{}", e))?;
Ok(())
}
pub fn save(&self) {
match self.save_err() {
Ok(()) => (),
Err(e) => println!("{}", e),
}
fn from_str(s: &str) -> Result<Self, String> {
toml::from_str(&s).map_err(|e| format!("{}", e))
}
}
fn get_app_config_dir_create() -> Result<PathBuf, String> {
let config_dir = get_app_config_dir()?;
std::fs::create_dir_all(&config_dir)
.map_err(|e| format!("{}", e))?;
Ok(config_dir)
}
fn get_app_config_dir() -> Result<PathBuf, String> {
let mut config_dir = get_xdg_config_dir()?;
config_dir.push("nvim-gtk");
Ok(config_dir)
}
fn get_xdg_config_dir() -> Result<PathBuf, String> {
if let Ok(config_path) = std::env::var("XDG_CONFIG_HOME") {
return Ok(PathBuf::from(config_path));
impl ProjectSettings {
fn new(projects: Vec<ProjectEntrySettings>) -> ProjectSettings {
ProjectSettings { projects }
}
let mut home_dir = std::env::home_dir()
.ok_or("Impossible to get your home dir!")?;
home_dir.push(".config");
Ok(home_dir)
}

View File

@ -101,3 +101,78 @@ fn monospace_font_changed(mut shell: &mut Shell, state: &mut State) {
state.update_font(&mut shell);
}
}
use std::path::Path;
use std::fs::File;
use std::io::prelude::*;
use toml;
use serde;
use dirs;
pub trait SettingsLoader: Sized + serde::Serialize {
const SETTINGS_FILE: &'static str;
fn empty() -> Self;
fn from_str(s: &str) -> Result<Self, String>;
fn load() -> Self {
match load_err() {
Ok(settings) => settings,
Err(e) => {
println!("{}", e);
Self::empty()
}
}
}
fn is_file_exists() -> bool {
if let Ok(mut toml_path) = dirs::get_app_config_dir() {
toml_path.push(Self::SETTINGS_FILE);
toml_path.is_file()
} else {
false
}
}
fn save(&self) {
match save_err(self) {
Ok(()) => (),
Err(e) => error!("{}", e),
}
}
}
fn load_from_file<T: SettingsLoader>(path: &Path) -> Result<T, String> {
if path.exists() {
let mut file = File::open(path).map_err(|e| format!("{}", e))?;
let mut contents = String::new();
file.read_to_string(&mut contents).map_err(
|e| format!("{}", e),
)?;
T::from_str(&contents)
} else {
Ok(T::empty())
}
}
fn load_err<T: SettingsLoader>() -> Result<T, String> {
let mut toml_path = dirs::get_app_config_dir_create()?;
toml_path.push(T::SETTINGS_FILE);
load_from_file(&toml_path)
}
fn save_err<T: SettingsLoader>(sl: &T) -> Result<(), String> {
let mut toml_path = dirs::get_app_config_dir_create()?;
toml_path.push(T::SETTINGS_FILE);
let mut file = File::create(toml_path).map_err(|e| format!("{}", e))?;
let contents = toml::to_vec::<T>(sl).map_err(|e| format!("{}", e))?;
file.write_all(&contents).map_err(|e| format!("{}", e))?;
Ok(())
}

View File

@ -79,6 +79,7 @@ pub struct State {
options: ShellOptions,
detach_cb: Option<Box<RefCell<FnMut() + Send + 'static>>>,
nvim_started_cb: Option<Box<RefCell<FnMut() + Send + 'static>>>,
}
impl State {
@ -112,6 +113,7 @@ impl State {
options,
detach_cb: None,
nvim_started_cb: None,
}
}
@ -156,6 +158,17 @@ impl State {
}
}
pub fn set_nvim_started_cb<F>(&mut self, cb: Option<F>)
where
F: FnMut() + Send + 'static,
{
if cb.is_some() {
self.nvim_started_cb = Some(Box::new(RefCell::new(cb.unwrap())));
} else {
self.nvim_started_cb = None;
}
}
pub fn get_font_desc(&self) -> &FontDescription {
self.font_ctx.font_description()
}
@ -581,6 +594,14 @@ impl Shell {
let mut state = self.state.borrow_mut();
state.set_detach_cb(cb);
}
pub fn set_nvim_started_cb<F>(&self, cb: Option<F>)
where
F: FnMut() + Send + 'static,
{
let mut state = self.state.borrow_mut();
state.set_nvim_started_cb(cb);
}
}
impl Deref for Shell {
@ -643,10 +664,7 @@ fn gtk_button_press(shell: &mut State, ui_state: &mut UiState, ev: &EventButton)
match ev.get_button() {
1 => mouse_input(shell, "LeftMouse", ev.get_state(), ev.get_position()),
2 => {
mouse_input(shell, "LeftMouse", ev.get_state(), ev.get_position());
shell.edit_paste("*");
}
2 => mouse_input(shell, "MiddleMouse", ev.get_state(), ev.get_position()),
3 => mouse_input(shell, "RightMouse", ev.get_state(), ev.get_position()),
_ => (),
}
@ -781,7 +799,7 @@ fn init_nvim_async(
fn set_nvim_initialized(nvim: Neovim, state_arc: Arc<UiMutex<State>>) {
let mut nvim = Some(nvim);
glib::idle_add(move || {
glib::idle_add(clone!(state_arc => move || {
let mut state = state_arc.borrow_mut();
state.nvim.borrow_mut().set_initialized(
nvim.take().unwrap(),
@ -789,7 +807,10 @@ fn set_nvim_initialized(nvim: Neovim, state_arc: Arc<UiMutex<State>>) {
state.cursor.as_mut().unwrap().start();
Continue(false)
});
}));
idle_cb_call!(state_arc.nvim_started_cb());
}
fn draw_initializing(state: &State, ctx: &cairo::Context) {

View File

@ -14,6 +14,24 @@ use settings::Settings;
use shell::{Shell, ShellOptions};
use shell_dlg;
use project::Projects;
use plug_manager;
macro_rules! clone {
(@param _) => ( _ );
(@param $x:ident) => ( $x );
($($n:ident),+ => move || $body:expr) => (
{
$( let $n = $n.clone(); )+
move || $body
}
);
($($n:ident),+ => move |$($p:tt),+| $body:expr) => (
{
$( let $n = $n.clone(); )+
move |$(clone!(@param $p),)+| $body
}
);
}
pub struct Ui {
initialized: bool,
@ -21,6 +39,7 @@ pub struct Ui {
settings: Rc<RefCell<Settings>>,
shell: Rc<RefCell<Shell>>,
projects: Rc<RefCell<Projects>>,
plug_manager: Arc<UiMutex<plug_manager::Manager>>,
}
pub struct Components {
@ -30,8 +49,8 @@ pub struct Components {
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 save_image =
Image::new_from_icon_name("document-open", gtk_sys::GTK_ICON_SIZE_SMALL_TOOLBAR as i32);
Components {
open_btn: ToolButton::new(Some(&save_image), "Open"),
@ -50,6 +69,9 @@ impl Components {
impl Ui {
pub fn new(options: ShellOptions) -> Ui {
let plug_manager = plug_manager::Manager::new();
let plug_manager = Arc::new(UiMutex::new(plug_manager));
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)));
@ -63,6 +85,7 @@ impl Ui {
shell,
settings,
projects,
plug_manager,
}
}
@ -96,24 +119,29 @@ impl Ui {
let projects = self.projects.clone();
header_bar.pack_start(&comps.open_btn);
comps
.open_btn
.connect_clicked(move |_| projects.borrow_mut().show());
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_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_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));
@ -134,29 +162,56 @@ impl Ui {
shell.grab_focus();
let comps_ref = self.comps.clone();
shell.set_detach_cb(Some(move || {
shell.set_detach_cb(Some(move || {
let comps_ref = comps_ref.clone();
gtk::idle_add(move || {
comps_ref.borrow().close_window();
Continue(false)
});
}));
let state_ref = self.shell.borrow().state.clone();
let plug_manager_ref = self.plug_manager.clone();
shell.set_nvim_started_cb(Some(move || {
plug_manager_ref.borrow_mut().init_nvim_client(
state_ref.borrow().nvim_clone(),
);
}));
}
fn create_main_menu(&self, app: &gtk::Application) {
let comps = self.comps.clone();
let plug_manager = self.plug_manager.clone();
let menu = Menu::new();
let plugs = MenuItem::new("Plugins", None);
plugs.set_detailed_action("app.Plugins");
menu.append_item(&plugs);
let about = MenuItem::new("About", None);
about.set_detailed_action("app.HelpAbout");
menu.append_item(&about);
app.set_app_menu(Some(&menu));
let plugs_action = SimpleAction::new("Plugins", None);
plugs_action.connect_activate(
clone!(comps => move |_, _| plug_manager::Ui::new(&plug_manager).show(
comps
.borrow()
.window
.as_ref()
.unwrap(),
)),
);
let about_action = SimpleAction::new("HelpAbout", None);
let comps = self.comps.clone();
about_action.connect_activate(move |_, _| on_help_about(&*comps.borrow()));
about_action.set_enabled(true);
app.add_action(&about_action);
app.add_action(&plugs_action);
}
}
@ -178,13 +233,13 @@ fn gtk_delete(comps: &UiMutex<Components>, shell: &RefCell<Shell>) -> Inhibit {
}
Inhibit(if shell_dlg::can_close_window(comps, shell) {
let comps = comps.borrow();
comps.close_window();
shell.borrow_mut().detach_ui();
false
} else {
true
})
let comps = comps.borrow();
comps.close_window();
shell.borrow_mut().detach_ui();
false
} else {
true
})
}
pub struct UiMutex<T: ?Sized> {