///|
/// Monaco-shaped language selector filter.
struct LanguageFilter {
language : String?
scheme : String?
pattern : String?
} derive(Debug)
///|
/// Monaco-shaped language selector used during provider dispatch.
pub(all) enum LanguageSelector {
LanguageId(String)
LanguageFilter(LanguageFilter)
LanguageSelectorList(Array[LanguageSelector])
} derive(Debug)
///|
/// Builds a selector filter with optional fields.
fn LanguageFilter::new_impl(
language? : String,
scheme? : String,
pattern? : String,
) -> LanguageFilter {
{ language, scheme, pattern, }
}
///|
/// Builds a selector filter with optional language, scheme, and path pattern.
pub fn LanguageFilter::LanguageFilter(
language? : String,
scheme? : String,
pattern? : String,
) -> LanguageFilter {
LanguageFilter::new_impl(language?, scheme?, pattern?)
}
///|
/// Whether this selector accepts a text model.
fn LanguageSelector::matches_impl(
self : LanguageSelector,
model : @model.TextModel,
) -> Bool {
self.score(model) > 0
}
///|
/// Monaco-compatible priority for the selector surface represented locally.
/// Exact language/scheme/pattern matches score 10, wildcard language/scheme
/// matches score 5, selector lists take their highest member, and a filter
/// without any constraining field does not select a model.
pub fn LanguageSelector::score(
self : LanguageSelector,
model : @model.TextModel,
) -> Int32 {
match self {
LanguageId("*") => 5
LanguageId(language_id) =>
if model.language_id == language_id {
10
} else {
0
}
LanguageFilter(filter) => filter.score(model)
LanguageSelectorList(selectors) =>
for selector in selectors; result = 0 {
let score = selector.score(model)
if score == 10 {
break 10
}
if score > result {
continue score
}
} nobreak {
result
}
}
}
///|
/// Whether this filter accepts a text model.
fn LanguageFilter::matches_impl(
self : LanguageFilter,
model : @model.TextModel,
) -> Bool {
self.score(model) > 0
}
///|
/// Scores the language, scheme, and path-pattern subset of Monaco's
/// `LanguageFilter`.
fn LanguageFilter::score_impl(
self : LanguageFilter,
model : @model.TextModel,
) -> Int32 {
let mut result = 0
if self.language is Some(language) {
if language == model.language_id {
result = 10
} else if language == "*" {
result = 5
} else {
return 0
}
}
if self.scheme is Some(scheme) {
if scheme == model.uri.scheme {
result = 10
} else if scheme == "*" {
if result < 5 {
result = 5
}
} else {
return 0
}
}
// Monaco's `score()` matches globs against `uri.path` (leading slash
// included); `path_pattern_matches` strips that slash so the relative
// patterns hosts register keep matching.
if self.pattern is Some(pattern) {
if path_pattern_matches(pattern, model.uri.path) {
result = 10
} else {
return 0
}
}
result
}
///|
fn path_pattern_matches(pattern : String, path : String) -> Bool {
if pattern == "" {
return false
}
if pattern == "*" {
return true
}
let path = if path.strip_prefix("/") is Some(rest) { rest } else { path }
match pattern.split_once("*") {
Some((prefix, suffix)) => path.has_prefix(prefix) && path.has_suffix(suffix)
None => path == pattern
}
}
///|
pub extend LanguageFilter with Debug::{to_repr}
///|
pub extend LanguageSelector with Debug::{to_repr}