From e6997418cb64acafb92164095af838bc984ec1c9 Mon Sep 17 00:00:00 2001 From: "K. Lange" Date: Sun, 10 Jan 2021 22:07:13 +0900 Subject: [PATCH] Improvements to repl tab completion --- kuroko.c | 126 ++++++++++++++++++++++++++++++++++++++---------------- rline.c | 14 +++--- rline.h | 1 + scanner.h | 96 +++++++++++++++++++++-------------------- 4 files changed, 148 insertions(+), 89 deletions(-) diff --git a/kuroko.c b/kuroko.c index e6f6216..bf2f18b 100644 --- a/kuroko.c +++ b/kuroko.c @@ -55,7 +55,7 @@ static KrkValue paste(int argc, KrkValue argv[]) { */ static KrkValue findFromProperty(KrkValue current, KrkToken next) { KrkValue value; - KrkValue member = OBJECT_VAL(krk_copyString(next.start, next.length)); + KrkValue member = OBJECT_VAL(krk_copyString(next.start, next.literalWidth)); krk_push(member); if (IS_INSTANCE(current)) { @@ -93,7 +93,9 @@ static void tab_complete_func(rline_context_t * c) { } while (space[count-1].type != TOKEN_EOF && space[count-1].type != TOKEN_ERROR); /* If count == 1, it was EOF or an error and we have nothing to complete. */ - if (count == 1) return; + if (count == 1) { + goto _cleanup; + } /* Otherwise we want to see if we're on an identifier or a dot. */ int base = 2; @@ -102,15 +104,11 @@ static void tab_complete_func(rline_context_t * c) { /* Dots we need to look back at the previous tokens for */ n--; base--; - } else if (space[count-base].type == TOKEN_IDENTIFIER) { - /* Identifiers we will consider as partial matches. */ + } else if (space[count-base].type >= TOKEN_IDENTIFIER && space[count-base].type <= TOKEN_WITH) { + /* Something alphanumeric; only for the last element */ } else { - /* TODO: What if something the user typed as a partial token was a keyword? - * The scanner will give us the keyword's token type... - * Need to split token types between word-y and non-word-y - * so we can quietly ignore keywords and let them continue... */ - free(tmp); - return; + /* Some other symbol */ + goto _cleanup; } /* Work backwards to find the start of this chain of identifiers */ @@ -125,6 +123,7 @@ static void tab_complete_func(rline_context_t * c) { if (n <= count) { /* Now work forwards, starting from the current globals. */ KrkValue root = OBJECT_VAL(vm.module); + int isGlobal = 1; while (n > base) { /* And look at the potential fields for instances/classes */ KrkValue next = findFromProperty(root, space[count-n]); @@ -132,41 +131,83 @@ static void tab_complete_func(rline_context_t * c) { /* If we hit None, we found something invalid (or literally hit a None * object, but really the difference is minimal in this case: Nothing * useful to tab complete from here. */ - free(tmp); - return; + goto _cleanup; } + isGlobal = 0; root = next; n -= 2; /* To skip every other dot. */ } /* Now figure out what we're completing - did we already have a partial symbol name? */ int length = (space[count-base].type == TOKEN_DOT) ? 0 : (space[count-base].length); - - /* Take the last symbol name from the chain and get its member list from dir() */ - KrkValue dirList = krk_dirObject(1,(KrkValue[]){root}); - if (!IS_INSTANCE(dirList)) { - fprintf(stderr,"\nInternal error while tab completting.\n"); - free(tmp); - return; - } - krk_push(dirList); - KrkValue _list_internal = OBJECT_VAL(AS_INSTANCE(dirList)->_internal); + isGlobal = isGlobal && (length != 0); /* Collect up to 256 of those that match */ char * matches[256]; - int matchCount = 0;; - for (size_t i = 0; i < AS_LIST(_list_internal)->count; ++i) { - KrkString * s = AS_STRING(AS_LIST(_list_internal)->values[i]); + int matchCount = 0; - /* If this symbol is shorter than the current submatch, skip it. */ - if (length && (int)s->length < length) continue; + /* Take the last symbol name from the chain and get its member list from dir() */ + KRK_PAUSE_GC(); - if (!memcmp(s->chars, space[count-base].start, length)) { - matches[matchCount] = s->chars; - matchCount++; - if (matchCount == 255) break; + for (;;) { + KrkValue dirList = krk_dirObject(1,(KrkValue[]){root}); + if (!IS_INSTANCE(dirList)) { + fprintf(stderr,"\nInternal error while tab completting.\n"); + goto _cleanup; + } + KrkValue _list_internal = OBJECT_VAL(AS_INSTANCE(dirList)->_internal); + + for (size_t i = 0; i < AS_LIST(_list_internal)->count; ++i) { + KrkString * s = AS_STRING(AS_LIST(_list_internal)->values[i]); + KrkToken asToken = {.start = s->chars, .literalWidth = s->length}; + KrkValue thisValue = findFromProperty(root, asToken); + if (IS_CLOSURE(thisValue) || IS_BOUND_METHOD(thisValue) || + (IS_NATIVE(thisValue) && ((KrkNative*)AS_OBJECT(thisValue))->isMethod != 2)) { + char * tmp = malloc(s->length + 2); + sprintf(tmp, "%s(", s->chars); + s = krk_takeString(tmp, strlen(tmp)); + } + + /* If this symbol is shorter than the current submatch, skip it. */ + if (length && (int)s->length < length) continue; + + /* See if it's already in the matches */ + int found = 0; + for (int i = 0; i < matchCount; ++i) { + if (!strcmp(matches[i], s->chars)) { + found = 1; + break; + } + } + if (found) continue; + + if (!memcmp(s->chars, space[count-base].start, length)) { + matches[matchCount] = s->chars; + matchCount++; + if (matchCount == 255) goto _toomany; + } + } + + /* + * If the object we were scanning was the current module, + * then we should also throw the builtins into the ring. + */ + if (isGlobal && AS_OBJECT(root) == (KrkObj*)vm.module) { + root = OBJECT_VAL(vm.builtins); + continue; + } else if (isGlobal && AS_OBJECT(root) == (KrkObj*)vm.builtins) { + extern char * syn_krk_keywords[]; + KrkInstance * fakeKeywordsObject = krk_newInstance(vm.objectClass); + for (char ** keyword = syn_krk_keywords; *keyword; keyword++) { + krk_attachNamedValue(&fakeKeywordsObject->fields, *keyword, NONE_VAL()); + } + root = OBJECT_VAL(fakeKeywordsObject); + continue; + } else { + break; } } +_toomany: /* Now we can do things with the matches. */ if (matchCount == 1) { @@ -191,12 +232,24 @@ static void tab_complete_func(rline_context_t * c) { } /* If no common sub string could be filled in, we print the list. */ if (j == length) { - /* We could do something prettier, but this will work for now. */ - fprintf(stderr, "\n"); + /* First find the maximum width of an entry */ + int maxWidth = 0; for (int i = 0; i < matchCount; ++i) { - fprintf(stderr, "%s ", matches[i]); + if ((int)strlen(matches[i]) > maxWidth) maxWidth = strlen(matches[i]); } + /* Now how many can we fit in a screen */ + int colsPerLine = rline_terminal_width / (maxWidth + 2); /* +2 for the spaces */ fprintf(stderr, "\n"); + int column = 0; + for (int i = 0; i < matchCount; ++i) { + fprintf(stderr, "%-*s ", maxWidth, matches[i]); + column += 1; + if (column >= colsPerLine) { + fprintf(stderr, "\n"); + column = 0; + } + } + if (column != 0) fprintf(stderr, "\n"); } else { /* If we do have a common sub string, fill in those characters. */ for (int i = length; i < j; ++i) { @@ -205,10 +258,11 @@ static void tab_complete_func(rline_context_t * c) { } } } - - krk_pop(); /* dirList */ } +_cleanup: + free(tmp); free(space); + KRK_RESUME_GC(); return; } } diff --git a/rline.c b/rline.c index 25144bd..6c738dc 100644 --- a/rline.c +++ b/rline.c @@ -28,6 +28,7 @@ int rline_history_count = 0; int rline_history_offset = 0; int rline_scroll = 0; char * rline_exit_string = "exit\n"; +int rline_terminal_width = 0; void rline_history_insert(char * str) { if (str[strlen(str)-1] == '\n') { @@ -161,7 +162,6 @@ static int loading = 0; static int column = 0; static int offset = 0; static int width = 0; -static int full_width = 0; static int show_right_side = 0; static int show_left_side = 0; static int prompt_width_calc = 0; @@ -1046,22 +1046,22 @@ static line_t * line_insert(line_t * line, char_t c, int offset) { static void get_size(void) { struct winsize w; ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); - full_width = w.ws_col; - if (full_width - prompt_right_width - prompt_width > MINIMUM_SIZE) { + rline_terminal_width = w.ws_col; + if (rline_terminal_width - prompt_right_width - prompt_width > MINIMUM_SIZE) { show_right_side = 1; show_left_side = 1; prompt_width_calc = prompt_width; - width = full_width - prompt_right_width; + width = rline_terminal_width - prompt_right_width; } else { show_right_side = 0; - if (full_width - prompt_width > MINIMUM_SIZE) { + if (rline_terminal_width - prompt_width > MINIMUM_SIZE) { show_left_side = 1; prompt_width_calc = prompt_width; } else { show_left_side = 0; prompt_width_calc = 1; } - width = full_width; + width = rline_terminal_width; } } @@ -1532,7 +1532,7 @@ static int read_line(void) { set_colors(COLOR_ALT_FG, COLOR_ALT_BG); fprintf(stdout, "◄\033[0m"); /* TODO: This could be retrieved from an envvar */ - for (int i = 0; i < full_width - 1; ++i) { + for (int i = 0; i < rline_terminal_width - 1; ++i) { fprintf(stdout, " "); } diff --git a/rline.h b/rline.h index 54c068a..3330a37 100644 --- a/rline.h +++ b/rline.h @@ -52,6 +52,7 @@ extern char * rline_history_prev(int item); extern void rline_place_cursor(void); extern void rline_set_colors(rline_style_t style); extern void rline_insert(rline_context_t * context, const char * what); +extern int rline_terminal_width; #define RLINE_HISTORY_ENTRIES 128 extern char * rline_history[RLINE_HISTORY_ENTRIES]; diff --git a/scanner.h b/scanner.h index 3e3804a..b0968a6 100644 --- a/scanner.h +++ b/scanner.h @@ -4,70 +4,74 @@ typedef enum { TOKEN_LEFT_PAREN, TOKEN_RIGHT_PAREN, TOKEN_LEFT_BRACE, TOKEN_RIGHT_BRACE, TOKEN_LEFT_SQUARE, TOKEN_RIGHT_SQUARE, - /* 6 */ TOKEN_COLON, - TOKEN_COMMA, TOKEN_DOT, TOKEN_MINUS, TOKEN_PLUS, - TOKEN_SEMICOLON, TOKEN_SOLIDUS, TOKEN_ASTERISK, - - /* 14 */ - TOKEN_BANG, TOKEN_BANG_EQUAL, - TOKEN_EQUAL, TOKEN_EQUAL_EQUAL, - TOKEN_GREATER, TOKEN_GREATER_EQUAL, - TOKEN_LESS, TOKEN_LESS_EQUAL, - - /* 22 */ - TOKEN_IDENTIFIER, TOKEN_STRING, TOKEN_NUMBER, TOKEN_BIG_STRING, - - TOKEN_AND, /* 26 and */ - TOKEN_CLASS, /* 27 class */ - TOKEN_DEF, /* 28 def */ - TOKEN_ELSE, /* 29 else */ - TOKEN_FALSE, /* 30 False */ - TOKEN_FOR, /* 31 for */ - TOKEN_IF, /* 32 if */ - TOKEN_IMPORT,/* 33 import */ - TOKEN_IN, /* 34 in */ - TOKEN_LET, /* 35 let */ - TOKEN_NONE, /* 36 None */ - TOKEN_NOT, /* 37 not */ - TOKEN_OR, /* 38 or */ - TOKEN_ELIF, /* Provided for compatibility, recommend `else if` instead. */ - TOKEN_RETURN,/* 40 return */ - TOKEN_SELF, /* 41 self */ - TOKEN_SUPER, /* 42 super */ - TOKEN_TRUE, /* 43 True */ - TOKEN_WHILE, /* 44 while */ - - TOKEN_INDENTATION, /* 45 */ - - TOKEN_RETRY, /* 46 */ - TOKEN_EOL, /* 47 */ - + TOKEN_COMMA, + TOKEN_DOT, + TOKEN_MINUS, + TOKEN_PLUS, + TOKEN_SEMICOLON, + TOKEN_SOLIDUS, + TOKEN_ASTERISK, TOKEN_MODULO, - TOKEN_TRY, TOKEN_EXCEPT, TOKEN_RAISE, - TOKEN_BREAK, TOKEN_CONTINUE, - - /* Decorators */ - TOKEN_AT, /* @ */ - /* Bitwise operators */ + TOKEN_AT, TOKEN_CARET, /* ^ (xor) */ TOKEN_AMPERSAND, /* & (and) */ TOKEN_PIPE, /* | (or) */ TOKEN_TILDE, /* ~ (negate) */ - /* Shifts */ TOKEN_LEFT_SHIFT, /* << */ TOKEN_RIGHT_SHIFT,/* >> */ - /* Assignment shortcuts */ TOKEN_PLUS_EQUAL, /* += */ TOKEN_MINUS_EQUAL,/* -= */ TOKEN_PLUS_PLUS, /* ++ */ TOKEN_MINUS_MINUS,/* -- */ + TOKEN_BANG, TOKEN_BANG_EQUAL, + TOKEN_EQUAL, TOKEN_EQUAL_EQUAL, + TOKEN_GREATER, TOKEN_GREATER_EQUAL, + TOKEN_LESS, TOKEN_LESS_EQUAL, + + TOKEN_STRING, + TOKEN_BIG_STRING, + TOKEN_NUMBER, + + /* + * Everything after this, up to indentation, + * consists of alphanumerics. + */ + TOKEN_IDENTIFIER, + TOKEN_AND, + TOKEN_CLASS, + TOKEN_DEF, + TOKEN_ELSE, + TOKEN_FALSE, + TOKEN_FOR, + TOKEN_IF, + TOKEN_IMPORT, + TOKEN_IN, + TOKEN_LET, + TOKEN_NONE, + TOKEN_NOT, + TOKEN_OR, + TOKEN_ELIF, + TOKEN_RETURN, + TOKEN_SELF, + TOKEN_SUPER, + TOKEN_TRUE, + TOKEN_WHILE, + TOKEN_TRY, + TOKEN_EXCEPT, + TOKEN_RAISE, + TOKEN_BREAK, + TOKEN_CONTINUE, TOKEN_AS, TOKEN_FROM, TOKEN_LAMBDA, TOKEN_WITH, + TOKEN_INDENTATION, + + TOKEN_EOL, + TOKEN_RETRY, TOKEN_ERROR, TOKEN_EOF, } KrkTokenType;