Refactoring for remove global variables.

This commit is contained in:
daa84 2017-04-12 13:12:05 +03:00
parent 057fad0e87
commit ade1f9a5de
8 changed files with 772 additions and 639 deletions

View File

@ -1,9 +1,10 @@
use cairo; use cairo;
use ui_model::Color; use ui_model::Color;
use ui::{SH, UiMutex}; use ui::UiMutex;
use shell::{Shell, NvimMode}; use shell;
use shell::NvimMode;
use nvim::{RepaintMode, RedrawEvents}; use nvim::{RepaintMode, RedrawEvents};
use std::sync::Arc; use std::sync::{Arc, Weak};
use glib; use glib;
@ -43,15 +44,17 @@ enum AnimPhase {
struct State { struct State {
alpha: Alpha, alpha: Alpha,
anim_phase: AnimPhase, anim_phase: AnimPhase,
shell: Weak<UiMutex<shell::State>>,
timer: Option<glib::SourceId>, timer: Option<glib::SourceId>,
} }
impl State { impl State {
fn new() -> State { fn new(shell: Weak<UiMutex<shell::State>>) -> State {
State { State {
alpha: Alpha(1.0), alpha: Alpha(1.0),
anim_phase: AnimPhase::Shown, anim_phase: AnimPhase::Shown,
shell: shell,
timer: None, timer: None,
} }
} }
@ -71,8 +74,8 @@ pub struct Cursor {
} }
impl Cursor { impl Cursor {
pub fn new() -> Cursor { pub fn new(shell: Weak<UiMutex<shell::State>>) -> Cursor {
Cursor { state: Arc::new(UiMutex::new(State::new())) } Cursor { state: Arc::new(UiMutex::new(State::new(shell))) }
} }
pub fn start(&mut self) { pub fn start(&mut self) {
@ -85,7 +88,7 @@ impl Cursor {
pub fn reset_state(&mut self) { pub fn reset_state(&mut self) {
self.start(); self.start();
} }
pub fn enter_focus(&mut self) { pub fn enter_focus(&mut self) {
self.start(); self.start();
} }
@ -104,7 +107,7 @@ impl Cursor {
pub fn draw(&self, pub fn draw(&self,
ctx: &cairo::Context, ctx: &cairo::Context,
shell: &Shell, shell: &shell::State,
char_width: f64, char_width: f64,
line_height: f64, line_height: f64,
line_y: f64, line_y: f64,
@ -140,7 +143,6 @@ impl Cursor {
} }
fn anim_step(state: &Arc<UiMutex<State>>) -> glib::Continue { fn anim_step(state: &Arc<UiMutex<State>>) -> glib::Continue {
let moved_state = state.clone();
let mut mut_state = state.borrow_mut(); let mut mut_state = state.borrow_mut();
let next_event = match mut_state.anim_phase { let next_event = match mut_state.anim_phase {
@ -175,13 +177,14 @@ fn anim_step(state: &Arc<UiMutex<State>>) -> glib::Continue {
AnimPhase::Busy => None, AnimPhase::Busy => None,
}; };
SHELL!(&shell = { let shell = mut_state.shell.upgrade().unwrap();
let point = shell.model.cur_point(); let shell = shell.borrow();
shell.on_redraw(&RepaintMode::Area(point)); let point = shell.model.cur_point();
}); shell.on_redraw(&RepaintMode::Area(point));
if let Some(timeout) = next_event { if let Some(timeout) = next_event {
let moved_state = state.clone();
mut_state.timer = Some(glib::timeout_add(timeout, move || anim_step(&moved_state))); mut_state.timer = Some(glib::timeout_add(timeout, move || anim_step(&moved_state)));
glib::Continue(false) glib::Continue(false)

View File

@ -9,7 +9,7 @@ extern crate pango;
extern crate pangocairo; extern crate pangocairo;
extern crate neovim_lib; extern crate neovim_lib;
extern crate phf; extern crate phf;
#[macro_use] #[macro_use]
extern crate log; extern crate log;
extern crate env_logger; extern crate env_logger;
@ -23,12 +23,10 @@ mod settings;
mod cursor; mod cursor;
mod shell_dlg; mod shell_dlg;
use std::thread;
use std::env; use std::env;
use gio::ApplicationExt; use gio::ApplicationExt;
use shell::Shell; use ui::Ui;
use ui::SH;
const BIN_PATH_ARG: &'static str = "--nvim-bin-path"; const BIN_PATH_ARG: &'static str = "--nvim-bin-path";
@ -41,8 +39,10 @@ fn main() {
app.connect_activate(activate); app.connect_activate(activate);
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();
let mut argv: Vec<&str> = let mut argv: Vec<&str> = args.iter()
args.iter().filter(|a| !a.starts_with(BIN_PATH_ARG)).map(String::as_str).collect(); .filter(|a| !a.starts_with(BIN_PATH_ARG))
.map(String::as_str)
.collect();
if open_arg().is_some() { if open_arg().is_some() {
argv.pop(); argv.pop();
} }
@ -50,24 +50,11 @@ fn main() {
} }
fn activate(app: &gtk::Application) { fn activate(app: &gtk::Application) {
ui::UI.with(|ui_cell| { let mut ui = Ui::new();
let mut ui = ui_cell.borrow_mut();
if !ui.initialized {
ui.init(app);
let path = nvim_bin_path(std::env::args()); ui.init(app,
SHELL!(shell = { nvim_bin_path(std::env::args()).as_ref(),
nvim::initialize(&mut shell, path.as_ref()) open_arg().as_ref());
.expect("Can't start nvim instance");
guard_dispatch_thread(&mut shell);
});
}
SHELL!(shell = {
nvim::open_file(shell.nvim(), open_arg().as_ref());
});
});
} }
fn nvim_bin_path<I>(args: I) -> Option<String> fn nvim_bin_path<I>(args: I) -> Option<String>
@ -89,24 +76,13 @@ fn open_arg_impl<I>(args: I) -> Option<String>
args.skip(1) args.skip(1)
.last() .last()
.map(|a| if !a.starts_with("-") { .map(|a| if !a.starts_with("-") {
Some(a.to_owned()) Some(a.to_owned())
} else { } else {
None None
}) })
.unwrap_or(None) .unwrap_or(None)
} }
fn guard_dispatch_thread(shell: &mut Shell) {
let guard = shell.nvim().session.take_dispatch_guard();
thread::spawn(move || {
guard.join().expect("Can't join dispatch thread");
glib::idle_add(move || {
ui::UI.with(|ui_cell| { ui_cell.borrow().close_window(); });
glib::Continue(false)
});
});
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -115,8 +91,8 @@ mod tests {
fn test_bin_path_arg() { fn test_bin_path_arg() {
assert_eq!(Some("/test_path".to_string()), assert_eq!(Some("/test_path".to_string()),
nvim_bin_path(vec!["neovim-gtk", "--nvim-bin-path=/test_path"] nvim_bin_path(vec!["neovim-gtk", "--nvim-bin-path=/test_path"]
.iter() .iter()
.map(|s| s.to_string()))); .map(|s| s.to_string())));
} }
#[test] #[test]
@ -125,8 +101,8 @@ mod tests {
open_arg_impl(vec!["neovim-gtk", open_arg_impl(vec!["neovim-gtk",
"--nvim-bin-path=/test_path", "--nvim-bin-path=/test_path",
"some_file.txt"] "some_file.txt"]
.iter() .iter()
.map(|s| s.to_string()))); .map(|s| s.to_string())));
} }
#[test] #[test]

View File

@ -1,9 +1,11 @@
use neovim_lib::{Handler, Neovim, NeovimApi, Session, Value, UiAttachOptions, CallError};
use std::io::{Result, Error, ErrorKind}; use std::io::{Result, Error, ErrorKind};
use std::result; use std::result;
use ui_model::{UiModel, ModelRect}; use std::sync::Arc;
use ui::SH;
use shell::Shell; use ui::UiMutex;
use neovim_lib::{Handler, Neovim, NeovimApi, Session, Value, UiAttachOptions, CallError};
use ui_model::ModelRect;
use shell;
use glib; use glib;
pub trait RedrawEvents { pub trait RedrawEvents {
@ -63,98 +65,111 @@ macro_rules! try_uint {
}) })
} }
pub fn initialize(ui: &mut Shell, nvim_bin_path: Option<&String>) -> Result<()> { pub fn initialize(shell: Arc<UiMutex<shell::State>>,
nvim_bin_path: Option<&String>)
-> Result<Neovim> {
let session = if let Some(path) = nvim_bin_path { let session = if let Some(path) = nvim_bin_path {
Session::new_child_path(path)? Session::new_child_path(path)?
} else { } else {
Session::new_child()? Session::new_child()?
}; };
let nvim = Neovim::new(session); let mut nvim = Neovim::new(session);
ui.set_nvim(nvim);
ui.model = UiModel::new(24, 80);
let mut nvim = ui.nvim(); nvim.session
.start_event_loop_handler(NvimHandler::new(shell));
nvim.ui_attach(80, 24, UiAttachOptions::new())
.map_err(|e| Error::new(ErrorKind::Other, e))?;
nvim.command("runtime! ginit.vim")
.map_err(|e| Error::new(ErrorKind::Other, e))?;
nvim.session.start_event_loop_handler(NvimHandler::new()); Ok(nvim)
nvim.ui_attach(80, 24, UiAttachOptions::new()).map_err(|e| Error::new(ErrorKind::Other, e))?;
nvim.command("runtime! ginit.vim").map_err(|e| Error::new(ErrorKind::Other, e))?;
Ok(())
} }
pub fn open_file(nvim: &mut NeovimApi, file: Option<&String>) { pub struct NvimHandler {
if let Some(file_name) = file { shell: Arc<UiMutex<shell::State>>,
nvim.command(&format!("e {}", file_name)).report_err(nvim);
}
} }
pub struct NvimHandler {}
impl NvimHandler { impl NvimHandler {
pub fn new() -> NvimHandler { pub fn new(shell: Arc<UiMutex<shell::State>>) -> NvimHandler {
NvimHandler {} NvimHandler { shell: shell }
}
fn nvim_cb(&self, method: &str, params: Vec<Value>) {
match method {
"redraw" => {
self.safe_call(move |ui| {
let mut repaint_mode = RepaintMode::Nothing;
for ev in &params {
if let Some(ev_args) = ev.as_array() {
if let Some(ev_name) = ev_args[0].as_str() {
for ref local_args in ev_args.iter().skip(1) {
let args = match *local_args {
&Value::Array(ref ar) => ar.clone(),
_ => vec![],
};
let call_reapint_mode = call(ui, ev_name, &args)?;
repaint_mode = repaint_mode.join(&call_reapint_mode);
}
} else {
println!("Unsupported event {:?}", ev_args);
}
} else {
println!("Unsupported event type {:?}", ev);
}
}
ui.on_redraw(&repaint_mode);
Ok(())
});
}
"Gui" => {
if params.len() > 0 {
if let Some(ev_name) = params[0].as_str().map(String::from) {
let args = params.iter().skip(1).cloned().collect();
self.safe_call(move |ui| {
call_gui_event(ui, &ev_name, &args)?;
ui.on_redraw(&RepaintMode::All);
Ok(())
});
} else {
println!("Unsupported event {:?}", params);
}
} else {
println!("Unsupported event {:?}", params);
}
}
_ => {
println!("Notification {}({:?})", method, params);
}
}
}
fn safe_call<F>(&self, cb: F)
where F: Fn(&mut shell::State) -> result::Result<(), String> + 'static + Send
{
let shell = self.shell.clone();
glib::idle_add(move || {
if let Err(msg) = cb(&mut shell.borrow_mut()) {
println!("Error call function: {}", msg);
}
glib::Continue(false)
});
} }
} }
impl Handler for NvimHandler { impl Handler for NvimHandler {
fn handle_notify(&mut self, name: &str, args: &Vec<Value>) { fn handle_notify(&mut self, name: &str, args: &Vec<Value>) {
nvim_cb(name, args.clone()); self.nvim_cb(name, args.clone());
} }
} }
fn nvim_cb(method: &str, params: Vec<Value>) {
match method {
"redraw" => {
safe_call(move |ui| {
let mut repaint_mode = RepaintMode::Nothing;
for ev in &params { fn call_gui_event(ui: &mut shell::State,
if let Some(ev_args) = ev.as_array() { method: &str,
if let Some(ev_name) = ev_args[0].as_str() { args: &Vec<Value>)
for ref local_args in ev_args.iter().skip(1) { -> result::Result<(), String> {
let args = match *local_args {
&Value::Array(ref ar) => ar.clone(),
_ => vec![],
};
let call_reapint_mode = call(ui, ev_name, &args)?;
repaint_mode = repaint_mode.join(&call_reapint_mode);
}
} else {
println!("Unsupported event {:?}", ev_args);
}
} else {
println!("Unsupported event type {:?}", ev);
}
}
ui.on_redraw(&repaint_mode);
Ok(())
});
}
"Gui" => {
if params.len() > 0 {
if let Some(ev_name) = params[0].as_str().map(String::from) {
let args = params.iter().skip(1).cloned().collect();
safe_call(move |ui| {
call_gui_event(ui, &ev_name, &args)?;
ui.on_redraw(&RepaintMode::All);
Ok(())
});
} else {
println!("Unsupported event {:?}", params);
}
} else {
println!("Unsupported event {:?}", params);
}
}
_ => {
println!("Notification {}({:?})", method, params);
}
}
}
fn call_gui_event(ui: &mut Shell, method: &str, args: &Vec<Value>) -> result::Result<(), String> {
match method { match method {
"Font" => ui.set_font(try_str!(args[0])), "Font" => ui.set_font(try_str!(args[0])),
_ => return Err(format!("Unsupported event {}({:?})", method, args)), _ => return Err(format!("Unsupported event {}({:?})", method, args)),
@ -162,55 +177,45 @@ fn call_gui_event(ui: &mut Shell, method: &str, args: &Vec<Value>) -> result::Re
Ok(()) Ok(())
} }
fn call(ui: &mut Shell, method: &str, args: &Vec<Value>) -> result::Result<RepaintMode, String> { fn call(ui: &mut shell::State,
method: &str,
args: &Vec<Value>)
-> result::Result<RepaintMode, String> {
Ok(match method { Ok(match method {
"cursor_goto" => ui.on_cursor_goto(try_uint!(args[0]), try_uint!(args[1])), "cursor_goto" => ui.on_cursor_goto(try_uint!(args[0]), try_uint!(args[1])),
"put" => ui.on_put(try_str!(args[0])), "put" => ui.on_put(try_str!(args[0])),
"clear" => ui.on_clear(), "clear" => ui.on_clear(),
"resize" => ui.on_resize(try_uint!(args[0]), try_uint!(args[1])), "resize" => ui.on_resize(try_uint!(args[0]), try_uint!(args[1])),
"highlight_set" => { "highlight_set" => {
if let Value::Map(ref attrs) = args[0] { if let Value::Map(ref attrs) = args[0] {
ui.on_highlight_set(attrs); ui.on_highlight_set(attrs);
} else { } else {
panic!("Supports only map value as argument"); panic!("Supports only map value as argument");
}
RepaintMode::Nothing
} }
"eol_clear" => ui.on_eol_clear(), RepaintMode::Nothing
"set_scroll_region" => { }
ui.on_set_scroll_region(try_uint!(args[0]), "eol_clear" => ui.on_eol_clear(),
try_uint!(args[1]), "set_scroll_region" => {
try_uint!(args[2]), ui.on_set_scroll_region(try_uint!(args[0]),
try_uint!(args[3])); try_uint!(args[1]),
RepaintMode::Nothing try_uint!(args[2]),
} try_uint!(args[3]));
"scroll" => ui.on_scroll(try_int!(args[0])), RepaintMode::Nothing
"update_bg" => ui.on_update_bg(try_int!(args[0])), }
"update_fg" => ui.on_update_fg(try_int!(args[0])), "scroll" => ui.on_scroll(try_int!(args[0])),
"update_sp" => ui.on_update_sp(try_int!(args[0])), "update_bg" => ui.on_update_bg(try_int!(args[0])),
"mode_change" => ui.on_mode_change(try_str!(args[0])), "update_fg" => ui.on_update_fg(try_int!(args[0])),
"mouse_on" => ui.on_mouse(true), "update_sp" => ui.on_update_sp(try_int!(args[0])),
"mouse_off" => ui.on_mouse(false), "mode_change" => ui.on_mode_change(try_str!(args[0])),
"busy_start" => ui.on_busy(true), "mouse_on" => ui.on_mouse(true),
"busy_stop" => ui.on_busy(false), "mouse_off" => ui.on_mouse(false),
_ => { "busy_start" => ui.on_busy(true),
println!("Event {}({:?})", method, args); "busy_stop" => ui.on_busy(false),
RepaintMode::Nothing _ => {
} println!("Event {}({:?})", method, args);
}) RepaintMode::Nothing
} }
})
fn safe_call<F>(cb: F)
where F: Fn(&mut Shell) -> result::Result<(), String> + 'static + Send
{
glib::idle_add(move || {
SHELL!(shell = {
if let Err(msg) = cb(&mut shell) {
println!("Error call function: {}", msg);
}
});
glib::Continue(false)
});
} }
pub trait ErrorReport { pub trait ErrorReport {

View File

@ -1,12 +1,9 @@
#[cfg(unix)] use std::rc::{Rc, Weak};
use ui::{SET, SH}; use std::cell::RefCell;
#[cfg(unix)] #[cfg(unix)]
use nvim::RepaintMode; use nvim::RepaintMode;
#[cfg(unix)]
use nvim::RedrawEvents;
use shell::Shell; use shell::Shell;
#[cfg(unix)] #[cfg(unix)]
use gio; use gio;
@ -19,37 +16,27 @@ pub enum FontSource {
Default, Default,
} }
struct State {
pub struct Settings {
font_source: FontSource, font_source: FontSource,
#[cfg(unix)] #[cfg(unix)]
gnome_interface_settings: gio::Settings, gnome_interface_settings: gio::Settings,
} }
impl Settings { impl State {
#[cfg(unix)] #[cfg(unix)]
pub fn new() -> Settings { pub fn new() -> State {
Settings { State {
font_source: FontSource::Default, font_source: FontSource::Default,
gnome_interface_settings: gio::Settings::new("org.gnome.desktop.interface"), gnome_interface_settings: gio::Settings::new("org.gnome.desktop.interface"),
} }
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub fn new() -> Settings { pub fn new() -> State {
Settings { font_source: FontSource::Default } State { font_source: FontSource::Default }
} }
#[cfg(unix)]
pub fn init(&mut self, shell: &mut Shell) {
self.gnome_interface_settings.connect_changed(|_, _| monospace_font_changed());
self.update_font(shell);
}
#[cfg(target_os = "windows")]
pub fn init(&mut self, _: &mut Shell) {}
#[cfg(unix)] #[cfg(unix)]
fn update_font(&mut self, shell: &mut Shell) { fn update_font(&mut self, shell: &mut Shell) {
// rpc is priority for font // rpc is priority for font
@ -64,22 +51,49 @@ impl Settings {
self.font_source = FontSource::Gnome; self.font_source = FontSource::Gnome;
} }
} }
}
pub struct Settings {
shell: Option<Weak<RefCell<Shell>>>,
state: Rc<RefCell<State>>,
}
impl Settings {
pub fn new() -> Settings {
Settings {
shell: None,
state: Rc::new(RefCell::new(State::new())),
}
}
pub fn set_shell(&mut self, shell: Weak<RefCell<Shell>>) {
self.shell = Some(shell);
}
#[cfg(unix)]
pub fn init(&mut self) {
let shell = Weak::upgrade(self.shell.as_ref().unwrap()).unwrap();
let state = self.state.clone();
self.state.borrow_mut().update_font(&mut *shell.borrow_mut());
self.state
.borrow()
.gnome_interface_settings
.connect_changed(move |_, _| monospace_font_changed(&mut *shell.borrow_mut(), &mut *state.borrow_mut()));
}
#[cfg(target_os = "windows")]
pub fn init(&mut self) {}
pub fn set_font_source(&mut self, src: FontSource) { pub fn set_font_source(&mut self, src: FontSource) {
self.font_source = src; self.state.borrow_mut().font_source = src;
} }
} }
#[cfg(unix)] #[cfg(unix)]
fn monospace_font_changed() { fn monospace_font_changed(mut shell: &mut Shell, state: &mut State) {
SET.with(|set_cell| { // rpc is priority for font
let mut set = set_cell.borrow_mut(); if state.font_source != FontSource::Rpc {
// rpc is priority for font state.update_font(&mut shell);
if set.font_source != FontSource::Rpc { shell.redraw(&RepaintMode::All);
SHELL!(shell = { }
set.update_font(&mut shell);
shell.on_redraw(&RepaintMode::All);
});
}
});
} }

View File

@ -1,11 +1,14 @@
use std::string::String; use std::string::String;
use std::cell::{Ref, RefMut, RefCell};
use std::rc::Rc;
use std::sync::Arc;
use cairo; use cairo;
use pangocairo as pc; use pangocairo as pc;
use pango; use pango;
use pango::FontDescription; use pango::FontDescription;
use gdk::{ModifierType, EventKey, EventConfigure, EventButton, EventMotion, EventType, EventScroll, use gdk::{ModifierType, EventKey, EventConfigure, EventButton, EventMotion, EventType,
ScrollDirection, EventFocus}; EventScroll, ScrollDirection};
use gdk_sys; use gdk_sys;
use glib; use glib;
use gtk::prelude::*; use gtk::prelude::*;
@ -13,12 +16,14 @@ use gtk::DrawingArea;
use neovim_lib::{Neovim, NeovimApi, Value}; use neovim_lib::{Neovim, NeovimApi, Value};
use settings; use settings::{Settings, FontSource};
use ui_model::{UiModel, Cell, Attrs, Color, ModelRect, COLOR_BLACK, COLOR_WHITE, COLOR_RED}; use ui_model::{UiModel, Cell, Attrs, Color, ModelRect, COLOR_BLACK, COLOR_WHITE, COLOR_RED};
use nvim::{RedrawEvents, GuiApi, RepaintMode}; use nvim;
use nvim::{RedrawEvents, GuiApi, RepaintMode, ErrorReport};
use input::{convert_key, keyval_to_input_string}; use input::{convert_key, keyval_to_input_string};
use ui::{UI, SH, SET};
use cursor::Cursor; use cursor::Cursor;
use ui;
use ui::UiMutex;
const DEFAULT_FONT_NAME: &'static str = "DejaVu Sans Mono 12"; const DEFAULT_FONT_NAME: &'static str = "DejaVu Sans Mono 12";
@ -30,78 +35,49 @@ pub enum NvimMode {
Other, Other,
} }
pub struct Shell { pub struct State {
pub model: UiModel, pub model: UiModel,
pub drawing_area: DrawingArea,
nvim: Option<Neovim>,
cur_attrs: Option<Attrs>,
bg_color: Color, bg_color: Color,
fg_color: Color, fg_color: Color,
sp_color: Color, sp_color: Color,
cur_attrs: Option<Attrs>,
pub mode: NvimMode,
mouse_enabled: bool,
drawing_area: DrawingArea,
nvim: Option<Neovim>,
font_desc: FontDescription,
cursor: Option<Cursor>,
settings: Rc<RefCell<Settings>>,
line_height: Option<f64>, line_height: Option<f64>,
char_width: Option<f64>, char_width: Option<f64>,
request_width: bool, request_width: bool,
pub mode: NvimMode,
mouse_enabled: bool,
mouse_pressed: bool,
font_desc: FontDescription,
resize_timer: Option<glib::SourceId>, resize_timer: Option<glib::SourceId>,
cursor: Cursor,
} }
impl Shell { impl State {
pub fn new() -> Shell { pub fn new(settings: Rc<RefCell<Settings>>) -> State {
Shell { State {
model: UiModel::empty(), model: UiModel::new(24, 80),
drawing_area: DrawingArea::new(), drawing_area: DrawingArea::new(),
nvim: None, nvim: None,
cur_attrs: None, cur_attrs: None,
bg_color: COLOR_BLACK, bg_color: COLOR_BLACK,
fg_color: COLOR_WHITE, fg_color: COLOR_WHITE,
sp_color: COLOR_RED, sp_color: COLOR_RED,
line_height: None,
char_width: None,
request_width: true,
mode: NvimMode::Normal, mode: NvimMode::Normal,
mouse_enabled: true, mouse_enabled: true,
mouse_pressed: false,
font_desc: FontDescription::from_string(DEFAULT_FONT_NAME), font_desc: FontDescription::from_string(DEFAULT_FONT_NAME),
cursor: None,
settings: settings,
line_height: None,
char_width: None,
resize_timer: None, resize_timer: None,
cursor: Cursor::new(), request_width: true,
} }
} }
pub fn init(&mut self) {
self.drawing_area.set_size_request(500, 300);
self.drawing_area.set_hexpand(true);
self.drawing_area.set_vexpand(true);
self.drawing_area.set_can_focus(true);
self.drawing_area
.set_events((gdk_sys::GDK_BUTTON_RELEASE_MASK | gdk_sys::GDK_BUTTON_PRESS_MASK |
gdk_sys::GDK_BUTTON_MOTION_MASK |
gdk_sys::GDK_SCROLL_MASK)
.bits() as i32);
self.drawing_area.connect_button_press_event(gtk_button_press);
self.drawing_area.connect_button_release_event(gtk_button_release);
self.drawing_area.connect_motion_notify_event(gtk_motion_notify);
self.drawing_area.connect_draw(gtk_draw);
self.drawing_area.connect_key_press_event(gtk_key_press);
self.drawing_area.connect_scroll_event(gtk_scroll_event);
self.drawing_area.connect_focus_in_event(gtk_focus_in);
self.drawing_area.connect_focus_out_event(gtk_focus_out);
}
pub fn add_configure_event(&mut self) {
self.drawing_area.connect_configure_event(gtk_configure_event);
self.cursor.start();
}
pub fn set_nvim(&mut self, nvim: Neovim) {
self.nvim = Some(nvim);
}
pub fn nvim(&mut self) -> &mut Neovim { pub fn nvim(&mut self) -> &mut Neovim {
self.nvim.as_mut().unwrap() self.nvim.as_mut().unwrap()
} }
@ -110,17 +86,7 @@ impl Shell {
self.font_desc.clone() self.font_desc.clone()
} }
fn request_width(&mut self) { fn colors<'a>(&'a self, cell: &'a Cell) -> (&'a Color, &'a Color) {
self.request_width = true;
}
pub fn set_font_desc(&mut self, desc: &str) {
self.font_desc = FontDescription::from_string(desc);
self.line_height = None;
self.char_width = None;
}
pub fn colors<'a>(&'a self, cell: &'a Cell) -> (&'a Color, &'a Color) {
let bg = if let Some(ref bg) = cell.attrs.background { let bg = if let Some(ref bg) = cell.attrs.background {
bg bg
} else { } else {
@ -138,142 +104,323 @@ impl Shell {
(bg, fg) (bg, fg)
} }
} }
pub fn set_font_desc(&mut self, desc: &str) {
self.font_desc = FontDescription::from_string(desc);
self.line_height = None;
self.char_width = None;
}
fn request_width(&mut self) {
self.request_width = true;
}
} }
fn gtk_focus_in(_: &DrawingArea, _: &EventFocus) -> Inhibit { pub struct UiState {
SHELL!(shell = { mouse_pressed: bool,
shell.cursor.enter_focus(); }
let point = shell.model.cur_point();
shell.on_redraw(&RepaintMode::Area(point)); impl UiState {
}); pub fn new() -> UiState {
UiState { mouse_pressed: false }
}
}
pub struct Shell {
state: Arc<UiMutex<State>>,
ui_state: Rc<RefCell<UiState>>,
}
impl Shell {
pub fn new(settings: Rc<RefCell<Settings>>) -> Shell {
let shell = Shell {
state: Arc::new(UiMutex::new(State::new(settings))),
ui_state: Rc::new(RefCell::new(UiState::new())),
};
let shell_ref = Arc::downgrade(&shell.state);
shell.state.borrow_mut().cursor = Some(Cursor::new(shell_ref));
shell
}
pub fn init(&mut self, parent: Arc<UiMutex<ui::Components>>) {
let state = self.state.borrow_mut();
state.drawing_area.set_size_request(500, 300);
state.drawing_area.set_hexpand(true);
state.drawing_area.set_vexpand(true);
state.drawing_area.set_can_focus(true);
state
.drawing_area
.set_events((gdk_sys::GDK_BUTTON_RELEASE_MASK | gdk_sys::GDK_BUTTON_PRESS_MASK |
gdk_sys::GDK_BUTTON_MOTION_MASK |
gdk_sys::GDK_SCROLL_MASK)
.bits() as i32);
let ref_state = self.state.clone();
let ref_ui_state = self.ui_state.clone();
state
.drawing_area
.connect_button_press_event(move |_, ev| {
gtk_button_press(&mut *ref_state.borrow_mut(),
&mut *ref_ui_state.borrow_mut(),
ev)
});
let ref_ui_state = self.ui_state.clone();
state
.drawing_area
.connect_button_release_event(move |_, _| {
gtk_button_release(&mut *ref_ui_state.borrow_mut())
});
let ref_state = self.state.clone();
let ref_ui_state = self.ui_state.clone();
state
.drawing_area
.connect_motion_notify_event(move |_, ev| {
gtk_motion_notify(&mut *ref_state.borrow_mut(),
&mut *ref_ui_state.borrow_mut(),
ev)
});
let ref_state = self.state.clone();
state
.drawing_area
.connect_draw(move |_, ctx| {
gtk_draw(&*parent.borrow(), &mut *ref_state.borrow_mut(), ctx)
});
let ref_state = self.state.clone();
state
.drawing_area
.connect_key_press_event(move |_, ev| gtk_key_press(&mut *ref_state.borrow_mut(), ev));
let ref_state = self.state.clone();
let ref_ui_state = self.ui_state.clone();
state
.drawing_area
.connect_scroll_event(move |_, ev| {
gtk_scroll_event(&mut *ref_state.borrow_mut(),
&mut *ref_ui_state.borrow_mut(),
ev)
});
let ref_state = self.state.clone();
state
.drawing_area
.connect_focus_in_event(move |_, _| gtk_focus_in(&mut *ref_state.borrow_mut()));
let ref_state = self.state.clone();
state
.drawing_area
.connect_focus_out_event(move |_, _| gtk_focus_out(&mut *ref_state.borrow_mut()));
}
pub fn state(&self) -> Ref<State> {
self.state.borrow()
}
pub fn drawing_area(&self) -> Ref<DrawingArea> {
Ref::map(self.state(), |s| &s.drawing_area)
}
pub fn redraw(&self, mode: &RepaintMode) {
self.state.borrow_mut().on_redraw(mode);
}
pub fn set_font_desc(&self, font_name: &str) {
self.state.borrow_mut().set_font_desc(font_name);
}
pub fn add_configure_event(&mut self) {
let mut state = self.state.borrow_mut();
let ref_state = self.state.clone();
state
.drawing_area
.connect_configure_event(move |_, ev| gtk_configure_event(&ref_state, ev));
state.cursor.as_mut().unwrap().start();
}
pub fn init_nvim(&mut self, nvim_bin_path: Option<&String>) {
let nvim =
nvim::initialize(self.state.clone(), nvim_bin_path).expect("Can't start nvim instance");
let mut state = self.state.borrow_mut();
state.nvim = Some(nvim);
state.request_width();
}
pub fn open_file(&self, path: &str) {
let mut nvim = self.nvim();
nvim.command(&format!("e {}", path))
.report_err(&mut *nvim);
}
pub fn detach_ui(&mut self) {
self.nvim().ui_detach().expect("Error in ui_detach");
}
pub fn edit_paste(&self) {
let mut state = self.state.borrow_mut();
let paste_command = if state.mode == NvimMode::Normal {
"\"*p"
} else {
"<Esc>\"*pa"
};
let mut nvim = state.nvim();
nvim.input(paste_command).report_err(nvim);
}
pub fn edit_save_all(&self) {
let mut nvim = &mut *self.nvim();
nvim.command(":wa").report_err(nvim);
}
pub fn nvim(&self) -> RefMut<Neovim> {
let state = self.state.borrow_mut();
RefMut::map(state, |s| s.nvim())
}
}
fn gtk_focus_in(state: &mut State) -> Inhibit {
state.cursor.as_mut().unwrap().enter_focus();
let point = state.model.cur_point();
state.on_redraw(&RepaintMode::Area(point));
Inhibit(false) Inhibit(false)
} }
fn gtk_focus_out(_: &DrawingArea, _: &EventFocus) -> Inhibit { fn gtk_focus_out(state: &mut State) -> Inhibit {
SHELL!(shell = { state.cursor.as_mut().unwrap().leave_focus();
shell.cursor.leave_focus(); let point = state.model.cur_point();
let point = shell.model.cur_point(); state.on_redraw(&RepaintMode::Area(point));
shell.on_redraw(&RepaintMode::Area(point));
});
Inhibit(false) Inhibit(false)
} }
fn gtk_scroll_event(_: &DrawingArea, ev: &EventScroll) -> Inhibit { fn gtk_scroll_event(state: &mut State, ui_state: &mut UiState, ev: &EventScroll) -> Inhibit {
SHELL!(shell = { if state.mouse_enabled {
if !shell.mouse_enabled { return Inhibit(false);
return; }
match ev.as_ref().direction {
ScrollDirection::Right => {
mouse_input(state,
ui_state,
"ScrollWheelRight",
ev.get_state(),
ev.get_position())
} }
ScrollDirection::Left => {
match ev.as_ref().direction { mouse_input(state,
ScrollDirection::Right => { ui_state,
mouse_input(&mut shell, "ScrollWheelLeft",
"ScrollWheelRight", ev.get_state(),
ev.get_state(), ev.get_position())
ev.get_position())
}
ScrollDirection::Left => {
mouse_input(&mut shell,
"ScrollWheelLeft",
ev.get_state(),
ev.get_position())
}
ScrollDirection::Up => {
mouse_input(&mut shell,
"ScrollWheelUp",
ev.get_state(),
ev.get_position())
}
ScrollDirection::Down => {
mouse_input(&mut shell,
"ScrollWheelDown",
ev.get_state(),
ev.get_position())
}
_ => (),
} }
}); ScrollDirection::Up => {
mouse_input(state,
ui_state,
"ScrollWheelUp",
ev.get_state(),
ev.get_position())
}
ScrollDirection::Down => {
mouse_input(state,
ui_state,
"ScrollWheelDown",
ev.get_state(),
ev.get_position())
}
_ => (),
}
Inhibit(false) Inhibit(false)
} }
fn gtk_button_press(_: &DrawingArea, ev: &EventButton) -> Inhibit { fn gtk_button_press(shell: &mut State, ui_state: &mut UiState, ev: &EventButton) -> Inhibit {
if ev.get_event_type() != EventType::ButtonPress { if ev.get_event_type() != EventType::ButtonPress {
return Inhibit(false); return Inhibit(false);
} }
SHELL!(shell = { if shell.mouse_enabled {
if !shell.mouse_enabled { mouse_input(shell,
return; ui_state,
} "LeftMouse",
ev.get_state(),
mouse_input(&mut shell, "LeftMouse", ev.get_state(), ev.get_position()); ev.get_position());
}); }
Inhibit(false) Inhibit(false)
} }
fn mouse_input(shell: &mut Shell, input: &str, state: ModifierType, position: (f64, f64)) { fn mouse_input(shell: &mut State,
ui_state: &mut UiState,
input: &str,
state: ModifierType,
position: (f64, f64)) {
if let Some(line_height) = shell.line_height { if let Some(line_height) = shell.line_height {
if let Some(char_width) = shell.char_width { if let Some(char_width) = shell.char_width {
shell.mouse_pressed = true; ui_state.mouse_pressed = true;
let nvim = shell.nvim(); let nvim = shell.nvim();
let (x, y) = position; let (x, y) = position;
let col = (x / char_width).trunc() as u64; let col = (x / char_width).trunc() as u64;
let row = (y / line_height).trunc() as u64; let row = (y / line_height).trunc() as u64;
let input_str = format!("{}<{},{}>", keyval_to_input_string(input, state), col, row); let input_str = format!("{}<{},{}>", keyval_to_input_string(input, state), col, row);
nvim.input(&input_str).expect("Can't send mouse input event"); nvim.input(&input_str)
.expect("Can't send mouse input event");
} }
} }
} }
fn gtk_button_release(_: &DrawingArea, _: &EventButton) -> Inhibit { fn gtk_button_release(ui_state: &mut UiState) -> Inhibit {
SHELL!(shell = { ui_state.mouse_pressed = false;
shell.mouse_pressed = false;
});
Inhibit(false) Inhibit(false)
} }
fn gtk_motion_notify(_: &DrawingArea, ev: &EventMotion) -> Inhibit { fn gtk_motion_notify(shell: &mut State, ui_state: &mut UiState, ev: &EventMotion) -> Inhibit {
SHELL!(shell = { if shell.mouse_enabled && ui_state.mouse_pressed {
if !shell.mouse_enabled || !shell.mouse_pressed { mouse_input(shell,
return; ui_state,
} "LeftDrag",
ev.get_state(),
mouse_input(&mut shell, "LeftDrag", ev.get_state(), ev.get_position()); ev.get_position());
}); }
Inhibit(false) Inhibit(false)
} }
fn gtk_key_press(_: &DrawingArea, ev: &EventKey) -> Inhibit { fn gtk_key_press(shell: &mut State, ev: &EventKey) -> Inhibit {
if let Some(input) = convert_key(ev) { if let Some(input) = convert_key(ev) {
SHELL!(shell = { debug!("nvim_input -> {}", input);
debug!("nvim_input -> {}", input); shell
shell.nvim().input(&input).expect("Error run input command to nvim"); .nvim()
shell.cursor.reset_state(); .input(&input)
}); .expect("Error run input command to nvim");
shell.cursor.as_mut().unwrap().reset_state();
Inhibit(true) Inhibit(true)
} else { } else {
Inhibit(false) Inhibit(false)
} }
} }
fn gtk_draw(_: &DrawingArea, ctx: &cairo::Context) -> Inhibit { fn gtk_draw(parent: &ui::Components, state: &mut State, ctx: &cairo::Context) -> Inhibit {
SHELL!(shell = { if state.line_height.is_none() {
if shell.line_height.is_none() { let (width, height) = calc_char_bounds(state, ctx);
let (width, height) = calc_char_bounds(&shell, ctx); state.line_height = Some(height as f64);
shell.line_height = Some(height as f64); state.char_width = Some(width as f64);
shell.char_width = Some(width as f64); }
}
draw(&shell, ctx); draw(state, ctx);
request_width(&mut shell); request_width(parent, state);
});
Inhibit(false) Inhibit(false)
} }
#[inline] #[inline]
fn draw_joined_rect(shell: &Shell, fn draw_joined_rect(state: &State,
ctx: &cairo::Context, ctx: &cairo::Context,
from_col_idx: usize, from_col_idx: usize,
col_idx: usize, col_idx: usize,
@ -283,7 +430,7 @@ fn draw_joined_rect(shell: &Shell,
let current_point = ctx.get_current_point(); let current_point = ctx.get_current_point();
let rect_width = char_width * (col_idx - from_col_idx) as f64; let rect_width = char_width * (col_idx - from_col_idx) as f64;
if &shell.bg_color != color { if &state.bg_color != color {
ctx.set_source_rgb(color.0, color.1, color.2); ctx.set_source_rgb(color.0, color.1, color.2);
ctx.rectangle(current_point.0, current_point.1, rect_width, line_height); ctx.rectangle(current_point.0, current_point.1, rect_width, line_height);
ctx.fill(); ctx.fill();
@ -292,29 +439,29 @@ fn draw_joined_rect(shell: &Shell,
ctx.move_to(current_point.0 + rect_width, current_point.1); ctx.move_to(current_point.0 + rect_width, current_point.1);
} }
fn draw(shell: &Shell, ctx: &cairo::Context) { fn draw(state: &State, ctx: &cairo::Context) {
ctx.set_source_rgb(shell.bg_color.0, shell.bg_color.1, shell.bg_color.2); ctx.set_source_rgb(state.bg_color.0, state.bg_color.1, state.bg_color.2);
ctx.paint(); ctx.paint();
let line_height = shell.line_height.unwrap(); let line_height = state.line_height.unwrap();
let char_width = shell.char_width.unwrap(); let char_width = state.char_width.unwrap();
let clip = ctx.clip_extents(); let clip = ctx.clip_extents();
let mut model_clip = let mut model_clip =
ModelRect::from_area(line_height, char_width, clip.0, clip.1, clip.2, clip.3); ModelRect::from_area(line_height, char_width, clip.0, clip.1, clip.2, clip.3);
shell.model.limit_to_model(&mut model_clip); state.model.limit_to_model(&mut model_clip);
let line_x = model_clip.left as f64 * char_width; let line_x = model_clip.left as f64 * char_width;
let mut line_y: f64 = model_clip.top as f64 * line_height; let mut line_y: f64 = model_clip.top as f64 * line_height;
let (row, col) = shell.model.get_cursor(); let (row, col) = state.model.get_cursor();
let mut buf = String::with_capacity(4); let mut buf = String::with_capacity(4);
let layout = pc::create_layout(ctx); let layout = pc::create_layout(ctx);
let mut desc = shell.create_pango_font(); let mut desc = state.create_pango_font();
for (line_idx, line) in shell.model.clip_model(&model_clip) { for (line_idx, line) in state.model.clip_model(&model_clip) {
ctx.move_to(line_x, line_y); ctx.move_to(line_x, line_y);
// first draw background // first draw background
@ -323,13 +470,13 @@ fn draw(shell: &Shell, ctx: &cairo::Context) {
let mut from_col_idx = model_clip.left; let mut from_col_idx = model_clip.left;
let mut from_bg = None; let mut from_bg = None;
for (col_idx, cell) in line.iter() { for (col_idx, cell) in line.iter() {
let (bg, _) = shell.colors(cell); let (bg, _) = state.colors(cell);
if from_bg.is_none() { if from_bg.is_none() {
from_bg = Some(bg); from_bg = Some(bg);
from_col_idx = col_idx; from_col_idx = col_idx;
} else if from_bg != Some(bg) { } else if from_bg != Some(bg) {
draw_joined_rect(shell, draw_joined_rect(state,
ctx, ctx,
from_col_idx, from_col_idx,
col_idx, col_idx,
@ -340,7 +487,7 @@ fn draw(shell: &Shell, ctx: &cairo::Context) {
from_col_idx = col_idx; from_col_idx = col_idx;
} }
} }
draw_joined_rect(shell, draw_joined_rect(state,
ctx, ctx,
from_col_idx, from_col_idx,
model_clip.right + 1, model_clip.right + 1,
@ -351,19 +498,25 @@ fn draw(shell: &Shell, ctx: &cairo::Context) {
ctx.move_to(line_x, line_y); ctx.move_to(line_x, line_y);
for (col_idx, cell) in line.iter() { for (col_idx, cell) in line.iter() {
let double_width = line.get(col_idx + 1).map(|c| c.attrs.double_width).unwrap_or(false); let double_width = line.get(col_idx + 1)
.map(|c| c.attrs.double_width)
.unwrap_or(false);
let current_point = ctx.get_current_point(); let current_point = ctx.get_current_point();
let (bg, fg) = shell.colors(cell); let (bg, fg) = state.colors(cell);
if row == line_idx && col == col_idx { if row == line_idx && col == col_idx {
shell.cursor.draw(ctx, state
shell, .cursor
char_width, .as_ref()
line_height, .unwrap()
line_y, .draw(ctx,
double_width, state,
bg); char_width,
line_height,
line_y,
double_width,
bg);
ctx.move_to(current_point.0, current_point.1); ctx.move_to(current_point.0, current_point.1);
} }
@ -400,7 +553,7 @@ fn draw(shell: &Shell, ctx: &cairo::Context) {
let sp = if let Some(ref sp) = cell.attrs.special { let sp = if let Some(ref sp) = cell.attrs.special {
sp sp
} else { } else {
&shell.sp_color &state.sp_color
}; };
ctx.set_source_rgba(sp.0, sp.1, sp.2, 0.7); ctx.set_source_rgba(sp.0, sp.1, sp.2, 0.7);
@ -437,7 +590,7 @@ fn update_font_description(desc: &mut FontDescription, attrs: &Attrs) {
} }
} }
fn calc_char_bounds(shell: &Shell, ctx: &cairo::Context) -> (i32, i32) { fn calc_char_bounds(shell: &State, ctx: &cairo::Context) -> (i32, i32) {
let layout = pc::create_layout(ctx); let layout = pc::create_layout(ctx);
let desc = shell.create_pango_font(); let desc = shell.create_pango_font();
@ -447,31 +600,27 @@ fn calc_char_bounds(shell: &Shell, ctx: &cairo::Context) -> (i32, i32) {
layout.get_pixel_size() layout.get_pixel_size()
} }
fn request_width(shell: &mut Shell) { fn request_width(parent: &ui::Components, state: &mut State) {
if !shell.request_width { if !state.request_width {
return; return;
} }
if shell.resize_timer.is_some() { if state.resize_timer.is_some() {
return; return;
} }
shell.request_width = false; state.request_width = false;
let width = shell.drawing_area.get_allocated_width(); let width = state.drawing_area.get_allocated_width();
let height = shell.drawing_area.get_allocated_height(); let height = state.drawing_area.get_allocated_height();
let request_height = (shell.model.rows as f64 * shell.line_height.unwrap()) as i32; let request_height = (state.model.rows as f64 * state.line_height.unwrap()) as i32;
let request_width = (shell.model.columns as f64 * shell.char_width.unwrap()) as i32; let request_width = (state.model.columns as f64 * state.char_width.unwrap()) as i32;
if width != request_width || height != request_height { if width != request_width || height != request_height {
UI.with(|ui_cell| { let window = parent.window();
let ui = ui_cell.borrow(); let (win_width, win_height) = window.get_size();
let h_border = win_width - width;
let window = ui.window.as_ref().unwrap(); let v_border = win_height - height;
let (win_width, win_height) = window.get_size(); window.resize(request_width + h_border, request_height + v_border);
let h_border = win_width - width;
let v_border = win_height - height;
window.resize(request_width + h_border, request_height + v_border);
});
} }
} }
@ -482,39 +631,41 @@ fn split_color(indexed_color: u64) -> Color {
Color(r / 255.0, g / 255.0, b / 255.0) Color(r / 255.0, g / 255.0, b / 255.0)
} }
fn gtk_configure_event(_: &DrawingArea, ev: &EventConfigure) -> bool { fn gtk_configure_event(state: &Arc<UiMutex<State>>, ev: &EventConfigure) -> bool {
SHELL!(shell = { let (width, height) = ev.get_size();
let (width, height) = ev.get_size();
if let Some(timer) = shell.resize_timer { let mut state_ref = state.borrow_mut();
glib::source_remove(timer);
if let Some(timer) = state_ref.resize_timer {
glib::source_remove(timer);
}
if let Some(line_height) = state_ref.line_height {
if let Some(char_width) = state_ref.char_width {
let state = state.clone();
state_ref.resize_timer = Some(glib::timeout_add(250, move || {
let mut state_ref = state.borrow_mut();
state_ref.resize_timer = None;
let rows = (height as f64 / line_height).trunc() as usize;
let columns = (width as f64 / char_width).trunc() as usize;
if state_ref.model.rows != rows || state_ref.model.columns != columns {
if let Err(err) = state_ref
.nvim()
.ui_try_resize(columns as u64, rows as u64) {
println!("Error trying resize nvim {}", err);
}
}
state_ref.request_width();
Continue(false)
}));
} }
if let Some(line_height) = shell.line_height { }
if let Some(char_width) = shell.char_width {
shell.resize_timer = Some(glib::timeout_add(250, move || {
SHELL!(shell = {
shell.resize_timer = None;
let rows = (height as f64 / line_height).trunc() as usize;
let columns = (width as f64 / char_width).trunc() as usize;
if shell.model.rows != rows || shell.model.columns != columns {
if let Err(err) = shell.nvim()
.ui_try_resize(columns as u64, rows as u64) {
println!("Error trying resize nvim {}", err);
}
}
shell.request_width();
});
Continue(false)
}));
}
}
});
false false
} }
impl RedrawEvents for Shell { impl RedrawEvents for State {
fn on_cursor_goto(&mut self, row: u64, col: u64) -> RepaintMode { fn on_cursor_goto(&mut self, row: u64, col: u64) -> RepaintMode {
RepaintMode::Area(self.model.set_cursor(row as usize, col as usize)) RepaintMode::Area(self.model.set_cursor(row as usize, col as usize))
} }
@ -643,21 +794,19 @@ impl RedrawEvents for Shell {
fn on_busy(&mut self, busy: bool) -> RepaintMode { fn on_busy(&mut self, busy: bool) -> RepaintMode {
if busy { if busy {
self.cursor.busy_on(); self.cursor.as_mut().unwrap().busy_on();
} else { } else {
self.cursor.busy_off(); self.cursor.as_mut().unwrap().busy_off();
} }
RepaintMode::Area(self.model.cur_point()) RepaintMode::Area(self.model.cur_point())
} }
} }
impl GuiApi for Shell { impl GuiApi for State {
fn set_font(&mut self, font_desc: &str) { fn set_font(&mut self, font_desc: &str) {
self.set_font_desc(font_desc); self.set_font_desc(font_desc);
SET.with(|settings| { let mut settings = self.settings.borrow_mut();
let mut settings = settings.borrow_mut(); settings.set_font_source(FontSource::Rpc);
settings.set_font_source(settings::FontSource::Rpc);
});
} }
} }

View File

@ -1,14 +1,18 @@
use ui::{SH, Ui}; use std::cell::RefCell;
use ui::{Components, UiMutex};
use shell::Shell;
use neovim_lib::{NeovimApi, CallError, Value}; use neovim_lib::{NeovimApi, CallError, Value};
use gtk; use gtk;
use gtk::prelude::*; use gtk::prelude::*;
use gtk::{MessageDialog, MessageType, ButtonsType}; use gtk::{MessageDialog, MessageType, ButtonsType};
pub fn can_close_window(ui: &Ui) -> bool { pub fn can_close_window(comps: &UiMutex<Components>, shell: &RefCell<Shell>) -> bool {
match get_changed_buffers() { let shell = shell.borrow();
match get_changed_buffers(&*shell) {
Ok(vec) => { Ok(vec) => {
if !vec.is_empty() { if !vec.is_empty() {
show_not_saved_dlg(ui, &vec) show_not_saved_dlg(comps, &*shell, &vec)
} else { } else {
true true
} }
@ -20,14 +24,18 @@ pub fn can_close_window(ui: &Ui) -> bool {
} }
} }
fn show_not_saved_dlg(ui: &Ui, changed_bufs: &Vec<String>) -> bool { fn show_not_saved_dlg(comps: &UiMutex<Components>,
let mut changed_files = changed_bufs.iter() shell: &Shell,
changed_bufs: &Vec<String>)
-> bool {
let mut changed_files = changed_bufs
.iter()
.map(|n| if n.is_empty() { "<No name>" } else { n }) .map(|n| if n.is_empty() { "<No name>" } else { n })
.fold(String::new(), |acc, v| acc + v + "\n"); .fold(String::new(), |acc, v| acc + v + "\n");
changed_files.pop(); changed_files.pop();
let flags = gtk::DIALOG_MODAL | gtk::DIALOG_DESTROY_WITH_PARENT; let flags = gtk::DIALOG_MODAL | gtk::DIALOG_DESTROY_WITH_PARENT;
let dlg = MessageDialog::new(ui.window.as_ref(), let dlg = MessageDialog::new(Some(comps.borrow().window()),
flags, flags,
MessageType::Question, MessageType::Question,
ButtonsType::None, ButtonsType::None,
@ -37,21 +45,21 @@ fn show_not_saved_dlg(ui: &Ui, changed_bufs: &Vec<String>) -> bool {
const CLOSE_WITHOUT_SAVE: i32 = 1; const CLOSE_WITHOUT_SAVE: i32 = 1;
const CANCEL_ID: i32 = 2; const CANCEL_ID: i32 = 2;
dlg.add_buttons(&[("_Yes", SAVE_ID), ("_No", CLOSE_WITHOUT_SAVE), ("_Cancel", CANCEL_ID)]); dlg.add_buttons(&[("_Yes", SAVE_ID),
("_No", CLOSE_WITHOUT_SAVE),
("_Cancel", CANCEL_ID)]);
let res = match dlg.run() { let res = match dlg.run() {
SAVE_ID => { SAVE_ID => {
SHELL!(shell = { let mut nvim = shell.nvim();
let mut nvim = shell.nvim(); match nvim.command("wa") {
match nvim.command("wa") { Err(ref err) => {
Err(ref err) => { println!("Error: {}", err);
println!("Error: {}", err); false
false
}
_ => true
} }
}) _ => true,
}, }
}
CLOSE_WITHOUT_SAVE => true, CLOSE_WITHOUT_SAVE => true,
CANCEL_ID => false, CANCEL_ID => false,
_ => false, _ => false,
@ -62,34 +70,33 @@ fn show_not_saved_dlg(ui: &Ui, changed_bufs: &Vec<String>) -> bool {
res res
} }
fn get_changed_buffers() -> Result<Vec<String>, CallError> { fn get_changed_buffers(shell: &Shell) -> Result<Vec<String>, CallError> {
SHELL!(shell = { let mut nvim = shell.nvim();
let mut nvim = shell.nvim(); let buffers = nvim.get_buffers().unwrap();
let buffers = nvim.get_buffers().unwrap();
Ok(buffers.iter() Ok(buffers
.map(|buf| { .iter()
(match buf.get_option(&mut nvim, "modified") { .map(|buf| {
Ok(Value::Boolean(val)) => val, (match buf.get_option(&mut nvim, "modified") {
Ok(_) => { Ok(Value::Boolean(val)) => val,
println!("Value must be boolean"); Ok(_) => {
false println!("Value must be boolean");
} false
Err(ref err) => { }
println!("Something going wrong while getting buffer option: {}", err); Err(ref err) => {
false println!("Something going wrong while getting buffer option: {}", err);
} false
}, }
match buf.get_name(&mut nvim) { },
Ok(name) => name, match buf.get_name(&mut nvim) {
Err(ref err) => { Ok(name) => name,
println!("Something going wrong while getting buffer name: {}", err); Err(ref err) => {
"<Error>".to_owned() println!("Something going wrong while getting buffer name: {}", err);
} "<Error>".to_owned()
}) }
}) })
.filter(|e| e.0) })
.map(|e| e.1) .filter(|e| e.0)
.collect()) .map(|e| e.1)
}) .collect())
} }

263
src/ui.rs
View File

@ -1,61 +1,36 @@
use std::cell::{RefCell, Ref, RefMut}; use std::cell::{RefCell, Ref, RefMut};
use std::thread; use std::thread;
use std::rc::Rc;
use std::sync::Arc;
use gtk; use gtk;
use gtk_sys; use gtk_sys;
use gtk::prelude::*; use gtk::prelude::*;
use gtk::{ApplicationWindow, HeaderBar, ToolButton, Image, AboutDialog}; use gtk::{ApplicationWindow, HeaderBar, ToolButton, Image, AboutDialog};
use gdk::Event;
use gio::{Menu, MenuItem, SimpleAction}; use gio::{Menu, MenuItem, SimpleAction};
use glib::variant::Variant; use glib;
use neovim_lib::NeovimApi; use settings::Settings;
use shell::Shell;
use settings;
use shell::{Shell, NvimMode};
use shell_dlg; use shell_dlg;
use nvim::ErrorReport;
macro_rules! ui_thread_var {
($id:ident, $ty:ty, $expr:expr) => (thread_local!(pub static $id: RefCell<$ty> = {
assert_ui_thread();
RefCell::new($expr)
});)
}
ui_thread_var!(UI, Ui, Ui::new());
ui_thread_var!(SH, Shell, Shell::new());
ui_thread_var!(SET, settings::Settings, settings::Settings::new());
#[macro_export]
macro_rules! SHELL {
(&$id:ident = $expr:expr) => (
SH.with(|shell_cell| {
let $id = &shell_cell.borrow();
$expr
});
);
($id:ident = $expr:expr) => (
SH.with(|shell_cell| {
let mut $id = &mut shell_cell.borrow_mut();
$expr
});
);
}
pub struct Ui { pub struct Ui {
pub initialized: bool, initialized: bool,
pub window: Option<ApplicationWindow>, comps: Arc<UiMutex<Components>>,
settings: Rc<RefCell<Settings>>,
shell: Rc<RefCell<Shell>>,
}
pub struct Components {
window: Option<ApplicationWindow>,
header_bar: HeaderBar, header_bar: HeaderBar,
} }
impl Ui { impl Components {
pub fn new() -> Ui { fn new() -> Components {
Ui { Components {
window: None, window: None,
header_bar: HeaderBar::new(), header_bar: HeaderBar::new(),
initialized: false,
} }
} }
@ -63,14 +38,29 @@ impl Ui {
self.window.as_ref().unwrap().destroy(); self.window.as_ref().unwrap().destroy();
} }
pub fn destroy(&mut self) { pub fn window(&self) -> &ApplicationWindow {
self.close_window(); self.window.as_ref().unwrap()
SHELL!(shell = { }
shell.nvim().ui_detach().expect("Error in ui_detach"); }
});
impl Ui {
pub fn new() -> Ui {
let settings = Rc::new(RefCell::new(Settings::new()));
let shell = Rc::new(RefCell::new(Shell::new(settings.clone())));
settings.borrow_mut().set_shell(Rc::downgrade(&shell));
Ui {
initialized: false,
comps: Arc::new(UiMutex::new(Components::new())),
shell: shell.clone(),
settings: settings,
}
} }
pub fn init(&mut self, app: &gtk::Application) { pub fn init(&mut self,
app: &gtk::Application,
nvim_bin_path: Option<&String>,
open_path: Option<&String>) {
if self.initialized { if self.initialized {
return; return;
} }
@ -78,39 +68,66 @@ impl Ui {
self.create_main_menu(app); self.create_main_menu(app);
SHELL!(shell = { let mut settings = self.settings.borrow_mut();
SET.with(|settings| { settings.init();
let mut settings = settings.borrow_mut();
settings.init(&mut shell);
});
self.header_bar.set_show_close_button(true); let mut comps = self.comps.borrow_mut();
let save_image = Image::new_from_icon_name("document-save", comps.header_bar.set_show_close_button(true);
gtk_sys::GTK_ICON_SIZE_SMALL_TOOLBAR as i32);
let save_btn = ToolButton::new(Some(&save_image), None);
save_btn.connect_clicked(|_| edit_save_all());
self.header_bar.pack_start(&save_btn);
let paste_image = Image::new_from_icon_name("edit-paste", let save_image = Image::new_from_icon_name("document-save",
gtk_sys::GTK_ICON_SIZE_SMALL_TOOLBAR as i32); gtk_sys::GTK_ICON_SIZE_SMALL_TOOLBAR as i32);
let paste_btn = ToolButton::new(Some(&paste_image), None); let save_btn = ToolButton::new(Some(&save_image), None);
paste_btn.connect_clicked(|_| edit_paste());
self.header_bar.pack_start(&paste_btn);
shell.init(); let shell = self.shell.clone();
save_btn.connect_clicked(move |_| { shell.borrow_mut().edit_save_all(); });
comps.header_bar.pack_start(&save_btn);
self.window = Some(ApplicationWindow::new(app)); let paste_image = Image::new_from_icon_name("edit-paste",
let window = self.window.as_ref().unwrap(); gtk_sys::GTK_ICON_SIZE_SMALL_TOOLBAR as i32);
let paste_btn = ToolButton::new(Some(&paste_image), None);
let shell = self.shell.clone();
paste_btn.connect_clicked(move |_| { shell.borrow_mut().edit_paste(); });
comps.header_bar.pack_start(&paste_btn);
window.set_titlebar(Some(&self.header_bar)); self.shell.borrow_mut().init(self.comps.clone());
window.add(&shell.drawing_area);
window.show_all();
window.connect_delete_event(gtk_delete);
window.set_title("Neovim-gtk");
shell.add_configure_event(); comps.window = Some(ApplicationWindow::new(app));
}); let window = comps.window.as_ref().unwrap();
window.set_titlebar(Some(&comps.header_bar));
let mut shell = self.shell.borrow_mut();
window.add(&*shell.drawing_area());
window.show_all();
window.set_title("Neovim-gtk");
let comps_ref = self.comps.clone();
let shell_ref = self.shell.clone();
window.connect_delete_event(move |_, _| gtk_delete(&*comps_ref, &*shell_ref));
shell.add_configure_event();
shell.init_nvim(nvim_bin_path);
if open_path.is_some() {
shell.open_file(open_path.unwrap());
}
self.guard_dispatch_thread(&mut shell);
}
fn guard_dispatch_thread(&self, shell: &mut Shell) {
let guard = shell.nvim().session.take_dispatch_guard();
let comps = self.comps.clone();
thread::spawn(move || {
guard.join().expect("Can't join dispatch thread");
glib::idle_add(move || {
comps.borrow().close_window();
glib::Continue(false)
});
});
} }
fn create_main_menu(&self, app: &gtk::Application) { fn create_main_menu(&self, app: &gtk::Application) {
@ -128,96 +145,76 @@ impl Ui {
let about_action = SimpleAction::new("HelpAbout", None); let about_action = SimpleAction::new("HelpAbout", None);
about_action.connect_activate(on_help_about); let comps = self.comps.clone();
about_action.connect_activate(move |_, _| on_help_about(&*comps.borrow()));
about_action.set_enabled(true); about_action.set_enabled(true);
app.add_action(&about_action); app.add_action(&about_action);
} }
} }
fn on_help_about(_: &SimpleAction, _: &Option<Variant>) { fn on_help_about(comps: &Components) {
UI.with(|ui_cell| { let about = AboutDialog::new();
let ui = ui_cell.borrow(); about.set_transient_for(comps.window.as_ref());
about.set_program_name("NeovimGtk");
about.set_version(env!("CARGO_PKG_VERSION"));
about.set_logo(None);
about.set_authors(&[env!("CARGO_PKG_AUTHORS")]);
let about = AboutDialog::new(); about.connect_response(|about, _| about.destroy());
about.set_transient_for(ui.window.as_ref()); about.show();
about.set_program_name("NeovimGtk");
about.set_version(env!("CARGO_PKG_VERSION"));
about.set_logo(None);
about.set_authors(&[env!("CARGO_PKG_AUTHORS")]);
about.connect_response(|about, _| about.destroy());
about.show();
});
} }
fn edit_paste() { fn gtk_delete(comps: &UiMutex<Components>, shell: &RefCell<Shell>) -> Inhibit {
SHELL!(shell = { Inhibit(if shell_dlg::can_close_window(comps, shell) {
let paste_command = if shell.mode == NvimMode::Normal { let comps = comps.borrow();
"\"*p" comps.close_window();
} else { shell.borrow_mut().detach_ui();
"<Esc>\"*pa" false
}; } else {
true
let mut nvim = shell.nvim(); })
nvim.input(paste_command).report_err(nvim);
});
}
fn edit_save_all() {
SHELL!(shell = {
let mut nvim = shell.nvim();
nvim.command(":wa").report_err(nvim);
});
}
fn gtk_delete(_: &ApplicationWindow, _: &Event) -> Inhibit {
Inhibit(UI.with(|ui_cell| {
let mut ui = ui_cell.borrow_mut();
if shell_dlg::can_close_window(&ui) {
ui.destroy();
false
} else {
true
}
}))
} }
pub struct UiMutex<T: ?Sized> { pub struct UiMutex<T: ?Sized> {
thread: String,
data: RefCell<T>, data: RefCell<T>,
} }
unsafe impl<T: ?Sized + Send> Send for UiMutex<T> {} unsafe impl<T: ?Sized> Send for UiMutex<T> {}
unsafe impl<T: ?Sized + Send> Sync for UiMutex<T> {} unsafe impl<T: ?Sized> Sync for UiMutex<T> {}
impl<T> UiMutex<T> { impl<T> UiMutex<T> {
pub fn new(t: T) -> UiMutex<T> { pub fn new(t: T) -> UiMutex<T> {
UiMutex { data: RefCell::new(t) } UiMutex {
thread: thread::current()
.name()
.expect("Can create UI only from main thread, current thiread has no name")
.to_owned(),
data: RefCell::new(t),
}
} }
} }
impl<T: ?Sized> UiMutex<T> { impl<T: ?Sized> UiMutex<T> {
pub fn borrow(&self) -> Ref<T> { pub fn borrow(&self) -> Ref<T> {
assert_ui_thread(); self.assert_ui_thread();
self.data.borrow() self.data.borrow()
} }
pub fn borrow_mut(&self) -> RefMut<T> { pub fn borrow_mut(&self) -> RefMut<T> {
assert_ui_thread(); self.assert_ui_thread();
self.data.borrow_mut() self.data.borrow_mut()
} }
}
#[inline]
#[inline] fn assert_ui_thread(&self) {
fn assert_ui_thread() { match thread::current().name() {
match thread::current().name() { Some(name) if name == self.thread => (),
Some("main") => (), Some(name) => {
Some(ref name) => { panic!("Can create UI only from main thread, {}", name);
panic!("Can create UI only from main thread, {}", name); }
None => panic!("Can create UI only from main thread, current thread has no name"),
} }
None => panic!("Can create UI only from main thread, current thiread has no name"),
} }
} }

View File

@ -81,20 +81,6 @@ pub struct UiModel {
} }
impl UiModel { impl UiModel {
pub fn empty() -> UiModel {
UiModel {
columns: 0,
rows: 0,
cur_row: 0,
cur_col: 0,
model: vec![],
top: 0,
bot: 0,
left: 0,
right: 0,
}
}
pub fn new(rows: u64, columns: u64) -> UiModel { pub fn new(rows: u64, columns: u64) -> UiModel {
let mut model = Vec::with_capacity(rows as usize); let mut model = Vec::with_capacity(rows as usize);
for i in 0..rows as usize { for i in 0..rows as usize {
@ -314,21 +300,13 @@ impl ModelRect {
} else { } else {
x1 x1
}; };
let x2 = if x2 > 0.0 { let x2 = if x2 > 0.0 { x2 - 1.0 } else { x2 };
x2 - 1.0
} else {
x2
};
let y1 = if y1 > 0.0 { let y1 = if y1 > 0.0 {
y1 // - 1.0 y1 // - 1.0
} else { } else {
y1 y1
}; };
let y2 = if y2 > 0.0 { let y2 = if y2 > 0.0 { y2 - 1.0 } else { y2 };
y2 - 1.0
} else {
y2
};
let left = (x1 / char_width) as usize; let left = (x1 / char_width) as usize;
let right = (x2 / char_width) as usize; let right = (x2 / char_width) as usize;
let top = (y1 / line_height) as usize; let top = (y1 / line_height) as usize;
@ -359,7 +337,9 @@ impl<'a> Iterator for ClipRowIterator<'a> {
fn next(&mut self) -> Option<(usize, ClipLine<'a>)> { fn next(&mut self) -> Option<(usize, ClipLine<'a>)> {
self.pos += 1; self.pos += 1;
self.iter.next().map(|line| (self.rect.top + self.pos - 1, ClipLine::new(line, self.rect))) self.iter
.next()
.map(|line| (self.rect.top + self.pos - 1, ClipLine::new(line, self.rect)))
} }
} }
@ -406,7 +386,9 @@ impl<'a> Iterator for ClipColIterator<'a> {
fn next(&mut self) -> Option<(usize, &'a Cell)> { fn next(&mut self) -> Option<(usize, &'a Cell)> {
self.pos += 1; self.pos += 1;
self.iter.next().map(|line| (self.rect.left + self.pos - 1, line)) self.iter
.next()
.map(|line| (self.rect.left + self.pos - 1, line))
} }
} }