%YAML 1.2 --- # Syntax for the just command runner # # Based on the just.sublime-syntax file by @TonioGela # Largely rewritten in 2022 by @nk9 using examples by @deathaxe # https://www.sublimetext.com/docs/syntax.html name: Just scope: source.just version: 2 hidden: false file_extensions: - .justfile - just - justfile variables: valid_name: '[a-zA-Z_][a-zA-Z0-9_-]*' built_in_functions: |- (?x: absolute_path | append | arch | blake3 | blake3_file | cache_dir | cache_directory | canonicalize | capitalize | choose | clean | config_dir | config_directory | config_local_dir | config_local_directory | data_dir | data_directory | data_local_dir | data_local_directory | datetime | datetime_utc | encode_uri_component | env | env_var | env_var_or_default | error | executable_dir | executable_directory | extension | file_name | file_stem | home_dir | home_directory | invocation_dir | invocation_dir_native | invocation_directory | invocation_directory_native | is_dependency | join | just_executable | just_pid | justfile | justfile_dir | justfile_directory | kebabcase | lowercamelcase | lowercase | module_dir | module_directory | module_file | num_cpus | os | os_family | parent_dir | parent_directory | path_exists | prepend | quote | read | replace | replace_regex | require | semver_matches | sha256 | sha256_file | shell | shoutykebabcase | shoutysnakecase | snakecase | source_dir | source_directory | source_file | style | titlecase | trim | trim_end | trim_end_match | trim_end_matches | trim_start | trim_start_match | trim_start_matches | uppercamelcase | uppercase | uuid | which | without_extension ) boolean_settings: |- (?x: allow-duplicate-recipes | allow-duplicate-variables | dotenv-load | export | fallback | ignore-comments | positional-arguments | quiet | unstable ) string_settings: |- (?x: dotenv-filename | dotenv-path | tempdir | working-directory ) shell_settings: |- (?x: script-interpreter | shell | windows-shell ) deprecated_settings: |- (?x: windows-powershell ) recipe_attributes_bare: |- (?x: confirm | linux | macos | no-cd | no-exit-message | no-quiet | openbsd | positional-arguments | private | script | unix | windows ) recipe_attributes_arguments: |- (?x: confirm | doc | extension | group | script | working-directory ) ############################################################################### # MAIN CONTEXT ############################################################################### contexts: main: - include: includes - include: modules - include: settings - include: aliases - include: comments - include: assignment - include: recipe-attributes - include: recipe-definition - include: recipe-generic-line prototype: - include: comments ###[ COMMENTS ]################################################################ comments: - match: '#[^!]' scope: punctuation.definition.comment.begin.just push: comment-line comment-line: - meta_include_prototype: false - meta_scope: comment.line.number-sign.just - include: line-end ###[ IMPORTS ]################################################################# includes: - match: ^(!include) (.*)$ scope: meta.statement.import.just captures: 1: keyword.control.import.just 2: meta.generic-name.just modules: - match: ^(mod)(\??)(\s+\w+) scope: meta.statement.mod.just captures: 1: keyword.control.import.just 2: keyword.operator.assignment.just 3: meta.generic-name.just - match: '`' scope: invalid.illegal.just pop: 2 - include: quoted-strings ###[ ALIASES ]################################################################# aliases: - match: ^(alias)\s+({{valid_name}})\s*(:=)\s+({{valid_name}})(?=.*$) captures: 1: support.function.export.just 2: variable.other.just 3: keyword.operator.assignment.just 4: variable.function.just ###[ STATEMENTS ]############################################################## just-expressions: - include: groups - include: operators - include: function-calls - include: if-statements - include: all-strings - include: operands-variables ###[ FUNCTION CALL ]########################################################### function-calls: - match: '{{built_in_functions}}\s*(?=\()' scope: meta.function-call.identifier.just support.function.builtin.just push: function-call-arguments function-call-arguments: - meta_include_prototype: false - match: \( scope: punctuation.section.group.begin.just set: function-call-arguments-body function-call-arguments-body: - meta_scope: meta.function-call.arguments.just - include: group-end - include: just-expressions - match: ({{valid_name}})\s* captures: 1: variable.parameter.just ###[ GROUPS ]################################################################## groups: - match: \( scope: punctuation.section.group.begin.just push: group-body group-end: - match: \) scope: punctuation.section.group.end.just pop: 1 group-body: - meta_scope: meta.group.just - include: group-end - include: just-expressions ###[ IF STATEMENT ]############################################################ if-statements: - match: if\b scope: keyword.control.conditional.if.just push: if-statement-condition-body - match: else\b scope: keyword.control.conditional.else.just push: else-statement-block if-statement-condition-body: - meta_scope: meta.statement.conditional.if.just - match: \{ scope: punctuation.section.block.begin.just push: if-else-block-body - match: '!=|==|=~' scope: keyword.operator.comparison.just - include: just-expressions - include: else-pop else-statement-block: - meta_scope: meta.statement.conditional.else.just - match: \{ scope: punctuation.section.block.begin.just push: if-else-block-body - include: else-pop if-else-block-body: - meta_scope: meta.block.just - match: \} scope: punctuation.section.block.end.just pop: 2 - include: just-expressions ###[ OPERATORS ]############################################################### operators: - include: punctuation-separators - match: (\+|\/) scope: keyword.operator.arithmetic.just punctuation-separators: - match: ',' scope: punctuation.separator.sequence.just ###[ CHARACTERS ]############################################################## all-strings: - include: quoted-strings - include: backtick-strings quoted-strings: - include: single-quote-block-strings - include: single-quote-strings - include: double-quote-block-strings - include: double-quote-strings backtick-strings: - include: backtick-quote-block-strings - include: backtick-quote-strings backtick-quote-block-strings: - match: '```' scope: punctuation.section.interpolation.begin.just push: - meta_scope: meta.string.shell meta.interpolation.command.shell - meta_include_prototype: false - match: '```' scope: punctuation.section.interpolation.end.just pop: 1 backtick-quote-strings: - match: '`' scope: punctuation.section.interpolation.begin.just push: - meta_scope: meta.string.shell meta.interpolation.command.shell - meta_include_prototype: false - match: '`' scope: punctuation.section.interpolation.end.just pop: 1 double-quote-block-strings: - match: '"""' scope: punctuation.definition.string.begin.just push: - meta_scope: string.quoted.double.block.just - meta_include_prototype: false - match: \\. scope: constant.character.escape.just - match: '"""' scope: punctuation.definition.string.end.just pop: 1 double-quote-strings: - match: '"' scope: punctuation.definition.string.begin.just push: - meta_scope: string.quoted.double.just - meta_include_prototype: false - match: \\. scope: constant.character.escape.just - match: '"' scope: punctuation.definition.string.end.just pop: 1 single-quote-block-strings: - match: "'''" scope: punctuation.definition.string.begin.just push: - meta_scope: string.quoted.single.block.just - meta_include_prototype: false - match: "'''" scope: punctuation.definition.string.end.just pop: 1 single-quote-strings: - match: "'" scope: punctuation.definition.string.begin.just push: - meta_scope: string.quoted.single.just - meta_include_prototype: false - match: "'" scope: punctuation.definition.string.end.just pop: 1 ###[ VARIABLES ]############################################################### operands-variables: # First check for an invalid function - match: \b({{valid_name}})\b\s*(\() captures: 1: source.just 2: invalid.illegal.just - match: \b(?:{{valid_name}})\b scope: variable.other.just ###[ VARIABLE ASSIGNMENT ]##################################################### assignment: - match: (export)?\s*({{valid_name}})\s*(?=:=) captures: 1: keyword.declaration.variable.just 2: variable.other.just push: assignment-value assignment-value: - meta_include_prototype: false - match: := scope: keyword.operator.assignment.just set: assignment-value-body assignment-value-body: - include: eol-pop - include: just-expressions ###[ RECIPE DEFINITION ]####################################################### # Recipe definition lines, including attributes, arguments and dependencies recipe-attributes: - match: ^\[ scope: meta.annotation.just variable.annotation.just push: - recipe-attributes-body - expect-recipe-attribute-name - include: eol-pop recipe-attributes-body: - meta_scope: meta.sequence.list.just meta.annotation.just - match: \] scope: variable.annotation.just pop: 1 - match: ',' scope: punctuation.separator.parameters.just push: expect-recipe-attribute-name - include: eol-pop expect-recipe-attribute-name: - match: ({{recipe_attributes_arguments}}) scope: variable.annotation.just set: recipe-attribute-argument - match: ({{recipe_attributes_bare}}) scope: variable.annotation.just pop: 1 - match: \] scope: invalid.illegal.just comment: If properly handled, this frame will be popped before the \] is encountered pop: 2 - include: else-pop recipe-attribute-argument: - match: \( scope: punctuation.definition.annotation.begin.just set: recipe-attribute-argument-paren-body - match: ':' scope: keyword.operator.assignment.just set: recipe-attribute-argument-colon-body - include: else-pop recipe-attribute-argument-paren-body: - match: \) scope: punctuation.definition.annotation.end.just pop: 1 - match: ',' scope: punctuation.separator.parameters.just - match: '`' scope: invalid.illegal.just pop: 1 - include: quoted-strings recipe-attribute-argument-colon-body: - match: '`' scope: invalid.illegal.just pop: 1 - include: quoted-strings - include: else-pop recipe-definition: - match: (?=^@?{{valid_name}}(?![^:]*:=)) # Matches '^recipeName' but not '^varName :=' push: - recipe-body - recipe-name - recipe-modifier recipe-modifier: - match: ^@ scope: meta.function.just storage.modifier.quiet.just - include: else-pop recipe-name: - match: \b{{valid_name}} scope: meta.function.just entity.name.function.just pop: 1 # Only match the first instance push: - recipe-dependencies - recipe-assignment - recipe-parameter - include: else-pop recipe-assignment: - match: ':' scope: keyword.operator.assignment.just - include: eol-pop - include: else-pop recipe-parameter: - meta_content_scope: meta.function.parameters.just - match: (?=[\+\*$a-zA-Z_]) push: - recipe-parameter-assignment - recipe-parameter-name - recipe-export-operator - recipe-variadic-operator - include: else-pop recipe-variadic-operator: - match: '[\+\*](?!\s*:)' scope: keyword.operator.variadic.just pop: 1 # Only one allowed - include: else-pop recipe-export-operator: - match: \$(?=\s*{{valid_name}}) scope: keyword.operator.exported.just - match: '[\+\*\$]' scope: invalid.illegal.just - include: else-pop recipe-parameter-assignment: - match: = scope: keyword.operator.assignment.just push: - include: just-expressions - match: (\s+|(?=:)) pop: 1 - include: else-pop recipe-parameter-name: - match: \b{{valid_name}}\b scope: variable.parameter.just - include: else-pop recipe-dependencies: - match: (?=\() push: recipe-dependency-with-args - match: \b{{valid_name}}\b scope: variable.function.just - match: '&&' scope: keyword.operator.logical.just - include: eol-pop - include: else-pop recipe-dependency-with-args: - match: \( scope: punctuation.section.group.begin.just push: recipe-dependency-with-args-body recipe-dependency-with-args-body: - meta_scope: meta.group.just - match: \b{{valid_name}} scope: variable.function.just push: - include: just-expressions - include: else-pop - include: recipe-dependency-group-end recipe-dependency-group-end: - match: \) scope: punctuation.section.group.end.just pop: 2 # End dependency group recipe-body: # Python script shebang - match: ^\s+(?=#!.*\bpython(?:\d(?:\.\d+)?)?\b) pop: 1 embed: scope:source.python.embedded.just escape: ^(?=\S) # Shell script shebang - match: ^\s+(?=#!.*\b(bash|zsh|sh)\b) pop: 1 embed: scope:source.shell.embedded.just escape: ^(?=\S) - match: ^ comment: Content using the default shell pop: 1 ###[ RECIPE CONTENTS ]######################################################### recipe-generic-line: - match: ^(?=\s+\S) push: - recipe-generic-content - recipe-content-modifiers recipe-generic-content: - include: recipe-content-interpolations - include: recipe-content-strings - match: (\\)$\n? captures: 1: punctuation.separator.continuation.line.just - match: $\n comment: | No trailing '?', so this will NOT match lines matched by the previous rule. There is no pop there, so this context will remain on the stack WITHOUT the recipe-content-modifiers. This ensures those modifiers are matched IFF they occur at the beginning of non-continuation lines. pop: 1 recipe-content-modifiers: - match: ^\s+((@)|(-)(@)|(-)|(@)(-))(?!-) captures: 2: storage.modifier.quiet.just 3: storage.modifier.ignore-error.just 4: storage.modifier.quiet.just 5: storage.modifier.ignore-error.just 6: storage.modifier.quiet.just 7: storage.modifier.ignore-error.just - include: else-pop recipe-content-interpolations: - match: \{\{\{\{ comment: Escaped double brace. Do nothing - match: \{\{(?!\{) scope: punctuation.section.interpolation.begin.just push: recipe-content-interpolation-body recipe-content-interpolation-body: - meta_scope: meta.interpolation.just - match: \}\} scope: punctuation.section.interpolation.end.just pop: 1 - include: just-expressions # Sadly, almost an exact duplicate of the 'strings' context, but # needed to include interpolations, which would have to be nested # inside a push: in the other context. recipe-content-strings: - match: '"' scope: punctuation.definition.string.begin.just push: - meta_scope: meta.string.just string.quoted.double.just - meta_include_prototype: false - match: \\. scope: constant.character.escape.just - match: '"' scope: punctuation.definition.string.end.just pop: 1 - include: recipe-content-string-interpolations - match: "'" scope: punctuation.definition.string.begin.just push: - meta_scope: meta.string.just string.quoted.single.just - meta_include_prototype: false - match: "'" scope: punctuation.definition.string.end.just pop: 1 - include: recipe-content-string-interpolations recipe-content-string-interpolations: - match: \{\{\{\{ comment: Escaped double brace. Do nothing - match: \{\{(?!\{) scope: punctuation.section.interpolation.begin.just push: recipe-content-string-interpolation-body recipe-content-string-interpolation-body: - clear_scopes: 1 - meta_scope: meta.interpolation.just - include: recipe-content-interpolation-body ###[ Set Expressions ]######################################################### # Ex: "set shell := ['zsh', '-cu']", "set dotenv-load", "set export := false" settings: - match: ^set(?=\s) scope: storage.modifier.definition.just push: settings-name settings-name: - meta_scope: meta.setting-name.just - include: settings-boolean-name - include: settings-shell-name - include: settings-string-name - include: settings-deprecated-name - include: settings-invalid-name - include: else-pop settings-boolean-name: - match: '{{boolean_settings}}\b' scope: entity.name.definition.just set: - settings-boolean-value - assignment-operator settings-boolean-value: - match: (?:true|false)\b scope: constant.language.boolean.just pop: 1 - include: else-pop - include: eol-pop settings-deprecated-name: - match: ({{deprecated_settings}})\b captures: 1: invalid.deprecated.just push: eol-pop settings-invalid-name: - match: ({{valid_name}})\b\s*:= captures: 1: invalid.illegal.just push: eol-pop settings-shell-name: - match: '{{shell_settings}}\b' scope: entity.name.definition.just set: - settings-shell-value - assignment-operator settings-shell-value: - match: \[ scope: punctuation.section.brackets.start.just set: string-array-body - include: else-pop - include: eol-pop string-array-body: - meta_scope: meta.sequence.list.just - match: \] scope: punctuation.section.brackets.end.just pop: 1 - match: ',' scope: punctuation.separator.parameters.just - include: all-strings - include: eol-pop #?? settings-string-name: - match: '{{string_settings}}\b' scope: entity.name.definition.just set: - settings-string-value - assignment-operator settings-string-value: - include: all-strings - include: eol-pop # ###[ General Types ]########################################################## variable-name: - match: \b{{valid_name}}\b scope: variable.other.just - include: else-pop assignment-operator: - match: := scope: keyword.operator.assignment.just - include: else-pop ###[ Common Prototypes ]####################################################### line-end: - match: $ pop: 1 # Remove the current stack item when we're about to reach a new character # Learn more: https://github.com/sublimehq/Packages/issues/757#issuecomment-287193733 else-pop: - match: (?=\S) pop: 1 eol-pop: - match: $\n? pop: 1