Return-Path: From: Jerzy Kasenberg To: CC: Jerzy Kasenberg Subject: [PATCH 1/3] android: Add tab completion to haltest Date: Fri, 18 Oct 2013 15:15:58 +0200 Message-ID: <1382102160-21532-2-git-send-email-jerzy.kasenberg@tieto.com> In-Reply-To: <1382102160-21532-1-git-send-email-jerzy.kasenberg@tieto.com> References: <1382102160-21532-1-git-send-email-jerzy.kasenberg@tieto.com> MIME-Version: 1.0 Content-Type: text/plain Sender: linux-bluetooth-owner@vger.kernel.org List-ID: This patch adds tab completion to interfaces, methods and parameters in haltest. --- Makefile.android | 2 + android/Android.mk | 1 + android/client/if-main.h | 28 +++- android/client/tabcompletion.c | 313 ++++++++++++++++++++++++++++++++++++++++ android/client/terminal.c | 2 +- 5 files changed, 342 insertions(+), 4 deletions(-) create mode 100644 android/client/tabcompletion.c diff --git a/Makefile.android b/Makefile.android index 56caf78..dcaca3c 100644 --- a/Makefile.android +++ b/Makefile.android @@ -42,6 +42,7 @@ android_haltest_SOURCES = android/client/haltest.c \ android/client/terminal.c \ android/client/history.c \ android/client/textconv.c \ + android/client/tabcompletion.c \ android/client/if-bt.c \ android/client/hwmodule.c @@ -67,6 +68,7 @@ EXTRA_DIST += android/client/terminal.c \ android/client/history.c \ android/client/if-bt.c \ android/client/textconv.c \ + android/client/tabcompletion.c \ android/client/textconv.h \ android/client/if-main.h \ android/client/pollhandler.h \ diff --git a/android/Android.mk b/android/Android.mk index 5858c3d..30b2169 100644 --- a/android/Android.mk +++ b/android/Android.mk @@ -78,6 +78,7 @@ LOCAL_SRC_FILES := \ client/terminal.c \ client/history.c \ client/textconv.c \ + client/tabcompletion.c \ client/if-bt.c \ LOCAL_SHARED_LIBRARIES := libhardware diff --git a/android/client/if-main.h b/android/client/if-main.h index 9cac7ef..3fb3007 100644 --- a/android/client/if-main.h +++ b/android/client/if-main.h @@ -56,9 +56,12 @@ extern const struct interface bluetooth_if; /* Interfaces that will show up in tool (first part of command line) */ extern const struct interface *interfaces[]; -#define METHOD(name, func) {name, func} -#define STD_METHOD(m) {#m, m##_p} -#define END_METHOD {"", NULL} +#define METHOD(name, func, comp, help) {name, func, comp, help} +#define STD_METHOD(m) {#m, m##_p, NULL, NULL} +#define STD_METHODC(m) {#m, m##_p, m##_c, NULL} +#define STD_METHODH(m, h) {#m, m##_p, NULL, h} +#define STD_METHODCH(m, h) {#m, m##_p, m##_c, h} +#define END_METHOD {"", NULL, NULL, NULL} /* * Function to parse argument for function, argv[0] and argv[1] are already @@ -69,12 +72,31 @@ extern const struct interface *interfaces[]; typedef void (*parse_and_call)(int argc, const char **argv); /* + * This is prototype of function that will return string for given number. + * Purpose is to enumerate string for auto completion. + * Function of this type will always be called in loop. + * First time function is called i = 0, then if function returns non-NULL + * it will be called again till for some value of i it will return NULL + */ +typedef const char *(*enum_func)(void *user, int i); + +/* + * This is prototype of function that when given argc, argv will + * fill penum_func with pointer to function that will enumerate + * parameters for argc argument, puser will be passed to penum_func. + */ +typedef void (*tab_complete)(int argc, const char **argv, + enum_func *penum_func, void **puser); + +/* * For each method there is name and two functions to parse command line * and call proper hal function on. */ struct method { const char *name; parse_and_call func; + tab_complete complete; + const char *help; }; int haltest_error(const char *format, ...); diff --git a/android/client/tabcompletion.c b/android/client/tabcompletion.c new file mode 100644 index 0000000..e9c9921 --- /dev/null +++ b/android/client/tabcompletion.c @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * Licensed 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. + * + */ + +#include +#include +#include "if-main.h" +#include "terminal.h" + +/* how many times tab was hit */ +static int tab_hit_count; + +typedef struct split_arg { + struct split_arg *next; /* next argument in buffer */ + const char *origin; /* pointer to original argument */ + char ntcopy[1]; /* null terminated copy of argument */ +} split_arg_t; + +/* function returns interface of given name or NULL if not found */ +static const struct interface *get_interface(const char *name) +{ + int i; + + for (i = 0; interfaces[i] != NULL; ++i) { + if (strcmp(interfaces[i]->name, name) == 0) + break; + } + + return interfaces[i]; +} + +/* function returns method of given name or NULL if not found */ +static const struct method *get_method(const char *iname, const char *mname) +{ + int i; + const struct interface *iface = get_interface(iname); + + if (iface == NULL) + return NULL; + + for (i = 0; iface->methods[i].name[0]; ++i) { + if (0 == strcmp(iface->methods[i].name, mname)) + return &iface->methods[i]; + } + return NULL; +} + +/* prints matching elements */ +static void print_matches(enum_func f, void *user, const char *prefix, int len) +{ + int i; + const char *enum_name; + + putchar('\n'); + for (i = 0; NULL != (enum_name = f(user, i)); ++i) { + if (strncmp(enum_name, prefix, len) == 0) + printf("%s\t", enum_name); + } + putchar('\n'); + terminal_draw_command_line(); +} + +/* + * This function splits command line into linked list of arguments. + * line_buffer - pointer to input comman line + * size - size of command line to parse + * buf - output buffer to keep splited arguments list + * buf_size_in_bytes - size of buf + */ +static int split_command(const char *line_buffer, int size, + split_arg_t *buf, int buf_size_in_bytes) +{ + split_arg_t *prev = NULL; + split_arg_t *arg = buf; + int argc = 0; + const char *p = line_buffer; + const char *e = p + (size > 0 ? size : (int) strlen(p)); + int len; + + do { + while (p < e && isspace(*p)) + p++; + arg->origin = p; + arg->next = NULL; + while (p < e && !isspace(*p)) + p++; + len = p - arg->origin; + if (&arg->ntcopy[0] + len + 1 > + (const char *) buf + buf_size_in_bytes) + break; + strncpy(arg->ntcopy, arg->origin, len); + arg->ntcopy[len] = 0; + if (prev != NULL) + prev->next = arg; + prev = arg; + arg += (2 * sizeof(*arg) + len) / sizeof(*arg); + argc++; + } while (p < e); + + return argc; +} + +/* Function to enumerate interface names */ +static const char *interface_name(void *v, int i) +{ + return interfaces[i] ? interfaces[i]->name : NULL; +} + +/* Function to enumerate method names */ +static const char *methods_name(void *v, int i) +{ + const struct interface *iface = v; + + return iface->methods[i].name[0] ? iface->methods[i].name : NULL; +} + +struct command_completion_args; +typedef void (*short_help)(struct command_completion_args *args); + +struct command_completion_args { + const split_arg_t *arg; /* list of arguments */ + const char *typed; /* last typed element */ + enum_func func; /* enumerating function */ + void *user; /* argument to enumerating function */ + short_help help; /* help function */ + void *user_help; /* additional data (used by short_help) */ +}; + +/* + * complete command line + */ +static void command_completion(struct command_completion_args *args) +{ + const char *name = args->typed; + const int len = strlen(name); + int i; + int j; + char prefix[128] = {0}; + int prefix_len = 0; + int count = 0; + const char *enum_name; + + for (i = 0; NULL != (enum_name = args->func(args->user, i)); ++i) { + /* prefix does not match */ + if (strncmp(enum_name, name, len) != 0) + continue; + /* prefix matches first time */ + if (count++ == 0) { + strcpy(prefix, enum_name); + prefix_len = strlen(prefix); + continue; + } + /* + * Prefix matches next time + * reduce prefix to common part + */ + for (j = 0; prefix[j] != 0 + && prefix[j] == enum_name[j];) + ++j; + prefix_len = j; + prefix[j] = 0; + } + + if (count == 0) { + /* no matches */ + if (args->help != NULL) + args->help(args); + tab_hit_count = 0; + return; + } + /* len == prefix_len => nothing new was added */ + if (len == prefix_len) { + if (count != 1) { + if (tab_hit_count == 1) + putchar('\a'); + else if (tab_hit_count == 2 || + args->help == NULL) { + print_matches(args->func, + args->user, name, len); + } else { + args->help(args); + tab_hit_count = 1; + } + } else if (count == 1) { + /* nothing to add, exact match add space */ + terminal_insert_into_command_line(" "); + } + } else { + /* new chars can be added from some interface name(s) */ + if (count == 1) { + /* exact match, add space */ + prefix[prefix_len++] = ' '; + prefix[prefix_len] = '\0'; + } + terminal_insert_into_command_line(prefix + len); + tab_hit_count = 0; + } +} + +/* interface completion */ +static void interface_completion(split_arg_t *arg) +{ + struct command_completion_args args = { + .arg = arg, + .typed = arg->ntcopy, + .func = interface_name + }; + + command_completion(&args); +} + +/* method completion */ +static void method_completion(const struct interface *iface, split_arg_t *arg) +{ + struct command_completion_args args = { + .arg = arg, + .typed = arg->next->ntcopy, + .func = methods_name, + .user = (void *) iface + }; + + if (iface == NULL) + return; + + command_completion(&args); +} + +/* prints short help on method for interface */ +static void method_help(struct command_completion_args *args) +{ + if (args->user_help == NULL) + return; + + haltest_info("%s %s %s\n", args->arg->ntcopy, + args->arg->next->ntcopy, args->user_help); +} + +/* So we have empty enumeration */ +static const char *return_null(void *user, int i) +{ + return NULL; +} + +/* parameter completion function */ +static void param_completion(int argc, const split_arg_t *arg) +{ + const struct method *method; + int i; + const char *argv[argc]; + const split_arg_t *tmp = arg; + struct command_completion_args args = { + .arg = arg, + .func = return_null + }; + + /* prepare standard argv from arg */ + for (i = 0; i < argc; ++i) { + argv[i] = tmp->ntcopy; + tmp = tmp->next; + } + + /* Find method for pair */ + method = get_method(argv[0], argv[1]); + + if (method != NULL && method->complete != NULL) { + /* ask method for completion function */ + method->complete(argc, argv, &args.func, &args.user); + } + + /* If method provided enumeration function call try to complete */ + if (args.func != NULL) { + args.typed = argv[argc - 1]; + args.help = method_help; + args.user_help = (void *) method->help; + + command_completion(&args); + } +} + +/* + * This methd gets called when user tapped tab key. + * line - points to comman line + * len - size of line that should be used for comletions. This should be + * cursor position during tab hit. + */ +void process_tab(const char *line, int len) +{ + int argc; + static split_arg_t buf[(LINE_BUF_MAX * 2) / sizeof(split_arg_t)]; + + argc = split_command(line, len, buf, sizeof(buf)); + tab_hit_count++; + + if (argc == 1) + interface_completion(buf); + else if (argc == 2) + method_completion(get_interface(buf[0].ntcopy), buf); + else if (argc > 2) + param_completion(argc, buf); +} diff --git a/android/client/terminal.c b/android/client/terminal.c index b484ef6..0421633 100644 --- a/android/client/terminal.c +++ b/android/client/terminal.c @@ -453,7 +453,7 @@ void terminal_process_char(int c, void (*process_line)(char *line)) break; case '\t': /* tab processing */ - /* TODO Add completion here */ + process_tab(line_buf, line_buf_ix); break; case KEY_BACKSPACE: if (line_buf_ix <= 0) -- 1.7.9.5