Skip to content

Commit

Permalink
feat: lsp file watcher. (#1744)
Browse files Browse the repository at this point in the history
* feat: lsp file watcher. Actively monitor file system changes. These changes will not be notified through lsp, e.g., execute `kcl mod add xxx`, `kcl fmt xxx`

Signed-off-by: he1pa <[email protected]>

* s/FsEventWatcher/RecommendedWatcher

Signed-off-by: he1pa <[email protected]>

* add lsp watcher ut

Signed-off-by: he1pa <[email protected]>

* fix typo

Signed-off-by: he1pa <[email protected]>

* fix ut

Signed-off-by: he1pa <[email protected]>

---------

Signed-off-by: he1pa <[email protected]>
  • Loading branch information
He1pa authored Nov 8, 2024
1 parent 841d614 commit ddfd018
Show file tree
Hide file tree
Showing 17 changed files with 431 additions and 79 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ubuntu_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ jobs:
go install kcl-lang.io/cli/cmd/kcl@main
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
echo "${{ github.workspace }}/go/bin" >> $GITHUB_PATH
- name: Unit test
working-directory: ./kclvm
run: export PATH=$PATH:$PWD/../_build/dist/linux/kclvm/bin && make test
shell: bash

37 changes: 29 additions & 8 deletions kclvm/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion kclvm/driver/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ edition = "2021"

[dependencies]
serde_json = "1.0.86"
notify = "6.1.1"

kclvm-config ={ path = "../config"}
kclvm-runtime ={ path = "../runtime"}
Expand Down
1 change: 1 addition & 0 deletions kclvm/sema/src/resolver/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ impl<'ctx> Resolver<'ctx> {
let real_path =
Path::new(&self.program.root).join(pkgpath.replace('.', "/"));
if !self.program.pkgs.contains_key(pkgpath) {
self.ctx.invalid_pkg_scope.insert(pkgpath.to_string());
if real_path.exists() {
self.handler.add_error(
ErrorKind::CannotFindModule,
Expand Down
10 changes: 8 additions & 2 deletions kclvm/sema/src/resolver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ mod var;
#[cfg(test)]
mod tests;

use indexmap::IndexMap;
use indexmap::{IndexMap, IndexSet};
use kclvm_error::diagnostic::Range;
use std::sync::Arc;
use std::{cell::RefCell, rc::Rc};
Expand Down Expand Up @@ -99,8 +99,12 @@ impl<'ctx> Resolver<'ctx> {

pub(crate) fn check_and_lint(&mut self, pkgpath: &str) -> ProgramScope {
self.check(pkgpath);
let mut scope_map = self.scope_map.clone();
for invalid_pkg_scope in &self.ctx.invalid_pkg_scope {
scope_map.remove(invalid_pkg_scope);
}
let mut scope = ProgramScope {
scope_map: self.scope_map.clone(),
scope_map,
import_names: self.ctx.import_names.clone(),
node_ty_map: self.node_ty_map.clone(),
handler: self.handler.clone(),
Expand Down Expand Up @@ -145,6 +149,8 @@ pub struct Context {
pub ty_ctx: TypeContext,
/// Type alias mapping
pub type_alias_mapping: IndexMap<String, IndexMap<String, String>>,
/// invalid pkg scope, remove when after resolve
pub invalid_pkg_scope: IndexSet<String>,
}

/// Resolve options.
Expand Down
3 changes: 2 additions & 1 deletion kclvm/tools/src/LSP/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,14 @@ anyhow = { version = "1.0", default-features = false, features = ["std"] }
crossbeam-channel = { version = "0.5.7", default-features = false }
ra_ap_vfs = "0.0.149"
ra_ap_vfs-notify = "0.0.149"
lsp-types = { version = "0.93.0", features = ["proposed"]}
lsp-types = { version = "0.93.0", features = ["proposed"] }
threadpool = { version = "1.8.1", default-features = false }
salsa = { version = "0.16.1", default-features = false }
serde_json = { version = "1.0", default-features = false }
parking_lot = { version = "0.12.0", default-features = false }
rustc-hash = { version = "1.1.0", default-features = false }
proc_macro_crate = { path = "../../benches/proc_macro_crate" }
notify = "7.0.0"

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { version = "1.37.0", features = ["full"] }
Expand Down
26 changes: 1 addition & 25 deletions kclvm/tools/src/LSP/src/notification.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
use kclvm_config::{
modfile::{KCL_MOD_FILE, KCL_WORK_FILE},
settings::DEFAULT_SETTING_FILE,
};
use kclvm_driver::lookup_compile_workspaces;
use lsp_types::notification::{
Cancel, DidChangeTextDocument, DidChangeWatchedFiles, DidCloseTextDocument,
DidOpenTextDocument, DidSaveTextDocument,
};
use std::{collections::HashSet, sync::Arc};
use std::collections::HashSet;

use crate::util::apply_document_changes;
use crate::{
Expand Down Expand Up @@ -137,27 +132,8 @@ impl LanguageServerState {
for change in params.changes {
let path = from_lsp::abs_path(&change.uri)?;
self.loader.handle.invalidate(path.clone());
if KCL_CONFIG_FILE.contains(&path.file_name().unwrap().to_str().unwrap()) {
self.entry_cache.write().clear();
let parent_path = path.parent().unwrap();
let path = parent_path.as_os_str().to_str().unwrap().to_string();
let tool = Arc::clone(&self.tool);
let (workspaces, failed) = lookup_compile_workspaces(&*tool.read(), &path, true);

if let Some(failed) = failed {
for (key, err) in failed {
self.log_message(format!("parse kcl.work failed: {}: {}", key, err));
}
}

for (workspace, opts) in workspaces {
self.async_compile(workspace, opts, None, false);
}
}
}

Ok(())
}
}

const KCL_CONFIG_FILE: [&str; 3] = [DEFAULT_SETTING_FILE, KCL_MOD_FILE, KCL_WORK_FILE];
20 changes: 12 additions & 8 deletions kclvm/tools/src/LSP/src/request.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use anyhow::anyhow;
use crossbeam_channel::Sender;

use kclvm_driver::WorkSpaceKind;
use kclvm_sema::info::is_valid_kcl_name;
use lsp_types::{Location, SemanticTokensResult, TextEdit};
use ra_ap_vfs::VfsPath;
Expand Down Expand Up @@ -97,7 +98,7 @@ impl LanguageServerSnapshot {
) -> anyhow::Result<Option<Arc<AnalysisDatabase>>> {
match self.try_get_db_state(path) {
Ok(db) => match db {
Some(db) => match db {
Some((_, db)) => match db {
DBState::Ready(db) => Ok(Some(db.clone())),
DBState::Compiling(_) | DBState::Init => {
log_message(
Expand All @@ -124,7 +125,10 @@ impl LanguageServerSnapshot {
/// return Ok(Some(db)) -> Compile completed
/// return Ok(None) -> RWlock, retry to unlock
/// return Err(_) -> Compile failed
pub(crate) fn try_get_db_state(&self, path: &VfsPath) -> anyhow::Result<Option<DBState>> {
pub(crate) fn try_get_db_state(
&self,
path: &VfsPath,
) -> anyhow::Result<Option<(WorkSpaceKind, DBState)>> {
match self.vfs.try_read() {
Some(vfs) => match vfs.file_id(path) {
Some(file_id) => {
Expand All @@ -134,7 +138,7 @@ impl LanguageServerSnapshot {
Some(option_workspace) => match option_workspace {
Some(work_space) => match self.workspaces.try_read() {
Some(workspaces) => match workspaces.get(work_space) {
Some(db) => Ok(Some(db.clone())),
Some(db) => Ok(Some((work_space.clone(), db.clone()))),
None => Err(anyhow::anyhow!(
LSPError::AnalysisDatabaseNotFound(path.clone())
)),
Expand All @@ -154,7 +158,7 @@ impl LanguageServerSnapshot {
let work_space = file_info.workspaces.iter().next().unwrap();
match self.workspaces.try_read() {
Some(workspaces) => match workspaces.get(work_space) {
Some(db) => Ok(Some(db.clone())),
Some(db) => Ok(Some((work_space.clone(), db.clone()))),
None => Err(anyhow::anyhow!(
LSPError::AnalysisDatabaseNotFound(path.clone())
)),
Expand Down Expand Up @@ -310,7 +314,7 @@ pub(crate) fn handle_completion(
return Ok(None);
}

let db_state = match snapshot.try_get_db_state(&path) {
let (workspace, db_state) = match snapshot.try_get_db_state(&path) {
Ok(option_state) => match option_state {
Some(db) => db,
None => return Err(anyhow!(LSPError::Retry)),
Expand Down Expand Up @@ -341,10 +345,10 @@ pub(crate) fn handle_completion(
let kcl_pos = kcl_pos(&file, params.text_document_position.position);

let metadata = snapshot
.entry_cache
.workspace_config_cache
.read()
.get(&file)
.and_then(|metadata| metadata.0 .2.clone());
.get(&workspace)
.and_then(|opt| opt.2.clone());

let res = completion(
completion_trigger_character,
Expand Down
Loading

0 comments on commit ddfd018

Please sign in to comment.