on master help sign in
add fish shell syntax definition
commitd72549a873a20f8fb89fdcaecc7a9a4b82883d61 authorTim Sampson <tim@sampson.fi> dateSat 2 Feb 2019, 11:45 (8 years ago) parent1ba8540
3 files changed+1013
.gitmodules+3 assets/syntaxes/fish.sublime-syntax+1009 assets/syntaxes/sublime-fish+1
.gitmodules+3
@@ -123,3 +123,6 @@
123123[submodule "assets/syntaxes/varlink"]
124124 path = assets/syntaxes/varlink
125125 url = https://github.com/varlink/syntax-highlight-varlink.git
126+[submodule "assets/syntaxes/sublime-fish"]
127+ path = assets/syntaxes/sublime-fish
128+ url = https://github.com/Phidica/sublime-fish.git
assets/syntaxes/fish.sublime-syntaxnew file+1009
@@ -0,0 +1,1009 @@
1+%YAML 1.2
2+---
3+# http://www.sublimetext.com/docs/3/syntax.html
4+name: friendly interactive shell (fish)
5+file_extensions:
6+ - fish
7+first_line_match: '^#!.*\b(fish)|^#\s*-\*-[^*]*mode:\s*shell-script[^*]*-\*-'
8+scope: source.shell.fish
9+contexts:
10+ main:
11+ - include: comment-external
12+ - include: line-continuation
13+ - match: \)|end
14+ comment: In an ideal world, command-call-standard would be performing this match because fish highlights the strings which follow as arguments. But we can't do that in a tmLanguage
15+ push:
16+ - meta_scope: meta.function-call.fish invalid.illegal.function-call.fish
17+ - match: '(?=[\s;&)|<>])'
18+ pop: true
19+ - match: (?=\S)
20+ comment: Anonymous scope - Base scope pipeline goes up until one of the definitive ends (newline and ';') or the sequences that could be an end if we're actually inside a $self scope right now (')' and "end")
21+ push:
22+ - match: \n|(;)|(?=\))|(?=end)
23+ captures:
24+ 1: meta.function-call.operator.fish keyword.operator.control.fish
25+ pop: true
26+ - match: '(?:[&|]|(?:[0-9]+)?(?:<|>>?|\^\^?))'
27+ comment: Match operators ('&', pipe, and redirect) which cannot start a pipeline because they must be consumed within or after a pipeline
28+ scope: invalid.illegal.operator.fish
29+ - include: comment-internal-end
30+ - match: (?=\S)
31+ comment: The reason we match '&' here is because we explicitly require it come after a command (unlike ';' which can be alone on a line)
32+ push:
33+ - match: '(?=[\n;)])|(&)'
34+ captures:
35+ 1: meta.function-call.operator.fish keyword.operator.control.fish
36+ pop: true
37+ - include: pipeline
38+ argument:
39+ - match: '(?![\s;&)|<>^])'
40+ comment: End arg if it precedes whitespace or operators (excluding stderr redirect '^' due to a fish quirk)
41+ push:
42+ - match: '(?=[\s;&)|<>])'
43+ pop: true
44+ - match: \%
45+ comment: Process expansion only occurs if the '%' is at the front of the argument, and continues for the entire argument
46+ captures:
47+ 0: meta.string.unquoted.fish punctuation.definition.process.fish
48+ push:
49+ - meta_scope: meta.parameter.argument.process-expansion.fish
50+ - match: '(?=[\s;&)|<>])'
51+ pop: true
52+ - match: '(?:self|last)(?=$|[\s;&)|<>])'
53+ comment: Match special process names. By a convention that I'm making up, scope them as a type of variable
54+ scope: meta.string.unquoted.fish variable.language.fish
55+ - include: parameter-patterns
56+ - match: '(?:[+-]?)(?:[0-9]+\.?[0-9]*|[0-9]*\.?[0-9]+)(?=$|[\s;&)|<>])'
57+ comment: Treat a sequence of integers (with possible sign and decimal separator) as a standalone constant. Do this separate to the
58+ scope: meta.parameter.argument.numeric.fish meta.string.unquoted.fish constant.numeric.fish
59+ - match: '(?![\s($])'
60+ comment: This scope can be used by plugins to locate arguments which don't *start* with command substitution or variable expansion and may directly resolve to file paths. Of course, they could have command substitution or variable expansion further on in them, but looking ahead for that too is nontrivial
61+ push:
62+ - meta_scope: meta.parameter.argument.path.fish
63+ - match: '(?=[\s;&)|<>])'
64+ pop: true
65+ - match: \~
66+ comment: Home directory expansion only occurs if the '~' is at the front of the argument, so check it first
67+ scope: meta.string.unquoted.fish keyword.operator.tilde.fish
68+ - include: parameter-patterns
69+ - match: (?!\s)
70+ comment: Use standard parameter patterns for whatever doesn't match the above
71+ push:
72+ - meta_scope: meta.parameter.argument.fish
73+ - match: '(?=[\s;&)|<>])'
74+ pop: true
75+ - include: parameter-patterns
76+ command-call-meta:
77+ - match: '(builtin|command|exec)\b(?!\s+[-&|])'
78+ comment: These meta commands force the parameter to behave as a standard command. They stop at piping
79+ captures:
80+ 1: support.function.fish
81+ push:
82+ - match: |-
83+ (?x)
84+ (?# Look ahead for control operations after whitespace)
85+ (?=\s*
86+ (?:
87+ (?# Find simple control operations)
88+ [\n;&)]
89+ |
90+ (?# Find piping)
91+ (?:(?:[0-9]+)?(?:<|>>?|\^\^?))?\|
92+ )
93+ )
94+ pop: true
95+ - include: line-continuation
96+ - include: command-call-standard
97+ - match: '(not)\b(?!\s+[-&|])'
98+ comment: This meta command acts as a unary operator on the command to the right, which can also be a meta command. It only applies to one command and stops at piping
99+ captures:
100+ 1: keyword.operator.word.fish
101+ push:
102+ - match: |-
103+ (?x)
104+ (?# Look ahead for control operations after whitespace)
105+ (?=\s*
106+ (?:
107+ (?# Find simple control operations)
108+ [\n;&)]
109+ |
110+ (?# Find piping)
111+ (?:(?:[0-9]+)?(?:<|>>?|\^\^?))?\|
112+ )
113+ )
114+ pop: true
115+ - include: line-continuation
116+ - include: command-call-meta
117+ - include: command-call-standard
118+ command-call-standard:
119+ - match: '\#'
120+ comment: A command call can't be a comment, but this match will only be satisfied if the command is first after a pipe because comments are otherwise consumed earlier
121+ push:
122+ - meta_scope: invalid.illegal.function-call.fish
123+ - match: '(?=[\n)])'
124+ pop: true
125+ - match: "(?:[&|<>^])"
126+ comment: Match an operator which cannot start a command call but does not stop the next characters from being interpreted as a command
127+ scope: invalid.illegal.operator.fish
128+ - match: (?=\S)
129+ comment: Anonymous scope - A complete command comprising a name element and optional parameter, redirection, and comment elements
130+ push:
131+ - match: |-
132+ (?x)
133+ (?# Look ahead for operators)
134+ (?=
135+ (?:
136+ (?# Find a control operator)
137+ [\n;&)]
138+ |
139+ (?# Find a pipe operator)
140+ (?:(?:[0-9]+)?(?:<|>>?|\^\^?))?\|
141+ )
142+ )
143+ pop: true
144+ - match: '(?![\s<>^%])'
145+ comment: Anonymous scope - A name or block element. If a block is found, everything up to the `end` command is captured here. Note that redirection and process expansion can't start the element
146+ push:
147+ - match: '(?=[\s;&)|<>])'
148+ pop: true
149+ - include: command-call-standard-block
150+ - match: '\[(?=[\s<>]|\\\n)'
151+ comment: "Look for the alternate form of test, which uses a matching pair of '[' ']'"
152+ captures:
153+ 0: support.function.test.begin.fish
154+ push:
155+ - match: '(\])|(\n|[;&)|].*)'
156+ captures:
157+ 1: support.function.test.end.fish
158+ 2: invalid.illegal.function-call.fish
159+ pop: true
160+ - include: line-continuation
161+ - include: parameter
162+ - include: redirection
163+ - match: '(?:break|continue|return)(?=[\s;&)|<>])'
164+ comment: Look for loop/function control commands. We perform no checking on the validity of their scope (because only allowing them in the correct scope won't work if they are used within if-blocks) or parameters (because fish does that during execution not parsing)
165+ captures:
166+ 0: keyword.control.conditional.fish
167+ - match: (?!\s)
168+ comment: Anonymous scope - A generic name element
169+ push:
170+ - match: '(?=[\s;&)|<>])'
171+ pop: true
172+ - match: (?=\()
173+ comment: fish would match the whole command name invalid if there was a command substitution anywhere in it, but we can't look ahead that effectively
174+ push:
175+ - meta_scope: invalid.illegal.function-call.fish
176+ - match: '(?=[\s;&)|<>])'
177+ pop: true
178+ - match: \(
179+ push:
180+ - match: '\)|(?=[\n;&)|<>])'
181+ pop: true
182+ - match: (?!\s)
183+ comment: Otherwise, treat the element as a fraction of a name made of arbitrary strings (which breaks at an escaped newline)
184+ push:
185+ - meta_scope: variable.function.fish
186+ - match: '(?=[\s;&()|<>])'
187+ pop: true
188+ - match: \$
189+ comment: The string scope explicitly forbids '$' so that the argument rule can pick it up as a variable expansion, but '$' is treated as a literal in command names, so we have to match it separately
190+ scope: meta.string.unquoted.fish
191+ - include: string
192+ - match: \%
193+ comment: A command name can't begin with a process expansion operator (however the variable expansion operator '$' is allowed)
194+ push:
195+ - meta_scope: invalid.illegal.function-call.fish
196+ - match: '(?=[\s;&)|<>])'
197+ pop: true
198+ - include: string
199+ - include: redirection
200+ - match: '(?:[^\n\S]+)'
201+ comment: Match any whitespace characters that aren't the newline
202+ push:
203+ - match: |-
204+ (?x)
205+ (?# Look ahead for operators)
206+ (?=
207+ (?:
208+ (?# Find a control operator)
209+ [\n;&)]
210+ |
211+ (?# Find a pipe operator)
212+ (?:(?:[0-9]+)?(?:<|>>?|\^\^?))?\|
213+ )
214+ )
215+ pop: true
216+ - match: '(?!--[\s;&)|<>])'
217+ comment: A list of elements that does not start with an end-of-options parameter
218+ push:
219+ - match: |-
220+ (?x)
221+ (?# Look ahead for operators or the end of options)
222+ (?=
223+ (?:
224+ (?# Find a control operator)
225+ [\n;&)]
226+ |
227+ (?# Find a pipe operator)
228+ (?:(?:[0-9]+)?(?:<|>>?|\^\^?))?\|
229+ |
230+ (?# Find a double hyphen)
231+ --[\s;&)|<>]
232+ )
233+ )
234+ pop: true
235+ - include: line-continuation
236+ - include: comment-internal-end
237+ - include: redirection
238+ - include: parameter
239+ - match: '(?=--[\s;&)|<>])'
240+ comment: A list of elements that starts with an end-of-options parameter
241+ push:
242+ - match: |-
243+ (?x)
244+ (?# Look ahead for operators)
245+ (?=
246+ (?:
247+ (?# Find a control operator)
248+ [\n;&)]
249+ |
250+ (?# Find a pipe operator)
251+ (?:(?:[0-9]+)?(?:<|>>?|\^\^?))?\|
252+ )
253+ )
254+ pop: true
255+ - match: '(?=--[\s;&)|<>])'
256+ comment: Contain just the end-of-options parameter and give it the normal scope
257+ push:
258+ - match: '(?=[\s;&)|<>])'
259+ pop: true
260+ - include: parameter
261+ - match: (?=\s)
262+ comment: A list of elements (now forcibly using arguments)
263+ push:
264+ - match: |-
265+ (?x)
266+ (?# Look ahead for operators)
267+ (?=
268+ (?:
269+ (?# Find a control operator)
270+ [\n;&)]
271+ |
272+ (?# Find a pipe operator)
273+ (?:(?:[0-9]+)?(?:<|>>?|\^\^?))?\|
274+ )
275+ )
276+ pop: true
277+ - include: line-continuation
278+ - include: comment-internal-end
279+ - include: redirection
280+ - include: argument
281+ command-call-standard-block:
282+ - match: '(begin|while|if|for|switch|function)\s*([&|<>])'
283+ comment: Block commands cannot be backgrounded, piped, or redirected
284+ captures:
285+ 1: variable.function.fish
286+ 2: invalid.illegal.operator.fish
287+ - match: (begin)\s*(\))
288+ comment: The begin command uniquely cannot be the last command in a command substitution
289+ captures:
290+ 1: variable.function.fish
291+ 2: invalid.illegal.operator.fish
292+ - match: 'begin(?=\s*$|\s*[\n;]|\s+[^\s-])'
293+ comment: The begin command can be alone on a line or followed by any command that doesn't start with a '-'. If a '-' is seen it shouldn't be treated as a block
294+ captures:
295+ 0: keyword.control.conditional.fish
296+ push:
297+ - meta_scope: meta.block.begin.fish
298+ - match: 'end(?=$|[\s;&)|<>])'
299+ captures:
300+ 0: keyword.control.conditional.fish
301+ pop: true
302+ - include: main
303+ - match: '(?=while\s+[^\s;)-])'
304+ comment: If the command name is followed by a valid string (not anything that closes the scope or a string starting with a '-') then open the scope
305+ push:
306+ - meta_scope: meta.block.while.fish
307+ - match: 'end(?=$|[\s;&)|<>])'
308+ captures:
309+ 0: keyword.control.conditional.fish
310+ pop: true
311+ - match: while
312+ comment: Anonymous scope - Capture the command name we know is there, include a single instance of a pipeline, and end when an operator is seen
313+ captures:
314+ 0: keyword.control.conditional.fish
315+ push:
316+ - match: '\s*(?=[\n;&)])'
317+ pop: true
318+ - include: line-continuation
319+ - include: pipeline
320+ - match: '\n|(;)|([&)])'
321+ comment: Anonymous scope - Capture the operator we know is there, include the base scope, and end when an `end` command is seen
322+ captures:
323+ 1: keyword.operator.control.fish
324+ 2: invalid.illegal.operator.fish
325+ push:
326+ - match: '(?=end(?:$|[\s;&)|<>]))'
327+ pop: true
328+ - include: main
329+ - match: '(?=if\s+[^\s;)-])'
330+ comment: If the command name is followed by a valid string (not anything that closes the scope or a string starting with a '-') then open the scope
331+ push:
332+ - meta_scope: meta.block.if.fish
333+ - match: 'end(?=$|[\s;&)|<>])'
334+ captures:
335+ 0: keyword.control.conditional.fish
336+ pop: true
337+ - include: command-call-standard-block-if-internal
338+ - match: '(?=for\s+[^\s;)-])'
339+ comment: If the command name is followed by a valid string (not anything that closes the scope or a string starting with a '-') then open the scope
340+ push:
341+ - meta_scope: meta.block.for-in.fish
342+ - match: 'end(?=$|[\s;&)|<>])'
343+ captures:
344+ 0: keyword.control.conditional.fish
345+ pop: true
346+ - match: (for)(?:\s+)
347+ comment: Anonymous scope - Capture the command name we know is there, include a single instance of a parameter (the varname), and end when the whitespace after the varname is captured
348+ captures:
349+ 1: keyword.control.conditional.fish
350+ push:
351+ - match: \s+
352+ pop: true
353+ - include: line-continuation
354+ - include: parameter
355+ - match: \S+
356+ comment: Capture anything that a parameter explicitly rejects, which is mostly operators
357+ scope: invalid.illegal.operator.fish
358+ - include: line-continuation
359+ - match: in(?=\s)
360+ comment: Anonymous scope - Capture the command name which might be there, include an arbitrary number of arguments, and end when the control operator is seen
361+ captures:
362+ 0: keyword.control.conditional.fish
363+ push:
364+ - match: '\s*(?=[\n;&)])'
365+ pop: true
366+ - include: line-continuation
367+ - include: comment-internal-end
368+ - include: argument
369+ - match: '\n|(;)|([&)])'
370+ comment: Anonymous scope - Capture the operator we know is there, include the base scope, and end when an `end` command is seen
371+ captures:
372+ 1: keyword.operator.control.fish
373+ 2: invalid.illegal.operator.fish
374+ push:
375+ - match: '(?=end(?:$|[\s;&)|<>]))'
376+ pop: true
377+ - include: main
378+ - match: '\S+?(?=[\s;&)])'
379+ comment: Anything beside line continuation, "in", or a control operator is invalid
380+ scope: invalid.illegal.function-call.fish
381+ - match: '(?=switch\s+[^\s;)-])'
382+ comment: If the command name is followed by a valid string (not anything that closes the scope or a string starting with a '-') then open the scope
383+ push:
384+ - meta_scope: meta.block.switch.fish
385+ - match: 'end(?=$|[\s;&)|<>])'
386+ captures:
387+ 0: keyword.control.conditional.fish
388+ pop: true
389+ - match: (?=switch)
390+ comment: Anonymous scope - Match the valid part of the switch statement, then look for an invalid part
391+ push:
392+ - match: '\s*(?=[\n;&)])'
393+ pop: true
394+ - match: (switch)(?:\s+)
395+ comment: Anonymous scope - Capture the command name we know is there, include a single instance of a parameter (the value), and end when whitespace or a control operator is seen
396+ captures:
397+ 1: keyword.control.conditional.fish
398+ push:
399+ - match: '(?=[\s;&)])'
400+ pop: true
401+ - include: line-continuation
402+ - include: parameter
403+ - match: \S+
404+ comment: Capture anything that a parameter explicitly rejects, which is mostly operators
405+ scope: invalid.illegal.operator.fish
406+ - match: \s+
407+ comment: Anonymous scope - Capture whitespace which might be there, match any non-control-operator strings as invalid, and end when a control operator is seen
408+ push:
409+ - match: '(?=[\n;&)])'
410+ pop: true
411+ - match: '\S+?(?=[\s;&)])'
412+ scope: invalid.illegal.string.fish
413+ - match: '\n|(;)|([&)])'
414+ comment: Anonymous scope - Capture the operator we know is there, include the base scope, and end when an `end` command is seen
415+ captures:
416+ 1: keyword.operator.control.fish
417+ 2: invalid.illegal.operator.fish
418+ push:
419+ - match: '(?=end(?:$|[\s;&)|<>]))'
420+ pop: true
421+ - match: 'case(?=[\s;&)])'
422+ comment: Anonymous scope - Capture the command name which might be there, include an arbitrary number of arguments, and end when the control operator is captured
423+ captures:
424+ 0: keyword.control.conditional.fish
425+ push:
426+ - match: '\n|(;)|([&)])'
427+ captures:
428+ 1: keyword.operator.control.fish
429+ 2: invalid.illegal.operator.fish
430+ pop: true
431+ - include: line-continuation
432+ - include: comment-internal-end
433+ - include: argument
434+ - match: '\S+?(?=[\s;&)])'
435+ comment: Anything else (eg, redirection) is illegal
436+ scope: invalid.illegal.operator.fish
437+ - include: main
438+ - match: '(?=function\s+[^\s;)-])'
439+ comment: If the command name is followed by a valid string (not anything that closes the scope or a string starting with a '-') then open the scope
440+ push:
441+ - meta_scope: meta.block.function.fish
442+ - match: 'end(?=$|[\s;&)|<>])'
443+ captures:
444+ 0: keyword.control.conditional.fish
445+ pop: true
446+ - match: (?=function)
447+ comment: Anonymous scope - Match the defined name of the function statement, then look for further parameters
448+ push:
449+ - match: '\s*(?=[\n;&)])'
450+ pop: true
451+ - match: (function)(?:\s+)
452+ comment: Anonymous scope - Capture the command name we know is there, include a single instance of a parameter (the value), and end when whitespace or a control operator is seen
453+ captures:
454+ 1: keyword.control.conditional.fish
455+ push:
456+ - match: '(?=[\s;&)])'
457+ pop: true
458+ - include: line-continuation
459+ - match: "(?:[()|<>])"
460+ push:
461+ - meta_scope: invalid.illegal.string.fish
462+ - match: '(?=[\s;&)])'
463+ pop: true
464+ - match: (?!\\\n)
465+ comment: Anonymous scope - Start when an escaped newline isn't present, and end when whitespace or an operator is seen
466+ push:
467+ - match: '(?=[\s;&()|<>])'
468+ pop: true
469+ - match: (?!\s)
470+ push:
471+ - meta_scope: entity.name.function.fish
472+ - match: '(?=[\s;&()|<>])'
473+ pop: true
474+ - include: parameter
475+ - match: \s+
476+ comment: Anonymous scope - Capture whitespace which might be there, then match anything normal for a command call
477+ push:
478+ - match: '(?=[\n;&)])'
479+ pop: true
480+ - include: line-continuation
481+ - include: comment-internal-end
482+ - include: redirection
483+ - include: parameter
484+ - match: '\n|(;)|([&)])'
485+ comment: Anonymous scope - Capture the operator we know is there, include the base scope, and end when an `end` command is seen
486+ captures:
487+ 1: keyword.operator.control.fish
488+ 2: invalid.illegal.operator.fish
489+ push:
490+ - match: '(?=end(?:$|[\s;&)|<>]))'
491+ pop: true
492+ - include: main
493+ command-call-standard-block-if-internal:
494+ - match: '(?=if(?:\s*\n|\s+[^\s;]))'
495+ comment: Anonymous scope - Capture an `if` and the command up to the control operator, then capture from the control operator indefinitely
496+ push:
497+ - match: '(?=end(?:$|[\s;&)|<>]))'
498+ pop: true
499+ - match: if
500+ comment: Anonymous scope - Match the command name we know is there, include a single instance of a pipeline, and end when a control operator is seen
501+ captures:
502+ 0: keyword.control.conditional.fish
503+ push:
504+ - match: '\s*(?=[\n;&])'
505+ pop: true
506+ - include: line-continuation
507+ - include: pipeline
508+ - match: \n|(;)|(&)
509+ comment: Anonymous scope - Match the operator we know is there, then include the base scope or an `else` structure
510+ captures:
511+ 1: keyword.operator.control.fish
512+ 2: invalid.illegal.operator.fish
513+ push:
514+ - match: '(?=end(?:$|[\s;&)|<>]))'
515+ pop: true
516+ - match: '(?=else\s*[\s;])'
517+ comment: Anonymous scope - Capture an `else` up to the control operator or the start of an `if` structure, then match from the control operator indefinitely or match an `if` structure
518+ push:
519+ - match: '(?=end(?:$|[\s;&)|<>]))'
520+ pop: true
521+ - match: 'else(?=\s*[\s;])'
522+ comment: Anonymous scope - Match the `else` we know is there and any comment, and mark anything besides an `if` as illegal
523+ captures:
524+ 0: keyword.control.conditional.fish
525+ push:
526+ - match: '\s*(?=[\n;&]|if(?:\s*\n|\s+[^\s;]))'
527+ pop: true
528+ - include: line-continuation
529+ - include: comment-internal-end
530+ - match: '\S+?(?=[\s;&])'
531+ comment: Anything else is illegal
532+ scope: invalid.illegal.string.fish
533+ - match: \n|(;)|(&)
534+ comment: Anonymous scope - Match the operator which will be there if no `if` was seen, then include the base scope which marks further `else` commands as invalid
535+ captures:
536+ 1: keyword.operator.control.fish
537+ 2: invalid.illegal.operator.fish
538+ push:
539+ - match: '(?=end(?:$|[\s;&)|<>]))'
540+ pop: true
541+ - include: main
542+ - include: command-call-standard-block-if-internal
543+ - include: main
544+ command-substitution:
545+ - match: (?=\()
546+ comment: 'Capture "(...)" or "(...)[...]"'
547+ push:
548+ - match: '(?![\(\[])'
549+ pop: true
550+ - match: \(
551+ captures:
552+ 0: punctuation.section.parens.begin.fish
553+ push:
554+ - meta_scope: meta.parens.command-substitution.fish
555+ - match: \)
556+ captures:
557+ 0: punctuation.section.parens.end.fish
558+ pop: true
559+ - include: main
560+ - include: index-expansion
561+ comment-external:
562+ - match: '\#'
563+ comment: A full or inline comment outside of any command call
564+ captures:
565+ 0: punctuation.definition.comment.fish
566+ push:
567+ - meta_scope: comment.line.external.fish
568+ - match: \n
569+ pop: true
570+ comment-internal-end:
571+ - match: '\#'
572+ comment: An inline comment at the end of a command call. Does not consume the newline, thus allowing the command call to capture it and end
573+ captures:
574+ 0: punctuation.definition.comment.fish
575+ push:
576+ - meta_scope: comment.line.internal.end.fish
577+ - match: (?=\n)
578+ pop: true
579+ index-expansion:
580+ - match: '\['
581+ comment: In other words, the anonymous scope which contains the variable and the index expansion parameter list should only be allowed to contain a single copy of each of those two things. We cannot enforce that without a scope stack. Our workaround is to allow an infinite number of these and hope the user can keep track of when there are too many
582+ captures:
583+ 0: punctuation.section.brackets.begin.fish
584+ push:
585+ - meta_scope: meta.brackets.index-expansion.fish
586+ - match: '\]'
587+ captures:
588+ 0: punctuation.section.brackets.end.fish
589+ pop: true
590+ - match: \.\.
591+ scope: keyword.operator.range.fish
592+ - include: command-substitution
593+ - include: variable-expansion
594+ - include: string-quoted
595+ - match: '(?:[+-]?[0-9]+)(?=[\s;&)|<>]|\]|\.\.)'
596+ scope: constant.numeric.fish
597+ - match: '(?![\s''"]|\.\.)'
598+ comment: 'Begin/end string as before with the addition of breaking at a '']'' or ".."'
599+ push:
600+ - match: '(?=[\s;&)|<>''"]|\]|\.\.)'
601+ pop: true
602+ - include: string-unquoted-patterns
603+ line-continuation:
604+ - match: (?=\\\n)
605+ comment: End when an unescaped newline is seen, the first character of a line isn't whitespace or a comment character or the escaped newline itself, or if the next character after some consumed whitespace isn't more whitespace or a comment character
606+ push:
607+ - match: '(?=\n)|^(?![\s\#\\])|\s(?![\s\#])'
608+ pop: true
609+ - match: \\\n
610+ scope: constant.character.escape
611+ - match: '\#'
612+ captures:
613+ 0: punctuation.definition.comment.fish
614+ push:
615+ - meta_scope: comment.line.continuation.fish
616+ - match: \n
617+ pop: true
618+ parameter:
619+ - match: '(?![\s;&)|<>^])'
620+ comment: See the argument rule for more general information on parameters
621+ push:
622+ - match: '(?=[\s;&)|<>])'
623+ pop: true
624+ - match: '(?:--)(?=[\s;&)|<>])'
625+ comment: End of options (parameter of just two hyphens)
626+ scope: meta.parameter.option.end.fish variable.parameter.fish punctuation.definition.option.end.fish meta.string.unquoted.fish
627+ - match: (?=--)
628+ comment: Long option (parameter starting with two hyphens)
629+ push:
630+ - meta_scope: meta.parameter.option.long.fish
631+ - match: '(?=[\s;&)|<>])'
632+ pop: true
633+ - match: (?:--)
634+ captures:
635+ 0: punctuation.definition.option.long.begin.fish meta.string.unquoted.fish
636+ push:
637+ - meta_scope: variable.parameter.fish
638+ - match: '(?=[\s;&)|<>]|=)'
639+ pop: true
640+ - include: command-substitution
641+ - match: (?=\$)
642+ push:
643+ - meta_scope: meta.string.unquoted.fish
644+ - match: (?!\$)
645+ pop: true
646+ - include: variable-expansion
647+ - include: string-quoted
648+ - match: '(?![''"])'
649+ push:
650+ - meta_scope: meta.string.unquoted.fish
651+ - match: '(?=[\s;&()|<>''"$]|\=)'
652+ pop: true
653+ - include: string-unquoted-patterns
654+ - match: (?:=)
655+ comment: Consume the '=' and then use standard parameter patterns as well as numerics
656+ captures:
657+ 0: variable.parameter.fish punctuation.definition.option.long.separator.fish meta.string.unquoted.fish
658+ push:
659+ - match: '(?=[\s;&)|<>])'
660+ pop: true
661+ - match: '(?:[+-]?)(?:[0-9]+\.?[0-9]*|[0-9]*\.?[0-9]+)(?=$|[\s;&)|<>])'
662+ scope: meta.string.unquoted.fish constant.numeric.fish
663+ - include: parameter-patterns
664+ - match: '(?:-)(?=[^\s;&)|<>])'
665+ comment: Short option (parameter starting with one hyphen)
666+ captures:
667+ 0: punctuation.definition.option.short.fish meta.string.unquoted.fish
668+ push:
669+ - meta_scope: meta.parameter.option.short.fish variable.parameter.fish
670+ - match: '(?=[\s;&)|<>])'
671+ pop: true
672+ - include: parameter-patterns
673+ - include: argument
674+ parameter-patterns:
675+ - include: command-substitution
676+ - match: (?=\$)
677+ comment: Give variable expansion the unquoted string scope
678+ push:
679+ - meta_scope: meta.string.unquoted.fish
680+ - match: (?!\$)
681+ pop: true
682+ - include: variable-expansion
683+ - include: string
684+ pipeline:
685+ - match: '(?:[&|]|(?:[0-9]+)?(?:<|>>?|\^\^?))'
686+ comment: Todo - Restructure pipeline so that this match isn't duplicated from the base scope, which it must also be for any other scopes which implement their own control operator consumption. Might require the unary operator commands to become an explicit recursive match (though we tried this once and it was more complicated than anything should be)
687+ scope: invalid.illegal.operator.fish
688+ - match: (and|or)\b(?!\s+-)
689+ comment: Todo - These commands cannot be followed by backgrounding, piping, or redirection alone. Add logic to catch these cases. It will be extensive...
690+ scope: meta.function-call.fish keyword.operator.word.fish
691+ - include: line-continuation
692+ - match: (not)\b(?!\s+-)
693+ comment: This is a hack for now, which allows nesting of 'not' and 'and'/'or' commands. A better solution will be explicit recursivity in these commands
694+ scope: meta.function-call.fish keyword.operator.word.fish
695+ - match: '(?:case|else|end)(?=[\s;&)|<>])'
696+ comment: Match a command which is illegal in the base scope
697+ scope: invalid.illegal.function-call.fish
698+ - match: '(?=[^\s#])'
699+ comment: Anonymous scope - Pipeline. Define a pipeline as either one command call, or multiple command calls linked by pipe operators ('|', '2>|', etc). The pipeline terminates at the first encounter of any control operator
700+ push:
701+ - match: '(\s*)(?=[\n;&)])'
702+ captures:
703+ 1: meta.function-call.fish
704+ pop: true
705+ - match: |-
706+ (?x)
707+ (?# Negative lookahead for piping)
708+ (?!
709+ (?:(?:[0-9]+)?(?:<|>>?|\^\^?))?\|
710+ )
711+ comment: Match the first command of a potential pipeline
712+ push:
713+ - meta_scope: meta.function-call.fish
714+ - match: |-
715+ (?x)
716+ (?# Look ahead for operators after whitespace)
717+ (?=\s*
718+ (?:
719+ (?# Find a control operator)
720+ [\n;&)]
721+ |
722+ (?# Find a pipe operator)
723+ (?:(?:[0-9]+)?(?:<|>>?|\^\^?))?\|
724+ )
725+ )
726+ pop: true
727+ - include: command-call-meta
728+ - include: command-call-standard
729+ - match: |-
730+ (?x)
731+ (?# Look ahead for piping)
732+ (?=
733+ (?:(?:[0-9]+)?(?:<|>>?|\^\^?))?\|
734+ )
735+ comment: Match a second or later command of a pipeline, starting with the connective piping
736+ push:
737+ - meta_scope: meta.function-call.fish
738+ - match: '(?=\s*[\n;&)])'
739+ pop: true
740+ - match: |-
741+ (?x)
742+ (?# Look ahead for piping followed by either control operators or piping)
743+ (?=
744+ (?:(?:[0-9]+)?(?:<|>>?|\^\^?))?\|
745+ \s*
746+ (?:
747+ $
748+ |
749+ [\n;&)]
750+ |
751+ (?:(?:[0-9]+)?(?:<|>>?|\^\^?))?\|
752+ )
753+ )
754+ comment: Match a pipe not followed by a command, hence a malformed segment of the pipeline
755+ push:
756+ - match: '(?=\s*$|\s*[\n;&)])'
757+ pop: true
758+ - match: '(?:(?:[0-9]+)?(?:<|>|>>))?\|(?=\s*$|\s*[\n)])'
759+ comment: If the pipeline would end implicitly (ie, with a newline or close parenthesis), then mark the pipe itself invalid
760+ scope: invalid.illegal.operator.fish
761+ - match: |-
762+ (?x)
763+ (?# Consume valid piping; captures 1 2 3)
764+ (?:([0-9]+)?(<|>>?|\^\^?))?(\|)
765+ (?# Consume whitespace)
766+ \s*
767+ (?# Consume remainder; capture 4)
768+ (.*)
769+ comment: If the pipeline would end with an explicit operator or encounter a second set of piping, then mark the first set of piping as valid and beyond as invalid
770+ captures:
771+ 1: meta.pipe.fish constant.numeric.file-descriptor.fish
772+ 2: meta.pipe.fish keyword.operator.redirect.fish
773+ 3: meta.pipe.fish keyword.operator.pipe.fish
774+ 4: invalid.illegal.function-call.fish
775+ - match: '(?:([0-9]+)?(<|>>?|\^\^?))?(\|)'
776+ comment: Pick up a legitimate pipe
777+ scope: meta.pipe.fish
778+ captures:
779+ 1: constant.numeric.file-descriptor.fish
780+ 2: keyword.operator.pipe.redirect.fish
781+ 3: keyword.operator.pipe.fish
782+ - include: line-continuation
783+ - include: command-call-meta
784+ - include: command-call-standard
785+ redirection:
786+ - match: '(?=(?:[0-9]+(?:<|>|>>)|>>|\^\^|[<>^])\&)'
787+ comment: End at anything that would end a parameter, including redirections *if* they are *not* this same type of redirection (ie, have an '&'), in which case this scope stays open and we match the next one. The negative lookahead for <>^ at the end is to keep ST2 happy (not hanging)
788+ push:
789+ - match: '(?=[\s;&)|]|(?:[0-9]+(?:<|>|>>)|>>|\^\^|[<>^])(?![&<>^]))'
790+ pop: true
791+ - match: '(?:([0-9]+)(<|>|>>)|(>>|\^\^|[<>^]))(\&)\s*'
792+ comment: We have to try and catch an '&' here because if it is seen by the outer end match then it will be considered a valid operator and the redirection scope will immediately terminate
793+ captures:
794+ 1: constant.numeric.file-descriptor.fish
795+ 2: keyword.operator.redirect.fish
796+ 3: keyword.operator.redirect.fish
797+ 4: keyword.operator.redirect.dereference.fish
798+ push:
799+ - meta_scope: meta.redirection.fish
800+ - match: '(\&.*$)|(?![&\\])'
801+ captures:
802+ 1: invalid.illegal.file-descriptor.fish
803+ pop: true
804+ - include: line-continuation
805+ - match: (?=\\\n)
806+ push:
807+ - meta_scope: meta.redirection.fish
808+ - match: (?!\\\n)
809+ pop: true
810+ - include: line-continuation
811+ - match: (?=\()
812+ comment: Evaluates to a string which may be an integer
813+ push:
814+ - meta_scope: meta.redirection.fish
815+ - match: (?!\()
816+ pop: true
817+ - include: command-substitution
818+ - match: (?=\$)
819+ comment: Evaluates to a string which may be an integer
820+ push:
821+ - meta_scope: meta.redirection.fish
822+ - match: (?!\$)
823+ pop: true
824+ - include: variable-expansion
825+ - match: '(?=[''"])'
826+ comment: May be a quoted integer, which is allowed
827+ push:
828+ - meta_scope: meta.redirection.fish
829+ - match: '(?![''"])'
830+ pop: true
831+ - include: string-quoted
832+ - match: '(?:[0-9]+)(?=$|[\s;&)|<>])'
833+ scope: meta.redirection.file-descriptor.fish constant.numeric.file-descriptor.fish
834+ - match: '(?:-)(?=$|[\s;&)|<>])'
835+ scope: meta.redirection.file-descriptor.fish keyword.operator.redirect.close.fish
836+ - match: (?:\S+.*)$
837+ comment: Anything else is illegal
838+ scope: meta.redirection.fish invalid.illegal.file-descriptor.fish
839+ - match: '(?=(?:[0-9]+(?:<|>|>>)|>>|\^\^|[<>^])\??)'
840+ comment: End at anything that would end a parameter, including redirections *if* they are *not* this same type of redirection (ie, redirection into file descriptor, or into pipe), in which case this scope stays open and we match the next one
841+ push:
842+ - match: '(?=[\s;&)|]|(?:[0-9]+(?:<|>|>>)|>>|\^\^|[<>^])[&|])'
843+ pop: true
844+ - match: '(?:([0-9]+)(<|>|>>)|(>>|\^\^|[<>^]))(\?)?\s*'
845+ comment: We have to try and catch bad operators here because if they are seen by the outer end match then they will be considered valid and the redirection scope will immediately terminate
846+ captures:
847+ 1: constant.numeric.file-descriptor.fish
848+ 2: keyword.operator.redirect.fish
849+ 3: keyword.operator.redirect.fish
850+ 4: keyword.operator.redirect.clobber-test.fish
851+ push:
852+ - meta_scope: meta.redirection.fish
853+ - match: "((?:[&?]|[0-9]*[<>^]).*$)|(?![&?<>^])"
854+ captures:
855+ 1: invalid.illegal.path.fish
856+ pop: true
857+ - include: line-continuation
858+ - match: (?=\\\n)
859+ push:
860+ - meta_scope: meta.redirection.fish
861+ - match: (?!\\\n)
862+ pop: true
863+ - include: line-continuation
864+ - match: (?=\()
865+ comment: Evaluates to a string, so path cannot begin with '('
866+ push:
867+ - meta_scope: meta.redirection.fish
868+ - match: (?!\()
869+ pop: true
870+ - include: command-substitution
871+ - match: (?=\$)
872+ comment: Evaluates to a string, so path cannot begin with '$'
873+ push:
874+ - meta_scope: meta.redirection.fish
875+ - match: (?!\$)
876+ pop: true
877+ - include: variable-expansion
878+ - match: "(?:[&?]|[0-9]*[<>^]).*$"
879+ comment: Check for characters which are associated with redirection, so path cannot begin with them. Don't put them in the meta.redirection.path scope, so that only valid paths are in there
880+ scope: meta.redirection.fish invalid.illegal.path.fish
881+ - match: \~
882+ scope: meta.redirection.path.fish keyword.operator.tilde.fish
883+ - match: '(?:\''(?:\\[\''\\]|[^\''\\])*\''|\"(?:\\[\"$\n\\]|[^\"$\n\\])*\"|(?:\\[abefnrtv $\\*?#(){}\[\]<>^&;|"'']|\\[~%]|\\[xX][0-9A-Fa-f]{1,2}|\\[0-7]{1,3}|\\u[0-9A-Fa-f]{1,4}|\\U[0-9A-Fa-f]{1,8}|\\c[?-~]|[^\s$\\*?~%#()<>&|;"'']|\\(?=[^abefnrtv\s$\\*?#(){}\[\]<>^&;|"''xXuUc])|\\\n|[~%#])+)+'
884+ comment: Use the function call match to build a file path, as the syntax is fairly similar (possibly identical, after exceptions caught above? I haven't checked)
885+ scope: meta.redirection.path.fish
886+ string:
887+ - include: string-quoted
888+ - include: string-unquoted
889+ string-quoted:
890+ - match: \'
891+ captures:
892+ 0: punctuation.definition.string.begin.fish
893+ push:
894+ - meta_scope: string.quoted.single.fish
895+ - match: \'
896+ captures:
897+ 0: punctuation.definition.string.end.fish
898+ pop: true
899+ - match: '\\[\''\\]'
900+ comment: Only accepted escapes are \' and \\
901+ scope: constant.character.escape.fish
902+ - match: \"
903+ captures:
904+ 0: punctuation.definition.string.begin.fish
905+ push:
906+ - meta_scope: string.quoted.double.fish
907+ - match: \"
908+ captures:
909+ 0: punctuation.definition.string.end.fish
910+ pop: true
911+ - match: '\\[\n\"\\$]'
912+ comment: Only accepted escapes are \<newline>, \", \\, and \$
913+ scope: constant.character.escape.fish
914+ - include: variable-expansion
915+ string-unquoted:
916+ - match: '(?![\s;&()|<>''"$])'
917+ comment: End unquoted string at anything that can't be in one
918+ push:
919+ - meta_scope: meta.string.unquoted.fish
920+ - match: '(?=[\s;&()|<>''"$])'
921+ pop: true
922+ - include: string-unquoted-patterns
923+ string-unquoted-patterns:
924+ - match: |-
925+ (?x)
926+ \\[abefnrtv $\\*?#(){}\[\]<>^&|;"']
927+ |
928+ \\[~%]
929+ |
930+ \\[xX][0-9A-Fa-f]{1,2}
931+ |
932+ \\[0-7]{1,3}
933+ |
934+ \\u[0-9A-Fa-f]{1,4}
935+ |
936+ \\U[0-9A-Fa-f]{1,8}
937+ |
938+ \\c[?-~]
939+ comment: This list follows the order given in official fish documentation. Technically '~' and '%' only need escaping if they appear at the front of a parameter. If they are escaped within a parameter, then fish does not *highlight* the escape, however it does silently *parse* the escape and the backslash is removed before the parameter is passed to the command. So, we highlight these escapes as well since they are actually treated as valid escapes by fish
940+ scope: constant.character.escape.fish
941+ - match: \\\n
942+ comment: Just for convenience we separate the newline escape
943+ scope: constant.character.escape.fish
944+ - match: '\{'
945+ captures:
946+ 0: punctuation.section.braces.begin.fish
947+ push:
948+ - meta_scope: meta.braces.brace-expansion.fish
949+ - match: '(\})|(\n|[;&)|].*)'
950+ captures:
951+ 1: punctuation.section.braces.end.fish
952+ 2: invalid.illegal.punctuation.section.fish
953+ pop: true
954+ - match: \,
955+ scope: punctuation.section.braces.separator.fish
956+ - include: command-substitution
957+ - include: variable-expansion
958+ - match: '(?:[^\S\n]+)'
959+ comment: Unescaped spaces aren't allowed, as technically that separates the braces into two separate arguments. Don't consume a newline though, so the scope end capture can get it
960+ scope: invalid.illegal.whitespace.fish
961+ - include: string-quoted
962+ - match: '(?:[+-]?)(?:[0-9]+\.?[0-9]*|[0-9]*\.?[0-9]+)(?=$|[\s;&)|<>]|\}|\,)'
963+ scope: constant.numeric.fish
964+ - match: '(?![\s;&)|<>''"])'
965+ comment: "Begin/end string as before with the addition of breaking at a '}' or ','"
966+ push:
967+ - match: '(?=[\s;&)|<>''"]|\}|\,)'
968+ pop: true
969+ - match: \\\,
970+ scope: constant.character.escape.fish
971+ - include: string-unquoted-patterns
972+ - match: (\*\*)|(\*)|(\?)
973+ scope: meta.wildcard-expansion.fish
974+ captures:
975+ 1: keyword.operator.double-star.fish
976+ 2: keyword.operator.single-star.fish
977+ 3: keyword.operator.question-mark.fish
978+ variable-expansion:
979+ - include: variable-expansion-illegal
980+ - match: (?=\$)
981+ comment: 'Capture "$foo" or "$foo[]" or "$$foo[][]" etc'
982+ push:
983+ - meta_scope: meta.variable-expansion.fish
984+ - match: '(?=[^\$\w\[])'
985+ pop: true
986+ - match: \$
987+ captures:
988+ 0: punctuation.definition.variable.fish
989+ push:
990+ - meta_scope: variable.other.fish
991+ - match: '(?=[^\$\w])'
992+ pop: true
993+ - include: variable-expansion-illegal
994+ - include: variable-expansion-simple
995+ - include: index-expansion
996+ variable-expansion-illegal:
997+ - match: '\$(?:(?=[,''"\]}\s;&)|])|[^\w\$][^$,''"\]}\s;&)|]*)'
998+ comment: A lone '$' in a scope, or an attempt to expand a variable starting with a nonword character, is an error. These boundaries are the same as for meta.string.unquoted
999+ scope: invalid.illegal.variable-expansion.fish
1000+ variable-expansion-simple:
1001+ - match: \$
1002+ captures:
1003+ 0: punctuation.definition.variable.fish
1004+ push:
1005+ - meta_scope: variable.other.fish
1006+ - match: '(?=[^\$\w])'
1007+ pop: true
1008+ - include: variable-expansion-illegal
1009+ - include: variable-expansion-simple
assets/syntaxes/sublime-fishnew file+1

Submodule changed.