Improvements to repl tab completion
This commit is contained in:
parent
be0e8dd6c6
commit
e6997418cb
126
kuroko.c
126
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;
|
||||
}
|
||||
}
|
||||
|
14
rline.c
14
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, " ");
|
||||
}
|
||||
|
||||
|
1
rline.h
1
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];
|
||||
|
96
scanner.h
96
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;
|
||||
|
Loading…
Reference in New Issue
Block a user