Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8f4e0fa7fb | |||
| 03931496bf |
@@ -7,3 +7,8 @@ Style/StringLiterals:
|
||||
|
||||
Style/StringLiteralsInInterpolation:
|
||||
EnforcedStyle: double_quotes
|
||||
|
||||
Metrics/BlockLength:
|
||||
AllowedMethods:
|
||||
- refine
|
||||
- Vamp.configure
|
||||
|
||||
Generated
+11
@@ -151,6 +151,16 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
|
||||
|
||||
[[package]]
|
||||
name = "codebook-tree-sitter-typst"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f785ad90ce80edd9e8b3d00890af0bc163e9a2c38f03daa18aede98d343d093"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "compact_str"
|
||||
version = "0.9.1"
|
||||
@@ -2003,6 +2013,7 @@ dependencies = [
|
||||
name = "vamp"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"codebook-tree-sitter-typst",
|
||||
"crossterm",
|
||||
"ignore",
|
||||
"ratatui",
|
||||
|
||||
@@ -27,6 +27,7 @@ tree-sitter-python = "0.25.0"
|
||||
tree-sitter-ruby = "0.23.1"
|
||||
tree-sitter-rust = "0.24.2"
|
||||
tree-sitter-toml-ng = "0.7.0"
|
||||
tree-sitter-typst = { package = "codebook-tree-sitter-typst", version = "=0.12.1" }
|
||||
tree-sitter-typescript = "0.23.2"
|
||||
tree-sitter-yaml = "0.7.2"
|
||||
tree-sitter-zig = "1.1.2"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
- [x] feature: syntax/lsp - add a default configuration for typst.
|
||||
- for the lsp, i'm thinking tinymist is a good candidate.
|
||||
- [ ] feature: diff view - GitHub code review integration after the local review UX is usable.
|
||||
- publish local reviews explicitly: general PR comments, inline line/range comments, review summaries, approval, and requests for changes.
|
||||
- preserve explicit PR identity and reviewed commit IDs; validate anchors against GitHub's PR diff and recheck the PR head before submission.
|
||||
|
||||
+11
-3
@@ -400,6 +400,7 @@ Vamp.configure do |config|
|
||||
config.lsp["rust"] = true # rust-analyzer, extensions: ["rs"]
|
||||
config.lsp["ruby"] = true # solargraph stdio, extensions: ["rb"]
|
||||
config.lsp["python"] = true # ty server + ruff server, extensions: ["py"]
|
||||
config.lsp["typst"] = true # tinymist lsp, extensions: ["typ"]
|
||||
|
||||
config.lsp["my-language"] = {
|
||||
command: ["my-language-server", "--stdio"],
|
||||
@@ -414,13 +415,15 @@ Vamp.configure do |config|
|
||||
end
|
||||
```
|
||||
|
||||
Keys are arbitrary nonempty strings, used as the protocol's `languageId`; they need no Tree-sitter grammar or hardcoded language enum. Absent/`false` entries are disabled. `true` enables a built-in preset: `"rust"` uses `rust-analyzer`/`rs`, `"python"` uses `ty server` plus `ruff server`/`py`, and `"ruby"` uses `solargraph stdio`/`rb`. Unknown keys need a configuration object with nonempty `command` argv or a `servers` list, plus `extensions` (without leading dots). Objects also explicitly enable servers; known presets supply omitted command/extensions. An explicit `command` replaces the entire preset with one server: `config.lsp["rust"] = { command: ["/custom/path/rust-analyzer"] }` retains `rs`, while a Python command override does not also launch Ruff.
|
||||
Keys are arbitrary nonempty strings, used as the protocol's `languageId`; they need no Tree-sitter grammar or hardcoded language enum. Absent/`false` entries are disabled. `true` enables a built-in preset: `"rust"` uses `rust-analyzer`/`rs`, `"python"` uses `ty server` plus `ruff server`/`py`, `"ruby"` uses `solargraph stdio`/`rb`, and `"typst"` uses `tinymist lsp`/`typ`. Unknown keys need a configuration object with nonempty `command` argv or a `servers` list, plus `extensions` (without leading dots). Objects also explicitly enable servers; known presets supply omitted command/extensions. An explicit `command` replaces the entire preset with one server: `config.lsp["rust"] = { command: ["/custom/path/rust-analyzer"] }` retains `rs`, while a Python command override does not also launch Ruff.
|
||||
|
||||
Only `command`, `extensions`, `initialization_options`, `settings`, and `servers` are accepted. Options/settings must be JSON-compatible objects. Without an explicit command/list, options apply to every default server; explicit `initialization_options` replaces preset defaults. `servers` must be a nonempty ordered list of objects with explicit `command` and optional per-server `initialization_options`/`settings`; it shares language-level extensions and cannot be combined with top-level command/options/settings. Invalid declarations reject configuration with a notice rather than activating a guessed server. Commands are executable/argument arrays, never shell commands; use an absolute path if the executable is not on PATH. No server installation or restart is automatic.
|
||||
|
||||
Install Solargraph yourself with `gem install solargraph` in the project's Ruby environment. The Ruby preset launches `["solargraph", "stdio"]`, following [Solargraph's stdio integration](https://solargraph.org/guides/language-server), and uses [YARD annotations](https://solargraph.org/guides/yard) for type information. Explicit `command` overrides replace the entire argv: for a bundled installation, use `config.lsp["ruby"] = { command: ["bundle", "exec", "solargraph", "stdio"] }`; Vamp retains the `rb` extension but does not append `stdio` to your override.
|
||||
|
||||
**Config-file lint style:** Solargraph's RuboCop reporter discovers `.rubocop.yml` from the Ruby file's directory, not Vamp's workspace. A config under `~/.config/vamp/` therefore does not inherit this repository's policy. To keep double quotes, multiline trailing argument commas, commented-out settings without a space after `#`, and no required frozen-string header, place this `.rubocop.yml` beside `config.rb` (also for a custom `--config` path):
|
||||
Install [Tinymist](https://github.com/Myriad-Dreamin/tinymist#installation) separately and enable `config.lsp["typst"] = true` for `.typ` files. The preset launches `["tinymist", "lsp"]` with language ID `typst`; it does not enable preview or add export settings. Tinymist builds that return overlapping semantic tokens despite Vamp advertising no overlap support retain Tree-sitter highlighting and report the rejected tokens; hover and other LSP requests remain available. This occurs with the installed Typst 0.14.2-based build (`tinymist/v0.14.6-rc2`).
|
||||
|
||||
**Config-file lint style:** Solargraph's RuboCop reporter discovers `.rubocop.yml` from the Ruby file's directory, not Vamp's workspace. A config under `~/.config/vamp/` therefore does not inherit this repository's policy. To keep double quotes, multiline trailing argument commas, commented-out settings without a space after `#`, no required frozen-string header, and no block-length limit on `Vamp.configure`, place this `.rubocop.yml` beside `config.rb` (also for a custom `--config` path):
|
||||
|
||||
```yaml
|
||||
Style/StringLiterals:
|
||||
@@ -434,9 +437,14 @@ Style/FrozenStringLiteralComment:
|
||||
|
||||
Layout/LeadingCommentSpace:
|
||||
Enabled: false
|
||||
|
||||
Metrics/BlockLength:
|
||||
AllowedMethods:
|
||||
- refine
|
||||
- Vamp.configure
|
||||
```
|
||||
|
||||
Syntax errors and other lint checks remain enabled. Restart Vamp after changing the policy so Solargraph reloads it; the Ruby config itself need not change.
|
||||
Syntax errors and other lint checks remain enabled. The block-length exemption applies only to `Vamp.configure`, alongside RuboCop's default `refine` exemption; ordinary blocks still have the normal limit. Restart Vamp after changing the policy so Solargraph reloads it; the Ruby config itself need not change.
|
||||
|
||||
**Python (Astral):** install [ty](https://docs.astral.sh/ty/editors/) and [Ruff](https://docs.astral.sh/ruff/editors/setup/) yourself, for example with `uv tool install ty` and `uv tool install ruff`. The Python preset sends the same documents to both servers, uses ty for hover/definition, and merges type errors with Ruff lint diagnostics. Clearing or losing one server preserves the other's diagnostics. Formatting and code actions remain unsupported by Vamp.
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ The default workspace member is the editor. `vendor/crossterm` is included to te
|
||||
|
||||
`vendor/tree-sitter-honey` contains a minimal Rust binding plus the unchanged generated parser, parser header, and highlight query from [`valdemar/tree-sitter-honey`](https://github.com/valdemar/tree-sitter-honey), revision `10f1ff746fe57736ae7d7c8c402f7b265680b26c` (MIT). Builds do not depend on a sibling checkout or an installed Tree-sitter CLI. To update, copy `src/parser.c`, `src/tree_sitter/parser.h`, and `queries/highlights.scm` from the same grammar revision, update the revision recorded here, and run `cargo test --lib syntax::tests`.
|
||||
|
||||
Typst uses the published [`codebook-tree-sitter-typst` 0.12.1 grammar](https://docs.rs/codebook-tree-sitter-typst/0.12.1/codebook_tree_sitter_typst/), aliased as `tree-sitter-typst` and pinned because the crate does not export queries. `src/syntax/languages.rs` supplies supported highlight captures and standard raw-code injection captures against that grammar's AST. After changing the grammar or query, run `cargo test --lib syntax::`; coverage includes Typst markup/code, incremental edits, Markdown fences, and embedded Rust. Live network-denied PTY smoke verifies heading/keyword/string/function colors, the opt-in Tinymist status, an unknown-variable diagnostic, and a `text()` hover signature with unchanged source bytes.
|
||||
|
||||
## Ruby types
|
||||
|
||||
Use YARD `@param`, `@return`, and `@yieldparam` annotations for Ruby APIs, and `@type` for locals where inference needs help. Examples annotate DSL block parameters before the call, without adding runtime assignments or dependencies. `Vamp::Action` / `Vamp::Mode` field types and the dynamically defined mode methods use Solargraph-supported directives.
|
||||
|
||||
+3
-1
@@ -629,7 +629,7 @@ config.bind "leader f", config.core.follow_link
|
||||
|
||||
## Syntax highlighting
|
||||
|
||||
Buffers highlight automatically using [Tree-sitter](https://tree-sitter.github.io/tree-sitter/) and compiled-in grammar/query packages. The selected language set is Rust, Ruby, Python, JSON, Bash, Makefile, Markdown, JavaScript/JSX, TypeScript/TSX, HTML, CSS, YAML, TOML, C, C++, Go, Zig (`.zig`), and Honey (`.hon`). Detection uses filename/extension or the in-memory first-line shebang, including common special filenames such as Makefile, Gemfile, and Cargo.lock; no file reread occurs. Unsupported formats use the normal foreground. The detected language appears in status, and save-as redetects it.
|
||||
Buffers highlight automatically using [Tree-sitter](https://tree-sitter.github.io/tree-sitter/) and compiled-in grammar/query packages. The selected language set is Rust, Ruby, Python, JSON, Bash, Makefile, Markdown, JavaScript/JSX, TypeScript/TSX, HTML, CSS, YAML, TOML, C, C++, Go, Zig (`.zig`), Honey (`.hon`), and Typst (`.typ`). Detection uses filename/extension or the in-memory first-line shebang, including common special filenames such as Makefile, Gemfile, and Cargo.lock; no file reread occurs. Unsupported formats use the normal foreground. The detected language appears in status, and save-as redetects it.
|
||||
|
||||
Files remain editable above the syntax budget: sources over 1 MiB, or with any line over 64 KiB, use plain text and show the reason in the language status. Syntax highlighting resumes automatically after shrinking below both limits. This avoids building a full parse tree for huge generated files; navigation, search, selections, undo/redo, and saving remain available.
|
||||
|
||||
@@ -637,6 +637,8 @@ Colors retain the terminal palette: comments dark gray, strings green, keywords
|
||||
|
||||
Honey uses its bundled grammar and highlight queries, including declarations, primitive types, literals, and comments. Markdown fences accept `honey`, `honeylang`, or `hon`; the language ID is `honey`.
|
||||
|
||||
Typst highlights headings, emphasis, references, raw code, comments, strings, numbers, keywords, and function calls without an LSP. Markdown fences accept `typst` or `typ`; named raw-code blocks inside Typst use the same bundled language registry. The language ID is `typst`. Native [Tinymist configuration](configuration.md#native-lsp) is opt-in.
|
||||
|
||||
Markdown heading markers (`#` through `######`, or Setext underlines) share the heading text's markup color. List markers (`-`, `+`, `*`, and ordered prefixes such as `1.` or `1)`) also use cyan markup, including nested and blockquoted lists. Plain-text hashes and other punctuation keep their existing styling.
|
||||
|
||||
An enabled supporting LSP server supplies semantic tokens automatically, overriding syntax colors only on recognized spans. Semantic colors also use the terminal palette: namespaces neutral gray, types cyan, variables/parameters light cyan, and properties blue. Readonly variables/parameters/properties keep those role colors and add italics; declarations/definitions are bold, documentation comments italic, and deprecated symbols struck through. Italic emphasis depends on terminal support. Edits retain semantic colors outside the changed range and shift them with the text until updated tokens arrive, avoiding whole-buffer flashes. Tree-sitter fills gaps, including edited ranges whose semantic tokens were discarded. Search, Visual selections, diagnostics, and carets keep their existing precedence. See [native LSP configuration](configuration.md#native-lsp) for provider support and server-stack routing.
|
||||
|
||||
@@ -45,6 +45,7 @@ Vamp.configure do |config|
|
||||
# config.lsp["rust"] = true # built-in rust-analyzer preset
|
||||
# config.lsp["ruby"] = true # built-in Solargraph stdio preset (gem install solargraph)
|
||||
# config.lsp["python"] = true # built-in ty + Ruff stack (install both separately)
|
||||
# config.lsp["typst"] = true # built-in Tinymist LSP preset (install tinymist separately)
|
||||
# config.lsp["my-language"] = {
|
||||
# command: ["my-language-server", "--stdio"], extensions: ["myl"],
|
||||
# initialization_options: {}, settings: {}
|
||||
|
||||
@@ -14,6 +14,7 @@ pub fn resolve(value: Value) -> Result<Vec<ServerConfig>, String> {
|
||||
crate::syntax::RUST_ID => Some((&[&["rust-analyzer"]], "rs")),
|
||||
crate::syntax::PYTHON_ID => Some((&[&["ty", "server"], &["ruff", "server"]], "py")),
|
||||
crate::syntax::RUBY_ID => Some((&[&["solargraph", "stdio"]], "rb")),
|
||||
crate::syntax::TYPST_ID => Some((&[&["tinymist", "lsp"]], "typ")),
|
||||
_ => None,
|
||||
};
|
||||
let mut options = match value {
|
||||
|
||||
@@ -125,6 +125,7 @@ Set VAMP_RUBY to choose the Ruby executable.
|
||||
|
||||
Native LSP is disabled by default: config.lsp[\"rust\"] = true enables a preset
|
||||
Presets: rust -> rust-analyzer, python -> ty server + ruff server, ruby -> solargraph stdio
|
||||
typst -> tinymist lsp
|
||||
Custom languages: config.lsp[\"my-language\"] = { command: [...], extensions: [...] }
|
||||
Server stacks: config.lsp[\"python\"] = { servers: [{ command: [...] }, { command: [...] }] }
|
||||
LSP commands: :lsp.definition, :lsp.diagnostics, :lsp.hover
|
||||
|
||||
@@ -13,6 +13,7 @@ use languages::LanguageSpec;
|
||||
pub(crate) const RUST_ID: &str = "rust";
|
||||
pub(crate) const RUBY_ID: &str = "ruby";
|
||||
pub(crate) const PYTHON_ID: &str = "python";
|
||||
pub(crate) const TYPST_ID: &str = "typst";
|
||||
|
||||
// Full trees and their indexes are optional; editing remains available beyond these budgets.
|
||||
const MAX_SYNTAX_BYTES: usize = 1024 * 1024;
|
||||
|
||||
+37
-1
@@ -90,6 +90,32 @@ const CPP_INJECTIONS: &str = "
|
||||
(raw_string_content) @injection.content)
|
||||
";
|
||||
|
||||
// The Typst crate exports its grammar but not queries. Use its AST nodes with
|
||||
// Vamp's supported capture roles, and standard captures for raw-code injections.
|
||||
const TYPST_HIGHLIGHTS: &str = r#"
|
||||
(comment) @comment
|
||||
(string) @string
|
||||
(number) @number
|
||||
[(bool) (none) (auto)] @keyword
|
||||
[
|
||||
"let" "if" "else" "while" "for" "in" "import" "as" "include"
|
||||
"show" "set" "return" "break" "continue" "context" "and" "or" "not"
|
||||
] @keyword
|
||||
(call item: (ident) @function)
|
||||
(call item: (field field: (ident) @function))
|
||||
[
|
||||
(heading) (emph) (strong) (url) (label) (ref) (symbol)
|
||||
(raw_blck) (raw_span)
|
||||
] @markup
|
||||
(item "-" @markup.list)
|
||||
(term ["/" ":"] @markup.list)
|
||||
"#;
|
||||
const TYPST_INJECTIONS: &str = "
|
||||
(raw_blck
|
||||
lang: (ident) @injection.language
|
||||
(blob) @injection.content)
|
||||
";
|
||||
|
||||
fn javascript_injections() -> &'static str {
|
||||
// The trailing upstream hbs/@glimmer rule uses a nonstandard #offset! directive,
|
||||
// not injection.* captures. Glimmer is outside the selected language set;
|
||||
@@ -118,7 +144,7 @@ fn spec(
|
||||
}
|
||||
}
|
||||
|
||||
static LANGUAGES: LazyLock<[LanguageSpec; 20]> = LazyLock::new(|| {
|
||||
static LANGUAGES: LazyLock<[LanguageSpec; 21]> = LazyLock::new(|| {
|
||||
[
|
||||
spec(
|
||||
super::RUST_ID,
|
||||
@@ -280,6 +306,14 @@ static LANGUAGES: LazyLock<[LanguageSpec; 20]> = LazyLock::new(|| {
|
||||
"",
|
||||
"",
|
||||
),
|
||||
spec(
|
||||
super::TYPST_ID,
|
||||
"Typst",
|
||||
tree_sitter_typst::LANGUAGE.into(),
|
||||
TYPST_HIGHLIGHTS,
|
||||
TYPST_INJECTIONS,
|
||||
"",
|
||||
),
|
||||
]
|
||||
});
|
||||
|
||||
@@ -320,6 +354,7 @@ pub(super) fn by_name(name: &str) -> Option<&'static LanguageSpec> {
|
||||
("golang", "Go"),
|
||||
("hon", "Honey"),
|
||||
("honeylang", "Honey"),
|
||||
("typ", "Typst"),
|
||||
];
|
||||
let name = ALIASES
|
||||
.iter()
|
||||
@@ -376,6 +411,7 @@ fn detect_path(path: &Path) -> Option<&'static LanguageSpec> {
|
||||
"go" => "Go",
|
||||
"zig" => "Zig",
|
||||
"hon" => "Honey",
|
||||
"typ" => "Typst",
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -1280,6 +1280,7 @@ fn lsp_configuration_is_disabled_unless_explicitly_enabled() {
|
||||
r#"Vamp.configure do |config|
|
||||
config.lsp["rust"] = false
|
||||
config.lsp["python"] = false
|
||||
config.lsp["typst"] = false
|
||||
config.lsp["not-a-preset"] = false
|
||||
end"#,
|
||||
] {
|
||||
@@ -1299,13 +1300,15 @@ fn lsp_presets_custom_languages_and_overrides_reach_native_configuration() {
|
||||
config.lsp["rust"] = true
|
||||
config.lsp["python"] = true
|
||||
config.lsp["ruby"] = true
|
||||
config.lsp["typst"] = true
|
||||
end"#,
|
||||
);
|
||||
let config = load_config(&path).unwrap();
|
||||
assert_eq!(config.lsp.len(), 4);
|
||||
assert_eq!(config.lsp.len(), 5);
|
||||
for (language, command, extension) in [
|
||||
("rust", &["rust-analyzer"][..], "rs"),
|
||||
("ruby", &["solargraph", "stdio"][..], "rb"),
|
||||
("typst", &["tinymist", "lsp"][..], "typ"),
|
||||
] {
|
||||
let server = config
|
||||
.lsp
|
||||
|
||||
@@ -648,6 +648,40 @@ fn honey_highlights_declarations_literals_edits_and_markdown_fences() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typst_highlights_markup_code_edits_and_fenced_languages() {
|
||||
let source = "= Heading\n#let message = \"hello é\"\n#let count = 42\n\
|
||||
#text(message) // note\n*strong* and _emphasis_\n\
|
||||
```rust\nfn main() { let n = 7; }\n```";
|
||||
let mut cache = self::cache(source, "main.typ");
|
||||
assert_eq!(cache.language(), Some("Typst"));
|
||||
assert!(!cache.tree.as_ref().unwrap().root_node().has_error());
|
||||
for (row, token, kind) in [
|
||||
(0, "Heading", SyntaxKind::Markup),
|
||||
(1, "let", SyntaxKind::Keyword),
|
||||
(1, "hello é", SyntaxKind::String),
|
||||
(2, "42", SyntaxKind::Number),
|
||||
(3, "text", SyntaxKind::Function),
|
||||
(3, "// note", SyntaxKind::Comment),
|
||||
(4, "strong", SyntaxKind::Markup),
|
||||
(4, "emphasis", SyntaxKind::Markup),
|
||||
(6, "fn", SyntaxKind::Keyword),
|
||||
(6, "7", SyntaxKind::Number),
|
||||
] {
|
||||
assert_token(&cache, source, row, token, kind);
|
||||
}
|
||||
let edited = source.replace("\"hello é\"", "false");
|
||||
cache.update(&lines(&edited), Some(Path::new("main.typ")), 1);
|
||||
assert_token(&cache, &edited, 1, "false", SyntaxKind::Keyword);
|
||||
assert_fresh(&cache, &edited, "main.typ");
|
||||
for tag in ["typst", "typ"] {
|
||||
let markdown = format!("```{tag}\n#let count = 42\n```");
|
||||
let injected = self::cache(&markdown, "README.md");
|
||||
assert_token(&injected, &markdown, 1, "let", SyntaxKind::Keyword);
|
||||
assert_token(&injected, &markdown, 1, "42", SyntaxKind::Number);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filenames_shebang_save_as_and_tsx_use_actual_grammar_identity() {
|
||||
let source = lines("\u{feff}#!/usr/bin/env python3\nreturn 42");
|
||||
|
||||
@@ -22,6 +22,8 @@ fn selected_files_and_auxiliary_grammars_are_registered() {
|
||||
("main.C", "C++"),
|
||||
("main.go", "Go"),
|
||||
("main.zig", "Zig"),
|
||||
("main.typ", "Typst"),
|
||||
("main.TYP", "Typst"),
|
||||
] {
|
||||
assert_eq!(
|
||||
detect(Some(Path::new(filename)), None).map(|spec| spec.name),
|
||||
|
||||
Reference in New Issue
Block a user