Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
10435 lines (8889 sloc) 326 KB
<
/* ################################################################### */
/* Copyright 2015 - Pierre Gentile */
/* */
/* This Software is licensed under the GPL licensed Version 2, */
/* please read http://www.gnu.org/copyleft/gpl.html */
/* */
/* you can redistribute it and/or modify it under the terms of the GNU */
/* General Public License as published by the Free Software */
/* Foundation; either version 2 of the License. */
/* */
/* This software is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU */
/* General Public License for more details. */
/* ################################################################### */
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <stdarg.h>
#include <signal.h>
#include <ctype.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <locale.h>
#include <langinfo.h>
#if defined(__sun) && defined(__SVR4)
#include <curses.h>
#endif
#include <term.h>
#include <termios.h>
#include <regex.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/param.h>
#include <wchar.h>
#include "xmalloc.h"
#include "list.h"
#include "index.h"
#include "utf8.h"
#include "fgetc.h"
#include "utils.h"
#include "getopt.h"
#include "usage.h"
#include "smenu.h"
/* **************** */
/* Extern variables */
/* **************** */
extern ll_t * tst_search_list;
extern char * eoptarg;
extern char * eopterr;
extern int eoptind;
extern int eoptopt;
extern int eoptbad;
extern int eopterrfd;
/* **************** */
/* Global variables */
/* **************** */
word_t * word_a; /* array containing words data (size: count) */
long count = 0; /* number of words read from stdin */
long current; /* index the current selection under the cursor) */
long new_current; /* final current position, (used in search function) */
long prev_current; /* previous position stored when using direct access */
long * line_nb_of_word_a; /* array containing the line number (from 0) *
* of each word read */
long * first_word_in_line_a; /* array containing the index of the first *
* word of each lines */
search_mode_t search_mode = NONE;
search_mode_t old_search_mode = NONE;
int help_mode = 0; /* 1 if help is displayed else 0 */
char * word_buffer;
int (*my_isprint)(int);
/* UTF-8 useful symbols */
/* """""""""""""""""""" */
char * left_arrow = "\xe2\x86\x90";
char * up_arrow = "\xe2\x86\x91";
char * right_arrow = "\xe2\x86\x92";
char * down_arrow = "\xe2\x86\x93";
char * vertical_bar = "\xe2\x94\x82"; /* box drawings light vertical */
char * shift_left_sym = "\xe2\x86\x90"; /* leftwards_arrow */
char * shift_right_sym = "\xe2\x86\x92"; /* rightwards_arrow */
char * sbar_line = "\xe2\x94\x82"; /* box_drawings_light_vertical */
char * sbar_top = "\xe2\x94\x90"; /* box_drawings_light_down_and_left */
char * sbar_down = "\xe2\x94\x98"; /* box_drawings_light_up_and_left */
char * sbar_curs = "\xe2\x95\x91"; /* box_drawings_double_vertical */
char * sbar_arr_up = "\xe2\x96\xb2"; /* black_up_pointing_triangle */
char * sbar_arr_down = "\xe2\x96\xbc"; /* black_down_pointing_triangle */
/* Variables used to manage the direct access entries */
/* """""""""""""""""""""""""""""""""""""""""""""""""" */
daccess_t daccess;
char * daccess_stack;
int daccess_stack_head;
/* Variables used for fuzzy and substring searching */
/* """""""""""""""""""""""""""""""""""""""""""""""" */
long * matching_words_a;
long matching_words_a_size;
long matches_count;
long * best_matching_words_a;
long best_matching_words_a_size;
long best_matches_count;
long * alt_matching_words_a = NULL;
long alt_matches_count;
/* Variables used in signal handlers */
/* """"""""""""""""""""""""""""""""" */
volatile sig_atomic_t got_winch = 0;
volatile sig_atomic_t got_winch_alrm = 0;
volatile sig_atomic_t got_help_alrm = 0;
volatile sig_atomic_t got_daccess_alrm = 0;
volatile sig_atomic_t got_timeout_tick = 0;
volatile sig_atomic_t got_sigsegv = 0;
volatile sig_atomic_t got_sigterm = 0;
volatile sig_atomic_t got_sighup = 0;
/* Variables used when a timeout is set (option -x) */
/* """""""""""""""""""""""""""""""""""""""""""""""" */
timeout_t timeout;
char * timeout_word; /* printed word when the timeout type is WORD. */
char * timeout_seconds; /* string containing the number of remaining *
* seconds. */
int quiet_timeout = 0; /* 1 when we want no message to be displayed. */
/* ************** */
/* Help functions */
/* ************** */
/* ==================== */
/* Help message display */
/* ==================== */
void
help(win_t * win, term_t * term, long last_line, toggle_t * toggle)
{
int index; /* used to identify the objects long the help line */
int line = 0; /* number of windows lines used by the help line */
int len = 0; /* length of the help line */
int offset = 0; /* offset from the first column of the terminal to *
* the start of the help line */
int entries_nb; /* number of help entries to display */
int help_len; /* total length of the help line */
struct entry_s
{
char attr; /* r=reverse, n=normal, b=bold */
char * str; /* string to be displayed for an object in the help line */
int len; /* length of one of these objects */
};
char * arrows = concat(left_arrow, up_arrow, right_arrow, down_arrow, NULL);
struct entry_s entries[] =
{ { 'n', "Move:", 5 }, { 'b', arrows, 4 }, { 'n', "|", 1 },
{ 'b', "h", 1 }, { 'b', "j", 1 }, { 'b', "k", 1 },
{ 'b', "l", 1 }, { 'n', ",", 1 }, { 'b', "PgUp", 4 },
{ 'n', "/", 1 }, { 'b', "PgDn", 4 }, { 'n', "/", 1 },
{ 'b', "Home", 4 }, { 'n', "/", 1 }, { 'b', "End", 3 },
{ 'n', " ", 1 }, { 'n', "Abort:", 6 }, { 'b', "q", 1 },
{ 'n', " ", 1 }, { 'n', "Find:", 5 }, { 'b', "/", 1 },
{ 'n', "|", 1 }, { 'b', "\"\'", 2 }, { 'n', "|", 1 },
{ 'b', "~*", 2 }, { 'n', "|", 1 }, { 'b', "=^", 2 },
{ 'n', ",", 1 }, { 'b', "SP", 2 }, { 'n', "|", 1 },
{ 'b', "nN", 2 }, { 'n', " ", 1 }, { 'n', "Select:", 7 },
{ 'b', "CR", 2 }, { 'n', "|", 1 }, { 'b', "tTU", 3 } };
entries_nb = sizeof(entries) / sizeof(struct entry_s);
/* Remove the last two entries if tagging is not enabled */
/* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
if (!toggle->taggable)
entries_nb -= 2;
/* Get the total length of the help line */
/* """"""""""""""""""""""""""""""""""""" */
help_len = 0;
for (index = 0; index < entries_nb; index++)
help_len += entries[index].len;
/* Save the position of the terminal cursor so that it can be */
/* put back there after printing of the help line */
/* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
tputs(TPARM1(save_cursor), 1, outch);
/* Center the help line if the -M (Middle option is set. */
/* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
if (win->offset > 0)
if ((offset = win->offset + win->max_width / 2 - help_len / 2) > 0)
{
int i;
for (i = 0; i < offset; i++)
fputc(' ', stdout);
}
/* Print the different objects forming the help line */
/* """"""""""""""""""""""""""""""""""""""""""""""""" */
for (index = 0; index < entries_nb; index++)
{
if ((len += entries[index].len) >= term->ncolumns - 1)
{
line++;
if (line > last_line || line == win->max_lines)
break;
len = entries[index].len;
fputc('\n', stdout);
}
switch (entries[index].attr)
{
case 'b':
if (term->has_bold)
tputs(TPARM1(enter_bold_mode), 1, outch);
break;
case 'r':
if (term->has_reverse)
tputs(TPARM1(enter_reverse_mode), 1, outch);
else if (term->has_standout)
tputs(TPARM1(enter_standout_mode), 1, outch);
break;
case 'n':
tputs(TPARM1(exit_attribute_mode), 1, outch);
break;
}
fputs(entries[index].str, stdout);
}
tputs(TPARM1(exit_attribute_mode), 1, outch);
tputs(TPARM1(clr_eol), 1, outch);
/* Relocate the cursor to its saved position */
/* """"""""""""""""""""""""""""""""""""""""" */
tputs(TPARM1(restore_cursor), 1, outch);
}
/* ********************************** */
/* Attributes string parsing function */
/* ********************************** */
/* ================================ */
/* Decode attributes toggles if any */
/* b -> bold */
/* d -> dim */
/* r -> reverse */
/* s -> standout */
/* u -> underline */
/* i -> italic */
/* */
/* Returns 0 if some unexpected */
/* toggle is found else 0 */
/* ================================ */
int
decode_attr_toggles(char * s, attr_t * attr)
{
int rc = 1;
attr->bold = attr->dim = attr->reverse = 0;
attr->standout = attr->underline = attr->italic = 0;
while (*s != '\0')
{
switch (*s)
{
case 'b':
attr->bold = 1;
attr->is_set = SET;
break;
case 'd':
attr->dim = 1;
attr->is_set = SET;
break;
case 'r':
attr->reverse = 1;
attr->is_set = SET;
break;
case 's':
attr->standout = 1;
attr->is_set = SET;
break;
case 'u':
attr->underline = 1;
attr->is_set = SET;
break;
case 'i':
attr->italic = 1;
attr->is_set = SET;
break;
default:
rc = 0;
break;
}
s++;
}
return rc;
}
/* =========================================================*/
/* Parse attributes in str in the form [fg][/bg][,toggles] */
/* where: */
/* fg and bg are short representing a color value */
/* toggles is an array of toggles (see decode_attr_toggles) */
/* Returns 1 on success else 0 */
/* attr will be filled by the function */
/* =========================================================*/
int
parse_attr(char * str, attr_t * attr, short max_color)
{
int n;
char * pos;
char s1[12] = { 0 };
char s2[7] = { 0 };
short d1 = -1, d2 = -1;
int rc = 1;
n = sscanf(str, "%11[^,],%6s", s1, s2);
if (n == 0)
return 0;
if ((pos = strchr(s1, '/')))
{
if (pos == s1) /* s1 starts with a / */
{
d1 = -1;
if (sscanf(s1 + 1, "%hd", &d2) == 0)
{
d2 = -1;
if (n == 1)
return 0;
}
}
else if (sscanf(s1, "%hd/%hd", &d1, &d2) < 2)
{
d1 = d2 = -1;
if (n == 1)
return 0;
}
}
else /* no / in the first string */
{
d2 = -1;
if (sscanf(s1, "%hd", &d1) == 0)
{
d1 = -1;
if (n == 2 || decode_attr_toggles(s1, attr) == 0)
return 0;
}
}
if (d1 < -1 || d2 < -1)
return 0;
if (max_color == 0)
{
attr->fg = -1;
attr->bg = -1;
}
else
{
attr->fg = d1;
attr->bg = d2;
}
if (n == 2)
rc = decode_attr_toggles(s2, attr);
return rc;
}
/* ============================================= */
/* Set the terminal attributes according to attr */
/* ============================================= */
void
apply_attr(term_t * term, attr_t attr)
{
if (attr.fg >= 0)
set_foreground_color(term, attr.fg);
if (attr.bg >= 0)
set_background_color(term, attr.bg);
if (attr.bold > 0)
tputs(TPARM1(enter_bold_mode), 1, outch);
if (attr.dim > 0)
tputs(TPARM1(enter_dim_mode), 1, outch);
if (attr.reverse > 0)
tputs(TPARM1(enter_reverse_mode), 1, outch);
if (attr.standout > 0)
tputs(TPARM1(enter_standout_mode), 1, outch);
if (attr.underline > 0)
tputs(TPARM1(enter_underline_mode), 1, outch);
if (attr.italic > 0)
tputs(TPARM1(enter_italics_mode), 1, outch);
}
/* ******************** */
/* ini parsing function */
/* ******************** */
/* ===================================================== */
/* Callback function called when parsing each non-header */
/* line of the ini file. */
/* Returns 0 if OK, 1 if not. */
/* ===================================================== */
int
ini_cb(win_t * win, term_t * term, limits_t * limits, timers_t * timers,
misc_t * misc, const char * section, const char * name, char * value)
{
int error = 0;
int has_colors = (term->colors > 7);
if (strcmp(section, "colors") == 0)
{
attr_t v = { UNSET, -1, -1, -1, -1, -1, -1, -1, -1 };
#define CHECK_ATTR(x) \
else if (strcmp(name, #x) == 0) \
{ \
error = !parse_attr(value, &v, term->colors); \
if (error) \
goto out; \
else \
{ \
if (win->x##_attr.is_set != FORCED) \
{ \
win->x##_attr.is_set = SET; \
if (v.fg >= 0) \
win->x##_attr.fg = v.fg; \
if (v.bg >= 0) \
win->x##_attr.bg = v.bg; \
if (v.bold >= 0) \
win->x##_attr.bold = v.bold; \
if (v.dim >= 0) \
win->x##_attr.dim = v.dim; \
if (v.reverse >= 0) \
win->x##_attr.reverse = v.reverse; \
if (v.standout >= 0) \
win->x##_attr.standout = v.standout; \
if (v.underline >= 0) \
win->x##_attr.underline = v.underline; \
if (v.italic >= 0) \
win->x##_attr.italic = v.italic; \
} \
} \
}
#define CHECK_ATT_ATTR(x, y) \
else if (strcmp(name, #x #y) == 0) \
{ \
error = !parse_attr(value, &v, term->colors); \
if (error) \
goto out; \
else \
{ \
if (win->x##_attr[y - 1].is_set != FORCED) \
{ \
win->x##_attr[y - 1].is_set = SET; \
if (v.fg >= 0) \
win->x##_attr[y - 1].fg = v.fg; \
if (v.bg >= 0) \
win->x##_attr[y - 1].bg = v.bg; \
if (v.bold >= 0) \
win->x##_attr[y - 1].bold = v.bold; \
if (v.dim >= 0) \
win->x##_attr[y - 1].dim = v.dim; \
if (v.reverse >= 0) \
win->x##_attr[y - 1].reverse = v.reverse; \
if (v.standout >= 0) \
win->x##_attr[y - 1].standout = v.standout; \
if (v.underline >= 0) \
win->x##_attr[y - 1].underline = v.underline; \
if (v.italic >= 0) \
win->x##_attr[y - 1].italic = v.italic; \
} \
} \
}
/* [colors] section */
/* """""""""""""""" */
if (has_colors)
{
if (strcmp(name, "method") == 0)
{
if (strcmp(value, "classic") == 0)
term->color_method = 0;
else if (strcmp(value, "ansi") == 0)
term->color_method = 1;
else
{
error = 1;
goto out;
}
}
/* clang-format off */
CHECK_ATTR(cursor)
CHECK_ATTR(bar)
CHECK_ATTR(shift)
CHECK_ATTR(message)
CHECK_ATTR(search_field)
CHECK_ATTR(search_text)
CHECK_ATTR(match_field)
CHECK_ATTR(match_text)
CHECK_ATTR(match_err_field)
CHECK_ATTR(match_err_text)
CHECK_ATTR(include)
CHECK_ATTR(exclude)
CHECK_ATTR(tag)
CHECK_ATTR(cursor_on_tag)
CHECK_ATTR(daccess)
CHECK_ATT_ATTR(special, 1)
CHECK_ATT_ATTR(special, 2)
CHECK_ATT_ATTR(special, 3)
CHECK_ATT_ATTR(special, 4)
CHECK_ATT_ATTR(special, 5)
/* clang-format on */
}
}
else if (strcmp(section, "window") == 0)
{
int v;
/* [window] section */
/* """""""""""""""" */
if (strcmp(name, "lines") == 0)
{
if ((error = !(sscanf(value, "%d", &v) == 1 && v >= 0)))
goto out;
else
win->asked_max_lines = v;
}
}
else if (strcmp(section, "limits") == 0)
{
int v;
/* [limits] section */
/* """""""""""""""" */
if (strcmp(name, "word_length") == 0)
{
if ((error = !(sscanf(value, "%d", &v) == 1 && v > 0)))
goto out;
else
limits->word_length = v;
}
else if (strcmp(name, "words") == 0)
{
if ((error = !(sscanf(value, "%d", &v) == 1 && v > 0)))
goto out;
else
limits->words = v;
}
else if (strcmp(name, "columns") == 0)
{
if ((error = !(sscanf(value, "%d", &v) == 1 && v > 0)))
goto out;
else
limits->cols = v;
}
}
else if (strcmp(section, "timers") == 0)
{
int v;
/* [timers] section */
/* """""""""""""""" */
if (strcmp(name, "help") == 0)
{
if ((error = !(sscanf(value, "%d", &v) == 1 && v > 0)))
goto out;
else
timers->help = v;
}
else if (strcmp(name, "window") == 0)
{
if ((error = !(sscanf(value, "%d", &v) == 1 && v > 0)))
goto out;
else
timers->winch = v;
}
else if (strcmp(name, "direct_access") == 0)
{
if ((error = !(sscanf(value, "%d", &v) == 1 && v > 0)))
goto out;
else
timers->direct_access = v;
}
}
else if (strcmp(section, "misc") == 0)
{
/* [misc] section */
/* """""""""""""""" */
if (strcmp(name, "default_search_method") == 0)
if (misc->default_search_method == NONE)
{
if (strcmp(value, "prefix") == 0)
misc->default_search_method = PREFIX;
else if (strcmp(value, "fuzzy") == 0)
misc->default_search_method = FUZZY;
else if (strcmp(value, "substring") == 0)
misc->default_search_method = SUBSTRING;
}
}
out:
return error;
}
/* ======================================================================== */
/* Load an .ini format file */
/* filename - path to a file */
/* report - callback can return non-zero to stop, the callback error code */
/* returned from this function. */
/* return - return 0 on success */
/* */
/* This function is public domain. No copyright is claimed. */
/* Jon Mayo April 2011 */
/* ======================================================================== */
int
ini_load(const char * filename, win_t * win, term_t * term, limits_t * limits,
timers_t * timers, misc_t * misc,
int (*report)(win_t * win, term_t * term, limits_t * limits,
timers_t * timers, misc_t * misc, const char * section,
const char * name, char * value))
{
char name[64] = "";
char value[256] = "";
char section[128] = "";
char * s;
FILE * f;
int cnt;
int error;
/* If the filename is empty we skip this phase and use the default values */
/* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
if (filename == NULL)
return 1;
/* We do that if the file is not readable as well */
/* """""""""""""""""""""""""""""""""""""""""""""" */
f = fopen(filename, "r");
if (!f)
return 0; /* Returns success as the presence of this file is optional */
error = 0;
/* Skip blank lines */
/* """""""""""""""" */
while (fscanf(f, "%*[\n]") == 1)
{
}
while (!feof(f))
{
if (fscanf(f, "[%127[^];\n]]", section) == 1)
{
/* Do nothing */
/* """""""""" */
}
else if ((cnt = fscanf(f, " %63[^=;\n] = %255[^;\n]", name, value)))
{
if (cnt == 1)
*value = 0;
for (s = name + strlen(name) - 1; s > name && isspace(*s); s--)
*s = 0;
for (s = value + strlen(value) - 1; s > value && isspace(*s); s--)
*s = 0;
/* Callback function calling */
/* """"""""""""""""""""""""" */
error = report(win, term, limits, timers, misc, section, name, value);
if (error)
goto out;
}
if (fscanf(f, " ;%*[^\n]"))
{
/* To silence the compiler about unused results */
}
/* Skip blank lines */
/* """""""""""""""" */
while (fscanf(f, "%*[\n]") == 1)
{
/* Do nothing */
/* """""""""" */
}
}
out:
fclose(f);
if (error)
fprintf(stderr, "Invalid entry found: %s=%s in %s.\n", name, value,
filename);
return error;
}
/* ======================================================= */
/* Return the full path on the configuration file supposed */
/* to be in the home directory of the user. */
/* NULL is returned if the built path is too large. */
/* ======================================================= */
char *
make_ini_path(char * name, char * base)
{
char * path;
char * home;
long path_max;
long len;
char * conf;
/* Set the prefix of the path from the environment */
/* base can be "HOME" or "PWD". */
/* """"""""""""""""""""""""""""""""""""""""""""""" */
home = getenv(base);
if (home == NULL)
home = "";
path_max = pathconf(".", _PC_PATH_MAX);
len = strlen(home) + strlen(name) + 3;
if (path_max < 0)
path_max = 4096; /* POSIX minimal value */
if (len <= path_max)
{
path = xmalloc(len);
conf = strrchr(name, '/');
if (conf != NULL)
conf++;
else
conf = name;
snprintf(path, 4096, "%s/.%s", home, conf);
}
else
path = NULL;
return path;
}
/* ******************************** */
/* Functions used when sorting tags */
/* ******************************** */
/* =========================================================== */
/* Compare the pin order of two pinned word in the output list */
/* =========================================================== */
int
tag_comp(void * a, void * b)
{
output_t * oa = (output_t *)a;
output_t * ob = (output_t *)b;
if (oa->order == ob->order)
return 0;
return (oa->order < ob->order) ? -1 : 1;
}
/* ======================================================== */
/* Swap the values of two selected words in the output list */
/* ======================================================== */
void
tag_swap(void * a, void * b)
{
output_t * oa = (output_t *)a;
output_t * ob = (output_t *)b;
char * tmp_str;
long tmp_order;
tmp_str = oa->output_str;
oa->output_str = ob->output_str;
ob->output_str = tmp_str;
tmp_order = oa->order;
oa->order = ob->order;
ob->order = tmp_order;
}
/* ***************** */
/* Utility functions */
/* ***************** */
/* =================================================================== */
/* Create a new element to be added to the tst_search_list used by the */
/* search mechanism. */
/* =================================================================== */
sub_tst_t *
sub_tst_new(void)
{
sub_tst_t * elem = xmalloc(sizeof(sub_tst_t));
elem->size = 64;
elem->count = 0;
elem->array = xmalloc(elem->size * sizeof(tst_node_t));
return elem;
}
/* ======================================== */
/* Emit a small (visual) beep warn the user */
/* ======================================== */
void
beep(toggle_t * toggle)
{
struct timespec ts, rem;
int rc;
if (!toggle->visual_bell)
fputc('\a', stdout);
else
{
tputs(TPARM1(cursor_visible), 1, outch);
ts.tv_sec = 0;
ts.tv_nsec = 200000000;
errno = 0;
rc = nanosleep(&ts, &rem);
while (rc < 0 && errno == EINTR)
{
errno = 0;
rc = nanosleep(&rem, &rem);
}
tputs(TPARM1(cursor_invisible), 1, outch);
}
}
/* ======================================================================== */
/* Update the bitmap associated with a word. This bitmap indicates the */
/* positions of the UFT-8 glyphs of the search buffer in each word. */
/* The disp_word function will use it to display these special characters. */
/* ======================================================================== */
void
update_bitmaps(search_mode_t mode, search_data_t * data,
bitmap_affinity_t affinity)
{
long i, j, n; /* work variables */
long lmg; /* position of the last matching glyph of the search buffer *
* in a word */
long sg; /* index going from lmg backward to 0 of the tested glyphs *
* of the search buffer (searched glyph) */
long bm_len; /* number of chars taken by the bitmask */
char * start; /* pointer on the position of the matching position *
* of the last search buffer glyph in the word */
char * bm; /* the word's current bitmap */
char * str; /* copy of the current word put in lower case */
char * str_orig; /* oiginal version of the word */
char * first_glyph;
char * sb_orig = data->buf; /* sb: search buffer */
char * sb;
long * o = data->utf8_off_a; /* array of the offsets of the search *
* buffer glyphs */
long * l = data->utf8_len_a; /* array of the lengths in bytes of the *
* search buffer glyphs */
long last = data->utf8_len - 1; /* offset of the last glyph in the *
* search buffer */
long badness = 0; /* number of 0s between two 1s */
best_matches_count = 0;
if (mode == FUZZY || mode == SUBSTRING)
{
first_glyph = xmalloc(5);
if (mode == FUZZY)
{
sb = xstrdup(sb_orig);
utf8_strtolower(sb, sb_orig);
}
else
sb = sb_orig;
for (i = 0; i < matches_count; i++)
{
n = matching_words_a[i];
str_orig = xstrdup(word_a[n].str + daccess.flength);
/* We need to remove the trailing spaces to use the */
/* following algorithm */
/* .len holds the original length in bytes of the word */
/* """""""""""""""""""""""""""""""""""""""""""""""""""" */
str_orig[word_a[n].len] = '\0';
bm_len = (word_a[n].mb - daccess.flength) / CHAR_BIT + 1;
bm = word_a[n].bitmap;
/* In fuzzy search mode str is converted in lowercases */
/* for comparison reason. */
/* """"""""""""""""""""""""""""""""""""""""""""""""""" */
if (mode == FUZZY)
{
str = xstrdup(str_orig);
utf8_strtolower(str, str_orig);
}
else
str = str_orig;
start = str;
lmg = 0;
/* starts points to the first UTF-8 glyph og the word */
/* """""""""""""""""""""""""""""""""""""""""""""""""" */
while ((size_t)(start - str) < word_a[n].len - daccess.flength)
{
/* Reset the bitmap */
/* """""""""""""""" */
memset(bm, '\0', bm_len);
/* Compare the glyph pointed to by start to the last glyph of */
/* the search buffer, the aim is to pinte to the first */
/* occurrence of the last glyph of it. */
/* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
if (memcmp(start, sb + o[last], l[last]) == 0)
{
char * p; /* Pointer to the beginning of an UTF-8 glyph in *
* the potential lowercase version of the word */
if (last == 0)
{
/* There is only one glyph in the search buffer, we can */
/* stop here. */
/* """""""""""""""""""""""""""""""""""""""""""""""""""" */
BIT_ON(bm, lmg);
if (affinity != END_AFFINITY)
break;
}
/* If the search buffer contains more than one glyph, we need */
/* to search the first combination which match the buffer in */
/* the word. */
/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
p = start;
j = last; /* j counts the number of glyphs in the search buffer *
* not found in the word */
/* Proceed backwards from the position of last glyph of the search */
/* to check if all the previous glyphs can be fond before in the */
/* word. If not try to find the next position of this last glyph */
/* in the word. */
/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
sg = lmg;
while (j > 0 && (p = utf8_prev(str, p)) != NULL)
{
if (memcmp(p, sb + o[j - 1], l[j - 1]) == 0)
{
BIT_ON(bm, sg - 1);
j--;
}
else if (mode == SUBSTRING)
break;
sg--;
}
/* All the glyphs have been found */
/* """""""""""""""""""""""""""""" */
if (j == 0)
{
BIT_ON(bm, lmg);
if (affinity != END_AFFINITY)
break;
}
}
lmg++;
start = utf8_next(start);
}
if (mode == FUZZY)
{
size_t utf8_index;
free(str);
/* We know that the first glyph is part of the pattern, so */
/* highlight it if it is not and un-highlight the next occurrence */
/* that must be here because this word has already been filtered */
/* by select_starting_pattern() */
/* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
if (affinity == START_AFFINITY)
{
char *ptr1, *ptr2;
long i = 1;
long utf8_len;
first_glyph = utf8_strprefix(first_glyph, word_a[n].str, 1,
&utf8_len);
if (!BIT_ISSET(word_a[n].bitmap, 0))
{
BIT_ON(word_a[n].bitmap, 0);
ptr1 = word_a[n].str;
while ((ptr2 = utf8_next(ptr1)) != NULL)
{
if (memcmp(ptr2, first_glyph, utf8_len) == 0)
{
if (BIT_ISSET(word_a[n].bitmap, i))
{
BIT_OFF(word_a[n].bitmap, i);
break;
}
else
ptr1 = ptr2;
}
else
ptr1 = ptr2;
i++;
}
}
}
/* Compute the number of 'holes' in the bitmap to determine the */
/* badness of a match. Th goal is to put the cursor on the word */
/* with the smallest badness. */
/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
utf8_index = 0;
j = 0;
badness = 0;
while (utf8_index < word_a[n].mb
&& !BIT_ISSET(word_a[n].bitmap, utf8_index))
utf8_index++;
while (utf8_index < word_a[n].mb)
{
if (!BIT_ISSET(word_a[n].bitmap, utf8_index))
badness++;
else
j++;
/* Stop here if all the possible bits has been checked as they */
/* cannot be more numerous than the number of UTF-8 glyphs in */
/* the search buffer. */
/* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
if (j == data->utf8_len)
break;
utf8_index++;
}
}
free(str_orig);
if (search_mode == FUZZY)
{
/* When the badness is zero (best match), add the word position. */
/* at the end of a special array which will be used to move the. */
/* cursor among this category of words. */
/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
if (badness == 0)
{
if (best_matches_count == best_matching_words_a_size)
{
best_matching_words_a = xrealloc(best_matching_words_a,
(best_matching_words_a_size + 16)
* sizeof(long));
best_matching_words_a_size += 16;
}
best_matching_words_a[best_matches_count] = n;
best_matches_count++;
}
}
}
if (mode == FUZZY)
free(sb);
free(first_glyph);
}
else if (mode == PREFIX)
{
for (i = 0; i < matches_count; i++)
{
n = matching_words_a[i];
bm = word_a[n].bitmap;
bm_len = (word_a[n].mb - daccess.flength) / CHAR_BIT + 1;
memset(bm, '\0', bm_len);
for (j = 0; j <= last; j++)
BIT_ON(bm, j);
}
}
}
/* ====================================================== */
/* Find the next word index in the list of matching words */
/* returns -1 of not found. */
/* ====================================================== */
long
find_next_matching_word(long * array, long nb, long value, long * index)
{
long left = 0, right = nb, middle;
/* Use the cached value when possible */
/* """""""""""""""""""""""""""""""""" */
if (*index >= 0 && *index < nb - 1 && array[*index] == value)
return (array[++(*index)]);
if (nb > 0)
{
/* bisection search */
/* """""""""""""""" */
while (left < right)
{
middle = (left + right) / 2;
if (value < array[middle])
right = middle;
else
left = middle + 1;
}
if (left < nb - 1)
{
*index = left;
return array[*index];
}
else
{
if (value > array[nb - 1])
{
*index = -1;
return *index;
}
else
{
*index = nb - 1;
return array[*index];
}
}
}
else
{
*index = -1;
return *index;
}
}
/* ========================================================== */
/* Find the previous word index in the list of matching words */
/* returns -1 of not found. */
/* ========================================================== */
long
find_prev_matching_word(long * array, long nb, long value, long * index)
{
long left = 0, right = nb, middle;
/* Use the cached value when possible */
/* """""""""""""""""""""""""""""""""" */
if (*index > 0 && array[*index] == value)
return (array[--(*index)]);
if (nb > 0)
{
/* bisection search */
/* """""""""""""""" */
while (left < right)
{
middle = (left + right) / 2;
if (array[middle] == value)
{
if (middle > 0)
{
*index = middle - 1;
return array[*index];
}
else
{
*index = -1;
return *index;
}
}
if (value < array[middle])
right = middle;
else
left = middle + 1;
}
if (left > 0)
{
*index = left - 1;
return array[*index];
}
else
{
*index = -1;
return *index;
}
}
else
{
*index = -1;
return *index;
}
}
/* ================================================================ */
/* Remove all traces of the matched words and redisplay the windows */
/* ================================================================ */
void
clean_matches(search_data_t * search_data, long size)
{
sub_tst_t * sub_tst_data;
ll_node_t * fuzzy_node;
long i;
/* Clean the list of lists data-structure containing the search levels */
/* Note that the first element of the list is never deleted. */
/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
if (tst_search_list)
{
fuzzy_node = tst_search_list->tail;
while (tst_search_list->len > 1)
{
sub_tst_data = (sub_tst_t *)(fuzzy_node->data);
free(sub_tst_data->array);
free(sub_tst_data);
ll_delete(tst_search_list, tst_search_list->tail);
fuzzy_node = tst_search_list->tail;
}
sub_tst_data = (sub_tst_t *)(fuzzy_node->data);
sub_tst_data->count = 0;
}
search_data->fuzzy_err = 0;
/* Clean the search buffer */
/* """"""""""""""""""""""" */
memset(search_data->buf, '\0', size - daccess.flength);
search_data->len = 0;
search_data->utf8_len = 0;
search_data->only_ending = 0;
search_data->only_starting = 0;
/* Clean the match flags and bitmaps */
/* """"""""""""""""""""""""""""""""" */
for (i = 0; i < matches_count; i++)
{
long n = matching_words_a[i];
word_a[n].is_matching = 0;
memset(word_a[n].bitmap, '\0',
(word_a[n].mb - daccess.flength) / CHAR_BIT + 1);
}
matches_count = 0;
}
/* ************************** */
/* Terminal utility functions */
/* ************************** */
/* ===================================================================== */
/* outch is a function version of putchar that can be passed to tputs as */
/* a routine to call. */
/* ===================================================================== */
int
#ifdef __sun
outch(char c)
#else
outch(int c)
#endif
{
putchar(c);
return 1;
}
/* =============================================== */
/* Set the terminal in non echo/non canonical mode */
/* wait for at least one byte, no timeout. */
/* =============================================== */
void
setup_term(int const fd)
{
/* Save the terminal parameters and configure it in row mode */
/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
tcgetattr(fd, &old_in_attrs);
new_in_attrs.c_iflag = 0;
new_in_attrs.c_oflag = old_in_attrs.c_oflag;
new_in_attrs.c_cflag = old_in_attrs.c_cflag;
new_in_attrs.c_lflag = old_in_attrs.c_lflag & ~(ICANON | ECHO | ISIG);
new_in_attrs.c_cc[VMIN] = 1; /* wait for at least 1 byte */
new_in_attrs.c_cc[VTIME] = 0; /* no timeout */
tcsetattr(fd, TCSANOW, &new_in_attrs);
}
/* ===================================== */
/* Set the terminal in its previous mode */
/* ===================================== */
void
restore_term(int const fd)
{
tcsetattr(fd, TCSANOW, &old_in_attrs);
}
/* ============================================== */
/* Get the terminal numbers of lines and columns */
/* Assume that the TIOCGWINSZ, ioctl is available */
/* Defaults to 80x24 */
/* ============================================== */
void
get_terminal_size(int * const r, int * const c)
{
struct winsize ws;
if (ioctl(0, TIOCGWINSZ, &ws) == 0)
{
*r = ws.ws_row;
*c = ws.ws_col;
}
else
{
*r = tigetnum("lines");
*c = tigetnum("cols");
if (*r < 0 || *c < 0)
{
*r = 80;
*c = 24;
}
}
}
/* ====================================================== */
/* Get cursor position the terminal */
/* Assume that the Escape sequence ESC [ 6 n is available */
/* ====================================================== */
int
get_cursor_position(int * const r, int * const c)
{
char buf[32];
size_t i = 0;
/* Report cursor location */
/* """""""""""""""""""""" */
if (write(1, "\x1b[6n", 4) != 4)
return -1;
/* Read the response: ESC [ rows ; cols R */
/* """""""""""""""""""""""""""""""""""""" */
while (i < sizeof(buf) - 1)
{
if (read(0, buf + i, 1) != 1)
break;
if (buf[i] == 'R')
break;
i++;
}
buf[i] = '\0';
/* Parse it. */
/* """"""""" */
if (buf[0] != 0x1b || buf[1] != '[')
return -1;
if (sscanf(buf + 2, "%d;%d", r, c) != 2)
return -1;
return 1;
}
/* ===================================================== */
/* Returns 1 if a string is empty or only made of spaces */
/* ===================================================== */
int
isempty(const char * s)
{
while (*s != '\0')
{
if (my_isprint(*s) && *s != ' ' && *s != '\t')
return 0;
s++;
}
return 1;
}
/* ======================================================================== */
/* Parse a regular expression based selector. */
/* The string to parse is bounded by a delimiter so we must parse something */
/* like: <delim><regex string><delim> as in /a.*b/ by example. */
/* */
/* str (in) delimited string to parse */
/* filter (in) INCLUDE_FILTER or EXCLUDE_FILTER */
/* inc_regex_list (out) INCLUDE_FILTER or EXCLUDE_FILTER */
/* regular expression if the filter is INCLUDE_FILTER */
/* exc_regex_list (out) INCLUDE_FILTER or EXCLUDE_FILTER */
/* regular expression if the filter is EXCLUDE_FILTER */
/* ======================================================================== */
void
parse_regex_selector_part(char * str, filters_t filter, ll_t ** inc_regex_list,
ll_t ** exc_regex_list)
{
regex_t * regex;
str[strlen(str) - 1] = '\0';
regex = xmalloc(sizeof(regex_t));
if (regcomp(regex, str + 1, REG_EXTENDED | REG_NOSUB) == 0)
{
if (filter == INCLUDE_FILTER)
{
if (*inc_regex_list == NULL)
*inc_regex_list = ll_new();
ll_append(*inc_regex_list, regex);
}
else
{
if (*exc_regex_list == NULL)
*exc_regex_list = ll_new();
ll_append(*exc_regex_list, regex);
}
}
}
/* ===================================================================== */
/* Parse a description string */
/* <letter><range1>,<range2>,... */
/* <range> is n1-n2 | n1 where n1 starts with 1 */
/* */
/* <letter> is a|A|s|S|r|R|u|U where */
/* a|A is for Add */
/* s|S is for Select (same as Add) */
/* r|R is for Remove */
/* d|D is for Deselect (same as Remove) */
/* */
/* str (in) string to parse */
/* filter (out) is INCLUDE_FILTER or EXCLUDE_FILTER according */
/* to <letter> */
/* unparsed (out) is empty on success and contains the unparsed */
/* part on failure */
/* inc_interval_list (out) is a list of the interval of elements to */
/* be included. */
/* inc_regex_list (out) is a list of extended regular expressions of */
/* elements to be included. */
/* exc_interval_list (out) is a list of the interval of elements to be */
/* excluded. */
/* exc_regex_list (out) is a list of extended interval of elements to */
/* be excluded. */
/* ===================================================================== */
void
parse_selectors(char * str, filters_t * filter, char * unparsed,
ll_t ** inc_interval_list, ll_t ** inc_regex_list,
ll_t ** exc_interval_list, ll_t ** exc_regex_list,
langinfo_t * langinfo)
{
char mark; /* Value to set */
char c;
size_t start = 1; /* column string offset in the parsed string */
size_t first, second; /* range starting and ending values */
char * ptr; /* pointer to the remaining string to parse */
interval_t * interval;
/* Replace the UTF-8 ascii representation in the selector by */
/* their binary values. */
/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
utf8_interpret(str, langinfo);
/* Get the first character to see if this is */
/* an additive or restrictive operation. */
/* """"""""""""""""""""""""""""""""""""""""" */
if (sscanf(str, "%c", &c) == 0)
return;
switch (c)
{
case 'i':
case 'I':
*filter = INCLUDE_FILTER;
mark = INCLUDE_MARK;
break;
case 'e':
case 'E':
*filter = EXCLUDE_FILTER;
mark = EXCLUDE_MARK;
break;
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
*filter = INCLUDE_FILTER;
mark = INCLUDE_MARK;
start = 0;
break;
default:
if (!isgraph(c))
return;
*filter = INCLUDE_FILTER;
mark = INCLUDE_MARK;
start = 0;
break;
}
/* Set ptr to the start of the interval list to parse */
/* """""""""""""""""""""""""""""""""""""""""""""""""" */
ptr = str + start;
first = second = -1;
/* Scan the comma separated ranges */
/* """"""""""""""""""""""""""""""" */
while (*ptr)
{
size_t swap;
int is_range = 0;
char delim1, delim2 = '\0';
char * oldptr;
oldptr = ptr;
while (*ptr && *ptr != ',')
{
if (*ptr == '-')
is_range = 1;
ptr++;
}
/* Forbid the trailing comma (ex: xxx,) */
/* """""""""""""""""""""""""""""""""""" */
if (*ptr == ',' && (*(ptr + 1) == '\0' || *(ptr + 1) == '-'))
{
my_strcpy(unparsed, ptr);
return;
}
/* Forbid the empty patterns (ex: xxx,,yyy) */
/* """""""""""""""""""""""""""""""""""""""" */
if (oldptr == ptr)
{
my_strcpy(unparsed, ptr);
return;
}
/* Mark the end of the interval found */
/* """""""""""""""""""""""""""""""""" */
if (*ptr)
*ptr++ = '\0';
/* If the regex contains at least three characters then delim1 */
/* and delim2 point to the first and last delimiter of the */
/* regular expression. Ex /abc/ */
/* ^ ^ */
/* | | */
/* delim1 delim2 */
/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
delim1 = *(str + start);
if (*ptr == '\0')
delim2 = *(ptr - 1);
else if (ptr > str + start + 2)
delim2 = *(ptr - 2);
/* Check is we have found a well describes regular expression */
/* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
if (ptr > str + start + 2 && delim1 == delim2 && isgraph(delim1)
&& isgraph(delim2) && !isdigit(delim1) && !isdigit(delim2))
{
/* Process the regex */
/* """"""""""""""""" */
parse_regex_selector_part(str + start, *filter, inc_regex_list,
exc_regex_list);
/* Adjust the start of the new interval to read in the initial string */
/* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
start = ptr - str;
continue;
}
else if (delim2 != '\0' && (!isdigit(delim1) || !isdigit(delim2)))
{
/* Both delimiter must be numeric if delim2 exist */
/* """""""""""""""""""""""""""""""""""""""""""""" */
my_strcpy(unparsed, str + start);
return;
}
if (is_range)
{
int rc;
int pos;
rc = sscanf(str + start, "%zu-%zu%n", &first, &second, &pos);
if (rc != 2 || *(str + start + pos) != '\0')
{
my_strcpy(unparsed, str + start);
return;
}
if (first < 1 || second < 1)
{
/* Both interval boundaries must be strictly positive */
/* """""""""""""""""""""""""""""""""""""""""""""""""" */
my_strcpy(unparsed, str + start);
return;
}
/* Ensure that the low bound of the interval is lower or equal */
/* to the high one. Swap them if needed. */
/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
interval = interval_new();
if (first > second)
{
swap = first;
first = second;
second = swap;
}
interval->low = first - 1;
interval->high = second - 1;
}
else
{
/* Read the number given */
/* """"""""""""""""""""" */
if (sscanf(str + start, "%zu", &first) != 1)
{
my_strcpy(unparsed, str + start);
return;
}
interval = interval_new();
interval->low = interval->high = first - 1;
}
/* Adjust the start of the new interval to read in the initial string */
/* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
start = ptr - str;
/* Add the new interval to the correct list */
/* """""""""""""""""""""""""""""""""""""""" */
if (mark == EXCLUDE_MARK)
{
if (*exc_interval_list == NULL)
*exc_interval_list = ll_new();
ll_append(*exc_interval_list, interval);
}
else
{
if (*inc_interval_list == NULL)
*inc_interval_list = ll_new();
ll_append(*inc_interval_list, interval);
}
}
/* If we are here, then all the intervals have be successfully parsed */
/* Ensure that the unparsed string is empty. */
/* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
*unparsed = '\0';
}
/* ======================================================== */
/* Parse the sed like string passed as argument to -S/-I/-E */
/* ======================================================== */
int
parse_sed_like_string(sed_t * sed)
{
char sep;
char * first_sep_pos;
char * last_sep_pos;
char * buf;
long index;
int icase;
char c;
if (strlen(sed->pattern) < 4)
return 0;
/* Get the separator (the 1st character) */
/* """"""""""""""""""""""""""""""""""""" */
buf = xstrdup(sed->pattern);
sep = buf[0];
/* Space like separators are not permitted */
/* """"""""""""""""""""""""""""""""""""""" */
if (isspace(sep))
goto err;
/* Get the extended regular expression */
/* """"""""""""""""""""""""""""""""""" */
if ((first_sep_pos = strchr(buf + 1, sep)) == NULL)
goto err;
*first_sep_pos = '\0';
/* Get the substitution string */
/* """"""""""""""""""""""""""" */
if ((last_sep_pos = strchr(first_sep_pos + 1, sep)) == NULL)
goto err;
*last_sep_pos = '\0';
sed->substitution = xstrdup(first_sep_pos + 1);
/* Get the global indicator (trailing g) */
/* and the visual indicator (trailing v) */
/* and the stop indicator (trailing s) */
/* """"""""""""""""""""""""""""""""""""" */
sed->global = sed->visual = icase = 0;
index = 1;
while ((c = *(last_sep_pos + index)) != '\0')
{
if (c == 'g')
sed->global = 1;
else if (c == 'v')
sed->visual = 1;
else if (c == 's')
sed->stop = 1;
else if (c == 'i')
icase = 1;
else
goto err;
index++;
}
/* Empty regular expression ? */
/* """""""""""""""""""""""""" */
if (*(buf + 1) == '\0')
goto err;
/* Compile the regular expression and abort on failure */
/* """"""""""""""""""""""""""""""""""""""""""""""""""" */
if (regcomp(&(sed->re), buf + 1,
!icase ? REG_EXTENDED : (REG_EXTENDED | REG_ICASE))
!= 0)
goto err;
free(buf);
return 1;
err:
free(buf);
return 0;
}
/* ===================================================================== */
/* Utility function used by replace to expand the replacement string */
/* IN: */
/* orig: matching part of the original string to be replaced */
/* repl: string containing the replacement directives */
/* subs_a: array of ranges containing the start and end offset */
/* of the remembered parts of the strings referenced by */
/* the sequence \n where n is in [1,10] */
/* match_start/end: offset in orig for the current matched string */
/* subs_nb: number of elements containing significant values in */
/* the array described above */
/* match: current match number in the original string */
/* */
/* OUT: */
/* The modified string according to the content of repl */
/* ===================================================================== */
char *
build_repl_string(char * orig, char * repl, long match_start, long match_end,
range_t * subs_a, long subs_nb, long match)
{
size_t rsize = 0;
size_t allocated = 16;
char * str = xmalloc(allocated);
int special = 0;
long offset = match * subs_nb; /* offset of the 1st sub *
* corresponding to the match */
if (*repl == '\0')
str = xstrdup("");
else
while (*repl)
{
switch (*repl)
{
case '\\':
if (special)
{
if (allocated == rsize)
str = xrealloc(str, allocated += 16);
str[rsize] = '\\';
rsize++;
str[rsize] = '\0';
special = 0;
}
else
special = 1;
break;
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
if (special)
{
if ((*repl) - '0' <= subs_nb)
{
long index = (*repl) - '0' - 1 + offset;
long delta = subs_a[index].end - subs_a[index].start;
if (allocated <= rsize + delta)
str = xrealloc(str, allocated += (delta + 16));
memcpy(str + rsize, orig + subs_a[index].start, delta);
rsize += delta;
str[rsize] = '\0';
}
special = 0;
}
else
{
if (allocated == rsize)
str = xrealloc(str, allocated += 16);
str[rsize] = *repl;
rsize++;
str[rsize] = '\0';
}
break;
case '&':
if (!special)
{
long delta = match_end - match_start;
if (allocated <= rsize + delta)
str = xrealloc(str, allocated += (delta + 16));
memcpy(str + rsize, orig + match_start, delta);
rsize += delta;
str[rsize] = '\0';
break;
}
/* No break here, '&' must be treated as a normal */
/* character when protected. */
/* '''''''''''''''''''''''''''''''''''''''''''''' */
default:
special = 0;
if (allocated == rsize)
str = xrealloc(str, allocated += 16);
str[rsize] = *repl;
rsize++;
str[rsize] = '\0';
}
repl++;
}
return str;
}
/* ====================================================================== */
/* Replace the part of a string matched by an extender regular expression */
/* by the substitution string */
/* The regex used must have been previously compiled */
/* */
/* orig: original string */
/* sed: composite variable containing the regular expression, a */
/* substitution string and various other informations. */
/* output: destination buffer */
/* */
/* RC: */
/* return 1 if the replacement has been successful else 0 */
/* */
/* NOTE: */
/* uses the global variable word_buffer */
/* ====================================================================== */
int
replace(char * orig, sed_t * sed)
{
size_t match_nb = 0; /* number of matches in the original string */
int sub_nb = 0; /* number of remembered matches in the *
* original sting */
size_t target_len = 0; /* length of the resulting string */
size_t subs_max = 0;
if (*orig == '\0')
return 1;
range_t * matches_a = xmalloc(strlen(orig) * sizeof(range_t));
range_t * subs_a = xmalloc(10 * sizeof(range_t));
const char * p = orig; /* points to the end of the previous match. */
regmatch_t m[10]; /* array containing the start/end offsets *
* of the matches found. */
while (1)
{
size_t i = 0;
size_t match; /* current match index */
size_t index = 0; /* current char index in the original string */
int nomatch = 0; /* equals to 1 when there is no more matching */
char * exp_repl; /* expanded replacement string */
if (*p == '\0')
nomatch = 1;
else
nomatch = regexec(&sed->re, p, 10, m, 0);
if (nomatch)
{
if (match_nb > 0)
{
for (index = 0; index < matches_a[0].start; index++)
word_buffer[target_len++] = orig[index];
for (match = 0; match < match_nb; match++)
{
size_t len;
size_t end;
exp_repl = build_repl_string(orig, sed->substitution,
matches_a[match].start,
matches_a[match].end, subs_a, subs_max,
match);
len = strlen(exp_repl);
my_strcpy(word_buffer + target_len, exp_repl);
target_len += len;
free(exp_repl);
index += matches_a[match].end - matches_a[match].start;
if (match < match_nb - 1 && sed->global)
end = matches_a[match + 1].start;
else
end = strlen(orig);
while (index < end)
word_buffer[target_len++] = orig[index++];
word_buffer[target_len] = '\0';
if (!sed->global)
break;
}
}
else
{
my_strcpy(word_buffer, orig);
return 0;
}
return nomatch;
}
subs_max = 0;
for (i = 0; i < 10; i++)
{
size_t start;
size_t finish;
if (m[i].rm_so == -1)
break;
start = m[i].rm_so + (p - orig);
finish = m[i].rm_eo + (p - orig);
if (i == 0)
{
matches_a[match_nb].start = start;
matches_a[match_nb].end = finish;
match_nb++;
if (match_nb > utf8_strlen(orig))
goto fail;
}
else
{
subs_a[sub_nb].start = start;
subs_a[sub_nb].end = finish;
sub_nb++;
subs_max++;
}
}
if (m[0].rm_eo > 0)
p += m[0].rm_eo;
else
p++; /* Empty match */
}
fail:
return 0;
}
/* ============================================================ */
/* Remove all ANSI color codes from s and puts the result in d. */
/* Memory space for d must have been allocated before. */
/* ============================================================ */
void
strip_ansi_color(char * s, toggle_t * toggle)
{
char * p = s;
long len = strlen(s);
while (*s != '\0')
{
/* Remove a sequence of \x1b[...m from s */
/* """"""""""""""""""""""""""""""""""""" */
if ((*s == 0x1b) && (*(s + 1) == '['))
{
while ((*s != '\0') && (*s++ != 'm'))
{
/* Do nothing */
}
}
/* Convert a single \x1b in '.' */
/* """""""""""""""""""""""""""" */
else if (*s == 0x1b)
{
if (toggle->blank_nonprintable && len > 1)
*s++ = ' ';
else
*s++ = '.';
p++;
}
/* No ESC char, we can move on */
/* """"""""""""""""""""""""""" */
else
*p++ = *s++;
}
*p = '\0';
}
/* ================================================================ */
/* Callback to add a word index in the sorted list of matched words */
/* ================================================================ */
int
set_matching_flag(void * elem)
{
ll_t * list = (ll_t *)elem;
ll_node_t * node = list->head;
while (node)
{
size_t pos;
pos = *(size_t *)(node->data);
if (word_a[pos].is_selectable)
word_a[pos].is_matching = 1;
insert_sorted_index(&matching_words_a, &matching_words_a_size,
&matches_count, pos);
node = node->next;
}
return 1;
}
/* ======================================================================= */
/* Callback function used by tst_traverse */
/* Iterate the linked list attached to the string containing the index of */
/* the words in the input flow. Each page number is then added in a sorted */
/* array avoiding duplications keeping the array sorted. */
/* Mark the identified words as a matching word. */
/* ======================================================================= */
int
tst_cb(void * elem)
{
/* The data attached to the string in the tst is a linked list of position */
/* of the string in the input flow, This list is naturally sorted */
/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
ll_t * list = (ll_t *)elem;
ll_node_t * node = list->head;
while (node)
{
size_t pos;
pos = *(long *)(node->data);
word_a[pos].is_matching = 1;
insert_sorted_index(&matching_words_a, &matching_words_a_size,
&matches_count, pos);
node = node->next;
}
return 1;
}
/* ======================================================================= */
/* Callback function used by tst_traverse */
/* Iterate the linked list attached to the string containing the index of */
/* the words in the input flow. Each page number is then used to determine */
/* the lower page greater than the cursor position */
/* ----------------------------------------------------------------------- */
/* This is a special version of tst_cb which permit to find the first word */
/* ----------------------------------------------------------------------- */
/* Require new_current to be set to count - 1 at start */
/* Update new_current to the smallest greater position than current */
/* ======================================================================= */
int
tst_cb_cli(void * elem)
{
long n = 0;
int rc = 0;
/* The data attached to the string in the tst is a linked list of position */
/* of the string in the input flow, This list is naturally sorted */
/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
ll_t * list = (ll_t *)elem;
ll_node_t * node = list->head;
while (n++ < list->len)
{
long pos;
pos = *(long *)(node->data);
word_a[pos].is_matching = 1;
insert_sorted_index(&matching_words_a, &matching_words_a_size,
&matches_count, pos);
/* We already are at the last word, report the finding */
/* """"""""""""""""""""""""""""""""""""""""""""""""""" */
if (pos == count - 1)
return 1;
/* Only consider the indexes above the current cursor position */
/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
if (pos >= current) /* Enable the search of the current word */
{
/* As the future new current index has been set to the highest possible */
/* value, each new possible position can only improve the estimation */
/* we set rc to 1 to mark that */
/* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
if (pos < new_current)
{
new_current = pos;
rc = 1;
}
}
node = node->next;
}
return rc;
}
/* *************** */
/* Input functions */
/* *************** */
/* =============================================== */
/* Non delay reading of a scancode. */
/* Update a scancodes buffer and return its length */
/* in bytes. */
/* =============================================== */
int
get_scancode(unsigned char * s, size_t max)
{
int c;
size_t i = 1;
struct termios original_ts, nowait_ts;
if ((c = my_fgetc(stdin)) == EOF)
return 0;
/* Initialize the string with the first byte */
/* """"""""""""""""""""""""""""""""""""""""" */
memset(s, '\0', max);
s[0] = c;
/* 0x1b (ESC) has been found, proceed to check if additional codes */
/* are available */
/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
if (c == 0x1b || c > 0x80)
{
/* Save the terminal parameters and configure getchar() */
/* to return immediately */
/* """""""""""""""""""""""""""""""""""""""""""""""""""" */
tcgetattr(0, &original_ts);
nowait_ts = original_ts;
nowait_ts.c_lflag &= ~ISIG;
nowait_ts.c_cc[VMIN] = 0;
nowait_ts.c_cc[VTIME] = 0;
tcsetattr(0, TCSADRAIN, &nowait_ts);
/* Check if additional code is available after 0x1b */
/* """""""""""""""""""""""""""""""""""""""""""""""" */
if ((c = my_fgetc(stdin)) != EOF)
{
s[1] = c;
i = 2;
while (i < max && (c = my_fgetc(stdin)) != EOF)
s[i++] = c;
}
else
{
/* There isn't a new code, this mean 0x1b came from ESC key */
/* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
}
/* Restore the save terminal parameters */
/* """""""""""""""""""""""""""""""""""" */
tcsetattr(0, TCSADRAIN, &original_ts);
/* Ignore EOF when a scancode contains an escape sequence. */
/* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */
clearerr(stdin);
}
return i;
}
/* ===================================================================== */
/* Get bytes from stdin. If the first byte is the leading character of a */
/* UTF-8 glyph, the following ones are also read. */
/* The utf8_get_length function is used to get the number of bytes of */
/* the character. */
/* ===================================================================== */
int
get_bytes(FILE * input, char * utf8_buffer, langinfo_t * langinfo)
{
int byte;
int last = 0;
int n;
/* Read the first byte */
/* """"""""""""""""""" */
byte = my_fgetc(input);
if (byte == EOF)
return EOF;
utf8_buffer[last++] = byte;
/* Check if we need to read more bytes to form a sequence */
/* and put the number of bytes of the sequence in last. */
/* """""""""""""""""""""""""""""""""""""""""""""""""""""" */
if (langinfo->utf8 && ((n = utf8_get_length(byte)) > 1))
{
while (last < n && (byte = my_fgetc(input)) != EOF && (byte & 0xc0) == 0x80)
utf8_buffer[last++] = byte;
if (byte == EOF)
return EOF;
}
utf8_buffer[last] = '\0';
/* Replace an invalid UTF-8 byte sequence by a single dot. */
/* In this case the original sequence is lost (unsupported */
/* encoding). */
/* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
if (langinfo->utf8 && !utf8_validate(utf8_buffer, last))
{
byte = utf8_buffer[0] = '.';
utf8_buffer[1] = '\0';
}
return byte;
}
/* ==========================================================================*/
/* Expand the string str by replacing all its embedded special characters by */
/* their corresponding escape sequence */
/* dest must be long enough to contain the expanded string */
/* ========================================================================= */
size_t
expand(char * src, char * dest, langinfo_t * langinfo, toggle_t * toggle)
{
char c;
int n;
int all_spaces = 1;
char * ptr = dest;
size_t len = 0;
while ((c = *(src++)))
{
/* UTF-8 codepoints take more than on character */
/* """""""""""""""""""""""""""""""""""""""""""" */
if ((n = utf8_get_length(c)) > 1)
{
all_spaces = 0;
if (langinfo->utf8)
/* If the locale is UTF-8 aware, copy src into ptr. */
/* """""""""""""""""""""""""""""""""""""""""""""""" */
do
{
*(ptr++) = c;
len++;
} while (--n && (c = *(src++)));
else
{
/* If not, ignore the bytes composing the UTF-8 */
/* glyph and replace them with a single '.'. */
/* """""""""""""""""""""""""""""""""""""""""""" */
do
{
/* Skip this byte. */
/* ''''''''''''''' */
} while (--n && ('\0' != *(src++)));
*(ptr++) = '.';
len++;
}
}
else
/* This is not an UTF-8 glyph */
/* """""""""""""""""""""""""" */
switch (c)
{
case '\a':
*(ptr++) = '\\';
*(ptr++) = 'a';
len += 2;
all_spaces = 0;
break;
case '\b':
*(ptr++) = '\\';
*(ptr++) = 'b';
len += 2;
all_spaces = 0;
break;
case '\t':
*(ptr++) = '\\';
*(ptr++) = 't';
len += 2;
all_spaces = 0;
break;
case '\n':
*(ptr++) = '\\';
*(ptr++) = 'n';
len += 2;
all_spaces = 0;
break;
case '\v':
*(ptr++) = '\\';
*(ptr++) = 'v';
len += 2;
all_spaces = 0;
break;
case '\f':
*(ptr++) = '\\';
*(ptr++) = 'f';
len += 2;
all_spaces = 0;
break;
case '\r':
*(ptr++) = '\\';
*(ptr++) = 'r';
len += 2;
all_spaces = 0;
break;
case '\\':
*(ptr++) = '\\';
*(ptr++) = '\\';
len += 2;
all_spaces = 0;
break;
default:
if (my_isprint(c))
{
*(ptr++) = c;
all_spaces = 0;
}
else
{
if (toggle->blank_nonprintable)
*(ptr++) = ' ';
else
{
*(ptr++) = '.';
all_spaces = 0;
}
}
len++;
}
}
/* If the word contains only spaces, replace them */
/* by underscores so that it can be seen */
/* """""""""""""""""""""""""""""""""""""""""""""" */
if (all_spaces)
memset(dest, ' ', len);
*ptr = '\0'; /* Ensure that dest has a nul terminator */
return len;
}
/* ===================================================================== */
/* get_word(input): return a char pointer to the next word (as a string) */
/* Accept: a FILE * for the input stream */
/* Return: a char * */
/* On Success: the return value will point to a nul-terminated string */
/* On Failure: the return value will be set to NULL */
/* ===================================================================== */
char *
get_word(FILE * input, ll_t * word_delims_list, ll_t * record_delims_list,
char * utf8_buffer, unsigned char * is_last, toggle_t * toggle,
langinfo_t * langinfo, win_t * win, limits_t * limits)
{
char * temp = NULL;
int byte;
long utf8_count = 0; /* count chars used in current allocation */
long wordsize; /* size of current allocation in chars */
int is_dquote; /* double quote presence indicator */
int is_squote; /* single quote presence indicator */
int is_special; /* a character is special after a \ */
/* Skip leading delimiters */
/* """"""""""""""""""""""" */
byte = get_bytes(input, utf8_buffer, langinfo);
while (byte == EOF
|| ll_find(word_delims_list, utf8_buffer, delims_cmp) != NULL)
{
if (byte == EOF)
return NULL;
byte = get_bytes(input, utf8_buffer, langinfo);
}
/* Allocate initial word storage space */
/* """"""""""""""""""""""""""""""""""" */
temp = xmalloc(wordsize = CHARSCHUNK);
/* Start stashing bytes. Stop when we meet a non delimiter or EOF */
/* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
utf8_count = 0;
is_dquote = 0;
is_squote = 0;
is_special = 0;
while (byte != EOF)
{
size_t i = 0;
if (utf8_count >= limits->word_length)
{
fprintf(stderr,
"The length of a word has reached the limit of "
"%ld characters.\n",
limits->word_length);
exit(EXIT_FAILURE);
}
if (byte == '\\' && !is_special)
{
is_special = 1;
goto next;
}
/* Parse special characters */
/* """""""""""""""""""""""" */
if (is_special)
switch (byte)
{
case 'a':
utf8_buffer[0] = byte = '\a';
utf8_buffer[1] = '\0';
break;
case 'b':
utf8_buffer[0] = byte = '\b';
utf8_buffer[1] = '\0';
break;
case 't':
utf8_buffer[0] = byte = '\t';
utf8_buffer[1] = '\0';
break;
case 'n':
utf8_buffer[0] = byte = '\n';
utf8_buffer[1] = '\0';
break;
case 'v':
utf8_buffer[0] = byte = '\v';
utf8_buffer[1] = '\0';
break;
case 'f':
utf8_buffer[0] = byte = '\f';
utf8_buffer[1] = '\0';
break;
case 'r':
utf8_buffer[0] = byte = '\r';
utf8_buffer[1] = '\0';
break;
case 'u':
utf8_buffer[0] = '\\';
utf8_buffer[1] = 'u';
utf8_buffer[2] = '\0';
break;
case '\\':
utf8_buffer[0] = byte = '\\';
utf8_buffer[1] = '\0';
break;
}
else
{
/* Manage double quotes */
/* """""""""""""""""""" */
if (byte == '"' && !is_squote)
is_dquote = !is_dquote;
/* Manage single quotes */
/* """""""""""""""""""" */
if (byte == '\'' && !is_dquote)
is_squote = !is_squote;
}
/* Only consider delimiters when outside quotations */
/* """""""""""""""""""""""""""""""""""""""""""""""" */
if ((!is_dquote && !is_squote)
&& ll_find(word_delims_list, utf8_buffer, delims_cmp) != NULL)
break;
/* We no dot count the significant quotes */
/* """""""""""""""""""""""""""""""""""""" */
if (!is_special
&& ((byte == '"' && !is_squote) || (byte == '\'' && !is_dquote)))
{
is_special = 0;
goto next;
}
/* Feed temp with the content of utf8_buffer */
/* """"""""""""""""""""""""""""""""""""""""" */
while (utf8_buffer[i] != '\0')
{
if (utf8_count >= wordsize - 1)
temp = xrealloc(temp,
wordsize += (utf8_count / CHARSCHUNK + 1) * CHARSCHUNK);
*(temp + utf8_count++) = utf8_buffer[i];
i++;
}
is_special = 0;
next:
byte = get_bytes(input, utf8_buffer, langinfo);
}
/* Nul-terminate the word to make it a string */
/* """""""""""""""""""""""""""""""""""""""""" */
*(temp + utf8_count) = '\0';
/* Replace the UTF-8 ASCII representations in the word just */
/* read by their binary values. */
/* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
utf8_interpret(temp, langinfo);
/* Skip all field delimiters before a record delimiter */
/* """"""""""""""""""""""""""""""""""""""""""""""""""" */
if (ll_find(record_delims_list, utf8_buffer, delims_cmp) == NULL)
{
byte = get_bytes(input, utf8_buffer, langinfo);
while (byte != EOF
&& ll_find(word_delims_list, utf8_buffer, delims_cmp) != NULL
&& ll_find(record_delims_list, utf8_buffer, delims_cmp) == NULL)
byte = get_bytes(input, utf8_buffer, langinfo);
if (langinfo->utf8 && utf8_get_length(utf8_buffer[0]) > 1)
{
size_t pos;
pos = strlen(utf8_buffer);
while (pos > 0)
my_ungetc(utf8_buffer[--pos]);
}
else
my_ungetc(byte);
}
/* Mark it as the last word of a record if its sequence matches a */
/* record delimiter except in tab mode */
/* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
if (byte == EOF
|| ((win->col_mode || win->line_mode)
&& ll_find(record_delims_list, utf8_buffer, delims_cmp) != NULL))
*is_last = 1;
else
*is_last = 0;
/* Remove the ANSI color escape sequences from the word */
/* """""""""""""""""""""""""""""""""""""""""""""""""""" */
strip_ansi_color(temp, toggle);
return temp;
}
/* ========================================================= */
/* Set a foreground color according to terminal capabilities */
/* ========================================================= */
void
set_foreground_color(term_t * term, short color)
{
if (term->color_method == 0 && term->has_setf)
tputs(TPARM2(set_foreground, color), 1, outch);
else if (term->color_method == 1 && term->has_setaf)
tputs(TPARM2(set_a_foreground, color), 1, outch);
}
/* ========================================================= */
/* Set a background color according to terminal capabilities */
/* ========================================================= */
void
set_background_color(term_t * term, short color)
{
if (term->color_method == 0 && term->has_setb)
tputs(TPARM2(set_background, color), 1, outch);
else if (term->color_method == 1 && term->has_setab)
tputs(TPARM2(set_a_background, color), 1, outch);
}
/* ====================================================== */
/* Put a scrolling symbol at the first column of the line */
/* ====================================================== */
void
left_margin_putp(char * s, term_t * term, win_t * win)
{
apply_attr(term, win->shift_attr);
/* We won't print this symbol when not in column mode */
/* """""""""""""""""""""""""""""""""""""""""""""""""" */
if (*s != '\0')
fputs(s, stdout);
tputs(TPARM1(exit_attribute_mode), 1, outch);
}
/* ===================================================== */
/* Put a scrolling symbol at the last column of the line */
/* ===================================================== */
void
right_margin_putp(char * s1, char * s2, langinfo_t * langinfo, term_t * term,
win_t * win, long line, long offset)
{
apply_attr(term, win->bar_attr);
if (term->has_hpa)
tputs(TPARM2(column_address, offset + win->max_width + 1), 1, outch);
else if (term->has_cursor_address)
tputs(TPARM3(cursor_address, term->curs_line + line - 2,
offset + win->max_width + 1),
1, outch);
else if (term->has_parm_right_cursor)
{
fputc('\r', stdout);
tputs(TPARM2(parm_right_cursor, offset + win->max_width + 1), 1, outch);
}
else
{
long i;
fputc('\r', stdout);
for (i = 0; i < offset + win->max_width + 1; i++)
tputs(TPARM1(cursor_right), 1, outch);
}
if (langinfo->utf8)
fputs(s1, stdout);
else
fputs(s2, stdout);
tputs(TPARM1(exit_attribute_mode), 1, outch);
}
/* ************** */
/* Core functions */
/* ************** */
/* ============================================================== */
/* Split the lines of the message given to -m to a linked list of */
/* lines. */
/* Also fill the maximum screen width and the maximum number */
/* of bytes of the longest line */
/* ============================================================== */
void
get_message_lines(char * message, ll_t * message_lines_list,
long * message_max_width, long * message_max_len)
{
char * str;
char * ptr;
char * cr_ptr;
long n;
wchar_t * w = NULL;
*message_max_width = 0;
*message_max_len = 0;
ptr = message;
/* For each line terminated with a EOL character */
/* """"""""""""""""""""""""""""""""""""""""""""" */
while (*ptr != '\0' && (cr_ptr = strchr(ptr, '\n')) != NULL)
{
if (cr_ptr > ptr)
{
str = xmalloc(cr_ptr - ptr + 1);
str[cr_ptr - ptr] = '\0';
memcpy(str, ptr, cr_ptr - ptr);
}
else
str = xstrdup("");
ll_append(message_lines_list, str);
/* If needed, update the message maximum width */
/* """"""""""""""""""""""""""""""""""""""""""" */
n = wcswidth((w = utf8_strtowcs(str)), utf8_strlen(str));
free(w);
if (n > *message_max_width)
*message_max_width = n;
/* If needed, update the message maximum number */
/* of bytes used by the longest line */
/* """""""""""""""""""""""""""""""""""""""""""" */
if ((n = (long)strlen(str)) > *message_max_len)
*message_max_len = n;
ptr = cr_ptr + 1;
}
/* For the last line */
/* """"""""""""""""" */
if (*ptr != '\0')
{
ll_append(message_lines_list, xstrdup(ptr));
n = wcswidth((w = utf8_strtowcs(ptr)), utf8_strlen(ptr));
free(w);
if (n > *message_max_width)
*message_max_width = n;
/* If needed, update the message maximum number */
/* of bytes used by the longest line */
/* """""""""""""""""""""""""""""""""""""""""""" */
if ((n = (long)strlen(ptr)) > *message_max_len)
*message_max_len = n;
}
else
ll_append(message_lines_list, xstrdup(""));
}
/* =================================================================== */
/* Set the new start and the new end of the window structure according */
/* to the current cursor position. */
/* =================================================================== */
void
set_win_start_end(win_t * win, long current, long last)
{
long cur_line, end_line;
cur_line = line_nb_of_word_a[current];
if (cur_line == last)
win->end = count - 1;
else
{
/* In help mode we must not modify the windows start/end position as */
/* It must be redrawn exactly as it was before. */
/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
if (!help_mode)
{
if (cur_line + win->max_lines / 2 + 1 <= last)
win->end = first_word_in_line_a[cur_line + win->max_lines / 2 + 1] - 1;
else
win->end = first_word_in_line_a[last];
}
}
end_line = line_nb_of_word_a[win->end];
if (end_line < win->max_lines)
win->start = 0;
else
win->start = first_word_in_line_a[end_line - win->max_lines + 1];
}
/* ========================================================================= */
/* Set the metadata associated with a word, its starting and ending position */
/* the line in which it is put and so on. */
/* Set win.start win.end and the starting and ending position of each word. */
/* This function is only called initially, when resizing the terminal and */
/* potentially when the search function is used. */
/* ========================================================================= */
long
build_metadata(term_t * term, long count, win_t * win)
{
long i = 0;
long word_len;
long len = 0;
long last = 0;
long word_width;
long tab_count; /* Current number of words in the line, *
* used in tab_mode */
wchar_t * w;
line_nb_of_word_a[0] = 0;
first_word_in_line_a[0] = 0;
/* In column mode we need to calculate win->max_width, first initialize */
/* it to 0 and increment it later in the loop */
/* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
if (!win->col_mode)
win->max_width = 0;
tab_count = 0;
while (i < count)
{
/* Determine the number of screen positions used by the word */
/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
word_len = mbstowcs(NULL, word_a[i].str, 0);
word_width = wcswidth((w = utf8_strtowcs(word_a[i].str)), word_len);
/* Manage the case where the word is larger than the terminal width */
/* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
if (word_width >= term->ncolumns - 2)
{
/* Shorten the word until it fits */
/* """""""""""""""""""""""""""""" */
do
{
word_width = wcswidth(w, word_len--);
} while (word_len > 0 && word_width >= term->ncolumns - 2);
}
free(w);
/* Look if there is enough remaining place on the line when not in column */
/* mode. Force a break if the 'is_last' flag is set in all modes or if we */
/* hit the max number of allowed columns in tab mode */
/* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
if ((!win->col_mode && !win->line_mode
&& (len + word_width + 1) >= term->ncolumns - 1)
|| ((win->col_mode || win->line_mode || win->tab_mode) && i > 0
&& word_a[i - 1].is_last)
|| (win->tab_mode && win->max_cols > 0 && tab_count >= win->max_cols))
{
/* We must build another line */
/* """""""""""""""""""""""""" */
line_nb_of_word_a[i] = ++last;
first_word_in_line_a[last] = i;
word_a[i].start = 0;
len = word_width + 1; /* Resets the current line length */
tab_count = 1; /* Resets the current number of words *
* in the line */
word_a[i].end = word_width - 1;
word_a[i].mb = word_len;
}
else
{
word_a[i].start = len;
word_a[i].end = word_a[i].start + word_width - 1;
word_a[i].mb = word_len;
line_nb_of_word_a[i] = last;
len += word_width + 1; /* Increase line length */
tab_count++; /* We've seen another word in the line */
}
/* If not in column mode, we need to calculate win->(real_)max_width */
/* as it hasn't been already done */
/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
if (len > win->max_width)
{
if (len > term->ncolumns)
win->max_width = term->ncolumns - 2;
else
win->max_width = len;
}
if (len > win->real_max_width)
win->real_max_width = len;
i++;
}
if (!win->center || win->max_width > term->ncolumns - 2)
win->offset = 0;
else
win->offset = (term->ncolumns - 2 - win->max_width) / 2;
/* We need to recalculate win->start and win->end here */
/* because of a possible terminal resizing */
/* """"""""""""""""""""""""""""""""""""""""""""""""""" */
set_win_start_end(win, current, last);
return last;
}
/* ==================================================================== */
/* Helper function used by disp_word to print a matching word under the */
/* cursor when not in search mode with the matching characters of the */
/* word highlighted. */
/* ==================================================================== */
void
disp_cursor_word(long pos, win_t * win, term_t * term, int err)
{
size_t i;
int att_set = 0;
char * p = word_a[pos].str + daccess.flength;
char * np;
/* Set the cursor attribute */
/* """""""""""""""""""""""" */
tputs(TPARM1(exit_attribute_mode), 1, outch);
tputs(TPARM1(exit_attribute_mode), 1, outch);
if (word_a[pos].is_tagged)
apply_attr(term, win->cursor_on_tag_attr);
else
apply_attr(term, win->cursor_attr);
for (i = 0; i < word_a[pos].mb - daccess.flength; i++)
{
if (BIT_ISSET(word_a[pos].bitmap, i))
{
if (!att_set)
{
att_set = 1;
/* Set the buffer display attribute */
/* """""""""""""""""""""""""""""""" */
tputs(TPARM1(exit_attribute_mode), 1, outch);
if (err)
apply_attr(term, win->match_err_text_attr);
else
apply_attr(term, win->match_text_attr);
if (word_a[pos].is_tagged)
apply_attr(term, win->cursor_on_tag_attr);
else
apply_attr(term, win->cursor_attr);
}
}
else
{
if (att_set)
{
att_set = 0;
/* Set the search cursor attribute */
/* """"""""""""""""""""""""""""""" */
tputs(TPARM1(exit_attribute_mode), 1, outch);
if (word_a[pos].is_tagged)
apply_attr(term, win->cursor_on_tag_attr);
else
apply_attr(term, win->cursor_attr);
}
}
np = utf8_next(p);
if (np == NULL)
fputs(p, stdout);
else
printf("%.*s", (int)(np - p), p);
p = np;
}
}
/* ==================================================================== */
/* Helper function used by disp_word to print a matching word NOT under */
/* the cursor with the matching characters of the word highlighted. */
/* ==================================================================== */
void
disp_matching_word(long pos, win_t * win, term_t * term, int is_cursor, int err)
{
size_t i;
int att_set = 0;
char * p = word_a[pos].str + daccess.flength;
char * np;
/* Set the search cursor attribute */
/* """"""""""""""""""""""""""""""" */
tputs(TPARM1(exit_attribute_mode), 1, outch);
if (is_cursor)
{
if (err)
apply_attr(term, win->match_err_field_attr);
else
apply_attr(term, win->match_field_attr);
}
else
{
if (err)
apply_attr(term, win->search_err_field_attr);
else
apply_attr(term, win->search_field_attr);
}
if (word_a[pos].is_tagged)
apply_attr(term, win->tag_attr);
for (i = 0; i < word_a[pos].mb - daccess.flength; i++)
{
if (BIT_ISSET(word_a[pos].bitmap, i))
{
if (!att_set)
{
att_set = 1;
/* Set the buffer display attribute */
/* """""""""""""""""""""""""""""""" */
tputs(TPARM1(exit_attribute_mode), 1, outch);
if (is_cursor)
{
if (err)
apply_attr(term, win->match_err_text_attr);
else
apply_attr(term, win->match_text_attr);
}
else
apply_attr(term, win->search_text_attr);
if (word_a[pos].is_tagged)
apply_attr(term, win->tag_attr);
}
}
else
{
if (att_set)
{
att_set = 0;
/* Set the search cursor attribute */
/* """"""""""""""""""""""""""""""" */
tputs(TPARM1(exit_attribute_mode), 1, outch);
if (is_cursor)
{
if (err)
apply_attr(term, win->match_err_field_attr);
else
apply_attr(term, win->match_field_attr);
}
else
{
if (err)
apply_attr(term, win->search_err_field_attr);
else
apply_attr(term, win->search_field_attr);
}
if (word_a[pos].is_tagged)
apply_attr(term, win->tag_attr);
}
}
np = utf8_next(p);
if (np == NULL)
fputs(p, stdout);
else
printf("%.*s", (int)(np - p), p);
p = np;
}
}
/* ====================================================================== */
/* Display a word in, the windows. Manages the following different cases: */
/* - Search mode display */
/* - Cursor display */
/* - Normal display */
/* - Color or mono display */
/* ====================================================================== */
void
disp_word(long pos, search_mode_t search_mode, search_data_t * search_data,
term_t * term, win_t * win, char * tmp_word)
{
long s = word_a[pos].start;
long e = word_a[pos].end;
long p;
char * buffer = search_data->buf;
if (pos == current)
{
if (search_mode != NONE)
{
utf8_strprefix(tmp_word, word_a[pos].str, (long)word_a[pos].mb, &p);
if (word_a[pos].is_numbered)
{
/* Set the direct access number attribute */
/* """""""""""""""""""""""""""""""""""""" */
apply_attr(term, win->daccess_attr);
/* And print it */
/* """""""""""" */
fputs(daccess.left, stdout);
printf("%.*s", daccess.length, tmp_word + 1);
fputs(daccess.right, stdout);
tputs(TPARM1(exit_attribute_mode), 1, outch);
fputc(' ', stdout);
}
else if (daccess.length > 0)
{
/* prints the leading spaces */
/* """"""""""""""""""""""""" */
tputs(TPARM1(exit_attribute_mode), 1, outch);
printf("%.*s", daccess.flength, tmp_word);
}
/* Set the search cursor attribute */
/* """"""""""""""""""""""""""""""" */
if (search_data->fuzzy_err)
apply_attr(term, win->search_err_field_attr);
else
apply_attr(term, win->search_field_attr);
/* The tab attribute must complete the attributes already set */
/* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
if (word_a[pos].is_tagged)
apply_attr(term, win->tag_attr);
/* Print the word part */
/* """"""""""""""""""" */
fputs(tmp_word + daccess.flength, stdout);
if (buffer[0] != '\0')
{
long i = 0;
/* Put the cursor at the beginning of the word */
/* """"""""""""""""""""""""""""""""""""""""""" */
for (i = 0; i < e - s + 1 - daccess.flength; i++)
tputs(TPARM1(cursor_left), 1, outch);
tputs(TPARM1(exit_attribute_mode), 1, outch);
/* Set the search cursor attribute */
/* """"""""""""""""""""""""""""""" */
if (search_data->fuzzy_err)
apply_attr(term, win->search_err_field_attr);
else
apply_attr(term, win->search_field_attr);
disp_matching_word(pos, win, term, 0, search_data->fuzzy_err);
}
}
else
{
if (daccess.length > 0)
{
/* If this word is not numbered, reset the display */
/* attributes before printing the leading spaces. */
/* """"""""""""""""""""""""""""""""""""""""""""""" */
if (!word_a[pos].is_numbered)
{
/* Print the non significant part of the word */
/* """""""""""""""""""""""""""""""""""""""""" */
tputs(TPARM1(exit_attribute_mode), 1, outch);
printf("%.*s", daccess.flength - 1, word_a[pos].str);
tputs(TPARM1(exit_attribute_mode), 1, outch);
fputc(' ', stdout);
}
else
{
apply_attr(term, win->daccess_attr);
/* Print the non significant part of the word */
/* """""""""""""""""""""""""""""""""""""""""" */
fputs(daccess.left, stdout);
printf("%.*s", daccess.length, word_a[pos].str + 1);
fputs(daccess.right, stdout);
tputs(TPARM1(exit_attribute_mode), 1, outch);
fputc(' ', stdout);
}
}
/* If we are not in search mode, display a normal cursor */
/* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
utf8_strprefix(tmp_word, word_a[pos].str, (long)word_a[pos].mb, &p);
if (word_a[pos].is_matching)
disp_cursor_word(pos, win, term, search_data->fuzzy_err);
else
{
if (word_a[pos].is_tagged)
apply_attr(term, win->cursor_on_tag_attr);
else
apply_attr(term, win->cursor_attr);
fputs(tmp_word + daccess.flength, stdout);
}
}
tputs(TPARM1(exit_attribute_mode), 1, outch);
}
else
{
/* Display a normal word without any attribute */
/* """"""""""""""""""""""""""""""""""""""""""" */
utf8_strprefix(tmp_word, word_a[pos].str, (long)word_a[pos].mb, &p);
/* If words are numbered, emphasis their numbers */
/* """"""""""""""""""""""""""""""""""""""""""""" */
if (word_a[pos].is_numbered)
{
apply_attr(term, win->daccess_attr);
fputs(daccess.left, stdout);
printf("%.*s", daccess.length, tmp_word + 1);
fputs(daccess.right, stdout);
tputs(TPARM1(exit_attribute_mode), 1, outch);
fputc(' ', stdout);
}
else if (daccess.length > 0)
{
long i;
/* Insert leading spaces if the word is non numbered and */
/* padding for all words is set */
/* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
tputs(TPARM1(exit_attribute_mode), 1, outch);
if (daccess.padding == 'a')
for (i = 0; i < daccess.flength; i++)
fputc(' ', stdout);
}
if (!word_a[pos].is_selectable)
apply_attr(term, win->exclude_attr);
else if (word_a[pos].special_level > 0)
{
long level = word_a[pos].special_level - 1;
apply_attr(term, win->special_attr[level]);
}
else
apply_attr(term, win->include_attr);
if (word_a[pos].is_matching)
disp_matching_word(pos, win, term, 1, search_data->fuzzy_err);
else
{
if (word_a[pos].is_tagged)
apply_attr(term, win->tag_attr);
if ((daccess.length > 0 && daccess.padding == 'a')
|| word_a[pos].is_numbered)
fputs(tmp_word + daccess.flength, stdout);
else
fputs(tmp_word, stdout);
}
tputs(TPARM1(exit_attribute_mode), 1, outch);
}
}
/* ======================================= */
/* Display a message line above the window */
/* ======================================= */
void
disp_message(ll_t * message_lines_list, long message_max_width,
long message_max_len, term_t * term, win_t * win)
{
ll_node_t * node;
char * line;
char * buf;
size_t len;
long size;
long offset;
wchar_t * w;
win->message_lines = 0;
/* Disarm the periodic timer to prevent the interruptions to corrupt */
/* screen by altering the timing of the decoding of the terminfo */
/* capabilities de */
/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
periodic_itv.it_value.tv_sec = 0;
periodic_itv.it_value.tv_usec = 0;
periodic_itv.it_interval.tv_sec = 0;
periodic_itv.it_interval.tv_usec = 0;
setitimer(ITIMER_REAL, &periodic_itv, NULL);
/* Do nothing if there is no message to display */
/* """""""""""""""""""""""""""""""""""""""""""" */
if (message_lines_list == NULL)
return;
node = message_lines_list->head;
buf = xmalloc(message_max_len + 1);
/* Follow the message lines list and display each line */
/* """"""""""""""""""""""""""""""""""""""""""""""""""" */
while (node != NULL)
{
long i;
line = node->data;
len = utf8_strlen(line);
w = utf8_strtowcs(line);
/* Adjust size and len if the terminal is not large enough */
/* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */
size = wcswidth(w, len);
while (len > 0 && size > term->ncolumns)
size = wcswidth(w, --len);
free(w);
/* Compute the offset from the left screen border if -M option is set */
/* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
offset = (term->ncolumns - message_max_width - 3) / 2;
if (win->center && offset > 0)
for (i = 0; i < offset; i++)
fputc(' ', stdout);
apply_attr(term, win->message_attr);
/* Only print the start of a line if the screen width if too small */
/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
utf8_strprefix(buf, line, len, &size);
/* Print the line without the ending \n */
/* '''''''''''''''''''''''''''''''''''' */
printf("%s", buf);
/* Complete the short line with spaces until it reach the */
/* message max size */
/* '''''''''''''''''''''''''''''''''''''''''''''''''''''' */
for (i = size; i < message_max_width; i++)
{
if (i + (offset < 0 ? 0 : offset) >= term->ncolumns)
break;
fputc(' ', stdout);
}
/* Drop the attributes and print a \n */
/* '''''''''''''''''''''''''''''''''' */
tputs(TPARM1(exit_attribute_mode), 1, outch);
puts("");
node = node->next;
win->message_lines++;
}
/* Add an empty line without attribute to separate the menu title */
/* and the menu content. */
/* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
puts("");
win->message_lines++;
free(buf);
/* Re-arm the periodic timer */
/* """"""""""""""""""""""""" */
periodic_itv.it_value.tv_sec = 0;
periodic_itv.it_value.tv_usec = TICK;
periodic_itv.it_interval.tv_sec = 0;
periodic_itv.it_interval.tv_usec = TICK;
setitimer(ITIMER_REAL, &periodic_itv, NULL);
}
/* ============================ */
/* Display the selection window */
/* ============================ */
long
disp_lines(win_t * win, toggle_t * toggle, long current, long count,
search_mode_t search_mode, search_data_t * search_data,
term_t * term, long last_line, char * tmp_word,
langinfo_t * langinfo)
{
long lines_disp;
long i;
char scroll_symbol[5];
long len;
long display_bar;
/* Disarm the periodic timer to prevent the interruptions to corrupt */
/* screen by altering the timing of the decoding of the terminfo */
/* capabilities de */
/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
periodic_itv.it_value.tv_sec = 0;
periodic_itv.it_value.tv_usec = 0;
periodic_itv.it_interval.tv_sec = 0;
periodic_itv.it_interval.tv_usec = 0;
setitimer(ITIMER_REAL, &periodic_itv, NULL);
scroll_symbol[0] = ' ';
scroll_symbol[1] = '\0';
lines_disp = 1;
tputs(TPARM1(save_cursor), 1, outch);
i = win->start;
/* Modify the max number of displayed lines if we do not have */
/* enough place */
/* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
if (win->max_lines > term->nlines - win->message_lines)
win->max_lines = term->nlines - win->message_lines;
if (last_line >= win->max_lines)
display_bar = 1;
else
display_bar = 0;
if (win->col_mode || win->line_mode)
len = term->ncolumns - 3;
else
len = term->ncolumns - 2;
/* If in column mode and the sum of the columns sizes + gutters is */
/* greater than the terminal width, then prepend a space to be able to */
/* display the left arrow indicating that the first displayed column */
/* is not the first one. */
/* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
if (len > 1
&& ((win->col_mode || win->line_mode)
&& win->real_max_width > term->ncolumns - 2))
{
if (win->first_column > 0)
{
if (langinfo->utf8)
strcpy(scroll_symbol, shift_left_sym);
else
strcpy(scroll_symbol, "<");
}
}
else
scroll_symbol[0] = '\0';
/* Center the display ? */
/* """""""""""""""""""" */
if (win->offset > 0)
{
long i;
for (i = 0; i < win->offset; i++)
fputc(' ', stdout);
}
left_margin_putp(scroll_symbol, term, win);
while (len > 1 && i <= count - 1)
{
/* Display one word and the space or symbol following it */
/* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
if (word_a[i].start >= win->first_column
&& word_a[i].end < len + win->first_column)
{
disp_word(i, search_mode, search_data, term, win, tmp_word);
/* If there are more element to be displayed after the right margin */
/* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
if ((win->col_mode || win->line_mode) && i < count - 1
&& word_a[i + 1].end >= len + win->first_column)
{
apply_attr(term, win->shift_attr);
if (langinfo->utf8)
fputs(shift_right_sym, stdout);
else
fputc('>', stdout);
tputs(TPARM1(exit_attribute_mode), 1, outch);
}
/* If we want to display the gutter */
/* """""""""""""""""""""""""""""""" */
else if (!word_a[i].is_last && win->col_sep
&& (win->tab_mode || win->col_mode))
{
long pos;
/* Make sure that we are using the right gutter character even */
/* if the first displayed word is * not the first of its line */
/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
pos = i - first_word_in_line_a[line_nb_of_word_a[i]];
if (pos >= win->gutter_nb) /* Use the last gutter character */
fputs(win->gutter_a[win->gutter_nb - 1], stdout);
else
fputs(win->gutter_a[pos], stdout);
}
else
/* Else just display a space */
/* """"""""""""""""""""""""" */
fputc(' ', stdout);
}
/* Mark the line as the current line, the line containing the cursor */
/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
if (i == current)
win->cur_line = lines_disp;
/* Check if we must start a new line */
/* """"""""""""""""""""""""""""""""" */
if (i == count - 1 || word_a[i + 1].start == 0)
{
tputs(TPARM1(clr_eol), 1, outch);
if (lines_disp < win->max_lines)
{
/* If we have more than one line to display */
/* """""""""""""""""""""""""""""""""""""""" */
if (display_bar && !toggle->no_scrollbar
&& (lines_disp > 1 || i < count - 1))
{
/* Display the next element of the scrollbar */
/* """"""""""""""""""""""""""""""""""""""""" */
if (line_nb_of_word_a[i] == 0)
{
if (win->max_lines > 1)
right_margin_putp(sbar_top, "\\", langinfo, term, win, lines_disp,
win->offset);
else
right_margin_putp(sbar_arr_down, "^", langinfo, term, win,
lines_disp, win->offset);
}
else if (lines_disp == 1)
right_margin_putp(sbar_arr_up, "^", langinfo, term, win, lines_disp,
win->offset);
else if (line_nb_of_word_a[i] == last_line)
{
if (win->max_lines > 1)
right_margin_putp(sbar_down, "/", langinfo, term, win, lines_disp,
win->offset);
else
right_margin_putp(sbar_arr_up, "^", langinfo, term, win,
lines_disp, win->offset);
}
else if (last_line + 1 > win->max_lines
&& (long)((float)(line_nb_of_word_a[current])
/ (last_line + 1) * (win->max_lines - 2)
+ 2)
== lines_disp)
right_margin_putp(sbar_curs, "+", langinfo, term, win, lines_disp,
win->offset);
else
right_margin_putp(sbar_line, "|", langinfo, term, win, lines_disp,
win->offset);
}
/* Print a newline character if we are not at the end of */
/* the input nor at the end of the window */
/* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
if (i < count - 1 && lines_disp < win->max_lines)
{
fputc('\n', stdout);
if (win->offset > 0)
{
long i;
for (i = 0; i < win->offset; i++)
fputc(' ', stdout);
}
left_margin_putp(scroll_symbol, term, win);