mirror of https://github.com/helix-editor/helix
feat: add function to get LanguageConfiguration from a LayerId
Co-authored-by: the-mikedavis <mcarsondavis@gmail.com>pull/12759/head
parent
48dec3ee8f
commit
70f27b390d
|
@ -15,7 +15,7 @@ use bitflags::bitflags;
|
|||
use globset::GlobSet;
|
||||
use hashbrown::raw::RawTable;
|
||||
use helix_stdx::rope::{self, RopeSliceExt};
|
||||
use slotmap::{DefaultKey as LayerId, HopSlotMap};
|
||||
use slotmap::{DefaultKey as LayerId, DefaultKey as LanguageId, HopSlotMap};
|
||||
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
|
@ -92,8 +92,10 @@ pub struct Configuration {
|
|||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||
pub struct LanguageConfiguration {
|
||||
#[serde(skip)]
|
||||
language_id: LanguageId,
|
||||
#[serde(rename = "name")]
|
||||
pub language_id: String, // c-sharp, rust, tsx
|
||||
pub language_name: String, // c-sharp, rust, tsx
|
||||
#[serde(rename = "language-id")]
|
||||
// see the table under https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentItem
|
||||
pub language_server_language_id: Option<String>, // csharp, rust, typescriptreact, for the language-server
|
||||
|
@ -743,32 +745,33 @@ pub fn read_query(language: &str, filename: &str) -> String {
|
|||
|
||||
impl LanguageConfiguration {
|
||||
fn initialize_highlight(&self, scopes: &[String]) -> Option<Arc<HighlightConfiguration>> {
|
||||
let highlights_query = read_query(&self.language_id, "highlights.scm");
|
||||
let highlights_query = read_query(&self.language_name, "highlights.scm");
|
||||
// always highlight syntax errors
|
||||
// highlights_query += "\n(ERROR) @error";
|
||||
|
||||
let injections_query = read_query(&self.language_id, "injections.scm");
|
||||
let locals_query = read_query(&self.language_id, "locals.scm");
|
||||
let injections_query = read_query(&self.language_name, "injections.scm");
|
||||
let locals_query = read_query(&self.language_name, "locals.scm");
|
||||
|
||||
if highlights_query.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let language = get_language(self.grammar.as_deref().unwrap_or(&self.language_id))
|
||||
let language = get_language(self.grammar.as_deref().unwrap_or(&self.language_name))
|
||||
.map_err(|err| {
|
||||
log::error!(
|
||||
"Failed to load tree-sitter parser for language {:?}: {:#}",
|
||||
self.language_id,
|
||||
self.language_name,
|
||||
err
|
||||
)
|
||||
})
|
||||
.ok()?;
|
||||
let config = HighlightConfiguration::new(
|
||||
self.language_id,
|
||||
language,
|
||||
&highlights_query,
|
||||
&injections_query,
|
||||
&locals_query,
|
||||
)
|
||||
.map_err(|err| log::error!("Could not parse queries for language {:?}. Are your grammars out of sync? Try running 'hx --grammar fetch' and 'hx --grammar build'. This query could not be parsed: {:?}", self.language_id, err))
|
||||
.map_err(|err| log::error!("Could not parse queries for language {:?}. Are your grammars out of sync? Try running 'hx --grammar fetch' and 'hx --grammar build'. This query could not be parsed: {:?}", self.language_name, err))
|
||||
.ok()?;
|
||||
|
||||
config.configure(scopes);
|
||||
|
@ -812,7 +815,7 @@ impl LanguageConfiguration {
|
|||
}
|
||||
|
||||
fn load_query(&self, kind: &str) -> Option<Query> {
|
||||
let query_text = read_query(&self.language_id, kind);
|
||||
let query_text = read_query(&self.language_name, kind);
|
||||
if query_text.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
@ -822,7 +825,7 @@ impl LanguageConfiguration {
|
|||
log::error!(
|
||||
"Failed to parse {} queries for {}: {}",
|
||||
kind,
|
||||
self.language_id,
|
||||
self.language_name,
|
||||
e
|
||||
)
|
||||
})
|
||||
|
@ -862,11 +865,11 @@ pub struct SoftWrap {
|
|||
#[derive(Debug)]
|
||||
struct FileTypeGlob {
|
||||
glob: globset::Glob,
|
||||
language_id: usize,
|
||||
language_id: LanguageId,
|
||||
}
|
||||
|
||||
impl FileTypeGlob {
|
||||
fn new(glob: globset::Glob, language_id: usize) -> Self {
|
||||
fn new(glob: globset::Glob, language_id: LanguageId) -> Self {
|
||||
Self { glob, language_id }
|
||||
}
|
||||
}
|
||||
|
@ -890,7 +893,7 @@ impl FileTypeGlobMatcher {
|
|||
})
|
||||
}
|
||||
|
||||
fn language_id_for_path(&self, path: &Path) -> Option<&usize> {
|
||||
fn language_id_for_path(&self, path: &Path) -> Option<&LanguageId> {
|
||||
self.matcher
|
||||
.matches(path)
|
||||
.iter()
|
||||
|
@ -905,10 +908,10 @@ impl FileTypeGlobMatcher {
|
|||
#[derive(Debug)]
|
||||
pub struct Loader {
|
||||
// highlight_names ?
|
||||
language_configs: Vec<Arc<LanguageConfiguration>>,
|
||||
language_config_ids_by_extension: HashMap<String, usize>, // Vec<usize>
|
||||
language_configs: HopSlotMap<LanguageId, Arc<LanguageConfiguration>>,
|
||||
language_config_ids_by_extension: HashMap<String, LanguageId>, // Vec<LanguageId>
|
||||
language_config_ids_glob_matcher: FileTypeGlobMatcher,
|
||||
language_config_ids_by_shebang: HashMap<String, usize>,
|
||||
language_config_ids_by_shebang: HashMap<String, LanguageId>,
|
||||
|
||||
language_server_configs: HashMap<String, LanguageServerConfiguration>,
|
||||
|
||||
|
@ -919,14 +922,17 @@ pub type LoaderError = globset::Error;
|
|||
|
||||
impl Loader {
|
||||
pub fn new(config: Configuration) -> Result<Self, LoaderError> {
|
||||
let mut language_configs = Vec::new();
|
||||
let mut language_configs = HopSlotMap::new();
|
||||
let mut language_config_ids_by_extension = HashMap::new();
|
||||
let mut language_config_ids_by_shebang = HashMap::new();
|
||||
let mut file_type_globs = Vec::new();
|
||||
|
||||
for config in config.language {
|
||||
// get the next id
|
||||
let language_id = language_configs.len();
|
||||
for mut config in config.language {
|
||||
let language_id = language_configs.insert_with_key(|id| {
|
||||
config.language_id = id;
|
||||
Arc::new(config)
|
||||
});
|
||||
let config = &language_configs[language_id];
|
||||
|
||||
for file_type in &config.file_types {
|
||||
// entry().or_insert(Vec::new).push(language_id);
|
||||
|
@ -942,8 +948,6 @@ impl Loader {
|
|||
for shebang in &config.shebangs {
|
||||
language_config_ids_by_shebang.insert(shebang.clone(), language_id);
|
||||
}
|
||||
|
||||
language_configs.push(Arc::new(config));
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
|
@ -989,18 +993,18 @@ impl Loader {
|
|||
|
||||
pub fn language_config_for_scope(&self, scope: &str) -> Option<Arc<LanguageConfiguration>> {
|
||||
self.language_configs
|
||||
.iter()
|
||||
.values()
|
||||
.find(|config| config.scope == scope)
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub fn language_config_for_language_id(
|
||||
pub fn language_config_for_language_name(
|
||||
&self,
|
||||
id: impl PartialEq<String>,
|
||||
name: impl PartialEq<String>,
|
||||
) -> Option<Arc<LanguageConfiguration>> {
|
||||
self.language_configs
|
||||
.iter()
|
||||
.find(|config| id.eq(&config.language_id))
|
||||
.values()
|
||||
.find(|config| name.eq(&config.language_name))
|
||||
.cloned()
|
||||
}
|
||||
|
||||
|
@ -1008,7 +1012,7 @@ impl Loader {
|
|||
/// function will perform a regex match on the given string to find the closest language match.
|
||||
pub fn language_config_for_name(&self, slice: RopeSlice) -> Option<Arc<LanguageConfiguration>> {
|
||||
// PERF: If the name matches up with the id, then this saves the need to do expensive regex.
|
||||
let shortcircuit = self.language_config_for_language_id(slice);
|
||||
let shortcircuit = self.language_config_for_language_name(slice);
|
||||
if shortcircuit.is_some() {
|
||||
return shortcircuit;
|
||||
}
|
||||
|
@ -1017,12 +1021,12 @@ impl Loader {
|
|||
|
||||
let mut best_match_length = 0;
|
||||
let mut best_match_position = None;
|
||||
for (i, configuration) in self.language_configs.iter().enumerate() {
|
||||
for (id, configuration) in self.language_configs.iter() {
|
||||
if let Some(injection_regex) = &configuration.injection_regex {
|
||||
if let Some(mat) = injection_regex.find(slice.regex_input()) {
|
||||
let length = mat.end() - mat.start();
|
||||
if length > best_match_length {
|
||||
best_match_position = Some(i);
|
||||
best_match_position = Some(id);
|
||||
best_match_length = length;
|
||||
}
|
||||
}
|
||||
|
@ -1037,7 +1041,7 @@ impl Loader {
|
|||
capture: &InjectionLanguageMarker,
|
||||
) -> Option<Arc<LanguageConfiguration>> {
|
||||
match capture {
|
||||
InjectionLanguageMarker::LanguageId(id) => self.language_config_for_language_id(*id),
|
||||
InjectionLanguageMarker::LanguageId(id) => self.language_config_for_language_name(*id),
|
||||
InjectionLanguageMarker::Name(name) => self.language_config_for_name(*name),
|
||||
InjectionLanguageMarker::Filename(file) => {
|
||||
let path_str: Cow<str> = (*file).into();
|
||||
|
@ -1053,7 +1057,7 @@ impl Loader {
|
|||
}
|
||||
|
||||
pub fn language_configs(&self) -> impl Iterator<Item = &Arc<LanguageConfiguration>> {
|
||||
self.language_configs.iter()
|
||||
self.language_configs.values()
|
||||
}
|
||||
|
||||
pub fn language_server_configs(&self) -> &HashMap<String, LanguageServerConfiguration> {
|
||||
|
@ -1066,7 +1070,7 @@ impl Loader {
|
|||
// Reconfigure existing grammars
|
||||
for config in self
|
||||
.language_configs
|
||||
.iter()
|
||||
.values()
|
||||
.filter(|cfg| cfg.is_highlight_initialized())
|
||||
{
|
||||
config.reconfigure(&self.scopes());
|
||||
|
@ -1425,6 +1429,13 @@ impl Syntax {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn layer_config(&self, layer_id: LayerId) -> Arc<LanguageConfiguration> {
|
||||
let loader = &self.loader.load();
|
||||
let language_id = self.layers[layer_id].config.language_id;
|
||||
|
||||
Arc::clone(&loader.language_configs[language_id])
|
||||
}
|
||||
|
||||
pub fn tree(&self) -> &Tree {
|
||||
self.layers[self.root].tree()
|
||||
}
|
||||
|
@ -1795,6 +1806,7 @@ pub enum HighlightEvent {
|
|||
#[derive(Debug)]
|
||||
pub struct HighlightConfiguration {
|
||||
pub language: Grammar,
|
||||
pub language_id: LanguageId,
|
||||
pub query: Query,
|
||||
injections_query: Query,
|
||||
combined_injections_patterns: Vec<usize>,
|
||||
|
@ -1891,6 +1903,7 @@ impl HighlightConfiguration {
|
|||
///
|
||||
/// Returns a `HighlightConfiguration` that can then be used with the `highlight` method.
|
||||
pub fn new(
|
||||
language_id: LanguageId,
|
||||
language: Grammar,
|
||||
highlights_query: &str,
|
||||
injection_query: &str,
|
||||
|
@ -1968,6 +1981,7 @@ impl HighlightConfiguration {
|
|||
let highlight_indices = ArcSwap::from_pointee(vec![None; query.capture_names().len()]);
|
||||
Ok(Self {
|
||||
language,
|
||||
language_id,
|
||||
query,
|
||||
injections_query,
|
||||
combined_injections_patterns,
|
||||
|
@ -2795,7 +2809,8 @@ mod test {
|
|||
let textobject = TextObjectQuery { query };
|
||||
let mut cursor = QueryCursor::new();
|
||||
|
||||
let config = HighlightConfiguration::new(language, "", "", "").unwrap();
|
||||
let config =
|
||||
HighlightConfiguration::new(LanguageId::default(), language, "", "", "").unwrap();
|
||||
let syntax = Syntax::new(
|
||||
source.slice(..),
|
||||
Arc::new(config),
|
||||
|
@ -2860,6 +2875,7 @@ mod test {
|
|||
|
||||
let language = get_language("rust").unwrap();
|
||||
let config = HighlightConfiguration::new(
|
||||
LanguageId::default(),
|
||||
language,
|
||||
&std::fs::read_to_string("../runtime/grammars/sources/rust/queries/highlights.scm")
|
||||
.unwrap(),
|
||||
|
@ -2971,7 +2987,8 @@ mod test {
|
|||
.unwrap();
|
||||
let language = get_language(language_name).unwrap();
|
||||
|
||||
let config = HighlightConfiguration::new(language, "", "", "").unwrap();
|
||||
let config =
|
||||
HighlightConfiguration::new(LanguageId::default(), language, "", "", "").unwrap();
|
||||
let syntax = Syntax::new(
|
||||
source.slice(..),
|
||||
Arc::new(config),
|
||||
|
|
|
@ -183,7 +183,7 @@ pub fn languages_all() -> std::io::Result<()> {
|
|||
|
||||
syn_loader_conf
|
||||
.language
|
||||
.sort_unstable_by_key(|l| l.language_id.clone());
|
||||
.sort_unstable_by_key(|l| l.language_name.clone());
|
||||
|
||||
let check_binary = |cmd: Option<&str>| match cmd {
|
||||
Some(cmd) => match helix_stdx::env::which(cmd) {
|
||||
|
@ -194,7 +194,7 @@ pub fn languages_all() -> std::io::Result<()> {
|
|||
};
|
||||
|
||||
for lang in &syn_loader_conf.language {
|
||||
write!(stdout, "{}", fit(&lang.language_id))?;
|
||||
write!(stdout, "{}", fit(&lang.language_name))?;
|
||||
|
||||
let mut cmds = lang.language_servers.iter().filter_map(|ls| {
|
||||
syn_loader_conf
|
||||
|
@ -214,7 +214,7 @@ pub fn languages_all() -> std::io::Result<()> {
|
|||
write!(stdout, "{}", check_binary(formatter))?;
|
||||
|
||||
for ts_feat in TsFeature::all() {
|
||||
match load_runtime_file(&lang.language_id, ts_feat.runtime_filename()).is_ok() {
|
||||
match load_runtime_file(&lang.language_name, ts_feat.runtime_filename()).is_ok() {
|
||||
true => write!(stdout, "{}", color(fit("✓"), Color::Green))?,
|
||||
false => write!(stdout, "{}", color(fit("✘"), Color::Red))?,
|
||||
}
|
||||
|
@ -257,7 +257,7 @@ pub fn language(lang_str: String) -> std::io::Result<()> {
|
|||
let lang = match syn_loader_conf
|
||||
.language
|
||||
.iter()
|
||||
.find(|l| l.language_id == lang_str)
|
||||
.find(|l| l.language_name == lang_str)
|
||||
{
|
||||
Some(l) => l,
|
||||
None => {
|
||||
|
@ -266,8 +266,11 @@ pub fn language(lang_str: String) -> std::io::Result<()> {
|
|||
let suggestions: Vec<&str> = syn_loader_conf
|
||||
.language
|
||||
.iter()
|
||||
.filter(|l| l.language_id.starts_with(lang_str.chars().next().unwrap()))
|
||||
.map(|l| l.language_id.as_str())
|
||||
.filter(|l| {
|
||||
l.language_name
|
||||
.starts_with(lang_str.chars().next().unwrap())
|
||||
})
|
||||
.map(|l| l.language_name.as_str())
|
||||
.collect();
|
||||
if !suggestions.is_empty() {
|
||||
let suggestions = suggestions.join(", ");
|
||||
|
@ -301,7 +304,7 @@ pub fn language(lang_str: String) -> std::io::Result<()> {
|
|||
.map(|formatter| formatter.command.to_string()),
|
||||
)?;
|
||||
|
||||
probe_parser(lang.grammar.as_ref().unwrap_or(&lang.language_id))?;
|
||||
probe_parser(lang.grammar.as_ref().unwrap_or(&lang.language_name))?;
|
||||
|
||||
for ts_feat in TsFeature::all() {
|
||||
probe_treesitter_feature(&lang_str, *ts_feat)?
|
||||
|
|
|
@ -496,7 +496,7 @@ pub mod completers {
|
|||
let loader = editor.syn_loader.load();
|
||||
let language_ids = loader
|
||||
.language_configs()
|
||||
.map(|config| &config.language_id)
|
||||
.map(|config| &config.language_name)
|
||||
.chain(std::iter::once(&text));
|
||||
|
||||
fuzzy_match(input, language_ids, false)
|
||||
|
|
|
@ -1232,7 +1232,7 @@ impl Document {
|
|||
) -> anyhow::Result<()> {
|
||||
let language_config = (*config_loader)
|
||||
.load()
|
||||
.language_config_for_language_id(language_id)
|
||||
.language_config_for_language_name(language_id)
|
||||
.ok_or_else(|| anyhow!("invalid language id: {}", language_id))?;
|
||||
self.set_language(Some(language_config), Some(config_loader));
|
||||
Ok(())
|
||||
|
@ -1714,7 +1714,7 @@ impl Document {
|
|||
pub fn language_name(&self) -> Option<&str> {
|
||||
self.language
|
||||
.as_ref()
|
||||
.map(|language| language.language_id.as_str())
|
||||
.map(|language| language.language_name.as_str())
|
||||
}
|
||||
|
||||
/// Language ID for the document. Either the `language-id`,
|
||||
|
|
|
@ -134,7 +134,7 @@ pub fn lang_features() -> Result<String, DynError> {
|
|||
let mut langs = config
|
||||
.language
|
||||
.iter()
|
||||
.map(|l| l.language_id.clone())
|
||||
.map(|l| l.language_name.clone())
|
||||
.collect::<Vec<_>>();
|
||||
langs.sort_unstable();
|
||||
|
||||
|
@ -148,9 +148,9 @@ pub fn lang_features() -> Result<String, DynError> {
|
|||
let lc = config
|
||||
.language
|
||||
.iter()
|
||||
.find(|l| l.language_id == lang)
|
||||
.find(|l| l.language_name == lang)
|
||||
.unwrap(); // lang comes from config
|
||||
row.push(lc.language_id.clone());
|
||||
row.push(lc.language_name.clone());
|
||||
|
||||
for (_feat, support_list) in &ts_features_to_langs {
|
||||
row.push(
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
use crate::DynError;
|
||||
|
||||
pub fn query_check() -> Result<(), DynError> {
|
||||
use crate::helpers::lang_config;
|
||||
use helix_core::{syntax::read_query, tree_sitter::Query};
|
||||
use helix_loader::grammar::get_language;
|
||||
|
||||
let query_files = [
|
||||
"highlights.scm",
|
||||
"locals.scm",
|
||||
"injections.scm",
|
||||
"textobjects.scm",
|
||||
"indents.scm",
|
||||
];
|
||||
|
||||
for language in lang_config().language {
|
||||
let language_name = &language.language_name;
|
||||
let grammar_name = language.grammar.as_ref().unwrap_or(language_name);
|
||||
for query_file in query_files {
|
||||
let language = get_language(grammar_name);
|
||||
let query_text = read_query(language_name, query_file);
|
||||
if let Ok(lang) = language {
|
||||
if !query_text.is_empty() {
|
||||
if let Err(reason) = Query::new(&lang, &query_text) {
|
||||
return Err(format!(
|
||||
"Failed to parse {} queries for {}: {}",
|
||||
query_file, language_name, reason
|
||||
)
|
||||
.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("Query check succeeded");
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue