Skip to content

Commit

Permalink
feat(nvim): build and append log to nvim buffer
Browse files Browse the repository at this point in the history
  • Loading branch information
kkharji committed May 6, 2022
1 parent d8d4ef3 commit a9eede7
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 59 deletions.
26 changes: 1 addition & 25 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 3 additions & 5 deletions Cargo.toml
Expand Up @@ -6,7 +6,7 @@ edition = "2021"
[features]
default = [ ]
xcodegen = [ "async", "dirs" ]
daemon = [ "serial", "compilation", "logging", "async", "watcher", "proc", "xcodegen", "parity-tokio-ipc", "nvim-rs" ]
daemon = [ "serial", "compilation", "logging", "async", "watcher", "proc", "xcodegen", "parity-tokio-ipc", "nvim-rs", "async-stream", "tokio-stream" ]
server = [ "serial", "logging", "dirs", "bsp-server", "url", "wax", "shell-words", "compilation" ]
lua = [ "mlua" ]
serial = [ "serde", "serde_json", "serde_yaml" ]
Expand Down Expand Up @@ -70,7 +70,5 @@ url = { version = "2.2.2", features = ["serde"], optional = t
nvim-rs = { version = "0.4.0", optional = true, features = ["use_tokio"] }
parity-tokio-ipc = { version = "0.9.0", optional = true }
strum = { version = "0.24.0", features = ["derive"], optional = true }
libc = "0.2.124"
polling = "2.2.0"
tokio-stream = {version = "0.1.8", features = ["io-util"]}
thiserror = "1.0.30"
tokio-stream = { version = "0.1.8", features = ["io-util"], optional = true }
async-stream = { version = "0.3.3", optional = true }
11 changes: 8 additions & 3 deletions lua/xcodebase/init.lua
Expand Up @@ -10,18 +10,23 @@ local address = vim.env.NVIM_LISTEN_ADDRESS
---@field register fun(pid: number, root: string):boolean
M.daemon = {}

M.daemon.project_info = function(root)
M.project_info = function(root)
require("xcodebase.state").projects[root] = nil
lib.daemon.project_info(pid, root)
while require("xcodebase.state").projects[root] == nil do
end
end

M.daemon.drop = function()
M.drop = function()
local root = vim.loop.cwd()
lib.daemon.drop(pid, root)
end

M.build = function(target, configuration, scheme)
local root = vim.loop.cwd()
lib.daemon.build(pid, root, target, configuration, scheme)
end

---@class XcodeBaseCommand
local command = lib.command

Expand Down Expand Up @@ -51,7 +56,7 @@ M.try_register = function(opts)
if M.should_register(root, opts) then
local _ = lib.daemon.ensure()
lib.daemon.register(pid, root, address)
vim.cmd [[ autocmd VimLeavePre * lua require'xcodebase'.daemon.drop()]]
vim.cmd [[ autocmd VimLeavePre * lua require'xcodebase'.drop()]]
else
return
end
Expand Down
1 change: 1 addition & 0 deletions src/daemon.rs
Expand Up @@ -82,6 +82,7 @@ impl Daemon {
table.set("ensure", lua.create_function(Self::ensure)?)?;
table.set("register", lua.create_function(Register::lua)?)?;
table.set("drop", lua.create_function(Drop::lua)?)?;
table.set("build", lua.create_function(Build::lua)?)?;
table.set("project_info", lua.create_function(ProjectInfo::lua)?)?;
Ok(table)
}
Expand Down
79 changes: 68 additions & 11 deletions src/daemon/nvim.rs
@@ -1,26 +1,35 @@
#![allow(dead_code)]
use std::{ops, path::Path};
use tokio_stream::StreamExt;

use anyhow::Result;
use nvim_rs::{compat::tokio::Compat, create, error::LoopError, rpc::handler::Dummy, Neovim};
use nvim_rs::{
compat::tokio::Compat, create, error::LoopError, rpc::handler::Dummy, Buffer, Neovim,
};
use parity_tokio_ipc::Connection;
use tokio::{io::WriteHalf, task::JoinHandle};

pub struct Nvim(
Neovim<Compat<WriteHalf<Connection>>>,
JoinHandle<Result<(), Box<LoopError>>>,
);
pub enum WindowType {
Float,
Vertical,
Horizontal,
}

impl std::fmt::Debug for Nvim {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("Nvim").finish()
}
pub struct Nvim {
pub nvim: Neovim<Compat<WriteHalf<Connection>>>,
handler: JoinHandle<Result<(), Box<LoopError>>>,
pub log_bufnr: i64,
}

impl Nvim {
pub async fn new<P: AsRef<Path> + Clone>(address: P) -> Result<Self> {
let (neovim, handler) = create::tokio::new_path(address, Dummy::new()).await?;
Ok(Self(neovim, handler))
let buffer = neovim.create_buf(false, true).await?;
Ok(Self {
nvim: neovim,
handler,
log_bufnr: buffer.get_number().await?,
})
}

async fn log(&self, level: &str, scope: &str, value: impl ToString) -> Result<()> {
Expand Down Expand Up @@ -50,12 +59,60 @@ impl Nvim {
pub async fn log_warn(&self, scope: &str, msg: impl ToString) -> Result<()> {
self.log("warn", scope, msg).await
}

pub async fn log_to_buffer(
&self,
title: &str,
direction: WindowType,
mut stream: impl tokio_stream::Stream<Item = String> + Unpin,
clear: bool,
) -> Result<()> {
let title = format!("[ {title} ]: ----> ");
let buffer = Buffer::new(self.log_bufnr.into(), self.nvim.clone());

if clear {
buffer.set_lines(0, -1, false, vec![]).await?;
}

let mut c = match buffer.line_count().await? {
1 => 0,
count => count,
};

// TODO(nvim): build log control what direction to open buffer
// TODO(nvim): build log correct width
// TODO(nvim): build log auto scroll
let command = match direction {
// TOOD: build log float
WindowType::Float => format!("sbuffer {}", self.log_bufnr),
WindowType::Vertical => format!("vert sbuffer {}", self.log_bufnr),
WindowType::Horizontal => format!("sbuffer {}", self.log_bufnr),
};

self.exec(&command, false).await?;

buffer.set_lines(c, c + 1, false, vec![title]).await?;
c += 1;

while let Some(line) = stream.next().await {
buffer.set_lines(c, c + 1, false, vec![line]).await?;
c += 1
}

Ok(())
}
}

impl std::fmt::Debug for Nvim {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("Nvim").finish()
}
}

impl ops::Deref for Nvim {
type Target = Neovim<Compat<WriteHalf<Connection>>>;
fn deref(&self) -> &Self::Target {
&self.0
&self.nvim
}
}

Expand Down
102 changes: 88 additions & 14 deletions src/daemon/requests/build.rs
@@ -1,6 +1,18 @@
#[cfg(feature = "mlua")]
use crate::daemon::Daemon;

#[cfg(feature = "daemon")]
use crate::daemon::nvim::WindowType;

#[cfg(feature = "daemon")]
use async_stream::stream;

#[cfg(feature = "daemon")]
use tokio_stream::StreamExt;

#[cfg(feature = "daemon")]
use tap::Pipe;

#[cfg(feature = "daemon")]
use crate::daemon::{DaemonRequestHandler, DaemonState};

Expand All @@ -10,6 +22,8 @@ use anyhow::Result;
/// Build a project.
#[derive(Debug)]
pub struct Build {
pub pid: i32,
pub root: String,
pub target: Option<String>,
pub configuration: Option<String>,
pub scheme: Option<String>,
Expand All @@ -27,29 +41,89 @@ impl Build {
#[cfg(feature = "daemon")]
#[async_trait::async_trait]
impl DaemonRequestHandler<Build> for Build {
fn parse(_args: Vec<&str>) -> Result<Self> {
Ok(Self {
target: None,
configuration: None,
scheme: None,
})
fn parse(args: Vec<&str>) -> Result<Self> {
if let (Some(pid), Some(root)) = (args.get(0), args.get(1)) {
Ok(Self {
pid: pid.parse::<i32>()?,
root: root.to_string(),
target: args.get(2).map(ToString::to_string),
configuration: args.get(3).map(ToString::to_string),
scheme: args.get(4).map(ToString::to_string),
})
} else {
anyhow::bail!("Missing arugments: {:?}", args)
}
}

async fn handle(&self, _state: DaemonState) -> Result<()> {
tracing::info!("build command");
async fn handle(&self, state: DaemonState) -> Result<()> {
tracing::debug!("Handling build request..");
let state = state.lock().await;
let ws = match state.workspaces.get(&self.root) {
Some(ws) => ws,
None => anyhow::bail!("No workspace for {}", self.root),
};

ws.project
.xcodebuild(&["build"])
.await?
.pipe(|mut logs| {
stream! {
while let Some(step) = logs.next().await {
let line = match step {
xcodebuild::parser::Step::Exit(_) => { continue; }
step => step.to_string().trim().to_string(),
};

if !line.is_empty() {
for line in line.split("\n") {
yield line.to_string();
}
}
}
}
})
.pipe(Box::pin)
.pipe(|stream| async {
let nvim = match ws.clients.get(&self.pid) {
Some(nvim) => nvim,
None => anyhow::bail!("No nvim client found to build project."),
};
nvim.log_to_buffer("Build", WindowType::Vertical, stream, true)
.await
})
.await?;

Ok(())
}
}

#[cfg(feature = "lua")]
impl Build {
pub fn lua(lua: &mlua::Lua, (t, c, s): (String, String, String)) -> mlua::Result<()> {
pub fn lua(
lua: &mlua::Lua,
(pid, root, t, c, s): (i32, String, Option<String>, Option<String>, Option<String>),
) -> mlua::Result<()> {
use crate::util::mlua::LuaExtension;
lua.trace(format!("Build (target: {t} configuration: {c}, scheme: {s})").as_ref())?;
Self::request(&t, &c, &s).map_err(mlua::Error::external)
}
lua.trace(
format!(
"Build (target: {:?} configuration: {:?}, scheme: {:?})",
t, c, s
)
.as_ref(),
)?;

let mut args = vec!["build".into(), pid.to_string(), root];

if let Some(target) = t {
args.push(target)
}
if let Some(configuration) = c {
args.push(configuration)
}
if let Some(scheme) = s {
args.push(scheme)
}

pub fn request(target: &str, configuration: &str, scheme: &str) -> mlua::Result<()> {
Daemon::execute(&["build", target, configuration, scheme])
Daemon::execute(&args.join(" ").split(" ").collect::<Vec<&str>>())
}
}
14 changes: 13 additions & 1 deletion src/daemon/state/project.rs
Expand Up @@ -86,7 +86,19 @@ impl Project {
```
*/

xcodebuild::runner::spawn(&self.root, &["build"]).await
self.xcodebuild(&["build"]).await
}

#[cfg(feature = "daemon")]
pub async fn xcodebuild<'a, I: 'a, S: 'a>(
&'a self,
args: I,
) -> anyhow::Result<impl tokio_stream::Stream<Item = xcodebuild::parser::Step> + 'a>
where
I: IntoIterator<Item = S>,
S: AsRef<std::ffi::OsStr>,
{
xcodebuild::runner::spawn(&self.root, args).await
}

pub fn config(&self) -> &LocalConfig {
Expand Down

0 comments on commit a9eede7

Please sign in to comment.