Skip to content

Commit

Permalink
feat: workspace/didChangeWatchedFiles を使う (refs #9)
Browse files Browse the repository at this point in the history
サーバーではなくクライアント側で監視すべきである理由はLSP Specificationに書いてある:
https://microsoft.github.io/language-server-protocol/specification#workspace_didChangeWatchedFiles

- ファイルシステムの監視はOSごとに方法が異なるので正しく実装するのが難しい。
    また、監視することのコストは小さくない。
- クライアントは複数のサーバーを起動することがあるので (その逆はない)、
    サーバーが監視するよりクライアントが監視するほうが効率がいい。
- サーバーの実装よりクライアントの実装のほうが数が少ないので、
    クライアント側に実装させたほうが (全体的に作業の) 効率がいい。

従来の実装では起動時に存在しているすべてのファイルに対してCreatedイベントを
発行していたが、LSPクライアントのウォッチャーはそういう挙動をしないので、
起動時に存在しているファイルを列挙するコードを追加した。
  • Loading branch information
vain0x committed Aug 20, 2021
1 parent 5a70bd8 commit b25630e
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 13 deletions.
52 changes: 43 additions & 9 deletions hsp3-analyzer-mini/ham-core/src/lang_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ pub(super) struct LangService {
docs: Docs,
diagnostics_cache: DiagnosticsCache,
file_watcher_opt: Option<FileWatcher>,

watchable: bool,
}

impl LangService {
Expand All @@ -78,6 +80,10 @@ impl LangService {
ls
}

pub(super) fn set_watchable(&mut self, watchable: bool) {
self.watchable = watchable;
}

pub(super) fn initialize(&mut self, root_uri_opt: Option<Url>) {
if let Some(uri) = root_uri_opt {
self.root_uri_opt = Some(CanonicalUri::from_url(&uri));
Expand Down Expand Up @@ -151,17 +157,30 @@ impl LangService {
entrypoints,
});

if self.options.watcher_enabled {
if let Some(watched_dir) = self
.root_uri_opt
.as_ref()
.and_then(|uri| uri.to_file_path())
{
let mut watcher = FileWatcher::new(watched_dir);
watcher.start_watch();
self.file_watcher_opt = Some(watcher);
info!("ルートディレクトリからスクリプトファイルを収集します。");
{
let root_dir_opt = self.root_uri_opt.as_ref().and_then(|x| x.to_file_path());
let script_files = root_dir_opt
.into_iter()
.filter_map(|dir| glob::glob(&format!("{}/**/*.hsp", dir.to_str()?)).ok())
.flatten()
.filter_map(|path_opt| path_opt.ok());
for path in script_files {
self.docs.change_file(&path);
}
}

// if self.options.watcher_enabled {
// if let Some(watched_dir) = self
// .root_uri_opt
// .as_ref()
// .and_then(|uri| uri.to_file_path())
// {
// let mut watcher = FileWatcher::new(watched_dir);
// watcher.start_watch();
// self.file_watcher_opt = Some(watcher);
// }
// }
}

/// ドキュメントの変更を集積して、解析器の状態を更新する。
Expand Down Expand Up @@ -261,6 +280,21 @@ impl LangService {
self.docs.close_doc_in_editor(uri);
}

pub(super) fn on_file_created(&mut self, uri: Url) {
let uri = CanonicalUri::from_url(&uri);
self.docs.change_file_by_uri(uri);
}

pub(super) fn on_file_changed(&mut self, uri: Url) {
let uri = CanonicalUri::from_url(&uri);
self.docs.change_file_by_uri(uri);
}

pub(super) fn on_file_deleted(&mut self, uri: Url) {
let uri = CanonicalUri::from_url(&uri);
self.docs.close_file_by_uri(uri);
}

pub(super) fn code_action(
&mut self,
uri: Url,
Expand Down
36 changes: 36 additions & 0 deletions hsp3-analyzer-mini/ham-core/src/lang_service/docs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,29 @@ impl Docs {
}
}

pub(crate) fn change_file_by_uri(&mut self, uri: CanonicalUri) -> Option<DocId> {
let path = uri.to_file_path()?;
let (created, doc) = self.touch_uri(uri);

let open_in_editor = !created && self.editor_docs.contains(&doc);
if open_in_editor {
#[cfg(trace_docs)]
trace!("ファイルは開かれているのでロードされません。");
return Some(doc);
}

let lang = Lang::from_path(&path)?;
let origin = DocChangeOrigin::Path(path.to_path_buf());
let opened = self.file_docs.insert(doc);
if opened {
self.do_open_doc(doc, NO_VERSION, lang, origin);
} else {
self.do_change_doc(doc, NO_VERSION, lang, origin);
}

Some(doc)
}

pub(crate) fn change_file(&mut self, path: &Path) -> Option<DocId> {
let uri = match CanonicalUri::from_file_path(path) {
Some(uri) => uri,
Expand Down Expand Up @@ -242,6 +265,19 @@ impl Docs {
Some(doc)
}

pub(crate) fn close_file_by_uri(&mut self, uri: CanonicalUri) {
let doc = match self.uri_to_doc.get(&uri) {
Some(&doc) => doc,
None => return,
};

self.file_docs.remove(&doc);

if !self.editor_docs.contains(&doc) {
self.do_close_doc(doc, &uri);
}
}

pub(crate) fn close_file(&mut self, path: &Path) {
let uri = match CanonicalUri::from_file_path(path) {
Some(uri) => uri,
Expand Down
2 changes: 1 addition & 1 deletion hsp3-analyzer-mini/ham-core/src/lsp_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ pub(super) struct LspNotification<Params> {
/// just for deserialization.
#[derive(Deserialize)]
pub(super) struct LspMessageOpaque {
pub(crate) method: String,
pub(crate) method: Option<String>,
pub(crate) id: Option<Value>,
}

Expand Down
65 changes: 64 additions & 1 deletion hsp3-analyzer-mini/ham-core/src/lsp_server/lsp_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,19 @@ impl<W: io::Write> LspHandler<W> {
}

fn initialize<'a>(&'a mut self, params: InitializeParams) -> InitializeResult {
let watchable = params
.capabilities
.workspace
.and_then(|x| x.did_change_watched_files)
.and_then(|x| x.dynamic_registration)
.unwrap_or(false);

self.model.initialize(params.root_uri);

if watchable {
self.model.set_watchable(true);
}

InitializeResult {
capabilities: ServerCapabilities {
text_document_sync: Some(TextDocumentSyncCapability::Options(
Expand Down Expand Up @@ -64,6 +75,29 @@ impl<W: io::Write> LspHandler<W> {

fn did_initialize(&mut self) {
self.model.did_initialize();

self.sender.send_request(
// 他のリクエストを送らないので id=1 しか使わない。
1,
"client/registerCapability",
RegistrationParams {
registrations: vec![Registration {
id: "1".into(),
method: "workspace/didChangeWatchedFiles".into(),
register_options: Some(
serde_json::to_value(DidChangeWatchedFilesRegistrationOptions {
watchers: vec![FileSystemWatcher {
kind: Some(
WatchKind::Create | WatchKind::Change | WatchKind::Delete,
),
glob_pattern: "**/*.hsp".into(),
}],
})
.unwrap(),
),
}],
},
);
}

fn shutdown(&mut self) {
Expand Down Expand Up @@ -191,6 +225,16 @@ impl<W: io::Write> LspHandler<W> {
self.model.signature_help(uri, position)
}

fn workspace_did_change_watched_files(&mut self, params: DidChangeWatchedFilesParams) {
for param in params.changes {
match param.typ {
FileChangeType::Created => self.model.on_file_created(param.uri),
FileChangeType::Changed => self.model.on_file_changed(param.uri),
FileChangeType::Deleted => self.model.on_file_deleted(param.uri),
}
}
}

fn workspace_symbol(&mut self, params: WorkspaceSymbolParams) -> Vec<SymbolInformation> {
self.model.workspace_symbol(params.query)
}
Expand All @@ -213,7 +257,20 @@ impl<W: io::Write> LspHandler<W> {
fn did_receive(&mut self, json: &str) {
let msg = serde_json::from_str::<LspMessageOpaque>(json).unwrap();

match msg.method.as_str() {
let method = match msg.method {
Some(it) => it,

// registerCapabilityのレスポンス。
None if json.contains("\"result\"") && !json.contains("\"error\"") => return,

None => {
// TODO: エラー処理?
warn!("no method: {}", json);
return;
}
};

match method.as_str() {
"initialize" => {
let msg = serde_json::from_str::<LspRequest<InitializeParams>>(json).unwrap();
let (params, msg_id) = (msg.params, msg.id);
Expand Down Expand Up @@ -331,6 +388,12 @@ impl<W: io::Write> LspHandler<W> {
let response = self.text_document_signature_help(msg.params);
self.sender.send_response(msg_id, response);
}
"workspace/didChangeWatchedFiles" => {
let msg: LspNotification<DidChangeWatchedFilesParams> =
serde_json::from_str(json).expect("workspace/didChangeWatchedFiles msg");
self.workspace_did_change_watched_files(msg.params);
self.diagnose();
}
request::WorkspaceSymbol::METHOD => {
let msg: LspRequest<WorkspaceSymbolParams> =
serde_json::from_str(json).expect("workspace/symbol msg");
Expand Down
18 changes: 17 additions & 1 deletion hsp3-analyzer-mini/ham-core/src/lsp_server/lsp_sender.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{LspError, LspErrorResponse, LspNotification, LspResponse};
use super::{LspError, LspErrorResponse, LspNotification, LspRequest, LspResponse};
use serde_json::Value;
use std::io::{self, Write as _};

Expand Down Expand Up @@ -36,6 +36,22 @@ impl<W: io::Write> LspSender<W> {
);
}

pub(crate) fn send_request<P: serde::Serialize>(&mut self, id: i64, method: &str, params: P) {
let mut buf = Vec::new();
serde_json::to_writer(
&mut buf,
&LspRequest::<P> {
jsonrpc: "2.0".to_string(),
id,
method: method.to_string(),
params,
},
)
.unwrap();

self.do_send(&buf);
}

pub(crate) fn send_notification<P: serde::Serialize>(&mut self, method: &str, params: P) {
let mut buf = Vec::new();
serde_json::to_writer(
Expand Down
3 changes: 2 additions & 1 deletion hsp3-analyzer-mini/vscode-ext/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ const newLspClient = (lspBin: string): LanguageClient => {
{ scheme: "file", language: "hsp3" },
],
synchronize: {
fileEvents: workspace.createFileSystemWatcher("**/.clientrc"),
// `workspace/didChangeWatchedFiles` のための監視対象
fileEvents: workspace.createFileSystemWatcher("**/*.hsp"),
},
}

Expand Down

0 comments on commit b25630e

Please sign in to comment.