commit b5d751bd99f2ccdd376ce89ab6117ba97e8de6be from: Omar Polo date: Sat Jul 07 10:10:50 2018 UTC the selected status now is outside the struct completion commit - 6254fed817b712dcf9568bd36e023d60c49de0ac commit + b5d751bd99f2ccdd376ce89ab6117ba97e8de6be blob - 86fc24eb58628748f497c5590450bf130a4c9084 blob + e7f560aa74c35df6bbd750c97cc063b3663c47e0 --- mymenu.c +++ mymenu.c @@ -33,6 +33,8 @@ #define resname "MyMenu" #define resclass "mymenu" +#define SYM_BUF_SIZE 4 + #ifdef USE_XFT # define default_fontname "monospace" #else @@ -42,6 +44,9 @@ #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MAX(a, b) ((a) > (b) ? (a) : (b)) +// modulo operator +#define mod(a, b) (a < 0 ? (a % b + b) : (a % b)) + // If we don't have it or we don't want an "ignore case" completion // style, fall back to `strstr(3)` #ifndef USE_STRCASESTR @@ -104,144 +109,108 @@ struct rendering { }; // A simple linked list to store the completions. -struct completions { +struct completion { char *completion; - bool selected; - struct completions *next; + struct completion *next; }; +struct completions { + struct completion *completions; + int selected; + int lenght; +}; + // return a newly allocated (and empty) completion list -struct completions *compl_new() { - struct completions *c = malloc(sizeof(struct completions)); +struct completions *compls_new() { + struct completions *cs = malloc(sizeof(struct completions)); + if (cs == nil) + return cs; + + cs->completions = nil; + cs->selected = -1; + cs->lenght = 0; + return cs; +} + +struct completion *compl_new() { + struct completion *c = malloc(sizeof(struct completion)); if (c == nil) return c; c->completion = nil; - c->selected = false; c->next = nil; return c; } // delete ONLY the given completion (i.e. does not free c->next...) -void compl_delete(struct completions *c) { +void compl_delete(struct completion *c) { free(c); } -// delete all completion -void compl_delete_rec(struct completions *c) { +// delete the current completion and the next (c->next) and so on... +void compl_delete_rec(struct completion *c) { while (c != nil) { - struct completions *t = c->next; + struct completion *t = c->next; free(c); c = t; } } -// given a completion list, select the next completion and return the -// element that is selected -struct completions *compl_select_next(struct completions *c) { - if (c == nil) - return nil; +void compls_delete(struct completions *cs) { + if (cs == nil) + return; - struct completions *orig = c; - while (c != nil) { - if (c->selected) { - c->selected = false; - if (c->next != nil) { - // the current one is selected and the next one exists - c->next->selected = true; - return c->next; - } else { - // the current one is selected and the next one is nil, - // select the first one - orig->selected = true; - return orig; - } - } - c = c->next; - } - - orig->selected = true; - return orig; + compl_delete_rec(cs->completions); + free(cs); } -// given a completion list, select the previous and return the element -// that is selected -struct completions *compl_select_prev(struct completions *c) { - if (c == nil) - return nil; - - struct completions *last = nil; - - if (c->selected) { // if the first is selected, select the last one - c->selected = false; - while (c != nil) { - if (c->next == nil) { - c->selected = true; - return c; - } - c = c->next; - } +// create a completion list from a text and the list of possible +// completions (null terminated). Expects a non-null `cs'. +void filter(struct completions *cs, char *text, char **lines) { + struct completion *c = compl_new(); + if (c == nil) { + return; } - // if the selected one is inside the list, select the previous one - while (c != nil) { - if (c->next == nil) { // if c is the last, save it for later - last = c; - } + cs->completions = c; - if (c->next && c->next->selected) { - c->selected = true; - c->next->selected = false; - return c; - } - c = c->next; - } + int index = 0; + int matching = 0; - // if nothing were selected, select the last one - if (c != nil) - c->selected = true; - return c; -} - -// create a completion list from a text and the list of possible completions -struct completions *filter(char *text, char **lines) { - int i = 0; - struct completions *root = compl_new(); - struct completions *c = root; - if (c == nil) - return nil; - - for (;;) { - char *l = lines[i]; + while (true) { + char *l = lines[index]; if (l == nil) break; if (strcasestr(l, text) != nil) { + matching++; + c->next = compl_new(); c = c->next; if (c == nil) { - compl_delete_rec(root); - return nil; + compls_delete(cs); + return; } c->completion = l; } - ++i; + index++; } - struct completions *r = root->next; - compl_delete(root); - return r; + struct completion *f = cs->completions->next; + compl_delete(cs->completions); + cs->completions = f; + cs->lenght = matching; + cs->selected = -1; } // update the given completion, that is: clean the old cs & generate a new one. -struct completions *update_completions(struct completions *cs, char *text, char **lines, bool first_selected) { - compl_delete_rec(cs); - cs = filter(text, lines); - if (first_selected && cs != nil) - cs->selected = true; - return cs; +void update_completions(struct completions *cs, char *text, char **lines, bool first_selected) { + compl_delete_rec(cs->completions); + filter(cs, text, lines); + if (first_selected && cs->lenght > 0) + cs->selected = 0; } // select the next, or the previous, selection and update some @@ -249,17 +218,18 @@ struct completions *update_completions(struct completi // `textlen' with the new lenght of `text'. If the memory cannot be // allocated, `status' will be set to `ERR'. void complete(struct completions *cs, bool first_selected, bool p, char **text, int *textlen, enum state *status) { + if (cs == nil || cs->lenght == 0) + return; + // if the first is always selected, and the first entry is different // from the text, expand the text and return if (first_selected - && cs != nil - && cs->selected - && strcmp(cs->completion, *text) != 0 + && cs->selected == 0 + && strcmp(cs->completions->completion, *text) != 0 && !p) { free(*text); - *text = strdup(cs->completion); + *text = strdup(cs->completions->completion); if (text == nil) { - fprintf(stderr, "Memory allocation error!\n"); *status = ERR; return; } @@ -267,21 +237,28 @@ void complete(struct completions *cs, bool first_selec return; } - struct completions *n = p - ? compl_select_prev(cs) - : compl_select_next(cs); + int index = cs->selected; - if (n != nil) { - free(*text); - *text = strdup(n->completion); - if (text == nil) { - fprintf(stderr, "Memory allocation error!\n"); - *status = ERR; - return; - } - *textlen = strlen(*text); + if (index == -1 && p) + index = 0; + index = cs->selected = mod((p ? index - 1 : index + 1), cs->lenght); + + struct completion *n = cs->completions; + + // find the selected item + while (index != 0) { + index--; + n = n->next; } + free(*text); + *text = strdup(n->completion); + if (text == nil) { + fprintf(stderr, "Memory allocation error!\n"); + *status = ERR; + return; + } + *textlen = strlen(*text); } // push the character c at the end of the string pointed by p @@ -516,23 +493,24 @@ void draw_horizontally(struct rendering *r, char *text XFillRectangle(r->d, r->w, r->completion_bg, start_at, 0, r->width, r->height); - while (cs != nil) { - enum text_type tt = cs->selected ? COMPL_HIGH : COMPL; - GC h = cs->selected ? r->completion_highlighted_bg : r->completion_bg; + struct completion *c = cs->completions; + for (int i = 0; c != nil; ++i) { + enum text_type tt = cs->selected == i ? COMPL_HIGH : COMPL; + GC h = cs->selected == i ? r->completion_highlighted_bg : r->completion_bg; - int len = strlen(cs->completion); - int text_width = text_extents(cs->completion, len, r, nil, nil); + int len = strlen(c->completion); + int text_width = text_extents(c->completion, len, r, nil, nil); XFillRectangle(r->d, r->w, h, start_at, 0, text_width + r->padding*2, r->height); - draw_string(cs->completion, len, start_at + r->padding, texty, r, tt); + draw_string(c->completion, len, start_at + r->padding, texty, r, tt); start_at += text_width + r->padding * 2; if (start_at > r->width) break; // don't draw completion if the space isn't enough - cs = cs->next; + c = c->next; } XFlush(r->d); @@ -562,21 +540,22 @@ void draw_vertically(struct rendering *r, char *text, draw_string(text, strlen(text), r->padding + ps1xlen, height + r->padding, r, PROMPT); start_at += r->padding; - while (cs != nil) { - enum text_type tt = cs->selected ? COMPL_HIGH : COMPL; - GC h = cs->selected ? r->completion_highlighted_bg : r->completion_bg; - - int len = strlen(cs->completion); - text_extents(cs->completion, len, r, &width, &height); + struct completion *c = cs->completions; + for (int i = 0; c != nil; ++i){ + enum text_type tt = cs->selected == i ? COMPL_HIGH : COMPL; + GC h = cs->selected == i ? r->completion_highlighted_bg : r->completion_bg; + + int len = strlen(c->completion); + text_extents(c->completion, len, r, &width, &height); XFillRectangle(r->d, r->w, h, 0, start_at, r->width, height + r->padding*2); - draw_string(cs->completion, len, r->padding, start_at + height + r->padding, r, tt); + draw_string(c->completion, len, r->padding, start_at + height + r->padding, r, tt); start_at += height + r->padding *2; if (start_at > r->height) break; // don't draw completion if the space isn't enough - cs = cs->next; + c = c->next; } XFlush(r->d); @@ -721,14 +700,6 @@ int parse_int_with_middle(const char *str, int default return parse_int_with_percentage(str, default_value, max); } -// Given the name of the program (argv[0]?) print a small help on stderr -void usage(char *prgname) { - fprintf(stderr, "Usage: %s [flags]\n", prgname); - fprintf(stderr, "\t-a: automatic mode, the first completion is " - "always selected;\n"); - fprintf(stderr, "\t-h: print this help.\n"); -} - // Given an event, try to understand what the user wants. If the // return value is ADD_CHAR then `input' is a pointer to a string that // will need to be free'ed. @@ -746,16 +717,15 @@ enum action parse_event(Display *d, XKeyPressedEvent * return EXIT; // try to read what the user pressed - int symbol = 0; + char str[SYM_BUF_SIZE] = {0}; Status s = 0; - Xutf8LookupString(xic, ev, (char*)&symbol, 4, 0, &s); + Xutf8LookupString(xic, ev, str, SYM_BUF_SIZE, 0, &s); if (s == XBufferOverflow) { // should not happen since there are no utf-8 characters larger // than 24bits fprintf(stderr, "Buffer overflow when trying to create keyboard symbol map.\n"); return EXIT; } - char *str = (char*)&symbol; if (ev->state & ControlMask) { if (!strcmp(str, "")) // C-u @@ -776,7 +746,7 @@ enum action parse_event(Display *d, XKeyPressedEvent * return TOGGLE_FIRST_SELECTED; } - *input = strdup((char*)&symbol); + *input = strdup(str); if (*input == nil) { fprintf(stderr, "Error while allocating memory for key.\n"); return EXIT; @@ -785,33 +755,12 @@ enum action parse_event(Display *d, XKeyPressedEvent * return ADD_CHAR; } -// clean up some resources -int exit_cleanup(struct rendering *r, char *ps1, char *fontname, char *text, char **lines, struct completions *cs, int status) { - release_keyboard(r->d); - -#ifdef USE_XFT - XftColorFree(r->d, DefaultVisual(r->d, 0), DefaultColormap(r->d, 0), &r->xft_prompt); - XftColorFree(r->d, DefaultVisual(r->d, 0), DefaultColormap(r->d, 0), &r->xft_completion); - XftColorFree(r->d, DefaultVisual(r->d, 0), DefaultColormap(r->d, 0), &r->xft_completion_highlighted); -#endif - - free(ps1); - free(fontname); - free(text); - - char *l = nil; - char **lns = lines; - while ((l = *lns) != nil) { - free(l); - ++lns; - } - free(lines); - compl_delete(cs); - - XDestroyWindow(r->d, r->w); - XCloseDisplay(r->d); - - return status; +// Given the name of the program (argv[0]?) print a small help on stderr +void usage(char *prgname) { + fprintf(stderr, "Usage: %s [flags]\n", prgname); + fprintf(stderr, "\t-a: automatic mode, the first completion is " + "always selected;\n"); + fprintf(stderr, "\t-h: print this help.\n"); } int main(int argc, char **argv) { @@ -876,9 +825,11 @@ int main(int argc, char **argv) { check_allocation(text); /* struct completions *cs = filter(text, lines); */ - struct completions *cs = nil; - cs = update_completions(cs, text, lines, first_selected); + struct completions *cs = compls_new(); + check_allocation(cs); + update_completions(cs, text, lines, first_selected); + // start talking to xorg Display *d = XOpenDisplay(nil); if (d == nil) { @@ -1207,9 +1158,9 @@ int main(int argc, char **argv) { // if first_selected is active and the first completion is // active be sure to 'expand' the text to match the selection - if (first_selected && cs && cs->selected) { + if (first_selected && cs && cs->selected == 0) { free(text); - text = strdup(cs->completion); + text = strdup(cs->completions->completion); if (text == nil) { fprintf(stderr, "Memory allocation error"); status = ERR; @@ -1230,7 +1181,7 @@ int main(int argc, char **argv) { case DEL_CHAR: popc(text, textlen); - cs = update_completions(cs, text, lines, first_selected); + update_completions(cs, text, lines, first_selected); break; case DEL_WORD: { @@ -1253,19 +1204,26 @@ int main(int argc, char **argv) { text[p] = 0; p--; } - cs = update_completions(cs, text, lines, first_selected); + update_completions(cs, text, lines, first_selected); break; } case DEL_LINE: { for (int i = 0; i < textlen; ++i) text[i] = 0; - cs = update_completions(cs, text, lines, first_selected); + update_completions(cs, text, lines, first_selected); break; } case ADD_CHAR: { int str_len = strlen(input); + + // sometimes a strange key is pressed (i.e. ctrl alone), + // so input will be empty. Don't need to update completion + // in this case + if (str_len == 0) + break; + for (int i = 0; i < str_len; ++i) { textlen = pushc(&text, textlen, input[i]); if (textlen == -1) { @@ -1273,7 +1231,9 @@ int main(int argc, char **argv) { status = ERR; break; } - cs = update_completions(cs, text, lines, first_selected); + } + if (status != ERR) { + update_completions(cs, text, lines, first_selected); free(input); } break; @@ -1281,14 +1241,13 @@ int main(int argc, char **argv) { case TOGGLE_FIRST_SELECTED: first_selected = !first_selected; - if (cs) - cs->selected = !cs->selected; + if (first_selected && cs->selected < 0) + cs->selected = 0; + if (!first_selected && cs->selected == 0) + cs->selected = -1; break; } } - - default: - fprintf(stderr, "Unknown event %d\n", e.type); } draw(&r, text, cs); @@ -1297,5 +1256,30 @@ int main(int argc, char **argv) { if (status == OK) printf("%s\n", text); - return exit_cleanup(&r, ps1, fontname, text, lines, cs, status == OK ? 0 : 1); + release_keyboard(r.d); + +#ifdef USE_XFT + XftColorFree(r.d, DefaultVisual(r.d, 0), DefaultColormap(r.d, 0), &r.xft_prompt); + XftColorFree(r.d, DefaultVisual(r.d, 0), DefaultColormap(r.d, 0), &r.xft_completion); + XftColorFree(r.d, DefaultVisual(r.d, 0), DefaultColormap(r.d, 0), &r.xft_completion_highlighted); +#endif + + free(ps1); + free(fontname); + free(text); + + char *l = nil; + char **lns = lines; + while ((l = *lns) != nil) { + free(l); + ++lns; + } + + free(lines); + compls_delete(cs); + + XDestroyWindow(r.d, r.w); + XCloseDisplay(r.d); + + return status; }