comparison src/search.c @ 20647:8a2b86a39ef4 v8.2.0877

patch 8.2.0877: cannot get the search statistics Commit: https://github.com/vim/vim/commit/e8f5ec0d30b629d7166f0ad03434065d8bc822df Author: Bram Moolenaar <Bram@vim.org> Date: Mon Jun 1 17:28:35 2020 +0200 patch 8.2.0877: cannot get the search statistics Problem: Cannot get the search statistics. Solution: Add the searchcount() function. (Fujiwara Takuya, closes https://github.com/vim/vim/issues/4446)
author Bram Moolenaar <Bram@vim.org>
date Mon, 01 Jun 2020 17:30:04 +0200
parents 00fff78a929a
children 3e03edae7e6f
comparison
equal deleted inserted replaced
20646:ad14bd3f8c4f 20647:8a2b86a39ef4
19 static int check_linecomment(char_u *line); 19 static int check_linecomment(char_u *line);
20 #ifdef FEAT_FIND_ID 20 #ifdef FEAT_FIND_ID
21 static void show_pat_in_path(char_u *, int, 21 static void show_pat_in_path(char_u *, int,
22 int, int, FILE *, linenr_T *, long); 22 int, int, FILE *, linenr_T *, long);
23 #endif 23 #endif
24 static void search_stat(int dirc, pos_T *pos, int show_top_bot_msg, char_u *msgbuf, int recompute); 24
25 typedef struct searchstat
26 {
27 int cur; // current position of found words
28 int cnt; // total count of found words
29 int exact_match; // TRUE if matched exactly on specified position
30 int incomplete; // 0: search was fully completed
31 // 1: recomputing was timed out
32 // 2: max count exceeded
33 int last_maxcount; // the max count of the last search
34 } searchstat_T;
35
36 static void cmdline_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, int show_top_bot_msg, char_u *msgbuf, int recompute, int maxcount, long timeout);
37 static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchstat_T *stat, int recompute, int maxcount, long timeout);
38
39 #define SEARCH_STAT_DEF_TIMEOUT 20L
40 #define SEARCH_STAT_DEF_MAX_COUNT 99
41 #define SEARCH_STAT_BUF_LEN 12
25 42
26 /* 43 /*
27 * This file contains various searching-related routines. These fall into 44 * This file contains various searching-related routines. These fall into
28 * three groups: 45 * three groups:
29 * 1. string searches (for /, ?, n, and N) 46 * 1. string searches (for /, ?, n, and N)
1201 char_u *strcopy = NULL; 1218 char_u *strcopy = NULL;
1202 char_u *ps; 1219 char_u *ps;
1203 char_u *msgbuf = NULL; 1220 char_u *msgbuf = NULL;
1204 size_t len; 1221 size_t len;
1205 int has_offset = FALSE; 1222 int has_offset = FALSE;
1206 #define SEARCH_STAT_BUF_LEN 12
1207 1223
1208 /* 1224 /*
1209 * A line offset is not remembered, this is vi compatible. 1225 * A line offset is not remembered, this is vi compatible.
1210 */ 1226 */
1211 if (spats[0].off.line && vim_strchr(p_cpo, CPO_LINEOFF) != NULL) 1227 if (spats[0].off.line && vim_strchr(p_cpo, CPO_LINEOFF) != NULL)
1589 && messaging() 1605 && messaging()
1590 && !msg_silent 1606 && !msg_silent
1591 && c != FAIL 1607 && c != FAIL
1592 && !shortmess(SHM_SEARCHCOUNT) 1608 && !shortmess(SHM_SEARCHCOUNT)
1593 && msgbuf != NULL) 1609 && msgbuf != NULL)
1594 search_stat(dirc, &pos, show_top_bot_msg, msgbuf, 1610 cmdline_search_stat(dirc, &pos, &curwin->w_cursor,
1595 (count != 1 || has_offset 1611 show_top_bot_msg, msgbuf,
1612 (count != 1 || has_offset
1596 #ifdef FEAT_FOLDING 1613 #ifdef FEAT_FOLDING
1597 || (!(fdo_flags & FDO_SEARCH) && 1614 || (!(fdo_flags & FDO_SEARCH)
1598 hasFolding(curwin->w_cursor.lnum, NULL, NULL)) 1615 && hasFolding(curwin->w_cursor.lnum,
1599 #endif 1616 NULL, NULL))
1600 )); 1617 #endif
1618 ),
1619 SEARCH_STAT_DEF_MAX_COUNT,
1620 SEARCH_STAT_DEF_TIMEOUT);
1601 1621
1602 /* 1622 /*
1603 * The search command can be followed by a ';' to do another search. 1623 * The search command can be followed by a ';' to do another search.
1604 * For example: "/pat/;/foo/+3;?bar" 1624 * For example: "/pat/;/foo/+3;?bar"
1605 * This is like doing another search command, except: 1625 * This is like doing another search command, except:
3059 } 3079 }
3060 #endif 3080 #endif
3061 3081
3062 /* 3082 /*
3063 * Add the search count "[3/19]" to "msgbuf". 3083 * Add the search count "[3/19]" to "msgbuf".
3064 * When "recompute" is TRUE always recompute the numbers. 3084 * See update_search_stat() for other arguments.
3065 */ 3085 */
3066 static void 3086 static void
3067 search_stat( 3087 cmdline_search_stat(
3068 int dirc, 3088 int dirc,
3069 pos_T *pos, 3089 pos_T *pos,
3070 int show_top_bot_msg, 3090 pos_T *cursor_pos,
3071 char_u *msgbuf, 3091 int show_top_bot_msg,
3072 int recompute) 3092 char_u *msgbuf,
3093 int recompute,
3094 int maxcount,
3095 long timeout)
3096 {
3097 searchstat_T stat;
3098
3099 update_search_stat(dirc, pos, cursor_pos, &stat, recompute, maxcount,
3100 timeout);
3101 if (stat.cur > 0)
3102 {
3103 char t[SEARCH_STAT_BUF_LEN];
3104 size_t len;
3105
3106 #ifdef FEAT_RIGHTLEFT
3107 if (curwin->w_p_rl && *curwin->w_p_rlc == 's')
3108 {
3109 if (stat.incomplete == 1)
3110 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]");
3111 else if (stat.cnt > maxcount && stat.cur > maxcount)
3112 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]",
3113 maxcount, maxcount);
3114 else if (stat.cnt > maxcount)
3115 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/%d]",
3116 maxcount, stat.cur);
3117 else
3118 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]",
3119 stat.cnt, stat.cur);
3120 }
3121 else
3122 #endif
3123 {
3124 if (stat.incomplete == 1)
3125 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]");
3126 else if (stat.cnt > maxcount && stat.cur > maxcount)
3127 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]",
3128 maxcount, maxcount);
3129 else if (stat.cnt > maxcount)
3130 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/>%d]",
3131 stat.cur, maxcount);
3132 else
3133 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]",
3134 stat.cur, stat.cnt);
3135 }
3136
3137 len = STRLEN(t);
3138 if (show_top_bot_msg && len + 2 < SEARCH_STAT_BUF_LEN)
3139 {
3140 mch_memmove(t + 2, t, len);
3141 t[0] = 'W';
3142 t[1] = ' ';
3143 len += 2;
3144 }
3145
3146 mch_memmove(msgbuf + STRLEN(msgbuf) - len, t, len);
3147 if (dirc == '?' && stat.cur == maxcount + 1)
3148 stat.cur = -1;
3149
3150 // keep the message even after redraw, but don't put in history
3151 msg_hist_off = TRUE;
3152 give_warning(msgbuf, FALSE);
3153 msg_hist_off = FALSE;
3154 }
3155 }
3156
3157 /*
3158 * Add the search count information to "stat".
3159 * "stat" must not be NULL.
3160 * When "recompute" is TRUE always recompute the numbers.
3161 * dirc == 0: don't find the next/previous match (only set the result to "stat")
3162 * dirc == '/': find the next match
3163 * dirc == '?': find the previous match
3164 */
3165 static void
3166 update_search_stat(
3167 int dirc,
3168 pos_T *pos,
3169 pos_T *cursor_pos,
3170 searchstat_T *stat,
3171 int recompute,
3172 int maxcount,
3173 long timeout)
3073 { 3174 {
3074 int save_ws = p_ws; 3175 int save_ws = p_ws;
3075 int wraparound = FALSE; 3176 int wraparound = FALSE;
3076 pos_T p = (*pos); 3177 pos_T p = (*pos);
3077 static pos_T lastpos = {0, 0, 0}; 3178 static pos_T lastpos = {0, 0, 0};
3078 static int cur = 0; 3179 static int cur = 0;
3079 static int cnt = 0; 3180 static int cnt = 0;
3181 static int exact_match = FALSE;
3182 static int incomplete = 0;
3183 static int last_maxcount = SEARCH_STAT_DEF_MAX_COUNT;
3080 static int chgtick = 0; 3184 static int chgtick = 0;
3081 static char_u *lastpat = NULL; 3185 static char_u *lastpat = NULL;
3082 static buf_T *lbuf = NULL; 3186 static buf_T *lbuf = NULL;
3083 #ifdef FEAT_RELTIME 3187 #ifdef FEAT_RELTIME
3084 proftime_T start; 3188 proftime_T start;
3085 #endif 3189 #endif
3086 #define OUT_OF_TIME 999 3190
3191 vim_memset(stat, 0, sizeof(searchstat_T));
3192
3193 if (dirc == 0 && !recompute && !EMPTY_POS(lastpos))
3194 {
3195 stat->cur = cur;
3196 stat->cnt = cnt;
3197 stat->exact_match = exact_match;
3198 stat->incomplete = incomplete;
3199 stat->last_maxcount = last_maxcount;
3200 return;
3201 }
3202 last_maxcount = maxcount;
3087 3203
3088 wraparound = ((dirc == '?' && LT_POS(lastpos, p)) 3204 wraparound = ((dirc == '?' && LT_POS(lastpos, p))
3089 || (dirc == '/' && LT_POS(p, lastpos))); 3205 || (dirc == '/' && LT_POS(p, lastpos)));
3090 3206
3091 // If anything relevant changed the count has to be recomputed. 3207 // If anything relevant changed the count has to be recomputed.
3092 // MB_STRNICMP ignores case, but we should not ignore case. 3208 // MB_STRNICMP ignores case, but we should not ignore case.
3093 // Unfortunately, there is no MB_STRNICMP function. 3209 // Unfortunately, there is no MB_STRNICMP function.
3210 // XXX: above comment should be "no MB_STRCMP function" ?
3094 if (!(chgtick == CHANGEDTICK(curbuf) 3211 if (!(chgtick == CHANGEDTICK(curbuf)
3095 && MB_STRNICMP(lastpat, spats[last_idx].pat, STRLEN(lastpat)) == 0 3212 && MB_STRNICMP(lastpat, spats[last_idx].pat, STRLEN(lastpat)) == 0
3096 && STRLEN(lastpat) == STRLEN(spats[last_idx].pat) 3213 && STRLEN(lastpat) == STRLEN(spats[last_idx].pat)
3097 && EQUAL_POS(lastpos, curwin->w_cursor) 3214 && EQUAL_POS(lastpos, *cursor_pos)
3098 && lbuf == curbuf) || wraparound || cur < 0 || cur > 99 || recompute) 3215 && lbuf == curbuf) || wraparound || cur < 0
3216 || (maxcount > 0 && cur > maxcount) || recompute)
3099 { 3217 {
3100 cur = 0; 3218 cur = 0;
3101 cnt = 0; 3219 cnt = 0;
3220 exact_match = FALSE;
3221 incomplete = 0;
3102 CLEAR_POS(&lastpos); 3222 CLEAR_POS(&lastpos);
3103 lbuf = curbuf; 3223 lbuf = curbuf;
3104 } 3224 }
3105 3225
3106 if (EQUAL_POS(lastpos, curwin->w_cursor) && !wraparound 3226 if (EQUAL_POS(lastpos, *cursor_pos) && !wraparound
3107 && (dirc == '/' ? cur < cnt : cur > 0)) 3227 && (dirc == 0 || dirc == '/' ? cur < cnt : cur > 0))
3108 cur += dirc == '/' ? 1 : -1; 3228 cur += dirc == 0 ? 0 : dirc == '/' ? 1 : -1;
3109 else 3229 else
3110 { 3230 {
3231 int done_search = FALSE;
3232 pos_T endpos = {0, 0, 0};
3233
3111 p_ws = FALSE; 3234 p_ws = FALSE;
3112 #ifdef FEAT_RELTIME 3235 #ifdef FEAT_RELTIME
3113 profile_setlimit(20L, &start); 3236 if (timeout > 0)
3114 #endif 3237 profile_setlimit(timeout, &start);
3115 while (!got_int && searchit(curwin, curbuf, &lastpos, NULL, 3238 #endif
3239 while (!got_int && searchit(curwin, curbuf, &lastpos, &endpos,
3116 FORWARD, NULL, 1, SEARCH_KEEP, RE_LAST, NULL) != FAIL) 3240 FORWARD, NULL, 1, SEARCH_KEEP, RE_LAST, NULL) != FAIL)
3117 { 3241 {
3242 done_search = TRUE;
3118 #ifdef FEAT_RELTIME 3243 #ifdef FEAT_RELTIME
3119 // Stop after passing the time limit. 3244 // Stop after passing the time limit.
3120 if (profile_passed_limit(&start)) 3245 if (timeout > 0 && profile_passed_limit(&start))
3121 { 3246 {
3122 cnt = OUT_OF_TIME; 3247 incomplete = 1;
3123 cur = OUT_OF_TIME;
3124 break; 3248 break;
3125 } 3249 }
3126 #endif 3250 #endif
3127 cnt++; 3251 cnt++;
3128 if (LTOREQ_POS(lastpos, p)) 3252 if (LTOREQ_POS(lastpos, p))
3129 cur++; 3253 {
3254 cur = cnt;
3255 if (LTOREQ_POS(p, endpos))
3256 exact_match = TRUE;
3257 }
3130 fast_breakcheck(); 3258 fast_breakcheck();
3131 if (cnt > 99) 3259 if (maxcount > 0 && cnt > maxcount)
3260 {
3261 incomplete = 2; // max count exceeded
3132 break; 3262 break;
3263 }
3133 } 3264 }
3134 if (got_int) 3265 if (got_int)
3135 cur = -1; // abort 3266 cur = -1; // abort
3136 } 3267 if (done_search)
3137 if (cur > 0) 3268 {
3138 { 3269 vim_free(lastpat);
3139 char t[SEARCH_STAT_BUF_LEN] = ""; 3270 lastpat = vim_strsave(spats[last_idx].pat);
3140 size_t len; 3271 chgtick = CHANGEDTICK(curbuf);
3141 3272 lbuf = curbuf;
3142 #ifdef FEAT_RIGHTLEFT 3273 lastpos = p;
3143 if (curwin->w_p_rl && *curwin->w_p_rlc == 's') 3274 }
3144 { 3275 }
3145 if (cur == OUT_OF_TIME) 3276 stat->cur = cur;
3146 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]"); 3277 stat->cnt = cnt;
3147 else if (cnt > 99 && cur > 99) 3278 stat->exact_match = exact_match;
3148 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>99/>99]"); 3279 stat->incomplete = incomplete;
3149 else if (cnt > 99) 3280 stat->last_maxcount = last_maxcount;
3150 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>99/%d]", cur);
3151 else
3152 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]", cnt, cur);
3153 }
3154 else
3155 #endif
3156 {
3157 if (cur == OUT_OF_TIME)
3158 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]");
3159 else if (cnt > 99 && cur > 99)
3160 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>99/>99]");
3161 else if (cnt > 99)
3162 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/>99]", cur);
3163 else
3164 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]", cur, cnt);
3165 }
3166
3167 len = STRLEN(t);
3168 if (show_top_bot_msg && len + 2 < SEARCH_STAT_BUF_LEN)
3169 {
3170 mch_memmove(t + 2, t, len);
3171 t[0] = 'W';
3172 t[1] = ' ';
3173 len += 2;
3174 }
3175
3176 mch_memmove(msgbuf + STRLEN(msgbuf) - len, t, len);
3177 if (dirc == '?' && cur == 100)
3178 cur = -1;
3179
3180 vim_free(lastpat);
3181 lastpat = vim_strsave(spats[last_idx].pat);
3182 chgtick = CHANGEDTICK(curbuf);
3183 lbuf = curbuf;
3184 lastpos = p;
3185
3186 // keep the message even after redraw, but don't put in history
3187 msg_hist_off = TRUE;
3188 give_warning(msgbuf, FALSE);
3189 msg_hist_off = FALSE;
3190 }
3191 p_ws = save_ws; 3281 p_ws = save_ws;
3192 } 3282 }
3193 3283
3194 #if defined(FEAT_FIND_ID) || defined(PROTO) 3284 #if defined(FEAT_FIND_ID) || defined(PROTO)
3195 /* 3285 /*
3957 get_spat_last_idx(void) 4047 get_spat_last_idx(void)
3958 { 4048 {
3959 return last_idx; 4049 return last_idx;
3960 } 4050 }
3961 #endif 4051 #endif
4052
4053 #ifdef FEAT_EVAL
4054 /*
4055 * "searchcount()" function
4056 */
4057 void
4058 f_searchcount(typval_T *argvars, typval_T *rettv)
4059 {
4060 pos_T pos = curwin->w_cursor;
4061 char_u *pattern = NULL;
4062 int maxcount = SEARCH_STAT_DEF_MAX_COUNT;
4063 long timeout = SEARCH_STAT_DEF_TIMEOUT;
4064 int recompute = TRUE;
4065 searchstat_T stat;
4066
4067 if (rettv_dict_alloc(rettv) == FAIL)
4068 return;
4069
4070 if (shortmess(SHM_SEARCHCOUNT)) // 'shortmess' contains 'S' flag
4071 recompute = TRUE;
4072
4073 if (argvars[0].v_type != VAR_UNKNOWN)
4074 {
4075 dict_T *dict = argvars[0].vval.v_dict;
4076 dictitem_T *di;
4077 listitem_T *li;
4078 int error = FALSE;
4079
4080 di = dict_find(dict, (char_u *)"timeout", -1);
4081 if (di != NULL)
4082 {
4083 timeout = (long)tv_get_number_chk(&di->di_tv, &error);
4084 if (error)
4085 return;
4086 }
4087 di = dict_find(dict, (char_u *)"maxcount", -1);
4088 if (di != NULL)
4089 {
4090 maxcount = (int)tv_get_number_chk(&di->di_tv, &error);
4091 if (error)
4092 return;
4093 }
4094 di = dict_find(dict, (char_u *)"recompute", -1);
4095 if (di != NULL)
4096 {
4097 recompute = tv_get_number_chk(&di->di_tv, &error);
4098 if (error)
4099 return;
4100 }
4101 di = dict_find(dict, (char_u *)"pattern", -1);
4102 if (di != NULL)
4103 {
4104 pattern = tv_get_string_chk(&di->di_tv);
4105 if (pattern == NULL)
4106 return;
4107 }
4108 di = dict_find(dict, (char_u *)"pos", -1);
4109 if (di != NULL)
4110 {
4111 if (di->di_tv.v_type != VAR_LIST)
4112 {
4113 semsg(_(e_invarg2), "pos");
4114 return;
4115 }
4116 if (list_len(di->di_tv.vval.v_list) != 3)
4117 {
4118 semsg(_(e_invarg2), "List format should be [lnum, col, off]");
4119 return;
4120 }
4121 li = list_find(di->di_tv.vval.v_list, 0L);
4122 if (li != NULL)
4123 {
4124 pos.lnum = tv_get_number_chk(&li->li_tv, &error);
4125 if (error)
4126 return;
4127 }
4128 li = list_find(di->di_tv.vval.v_list, 1L);
4129 if (li != NULL)
4130 {
4131 pos.col = tv_get_number_chk(&li->li_tv, &error) - 1;
4132 if (error)
4133 return;
4134 }
4135 li = list_find(di->di_tv.vval.v_list, 2L);
4136 if (li != NULL)
4137 {
4138 pos.coladd = tv_get_number_chk(&li->li_tv, &error);
4139 if (error)
4140 return;
4141 }
4142 }
4143 }
4144
4145 save_last_search_pattern();
4146 if (pattern != NULL)
4147 {
4148 if (*pattern == NUL)
4149 goto the_end;
4150 spats[last_idx].pat = vim_strsave(pattern);
4151 }
4152 if (spats[last_idx].pat == NULL || *spats[last_idx].pat == NUL)
4153 goto the_end; // the previous pattern was never defined
4154
4155 update_search_stat(0, &pos, &pos, &stat, recompute, maxcount, timeout);
4156
4157 dict_add_number(rettv->vval.v_dict, "current", stat.cur);
4158 dict_add_number(rettv->vval.v_dict, "total", stat.cnt);
4159 dict_add_number(rettv->vval.v_dict, "exact_match", stat.exact_match);
4160 dict_add_number(rettv->vval.v_dict, "incomplete", stat.incomplete);
4161 dict_add_number(rettv->vval.v_dict, "maxcount", stat.last_maxcount);
4162
4163 the_end:
4164 restore_last_search_pattern();
4165 }
4166 #endif