mirror of https://github.com/sgoudham/neovide.git
Settings derive macro (#439)
* Added procedural macro crate * Initial derive macro implementation * Compiles for cursor settings * Derive macro working correctly * Derive macro working for all settings structs * Cleanup * Moved the binary back to the project root * remove unused utils file Co-authored-by: Tim Harding <Tim@TimHarding.co>macos-click-through
parent
33f6a4b914
commit
af93c54f3e
@ -1,8 +1,8 @@
|
|||||||
fn main() {
|
fn main() {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
let mut res = winres::WindowsResource::new();
|
let mut res = winres::WindowsResource::new();
|
||||||
res.set_icon("assets/nvim.ico");
|
res.set_icon("assets/nvim.ico");
|
||||||
res.compile().expect("Could not attach exe icon");
|
res.compile().expect("Could not attach exe icon");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "neovide-derive"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
syn = "1.0"
|
||||||
|
quote = "1.0"
|
@ -0,0 +1,77 @@
|
|||||||
|
use proc_macro::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
use syn::{parse_macro_input, Attribute, Data, DataStruct, DeriveInput, Error, Ident, Lit, Meta};
|
||||||
|
|
||||||
|
#[proc_macro_derive(SettingGroup, attributes(setting_prefix))]
|
||||||
|
pub fn setting_group(item: TokenStream) -> TokenStream {
|
||||||
|
let input = parse_macro_input!(item as DeriveInput);
|
||||||
|
let prefix = setting_prefix(input.attrs.as_ref())
|
||||||
|
.map(|p| format!("{}_", p))
|
||||||
|
.unwrap_or("".to_string());
|
||||||
|
stream(input, prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stream(input: DeriveInput, prefix: String) -> TokenStream {
|
||||||
|
const ERR_MSG: &'static str = "Derive macro expects a struct";
|
||||||
|
match input.data {
|
||||||
|
Data::Struct(ref data) => struct_stream(input.ident, prefix, data),
|
||||||
|
Data::Enum(data) => Error::new_spanned(data.enum_token, ERR_MSG)
|
||||||
|
.to_compile_error()
|
||||||
|
.into(),
|
||||||
|
Data::Union(data) => Error::new_spanned(data.union_token, ERR_MSG)
|
||||||
|
.to_compile_error()
|
||||||
|
.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn struct_stream(name: Ident, prefix: String, data: &DataStruct) -> TokenStream {
|
||||||
|
let fragments = data.fields.iter().map(|field| match field.ident {
|
||||||
|
Some(ref ident) => {
|
||||||
|
let vim_setting_name = format!("{}{}", prefix, ident);
|
||||||
|
quote! {{
|
||||||
|
fn update_func(value: rmpv::Value) {
|
||||||
|
let mut s = crate::settings::SETTINGS.get::<#name>();
|
||||||
|
s.#ident.from_value(value);
|
||||||
|
crate::settings::SETTINGS.set(&s);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reader_func() -> rmpv::Value {
|
||||||
|
let s = crate::settings::SETTINGS.get::<#name>();
|
||||||
|
s.#ident.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
crate::settings::SETTINGS.set_setting_handlers(
|
||||||
|
#vim_setting_name,
|
||||||
|
update_func,
|
||||||
|
reader_func
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
Error::new_spanned(field.colon_token, "Expected named struct fields").to_compile_error()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let expanded = quote! {
|
||||||
|
impl #name {
|
||||||
|
pub fn register() {
|
||||||
|
let s: Self = Default::default();
|
||||||
|
crate::settings::SETTINGS.set(&s);
|
||||||
|
#(#fragments)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
TokenStream::from(expanded)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setting_prefix(attrs: &[Attribute]) -> Option<String> {
|
||||||
|
for attr in attrs.iter() {
|
||||||
|
if let Ok(Meta::NameValue(name_value)) = attr.parse_meta() {
|
||||||
|
if name_value.path.is_ident("setting_prefix") {
|
||||||
|
if let Lit::Str(literal) = name_value.lit {
|
||||||
|
return Some(literal.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
@ -1,172 +1,173 @@
|
|||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod settings;
|
extern crate neovide_derive;
|
||||||
|
|
||||||
mod bridge;
|
mod bridge;
|
||||||
mod editor;
|
mod editor;
|
||||||
mod error_handling;
|
mod error_handling;
|
||||||
mod redraw_scheduler;
|
mod redraw_scheduler;
|
||||||
mod renderer;
|
mod renderer;
|
||||||
mod window;
|
mod settings;
|
||||||
|
mod window;
|
||||||
#[macro_use]
|
|
||||||
extern crate derive_new;
|
#[macro_use]
|
||||||
#[macro_use]
|
extern crate derive_new;
|
||||||
extern crate rust_embed;
|
#[macro_use]
|
||||||
#[macro_use]
|
extern crate rust_embed;
|
||||||
extern crate lazy_static;
|
#[macro_use]
|
||||||
|
extern crate lazy_static;
|
||||||
use std::process;
|
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::{
|
||||||
use std::sync::mpsc::channel;
|
process,
|
||||||
use std::sync::Arc;
|
sync::{atomic::AtomicBool, mpsc::channel, Arc},
|
||||||
|
};
|
||||||
use crossfire::mpsc::unbounded_future;
|
|
||||||
|
use crossfire::mpsc::unbounded_future;
|
||||||
use window::window_geometry;
|
|
||||||
|
use bridge::start_bridge;
|
||||||
use bridge::start_bridge;
|
use editor::start_editor;
|
||||||
use editor::start_editor;
|
use renderer::{cursor_renderer::CursorSettings, RendererSettings};
|
||||||
use window::create_window;
|
use window::{create_window, window_geometry, KeyboardSettings, WindowSettings};
|
||||||
|
|
||||||
pub const INITIAL_DIMENSIONS: (u64, u64) = (100, 50);
|
pub const INITIAL_DIMENSIONS: (u64, u64) = (100, 50);
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// -----------
|
// -----------
|
||||||
// | DATA FLOW |
|
// | DATA FLOW |
|
||||||
// -----------
|
// -----------
|
||||||
//
|
//
|
||||||
// Data flows in a circular motion via channels. This allows each component to handle and
|
// Data flows in a circular motion via channels. This allows each component to handle and
|
||||||
// process data on their own thread while not blocking the other components from processing.
|
// process data on their own thread while not blocking the other components from processing.
|
||||||
//
|
//
|
||||||
// This way Neovim continues to produce events, the window doesn't freeze and queues up ui
|
// This way Neovim continues to produce events, the window doesn't freeze and queues up ui
|
||||||
// commands, and the editor can do the processing necessary to handle the UI events
|
// commands, and the editor can do the processing necessary to handle the UI events
|
||||||
// effectively.
|
// effectively.
|
||||||
//
|
//
|
||||||
// BRIDGE
|
// BRIDGE
|
||||||
// V REDRAW EVENT
|
// V REDRAW EVENT
|
||||||
// EDITOR
|
// EDITOR
|
||||||
// V DRAW COMMAND
|
// V DRAW COMMAND
|
||||||
// WINDOW
|
// WINDOW
|
||||||
// V UI COMMAND
|
// V UI COMMAND
|
||||||
// BRIDGE
|
// BRIDGE
|
||||||
//
|
//
|
||||||
// BRIDGE:
|
// BRIDGE:
|
||||||
// The bridge is responsible for the connection to the neovim process itself. It is in charge
|
// The bridge is responsible for the connection to the neovim process itself. It is in charge
|
||||||
// of starting and communicating to and from the process.
|
// of starting and communicating to and from the process.
|
||||||
//
|
//
|
||||||
// REDRAW EVENT:
|
// REDRAW EVENT:
|
||||||
// Redraw events are direct events from the neovim process meant to specify how the editor
|
// Redraw events are direct events from the neovim process meant to specify how the editor
|
||||||
// should be drawn to the screen. They also include other things such as whether the mouse is
|
// should be drawn to the screen. They also include other things such as whether the mouse is
|
||||||
// enabled. The bridge takes these events, filters out some of them meant only for
|
// enabled. The bridge takes these events, filters out some of them meant only for
|
||||||
// filtering, and forwards them to the editor.
|
// filtering, and forwards them to the editor.
|
||||||
//
|
//
|
||||||
// EDITOR:
|
// EDITOR:
|
||||||
// The editor is responsible for processing and transforming redraw events into something
|
// The editor is responsible for processing and transforming redraw events into something
|
||||||
// more readily renderable. Ligature support and multi window management requires some
|
// more readily renderable. Ligature support and multi window management requires some
|
||||||
// significant preprocessing of the redraw events in order to capture what exactly should get
|
// significant preprocessing of the redraw events in order to capture what exactly should get
|
||||||
// drawn where. Futher this step takes a bit of processing power to accomplish, so it is done
|
// drawn where. Futher this step takes a bit of processing power to accomplish, so it is done
|
||||||
// on it's own thread. Ideally heavily computationally expensive tasks should be done in the
|
// on it's own thread. Ideally heavily computationally expensive tasks should be done in the
|
||||||
// editor.
|
// editor.
|
||||||
//
|
//
|
||||||
// DRAW COMMAND:
|
// DRAW COMMAND:
|
||||||
// The draw commands are distilled render information describing actions to be done at the
|
// The draw commands are distilled render information describing actions to be done at the
|
||||||
// window by window level.
|
// window by window level.
|
||||||
//
|
//
|
||||||
// WINDOW:
|
// WINDOW:
|
||||||
// The window is responsible for rendering and gathering input events from the user. This
|
// The window is responsible for rendering and gathering input events from the user. This
|
||||||
// inncludes taking the draw commands from the editor and turning them into pixels on the
|
// inncludes taking the draw commands from the editor and turning them into pixels on the
|
||||||
// screen. The ui commands are then forwarded back to the BRIDGE to convert them into
|
// screen. The ui commands are then forwarded back to the BRIDGE to convert them into
|
||||||
// commands for neovim to handle properly.
|
// commands for neovim to handle properly.
|
||||||
//
|
//
|
||||||
// UI COMMAND:
|
// UI COMMAND:
|
||||||
// The ui commands are things like text input/key bindings, outer window resizes, and mouse
|
// The ui commands are things like text input/key bindings, outer window resizes, and mouse
|
||||||
// inputs.
|
// inputs.
|
||||||
//
|
//
|
||||||
// ------------------
|
// ------------------
|
||||||
// | Other Components |
|
// | Other Components |
|
||||||
// ------------------
|
// ------------------
|
||||||
//
|
//
|
||||||
// Neovide also includes some other systems which are globally available via lazy static
|
// Neovide also includes some other systems which are globally available via lazy static
|
||||||
// instantiations.
|
// instantiations.
|
||||||
//
|
//
|
||||||
// SETTINGS:
|
// SETTINGS:
|
||||||
// The settings system is live updated from global variables in neovim with the prefix
|
// The settings system is live updated from global variables in neovim with the prefix
|
||||||
// "neovide". They allow us to configure and manage the functionality of neovide from neovim
|
// "neovide". They allow us to configure and manage the functionality of neovide from neovim
|
||||||
// init scripts and variables.
|
// init scripts and variables.
|
||||||
//
|
//
|
||||||
// REDRAW SCHEDULER:
|
// REDRAW SCHEDULER:
|
||||||
// The redraw scheduler is a simple system in charge of deciding if the renderer should draw
|
// The redraw scheduler is a simple system in charge of deciding if the renderer should draw
|
||||||
// another frame next frame, or if it can safely skip drawing to save battery and cpu power.
|
// another frame next frame, or if it can safely skip drawing to save battery and cpu power.
|
||||||
// Multiple other parts of the app "queue_next_frame" function to ensure animations continue
|
// Multiple other parts of the app "queue_next_frame" function to ensure animations continue
|
||||||
// properly or updates to the graphics are pushed to the screen.
|
// properly or updates to the graphics are pushed to the screen.
|
||||||
|
|
||||||
if let Err(err) = window_geometry() {
|
if let Err(err) = window_geometry() {
|
||||||
eprintln!("{}", err);
|
eprintln!("{}", err);
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
// incase of app bundle, we can just pass --disowned option straight away to bypass this check
|
// incase of app bundle, we can just pass --disowned option straight away to bypass this check
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
if !std::env::args().any(|f| f == "--disowned") {
|
if !std::env::args().any(|f| f == "--disowned") {
|
||||||
if let Ok(curr_exe) = std::env::current_exe() {
|
if let Ok(curr_exe) = std::env::current_exe() {
|
||||||
assert!(std::process::Command::new(curr_exe)
|
assert!(std::process::Command::new(curr_exe)
|
||||||
.args(std::env::args().skip(1))
|
.args(std::env::args().skip(1))
|
||||||
.arg("--disowned")
|
.arg("--disowned")
|
||||||
.spawn()
|
.spawn()
|
||||||
.is_ok());
|
.is_ok());
|
||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
} else {
|
} else {
|
||||||
eprintln!("error in disowning process, cannot obtain the path for the current executable, continuing without disowning...");
|
eprintln!("error in disowning process, cannot obtain the path for the current executable, continuing without disowning...");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
if env::var_os("TERM").is_none() {
|
if env::var_os("TERM").is_none() {
|
||||||
let mut profile_path = dirs::home_dir().unwrap();
|
let mut profile_path = dirs::home_dir().unwrap();
|
||||||
profile_path.push(".profile");
|
profile_path.push(".profile");
|
||||||
let shell = env::var("SHELL").unwrap();
|
let shell = env::var("SHELL").unwrap();
|
||||||
let cmd = format!(
|
let cmd = format!(
|
||||||
"(source /etc/profile && source {} && echo $PATH)",
|
"(source /etc/profile && source {} && echo $PATH)",
|
||||||
profile_path.to_str().unwrap()
|
profile_path.to_str().unwrap()
|
||||||
);
|
);
|
||||||
if let Ok(path) = process::Command::new(shell).arg("-c").arg(cmd).output() {
|
if let Ok(path) = process::Command::new(shell).arg("-c").arg(cmd).output() {
|
||||||
env::set_var("PATH", std::str::from_utf8(&path.stdout).unwrap());
|
env::set_var("PATH", std::str::from_utf8(&path.stdout).unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window::initialize_settings();
|
KeyboardSettings::register();
|
||||||
redraw_scheduler::initialize_settings();
|
WindowSettings::register();
|
||||||
renderer::initialize_settings();
|
redraw_scheduler::RedrawSettings::register();
|
||||||
renderer::cursor_renderer::initialize_settings();
|
RendererSettings::register();
|
||||||
|
CursorSettings::register();
|
||||||
let running = Arc::new(AtomicBool::new(true));
|
|
||||||
|
let running = Arc::new(AtomicBool::new(true));
|
||||||
let (redraw_event_sender, redraw_event_receiver) = unbounded_future();
|
|
||||||
let (batched_draw_command_sender, batched_draw_command_receiver) = channel();
|
let (redraw_event_sender, redraw_event_receiver) = unbounded_future();
|
||||||
let (ui_command_sender, ui_command_receiver) = unbounded_future();
|
let (batched_draw_command_sender, batched_draw_command_receiver) = channel();
|
||||||
let (window_command_sender, window_command_receiver) = channel();
|
let (ui_command_sender, ui_command_receiver) = unbounded_future();
|
||||||
|
let (window_command_sender, window_command_receiver) = channel();
|
||||||
// We need to keep the bridge reference around to prevent the tokio runtime from getting freed
|
|
||||||
let _bridge = start_bridge(
|
// We need to keep the bridge reference around to prevent the tokio runtime from getting freed
|
||||||
ui_command_sender.clone(),
|
let _bridge = start_bridge(
|
||||||
ui_command_receiver,
|
ui_command_sender.clone(),
|
||||||
redraw_event_sender,
|
ui_command_receiver,
|
||||||
running.clone(),
|
redraw_event_sender,
|
||||||
);
|
running.clone(),
|
||||||
start_editor(
|
);
|
||||||
redraw_event_receiver,
|
start_editor(
|
||||||
batched_draw_command_sender,
|
redraw_event_receiver,
|
||||||
window_command_sender,
|
batched_draw_command_sender,
|
||||||
);
|
window_command_sender,
|
||||||
create_window(
|
);
|
||||||
batched_draw_command_receiver,
|
create_window(
|
||||||
window_command_receiver,
|
batched_draw_command_receiver,
|
||||||
ui_command_sender,
|
window_command_receiver,
|
||||||
running,
|
ui_command_sender,
|
||||||
);
|
running,
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
use super::KeyboardLayout;
|
use super::KeyboardLayout;
|
||||||
use crate::{
|
use crate::settings::FromValue;
|
||||||
register_nvim_setting,
|
|
||||||
settings::{FromValue, Value, SETTINGS},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[setting_prefix = "keyboard"]
|
||||||
|
#[derive(Clone, SettingGroup)]
|
||||||
pub struct KeyboardSettings {
|
pub struct KeyboardSettings {
|
||||||
pub layout: KeyboardLayout,
|
pub layout: KeyboardLayout,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn initialize_settings() {
|
impl Default for KeyboardSettings {
|
||||||
SETTINGS.set(&KeyboardSettings {
|
fn default() -> Self {
|
||||||
layout: KeyboardLayout::Qwerty,
|
Self {
|
||||||
});
|
layout: KeyboardLayout::Qwerty,
|
||||||
register_nvim_setting!("keyboard_layout", KeyboardSettings::layout);
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue