feat: Finish initial implementation
This commit is contained in:
parent
402513b11d
commit
1681a34cdb
11 changed files with 360 additions and 53 deletions
7
CHANGELOG.adoc
Normal file
7
CHANGELOG.adoc
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
= Changelog
|
||||||
|
|
||||||
|
== [Unreleased]
|
||||||
|
|
||||||
|
=== Added
|
||||||
|
|
||||||
|
- initial implementation
|
65
Cargo.lock
generated
65
Cargo.lock
generated
|
@ -17,6 +17,15 @@ version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "0.7.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.2.6"
|
version = "0.2.6"
|
||||||
|
@ -357,6 +366,9 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-error",
|
"tracing-error",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
"tree-sitter",
|
||||||
|
"tree-sitter-highlight",
|
||||||
|
"tree-sitter-rust",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -486,6 +498,8 @@ version = "1.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d"
|
checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
"regex-syntax",
|
"regex-syntax",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -621,6 +635,26 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.40"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.40"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.14",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thread_local"
|
name = "thread_local"
|
||||||
version = "1.1.7"
|
version = "1.1.7"
|
||||||
|
@ -737,6 +771,37 @@ dependencies = [
|
||||||
"tracing-log",
|
"tracing-log",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter"
|
||||||
|
version = "0.20.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e747b1f9b7b931ed39a548c1fae149101497de3c1fc8d9e18c62c1a66c683d3d"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter-highlight"
|
||||||
|
version = "0.20.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "042342584c5a7a0b833d9fc4e2bdab3f9868ddc6c4b339a1e01451c6720868bc"
|
||||||
|
dependencies = [
|
||||||
|
"regex",
|
||||||
|
"thiserror",
|
||||||
|
"tree-sitter",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter-rust"
|
||||||
|
version = "0.20.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "797842733e252dc11ae5d403a18060bf337b822fc2ae5ddfaa6ff4d9cc20bda6"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"tree-sitter",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicase"
|
name = "unicase"
|
||||||
version = "2.6.0"
|
version = "2.6.0"
|
||||||
|
|
|
@ -15,3 +15,6 @@ toml = "0.7.3"
|
||||||
tracing = "0.1.37"
|
tracing = "0.1.37"
|
||||||
tracing-error = "0.2.0"
|
tracing-error = "0.2.0"
|
||||||
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
|
||||||
|
tree-sitter = "0.20.10"
|
||||||
|
tree-sitter-highlight = "0.20.1"
|
||||||
|
tree-sitter-rust = "0.20.3"
|
||||||
|
|
54
rc/highlight.kak
Normal file
54
rc/highlight.kak
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
declare-option str kak_highlight_cmd "kak-highlight"
|
||||||
|
set-option global kak_highlight_cmd "kak-highlight -vv"
|
||||||
|
|
||||||
|
declare-option str kak_highlight_log "/tmp/kak-highlight.log"
|
||||||
|
declare-option -hidden range-specs kak_highlight_ranges
|
||||||
|
|
||||||
|
# Option to store draft of the current buffer before passing to shell.
|
||||||
|
declare-option -hidden str kak_highlight_draft
|
||||||
|
|
||||||
|
define-command kak-highlight -docstring %{
|
||||||
|
kak-highlight
|
||||||
|
Request highlighting for the current buffer
|
||||||
|
} %{
|
||||||
|
evaluate-commands -draft -no-hooks %{exec '%'; set buffer kak_highlight_draft %val{selection}}
|
||||||
|
evaluate-commands %sh{
|
||||||
|
kak_highlight_draft=$(printf '%s.' "${kak_opt_kak_highlight_draft}" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | sed "s/$(printf '\t')/\\\\t/g")
|
||||||
|
kak_highlight_draft=${kak_highlight_draft%.}
|
||||||
|
|
||||||
|
printf '
|
||||||
|
timestamp = %d
|
||||||
|
session = "%s"
|
||||||
|
client = "%s"
|
||||||
|
content = """
|
||||||
|
%s"""
|
||||||
|
' "${kak_timestamp}" "${kak_session}" "${kak_client}" "${kak_highlight_draft}" | ${kak_opt_kak_highlight_cmd} request
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
define-command kak-highlight-enable -docstring %{
|
||||||
|
kak-highlight-enable
|
||||||
|
Start a daemon for the current session
|
||||||
|
} %{
|
||||||
|
nop %sh{
|
||||||
|
(eval "${kak_opt_kak_highlight_cmd} --log '${kak_opt_kak_highlight_log}' daemon '${kak_session}'") >/dev/null 2>&1 </dev/null &
|
||||||
|
}
|
||||||
|
|
||||||
|
hook global KakEnd .* %{
|
||||||
|
nop %sh{
|
||||||
|
pkill -f "${kak_opt_kak_highlight_cmd} .* daemon ${kak_session}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hook global WinSetOption filetype=rust %{
|
||||||
|
add-highlighter window/kak_highlight_ranges ranges kak_highlight_ranges
|
||||||
|
|
||||||
|
hook window -group kak-highlight NormalIdle .* kak-highlight
|
||||||
|
hook window -group kak-highlight InsertIdle .* kak-highlight
|
||||||
|
hook window -group kak-highlight BufReload .* kak-highlight
|
||||||
|
|
||||||
|
hook -once -always window WinSetOption filetype=.* %{
|
||||||
|
remove-highlighter window/kak_highlight_ranges
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ use color_eyre::Result;
|
||||||
|
|
||||||
use crate::Request;
|
use crate::Request;
|
||||||
|
|
||||||
pub fn handle(runtime_dir: PathBuf, session: String) -> Result<()> {
|
pub fn handle(runtime_dir: PathBuf) -> Result<()> {
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
stdin()
|
stdin()
|
||||||
.read_to_string(&mut buf)
|
.read_to_string(&mut buf)
|
||||||
|
@ -17,7 +17,7 @@ pub fn handle(runtime_dir: PathBuf, session: String) -> Result<()> {
|
||||||
|
|
||||||
tracing::trace!("Received request: {:?}", req);
|
tracing::trace!("Received request: {:?}", req);
|
||||||
|
|
||||||
let path = runtime_dir.join(session).with_extension("s");
|
let path = runtime_dir.join(req.session).with_extension("s");
|
||||||
tracing::debug!(path = %path.display());
|
tracing::debug!(path = %path.display());
|
||||||
|
|
||||||
let mut socket = UnixStream::connect(&path)
|
let mut socket = UnixStream::connect(&path)
|
||||||
|
@ -27,7 +27,7 @@ pub fn handle(runtime_dir: PathBuf, session: String) -> Result<()> {
|
||||||
.write_all(buf.as_bytes())
|
.write_all(buf.as_bytes())
|
||||||
.wrap_err("Failed to send request")?;
|
.wrap_err("Failed to send request")?;
|
||||||
|
|
||||||
tracing::info!("Sent request to {}", path.display());
|
tracing::debug!("Sent request to {}", path.display());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::{env, fs};
|
use std::{env, fs};
|
||||||
|
@ -6,10 +7,13 @@ use color_eyre::eyre::Context;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// The number of worker threads to spawn
|
/// The number of worker threads to spawn
|
||||||
|
#[serde(default = "Config::default_workers")]
|
||||||
pub workers: u8,
|
pub workers: u8,
|
||||||
|
/// A collection mapping highlighter tokens to Kakoune faces
|
||||||
|
pub tokens: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
|
@ -35,11 +39,18 @@ impl Config {
|
||||||
let buf = String::from_utf8_lossy(&buf);
|
let buf = String::from_utf8_lossy(&buf);
|
||||||
toml::from_str(&buf).wrap_err("Failed to deserialize config")
|
toml::from_str(&buf).wrap_err("Failed to deserialize config")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_workers() -> u8 {
|
||||||
|
2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self { workers: 2 }
|
Self {
|
||||||
|
workers: Self::default_workers(),
|
||||||
|
tokens: Default::default(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ use color_eyre::eyre::Context;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use crossbeam::channel::bounded;
|
use crossbeam::channel::bounded;
|
||||||
use crossbeam::select;
|
use crossbeam::select;
|
||||||
use signal_hook::consts::{SIGINT, TERM_SIGNALS};
|
use signal_hook::consts::TERM_SIGNALS;
|
||||||
use signal_hook::flag;
|
use signal_hook::flag;
|
||||||
use signal_hook::iterator::Signals;
|
use signal_hook::iterator::Signals;
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ pub fn handle(runtime_dir: PathBuf, config_path: Option<PathBuf>, session: Strin
|
||||||
tracing::debug!(?config);
|
tracing::debug!(?config);
|
||||||
|
|
||||||
let rx_signals = {
|
let rx_signals = {
|
||||||
let mut signals = Signals::new([SIGINT])?;
|
let mut signals = Signals::new(TERM_SIGNALS)?;
|
||||||
let (tx, rx) = bounded(1);
|
let (tx, rx) = bounded(1);
|
||||||
|
|
||||||
thread::Builder::new()
|
thread::Builder::new()
|
||||||
|
@ -51,8 +51,8 @@ pub fn handle(runtime_dir: PathBuf, config_path: Option<PathBuf>, session: Strin
|
||||||
rx
|
rx
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut scheduler =
|
let mut scheduler = TaskScheduler::new(config.workers, config.tokens)
|
||||||
TaskScheduler::new(config.workers).wrap_err("Failed to create task scheduler")?;
|
.wrap_err("Failed to create task scheduler")?;
|
||||||
let listener = Listener::new(runtime_dir, session).wrap_err("Failed to create listener")?;
|
let listener = Listener::new(runtime_dir, session).wrap_err("Failed to create listener")?;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
use std::io::Read;
|
use std::collections::{HashMap, VecDeque};
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use std::iter;
|
||||||
use std::os::unix::net::UnixStream;
|
use std::os::unix::net::UnixStream;
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::thread::{self, JoinHandle};
|
use std::thread::{self, JoinHandle};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{fs, iter};
|
|
||||||
|
|
||||||
use color_eyre::eyre::{self, Context};
|
use color_eyre::eyre::{self, Context};
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use crossbeam::deque::{Injector, Stealer, Worker};
|
use crossbeam::deque::{Injector, Stealer, Worker};
|
||||||
|
use tree_sitter_highlight::{HighlightConfiguration, HighlightEvent, Highlighter};
|
||||||
|
|
||||||
use crate::kakoune::editor_quote;
|
use crate::kakoune::{self, editor_quote};
|
||||||
use crate::Request;
|
use crate::Request;
|
||||||
|
|
||||||
type Task = Result<UnixStream>;
|
type Task = Result<UnixStream>;
|
||||||
|
@ -21,8 +24,18 @@ pub struct TaskScheduler {
|
||||||
threads: Vec<JoinHandle<()>>,
|
threads: Vec<JoinHandle<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct TaskContext {
|
||||||
|
worker: Worker<Task>,
|
||||||
|
injector: Arc<Injector<Task>>,
|
||||||
|
stealers: Arc<Vec<Stealer<Task>>>,
|
||||||
|
terminate: Arc<AtomicBool>,
|
||||||
|
highlighter: Highlighter,
|
||||||
|
highlight_config: Arc<HighlightConfiguration>,
|
||||||
|
tokens: Arc<HashMap<String, String>>,
|
||||||
|
}
|
||||||
|
|
||||||
impl TaskScheduler {
|
impl TaskScheduler {
|
||||||
pub fn new(workers: u8) -> Result<Self> {
|
pub fn new(workers: u8, tokens: HashMap<String, String>) -> Result<Self> {
|
||||||
let terminate = Arc::new(AtomicBool::new(false));
|
let terminate = Arc::new(AtomicBool::new(false));
|
||||||
|
|
||||||
let injector = Arc::new(Injector::new());
|
let injector = Arc::new(Injector::new());
|
||||||
|
@ -30,17 +43,38 @@ impl TaskScheduler {
|
||||||
let stealers: Vec<_> = workers.iter().map(|w| w.stealer()).collect();
|
let stealers: Vec<_> = workers.iter().map(|w| w.stealer()).collect();
|
||||||
let stealers = Arc::new(stealers);
|
let stealers = Arc::new(stealers);
|
||||||
|
|
||||||
|
let mut highlight_config = HighlightConfiguration::new(
|
||||||
|
tree_sitter_rust::language(),
|
||||||
|
tree_sitter_rust::HIGHLIGHT_QUERY,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
.wrap_err("Invalid highlighter config")?;
|
||||||
|
|
||||||
|
let names: Vec<_> = tokens.keys().collect();
|
||||||
|
tracing::debug!("Highlighter tokens: {:?}", names);
|
||||||
|
highlight_config.configure(&names);
|
||||||
|
|
||||||
|
let highlight_config = Arc::new(highlight_config);
|
||||||
|
let tokens = Arc::new(tokens);
|
||||||
|
|
||||||
let threads = workers
|
let threads = workers
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, worker)| {
|
.map(|(i, worker)| {
|
||||||
let injector = injector.clone();
|
let ctx = TaskContext {
|
||||||
let stealers = stealers.clone();
|
worker,
|
||||||
let terminate = terminate.clone();
|
injector: injector.clone(),
|
||||||
|
stealers: stealers.clone(),
|
||||||
|
terminate: terminate.clone(),
|
||||||
|
highlighter: Highlighter::new(),
|
||||||
|
highlight_config: highlight_config.clone(),
|
||||||
|
tokens: tokens.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
thread::Builder::new()
|
thread::Builder::new()
|
||||||
.name(format!("worker-{}", i))
|
.name(format!("worker-{}", i))
|
||||||
.spawn(|| thread_handler(worker, injector, stealers, terminate))
|
.spawn(|| thread_handler(ctx))
|
||||||
.map_err(From::from)
|
.map_err(From::from)
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>>>()
|
.collect::<Result<Vec<_>>>()
|
||||||
|
@ -83,34 +117,29 @@ fn find_task<T>(local: &Worker<T>, global: &Injector<T>, stealers: &[Stealer<T>]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument(skip_all)]
|
||||||
fn thread_handler(
|
fn thread_handler(mut ctx: TaskContext) {
|
||||||
worker: Worker<Task>,
|
|
||||||
injector: Arc<Injector<Task>>,
|
|
||||||
stealers: Arc<Vec<Stealer<Task>>>,
|
|
||||||
terminate: Arc<AtomicBool>,
|
|
||||||
) {
|
|
||||||
loop {
|
loop {
|
||||||
let task = 'find_task: loop {
|
let task = 'find_task: loop {
|
||||||
if terminate.load(Ordering::Relaxed) {
|
if ctx.terminate.load(Ordering::Relaxed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(task) = find_task(&worker, &injector, &stealers) {
|
if let Some(task) = find_task(&ctx.worker, &ctx.injector, &ctx.stealers) {
|
||||||
break 'find_task task;
|
break 'find_task task;
|
||||||
}
|
}
|
||||||
|
|
||||||
thread::sleep(Duration::from_millis(50));
|
thread::sleep(Duration::from_millis(50));
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(err) = handle_connection(task) {
|
if let Err(err) = handle_connection(&mut ctx, task) {
|
||||||
tracing::error!("{:?}", err);
|
tracing::error!("{:?}", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
fn handle_connection(task: Task) -> Result<()> {
|
fn handle_connection(ctx: &mut TaskContext, task: Task) -> Result<()> {
|
||||||
let mut stream = task.wrap_err("Failed to receive client connection")?;
|
let mut stream = task.wrap_err("Failed to receive client connection")?;
|
||||||
|
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
|
@ -119,19 +148,99 @@ fn handle_connection(task: Task) -> Result<()> {
|
||||||
.wrap_err("Failed to read from connection")?;
|
.wrap_err("Failed to read from connection")?;
|
||||||
let req: Request = toml::from_str(&buf).wrap_err("Failed to parse request")?;
|
let req: Request = toml::from_str(&buf).wrap_err("Failed to parse request")?;
|
||||||
|
|
||||||
tracing::info!("Received request: {:?}", req);
|
tracing::info!("Received request");
|
||||||
|
tracing::debug!(?req);
|
||||||
|
|
||||||
let response = process_request(&req)
|
let response = process_request(ctx, &req)
|
||||||
.unwrap_or_else(|err| format!("fail {}", editor_quote(format!("{}", err))));
|
.unwrap_or_else(|err| format!("fail {}", editor_quote(format!("{}", err))));
|
||||||
|
|
||||||
tracing::debug!("Sending response:\n{}", response);
|
let mut child = Command::new("kak")
|
||||||
|
.args(["-p", &req.session])
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.stderr(Stdio::null())
|
||||||
|
.spawn()
|
||||||
|
.wrap_err("Failed to spawn Kakoune command")?;
|
||||||
|
|
||||||
fs::write(&req.fifo, response.as_bytes()).wrap_err("Failed to write to command fifo")?;
|
if let Some(stdin) = child.stdin.as_mut() {
|
||||||
|
let command = format!(
|
||||||
|
"evaluate-commands -client {} -verbatim -- {}",
|
||||||
|
req.client, response
|
||||||
|
);
|
||||||
|
|
||||||
|
tracing::info!("Writing response");
|
||||||
|
tracing::debug!(command);
|
||||||
|
|
||||||
|
stdin
|
||||||
|
.write_all(command.as_bytes())
|
||||||
|
.wrap_err("Failed to write to Kakoune stdin")?;
|
||||||
|
} else {
|
||||||
|
eyre::bail!("Failed to get stdin for Kakoune command");
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument(skip(ctx, req), fields(
|
||||||
fn process_request(req: &Request) -> Result<String> {
|
session = req.session,
|
||||||
eyre::bail!("Not implemented")
|
client = req.client,
|
||||||
|
content_len = req.content.len(),
|
||||||
|
timestamp = req.timestamp,
|
||||||
|
))]
|
||||||
|
fn process_request(ctx: &mut TaskContext, req: &Request) -> Result<String> {
|
||||||
|
let names: Vec<_> = ctx.tokens.keys().collect();
|
||||||
|
|
||||||
|
let highlights = ctx
|
||||||
|
.highlighter
|
||||||
|
.highlight(&ctx.highlight_config, req.content.as_bytes(), None, |_| {
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.wrap_err("Failed to highlight content")?;
|
||||||
|
|
||||||
|
let mut stack = VecDeque::new();
|
||||||
|
let mut range_spec = String::new();
|
||||||
|
|
||||||
|
for res in highlights {
|
||||||
|
match res? {
|
||||||
|
HighlightEvent::Source { start, end } => {
|
||||||
|
if let Some(index) = stack.back() {
|
||||||
|
// Tree-sitter actually returns the byte position after the token
|
||||||
|
// as `end` here.
|
||||||
|
let end = end.saturating_sub(1);
|
||||||
|
|
||||||
|
let range =
|
||||||
|
kakoune::range_from_byte_offsets(req.content.as_bytes(), start, end);
|
||||||
|
|
||||||
|
tracing::trace!(start, end, ?range, index);
|
||||||
|
|
||||||
|
let spec = format!(
|
||||||
|
"{}.{},{}.{}|{}",
|
||||||
|
range.start_point.row,
|
||||||
|
range.start_point.column,
|
||||||
|
range.end_point.row,
|
||||||
|
range.end_point.column,
|
||||||
|
ctx.tokens[names[*index]]
|
||||||
|
);
|
||||||
|
|
||||||
|
range_spec.push(' ');
|
||||||
|
range_spec.push_str(&spec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HighlightEvent::HighlightStart(index) => {
|
||||||
|
stack.push_back(index.0);
|
||||||
|
}
|
||||||
|
HighlightEvent::HighlightEnd => {
|
||||||
|
// Tree-sitter shouldn't call this when there is nothing on the stack,
|
||||||
|
// but it wouldn't matter anyways.
|
||||||
|
let _ = stack.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = format!(
|
||||||
|
"set-option buffer kak_highlight_ranges {}{range_spec}",
|
||||||
|
req.timestamp
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,47 @@
|
||||||
|
use tree_sitter::{Point, Range};
|
||||||
|
|
||||||
pub fn editor_quote(s: impl AsRef<str>) -> String {
|
pub fn editor_quote(s: impl AsRef<str>) -> String {
|
||||||
// TODO
|
// TODO
|
||||||
format!("'{}'", s.as_ref())
|
format!("'{}'", s.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn range_from_byte_offsets(content: impl AsRef<[u8]>, start: usize, end: usize) -> Range {
|
||||||
|
// Kakoune's line indices are 1-based
|
||||||
|
let mut start_row = 1;
|
||||||
|
let mut start_column = 1;
|
||||||
|
let mut end_row = 1;
|
||||||
|
let mut end_column = 1;
|
||||||
|
|
||||||
|
for (i, byte) in content.as_ref().iter().enumerate() {
|
||||||
|
if i < start {
|
||||||
|
if *byte == b'\n' {
|
||||||
|
start_row += 1;
|
||||||
|
start_column = 1;
|
||||||
|
} else {
|
||||||
|
start_column += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if i < end {
|
||||||
|
if *byte == b'\n' {
|
||||||
|
end_row += 1;
|
||||||
|
end_column = 1;
|
||||||
|
} else {
|
||||||
|
end_column += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Range {
|
||||||
|
start_byte: start,
|
||||||
|
end_byte: end,
|
||||||
|
start_point: Point {
|
||||||
|
row: start_row,
|
||||||
|
column: start_column,
|
||||||
|
},
|
||||||
|
end_point: Point {
|
||||||
|
row: end_row,
|
||||||
|
column: end_column,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
34
src/main.rs
34
src/main.rs
|
@ -36,10 +36,7 @@ struct Cli {
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
enum Command {
|
enum Command {
|
||||||
/// Send a request payload to a running daemon and wait for the response
|
/// Send a request payload to a running daemon and wait for the response
|
||||||
Request {
|
Request,
|
||||||
/// The Kakoune session this daemon belongs to
|
|
||||||
session: String,
|
|
||||||
},
|
|
||||||
/// Start a daemon
|
/// Start a daemon
|
||||||
Daemon {
|
Daemon {
|
||||||
/// The Kakoune session this daemon belongs to
|
/// The Kakoune session this daemon belongs to
|
||||||
|
@ -49,8 +46,14 @@ enum Command {
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
struct Request {
|
struct Request {
|
||||||
/// The command FIFO provided by Kakoune
|
/// The buffer content
|
||||||
fifo: String,
|
content: String,
|
||||||
|
/// The Kakoune timestamp
|
||||||
|
timestamp: u64,
|
||||||
|
/// The Kakoune session
|
||||||
|
session: String,
|
||||||
|
/// The Kakoune client
|
||||||
|
client: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
|
@ -65,25 +68,20 @@ fn main() -> Result<()> {
|
||||||
_ => LevelFilter::TRACE,
|
_ => LevelFilter::TRACE,
|
||||||
};
|
};
|
||||||
|
|
||||||
let stderr_layer = if cfg!(debug_assertions) {
|
let output_layer = if let Some(path) = cli.log {
|
||||||
|
let f = File::create(&path)
|
||||||
|
.wrap_err_with(|| format!("Failed to create log file '{}'", path.display()))?;
|
||||||
|
fmt::layer().compact().with_writer(f).boxed()
|
||||||
|
} else if cfg!(debug_assertions) {
|
||||||
fmt::layer().pretty().with_writer(stderr).boxed()
|
fmt::layer().pretty().with_writer(stderr).boxed()
|
||||||
} else {
|
} else {
|
||||||
fmt::layer().compact().with_writer(stderr).boxed()
|
fmt::layer().compact().with_writer(stderr).boxed()
|
||||||
};
|
};
|
||||||
let file_layer = if let Some(path) = cli.log {
|
|
||||||
let f = File::create(&path)
|
|
||||||
.wrap_err_with(|| format!("Failed to create log file '{}'", path.display()))?;
|
|
||||||
let layer = fmt::layer().pretty().with_writer(f);
|
|
||||||
Some(layer)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
tracing_subscriber::registry()
|
tracing_subscriber::registry()
|
||||||
.with(filter_layer)
|
.with(filter_layer)
|
||||||
.with(stderr_layer)
|
|
||||||
.with(file_layer)
|
|
||||||
.with(ErrorLayer::new(fmt::format::Pretty::default()))
|
.with(ErrorLayer::new(fmt::format::Pretty::default()))
|
||||||
|
.with(output_layer)
|
||||||
.init();
|
.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,6 +92,6 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
Command::Daemon { session } => daemon::handle(runtime_dir, cli.config, session),
|
Command::Daemon { session } => daemon::handle(runtime_dir, cli.config, session),
|
||||||
Command::Request { session } => client::handle(runtime_dir, session),
|
Command::Request => client::handle(runtime_dir),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
17
test.sh
Executable file
17
test.sh
Executable file
|
@ -0,0 +1,17 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
FIFO=/tmp/kak-highlight.test.fifo
|
||||||
|
REQUEST=$(cat)
|
||||||
|
|
||||||
|
mkfifo $FIFO
|
||||||
|
|
||||||
|
cargo run -- request test <<EOF
|
||||||
|
fifo = "$FIFO"
|
||||||
|
$REQUEST
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat <$FIFO
|
||||||
|
|
||||||
|
rm $FIFO
|
Loading…
Add table
Reference in a new issue