yanglint FEATURE hints for option arguments

... and also completion if prefix is also specified.
diff --git a/tools/lint/cmd.c b/tools/lint/cmd.c
index 10e7446..72bc680 100644
--- a/tools/lint/cmd.c
+++ b/tools/lint/cmd.c
@@ -54,7 +54,7 @@
         goto cleanup;
     }
 
-    while ((opt = getopt_long(argc, argv, "h", options, &opt_index)) != -1) {
+    while ((opt = getopt_long(argc, argv, commands[CMD_DEBUG].optstring, options, &opt_index)) != -1) {
         switch (opt) {
         case 'h':
             cmd_debug_help();
@@ -110,7 +110,7 @@
         goto cleanup;
     }
 
-    while ((opt = getopt_long(argc, argv, "h", options, &opt_index)) != -1) {
+    while ((opt = getopt_long(argc, argv, commands[CMD_VERB].optstring, options, &opt_index)) != -1) {
         switch (opt) {
         case 'h':
             cmd_verb_help();
@@ -188,7 +188,7 @@
         goto cleanup;
     }
 
-    while ((opt = getopt_long(argc, argv, "h", options, &opt_index)) != -1) {
+    while ((opt = getopt_long(argc, argv, commands[CMD_HELP].optstring, options, &opt_index)) != -1) {
         switch (opt) {
         case 'h':
             cmd_help_help();
@@ -237,23 +237,24 @@
     free_cmdline(argv);
 }
 
+/* Also keep enum COMMAND_INDEX updated. */
 COMMAND commands[] = {
-    {"help", cmd_help, cmd_help_help, "Display commands description"},
-    {"add", cmd_add, cmd_add_help, "Add a new module from a specific file"},
-    {"load", cmd_load, cmd_load_help, "Load a new schema from the searchdirs"},
-    {"print", cmd_print, cmd_print_help, "Print a module"},
-    {"data", cmd_data, cmd_data_help, "Load, validate and optionally print instance data"},
-    {"list", cmd_list, cmd_list_help, "List all the loaded modules"},
-    {"feature", cmd_feature, cmd_feature_help, "Print all features of module(s) with their state"},
-    {"searchpath", cmd_searchpath, cmd_searchpath_help, "Print/set the search path(s) for schemas"},
-    {"clear", cmd_clear, cmd_clear_help, "Clear the context - remove all the loaded modules"},
-    {"verb", cmd_verb, cmd_verb_help, "Change verbosity"},
+    {"help", cmd_help, cmd_help_help, "Display commands description", "h"},
+    {"add", cmd_add, cmd_add_help, "Add a new module from a specific file", "DF:hi"},
+    {"load", cmd_load, cmd_load_help, "Load a new schema from the searchdirs", "F:hi"},
+    {"print", cmd_print, cmd_print_help, "Print a module", "f:hL:o:P:q"},
+    {"data", cmd_data, cmd_data_help, "Load, validate and optionally print instance data", "d:ef:F:hmo:O:r:nt:x:"},
+    {"list", cmd_list, cmd_list_help, "List all the loaded modules", "f:h"},
+    {"feature", cmd_feature, cmd_feature_help, "Print all features of module(s) with their state", "haf"},
+    {"searchpath", cmd_searchpath, cmd_searchpath_help, "Print/set the search path(s) for schemas", "ch"},
+    {"clear", cmd_clear, cmd_clear_help, "Clear the context - remove all the loaded modules", "iyh"},
+    {"verb", cmd_verb, cmd_verb_help, "Change verbosity", "h"},
 #ifndef NDEBUG
-    {"debug", cmd_debug, cmd_debug_help, "Display specific debug message groups"},
+    {"debug", cmd_debug, cmd_debug_help, "Display specific debug message groups", "h"},
 #endif
-    {"quit", cmd_quit, NULL, "Quit the program"},
+    {"quit", cmd_quit, NULL, "Quit the program", "h"},
     /* synonyms for previous commands */
-    {"?", cmd_help, NULL, "Display commands description"},
-    {"exit", cmd_quit, NULL, "Quit the program"},
-    {NULL, NULL, NULL, NULL}
+    {"?", cmd_help, NULL, "Display commands description", "h"},
+    {"exit", cmd_quit, NULL, "Quit the program", "h"},
+    {NULL, NULL, NULL, NULL, NULL}
 };
diff --git a/tools/lint/cmd.h b/tools/lint/cmd.h
index 9f6f88d..071348f 100644
--- a/tools/lint/cmd.h
+++ b/tools/lint/cmd.h
@@ -27,6 +27,7 @@
     void (*func)(struct ly_ctx **ctx, const char *); /* Function to call to do the command. */
     void (*help_func)(void);                         /* Display command help. */
     char *helpstring;                                /* Documentation for this function. */
+    char *optstring;                                 /* Option characters used in function getopt_long. */
 } COMMAND;
 
 /**
@@ -34,6 +35,25 @@
  */
 extern COMMAND commands[];
 
+/**
+ * @brief Index for global variable ::commands.
+ */
+enum COMMAND_INDEX {
+    CMD_HELP = 0,
+    CMD_ADD,
+    CMD_LOAD,
+    CMD_PRINT,
+    CMD_DATA,
+    CMD_LIST,
+    CMD_FEATURE,
+    CMD_SEARCHPATH,
+    CMD_CLEAR,
+    CMD_VERB,
+#ifndef NDEBUG
+    CMD_DEBUG,
+#endif
+};
+
 /* cmd_add.c */
 void cmd_add(struct ly_ctx **ctx, const char *cmdline);
 void cmd_add_help(void);
diff --git a/tools/lint/cmd_add.c b/tools/lint/cmd_add.c
index 38663c2..aa78156 100644
--- a/tools/lint/cmd_add.c
+++ b/tools/lint/cmd_add.c
@@ -67,7 +67,7 @@
         goto cleanup;
     }
 
-    while ((opt = getopt_long(argc, argv, "DF:hi", options, &opt_index)) != -1) {
+    while ((opt = getopt_long(argc, argv, commands[CMD_ADD].optstring, options, &opt_index)) != -1) {
         switch (opt) {
         case 'D': /* --disable--search */
             if (options_ctx & LY_CTX_DISABLE_SEARCHDIRS) {
diff --git a/tools/lint/cmd_clear.c b/tools/lint/cmd_clear.c
index 7420704..cbc5e10 100644
--- a/tools/lint/cmd_clear.c
+++ b/tools/lint/cmd_clear.c
@@ -64,7 +64,7 @@
         goto cleanup;
     }
 
-    while ((opt = getopt_long(argc, argv, "iyh", options, &opt_index)) != -1) {
+    while ((opt = getopt_long(argc, argv, commands[CMD_CLEAR].optstring, options, &opt_index)) != -1) {
         switch (opt) {
         case 'i':
             if (options_ctx & LY_CTX_REF_IMPLEMENTED) {
diff --git a/tools/lint/cmd_data.c b/tools/lint/cmd_data.c
index 945bc1d..f816d9a 100644
--- a/tools/lint/cmd_data.c
+++ b/tools/lint/cmd_data.c
@@ -138,7 +138,7 @@
         goto cleanup;
     }
 
-    while ((opt = getopt_long(argc, argv, "d:ef:F:hmo:O:r:nt:x:", options, &opt_index)) != -1) {
+    while ((opt = getopt_long(argc, argv, commands[CMD_DATA].optstring, options, &opt_index)) != -1) {
         switch (opt) {
         case 'd': /* --default */
             if (!strcasecmp(optarg, "all")) {
diff --git a/tools/lint/cmd_feature.c b/tools/lint/cmd_feature.c
index 6b332ab..383cf93 100644
--- a/tools/lint/cmd_feature.c
+++ b/tools/lint/cmd_feature.c
@@ -59,7 +59,7 @@
         goto cleanup;
     }
 
-    while ((opt = getopt_long(argc, argv, "haf", options, &opt_index)) != -1) {
+    while ((opt = getopt_long(argc, argv, commands[CMD_FEATURE].optstring, options, &opt_index)) != -1) {
         switch (opt) {
         case 'h':
             cmd_feature_help();
diff --git a/tools/lint/cmd_list.c b/tools/lint/cmd_list.c
index ec7a021..77b3ec4 100644
--- a/tools/lint/cmd_list.c
+++ b/tools/lint/cmd_list.c
@@ -55,7 +55,7 @@
         goto cleanup;
     }
 
-    while ((opt = getopt_long(argc, argv, "f:h", options, &opt_index)) != -1) {
+    while ((opt = getopt_long(argc, argv, commands[CMD_LIST].optstring, options, &opt_index)) != -1) {
         switch (opt) {
         case 'f': /* --format */
             if (!strcasecmp(optarg, "xml")) {
diff --git a/tools/lint/cmd_load.c b/tools/lint/cmd_load.c
index f5883e9..af204ca 100644
--- a/tools/lint/cmd_load.c
+++ b/tools/lint/cmd_load.c
@@ -62,7 +62,7 @@
         goto cleanup;
     }
 
-    while ((opt = getopt_long(argc, argv, "F:hi", options, &opt_index)) != -1) {
+    while ((opt = getopt_long(argc, argv, commands[CMD_LOAD].optstring, options, &opt_index)) != -1) {
         switch (opt) {
         case 'F': /* --features */
             if (parse_features(optarg, &fset)) {
diff --git a/tools/lint/cmd_print.c b/tools/lint/cmd_print.c
index 0017044..b187a71 100644
--- a/tools/lint/cmd_print.c
+++ b/tools/lint/cmd_print.c
@@ -164,7 +164,7 @@
         goto cleanup;
     }
 
-    while ((opt = getopt_long(argc, argv, "f:hL:o:P:q", options, &opt_index)) != -1) {
+    while ((opt = getopt_long(argc, argv, commands[CMD_PRINT].optstring, options, &opt_index)) != -1) {
         switch (opt) {
         case 'o': /* --output */
             if (out) {
diff --git a/tools/lint/cmd_searchpath.c b/tools/lint/cmd_searchpath.c
index 3a6e184..b3a83ad 100644
--- a/tools/lint/cmd_searchpath.c
+++ b/tools/lint/cmd_searchpath.c
@@ -53,7 +53,7 @@
         goto cleanup;
     }
 
-    while ((opt = getopt_long(argc, argv, "ch", options, &opt_index)) != -1) {
+    while ((opt = getopt_long(argc, argv, commands[CMD_SEARCHPATH].optstring, options, &opt_index)) != -1) {
         switch (opt) {
         case 'c':
             ly_ctx_unset_searchdir(*ctx, NULL);
diff --git a/tools/lint/completion.c b/tools/lint/completion.c
index 9843816..b174302 100644
--- a/tools/lint/completion.c
+++ b/tools/lint/completion.c
@@ -76,6 +76,34 @@
 }
 
 /**
+ * @brief Provides completion for arguments.
+ *
+ * @param[in] hint User input.
+ * @param[in] args Array of all possible arguments. The last element must be NULL.
+ * @param[out] matches Matches provided to the user as a completion hint.
+ * @param[out] match_count Number of matches.
+ */
+static void
+get_arg_completion(const char *hint, const char **args, char ***matches, unsigned int *match_count)
+{
+    int i;
+
+    *match_count = 0;
+    *matches = NULL;
+
+    for (i = 0; args[i]; i++) {
+        if (!strncmp(hint, args[i], strlen(hint))) {
+            cmd_completion_add_match(args[i], matches, match_count);
+        }
+    }
+    if (*match_count == 0) {
+        for (i = 0; args[i]; i++) {
+            cmd_completion_add_match(args[i], matches, match_count);
+        }
+    }
+}
+
+/**
  * @brief Provides completion for module names.
  *
  * @param[in] hint User input.
@@ -277,6 +305,65 @@
 }
 
 /**
+ * @brief Get all possible argument hints for option.
+ *
+ * @param[in] hint User input.
+ * @param[out] matches Matches provided to the user as a completion hint.
+ * @param[out] match_count Number of matches.
+ */
+static void
+get_print_format_arg(const char *hint, char ***matches, unsigned int *match_count)
+{
+    const char *args[] = {"yang", "yin", "tree", "info", NULL};
+
+    get_arg_completion(hint, args, matches, match_count);
+}
+
+/**
+ * @copydoc get_print_format_arg
+ */
+static void
+get_data_type_arg(const char *hint, char ***matches, unsigned int *match_count)
+{
+    const char *args[] = {"data", "config", "get", "getconfig", "edit", "rpc", "reply", "notif", NULL};
+
+    get_arg_completion(hint, args, matches, match_count);
+}
+
+/**
+ * @copydoc get_print_format_arg
+ */
+static void
+get_data_in_format_arg(const char *hint, char ***matches, unsigned int *match_count)
+{
+    const char *args[] = {"xml", "json", "lyb", NULL};
+
+    get_arg_completion(hint, args, matches, match_count);
+}
+
+/**
+ * @copydoc get_print_format_arg
+ */
+static void
+get_data_default_arg(const char *hint, char ***matches, unsigned int *match_count)
+{
+    const char *args[] = {"all", "all-tagged", "trim", "implicit-tagged", NULL};
+
+    get_arg_completion(hint, args, matches, match_count);
+}
+
+/**
+ * @copydoc get_print_format_arg
+ */
+static void
+get_list_format_arg(const char *hint, char ***matches, unsigned int *match_count)
+{
+    const char *args[] = {"xml", "json", NULL};
+
+    get_arg_completion(hint, args, matches, match_count);
+}
+
+/**
  * @brief Get the string before the hint, which autocompletion is for.
  *
  * @param[in] buf Complete user input.
@@ -315,22 +402,30 @@
 complete_cmd(const char *buf, const char *hint, linenoiseCompletions *lc)
 {
     struct autocomplete {
-        const char *cmd;    /**< command */
-        const char *opt;    /**< optional option */
-        int last_opt;       /**< whether to autocomplete even if an option is last in the hint */
-
+        enum COMMAND_INDEX ci;      /**< command index to global variable 'commands' */
+        const char *opt;            /**< optional option */
         void (*ln_cb)(const char *, const char *, linenoiseCompletions *);  /**< linenoise callback to call */
         void (*yl_cb)(const char *, char ***, unsigned int *);              /**< yanglint callback to call */
     } ac[] = {
-        {"add", NULL, 1, linenoisePathCompletion, NULL},
-        {"searchpath", NULL, 0, linenoisePathCompletion, NULL},
-        {"data", NULL, 0, linenoisePathCompletion, NULL},
-        {"print", NULL, 0, NULL, get_model_completion},
-        {"feature", NULL, 0, NULL, get_model_completion},
-        {"print", "-P", 1, NULL, get_schema_completion},
+        {CMD_ADD,         NULL,    linenoisePathCompletion, NULL},
+        {CMD_PRINT,       "-f",    NULL, get_print_format_arg},
+        {CMD_PRINT,       "-P",    NULL, get_schema_completion},
+        {CMD_PRINT,       "-o",    linenoisePathCompletion, NULL},
+        {CMD_PRINT,       NULL,    NULL, get_model_completion},
+        {CMD_SEARCHPATH,  NULL,    linenoisePathCompletion, NULL},
+        {CMD_DATA,        "-t",    NULL, get_data_type_arg},
+        {CMD_DATA,        "-O",    linenoisePathCompletion, NULL},
+        {CMD_DATA,        "-f",    NULL, get_data_in_format_arg},
+        {CMD_DATA,        "-F",    NULL, get_data_in_format_arg},
+        {CMD_DATA,        "-d",    NULL, get_data_default_arg},
+        {CMD_DATA,        "-o",    linenoisePathCompletion, NULL},
+        {CMD_DATA,        NULL,    linenoisePathCompletion, NULL},
+        {CMD_LIST,        NULL,    NULL, get_list_format_arg},
+        {CMD_FEATURE,     NULL,    NULL, get_model_completion},
     };
-    size_t cmd_len;
-    const char *last;
+    size_t name_len;
+    const char *last, *name, *getoptstr;
+    char opt[3] = {'\0', ':', '\0'};
     char **matches = NULL;
     unsigned int match_count = 0, i;
 
@@ -340,24 +435,27 @@
 
     } else {
         for (i = 0; i < (sizeof ac / sizeof *ac); ++i) {
-            cmd_len = strlen(ac[i].cmd);
-            if (strncmp(buf, ac[i].cmd, cmd_len) || (buf[cmd_len] != ' ')) {
+            /* Find the right command. */
+            name = commands[ac[i].ci].name;
+            name_len = strlen(name);
+            if (strncmp(buf, name, name_len) || (buf[name_len] != ' ')) {
                 /* not this command */
                 continue;
             }
 
+            /* Select based on the right option. */
             last = get_last_str(buf, hint);
-            if (ac[i].opt && strncmp(ac[i].opt, last, strlen(ac[i].opt))) {
-                /* autocompletion for (another) option */
+            opt[0] = (last[0] == '-') && last[1] ? last[1] : '\0';
+            getoptstr = commands[ac[i].ci].optstring;
+            if (!ac[i].opt && opt[0] && strstr(getoptstr, opt)) {
+                /* completion for the argument must be defined */
                 continue;
-            }
-            if (!ac[i].last_opt && (last[0] == '-')) {
-                /* autocompletion for the command, not an option */
+            } else if (ac[i].opt && opt[0] && strncmp(ac[i].opt, last, strlen(ac[i].opt))) {
+                /* completion for (another) option */
                 continue;
-            }
-            if ((last != buf) && (last[0] != '-')) {
-                /* autocompleted */
-                return;
+            } else if (ac[i].opt && !opt[0]) {
+                /* completion is defined for option */
+                continue;
             }
 
             /* callback */
diff --git a/tools/lint/tests/interactive/completion.test b/tools/lint/tests/interactive/completion.test
index 633fabb..86ded1f 100644
--- a/tools/lint/tests/interactive/completion.test
+++ b/tools/lint/tests/interactive/completion.test
@@ -1,5 +1,7 @@
 source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
 
+set mdir "$::env(YANG_MODULES_DIR)"
+
 variable ly_cleanup {
     ly_ignore
     ly_exit
@@ -7,7 +9,7 @@
 
 test completion_hints_ietf_ip {Completion and hints for ietf-ip.yang} {
 -setup $ly_setup -cleanup $ly_cleanup -body {
-    ly_cmd "add $::env(YANG_MODULES_DIR)/ietf-ip.yang"
+    ly_cmd "add $mdir/ietf-ip.yang"
 
     # completion and hint
     ly_completion "print -f info -P " "print -f info -P /ietf-"
@@ -41,4 +43,27 @@
     ly_completion "/e" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv4/enabled "
 }}
 
+# Note that somehow a command is automatically sent again (\t\t replaced by \r) after the hints.
+# But that doesn't affect the test because the tests only focus on the word in the hint.
+
+test hint_data_file {Show file hints for command data} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_hint "data $mdir\t\t" "data $mdir" "modleaf.yang.*"
+}}
+
+test hint_data_format {Show print hints for command data --format} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_hint "data -f \t\t" "data -f " "xml.*"
+}}
+
+test hint_data_file_after_opt {Show file hints after option with argument} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_hint "data -f xml $mdir\t\t" "data -f xml $mdir" "modleaf.yang.*"
+}}
+
+test hint_data_file_after_opt2 {Show file hints after option without argument} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_hint "data -m $mdir\t\t" "data -m $mdir" "modleaf.yang.*"
+}}
+
 cleanupTests
diff --git a/tools/lint/tests/interactive/ly.tcl b/tools/lint/tests/interactive/ly.tcl
index 8386848..3fa2c82 100644
--- a/tools/lint/tests/interactive/ly.tcl
+++ b/tools/lint/tests/interactive/ly.tcl
@@ -165,7 +165,7 @@
     send -- "${input}\t"
     # expecting the hints, previous input from which the hints were generated
     # and some number of terminal control characters
-    expect -re "^\r\n${output}\r> ${prev_input}.*\r.*$"
+    expect -re "${output}\r> ${prev_input}.*\r.*$"
 }
 
 # Send 'exit' and wait for eof.