Merge branch 'master' into imcontext
This commit is contained in:
128
src/cursor.rs
128
src/cursor.rs
@@ -2,7 +2,8 @@ use cairo;
|
||||
use ui_model::Color;
|
||||
use ui::UiMutex;
|
||||
use shell;
|
||||
use shell::NvimMode;
|
||||
use mode;
|
||||
use nvim;
|
||||
use nvim::{RepaintMode, RedrawEvents};
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
@@ -123,7 +124,63 @@ impl Cursor {
|
||||
let current_point = ctx.get_current_point();
|
||||
ctx.set_source_rgba(1.0 - bg.0, 1.0 - bg.1, 1.0 - bg.2, 0.6 * state.alpha.0);
|
||||
|
||||
let cursor_width = if shell.mode == NvimMode::Insert {
|
||||
let (y, width, height) =
|
||||
cursor_rect(&shell.mode, char_width, line_height, line_y, double_width);
|
||||
|
||||
ctx.rectangle(current_point.0, y, width, height);
|
||||
if state.anim_phase == AnimPhase::NoFocus {
|
||||
ctx.stroke();
|
||||
} else {
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn cursor_rect(mode: &mode::Mode,
|
||||
char_width: f64,
|
||||
line_height: f64,
|
||||
line_y: f64,
|
||||
double_width: bool)
|
||||
-> (f64, f64, f64) {
|
||||
if let Some(mode_info) = mode.mode_info() {
|
||||
match mode_info.cursor_shape() {
|
||||
None |
|
||||
Some(&nvim::CursorShape::Unknown) |
|
||||
Some(&nvim::CursorShape::Block) => {
|
||||
let cursor_width = if double_width {
|
||||
char_width * 2.0
|
||||
} else {
|
||||
char_width
|
||||
};
|
||||
(line_y, cursor_width, line_height)
|
||||
}
|
||||
Some(&nvim::CursorShape::Vertical) => {
|
||||
let cell_percentage = mode_info.cell_percentage();
|
||||
let cursor_width = if cell_percentage > 0 {
|
||||
(char_width * cell_percentage as f64) / 100.0
|
||||
} else {
|
||||
char_width
|
||||
};
|
||||
(line_y, cursor_width, line_height)
|
||||
}
|
||||
Some(&nvim::CursorShape::Horizontal) => {
|
||||
let cell_percentage = mode_info.cell_percentage();
|
||||
let cursor_width = if double_width {
|
||||
char_width * 2.0
|
||||
} else {
|
||||
char_width
|
||||
};
|
||||
|
||||
if cell_percentage > 0 {
|
||||
let height = (line_height * cell_percentage as f64) / 100.0;
|
||||
(line_y + line_height - height, cursor_width, height)
|
||||
} else {
|
||||
(line_y, cursor_width, line_height)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let cursor_width = if mode.is(&mode::NvimMode::Insert) {
|
||||
char_width / 5.0
|
||||
} else {
|
||||
if double_width {
|
||||
@@ -133,15 +190,9 @@ impl Cursor {
|
||||
}
|
||||
};
|
||||
|
||||
ctx.rectangle(current_point.0, line_y, cursor_width, line_height);
|
||||
if state.anim_phase == AnimPhase::NoFocus {
|
||||
ctx.stroke();
|
||||
} else {
|
||||
ctx.fill();
|
||||
}
|
||||
(line_y, cursor_width, line_height)
|
||||
}
|
||||
}
|
||||
|
||||
fn anim_step(state: &Arc<UiMutex<State>>) -> glib::Continue {
|
||||
let mut mut_state = state.borrow_mut();
|
||||
|
||||
@@ -201,3 +252,62 @@ impl Drop for Cursor {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_cursor_rect_horizontal() {
|
||||
let mut mode = mode::Mode::new();
|
||||
let mode_info = nvim::ModeInfo::new(&vec![(From::from("cursor_shape"),
|
||||
From::from("horizontal")),
|
||||
(From::from("cell_percentage"), From::from(25))]);
|
||||
mode.update("insert", 0);
|
||||
mode.set_info(true, vec![mode_info.unwrap()]);
|
||||
let char_width = 50.0;
|
||||
let line_height = 30.0;
|
||||
let line_y = 0.0;
|
||||
|
||||
let (y, width, height) = cursor_rect(&mode, char_width, line_height, line_y, false);
|
||||
assert_eq!(line_y + line_height - line_height / 4.0, y);
|
||||
assert_eq!(char_width, width);
|
||||
assert_eq!(line_height / 4.0, height);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cursor_rect_horizontal_doublewidth() {
|
||||
let mut mode = mode::Mode::new();
|
||||
let mode_info = nvim::ModeInfo::new(&vec![(From::from("cursor_shape"),
|
||||
From::from("horizontal")),
|
||||
(From::from("cell_percentage"), From::from(25))]);
|
||||
mode.update("insert", 0);
|
||||
mode.set_info(true, vec![mode_info.unwrap()]);
|
||||
let char_width = 50.0;
|
||||
let line_height = 30.0;
|
||||
let line_y = 0.0;
|
||||
|
||||
let (y, width, height) = cursor_rect(&mode, char_width, line_height, line_y, true);
|
||||
assert_eq!(line_y + line_height - line_height / 4.0, y);
|
||||
assert_eq!(char_width * 2.0, width);
|
||||
assert_eq!(line_height / 4.0, height);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cursor_rect_vertical() {
|
||||
let mut mode = mode::Mode::new();
|
||||
let mode_info = nvim::ModeInfo::new(&vec![(From::from("cursor_shape"),
|
||||
From::from("vertical")),
|
||||
(From::from("cell_percentage"), From::from(25))]);
|
||||
mode.update("insert", 0);
|
||||
mode.set_info(true, vec![mode_info.unwrap()]);
|
||||
let char_width = 50.0;
|
||||
let line_height = 30.0;
|
||||
let line_y = 0.0;
|
||||
|
||||
let (y, width, height) = cursor_rect(&mode, char_width, line_height, line_y, false);
|
||||
assert_eq!(line_y, y);
|
||||
assert_eq!(char_width / 4.0, width);
|
||||
assert_eq!(line_height, height);
|
||||
}
|
||||
}
|
||||
|
||||
52
src/error.rs
Normal file
52
src/error.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use htmlescape::encode_minimal;
|
||||
|
||||
use gtk;
|
||||
use gtk::prelude::*;
|
||||
|
||||
use gtk_sys;
|
||||
|
||||
use shell;
|
||||
|
||||
pub struct ErrorArea {
|
||||
base: gtk::Box,
|
||||
label: gtk::Label,
|
||||
}
|
||||
|
||||
impl ErrorArea {
|
||||
pub fn new() -> Self {
|
||||
let base = gtk::Box::new(gtk::Orientation::Horizontal, 0);
|
||||
|
||||
let label = gtk::Label::new(None);
|
||||
label.set_line_wrap(true);
|
||||
let error_image = gtk::Image::new_from_icon_name("dialog-error",
|
||||
gtk_sys::GTK_ICON_SIZE_DIALOG as i32);
|
||||
base.pack_start(&error_image, false, true, 10);
|
||||
base.pack_start(&label, true, true, 1);
|
||||
|
||||
ErrorArea { base, label }
|
||||
}
|
||||
|
||||
pub fn show_nvim_start_error(&self, err: &str, cmd: &str) {
|
||||
error!("Can't start nvim: {}\nCommand line: {}", err, cmd);
|
||||
self.label.set_markup(&format!("<big>Can't start nvim instance:</big>\n\
|
||||
<i>{}</i>\n\
|
||||
<span foreground=\"red\"><i>{}</i></span>\n\n\
|
||||
<big>Possible error reasons:</big>\n\
|
||||
● Not supported nvim version (minimum supported version is <b>{}</b>)\n\
|
||||
● Error in configuration file (init.vim or ginit.vim)\n\
|
||||
● Wrong nvim binary path \
|
||||
(right path can be passed with <i>--nvim-bin-path=path_here</i>)",
|
||||
encode_minimal(cmd), encode_minimal(err), shell::MINIMUM_SUPPORTED_NVIM_VERSION));
|
||||
self.base.show_all();
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for ErrorArea {
|
||||
type Target = gtk::Box;
|
||||
|
||||
fn deref(&self) -> >k::Box {
|
||||
&self.base
|
||||
}
|
||||
}
|
||||
21
src/main.rs
21
src/main.rs
@@ -19,6 +19,8 @@ extern crate serde_derive;
|
||||
extern crate serde;
|
||||
extern crate toml;
|
||||
|
||||
mod value;
|
||||
mod mode;
|
||||
mod ui_model;
|
||||
#[macro_use]
|
||||
mod ui;
|
||||
@@ -31,14 +33,16 @@ mod shell_dlg;
|
||||
mod popup_menu;
|
||||
mod project;
|
||||
mod tabline;
|
||||
mod error;
|
||||
|
||||
|
||||
use std::env;
|
||||
use gtk::prelude::*;
|
||||
use gio::{ApplicationExt, FileExt};
|
||||
|
||||
use ui::Ui;
|
||||
|
||||
use shell::ShellOptions;
|
||||
|
||||
const BIN_PATH_ARG: &'static str = "--nvim-bin-path";
|
||||
|
||||
fn main() {
|
||||
@@ -65,25 +69,22 @@ fn main() {
|
||||
.filter(|a| !a.starts_with(BIN_PATH_ARG))
|
||||
.map(String::as_str)
|
||||
.collect();
|
||||
app.run(argv.len() as i32, &argv);
|
||||
app.run(&argv);
|
||||
}
|
||||
|
||||
fn open(app: >k::Application, files: &[gio::File], _: &str) {
|
||||
for f in files {
|
||||
let mut ui = Ui::new();
|
||||
let mut ui = Ui::new(ShellOptions::new(nvim_bin_path(std::env::args()),
|
||||
f.get_path().and_then(|p| p.to_str().map(str::to_owned))));
|
||||
|
||||
ui.init(app,
|
||||
nvim_bin_path(std::env::args()).as_ref(),
|
||||
f.get_path().as_ref().map(|p| p.to_str()).unwrap_or(None));
|
||||
ui.init(app);
|
||||
}
|
||||
}
|
||||
|
||||
fn activate(app: >k::Application) {
|
||||
let mut ui = Ui::new();
|
||||
let mut ui = Ui::new(ShellOptions::new(nvim_bin_path(std::env::args()), None));
|
||||
|
||||
ui.init(app,
|
||||
nvim_bin_path(std::env::args()).as_ref(),
|
||||
None);
|
||||
ui.init(app);
|
||||
}
|
||||
|
||||
fn nvim_bin_path<I>(args: I) -> Option<String>
|
||||
|
||||
52
src/mode.rs
Normal file
52
src/mode.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
use nvim;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum NvimMode {
|
||||
Normal,
|
||||
Insert,
|
||||
Other,
|
||||
}
|
||||
|
||||
pub struct Mode {
|
||||
mode: NvimMode,
|
||||
idx: usize,
|
||||
info: Option<Vec<nvim::ModeInfo>>,
|
||||
}
|
||||
|
||||
impl Mode {
|
||||
pub fn new() -> Self {
|
||||
Mode {
|
||||
mode: NvimMode::Normal,
|
||||
idx: 0,
|
||||
info: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is(&self, mode: &NvimMode) -> bool {
|
||||
self.mode == *mode
|
||||
}
|
||||
|
||||
pub fn mode_info(&self) -> Option<&nvim::ModeInfo> {
|
||||
self.info
|
||||
.as_ref()
|
||||
.and_then(|i| i.get(self.idx))
|
||||
}
|
||||
|
||||
pub fn update(&mut self, mode: &str, idx: usize) {
|
||||
match mode {
|
||||
"normal" => self.mode = NvimMode::Normal,
|
||||
"insert" => self.mode = NvimMode::Insert,
|
||||
_ => self.mode = NvimMode::Other,
|
||||
}
|
||||
|
||||
self.idx = idx;
|
||||
}
|
||||
|
||||
pub fn set_info(&mut self, cursor_style_enabled: bool, info: Vec<nvim::ModeInfo>) {
|
||||
self.info = if cursor_style_enabled {
|
||||
Some(info)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
}
|
||||
309
src/nvim.rs
309
src/nvim.rs
@@ -1,8 +1,10 @@
|
||||
use std::io::{Result, Error, ErrorKind};
|
||||
use std::error;
|
||||
use std::fmt;
|
||||
use std::env;
|
||||
use std::process::{Stdio, Command};
|
||||
use std::result;
|
||||
use std::sync::Arc;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use neovim_lib::{Handler, Neovim, NeovimApi, Session, Value, UiAttachOptions, CallError, UiOption};
|
||||
use neovim_lib::neovim_api::Tabpage;
|
||||
@@ -12,6 +14,8 @@ use ui_model::{ModelRect, ModelRectVec};
|
||||
use shell;
|
||||
use glib;
|
||||
|
||||
use value::ValueMapExt;
|
||||
|
||||
pub trait RedrawEvents {
|
||||
fn on_cursor_goto(&mut self, row: u64, col: u64) -> RepaintMode;
|
||||
|
||||
@@ -37,7 +41,7 @@ pub trait RedrawEvents {
|
||||
|
||||
fn on_update_sp(&mut self, sp: i64) -> RepaintMode;
|
||||
|
||||
fn on_mode_change(&mut self, mode: &str) -> RepaintMode;
|
||||
fn on_mode_change(&mut self, mode: &str, idx: u64) -> RepaintMode;
|
||||
|
||||
fn on_mouse(&mut self, on: bool) -> RepaintMode;
|
||||
|
||||
@@ -56,8 +60,13 @@ pub trait RedrawEvents {
|
||||
|
||||
fn tabline_update(&mut self,
|
||||
selected: Tabpage,
|
||||
tabs: Vec<(Tabpage, Option<&str>)>)
|
||||
tabs: Vec<(Tabpage, Option<String>)>)
|
||||
-> RepaintMode;
|
||||
|
||||
fn mode_info_set(&mut self,
|
||||
cursor_style_enabled: bool,
|
||||
mode_info: Vec<ModeInfo>)
|
||||
-> RepaintMode;
|
||||
}
|
||||
|
||||
pub trait GuiApi {
|
||||
@@ -76,9 +85,136 @@ macro_rules! try_uint {
|
||||
($exp:expr) => ($exp.as_u64().ok_or("Can't convert argument to u64".to_owned())?)
|
||||
}
|
||||
|
||||
macro_rules! try_bool {
|
||||
($exp:expr) => ($exp.as_bool().ok_or("Can't convert argument to bool".to_owned())?)
|
||||
}
|
||||
|
||||
macro_rules! map_array {
|
||||
($arg:expr, $err:expr, |$item:ident| $exp:expr) => (
|
||||
$arg.as_array()
|
||||
.ok_or($err)
|
||||
.and_then(|items| items.iter().map(|$item| {
|
||||
$exp
|
||||
}).collect::<Result<Vec<_>, _>>())
|
||||
);
|
||||
($arg:expr, $err:expr, |$item:ident| {$exp:expr}) => (
|
||||
$arg.as_array()
|
||||
.ok_or($err)
|
||||
.and_then(|items| items.iter().map(|$item| {
|
||||
$exp
|
||||
}).collect::<Result<Vec<_>, _>>())
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CursorShape {
|
||||
Block,
|
||||
Horizontal,
|
||||
Vertical,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl CursorShape {
|
||||
fn new(shape_code: &Value) -> Result<CursorShape, String> {
|
||||
let str_code = shape_code
|
||||
.as_str()
|
||||
.ok_or("Can't convert cursor shape to string".to_owned())?;
|
||||
|
||||
Ok(match str_code {
|
||||
"block" => CursorShape::Block,
|
||||
"horizontal" => CursorShape::Horizontal,
|
||||
"vertical" => CursorShape::Vertical,
|
||||
_ => {
|
||||
error!("Unknown cursor_shape {}", str_code);
|
||||
CursorShape::Unknown
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ModeInfo {
|
||||
cursor_shape: Option<CursorShape>,
|
||||
cell_percentage: Option<u64>,
|
||||
}
|
||||
|
||||
impl ModeInfo {
|
||||
pub fn new(mode_info_arr: &Vec<(Value, Value)>) -> Result<Self, String> {
|
||||
let mode_info_map = mode_info_arr.to_attrs_map()?;
|
||||
|
||||
let cursor_shape = if let Some(shape) = mode_info_map.get("cursor_shape") {
|
||||
Some(CursorShape::new(shape)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let cell_percentage = if let Some(cell_percentage) = mode_info_map.get("cell_percentage") {
|
||||
cell_percentage.as_u64()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(ModeInfo {
|
||||
cursor_shape,
|
||||
cell_percentage,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn cursor_shape(&self) -> Option<&CursorShape> {
|
||||
self.cursor_shape.as_ref()
|
||||
}
|
||||
|
||||
pub fn cell_percentage(&self) -> u64 {
|
||||
self.cell_percentage.unwrap_or(0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NvimInitError {
|
||||
source: Box<error::Error>,
|
||||
cmd: String,
|
||||
}
|
||||
|
||||
impl NvimInitError {
|
||||
pub fn new<E>(cmd: &Command, error: E) -> NvimInitError
|
||||
where E: Into<Box<error::Error>>
|
||||
{
|
||||
NvimInitError {
|
||||
cmd: format!("{:?}", cmd),
|
||||
source: error.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn source(&self) -> String {
|
||||
format!("{}", self.source)
|
||||
}
|
||||
|
||||
pub fn cmd(&self) -> &str {
|
||||
&self.cmd
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for NvimInitError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{:?}", self.source)
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for NvimInitError {
|
||||
fn description(&self) -> &str {
|
||||
"Can't start nvim instance"
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&error::Error> {
|
||||
Some(&*self.source)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn initialize(shell: Arc<UiMutex<shell::State>>,
|
||||
nvim_bin_path: Option<&String>)
|
||||
-> Result<Neovim> {
|
||||
nvim_bin_path: Option<&String>,
|
||||
cols: u64,
|
||||
rows: u64)
|
||||
-> result::Result<Neovim, NvimInitError> {
|
||||
let mut cmd = if let Some(path) = nvim_bin_path {
|
||||
Command::new(path)
|
||||
} else {
|
||||
@@ -106,10 +242,7 @@ pub fn initialize(shell: Arc<UiMutex<shell::State>>,
|
||||
let session = Session::new_child_cmd(&mut cmd);
|
||||
|
||||
let session = match session {
|
||||
Err(e) => {
|
||||
println!("Error execute: {:?}", cmd);
|
||||
return Err(From::from(e));
|
||||
}
|
||||
Err(e) => return Err(NvimInitError::new(&cmd, e)),
|
||||
Ok(s) => s,
|
||||
};
|
||||
|
||||
@@ -120,10 +253,10 @@ pub fn initialize(shell: Arc<UiMutex<shell::State>>,
|
||||
let mut opts = UiAttachOptions::new();
|
||||
opts.set_popupmenu_external(false);
|
||||
opts.set_tabline_external(true);
|
||||
nvim.ui_attach(80, 24, opts)
|
||||
.map_err(|e| Error::new(ErrorKind::Other, e))?;
|
||||
nvim.ui_attach(cols, rows, opts)
|
||||
.map_err(|e| NvimInitError::new(&cmd, e))?;
|
||||
nvim.command("runtime! ginit.vim")
|
||||
.map_err(|e| Error::new(ErrorKind::Other, e))?;
|
||||
.map_err(|e| NvimInitError::new(&cmd, e))?;
|
||||
|
||||
Ok(nvim)
|
||||
}
|
||||
@@ -263,23 +396,17 @@ fn call(ui: &mut shell::State,
|
||||
"update_bg" => ui.on_update_bg(try_int!(args[0])),
|
||||
"update_fg" => ui.on_update_fg(try_int!(args[0])),
|
||||
"update_sp" => ui.on_update_sp(try_int!(args[0])),
|
||||
"mode_change" => ui.on_mode_change(try_str!(args[0])),
|
||||
"mode_change" => ui.on_mode_change(try_str!(args[0]), try_uint!(args[1])),
|
||||
"mouse_on" => ui.on_mouse(true),
|
||||
"mouse_off" => ui.on_mouse(false),
|
||||
"busy_start" => ui.on_busy(true),
|
||||
"busy_stop" => ui.on_busy(false),
|
||||
"popupmenu_show" => {
|
||||
let mut menu_items = Vec::new();
|
||||
|
||||
let items = args[0].as_array().ok_or("Error get menu list array")?;
|
||||
for item in items {
|
||||
let item_line: result::Result<Vec<_>, &str> = item.as_array()
|
||||
.ok_or("Error get menu item array")?
|
||||
.iter()
|
||||
.map(|col| col.as_str().ok_or("Error get menu column"))
|
||||
.collect();
|
||||
menu_items.push(item_line?);
|
||||
}
|
||||
let menu_items = map_array!(args[0], "Error get menu list array", |item| {
|
||||
map_array!(item,
|
||||
"Error get menu item array",
|
||||
|col| col.as_str().ok_or("Error get menu column"))
|
||||
})?;
|
||||
|
||||
ui.popupmenu_show(&menu_items,
|
||||
try_int!(args[1]),
|
||||
@@ -289,27 +416,34 @@ fn call(ui: &mut shell::State,
|
||||
"popupmenu_hide" => ui.popupmenu_hide(),
|
||||
"popupmenu_select" => ui.popupmenu_select(try_int!(args[0])),
|
||||
"tabline_update" => {
|
||||
let tabs_in = args[1].as_array().ok_or("Error get tabline list")?;
|
||||
let tabs_out = map_array!(args[1], "Error get tabline list".to_owned(), |tab| {
|
||||
tab.as_map()
|
||||
.ok_or("Error get map for tab".to_owned())
|
||||
.and_then(|tab_map| tab_map.to_attrs_map())
|
||||
.map(|tab_attrs| {
|
||||
let name_attr = tab_attrs
|
||||
.get("name")
|
||||
.and_then(|n| n.as_str().map(|s| s.to_owned()));
|
||||
let tab_attr = tab_attrs
|
||||
.get("tab")
|
||||
.map(|tab_id| Tabpage::new(tab_id.clone()))
|
||||
.unwrap();
|
||||
|
||||
let mut tabs_out = Vec::new();
|
||||
for tab in tabs_in {
|
||||
let tab_attrs = tab.as_map().ok_or("Error get map for tab")?;
|
||||
|
||||
let mut tab_attr = None;
|
||||
let mut name_attr = None;
|
||||
|
||||
for attr in tab_attrs {
|
||||
let key = attr.0.as_str().ok_or("Error get key value")?;
|
||||
if key == "tab" {
|
||||
tab_attr = Some(Tabpage::new(attr.1.clone()));
|
||||
} else if key == "name" {
|
||||
name_attr = attr.1.as_str();
|
||||
}
|
||||
}
|
||||
tabs_out.push((tab_attr.unwrap(), name_attr));
|
||||
}
|
||||
(tab_attr, name_attr)
|
||||
})
|
||||
})?;
|
||||
ui.tabline_update(Tabpage::new(args[0].clone()), tabs_out)
|
||||
}
|
||||
"mode_info_set" => {
|
||||
let mode_info = map_array!(args[1],
|
||||
"Error get array key value for mode_info".to_owned(),
|
||||
|mi| {
|
||||
mi.as_map()
|
||||
.ok_or("Erro get map for mode_info".to_owned())
|
||||
.and_then(|mi_map| ModeInfo::new(mi_map))
|
||||
})?;
|
||||
ui.mode_info_set(try_bool!(args[0]), mode_info)
|
||||
}
|
||||
_ => {
|
||||
println!("Event {}({:?})", method, args);
|
||||
RepaintMode::Nothing
|
||||
@@ -370,6 +504,97 @@ impl RepaintMode {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum NeovimClientWrapper {
|
||||
Uninitialized,
|
||||
Initialized(Neovim),
|
||||
Error,
|
||||
}
|
||||
|
||||
impl NeovimClientWrapper {
|
||||
pub fn is_initialized(&self) -> bool {
|
||||
match *self {
|
||||
NeovimClientWrapper::Initialized(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_error(&self) -> bool {
|
||||
match *self {
|
||||
NeovimClientWrapper::Error => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn nvim(&self) -> &Neovim {
|
||||
match *self {
|
||||
NeovimClientWrapper::Initialized(ref nvim) => nvim,
|
||||
NeovimClientWrapper::Uninitialized => panic!("Access to uninitialized neovim client"),
|
||||
NeovimClientWrapper::Error => {
|
||||
panic!("Access to neovim client that is not started due to some error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn nvim_mut(&mut self) -> &mut Neovim {
|
||||
match *self {
|
||||
NeovimClientWrapper::Initialized(ref mut nvim) => nvim,
|
||||
NeovimClientWrapper::Uninitialized => panic!("Access to uninitialized neovim client"),
|
||||
NeovimClientWrapper::Error => {
|
||||
panic!("Access to neovim client that is not started due to some error")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NeovimClient {
|
||||
nvim: NeovimClientWrapper,
|
||||
}
|
||||
|
||||
impl NeovimClient {
|
||||
pub fn new() -> Self {
|
||||
NeovimClient { nvim: NeovimClientWrapper::Uninitialized }
|
||||
}
|
||||
|
||||
pub fn set_nvim(&mut self, nvim: Neovim) {
|
||||
self.nvim = NeovimClientWrapper::Initialized(nvim);
|
||||
}
|
||||
|
||||
pub fn set_error(&mut self) {
|
||||
self.nvim = NeovimClientWrapper::Error;
|
||||
}
|
||||
|
||||
pub fn is_initialized(&self) -> bool {
|
||||
self.nvim.is_initialized()
|
||||
}
|
||||
|
||||
pub fn is_error(&self) -> bool {
|
||||
self.nvim.is_error()
|
||||
}
|
||||
|
||||
pub fn nvim(&self) -> &Neovim {
|
||||
self.nvim.nvim()
|
||||
}
|
||||
|
||||
pub fn nvim_mut(&mut self) -> &mut Neovim {
|
||||
self.nvim.nvim_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for NeovimClient {
|
||||
type Target = Neovim;
|
||||
|
||||
fn deref(&self) -> &Neovim {
|
||||
self.nvim()
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for NeovimClient {
|
||||
fn deref_mut(&mut self) -> &mut Neovim {
|
||||
self.nvim_mut()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -9,6 +9,7 @@ use gdk::{EventButton, EventType};
|
||||
|
||||
use neovim_lib::{Neovim, NeovimApi};
|
||||
|
||||
use nvim;
|
||||
use nvim::ErrorReport;
|
||||
use shell;
|
||||
use input;
|
||||
@@ -16,7 +17,7 @@ use input;
|
||||
const MAX_VISIBLE_ROWS: i32 = 10;
|
||||
|
||||
struct State {
|
||||
nvim: Option<Rc<RefCell<Neovim>>>,
|
||||
nvim: Option<Rc<RefCell<nvim::NeovimClient>>>,
|
||||
renderer: gtk::CellRendererText,
|
||||
tree: gtk::TreeView,
|
||||
scroll: gtk::ScrolledWindow,
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use std::rc::{Rc, Weak};
|
||||
use std::cell::RefCell;
|
||||
|
||||
use gio::SettingsExt;
|
||||
|
||||
#[cfg(unix)]
|
||||
use nvim::RepaintMode;
|
||||
|
||||
|
||||
347
src/shell.rs
347
src/shell.rs
@@ -1,8 +1,8 @@
|
||||
use std::cell::{RefMut, RefCell};
|
||||
use std::rc::Rc;
|
||||
use std::sync;
|
||||
use std::sync::Arc;
|
||||
use std::ops::Deref;
|
||||
use std::thread;
|
||||
|
||||
use cairo;
|
||||
use pangocairo::CairoContextExt;
|
||||
@@ -21,23 +21,29 @@ use neovim_lib::neovim_api::Tabpage;
|
||||
use settings::{Settings, FontSource};
|
||||
use ui_model::{UiModel, Cell, Attrs, Color, ModelRect, COLOR_BLACK, COLOR_WHITE, COLOR_RED};
|
||||
use nvim;
|
||||
use nvim::{RedrawEvents, GuiApi, RepaintMode, ErrorReport};
|
||||
use nvim::{RedrawEvents, GuiApi, RepaintMode, ErrorReport, NeovimClient};
|
||||
use input;
|
||||
use input::keyval_to_input_string;
|
||||
use cursor::Cursor;
|
||||
use ui;
|
||||
use ui::UiMutex;
|
||||
use popup_menu::PopupMenu;
|
||||
use tabline::Tabline;
|
||||
use error;
|
||||
use mode;
|
||||
|
||||
const DEFAULT_FONT_NAME: &'static str = "DejaVu Sans Mono 12";
|
||||
const DEFAULT_FONT_NAME: &str = "DejaVu Sans Mono 12";
|
||||
pub const MINIMUM_SUPPORTED_NVIM_VERSION: &str = "0.2";
|
||||
|
||||
macro_rules! idle_cb_call {
|
||||
($state:ident.$cb:ident($( $x:expr ),*)) => (
|
||||
glib::idle_add(move || {
|
||||
if let Some(ref cb) = $state.borrow().$cb {
|
||||
(&mut *cb.borrow_mut())($($x),*);
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum NvimMode {
|
||||
Normal,
|
||||
Insert,
|
||||
Other,
|
||||
glib::Continue(false)
|
||||
});
|
||||
)
|
||||
}
|
||||
|
||||
pub struct State {
|
||||
@@ -46,55 +52,66 @@ pub struct State {
|
||||
fg_color: Color,
|
||||
sp_color: Color,
|
||||
cur_attrs: Option<Attrs>,
|
||||
pub mode: NvimMode,
|
||||
mouse_enabled: bool,
|
||||
nvim: Option<Rc<RefCell<Neovim>>>,
|
||||
nvim: Rc<RefCell<NeovimClient>>,
|
||||
font_desc: FontDescription,
|
||||
cursor: Option<Cursor>,
|
||||
popup_menu: RefCell<PopupMenu>,
|
||||
settings: Rc<RefCell<Settings>>,
|
||||
|
||||
pub mode: mode::Mode,
|
||||
|
||||
stack: gtk::Stack,
|
||||
drawing_area: gtk::DrawingArea,
|
||||
tabs: Tabline,
|
||||
im_context: gtk::IMMulticontext,
|
||||
error_area: error::ErrorArea,
|
||||
|
||||
line_height: Option<f64>,
|
||||
char_width: Option<f64>,
|
||||
request_resize: bool,
|
||||
resize_timer: Option<glib::SourceId>,
|
||||
|
||||
parent: sync::Weak<UiMutex<ui::Components>>,
|
||||
options: ShellOptions,
|
||||
|
||||
detach_cb: Option<Box<RefCell<FnMut() + Send + 'static>>>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn new(settings: Rc<RefCell<Settings>>, parent: &Arc<UiMutex<ui::Components>>) -> State {
|
||||
pub fn new(settings: Rc<RefCell<Settings>>, options: ShellOptions) -> State {
|
||||
let drawing_area = gtk::DrawingArea::new();
|
||||
let popup_menu = RefCell::new(PopupMenu::new(&drawing_area));
|
||||
|
||||
State {
|
||||
model: UiModel::new(24, 80),
|
||||
nvim: None,
|
||||
model: UiModel::new(1, 1),
|
||||
nvim: Rc::new(RefCell::new(NeovimClient::new())),
|
||||
cur_attrs: None,
|
||||
bg_color: COLOR_BLACK,
|
||||
fg_color: COLOR_WHITE,
|
||||
sp_color: COLOR_RED,
|
||||
mode: NvimMode::Normal,
|
||||
mouse_enabled: true,
|
||||
font_desc: FontDescription::from_string(DEFAULT_FONT_NAME),
|
||||
cursor: None,
|
||||
popup_menu,
|
||||
settings: settings,
|
||||
|
||||
mode: mode::Mode::new(),
|
||||
|
||||
// UI
|
||||
stack: gtk::Stack::new(),
|
||||
drawing_area,
|
||||
tabs: Tabline::new(),
|
||||
im_context: gtk::IMMulticontext::new(),
|
||||
error_area: error::ErrorArea::new(),
|
||||
|
||||
line_height: None,
|
||||
char_width: None,
|
||||
resize_timer: None,
|
||||
request_resize: false,
|
||||
|
||||
parent: Arc::downgrade(parent),
|
||||
options,
|
||||
|
||||
detach_cb: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,11 +124,21 @@ impl State {
|
||||
}
|
||||
|
||||
pub fn nvim(&self) -> RefMut<Neovim> {
|
||||
self.nvim.as_ref().unwrap().borrow_mut()
|
||||
RefMut::map(self.nvim.borrow_mut(), |n| n.nvim_mut())
|
||||
}
|
||||
|
||||
pub fn nvim_clone(&self) -> Rc<RefCell<Neovim>> {
|
||||
self.nvim.as_ref().unwrap().clone()
|
||||
pub fn nvim_clone(&self) -> Rc<RefCell<NeovimClient>> {
|
||||
self.nvim.clone()
|
||||
}
|
||||
|
||||
pub fn set_detach_cb<F>(&mut self, cb: Option<F>)
|
||||
where F: FnMut() + Send + 'static
|
||||
{
|
||||
if cb.is_some() {
|
||||
self.detach_cb = Some(Box::new(RefCell::new(cb.unwrap())));
|
||||
} else {
|
||||
self.detach_cb = None;
|
||||
}
|
||||
}
|
||||
|
||||
fn create_pango_font(&self) -> FontDescription {
|
||||
@@ -186,10 +213,38 @@ impl State {
|
||||
}
|
||||
|
||||
fn im_commit(&self, ch: &str) {
|
||||
if let Some(ref nvim) = self.nvim {
|
||||
input::im_input(&mut *nvim.borrow_mut(), ch);
|
||||
input::im_input(&mut self.nvim.borrow_mut(), ch);
|
||||
}
|
||||
|
||||
fn calc_char_bounds(&self, ctx: &cairo::Context) -> (i32, i32) {
|
||||
let layout = ctx.create_pango_layout();
|
||||
|
||||
let desc = self.create_pango_font();
|
||||
layout.set_font_description(Some(&desc));
|
||||
layout.set_text("A", -1);
|
||||
|
||||
layout.get_pixel_size()
|
||||
}
|
||||
|
||||
fn calc_line_metrics(&mut self, ctx: &cairo::Context) {
|
||||
if self.line_height.is_none() {
|
||||
let (width, height) = self.calc_char_bounds(ctx);
|
||||
self.line_height = Some(height as f64);
|
||||
self.char_width = Some(width as f64);
|
||||
}
|
||||
}
|
||||
|
||||
fn calc_nvim_size(&self) -> Option<(usize, usize)> {
|
||||
if let Some(line_height) = self.line_height {
|
||||
if let Some(char_width) = self.char_width {
|
||||
let alloc = self.drawing_area.get_allocation();
|
||||
return Some(((alloc.width as f64 / char_width).trunc() as usize,
|
||||
(alloc.height as f64 / line_height).trunc() as usize));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UiState {
|
||||
@@ -202,6 +257,20 @@ impl UiState {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ShellOptions {
|
||||
nvim_bin_path: Option<String>,
|
||||
open_path: Option<String>,
|
||||
}
|
||||
|
||||
impl ShellOptions {
|
||||
pub fn new(nvim_bin_path: Option<String>, open_path: Option<String>) -> Self {
|
||||
ShellOptions {
|
||||
nvim_bin_path,
|
||||
open_path,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Shell {
|
||||
pub state: Arc<UiMutex<State>>,
|
||||
ui_state: Rc<RefCell<UiState>>,
|
||||
@@ -210,9 +279,9 @@ pub struct Shell {
|
||||
}
|
||||
|
||||
impl Shell {
|
||||
pub fn new(settings: Rc<RefCell<Settings>>, parent: &Arc<UiMutex<ui::Components>>) -> Shell {
|
||||
pub fn new(settings: Rc<RefCell<Settings>>, options: ShellOptions) -> Shell {
|
||||
let shell = Shell {
|
||||
state: Arc::new(UiMutex::new(State::new(settings, parent))),
|
||||
state: Arc::new(UiMutex::new(State::new(settings, options))),
|
||||
ui_state: Rc::new(RefCell::new(UiState::new())),
|
||||
|
||||
widget: gtk::Box::new(gtk::Orientation::Vertical, 0),
|
||||
@@ -224,17 +293,29 @@ impl Shell {
|
||||
shell
|
||||
}
|
||||
|
||||
pub fn is_nvim_initialized(&self) -> bool {
|
||||
let state = self.state.borrow();
|
||||
let nvim = state.nvim.borrow();
|
||||
nvim.is_initialized()
|
||||
}
|
||||
|
||||
pub fn init(&mut self) {
|
||||
let state = self.state.borrow_mut();
|
||||
state.drawing_area.set_size_request(500, 300);
|
||||
let mut state = self.state.borrow_mut();
|
||||
state.drawing_area.set_hexpand(true);
|
||||
state.drawing_area.set_vexpand(true);
|
||||
state.drawing_area.set_can_focus(true);
|
||||
|
||||
state.im_context.set_use_preedit(false);
|
||||
|
||||
self.widget.pack_start(&*state.tabs, false, true, 0);
|
||||
self.widget.pack_start(&state.drawing_area, true, true, 0);
|
||||
let nvim_box = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||
|
||||
nvim_box.pack_start(&*state.tabs, false, true, 0);
|
||||
nvim_box.pack_start(&state.drawing_area, true, true, 0);
|
||||
|
||||
state.stack.add_named(&nvim_box, "Nvim");
|
||||
state.stack.add_named(&*state.error_area, "Error");
|
||||
|
||||
self.widget.pack_start(&state.stack, true, true, 0);
|
||||
|
||||
state
|
||||
.drawing_area
|
||||
@@ -274,12 +355,7 @@ impl Shell {
|
||||
let ref_state = self.state.clone();
|
||||
state
|
||||
.drawing_area
|
||||
.connect_draw(move |_, ctx| {
|
||||
let mut state = ref_state.borrow_mut();
|
||||
let ref_parent = sync::Weak::upgrade(&state.parent).unwrap();
|
||||
let parent = ref_parent.borrow();
|
||||
gtk_draw(&*parent, &mut *state, ctx)
|
||||
});
|
||||
.connect_draw(move |_, ctx| gtk_draw(&ref_state, ctx));
|
||||
|
||||
let ref_state = self.state.clone();
|
||||
state
|
||||
@@ -294,11 +370,11 @@ impl Shell {
|
||||
ev.get_keyval() < gdk_sys::GDK_KEY_KP_Space as u32 &&
|
||||
ev.get_keyval() > gdk_sys::GDK_KEY_KP_Divide as u32 &&
|
||||
shell.im_context.filter_keypress(ev) {
|
||||
println!("Filter");
|
||||
println!("Filter");
|
||||
Inhibit(true)
|
||||
} else {
|
||||
if let Some(ref nvim) = shell.nvim {
|
||||
input::gtk_key_press(&mut *nvim.borrow_mut(), ev)
|
||||
if shell.nvim.borrow().is_initialized() {
|
||||
input::gtk_key_press(&mut shell.nvim.borrow_mut(), ev)
|
||||
} else {
|
||||
Inhibit(false)
|
||||
}
|
||||
@@ -308,9 +384,9 @@ impl Shell {
|
||||
state
|
||||
.drawing_area
|
||||
.connect_key_release_event(move |_, ev| {
|
||||
ref_state.borrow().im_context.filter_keypress(ev);
|
||||
Inhibit(false)
|
||||
});
|
||||
ref_state.borrow().im_context.filter_keypress(ev);
|
||||
Inhibit(false)
|
||||
});
|
||||
|
||||
let ref_state = self.state.clone();
|
||||
state
|
||||
@@ -342,6 +418,12 @@ impl Shell {
|
||||
.im_context
|
||||
.connect_commit(move |_, ch| ref_state.borrow().im_commit(ch));
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
@@ -354,24 +436,6 @@ impl Shell {
|
||||
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(Rc::new(RefCell::new(nvim)));
|
||||
}
|
||||
|
||||
pub fn grab_focus(&self) {
|
||||
self.state.borrow().drawing_area.grab_focus();
|
||||
}
|
||||
@@ -391,7 +455,7 @@ impl Shell {
|
||||
|
||||
pub fn edit_paste(&self) {
|
||||
let state = self.state.borrow();
|
||||
let paste_command = if state.mode == NvimMode::Normal {
|
||||
let paste_command = if state.mode.is(&mode::NvimMode::Normal) {
|
||||
"\"*p"
|
||||
} else {
|
||||
"<Esc>\"*pa"
|
||||
@@ -406,6 +470,13 @@ impl Shell {
|
||||
let mut nvim = &mut *state.nvim();
|
||||
nvim.command(":wa").report_err(nvim);
|
||||
}
|
||||
|
||||
pub fn set_detach_cb<F>(&self, cb: Option<F>)
|
||||
where F: FnMut() + Send + 'static
|
||||
{
|
||||
let mut state = self.state.borrow_mut();
|
||||
state.set_detach_cb(cb);
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Shell {
|
||||
@@ -498,33 +569,74 @@ fn gtk_motion_notify(shell: &mut State, ui_state: &mut UiState, ev: &EventMotion
|
||||
Inhibit(false)
|
||||
}
|
||||
|
||||
fn gtk_draw(parent: &ui::Components, state: &mut State, ctx: &cairo::Context) -> Inhibit {
|
||||
#[inline]
|
||||
fn update_line_metrics(state_arc: &Arc<UiMutex<State>>, ctx: &cairo::Context) {
|
||||
let mut state = state_arc.borrow_mut();
|
||||
|
||||
if state.line_height.is_none() {
|
||||
let (width, height) = calc_char_bounds(state, ctx);
|
||||
state.line_height = Some(height as f64);
|
||||
state.char_width = Some(width as f64);
|
||||
state.calc_line_metrics(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
fn gtk_draw(state_arc: &Arc<UiMutex<State>>, ctx: &cairo::Context) -> Inhibit {
|
||||
update_line_metrics(state_arc, ctx);
|
||||
init_nvim(state_arc);
|
||||
|
||||
let mut state = state_arc.borrow_mut();
|
||||
// in case nvim not initialized
|
||||
if !state.nvim.borrow().is_error() {
|
||||
draw(&*state, ctx);
|
||||
request_window_resize(&mut *state);
|
||||
}
|
||||
|
||||
//TODO: to much call of this function
|
||||
let (row, col) = state.model.get_cursor();
|
||||
let (x, y, width, height) = ModelRect::point(col, row)
|
||||
.to_area(state.line_height.unwrap(), state.char_width.unwrap());
|
||||
state
|
||||
.im_context
|
||||
.set_cursor_location(&gdk::Rectangle {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
|
||||
draw(state, ctx);
|
||||
request_window_resize(parent, state);
|
||||
|
||||
|
||||
Inhibit(false)
|
||||
}
|
||||
|
||||
|
||||
fn init_nvim(state_arc: &Arc<UiMutex<State>>) {
|
||||
let state = state_arc.borrow();
|
||||
|
||||
let mut nvim_client = state.nvim.borrow_mut();
|
||||
if !nvim_client.is_initialized() && !nvim_client.is_error() {
|
||||
let (cols, rows) = state.calc_nvim_size().unwrap();
|
||||
let mut nvim = match nvim::initialize(state_arc.clone(),
|
||||
state.options.nvim_bin_path.as_ref(),
|
||||
cols as u64,
|
||||
rows as u64) {
|
||||
Ok(nvim) => nvim,
|
||||
Err(err) => {
|
||||
nvim_client.set_error();
|
||||
state
|
||||
.error_area
|
||||
.show_nvim_start_error(&err.source(), err.cmd());
|
||||
|
||||
let stack = state.stack.clone();
|
||||
gtk::idle_add(move || {
|
||||
stack.set_visible_child_name("Error");
|
||||
Continue(false)
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(ref path) = state.options.open_path {
|
||||
nvim.command(&format!("e {}", path)).report_err(&mut nvim);
|
||||
}
|
||||
|
||||
let guard = nvim.session.take_dispatch_guard();
|
||||
|
||||
let state_ref = state_arc.clone();
|
||||
thread::spawn(move || {
|
||||
guard.join().expect("Can't join dispatch thread");
|
||||
|
||||
idle_cb_call!(state_ref.detach_cb());
|
||||
});
|
||||
|
||||
nvim_client.set_nvim(nvim);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_model_clip(state: &State,
|
||||
line_height: f64,
|
||||
@@ -711,17 +823,7 @@ fn update_font_description(desc: &mut FontDescription, attrs: &Attrs) {
|
||||
}
|
||||
}
|
||||
|
||||
fn calc_char_bounds(shell: &State, ctx: &cairo::Context) -> (i32, i32) {
|
||||
let layout = ctx.create_pango_layout();
|
||||
|
||||
let desc = shell.create_pango_font();
|
||||
layout.set_font_description(Some(&desc));
|
||||
layout.set_text("A", -1);
|
||||
|
||||
layout.get_pixel_size()
|
||||
}
|
||||
|
||||
fn request_window_resize(parent: &ui::Components, state: &mut State) {
|
||||
fn request_window_resize(state: &mut State) {
|
||||
if !state.request_resize {
|
||||
return;
|
||||
}
|
||||
@@ -737,7 +839,12 @@ fn request_window_resize(parent: &ui::Components, state: &mut State) {
|
||||
let request_width = (state.model.columns as f64 * state.char_width.unwrap()) as i32;
|
||||
|
||||
if width != request_width || height != request_height {
|
||||
let window = parent.window();
|
||||
let window: gtk::Window = state
|
||||
.drawing_area
|
||||
.get_toplevel()
|
||||
.unwrap()
|
||||
.downcast()
|
||||
.unwrap();
|
||||
let (win_width, win_height) = window.get_size();
|
||||
let h_border = win_width - width;
|
||||
let v_border = win_height - height;
|
||||
@@ -752,33 +859,31 @@ fn split_color(indexed_color: u64) -> Color {
|
||||
Color(r / 255.0, g / 255.0, b / 255.0)
|
||||
}
|
||||
|
||||
fn gtk_configure_event(state: &Arc<UiMutex<State>>, ev: &EventConfigure) -> bool {
|
||||
let (width, height) = ev.get_size();
|
||||
|
||||
fn gtk_configure_event(state: &Arc<UiMutex<State>>, _: &EventConfigure) -> bool {
|
||||
let mut state_ref = state.borrow_mut();
|
||||
|
||||
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();
|
||||
if !state_ref.nvim.borrow().is_initialized() {
|
||||
return false;
|
||||
}
|
||||
|
||||
state_ref.resize_timer = None;
|
||||
if let Some((columns, rows)) = state_ref.calc_nvim_size() {
|
||||
let state = state.clone();
|
||||
state_ref.resize_timer = Some(glib::timeout_add(250, move || {
|
||||
let mut state_ref = state.borrow_mut();
|
||||
|
||||
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.resize_timer = None;
|
||||
|
||||
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);
|
||||
}
|
||||
Continue(false)
|
||||
}));
|
||||
}
|
||||
}
|
||||
Continue(false)
|
||||
}));
|
||||
}
|
||||
false
|
||||
}
|
||||
@@ -889,13 +994,8 @@ impl RedrawEvents for State {
|
||||
RepaintMode::Nothing
|
||||
}
|
||||
|
||||
fn on_mode_change(&mut self, mode: &str) -> RepaintMode {
|
||||
match mode {
|
||||
"normal" => self.mode = NvimMode::Normal,
|
||||
"insert" => self.mode = NvimMode::Insert,
|
||||
_ => self.mode = NvimMode::Other,
|
||||
}
|
||||
|
||||
fn on_mode_change(&mut self, mode: &str, idx: u64) -> RepaintMode {
|
||||
self.mode.update(mode, idx as usize);
|
||||
RepaintMode::Area(self.model.cur_point())
|
||||
}
|
||||
|
||||
@@ -947,13 +1047,20 @@ impl RedrawEvents for State {
|
||||
|
||||
fn tabline_update(&mut self,
|
||||
selected: Tabpage,
|
||||
tabs: Vec<(Tabpage, Option<&str>)>)
|
||||
tabs: Vec<(Tabpage, Option<String>)>)
|
||||
-> RepaintMode {
|
||||
self.tabs
|
||||
.update_tabs(&self.nvim.as_ref().unwrap(), &selected, &tabs);
|
||||
self.tabs.update_tabs(&self.nvim, &selected, &tabs);
|
||||
|
||||
RepaintMode::Nothing
|
||||
}
|
||||
|
||||
fn mode_info_set(&mut self,
|
||||
cursor_style_enabled: bool,
|
||||
mode_info: Vec<nvim::ModeInfo>)
|
||||
-> RepaintMode {
|
||||
self.mode.set_info(cursor_style_enabled, mode_info);
|
||||
RepaintMode::Nothing
|
||||
}
|
||||
}
|
||||
|
||||
impl GuiApi for State {
|
||||
|
||||
@@ -7,15 +7,16 @@ use gtk::prelude::*;
|
||||
|
||||
use glib::signal;
|
||||
|
||||
use neovim_lib::{Neovim, NeovimApi};
|
||||
use neovim_lib::NeovimApi;
|
||||
use neovim_lib::neovim_api::Tabpage;
|
||||
|
||||
use nvim;
|
||||
use nvim::ErrorReport;
|
||||
|
||||
struct State {
|
||||
data: Vec<Tabpage>,
|
||||
selected: Option<Tabpage>,
|
||||
nvim: Option<Rc<RefCell<Neovim>>>,
|
||||
nvim: Option<Rc<RefCell<nvim::NeovimClient>>>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
@@ -31,7 +32,7 @@ impl State {
|
||||
let target = &self.data[idx as usize];
|
||||
if Some(target) != self.selected.as_ref() {
|
||||
let mut nvim = self.nvim.as_ref().unwrap().borrow_mut();
|
||||
nvim.set_current_tabpage(&target).report_err(&mut *nvim);
|
||||
nvim.set_current_tabpage(&target).report_err(&mut **nvim);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,9 +67,9 @@ impl Tabline {
|
||||
}
|
||||
|
||||
fn update_state(&self,
|
||||
nvim: &Rc<RefCell<Neovim>>,
|
||||
nvim: &Rc<RefCell<nvim::NeovimClient>>,
|
||||
selected: &Tabpage,
|
||||
tabs: &Vec<(Tabpage, Option<&str>)>) {
|
||||
tabs: &Vec<(Tabpage, Option<String>)>) {
|
||||
let mut state = self.state.borrow_mut();
|
||||
|
||||
if state.nvim.is_none() {
|
||||
@@ -81,9 +82,9 @@ impl Tabline {
|
||||
}
|
||||
|
||||
pub fn update_tabs(&self,
|
||||
nvim: &Rc<RefCell<Neovim>>,
|
||||
nvim: &Rc<RefCell<nvim::NeovimClient>>,
|
||||
selected: &Tabpage,
|
||||
tabs: &Vec<(Tabpage, Option<&str>)>) {
|
||||
tabs: &Vec<(Tabpage, Option<String>)>) {
|
||||
if tabs.len() <= 1 {
|
||||
self.tabs.hide();
|
||||
return;
|
||||
@@ -112,7 +113,7 @@ impl Tabline {
|
||||
for (idx, tab) in tabs.iter().enumerate() {
|
||||
let tab_child = self.tabs.get_nth_page(Some(idx as u32));
|
||||
self.tabs
|
||||
.set_tab_label_text(&tab_child.unwrap(), &tab.1.unwrap_or("??"));
|
||||
.set_tab_label_text(&tab_child.unwrap(), &tab.1.as_ref().unwrap_or(&"??".to_owned()));
|
||||
|
||||
if *selected == tab.0 {
|
||||
self.tabs.set_current_page(Some(idx as u32));
|
||||
|
||||
54
src/ui.rs
54
src/ui.rs
@@ -10,10 +10,9 @@ use gtk::ApplicationExt;
|
||||
use gtk::{ApplicationWindow, HeaderBar, ToolButton, Image, AboutDialog};
|
||||
use gio::prelude::*;
|
||||
use gio::{Menu, MenuExt, MenuItem, MenuItemExt, SimpleAction};
|
||||
use glib;
|
||||
|
||||
use settings::Settings;
|
||||
use shell::Shell;
|
||||
use shell::{Shell, ShellOptions};
|
||||
use shell_dlg;
|
||||
use project::Projects;
|
||||
|
||||
@@ -53,10 +52,10 @@ impl Components {
|
||||
}
|
||||
|
||||
impl Ui {
|
||||
pub fn new() -> Ui {
|
||||
pub fn new(options: ShellOptions) -> Ui {
|
||||
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(), &comps)));
|
||||
let shell = Rc::new(RefCell::new(Shell::new(settings.clone(), options)));
|
||||
settings.borrow_mut().set_shell(Rc::downgrade(&shell));
|
||||
|
||||
let projects = Projects::new(&comps.borrow().open_btn, shell.clone());
|
||||
@@ -70,10 +69,7 @@ impl Ui {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(&mut self,
|
||||
app: >k::Application,
|
||||
nvim_bin_path: Option<&String>,
|
||||
open_path: Option<&str>) {
|
||||
pub fn init(&mut self, app: >k::Application) {
|
||||
if self.initialized {
|
||||
return;
|
||||
}
|
||||
@@ -91,7 +87,9 @@ impl Ui {
|
||||
|
||||
let projects = self.projects.clone();
|
||||
comps.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);
|
||||
@@ -114,40 +112,28 @@ impl Ui {
|
||||
let window = comps.window.as_ref().unwrap();
|
||||
|
||||
window.set_titlebar(Some(&comps.header_bar));
|
||||
window.set_default_size(800, 600);
|
||||
|
||||
let mut shell = self.shell.borrow_mut();
|
||||
let shell = self.shell.borrow();
|
||||
window.add(&**shell);
|
||||
|
||||
window.show_all();
|
||||
window.set_title("Neovim-gtk");
|
||||
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);
|
||||
shell.grab_focus();
|
||||
|
||||
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 state = shell.state.borrow();
|
||||
let guard = state.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)
|
||||
});
|
||||
});
|
||||
let comps_ref = self.comps.clone();
|
||||
shell.set_detach_cb(Some(move || {
|
||||
let comps_ref = comps_ref.clone();
|
||||
gtk::idle_add(move || {
|
||||
comps_ref.borrow().close_window();
|
||||
Continue(false)
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
fn create_main_menu(&self, app: >k::Application) {
|
||||
@@ -181,6 +167,10 @@ fn on_help_about(comps: &Components) {
|
||||
}
|
||||
|
||||
fn gtk_delete(comps: &UiMutex<Components>, shell: &RefCell<Shell>) -> Inhibit {
|
||||
if !shell.borrow().is_nvim_initialized() {
|
||||
return Inhibit(false);
|
||||
}
|
||||
|
||||
Inhibit(if shell_dlg::can_close_window(comps, shell) {
|
||||
let comps = comps.borrow();
|
||||
comps.close_window();
|
||||
|
||||
20
src/value.rs
Normal file
20
src/value.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
use std::collections::HashMap;
|
||||
use neovim_lib::Value;
|
||||
|
||||
pub trait ValueMapExt {
|
||||
fn to_attrs_map(&self) -> Result<HashMap<&str, Value>, String>;
|
||||
}
|
||||
|
||||
impl ValueMapExt for Vec<(Value, Value)> {
|
||||
fn to_attrs_map(&self) -> Result<HashMap<&str, Value>, String> {
|
||||
self.iter()
|
||||
.map(|p| {
|
||||
p.0
|
||||
.as_str()
|
||||
.ok_or("Can't convert map key to string".to_owned())
|
||||
.map(|key| (key, p.1.clone()))
|
||||
})
|
||||
.collect::<Result<HashMap<&str, Value>, String>>()
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user