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 0c16330bc9 Add YAML file type support in Explorer perspective (#7632)
0c16330bc9 is described below

commit 0c16330bc90d56bb36a36569477831e29809f0ee
Author: Lance <[email protected]>
AuthorDate: Mon Jul 27 15:42:52 2026 +0800

    Add YAML file type support in Explorer perspective (#7632)
    
    Signed-off-by: lance <[email protected]>
---
 .../hop/ui/hopgui/ContentEditorTm4eSupport.java    | 109 ++--
 .../hopgui/RuleBasedSourceViewerConfiguration.java |   6 +-
 .../org/apache/hop/ui/hopgui/grammars/yaml.json    | 610 +++++++++++++++++++++
 .../ui/hopgui/ContentEditorTm4eSupportTest.java    |  19 +
 .../file/types/yaml/YamlExplorerFileType.java      |  67 +++
 .../types/yaml/YamlExplorerFileTypeHandler.java    |  37 ++
 .../yaml/YamlExplorerFileTypeHandlerTest.java      |  68 +++
 .../file/types/yaml/YamlExplorerFileTypeTest.java  | 136 +++++
 8 files changed, 1007 insertions(+), 45 deletions(-)

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 5725c07b68..89a348063f 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
@@ -28,6 +28,7 @@ import java.util.List;
 import java.util.Map;
 import org.apache.hop.ui.core.PropsUi;
 import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.TextAttribute;
 import org.eclipse.jface.text.rules.IToken;
 import org.eclipse.jface.text.rules.Token;
 import org.eclipse.swt.graphics.Color;
@@ -53,6 +54,7 @@ final class ContentEditorTm4eSupport {
   private static final String SCOPE_XML = "text.xml";
   private static final String SCOPE_SQL = "source.sql";
   private static final String SCOPE_PYTHON = "source.python";
+  private static final String SCOPE_YAML = "source.yaml";
 
   /** Maps TM4E scope names to grammar resource filenames (classpath-relative 
to grammars/). */
   private static final Map<String, String> GRAMMAR_FILES =
@@ -60,7 +62,8 @@ final class ContentEditorTm4eSupport {
           SCOPE_JSON, "json.json",
           SCOPE_XML, "xml.json",
           SCOPE_SQL, "sql.json",
-          SCOPE_PYTHON, "python.json");
+          SCOPE_PYTHON, "python.json",
+          SCOPE_YAML, "yaml.json");
 
   // Same palette as before (light/dark) for consistency
   private static final RGB L_COMMENT = new RGB(128, 128, 128);
@@ -101,20 +104,18 @@ final class ContentEditorTm4eSupport {
 
   /** Returns the grammar scope name for the given language id, or null if not 
supported by TM4E. */
   static String scopeForLanguage(String languageId) {
-    if (languageId == null) return null;
-    switch (languageId.toLowerCase(java.util.Locale.ROOT)) {
-      case "json":
-        return SCOPE_JSON;
-      case "xml":
-        return SCOPE_XML;
-      case "sql":
-        return SCOPE_SQL;
-      case "python":
-      case "py":
-        return SCOPE_PYTHON;
-      default:
-        return null;
+    if (languageId == null) {
+      return null;
     }
+
+    return switch (languageId.toLowerCase(java.util.Locale.ROOT)) {
+      case "json" -> SCOPE_JSON;
+      case "xml" -> SCOPE_XML;
+      case "sql" -> SCOPE_SQL;
+      case "python", "py" -> SCOPE_PYTHON;
+      case "yaml", "yml" -> SCOPE_YAML;
+      default -> null;
+    };
   }
 
   /**
@@ -124,10 +125,14 @@ final class ContentEditorTm4eSupport {
   static org.eclipse.jface.text.source.SourceViewerConfiguration 
createConfiguration(
       String languageId, Display display) {
     String scopeName = scopeForLanguage(languageId);
-    if (scopeName == null) return null;
+    if (scopeName == null) {
+      return null;
+    }
     ContentEditorTm4eSupport support = new ContentEditorTm4eSupport(display);
     IGrammar grammar = support.getGrammar(scopeName);
-    if (grammar == null) return null;
+    if (grammar == null) {
+      return null;
+    }
     return ContentEditorTm4eSupport.createReconciler(support, grammar);
   }
 
@@ -210,7 +215,9 @@ final class ContentEditorTm4eSupport {
   }
 
   private RGB scopeToRgb(List<String> scopes) {
-    if (scopes == null || scopes.isEmpty()) return isDark() ? D_DEFAULT : 
L_DEFAULT;
+    if (scopes == null || scopes.isEmpty()) {
+      return isDark() ? D_DEFAULT : L_DEFAULT;
+    }
     String scope = String.join(" ", scopes);
     boolean dark = isDark();
 
@@ -219,33 +226,59 @@ final class ContentEditorTm4eSupport {
     }
 
     // Comments (all languages)
-    if (scope.contains("comment")) return dark ? D_COMMENT : L_COMMENT;
+    if (scope.contains("comment")) {
+      return dark ? D_COMMENT : L_COMMENT;
+    }
 
     // JSON keys: must be checked before "string" - VS Code uses 
"support.type.property-name.json"
-    if (scope.contains("support.type.property-name") || 
scope.contains("property-name"))
+    if (scope.contains("support.type.property-name") || 
scope.contains("property-name")) {
+      return dark ? D_JSON_KEY : L_JSON_KEY;
+    }
+
+    // YAML keys: TextMate uses entity.name.tag.yaml (often with 
string.unquoted.*)
+    if (scope.contains("entity.name.tag.yaml")) {
       return dark ? D_JSON_KEY : L_JSON_KEY;
+    }
 
-    // Strings (JSON: string.quoted.double; XML/SQL: string.quoted.*)
-    if (scope.contains("string")) return dark ? D_STRING : L_STRING;
+    // Strings (JSON: string.quoted.double; XML/SQL: string.quoted.*; YAML: 
string.*)
+    if (scope.contains("string")) {
+      return dark ? D_STRING : L_STRING;
+    }
 
     // Numbers and constants
-    if (scope.contains("constant.numeric") || scope.contains("number"))
+    if (scope.contains("constant.numeric") || scope.contains("number")) {
       return dark ? D_NUMBER : L_NUMBER;
-    if (scope.contains("constant.language")) return dark ? D_CONSTANT : 
L_CONSTANT;
-    if (scope.contains("constant.other")) return dark ? D_CONSTANT : 
L_CONSTANT;
+    }
+    if (scope.contains("constant.language")) {
+      return dark ? D_CONSTANT : L_CONSTANT;
+    }
+    if (scope.contains("constant.other")) {
+      return dark ? D_CONSTANT : L_CONSTANT;
+    }
 
     // Keywords (SQL, etc.)
     if (scope.contains("keyword")
         || scope.contains("support.function")
-        || scope.contains("support.type")) return dark ? D_KEYWORD : L_KEYWORD;
-    if (scope.contains("storage.type") || scope.contains("storage.modifier"))
+        || scope.contains("support.type")) {
+      return dark ? D_KEYWORD : L_KEYWORD;
+    }
+    if (scope.contains("storage.type") || scope.contains("storage.modifier")) {
       return dark ? D_KEYWORD : L_KEYWORD;
-    if (scope.contains("entity.name.function")) return dark ? D_KEYWORD : 
L_KEYWORD;
+    }
+    if (scope.contains("entity.name.function")) {
+      return dark ? D_KEYWORD : L_KEYWORD;
+    }
 
     // XML: <? ?> then tags then attribute names
-    if (scope.contains("meta.tag.preprocessor")) return dark ? D_XML_HEADER : 
L_XML_HEADER;
-    if (scope.contains("entity.name.tag") || scope.contains(".tag")) return 
dark ? D_TAG : L_TAG;
-    if (scope.contains("entity.other.attribute-name")) return dark ? 
D_JSON_KEY : L_JSON_KEY;
+    if (scope.contains("meta.tag.preprocessor")) {
+      return dark ? D_XML_HEADER : L_XML_HEADER;
+    }
+    if (scope.contains("entity.name.tag") || scope.contains(".tag")) {
+      return dark ? D_TAG : L_TAG;
+    }
+    if (scope.contains("entity.other.attribute-name")) {
+      return dark ? D_JSON_KEY : L_JSON_KEY;
+    }
 
     return dark ? D_DEFAULT : L_DEFAULT;
   }
@@ -356,7 +389,9 @@ final class ContentEditorTm4eSupport {
         int rangeEnd = Math.min(rangeOffset + rangeLength, len);
 
         for (int lineIndex = 0; lineIndex < lines.length; lineIndex++) {
-          if (lineIndex >= MAX_LINES_TO_TOKENIZE) break;
+          if (lineIndex >= MAX_LINES_TO_TOKENIZE) {
+            break;
+          }
 
           String raw = lines[lineIndex];
           int rawLen = raw.length();
@@ -409,16 +444,6 @@ final class ContentEditorTm4eSupport {
       return result;
     }
 
-    private static final class ColoredToken {
-      final int offset;
-      final int length;
-      final org.eclipse.jface.text.TextAttribute attribute;
-
-      ColoredToken(int offset, int length, 
org.eclipse.jface.text.TextAttribute attribute) {
-        this.offset = offset;
-        this.length = length;
-        this.attribute = attribute;
-      }
-    }
+    private record ColoredToken(int offset, int length, TextAttribute 
attribute) {}
   }
 }
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 fef246f722..123ede5fc3 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
@@ -22,7 +22,7 @@ import org.eclipse.swt.widgets.Display;
 
 /**
  * Builds a {@link SourceViewerConfiguration} for the content editor. Uses 
TM4E with TextMate
- * grammars for syntax highlighting of JSON, XML, SQL, and Python; other 
languages get a plain
+ * grammars for syntax highlighting of JSON, XML, SQL, Python, and YAML; other 
languages get a plain
  * config.
  */
 public final class RuleBasedSourceViewerConfiguration {
@@ -31,9 +31,9 @@ public final class RuleBasedSourceViewerConfiguration {
 
   /**
    * Creates a configuration for the given language. Uses TM4E when a grammar 
is available (json,
-   * xml, sql, python); otherwise returns a plain configuration with no syntax 
highlighting.
+   * xml, sql, python, yaml); otherwise returns a plain configuration with no 
syntax highlighting.
    *
-   * @param languageId language id (e.g. "json", "xml", "sql"), or null for 
plain text
+   * @param languageId language id (e.g. "json", "xml", "sql", "yaml"), or 
null for plain text
    * @return configuration, never null
    */
   public static SourceViewerConfiguration create(String languageId) {
diff --git a/rcp/src/main/resources/org/apache/hop/ui/hopgui/grammars/yaml.json 
b/rcp/src/main/resources/org/apache/hop/ui/hopgui/grammars/yaml.json
new file mode 100644
index 0000000000..bf7bd52b8b
--- /dev/null
+++ b/rcp/src/main/resources/org/apache/hop/ui/hopgui/grammars/yaml.json
@@ -0,0 +1,610 @@
+{
+       "information_for_contributors": [
+               "This file was obtained from 
https://github.com/redhat-developer/vscode-yaml/blob/main/syntaxes/yaml.tmLanguage.json";,
+               "That file was converted from 
https://github.com/textmate/yaml.tmbundle/blob/master/Syntaxes/YAML.tmLanguage";,
+               "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/textmate/yaml.tmbundle/commit/e54ceae3b719506dba7e481a77cea4a8b576ae46";,
+       "name": "YAML",
+       "scopeName": "source.yaml",
+       "patterns": [
+               {
+                       "include": "#comment"
+               },
+               {
+                       "include": "#property"
+               },
+               {
+                       "include": "#directive"
+               },
+               {
+                       "match": "^---",
+                       "name": "entity.other.document.begin.yaml"
+               },
+               {
+                       "match": "^\\.{3}",
+                       "name": "entity.other.document.end.yaml"
+               },
+               {
+                       "include": "#node"
+               }
+       ],
+       "repository": {
+               "block-collection": {
+                       "patterns": [
+                               {
+                                       "include": "#block-sequence"
+                               },
+                               {
+                                       "include": "#block-mapping"
+                               }
+                       ]
+               },
+               "block-mapping": {
+                       "patterns": [
+                               {
+                                       "include": "#block-pair"
+                               }
+                       ]
+               },
+               "block-node": {
+                       "patterns": [
+                               {
+                                       "include": "#prototype"
+                               },
+                               {
+                                       "include": "#block-scalar"
+                               },
+                               {
+                                       "include": "#block-collection"
+                               },
+                               {
+                                       "include": "#flow-scalar-plain-out"
+                               },
+                               {
+                                       "include": "#flow-node"
+                               }
+                       ]
+               },
+               "block-pair": {
+                       "patterns": [
+                               {
+                                       "begin": "\\?",
+                                       "beginCaptures": {
+                                               "1": {
+                                                       "name": 
"punctuation.definition.key-value.begin.yaml"
+                                               }
+                                       },
+                                       "end": "(?=\\?)|^ *(:)|(:)",
+                                       "endCaptures": {
+                                               "1": {
+                                                       "name": 
"punctuation.separator.key-value.mapping.yaml"
+                                               },
+                                               "2": {
+                                                       "name": 
"invalid.illegal.expected-newline.yaml"
+                                               }
+                                       },
+                                       "name": "meta.block-mapping.yaml",
+                                       "patterns": [
+                                               {
+                                                       "include": "#block-node"
+                                               }
+                                       ]
+                               },
+                               {
+                                       "begin": "(?x)\n                        
(?=\n                            (?x:\n                                  
[^\\s[-?:,\\[\\]{}#&*!|>'\"%@`]]\n                                | [?:-] \\S\n 
                           )\n                            (\n                   
               [^\\s:]\n                                | : \\S\n               
                 | \\s+ (?![#\\s])\n                            )*\n            
                \\s*\n                            :\n\ [...]
+                                       "end": "(?x)\n                        
(?=\n                              \\s* $\n                            | \\s+ 
\\#\n                            | \\s* : (\\s|$)\n                        )\n  
                  ",
+                                       "patterns": [
+                                               {
+                                                       "include": 
"#flow-scalar-plain-out-implicit-type"
+                                               },
+                                               {
+                                                       "begin": "(?x)\n        
                          [^\\s[-?:,\\[\\]{}#&*!|>'\"%@`]]\n                    
            | [?:-] \\S\n                            ",
+                                                       "beginCaptures": {
+                                                               "0": {
+                                                                       "name": 
"entity.name.tag.yaml"
+                                                               }
+                                                       },
+                                                       "contentName": 
"entity.name.tag.yaml",
+                                                       "end": "(?x)\n          
                      (?=\n                                      \\s* $\n       
                             | \\s+ \\#\n                                    | 
\\s* : (\\s|$)\n                                )\n                            
",
+                                                       "name": 
"string.unquoted.plain.out.yaml"
+                                               }
+                                       ]
+                               },
+                               {
+                                       "match": ":(?=\\s|$)",
+                                       "name": 
"punctuation.separator.key-value.mapping.yaml"
+                               }
+                       ]
+               },
+               "block-scalar": {
+                       "begin": "(?:(\\|)|(>))([1-9])?([-+])?(.*\\n?)",
+                       "beginCaptures": {
+                               "1": {
+                                       "name": 
"keyword.control.flow.block-scalar.literal.yaml"
+                               },
+                               "2": {
+                                       "name": 
"keyword.control.flow.block-scalar.folded.yaml"
+                               },
+                               "3": {
+                                       "name": 
"constant.numeric.indentation-indicator.yaml"
+                               },
+                               "4": {
+                                       "name": 
"storage.modifier.chomping-indicator.yaml"
+                               },
+                               "5": {
+                                       "patterns": [
+                                               {
+                                                       "include": "#comment"
+                                               },
+                                               {
+                                                       "match": ".+",
+                                                       "name": 
"invalid.illegal.expected-comment-or-newline.yaml"
+                                               }
+                                       ]
+                               }
+                       },
+                       "end": "^(?=\\S)|(?!\\G)",
+                       "patterns": [
+                               {
+                                       "begin": "^([ ]+)(?! )",
+                                       "end": "^(?!\\1|\\s*$)",
+                                       "name": "string.unquoted.block.yaml"
+                               }
+                       ]
+               },
+               "block-sequence": {
+                       "match": "(-)(?!\\S)",
+                       "name": 
"punctuation.definition.block.sequence.item.yaml"
+               },
+               "comment": {
+                       "begin": "(?:(^[ \\t]*)|[ \\t]+)(?=#\\p{Print}*$)",
+                       "beginCaptures": {
+                               "1": {
+                                       "name": 
"punctuation.whitespace.comment.leading.yaml"
+                               }
+                       },
+                       "end": "(?!\\G)",
+                       "patterns": [
+                               {
+                                       "begin": "#",
+                                       "beginCaptures": {
+                                               "0": {
+                                                       "name": 
"punctuation.definition.comment.yaml"
+                                               }
+                                       },
+                                       "end": "\\n",
+                                       "name": "comment.line.number-sign.yaml"
+                               }
+                       ]
+               },
+               "directive": {
+                       "begin": "^%",
+                       "beginCaptures": {
+                               "0": {
+                                       "name": 
"punctuation.definition.directive.begin.yaml"
+                               }
+                       },
+                       "end": "(?=$|[ \\t]+($|#))",
+                       "name": "meta.directive.yaml",
+                       "patterns": [
+                               {
+                                       "captures": {
+                                               "1": {
+                                                       "name": 
"keyword.other.directive.yaml.yaml"
+                                               },
+                                               "2": {
+                                                       "name": 
"constant.numeric.yaml-version.yaml"
+                                               }
+                                       },
+                                       "match": "\\G(YAML)[ \\t]+(\\d+\\.\\d+)"
+                               },
+                               {
+                                       "captures": {
+                                               "1": {
+                                                       "name": 
"keyword.other.directive.tag.yaml"
+                                               },
+                                               "2": {
+                                                       "name": 
"storage.type.tag-handle.yaml"
+                                               },
+                                               "3": {
+                                                       "name": 
"support.type.tag-prefix.yaml"
+                                               }
+                                       },
+                                       "match": "(?x)\n                        
\\G\n                        (TAG)\n                        (?:[ \\t]+\n        
                    ((?:!(?:[0-9A-Za-z\\-]*!)?))\n                            
(?:[ \\t]+ (\n                                  !              (?x: 
%[0-9A-Fa-f]{2} | [0-9A-Za-z\\-#;/?:@&=+$,_.!~*'()\\[\\]] )*\n                  
              | (?![,!\\[\\]{}]) (?x: %[0-9A-Fa-f]{2} | 
[0-9A-Za-z\\-#;/?:@&=+$,_.!~*'()\\[\\]] )+\n                                )\n 
      [...]
+                               },
+                               {
+                                       "captures": {
+                                               "1": {
+                                                       "name": 
"support.other.directive.reserved.yaml"
+                                               },
+                                               "2": {
+                                                       "name": 
"string.unquoted.directive-name.yaml"
+                                               },
+                                               "3": {
+                                                       "name": 
"string.unquoted.directive-parameter.yaml"
+                                               }
+                                       },
+                                       "match": "(?x) \\G (\\w+) (?:[ \\t]+ 
(\\w+) (?:[ \\t]+ (\\w+))? )?"
+                               },
+                               {
+                                       "match": "\\S+",
+                                       "name": 
"invalid.illegal.unrecognized.yaml"
+                               }
+                       ]
+               },
+               "flow-alias": {
+                       "captures": {
+                               "1": {
+                                       "name": 
"keyword.control.flow.alias.yaml"
+                               },
+                               "2": {
+                                       "name": 
"punctuation.definition.alias.yaml"
+                               },
+                               "3": {
+                                       "name": "variable.other.alias.yaml"
+                               },
+                               "4": {
+                                       "name": 
"invalid.illegal.character.anchor.yaml"
+                               }
+                       },
+                       "match": 
"((\\*))([^\\s\\[\\]\/{\/},]+)([^\\s\\]},]\\S*)?"
+               },
+               "flow-collection": {
+                       "patterns": [
+                               {
+                                       "include": "#flow-sequence"
+                               },
+                               {
+                                       "include": "#flow-mapping"
+                               }
+                       ]
+               },
+               "flow-mapping": {
+                       "begin": "\\{",
+                       "beginCaptures": {
+                               "0": {
+                                       "name": 
"punctuation.definition.mapping.begin.yaml"
+                               }
+                       },
+                       "end": "\\}",
+                       "endCaptures": {
+                               "0": {
+                                       "name": 
"punctuation.definition.mapping.end.yaml"
+                               }
+                       },
+                       "name": "meta.flow-mapping.yaml",
+                       "patterns": [
+                               {
+                                       "include": "#prototype"
+                               },
+                               {
+                                       "match": ",",
+                                       "name": 
"punctuation.separator.mapping.yaml"
+                               },
+                               {
+                                       "include": "#flow-pair"
+                               }
+                       ]
+               },
+               "flow-node": {
+                       "patterns": [
+                               {
+                                       "include": "#prototype"
+                               },
+                               {
+                                       "include": "#flow-alias"
+                               },
+                               {
+                                       "include": "#flow-collection"
+                               },
+                               {
+                                       "include": "#flow-scalar"
+                               }
+                       ]
+               },
+               "flow-pair": {
+                       "patterns": [
+                               {
+                                       "begin": "\\?",
+                                       "beginCaptures": {
+                                               "0": {
+                                                       "name": 
"punctuation.definition.key-value.begin.yaml"
+                                               }
+                                       },
+                                       "end": "(?=[},\\]])",
+                                       "name": "meta.flow-pair.explicit.yaml",
+                                       "patterns": [
+                                               {
+                                                       "include": "#prototype"
+                                               },
+                                               {
+                                                       "include": "#flow-pair"
+                                               },
+                                               {
+                                                       "include": "#flow-node"
+                                               },
+                                               {
+                                                       "begin": 
":(?=\\s|$|[\\[\\]{},])",
+                                                       "beginCaptures": {
+                                                               "0": {
+                                                                       "name": 
"punctuation.separator.key-value.mapping.yaml"
+                                                               }
+                                                       },
+                                                       "end": "(?=[},\\]])",
+                                                       "patterns": [
+                                                               {
+                                                                       
"include": "#flow-value"
+                                                               }
+                                                       ]
+                                               }
+                                       ]
+                               },
+                               {
+                                       "begin": "(?x)\n                        
(?=\n                            (?:\n                                
[^\\s[-?:,\\[\\]{}#&*!|>'\"%@`]]\n                              | [?:-] 
[^\\s[\\[\\]{},]]\n                            )\n                            
(\n                                  [^\\s:[\\[\\]{},]]\n                       
         | : [^\\s[\\[\\]{},]]\n                                | \\s+ 
(?![#\\s])\n                            )*\n                            \\s* 
[...]
+                                       "end": "(?x)\n                        
(?=\n                              \\s* $\n                            | \\s+ 
\\#\n                            | \\s* : (\\s|$)\n                            
| \\s* : [\\[\\]{},]\n                            | \\s* [\\[\\]{},]\n          
              )\n                    ",
+                                       "name": "meta.flow-pair.key.yaml",
+                                       "patterns": [
+                                               {
+                                                       "include": 
"#flow-scalar-plain-in-implicit-type"
+                                               },
+                                               {
+                                                       "begin": "(?x)\n        
                          [^\\s[-?:,\\[\\]{}#&*!|>'\"%@`]]\n                    
            | [?:-] [^\\s[\\[\\]{},]]\n                            ",
+                                                       "beginCaptures": {
+                                                               "0": {
+                                                                       "name": 
"entity.name.tag.yaml"
+                                                               }
+                                                       },
+                                                       "contentName": 
"entity.name.tag.yaml",
+                                                       "end": "(?x)\n          
                      (?=\n                                      \\s* $\n       
                             | \\s+ \\#\n                                    | 
\\s* : (\\s|$)\n                                    | \\s* : [\\[\\]{},]\n      
                              | \\s* [\\[\\]{},]\n                              
  )\n                            ",
+                                                       "name": 
"string.unquoted.plain.in.yaml"
+                                               }
+                                       ]
+                               },
+                               {
+                                       "include": "#flow-node"
+                               },
+                               {
+                                       "begin": ":(?=\\s|$|[\\[\\]{},])",
+                                       "captures": {
+                                               "0": {
+                                                       "name": 
"punctuation.separator.key-value.mapping.yaml"
+                                               }
+                                       },
+                                       "end": "(?=[},\\]])",
+                                       "name": "meta.flow-pair.yaml",
+                                       "patterns": [
+                                               {
+                                                       "include": "#flow-value"
+                                               }
+                                       ]
+                               }
+                       ]
+               },
+               "flow-scalar": {
+                       "patterns": [
+                               {
+                                       "include": "#flow-scalar-double-quoted"
+                               },
+                               {
+                                       "include": "#flow-scalar-single-quoted"
+                               },
+                               {
+                                       "include": "#flow-scalar-plain-in"
+                               }
+                       ]
+               },
+               "flow-scalar-double-quoted": {
+                       "begin": "\"",
+                       "beginCaptures": {
+                               "0": {
+                                       "name": 
"punctuation.definition.string.begin.yaml"
+                               }
+                       },
+                       "end": "\"",
+                       "endCaptures": {
+                               "0": {
+                                       "name": 
"punctuation.definition.string.end.yaml"
+                               }
+                       },
+                       "name": "string.quoted.double.yaml",
+                       "patterns": [
+                               {
+                                       "match": "\\\\([0abtnvfre 
\"/\\\\N_Lp]|x\\d\\d|u\\d{4}|U\\d{8})",
+                                       "name": "constant.character.escape.yaml"
+                               },
+                               {
+                                       "match": "\\\\\\n",
+                                       "name": 
"constant.character.escape.double-quoted.newline.yaml"
+                               }
+                       ]
+               },
+               "flow-scalar-plain-in": {
+                       "patterns": [
+                               {
+                                       "include": 
"#flow-scalar-plain-in-implicit-type"
+                               },
+                               {
+                                       "begin": "(?x)\n                        
  [^\\s[-?:,\\[\\]{}#&*!|>'\"%@`]]\n                        | [?:-] 
[^\\s[\\[\\]{},]]\n                    ",
+                                       "end": "(?x)\n                        
(?=\n                              \\s* $\n                            | \\s+ 
\\#\n                            | \\s* : (\\s|$)\n                            
| \\s* : [\\[\\]{},]\n                            | \\s* [\\[\\]{},]\n          
              )\n                    ",
+                                       "name": "string.unquoted.plain.in.yaml"
+                               }
+                       ]
+               },
+               "flow-scalar-plain-in-implicit-type": {
+                       "patterns": [
+                               {
+                                       "captures": {
+                                               "1": {
+                                                       "name": 
"constant.language.null.yaml"
+                                               },
+                                               "2": {
+                                                       "name": 
"constant.language.boolean.yaml"
+                                               },
+                                               "3": {
+                                                       "name": 
"constant.numeric.integer.yaml"
+                                               },
+                                               "4": {
+                                                       "name": 
"constant.numeric.float.yaml"
+                                               },
+                                               "5": {
+                                                       "name": 
"keyword.operator.yaml"
+                                               }
+                                       },
+                                       "match": "(?x) (?x:(null|Null|NULL|~)  
|  
(y|Y|yes|Yes|YES|n|N|no|No|NO|true|True|TRUE|false|False|FALSE|on|On|ON|off|Off|OFF)
  |  ((?:[-+]? [0-9]+) | (?:0o [0-7]+) | (?:0x [0-9a-fA-F]+) | (?:[-+]? [1-9] 
[0-9_]* (?: :[0-5]?[0-9])+))  |  ((?:[-+]? (?: \\. [0-9]+ | [0-9]+ (?: \\. 
[0-9]* )? ) (?: [eE] [-+]? [0-9]+ )?) | (?:[-+]? [0-9] [0-9_]* (?: 
:[0-5]?[0-9])+ \\. [0-9_]*) | (?:[-+]? (?: \\.inf | \\.Inf | \\.INF )) | 
(?:\\.nan | \\.NaN | \\.NAN) )  |  (<<) )     (?: (?= \\s* $ | \\s+  [...]
+                               }
+                       ]
+               },
+               "flow-scalar-plain-out": {
+                       "patterns": [
+                               {
+                                       "include": 
"#flow-scalar-plain-out-implicit-type"
+                               },
+                               {
+                                       "begin": "(?x)\n                        
  [^\\s[-?:,\\[\\]{}#&*!|>'\"%@`]]\n                        | [?:-] \\S\n       
             ",
+                                       "end": "(?x)\n                        
(?=\n                              \\s* $\n                            | \\s+ 
\\#\n                            | \\s* : (\\s|$)\n                        )\n  
                  ",
+                                       "name": "string.unquoted.plain.out.yaml"
+                               }
+                       ]
+               },
+               "flow-scalar-plain-out-implicit-type": {
+                       "patterns": [
+                               {
+                                       "captures": {
+                                               "1": {
+                                                       "name": 
"constant.language.null.yaml"
+                                               },
+                                               "2": {
+                                                       "name": 
"constant.language.boolean.yaml"
+                                               },
+                                               "3": {
+                                                       "name": 
"constant.numeric.integer.yaml"
+                                               },
+                                               "4": {
+                                                       "name": 
"constant.numeric.float.yaml"
+                                               },
+                                               "5": {
+                                                       "name": 
"keyword.operator.yaml"
+                                               }
+                                       },
+                                       "match": "(?x) (?x:(null|Null|NULL|~)  
|  
(y|Y|yes|Yes|YES|n|N|no|No|NO|true|True|TRUE|false|False|FALSE|on|On|ON|off|Off|OFF)
  |  ((?:[-+]? [0-9]+) | (?:0o [0-7]+) | (?:0x [0-9a-fA-F]+) | (?:[-+]? [1-9] 
[0-9_]* (?: :[0-5]?[0-9])+))  |  ((?:[-+]? (?: \\. [0-9]+ | [0-9]+ (?: \\. 
[0-9]* )? ) (?: [eE] [-+]? [0-9]+ )?) | (?:[-+]? [0-9] [0-9_]* (?: 
:[0-5]?[0-9])+ \\. [0-9_]*) | (?:[-+]? (?: \\.inf | \\.Inf | \\.INF )) | 
(?:\\.nan | \\.NaN | \\.NAN) )  |  (<<) )     (?x: (?= \\s* $ | \\s+ [...]
+                               }
+                       ]
+               },
+               "flow-scalar-single-quoted": {
+                       "begin": "'",
+                       "beginCaptures": {
+                               "0": {
+                                       "name": 
"punctuation.definition.string.begin.yaml"
+                               }
+                       },
+                       "end": "'(?!')",
+                       "endCaptures": {
+                               "0": {
+                                       "name": 
"punctuation.definition.string.end.yaml"
+                               }
+                       },
+                       "name": "string.quoted.single.yaml",
+                       "patterns": [
+                               {
+                                       "match": "''",
+                                       "name": 
"constant.character.escape.single-quoted.yaml"
+                               }
+                       ]
+               },
+               "flow-sequence": {
+                       "begin": "\\[",
+                       "beginCaptures": {
+                               "0": {
+                                       "name": 
"punctuation.definition.sequence.begin.yaml"
+                               }
+                       },
+                       "end": "\\]",
+                       "endCaptures": {
+                               "0": {
+                                       "name": 
"punctuation.definition.sequence.end.yaml"
+                               }
+                       },
+                       "name": "meta.flow-sequence.yaml",
+                       "patterns": [
+                               {
+                                       "include": "#prototype"
+                               },
+                               {
+                                       "match": ",",
+                                       "name": 
"punctuation.separator.sequence.yaml"
+                               },
+                               {
+                                       "include": "#flow-pair"
+                               },
+                               {
+                                       "include": "#flow-node"
+                               }
+                       ]
+               },
+               "flow-value": {
+                       "patterns": [
+                               {
+                                       "begin": "\\G(?![},\\]])",
+                                       "end": "(?=[},\\]])",
+                                       "name": "meta.flow-pair.value.yaml",
+                                       "patterns": [
+                                               {
+                                                       "include": "#flow-node"
+                                               }
+                                       ]
+                               }
+                       ]
+               },
+               "node": {
+                       "patterns": [
+                               {
+                                       "include": "#block-node"
+                               }
+                       ]
+               },
+               "property": {
+                       "begin": "(?=!|&)",
+                       "end": "(?!\\G)",
+                       "name": "meta.property.yaml",
+                       "patterns": [
+                               {
+                                       "captures": {
+                                               "1": {
+                                                       "name": 
"keyword.control.property.anchor.yaml"
+                                               },
+                                               "2": {
+                                                       "name": 
"punctuation.definition.anchor.yaml"
+                                               },
+                                               "3": {
+                                                       "name": 
"entity.name.type.anchor.yaml"
+                                               },
+                                               "4": {
+                                                       "name": 
"invalid.illegal.character.anchor.yaml"
+                                               }
+                                       },
+                                       "match": 
"\\G((&))([^\\s\\[\\]/{/},]+)(\\S+)?"
+                               },
+                               {
+                                       "match": "(?x)\n                        
\\G\n                        (?:\n                            ! < (?: 
%[0-9A-Fa-f]{2} | [0-9A-Za-z\\-#;/?:@&=+$,_.!~*'()\\[\\]] )+ >\n                
          | (?:!(?:[0-9A-Za-z\\-]*!)?) (?: %[0-9A-Fa-f]{2} | 
[0-9A-Za-z\\-#;/?:@&=+$_.~*'()] )+\n                          | !\n             
           )\n                        (?=\\ |\\t|$)\n                    ",
+                                       "name": "storage.type.tag-handle.yaml"
+                               },
+                               {
+                                       "match": "\\S+",
+                                       "name": 
"invalid.illegal.tag-handle.yaml"
+                               }
+                       ]
+               },
+               "prototype": {
+                       "patterns": [
+                               {
+                                       "include": "#comment"
+                               },
+                               {
+                                       "include": "#property"
+                               }
+                       ]
+               }
+       }
+}
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 63cfc77a72..cca9c3b590 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
@@ -44,4 +44,23 @@ class ContentEditorTm4eSupportTest {
       assertTrue(in.read() >= 0, "python.json should not be empty");
     }
   }
+
+  @Test
+  void scopeForLanguage_yaml_returnsSourceYaml() {
+    assertEquals("source.yaml", 
ContentEditorTm4eSupport.scopeForLanguage("yaml"));
+  }
+
+  @Test
+  void scopeForLanguage_yml_returnsSourceYaml() {
+    assertEquals("source.yaml", 
ContentEditorTm4eSupport.scopeForLanguage("yml"));
+  }
+
+  @Test
+  void yamlGrammarResourceIsOnClasspath() throws Exception {
+    try (InputStream in =
+        
ContentEditorTm4eSupport.class.getResourceAsStream("grammars/yaml.json")) {
+      assertNotNull(in, "grammars/yaml.json should be on the classpath");
+      assertTrue(in.read() >= 0, "yaml.json should not be empty");
+    }
+  }
 }
diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/yaml/YamlExplorerFileType.java
 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/yaml/YamlExplorerFileType.java
new file mode 100644
index 0000000000..76dc936794
--- /dev/null
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/yaml/YamlExplorerFileType.java
@@ -0,0 +1,67 @@
+/*
+ * 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.ui.hopgui.perspective.explorer.file.types.yaml;
+
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.file.HopFileTypePlugin;
+import org.apache.hop.ui.hopgui.file.IHopFileType;
+import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
+import org.apache.hop.ui.hopgui.file.empty.EmptyHopFileTypeHandler;
+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.capabilities.FileTypeCapabilities;
+import 
org.apache.hop.ui.hopgui.perspective.explorer.file.types.text.BaseTextExplorerFileType;
+
+@HopFileTypePlugin(
+    id = "YamlExplorerFileType",
+    name = "YAML File Type",
+    description = "YAML file handling in the explorer perspective",
+    image = "ui/images/file.svg")
+public class YamlExplorerFileType extends 
BaseTextExplorerFileType<YamlExplorerFileTypeHandler> {
+
+  public YamlExplorerFileType() {
+    super(
+        "YAML File",
+        ".yaml",
+        new String[] {"*.yml", "*.yaml"},
+        new String[] {"YAML files", "YAML files"},
+        FileTypeCapabilities.getCapabilities(
+            IHopFileType.CAPABILITY_SAVE,
+            IHopFileType.CAPABILITY_SAVE_AS,
+            IHopFileType.CAPABILITY_CLOSE,
+            IHopFileType.CAPABILITY_FILE_HISTORY,
+            IHopFileType.CAPABILITY_COPY,
+            IHopFileType.CAPABILITY_CUT,
+            IHopFileType.CAPABILITY_PASTE,
+            IHopFileType.CAPABILITY_SELECT));
+  }
+
+  @Override
+  public YamlExplorerFileTypeHandler createFileTypeHandler(
+      HopGui hopGui, ExplorerPerspective perspective, ExplorerFile file) {
+    return new YamlExplorerFileTypeHandler(hopGui, perspective, file);
+  }
+
+  @Override
+  public IHopFileTypeHandler newFile(HopGui hopGui, IVariables 
parentVariableSpace)
+      throws HopException {
+    return new EmptyHopFileTypeHandler();
+  }
+}
diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/yaml/YamlExplorerFileTypeHandler.java
 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/yaml/YamlExplorerFileTypeHandler.java
new file mode 100644
index 0000000000..107a1b1975
--- /dev/null
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/yaml/YamlExplorerFileTypeHandler.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.ui.hopgui.perspective.explorer.file.types.yaml;
+
+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 YAML files in the file explorer perspective. */
+public class YamlExplorerFileTypeHandler extends 
BaseTextExplorerFileTypeHandler {
+
+  public YamlExplorerFileTypeHandler(
+      HopGui hopGui, ExplorerPerspective perspective, ExplorerFile 
explorerFile) {
+    super(hopGui, perspective, explorerFile);
+  }
+
+  @Override
+  protected String getLanguageId() {
+    return "yaml";
+  }
+}
diff --git 
a/ui/src/test/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/yaml/YamlExplorerFileTypeHandlerTest.java
 
b/ui/src/test/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/yaml/YamlExplorerFileTypeHandlerTest.java
new file mode 100644
index 0000000000..98a3dabcde
--- /dev/null
+++ 
b/ui/src/test/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/yaml/YamlExplorerFileTypeHandlerTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.ui.hopgui.perspective.explorer.file.types.yaml;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+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.IExplorerFileTypeHandler;
+import 
org.apache.hop.ui.hopgui.perspective.explorer.file.types.text.BaseTextExplorerFileTypeHandler;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/** Unit test for {@link YamlExplorerFileTypeHandler} */
+class YamlExplorerFileTypeHandlerTest {
+
+  private YamlExplorerFileTypeHandler handler;
+
+  @BeforeEach
+  void setUp() {
+    handler = new YamlExplorerFileTypeHandler(null, null, null);
+  }
+
+  @Test
+  void testHandlerExtendsBaseTextExplorerFileTypeHandler() {
+    assertInstanceOf(BaseTextExplorerFileTypeHandler.class, handler);
+  }
+
+  @Test
+  void testHandlerImplementsIExplorerFileTypeHandler() {
+    assertInstanceOf(IExplorerFileTypeHandler.class, handler);
+  }
+
+  @Test
+  void testHandlerHasCorrectConstructor() throws Exception {
+    Constructor<YamlExplorerFileTypeHandler> constructor =
+        YamlExplorerFileTypeHandler.class.getConstructor(
+            HopGui.class, ExplorerPerspective.class, ExplorerFile.class);
+    assertNotNull(constructor);
+  }
+
+  @Test
+  void testHandlerReturnsYamlLanguageId() throws Exception {
+    Method method = 
BaseTextExplorerFileTypeHandler.class.getDeclaredMethod("getLanguageId");
+    method.setAccessible(true);
+    assertEquals("yaml", method.invoke(handler));
+  }
+}
diff --git 
a/ui/src/test/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/yaml/YamlExplorerFileTypeTest.java
 
b/ui/src/test/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/yaml/YamlExplorerFileTypeTest.java
new file mode 100644
index 0000000000..6523a3e922
--- /dev/null
+++ 
b/ui/src/test/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/yaml/YamlExplorerFileTypeTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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.ui.hopgui.perspective.explorer.file.types.yaml;
+
+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 org.apache.hop.ui.hopgui.file.IHopFileType;
+import org.apache.hop.ui.hopgui.file.empty.EmptyHopFileTypeHandler;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/** Unit test for {@link YamlExplorerFileType} */
+class YamlExplorerFileTypeTest {
+
+  private YamlExplorerFileType fileType;
+
+  @BeforeEach
+  void setUp() {
+    fileType = new YamlExplorerFileType();
+  }
+
+  @Test
+  void testConstructor() {
+    assertNotNull(fileType);
+  }
+
+  @Test
+  void testGetName() {
+    assertEquals("YAML File", fileType.getName());
+  }
+
+  @Test
+  void testGetDefaultFileExtension() {
+    assertEquals(".yaml", fileType.getDefaultFileExtension());
+  }
+
+  @Test
+  void testGetFilterExtensions() {
+    String[] extensions = fileType.getFilterExtensions();
+    assertNotNull(extensions);
+    assertEquals(2, extensions.length);
+    assertEquals("*.yml", extensions[0]);
+    assertEquals("*.yaml", extensions[1]);
+  }
+
+  @Test
+  void testGetFilterNames() {
+    String[] names = fileType.getFilterNames();
+    assertNotNull(names);
+    assertEquals(2, names.length);
+    assertEquals("YAML files", names[0]);
+    assertEquals("YAML files", names[1]);
+  }
+
+  @Test
+  void testFilterExtensionsMatchFilterNames() {
+    String[] extensions = fileType.getFilterExtensions();
+    String[] names = fileType.getFilterNames();
+    assertEquals(extensions.length, names.length);
+    for (int i = 0; i < extensions.length; i++) {
+      assertNotNull(extensions[i]);
+      assertNotNull(names[i]);
+      assertFalse(extensions[i].isEmpty());
+      assertFalse(names[i].isEmpty());
+    }
+  }
+
+  @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_CUT));
+    assertTrue(fileType.hasCapability(IHopFileType.CAPABILITY_PASTE));
+    assertTrue(fileType.hasCapability(IHopFileType.CAPABILITY_SELECT));
+    assertTrue(fileType.hasCapability(IHopFileType.CAPABILITY_SEARCH));
+    assertFalse(fileType.hasCapability(IHopFileType.CAPABILITY_NEW));
+    assertFalse(fileType.hasCapability(IHopFileType.CAPABILITY_START));
+  }
+
+  @Test
+  void testGetCapabilities() {
+    assertNotNull(fileType.getCapabilities());
+    assertFalse(fileType.getCapabilities().isEmpty());
+  }
+
+  @Test
+  void testGetFileTypeImage() {
+    assertEquals("ui/images/file.svg", fileType.getFileTypeImage());
+  }
+
+  @Test
+  void testCreateFileTypeHandlerReturnsYamlHandler() {
+    assertInstanceOf(
+        YamlExplorerFileTypeHandler.class, 
fileType.createFileTypeHandler(null, null, null));
+  }
+
+  @Test
+  void testNewFileReturnsEmptyHandler() throws Exception {
+    assertInstanceOf(EmptyHopFileTypeHandler.class, fileType.newFile(null, 
null));
+  }
+
+  @Test
+  void testIsHandledByYmlAndYamlExtensions() throws Exception {
+    assertTrue(fileType.isHandledBy("config.yml", false));
+    assertTrue(fileType.isHandledBy("config.yaml", false));
+    assertFalse(fileType.isHandledBy("config.json", false));
+    assertFalse(fileType.isHandledBy("config.txt", false));
+  }
+
+  @Test
+  void testSupportsOpening() {
+    assertTrue(fileType.supportsOpening());
+  }
+}


Reply via email to