8000 feat: support schema cache. by ya7010 · Pull Request #641 · tombi-toml/tombi · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

feat: support schema cache. #641

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jul 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Cargo.lock

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

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ anyhow = "1.0.98"
async-trait = "0.1.88"
bytes = "1.10.1"
chrono = { version = "0.4.41", features = ["serde"] }
clap = { version = "4.5.37", features = ["derive", "string"] }
clap = { version = "4.5.37", features = ["derive", "env", "string"] }
clap-verbosity-flag = "3.0.2"
compact_str = "0.9.0"
console_error_panic_hook = "0.1.7"
Expand Down Expand Up @@ -77,9 +77,10 @@ tempfile = { version = "3.15.0" }
textwrap = { version = "0.16.2" }
thiserror = { version = "2.0.12" }
time = { version = "0.3.36" }
tokio = { version = "1.45.0", default-features = false, features = ["rt"] }
tokio = { version = "1.45.0", default-features = false }
tombi-ast = { path = "crates/tombi-ast" }
tombi-ast-editor = { path = "crates/tombi-ast-editor" }
tombi-cache = { path = "crates/tombi-cache" }
tombi-config = { path = "crates/tombi-config" }
tombi-date-time = { path = "crates/tombi-date-time" }
tombi-diagnostic = { path = "crates/tombi-diagnostic" }
Expand Down
14 changes: 14 additions & 0 deletions crates/tombi-cache/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "tombi-cache"
version.workspace = true
authors.workspace = true
edition.workspace = true
repository.workspace = true
license.workspace = true

[dependencies]
dirs.workspace = true
thiserror.workspace = true
tokio.workspace = true
tracing.workspace = true
url.workspace = true
39 changes: 39 additions & 0 deletions crates/tombi-cache/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use std::path::PathBuf;

#[derive(thiserror::Error, Debug, Clone)]
pub enum Error {
#[error("cache file read failed: {cache_file_path}, reason: {reason}")]
CacheFileReadFailed {
cache_file_path: PathBuf,
reason: String,
},

#[error("cache file parent directory not found: {cache_file_path}")]
CacheFileParentDirectoryNotFound { cache_file_path: PathBuf },

#[error("failed to save to cache: {cache_file_path}, reason: {reason}")]
CacheFileSaveFailed {
cache_file_path: PathBuf,
reason: String,
},

#[error("failed to remove cache directory: {cache_dir_path}, reason: {reason}")]
CacheDirectoryRemoveFailed {
cache_dir_path: PathBuf,
reason: String,
},
}

impl Error {
#[inline]
pub fn code(&self) -> &'static str {
match self {
Self::CacheFileReadFailed { .. } => "cache-file-read-failed",
Self::CacheFileParentDirectoryNotFound { .. } => {
"cache-file-parent-directory-not-found"
}
Self::CacheFileSaveFailed { .. } => "cache-file-save-failed",
Self::CacheDirectoryRemoveFailed { .. } => "cache-directory-remove-failed",
}
}
}
143 changes: 143 additions & 0 deletions crates/tombi-cache/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
mod error;
mod options;
pub use error::Error;
pub use options::{Options, DEFAULT_CACHE_TTL};

pub async fn get_tombi_cache_dir_path() -> Option<std::path::PathBuf> {
if let Ok(xdg_cache_home) = std::env::var("XDG_CACHE_HOME") {
let mut cache_dir_path = std::path::PathBuf::from(xdg_cache_home);
cache_dir_path.push("tombi");

if !cache_dir_path.is_dir() {
if let Err(error) = tokio::fs::create_dir_all(&cache_dir_path).await {
tracing::error!("Failed to create cache directory: {error}");
return None;
}
}
return Some(cache_dir_path);
}

if let Some(home_dir) = dirs::home_dir() {
let mut cache_dir_path = home_dir.clone();
cache_dir_path.push(".cache");
cache_dir_path.push("tombi");
if !cache_dir_path.is_dir() {
if let Err(error) = std::fs::create_dir_all(&cache_dir_path) {
tracing::error!("Failed to create cache directory: {error}");
return None;
}
}
return Some(cache_dir_path);
}

None
}

pub async fn get_cache_file_path(cache_file_url: &url::Url) -> Option<std::path::PathBuf> {
get_tombi_cache_dir_path().await.map(|mut dir_path| {
dir_path.push(cache_file_url.scheme());
if let Some(host) = cache_file_url.host() {
dir_path.push(host.to_string());
}
if let Some(path_segments) = cache_file_url.path_segments() {
for segment in path_segments {
dir_path.push(segment)
}
}

dir_path
})
}

pub async fn read_from_cache(
cache_file_path: Option<&std::path::Path>,
options: Option<&Options>,
) -> Result<Option<String>, crate::Error> {
if options
.and_then(|options| options.no_cache)
.unwrap_or_default()
{
return Ok(None);
}

if let Some(cache_file_path) = cache_file_path {
if cache_file_path.is_file() {
if let Some(ttl) = options.and_then(|options| options.cache_ttl) {
let Ok(metadata) = tokio::fs::metadata(cache_file_path).await else {
return Ok(None);
};
if let Ok(modified) = metadata.modified() {
if let Ok(elapsed) = modified.elapsed() {
if elapsed > ttl {
return Ok(None);
}
}
}
}
return Ok(Some(
tokio::fs::read_to_string(&cache_file_path)
.await
.map_err(|err| crate::Error::CacheFileReadFailed {
cache_file_path: cache_file_path.to_path_buf(),
reason: err.to_string(),
})?,
));
}
}

Ok(None)
}

pub async fn save_to_cache(
cache_file_path: Option<&std::path::Path>,
bytes: &[u8],
) -> Result<(), crate::Error> {
if let Some(cache_file_path) = cache_file_path {
if !cache_file_path.is_file() {
let Some(cache_dir_path) = cache_file_path.parent() else {
return Err(crate::Error::CacheFileParentDirectoryNotFound {
cache_file_path: cache_file_path.to_owned(),
});
};

if let Err(err) = tokio::fs::create_dir_all(cache_dir_path).await {
return Err(crate::Error::CacheFileSaveFailed {
cache_file_path: cache_file_path.to_owned(),
reason: err.to_string(),
});
}
}
if let Err(err) = tokio::fs::write(cache_file_path, &bytes).await {
return Err(crate::Error::CacheFileSaveFailed {
cache_file_path: cache_file_path.to_owned(),
reason: err.to_string(),
});
}
}

Ok(())
}

pub async fn refresh_cache() -> Result<bool, crate::Error> {
if let Some(cache_dir_path) = get_tombi_cache_dir_path().await {
// Remove all contents of the cache directory but keep the directory itself
if let Ok(mut entries) = tokio::fs::read_dir(&cache_dir_path).await {
while let Ok(Some(entry)) = entries.next_entry().await {
if let Ok(file_type) = entry.file_type().await {
if file_type.is_dir() {
let path = entry.path();
if let Err(err) = tokio::fs::remove_dir_all(&path).await {
return Err(crate::Error::CacheDirectoryRemoveFailed {
cache_dir_path: path,
reason: err.to_string(),
});
}
}
}
}
}
return Ok(true);
}

Ok(false)
}
16 changes: 16 additions & 0 deletions crates/tombi-cache/src/options.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#[derive(Debug, Clone)]
pub struct Options {
pub no_cache: Option<bool>,
pub cache_ttl: Option<std::time::Duration>,
}

impl Default for Options {
fn default() -> Self {
Self {
no_cache: None,
cache_ttl: Some(DEFAULT_CACHE_TTL),
}
}
}

pub const DEFAULT_CACHE_TTL: std::time::Duration = std::time::Duration::from_secs(60 * 60 * 24);
3 changes: 2 additions & 1 deletion crates/tombi-lsp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ serde_tombi.workspace = true
thiserror.workspace = true
tokio = { workspace = true, features = ["fs", "io-std", "rt-multi-thread"] }
tombi-ast.workspace = true
tombi-cache.workspace = true
tombi-config.workspace = true
tombi-date-time.workspace = true
tombi-diagnostic.workspace = true
Expand Down Expand Up @@ -51,7 +52,7 @@ pretty_assertions.workspace = true
rstest.workspace = true
tempfile.workspace = true
textwrap.workspace = true
tokio = { workspace = true, features = ["macros"] }
tokio = { workspace = true, features = ["fs", "macros", "rt"] }
tombi-test-lib.workspace = true

[features]
Expand Down
18 changes: 16 additions & 2 deletions crates/tombi-lsp/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ use crate::{
handle_did_close, handle_did_open, handle_did_save, handle_document_link,
handle_document_symbol, handle_folding_range, handle_formatting, handle_get_toml_version,
handle_goto_declaration, handle_goto_definition, handle_goto_type_definition, handle_hover,
handle_initialize, handle_initialized, handle_semantic_tokens_full, handle_shutdown,
handle_update_config, handle_update_schema, AssociateSchemaParams, GetTomlVersionResponse,
handle_initialize, handle_initialized, handle_refresh_cache, handle_semantic_tokens_full,
handle_shutdown, handle_update_config, handle_update_schema, AssociateSchemaParams,
GetTomlVersionResponse, RefreshCacheParams,
},
};

Expand All @@ -52,6 +53,7 @@ pub struct Backend {
#[derive(Debug, Clone, Default)]
pub struct Options {
pub offline: Option<bool>,
pub no_cache: Option<bool>,
}

impl Backend {
Expand All @@ -68,6 +70,10 @@ impl Backend {
let options = tombi_schema_store::Options {
offline: options.offline,
strict: config.schema.as_ref().and_then(|schema| schema.strict()),
cache: Some(tombi_cache::Options {
no_cache: options.no_cache,
..Default::default()
}),
};

Self {
Expand Down Expand Up @@ -364,4 +370,12 @@ impl Backend {
pub async fn associate_schema(&self, params: AssociateSchemaParams) {
handle_associate_schema(self, params).await
}

#[inline]
pub async fn refresh_cache(
&self,
params: RefreshCacheParams,
) -> Result<bool, tower_lsp::jsonrpc::Error> {
handle_refresh_cache(self, params).await
}
}
2 changes: 2 additions & 0 deletions crates/tombi-lsp/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ mod goto_type_definition;
mod hover;
mod initialize;
mod initialized;
mod refresh_cache;
mod semantic_tokens_full;
mod shutdown;
mod update_config;
Expand All @@ -45,6 +46,7 @@ pub use goto_type_definition::handle_goto_type_definition;
pub use hover::handle_hover;
pub use initialize::handle_initialize;
pub use initialized::handle_initialized;
pub use refresh_cache::{handle_refresh_cache, RefreshCacheParams};
pub use semantic_tokens_full::handle_semantic_tokens_full;
pub use shutdown::handle_shutdown;
pub use update_config::handle_update_config;
Expand Down
37 changes: 37 additions & 0 deletions crates/tombi-lsp/src/handler/refresh_cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use std::borrow::Cow;

use crate::Backend;

#[derive(Debug, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RefreshCacheParams {}

pub async fn handle_refresh_cache(
backend: &Backend,
_params: RefreshCacheParams,
) -> Result<bool, tower_lsp::jsonrpc::Error> {
tracing::info!("handle_refresh_cache");

match backend
.schema_store
.refresh_cache(&backend.config().await, backend.config_path.as_deref())
.await
{
Ok(true) => {
tracing::info!("Cache refreshed");
Ok(true)
}
Ok(false) => {
tracing::info!("No cache to refresh");
Ok(false)
}
Err(err) => {
tracing::error!("Failed to refresh cache: {err}");
Err(tower_lsp::jsonrpc::Error {
code: tower_lsp::jsonrpc::ErrorCode::InternalError,
message: Cow::Owned(format!("Failed to refresh cache: {err}")),
data: None,
})
}
}
}
Loading
Loading