use crate::{
LspQueryReadView, LspStyleDocumentSummary, LspStyleHoverCandidate, LspTextDocumentState,
build_source_syntax_index, collect_source_imports, collect_style_hover_candidates,
protocol::{
byte_offset_for_parser_position, file_uri_to_path, is_style_document_uri,
parser_range_for_byte_span,
},
source_selector_candidates_from_index,
state::LspCascadeNarrowingSubstrateMemo,
summarize_style_document,
};
#[cfg(not(feature = "salsa-style-diagnostics"))]
use omena_query::collect_omena_query_style_cascade_narrowing_substrate_with_external_sifs;
use omena_query::{
OmenaQueryStyleCascadeNarrowingSubstrateV0, OmenaQueryStyleResolutionInputsV0,
OmenaQueryStyleSourceInputV0, ParserByteSpanV0, append_omena_query_utility_class_intelligence,
load_omena_query_workspace_utility_class_intelligence,
};
use omena_sif::compute_omena_sif_leaf_hash_v1;
use serde::Serialize;
use std::sync::Arc;
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RustQueryReuseBoundaryV0 {
pub product: &'static str,
pub owner: &'static str,
pub reuse_model: &'static str,
pub cached_surfaces: Vec<&'static str>,
pub invalidation_policy: Vec<&'static str>,
pub request_path_policy: Vec<&'static str>,
}
pub fn rust_query_reuse_contract() -> RustQueryReuseBoundaryV0 {
RustQueryReuseBoundaryV0 {
product: "omena-lsp-server.query-reuse",
owner: "omena-lsp-server/documentQueryReuse",
reuse_model: "documentRevisionOwnedReusableIndexes",
cached_surfaces: vec![
"workspaceStyleResolutionInputs",
"styleDocumentSummary",
"styleHoverCandidates",
"optimizingTierFeedback",
"sourceSyntaxIndex",
"sourceSelectorCandidates",
"sourceTypeFactCache",
"sourceDocumentIndexSidecar",
"sourceSelectorOccurrenceSidecar",
"cascadeNarrowingSubstrate",
"visibleSassSymbolCompletionSubstrate",
],
invalidation_policy: vec![
"refreshOnDocumentOpen",
"refreshOnDocumentContentChange",
"refreshOnWorkspaceFileReload",
"refreshOnResolutionConfigChange",
"refreshOnResolutionSettingsChange",
"rebuildCascadeNarrowingSubstrateOnInputContentMismatch",
"rebuildSourceTypeFactCacheOnContentConfigOrWorkspaceSourceMismatch",
"rebuildSourceDocumentIndexSidecarOnTextResolutionOrLanguageMismatch",
"rebuildSourceSelectorOccurrenceSidecarOnDocumentKeyMismatch",
],
request_path_policy: vec![
"noPackageManifestOrConfigReadOnProviderRequest",
"noRawSourceRescanOnProviderRequest",
"noStyleSelectorRescanOnProviderRequest",
"typeFactRefreshConsumesCacheBeforeTsgoTransport",
"providerRequestsConsumeDocumentIndexes",
],
}
}
pub(crate) fn cascade_narrowing_substrate_for_style_sources(
state: &dyn LspQueryReadView,
style_sources: &[OmenaQueryStyleSourceInputV0],
resolution_inputs: &OmenaQueryStyleResolutionInputsV0,
) -> Arc<OmenaQueryStyleCascadeNarrowingSubstrateV0> {
let package_manifests = effective_style_package_manifests(state, resolution_inputs);
let external_sifs = state.query_resolution().external_sifs.as_slice();
{
let memo = state.cascade_narrowing_substrate_memo_lock();
if let Some(memo) = memo.as_ref()
&& memo.style_sources.as_slice() == style_sources
&& memo.package_manifests == package_manifests
&& memo.external_sifs.as_slice() == external_sifs
&& &memo.resolution_inputs == resolution_inputs
{
return Arc::clone(&memo.substrate);
}
}
let substrate = Arc::new(collect_cascade_narrowing_substrate(
style_sources,
package_manifests.as_slice(),
external_sifs,
resolution_inputs,
));
*state.cascade_narrowing_substrate_memo_lock() = Some(LspCascadeNarrowingSubstrateMemo {
style_sources: style_sources.to_vec(),
package_manifests,
external_sifs: external_sifs.to_vec(),
resolution_inputs: resolution_inputs.clone(),
substrate: Arc::clone(&substrate),
});
substrate
}
fn collect_cascade_narrowing_substrate(
style_sources: &[OmenaQueryStyleSourceInputV0],
package_manifests: &[omena_query::OmenaQueryStylePackageManifestV0],
external_sifs: &[omena_query::OmenaQueryExternalSifInputV0],
resolution_inputs: &OmenaQueryStyleResolutionInputsV0,
) -> OmenaQueryStyleCascadeNarrowingSubstrateV0 {
omena_query::collect_omena_query_style_cascade_narrowing_substrate_with_external_sifs(
style_sources,
package_manifests,
external_sifs,
resolution_inputs,
)
}
pub(crate) fn effective_style_package_manifests(
state: &dyn LspQueryReadView,
resolution_inputs: &OmenaQueryStyleResolutionInputsV0,
) -> Vec<omena_query::OmenaQueryStylePackageManifestV0> {
let mut package_manifests = state.query_resolution().package_manifests.clone();
package_manifests.extend(resolution_inputs.package_manifests.clone());
package_manifests.sort_by(|left, right| {
(
left.package_json_path.as_str(),
left.package_json_source.as_str(),
)
.cmp(&(
right.package_json_path.as_str(),
right.package_json_source.as_str(),
))
});
package_manifests.dedup();
package_manifests
}
pub(crate) fn refresh_document_reusable_indexes(
document: &mut LspTextDocumentState,
resolution_inputs: &OmenaQueryStyleResolutionInputsV0,
) {
document.optimizing_tier_feedback = None;
document.text_hash = compute_omena_sif_leaf_hash_v1(document.text.as_bytes())
.as_str()
.to_string();
if is_style_document_uri(document.uri.as_str()) {
document.style_summary =
summarize_style_document(document.uri.as_str(), Some(document.text.as_str()));
document.style_candidates =
collect_style_hover_candidates(document.uri.as_str(), document.text.as_str())
.map(|(_, candidates)| candidates)
.unwrap_or_default();
} else if let Some((summary, candidates)) = collect_vue_embedded_module_style_indexes(document)
{
document.style_summary = Some(summary);
document.style_candidates = candidates;
} else {
document.style_summary = None;
document.style_candidates = Vec::new();
}
let mut source_syntax_index = build_source_syntax_index(document, resolution_inputs);
if !is_style_document_uri(document.uri.as_str())
&& let Some(workspace_root) = document
.workspace_folder_uri
.as_deref()
.and_then(file_uri_to_path)
{
let utility_intelligence =
load_omena_query_workspace_utility_class_intelligence(workspace_root.as_path(), None);
append_omena_query_utility_class_intelligence(
&mut source_syntax_index,
document.text.as_str(),
&utility_intelligence,
);
}
document.has_unresolved_style_import =
collect_source_imports(document, resolution_inputs).has_unresolved_style_import;
document.source_selector_candidates =
source_selector_candidates_from_index(document, &source_syntax_index);
document.source_syntax_index = source_syntax_index;
document.source_type_fact_selector_references.clear();
}
fn collect_vue_embedded_module_style_indexes(
document: &LspTextDocumentState,
) -> Option<(LspStyleDocumentSummary, Vec<LspStyleHoverCandidate>)> {
let embedded = embedded_vue_module_style(document)?;
let summary =
summarize_style_document(embedded.virtual_uri.as_str(), Some(embedded.style_source))?;
let (_, candidates) =
collect_style_hover_candidates(embedded.virtual_uri.as_str(), embedded.style_source)?;
let candidates = candidates
.into_iter()
.filter_map(|mut candidate| {
candidate.range = embedded_range_to_document_range(
document.text.as_str(),
embedded.style_source,
embedded.content_start,
candidate.range,
)?;
Some(candidate)
})
.collect();
Some((summary, candidates))
}
struct EmbeddedVueModuleStyle<'a> {
virtual_uri: String,
style_source: &'a str,
content_start: usize,
}
fn embedded_vue_module_style(
document: &LspTextDocumentState,
) -> Option<EmbeddedVueModuleStyle<'_>> {
if document.language_id != "vue" && !document.uri.ends_with(".vue") {
return None;
}
let lower = document.text.to_ascii_lowercase();
let mut cursor = 0usize;
while let Some(relative_start) = lower[cursor..].find("<style") {
let tag_start = cursor + relative_start;
let relative_tag_end = lower[tag_start..].find('>')?;
let tag_end = tag_start + relative_tag_end + 1;
let tag = &lower[tag_start..tag_end];
let close_start = lower[tag_end..].find("</style>")? + tag_end;
let content_start = tag_end;
let content_end = close_start;
if tag.contains("module") {
return Some(EmbeddedVueModuleStyle {
virtual_uri: format!(
"{}{}",
document.uri,
vue_embedded_style_virtual_extension(tag)
),
style_source: &document.text[content_start..content_end],
content_start,
});
}
cursor = close_start + "</style>".len();
}
None
}
fn vue_embedded_style_virtual_extension(tag: &str) -> &'static str {
if tag.contains("lang=\"scss\"")
|| tag.contains("lang='scss'")
|| tag.contains("lang=scss")
|| tag.contains("lang=\"sass\"")
|| tag.contains("lang='sass'")
|| tag.contains("lang=sass")
{
".module.scss"
} else if tag.contains("lang=\"less\"")
|| tag.contains("lang='less'")
|| tag.contains("lang=less")
{
".module.less"
} else {
".module.css"
}
}
fn embedded_range_to_document_range(
document_source: &str,
embedded_source: &str,
content_start: usize,
range: omena_query::ParserRangeV0,
) -> Option<omena_query::ParserRangeV0> {
let start = content_start + byte_offset_for_parser_position(embedded_source, range.start)?;
let end = content_start + byte_offset_for_parser_position(embedded_source, range.end)?;
Some(parser_range_for_byte_span(
document_source,
ParserByteSpanV0 { start, end },
))
}