Skip to content

Commit a9eede7

Browse files
committed
feat(nvim): build and append log to nvim buffer
1 parent d8d4ef3 commit a9eede7

File tree

7 files changed

+182
-59
lines changed

7 files changed

+182
-59
lines changed

Cargo.lock

Lines changed: 1 addition & 25 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ edition = "2021"
66
[features]
77
default = [ ]
88
xcodegen = [ "async", "dirs" ]
9-
daemon = [ "serial", "compilation", "logging", "async", "watcher", "proc", "xcodegen", "parity-tokio-ipc", "nvim-rs" ]
9+
daemon = [ "serial", "compilation", "logging", "async", "watcher", "proc", "xcodegen", "parity-tokio-ipc", "nvim-rs", "async-stream", "tokio-stream" ]
1010
server = [ "serial", "logging", "dirs", "bsp-server", "url", "wax", "shell-words", "compilation" ]
1111
lua = [ "mlua" ]
1212
serial = [ "serde", "serde_json", "serde_yaml" ]
@@ -70,7 +70,5 @@ url = { version = "2.2.2", features = ["serde"], optional = t
7070
nvim-rs = { version = "0.4.0", optional = true, features = ["use_tokio"] }
7171
parity-tokio-ipc = { version = "0.9.0", optional = true }
7272
strum = { version = "0.24.0", features = ["derive"], optional = true }
73-
libc = "0.2.124"
74-
polling = "2.2.0"
75-
tokio-stream = {version = "0.1.8", features = ["io-util"]}
76-
thiserror = "1.0.30"
73+
tokio-stream = { version = "0.1.8", features = ["io-util"], optional = true }
74+
async-stream = { version = "0.3.3", optional = true }

lua/xcodebase/init.lua

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,23 @@ local address = vim.env.NVIM_LISTEN_ADDRESS
1010
---@field register fun(pid: number, root: string):boolean
1111
M.daemon = {}
1212

13-
M.daemon.project_info = function(root)
13+
M.project_info = function(root)
1414
require("xcodebase.state").projects[root] = nil
1515
lib.daemon.project_info(pid, root)
1616
while require("xcodebase.state").projects[root] == nil do
1717
end
1818
end
1919

20-
M.daemon.drop = function()
20+
M.drop = function()
2121
local root = vim.loop.cwd()
2222
lib.daemon.drop(pid, root)
2323
end
2424

25+
M.build = function(target, configuration, scheme)
26+
local root = vim.loop.cwd()
27+
lib.daemon.build(pid, root, target, configuration, scheme)
28+
end
29+
2530
---@class XcodeBaseCommand
2631
local command = lib.command
2732

@@ -51,7 +56,7 @@ M.try_register = function(opts)
5156
if M.should_register(root, opts) then
5257
local _ = lib.daemon.ensure()
5358
lib.daemon.register(pid, root, address)
54-
vim.cmd [[ autocmd VimLeavePre * lua require'xcodebase'.daemon.drop()]]
59+
vim.cmd [[ autocmd VimLeavePre * lua require'xcodebase'.drop()]]
5560
else
5661
return
5762
end

src/daemon.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ impl Daemon {
8282
table.set("ensure", lua.create_function(Self::ensure)?)?;
8383
table.set("register", lua.create_function(Register::lua)?)?;
8484
table.set("drop", lua.create_function(Drop::lua)?)?;
85+
table.set("build", lua.create_function(Build::lua)?)?;
8586
table.set("project_info", lua.create_function(ProjectInfo::lua)?)?;
8687
Ok(table)
8788
}

src/daemon/nvim.rs

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,35 @@
11
#![allow(dead_code)]
22
use std::{ops, path::Path};
3+
use tokio_stream::StreamExt;
34

45
use anyhow::Result;
5-
use nvim_rs::{compat::tokio::Compat, create, error::LoopError, rpc::handler::Dummy, Neovim};
6+
use nvim_rs::{
7+
compat::tokio::Compat, create, error::LoopError, rpc::handler::Dummy, Buffer, Neovim,
8+
};
69
use parity_tokio_ipc::Connection;
710
use tokio::{io::WriteHalf, task::JoinHandle};
811

9-
pub struct Nvim(
10-
Neovim<Compat<WriteHalf<Connection>>>,
11-
JoinHandle<Result<(), Box<LoopError>>>,
12-
);
12+
pub enum WindowType {
13+
Float,
14+
Vertical,
15+
Horizontal,
16+
}
1317

14-
impl std::fmt::Debug for Nvim {
15-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16-
f.debug_tuple("Nvim").finish()
17-
}
18+
pub struct Nvim {
19+
pub nvim: Neovim<Compat<WriteHalf<Connection>>>,
20+
handler: JoinHandle<Result<(), Box<LoopError>>>,
21+
pub log_bufnr: i64,
1822
}
1923

2024
impl Nvim {
2125
pub async fn new<P: AsRef<Path> + Clone>(address: P) -> Result<Self> {
2226
let (neovim, handler) = create::tokio::new_path(address, Dummy::new()).await?;
23-
Ok(Self(neovim, handler))
27+
let buffer = neovim.create_buf(false, true).await?;
28+
Ok(Self {
29+
nvim: neovim,
30+
handler,
31+
log_bufnr: buffer.get_number().await?,
32+
})
2433
}
2534

2635
async fn log(&self, level: &str, scope: &str, value: impl ToString) -> Result<()> {
@@ -50,12 +59,60 @@ impl Nvim {
5059
pub async fn log_warn(&self, scope: &str, msg: impl ToString) -> Result<()> {
5160
self.log("warn", scope, msg).await
5261
}
62+
63+
pub async fn log_to_buffer(
64+
&self,
65+
title: &str,
66+
direction: WindowType,
67+
mut stream: impl tokio_stream::Stream<Item = String> + Unpin,
68+
clear: bool,
69+
) -> Result<()> {
70+
let title = format!("[ {title} ]: ----> ");
71+
let buffer = Buffer::new(self.log_bufnr.into(), self.nvim.clone());
72+
73+
if clear {
74+
buffer.set_lines(0, -1, false, vec![]).await?;
75+
}
76+
77+
let mut c = match buffer.line_count().await? {
78+
1 => 0,
79+
count => count,
80+
};
81+
82+
// TODO(nvim): build log control what direction to open buffer
83+
// TODO(nvim): build log correct width
84+
// TODO(nvim): build log auto scroll
85+
let command = match direction {
86+
// TOOD: build log float
87+
WindowType::Float => format!("sbuffer {}", self.log_bufnr),
88+
WindowType::Vertical => format!("vert sbuffer {}", self.log_bufnr),
89+
WindowType::Horizontal => format!("sbuffer {}", self.log_bufnr),
90+
};
91+
92+
self.exec(&command, false).await?;
93+
94+
buffer.set_lines(c, c + 1, false, vec![title]).await?;
95+
c += 1;
96+
97+
while let Some(line) = stream.next().await {
98+
buffer.set_lines(c, c + 1, false, vec![line]).await?;
99+
c += 1
100+
}
101+
102+
Ok(())
103+
}
104+
}
105+
106+
impl std::fmt::Debug for Nvim {
107+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108+
f.debug_tuple("Nvim").finish()
109+
}
53110
}
54111

55112
impl ops::Deref for Nvim {
56113
type Target = Neovim<Compat<WriteHalf<Connection>>>;
57114
fn deref(&self) -> &Self::Target {
58-
&self.0
115+
&self.nvim
59116
}
60117
}
61118

src/daemon/requests/build.rs

Lines changed: 88 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
11
#[cfg(feature = "mlua")]
22
use crate::daemon::Daemon;
33

4+
#[cfg(feature = "daemon")]
5+
use crate::daemon::nvim::WindowType;
6+
7+
#[cfg(feature = "daemon")]
8+
use async_stream::stream;
9+
10+
#[cfg(feature = "daemon")]
11+
use tokio_stream::StreamExt;
12+
13+
#[cfg(feature = "daemon")]
14+
use tap::Pipe;
15+
416
#[cfg(feature = "daemon")]
517
use crate::daemon::{DaemonRequestHandler, DaemonState};
618

@@ -10,6 +22,8 @@ use anyhow::Result;
1022
/// Build a project.
1123
#[derive(Debug)]
1224
pub struct Build {
25+
pub pid: i32,
26+
pub root: String,
1327
pub target: Option<String>,
1428
pub configuration: Option<String>,
1529
pub scheme: Option<String>,
@@ -27,29 +41,89 @@ impl Build {
2741
#[cfg(feature = "daemon")]
2842
#[async_trait::async_trait]
2943
impl DaemonRequestHandler<Build> for Build {
30-
fn parse(_args: Vec<&str>) -> Result<Self> {
31-
Ok(Self {
32-
target: None,
33-
configuration: None,
34-
scheme: None,
35-
})
44+
fn parse(args: Vec<&str>) -> Result<Self> {
45+
if let (Some(pid), Some(root)) = (args.get(0), args.get(1)) {
46+
Ok(Self {
47+
pid: pid.parse::<i32>()?,
48+
root: root.to_string(),
49+
target: args.get(2).map(ToString::to_string),
50+
configuration: args.get(3).map(ToString::to_string),
51+
scheme: args.get(4).map(ToString::to_string),
52+
})
53+
} else {
54+
anyhow::bail!("Missing arugments: {:?}", args)
55+
}
3656
}
3757

38-
async fn handle(&self, _state: DaemonState) -> Result<()> {
39-
tracing::info!("build command");
58+
async fn handle(&self, state: DaemonState) -> Result<()> {
59+
tracing::debug!("Handling build request..");
60+
let state = state.lock().await;
61+
let ws = match state.workspaces.get(&self.root) {
62+
Some(ws) => ws,
63+
None => anyhow::bail!("No workspace for {}", self.root),
64+
};
65+
66+
ws.project
67+
.xcodebuild(&["build"])
68+
.await?
69+
.pipe(|mut logs| {
70+
stream! {
71+
while let Some(step) = logs.next().await {
72+
let line = match step {
73+
xcodebuild::parser::Step::Exit(_) => { continue; }
74+
step => step.to_string().trim().to_string(),
75+
};
76+
77+
if !line.is_empty() {
78+
for line in line.split("\n") {
79+
yield line.to_string();
80+
}
81+
}
82+
}
83+
}
84+
})
85+
.pipe(Box::pin)
86+
.pipe(|stream| async {
87+
let nvim = match ws.clients.get(&self.pid) {
88+
Some(nvim) => nvim,
89+
None => anyhow::bail!("No nvim client found to build project."),
90+
};
91+
nvim.log_to_buffer("Build", WindowType::Vertical, stream, true)
92+
.await
93+
})
94+
.await?;
95+
4096
Ok(())
4197
}
4298
}
4399

44100
#[cfg(feature = "lua")]
45101
impl Build {
46-
pub fn lua(lua: &mlua::Lua, (t, c, s): (String, String, String)) -> mlua::Result<()> {
102+
pub fn lua(
103+
lua: &mlua::Lua,
104+
(pid, root, t, c, s): (i32, String, Option<String>, Option<String>, Option<String>),
105+
) -> mlua::Result<()> {
47106
use crate::util::mlua::LuaExtension;
48-
lua.trace(format!("Build (target: {t} configuration: {c}, scheme: {s})").as_ref())?;
49-
Self::request(&t, &c, &s).map_err(mlua::Error::external)
50-
}
107+
lua.trace(
108+
format!(
109+
"Build (target: {:?} configuration: {:?}, scheme: {:?})",
110+
t, c, s
111+
)
112+
.as_ref(),
113+
)?;
114+
115+
let mut args = vec!["build".into(), pid.to_string(), root];
116+
117+
if let Some(target) = t {
118+
args.push(target)
119+
}
120+
if let Some(configuration) = c {
121+
args.push(configuration)
122+
}
123+
if let Some(scheme) = s {
124+
args.push(scheme)
125+
}
51126

52-
pub fn request(target: &str, configuration: &str, scheme: &str) -> mlua::Result<()> {
53-
Daemon::execute(&["build", target, configuration, scheme])
127+
Daemon::execute(&args.join(" ").split(" ").collect::<Vec<&str>>())
54128
}
55129
}

src/daemon/state/project.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,19 @@ impl Project {
8686
```
8787
*/
8888

89-
xcodebuild::runner::spawn(&self.root, &["build"]).await
89+
self.xcodebuild(&["build"]).await
90+
}
91+
92+
#[cfg(feature = "daemon")]
93+
pub async fn xcodebuild<'a, I: 'a, S: 'a>(
94+
&'a self,
95+
args: I,
96+
) -> anyhow::Result<impl tokio_stream::Stream<Item = xcodebuild::parser::Step> + 'a>
97+
where
98+
I: IntoIterator<Item = S>,
99+
S: AsRef<std::ffi::OsStr>,
100+
{
101+
xcodebuild::runner::spawn(&self.root, args).await
90102
}
91103

92104
pub fn config(&self) -> &LocalConfig {

0 commit comments

Comments
 (0)