This extracts a `language_core` crate from the existing `language`
crate, and creates a `grammars` data crate. The goal is to separate
tree-sitter grammar infrastructure, language configuration, and LSP
adapter types from the heavier buffer/editor integration layer in
`language`.
## Motivation
The `language` crate pulls in `text`, `theme`, `settings`, `rpc`,
`task`, `fs`, `clock`, `sum_tree`, and `fuzzy` — all of which are needed
for buffer integration (`Buffer`, `SyntaxMap`, `Outline`,
`DiagnosticSet`) but not for grammar parsing or language configuration.
Extracting the core types lets downstream consumers depend on
`language_core` without pulling in the full integration stack.
## Dependency graph after extraction
```
language_core ← gpui, lsp, tree-sitter, util, collections
grammars ← language_core, rust_embed, tree-sitter-{rust,python,...}
language ← language_core, text, theme, settings, rpc, task, fs, ...
languages ← language, grammars
```
## What moved to `language_core`
- `Grammar`, `GrammarId`, and all query config/builder types
- `LanguageConfig`, `LanguageMatcher`, bracket/comment/indent config
types
- `HighlightMap`, `HighlightId` (theme-dependent free functions
`highlight_style` and `highlight_name` stay in `language`)
- `LanguageName`, `LanguageId`
- `LanguageQueries`, `QUERY_FILENAME_PREFIXES`
- `CodeLabel`, `CodeLabelBuilder`, `Symbol`
- `Diagnostic`, `DiagnosticSourceKind`
- `Toolchain`, `ToolchainScope`, `ToolchainList`, `ToolchainMetadata`
- `ManifestName`
- `SoftWrap`
- LSP data types: `BinaryStatus`, `ServerHealth`,
`LanguageServerStatusUpdate`, `PromptResponseContext`, `ToLspPosition`
## What stays in `language`
- `Buffer`, `BufferSnapshot`, `SyntaxMap`, `Outline`, `DiagnosticSet`,
`LanguageScope`
- `LspAdapter`, `CachedLspAdapter`, `LspAdapterDelegate` (reference
`Arc<Language>` and `WorktreeId`)
- `ToolchainLister`, `LanguageToolchainStore` (reference `task` and
`settings` types)
- `ManifestQuery`, `ManifestProvider`, `ManifestDelegate` (reference
`WorktreeId`)
- Parser/query cursor pools, `PLAIN_TEXT`, point conversion functions
## What the `grammars` crate provides
- Embedded `.scm` query files and `config.toml` files for all built-in
languages (via `rust_embed`)
- `load_queries(name)`, `load_config(name)`,
`load_config_for_feature(name, grammars_loaded)`, and `get_file(path)`
functions
- `native_grammars()` for tree-sitter grammar registration (behind
`load-grammars` feature)
## Pre-cleanup (also in this PR)
- Removed unused `Option<&Buffer>` from
`LspAdapter::process_diagnostics`
- Removed unused `&App` from `LspAdapter::retain_old_diagnostic`
- Removed `fs: &dyn Fs` from `ToolchainLister` trait methods
(`PythonToolchainProvider` captures `fs` at construction time instead)
- Moved `Diagnostic`/`DiagnosticSourceKind` out of `buffer.rs` into
their own module
## Backward compatibility
The `language` crate re-exports everything from `language_core`, so
existing `use language::Grammar` (etc.) continues to work unchanged. The
only downstream change required is importing `CodeLabelExt` where
`.fallback_for_completion()` is called on the now-foreign `CodeLabel`
type.
Release Notes:
- N/A
---------
Co-authored-by: Agus Zubiaga <agus@zed.dev>
Co-authored-by: Tom Houlé <tom@tomhoule.com>
263 lines
7.6 KiB
Rust
263 lines
7.6 KiB
Rust
use settings::SemanticTokenRules;
|
|
|
|
pub(crate) fn semantic_token_rules() -> SemanticTokenRules {
|
|
let content = grammars::get_file("cpp/semantic_token_rules.json")
|
|
.expect("missing cpp/semantic_token_rules.json");
|
|
let json = std::str::from_utf8(&content.data).expect("invalid utf-8 in semantic_token_rules");
|
|
settings::parse_json_with_comments::<SemanticTokenRules>(json)
|
|
.expect("failed to parse cpp semantic_token_rules.json")
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use gpui::{AppContext as _, BorrowAppContext, TestAppContext};
|
|
use language::{AutoindentMode, Buffer};
|
|
use settings::SettingsStore;
|
|
use std::num::NonZeroU32;
|
|
use unindent::Unindent;
|
|
|
|
#[gpui::test]
|
|
async fn test_cpp_autoindent_basic(cx: &mut TestAppContext) {
|
|
cx.update(|cx| {
|
|
let test_settings = SettingsStore::test(cx);
|
|
cx.set_global(test_settings);
|
|
cx.update_global::<SettingsStore, _>(|store, cx| {
|
|
store.update_user_settings(cx, |s| {
|
|
s.project.all_languages.defaults.tab_size = NonZeroU32::new(2);
|
|
});
|
|
});
|
|
});
|
|
let language = crate::language("cpp", tree_sitter_cpp::LANGUAGE.into());
|
|
|
|
cx.new(|cx| {
|
|
let mut buffer = Buffer::local("", cx).with_language(language, cx);
|
|
|
|
buffer.edit([(0..0, "int main() {}")], None, cx);
|
|
|
|
let ix = buffer.len() - 1;
|
|
buffer.edit([(ix..ix, "\n\n")], Some(AutoindentMode::EachLine), cx);
|
|
assert_eq!(
|
|
buffer.text(),
|
|
"int main() {\n \n}",
|
|
"content inside braces should be indented"
|
|
);
|
|
|
|
buffer
|
|
});
|
|
}
|
|
|
|
#[gpui::test]
|
|
async fn test_cpp_autoindent_if_else(cx: &mut TestAppContext) {
|
|
cx.update(|cx| {
|
|
let test_settings = SettingsStore::test(cx);
|
|
cx.set_global(test_settings);
|
|
cx.update_global::<SettingsStore, _>(|store, cx| {
|
|
store.update_user_settings(cx, |s| {
|
|
s.project.all_languages.defaults.tab_size = NonZeroU32::new(2);
|
|
});
|
|
});
|
|
});
|
|
let language = crate::language("cpp", tree_sitter_cpp::LANGUAGE.into());
|
|
|
|
cx.new(|cx| {
|
|
let mut buffer = Buffer::local("", cx).with_language(language, cx);
|
|
|
|
buffer.edit(
|
|
[(
|
|
0..0,
|
|
r#"
|
|
int main() {
|
|
if (a)
|
|
b;
|
|
}
|
|
"#
|
|
.unindent(),
|
|
)],
|
|
Some(AutoindentMode::EachLine),
|
|
cx,
|
|
);
|
|
assert_eq!(
|
|
buffer.text(),
|
|
r#"
|
|
int main() {
|
|
if (a)
|
|
b;
|
|
}
|
|
"#
|
|
.unindent(),
|
|
"body of if-statement without braces should be indented"
|
|
);
|
|
|
|
let ix = buffer.len() - 4;
|
|
buffer.edit([(ix..ix, "\n.c")], Some(AutoindentMode::EachLine), cx);
|
|
assert_eq!(
|
|
buffer.text(),
|
|
r#"
|
|
int main() {
|
|
if (a)
|
|
b
|
|
.c;
|
|
}
|
|
"#
|
|
.unindent(),
|
|
"field expression (.c) should be indented further than the statement body"
|
|
);
|
|
|
|
buffer.edit([(0..buffer.len(), "")], Some(AutoindentMode::EachLine), cx);
|
|
buffer.edit(
|
|
[(
|
|
0..0,
|
|
r#"
|
|
int main() {
|
|
if (a) a++;
|
|
else b++;
|
|
}
|
|
"#
|
|
.unindent(),
|
|
)],
|
|
Some(AutoindentMode::EachLine),
|
|
cx,
|
|
);
|
|
assert_eq!(
|
|
buffer.text(),
|
|
r#"
|
|
int main() {
|
|
if (a) a++;
|
|
else b++;
|
|
}
|
|
"#
|
|
.unindent(),
|
|
"single-line if/else without braces should align at the same level"
|
|
);
|
|
|
|
buffer.edit([(0..buffer.len(), "")], Some(AutoindentMode::EachLine), cx);
|
|
buffer.edit(
|
|
[(
|
|
0..0,
|
|
r#"
|
|
int main() {
|
|
if (a)
|
|
b++;
|
|
else
|
|
c++;
|
|
}
|
|
"#
|
|
.unindent(),
|
|
)],
|
|
Some(AutoindentMode::EachLine),
|
|
cx,
|
|
);
|
|
assert_eq!(
|
|
buffer.text(),
|
|
r#"
|
|
int main() {
|
|
if (a)
|
|
b++;
|
|
else
|
|
c++;
|
|
}
|
|
"#
|
|
.unindent(),
|
|
"multi-line if/else without braces should indent statement bodies"
|
|
);
|
|
|
|
buffer.edit([(0..buffer.len(), "")], Some(AutoindentMode::EachLine), cx);
|
|
buffer.edit(
|
|
[(
|
|
0..0,
|
|
r#"
|
|
int main() {
|
|
if (a)
|
|
if (b)
|
|
c++;
|
|
}
|
|
"#
|
|
.unindent(),
|
|
)],
|
|
Some(AutoindentMode::EachLine),
|
|
cx,
|
|
);
|
|
assert_eq!(
|
|
buffer.text(),
|
|
r#"
|
|
int main() {
|
|
if (a)
|
|
if (b)
|
|
c++;
|
|
}
|
|
"#
|
|
.unindent(),
|
|
"nested if statements without braces should indent properly"
|
|
);
|
|
|
|
buffer.edit([(0..buffer.len(), "")], Some(AutoindentMode::EachLine), cx);
|
|
buffer.edit(
|
|
[(
|
|
0..0,
|
|
r#"
|
|
int main() {
|
|
if (a)
|
|
b++;
|
|
else if (c)
|
|
d++;
|
|
else
|
|
f++;
|
|
}
|
|
"#
|
|
.unindent(),
|
|
)],
|
|
Some(AutoindentMode::EachLine),
|
|
cx,
|
|
);
|
|
assert_eq!(
|
|
buffer.text(),
|
|
r#"
|
|
int main() {
|
|
if (a)
|
|
b++;
|
|
else if (c)
|
|
d++;
|
|
else
|
|
f++;
|
|
}
|
|
"#
|
|
.unindent(),
|
|
"else-if chains should align all conditions at same level with indented bodies"
|
|
);
|
|
|
|
buffer.edit([(0..buffer.len(), "")], Some(AutoindentMode::EachLine), cx);
|
|
buffer.edit(
|
|
[(
|
|
0..0,
|
|
r#"
|
|
int main() {
|
|
if (a) {
|
|
b++;
|
|
} else
|
|
c++;
|
|
}
|
|
"#
|
|
.unindent(),
|
|
)],
|
|
Some(AutoindentMode::EachLine),
|
|
cx,
|
|
);
|
|
assert_eq!(
|
|
buffer.text(),
|
|
r#"
|
|
int main() {
|
|
if (a) {
|
|
b++;
|
|
} else
|
|
c++;
|
|
}
|
|
"#
|
|
.unindent(),
|
|
"mixed braces should indent properly"
|
|
);
|
|
|
|
buffer
|
|
});
|
|
}
|
|
}
|