1
0
Fork 0
blog/syntaxes/Sublime-console/console.sublime-syntax

1380 lines
45 KiB
Plaintext
Raw Normal View History

2020-05-19 18:20:18 +02:00
%YAML 1.2
# [Sublime]: https://www.sublimetext.com/docs/3/syntax.html
# [Bash]: https://www.gnu.org/software/bash/manual/bash.html
--- #---------------------------------------------------------------------------
name: console
scope: source.shell.console
#-------------------------------------------------------------------------------
variables:
call_token: \./
cmd_boundary: (?=\s|;|$|>|<)
extension: \.sh
identifier: '[[:alpha:]_][[:alnum:]_]*'
identifier_non_posix: '[^{{metachar}}\d][^{{metachar}}=]*'
is_command: (?=\S)
is_end_of_interpolation: \)
is_end_of_option: '[^\w$-]|$'
is_function: \s*\b(function)\s+|(?=\s*{{identifier_non_posix}}\s*\(\s*\))
is_path_component: (?=[^\s/]*/)
is_start_of_arguments: '[`=|&;()<>\s]'
is_variable: (?=\s*{{nbc}}(?:[({]{{nbc}}[)}])?{{nbc}}=)
keyword_break: (?![-=\w])
# A character that, when unquoted, separates words. A metacharacter is a
# space, tab, newline, or one of the following characters: |, &, ;,
# (, ), <, or >.
metachar: '[\s\t\n|&;()<>]'
nbc: '[^{}()=\s]*' # non bracket characters (and also non-whitespace, parens)
start_of_option: (?:\s+|^)--?(?=[\w$])
varassign: '[+\-?]?='
#-------------------------------------------------------------------------------
contexts:
comment:
- match: (?:^\s*|\s+)(\#)
captures:
1:
comment.line.number-sign.shell
punctuation.definition.comment.begin.shell
push:
- meta_content_scope: comment.line.number-sign.shell
# NOTE: The reason for consuming the newline character is as follows.
# When triggering a snippet, its scope is tested to the *right* of the
# cursor. So, if you don't want your snippet to trigger in a comment,
# you have to use something like <scope>source.shell - comment</scope>.
# If the newline character is not scoped as a comment too, then that
# scope will never work, because the scope to the right of the cursor
# will never be a comment scope. That is, unless we consume the newline
# character (or we are editing something in the middle of an existing
# comment).
- match: '(?=\n)'
scope: comment.line.number-sign.shell
pop: true
line-continuation-or-pop-at-end:
- include: pop-at-end
- include: line-continuation
pop-at-end:
- match: $
pop: true
any-escape:
- match: \\.
scope: constant.character.escape.shell
line-continuation:
- match: \\\n
scope: punctuation.separator.continuation.line.shell
push:
- match: ^
pop: true
- match: \\(\s+)\n
captures:
1: invalid.illegal.extraneous-spaces-after-line-continuation.shell
prototype:
- include: comment
- include: line-continuation
- include: any-escape
main:
- meta_include_prototype: false
- match: ^(\$|\#|)(?:\s)
captures:
1: keyword.operator.assignment.redirection.process.shell
push: console-line
console-line:
2020-05-19 20:38:33 +02:00
- match: $
pop: true
2020-05-19 18:20:18 +02:00
- include: prototype
- include: funcdef
- include: vardef
- include: redirection
- include: operator-exclamation
- match: '{{is_command}}'
push: cmd
# NOTE: Contexts with a "-bt" suffix are the "backtick" contexts. They mirror
# the ordinary contexts, except that when a backtick is encountered while in
# a "-bt" context, we pop.
# Normally, we are in a non-bt context. When we encounter a backtick character
# (the ` character), we enter the main-bt context. Popping when encountering
# another ` character then ensures that we don't enter yet another backtick
# context.
# The "expansion" context is the **only** place where this main-bt context is
# used. If you, the reader, knows of a more elegant way to handle backticks,
# don't hesitate to change it.
main-bt:
- include: funcdef-bt
- include: vardef
- include: redirection
- match: '{{is_command}}'
push: cmd-bt
control:
- match: \bif{{keyword_break}}
scope: keyword.control.conditional.if.shell
pop: true
- match: \bthen{{keyword_break}}
scope: keyword.control.conditional.then.shell
pop: true
- match: \belif{{keyword_break}}
scope: keyword.control.conditional.elseif.shell
pop: true
- match: \bfi{{keyword_break}}
scope: keyword.control.conditional.end.shell
set: [cmd-post, cmd-args]
- match: \belse{{keyword_break}}
scope: keyword.control.conditional.else.shell
pop: true
- match: \bfor{{keyword_break}}
scope: keyword.control.loop.for.shell
set: [cmd-post, for-args]
- match: \bdo{{keyword_break}}
scope: keyword.control.loop.do.shell
pop: true
- match: \bdone{{keyword_break}}
scope: keyword.control.loop.end.shell
set: [cmd-post, cmd-args]
- match: \bwhile{{keyword_break}}
scope: keyword.control.loop.while.shell
- match: \buntil{{keyword_break}}
scope: keyword.control.loop.until.shell
- match: \bcase{{keyword_break}}
scope: keyword.control.conditional.case.shell
set: [case-body, case-word]
- match: \bcontinue{{keyword_break}}
scope: keyword.control.flow.continue.shell
- match: \bbreak{{keyword_break}}
scope: keyword.control.flow.break.shell
set: [cmd-post, cmd-args]
- match: \besac{{keyword_break}}
scope: keyword.control.conditional.end.shell
pop: true
case-word:
- match: \bin{{keyword_break}}
scope: keyword.control.in.shell
pop: true
- include: case-end-ahead
- include: expansion-and-string
case-body:
- meta_scope: meta.conditional.case.shell
- match: \besac{{keyword_break}}
scope: keyword.control.conditional.end.shell
pop: true
- match: (?=\()
push:
- clear_scopes: 1 # remove meta.conditional.case.shell
- match: \(
scope: keyword.control.conditional.patterns.begin.shell
set: case-clause-patterns
- match: (?=\S)
push: case-clause-patterns
case-clause-patterns:
- clear_scopes: 1 # remove meta.conditional.case.shell
- meta_scope: meta.conditional.case.clause.patterns.shell
- match: \)
scope: keyword.control.conditional.patterns.end.shell
set: case-clause-commands
# emergency bail outs if ')' is missing
- match: (?=;;&?|;&)
set: case-clause-commands
- include: case-end-ahead
- include: case-clause-patterns-body
case-clause-patterns-body:
# [Bash] 3.2.4.2: Each pattern undergoes tilde expansion, parameter
# expansion, command substitution, and arithmetic expansion.
- include: expansion-pattern
- include: expansion-tilde
- include: expansion-parameter
- include: expansion-command
- include: expansion-arithmetic
- include: string
- match: \|
scope: keyword.operator.logical.shell
- match: \(
scope: punctuation.section.parens.begin.shell
push:
- match: \)
scope: punctuation.section.parens.end.shell
pop: true
- include: case-clause-patterns-body
case-clause-commands:
- clear_scopes: 1 # remove meta.conditional.case.shell
- meta_content_scope: meta.conditional.case.clause.commands.shell
- match: ;;&?|;&
scope:
meta.conditional.case.clause.commands.shell
punctuation.terminator.case.clause.shell
pop: true
- include: case-end-ahead
- include: main
case-end-ahead:
- match: (?=\besac{{keyword_break}})
pop: true
# I don't think anybody will write a for-loop inside backticks. Hence no
# for-args-bt context.
for-args:
- match: ""
set:
- meta_scope: meta.group.for.shell
- include: cmd-args-boilerplate
- include: arithmetic
- match: \bin{{keyword_break}}
scope: keyword.control.in.shell
expansion-and-string:
- include: string
- include: expansion
funcdef:
- match: '{{is_function}}'
captures:
1: storage.type.function.shell
push: [funcdef-body, funcdef-parens, funcdef-name]
- match: \bcoproc{{keyword_break}}
scope: keyword.other.coproc.shell
push: [cmd-post, cmd-args, coproc-body]
funcdef-bt:
- match: '{{is_function}}'
captures:
1: storage.type.function.shell
push: [funcdef-body-bt, funcdef-parens, funcdef-name]
- match: \bcoproc{{keyword_break}}
scope: keyword.other.coproc.shell
push: [cmd-post, cmd-args-bt, coproc-body]
coproc-body:
- match: \s*(?=\S+\s*\{)
set:
- meta_content_scope: entity.name.function.coproc.shell
- match: (?=\s*\{)
set:
- match: \{
scope: punctuation.section.braces.begin.shell
set:
- meta_scope: meta.function.coproc.shell
- match: \}
scope: punctuation.section.braces.end.shell
pop: true
- include: main
- match: ""
set: main-with-pop-at-end
funcdef-name:
- match: \s*
set:
- meta_content_scope: entity.name.function.shell
- match: (?=\s*[({]|$)
pop: true
funcdef-parens:
- match: (\()\s*(\))
captures:
1: punctuation.section.parens.begin.shell
2: punctuation.section.parens.end.shell
- match: \{
scope: punctuation.section.braces.begin.shell
pop: true
- match: \(
scope: punctuation.definition.compound.begin.shell
pop: true
funcdef-body:
- meta_scope: meta.function.shell
- match: \}
scope: punctuation.section.braces.end.shell
pop: true
- match: \)
scope: punctuation.definition.compound.end.shell
pop: true
- include: main
funcdef-body-bt:
- meta_scope: meta.function.shell
- match: \}
scope: punctuation.section.braces.end.shell
pop: true
- match: \)
scope: punctuation.definition.compound.end.shell
pop: true
- include: main-bt
vardef:
- match: \s*\b(alias){{keyword_break}}
captures:
1: support.function.alias.shell
push:
- vardef-ensure-function-call-scope
- vardef-maybe-more
- vardef-value
- vardef-assign
- vardef-alias-name
- vardef-alias-options
- match: \s*\b(typeset|declare|local){{keyword_break}}
captures:
1: storage.modifier.shell
push:
- vardef-ensure-function-call-scope
- vardef-maybe-more
- vardef-value
- vardef-assign
- vardef-name
- vardef-declare-options
- match: \s*\b(export){{keyword_break}}
captures:
1: storage.modifier.shell
push:
- vardef-ensure-function-call-scope
- vardef-maybe-more
- vardef-value
- vardef-assign
- vardef-name
- vardef-export-options
- match: \s*\b(readonly){{keyword_break}}
captures:
1: storage.modifier.shell
push:
- vardef-ensure-function-call-scope
- vardef-maybe-more
- vardef-value
- vardef-assign
- vardef-name
- vardef-readonly-options
- match: '{{is_variable}}'
push:
- vardef-value
- vardef-assign
- vardef-name
vardef-readonly-options:
- match: \s*((-)(?:[aAf]+|p))
captures:
2: punctuation.definition.parameter.shell
1: variable.parameter.option.shell
- match: \s*
pop: true
vardef-export-options:
- match: \s*((-)(?:[fn]+|p))
captures:
2: punctuation.definition.parameter.shell
1: variable.parameter.option.shell
- match: \s*
pop: true
vardef-ensure-function-call-scope:
- meta_include_prototype: false
- meta_scope: meta.function-call.shell
- match: ""
pop: true
vardef-maybe-more:
- meta_include_prototype: false
- match: (?=`)
pop: true
- match: (?=\s*#)
pop: true
- include: cmd-args-boilerplate
- match: (?=\S)
push: [vardef-value, vardef-assign, vardef-name]
vardef-alias-options:
- match: \s*((-)p)
captures:
2: punctuation.definition.parameter.shell
1: variable.parameter.option.shell
- match: \s*
pop: true
vardef-alias-name:
- match: \s*
set:
- meta_include_prototype: false
- meta_content_scope: entity.name.function.alias.shell
- include: line-continuation-or-pop-at-end
- include: any-escape
- match: (?={{varassign}}|\s)|$
pop: true
- include: array
- match: \s*$
pop: true
- include: string
vardef-declare-options:
- match: \s*((-)(?:[aAfFgilnrtux]+|p))
captures:
2: punctuation.definition.parameter.shell
1: variable.parameter.option.shell
- match: \s*
pop: true
vardef-name:
- match: \s*
set:
- meta_include_prototype: false
- meta_content_scope: variable.other.readwrite.assignment.shell
- include: line-continuation-or-pop-at-end
- include: any-escape
- match: (?={{varassign}}|\s)|$|(?=[;&`]|{{metachar}})
pop: true
- include: array
- match: \s*$
pop: true
- include: string
vardef-assign:
- meta_include_prototype: false
- include: line-continuation-or-pop-at-end
- include: any-escape
- match: '{{varassign}}'
scope: keyword.operator.assignment.shell
pop: true
- match: ""
pop: true
vardef-value:
- meta_include_prototype: false
- match: \(
scope: punctuation.section.parens.begin.shell
set:
- match: \)
scope: punctuation.section.parens.end.shell
pop: true
- match: \[
scope: punctuation.section.brackets.begin.shell
push:
- match: \]
scope: punctuation.section.brackets.end.shell
set:
- match: =
scope: keyword.operator.assignment.shell
pop: true
- match: ""
pop: true
- include: expansion-and-string
- include: expansion-and-string
- match: (?=[&`])
pop: true
- match: ""
set:
- meta_include_prototype: false
- meta_scope: string.unquoted.shell
- match: (?=`)
pop: true
- include: expansion-and-string
- include: line-continuation-or-pop-at-end
- include: any-escape
- match: (?={{metachar}})
pop: true
redirection:
- include: redirection-here-string
- include: redirection-here-document
- include: redirection-process
- include: redirection-input
- include: redirection-output
- include: redirection-inout
redirection-process:
- match: (\d*)([<>])(\()
captures:
1: constant.numeric.integer.decimal.file-descriptor.shell
2: keyword.operator.assignment.redirection.process.shell
3: punctuation.section.parens.begin.shell
push:
- match: \)
scope: punctuation.section.parens.end.shell
pop: true
- include: main
redirection-output:
- match: (\d*)(>>!?|>&?|&>|&?>(?:\||>))
captures:
1: constant.numeric.integer.decimal.file-descriptor.shell
2: keyword.operator.assignment.redirection.shell
push: redirection-post
redirection-input:
- match: (\d*)(<&?)
captures:
1: constant.numeric.integer.decimal.file-descriptor.shell
2: keyword.operator.assignment.redirection.shell
push: redirection-post
redirection-post:
- match: \s*(?:(\d+)|(-))
captures:
1: constant.numeric.integer.decimal.file-descriptor.shell
2: punctuation.terminator.file-descriptor.shell
pop: true
- match: \s*(?=\S)
set:
- match: (?={{metachar}}|`)
pop: true
- include: expansion-and-string
- match: \s*
pop: true
redirection-inout:
- match: (\d*)(<>)
captures:
1: constant.numeric.integer.decimal.file-descriptor.shell
2: keyword.operator.assignment.redirection.shell
redirection-here-string:
- match: (\d*)(<<<)\s
captures:
1: constant.numeric.integer.decimal.file-descriptor.shell
2: keyword.operator.herestring.shell
redirection-here-document:
# These are the variants that allow tabs before the end token
- match: (\d*)(<<-)\s*(')({{identifier}})(')
captures:
1: constant.numeric.integer.decimal.file-descriptor.shell
2: keyword.operator.assignment.redirection.shell
3: punctuation.definition.string.begin.shell
4: keyword.control.heredoc-token.shell
5: punctuation.definition.string.end.shell
push: [heredocs-body-allow-tabs-no-expansion, heredocs-preamble]
- match: (\d*)(<<-)\s*(")({{identifier}})(")
captures:
1: constant.numeric.integer.decimal.file-descriptor.shell
2: keyword.operator.assignment.redirection.shell
3: punctuation.definition.string.begin.shell
4: keyword.control.heredoc-token.shell
5: punctuation.definition.string.end.shell
push: [heredocs-body-allow-tabs-no-expansion, heredocs-preamble]
- match: (\d*)(<<-)\s*(\\)({{identifier}})
captures:
1: constant.numeric.integer.decimal.file-descriptor.shell
2: keyword.operator.assignment.redirection.shell
3: punctuation.definition.string.shell
4: keyword.control.heredoc-token.shell
push: [heredocs-body-allow-tabs-no-expansion, heredocs-preamble]
- match: (\d*)(<<-)\s*({{identifier}})
captures:
1: constant.numeric.integer.decimal.file-descriptor.shell
2: keyword.operator.assignment.redirection.shell
3: keyword.control.heredoc-token.shell
push: [heredocs-body-allow-tabs, heredocs-preamble]
# These are the variants that DON'T allow tabs before the end token
- match: (\d*)(<<)\s*(')({{identifier}})(')
captures:
1: constant.numeric.integer.decimal.file-descriptor.shell
2: keyword.operator.assignment.redirection.shell
3: punctuation.definition.string.begin.shell
4: keyword.control.heredoc-token.shell
5: punctuation.definition.string.end.shell
push: [heredocs-body-no-expansion, heredocs-preamble]
- match: (\d*)(<<)\s*(")({{identifier}})(")
captures:
1: constant.numeric.integer.decimal.file-descriptor.shell
2: keyword.operator.assignment.redirection.shell
3: punctuation.definition.string.begin.shell
4: keyword.control.heredoc-token.shell
5: punctuation.definition.string.end.shell
push: [heredocs-body-no-expansion, heredocs-preamble]
- match: (\d*)(<<)\s*(\\)({{identifier}})
captures:
1: constant.numeric.integer.decimal.file-descriptor.shell
2: keyword.operator.assignment.redirection.shell
3: punctuation.definition.string.shell
4: keyword.control.heredoc-token.shell
push: [heredocs-body-no-expansion, heredocs-preamble]
- match: (\d*)(<<)\s*({{identifier}})
captures:
1: constant.numeric.integer.decimal.file-descriptor.shell
2: keyword.operator.assignment.redirection.shell
3: keyword.control.heredoc-token.shell
push: [heredocs-body, heredocs-preamble]
heredocs-body:
- meta_include_prototype: false
- meta_scope: string.unquoted.heredoc.shell
- include: heredocs-body-common-with-expansion
- match: ^\3(\s+)\n # the third capture from redirection-here-document
captures:
1: invalid.illegal.no-spaces-allowed-after-heredoc-token.shell
# rather not pop, but sublime throws an error otherwise.
pop: true
- match: ^\3$ # the third capture from redirection-here-document
scope: keyword.control.heredoc-token.shell
pop: true
heredocs-body-allow-tabs:
- meta_include_prototype: false
- meta_scope: string.unquoted.heredoc.shell
- include: heredocs-body-common-with-expansion
- match: ^\s*\3(\s+)\n # the third capture from redirection-here-document
captures:
1: invalid.illegal.no-spaces-allowed-after-heredoc-token.shell
# rather not pop, but sublime throws an error otherwise.
pop: true
- match: ^\s*(\3)$ # the third capture from redirection-here-document
captures:
1: keyword.control.heredoc-token.shell
pop: true
heredocs-body-common-with-expansion:
# [Bash] 3.6.6: all lines of the here-document are subjected to parameter
# expansion, command substitution, and arithmetic expansion, the character
# sequence \newline is ignored, and \ must be used to quote the
# characters \, $, and `.
- match: \\[`$"\\]
scope: constant.character.escape.backtick.shell
- include: expansion-parameter
- include: expansion-arithmetic
- include: expansion-command
heredocs-body-no-expansion:
- meta_include_prototype: false
- meta_scope: string.unquoted.heredoc.shell
- match: ^\4(\s+)\n # the fourth capture from redirection-here-document
captures:
1: invalid.illegal.no-spaces-allowed-after-heredoc-token.shell
# rather not pop, but sublime throws an error otherwise.
pop: true
- match: ^\4$ # the fourth capture from redirection-here-document
scope: keyword.control.heredoc-token.shell
pop: true
heredocs-body-allow-tabs-no-expansion:
- meta_include_prototype: false
- meta_scope: string.unquoted.heredoc.shell
- match: ^\s*\4(\s+)\n # the fourth capture from redirection-here-document
captures:
1: invalid.illegal.no-spaces-allowed-after-heredoc-token.shell
# rather not pop, but sublime throws an error otherwise.
pop: true
- match: ^\s*(\4)$ # the fourth capture from redirection-here-document
captures:
1: keyword.control.heredoc-token.shell
pop: true
heredocs-preamble:
- match: ""
set:
# This enables us to keep parsing on the line where the start token of
# the heredoc is. Once the first line has ended, we enter the body of
# the heredoc, where everything is just an unquoted string.
# One clear_scope for the string.unquoted.
# The problem with this is that when we also end a function definition
# on the same line (with the "}" token), we cannot do that.
- clear_scopes: 1
- match: $
pop: true
- match: \s*(?=\S)
push: [main-with-pop-at-end, cmd-post, cmd-args]
main-with-pop-at-end:
- include: line-continuation-or-pop-at-end
- include: main
cmd-name-common:
- match: (?=}|\s+#|\s*(?:[|;]|&(?!>)))
pop: true
- include: string
- include: expansion-parameter
- include: expansion-arithmetic
- include: expansion-command
- include: expansion-tilde
- include: expansion-job
- include: line-continuation-or-pop-at-end
cmd-args-common:
- match: (?=}|\s+#)
pop: true
- include: redirection
- match: (?=\s*([|;]|&(?!>)))
pop: true
- include: expansion-and-string
- include: line-continuation-or-pop-at-end
cmd-post: # looks like [main, cmd-post] at this point
- match: ;(?![;&])
scope: keyword.operator.logical.continue.shell
pop: true
- match: \|\|
scope: keyword.operator.logical.or.shell
pop: true
- match: \|
scope: keyword.operator.logical.pipe.shell
pop: true
- match: \&\&
scope: keyword.operator.logical.and.shell
pop: true
- match: \&
scope: keyword.operator.logical.job.shell
pop: true
- match: $|(?=\S)
pop: true
cmd-args-boilerplate:
- match: (?={{is_end_of_interpolation}})
pop: true
- include: cmd-args-common
- match: (?:\s+|^)--(?=\s|$)
scope: keyword.operator.end-of-options.shell
set:
- meta_content_scope: meta.function-call.arguments.shell
- include: end-of-options-common
cmd-args-boilerplate-bt:
- match: (?={{is_end_of_interpolation}}|`) # <-------------- extra backtick
pop: true
- include: cmd-args-common
- match: (?:\s+|^)--(?=\s|$)
scope: keyword.operator.end-of-options.shell
set:
- meta_content_scope: meta.function-call.arguments.shell
- match: (?=`) # <-------------------------------------- extra backtick
pop: true
- include: end-of-options-common
end-of-options-common:
- include: redirection
- match: (?=[)};&|])
pop: true
- include: expansion-and-string
- include: line-continuation-or-pop-at-end
cmd-args:
- match: ""
set:
- meta_scope: meta.function-call.arguments.shell
- include: cmd-args-boilerplate
- match: '{{start_of_option}}'
scope: punctuation.definition.parameter.shell
push:
- meta_scope: variable.parameter.option.shell
- match: (?==)
set:
- match: =
scope: keyword.operator.assignment.option.shell
pop: true
- match: (?={{is_end_of_option}})
pop: true
- include: expansion-and-string
cmd-args-bt:
- match: ""
set:
- meta_scope: meta.function-call.arguments.shell
- include: cmd-args-boilerplate-bt
- match: '{{start_of_option}}'
scope: punctuation.definition.parameter.shell
push:
- meta_scope: variable.parameter.option.shell
- match: (?==)
set:
- match: =
scope: keyword.operator.assignment.option.shell
pop: true
- match: (?={{is_end_of_option}}|`) # <------------- extra backtick
pop: true
- include: expansion-and-string
cmd:
- include: cmd-common
- match: \(
scope: punctuation.definition.compound.begin.shell
push:
- match: \)
scope: punctuation.definition.compound.end.shell
set: [cmd-post, cmd-args]
- include: main
- include: scope:commands.builtin.shell.bash#main
- match: \blet\b
scope: support.function.let.bash
push:
- meta_scope: meta.function-call.shell
- match: $
pop: true
- include: expression
- match: (\[\[)(?=\s)
captures:
1: support.function.double-brace.begin.shell
set: [cmd-post, cmd-test-double-brace-args]
- match: (\[)(?=\s)
captures:
1: support.function.test.begin.shell
set: [cmd-post, cmd-test-brace-args]
- match: (\{)(?=\s)
captures:
1: punctuation.definition.compound.braces.begin.shell
push:
- match: \}
scope: punctuation.definition.compound.braces.end.shell
set: [cmd-post, cmd-args]
- include: main
- match: (?=\S)
set: [cmd-post, cmd-args, cmd-name]
cmd-bt:
- include: cmd-common
- match: \(
scope: punctuation.definition.compound.begin.shell
push:
- match: \)
scope: punctuation.definition.compound.end.shell
set: [cmd-post, cmd-args-bt]
- include: main
- include: scope:commands.builtin.shell.bash#main-bt
- match: \blet\b
scope: support.function.let.bash
push:
- meta_scope: meta.function-call.shell
- match: $|(?=\`)
pop: true
- include: expression
- match: (\[\[)(?=\s)
captures:
1: support.function.double-brace.begin.shell
set: [cmd-post, cmd-test-double-brace-args-bt]
- match: (\[)(?=\s)
captures:
1: support.function.test.begin.shell
set: [cmd-post, cmd-test-brace-args-bt]
- match: (\{)(?=\s)
captures:
1: punctuation.definition.compound.braces.begin.shell
push:
- match: \}
scope: punctuation.definition.compound.braces.end.shell
set: [cmd-post, cmd-args-bt]
- include: main-bt
- match: (?=\S)
set: [cmd-post, cmd-args-bt, cmd-name-bt]
cmd-test-brace-args:
- match: ""
set:
- meta_scope: meta.function-call.arguments.shell
- include: cmd-args-boilerplate
- match: \s+(\])
captures:
1: support.function.test.end.shell
pop: true
- include: expression-test
cmd-test-brace-args-bt:
- match: ""
set:
- meta_scope: meta.function-call.arguments.shell
- include: cmd-args-boilerplate-bt
- match: \s+(\])
captures:
1: support.function.test.end.shell
pop: true
- include: expression-test
cmd-test-double-brace-args:
- meta_scope: meta.function-call.arguments.shell
- match: \s+(\]\])
captures:
1: support.function.double-brace.end.shell
pop: true
- include: expression-test
# - include: cmd-args-boilerplate
cmd-test-double-brace-args-bt:
- meta_scope: meta.function-call.arguments.shell
- match: \s+(\]\])
captures:
1: support.function.double-brace.end.shell
pop: true
- include: expression-test
# - include: cmd-args-boilerplate-bt
cmd-name:
- match: ""
set:
- meta_scope: meta.function-call.shell variable.function.shell
- match: (?={{is_start_of_arguments}}|{{is_end_of_interpolation}})
pop: true
- include: cmd-name-common
cmd-name-bt:
- match: ""
set:
- meta_scope: meta.function-call.shell variable.function.shell
# extra backtick
- match: (?={{is_start_of_arguments}}|{{is_end_of_interpolation}}|`)
pop: true
- include: cmd-name-common
cmd-common:
- include: control
- include: arithmetic
- match: (?=\)|})
pop: true
- include: line-continuation-or-pop-at-end
arithmetic:
- match: \(\((?=.+\)\))
scope: punctuation.section.arithmetic.begin.shell
push:
- meta_scope: meta.group.arithmetic.shell
- match: \)\)
scope: punctuation.section.arithmetic.end.shell
pop: true
- include: expression
expansion-tilde:
- match: '~'
scope: meta.group.expansion.tilde variable.language.tilde.shell
expansion-brace:
- match: \{
scope: punctuation.section.expansion.brace.begin.shell
push:
- meta_scope: meta.group.expansion.brace.shell
- match: \}
scope: punctuation.section.expansion.brace.end.shell
pop: true
- match: \,
scope: punctuation.separator.shell
- include: expansion-and-string
expansion-parameter:
- match: (\$)(\{)
captures:
0: meta.group.expansion.parameter.shell
1: punctuation.definition.variable.shell
2: punctuation.section.expansion.parameter.begin.shell
push:
- meta_content_scope: meta.group.expansion.parameter.shell
- meta_include_prototype: false
- match: \!
scope: keyword.operator.indirection.shell
set: expansion-parameter-post-first-character
- match: \#
scope: keyword.operator.arithmetic.shell
set: expansion-parameter-post-first-character
- match: ""
set: expansion-parameter-post-first-character
- match: (\$)(\d)
captures:
0: meta.group.expansion.parameter.shell
1: punctuation.definition.variable.shell
2: variable.other.readwrite.shell
- match: (\$)([$#@!~*?_-])(?!\w)
captures:
0: meta.group.expansion.parameter.shell
1: punctuation.definition.variable.shell
2: variable.language.shell
- match: (\$)({{identifier}})
captures:
0: meta.group.expansion.parameter.shell
1: punctuation.definition.variable.shell
2: variable.other.readwrite.shell
expansion-pattern:
- match: ([?*+@!])(\()
captures:
1: keyword.operator.regexp.quantifier.shell
2: punctuation.section.parens.begin.shell
push:
- match: \)
scope: punctuation.section.parens.end.shell
pop: true
- match: \|
scope: keyword.operator.logical.or.shell
- include: expansion-and-string
- match: '[*?]'
scope: keyword.operator.regexp.quantifier.shell
- match: \[(?=.*])
scope: keyword.control.regexp.set.begin.shell
push:
- match: (?=])
set: expansion-pattern-post-first-char
- match: '[!^]'
scope: keyword.operator.logical.not.shell
set: expansion-pattern-post-first-char
- match: \-
set: expansion-pattern-post-first-char
- match: ""
set: expansion-pattern-post-first-char
expansion-pattern-post-first-char:
- match: (?:-)?(\])
captures:
1: keyword.control.regexp.set.end.shell
pop: true
- match: \-
scope: keyword.operator.word.shell
- match: (\.)[[:word:]](\.)
captures:
1: punctuation.separator.collate.begin.shell
2: punctuation.separator.collate.end.shell
- match: (=)[[:word:]](=)
captures:
1: punctuation.separator.equivalence-class.begin.shell
2: punctuation.separator.equivalence-class.end.shell
- match: (:)[[:lower:]]+(:)
captures:
1: punctuation.separator.character-class.begin.shell
2: punctuation.separator.character-class.end.shell
# You cannot have a regex set inside a regex set, so just consume this
# character in order to not push into another regex set.
# Except when writing a character class like [:lower:], so negative look
# ahead for that possibility.
- match: \[(?![\.=:])
- include: expansion-and-string
expansion-arithmetic:
- match: (\$)(\(\()(?=.+\)\))
captures:
1: punctuation.definition.variable.shell
2: punctuation.section.parens.begin.shell
push:
- meta_scope: meta.group.expansion.arithmetic.shell
- match: \)\)
scope: punctuation.section.parens.end.shell
pop: true
- include: expression
expansion-command:
- match: (\$)(\()
captures:
1: punctuation.definition.variable.shell
2: punctuation.section.parens.begin.shell
push:
- meta_scope: meta.group.expansion.command.parens.shell
- match: \s*(\))
captures:
1: punctuation.section.parens.end.shell
pop: true
- include: main
- match: \`
scope: punctuation.section.group.begin.shell
push:
- meta_scope: meta.group.expansion.command.backticks.shell
- match: \`
scope: punctuation.section.group.end.shell
pop: true
- include: main-bt # all those *-bt contexts just for this!!!!
expansion:
- include: expansion-pattern
- include: expansion-parameter
- include: expansion-brace
- include: expansion-arithmetic
- include: expansion-command
- include: expansion-tilde
- include: expansion-job
expansion-parameter-common:
- meta_content_scope: meta.group.expansion.parameter.shell
- match: \}
scope:
meta.group.expansion.parameter.shell
punctuation.section.expansion.parameter.end.shell
pop: true
- include: string
- include: expansion-parameter
# no brace expansion
- include: expansion-arithmetic
- include: expansion-command
- include: expansion-tilde
# no pattern expansion
- include: any-escape
array:
- match: \[
scope: punctuation.section.braces.begin.shell
push:
- match: \]
scope: punctuation.section.braces.end.shell
pop: true
- match: '[*@]'
scope: variable.language.array.shell
- include: expression
expansion-parameter-post-first-character:
- meta_content_scope:
meta.group.expansion.parameter.shell
variable.other.readwrite.shell
- include: expansion-parameter-common
- match: (?=[@*]?/)
set:
- meta_content_scope: meta.group.expansion.parameter.shell
- match: ([@*])?(/)
captures:
1: variable.language.shell
2: keyword.operator.substitution.shell
set:
- meta_include_prototype: false
- meta_content_scope: meta.group.expansion.parameter.shell
- match: '[/#%]'
scope: variable.parameter.switch.shell
set: expansion-parameter-pattern
- match: ""
set: expansion-parameter-pattern
- match: (?=\:?[-+=?])
set:
- meta_content_scope: meta.group.expansion.parameter.shell
- match: \:?[-+=?]
scope: keyword.operator.assignment.shell
set: expansion-parameter-common
- match: (?=@?:)
set:
- meta_content_scope: meta.group.expansion.parameter.shell
- match: '(@)?(:)'
captures:
1: variable.language.shell
2: keyword.operator.substring.begin.shell
set:
- meta_content_scope: meta.group.expansion.parameter.shell
- match: (?=:)
set:
- meta_content_scope: meta.group.expansion.parameter.shell
- match: ":"
scope: keyword.operator.substring.end.shell
set:
- meta_content_scope: meta.group.expansion.parameter.shell
- include: expression
- include: expansion-parameter-common
- include: expression
- include: expansion-parameter-common
- match: \#(?=})
- match: ([@*])?(\#\#?|%%?|\^\^?|,,?)
captures:
1: variable.language.shell
2: keyword.operator.expansion.shell
set:
- meta_include_prototype: false
- meta_content_scope: meta.group.expansion.parameter.shell
- include: expansion-parameter-common
- include: expansion-pattern
- match: ([@*]?)(@)([QEPAa])(?=})
captures:
1: variable.language.shell
2: keyword.operator.expansion.shell
3: variable.parameter.switch.shell
- include: array
- match: '[*@](?=})'
scope: variable.language.shell
expansion-parameter-pattern:
- meta_content_scope: meta.group.expansion.parameter.shell
- match: /
scope: keyword.operator.substitution.shell
set: expansion-parameter-common
- include: expansion-parameter-common
- include: expansion-pattern
expansion-job:
# There are a number of ways to refer to a job in the shell.
# The symbols %% and %+ refer to the shells notion of the current job,
# which is the last job stopped while it was in the foreground or started in
# the background. The previous job may be referenced using %-.
- match: (%)([%+-])
captures:
0: meta.group.expansion.job.shell
1: punctuation.definition.variable.job.shell
2: variable.language.job.shell
# The character % introduces a job specification (jobspec). Job number n
# may be referred to as %n.
- match: (%)(\d+)
captures:
0: meta.group.expansion.job.shell
1: punctuation.definition.variable.job.shell
2: constant.numeric.integer.decimal.job.shell
# A job may also be referred to using a prefix of the name used to start it,
# or using a substring that appears in its command line. For example, %ce
# refers to a stopped ce job. Using %?ce, on the other hand, refers to any
# job containing the string ce in its command line. If the prefix or
# substring matches more than one job, Bash reports an error.
- match: (%)(\??)(\w+)
captures:
0: meta.group.expansion.job.shell
1: punctuation.definition.variable.job.shell
2: keyword.operator.regexp.quantifier.shell
3: variable.other.readwrite.shell
# A single % (with no accompanying job specification) also refers to the
# current job.
- match: '%'
scope:
meta.group.expansion.job.shell
punctuation.definition.variable.job.shell
expression:
# A leading 0x or 0X denotes hexadecimal.
- match: \b0[xX]
scope: punctuation.definition.numeric.base.shell
push:
- meta_scope: constant.numeric.integer.hexadecimal.shell
- match: '[g-zG-Z]'
scope: invalid.illegal.not-a-hex-character.shell
pop: true
- match: (?=\H)
pop: true
# Constants with a leading 0 are interpreted as octal numbers.
- match: \b0(?=[0-7])
scope: punctuation.definition.numeric.base.shell
push:
- meta_scope: constant.numeric.integer.octal.shell
- match: '[89]'
scope: invalid.illegal.not-an-octal-character.shell
pop: true
- match: (?=[^0-7])
pop: true
# Otherwise, numbers take the form [base#]n, where the optional base is a
# decimal number between 2 and 64 representing the arithmetic base, and n is
# a number in that base. When specifying n, the digits greater than 9 are
# represented by the lowercase letters, the uppercase letters, @, and _,
# in that order.
- match: \b(\d+#)[a-zA-Z0-9@_]+
scope: constant.numeric.integer.other.shell
captures:
1: punctuation.definition.numeric.base.shell
# If base# is omitted, then base 10 is used.
- match: \b\d+
scope: constant.numeric.integer.decimal.shell
- match: '[*/%+\-&^|]?=|<<=|>>='
scope: keyword.operator.assignment.shell
- match: \+\+?|\-\-?|\*\*?|%|/
scope: keyword.operator.arithmetic.shell
- match: <[=<]?|>[=>]?|[=!]=|&&|\:|\|\||!
scope: keyword.operator.logical.shell
- match: '[&|^~]'
scope: keyword.operator.bitwise.shell
- match: '[,;]'
scope: punctuation.separator.shell
- match: \?
scope: keyword.operator.ternary.shell
- match: \(
scope: punctuation.section.parens.begin.shell
push:
- meta_scope: meta.group.parens.shell
- match: \)
scope: punctuation.section.parens.end.shell
pop: true
- include: expression
# Shell variables are allowed as operands; parameter expansion is performed
# before the expression is evaluated. Within an expression, shell variables
# may also be referenced by name without using the parameter expansion
# syntax.
- include: string
- include: expansion-parameter
- include: expansion-arithmetic
- include: expansion-command
expression-test:
- include: expansion-and-string
- match: ((-)[aobcdefghknoprstuvwxzGLNORS])(?=\s)
captures:
2: punctuation.definition.parameter.shell
1: variable.parameter.option.shell
- match: ((-)(?:ef|nt|ot|eq|ne|lt|le|gt|ge))(?=\s)
captures:
2: punctuation.definition.parameter.shell
1: variable.parameter.option.shell
- match: (=~)\s*
captures:
1: keyword.operator.logical.shell
push:
- meta_content_scope: meta.regexp.shell
- match: (?=\s)
pop: true
- include: expansion-and-string
- match: ==?|!=?|<|>|\|\||&&
scope: keyword.operator.logical.shell
operator-exclamation:
- match: \!(?!\S)
scope: keyword.operator.logical.shell
- match: (\!)(-?\d+|!)
scope: variable.language.history.shell
captures:
1: punctuation.definition.history.shell
- match: \!
scope: punctuation.definition.history.shell
string:
- include: string-quoted-double
- include: string-quoted-single
- include: string-ansi-c
- include: string-locale
# nothing is escaped in a singly-quoted string!
string-quoted-single:
- match: \'
scope: punctuation.definition.string.begin.shell
push:
- meta_include_prototype: false
- meta_scope: string.quoted.single.shell
- match: \'
scope: punctuation.definition.string.end.shell
pop: true
string-quoted-double:
- match: \"
scope: punctuation.definition.string.begin.shell
push:
- meta_include_prototype: false
- meta_scope: string.quoted.double.shell
- include: string-quoted-double-common
string-quoted-double-escape-character:
- match: \\[$`"\\]
scope: constant.character.escape.shell
- match: \\\n
scope: constant.character.escape.shell
push:
- meta_include_prototype: false
- match: (?=\S)
pop: true
# [Bash] 3.1.2.4
string-ansi-c:
- match: \$'
scope: punctuation.definition.string.begin.shell
push:
- meta_include_prototype: false
- meta_scope: string.quoted.single.ansi-c.shell
- match: "'"
scope: punctuation.definition.string.end.shell
pop: true
- include: string-quoted-double-escape-character
- match: \\([abfnrtv'"?]|[0-8]{1,3}|x\h{1,8}|c[a-z])
scope: constant.character.escape.shell
# [Bash] 3.1.2.5
# If the string is translated and replaced, the replacement is double-quoted.
string-locale:
- match: \$"
scope: punctuation.definition.string.begin.shell
push:
- meta_include_prototype: false
- meta_scope: string.quoted.double.locale.shell
- include: string-quoted-double-common
string-quoted-double-common:
- match: \"
scope: punctuation.definition.string.end.shell
pop: true
- include: string-quoted-double-escape-character
- include: expansion-parameter
- include: expansion-arithmetic
- include: expansion-command