use lsp_types as lsp;
use lsp::{notification::Notification, request::Request};
use lsp_server;
use serde_json;
use std::collections::HashMap;
use std::collections::VecDeque;
use std::error::Error;
use std::sync::{Arc,Mutex};
use a2kit::lang::server::TOKEN_TYPES; use a2kit::lang::server::{Analysis,Checkpoint};
use a2kit::lang::integer;
use a2kit::lang::integer::diagnostics::Analyzer;
use a2kit::lang::integer::checkpoint::CheckpointManager;
use a2kit::lang::disk_server::DiskServer;
mod notification;
mod request;
mod response;
mod rpc_error {
pub const PARSE_ERROR: i32 = -32700;
}
enum WorkspaceScanMethod {
None,
UseCheckpoints,
FullUpdate,
}
#[derive(thiserror::Error,Debug)]
enum ServerError {
#[error("Parsing")]
Parsing
}
struct AnalysisResult {
uri: lsp::Uri,
version: Option<i32>,
diagnostics: Vec<lsp::Diagnostic>,
symbols: integer::Symbols,
workspace: integer::Workspace
}
fn logger(connection: &lsp_server::Connection, message: &str) {
let note = lsp_server::Notification::new(
lsp::notification::LogMessage::METHOD.to_string(),
lsp::LogMessageParams {
typ: lsp::MessageType::LOG,
message: message.to_string()
}
);
match connection.sender.send(lsp_server::Message::Notification(note)) {
Err(_) => {}, Ok(()) => {}
}
}
fn request_configuration(connection: &lsp_server::Connection) -> Result<(),Box<dyn Error>> {
let req = lsp_server::Request::new(
lsp_server::RequestId::from("integerbasic-pull-config".to_string()),
lsp::request::WorkspaceConfiguration::METHOD.to_string(),
lsp::ConfigurationParams { items: vec![
lsp::ConfigurationItem {
scope_uri: None,
section: Some("integerbasic".to_string())
}
]}
);
match connection.sender.send(req.into()) {
Ok(()) => Ok(()),
Err(e) => Err(Box::new(e))
}
}
fn parse_configuration(resp: lsp_server::Response) -> Result<integer::settings::Settings,Box<dyn Error>> {
if let Some(result) = resp.result {
if let Some(ary) = result.as_array() {
for item in ary {
let json_config = item.to_string();
match integer::settings::parse(&json_config) {
Ok(config) => return Ok(config),
Err(e) => return Err(e)
}
}
}
}
Err(Box::new(ServerError::Parsing))
}
fn launch_analysis_thread(analyzer: Arc<Mutex<Analyzer>>, doc: a2kit::lang::Document, ws_scan: WorkspaceScanMethod, chks: &HashMap<String,CheckpointManager>) -> std::thread::JoinHandle<Option<AnalysisResult>> {
let checkpoints = match ws_scan {
WorkspaceScanMethod::FullUpdate => {
let mut ans = Vec::new();
for chk in chks.values() {
ans.push(chk.get_doc());
}
ans
},
_ => Vec::new()
};
std::thread::spawn( move || {
match analyzer.lock() {
Ok(mut analyzer) => {
match ws_scan {
WorkspaceScanMethod::None => {},
WorkspaceScanMethod::UseCheckpoints => {
match analyzer.rescan_workspace(false) {
_ => {}
}
},
WorkspaceScanMethod::FullUpdate => {
match analyzer.rescan_workspace_and_update(checkpoints) {
_ => {}
}
}
};
match analyzer.analyze(&doc) {
Ok(()) => Some(AnalysisResult {
uri: doc.uri.clone(),
version: doc.version,
diagnostics: analyzer.get_diags(&doc),
symbols: analyzer.get_symbols(),
workspace: analyzer.get_workspace().clone()
}),
Err(_) => None
}
}
Err(_) => None
}
})
}
pub fn push_diagnostics(connection: &lsp_server::Connection,uri: lsp::Uri, version: Option<i32>, diagnostics: Vec<lsp::Diagnostic>) {
let note = lsp_server::Notification::new(
"textDocument/publishDiagnostics".to_string(),
lsp::PublishDiagnosticsParams {
uri,
diagnostics,
version
}
);
match connection.sender.send(lsp_server::Message::Notification(note)) {
Err(_) => logger(connection,"could not push diagnostics"),
Ok(()) => {}
}
}
struct Tools {
config: integer::settings::Settings,
workspace: integer::Workspace,
thread_handles: VecDeque<std::thread::JoinHandle<Option<AnalysisResult>>>,
doc_chkpts: HashMap<String,integer::checkpoint::CheckpointManager>,
analyzer: Arc<Mutex<integer::diagnostics::Analyzer>>,
hover_provider: integer::hovers::HoverProvider,
completion_provider: integer::completions::CompletionProvider,
highlighter: integer::semantic_tokens::SemanticTokensProvider,
tokenizer: integer::tokenizer::Tokenizer,
disk: DiskServer
}
impl Tools {
pub fn new() -> Self {
Self {
config: integer::settings::Settings::new(),
workspace: integer::Workspace::new(),
thread_handles: VecDeque::new(),
doc_chkpts: HashMap::new(),
analyzer: Arc::new(Mutex::new(integer::diagnostics::Analyzer::new())),
hover_provider: integer::hovers::HoverProvider::new(),
completion_provider: integer::completions::CompletionProvider::new(),
highlighter: integer::semantic_tokens::SemanticTokensProvider::new(),
tokenizer: integer::tokenizer::Tokenizer::new(),
disk: DiskServer::new()
}
}
}
fn main() -> Result<(), Box<dyn Error + Sync + Send>> {
let (bools,_) = a2kit::lang::server::parse_args();
let suppress_tokens = bools[0];
let mut tools = Tools::new();
let (connection, io_threads) = lsp_server::Connection::stdio();
logger(&connection,"start initializing connection");
let (id,params) = connection.initialize_start()?;
let params: lsp::InitializeParams = serde_json::from_value(params)?;
let result = lsp::InitializeResult {
capabilities: lsp::ServerCapabilities {
text_document_sync: Some(lsp::TextDocumentSyncCapability::Options(
lsp::TextDocumentSyncOptions {
open_close: Some(true),
change: Some(lsp::TextDocumentSyncKind::FULL),
will_save: None,
will_save_wait_until: None,
save: None
}
)),
definition_provider: Some(lsp::OneOf::Left(true)),
declaration_provider: Some(lsp::DeclarationCapability::Simple(true)),
references_provider: Some(lsp::OneOf::Left(true)),
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
completion_provider: Some(lsp::CompletionOptions {
resolve_provider: Some(false),
trigger_characters: Some(vec!["\n".to_string()," ".to_string()]),
..lsp::CompletionOptions::default()
}),
document_symbol_provider: Some(lsp::OneOf::Left(true)),
workspace_symbol_provider: Some(lsp::OneOf::Left(true)),
rename_provider: Some(lsp::OneOf::Left(true)),
semantic_tokens_provider: match suppress_tokens {
true => None,
false => Some(lsp::SemanticTokensServerCapabilities::SemanticTokensOptions(lsp::SemanticTokensOptions {
work_done_progress_options: lsp::WorkDoneProgressOptions {
work_done_progress: None
},
legend: lsp::SemanticTokensLegend {
token_types: TOKEN_TYPES.iter().map(|x| lsp::SemanticTokenType::new(x)).collect(),
token_modifiers: vec![]
},
range: None,
full: Some(lsp::SemanticTokensFullOptions::Bool(true))
}))
},
execute_command_provider: Some(lsp::ExecuteCommandOptions {
commands: [
"integerbasic.semantic.tokens",
"integerbasic.tokenize",
"integerbasic.detokenize",
"integerbasic.renumber",
"integerbasic.move",
"integerbasic.disk.mount",
"integerbasic.disk.pick",
"integerbasic.disk.put",
"integerbasic.disk.delete",
].iter().map(|x| x.to_string()).collect::<Vec<String>>(),
work_done_progress_options: lsp::WorkDoneProgressOptions {
work_done_progress: None
}
}),
..lsp::ServerCapabilities::default()
},
server_info: Some(lsp::ServerInfo {
name: "integerbasic".to_string(),
version: Some(env!("CARGO_PKG_VERSION").to_string())
})
};
connection.initialize_finish(id, serde_json::to_value(result)?)?;
logger(&connection,"connection initialized");
let mut registrations: Vec<lsp::Registration> = Vec::new();
if let Some(workspace) = params.capabilities.workspace {
if let Some(config) = workspace.configuration {
if config {
registrations.push(lsp::Registration {
id: "pull-config".to_string(),
method: lsp::notification::DidChangeConfiguration::METHOD.to_string(),
register_options: None
});
}
}
}
let req = lsp_server::Request::new(
lsp_server::RequestId::from("integerbasic-reg-config".to_string()),
lsp::request::RegisterCapability::METHOD.to_string(),
lsp::RegistrationParams { registrations });
if let Err(_) = connection.sender.send(req.into()) {
logger(&connection,"Could not register change configuration capability");
}
match request_configuration(&connection) {
Ok(()) => {},
Err(_) => logger(&connection,"could not request starting configuration")
}
if let Some(folders) = params.workspace_folders {
let source_dirs = folders.iter().map(|f| f.uri.clone()).collect::<Vec<lsp::Uri>>();
if let Ok(mut mutex) = tools.analyzer.lock() {
match mutex.init_workspace(source_dirs, Vec::new()) {
Ok(()) => {},
Err(e) => logger(&connection,&format!("initial workspace scan failed: {}",e))
}
}
}
loop {
if let Some(oldest) = tools.thread_handles.front() {
if oldest.is_finished() {
let done = tools.thread_handles.pop_front().unwrap();
if let Ok(Some(result)) = done.join() {
tools.workspace = result.workspace;
if let Some(chkpt) = tools.doc_chkpts.get_mut(&result.uri.to_string()) {
chkpt.update_symbols(result.symbols);
chkpt.update_ws_symbols(tools.workspace.borrow_ws_symbols().clone());
tools.hover_provider.use_shared_symbols(chkpt.shared_symbols());
tools.completion_provider.use_shared_symbols(chkpt.shared_symbols());
}
push_diagnostics(&connection, result.uri, result.version, result.diagnostics);
}
}
}
if let Ok(msg) = connection.receiver.recv_timeout(std::time::Duration::from_millis(100)) {
match msg {
lsp_server::Message::Notification(note) => {
notification::handle_notification(&connection,note,&mut tools);
}
lsp_server::Message::Request(req) => {
if request::handle_request(&connection, req, &mut tools) {
break;
}
},
lsp_server::Message::Response(resp) => {
response::handle_response(&connection, resp, &mut tools);
}
}
}
}
io_threads.join()?;
Ok(())
}