diff --git a/README.md b/README.md index 351fc92..0acfec8 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,11 @@ # tree-sitter-just -WIP: Tree-sitter grammar for Justfiles ([casey/just](https://github.com/casey/just)) +Tree-sitter grammar for Justfiles ([casey/just](https://github.com/casey/just)) -To use treesitter based highlighting, folds etc. the queries need to be added to the runtimepath, until I get a PR into `nvim-treesitter/nvim-treesitter` you can install this repo as a plugin using Plug/packer/manual clone etc. This plugin also adds a simple `ftdetect` plugin for detecting justfiles. +To use treesitter based highlighting, folds etc. the queries need to be added +to the runtimepath, until I get a PR into `nvim-treesitter/nvim-treesitter` +you can install this repo as a plugin using Plug/packer/manual clone etc. This +plugin also adds a simple `ftdetect` plugin for detecting justfiles. Packer: @@ -19,12 +22,16 @@ Plug 'IndianBoy42/tree-sitter-just' Manual ``` -git clone https://github.com/IndianBoy42/tree-sitter-just ~/.local/share/nvim/site/pack/tree-sitter-queries/start/tree-sitter-just +git clone https://github.com/IndianBoy42/tree-sitter-just \ + ~/.local/share/nvim/site/pack/tree-sitter-queries/start/tree-sitter-just ``` -You can then do `require('tree-sitter-just').setup({})` to register the parser with tree-sitter. You can then do `TSInstall`/`TSUpdate` as usual to install the parser +You can then do `require('tree-sitter-just').setup({})` to register the parser +with tree-sitter. You can then do `TSInstall`/`TSUpdate` as usual to install +the parser -You can also add the parser manually using (This is similar to what is done in `require"tree-sitter-just".setup({})`) +You can also add the parser manually using (This is similar to what is done in +`require"tree-sitter-just".setup({})`) ```lua require("nvim-treesitter.parsers").get_parser_configs().just = { @@ -38,13 +45,11 @@ require("nvim-treesitter.parsers").get_parser_configs().just = { } ``` -Don't forget to `:TSInstall` after adding this. With this method you do not have to add this repo as a plugin. +Don't forget to `:TSInstall` after adding this. With this method you do not +have to add this repo as a plugin. -If you run into problems relating to C++ 11 features, try including this in your setup (you may have to `brew install gcc@11`): - -```lua -require"nvim-treesitter.install".compilers = {"gcc-11"} -``` +Please note that the nightly version of `nvim-treesitter`, at least since +`5b90ea2abaa4303b9205b5c9002a8cdd0acd11a5` (2024-01-19). ## Contributing @@ -76,7 +81,9 @@ tree-sitter, and should be usable by helix. To generate queries for NeoVim, run `./build-flavored-queries.py` (this is run as part of `npm run gen`). You can use the [`:InspectTree`](https://neovim.io/doc/user/treesitter.html#%3AInspectTree) -command to explore the resulting parse tree, and [`:Inspect`](https://neovim.io/doc/user/lua.html#%3AInspect) to view highlight groups. +command to explore the resulting parse tree, and +[`:Inspect`](https://neovim.io/doc/user/lua.html#%3AInspect) to view highlight +groups. ## Quirks of Just @@ -103,28 +110,3 @@ The tests directory contains "corpus" tests that are checked for syntax, as well as "highlight" tests that check the result. The "highlight" test directory includes some test files generated by the fuzzer that aren't always human readable. - -## TODO - -- [x] Implement a basic parser that is able to understand all features of Justfiles -- [x] Implement support for highlighting using `nvim-treesitter` ([reference](https://tree-sitter.github.io/tree-sitter/syntax-highlighting), `highlights.scm`) - - [x] Write the queries - - [x] Implement locals queries -- [x] Implement queries for textobjects compatible with [`nvim-treesitter/nvim-treesitter-textobjects`](https://github.com/nvim-treesitter/nvim-treesitter-textobjects) - - [x] Write the queries - - [x] @block.inner|outer (recipe) - - [x] @function.inner|outer (recipe) - - [x] @call.inner|outer (dependencies) - - [x] @comment.outer - - [x] @parameter.inner|outer - - [x] @conditional.inner|outer - - [x] @statement.outer (recipe line or one variable) - - [x] Definitions and references - - [ ] Scopenames -- [ ] Implement support for indentation using `nvim-treesitter` - - [ ] Write the queries -- [x] Implement support for code folding using `nvim-treesitter` (`folds.scm`) - - [x] Write the queries -- [ ] Write Tests -- [x] Highlight the fish/bash/etc inside recipes (use tree-sitter injections) -- [ ] Fix weirdness around trailing whitespace (don't leave trailing whitespace after the recipe header) diff --git a/build-flavored-queries.py b/build-flavored-queries.py index 55cd8f4..c8eddb3 100755 --- a/build-flavored-queries.py +++ b/build-flavored-queries.py @@ -15,7 +15,6 @@ from glob import glob from pathlib import Path - REPLACEMENTS_HELIX = [] REPLACEMENTS_ZED = [] REPLACEMENTS_LAPCE = [] @@ -26,187 +25,6 @@ "#match?", ] -ALLOWED_CAPS_NVIM_OLD = { - "highlights.scm": [ - "@comment", - "@comment.documentation", - "@error", - "@none", - "@preproc", - "@define", - "@operator", - "@punctuation.delimiter", - "@punctuation.bracket", - "@punctuation.special", - "@string", - "@string.documentation", - "@string.regex", - "@string.escape", - "@string.special", - "@character", - "@character.special", - "@boolean", - "@number", - "@float", - "@function", - "@function.builtin", - "@function.call", - "@function.macro", - "@method", - "@method.call", - "@constructor", - "@parameter", - "@keyword", - "@keyword.coroutine", - "@keyword.function", - "@keyword.operator", - "@keyword.return", - "@conditional", - "@conditional.ternary", - "@repeat", - "@debug", - "@label", - "@include", - "@exception", - "@type", - "@type.builtin", - "@type.definition", - "@type.qualifier", - "@storageclass", - "@attribute", - "@field", - "@property", - "@variable", - "@variable.builtin", - "@constant", - "@constant.builtin", - "@constant.macro", - "@namespace", - "@symbol", - "@text", - "@text.strong", - "@text.emphasis", - "@text.underline", - "@text.strike", - "@text.title", - "@text.quote", - "@text.uri", - "@text.math", - "@text.environment", - "@text.environment.name", - "@text.reference", - "@text.literal", - "@text.literal.block", - "@text.todo", - "@text.note", - "@text.warning", - "@text.danger", - "@text.diff.add", - "@text.diff.delete", - "@tag", - "@tag.attribute", - "@tag.delimiter", - "@conceal", - "@spell", - "@nospell", - ], - "injections.scm": [ - "@injection.language", - "@injection.content", - "@comment", - ], - "locals.scm": [ - "@definition", - "@definition.constant", - "@definition.function", - "@definition.method", - "@definition.var", - "@definition.parameter", - "@definition.macro", - "@definition.type", - "@definition.field", - "@definition.enum", - "@definition.namespace", - "@definition.import", - "@definition.associated", - "@scope", - "@reference", - ], - "folds.scm": [ - "@fold", - ], - "indents.scm": [ - "@indent.begin", - "@indent.end", - "@indent.align", - "@indent.dedent", - "@indent.branch", - "@indent.ignore", - "@indent.auto", - "@indent.zero", - ], - "textobjects.scm": [ - "@attribute.inner", - "@attribute.outer", - "@function.inner", - "@function.outer", - "@class.inner", - "@class.outer", - "@conditional.inner", - "@conditional.outer", - "@loop.inner", - "@loop.outer", - "@call.inner", - "@call.outer", - "@block.inner", - "@block.outer", - "@parameter.inner", - "@parameter.outer", - "@regex.inner", - "@regex.outer", - "@comment.inner", - "@comment.outer", - "@assignment.inner", - "@assignment.outer", - "@return.inner", - "@return.outer", - "@frame.inner", - "@frame.outer", - ], -} - -ALLOWED_SETTINGS_NVIM_OLD = { - "injections.scm": [ - "injection.combined", - "injection.language", - "injection.include-children", - ] -} - -# Old nvim-treesitter before updates -REPLACEMENTS_NVIM_OLD = [ - # Changes to local captures - (r"@local.definition", r"@definition"), - (r"@local.reference[\w.]*", r"@reference"), - (r"@local.scope", r"@scope"), - # Changes to highlight queries - (r"@comment.line", r"@comment"), - (r"@constant.builtin.boolean", r"@boolean"), - (r"@constant.character.escape", "@string.escape"), - (r"@keyword.control.conditional", r"@conditional"), - (r"@keyword.control.import", r"@keyword"), - (r"@keyword.module", r"@keyword"), - (r"@keyword.directive", r"@comment"), - (r"@variable.parameter", r"@parameter"), - # Changes to indent queries - (r"@indent\s+@extend", r"@indent.begin"), - # Changes to textobject queries - (r"(@[\w.]+\.)inside", r"\1inner"), - (r"(@[\w.]+\.)around", r"\1outer"), - # nvim uses `var` rather than `variable` - (r"(@[\w.]+)\.variable", r"\1.var"), -] - ALLOWED_CAPS_NVIM = { "highlights.scm": [ "@variable", @@ -312,7 +130,34 @@ "@indent.auto", "@indent.zero", ], - "textobjects.scm": ALLOWED_CAPS_NVIM_OLD["textobjects.scm"], + "textobjects.scm": [ + "@attribute.inner", + "@attribute.outer", + "@function.inner", + "@function.outer", + "@class.inner", + "@class.outer", + "@conditional.inner", + "@conditional.outer", + "@loop.inner", + "@loop.outer", + "@call.inner", + "@call.outer", + "@block.inner", + "@block.outer", + "@parameter.inner", + "@parameter.outer", + "@regex.inner", + "@regex.outer", + "@comment.inner", + "@comment.outer", + "@assignment.inner", + "@assignment.outer", + "@return.inner", + "@return.outer", + "@frame.inner", + "@frame.outer", + ], } ALLOWED_SETTINGS_NVIM = { @@ -460,21 +305,13 @@ # (rname, eplacements, allowed caps, base path) mappings to create a flavor FLAVOR_MAPPINGS = [ - ( - "old NeoVim", - "NVIM-OLD", - REPLACEMENTS_NVIM_OLD, - ALLOWED_CAPS_NVIM_OLD, - ALLOWED_SETTINGS_NVIM_OLD, - Path("queries") / "just", - ), ( "new NeoVim", "NVIM", REPLACEMENTS_NVIM, ALLOWED_CAPS_NVIM, ALLOWED_SETTINGS_NVIM, - Path("queries-flavored") / "nvim-next", + Path("queries") / "just", ), ( "Helix", diff --git a/queries-flavored/nvim-next/folds.scm b/queries-flavored/nvim-next/folds.scm deleted file mode 100644 index 62dc1a4..0000000 --- a/queries-flavored/nvim-next/folds.scm +++ /dev/null @@ -1,10 +0,0 @@ -; File autogenerated by build-queries-nvim.py; do not edit - -; Define collapse points - -[ - (recipe) - (string) - (external_command) -] @fold -(#trim! @fold) diff --git a/queries-flavored/nvim-next/highlights.scm b/queries-flavored/nvim-next/highlights.scm deleted file mode 100644 index 68e03e3..0000000 --- a/queries-flavored/nvim-next/highlights.scm +++ /dev/null @@ -1,149 +0,0 @@ -; File autogenerated by build-queries-nvim.py; do not edit - -; This file specifies how matched syntax patterns should be highlighted - -[ - "export" - "import" -] @keyword.import - -"mod" @module - -[ - "alias" - "set" - "shell" -] @keyword - -[ - "if" - "else" -] @keyword.conditional - -; Variables - -(value - (identifier) @variable) - -(alias - left: (identifier) @variable) - -(assignment - left: (identifier) @variable) - -; Functions - -(recipe_header - name: (identifier) @function) - -(dependency - name: (identifier) @function.call) - -(dependency_expression - name: (identifier) @function.call) - -(function_call - name: (identifier) @function.call) - -; Parameters - -(parameter - name: (identifier) @variable.parameter) - -; Namespaces - -(module - name: (identifier) @module) - -; Operators - -[ - ":=" - "?" - "==" - "!=" - "=~" - "@" - "=" - "$" - "*" - "+" - "&&" - "@-" - "-@" - "-" - "/" - ":" -] @operator - -; Punctuation - -"," @punctuation.delimiter - -[ - "{" - "}" - "[" - "]" - "(" - ")" - "{{" - "}}" -] @punctuation.bracket - -[ "`" "```" ] @punctuation.special - -; Literals - -(boolean) @boolean - -[ - (string) - (external_command) -] @string - -(escape_sequence) @string.escape - -; Comments - -(comment) @spell @comment - -(shebang) @keyword.directive - -; highlight known settings (filtering does not always work) -(setting - left: (identifier) @keyword - (#any-of? @keyword - "allow-duplicate-recipes" - "dotenv-filename" - "dotenv-load" - "dotenv-path" - "export" - "fallback" - "ignore-comments" - "positional-arguments" - "shell" - "tempdi" - "windows-powershell" - "windows-shell")) - -; highlight known attributes (filtering does not always work) -(attribute - (identifier) @attribute - (#any-of? @attribute - "private" - "allow-duplicate-recipes" - "dotenv-filename" - "dotenv-load" - "dotenv-path" - "export" - "fallback" - "ignore-comments" - "positional-arguments" - "shell" - "tempdi" - "windows-powershell" - "windows-shell")) - -; Numbers are part of the syntax tree, even if disallowed -(numeric_error) @error diff --git a/queries-flavored/nvim-next/indents.scm b/queries-flavored/nvim-next/indents.scm deleted file mode 100644 index a7f5228..0000000 --- a/queries-flavored/nvim-next/indents.scm +++ /dev/null @@ -1,12 +0,0 @@ -; File autogenerated by build-queries-nvim.py; do not edit - -; This query specifies how to auto-indent logical blocks. -; - -[ - (recipe) - (string) - (external_command) -] @indent.begin - -(comment) @indent.auto diff --git a/queries-flavored/nvim-next/injections.scm b/queries-flavored/nvim-next/injections.scm deleted file mode 100644 index 60ab8b8..0000000 --- a/queries-flavored/nvim-next/injections.scm +++ /dev/null @@ -1,104 +0,0 @@ -; File autogenerated by build-queries-nvim.py; do not edit - -; Specify nested languages that live within a `justfile` - -; ================ Always applicable ================ - -((comment) @injection.content - (#set! injection.language "comment")) - -; Highlight the RHS of `=~` as regex -((regex_literal - (_) @injection.content) - (#set! injection.language "regex")) - -; ================ Global defaults ================ - -; Default everything to be bash -(recipe_body - !shebang - (#set! injection.language "bash") - (#set! injection.include-children)) @injection.content - -(external_command - (command_body) @injection.content - (#set! injection.language "bash")) - -; ================ Global language specified ================ -; Global language is set with something like one of the following: -; -; set shell := ["bash", "-c", ...] -; set shell := ["pwsh.exe"] -; -; We can extract the first item of the array, but we can't extract the language -; name from the string with something like regex. So instead we special case -; two things: powershell, which is likely to come with a `.exe` attachment that -; we need to strip, and everything else which hopefully has no extension. We -; separate this with a `#match?`. -; -; Unfortunately, there also isn't a way to allow arbitrary nesting or -; alternatively set "global" capture variables. So we can set this for item- -; level external commands, but not for e.g. external commands within an -; expression without getting _really_ annoying. Should at least look fine since -; they default to bash. Limitations... -; See https://github.com/tree-sitter/tree-sitter/issues/880 for more on that. - -(source_file - (setting "shell" ":=" "[" (string) @_langstr - (#match? @_langstr ".*(powershell|pwsh|cmd).*") - (#set! injection.language "powershell")) - [ - (recipe - (recipe_body - !shebang - (#set! injection.include-children)) @injection.content) - - (assignment - (expression - (value - (external_command - (command_body) @injection.content)))) - ]) - -(source_file - (setting "shell" ":=" "[" (string) @injection.language - (#not-match? @injection.language ".*(powershell|pwsh|cmd).*")) - [ - (recipe - (recipe_body - !shebang - (#set! injection.include-children)) @injection.content) - - (assignment - (expression - (value - (external_command - (command_body) @injection.content)))) - ]) - -; ================ Recipe language specified ================ - -; Set highlighting for recipes that specify a language, using the exact name by default -(recipe_body ; - (shebang ; - (language) @injection.language) - (#not-any-of? @injection.language "python3" "nodejs" "node") - (#set! injection.include-children)) @injection.content - -; Transform some known executables - -; python3 -> python -(recipe_body - (shebang - (language) @_lang) - (#eq? @_lang "python3") - (#set! injection.language "python") - (#set! injection.include-children)) @injection.content - -; node/nodejs -> javascript -(recipe_body - (shebang - (language) @_lang) - (#any-of? @_lang "node" "nodejs") - (#set! injection.language "javascript") - (#set! injection.include-children)) @injection.content diff --git a/queries-flavored/nvim-next/locals.scm b/queries-flavored/nvim-next/locals.scm deleted file mode 100644 index ec9bc79..0000000 --- a/queries-flavored/nvim-next/locals.scm +++ /dev/null @@ -1,42 +0,0 @@ -; File autogenerated by build-queries-nvim.py; do not edit - -; This file tells us about the scope of variables so e.g. local -; variables override global functions with the same name - -; Scope - -(recipe) @local.scope - -; Definitions - -(alias - left: (identifier) @local.definition.var) - -(assignment - left: (identifier) @local.definition.var) - -(module - name: (identifier) @local.definition.namespace) - -(parameter - name: (identifier) @local.definition.var) - -(recipe_header - name: (identifier) @local.definition.function) - -; References - -(alias - right: (identifier) @local.reference) - -(function_call - name: (identifier) @local.reference) - -(dependency - name: (identifier) @local.reference) - -(dependency_expression - name: (identifier) @local.reference) - -(value - (identifier) @local.reference) diff --git a/queries-flavored/nvim-next/textobjects.scm b/queries-flavored/nvim-next/textobjects.scm deleted file mode 100644 index b010a8e..0000000 --- a/queries-flavored/nvim-next/textobjects.scm +++ /dev/null @@ -1,18 +0,0 @@ -; File autogenerated by build-queries-nvim.py; do not edit - -; Specify how to navigate around logical blocks in code - -(recipe - (recipe_body) @function.inner) @function.outer - -(parameters - ((_) @parameter.inner . ","? @parameter.outer)) @parameter.outer - -(dependency_expression - (_) @parameter.inner) @parameter.outer - -(function_call - arguments: (sequence - (expression) @parameter.inner) @parameter.outer) @function.outer - -(comment) @comment.outer diff --git a/queries/just/highlights.scm b/queries/just/highlights.scm index 852ace7..68e03e3 100644 --- a/queries/just/highlights.scm +++ b/queries/just/highlights.scm @@ -5,9 +5,9 @@ [ "export" "import" -] @keyword +] @keyword.import -"mod" @keyword +"mod" @module [ "alias" @@ -18,7 +18,7 @@ [ "if" "else" -] @conditional +] @keyword.conditional ; Variables @@ -48,12 +48,12 @@ ; Parameters (parameter - name: (identifier) @parameter) + name: (identifier) @variable.parameter) ; Namespaces (module - name: (identifier) @namespace) + name: (identifier) @module) ; Operators @@ -108,7 +108,7 @@ (comment) @spell @comment -(shebang) @comment +(shebang) @keyword.directive ; highlight known settings (filtering does not always work) (setting diff --git a/queries/just/indents.scm b/queries/just/indents.scm index 424a02d..a7f5228 100644 --- a/queries/just/indents.scm +++ b/queries/just/indents.scm @@ -2,7 +2,6 @@ ; This query specifies how to auto-indent logical blocks. ; -; Better documentation with diagrams is in https://docs.helix-editor.com/guides/indent.html [ (recipe) diff --git a/queries/just/locals.scm b/queries/just/locals.scm index 0c25c23..ec9bc79 100644 --- a/queries/just/locals.scm +++ b/queries/just/locals.scm @@ -5,38 +5,38 @@ ; Scope -(recipe) @scope +(recipe) @local.scope ; Definitions (alias - left: (identifier) @definition.var) + left: (identifier) @local.definition.var) (assignment - left: (identifier) @definition.var) + left: (identifier) @local.definition.var) (module - name: (identifier) @definition.namespace) + name: (identifier) @local.definition.namespace) (parameter - name: (identifier) @definition.var) + name: (identifier) @local.definition.var) (recipe_header - name: (identifier) @definition.function) + name: (identifier) @local.definition.function) ; References (alias - right: (identifier) @reference) + right: (identifier) @local.reference) (function_call - name: (identifier) @reference) + name: (identifier) @local.reference) (dependency - name: (identifier) @reference) + name: (identifier) @local.reference) (dependency_expression - name: (identifier) @reference) + name: (identifier) @local.reference) (value - (identifier) @reference) + (identifier) @local.reference)