实现 server 的 execute_command 管线,并实现前端主动发起 lint 请求

This commit is contained in:
锦恢 2024-12-17 18:20:44 +08:00
parent 574c50325e
commit 826d62dfbd
7 changed files with 88 additions and 49 deletions

View File

@ -0,0 +1,45 @@
use std::{path::PathBuf, str::FromStr};
use serde_json::Value;
use tower_lsp::lsp_types::{Diagnostic, Url};
use crate::{diagnostics::provide_diagnostics, server::Backend, utils::{from_uri_to_escape_path_string, open_doc_as_rope}};
/// 前端请求,发布诊断结果,仅在初始化和修改配置时触发
/// 参数为 [file_path: string]
pub async fn publish_diagnostics(
backend: &Backend,
arguments: Vec<Value>
) -> tower_lsp::jsonrpc::Result<Option<Value>> {
let path_string = arguments.get(0).unwrap().to_string();
let uri = Url::from_file_path(path_string).unwrap();
let path_string = from_uri_to_escape_path_string(&uri).unwrap();
let pathbuf = PathBuf::from_str(&path_string).unwrap();
// 考虑到性能,如果后端文本缓冲器内存在当前路径的 文本备份,则使用它作为 rope
// 否则,进行 IO 后再转换
let rope = open_doc_as_rope(&pathbuf).unwrap();
let diagnostics_params = provide_diagnostics(uri, &rope, &backend.server);
backend.client.publish_diagnostics(
diagnostics_params.uri,
diagnostics_params.diagnostics,
None
).await;
Ok(None)
}
/// 前端请求,清除诊断结果,仅在初始化和修改配置时触发
/// 参数为 [file_path: string]
pub async fn clear_diagnostics(
backend: &Backend,
arguments: Vec<Value>
) -> tower_lsp::jsonrpc::Result<Option<Value>> {
let path_string = arguments.get(0).unwrap().to_string();
let uri = Url::from_file_path(path_string).unwrap();
let diagnostics = Vec::<Diagnostic>::new();
backend.client.publish_diagnostics(uri, diagnostics, None).await;
Ok(None)
}

View File

@ -0,0 +1,25 @@
use serde_json::Value;
use tower_lsp::lsp_types::*;
use crate::server::Backend;
mod diagnostics;
pub async fn execute_command(
backend: &Backend,
params: ExecuteCommandParams
) -> tower_lsp::jsonrpc::Result<Option<Value>> {
match params.command.as_str() {
"publish-diagnostics" => {
diagnostics::publish_diagnostics(backend, params.arguments).await
}
"clear-diagnostics" => {
diagnostics::clear_diagnostics(backend, params.arguments).await
}
_ => {
Ok(None)
}
}
}

View File

@ -43,5 +43,8 @@ pub mod sources;
// 自定义发送请求
pub mod request;
// 自定义异步命令
pub mod execute_command;
// 测试模块
pub mod test;

View File

@ -7,8 +7,7 @@ use request::{
fast::SyncFastApi,
config::UpdateConfigurationApi,
primitives::DoPrimitivesJudgeApi,
linter::LinterStatusApi,
linter::DoLintApi
linter::LinterStatusApi
};
use log::info;
@ -30,6 +29,7 @@ mod format;
mod server;
mod sources;
mod request;
mod execute_command;
use server::Backend;
@ -60,7 +60,6 @@ async fn main() {
.custom_method("api/update-configuration", UpdateConfigurationApi)
.custom_method("api/sync-fast", SyncFastApi)
.custom_method("api/linter-status", LinterStatusApi)
.custom_method("api/do-lint", DoLintApi)
.finish();
Server::new(stdin, stdout, socket)

View File

@ -4,11 +4,9 @@ use std::future;
use log::info;
use serde::{Deserialize, Serialize};
use tower_lsp::jsonrpc::Result;
use tower_lsp::lsp_types::{Diagnostic, Url};
use crate::diagnostics::{provide_diagnostics, AbstractLinterConfiguration, LinterStatus};
use crate::diagnostics::{AbstractLinterConfiguration, LinterStatus};
use crate::server::Backend;
use crate::utils::from_uri_to_escape_path_string;
#[derive(Clone)]
pub struct LinterStatusApi;
@ -95,40 +93,3 @@ fn get_linter_status(
}
}
#[derive(Clone)]
pub struct DoLintApi;
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct DoLintParams {
path: String,
}
impl <'a>tower_lsp::jsonrpc::Method<&'a Arc<Backend>, (DoLintParams, ), Result<Vec<Diagnostic>>> for DoLintApi {
type Future = future::Ready<Result<Vec<Diagnostic>>>;
fn invoke(&self, _server: &'a Arc<Backend>, _params: (DoLintParams, )) -> Self::Future {
let path = _params.0.path;
let uri = Url::from_file_path(&path).unwrap();
let path_string = from_uri_to_escape_path_string(&uri).unwrap();
let server = &_server.server;
let result = if let Some(source) = server.srcs.get_source(&path_string) {
let source = source.read().unwrap();
let diags_param = provide_diagnostics(uri, &source.text, server);
Ok(diags_param.diagnostics)
} else {
Err(tower_lsp::jsonrpc::Error {
code: tower_lsp::jsonrpc::ErrorCode::InvalidRequest,
message: Cow::Owned(format!("文件尚未初始化 {}", path)),
data: None
})
};
future::ready(result)
}
}

View File

@ -230,6 +230,10 @@ impl LanguageServer for Backend {
.await;
}
async fn execute_command(&self, params: ExecuteCommandParams) -> Result<Option<serde_json::Value>> {
crate::execute_command::execute_command(self, params).await
}
async fn shutdown(&self) -> Result<()> {
Ok(())
}

View File

@ -321,13 +321,10 @@ impl Sources {
// 对于当前的文件增加一个解析线程,不断进行解析和同步
#[allow(clippy::mutex_atomic)] // https://github.com/rust-lang/rust-clippy/issues/1516
let valid_parse = Arc::new((Mutex::new(false), Condvar::new()));
let valid_parse2 = valid_parse.clone();
let valid_parse_handle = valid_parse.clone();
let mut sources = self.sources.write().unwrap();
// uri 转换为标准文件路径
let path_string = from_uri_to_escape_path_string(&doc.uri).unwrap();
// 为新加入的文件构建后端文本缓冲器副本
let source_handle = Arc::new(RwLock::new(Source {
uri: doc.uri.clone(),
@ -362,7 +359,9 @@ impl Sources {
// 创建一个解析线程
let parse_handle = thread::spawn(move || {
let (lock, cvar) = &*valid_parse2;
// TODO: 检查这里
let (ref lock, ref cvar) = *valid_parse_handle;
loop {
info!("do parse in {:?}, language_id: {:?}", uri.to_string(), language_id);
@ -404,9 +403,11 @@ impl Sources {
}
}
// 通过条件锁进行控制
// 下方基于消费者-生产者模型进行控制
// lock 会尝试去获取互斥锁,如果当前持有互斥锁的线程发生了 panic则当前 unwrap 操作会失败
let mut valid = lock.lock().unwrap();
*valid = true;
// 唤醒所有正在等待条件变量的线程,即所有被下方 cvar.wait 阻塞的线程将被激活
cvar.notify_all();
while *valid {
valid = cvar.wait(valid).unwrap();
@ -455,6 +456,7 @@ impl Sources {
let source_status_handle = self.get_source_status(path_string).unwrap(); // status 和 source 是同步创建的,要有一起有
let (lock, cvar) = &*source_status_handle.read().unwrap().valid_parse;
let mut valid = lock.lock().unwrap();
// 解析线程一轮解析结束后, valid 会变为 true结束循环
while !*valid {
valid = cvar.wait(valid).unwrap();
}