You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@celix.apache.org by er...@apache.org on 2017/02/24 21:30:08 UTC
celix git commit: Adding history and autocompletion to shell_tui
Repository: celix
Updated Branches:
refs/heads/develop 3b802c90f -> 6785a38c4
Adding history and autocompletion to shell_tui
Project: http://git-wip-us.apache.org/repos/asf/celix/repo
Commit: http://git-wip-us.apache.org/repos/asf/celix/commit/6785a38c
Tree: http://git-wip-us.apache.org/repos/asf/celix/tree/6785a38c
Diff: http://git-wip-us.apache.org/repos/asf/celix/diff/6785a38c
Branch: refs/heads/develop
Commit: 6785a38c4a1dbdb20a93b94b33a3fec120d91933
Parents: 3b802c9
Author: Erjan Altena <er...@gmail.com>
Authored: Wed Feb 22 22:28:29 2017 +0100
Committer: Erjan Altena <er...@gmail.com>
Committed: Fri Feb 24 22:27:12 2017 +0100
----------------------------------------------------------------------
shell_tui/CMakeLists.txt | 1 +
shell_tui/private/include/history.h | 39 +++++
shell_tui/private/src/history.c | 87 ++++++++++
shell_tui/private/src/shell_tui.c | 263 ++++++++++++++++++++++++++-----
4 files changed, 354 insertions(+), 36 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/celix/blob/6785a38c/shell_tui/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/shell_tui/CMakeLists.txt b/shell_tui/CMakeLists.txt
index fdc9e7c..0216261 100644
--- a/shell_tui/CMakeLists.txt
+++ b/shell_tui/CMakeLists.txt
@@ -24,6 +24,7 @@ if (SHELL_TUI)
SOURCES
private/src/activator
private/src/shell_tui
+ private/src/history
)
install_bundle(shell_tui)
http://git-wip-us.apache.org/repos/asf/celix/blob/6785a38c/shell_tui/private/include/history.h
----------------------------------------------------------------------
diff --git a/shell_tui/private/include/history.h b/shell_tui/private/include/history.h
new file mode 100644
index 0000000..b5e6320
--- /dev/null
+++ b/shell_tui/private/include/history.h
@@ -0,0 +1,39 @@
+/**
+ *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.
+ */
+/*
+ * history.h
+ *
+ * \date Jan 16, 2016
+ * \author <a href="mailto:dev@celix.apache.org">Apache Celix Project Team</a>
+ * \copyright Apache License, Version 2.0
+ */
+#ifndef SHELL_TUI_HISTORY
+#define SHELL_TUI_HISTORY
+
+typedef struct history history_t;
+
+history_t *historyCreate();
+void historyDestroy(history_t *hist);
+void history_addLine(history_t *hist, const char *line);
+char *historyGetPrevLine(history_t *hist);
+char *historyGetNextLine(history_t *hist);
+void historyLineReset(history_t *hist);
+unsigned int historySize(history_t *hist);
+
+#endif // SHELL_TUI_HISTORY
http://git-wip-us.apache.org/repos/asf/celix/blob/6785a38c/shell_tui/private/src/history.c
----------------------------------------------------------------------
diff --git a/shell_tui/private/src/history.c b/shell_tui/private/src/history.c
new file mode 100644
index 0000000..ee77f2e
--- /dev/null
+++ b/shell_tui/private/src/history.c
@@ -0,0 +1,87 @@
+/**
+ *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.
+ */
+/*
+ * activator.c
+ *
+ * \date Jan 15, 2016
+ * \author <a href="mailto:dev@celix.apache.org">Apache Celix Project Team</a>
+ * \copyright Apache License, Version 2.0
+ */
+
+#include "history.h"
+#include <stdlib.h>
+#include <string.h>
+#include "linked_list.h"
+
+#define HIST_SIZE 32
+
+struct history {
+ linked_list_pt history_lines;
+ int currentLine;
+};
+
+history_t *historyCreate() {
+ history_t* hist = calloc(1, sizeof(*hist));
+ linkedList_create(&hist->history_lines);
+ hist->currentLine = -1;
+ return hist;
+}
+
+void historyDestroy(history_t *hist) {
+ unsigned int size = linkedList_size(hist->history_lines);
+ for(unsigned int i = 0; i < size; i++) {
+ char *line = linkedList_get(hist->history_lines, i);
+ free(line);
+ }
+ linkedList_destroy(hist->history_lines);
+ free(hist);
+}
+
+void history_addLine(history_t *hist, const char *line) {
+ linkedList_addFirst(hist->history_lines, strdup(line));
+ if(linkedList_size(hist->history_lines) == HIST_SIZE) {
+ char *lastLine = (char*)linkedList_get(hist->history_lines, HIST_SIZE-1);
+ free(lastLine);
+ linkedList_removeIndex(hist->history_lines, HIST_SIZE-1);
+ }
+}
+
+char *historyGetPrevLine(history_t *hist) {
+ hist->currentLine = (hist->currentLine + 1) % linkedList_size(hist->history_lines);
+ return (char*)linkedList_get(hist->history_lines, hist->currentLine);
+}
+
+char *historyGetNextLine(history_t *hist) {
+ if(linkedList_size(hist->history_lines) > 0) {
+ if (hist->currentLine <= 0) {
+ hist->currentLine = linkedList_size(hist->history_lines) - 1;
+ } else {
+ hist->currentLine--;
+ }
+ }
+ return (char*)linkedList_get(hist->history_lines, hist->currentLine);
+}
+
+void historyLineReset(history_t *hist) {
+ hist->currentLine = -1;
+}
+
+unsigned int historySize(history_t *hist) {
+ return linkedList_size(hist->history_lines);
+}
http://git-wip-us.apache.org/repos/asf/celix/blob/6785a38c/shell_tui/private/src/shell_tui.c
----------------------------------------------------------------------
diff --git a/shell_tui/private/src/shell_tui.c b/shell_tui/private/src/shell_tui.c
index b659188..7c2fa3b 100644
--- a/shell_tui/private/src/shell_tui.c
+++ b/shell_tui/private/src/shell_tui.c
@@ -26,35 +26,86 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <termios.h>
+#include <unistd.h>
#include <sys/time.h>
#include <sys/select.h>
#include "bundle_context.h"
#include "bundle_activator.h"
+#include "linked_list.h"
#include "shell.h"
#include "shell_tui.h"
#include "utils.h"
#include <signal.h>
#include <unistd.h>
+#include "history.h"
+
+#define LINE_SIZE 256
+#define PROMPT "-> "
+
+#define KEY_ESC1 '\033'
+#define KEY_ESC2 '['
+#define KEY_BACKSPACE 127
+#define KEY_TAB 9
+#define KEY_ENTER '\n'
+#define KEY_UP 'A'
+#define KEY_DOWN 'B'
+#define KEY_RIGHT 'C'
+#define KEY_LEFT 'D'
+#define KEY_DEL1 '3'
+#define KEY_DEL2 '~'
+
+// static function declarations
+static void remove_newlines(char* line);
+static void clearLine();
+static void cursorLeft(int n);
+static void writeLine(const char*line, int pos);
+static int autoComplete(shell_service_pt shellSvc, char *in, int cursorPos, size_t maxLen);
+static void* shellTui_runnable(void *data);
+
+
+celix_status_t shellTui_start(shell_tui_activator_pt activator) {
+
+ celix_status_t status = CELIX_SUCCESS;
+
+ activator->running = true;
+ celixThread_create(&activator->runnable, NULL, shellTui_runnable, activator);
+
+ return status;
+}
+
+celix_status_t shellTui_stop(shell_tui_activator_pt act) {
+ celix_status_t status = CELIX_SUCCESS;
+ act->running = false;
+ celixThread_kill(act->runnable, SIGUSR1);
+ celixThread_join(act->runnable, NULL);
+ return status;
+}
+
static void* shellTui_runnable(void *data) {
shell_tui_activator_pt act = (shell_tui_activator_pt) data;
- char in[256];
- char dline[256];
- bool needPrompt = true;
-
+ char in[LINE_SIZE] = "";
+ char buffer[LINE_SIZE];
+ int pos = 0;
+ char dline[LINE_SIZE];
fd_set rfds;
struct timeval tv;
+ struct termios term_org, term_new;
+ tcgetattr(STDIN_FILENO, &term_org);
+ term_new = term_org;
+ term_new.c_lflag &= ~( ICANON | ECHO);
+ tcsetattr(STDIN_FILENO, TCSANOW, &term_new);
+
+ history_t *hist = historyCreate();
+
while (act->running) {
char * line = NULL;
- if (needPrompt) {
- printf("-> ");
- fflush(stdout);
- needPrompt = false;
- }
+ writeLine(in, pos);
FD_ZERO(&rfds);
FD_SET(STDIN_FILENO, &rfds);
@@ -62,44 +113,184 @@ static void* shellTui_runnable(void *data) {
tv.tv_usec = 0;
if (select(1, &rfds, NULL, NULL, &tv) > 0) {
- fgets(in, sizeof(in)-1, stdin);
- needPrompt = true;
- memset(dline, 0, 256);
- strncpy(dline, in, 256);
-
- line = utils_stringTrim(dline);
- if ((strlen(line) == 0) || (act->shell == NULL)) {
- continue;
- }
+ int nr_chars = read(STDIN_FILENO, buffer, LINE_SIZE-pos);
+ for(int bufpos = 0; bufpos < nr_chars; bufpos++) {
+ if (buffer[bufpos] == KEY_ESC1 && buffer[bufpos+1] == KEY_ESC2) {
+ switch (buffer[bufpos+2]) {
+ case KEY_UP:
+ if(historySize(hist) > 0) {
+ strncpy(in, historyGetPrevLine(hist), LINE_SIZE);
+ pos = strlen(in);
+ writeLine(in, pos);
+ }
+ break;
+ case KEY_DOWN:
+ if(historySize(hist) > 0) {
+ strncpy(in, historyGetNextLine(hist), LINE_SIZE);
+ pos = strlen(in);
+ writeLine(in, pos);
+ }
+ break;
+ case KEY_RIGHT:
+ if (pos < strlen(in)) {
+ pos++;
+ }
+ writeLine(in, pos);
+ break;
+ case KEY_LEFT:
+ if (pos > 0) {
+ pos--;
+ }
+ writeLine(in, pos);
+ break;
+ case KEY_DEL1:
+ if(buffer[bufpos+3] == KEY_DEL2) {
+ bufpos++; // delete cmd takes 4 chars
+ int len = strlen(in);
+ if (pos < len) {
+ for (int i = pos; i <= len; i++) {
+ in[i] = in[i + 1];
+ }
+ }
+ writeLine(in, pos);
+ }
+ break;
+ default:
+ // Unsupported char, do nothing
+ break;
+ }
+ bufpos+=2;
+ continue;
+ } else if(buffer[bufpos] == KEY_BACKSPACE) { // backspace
+ if(pos > 0) {
+ int len = strlen(in);
+ for(int i = pos-1; i <= len; i++) {
+ in[i] = in[i+1];
+ }
+ pos--;
+ }
+ writeLine(in, pos);
+ continue;
+ } else if(buffer[bufpos] == KEY_TAB) {
+ pos = autoComplete(act->shell, in, pos, LINE_SIZE);
+ continue;
+ } else if(buffer[bufpos] != KEY_ENTER) { //text
+ if(in[pos] == '\0') {
+ in[pos+1] = '\0';
+ }
+ in[pos] = buffer[bufpos];
+ pos++;
+ writeLine(in, pos);
+ fflush(stdout);
+ continue;
+ }
+ writeLine(in, pos);
+ write(STDOUT_FILENO, "\n", 1);
+ remove_newlines(in);
+ history_addLine(hist, in);
- celixThreadMutex_lock(&act->mutex);
- if (act->shell != NULL) {
- act->shell->executeCommand(act->shell->shell, line, stdout, stderr);
- } else {
- fprintf(stderr, "Shell service not available\n");
- }
- celixThreadMutex_unlock(&act->mutex);
+ memset(dline, 0, LINE_SIZE);
+ strncpy(dline, in, LINE_SIZE);
+
+ pos = 0;
+ in[pos] = '\0';
+
+ line = utils_stringTrim(dline);
+ if ((strlen(line) == 0) || (act->shell == NULL)) {
+ continue;
+ }
+ historyLineReset(hist);
+ celixThreadMutex_lock(&act->mutex);
+ if (act->shell != NULL) {
+ act->shell->executeCommand(act->shell->shell, line, stdout, stderr);
+ pos = 0;
+ nr_chars = 0;
+ celixThreadMutex_unlock(&act->mutex);
+ break;
+ } else {
+ fprintf(stderr, "Shell service not available\n");
+ }
+ celixThreadMutex_unlock(&act->mutex);
+ } // for
}
}
+ tcsetattr(STDIN_FILENO, TCSANOW, &term_org);
+ historyDestroy(hist);
return NULL;
}
-celix_status_t shellTui_start(shell_tui_activator_pt activator) {
+static void remove_newlines(char* line) {
+ for(int i = 0; i < strlen(line); i++) {
+ if(line[i] == '\n') {
+ for(int j = 0; j < strlen(&line[i]); j++) {
+ line[i+j] = line[i+j+1];
+ }
+ }
+ }
+}
- celix_status_t status = CELIX_SUCCESS;
+static void clearLine() {
+ printf("\033[2K\r");
+ fflush(stdout);
+}
- activator->running = true;
- celixThread_create(&activator->runnable, NULL, shellTui_runnable, activator);
+static void cursorLeft(int n) {
+ if(n>0) {
+ printf("\033[%dD", n);
+ fflush(stdout);
+ }
+}
- return status;
+static void writeLine(const char*line, int pos) {
+ clearLine();
+ write(STDOUT_FILENO, PROMPT, strlen(PROMPT));
+ write(STDOUT_FILENO, line, strlen(line));
+ cursorLeft(strlen(line)-pos);
}
-celix_status_t shellTui_stop(shell_tui_activator_pt act) {
- celix_status_t status = CELIX_SUCCESS;
- act->running = false;
- celixThread_kill(act->runnable, SIGUSR1);
- celixThread_join(act->runnable, NULL);
- return status;
+static int autoComplete(shell_service_pt shellSvc, char *in, int cursorPos, size_t maxLen) {
+ array_list_pt commandList = NULL;
+ array_list_pt possibleCmdList = NULL;
+ shellSvc->getCommands(shellSvc->shell, &commandList);
+ int nrCmds = arrayList_size(commandList);
+ arrayList_create(&possibleCmdList);
+
+ for (int i = 0; i < nrCmds; i++) {
+ char *cmd = arrayList_get(commandList, i);
+ if (strncmp(in, cmd, cursorPos) == 0) {
+ arrayList_add(possibleCmdList, cmd);
+ }
+ }
+
+ int nrPossibleCmds = arrayList_size(possibleCmdList);
+ if (nrPossibleCmds == 0) {
+ // Check if complete command with space is entered: show usage if this is the case
+ if(in[strlen(in) - 1] == ' ') {
+ for (int i = 0; i < nrCmds; i++) {
+ char *cmd = arrayList_get(commandList, i);
+ if (strncmp(in, cmd, strlen(cmd)) == 0) {
+ clearLine();
+ char* usage = NULL;
+ shellSvc->getCommandUsage(shellSvc->shell, cmd, &usage);
+ printf("Usage:\n %s\n", usage);
+ }
+ }
+ }
+ } else if (nrPossibleCmds == 1) {
+ //Replace input string with the only possibility
+ snprintf(in, maxLen, "%s ", (char*)arrayList_get(possibleCmdList, 0));
+ cursorPos = strlen(in);
+ } else {
+ // Show possibilities
+ clearLine();
+ for(int i = 0; i < nrPossibleCmds; i++) {
+ printf("%s ", (char*)arrayList_get(possibleCmdList, i));
+ }
+ printf("\n");
+ }
+ arrayList_destroy(commandList);
+ arrayList_destroy(possibleCmdList);
+ return cursorPos;
}