This is an automated email from the ASF dual-hosted git repository. hansva pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/hop.git
The following commit(s) were added to refs/heads/main by this push:
new b371c0646d add support for sh and cmd files to the text editor, fixes
#7640 (#7645)
b371c0646d is described below
commit b371c0646d77a5644c96cd322a4b58622a1ddf97
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Mon Jul 27 17:20:13 2026 +0200
add support for sh and cmd files to the text editor, fixes #7640 (#7645)
---
.../transforms/types/BatExplorerFileType.java | 6 +-
.../types/BatExplorerFileTypeHandler.java | 40 +
...lorerFileType.java => CmdExplorerFileType.java} | 22 +-
.../transforms/types/ShellExplorerFileType.java | 6 +-
.../types/ShellExplorerFileTypeHandler.java | 37 +
.../transforms/types/CmdExplorerFileTypeTest.java | 92 +
.../org/apache/hop/ui/hopgui/monaco-editor.js | 6 +
.../hop/ui/hopgui/ContentEditorTm4eSupport.java | 12 +-
.../hopgui/RuleBasedSourceViewerConfiguration.java | 3 +-
.../org/apache/hop/ui/hopgui/grammars/bat.json | 788 +++++++
.../org/apache/hop/ui/hopgui/grammars/shell.json | 2353 ++++++++++++++++++++
.../ui/hopgui/ContentEditorTm4eSupportTest.java | 114 +
12 files changed, 3460 insertions(+), 19 deletions(-)
diff --git
a/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/BatExplorerFileType.java
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/BatExplorerFileType.java
index e897142597..a60a687c30 100644
---
a/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/BatExplorerFileType.java
+++
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/BatExplorerFileType.java
@@ -34,7 +34,7 @@ import
org.apache.hop.ui.hopgui.perspective.explorer.file.types.text.BaseTextExp
name = "Windows Batch File Type",
description = "Batch file handling in the explorer perspective",
image = "ui/images/script.svg")
-public class BatExplorerFileType extends
BaseTextExplorerFileType<TextExplorerFileTypeHandler> {
+public class BatExplorerFileType extends
BaseTextExplorerFileType<BatExplorerFileTypeHandler> {
public BatExplorerFileType() {
super(
@@ -54,9 +54,9 @@ public class BatExplorerFileType extends
BaseTextExplorerFileType<TextExplorerFi
}
@Override
- public TextExplorerFileTypeHandler createFileTypeHandler(
+ public BatExplorerFileTypeHandler createFileTypeHandler(
HopGui hopGui, ExplorerPerspective perspective, ExplorerFile file) {
- return new TextExplorerFileTypeHandler(hopGui, perspective, file);
+ return new BatExplorerFileTypeHandler(hopGui, perspective, file);
}
@Override
diff --git
a/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/BatExplorerFileTypeHandler.java
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/BatExplorerFileTypeHandler.java
new file mode 100644
index 0000000000..e1fdded62d
--- /dev/null
+++
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/BatExplorerFileTypeHandler.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.pipeline.transforms.types;
+
+import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerFile;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
+import
org.apache.hop.ui.hopgui.perspective.explorer.file.types.text.BaseTextExplorerFileTypeHandler;
+
+/**
+ * Handler for Windows batch and command files (.bat / .cmd) in the file
explorer perspective
+ * (syntax-highlighted edit). Both extensions are the same language, mapped to
the "bat" grammar.
+ */
+public class BatExplorerFileTypeHandler extends
BaseTextExplorerFileTypeHandler {
+
+ public BatExplorerFileTypeHandler(
+ HopGui hopGui, ExplorerPerspective perspective, ExplorerFile
explorerFile) {
+ super(hopGui, perspective, explorerFile);
+ }
+
+ @Override
+ protected String getLanguageId() {
+ return "bat";
+ }
+}
diff --git
a/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/BatExplorerFileType.java
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/CmdExplorerFileType.java
similarity index 81%
copy from
plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/BatExplorerFileType.java
copy to
plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/CmdExplorerFileType.java
index e897142597..ae14188dea 100644
---
a/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/BatExplorerFileType.java
+++
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/CmdExplorerFileType.java
@@ -30,18 +30,18 @@ import
org.apache.hop.ui.hopgui.perspective.explorer.file.capabilities.FileTypeC
import
org.apache.hop.ui.hopgui.perspective.explorer.file.types.text.BaseTextExplorerFileType;
@HopFileTypePlugin(
- id = "BatExplorerFileType",
- name = "Windows Batch File Type",
- description = "Batch file handling in the explorer perspective",
+ id = "CmdExplorerFileType",
+ name = "Windows Command File Type",
+ description = "Windows command file handling in the explorer perspective",
image = "ui/images/script.svg")
-public class BatExplorerFileType extends
BaseTextExplorerFileType<TextExplorerFileTypeHandler> {
+public class CmdExplorerFileType extends
BaseTextExplorerFileType<BatExplorerFileTypeHandler> {
- public BatExplorerFileType() {
+ public CmdExplorerFileType() {
super(
- "Batch file",
- ".bat",
- new String[] {"*.bat", "*.BAT"},
- new String[] {"Batch files"},
+ "Command file",
+ ".cmd",
+ new String[] {"*.cmd", "*.CMD"},
+ new String[] {"Command files"},
FileTypeCapabilities.getCapabilities(
IHopFileType.CAPABILITY_SAVE,
IHopFileType.CAPABILITY_SAVE_AS,
@@ -54,9 +54,9 @@ public class BatExplorerFileType extends
BaseTextExplorerFileType<TextExplorerFi
}
@Override
- public TextExplorerFileTypeHandler createFileTypeHandler(
+ public BatExplorerFileTypeHandler createFileTypeHandler(
HopGui hopGui, ExplorerPerspective perspective, ExplorerFile file) {
- return new TextExplorerFileTypeHandler(hopGui, perspective, file);
+ return new BatExplorerFileTypeHandler(hopGui, perspective, file);
}
@Override
diff --git
a/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/ShellExplorerFileType.java
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/ShellExplorerFileType.java
index c8ecd00dd0..7493c3670f 100644
---
a/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/ShellExplorerFileType.java
+++
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/ShellExplorerFileType.java
@@ -34,7 +34,7 @@ import
org.apache.hop.ui.hopgui.perspective.explorer.file.types.text.BaseTextExp
name = "Shell File Type",
description = "Shell file handling in the explorer perspective",
image = "ui/images/script.svg")
-public class ShellExplorerFileType extends
BaseTextExplorerFileType<TextExplorerFileTypeHandler> {
+public class ShellExplorerFileType extends
BaseTextExplorerFileType<ShellExplorerFileTypeHandler> {
public ShellExplorerFileType() {
super(
@@ -54,9 +54,9 @@ public class ShellExplorerFileType extends
BaseTextExplorerFileType<TextExplorer
}
@Override
- public TextExplorerFileTypeHandler createFileTypeHandler(
+ public ShellExplorerFileTypeHandler createFileTypeHandler(
HopGui hopGui, ExplorerPerspective perspective, ExplorerFile file) {
- return new TextExplorerFileTypeHandler(hopGui, perspective, file);
+ return new ShellExplorerFileTypeHandler(hopGui, perspective, file);
}
@Override
diff --git
a/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/ShellExplorerFileTypeHandler.java
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/ShellExplorerFileTypeHandler.java
new file mode 100644
index 0000000000..fe250cb91a
--- /dev/null
+++
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/ShellExplorerFileTypeHandler.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.pipeline.transforms.types;
+
+import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerFile;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
+import
org.apache.hop.ui.hopgui.perspective.explorer.file.types.text.BaseTextExplorerFileTypeHandler;
+
+/** Handler for shell script files in the file explorer perspective
(syntax-highlighted edit). */
+public class ShellExplorerFileTypeHandler extends
BaseTextExplorerFileTypeHandler {
+
+ public ShellExplorerFileTypeHandler(
+ HopGui hopGui, ExplorerPerspective perspective, ExplorerFile
explorerFile) {
+ super(hopGui, perspective, explorerFile);
+ }
+
+ @Override
+ protected String getLanguageId() {
+ return "shell";
+ }
+}
diff --git
a/plugins/transforms/textfile/src/test/java/org/apache/hop/pipeline/transforms/types/CmdExplorerFileTypeTest.java
b/plugins/transforms/textfile/src/test/java/org/apache/hop/pipeline/transforms/types/CmdExplorerFileTypeTest.java
new file mode 100644
index 0000000000..562b830ffc
--- /dev/null
+++
b/plugins/transforms/textfile/src/test/java/org/apache/hop/pipeline/transforms/types/CmdExplorerFileTypeTest.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.pipeline.transforms.types;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.lang.reflect.Method;
+import org.apache.hop.ui.hopgui.file.IHopFileType;
+import
org.apache.hop.ui.hopgui.perspective.explorer.file.types.text.BaseTextExplorerFileTypeHandler;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class CmdExplorerFileTypeTest {
+
+ private CmdExplorerFileType fileType;
+
+ @BeforeEach
+ void setUp() {
+ fileType = new CmdExplorerFileType();
+ }
+
+ @Test
+ void testConstructor() {
+ assertNotNull(fileType);
+ }
+
+ @Test
+ void testGetDefaultFileExtension() {
+ assertEquals(".cmd", fileType.getDefaultFileExtension());
+ }
+
+ @Test
+ void testGetFilterExtensions() {
+ String[] extensions = fileType.getFilterExtensions();
+ assertNotNull(extensions);
+ assertEquals(2, extensions.length);
+ assertEquals("*.cmd", extensions[0]);
+ assertEquals("*.CMD", extensions[1]);
+ }
+
+ @Test
+ void testGetFilterNames() {
+ String[] names = fileType.getFilterNames();
+ assertNotNull(names);
+ assertEquals(1, names.length);
+ assertEquals("Command files", names[0]);
+ }
+
+ @Test
+ void testHasExpectedCapabilities() {
+ assertTrue(fileType.hasCapability(IHopFileType.CAPABILITY_SAVE));
+ assertTrue(fileType.hasCapability(IHopFileType.CAPABILITY_SAVE_AS));
+ assertTrue(fileType.hasCapability(IHopFileType.CAPABILITY_CLOSE));
+ assertTrue(fileType.hasCapability(IHopFileType.CAPABILITY_FILE_HISTORY));
+ assertTrue(fileType.hasCapability(IHopFileType.CAPABILITY_COPY));
+ assertTrue(fileType.hasCapability(IHopFileType.CAPABILITY_SELECT));
+ assertFalse(fileType.hasCapability(IHopFileType.CAPABILITY_NEW));
+ }
+
+ @Test
+ void testCreateFileTypeHandlerReturnsBatHandler() {
+ assertInstanceOf(
+ BatExplorerFileTypeHandler.class, fileType.createFileTypeHandler(null,
null, null));
+ }
+
+ @Test
+ void testHandlerReturnsBatLanguageId() throws Exception {
+ BatExplorerFileTypeHandler handler = new BatExplorerFileTypeHandler(null,
null, null);
+ Method method =
BaseTextExplorerFileTypeHandler.class.getDeclaredMethod("getLanguageId");
+ method.setAccessible(true);
+ assertEquals("bat", method.invoke(handler));
+ }
+}
diff --git a/rap/src/main/resources/org/apache/hop/ui/hopgui/monaco-editor.js
b/rap/src/main/resources/org/apache/hop/ui/hopgui/monaco-editor.js
index 61e8e87a4e..9c463327f2 100644
--- a/rap/src/main/resources/org/apache/hop/ui/hopgui/monaco-editor.js
+++ b/rap/src/main/resources/org/apache/hop/ui/hopgui/monaco-editor.js
@@ -48,6 +48,12 @@
sql: "sql",
python: "python",
py: "python",
+ shell: "shell",
+ bash: "shell",
+ sh: "shell",
+ bat: "bat",
+ cmd: "bat",
+ batch: "bat",
yaml: "yaml",
yml: "yaml",
properties: "ini"
diff --git
a/rcp/src/main/java/org/apache/hop/ui/hopgui/ContentEditorTm4eSupport.java
b/rcp/src/main/java/org/apache/hop/ui/hopgui/ContentEditorTm4eSupport.java
index 89a348063f..fc2ec902f5 100644
--- a/rcp/src/main/java/org/apache/hop/ui/hopgui/ContentEditorTm4eSupport.java
+++ b/rcp/src/main/java/org/apache/hop/ui/hopgui/ContentEditorTm4eSupport.java
@@ -55,6 +55,8 @@ final class ContentEditorTm4eSupport {
private static final String SCOPE_SQL = "source.sql";
private static final String SCOPE_PYTHON = "source.python";
private static final String SCOPE_YAML = "source.yaml";
+ private static final String SCOPE_SHELL = "source.shell";
+ private static final String SCOPE_BATCH = "source.batchfile";
/** Maps TM4E scope names to grammar resource filenames (classpath-relative
to grammars/). */
private static final Map<String, String> GRAMMAR_FILES =
@@ -63,7 +65,9 @@ final class ContentEditorTm4eSupport {
SCOPE_XML, "xml.json",
SCOPE_SQL, "sql.json",
SCOPE_PYTHON, "python.json",
- SCOPE_YAML, "yaml.json");
+ SCOPE_YAML, "yaml.json",
+ SCOPE_SHELL, "shell.json",
+ SCOPE_BATCH, "bat.json");
// Same palette as before (light/dark) for consistency
private static final RGB L_COMMENT = new RGB(128, 128, 128);
@@ -114,6 +118,8 @@ final class ContentEditorTm4eSupport {
case "sql" -> SCOPE_SQL;
case "python", "py" -> SCOPE_PYTHON;
case "yaml", "yml" -> SCOPE_YAML;
+ case "shell", "bash", "sh" -> SCOPE_SHELL;
+ case "bat", "cmd", "batch" -> SCOPE_BATCH;
default -> null;
};
}
@@ -269,6 +275,10 @@ final class ContentEditorTm4eSupport {
return dark ? D_KEYWORD : L_KEYWORD;
}
+ // Variables ($VAR in shell, %VAR% in batch): VS Code renders these in the
same light blue as
+ // JSON keys. Checked after string so interpolation inside a string stays
string-colored.
+ if (scope.contains("variable")) return dark ? D_JSON_KEY : L_JSON_KEY;
+
// XML: <? ?> then tags then attribute names
if (scope.contains("meta.tag.preprocessor")) {
return dark ? D_XML_HEADER : L_XML_HEADER;
diff --git
a/rcp/src/main/java/org/apache/hop/ui/hopgui/RuleBasedSourceViewerConfiguration.java
b/rcp/src/main/java/org/apache/hop/ui/hopgui/RuleBasedSourceViewerConfiguration.java
index 123ede5fc3..bda9e9a047 100644
---
a/rcp/src/main/java/org/apache/hop/ui/hopgui/RuleBasedSourceViewerConfiguration.java
+++
b/rcp/src/main/java/org/apache/hop/ui/hopgui/RuleBasedSourceViewerConfiguration.java
@@ -31,7 +31,8 @@ public final class RuleBasedSourceViewerConfiguration {
/**
* Creates a configuration for the given language. Uses TM4E when a grammar
is available (json,
- * xml, sql, python, yaml); otherwise returns a plain configuration with no
syntax highlighting.
+ * xml, sql, python, yaml, shell, bat); otherwise returns a plain
configuration with no syntax
+ * highlighting.
*
* @param languageId language id (e.g. "json", "xml", "sql", "yaml"), or
null for plain text
* @return configuration, never null
diff --git a/rcp/src/main/resources/org/apache/hop/ui/hopgui/grammars/bat.json
b/rcp/src/main/resources/org/apache/hop/ui/hopgui/grammars/bat.json
new file mode 100644
index 0000000000..85e03dbc85
--- /dev/null
+++ b/rcp/src/main/resources/org/apache/hop/ui/hopgui/grammars/bat.json
@@ -0,0 +1,788 @@
+{
+ "information_for_contributors": [
+ "This file has been converted from
https://github.com/mmims/language-batchfile/blob/master/grammars/batchfile.cson",
+ "If you want to provide a fix or improvement, please create a
pull request against the original repository.",
+ "Once accepted there, we are happy to receive an update
request."
+ ],
+ "version":
"https://github.com/mmims/language-batchfile/commit/6154ae25a24e01ac9329e7bcf958e093cd8733a9",
+ "name": "Batch File",
+ "scopeName": "source.batchfile",
+ "injections": {
+ "L:meta.block.repeat.batchfile": {
+ "patterns": [
+ {
+ "include": "#repeatParameter"
+ }
+ ]
+ }
+ },
+ "patterns": [
+ {
+ "include": "#commands"
+ },
+ {
+ "include": "#comments"
+ },
+ {
+ "include": "#constants"
+ },
+ {
+ "include": "#controls"
+ },
+ {
+ "include": "#escaped_characters"
+ },
+ {
+ "include": "#labels"
+ },
+ {
+ "include": "#numbers"
+ },
+ {
+ "include": "#operators"
+ },
+ {
+ "include": "#parens"
+ },
+ {
+ "include": "#strings"
+ },
+ {
+ "include": "#variables"
+ }
+ ],
+ "repository": {
+ "commands": {
+ "patterns": [
+ {
+ "match":
"(?<=^|[\\s@])(?i:adprep|append|arp|assoc|at|atmadm|attrib|auditpol|autochk|autoconv|autofmt|bcdboot|bcdedit|bdehdcfg|bitsadmin|bootcfg|brea|cacls|cd|certreq|certutil|change|chcp|chdir|chglogon|chgport|chgusr|chkdsk|chkntfs|choice|cipher|clip|cls|clscluadmin|cluster|cmd|cmdkey|cmstp|color|comp|compact|convert|copy|cprofile|cscript|csvde|date|dcdiag|dcgpofix|dcpromo|defra|del|dfscmd|dfsdiag|dfsrmig|diantz|dir|dirquota|diskcomp|diskcopy|diskpart|diskperf|diskraid|diskshadow|d
[...]
+ "name": "keyword.command.batchfile"
+ },
+ {
+ "begin":
"(?i)(?<=^|[\\s@])(echo)(?:(?=$|\\.|:)|\\s+(?:(on|off)(?=\\s*$))?)",
+ "beginCaptures": {
+ "1": {
+ "name":
"keyword.command.batchfile"
+ },
+ "2": {
+ "name":
"keyword.other.special-method.batchfile"
+ }
+ },
+ "end": "(?=$\\n|[&|><)])",
+ "patterns": [
+ {
+ "include":
"#escaped_characters"
+ },
+ {
+ "include": "#variables"
+ },
+ {
+ "include": "#numbers"
+ },
+ {
+ "include": "#strings"
+ }
+ ]
+ },
+ {
+ "match":
"(?i)(?<=^|[\\s@])(setlocal)(?:\\s*$|\\s+(EnableExtensions|DisableExtensions|EnableDelayedExpansion|DisableDelayedExpansion)(?=\\s*$))",
+ "captures": {
+ "1": {
+ "name":
"keyword.command.batchfile"
+ },
+ "2": {
+ "name":
"keyword.other.special-method.batchfile"
+ }
+ }
+ },
+ {
+ "include": "#command_set"
+ }
+ ]
+ },
+ "command_set": {
+ "patterns": [
+ {
+ "begin":
"(?<=^|[\\s@])(?i:SET)(?=$|\\s)",
+ "beginCaptures": {
+ "0": {
+ "name":
"keyword.command.batchfile"
+ }
+ },
+ "end": "(?=$\\n|[&|><)])",
+ "patterns": [
+ {
+ "include":
"#command_set_inside"
+ }
+ ]
+ }
+ ]
+ },
+ "command_set_inside": {
+ "patterns": [
+ {
+ "include": "#escaped_characters"
+ },
+ {
+ "include": "#variables"
+ },
+ {
+ "include": "#numbers"
+ },
+ {
+ "include": "#parens"
+ },
+ {
+ "include": "#command_set_strings"
+ },
+ {
+ "include": "#strings"
+ },
+ {
+ "begin": "([^ ][^=]*)(=)",
+ "beginCaptures": {
+ "1": {
+ "name":
"variable.other.readwrite.batchfile"
+ },
+ "2": {
+ "name":
"keyword.operator.assignment.batchfile"
+ }
+ },
+ "end": "(?=$\\n|[&|><)])",
+ "patterns": [
+ {
+ "include":
"#escaped_characters"
+ },
+ {
+ "include": "#variables"
+ },
+ {
+ "include": "#numbers"
+ },
+ {
+ "include": "#parens"
+ },
+ {
+ "include": "#strings"
+ }
+ ]
+ },
+ {
+ "begin": "\\s+/[aA]\\s+",
+ "end": "(?=$\\n|[&|><)])",
+ "name": "meta.expression.set.batchfile",
+ "patterns": [
+ {
+ "begin": "\"",
+ "beginCaptures": {
+ "0": {
+ "name":
"punctuation.definition.string.begin.batchfile"
+ }
+ },
+ "end": "\"",
+ "endCaptures": {
+ "0": {
+ "name":
"punctuation.definition.string.end.batchfile"
+ }
+ },
+ "name":
"string.quoted.double.batchfile",
+ "patterns": [
+ {
+
"include": "#command_set_inside_arithmetic"
+ },
+ {
+
"include": "#command_set_group"
+ },
+ {
+
"include": "#variables"
+ }
+ ]
+ },
+ {
+ "include":
"#command_set_inside_arithmetic"
+ },
+ {
+ "include":
"#command_set_group"
+ }
+ ]
+ },
+ {
+ "begin": "\\s+/[pP]\\s+",
+ "end": "(?=$\\n|[&|><)])",
+ "patterns": [
+ {
+ "include":
"#command_set_strings"
+ },
+ {
+ "begin": "([^
][^=]*)(=)",
+ "beginCaptures": {
+ "1": {
+ "name":
"variable.other.readwrite.batchfile"
+ },
+ "2": {
+ "name":
"keyword.operator.assignment.batchfile"
+ }
+ },
+ "end":
"(?=$\\n|[&|><)])",
+ "name":
"meta.prompt.set.batchfile",
+ "patterns": [
+ {
+
"include": "#strings"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ "command_set_group": {
+ "patterns": [
+ {
+ "begin": "\\(",
+ "beginCaptures": {
+ "0": {
+ "name":
"punctuation.section.group.begin.batchfile"
+ }
+ },
+ "end": "\\)",
+ "endCaptures": {
+ "0": {
+ "name":
"punctuation.section.group.end.batchfile"
+ }
+ },
+ "patterns": [
+ {
+ "include":
"#command_set_inside_arithmetic"
+ }
+ ]
+ }
+ ]
+ },
+ "command_set_inside_arithmetic": {
+ "patterns": [
+ {
+ "include": "#command_set_operators"
+ },
+ {
+ "include": "#numbers"
+ },
+ {
+ "match": ",",
+ "name":
"punctuation.separator.batchfile"
+ }
+ ]
+ },
+ "command_set_operators": {
+ "patterns": [
+ {
+ "match": "([^
]*)(\\+\\=|\\-\\=|\\*\\=|\\/\\=|%%\\=|&\\=|\\|\\=|\\^\\=|<<\\=|>>\\=)",
+ "captures": {
+ "1": {
+ "name":
"variable.other.readwrite.batchfile"
+ },
+ "2": {
+ "name":
"keyword.operator.assignment.augmented.batchfile"
+ }
+ }
+ },
+ {
+ "match":
"\\+|\\-|/|\\*|%%|\\||&|\\^|<<|>>|~",
+ "name":
"keyword.operator.arithmetic.batchfile"
+ },
+ {
+ "match": "!",
+ "name":
"keyword.operator.logical.batchfile"
+ },
+ {
+ "match": "([^ =]*)(=)",
+ "captures": {
+ "1": {
+ "name":
"variable.other.readwrite.batchfile"
+ },
+ "2": {
+ "name":
"keyword.operator.assignment.batchfile"
+ }
+ }
+ }
+ ]
+ },
+ "command_set_strings": {
+ "patterns": [
+ {
+ "begin": "(\")\\s*([^ ][^=]*)(=)",
+ "beginCaptures": {
+ "1": {
+ "name":
"punctuation.definition.string.begin.batchfile"
+ },
+ "2": {
+ "name":
"variable.other.readwrite.batchfile"
+ },
+ "3": {
+ "name":
"keyword.operator.assignment.batchfile"
+ }
+ },
+ "end": "\"",
+ "endCaptures": {
+ "0": {
+ "name":
"punctuation.definition.string.end.batchfile"
+ }
+ },
+ "name":
"string.quoted.double.batchfile",
+ "patterns": [
+ {
+ "include": "#variables"
+ },
+ {
+ "include": "#numbers"
+ },
+ {
+ "include":
"#escaped_characters"
+ }
+ ]
+ }
+ ]
+ },
+ "comments": {
+ "patterns": [
+ {
+ "begin": "(?:^|(&))\\s*(?=((?::[+=,;:
])))",
+ "beginCaptures": {
+ "1": {
+ "name":
"keyword.operator.conditional.batchfile"
+ }
+ },
+ "end": "\\n",
+ "patterns": [
+ {
+ "begin": "((?::[+=,;:
]))",
+ "beginCaptures": {
+ "1": {
+ "name":
"punctuation.definition.comment.batchfile"
+ }
+ },
+ "end": "(?=\\n)",
+ "name":
"comment.line.colon.batchfile"
+ }
+ ]
+ },
+ {
+ "begin": "(?<=^|[\\s@])(?i)(REM)(\\.)",
+ "beginCaptures": {
+ "1": {
+ "name":
"keyword.command.rem.batchfile"
+ },
+ "2": {
+ "name":
"punctuation.separator.batchfile"
+ }
+ },
+ "end": "(?=$\\n|[&|><)])",
+ "name": "comment.line.rem.batchfile"
+ },
+ {
+ "begin": "(?<=^|[\\s@])(?i:rem)\\b",
+ "beginCaptures": {
+ "0": {
+ "name":
"keyword.command.rem.batchfile"
+ }
+ },
+ "end": "\\n",
+ "name": "comment.line.rem.batchfile",
+ "patterns": [
+ {
+ "match": "[><|]",
+ "name":
"invalid.illegal.unexpected-character.batchfile"
+ }
+ ]
+ }
+ ]
+ },
+ "constants": {
+ "patterns": [
+ {
+ "match": "\\b(?i:NUL)\\b",
+ "name": "constant.language.batchfile"
+ }
+ ]
+ },
+ "controls": {
+ "patterns": [
+ {
+ "match":
"(?i)(?<=^|\\s)(?:call|exit(?=$|\\s)|goto(?=$|\\s|:))",
+ "name":
"keyword.control.statement.batchfile"
+ },
+ {
+ "match":
"(?<=^|\\s)(?i)(if)\\s+(?:(not)\\s+)?(exist|defined|errorlevel|cmdextversion)(?=\\s)",
+ "captures": {
+ "1": {
+ "name":
"keyword.control.conditional.batchfile"
+ },
+ "2": {
+ "name":
"keyword.operator.logical.batchfile"
+ },
+ "3": {
+ "name":
"keyword.other.special-method.batchfile"
+ }
+ }
+ },
+ {
+ "match":
"(?<=^|\\s)(?i)(?:if|else)(?=$|\\s)",
+ "name":
"keyword.control.conditional.batchfile"
+ },
+ {
+ "begin":
"(?<=^|[\\s(&^])(?i)for(?=\\s)",
+ "beginCaptures": {
+ "0": {
+ "name":
"keyword.control.repeat.batchfile"
+ }
+ },
+ "name": "meta.block.repeat.batchfile",
+ "end": "\\n",
+ "patterns": [
+ {
+ "begin":
"(?<=[\\s^])(?i)in(?=\\s)",
+ "beginCaptures": {
+ "0": {
+ "name":
"keyword.control.repeat.in.batchfile"
+ }
+ },
+ "end":
"(?<=[\\s)^])(?i)do(?=\\s)|\\n",
+ "endCaptures": {
+ "0": {
+ "name":
"keyword.control.repeat.do.batchfile"
+ }
+ },
+ "patterns": [
+ {
+
"include": "$self"
+ }
+ ]
+ },
+ {
+ "include": "$self"
+ }
+ ]
+ }
+ ]
+ },
+ "escaped_characters": {
+ "patterns": [
+ {
+ "match": "%%|\\^\\^!|\\^(?=.)|\\^\\n",
+ "name":
"constant.character.escape.batchfile"
+ }
+ ]
+ },
+ "labels": {
+ "patterns": [
+ {
+ "match":
"(?i)(?:^\\s*|(?<=call|goto)\\s*)(:)([^+=,;:\\s]\\S*)",
+ "captures": {
+ "1": {
+ "name":
"punctuation.separator.batchfile"
+ },
+ "2": {
+ "name":
"keyword.other.special-method.batchfile"
+ }
+ }
+ }
+ ]
+ },
+ "numbers": {
+ "patterns": [
+ {
+ "match":
"(?<=^|\\s|=)(0[xX][0-9A-Fa-f]*|[+-]?\\d+)(?=$|\\s|<|>)",
+ "name": "constant.numeric.batchfile"
+ }
+ ]
+ },
+ "operators": {
+ "patterns": [
+ {
+ "match": "@(?=\\S)",
+ "name": "keyword.operator.at.batchfile"
+ },
+ {
+ "match":
"(?<=\\s)(?i:EQU|NEQ|LSS|LEQ|GTR|GEQ)(?=\\s)|==",
+ "name":
"keyword.operator.comparison.batchfile"
+ },
+ {
+ "match": "(?<=\\s)(?i)(NOT)(?=\\s)",
+ "name":
"keyword.operator.logical.batchfile"
+ },
+ {
+ "match": "(?<!\\^)&&?|\\|\\|",
+ "name":
"keyword.operator.conditional.batchfile"
+ },
+ {
+ "match": "(?<!\\^)\\|",
+ "name":
"keyword.operator.pipe.batchfile"
+ },
+ {
+ "match": "<&?|>[&>]?",
+ "name":
"keyword.operator.redirection.batchfile"
+ }
+ ]
+ },
+ "parens": {
+ "patterns": [
+ {
+ "begin": "\\(",
+ "beginCaptures": {
+ "0": {
+ "name":
"punctuation.section.group.begin.batchfile"
+ }
+ },
+ "end": "\\)",
+ "endCaptures": {
+ "0": {
+ "name":
"punctuation.section.group.end.batchfile"
+ }
+ },
+ "name": "meta.group.batchfile",
+ "patterns": [
+ {
+ "match": ",|;",
+ "name":
"punctuation.separator.batchfile"
+ },
+ {
+ "include": "$self"
+ }
+ ]
+ }
+ ]
+ },
+ "repeatParameter": {
+ "patterns": [
+ {
+ "match":
"(%%)(?:(?i:~[fdpnxsatz]*(?:\\$PATH:)?)?[a-zA-Z])",
+ "captures": {
+ "1": {
+ "name":
"punctuation.definition.variable.batchfile"
+ }
+ },
+ "name":
"variable.parameter.repeat.batchfile"
+ }
+ ]
+ },
+ "strings": {
+ "patterns": [
+ {
+ "begin": "\"",
+ "beginCaptures": {
+ "0": {
+ "name":
"punctuation.definition.string.begin.batchfile"
+ }
+ },
+ "end": "(\")|(\\n)",
+ "endCaptures": {
+ "1": {
+ "name":
"punctuation.definition.string.end.batchfile"
+ },
+ "2": {
+ "name":
"invalid.illegal.newline.batchfile"
+ }
+ },
+ "name":
"string.quoted.double.batchfile",
+ "patterns": [
+ {
+ "match": "%%",
+ "name":
"constant.character.escape.batchfile"
+ },
+ {
+ "include": "#variables"
+ }
+ ]
+ }
+ ]
+ },
+ "variables": {
+ "patterns": [
+ {
+ "match":
"(%)(?:(?i:~[fdpnxsatz]*(?:\\$PATH:)?)?\\d|\\*)",
+ "captures": {
+ "1": {
+ "name":
"punctuation.definition.variable.batchfile"
+ }
+ },
+ "name": "variable.parameter.batchfile"
+ },
+ {
+ "include": "#variable"
+ },
+ {
+ "include": "#variable_delayed_expansion"
+ }
+ ]
+ },
+ "variable": {
+ "patterns": [
+ {
+ "begin": "%(?=[^%]+%)",
+ "beginCaptures": {
+ "0": {
+ "name":
"punctuation.definition.variable.begin.batchfile"
+ }
+ },
+ "end": "(%)|\\n",
+ "endCaptures": {
+ "1": {
+ "name":
"punctuation.definition.variable.end.batchfile"
+ }
+ },
+ "name":
"variable.other.readwrite.batchfile",
+ "patterns": [
+ {
+ "begin": ":~",
+ "beginCaptures": {
+ "0": {
+ "name":
"punctuation.separator.batchfile"
+ }
+ },
+ "end": "(?=%|\\n)",
+ "name":
"meta.variable.substring.batchfile",
+ "patterns": [
+ {
+
"include": "#variable_substring"
+ }
+ ]
+ },
+ {
+ "begin": ":",
+ "beginCaptures": {
+ "0": {
+ "name":
"punctuation.separator.batchfile"
+ }
+ },
+ "end": "(?=%|\\n)",
+ "name":
"meta.variable.substitution.batchfile",
+ "patterns": [
+ {
+
"include": "#variable_replace"
+ },
+ {
+
"begin": "=",
+
"beginCaptures": {
+
"0": {
+
"name": "punctuation.separator.batchfile"
+
}
+ },
+ "end":
"(?=%|\\n)",
+
"patterns": [
+
{
+
"include": "#variable_delayed_expansion"
+
},
+
{
+
"match": "[^%]+",
+
"name": "string.unquoted.batchfile"
+
}
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ "variable_delayed_expansion": {
+ "patterns": [
+ {
+ "begin": "!(?=[^!]+!)",
+ "beginCaptures": {
+ "0": {
+ "name":
"punctuation.definition.variable.begin.batchfile"
+ }
+ },
+ "end": "(!)|\\n",
+ "endCaptures": {
+ "1": {
+ "name":
"punctuation.definition.variable.end.batchfile"
+ }
+ },
+ "name":
"variable.other.readwrite.batchfile",
+ "patterns": [
+ {
+ "begin": ":~",
+ "beginCaptures": {
+ "0": {
+ "name":
"punctuation.separator.batchfile"
+ }
+ },
+ "end": "(?=!|\\n)",
+ "name":
"meta.variable.substring.batchfile",
+ "patterns": [
+ {
+
"include": "#variable_substring"
+ }
+ ]
+ },
+ {
+ "begin": ":",
+ "beginCaptures": {
+ "0": {
+ "name":
"punctuation.separator.batchfile"
+ }
+ },
+ "end": "(?=!|\\n)",
+ "name":
"meta.variable.substitution.batchfile",
+ "patterns": [
+ {
+
"include": "#escaped_characters"
+ },
+ {
+
"include": "#variable_replace"
+ },
+ {
+
"include": "#variable"
+ },
+ {
+
"begin": "=",
+
"beginCaptures": {
+
"0": {
+
"name": "punctuation.separator.batchfile"
+
}
+ },
+ "end":
"(?=!|\\n)",
+
"patterns": [
+
{
+
"include": "#variable"
+
},
+
{
+
"match": "[^!]+",
+
"name": "string.unquoted.batchfile"
+
}
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ "variable_replace": {
+ "patterns": [
+ {
+ "match": "[^=%!\\n]+",
+ "name": "string.unquoted.batchfile"
+ }
+ ]
+ },
+ "variable_substring": {
+ "patterns": [
+ {
+ "match":
"([+-]?\\d+)(?:(,)([+-]?\\d+))?",
+ "captures": {
+ "1": {
+ "name":
"constant.numeric.batchfile"
+ },
+ "2": {
+ "name":
"punctuation.separator.batchfile"
+ },
+ "3": {
+ "name":
"constant.numeric.batchfile"
+ }
+ }
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git
a/rcp/src/main/resources/org/apache/hop/ui/hopgui/grammars/shell.json
b/rcp/src/main/resources/org/apache/hop/ui/hopgui/grammars/shell.json
new file mode 100644
index 0000000000..255638d7db
--- /dev/null
+++ b/rcp/src/main/resources/org/apache/hop/ui/hopgui/grammars/shell.json
@@ -0,0 +1,2353 @@
+{
+ "information_for_contributors": [
+ "This file has been converted from
https://github.com/jeff-hykin/better-shell-syntax/blob/master/autogenerated/shell.tmLanguage.json",
+ "If you want to provide a fix or improvement, please create a
pull request against the original repository.",
+ "Once accepted there, we are happy to receive an update
request."
+ ],
+ "version":
"https://github.com/jeff-hykin/better-shell-syntax/commit/35020b0bd79a90d3b262b4c13a8bb0b33adc1f45",
+ "name": "Shell Script",
+ "scopeName": "source.shell",
+ "patterns": [
+ {
+ "include": "#initial_context"
+ }
+ ],
+ "repository": {
+ "alias_statement": {
+ "begin": "(?:(?:[ \\t]*+)(alias)(?:[
\\t]*+)((?:(?:((?<!\\w)-\\w+\\b)(?:[ \\t]*+))*))(?:(?:[
\\t]*+)(?:((?<!\\w)(?:[a-zA-Z_0-9-]+)(?!\\w))(?:(?:(\\[)((?:(?:(?:(?:\\$?)(?:(?<!\\w)(?:[a-zA-Z_0-9-]+)(?!\\w))|@)|\\*)|(-?\\d+)))(\\]))?))(?:(?:(\\=)|(\\+\\=))|(\\-\\=))))",
+ "end": "(?:(?=
|\\t|$)|(?:(?:(?:(;)|(&&))|(\\|\\|))|(&)))",
+ "beginCaptures": {
+ "1": {
+ "name": "storage.type.alias.shell"
+ },
+ "2": {
+ "patterns": [
+ {
+ "match":
"(?<!\\w)-\\w+\\b",
+ "name":
"string.unquoted.argument.shell constant.other.option.shell"
+ }
+ ]
+ },
+ "3": {
+ "name": "string.unquoted.argument.shell
constant.other.option.shell"
+ },
+ "4": {
+ "name":
"variable.other.assignment.shell"
+ },
+ "5": {
+ "name":
"punctuation.definition.array.access.shell"
+ },
+ "6": {
+ "name":
"variable.other.assignment.shell"
+ },
+ "7": {
+ "name": "constant.numeric.shell
constant.numeric.integer.shell"
+ },
+ "8": {
+ "name":
"punctuation.definition.array.access.shell"
+ },
+ "9": {
+ "name":
"keyword.operator.assignment.shell"
+ },
+ "10": {
+ "name":
"keyword.operator.assignment.compound.shell"
+ },
+ "11": {
+ "name":
"keyword.operator.assignment.compound.shell"
+ }
+ },
+ "endCaptures": {
+ "1": {
+ "name":
"punctuation.terminator.statement.semicolon.shell"
+ },
+ "2": {
+ "name":
"punctuation.separator.statement.and.shell"
+ },
+ "3": {
+ "name":
"punctuation.separator.statement.or.shell"
+ },
+ "4": {
+ "name":
"punctuation.separator.statement.background.shell"
+ }
+ },
+ "name": "meta.expression.assignment.alias.shell",
+ "patterns": [
+ {
+ "include": "#normal_context"
+ }
+ ]
+ },
+ "argument": {
+ "begin": "(?:[ \\t]++)(?!(?:&|\\||\\(|\\[|#|\\n|$|;))",
+ "end": "(?= |\\t|;|\\||&|$|\\n|\\)|\\`)",
+ "beginCaptures": {},
+ "endCaptures": {},
+ "name": "meta.argument.shell",
+ "patterns": [
+ {
+ "include": "#argument_context"
+ },
+ {
+ "include": "#line_continuation"
+ }
+ ]
+ },
+ "argument_context": {
+ "patterns": [
+ {
+ "match": "(?:[ \\t]*+)((?:[^
\t\n>&;<>\\(\\)\\$`\\\\\"'<\\|]+)(?!>))",
+ "captures": {
+ "1": {
+ "name":
"string.unquoted.argument.shell",
+ "patterns": [
+ {
+
"match": "\\*",
+ "name":
"variable.language.special.wildcard.shell"
+ },
+ {
+
"include": "#variable"
+ },
+ {
+
"include": "#numeric_literal"
+ },
+ {
+
"match": "(?<!\\w)(\\b(?:true|false)\\b)(?!\\w)",
+
"captures": {
+
"1": {
+
"name": "constant.language.$1.shell"
+
}
+ }
+ }
+ ]
+ }
+ }
+ },
+ {
+ "include": "#normal_context"
+ }
+ ]
+ },
+ "arithmetic_double": {
+ "patterns": [
+ {
+ "begin": "\\(\\(",
+ "end": "\\)(?:\\s*)\\)",
+ "beginCaptures": {
+ "0": {
+ "name":
"punctuation.section.arithmetic.double.shell"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name":
"punctuation.section.arithmetic.double.shell"
+ }
+ },
+ "name": "meta.arithmetic.shell",
+ "patterns": [
+ {
+ "include": "#math"
+ },
+ {
+ "include": "#string"
+ }
+ ]
+ }
+ ]
+ },
+ "arithmetic_no_dollar": {
+ "patterns": [
+ {
+ "begin": "\\(",
+ "end": "\\)",
+ "beginCaptures": {
+ "0": {
+ "name":
"punctuation.section.arithmetic.single.shell"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name":
"punctuation.section.arithmetic.single.shell"
+ }
+ },
+ "name": "meta.arithmetic.shell",
+ "patterns": [
+ {
+ "include": "#math"
+ },
+ {
+ "include": "#string"
+ }
+ ]
+ }
+ ]
+ },
+ "array_access_inline": {
+ "match": "(?:(\\[)([^\\[\\]]+)(\\]))",
+ "captures": {
+ "1": {
+ "name":
"punctuation.section.array.shell"
+ },
+ "2": {
+ "patterns": [
+ {
+ "include":
"#special_expansion"
+ },
+ {
+ "include": "#string"
+ },
+ {
+ "include": "#variable"
+ }
+ ]
+ },
+ "3": {
+ "name":
"punctuation.section.array.shell"
+ }
+ }
+ },
+ "array_value": {
+ "begin": "(?:[
\\t]*+)(?:((?<!\\w)(?:[a-zA-Z_0-9-]+)(?!\\w))(?:(?:(\\[)((?:(?:(?:(?:\\$?)(?:(?<!\\w)(?:[a-zA-Z_0-9-]+)(?!\\w))|@)|\\*)|(-?\\d+)))(\\]))?))(?:(?:(\\=)|(\\+\\=))|(\\-\\=))(?:[
\\t]*+)(\\()",
+ "end": "\\)",
+ "beginCaptures": {
+ "1": {
+ "name":
"variable.other.assignment.shell"
+ },
+ "2": {
+ "name":
"punctuation.definition.array.access.shell"
+ },
+ "3": {
+ "name":
"variable.other.assignment.shell"
+ },
+ "4": {
+ "name": "constant.numeric.shell
constant.numeric.integer.shell"
+ },
+ "5": {
+ "name":
"punctuation.definition.array.access.shell"
+ },
+ "6": {
+ "name":
"keyword.operator.assignment.shell"
+ },
+ "7": {
+ "name":
"keyword.operator.assignment.compound.shell"
+ },
+ "8": {
+ "name":
"keyword.operator.assignment.compound.shell"
+ },
+ "9": {
+ "name":
"punctuation.definition.array.shell"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name":
"punctuation.definition.array.shell"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "match":
"(?:((?<!\\w)(?:[a-zA-Z_0-9-]+)(?!\\w))(\\=))",
+ "captures": {
+ "1": {
+ "name":
"variable.other.assignment.array.shell entity.other.attribute-name.shell"
+ },
+ "2": {
+ "name":
"keyword.operator.assignment.shell punctuation.definition.assignment.shell"
+ }
+ }
+ },
+ {
+ "match": "(?:(\\[)(.+?)(\\])(\\=))",
+ "captures": {
+ "1": {
+ "name":
"punctuation.definition.bracket.named-array.shell"
+ },
+ "2": {
+ "name":
"string.unquoted.shell entity.other.attribute-name.bracket.shell"
+ },
+ "3": {
+ "name":
"punctuation.definition.bracket.named-array.shell"
+ },
+ "4": {
+ "name":
"punctuation.definition.assignment.shell"
+ }
+ }
+ },
+ {
+ "include": "#normal_context"
+ },
+ {
+ "include": "#simple_unquoted"
+ }
+ ]
+ },
+ "assignment_statement": {
+ "patterns": [
+ {
+ "include": "#array_value"
+ },
+ {
+ "include":
"#modified_assignment_statement"
+ },
+ {
+ "include":
"#normal_assignment_statement"
+ }
+ ]
+ },
+ "basic_command_name": {
+ "match":
"(?:(?:(?!(?:!|&|\\||\\(|\\)|\\{|\\[|<|>|#|\\n|$|;|[ \\t]))(?!nocorrect
|nocorrect\t|nocorrect$|readonly |readonly\t|readonly$|function
|function\t|function$|foreach |foreach\t|foreach$|coproc
|coproc\t|coproc$|logout |logout\t|logout$|export |export\t|export$|select
|select\t|select$|repeat |repeat\t|repeat$|pushd |pushd\t|pushd$|until
|until\t|until$|while |while\t|while$|local |local\t|local$|case
|case\t|case$|done |done\t|done$|elif |elif\t|elif$|else |else\t|else$|esac
[...]
+ "captures": {
+ "1": {
+ "name": "storage.modifier.$1.shell"
+ },
+ "2": {
+ "name":
"entity.name.function.call.shell entity.name.command.shell",
+ "patterns": [
+ {
+ "match":
"(?<!\\w)(?:continue|return|break)(?!\\w)",
+ "name":
"keyword.control.$0.shell"
+ },
+ {
+ "match":
"(?<!\\w)(?:(?:unfunction|continue|autoload|unsetopt|bindkey|builtin|getopts|command|declare|unalias|history|unlimit|typeset|suspend|source|printf|unhash|disown|ulimit|return|which|alias|break|false|print|shift|times|umask|umask|unset|read|type|exec|eval|wait|echo|dirs|jobs|kill|hash|stat|exit|test|trap|true|let|set|pwd|cd|fg|bg|fc|:|\\.)(?!\\/))(?!\\w)(?!-)",
+ "name":
"support.function.builtin.shell"
+ },
+ {
+ "include": "#variable"
+ }
+ ]
+ }
+ },
+ "name": "meta.statement.command.name.basic.shell"
+ },
+ "block_comment": {
+ "begin": "(?:(?:\\s*+)(\\/\\*))",
+ "end": "\\*\\/",
+ "beginCaptures": {
+ "1": {
+ "name":
"punctuation.definition.comment.begin.shell"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name":
"punctuation.definition.comment.end.shell"
+ }
+ },
+ "name": "comment.block.shell"
+ },
+ "boolean": {
+ "match": "\\b(?:true|false)\\b",
+ "name": "constant.language.$0.shell"
+ },
+ "case_statement": {
+ "begin": "(?:(\\bcase\\b)(?:[ \\t]*+)(.+?)(?:[
\\t]*+)(\\bin\\b))",
+ "end": "\\besac\\b",
+ "beginCaptures": {
+ "1": {
+ "name": "keyword.control.case.shell"
+ },
+ "2": {
+ "patterns": [
+ {
+ "include":
"#initial_context"
+ }
+ ]
+ },
+ "3": {
+ "name": "keyword.control.in.shell"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name": "keyword.control.esac.shell"
+ }
+ },
+ "name": "meta.case.shell",
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "match": "(?:[ \\t]*+)(\\* *\\))",
+ "captures": {
+ "1": {
+ "name":
"keyword.operator.pattern.case.default.shell"
+ }
+ }
+ },
+ {
+ "begin": "(?<!\\))(?!(?:[
\\t]*+)(?:esac\\b|$))",
+ "end": "(?:(?=\\besac\\b)|(\\)))",
+ "beginCaptures": {},
+ "endCaptures": {
+ "1": {
+ "name":
"keyword.operator.pattern.case.shell"
+ }
+ },
+ "name": "meta.case.entry.pattern.shell",
+ "patterns": [
+ {
+ "include":
"#case_statement_context"
+ }
+ ]
+ },
+ {
+ "begin": "(?<=\\))",
+ "end": "(?:(;;)|(?=\\besac\\b))",
+ "beginCaptures": {},
+ "endCaptures": {
+ "1": {
+ "name":
"punctuation.terminator.statement.case.shell"
+ }
+ },
+ "name": "meta.case.entry.body.shell",
+ "patterns": [
+ {
+ "include":
"#typical_statements"
+ },
+ {
+ "include":
"#initial_context"
+ }
+ ]
+ }
+ ]
+ },
+ "case_statement_context": {
+ "patterns": [
+ {
+ "match": "\\*",
+ "name":
"variable.language.special.quantifier.star.shell
keyword.operator.quantifier.star.shell
punctuation.definition.arbitrary-repetition.shell
punctuation.definition.regex.arbitrary-repetition.shell"
+ },
+ {
+ "match": "\\+",
+ "name":
"variable.language.special.quantifier.plus.shell
keyword.operator.quantifier.plus.shell
punctuation.definition.arbitrary-repetition.shell
punctuation.definition.regex.arbitrary-repetition.shell"
+ },
+ {
+ "match": "\\?",
+ "name":
"variable.language.special.quantifier.question.shell
keyword.operator.quantifier.question.shell
punctuation.definition.arbitrary-repetition.shell
punctuation.definition.regex.arbitrary-repetition.shell"
+ },
+ {
+ "match": "@",
+ "name":
"variable.language.special.at.shell keyword.operator.at.shell
punctuation.definition.regex.at.shell"
+ },
+ {
+ "match": "\\|",
+ "name":
"keyword.operator.orvariable.language.special.or.shell
keyword.operator.alternation.ruby.shell
punctuation.definition.regex.alternation.shell
punctuation.separator.regex.alternation.shell"
+ },
+ {
+ "match": "\\\\.",
+ "name":
"constant.character.escape.shell"
+ },
+ {
+ "match": "(?<=\\tin| in| |\\t|;;)\\(",
+ "name":
"keyword.operator.pattern.case.shell"
+ },
+ {
+ "begin": "(?<=\\S)(\\()",
+ "end": "\\)",
+ "beginCaptures": {
+ "1": {
+ "name":
"punctuation.definition.group.shell punctuation.definition.regex.group.shell"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name":
"punctuation.definition.group.shell punctuation.definition.regex.group.shell"
+ }
+ },
+ "name": "meta.parenthese.shell",
+ "patterns": [
+ {
+ "include":
"#case_statement_context"
+ }
+ ]
+ },
+ {
+ "begin": "\\[",
+ "end": "\\]",
+ "beginCaptures": {
+ "0": {
+ "name":
"punctuation.definition.character-class.shell"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name":
"punctuation.definition.character-class.shell"
+ }
+ },
+ "name":
"string.regexp.character-class.shell",
+ "patterns": [
+ {
+ "match": "\\\\.",
+ "name":
"constant.character.escape.shell"
+ }
+ ]
+ },
+ {
+ "include": "#string"
+ },
+ {
+ "match": "[^) \\t\\n\\[\\?\\*\\|\\@]",
+ "name": "string.unquoted.pattern.shell
string.regexp.unquoted.shell"
+ }
+ ]
+ },
+ "command_name_range": {
+ "begin": "\\G",
+ "end": "(?:(?= |\\t|;|\\||&|$|\\n|\\)|\\`)|(?=<))",
+ "beginCaptures": {},
+ "endCaptures": {},
+ "name": "meta.statement.command.name.shell",
+ "patterns": [
+ {
+ "match":
"(?<!\\w)(?:continue|return|break)(?!\\w)",
+ "name":
"entity.name.function.call.shell entity.name.command.shell
keyword.control.$0.shell"
+ },
+ {
+ "match":
"(?<!\\w)(?:(?:unfunction|continue|autoload|unsetopt|bindkey|builtin|getopts|command|declare|unalias|history|unlimit|typeset|suspend|source|printf|unhash|disown|ulimit|return|which|alias|break|false|print|shift|times|umask|umask|unset|read|type|exec|eval|wait|echo|dirs|jobs|kill|hash|stat|exit|test|trap|true|let|set|pwd|cd|fg|bg|fc|:|\\.)(?!\\/))(?!\\w)(?!-)",
+ "name":
"entity.name.function.call.shell entity.name.command.shell
support.function.builtin.shell"
+ },
+ {
+ "include": "#variable"
+ },
+ {
+ "match":
"(?:(?<!\\w)(?<=\\G|'|\"|\\}|\\))([^ \\n\\t\\r\"'=;&\\|`\\)\\{<>]+))",
+ "captures": {
+ "1": {
+ "name":
"entity.name.function.call.shell entity.name.command.shell"
+ }
+ }
+ },
+ {
+ "begin": "(?:(?:\\G|(?<!
|\\t|;|\\||&|\\n|\\{|#))(?:(\\$?)((?:(\")|(')))))",
+ "end": "(?<!\\G)(?<=(?:\\2))",
+ "beginCaptures": {
+ "1": {
+ "name":
"meta.statement.command.name.quoted.shell punctuation.definition.string.shell
entity.name.function.call.shell entity.name.command.shell"
+ },
+ "2": {},
+ "3": {
+ "name":
"meta.statement.command.name.quoted.shell string.quoted.double.shell
punctuation.definition.string.begin.shell entity.name.function.call.shell
entity.name.command.shell"
+ },
+ "4": {
+ "name":
"meta.statement.command.name.quoted.shell string.quoted.single.shell
punctuation.definition.string.begin.shell entity.name.function.call.shell
entity.name.command.shell"
+ }
+ },
+ "endCaptures": {},
+ "patterns": [
+ {
+ "include":
"#continuation_of_single_quoted_command_name"
+ },
+ {
+ "include":
"#continuation_of_double_quoted_command_name"
+ }
+ ]
+ },
+ {
+ "include": "#line_continuation"
+ },
+ {
+ "include": "#simple_unquoted"
+ }
+ ]
+ },
+ "command_statement": {
+ "begin": "(?:(?:[
\\t]*+)(?:(?!(?:!|&|\\||\\(|\\)|\\{|\\[|<|>|#|\\n|$|;|[ \\t]))(?!nocorrect
|nocorrect\t|nocorrect$|readonly |readonly\t|readonly$|function
|function\t|function$|foreach |foreach\t|foreach$|coproc
|coproc\t|coproc$|logout |logout\t|logout$|export |export\t|export$|select
|select\t|select$|repeat |repeat\t|repeat$|pushd |pushd\t|pushd$|until
|until\t|until$|while |while\t|while$|local |local\t|local$|case
|case\t|case$|done |done\t|done$|elif |elif\t|elif$|else |else\t [...]
+ "end": "(?=;|\\||&|\\n|\\)|\\`|\\{|\\}|[
\\t]*#|\\])(?<!\\\\)",
+ "beginCaptures": {},
+ "endCaptures": {},
+ "name": "meta.statement.command.shell",
+ "patterns": [
+ {
+ "include": "#command_name_range"
+ },
+ {
+ "include": "#line_continuation"
+ },
+ {
+ "include": "#option"
+ },
+ {
+ "include": "#argument"
+ },
+ {
+ "include": "#string"
+ },
+ {
+ "include": "#heredoc"
+ }
+ ]
+ },
+ "comment": {
+ "match": "(?:(?:^|(?:[
\\t]++))(?:((?:(#!)(?:.*)))|((?:(#)(?:.*)))))",
+ "captures": {
+ "1": {
+ "name": "comment.line.number-sign.shell
meta.shebang.shell"
+ },
+ "2": {
+ "name":
"punctuation.definition.comment.shebang.shell"
+ },
+ "3": {
+ "name": "comment.line.number-sign.shell"
+ },
+ "4": {
+ "name":
"punctuation.definition.comment.shell"
+ }
+ }
+ },
+ "comments": {
+ "patterns": [
+ {
+ "include": "#block_comment"
+ },
+ {
+ "include": "#line_comment"
+ }
+ ]
+ },
+ "compound-command": {
+ "patterns": [
+ {
+ "begin": "\\[",
+ "beginCaptures": {
+ "0": {
+ "name":
"punctuation.definition.logical-expression.shell"
+ }
+ },
+ "end": "\\]",
+ "endCaptures": {
+ "0": {
+ "name":
"punctuation.definition.logical-expression.shell"
+ }
+ },
+ "name":
"meta.scope.logical-expression.shell",
+ "patterns": [
+ {
+ "include":
"#logical-expression"
+ },
+ {
+ "include":
"#initial_context"
+ }
+ ]
+ },
+ {
+ "begin": "(?<=\\s|^){(?=\\s|$)",
+ "beginCaptures": {
+ "0": {
+ "name":
"punctuation.definition.group.shell"
+ }
+ },
+ "end": "(?<=^|;)\\s*(})",
+ "endCaptures": {
+ "1": {
+ "name":
"punctuation.definition.group.shell"
+ }
+ },
+ "name": "meta.scope.group.shell",
+ "patterns": [
+ {
+ "include":
"#initial_context"
+ }
+ ]
+ }
+ ]
+ },
+ "continuation_of_double_quoted_command_name": {
+ "begin": "(?:\\G(?<=\"))",
+ "end": "\"",
+ "beginCaptures": {},
+ "endCaptures": {
+ "0": {
+ "name": "string.quoted.double.shell
punctuation.definition.string.end.shell entity.name.function.call.shell
entity.name.command.shell"
+ }
+ },
+ "contentName":
"meta.statement.command.name.continuation string.quoted.double
entity.name.function.call entity.name.command",
+ "patterns": [
+ {
+ "match": "\\\\[\\$\\n`\"\\\\]",
+ "name":
"constant.character.escape.shell"
+ },
+ {
+ "include": "#variable"
+ },
+ {
+ "include": "#interpolation"
+ }
+ ]
+ },
+ "continuation_of_single_quoted_command_name": {
+ "begin": "(?:\\G(?<='))",
+ "end": "'",
+ "beginCaptures": {},
+ "endCaptures": {
+ "0": {
+ "name": "string.quoted.single.shell
punctuation.definition.string.end.shell entity.name.function.call.shell
entity.name.command.shell"
+ }
+ },
+ "contentName":
"meta.statement.command.name.continuation string.quoted.single
entity.name.function.call entity.name.command"
+ },
+ "custom_command_names": {
+ "patterns": []
+ },
+ "custom_commands": {
+ "patterns": []
+ },
+ "double_quote_context": {
+ "patterns": [
+ {
+ "match": "\\\\[\\$`\"\\\\\\n]",
+ "name":
"constant.character.escape.shell"
+ },
+ {
+ "include": "#variable"
+ },
+ {
+ "include": "#interpolation"
+ }
+ ]
+ },
+ "double_quote_escape_char": {
+ "match": "\\\\[\\$`\"\\\\\\n]",
+ "name": "constant.character.escape.shell"
+ },
+ "floating_keyword": {
+ "patterns": [
+ {
+ "match": "(?<=^|;|&|
|\\t)(?:then|elif|else|done|end|do|if|fi)(?= |\\t|;|&|$)",
+ "name": "keyword.control.$0.shell"
+ }
+ ]
+ },
+ "for_statement": {
+ "patterns": [
+ {
+ "begin": "(?:(\\bfor\\b)(?:(?:[
\\t]*+)((?<!\\w)(?:[a-zA-Z_0-9-]+)(?!\\w))(?:[ \\t]*+)(\\bin\\b)))",
+ "end":
"(?=;|\\||&|\\n|\\)|\\`|\\{|\\}|[ \\t]*#|\\])(?<!\\\\)",
+ "beginCaptures": {
+ "1": {
+ "name":
"keyword.control.for.shell"
+ },
+ "2": {
+ "name":
"variable.other.for.shell"
+ },
+ "3": {
+ "name":
"keyword.control.in.shell"
+ }
+ },
+ "endCaptures": {},
+ "name": "meta.for.in.shell",
+ "patterns": [
+ {
+ "include": "#string"
+ },
+ {
+ "include":
"#simple_unquoted"
+ },
+ {
+ "include":
"#normal_context"
+ }
+ ]
+ },
+ {
+ "begin": "(\\bfor\\b)",
+ "end":
"(?=;|\\||&|\\n|\\)|\\`|\\{|\\}|[ \\t]*#|\\])(?<!\\\\)",
+ "beginCaptures": {
+ "1": {
+ "name":
"keyword.control.for.shell"
+ }
+ },
+ "endCaptures": {},
+ "name": "meta.for.shell",
+ "patterns": [
+ {
+ "include":
"#arithmetic_double"
+ },
+ {
+ "include":
"#normal_context"
+ }
+ ]
+ }
+ ]
+ },
+ "function_definition": {
+ "begin": "(?:[ \\t]*+)(?:(?:(\\bfunction\\b)(?:[
\\t]*+)([^ \\t\\n\\r\\(\\)=\"']+)(?:(?:(\\()(?:[ \\t]*+)(\\)))?))|(?:([^
\\t\\n\\r\\(\\)=\"']+)(?:[ \\t]*+)(\\()(?:[ \\t]*+)(\\))))",
+ "end": "(?<=\\}|\\))",
+ "beginCaptures": {
+ "1": {
+ "name": "storage.type.function.shell"
+ },
+ "2": {
+ "name": "entity.name.function.shell"
+ },
+ "3": {
+ "name":
"punctuation.definition.arguments.shell"
+ },
+ "4": {
+ "name":
"punctuation.definition.arguments.shell"
+ },
+ "5": {
+ "name": "entity.name.function.shell"
+ },
+ "6": {
+ "name":
"punctuation.definition.arguments.shell"
+ },
+ "7": {
+ "name":
"punctuation.definition.arguments.shell"
+ }
+ },
+ "endCaptures": {},
+ "name": "meta.function.shell",
+ "patterns": [
+ {
+ "match": "(?:\\G(?:\\t| |\\n))"
+ },
+ {
+ "begin": "\\{",
+ "end": "\\}",
+ "beginCaptures": {
+ "0": {
+ "name":
"punctuation.definition.group.shell
punctuation.section.function.definition.shell"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name":
"punctuation.definition.group.shell
punctuation.section.function.definition.shell"
+ }
+ },
+ "name": "meta.function.body.shell",
+ "patterns": [
+ {
+ "include":
"#initial_context"
+ }
+ ]
+ },
+ {
+ "begin": "\\(",
+ "end": "\\)",
+ "beginCaptures": {
+ "0": {
+ "name":
"punctuation.definition.group.shell
punctuation.section.function.definition.shell"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name":
"punctuation.definition.group.shell
punctuation.section.function.definition.shell"
+ }
+ },
+ "name": "meta.function.body.shell",
+ "patterns": [
+ {
+ "include":
"#initial_context"
+ }
+ ]
+ },
+ {
+ "include": "#initial_context"
+ }
+ ],
+ "applyEndPatternLast": 1
+ },
+ "heredoc": {
+ "patterns": [
+ {
+ "begin": "(?:((?<!<)(?:<<-))(?:[
\\t]*+)(\"|')(?:[ \\t]*+)([^\"']+?)(?=\\s|;|&|<|\"|')((?:\\2))(.*))",
+ "end":
"(?:(?:^\\t*)(?:\\3)(?=\\s|;|&|$))",
+ "beginCaptures": {
+ "1": {
+ "name":
"keyword.operator.heredoc.shell"
+ },
+ "2": {
+ "name":
"punctuation.definition.string.heredoc.quote.shell"
+ },
+ "3": {
+ "name":
"punctuation.definition.string.heredoc.delimiter.shell"
+ },
+ "4": {
+ "name":
"punctuation.definition.string.heredoc.quote.shell"
+ },
+ "5": {
+ "patterns": [
+ {
+
"include": "#redirect_fix"
+ },
+ {
+
"include": "#typical_statements"
+ }
+ ]
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name":
"punctuation.definition.string.heredoc.$0.shell"
+ }
+ },
+ "contentName":
"string.quoted.heredoc.indent.$3",
+ "patterns": []
+ },
+ {
+ "begin": "(?:((?<!<)(?:<<)(?!<))(?:[
\\t]*+)(\"|')(?:[ \\t]*+)([^\"']+?)(?=\\s|;|&|<|\"|')((?:\\2))(.*))",
+ "end": "(?:^(?:\\3)(?=\\s|;|&|$))",
+ "beginCaptures": {
+ "1": {
+ "name":
"keyword.operator.heredoc.shell"
+ },
+ "2": {
+ "name":
"punctuation.definition.string.heredoc.quote.shell"
+ },
+ "3": {
+ "name":
"punctuation.definition.string.heredoc.delimiter.shell"
+ },
+ "4": {
+ "name":
"punctuation.definition.string.heredoc.quote.shell"
+ },
+ "5": {
+ "patterns": [
+ {
+
"include": "#redirect_fix"
+ },
+ {
+
"include": "#typical_statements"
+ }
+ ]
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name":
"punctuation.definition.string.heredoc.delimiter.shell"
+ }
+ },
+ "contentName":
"string.quoted.heredoc.no-indent.$3",
+ "patterns": []
+ },
+ {
+ "begin": "(?:((?<!<)(?:<<-))(?:[
\\t]*+)([^\"' \\t]+)(?=\\s|;|&|<|\"|')(.*))",
+ "end":
"(?:(?:^\\t*)(?:\\2)(?=\\s|;|&|$))",
+ "beginCaptures": {
+ "1": {
+ "name":
"keyword.operator.heredoc.shell"
+ },
+ "2": {
+ "name":
"punctuation.definition.string.heredoc.delimiter.shell"
+ },
+ "3": {
+ "patterns": [
+ {
+
"include": "#redirect_fix"
+ },
+ {
+
"include": "#typical_statements"
+ }
+ ]
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name":
"punctuation.definition.string.heredoc.delimiter.shell"
+ }
+ },
+ "contentName":
"string.unquoted.heredoc.indent.$2",
+ "patterns": [
+ {
+ "include":
"#double_quote_escape_char"
+ },
+ {
+ "include": "#variable"
+ },
+ {
+ "include":
"#interpolation"
+ }
+ ]
+ },
+ {
+ "begin": "(?:((?<!<)(?:<<)(?!<))(?:[
\\t]*+)([^\"' \\t]+)(?=\\s|;|&|<|\"|')(.*))",
+ "end": "(?:^(?:\\2)(?=\\s|;|&|$))",
+ "beginCaptures": {
+ "1": {
+ "name":
"keyword.operator.heredoc.shell"
+ },
+ "2": {
+ "name":
"punctuation.definition.string.heredoc.delimiter.shell"
+ },
+ "3": {
+ "patterns": [
+ {
+
"include": "#redirect_fix"
+ },
+ {
+
"include": "#typical_statements"
+ }
+ ]
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name":
"punctuation.definition.string.heredoc.delimiter.shell"
+ }
+ },
+ "contentName":
"string.unquoted.heredoc.no-indent.$2",
+ "patterns": [
+ {
+ "include":
"#double_quote_escape_char"
+ },
+ {
+ "include": "#variable"
+ },
+ {
+ "include":
"#interpolation"
+ }
+ ]
+ }
+ ]
+ },
+ "herestring": {
+ "patterns": [
+ {
+ "begin": "(<<<)\\s*(('))",
+ "beginCaptures": {
+ "1": {
+ "name":
"keyword.operator.herestring.shell"
+ },
+ "2": {
+ "name":
"string.quoted.single.shell"
+ },
+ "3": {
+ "name":
"punctuation.definition.string.begin.shell"
+ }
+ },
+ "end": "(')",
+ "endCaptures": {
+ "0": {
+ "name":
"string.quoted.single.shell"
+ },
+ "1": {
+ "name":
"punctuation.definition.string.end.shell"
+ }
+ },
+ "name": "meta.herestring.shell",
+ "contentName":
"string.quoted.single.shell"
+ },
+ {
+ "begin": "(<<<)\\s*((\"))",
+ "beginCaptures": {
+ "1": {
+ "name":
"keyword.operator.herestring.shell"
+ },
+ "2": {
+ "name":
"string.quoted.double.shell"
+ },
+ "3": {
+ "name":
"punctuation.definition.string.begin.shell"
+ }
+ },
+ "end": "(\")",
+ "endCaptures": {
+ "0": {
+ "name":
"string.quoted.double.shell"
+ },
+ "1": {
+ "name":
"punctuation.definition.string.end.shell"
+ }
+ },
+ "name": "meta.herestring.shell",
+ "contentName":
"string.quoted.double.shell",
+ "patterns": [
+ {
+ "include":
"#double_quote_context"
+ }
+ ]
+ },
+ {
+ "captures": {
+ "1": {
+ "name":
"keyword.operator.herestring.shell"
+ },
+ "2": {
+ "name":
"string.unquoted.herestring.shell",
+ "patterns": [
+ {
+
"include": "#initial_context"
+ }
+ ]
+ }
+ },
+ "match":
"(<<<)\\s*(([^\\s)\\\\]|\\\\.)+)",
+ "name": "meta.herestring.shell"
+ }
+ ]
+ },
+ "initial_context": {
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "include": "#pipeline"
+ },
+ {
+ "include": "#normal_statement_seperator"
+ },
+ {
+ "include": "#logical_expression_double"
+ },
+ {
+ "include": "#logical_expression_single"
+ },
+ {
+ "include": "#assignment_statement"
+ },
+ {
+ "include": "#case_statement"
+ },
+ {
+ "include": "#for_statement"
+ },
+ {
+ "include": "#loop"
+ },
+ {
+ "include": "#function_definition"
+ },
+ {
+ "include": "#line_continuation"
+ },
+ {
+ "include": "#arithmetic_double"
+ },
+ {
+ "include": "#misc_ranges"
+ },
+ {
+ "include": "#variable"
+ },
+ {
+ "include": "#interpolation"
+ },
+ {
+ "include": "#heredoc"
+ },
+ {
+ "include": "#herestring"
+ },
+ {
+ "include": "#redirection"
+ },
+ {
+ "include": "#pathname"
+ },
+ {
+ "include": "#floating_keyword"
+ },
+ {
+ "include": "#alias_statement"
+ },
+ {
+ "include": "#normal_statement"
+ },
+ {
+ "include": "#string"
+ },
+ {
+ "include": "#support"
+ }
+ ]
+ },
+ "inline_comment": {
+ "match":
"(\\/\\*)((?:(?:[^\\*]|(?:(?:\\*++)[^\\/]))*+)((?:(?:\\*++)\\/)))",
+ "captures": {
+ "1": {
+ "name": "comment.block.shell
punctuation.definition.comment.begin.shell"
+ },
+ "2": {
+ "name": "comment.block.shell"
+ },
+ "3": {
+ "patterns": [
+ {
+ "match": "\\*\\/",
+ "name":
"comment.block.shell punctuation.definition.comment.end.shell"
+ },
+ {
+ "match": "\\*",
+ "name":
"comment.block.shell"
+ }
+ ]
+ }
+ }
+ },
+ "interpolation": {
+ "patterns": [
+ {
+ "include": "#arithmetic_dollar"
+ },
+ {
+ "include": "#subshell_dollar"
+ },
+ {
+ "begin": "`",
+ "beginCaptures": {
+ "0": {
+ "name":
"punctuation.definition.evaluation.backticks.shell"
+ }
+ },
+ "end": "`",
+ "endCaptures": {
+ "0": {
+ "name":
"punctuation.definition.evaluation.backticks.shell"
+ }
+ },
+ "name":
"string.interpolated.backtick.shell",
+ "patterns": [
+ {
+ "match": "\\\\[`\\\\$]",
+ "name":
"constant.character.escape.shell"
+ },
+ {
+ "begin":
"(?<=\\W)(?=#)(?!#{)",
+ "beginCaptures": {
+ "1": {
+ "name":
"punctuation.whitespace.comment.leading.shell"
+ }
+ },
+ "end": "(?!\\G)",
+ "patterns": [
+ {
+
"begin": "#",
+
"beginCaptures": {
+
"0": {
+
"name": "punctuation.definition.comment.shell"
+
}
+ },
+ "end":
"(?=`)",
+ "name":
"comment.line.number-sign.shell"
+ }
+ ]
+ },
+ {
+ "include":
"#initial_context"
+ }
+ ]
+ }
+ ]
+ },
+ "keyword": {
+ "patterns": [
+ {
+ "match":
"(?<=^|;|&|\\s)(then|else|elif|fi|for|in|do|done|select|continue|esac|while|until|return)(?=\\s|;|&|$)",
+ "name": "keyword.control.shell"
+ },
+ {
+ "match":
"(?<=^|;|&|\\s)(?:export|declare|typeset|local|readonly)(?=\\s|;|&|$)",
+ "name": "storage.modifier.shell"
+ }
+ ]
+ },
+ "line_comment": {
+ "begin": "(?:\\s*+)(\\/\\/)",
+ "end": "(?<=\\n)(?<!\\\\\\n)",
+ "beginCaptures": {
+ "1": {
+ "name":
"punctuation.definition.comment.shell"
+ }
+ },
+ "endCaptures": {},
+ "name": "comment.line.double-slash.shell",
+ "patterns": [
+ {
+ "include":
"#line_continuation_character"
+ }
+ ]
+ },
+ "line_continuation": {
+ "match": "\\\\(?=\\n)",
+ "name":
"constant.character.escape.line-continuation.shell"
+ },
+ "logical-expression": {
+ "patterns": [
+ {
+ "include": "#arithmetic_no_dollar"
+ },
+ {
+ "comment": "do we want a special rule
for ( expr )?",
+ "match": "=[=~]?|!=?|<|>|&&|\\|\\|",
+ "name": "keyword.operator.logical.shell"
+ },
+ {
+ "match":
"(?<!\\S)-(nt|ot|ef|eq|ne|l[te]|g[te]|[a-hknoprstuwxzOGLSN])\\b",
+ "name": "keyword.operator.logical.shell"
+ }
+ ]
+ },
+ "logical_expression_context": {
+ "patterns": [
+ {
+ "include": "#regex_comparison"
+ },
+ {
+ "include": "#arithmetic_no_dollar"
+ },
+ {
+ "include": "#logical-expression"
+ },
+ {
+ "include": "#logical_expression_single"
+ },
+ {
+ "include": "#logical_expression_double"
+ },
+ {
+ "include": "#comment"
+ },
+ {
+ "include": "#boolean"
+ },
+ {
+ "include": "#redirect_number"
+ },
+ {
+ "include": "#numeric_literal"
+ },
+ {
+ "include": "#pipeline"
+ },
+ {
+ "include": "#normal_statement_seperator"
+ },
+ {
+ "include": "#string"
+ },
+ {
+ "include": "#variable"
+ },
+ {
+ "include": "#interpolation"
+ },
+ {
+ "include": "#heredoc"
+ },
+ {
+ "include": "#herestring"
+ },
+ {
+ "include": "#pathname"
+ },
+ {
+ "include": "#floating_keyword"
+ },
+ {
+ "include": "#support"
+ }
+ ]
+ },
+ "logical_expression_double": {
+ "begin": "\\[\\[",
+ "end": "\\]\\]",
+ "beginCaptures": {
+ "0": {
+ "name":
"punctuation.definition.logical-expression.shell"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name":
"punctuation.definition.logical-expression.shell"
+ }
+ },
+ "name": "meta.scope.logical-expression.shell",
+ "patterns": [
+ {
+ "include": "#logical_expression_context"
+ }
+ ]
+ },
+ "logical_expression_single": {
+ "begin": "\\[",
+ "end": "\\]",
+ "beginCaptures": {
+ "0": {
+ "name":
"punctuation.definition.logical-expression.shell"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name":
"punctuation.definition.logical-expression.shell"
+ }
+ },
+ "name": "meta.scope.logical-expression.shell",
+ "patterns": [
+ {
+ "include": "#logical_expression_context"
+ }
+ ]
+ },
+ "loop": {
+ "patterns": [
+ {
+ "begin":
"(?<=^|;|&|\\s)(for)\\s+(.+?)\\s+(in)(?=\\s|;|&|$)",
+ "beginCaptures": {
+ "1": {
+ "name":
"keyword.control.shell"
+ },
+ "2": {
+ "name":
"variable.other.loop.shell",
+ "patterns": [
+ {
+
"include": "#string"
+ }
+ ]
+ },
+ "3": {
+ "name":
"keyword.control.shell"
+ }
+ },
+ "end":
"(?<=^|;|&|\\s)done(?=\\s|;|&|$|\\))",
+ "endCaptures": {
+ "0": {
+ "name":
"keyword.control.shell"
+ }
+ },
+ "name": "meta.scope.for-in-loop.shell",
+ "patterns": [
+ {
+ "include":
"#initial_context"
+ }
+ ]
+ },
+ {
+ "begin":
"(?<=^|;|&|\\s)(while|until)(?=\\s|;|&|$)",
+ "beginCaptures": {
+ "1": {
+ "name":
"keyword.control.shell"
+ }
+ },
+ "end":
"(?<=^|;|&|\\s)done(?=\\s|;|&|$|\\))",
+ "endCaptures": {
+ "0": {
+ "name":
"keyword.control.shell"
+ }
+ },
+ "name": "meta.scope.while-loop.shell",
+ "patterns": [
+ {
+ "include":
"#initial_context"
+ }
+ ]
+ },
+ {
+ "begin":
"(?<=^|;|&|\\s)(select)\\s+((?:[^\\s\\\\]|\\\\.)+)(?=\\s|;|&|$)",
+ "beginCaptures": {
+ "1": {
+ "name":
"keyword.control.shell"
+ },
+ "2": {
+ "name":
"variable.other.loop.shell"
+ }
+ },
+ "end":
"(?<=^|;|&|\\s)(done)(?=\\s|;|&|$|\\))",
+ "endCaptures": {
+ "1": {
+ "name":
"keyword.control.shell"
+ }
+ },
+ "name": "meta.scope.select-block.shell",
+ "patterns": [
+ {
+ "include":
"#initial_context"
+ }
+ ]
+ },
+ {
+ "begin":
"(?<=^|;|&|\\s)if(?=\\s|;|&|$)",
+ "beginCaptures": {
+ "0": {
+ "name":
"keyword.control.if.shell"
+ }
+ },
+ "end": "(?<=^|;|&|\\s)fi(?=\\s|;|&|$)",
+ "endCaptures": {
+ "0": {
+ "name":
"keyword.control.fi.shell"
+ }
+ },
+ "name": "meta.scope.if-block.shell",
+ "patterns": [
+ {
+ "include":
"#initial_context"
+ }
+ ]
+ }
+ ]
+ },
+ "math": {
+ "patterns": [
+ {
+ "include": "#variable"
+ },
+ {
+ "match":
"\\+{1,2}|-{1,2}|!|~|\\*{1,2}|/|%|<[<=]?|>[>=]?|==|!=|^|\\|{1,2}|&{1,2}|\\?|\\:|,|=|[*/%+\\-&^|]=|<<=|>>=",
+ "name":
"keyword.operator.arithmetic.shell"
+ },
+ {
+ "match": "0[xX][0-9A-Fa-f]+",
+ "name": "constant.numeric.hex.shell"
+ },
+ {
+ "match": ";",
+ "name":
"punctuation.separator.semicolon.range"
+ },
+ {
+ "match": "0\\d+",
+ "name": "constant.numeric.octal.shell"
+ },
+ {
+ "match": "\\d{1,2}#[0-9a-zA-Z@_]+",
+ "name": "constant.numeric.other.shell"
+ },
+ {
+ "match": "\\d+",
+ "name": "constant.numeric.integer.shell"
+ },
+ {
+ "match":
"(?<!\\w)(?:[a-zA-Z_0-9]+)(?!\\w)",
+ "name": "variable.other.normal.shell"
+ }
+ ]
+ },
+ "math_operators": {
+ "patterns": [
+ {
+ "match":
"\\+{1,2}|-{1,2}|!|~|\\*{1,2}|/|%|<[<=]?|>[>=]?|==|!=|^|\\|{1,2}|&{1,2}|\\?|\\:|,|=|[*/%+\\-&^|]=|<<=|>>=",
+ "name":
"keyword.operator.arithmetic.shell"
+ },
+ {
+ "match": "0[xX][0-9A-Fa-f]+",
+ "name": "constant.numeric.hex.shell"
+ },
+ {
+ "match": "0\\d+",
+ "name": "constant.numeric.octal.shell"
+ },
+ {
+ "match": "\\d{1,2}#[0-9a-zA-Z@_]+",
+ "name": "constant.numeric.other.shell"
+ },
+ {
+ "match": "\\d+",
+ "name": "constant.numeric.integer.shell"
+ }
+ ]
+ },
+ "misc_ranges": {
+ "patterns": [
+ {
+ "include": "#logical_expression_single"
+ },
+ {
+ "include": "#logical_expression_double"
+ },
+ {
+ "include": "#subshell_dollar"
+ },
+ {
+ "begin": "(?<![^ \\t])({)(?!\\w|\\$)",
+ "end": "}",
+ "beginCaptures": {
+ "1": {
+ "name":
"punctuation.definition.group.shell"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name":
"punctuation.definition.group.shell"
+ }
+ },
+ "name": "meta.scope.group.shell",
+ "patterns": [
+ {
+ "include":
"#initial_context"
+ }
+ ]
+ }
+ ]
+ },
+ "modified_assignment_statement": {
+ "begin": "(?<=^|;|&|[
\\t])(?:readonly|declare|typeset|export|local)(?=[ \\t]|;|&|$)",
+ "end": "(?=;|\\||&|\\n|\\)|\\`|\\{|\\}|[
\\t]*#|\\])(?<!\\\\)",
+ "beginCaptures": {
+ "0": {
+ "name": "storage.modifier.$0.shell"
+ }
+ },
+ "endCaptures": {},
+ "name": "meta.statement.shell
meta.expression.assignment.modified.shell",
+ "patterns": [
+ {
+ "match": "(?<!\\w)-\\w+\\b",
+ "name": "string.unquoted.argument.shell
constant.other.option.shell"
+ },
+ {
+ "include": "#array_value"
+ },
+ {
+ "match":
"(?:((?<!\\w)(?:[a-zA-Z_0-9-]+)(?!\\w))(?:(?:(\\[)((?:(?:(?:(?:\\$?)(?:(?<!\\w)(?:[a-zA-Z_0-9-]+)(?!\\w))|@)|\\*)|(-?\\d+)))(\\]))?)(?:(?:(?:(\\=)|(\\+\\=))|(\\-\\=))?)(?:(?:(?<==|
|\\t|^|\\{|\\(|\\[)(?:(?:(?:(?:(?:(0[xX][0-9A-Fa-f]+)|(0\\d+))|(\\d{1,2}#[0-9a-zA-Z@_]+))|(-?\\d+(?:\\.\\d+)))|(-?\\d+(?:\\.\\d+)+))|(-?\\d+))(?=
|\\t|$|\\}|\\)|;))?))",
+ "captures": {
+ "1": {
+ "name":
"variable.other.assignment.shell"
+ },
+ "2": {
+ "name":
"punctuation.definition.array.access.shell"
+ },
+ "3": {
+ "name":
"variable.other.assignment.shell"
+ },
+ "4": {
+ "name":
"constant.numeric.shell constant.numeric.integer.shell"
+ },
+ "5": {
+ "name":
"punctuation.definition.array.access.shell"
+ },
+ "6": {
+ "name":
"keyword.operator.assignment.shell"
+ },
+ "7": {
+ "name":
"keyword.operator.assignment.compound.shell"
+ },
+ "8": {
+ "name":
"keyword.operator.assignment.compound.shell"
+ },
+ "9": {
+ "name":
"constant.numeric.shell constant.numeric.hex.shell"
+ },
+ "10": {
+ "name":
"constant.numeric.shell constant.numeric.octal.shell"
+ },
+ "11": {
+ "name":
"constant.numeric.shell constant.numeric.other.shell"
+ },
+ "12": {
+ "name":
"constant.numeric.shell constant.numeric.decimal.shell"
+ },
+ "13": {
+ "name":
"constant.numeric.shell constant.numeric.version.shell"
+ },
+ "14": {
+ "name":
"constant.numeric.shell constant.numeric.integer.shell"
+ }
+ }
+ },
+ {
+ "include": "#normal_context"
+ }
+ ]
+ },
+ "modifiers": {
+ "match": "(?<=^|;|&|[
\\t])(?:readonly|declare|typeset|export|local)(?=[ \\t]|;|&|$)",
+ "name": "storage.modifier.$0.shell"
+ },
+ "normal_assignment_statement": {
+ "begin": "(?:[
\\t]*+)(?:((?<!\\w)(?:[a-zA-Z_0-9-]+)(?!\\w))(?:(?:(\\[)((?:(?:(?:(?:\\$?)(?:(?<!\\w)(?:[a-zA-Z_0-9-]+)(?!\\w))|@)|\\*)|(-?\\d+)))(\\]))?))(?:(?:(\\=)|(\\+\\=))|(\\-\\=))",
+ "end": "(?=;|\\||&|\\n|\\)|\\`|\\{|\\}|[
\\t]*#|\\])(?<!\\\\)",
+ "beginCaptures": {
+ "1": {
+ "name":
"variable.other.assignment.shell"
+ },
+ "2": {
+ "name":
"punctuation.definition.array.access.shell"
+ },
+ "3": {
+ "name":
"variable.other.assignment.shell"
+ },
+ "4": {
+ "name": "constant.numeric.shell
constant.numeric.integer.shell"
+ },
+ "5": {
+ "name":
"punctuation.definition.array.access.shell"
+ },
+ "6": {
+ "name":
"keyword.operator.assignment.shell"
+ },
+ "7": {
+ "name":
"keyword.operator.assignment.compound.shell"
+ },
+ "8": {
+ "name":
"keyword.operator.assignment.compound.shell"
+ }
+ },
+ "endCaptures": {},
+ "name": "meta.expression.assignment.shell",
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "include": "#string"
+ },
+ {
+ "include":
"#normal_assignment_statement"
+ },
+ {
+ "begin": "(?<= |\\t)(?! |\\t|\\w+=)",
+ "end":
"(?=;|\\||&|\\n|\\)|\\`|\\{|\\}|[ \\t]*#|\\])(?<!\\\\)",
+ "beginCaptures": {},
+ "endCaptures": {},
+ "name":
"meta.statement.command.env.shell",
+ "patterns": [
+ {
+ "include":
"#command_name_range"
+ },
+ {
+ "include":
"#line_continuation"
+ },
+ {
+ "include": "#option"
+ },
+ {
+ "include": "#argument"
+ },
+ {
+ "include": "#string"
+ }
+ ]
+ },
+ {
+ "include": "#simple_unquoted"
+ },
+ {
+ "include": "#normal_context"
+ }
+ ]
+ },
+ "normal_context": {
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "include": "#pipeline"
+ },
+ {
+ "include": "#normal_statement_seperator"
+ },
+ {
+ "include": "#misc_ranges"
+ },
+ {
+ "include": "#boolean"
+ },
+ {
+ "include": "#redirect_number"
+ },
+ {
+ "include": "#numeric_literal"
+ },
+ {
+ "include": "#string"
+ },
+ {
+ "include": "#variable"
+ },
+ {
+ "include": "#interpolation"
+ },
+ {
+ "include": "#heredoc"
+ },
+ {
+ "include": "#herestring"
+ },
+ {
+ "include": "#redirection"
+ },
+ {
+ "include": "#pathname"
+ },
+ {
+ "include": "#floating_keyword"
+ },
+ {
+ "include": "#support"
+ },
+ {
+ "include": "#parenthese"
+ }
+ ]
+ },
+ "normal_statement": {
+ "begin": "(?:(?!^[ \\t]*+$)(?:(?<=^until | until
|\\tuntil |^while | while |\\twhile |^elif | elif |\\telif |^else | else
|\\telse |^then | then |\\tthen |^do | do |\\tdo |^if | if |\\tif
)|(?<=(?:^|;|\\||&|!|\\(|\\{|\\`)))(?:[
\\t]*+)(?!nocorrect\\W|nocorrect\\$|function\\W|function\\$|foreach\\W|foreach\\$|repeat\\W|repeat\\$|logout\\W|logout\\$|coproc\\W|coproc\\$|select\\W|select\\$|while\\W|while\\$|pushd\\W|pushd\\$|until\\W|until\\$|case\\W|case\\$|done\\W|done\\$|elif\\W|elif\
[...]
+ "end": "(?=;|\\||&|\\n|\\)|\\`|\\{|\\}|[
\\t]*#|\\])(?<!\\\\)",
+ "beginCaptures": {},
+ "endCaptures": {},
+ "name": "meta.statement.shell",
+ "patterns": [
+ {
+ "include": "#typical_statements"
+ }
+ ]
+ },
+ "normal_statement_seperator": {
+ "match": "(?:(?:(?:(;)|(&&))|(\\|\\|))|(&))",
+ "captures": {
+ "1": {
+ "name":
"punctuation.terminator.statement.semicolon.shell"
+ },
+ "2": {
+ "name":
"punctuation.separator.statement.and.shell"
+ },
+ "3": {
+ "name":
"punctuation.separator.statement.or.shell"
+ },
+ "4": {
+ "name":
"punctuation.separator.statement.background.shell"
+ }
+ }
+ },
+ "numeric_literal": {
+ "match": "(?<==|
|\\t|^|\\{|\\(|\\[)(?:(?:(?:(?:(?:(0[xX][0-9A-Fa-f]+)|(0\\d+))|(\\d{1,2}#[0-9a-zA-Z@_]+))|(-?\\d+(?:\\.\\d+)))|(-?\\d+(?:\\.\\d+)+))|(-?\\d+))(?=
|\\t|$|\\}|\\)|;)",
+ "captures": {
+ "1": {
+ "name": "constant.numeric.shell
constant.numeric.hex.shell"
+ },
+ "2": {
+ "name": "constant.numeric.shell
constant.numeric.octal.shell"
+ },
+ "3": {
+ "name": "constant.numeric.shell
constant.numeric.other.shell"
+ },
+ "4": {
+ "name": "constant.numeric.shell
constant.numeric.decimal.shell"
+ },
+ "5": {
+ "name": "constant.numeric.shell
constant.numeric.version.shell"
+ },
+ "6": {
+ "name": "constant.numeric.shell
constant.numeric.integer.shell"
+ }
+ }
+ },
+ "option": {
+ "begin": "(?:(?:[
\\t]++)(-)((?!(?:!|&|\\||\\(|\\)|\\{|\\[|<|>|#|\\n|$|;|[ \\t]))))",
+ "end": "(?:(?=[
\\t])|(?:(?=;|\\||&|\\n|\\)|\\`|\\{|\\}|[ \\t]*#|\\])(?<!\\\\)))",
+ "beginCaptures": {
+ "1": {
+ "name": "string.unquoted.argument.shell
constant.other.option.dash.shell"
+ },
+ "2": {
+ "name": "string.unquoted.argument.shell
constant.other.option.shell"
+ }
+ },
+ "endCaptures": {},
+ "contentName": "string.unquoted.argument
constant.other.option",
+ "patterns": [
+ {
+ "include": "#option_context"
+ }
+ ]
+ },
+ "option_context": {
+ "patterns": [
+ {
+ "include": "#misc_ranges"
+ },
+ {
+ "include": "#string"
+ },
+ {
+ "include": "#variable"
+ },
+ {
+ "include": "#interpolation"
+ },
+ {
+ "include": "#heredoc"
+ },
+ {
+ "include": "#herestring"
+ },
+ {
+ "include": "#redirection"
+ },
+ {
+ "include": "#pathname"
+ },
+ {
+ "include": "#floating_keyword"
+ },
+ {
+ "include": "#support"
+ }
+ ]
+ },
+ "parenthese": {
+ "patterns": [
+ {
+ "begin": "\\(",
+ "end": "\\)",
+ "beginCaptures": {
+ "0": {
+ "name":
"punctuation.section.parenthese.shell"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name":
"punctuation.section.parenthese.shell"
+ }
+ },
+ "name": "meta.parenthese.group.shell",
+ "patterns": [
+ {
+ "include":
"#initial_context"
+ }
+ ]
+ }
+ ]
+ },
+ "pathname": {
+ "patterns": [
+ {
+ "match": "(?<=\\s|:|=|^)~",
+ "name": "keyword.operator.tilde.shell"
+ },
+ {
+ "match": "\\*|\\?",
+ "name": "keyword.operator.glob.shell"
+ },
+ {
+ "begin": "([?*+@!])(\\()",
+ "beginCaptures": {
+ "1": {
+ "name":
"keyword.operator.extglob.shell"
+ },
+ "2": {
+ "name":
"punctuation.definition.extglob.shell"
+ }
+ },
+ "end": "\\)",
+ "endCaptures": {
+ "0": {
+ "name":
"punctuation.definition.extglob.shell"
+ }
+ },
+ "name": "meta.structure.extglob.shell",
+ "patterns": [
+ {
+ "include":
"#initial_context"
+ }
+ ]
+ }
+ ]
+ },
+ "pipeline": {
+ "patterns": [
+ {
+ "match":
"(?<=^|;|&|\\s)(time)(?=\\s|;|&|$)",
+ "name": "keyword.other.shell"
+ },
+ {
+ "match": "[|!]",
+ "name": "keyword.operator.pipe.shell"
+ }
+ ]
+ },
+ "redirect_fix": {
+ "match": "(?:(>>?)(?:[ \\t]*+)([^
\t\n>&;<>\\(\\)\\$`\\\\\"'<\\|]+))",
+ "captures": {
+ "1": {
+ "name":
"keyword.operator.redirect.shell"
+ },
+ "2": {
+ "name": "string.unquoted.argument.shell"
+ }
+ }
+ },
+ "redirect_number": {
+ "match": "(?<=[ \\t])(?:(?:(1)|(2)|(\\d+))(?=>))",
+ "captures": {
+ "1": {
+ "name":
"keyword.operator.redirect.stdout.shell"
+ },
+ "2": {
+ "name":
"keyword.operator.redirect.stderr.shell"
+ },
+ "3": {
+ "name":
"keyword.operator.redirect.$3.shell"
+ }
+ }
+ },
+ "redirection": {
+ "patterns": [
+ {
+ "begin": "[><]\\(",
+ "beginCaptures": {
+ "0": {
+ "name":
"punctuation.definition.string.begin.shell"
+ }
+ },
+ "end": "\\)",
+ "endCaptures": {
+ "0": {
+ "name":
"punctuation.definition.string.end.shell"
+ }
+ },
+ "name":
"string.interpolated.process-substitution.shell",
+ "patterns": [
+ {
+ "include":
"#initial_context"
+ }
+ ]
+ },
+ {
+ "match":
"(?<![<>])(&>|\\d*>&\\d*|\\d*(>>|>|<)|\\d*<&|\\d*<>)(?![<>])",
+ "name":
"keyword.operator.redirect.shell"
+ }
+ ]
+ },
+ "regex_comparison": {
+ "match": "\\=~",
+ "name": "keyword.operator.logical.regex.shell"
+ },
+ "regexp": {
+ "patterns": [
+ {
+ "match": "(?:.+)"
+ }
+ ]
+ },
+ "simple_options": {
+ "match": "(?:(?:[ \\t]++)\\-(?:\\w+))*",
+ "captures": {
+ "0": {
+ "patterns": [
+ {
+ "match": "(?:[
\\t]++)(\\-)(\\w+)",
+ "captures": {
+ "1": {
+ "name":
"string.unquoted.argument.shell constant.other.option.dash.shell"
+ },
+ "2": {
+ "name":
"string.unquoted.argument.shell constant.other.option.shell"
+ }
+ }
+ }
+ ]
+ }
+ }
+ },
+ "simple_unquoted": {
+ "match": "[^ \\t\\n>&;<>\\(\\)\\$`\\\\\"'<\\|]",
+ "name": "string.unquoted.shell"
+ },
+ "special_expansion": {
+ "match": "!|:[-=?]?|\\*|@|##|#|%%|%|\\/",
+ "name": "keyword.operator.expansion.shell"
+ },
+ "start_of_command": {
+ "match": "(?:(?:[
\\t]*+)(?:(?!(?:!|&|\\||\\(|\\)|\\{|\\[|<|>|#|\\n|$|;|[ \\t]))(?!nocorrect
|nocorrect\t|nocorrect$|readonly |readonly\t|readonly$|function
|function\t|function$|foreach |foreach\t|foreach$|coproc
|coproc\t|coproc$|logout |logout\t|logout$|export |export\t|export$|select
|select\t|select$|repeat |repeat\t|repeat$|pushd |pushd\t|pushd$|until
|until\t|until$|while |while\t|while$|local |local\t|local$|case
|case\t|case$|done |done\t|done$|elif |elif\t|elif$|else |else\t [...]
+ },
+ "string": {
+ "patterns": [
+ {
+ "match": "\\\\.",
+ "name":
"constant.character.escape.shell"
+ },
+ {
+ "begin": "'",
+ "end": "'",
+ "beginCaptures": {
+ "0": {
+ "name":
"punctuation.definition.string.begin.shell"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name":
"punctuation.definition.string.end.shell"
+ }
+ },
+ "name": "string.quoted.single.shell"
+ },
+ {
+ "begin": "\\$?\"",
+ "end": "\"",
+ "beginCaptures": {
+ "0": {
+ "name":
"punctuation.definition.string.begin.shell"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name":
"punctuation.definition.string.end.shell"
+ }
+ },
+ "name": "string.quoted.double.shell",
+ "patterns": [
+ {
+ "match":
"\\\\[\\$\\n`\"\\\\]",
+ "name":
"constant.character.escape.shell"
+ },
+ {
+ "include": "#variable"
+ },
+ {
+ "include":
"#interpolation"
+ }
+ ]
+ },
+ {
+ "begin": "\\$'",
+ "end": "'",
+ "beginCaptures": {
+ "0": {
+ "name":
"punctuation.definition.string.begin.shell"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name":
"punctuation.definition.string.end.shell"
+ }
+ },
+ "name":
"string.quoted.single.dollar.shell",
+ "patterns": [
+ {
+ "match":
"\\\\(?:a|b|e|f|n|r|t|v|\\\\|')",
+ "name":
"constant.character.escape.ansi-c.shell"
+ },
+ {
+ "match":
"\\\\[0-9]{3}\"",
+ "name":
"constant.character.escape.octal.shell"
+ },
+ {
+ "match":
"\\\\x[0-9a-fA-F]{2}\"",
+ "name":
"constant.character.escape.hex.shell"
+ },
+ {
+ "match": "\\\\c.\"",
+ "name":
"constant.character.escape.control-char.shell"
+ }
+ ]
+ }
+ ]
+ },
+ "subshell_dollar": {
+ "patterns": [
+ {
+ "begin": "(?:\\$\\()",
+ "end": "\\)",
+ "beginCaptures": {
+ "0": {
+ "name":
"punctuation.definition.subshell.single.shell"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name":
"punctuation.definition.subshell.single.shell"
+ }
+ },
+ "name": "meta.scope.subshell",
+ "patterns": [
+ {
+ "include": "#parenthese"
+ },
+ {
+ "include":
"#initial_context"
+ }
+ ]
+ }
+ ]
+ },
+ "support": {
+ "patterns": [
+ {
+ "match":
"(?<=^|;|&|\\s)(?::|\\.)(?=\\s|;|&|$)",
+ "name": "support.function.builtin.shell"
+ }
+ ]
+ },
+ "typical_statements": {
+ "patterns": [
+ {
+ "include": "#assignment_statement"
+ },
+ {
+ "include": "#case_statement"
+ },
+ {
+ "include": "#for_statement"
+ },
+ {
+ "include": "#while_statement"
+ },
+ {
+ "include": "#function_definition"
+ },
+ {
+ "include": "#command_statement"
+ },
+ {
+ "include": "#line_continuation"
+ },
+ {
+ "include": "#arithmetic_double"
+ },
+ {
+ "include": "#normal_context"
+ }
+ ]
+ },
+ "variable": {
+ "patterns": [
+ {
+ "match": "(?:(\\$)(\\@(?!\\w)))",
+ "captures": {
+ "1": {
+ "name":
"punctuation.definition.variable.shell variable.parameter.positional.all.shell"
+ },
+ "2": {
+ "name":
"variable.parameter.positional.all.shell"
+ }
+ }
+ },
+ {
+ "match": "(?:(\\$)([0-9](?!\\w)))",
+ "captures": {
+ "1": {
+ "name":
"punctuation.definition.variable.shell variable.parameter.positional.shell"
+ },
+ "2": {
+ "name":
"variable.parameter.positional.shell"
+ }
+ }
+ },
+ {
+ "match": "(?:(\\$)([-*#?$!0_](?!\\w)))",
+ "captures": {
+ "1": {
+ "name":
"punctuation.definition.variable.shell variable.language.special.shell"
+ },
+ "2": {
+ "name":
"variable.language.special.shell"
+ }
+ }
+ },
+ {
+ "begin": "(?:(\\$)(\\{)(?:[
\\t]*+)(?=\\d))",
+ "end": "\\}",
+ "beginCaptures": {
+ "1": {
+ "name":
"punctuation.definition.variable.shell variable.parameter.positional.shell"
+ },
+ "2": {
+ "name":
"punctuation.section.bracket.curly.variable.begin.shell
punctuation.definition.variable.shell variable.parameter.positional.shell"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name":
"punctuation.section.bracket.curly.variable.end.shell
punctuation.definition.variable.shell variable.parameter.positional.shell"
+ }
+ },
+ "contentName":
"meta.parameter-expansion",
+ "patterns": [
+ {
+ "include":
"#special_expansion"
+ },
+ {
+ "include":
"#array_access_inline"
+ },
+ {
+ "match": "[0-9]+",
+ "name":
"variable.parameter.positional.shell"
+ },
+ {
+ "match":
"(?<!\\w)(?:[a-zA-Z_0-9-]+)(?!\\w)",
+ "name":
"variable.other.normal.shell"
+ },
+ {
+ "include": "#variable"
+ },
+ {
+ "include": "#string"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(\\$)(\\{))",
+ "end": "\\}",
+ "beginCaptures": {
+ "1": {
+ "name":
"punctuation.definition.variable.shell"
+ },
+ "2": {
+ "name":
"punctuation.section.bracket.curly.variable.begin.shell
punctuation.definition.variable.shell"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name":
"punctuation.section.bracket.curly.variable.end.shell
punctuation.definition.variable.shell"
+ }
+ },
+ "contentName":
"meta.parameter-expansion",
+ "patterns": [
+ {
+ "include":
"#special_expansion"
+ },
+ {
+ "include":
"#array_access_inline"
+ },
+ {
+ "match":
"(?<!\\w)(?:[a-zA-Z_0-9-]+)(?!\\w)",
+ "name":
"variable.other.normal.shell"
+ },
+ {
+ "include": "#variable"
+ },
+ {
+ "include": "#string"
+ }
+ ]
+ },
+ {
+ "match": "(?:(\\$)((?:\\w+)(?!\\w)))",
+ "captures": {
+ "1": {
+ "name":
"punctuation.definition.variable.shell variable.other.normal.shell"
+ },
+ "2": {
+ "name":
"variable.other.normal.shell"
+ }
+ }
+ }
+ ]
+ },
+ "while_statement": {
+ "patterns": [
+ {
+ "begin": "(\\bwhile\\b)",
+ "end":
"(?=;|\\||&|\\n|\\)|\\`|\\{|\\}|[ \\t]*#|\\])(?<!\\\\)",
+ "beginCaptures": {
+ "1": {
+ "name":
"keyword.control.while.shell"
+ }
+ },
+ "endCaptures": {},
+ "name": "meta.while.shell",
+ "patterns": [
+ {
+ "include":
"#line_continuation"
+ },
+ {
+ "include":
"#math_operators"
+ },
+ {
+ "include": "#option"
+ },
+ {
+ "include":
"#simple_unquoted"
+ },
+ {
+ "include":
"#normal_context"
+ },
+ {
+ "include": "#string"
+ }
+ ]
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git
a/rcp/src/test/java/org/apache/hop/ui/hopgui/ContentEditorTm4eSupportTest.java
b/rcp/src/test/java/org/apache/hop/ui/hopgui/ContentEditorTm4eSupportTest.java
index cca9c3b590..37e9d499a1 100644
---
a/rcp/src/test/java/org/apache/hop/ui/hopgui/ContentEditorTm4eSupportTest.java
+++
b/rcp/src/test/java/org/apache/hop/ui/hopgui/ContentEditorTm4eSupportTest.java
@@ -21,11 +21,74 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.io.IOException;
import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import org.eclipse.tm4e.core.grammar.IGrammar;
+import org.eclipse.tm4e.core.grammar.ITokenizeLineResult;
+import org.eclipse.tm4e.core.registry.IGrammarSource;
+import org.eclipse.tm4e.core.registry.IRegistryOptions;
+import org.eclipse.tm4e.core.registry.Registry;
import org.junit.jupiter.api.Test;
class ContentEditorTm4eSupportTest {
+ /**
+ * Loads a vendored grammar the same way ContentEditorTm4eSupport does,
without an SWT Display.
+ */
+ private static IGrammar loadGrammar(String scopeName, String fileName)
throws Exception {
+ Map<String, String> files = Map.of(scopeName, fileName);
+ Registry registry =
+ new Registry(
+ new IRegistryOptions() {
+ @Override
+ public IGrammarSource getGrammarSource(String scope) {
+ String file = files.get(scope);
+ if (file == null) {
+ return null;
+ }
+ return new IGrammarSource() {
+ @Override
+ public URI getURI() {
+ return URI.create("hop://grammar/" + scope);
+ }
+
+ @Override
+ public Reader getReader() throws IOException {
+ InputStream in =
+
ContentEditorTm4eSupport.class.getResourceAsStream("grammars/" + file);
+ if (in == null) {
+ throw new IOException("Grammar resource not found:
grammars/" + file);
+ }
+ return new InputStreamReader(in, StandardCharsets.UTF_8);
+ }
+
+ @Override
+ public long getLastModified() {
+ return 0;
+ }
+
+ @Override
+ public IGrammarSource.ContentType getContentType() {
+ return IGrammarSource.ContentType.JSON;
+ }
+ };
+ }
+
+ @Override
+ public Collection<String> getInjections(String scope) {
+ return List.of();
+ }
+ });
+ return registry.loadGrammar(scopeName);
+ }
+
@Test
void scopeForLanguage_python_returnsSourcePython() {
assertEquals("source.python",
ContentEditorTm4eSupport.scopeForLanguage("python"));
@@ -45,6 +108,57 @@ class ContentEditorTm4eSupportTest {
}
}
+ @Test
+ void scopeForLanguage_shellAliases_returnSourceShell() {
+ assertEquals("source.shell",
ContentEditorTm4eSupport.scopeForLanguage("shell"));
+ assertEquals("source.shell",
ContentEditorTm4eSupport.scopeForLanguage("bash"));
+ assertEquals("source.shell",
ContentEditorTm4eSupport.scopeForLanguage("sh"));
+ }
+
+ @Test
+ void scopeForLanguage_batchAliases_returnSourceBatchfile() {
+ assertEquals("source.batchfile",
ContentEditorTm4eSupport.scopeForLanguage("bat"));
+ assertEquals("source.batchfile",
ContentEditorTm4eSupport.scopeForLanguage("cmd"));
+ assertEquals("source.batchfile",
ContentEditorTm4eSupport.scopeForLanguage("batch"));
+ }
+
+ @Test
+ void shellGrammarResourceIsOnClasspath() throws Exception {
+ try (InputStream in =
+
ContentEditorTm4eSupport.class.getResourceAsStream("grammars/shell.json")) {
+ assertNotNull(in, "grammars/shell.json should be on the classpath");
+ assertTrue(in.read() >= 0, "shell.json should not be empty");
+ }
+ }
+
+ @Test
+ void batGrammarResourceIsOnClasspath() throws Exception {
+ try (InputStream in =
ContentEditorTm4eSupport.class.getResourceAsStream("grammars/bat.json")) {
+ assertNotNull(in, "grammars/bat.json should be on the classpath");
+ assertTrue(in.read() >= 0, "bat.json should not be empty");
+ }
+ }
+
+ @Test
+ void shellGrammarLoadsAndTokenizes() throws Exception {
+ IGrammar grammar = loadGrammar("source.shell", "shell.json");
+ assertNotNull(grammar, "TM4E should load the shell grammar");
+ ITokenizeLineResult<org.eclipse.tm4e.core.grammar.IToken[]> result =
+ grammar.tokenizeLine("# comment\necho \"$HOME\"", null, null);
+ assertNotNull(result);
+ assertTrue(result.getTokens().length > 0, "shell line should produce
tokens");
+ }
+
+ @Test
+ void batGrammarLoadsAndTokenizes() throws Exception {
+ IGrammar grammar = loadGrammar("source.batchfile", "bat.json");
+ assertNotNull(grammar, "TM4E should load the batch grammar");
+ ITokenizeLineResult<org.eclipse.tm4e.core.grammar.IToken[]> result =
+ grammar.tokenizeLine("REM comment\nECHO %PATH%", null, null);
+ assertNotNull(result);
+ assertTrue(result.getTokens().length > 0, "batch line should produce
tokens");
+ }
+
@Test
void scopeForLanguage_yaml_returnsSourceYaml() {
assertEquals("source.yaml",
ContentEditorTm4eSupport.scopeForLanguage("yaml"));
