evil-qemacs/util.c
Charlie Gordon fcf9136fdb Improve naming consistency
- rename qe_skip_spaces() as eb_skip_spaces() and take EditBuffer
- rename skip_spaces() as qe_skip_spaces()
- use qe_skip_spaces() return value to simplify loops
2020-11-25 18:55:15 +01:00

2524 lines
70 KiB
C

/*
* Utilities for qemacs.
*
* Copyright (c) 2001 Fabrice Bellard.
* Copyright (c) 2002-2020 Charlie Gordon.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "qe.h"
#include <dirent.h>
#ifdef CONFIG_WIN32
#include <sys/timeb.h>
/* XXX: not sufficient, but OK for basic operations */
int fnmatch(const char *pattern, const char *string, int flags)
{
if (pattern[0] == '*' && pattern[1] == '\0')
return 0;
else
return !strequal(pattern, string);
}
#else
#include <fnmatch.h>
#endif
struct FindFileState {
char path[MAX_FILENAME_SIZE];
char dirpath[MAX_FILENAME_SIZE]; /* current dir path */
char pattern[MAX_FILENAME_SIZE]; /* search pattern */
const char *bufptr;
int flags;
int depth;
DIR *dir;
DIR *parent_dir[FF_DEPTH];
size_t parent_len[FF_DEPTH];
};
FindFileState *find_file_open(const char *path, const char *pattern, int flags)
{
FindFileState *s;
s = qe_mallocz(FindFileState);
if (!s)
return NULL;
pstrcpy(s->path, sizeof(s->path), path);
pstrcpy(s->pattern, sizeof(s->pattern), pattern);
s->bufptr = s->path;
s->dir = NULL;
s->flags = flags;
return s;
}
int find_file_next(FindFileState *s, char *filename, int filename_size_max)
{
struct dirent *dirent;
const char *p;
for (;;) {
if (!s->dir || (dirent = readdir(s->dir)) == NULL) {
if (s->dir) {
closedir(s->dir);
s->dir = NULL;
}
if (s->depth) {
s->depth--;
s->dir = s->parent_dir[s->depth];
s->parent_dir[s->depth] = NULL;
s->dirpath[s->parent_len[s->depth]] = '\0';
continue;
}
p = s->bufptr;
if (*p == '\0')
return -1;
if (s->flags & FF_PATH)
p += strcspn(p, ":");
else
p += strlen(p);
pstrncpy(s->dirpath, sizeof(s->dirpath), s->bufptr, p - s->bufptr);
if (*p == ':')
p++;
s->bufptr = p;
s->dir = opendir(s->dirpath);
} else {
if (dirent->d_type == DT_DIR) {
if (*dirent->d_name == '.'
&& (strequal(dirent->d_name, ".") || strequal(dirent->d_name, ".."))) {
if (s->flags & FF_NOXXDIR)
continue;
} else {
if (s->depth < (s->flags & FF_DEPTH)) {
s->parent_dir[s->depth] = s->dir;
s->parent_len[s->depth] = strlen(s->dirpath);
s->depth++;
makepath(s->dirpath, sizeof(s->dirpath), s->dirpath, dirent->d_name);
s->dir = opendir(s->dirpath);
continue;
}
}
if (s->flags & FF_NODIR)
continue;
} else {
if (s->flags & FF_ONLYDIR)
continue;
}
if (fnmatch(s->pattern, dirent->d_name, 0) == 0) {
makepath(filename, filename_size_max,
s->dirpath, dirent->d_name);
return 0;
}
}
}
}
void find_file_close(FindFileState **sp)
{
if (*sp) {
FindFileState *s = *sp;
while (s->depth) {
closedir(s->parent_dir[--s->depth]);
}
if (s->dir)
closedir(s->dir);
qe_free(sp);
}
}
#ifdef CONFIG_WIN32
/* convert '\' to '/' */
static void path_win_to_unix(char *buf)
{
char *p;
for (p = buf; *p; p++) {
if (*p == '\\')
*p = '/';
}
}
#endif
int is_directory(const char *path)
{
struct stat st;
if (!stat(path, &st) && S_ISDIR(st.st_mode))
return 1;
else
return 0;
}
int is_filepattern(const char *filespec)
{
// XXX: should also accept character ranges and {} comprehensions
int pos = strcspn(filespec, "*?");
return filespec[pos] != '\0';
}
/* suppress redundant ".", ".." and "/" from paths */
/* XXX: make it better */
static void canonicalize_path1(char *buf, int buf_size, const char *path)
{
const char *p;
char *q, *q1;
int c, abs_path;
char file[MAX_FILENAME_SIZE];
p = path;
abs_path = (p[0] == '/');
buf[0] = '\0';
for (;;) {
/* extract file */
q = file;
for (;;) {
c = *p;
if (c == '\0')
break;
p++;
if (c == '/')
break;
if ((q - file) < (int)sizeof(file) - 1)
*q++ = c;
}
*q = '\0';
if (file[0] == '\0') {
/* nothing to do */
} else if (file[0] == '.' && file[1] == '\0') {
/* nothing to do */
} else if (file[0] == '.' && file[1] == '.' && file[2] == '\0') {
/* go up one dir */
if (buf[0] == '\0') {
if (!abs_path)
goto copy;
} else {
/* go to previous directory, if possible */
q1 = strrchr(buf, '/');
/* if already going up, cannot do more */
if (!q1 || (q1[1] == '.' && q1[2] == '.' && q1[3] == '\0'))
goto copy;
else
*q1 = '\0';
}
} else {
copy:
/* add separator if needed */
if (buf[0] != '\0' || (buf[0] == '\0' && abs_path))
pstrcat(buf, buf_size, "/");
pstrcat(buf, buf_size, file);
}
if (c == '\0')
break;
}
/* add at least '.' or '/' */
if (buf[0] == '\0') {
if (abs_path)
pstrcat(buf, buf_size, "/");
else
pstrcat(buf, buf_size, ".");
}
}
void canonicalize_path(char *buf, int buf_size, const char *path)
{
const char *p;
/* check for URL protocol or windows drive */
/* CG: should not skip '/' */
/* XXX: bogus if filename contains ':' */
p = strchr(path, ':');
if (p) {
if ((p - path) == 1) {
/* windows drive: we canonicalize only the following path */
buf[0] = p[0];
buf[1] = p[1];
/* CG: this will not work for non current drives */
canonicalize_path1(buf + 2, buf_size - 2, p);
} else {
/* URL: it is already canonical */
pstrcpy(buf, buf_size, path);
}
} else {
/* simple unix path */
canonicalize_path1(buf, buf_size, path);
}
}
/* reduce path relative to homedir */
char *make_user_path(char *buf, int buf_size, const char *path)
{
char *homedir;
homedir = getenv("HOME");
if (homedir) {
int len = strlen(homedir);
if (len && homedir[len - 1] == '/')
len--;
if (!memcmp(path, homedir, len) && path[len] == '/') {
pstrcpy(buf, buf_size, "~");
return pstrcat(buf, buf_size, path + len);
}
}
return pstrcpy(buf, buf_size, path);
}
char *reduce_filename(char *dest, int size, const char *filename)
{
const char *base = get_basename(filename);
char *dbase, *ext, *p;
/* Copy path unchanged */
pstrncpy(dest, size, filename, base - filename);
/* Strip cvs temp file prefix */
if (base[0] == '.' && base[1] == '#' && base[2] != '\0')
base += 2;
pstrcat(dest, size, base);
dbase = get_basename_nc(dest);
/* Strip some numeric extensions (vcs version numbers) */
for (;;) {
/* get the last extension */
ext = get_extension_nc(dbase);
/* no extension */
if (*ext != '.')
break;
/* keep non numeric extension */
if (!qe_isdigit(ext[1]))
break;
/* keep the last extension */
if (strchr(dbase, '.') == ext)
break;
/* only strip multidigit extensions */
if (!qe_isdigit(ext[2]))
break;
*ext = '\0';
}
if (*ext == '.') {
/* Convert all upper case filenames with extension to lower
* case */
for (p = dbase; *p; p++) {
if (qe_islower(*p))
break;
}
if (!*p) {
qe_strtolower(dbase, dest + size - dbase, dbase);
}
}
/* Strip backup file suffix or cvs temp file suffix */
p = dbase + strlen(dbase);
if (p > dbase + 1 && (p[-1] == '~' || p[-1] == '#'))
*--p = '\0';
return dest;
}
/* Return 1 iff filename extension appears in | separated list extlist.
* Initial and final | do not match an empty extension, but || does.
* Multiple tacked extensions may appear un extlist eg. |tar.gz|
* Initial dots do not account as extension delimiters.
* . and .. do not have an empty extension, nor do they match ||
*/
int match_extension(const char *filename, const char *extlist)
{
const char *base, *p, *q;
int len;
if (!extlist)
return 0;
base = get_basename(filename);
while (*base == '.')
base++;
len = strlen(base);
if (len == 0)
return 0;
for (p = q = extlist;; p++) {
int c = *p;
if (c == '|' || c == '\0') {
int len1 = p - q;
if (len1 != 0 || (q != extlist && c != '\0')) {
if (len > len1 && base[len - len1 - 1] == '.'
&& !qe_memicmp(base + (len - len1), q, len1)) {
return 1;
}
}
if (c == '|')
q = p + 1;
else
break;
}
}
return 0;
}
int match_shell_handler(const char *p, const char *list)
{
const char *base;
if (!list)
return 0;
if (p[0] == '#' && p[1] == '!') {
for (p += 2; qe_isblank(*p); p++)
continue;
for (base = p; *p && !qe_isspace(*p); p++) {
if (*p == '/')
base = p + 1;
}
if (memfind(list, base, p - base))
return 1;
if (p - base == 3 && !memcmp(base, "env", 3)) {
while (*p != '\0' && *p != '\n') {
for (; qe_isblank(*p); p++)
continue;
base = p;
for (; *p && !qe_isspace(*p); p++)
continue;
if (*base != '-')
return memfind(list, base, p - base);
}
}
}
return 0;
}
/* Remove trailing slash from path, except for / directory */
int remove_slash(char *buf)
{
int len;
len = strlen(buf);
if (len > 1 && buf[len - 1] == '/') {
buf[--len] = '\0';
}
return len;
}
/* Append trailing slash to path if none there already */
int append_slash(char *buf, int buf_size)
{
int len;
len = strlen(buf);
if (len > 0 && buf[len - 1] != '/' && len + 1 < buf_size) {
buf[len++] = '/';
buf[len] = '\0';
}
return len;
}
char *makepath(char *buf, int buf_size, const char *path,
const char *filename)
{
if (buf != path)
pstrcpy(buf, buf_size, path);
append_slash(buf, buf_size);
return pstrcat(buf, buf_size, filename);
}
void splitpath(char *dirname, int dirname_size,
char *filename, int filename_size, const char *pathname)
{
const char *base;
base = get_basename(pathname);
if (dirname)
pstrncpy(dirname, dirname_size, pathname, base - pathname);
if (filename)
pstrcpy(filename, filename_size, base);
}
/* smart compare strings, lexicographical order, but collate numbers in
* numeric order, and push * at end */
int qe_strcollate(const char *s1, const char *s2)
{
int last, c1, c2, res, flags;
last = '\0';
for (;;) {
c1 = (unsigned char)*s1++;
c2 = (unsigned char)*s2++;
if (c1 == c2) {
last = c1;
if (c1 == '\0')
return 0;
} else {
break;
}
}
if (c1 == '*')
res = 1;
else
if (c2 == '*')
res = -1;
else
res = (c1 < c2) ? -1 : 1;
for (;;) {
flags = qe_isdigit(c1) * 2 + qe_isdigit(c2);
if (flags == 3) {
last = c1;
c1 = (unsigned char)*s1++;
c2 = (unsigned char)*s2++;
} else {
break;
}
}
if (!qe_isdigit(last) || flags == 0)
return res;
return (flags == 1) ? -1 : 1;
}
/* CG: need a local version of strcasecmp: qe_strcasecmp() */
int qe_strtobool(const char *s, int def) {
if (s && *s) {
return strxfind("1|y|yes|t|true", s) ? 1 : 0;
} else {
return def;
}
}
/* Should return int, length of converted string? */
void qe_strtolower(char *buf, int size, const char *str)
{
int c;
/* This version only handles ASCII */
if (size > 0) {
while ((c = (unsigned char)*str++) != '\0' && size > 1) {
*buf++ = qe_tolower(c);
size--;
}
*buf = '\0';
}
}
int qe_skip_spaces(const char **pp)
{
const char *p;
int c;
p = *pp;
while (qe_isspace(c = *p))
p++;
*pp = p;
return c;
}
int memfind(const char *list, const char *s, int len)
{
const char *q = list;
if (!q)
return 0;
for (;;) {
int i = 0;
for (;;) {
int c2 = q[i];
if (c2 == '|') {
if (i == len)
return 1;
break;
}
if (c2 == '\0') {
/* match the empty string against || only */
return (len > 0 && i == len);
}
if (c2 == s[i++] && i <= len)
continue;
for (q += i;;) {
c2 = *q++;
if (c2 == '\0')
return 0;
if (c2 == '|')
break;
}
break;
}
}
}
#if 0
/* find a word in a list using '|' as separator */
int strfind(const char *keytable, const char *str)
{
int c, len;
const char *p;
if (!keytable)
return 0;
c = *str;
len = strlen(str);
/* need to special case the empty string */
if (len == 0)
return strstr(keytable, "||") != NULL;
/* initial and trailing | are optional */
/* they do not cause the empty string to match */
for (p = keytable;;) {
if (!memcmp(p, str, len) && (p[len] == '|' || p[len] == '\0'))
return 1;
for (;;) {
p = strchr(p + 1, c);
if (!p)
return 0;
if (p[-1] == '|')
break;
}
}
}
#else
/* Search for the string s in '|' delimited list of strings */
int strfind(const char *list, const char *s)
{
const char *p, *q;
int c1, c2;
q = list;
if (!q)
return 0;
if (*s == '\0') {
/* special case the empty string: must match || in list */
while (*q) {
if (q[0] == '|' && q[1] == '|')
return 1;
q++;
}
return 0;
} else {
scan:
p = s;
for (;;) {
c1 = *p++;
c2 = *q++;
if (c1 == '\0') {
if (c2 == '\0' || c2 == '|')
return 1;
goto skip;
}
if (c1 != c2) {
for (;;) {
if (c2 == '|')
goto scan;
if (c2 == '\0')
return 0;
skip:
c2 = *q++;
}
}
}
}
}
#endif
/* find a word in a list using '|' as separator, ignore case and "-_ ". */
int strxfind(const char *list, const char *s)
{
const char *p, *q;
int c1, c2;
q = list;
if (!q)
return 0;
if (*s == '\0') {
/* special case the empty string: must match || in list */
while (*q) {
if (q[0] == '|' && q[1] == '|')
return 1;
q++;
}
return 0;
} else {
scan:
p = s;
for (;;) {
do {
c1 = qe_toupper((unsigned char)*p++);
} while (c1 == '-' || c1 == '_' || c1 == ' ');
do {
c2 = qe_toupper((unsigned char)*q++);
} while (c2 == '-' || c2 == '_' || c2 == ' ');
if (c1 == '\0') {
if (c2 == '\0' || c2 == '|')
return 1;
goto skip;
}
if (c1 != c2) {
for (;;) {
if (c2 == '|')
goto scan;
if (c2 == '\0')
return 0;
skip:
c2 = *q++;
}
}
}
}
}
const char *strmem(const char *str, const void *mem, int size)
{
int c, len;
const char *p, *str_max, *p1 = mem;
if (size <= 0)
return (size < 0) ? NULL : str;
size--;
c = *p1++;
if (!c) {
/* cannot find chunk with embedded nuls */
return NULL;
}
len = strlen(str);
if (size >= len)
return NULL;
str_max = str + len - size;
for (p = str; p < str_max; p++) {
if (*p == c && !memcmp(p + 1, p1, size))
return p;
}
return NULL;
}
const void *memstr(const void *buf, int size, const char *str)
{
int c, len;
const u8 *p, *buf_max;
c = *str++;
if (!c) {
/* empty string matches start of buffer */
return buf;
}
len = strlen(str);
if (len >= size)
return NULL;
buf_max = (const u8*)buf + size - len;
for (p = buf; p < buf_max; p++) {
if (*p == c && !memcmp(p + 1, str, len))
return p;
}
return NULL;
}
int qe_memicmp(const void *p1, const void *p2, int count)
{
const u8 *s1 = (const u8 *)p1;
const u8 *s2 = (const u8 *)p2;
for (; count-- > 0; s1++, s2++) {
if (*s1 != *s2) {
int c1 = qe_toupper(*s1);
int c2 = qe_toupper(*s2);
if (c1 != c2)
return (c2 < c1) - (c1 < c2);
}
}
return 0;
}
const char *qe_stristr(const char *s1, const char *s2)
{
int c, c1, c2, len;
len = strlen(s2);
if (!len)
return s1;
c = *s2++;
len--;
c1 = qe_toupper(c);
c2 = qe_tolower(c);
while ((c = *s1++) != '\0') {
if (c == c1 || c == c2) {
if (!qe_memicmp(s1, s2, len))
return s1 - 1;
}
}
return NULL;
}
/**
* Return TRUE if val is a prefix of str (case independent). If it
* returns TRUE, ptr is set to the next character in 'str' after the
* prefix.
*
* @param str input string
* @param val prefix to test
* @param ptr updated after the prefix in str in there is a match
* @return TRUE if there is a match */
int stristart(const char *str, const char *val, const char **ptr)
{
const char *p, *q;
p = str;
q = val;
while (*q != '\0') {
if (qe_toupper((unsigned char)*p) != qe_toupper((unsigned char)*q)) {
return 0;
}
p++;
q++;
}
if (ptr)
*ptr = p;
return 1;
}
/**
* Return TRUE if val is a prefix of str (case independent). If it
* returns TRUE, ptr is set to the next character in 'str' after the
* prefix.
*
* Spaces, dashes and underscores are also ignored in this comparison.
*
* @param str input string
* @param val prefix to test
* @param ptr updated after the prefix in str in there is a match
* @return TRUE if there is a match */
int strxstart(const char *str, const char *val, const char **ptr)
{
const char *p, *q;
p = str;
q = val;
while (*q != '\0') {
if (qe_toupper((unsigned char)*p) != qe_toupper((unsigned char)*q)) {
if (*q == '-' || *q == '_' || *q == ' ') {
q++;
continue;
}
if (*p == '-' || *p == '_' || *p == ' ') {
p++;
continue;
}
return 0;
}
p++;
q++;
}
if (ptr)
*ptr = p;
return 1;
}
/**
* Compare strings str1 and str2 case independently.
* Spaces, dashes and underscores are also ignored in this comparison.
*
* @param str1 input string 1 (left operand)
* @param str2 input string 2 (right operand)
* @return -1, 0, +1 reflecting the sign of str1 <=> str2
*/
int strxcmp(const char *str1, const char *str2)
{
const char *p, *q;
int d;
p = str1;
q = str2;
for (;;) {
d = qe_toupper((unsigned char)*p) - qe_toupper((unsigned char)*q);
if (d) {
if (*q == '-' || *q == '_' || *q == ' ') {
q++;
continue;
}
if (*p == '-' || *p == '_' || *p == ' ') {
p++;
continue;
}
return d < 0 ? -1 : +1;
}
if (!*p)
break;
p++;
q++;
}
return 0;
}
int ustrstart(const unsigned int *str0, const char *val, int *lenp)
{
const unsigned int *str = str0;
for (; *val != '\0'; val++, str++) {
/* assuming val is ASCII or Latin1 */
if (*str != *val)
return 0;
}
if (lenp)
*lenp = str - str0;
return 1;
}
const unsigned int *ustrstr(const unsigned int *str, const char *val)
{
int c = val[0];
for (; *str != '\0'; str++) {
if (*str == (unsigned int)c && ustrstart(str, val, NULL))
return str;
}
return NULL;
}
int ustristart(const unsigned int *str0, const char *val, int *lenp)
{
const unsigned int *str = str0;
for (; *val != '\0'; val++, str++) {
/* assuming val is ASCII or Latin1 */
if (qe_toupper(*str) != qe_toupper(*val))
return 0;
}
if (lenp)
*lenp = str - str0;
return 1;
}
const unsigned int *ustristr(const unsigned int *str, const char *val)
{
int c = qe_toupper(val[0]);
for (; *str != '\0'; str++) {
if (qe_toupper(*str) == c && ustristart(str, val, NULL))
return str;
}
return NULL;
}
int umemcmp(const unsigned int *s1, const unsigned int *s2, int count)
{
for (; count > 0; count--, s1++, s2++) {
if (*s1 != *s2) {
return *s1 < *s2 ? -1 : 1;
}
}
return 0;
}
int ustr_get_identifier(char *buf, int buf_size, int c,
const unsigned int *str, int i, int n)
{
int len = 0, j;
if (len < buf_size - 1) {
/* c is assumed to be an ASCII character */
buf[len++] = c;
}
for (j = i; j < n; j++) {
c = str[j];
if (!qe_isalnum_(c))
break;
if (len < buf_size - 1)
buf[len++] = c;
}
if (len < buf_size) {
buf[len] = '\0';
}
return j - i;
}
int ustr_get_identifier_lc(char *buf, int buf_size, int c,
const unsigned int *str, int i, int n)
{
int len = 0, j;
if (len < buf_size) {
/* c is assumed to be an ASCII character */
buf[len++] = qe_tolower(c);
}
for (j = i; j < n; j++) {
c = str[j];
if (!qe_isalnum_(c))
break;
if (len < buf_size - 1)
buf[len++] = qe_tolower(c);
}
if (len < buf_size) {
buf[len] = '\0';
}
return j - i;
}
int ustr_get_word(char *buf, int buf_size, int c,
const unsigned int *str, int i, int n)
{
buf_t outbuf, *out;
int j;
out = buf_init(&outbuf, buf, buf_size);
buf_putc_utf8(out, c);
for (j = i; j < n; j++) {
c = str[j];
if (!qe_isword(c))
break;
buf_putc_utf8(out, c);
}
return j - i;
}
/* Read a token from a string, stop on a set of characters.
* Skip spaces before and after token.
*/
void get_str(const char **pp, char *buf, int buf_size, const char *stop)
{
char *q;
const char *p;
int c;
qe_skip_spaces(pp);
p = *pp;
q = buf;
for (;;) {
c = *p;
/* Stop on spaces and eat them */
if (c == '\0' || qe_isspace(c) || strchr(stop, c))
break;
if ((q - buf) < buf_size - 1)
*q++ = c;
p++;
}
*q = '\0';
*pp = p;
qe_skip_spaces(pp);
}
/* scans a comma separated list of entries, return index of match or -1 */
/* CG: very similar to strfind */
int css_get_enum(const char *str, const char *enum_str)
{
int val, len;
const char *s, *s1;
s = enum_str;
val = 0;
len = strlen(str);
for (;;) {
s1 = strchr(s, ',');
if (s1) {
if (len == (s1 - s) && !memcmp(s, str, len))
return val;
s = s1 + 1;
} else {
if (strequal(s, str))
return val;
else
break;
}
val++;
}
return -1;
}
static unsigned short const keycodes[] = {
KEY_SPC, KEY_DEL, KEY_RET, KEY_ESC, KEY_TAB, KEY_SHIFT_TAB,
KEY_CTRL(' '), KEY_DEL, KEY_CTRL('\\'),
KEY_CTRL(']'), KEY_CTRL('^'), KEY_CTRL('_'), KEY_CTRL('_'),
KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN,
KEY_HOME, KEY_END, KEY_PAGEUP, KEY_PAGEDOWN,
KEY_CTRL_LEFT, KEY_CTRL_RIGHT, KEY_CTRL_UP, KEY_CTRL_DOWN,
KEY_CTRL_HOME, KEY_CTRL_END, KEY_CTRL_PAGEUP, KEY_CTRL_PAGEDOWN,
KEY_PAGEUP, KEY_PAGEDOWN, KEY_CTRL_PAGEUP, KEY_CTRL_PAGEDOWN,
KEY_INSERT, KEY_DELETE, KEY_DEFAULT,
KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5,
KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10,
KEY_F11, KEY_F12, KEY_F13, KEY_F14, KEY_F15,
KEY_F16, KEY_F17, KEY_F18, KEY_F19, KEY_F20,
'{', '}', '|',
};
static const char * const keystr[] = {
"SPC", "DEL", "RET", "ESC", "TAB", "S-TAB",
"C-SPC", "C-?", "C-\\", "C-]", "C-^", "C-_", "C-/",
"left", "right", "up", "down",
"home", "end", "prior", "next",
"C-left", "C-right", "C-up", "C-down",
"C-home", "C-end", "C-prior", "C-next",
"pageup", "pagedown", "C-pageup", "C-pagedown",
"insert", "delete", "default",
"f1", "f2", "f3", "f4", "f5",
"f6", "f7", "f8", "f9", "f10",
"f11", "f12", "f13", "f14", "f15",
"f16", "f17", "f18", "f19", "f20",
"LB", "RB", "VB",
};
int compose_keys(unsigned int *keys, int *nb_keys)
{
unsigned int *keyp;
if (*nb_keys < 2)
return 0;
/* compose KEY_ESC as META prefix */
keyp = keys + *nb_keys - 2;
if (keyp[0] == KEY_ESC) {
if (keyp[1] != KEY_ESC && keyp[1] <= 0xff) {
keyp[0] = KEY_META(keyp[1]);
--*nb_keys;
return 1;
}
}
return 0;
}
/* CG: this code is still quite inelegant */
static int strtokey1(const char **pp)
{
const char *p, *p1, *q;
int i, key;
/* should return KEY_NONE at end and KEY_UNKNOWN if unrecognized */
p = *pp;
/* scan for separator */
for (p1 = p; *p1 && *p1 != ' '; p1++)
continue;
for (i = 0; i < countof(keycodes); i++) {
if (strstart(p, keystr[i], &q) && q == p1) {
key = keycodes[i];
*pp = p1;
return key;
}
}
#if 0
/* Cannot do this because KEY_F1..KEY_F20 are not consecutive */
if (p[0] == 'f' && p[1] >= '1' && p[1] <= '9') {
i = p[1] - '0';
p += 2;
if (qe_isdigit(*p))
i = i * 10 + *p++ - '0';
key = KEY_F1 + i - 1;
*pp = p1;
return key;
}
#endif
/* Should also support backslash escapes: \000 \x00 \u0000 */
/* Should also support ^x and syntax and Ctrl- prefix for control keys */
/* Should test for p[2] in range 'a'..'z', '@'..'_', '?' */
if (p[0] == 'C' && p[1] == '-' && p1 == p + 3) {
/* control */
key = KEY_CTRL(p[2]);
*pp = p1;
} else {
key = utf8_decode(&p);
*pp = p;
}
return key;
}
int strtokey(const char **pp)
{
const char *p;
int key;
p = *pp;
/* Should also support A- and Alt- prefix for meta keys */
if (p[0] == 'M' && p[1] == '-') {
p += 2;
key = KEY_META(strtokey1(&p));
} else
if (p[0] == 'C' && p[1] == '-' && p[2] == 'M' && p[3] == '-') {
/* Should pass buffer with C-xxx to strtokey1 */
p += 4;
key = KEY_META(KEY_CTRL(strtokey1(&p)));
} else {
key = strtokey1(&p);
}
*pp = p;
return key;
}
int strtokeys(const char *kstr, unsigned int *keys, int max_keys)
{
int key, nb_keys;
const char *p;
p = kstr;
nb_keys = 0;
while (qe_skip_spaces(&p)) {
key = strtokey(&p);
keys[nb_keys++] = key;
compose_keys(keys, &nb_keys);
if (nb_keys >= max_keys)
break;
}
return nb_keys;
}
int buf_put_key(buf_t *out, int key)
{
int i, start = out->len;
for (i = 0; i < countof(keycodes); i++) {
if (keycodes[i] == key) {
return buf_puts(out, keystr[i]);
}
}
if (key >= KEY_META(0) && key <= KEY_META(0xff)) {
buf_puts(out, "M-");
buf_put_key(out, key & 0xff);
} else
if (key >= KEY_CTRL('a') && key <= KEY_CTRL('z')) {
buf_printf(out, "C-%c", key + 'a' - 1);
} else
#if 0
/* Cannot do this because KEY_F1..KEY_F20 are not consecutive */
if (key >= KEY_F1 && key <= KEY_F20) {
buf_printf(out, "f%d", key - KEY_F1 + 1);
} else
#endif
{
buf_putc_utf8(out, key);
}
return out->len - start;
}
int buf_put_keys(buf_t *out, unsigned int *keys, int nb_keys)
{
int i, start = out->len;
for (i = 0; i < nb_keys; i++) {
if (i != 0)
buf_put_byte(out, ' ');
buf_put_key(out, keys[i]);
}
return out->len - start;
}
unsigned char const qe_digit_value__[128] = {
#define REPEAT16(x) x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x
REPEAT16(255), REPEAT16(255), REPEAT16(255),
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
255, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 255, 255, 255, 255, 255,
255, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 255, 255, 255, 255, 255,
};
#if 1
/* Should move all this to a separate source file color.c */
/* For 8K colors, We use a color system with 7936 colors:
* - 16 standard colors
* - 240 standard palette colors
* - 4096 colors in a 16x16x16 cube
* - a 256 level gray ramp
* - 6 256-level fade to black ramps
* - 6 256-level fade to white ramps
* - a 256 color palette with default xterm values
* - 256 unused slots
*/
/* Alternately we could use a system with 8157 colors:
* - 2 default color
* - 16 standard colors
* - 256 standard palette colors
* - 6859 colors in a 19x19x19 cube
* with ramp 0,15,31,47,63,79,95,108,121,135,
* 148,161,175,188,201,215,228,241,255 values
* - 256 level gray ramp
* - extra space for 3 256 level ramps or 12 64 level ramps
* - 15 unused slots
*/
/* Another possible system for 8K colors has 8042+ colors:
* - 2 default color
* - 16 standard colors
* - 24 standard grey scale colors
* - 8000 colors in a 20x20x20 cube
* with ramp 0,13,27,40,54,67,81,95,108,121,135,
* 148,161,175,188,201,215,228,241,255 values
* - extra grey scale colors
* - some unused slots
*/
typedef struct ColorDef {
const char *name;
unsigned int color;
} ColorDef;
static ColorDef const default_colors[] = {
/* From HTML 4.0 spec */
{ "black", QERGB(0x00, 0x00, 0x00) },
{ "green", QERGB(0x00, 0x80, 0x00) },
{ "silver", QERGB(0xc0, 0xc0, 0xc0) },
{ "lime", QERGB(0x00, 0xff, 0x00) },
{ "gray", QERGB(0xbe, 0xbe, 0xbe) },
{ "olive", QERGB(0x80, 0x80, 0x00) },
{ "white", QERGB(0xff, 0xff, 0xff) },
{ "yellow", QERGB(0xff, 0xff, 0x00) },
{ "maroon", QERGB(0x80, 0x00, 0x00) },
{ "navy", QERGB(0x00, 0x00, 0x80) },
{ "red", QERGB(0xff, 0x00, 0x00) },
{ "blue", QERGB(0x00, 0x00, 0xff) },
{ "purple", QERGB(0x80, 0x00, 0x80) },
{ "teal", QERGB(0x00, 0x80, 0x80) },
{ "fuchsia", QERGB(0xff, 0x00, 0xff) },
{ "aqua", QERGB(0x00, 0xff, 0xff) },
/* more colors */
{ "cyan", QERGB(0x00, 0xff, 0xff) },
{ "magenta", QERGB(0xff, 0x00, 0xff) },
{ "grey", QERGB(0xbe, 0xbe, 0xbe) },
{ "transparent", COLOR_TRANSPARENT },
};
#define nb_default_colors countof(default_colors)
static ColorDef *qe_colors = (ColorDef *)default_colors;
static int nb_qe_colors = nb_default_colors;
#if 0
static QEColor const tty_full_colors[8] = {
QERGB(0x00, 0x00, 0x00), /* black */
QERGB(0xff, 0x00, 0x00), /* red */
QERGB(0x00, 0xff, 0x00), /* lime */
QERGB(0xff, 0xff, 0x00), /* yellow */
QERGB(0x00, 0x00, 0xff), /* blue */
QERGB(0xff, 0x00, 0xff), /* fuchsia */
QERGB(0x00, 0xff, 0xff), /* aqua */
QERGB(0xff, 0xff, 0xff), /* white */
};
#endif
QEColor const xterm_colors[256] = {
QERGB(0x00, 0x00, 0x00), /* black */
QERGB(0xbb, 0x00, 0x00),
QERGB(0x00, 0xbb, 0x00),
QERGB(0xbb, 0xbb, 0x00),
QERGB(0x00, 0x00, 0xbb),
QERGB(0xbb, 0x00, 0xbb),
QERGB(0x00, 0xbb, 0xbb),
QERGB(0xbb, 0xbb, 0xbb),
QERGB(0x55, 0x55, 0x55),
QERGB(0xff, 0x55, 0x55),
QERGB(0x55, 0xff, 0x55),
QERGB(0xff, 0xff, 0x55),
QERGB(0x55, 0x55, 0xff),
QERGB(0xff, 0x55, 0xff),
QERGB(0x55, 0xff, 0xff),
QERGB(0xff, 0xff, 0xff), /* white */
#if 1
/* Extended color palette for xterm 256 color mode */
/* From XFree86: xc/programs/xterm/256colres.h,
* v 1.5 2002/10/05 17:57:11 dickey Exp
*/
/* 216 entry RGB cube with axes 0,95,135,175,215,255 */
/* followed by 24 entry grey scale 8,18..238 */
QERGB(0x00, 0x00, 0x00), /* 16: Grey0 */
QERGB(0x00, 0x00, 0x5f), /* 17: NavyBlue */
QERGB(0x00, 0x00, 0x87), /* 18: DarkBlue */
QERGB(0x00, 0x00, 0xaf), /* 19: Blue3 */
QERGB(0x00, 0x00, 0xd7), /* 20: Blue3 */
QERGB(0x00, 0x00, 0xff), /* 21: Blue1 */
QERGB(0x00, 0x5f, 0x00), /* 22: DarkGreen */
QERGB(0x00, 0x5f, 0x5f), /* 23: DeepSkyBlue4 */
QERGB(0x00, 0x5f, 0x87), /* 24: DeepSkyBlue4 */
QERGB(0x00, 0x5f, 0xaf), /* 25: DeepSkyBlue4 */
QERGB(0x00, 0x5f, 0xd7), /* 26: DodgerBlue3 */
QERGB(0x00, 0x5f, 0xff), /* 27: DodgerBlue2 */
QERGB(0x00, 0x87, 0x00), /* 28: Green4 */
QERGB(0x00, 0x87, 0x5f), /* 29: SpringGreen4 */
QERGB(0x00, 0x87, 0x87), /* 30: Turquoise4 */
QERGB(0x00, 0x87, 0xaf), /* 31: DeepSkyBlue3 */
QERGB(0x00, 0x87, 0xd7), /* 32: DeepSkyBlue3 */
QERGB(0x00, 0x87, 0xff), /* 33: DodgerBlue1 */
QERGB(0x00, 0xaf, 0x00), /* 34: Green3 */
QERGB(0x00, 0xaf, 0x5f), /* 35: SpringGreen3 */
QERGB(0x00, 0xaf, 0x87), /* 36: DarkCyan */
QERGB(0x00, 0xaf, 0xaf), /* 37: LightSeaGreen */
QERGB(0x00, 0xaf, 0xd7), /* 38: DeepSkyBlue2 */
QERGB(0x00, 0xaf, 0xff), /* 39: DeepSkyBlue1 */
QERGB(0x00, 0xd7, 0x00), /* 40: Green3 */
QERGB(0x00, 0xd7, 0x5f), /* 41: SpringGreen3 */
QERGB(0x00, 0xd7, 0x87), /* 42: SpringGreen2 */
QERGB(0x00, 0xd7, 0xaf), /* 43: Cyan3 */
QERGB(0x00, 0xd7, 0xd7), /* 44: DarkTurquoise */
QERGB(0x00, 0xd7, 0xff), /* 45: Turquoise2 */
QERGB(0x00, 0xff, 0x00), /* 46: Green1 */
QERGB(0x00, 0xff, 0x5f), /* 47: SpringGreen2 */
QERGB(0x00, 0xff, 0x87), /* 48: SpringGreen1 */
QERGB(0x00, 0xff, 0xaf), /* 49: MediumSpringGreen */
QERGB(0x00, 0xff, 0xd7), /* 50: Cyan2 */
QERGB(0x00, 0xff, 0xff), /* 51: Cyan1 */
QERGB(0x5f, 0x00, 0x00), /* 52: DarkRed */
QERGB(0x5f, 0x00, 0x5f), /* 53: DeepPink4 */
QERGB(0x5f, 0x00, 0x87), /* 54: Purple4 */
QERGB(0x5f, 0x00, 0xaf), /* 55: Purple4 */
QERGB(0x5f, 0x00, 0xd7), /* 56: Purple3 */
QERGB(0x5f, 0x00, 0xff), /* 57: BlueViolet */
QERGB(0x5f, 0x5f, 0x00), /* 58: Orange4 */
QERGB(0x5f, 0x5f, 0x5f), /* 59: Grey37 */
QERGB(0x5f, 0x5f, 0x87), /* 60: MediumPurple4 */
QERGB(0x5f, 0x5f, 0xaf), /* 61: SlateBlue3 */
QERGB(0x5f, 0x5f, 0xd7), /* 62: SlateBlue3 */
QERGB(0x5f, 0x5f, 0xff), /* 63: RoyalBlue1 */
QERGB(0x5f, 0x87, 0x00), /* 64: Chartreuse4 */
QERGB(0x5f, 0x87, 0x5f), /* 65: DarkSeaGreen4 */
QERGB(0x5f, 0x87, 0x87), /* 66: PaleTurquoise4 */
QERGB(0x5f, 0x87, 0xaf), /* 67: SteelBlue */
QERGB(0x5f, 0x87, 0xd7), /* 68: SteelBlue3 */
QERGB(0x5f, 0x87, 0xff), /* 69: CornflowerBlue */
QERGB(0x5f, 0xaf, 0x00), /* 70: Chartreuse3 */
QERGB(0x5f, 0xaf, 0x5f), /* 71: DarkSeaGreen4 */
QERGB(0x5f, 0xaf, 0x87), /* 72: CadetBlue */
QERGB(0x5f, 0xaf, 0xaf), /* 73: CadetBlue */
QERGB(0x5f, 0xaf, 0xd7), /* 74: SkyBlue3 */
QERGB(0x5f, 0xaf, 0xff), /* 75: SteelBlue1 */
QERGB(0x5f, 0xd7, 0x00), /* 76: Chartreuse3 */
QERGB(0x5f, 0xd7, 0x5f), /* 77: PaleGreen3 */
QERGB(0x5f, 0xd7, 0x87), /* 78: SeaGreen3 */
QERGB(0x5f, 0xd7, 0xaf), /* 79: Aquamarine3 */
QERGB(0x5f, 0xd7, 0xd7), /* 80: MediumTurquoise */
QERGB(0x5f, 0xd7, 0xff), /* 81: SteelBlue1 */
QERGB(0x5f, 0xff, 0x00), /* 82: Chartreuse2 */
QERGB(0x5f, 0xff, 0x5f), /* 83: SeaGreen2 */
QERGB(0x5f, 0xff, 0x87), /* 84: SeaGreen1 */
QERGB(0x5f, 0xff, 0xaf), /* 85: SeaGreen1 */
QERGB(0x5f, 0xff, 0xd7), /* 86: Aquamarine1 */
QERGB(0x5f, 0xff, 0xff), /* 87: DarkSlateGray2 */
QERGB(0x87, 0x00, 0x00), /* 88: DarkRed */
QERGB(0x87, 0x00, 0x5f), /* 89: DeepPink4 */
QERGB(0x87, 0x00, 0x87), /* 90: DarkMagenta */
QERGB(0x87, 0x00, 0xaf), /* 91: DarkMagenta */
QERGB(0x87, 0x00, 0xd7), /* 92: DarkViolet */
QERGB(0x87, 0x00, 0xff), /* 93: Purple */
QERGB(0x87, 0x5f, 0x00), /* 94: Orange4 */
QERGB(0x87, 0x5f, 0x5f), /* 95: LightPink4 */
QERGB(0x87, 0x5f, 0x87), /* 96: Plum4 */
QERGB(0x87, 0x5f, 0xaf), /* 97: MediumPurple3 */
QERGB(0x87, 0x5f, 0xd7), /* 98: MediumPurple3 */
QERGB(0x87, 0x5f, 0xff), /* 99: SlateBlue1 */
QERGB(0x87, 0x87, 0x00), /* 100: Yellow4 */
QERGB(0x87, 0x87, 0x5f), /* 101: Wheat4 */
QERGB(0x87, 0x87, 0x87), /* 102: Grey53 */
QERGB(0x87, 0x87, 0xaf), /* 103: LightSlateGrey */
QERGB(0x87, 0x87, 0xd7), /* 104: MediumPurple */
QERGB(0x87, 0x87, 0xff), /* 105: LightSlateBlue */
QERGB(0x87, 0xaf, 0x00), /* 106: Yellow4 */
QERGB(0x87, 0xaf, 0x5f), /* 107: DarkOliveGreen3 */
QERGB(0x87, 0xaf, 0x87), /* 108: DarkSeaGreen */
QERGB(0x87, 0xaf, 0xaf), /* 109: LightSkyBlue3 */
QERGB(0x87, 0xaf, 0xd7), /* 110: LightSkyBlue3 */
QERGB(0x87, 0xaf, 0xff), /* 111: SkyBlue2 */
QERGB(0x87, 0xd7, 0x00), /* 112: Chartreuse2 */
QERGB(0x87, 0xd7, 0x5f), /* 113: DarkOliveGreen3 */
QERGB(0x87, 0xd7, 0x87), /* 114: PaleGreen3 */
QERGB(0x87, 0xd7, 0xaf), /* 115: DarkSeaGreen3 */
QERGB(0x87, 0xd7, 0xd7), /* 116: DarkSlateGray3 */
QERGB(0x87, 0xd7, 0xff), /* 117: SkyBlue1 */
QERGB(0x87, 0xff, 0x00), /* 118: Chartreuse1 */
QERGB(0x87, 0xff, 0x5f), /* 119: LightGreen */
QERGB(0x87, 0xff, 0x87), /* 120: LightGreen */
QERGB(0x87, 0xff, 0xaf), /* 121: PaleGreen1 */
QERGB(0x87, 0xff, 0xd7), /* 122: Aquamarine1 */
QERGB(0x87, 0xff, 0xff), /* 123: DarkSlateGray1 */
QERGB(0xaf, 0x00, 0x00), /* 124: Red3 */
QERGB(0xaf, 0x00, 0x5f), /* 125: DeepPink4 */
QERGB(0xaf, 0x00, 0x87), /* 126: MediumVioletRed */
QERGB(0xaf, 0x00, 0xaf), /* 127: Magenta3 */
QERGB(0xaf, 0x00, 0xd7), /* 128: DarkViolet */
QERGB(0xaf, 0x00, 0xff), /* 129: Purple */
QERGB(0xaf, 0x5f, 0x00), /* 130: DarkOrange3 */
QERGB(0xaf, 0x5f, 0x5f), /* 131: IndianRed */
QERGB(0xaf, 0x5f, 0x87), /* 132: HotPink3 */
QERGB(0xaf, 0x5f, 0xaf), /* 133: MediumOrchid3 */
QERGB(0xaf, 0x5f, 0xd7), /* 134: MediumOrchid */
QERGB(0xaf, 0x5f, 0xff), /* 135: MediumPurple2 */
QERGB(0xaf, 0x87, 0x00), /* 136: DarkGoldenrod */
QERGB(0xaf, 0x87, 0x5f), /* 137: LightSalmon3 */
QERGB(0xaf, 0x87, 0x87), /* 138: RosyBrown */
QERGB(0xaf, 0x87, 0xaf), /* 139: Grey63 */
QERGB(0xaf, 0x87, 0xd7), /* 140: MediumPurple2 */
QERGB(0xaf, 0x87, 0xff), /* 141: MediumPurple1 */
QERGB(0xaf, 0xaf, 0x00), /* 142: Gold3 */
QERGB(0xaf, 0xaf, 0x5f), /* 143: DarkKhaki */
QERGB(0xaf, 0xaf, 0x87), /* 144: NavajoWhite3 */
QERGB(0xaf, 0xaf, 0xaf), /* 145: Grey69 */
QERGB(0xaf, 0xaf, 0xd7), /* 146: LightSteelBlue3 */
QERGB(0xaf, 0xaf, 0xff), /* 147: LightSteelBlue */
QERGB(0xaf, 0xd7, 0x00), /* 148: Yellow3 */
QERGB(0xaf, 0xd7, 0x5f), /* 149: DarkOliveGreen3 */
QERGB(0xaf, 0xd7, 0x87), /* 150: DarkSeaGreen3 */
QERGB(0xaf, 0xd7, 0xaf), /* 151: DarkSeaGreen2 */
QERGB(0xaf, 0xd7, 0xd7), /* 152: LightCyan3 */
QERGB(0xaf, 0xd7, 0xff), /* 153: LightSkyBlue1 */
QERGB(0xaf, 0xff, 0x00), /* 154: GreenYellow */
QERGB(0xaf, 0xff, 0x5f), /* 155: DarkOliveGreen2 */
QERGB(0xaf, 0xff, 0x87), /* 156: PaleGreen1 */
QERGB(0xaf, 0xff, 0xaf), /* 157: DarkSeaGreen2 */
QERGB(0xaf, 0xff, 0xd7), /* 158: DarkSeaGreen1 */
QERGB(0xaf, 0xff, 0xff), /* 159: PaleTurquoise1 */
QERGB(0xd7, 0x00, 0x00), /* 160: Red3 */
QERGB(0xd7, 0x00, 0x5f), /* 161: DeepPink3 */
QERGB(0xd7, 0x00, 0x87), /* 162: DeepPink3 */
QERGB(0xd7, 0x00, 0xaf), /* 163: Magenta3 */
QERGB(0xd7, 0x00, 0xd7), /* 164: Magenta3 */
QERGB(0xd7, 0x00, 0xff), /* 165: Magenta2 */
QERGB(0xd7, 0x5f, 0x00), /* 166: DarkOrange3 */
QERGB(0xd7, 0x5f, 0x5f), /* 167: IndianRed */
QERGB(0xd7, 0x5f, 0x87), /* 168: HotPink3 */
QERGB(0xd7, 0x5f, 0xaf), /* 169: HotPink2 */
QERGB(0xd7, 0x5f, 0xd7), /* 170: Orchid */
QERGB(0xd7, 0x5f, 0xff), /* 171: MediumOrchid1 */
QERGB(0xd7, 0x87, 0x00), /* 172: Orange3 */
QERGB(0xd7, 0x87, 0x5f), /* 173: LightSalmon3 */
QERGB(0xd7, 0x87, 0x87), /* 174: LightPink3 */
QERGB(0xd7, 0x87, 0xaf), /* 175: Pink3 */
QERGB(0xd7, 0x87, 0xd7), /* 176: Plum3 */
QERGB(0xd7, 0x87, 0xff), /* 177: Violet */
QERGB(0xd7, 0xaf, 0x00), /* 178: Gold3 */
QERGB(0xd7, 0xaf, 0x5f), /* 179: LightGoldenrod3 */
QERGB(0xd7, 0xaf, 0x87), /* 180: Tan */
QERGB(0xd7, 0xaf, 0xaf), /* 181: MistyRose3 */
QERGB(0xd7, 0xaf, 0xd7), /* 182: Thistle3 */
QERGB(0xd7, 0xaf, 0xff), /* 183: Plum2 */
QERGB(0xd7, 0xd7, 0x00), /* 184: Yellow3 */
QERGB(0xd7, 0xd7, 0x5f), /* 185: Khaki3 */
QERGB(0xd7, 0xd7, 0x87), /* 186: LightGoldenrod2 */
QERGB(0xd7, 0xd7, 0xaf), /* 187: LightYellow3 */
QERGB(0xd7, 0xd7, 0xd7), /* 188: Grey84 */
QERGB(0xd7, 0xd7, 0xff), /* 189: LightSteelBlue1 */
QERGB(0xd7, 0xff, 0x00), /* 190: Yellow2 */
QERGB(0xd7, 0xff, 0x5f), /* 191: DarkOliveGreen1 */
QERGB(0xd7, 0xff, 0x87), /* 192: DarkOliveGreen1 */
QERGB(0xd7, 0xff, 0xaf), /* 193: DarkSeaGreen1 */
QERGB(0xd7, 0xff, 0xd7), /* 194: Honeydew2 */
QERGB(0xd7, 0xff, 0xff), /* 195: LightCyan1 */
QERGB(0xff, 0x00, 0x00), /* 196: Red1 */
QERGB(0xff, 0x00, 0x5f), /* 197: DeepPink2 */
QERGB(0xff, 0x00, 0x87), /* 198: DeepPink1 */
QERGB(0xff, 0x00, 0xaf), /* 199: DeepPink1 */
QERGB(0xff, 0x00, 0xd7), /* 200: Magenta2 */
QERGB(0xff, 0x00, 0xff), /* 201: Magenta1 */
QERGB(0xff, 0x5f, 0x00), /* 202: OrangeRed1 */
QERGB(0xff, 0x5f, 0x5f), /* 203: IndianRed1 */
QERGB(0xff, 0x5f, 0x87), /* 204: IndianRed1 */
QERGB(0xff, 0x5f, 0xaf), /* 205: HotPink */
QERGB(0xff, 0x5f, 0xd7), /* 206: HotPink */
QERGB(0xff, 0x5f, 0xff), /* 207: MediumOrchid1 */
QERGB(0xff, 0x87, 0x00), /* 208: DarkOrange */
QERGB(0xff, 0x87, 0x5f), /* 209: Salmon1 */
QERGB(0xff, 0x87, 0x87), /* 210: LightCoral */
QERGB(0xff, 0x87, 0xaf), /* 211: PaleVioletRed1 */
QERGB(0xff, 0x87, 0xd7), /* 212: Orchid2 */
QERGB(0xff, 0x87, 0xff), /* 213: Orchid1 */
QERGB(0xff, 0xaf, 0x00), /* 214: Orange1 */
QERGB(0xff, 0xaf, 0x5f), /* 215: SandyBrown */
QERGB(0xff, 0xaf, 0x87), /* 216: LightSalmon1 */
QERGB(0xff, 0xaf, 0xaf), /* 217: LightPink1 */
QERGB(0xff, 0xaf, 0xd7), /* 218: Pink1 */
QERGB(0xff, 0xaf, 0xff), /* 219: Plum1 */
QERGB(0xff, 0xd7, 0x00), /* 220: Gold1 */
QERGB(0xff, 0xd7, 0x5f), /* 221: LightGoldenrod2 */
QERGB(0xff, 0xd7, 0x87), /* 222: LightGoldenrod2 */
QERGB(0xff, 0xd7, 0xaf), /* 223: NavajoWhite1 */
QERGB(0xff, 0xd7, 0xd7), /* 224: MistyRose1 */
QERGB(0xff, 0xd7, 0xff), /* 225: Thistle1 */
QERGB(0xff, 0xff, 0x00), /* 226: Yellow1 */
QERGB(0xff, 0xff, 0x5f), /* 227: LightGoldenrod1 */
QERGB(0xff, 0xff, 0x87), /* 228: Khaki1 */
QERGB(0xff, 0xff, 0xaf), /* 229: Wheat1 */
QERGB(0xff, 0xff, 0xd7), /* 230: Cornsilk1 */
QERGB(0xff, 0xff, 0xff), /* 231: Grey100 */
QERGB(0x08, 0x08, 0x08), /* 232: Grey3 */
QERGB(0x12, 0x12, 0x12), /* 233: Grey7 */
QERGB(0x1c, 0x1c, 0x1c), /* 234: Grey11 */
QERGB(0x26, 0x26, 0x26), /* 235: Grey15 */
QERGB(0x30, 0x30, 0x30), /* 236: Grey19 */
QERGB(0x3a, 0x3a, 0x3a), /* 237: Grey23 */
QERGB(0x44, 0x44, 0x44), /* 238: Grey27 */
QERGB(0x4e, 0x4e, 0x4e), /* 239: Grey30 */
QERGB(0x58, 0x58, 0x58), /* 240: Grey35 */
QERGB(0x62, 0x62, 0x62), /* 241: Grey39 */
QERGB(0x6c, 0x6c, 0x6c), /* 242: Grey42 */
QERGB(0x76, 0x76, 0x76), /* 243: Grey46 */
QERGB(0x80, 0x80, 0x80), /* 244: Grey50 */
QERGB(0x8a, 0x8a, 0x8a), /* 245: Grey54 */
QERGB(0x94, 0x94, 0x94), /* 246: Grey58 */
QERGB(0x9e, 0x9e, 0x9e), /* 247: Grey62 */
QERGB(0xa8, 0xa8, 0xa8), /* 248: Grey66 */
QERGB(0xb2, 0xb2, 0xb2), /* 249: Grey70 */
QERGB(0xbc, 0xbc, 0xbc), /* 250: Grey74 */
QERGB(0xc6, 0xc6, 0xc6), /* 251: Grey78 */
QERGB(0xd0, 0xd0, 0xd0), /* 252: Grey82 */
QERGB(0xda, 0xda, 0xda), /* 253: Grey85 */
QERGB(0xe4, 0xe4, 0xe4), /* 254: Grey89 */
QERGB(0xee, 0xee, 0xee), /* 255: Grey93 */
#endif
};
#if 0
static unsigned char const scale_cube[256] = {
/* This array is used for mapping rgb colors to the standard palette */
/* 216 entry RGB cube with axes 0,95,135,175,215,255 */
#define REP5(x) (x), (x), (x), (x), (x)
#define REP10(x) REP5(x), REP5(x)
#define REP20(x) REP10(x), REP10(x)
#define REP40(x) REP20(x), REP20(x)
#define REP25(x) REP5(x), REP5(x), REP5(x), REP5(x), REP5(x)
#define REP47(x) REP10(x), REP10(x), REP10(x), REP10(x), REP5(x), (x), (x)
REP47(0), REP47(1), REP20(1), REP40(2), REP40(3), REP40(4), REP20(5), 5
};
static unsigned char const scale_grey[256] = {
/* This array is used for mapping gray levels to the standard palette */
/* 232..255: 24 entry grey scale 8,18..238 */
16, 16, 16, 16,
REP10(232), REP10(233), REP10(234), REP10(235),
REP10(236), REP10(237), REP10(238), REP10(239),
REP10(240), REP10(241), REP10(242), REP10(243),
REP10(244), REP10(245), REP10(246), REP10(247),
REP10(248), REP10(249), REP10(250), REP10(251),
REP10(252), REP10(253), REP10(254), REP10(255),
255, 255, 255,
231, 231, 231, 231, 231, 231, 231, 231, 231
};
#endif
static inline int color_dist(QEColor c1, QEColor c2) {
/* using casts because c1 and c2 are unsigned */
#if 0
/* using a quick approximation to give green extra weight */
return abs((int)((c1 >> 0) & 0xff) - (int)((c2 >> 0) & 0xff)) +
2 * abs((int)((c1 >> 8) & 0xff) - (int)((c2 >> 8) & 0xff)) +
abs((int)((c1 >> 16) & 0xff) - (int)((c2 >> 16) & 0xff));
#else
/* using different weights to R, G, B according to luminance levels */
return 11 * abs((int)((c1 >> 0) & 0xff) - (int)((c2 >> 0) & 0xff)) +
59 * abs((int)((c1 >> 8) & 0xff) - (int)((c2 >> 8) & 0xff)) +
30 * abs((int)((c1 >> 16) & 0xff) - (int)((c2 >> 16) & 0xff));
#endif
}
/* XXX: should have a more generic API with precomputed mapping scales */
/* Convert RGB triplet to a composite color */
unsigned int qe_map_color(QEColor color, QEColor const *colors, int count, int *dist)
{
int i, cmin, dmin, d;
color &= 0xFFFFFF; /* mask off the alpha channel */
if (count >= 0x1000000) {
cmin = color | 0x1000000; /* force explicit RGB triplet */
dmin = 0;
} else {
dmin = INT_MAX;
cmin = 0;
if (count <= 16) {
for (i = 0; i < count; i++) {
d = color_dist(color, colors[i]);
if (d < dmin) {
cmin = i;
dmin = d;
}
}
} else { /* if (dmin > 0 && count > 16) */
unsigned int r = (color >> 16) & 0xff;
unsigned int g = (color >> 8) & 0xff;
unsigned int b = (color >> 0) & 0xff;
#if 0
if (r == g && g == b) {
i = scale_grey[r];
d = color_dist(color, colors[i]);
if (d < dmin) {
cmin = i;
dmin = d;
}
} else {
/* XXX: should use more precise projection */
i = 16 + scale_cube[r] * 36 +
scale_cube[g] * 6 +
scale_cube[b];
d = color_dist(color, colors[i]);
if (d < dmin) {
cmin = i;
dmin = d;
}
}
#else
if (r == g && g == b) {
/* color is a gray tone:
* map to the closest palette entry
*/
d = color_dist(color, colors[16]);
if (d < dmin) {
cmin = 16;
dmin = d;
}
for (i = 231; i < 256; i++) {
d = color_dist(color, colors[i]);
if (d < dmin) {
cmin = i;
dmin = d;
}
}
} else {
/* general case: try and match a palette entry
* from the 6x6x6 color cube .
*/
/* XXX: this causes gliches on true color terminals
* with a non standard xterm palette, such as iTerm2.
* On true color terminals, we should treat palette
* colors and rgb colors differently in the shell buffer
* terminal emulator.
*/
for (i = 16; i < 232; i++) {
d = color_dist(color, colors[i]);
if (d < dmin) {
cmin = i;
dmin = d;
}
}
}
#endif
if (dmin > 0 && count >= 4096) {
/* 13-bit 7936 color system */
d = 0;
for (;;) {
if (r == g) {
if (g == b) { /* #xxxxxx */
i = 0x700 + r;
break;
}
if (r == 0) { /* #0000xx */
i = 0x100 + b;
break;
}
if (r == 255) { /* #FFFFxx */
i = 0x800 + 0x100 + b;
break;
}
if (b == 0) { /* #xxxx00 */
i = 0x600 + r;
break;
}
if (b == 255) { /* #xxxxFF */
i = 0x800 + 0x600 + r;
break;
}
} else
if (r == b) {
if (r == 0) { /* #00xx00 */
i = 0x200 + g;
break;
}
if (r == 255) { /* #FFxxFF */
i = 0x800 + 0x200 + g;
break;
}
if (g == 0) { /* #xx00xx */
i = 0x500 + r;
break;
}
if (g == 255) { /* #xxFFxx */
i = 0x800 + 0x500 + r;
break;
}
} else
if (g == b) {
if (g == 0) { /* #xx0000 */
i = 0x400 + r;
break;
}
if (g == 255) { /* #xxFFFF */
i = 0x800 + 0x400 + r;
break;
}
if (r == 0) { /* #00xxxx */
i = 0x300 + g;
break;
}
if (r == 255) { /* #FFxxxx */
i = 0x800 + 0x300 + g;
break;
}
}
i = 0x1000 | ((r >> 4) << 8) | ((g >> 4) << 4) | (b >> 4);
d = color_dist(color, (color & 0xF0F0F0) | ((color & 0xF0F0F0) >> 4));
break;
}
if (d < dmin) {
cmin = i;
dmin = d;
}
}
}
}
if (dist) {
*dist = dmin;
}
return cmin;
}
/* Convert a composite color to an RGB triplet */
QEColor qe_unmap_color(int color, int count) {
/* XXX: Should use an 8K array for all colors <= 8192 */
if (color < 256) {
return xterm_colors[color];
}
if (color < 8192) {
/* 13-bit 7936 color system */
if (color & 0x1000) {
/* explicit 12-bit color */
QEColor rgb = (((color & 0xF00) << 12) |
((color & 0x0F0) << 4) |
((color & 0x00F) << 0));
return rgb | (rgb << 4);
}
if ((color & 0xf00) < 0xf00) {
/* 256 level color ramps */
/* 0x800 is unused and converts to white */
int r, g, b;
r = g = b = color & 0xFF;
if (!(color & 0x400)) r = -(color >> 11) & 0xFF;
if (!(color & 0x200)) g = -(color >> 11) & 0xFF;
if (!(color & 0x100)) b = -(color >> 11) & 0xFF;
return QERGB(r, g, b);
} else {
/* 0xf00 indicates the standard xterm color palette */
return xterm_colors[color & 255];
}
}
/* explicit RGB color */
return color & 0xFFFFFF;
}
void color_complete(CompleteState *cp)
{
ColorDef const *def;
int count;
def = qe_colors;
count = nb_qe_colors;
while (count > 0) {
if (strxstart(def->name, cp->current, NULL))
add_string(&cp->cs, def->name, 0);
def++;
count--;
}
}
static int css_lookup_color(ColorDef const *def, int count,
const char *name)
{
int i;
for (i = 0; i < count; i++) {
if (!strxcmp(def[i].name, name))
return i;
}
return -1;
}
int css_define_color(const char *name, const char *value)
{
ColorDef *def;
QEColor color;
int index;
/* Check color validity */
if (css_get_color(&color, value))
return -1;
/* First color definition: allocate modifiable array */
if (qe_colors == default_colors) {
qe_colors = qe_malloc_dup(default_colors, sizeof(default_colors));
}
/* Make room: reallocate table in chunks of 8 entries */
if (((nb_qe_colors - nb_default_colors) & 7) == 0) {
if (!qe_realloc(&qe_colors,
(nb_qe_colors + 8) * sizeof(ColorDef))) {
return -1;
}
}
/* Check for redefinition */
index = css_lookup_color(qe_colors, nb_qe_colors, name);
if (index >= 0) {
qe_colors[index].color = color;
return 0;
}
def = &qe_colors[nb_qe_colors];
def->name = qe_strdup(name);
def->color = color;
nb_qe_colors++;
return 0;
}
void css_free_colors(void)
{
if (qe_colors != default_colors) {
while (nb_qe_colors > nb_default_colors) {
nb_qe_colors--;
qe_free((char **)&qe_colors[nb_qe_colors].name);
}
qe_free(&qe_colors);
qe_colors = (ColorDef *)default_colors;
}
}
/* XXX: make HTML parsing optional ? */
int css_get_color(QEColor *color_ptr, const char *p)
{
ColorDef const *def;
int count, index, len, v, i, n;
unsigned char rgba[4];
/* search in tables */
def = qe_colors;
count = nb_qe_colors;
index = css_lookup_color(def, count, p);
if (index >= 0) {
*color_ptr = def[index].color;
return 0;
}
rgba[3] = 0xff;
if (qe_isxdigit((unsigned char)*p)) {
goto parse_num;
} else
if (*p == '#') {
/* handle '#' notation */
p++;
parse_num:
len = strlen(p);
switch (len) {
case 3:
for (i = 0; i < 3; i++) {
v = qe_digit_value(*p++);
rgba[i] = v | (v << 4);
}
break;
case 6:
for (i = 0; i < 3; i++) {
v = qe_digit_value(*p++) << 4;
v |= qe_digit_value(*p++);
rgba[i] = v;
}
break;
default:
/* error */
return -1;
}
} else
if (strstart(p, "rgb(", &p)) {
n = 3;
goto parse_rgba;
} else
if (strstart(p, "rgba(", &p)) {
/* extension for alpha */
n = 4;
parse_rgba:
for (i = 0; i < n; i++) {
/* XXX: floats ? */
qe_skip_spaces(&p);
v = strtol(p, (char **)&p, 0);
if (*p == '%') {
v = (v * 255) / 100;
p++;
}
rgba[i] = v;
if (qe_skip_spaces(&p) == ',')
p++;
}
} else {
return -1;
}
*color_ptr = (rgba[0] << 16) | (rgba[1] << 8) |
(rgba[2]) | (rgba[3] << 24);
return 0;
}
/* return 0 if unknown font */
int css_get_font_family(const char *str)
{
int v;
if (!strcasecmp(str, "serif") ||
!strcasecmp(str, "times"))
v = QE_FONT_FAMILY_SERIF;
else
if (!strcasecmp(str, "sans") ||
!strcasecmp(str, "arial") ||
!strcasecmp(str, "helvetica"))
v = QE_FONT_FAMILY_SANS;
else
if (!strcasecmp(str, "fixed") ||
!strcasecmp(str, "monospace") ||
!strcasecmp(str, "courier"))
v = QE_FONT_FAMILY_FIXED;
else
v = 0; /* inherit */
return v;
}
#endif /* style stuff */
/* a = a union b */
void css_union_rect(CSSRect *a, const CSSRect *b)
{
if (css_is_null_rect(b))
return;
if (css_is_null_rect(a)) {
*a = *b;
} else {
if (b->x1 < a->x1)
a->x1 = b->x1;
if (b->y1 < a->y1)
a->y1 = b->y1;
if (b->x2 > a->x2)
a->x2 = b->x2;
if (b->y2 > a->y2)
a->y2 = b->y2;
}
}
#ifdef __TINYC__
/* the glibc folks use wrappers, but forgot to put a compatibility
function for non GCC compilers ! */
int stat(__const char *__path,
struct stat *__statbuf)
{
return __xstat(_STAT_VER, __path, __statbuf);
}
#endif
int get_clock_ms(void)
{
#ifdef CONFIG_WIN32
struct _timeb tb;
_ftime(&tb);
return tb.time * 1000 + tb.millitm;
#else
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec * 1000 + (tv.tv_usec / 1000);
#endif
}
int get_clock_usec(void)
{
#ifdef CONFIG_WIN32
struct _timeb tb;
_ftime(&tb);
return tb.time * 1000000 + tb.millitm * 1000;
#else
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec * 1000000 + tv.tv_usec;
#endif
}
/* set one string. */
StringItem *set_string(StringArray *cs, int index, const char *str, int group)
{
StringItem *v;
int len;
if (index >= cs->nb_items)
return NULL;
len = strlen(str);
v = qe_malloc_hack(StringItem, len);
if (!v)
return NULL;
v->selected = 0;
v->group = group;
memcpy(v->str, str, len + 1);
if (cs->items[index])
qe_free(&cs->items[index]);
cs->items[index] = v;
return v;
}
/* make a generic array alloc */
StringItem *add_string(StringArray *cs, const char *str, int group)
{
int n;
if (cs->nb_items >= cs->nb_allocated) {
n = cs->nb_allocated + 32;
if (!qe_realloc(&cs->items, n * sizeof(StringItem *)))
return NULL;
cs->nb_allocated = n;
}
cs->items[cs->nb_items++] = NULL;
return set_string(cs, cs->nb_items - 1, str, group);
}
void free_strings(StringArray *cs)
{
int i;
for (i = 0; i < cs->nb_items; i++)
qe_free(&cs->items[i]);
qe_free(&cs->items);
memset(cs, 0, sizeof(StringArray));
}
/**
* Add a memory region to a dynamic string. In case of allocation
* failure, the data is not added. The dynamic string is guaranteed to
* be 0 terminated, although it can be longer if it contains zeros.
*
* @return 0 if OK, -1 if allocation error.
*/
int qmemcat(QString *q, const unsigned char *data1, int len1)
{
int new_len, len, alloc_size;
len = q->len;
new_len = len + len1;
/* see if we got a new power of two */
/* NOTE: we got this trick from the excellent 'links' browser */
if ((len ^ new_len) >= len) {
/* find immediately bigger 2^n - 1 */
alloc_size = new_len;
alloc_size |= (alloc_size >> 1);
alloc_size |= (alloc_size >> 2);
alloc_size |= (alloc_size >> 4);
alloc_size |= (alloc_size >> 8);
alloc_size |= (alloc_size >> 16);
/* allocate one more byte for end of string marker */
if (!qe_realloc(&q->data, alloc_size + 1))
return -1;
}
memcpy(q->data + len, data1, len1);
q->data[new_len] = '\0'; /* we force a trailing '\0' */
q->len = new_len;
return 0;
}
/*
* add a string to a dynamic string
*/
int qstrcat(QString *q, const char *str)
{
return qmemcat(q, (unsigned char *)str, strlen(str));
}
/* XXX: we use a fixed size buffer */
int qprintf(QString *q, const char *fmt, ...)
{
char buf[4096];
va_list ap;
int len, ret;
va_start(ap, fmt);
len = vsnprintf(buf, sizeof(buf), fmt, ap);
/* avoid problems for non C99 snprintf() which can return -1 if overflow */
if (len < 0)
len = strlen(buf);
ret = qmemcat(q, (unsigned char *)buf, len);
va_end(ap);
return ret;
}
int buf_write(buf_t *bp, const void *src, int size)
{
int n = 0;
if (bp->pos < bp->size) {
int n = bp->size - bp->pos - 1;
if (n > size)
n = size;
memcpy(bp->buf + bp->len, src, n);
bp->len += n;
bp->buf[bp->len] = '\0';
}
bp->pos += size;
return n;
}
int buf_printf(buf_t *bp, const char *fmt, ...)
{
va_list ap;
int len;
va_start(ap, fmt);
len = vsnprintf(bp->buf + bp->len,
(bp->pos < bp->size) ? bp->size - bp->pos : 1, fmt, ap);
va_end(ap);
bp->pos += len;
bp->len += len;
if (bp->len >= bp->size) {
bp->len = bp->size - 1;
if (bp->len < 0)
bp->len = 0;
}
return len;
}
int buf_putc_utf8(buf_t *bp, int c)
{
if (c < 0x80) {
bp->pos++;
if (bp->pos < bp->size) {
bp->buf[bp->len++] = c;
bp->buf[bp->len] = '\0';
return 1;
} else {
return 0;
}
} else {
char buf[6];
int len;
len = utf8_encode(buf, c);
if (bp->pos + len < bp->size) {
memcpy(bp->buf + bp->len, buf, len);
bp->pos += len;
bp->len += len;
bp->buf[bp->len] = '\0';
return len;
}
bp->pos += len;
return 0;
}
}
int strsubst(char *buf, int buf_size, const char *from,
const char *s1, const char *s2)
{
const char *p, *q;
buf_t outbuf, *out;
out = buf_init(&outbuf, buf, buf_size);
p = from;
while ((q = strstr(p, s1)) != NULL) {
buf_write(out, p, q - p);
buf_puts(out, s2);
p = q + strlen(s1);
}
buf_puts(out, p);
return out->pos;
}
int strquote(char *dest, int size, const char *str, int len)
{
if (str) {
if (len < 0)
len = strlen(str);
/* TODO: quote special chars */
return snprintf(dest, size, "\"%.*s\"", len, str);
} else {
return snprintf(dest, size, "null");
}
}
#if 0
/* TODO */
int strunquote(char *dest, int size, const char *str, int len)
{
}
#endif
/*---------------- allocation routines ----------------*/
void *qe_malloc_bytes(size_t size)
{
return (malloc)(size);
}
void *qe_mallocz_bytes(size_t size)
{
void *p = (malloc)(size);
if (p)
memset(p, 0, size);
return p;
}
void *qe_malloc_dup(const void *src, size_t size)
{
void *p = (malloc)(size);
if (p)
memcpy(p, src, size);
return p;
}
char *qe_strdup(const char *str)
{
size_t size = strlen(str) + 1;
char *p = (malloc)(size);
if (p)
memcpy(p, str, size);
return p;
}
void *qe_realloc(void *pp, size_t size)
{
void *p = (realloc)(*(void **)pp, size);
if (p || !size)
*(void **)pp = p;
return p;
}
/*---------------- bounded strings ----------------*/
/* get the n-th string from a `|` separated list */
bstr_t bstr_get_nth(const char *s, int n) {
bstr_t bs;
for (bs.s = s;; s++) {
if (*s == '\0' || *s == '|') {
if (n-- == 0) {
bs.len = s - bs.s;
break;
}
if (*s) {
bs.s = s + 1;
} else {
bs.len = 0;
bs.s = NULL;
break;
}
}
}
return bs;
}
/* get the first string from a list and push pointer */
bstr_t bstr_token(const char *s, int sep, const char **pp) {
bstr_t bs = { s, 0 };
if (s) {
/* XXX: should special case spaces? */
for (; *s != '\0' && *s != sep; s++)
continue;
bs.len = s - bs.s;
if (pp) {
*pp = *s ? s + 1 : NULL;
}
}
return bs;
}
/*---------------- qe_qsort_r ----------------*/
/* Our own implementation of qsort_r() since it is not available
* on some targets, such as OpenBSD.
*/
typedef void (*exchange_f)(void *a, void *b, size_t size);
static void exchange_bytes(void *a, void *b, size_t size) {
unsigned char t;
unsigned char *ac = (unsigned char *)a;
unsigned char *bc = (unsigned char *)b;
while (size-- > 0) {
t = *ac;
*ac++ = *bc;
*bc++ = t;
}
}
static void exchange_ints(void *a, void *b, size_t size) {
int *ai = (int *)a;
int *bi = (int *)b;
for (size /= sizeof(int); size-- != 0;) {
int t = *ai;
*ai++ = *bi;
*bi++ = t;
}
}
static void exchange_one_int(void *a, void *b, size_t size) {
int *ai = (int *)a;
int *bi = (int *)b;
int t = *ai;
*ai = *bi;
*bi = t;
}
#if LONG_MAX != INT_MAX
static void exchange_longs(void *a, void *b, size_t size) {
long *ai = (long *)a;
long *bi = (long *)b;
for (size /= sizeof(long); size-- != 0;) {
long t = *ai;
*ai++ = *bi;
*bi++ = t;
}
}
static void exchange_one_long(void *a, void *b, size_t size) {
long *ai = (long *)a;
long *bi = (long *)b;
long t = *ai;
*ai = *bi;
*bi = t;
}
#endif
static inline exchange_f exchange_func(void *base, size_t size) {
exchange_f exchange = exchange_bytes;
#if LONG_MAX != INT_MAX
if ((((uintptr_t)base | (uintptr_t)size) & (sizeof(long) - 1)) == 0) {
exchange = exchange_longs;
if (size == sizeof(long))
exchange = exchange_one_long;
} else
#endif
if ((((uintptr_t)base | (uintptr_t)size) & (sizeof(int) - 1)) == 0) {
exchange = exchange_ints;
if (size == sizeof(int))
exchange = exchange_one_int;
}
return exchange;
}
static inline void *med3_r(void *a, void *b, void *c, void *thunk,
int (*compare)(void *, const void *, const void *))
{
return compare(thunk, a, b) < 0 ?
(compare(thunk, b, c) < 0 ? b : (compare(thunk, a, c) < 0 ? c : a )) :
(compare(thunk, b, c) > 0 ? b : (compare(thunk, a, c) < 0 ? a : c ));
}
#define MAXSTACK 64
void qe_qsort_r(void *base, size_t nmemb, size_t size, void *thunk,
int (*compare)(void *, const void *, const void *))
{
struct {
unsigned char *base;
size_t count;
} stack[MAXSTACK], *sp;
size_t m0, n;
unsigned char *lb, *m, *i, *j;
exchange_f exchange = exchange_func(base, size);
if (nmemb < 2 || size <= 0)
return;
sp = stack;
sp->base = base;
sp->count = nmemb;
sp++;
while (sp-- > stack) {
lb = sp->base;
n = sp->count;
while (n >= 7) {
/* partition into two segments */
i = lb + size;
j = lb + (n - 1) * size;
/* select pivot and exchange with 1st element */
m0 = (n >> 2) * size;
/* should use median of 3 or 9 */
m = med3_r(lb + m0, lb + 2 * m0, lb + 3 * m0, thunk, compare);
exchange(lb, m, size);
m0 = n - 1; /* m is the offset of j */
for (;;) {
while (i < j && compare(thunk, lb, i) > 0) {
i += size;
}
while (j >= i && compare(thunk, j, lb) > 0) {
j -= size;
m0--;
}
if (i >= j)
break;
exchange(i, j, size);
i += size;
j -= size;
m0--;
}
/* pivot belongs in A[j] */
exchange(lb, j, size);
/* keep processing smallest segment,
* and stack largest */
n = n - m0 - 1;
if (m0 < n) {
if (n > 1) {
sp->base = j + size;
sp->count = n;
sp++;
}
n = m0;
} else {
if (m0 > 1) {
sp->base = lb;
sp->count = m0;
sp++;
}
lb = j + size;
}
}
/* Use insertion sort for small fragments */
for (i = lb + size, j = lb + n * size; i < j; i += size) {
for (m = i; m > lb && compare(thunk, m - size, m) > 0; m -= size)
exchange(m, m - size, size);
}
}
}