/* ---------------------------------------------------------------------------- Copyright (c) 2021, Daan Leijen This is free software; you can redistribute it and/or modify it under the terms of the MIT License. A copy of the license can be found in the "LICENSE" file at the root of this distribution. -----------------------------------------------------------------------------*/ //------------------------------------------------------------- // History search: this file is included in editline.c //------------------------------------------------------------- static void edit_history_at(ic_env_t* env, editor_t* eb, int ofs ) { if (eb->modified) { history_update(env->history, sbuf_string(eb->input)); // update first entry if modified eb->history_idx = 0; // and start again eb->modified = false; } const char* entry = history_get(env->history,eb->history_idx + ofs); // debug_msg( "edit: history: at: %d + %d, found: %s\n", eb->history_idx, ofs, entry); if (entry == NULL) { term_beep(env->term); } else { eb->history_idx += ofs; sbuf_replace(eb->input, entry); if (ofs > 0) { // at end of first line when scrolling up ssize_t end = sbuf_find_line_end(eb->input,0); eb->pos = (end < 0 ? 0 : end); } else { eb->pos = sbuf_len(eb->input); // at end of last line when scrolling down } edit_refresh(env, eb); } } static void edit_history_prev(ic_env_t* env, editor_t* eb) { edit_history_at(env,eb, 1 ); } static void edit_history_next(ic_env_t* env, editor_t* eb) { edit_history_at(env,eb, -1 ); } typedef struct hsearch_s { struct hsearch_s* next; ssize_t hidx; ssize_t match_pos; ssize_t match_len; bool cinsert; } hsearch_t; static void hsearch_push( alloc_t* mem, hsearch_t** hs, ssize_t hidx, ssize_t mpos, ssize_t mlen, bool cinsert ) { hsearch_t* h = mem_zalloc_tp( mem, hsearch_t ); if (h == NULL) return; h->hidx = hidx; h->match_pos = mpos; h->match_len = mlen; h->cinsert = cinsert; h->next = *hs; *hs = h; } static bool hsearch_pop( alloc_t* mem, hsearch_t** hs, ssize_t* hidx, ssize_t* match_pos, ssize_t* match_len, bool* cinsert ) { hsearch_t* h = *hs; if (h == NULL) return false; *hs = h->next; if (hidx != NULL) *hidx = h->hidx; if (match_pos != NULL) *match_pos = h->match_pos; if (match_len != NULL) *match_len = h->match_len; if (cinsert != NULL) *cinsert = h->cinsert; mem_free(mem, h); return true; } static void hsearch_done( alloc_t* mem, hsearch_t* hs ) { while (hs != NULL) { hsearch_t* next = hs->next; mem_free(mem, hs); hs = next; } } static void edit_history_search(ic_env_t* env, editor_t* eb, char* initial ) { if (history_count( env->history ) <= 0) { term_beep(env->term); return; } // update history if (eb->modified) { history_update(env->history, sbuf_string(eb->input)); // update first entry if modified eb->history_idx = 0; // and start again eb->modified = false; } // set a search prompt and remember the previous state editor_undo_capture(eb); eb->disable_undo = true; bool old_hint = ic_enable_hint(false); const char* prompt_text = eb->prompt_text; eb->prompt_text = "history search"; // search state hsearch_t* hs = NULL; // search undo ssize_t hidx = 1; // current history entry ssize_t match_pos = 0; // current matched position ssize_t match_len = 0; // length of the match const char* hentry = NULL; // current history entry // Simulate per character searches for each letter in `initial` (so backspace works) if (initial != NULL) { const ssize_t initial_len = ic_strlen(initial); ssize_t ipos = 0; while( ipos < initial_len ) { ssize_t next = str_next_ofs( initial, initial_len, ipos, NULL ); if (next < 0) break; hsearch_push( eb->mem, &hs, hidx, match_pos, match_len, true); char c = initial[ipos + next]; // terminate temporarily initial[ipos + next] = 0; if (history_search( env->history, hidx, initial, true, &hidx, &match_pos )) { match_len = ipos + next; } else if (ipos + next >= initial_len) { term_beep(env->term); } initial[ipos + next] = c; // restore ipos += next; } sbuf_replace( eb->input, initial); eb->pos = ipos; } else { sbuf_clear( eb->input ); eb->pos = 0; } // Incremental search again: hentry = history_get(env->history,hidx); if (hentry != NULL) { sbuf_appendf(eb->extra, "[ic-info]%zd. [/][ic-diminish][!pre]", hidx); sbuf_append_n( eb->extra, hentry, match_pos ); sbuf_append(eb->extra, "[/pre][u ic-emphasis][!pre]" ); sbuf_append_n( eb->extra, hentry + match_pos, match_len ); sbuf_append(eb->extra, "[/pre][/u][!pre]" ); sbuf_append(eb->extra, hentry + match_pos + match_len ); sbuf_append(eb->extra, "[/pre][/ic-diminish]"); if (!env->no_help) { sbuf_append(eb->extra, "\n[ic-info](use tab for the next match)[/]"); } sbuf_append(eb->extra, "\n" ); } edit_refresh(env, eb); // Wait for input code_t c = (hentry == NULL ? KEY_ESC : tty_read(env->tty)); if (tty_term_resize_event(env->tty)) { edit_resize(env, eb); } sbuf_clear(eb->extra); // Process commands if (c == KEY_ESC || c == KEY_BELL /* ^G */ || c == KEY_CTRL_C) { c = 0; eb->disable_undo = false; editor_undo_restore(eb, false); } else if (c == KEY_ENTER) { c = 0; editor_undo_forget(eb); sbuf_replace( eb->input, hentry ); eb->pos = sbuf_len(eb->input); eb->modified = false; eb->history_idx = hidx; } else if (c == KEY_BACKSP || c == KEY_CTRL_Z) { // undo last search action bool cinsert; if (hsearch_pop(env->mem,&hs, &hidx, &match_pos, &match_len, &cinsert)) { if (cinsert) edit_backspace(env,eb); } goto again; } else if (c == KEY_CTRL_R || c == KEY_TAB || c == KEY_UP) { // search backward hsearch_push(env->mem, &hs, hidx, match_pos, match_len, false); if (!history_search( env->history, hidx+1, sbuf_string(eb->input), true, &hidx, &match_pos )) { hsearch_pop(env->mem,&hs,NULL,NULL,NULL,NULL); term_beep(env->term); }; goto again; } else if (c == KEY_CTRL_S || c == KEY_SHIFT_TAB || c == KEY_DOWN) { // search forward hsearch_push(env->mem, &hs, hidx, match_pos, match_len, false); if (!history_search( env->history, hidx-1, sbuf_string(eb->input), false, &hidx, &match_pos )) { hsearch_pop(env->mem, &hs,NULL,NULL,NULL,NULL); term_beep(env->term); }; goto again; } else if (c == KEY_F1) { edit_show_help(env, eb); goto again; } else { // insert character and search further backward char chr; unicode_t uchr; if (code_is_ascii_char(c,&chr)) { hsearch_push(env->mem, &hs, hidx, match_pos, match_len, true); edit_insert_char(env,eb,chr); } else if (code_is_unicode(c,&uchr)) { hsearch_push(env->mem, &hs, hidx, match_pos, match_len, true); edit_insert_unicode(env,eb,uchr); } else { // ignore command term_beep(env->term); goto again; } // search for the new input if (history_search( env->history, hidx, sbuf_string(eb->input), true, &hidx, &match_pos )) { match_len = sbuf_len(eb->input); } else { term_beep(env->term); }; goto again; } // done eb->disable_undo = false; hsearch_done(env->mem,hs); eb->prompt_text = prompt_text; ic_enable_hint(old_hint); edit_refresh(env,eb); if (c != 0) tty_code_pushback(env->tty, c); } // Start an incremental search with the current word static void edit_history_search_with_current_word(ic_env_t* env, editor_t* eb) { char* initial = NULL; ssize_t start = sbuf_find_word_start( eb->input, eb->pos ); if (start >= 0) { const ssize_t next = sbuf_next(eb->input, start, NULL); if (!ic_char_is_idletter(sbuf_string(eb->input) + start, (long)(next - start))) { start = next; } if (start >= 0 && start < eb->pos) { initial = mem_strndup(eb->mem, sbuf_string(eb->input) + start, eb->pos - start); } } edit_history_search( env, eb, initial); mem_free(env->mem, initial); }