Merge pull request #63 from christopher-l/feature/headerbar-title
Show filename and dir in header bar
This commit is contained in:
commit
14bd3d3515
@ -48,6 +48,7 @@ mod popup_menu;
|
||||
mod project;
|
||||
mod tabline;
|
||||
mod error;
|
||||
mod subscriptions;
|
||||
|
||||
use std::env;
|
||||
use std::time::Duration;
|
||||
|
@ -83,6 +83,12 @@ impl NvimHandler {
|
||||
error!("Unsupported event {:?}", params);
|
||||
}
|
||||
}
|
||||
"subscription" => {
|
||||
self.safe_call(move |ui| {
|
||||
let ui = &ui.borrow();
|
||||
ui.notify(params)
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
error!("Notification {}({:?})", method, params);
|
||||
}
|
||||
@ -130,7 +136,7 @@ impl NvimHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn safe_call<F>(&self, cb: F)
|
||||
where
|
||||
F: FnOnce(&Arc<UiMutex<shell::State>>) -> result::Result<(), String> + 'static + Send,
|
||||
|
@ -59,7 +59,7 @@ pub struct Projects {
|
||||
}
|
||||
|
||||
impl Projects {
|
||||
pub fn new(ref_widget: >k::ToolButton, shell: Rc<RefCell<Shell>>) -> Rc<RefCell<Projects>> {
|
||||
pub fn new(ref_widget: >k::Button, shell: Rc<RefCell<Shell>>) -> Rc<RefCell<Projects>> {
|
||||
let projects = Projects {
|
||||
shell,
|
||||
popup: Popover::new(Some(ref_widget)),
|
||||
|
39
src/shell.rs
39
src/shell.rs
@ -33,6 +33,7 @@ use error;
|
||||
use mode;
|
||||
use render;
|
||||
use render::CellMetrics;
|
||||
use subscriptions::{SubscriptionHandle, Subscriptions};
|
||||
|
||||
const DEFAULT_FONT_NAME: &str = "DejaVu Sans Mono 12";
|
||||
pub const MINIMUM_SUPPORTED_NVIM_VERSION: &str = "0.2.2";
|
||||
@ -78,6 +79,8 @@ pub struct State {
|
||||
|
||||
detach_cb: Option<Box<RefCell<FnMut() + Send + 'static>>>,
|
||||
nvim_started_cb: Option<Box<RefCell<FnMut() + Send + 'static>>>,
|
||||
|
||||
subscriptions: RefCell<Subscriptions>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
@ -116,6 +119,8 @@ impl State {
|
||||
|
||||
detach_cb: None,
|
||||
nvim_started_cb: None,
|
||||
|
||||
subscriptions: RefCell::new(Subscriptions::new()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -328,6 +333,31 @@ impl State {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subscribe<F>(&self, event_name: &str, args: &[&str], cb: F) -> SubscriptionHandle
|
||||
where
|
||||
F: Fn(Vec<String>) + 'static,
|
||||
{
|
||||
self.subscriptions
|
||||
.borrow_mut()
|
||||
.subscribe(event_name, args, cb)
|
||||
}
|
||||
|
||||
pub fn set_autocmds(&self) {
|
||||
self.subscriptions
|
||||
.borrow()
|
||||
.set_autocmds(&mut self.nvim().unwrap());
|
||||
}
|
||||
|
||||
pub fn notify(&self, params: Vec<Value>) -> Result<(), String> {
|
||||
self.subscriptions.borrow().notify(params)
|
||||
}
|
||||
|
||||
pub fn run_now(&self, handle: &SubscriptionHandle) {
|
||||
self.subscriptions
|
||||
.borrow()
|
||||
.run_now(handle, &mut self.nvim().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UiState {
|
||||
@ -574,6 +604,15 @@ impl Shell {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_tab(&self) {
|
||||
let state = self.state.borrow();
|
||||
|
||||
let nvim = state.nvim();
|
||||
if let Some(mut nvim) = nvim {
|
||||
nvim.command(":tabe").report_err();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_detach_cb<F>(&self, cb: Option<F>)
|
||||
where
|
||||
F: FnMut() + Send + 'static,
|
||||
|
157
src/subscriptions.rs
Normal file
157
src/subscriptions.rs
Normal file
@ -0,0 +1,157 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use neovim_lib::{NeovimApi, Value};
|
||||
|
||||
use nvim::NeovimRef;
|
||||
|
||||
/// A subscription to a Neovim autocmd event.
|
||||
struct Subscription {
|
||||
/// A callback to be executed each time the event triggers.
|
||||
cb: Box<Fn(Vec<String>) + 'static>,
|
||||
/// A list of expressions which will be evaluated when the event triggers. The result is passed
|
||||
/// to the callback.
|
||||
args: Vec<String>,
|
||||
}
|
||||
|
||||
/// A map of all registered subscriptions.
|
||||
pub struct Subscriptions(HashMap<String, Vec<Subscription>>);
|
||||
|
||||
/// A handle to identify a `Subscription` within the `Subscriptions` map.
|
||||
///
|
||||
/// Can be used to trigger the subscription manually even when the event was not triggered.
|
||||
///
|
||||
/// Could be used in the future to suspend individual subscriptions.
|
||||
#[derive(Debug)]
|
||||
pub struct SubscriptionHandle {
|
||||
event_name: String,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl Subscriptions {
|
||||
pub fn new() -> Self {
|
||||
Subscriptions(HashMap::new())
|
||||
}
|
||||
|
||||
/// Subscribe to a Neovim autocmd event.
|
||||
///
|
||||
/// Subscriptions are not active immediately but only after `set_autocmds` is called. At the
|
||||
/// moment, all calls to `subscribe` must be made before calling `set_autocmds`.
|
||||
///
|
||||
/// This function is wrapped by `shell::State`.
|
||||
///
|
||||
/// # Arguments:
|
||||
///
|
||||
/// - `event_name`: The event to register.
|
||||
/// See `:help autocmd-events` for a list of supported event names. Event names can be
|
||||
/// comma-separated.
|
||||
///
|
||||
/// - `args`: A list of expressions to be evaluated when the event triggers.
|
||||
/// Expressions are evaluated using Vimscript. The results are passed to the callback as a
|
||||
/// list of Strings.
|
||||
/// This is especially useful as `Neovim::eval` is synchronous and might block if called from
|
||||
/// the callback function; so always use the `args` mechanism instead.
|
||||
///
|
||||
/// - `cb`: The callback function.
|
||||
/// This will be called each time the event triggers or when `run_now` is called.
|
||||
/// It is passed a vector with the results of the evaluated expressions given with `args`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Call a function each time a buffer is entered or the current working directory is changed.
|
||||
/// Pass the current buffer name and directory to the callback.
|
||||
/// ```
|
||||
/// let my_subscription = shell.state.borrow()
|
||||
/// .subscribe("BufEnter,DirChanged", &["expand(@%)", "getcwd()"], move |args| {
|
||||
/// let filename = &args[0];
|
||||
/// let dir = &args[1];
|
||||
/// // do stuff
|
||||
/// });
|
||||
/// ```
|
||||
pub fn subscribe<F>(&mut self, event_name: &str, args: &[&str], cb: F) -> SubscriptionHandle
|
||||
where
|
||||
F: Fn(Vec<String>) + 'static,
|
||||
{
|
||||
let entry = self.0.entry(event_name.to_owned()).or_insert(Vec::new());
|
||||
let index = entry.len();
|
||||
entry.push(Subscription {
|
||||
cb: Box::new(cb),
|
||||
args: args.into_iter().map(|&s| s.to_owned()).collect(),
|
||||
});
|
||||
SubscriptionHandle {
|
||||
event_name: event_name.to_owned(),
|
||||
index,
|
||||
}
|
||||
}
|
||||
|
||||
/// Register all subscriptions with Neovim.
|
||||
///
|
||||
/// This function is wrapped by `shell::State`.
|
||||
pub fn set_autocmds(&self, nvim: &mut NeovimRef) {
|
||||
for (event_name, subscriptions) in &self.0 {
|
||||
for (i, subscription) in subscriptions.iter().enumerate() {
|
||||
let args = subscription
|
||||
.args
|
||||
.iter()
|
||||
.fold("".to_owned(), |acc, arg| acc + ", " + &arg);
|
||||
nvim.command(&format!(
|
||||
"au {} * call rpcnotify(1, 'subscription', '{}', {} {})",
|
||||
event_name, event_name, i, args,
|
||||
)).unwrap_or_else(|err| {
|
||||
error!("Could not set autocmd: {}", err);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Trigger given event.
|
||||
fn on_notify(&self, event_name: &str, index: usize, args: Vec<String>) {
|
||||
if let Some(subscription) = self.0.get(event_name).and_then(|v| v.get(index)) {
|
||||
(*subscription.cb)(args);
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper around `on_notify` for easy calling with a `neovim_lib::Handler` implementation.
|
||||
///
|
||||
/// This function is wrapped by `shell::State`.
|
||||
pub fn notify(&self, params: Vec<Value>) -> Result<(), String> {
|
||||
let mut params_iter = params.into_iter();
|
||||
let ev_name = params_iter.next();
|
||||
let ev_name = ev_name
|
||||
.as_ref()
|
||||
.and_then(Value::as_str)
|
||||
.ok_or("Error reading event name")?;
|
||||
let index = params_iter
|
||||
.next()
|
||||
.and_then(|i| i.as_u64())
|
||||
.ok_or("Error reading index")? as usize;
|
||||
let args = params_iter
|
||||
.map(|arg| arg.as_str().map(|s| s.to_owned()))
|
||||
.collect::<Option<Vec<String>>>()
|
||||
.ok_or("Error reading args")?;
|
||||
self.on_notify(ev_name, index, args);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Manually trigger the given subscription.
|
||||
///
|
||||
/// The `nvim` instance is needed to evaluate the `args` expressions.
|
||||
///
|
||||
/// This function is wrapped by `shell::State`.
|
||||
pub fn run_now(&self, handle: &SubscriptionHandle, nvim: &mut NeovimRef) {
|
||||
let subscription = &self.0.get(&handle.event_name).unwrap()[handle.index];
|
||||
let args = subscription
|
||||
.args
|
||||
.iter()
|
||||
.map(|arg| nvim.eval(arg))
|
||||
.map(|res| {
|
||||
res.ok()
|
||||
.and_then(|val| val.as_str().map(|s: &str| s.to_owned()))
|
||||
})
|
||||
.collect::<Option<Vec<String>>>();
|
||||
if let Some(args) = args {
|
||||
self.on_notify(&handle.event_name, handle.index, args);
|
||||
} else {
|
||||
error!("Error manually running {:?}", handle);
|
||||
}
|
||||
}
|
||||
}
|
160
src/ui.rs
160
src/ui.rs
@ -1,13 +1,13 @@
|
||||
use std::cell::{Ref, RefCell, RefMut};
|
||||
use std::{env, thread};
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use gdk;
|
||||
use gtk;
|
||||
use gtk_sys;
|
||||
use gtk::prelude::*;
|
||||
use gtk::{AboutDialog, ApplicationWindow, HeaderBar, Image, SettingsExt, ToolButton};
|
||||
use gtk::{AboutDialog, ApplicationWindow, Button, HeaderBar, SettingsExt};
|
||||
use gio::prelude::*;
|
||||
use gio::{Menu, MenuExt, MenuItem, SimpleAction};
|
||||
use toml;
|
||||
@ -17,6 +17,7 @@ use shell::{self, Shell, ShellOptions};
|
||||
use shell_dlg;
|
||||
use project::Projects;
|
||||
use plug_manager;
|
||||
use subscriptions::SubscriptionHandle;
|
||||
|
||||
macro_rules! clone {
|
||||
(@param _) => ( _ );
|
||||
@ -50,16 +51,24 @@ pub struct Ui {
|
||||
pub struct Components {
|
||||
window: Option<ApplicationWindow>,
|
||||
window_state: WindowState,
|
||||
open_btn: ToolButton,
|
||||
open_btn: Button,
|
||||
}
|
||||
|
||||
impl Components {
|
||||
fn new() -> Components {
|
||||
let save_image =
|
||||
Image::new_from_icon_name("document-open", gtk_sys::GTK_ICON_SIZE_SMALL_TOOLBAR as i32);
|
||||
|
||||
let open_btn = Button::new();
|
||||
let open_btn_box = gtk::Box::new(gtk::Orientation::Horizontal, 3);
|
||||
open_btn_box.pack_start(>k::Label::new("Open"), false, false, 3);
|
||||
open_btn_box.pack_start(
|
||||
>k::Image::new_from_icon_name("pan-down-symbolic", gtk::IconSize::Menu.into()),
|
||||
false,
|
||||
false,
|
||||
3,
|
||||
);
|
||||
open_btn.add(&open_btn_box);
|
||||
open_btn.set_can_focus(false);
|
||||
Components {
|
||||
open_btn: ToolButton::new(Some(&save_image), "Open"),
|
||||
open_btn,
|
||||
window: None,
|
||||
window_state: WindowState::load(),
|
||||
}
|
||||
@ -126,48 +135,6 @@ impl Ui {
|
||||
}
|
||||
}
|
||||
|
||||
// Client side decorations including the toolbar are disabled via NVIM_GTK_NO_HEADERBAR=1
|
||||
let use_header_bar = env::var("NVIM_GTK_NO_HEADERBAR")
|
||||
.map(|opt| opt.trim() != "1")
|
||||
.unwrap_or(true);
|
||||
|
||||
if app.prefers_app_menu() || use_header_bar {
|
||||
self.create_main_menu(app, &window);
|
||||
}
|
||||
|
||||
if use_header_bar {
|
||||
let header_bar = HeaderBar::new();
|
||||
|
||||
let projects = self.projects.clone();
|
||||
header_bar.pack_start(&comps.open_btn);
|
||||
comps
|
||||
.open_btn
|
||||
.connect_clicked(move |_| projects.borrow_mut().show());
|
||||
|
||||
let save_image = Image::new_from_icon_name(
|
||||
"document-save",
|
||||
gtk_sys::GTK_ICON_SIZE_SMALL_TOOLBAR as i32,
|
||||
);
|
||||
let save_btn = ToolButton::new(Some(&save_image), "Save");
|
||||
|
||||
let shell = self.shell.clone();
|
||||
save_btn.connect_clicked(move |_| shell.borrow_mut().edit_save_all());
|
||||
header_bar.pack_start(&save_btn);
|
||||
|
||||
let paste_image = Image::new_from_icon_name(
|
||||
"edit-paste",
|
||||
gtk_sys::GTK_ICON_SIZE_SMALL_TOOLBAR as i32,
|
||||
);
|
||||
let paste_btn = ToolButton::new(Some(&paste_image), "Paste");
|
||||
let shell = self.shell.clone();
|
||||
paste_btn.connect_clicked(move |_| shell.borrow_mut().edit_paste());
|
||||
header_bar.pack_start(&paste_btn);
|
||||
|
||||
header_bar.set_show_close_button(true);
|
||||
|
||||
window.set_titlebar(Some(&header_bar));
|
||||
}
|
||||
|
||||
if restore_win_state {
|
||||
if comps.window_state.is_maximized {
|
||||
window.maximize();
|
||||
@ -182,6 +149,21 @@ impl Ui {
|
||||
}
|
||||
}
|
||||
|
||||
// Client side decorations including the toolbar are disabled via NVIM_GTK_NO_HEADERBAR=1
|
||||
let use_header_bar = env::var("NVIM_GTK_NO_HEADERBAR")
|
||||
.map(|opt| opt.trim() != "1")
|
||||
.unwrap_or(true);
|
||||
|
||||
if app.prefers_app_menu() || use_header_bar {
|
||||
self.create_main_menu(app, &window);
|
||||
}
|
||||
|
||||
let update_subtitle = if use_header_bar {
|
||||
Some(self.create_header_bar())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let comps_ref = self.comps.clone();
|
||||
window.connect_size_allocate(move |window, _| {
|
||||
gtk_window_size_allocate(window, &mut *comps_ref.borrow_mut())
|
||||
@ -202,7 +184,30 @@ impl Ui {
|
||||
window.add(&**shell);
|
||||
|
||||
window.show_all();
|
||||
window.set_title("NeovimGtk");
|
||||
|
||||
let comps_ref = self.comps.clone();
|
||||
let update_title = shell.state.borrow().subscribe(
|
||||
"BufEnter,DirChanged",
|
||||
&["expand('%:p')", "getcwd()"],
|
||||
move |args| {
|
||||
let comps = comps_ref.borrow();
|
||||
let window = comps.window.as_ref().unwrap();
|
||||
let file_path = &args[0];
|
||||
let dir = Path::new(&args[1]);
|
||||
let filename = if file_path.is_empty() {
|
||||
"[No Name]"
|
||||
} else if let Some(rel_path) = Path::new(&file_path)
|
||||
.strip_prefix(&dir)
|
||||
.ok()
|
||||
.and_then(|p| p.to_str())
|
||||
{
|
||||
rel_path
|
||||
} else {
|
||||
&file_path
|
||||
};
|
||||
window.set_title(filename);
|
||||
},
|
||||
);
|
||||
|
||||
let comps_ref = self.comps.clone();
|
||||
let shell_ref = self.shell.clone();
|
||||
@ -222,12 +227,67 @@ impl Ui {
|
||||
let state_ref = self.shell.borrow().state.clone();
|
||||
let plug_manager_ref = self.plug_manager.clone();
|
||||
shell.set_nvim_started_cb(Some(move || {
|
||||
let state = state_ref.borrow();
|
||||
plug_manager_ref
|
||||
.borrow_mut()
|
||||
.init_nvim_client(state_ref.borrow().nvim_clone());
|
||||
state.set_autocmds();
|
||||
state.run_now(&update_title);
|
||||
if let Some(ref update_subtitle) = update_subtitle {
|
||||
state.run_now(&update_subtitle);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
fn create_header_bar(&self) -> SubscriptionHandle {
|
||||
let header_bar = HeaderBar::new();
|
||||
let comps = self.comps.borrow();
|
||||
let window = comps.window.as_ref().unwrap();
|
||||
|
||||
let projects = self.projects.clone();
|
||||
header_bar.pack_start(&comps.open_btn);
|
||||
comps
|
||||
.open_btn
|
||||
.connect_clicked(move |_| projects.borrow_mut().show());
|
||||
|
||||
let new_tab_btn =
|
||||
Button::new_from_icon_name("tab-new-symbolic", gtk::IconSize::SmallToolbar.into());
|
||||
let shell_ref = Rc::clone(&self.shell);
|
||||
new_tab_btn.connect_clicked(move |_| shell_ref.borrow_mut().new_tab());
|
||||
new_tab_btn.set_can_focus(false);
|
||||
new_tab_btn.set_tooltip_text("Open a new tab");
|
||||
header_bar.pack_start(&new_tab_btn);
|
||||
|
||||
let paste_btn =
|
||||
Button::new_from_icon_name("edit-paste-symbolic", gtk::IconSize::SmallToolbar.into());
|
||||
let shell = self.shell.clone();
|
||||
paste_btn.connect_clicked(move |_| shell.borrow_mut().edit_paste());
|
||||
paste_btn.set_can_focus(false);
|
||||
paste_btn.set_tooltip_text("Paste from clipboard");
|
||||
header_bar.pack_end(&paste_btn);
|
||||
|
||||
let save_btn = Button::new_with_label("Save All");
|
||||
let shell = self.shell.clone();
|
||||
save_btn.connect_clicked(move |_| shell.borrow_mut().edit_save_all());
|
||||
save_btn.set_can_focus(false);
|
||||
header_bar.pack_end(&save_btn);
|
||||
|
||||
header_bar.set_show_close_button(true);
|
||||
|
||||
window.set_titlebar(Some(&header_bar));
|
||||
|
||||
let shell = self.shell.borrow();
|
||||
let update_subtitle = shell.state.borrow().subscribe(
|
||||
"DirChanged",
|
||||
&["getcwd()"],
|
||||
move |args| {
|
||||
header_bar.set_subtitle(&*args[0]);
|
||||
},
|
||||
);
|
||||
|
||||
update_subtitle
|
||||
}
|
||||
|
||||
fn create_main_menu(&self, app: >k::Application, window: >k::ApplicationWindow) {
|
||||
let plug_manager = self.plug_manager.clone();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user