feat: init ccserver
This commit is contained in:
commit
f3a7f8ee03
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
1341
Cargo.lock
generated
Normal file
1341
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
14
Cargo.toml
Normal file
14
Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "ccserver"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
home = "0.5"
|
||||
serde = { version="1", features = ["derive"] }
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
tokio = { version = "1", features = ["fs", "io-std", "io-util", "macros", "rt-multi-thread"] }
|
||||
tower-lsp = "0.19"
|
||||
|
37
README.md
Normal file
37
README.md
Normal file
|
@ -0,0 +1,37 @@
|
|||
# ccserver
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This is currently a work in progress.
|
||||
|
||||
**ccserver** is a LSP server for ML code completion (and more?).
|
||||
|
||||
## Developing
|
||||
|
||||
Clone/fork this repo and run `cargo build [--release]`.
|
||||
|
||||
Then add the following code to your lua config:
|
||||
|
||||
```lua
|
||||
local client_id = vim.lsp.start({
|
||||
name = "ccserver",
|
||||
cmd = { "/path/to/ccserver/target/{debug|release}/ccserver" },
|
||||
root_dir = vim.fs.dirname(vim.fs.find({ ".git" }, { upward = true })[1]),
|
||||
})
|
||||
|
||||
if client_id == nil then
|
||||
vim.notify("[ccserver] Error starting server", vim.log.levels.ERROR)
|
||||
else
|
||||
local augroup = "ccserver"
|
||||
|
||||
vim.api.nvim_create_augroup(augroup, { clear = true })
|
||||
|
||||
vim.api.nvim_create_autocmd("BufEnter", {
|
||||
pattern = "*",
|
||||
callback = function(ev)
|
||||
if not vim.lsp.buf_is_attached(ev.buf, client_id) then
|
||||
vim.lsp.buf_attach_client(ev.buf, client_id)
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
```
|
165
src/main.rs
Normal file
165
src/main.rs
Normal file
|
@ -0,0 +1,165 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Display;
|
||||
use std::path::PathBuf;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tower_lsp::jsonrpc::{Error, Result};
|
||||
use tower_lsp::lsp_types::*;
|
||||
use tower_lsp::{Client, LanguageServer, LspService, Server};
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct RequestParams {
|
||||
max_new_tokens: u32,
|
||||
temperature: f32,
|
||||
do_sample: bool,
|
||||
top_p: f32,
|
||||
stop_token: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct APIRequest {
|
||||
inputs: String,
|
||||
parameters: RequestParams,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct APIResponse {
|
||||
generated_text: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Backend {
|
||||
client: Client,
|
||||
http_client: reqwest::Client,
|
||||
}
|
||||
|
||||
fn internal_error<E: Display>(err: E) -> Error {
|
||||
Error {
|
||||
code: tower_lsp::jsonrpc::ErrorCode::InternalError,
|
||||
message: err.to_string(),
|
||||
data: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_cache_dir_path() -> Result<PathBuf> {
|
||||
let home_dir = home::home_dir().ok_or(internal_error("Failed to find home dir"))?;
|
||||
Ok(home_dir.join(".cache/ccserver"))
|
||||
}
|
||||
|
||||
async fn request_completion(http_client: &reqwest::Client) -> Result<Vec<APIResponse>> {
|
||||
http_client
|
||||
.post("https://api-inference.huggingface.co/models/bigcode/starcoder")
|
||||
.json(&APIRequest {
|
||||
inputs: "Hello my name is ".to_owned(),
|
||||
parameters: RequestParams {
|
||||
max_new_tokens: 60,
|
||||
temperature: 0.2,
|
||||
do_sample: true,
|
||||
top_p: 0.95,
|
||||
stop_token: "\n".to_owned(),
|
||||
},
|
||||
})
|
||||
.send()
|
||||
.await
|
||||
.map_err(internal_error)?
|
||||
.json()
|
||||
.await
|
||||
.map_err(internal_error)?
|
||||
}
|
||||
|
||||
#[tower_lsp::async_trait]
|
||||
impl LanguageServer for Backend {
|
||||
async fn initialize(&self, _: InitializeParams) -> Result<InitializeResult> {
|
||||
tokio::fs::create_dir_all(get_cache_dir_path()?)
|
||||
.await
|
||||
.map_err(internal_error)?;
|
||||
Ok(InitializeResult {
|
||||
capabilities: ServerCapabilities {
|
||||
completion_provider: Some(CompletionOptions {
|
||||
resolve_provider: Some(false),
|
||||
trigger_characters: Some(vec![
|
||||
".".to_owned(),
|
||||
"(".to_owned(),
|
||||
"{".to_owned(),
|
||||
":".to_owned(),
|
||||
":".to_owned(),
|
||||
]),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
async fn initialized(&self, _: InitializedParams) {
|
||||
self.client
|
||||
.log_message(MessageType::INFO, "{ccserver} initialized")
|
||||
.await;
|
||||
if let Ok(cache_dir) = get_cache_dir_path() {
|
||||
tokio::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(cache_dir.join("ccserver.log"))
|
||||
.await
|
||||
.unwrap()
|
||||
.write_all(b"initialized\n")
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: tbd if we use code action or completion
|
||||
async fn completion(&self, _: CompletionParams) -> Result<Option<CompletionResponse>> {
|
||||
let result = request_completion(&self.http_client).await?;
|
||||
if result.len() > 0 {
|
||||
let generated_text = result[0].generated_text.clone();
|
||||
|
||||
tokio::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(get_cache_dir_path()?.join("ccserver.log"))
|
||||
.await
|
||||
.unwrap()
|
||||
.write_all(format!("completion request: {generated_text}\n").as_bytes())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
Ok(Some(CompletionResponse::Array(vec![CompletionItem {
|
||||
label: "ccserver completion".to_owned(),
|
||||
insert_text: Some(generated_text.clone()),
|
||||
kind: Some(CompletionItemKind::TEXT),
|
||||
detail: Some(generated_text),
|
||||
..Default::default()
|
||||
}])))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
async fn shutdown(&self) -> Result<()> {
|
||||
tokio::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(get_cache_dir_path()?.join("ccserver.log"))
|
||||
.await
|
||||
.unwrap()
|
||||
.write_all(b"shutdown\n")
|
||||
.await
|
||||
.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let stdin = tokio::io::stdin();
|
||||
let stdout = tokio::io::stdout();
|
||||
|
||||
let http_client = reqwest::Client::new();
|
||||
|
||||
let (service, socket) = LspService::new(|client| Backend {
|
||||
client,
|
||||
http_client,
|
||||
});
|
||||
Server::new(stdin, stdout, socket).serve(service).await;
|
||||
}
|
Loading…
Reference in a new issue