080b4a0c0f
Compared to current version in base: - great improvements on the Unicode support - full support for filename completion including quoting which means we do not need anymore our custom addition) - Improved readline compatiblity Upgrading libedit has been a pain in the past, because somehow we never managed to properly cleanup the tree in lib/libedit and each merge has always been very painful. After years of fighting give up and refresh a merge from scrarch properly in contrib. Note that the switch to this version will be done in another commit.
670 lines
16 KiB
C
670 lines
16 KiB
C
/* $NetBSD: keymacro.c,v 1.24 2019/07/23 10:18:52 christos Exp $ */
|
|
|
|
/*-
|
|
* Copyright (c) 1992, 1993
|
|
* The Regents of the University of California. All rights reserved.
|
|
*
|
|
* This code is derived from software contributed to Berkeley by
|
|
* Christos Zoulas of Cornell University.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. Neither the name of the University nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#if !defined(lint) && !defined(SCCSID)
|
|
#if 0
|
|
static char sccsid[] = "@(#)key.c 8.1 (Berkeley) 6/4/93";
|
|
#else
|
|
__RCSID("$NetBSD: keymacro.c,v 1.24 2019/07/23 10:18:52 christos Exp $");
|
|
#endif
|
|
#endif /* not lint && not SCCSID */
|
|
|
|
/*
|
|
* keymacro.c: This module contains the procedures for maintaining
|
|
* the extended-key map.
|
|
*
|
|
* An extended-key (key) is a sequence of keystrokes introduced
|
|
* with a sequence introducer and consisting of an arbitrary
|
|
* number of characters. This module maintains a map (the
|
|
* el->el_keymacro.map)
|
|
* to convert these extended-key sequences into input strs
|
|
* (XK_STR) or editor functions (XK_CMD).
|
|
*
|
|
* Warning:
|
|
* If key is a substr of some other keys, then the longer
|
|
* keys are lost!! That is, if the keys "abcd" and "abcef"
|
|
* are in el->el_keymacro.map, adding the key "abc" will cause
|
|
* the first two definitions to be lost.
|
|
*
|
|
* Restrictions:
|
|
* -------------
|
|
* 1) It is not possible to have one key that is a
|
|
* substr of another.
|
|
*/
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "el.h"
|
|
#include "fcns.h"
|
|
|
|
/*
|
|
* The Nodes of the el->el_keymacro.map. The el->el_keymacro.map is a
|
|
* linked list of these node elements
|
|
*/
|
|
struct keymacro_node_t {
|
|
wchar_t ch; /* single character of key */
|
|
int type; /* node type */
|
|
keymacro_value_t val; /* command code or pointer to str, */
|
|
/* if this is a leaf */
|
|
struct keymacro_node_t *next; /* ptr to next char of this key */
|
|
struct keymacro_node_t *sibling;/* ptr to another key with same prefix*/
|
|
};
|
|
|
|
static int node_trav(EditLine *, keymacro_node_t *, wchar_t *,
|
|
keymacro_value_t *);
|
|
static int node__try(EditLine *, keymacro_node_t *,
|
|
const wchar_t *, keymacro_value_t *, int);
|
|
static keymacro_node_t *node__get(wint_t);
|
|
static void node__free(keymacro_node_t *);
|
|
static void node__put(EditLine *, keymacro_node_t *);
|
|
static int node__delete(EditLine *, keymacro_node_t **,
|
|
const wchar_t *);
|
|
static int node_lookup(EditLine *, const wchar_t *,
|
|
keymacro_node_t *, size_t);
|
|
static int node_enum(EditLine *, keymacro_node_t *, size_t);
|
|
|
|
#define KEY_BUFSIZ EL_BUFSIZ
|
|
|
|
|
|
/* keymacro_init():
|
|
* Initialize the key maps
|
|
*/
|
|
libedit_private int
|
|
keymacro_init(EditLine *el)
|
|
{
|
|
|
|
el->el_keymacro.buf = el_calloc(KEY_BUFSIZ,
|
|
sizeof(*el->el_keymacro.buf));
|
|
if (el->el_keymacro.buf == NULL)
|
|
return -1;
|
|
el->el_keymacro.map = NULL;
|
|
keymacro_reset(el);
|
|
return 0;
|
|
}
|
|
|
|
/* keymacro_end():
|
|
* Free the key maps
|
|
*/
|
|
libedit_private void
|
|
keymacro_end(EditLine *el)
|
|
{
|
|
|
|
el_free(el->el_keymacro.buf);
|
|
el->el_keymacro.buf = NULL;
|
|
node__free(el->el_keymacro.map);
|
|
}
|
|
|
|
|
|
/* keymacro_map_cmd():
|
|
* Associate cmd with a key value
|
|
*/
|
|
libedit_private keymacro_value_t *
|
|
keymacro_map_cmd(EditLine *el, int cmd)
|
|
{
|
|
|
|
el->el_keymacro.val.cmd = (el_action_t) cmd;
|
|
return &el->el_keymacro.val;
|
|
}
|
|
|
|
|
|
/* keymacro_map_str():
|
|
* Associate str with a key value
|
|
*/
|
|
libedit_private keymacro_value_t *
|
|
keymacro_map_str(EditLine *el, wchar_t *str)
|
|
{
|
|
|
|
el->el_keymacro.val.str = str;
|
|
return &el->el_keymacro.val;
|
|
}
|
|
|
|
|
|
/* keymacro_reset():
|
|
* Takes all nodes on el->el_keymacro.map and puts them on free list.
|
|
* Then initializes el->el_keymacro.map with arrow keys
|
|
* [Always bind the ansi arrow keys?]
|
|
*/
|
|
libedit_private void
|
|
keymacro_reset(EditLine *el)
|
|
{
|
|
|
|
node__put(el, el->el_keymacro.map);
|
|
el->el_keymacro.map = NULL;
|
|
return;
|
|
}
|
|
|
|
|
|
/* keymacro_get():
|
|
* Calls the recursive function with entry point el->el_keymacro.map
|
|
* Looks up *ch in map and then reads characters until a
|
|
* complete match is found or a mismatch occurs. Returns the
|
|
* type of the match found (XK_STR or XK_CMD).
|
|
* Returns NULL in val.str and XK_STR for no match.
|
|
* Returns XK_NOD for end of file or read error.
|
|
* The last character read is returned in *ch.
|
|
*/
|
|
libedit_private int
|
|
keymacro_get(EditLine *el, wchar_t *ch, keymacro_value_t *val)
|
|
{
|
|
|
|
return node_trav(el, el->el_keymacro.map, ch, val);
|
|
}
|
|
|
|
|
|
/* keymacro_add():
|
|
* Adds key to the el->el_keymacro.map and associates the value in
|
|
* val with it. If key is already is in el->el_keymacro.map, the new
|
|
* code is applied to the existing key. Ntype specifies if code is a
|
|
* command, an out str or a unix command.
|
|
*/
|
|
libedit_private void
|
|
keymacro_add(EditLine *el, const wchar_t *key, keymacro_value_t *val,
|
|
int ntype)
|
|
{
|
|
|
|
if (key[0] == '\0') {
|
|
(void) fprintf(el->el_errfile,
|
|
"keymacro_add: Null extended-key not allowed.\n");
|
|
return;
|
|
}
|
|
if (ntype == XK_CMD && val->cmd == ED_SEQUENCE_LEAD_IN) {
|
|
(void) fprintf(el->el_errfile,
|
|
"keymacro_add: sequence-lead-in command not allowed\n");
|
|
return;
|
|
}
|
|
if (el->el_keymacro.map == NULL)
|
|
/* tree is initially empty. Set up new node to match key[0] */
|
|
el->el_keymacro.map = node__get(key[0]);
|
|
/* it is properly initialized */
|
|
|
|
/* Now recurse through el->el_keymacro.map */
|
|
(void) node__try(el, el->el_keymacro.map, key, val, ntype);
|
|
return;
|
|
}
|
|
|
|
|
|
/* keymacro_clear():
|
|
*
|
|
*/
|
|
libedit_private void
|
|
keymacro_clear(EditLine *el, el_action_t *map, const wchar_t *in)
|
|
{
|
|
if (*in > N_KEYS) /* can't be in the map */
|
|
return;
|
|
if ((map[(unsigned char)*in] == ED_SEQUENCE_LEAD_IN) &&
|
|
((map == el->el_map.key &&
|
|
el->el_map.alt[(unsigned char)*in] != ED_SEQUENCE_LEAD_IN) ||
|
|
(map == el->el_map.alt &&
|
|
el->el_map.key[(unsigned char)*in] != ED_SEQUENCE_LEAD_IN)))
|
|
(void) keymacro_delete(el, in);
|
|
}
|
|
|
|
|
|
/* keymacro_delete():
|
|
* Delete the key and all longer keys staring with key, if
|
|
* they exists.
|
|
*/
|
|
libedit_private int
|
|
keymacro_delete(EditLine *el, const wchar_t *key)
|
|
{
|
|
|
|
if (key[0] == '\0') {
|
|
(void) fprintf(el->el_errfile,
|
|
"keymacro_delete: Null extended-key not allowed.\n");
|
|
return -1;
|
|
}
|
|
if (el->el_keymacro.map == NULL)
|
|
return 0;
|
|
|
|
(void) node__delete(el, &el->el_keymacro.map, key);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* keymacro_print():
|
|
* Print the binding associated with key key.
|
|
* Print entire el->el_keymacro.map if null
|
|
*/
|
|
libedit_private void
|
|
keymacro_print(EditLine *el, const wchar_t *key)
|
|
{
|
|
|
|
/* do nothing if el->el_keymacro.map is empty and null key specified */
|
|
if (el->el_keymacro.map == NULL && *key == 0)
|
|
return;
|
|
|
|
el->el_keymacro.buf[0] = '"';
|
|
if (node_lookup(el, key, el->el_keymacro.map, (size_t)1) <= -1)
|
|
/* key is not bound */
|
|
(void) fprintf(el->el_errfile, "Unbound extended key \"%ls"
|
|
"\"\n", key);
|
|
return;
|
|
}
|
|
|
|
|
|
/* node_trav():
|
|
* recursively traverses node in tree until match or mismatch is
|
|
* found. May read in more characters.
|
|
*/
|
|
static int
|
|
node_trav(EditLine *el, keymacro_node_t *ptr, wchar_t *ch,
|
|
keymacro_value_t *val)
|
|
{
|
|
|
|
if (ptr->ch == *ch) {
|
|
/* match found */
|
|
if (ptr->next) {
|
|
/* key not complete so get next char */
|
|
if (el_wgetc(el, ch) != 1)
|
|
return XK_NOD;
|
|
return node_trav(el, ptr->next, ch, val);
|
|
} else {
|
|
*val = ptr->val;
|
|
if (ptr->type != XK_CMD)
|
|
*ch = '\0';
|
|
return ptr->type;
|
|
}
|
|
} else {
|
|
/* no match found here */
|
|
if (ptr->sibling) {
|
|
/* try next sibling */
|
|
return node_trav(el, ptr->sibling, ch, val);
|
|
} else {
|
|
/* no next sibling -- mismatch */
|
|
val->str = NULL;
|
|
return XK_STR;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* node__try():
|
|
* Find a node that matches *str or allocate a new one
|
|
*/
|
|
static int
|
|
node__try(EditLine *el, keymacro_node_t *ptr, const wchar_t *str,
|
|
keymacro_value_t *val, int ntype)
|
|
{
|
|
|
|
if (ptr->ch != *str) {
|
|
keymacro_node_t *xm;
|
|
|
|
for (xm = ptr; xm->sibling != NULL; xm = xm->sibling)
|
|
if (xm->sibling->ch == *str)
|
|
break;
|
|
if (xm->sibling == NULL)
|
|
xm->sibling = node__get(*str); /* setup new node */
|
|
ptr = xm->sibling;
|
|
}
|
|
if (*++str == '\0') {
|
|
/* we're there */
|
|
if (ptr->next != NULL) {
|
|
node__put(el, ptr->next);
|
|
/* lose longer keys with this prefix */
|
|
ptr->next = NULL;
|
|
}
|
|
switch (ptr->type) {
|
|
case XK_CMD:
|
|
case XK_NOD:
|
|
break;
|
|
case XK_STR:
|
|
if (ptr->val.str)
|
|
el_free(ptr->val.str);
|
|
break;
|
|
default:
|
|
EL_ABORT((el->el_errfile, "Bad XK_ type %d\n",
|
|
ptr->type));
|
|
break;
|
|
}
|
|
|
|
switch (ptr->type = ntype) {
|
|
case XK_CMD:
|
|
ptr->val = *val;
|
|
break;
|
|
case XK_STR:
|
|
if ((ptr->val.str = wcsdup(val->str)) == NULL)
|
|
return -1;
|
|
break;
|
|
default:
|
|
EL_ABORT((el->el_errfile, "Bad XK_ type %d\n", ntype));
|
|
break;
|
|
}
|
|
} else {
|
|
/* still more chars to go */
|
|
if (ptr->next == NULL)
|
|
ptr->next = node__get(*str); /* setup new node */
|
|
(void) node__try(el, ptr->next, str, val, ntype);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* node__delete():
|
|
* Delete node that matches str
|
|
*/
|
|
static int
|
|
node__delete(EditLine *el, keymacro_node_t **inptr, const wchar_t *str)
|
|
{
|
|
keymacro_node_t *ptr;
|
|
keymacro_node_t *prev_ptr = NULL;
|
|
|
|
ptr = *inptr;
|
|
|
|
if (ptr->ch != *str) {
|
|
keymacro_node_t *xm;
|
|
|
|
for (xm = ptr; xm->sibling != NULL; xm = xm->sibling)
|
|
if (xm->sibling->ch == *str)
|
|
break;
|
|
if (xm->sibling == NULL)
|
|
return 0;
|
|
prev_ptr = xm;
|
|
ptr = xm->sibling;
|
|
}
|
|
if (*++str == '\0') {
|
|
/* we're there */
|
|
if (prev_ptr == NULL)
|
|
*inptr = ptr->sibling;
|
|
else
|
|
prev_ptr->sibling = ptr->sibling;
|
|
ptr->sibling = NULL;
|
|
node__put(el, ptr);
|
|
return 1;
|
|
} else if (ptr->next != NULL &&
|
|
node__delete(el, &ptr->next, str) == 1) {
|
|
if (ptr->next != NULL)
|
|
return 0;
|
|
if (prev_ptr == NULL)
|
|
*inptr = ptr->sibling;
|
|
else
|
|
prev_ptr->sibling = ptr->sibling;
|
|
ptr->sibling = NULL;
|
|
node__put(el, ptr);
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
/* node__put():
|
|
* Puts a tree of nodes onto free list using free(3).
|
|
*/
|
|
static void
|
|
node__put(EditLine *el, keymacro_node_t *ptr)
|
|
{
|
|
if (ptr == NULL)
|
|
return;
|
|
|
|
if (ptr->next != NULL) {
|
|
node__put(el, ptr->next);
|
|
ptr->next = NULL;
|
|
}
|
|
node__put(el, ptr->sibling);
|
|
|
|
switch (ptr->type) {
|
|
case XK_CMD:
|
|
case XK_NOD:
|
|
break;
|
|
case XK_STR:
|
|
if (ptr->val.str != NULL)
|
|
el_free(ptr->val.str);
|
|
break;
|
|
default:
|
|
EL_ABORT((el->el_errfile, "Bad XK_ type %d\n", ptr->type));
|
|
break;
|
|
}
|
|
el_free(ptr);
|
|
}
|
|
|
|
|
|
/* node__get():
|
|
* Returns pointer to a keymacro_node_t for ch.
|
|
*/
|
|
static keymacro_node_t *
|
|
node__get(wint_t ch)
|
|
{
|
|
keymacro_node_t *ptr;
|
|
|
|
ptr = el_malloc(sizeof(*ptr));
|
|
if (ptr == NULL)
|
|
return NULL;
|
|
ptr->ch = ch;
|
|
ptr->type = XK_NOD;
|
|
ptr->val.str = NULL;
|
|
ptr->next = NULL;
|
|
ptr->sibling = NULL;
|
|
return ptr;
|
|
}
|
|
|
|
static void
|
|
node__free(keymacro_node_t *k)
|
|
{
|
|
if (k == NULL)
|
|
return;
|
|
node__free(k->sibling);
|
|
node__free(k->next);
|
|
el_free(k);
|
|
}
|
|
|
|
/* node_lookup():
|
|
* look for the str starting at node ptr.
|
|
* Print if last node
|
|
*/
|
|
static int
|
|
node_lookup(EditLine *el, const wchar_t *str, keymacro_node_t *ptr,
|
|
size_t cnt)
|
|
{
|
|
ssize_t used;
|
|
|
|
if (ptr == NULL)
|
|
return -1; /* cannot have null ptr */
|
|
|
|
if (!str || *str == 0) {
|
|
/* no more chars in str. node_enum from here. */
|
|
(void) node_enum(el, ptr, cnt);
|
|
return 0;
|
|
} else {
|
|
/* If match put this char into el->el_keymacro.buf. Recurse */
|
|
if (ptr->ch == *str) {
|
|
/* match found */
|
|
used = ct_visual_char(el->el_keymacro.buf + cnt,
|
|
KEY_BUFSIZ - cnt, ptr->ch);
|
|
if (used == -1)
|
|
return -1; /* ran out of buffer space */
|
|
if (ptr->next != NULL)
|
|
/* not yet at leaf */
|
|
return (node_lookup(el, str + 1, ptr->next,
|
|
(size_t)used + cnt));
|
|
else {
|
|
/* next node is null so key should be complete */
|
|
if (str[1] == 0) {
|
|
size_t px = cnt + (size_t)used;
|
|
el->el_keymacro.buf[px] = '"';
|
|
el->el_keymacro.buf[px + 1] = '\0';
|
|
keymacro_kprint(el, el->el_keymacro.buf,
|
|
&ptr->val, ptr->type);
|
|
return 0;
|
|
} else
|
|
return -1;
|
|
/* mismatch -- str still has chars */
|
|
}
|
|
} else {
|
|
/* no match found try sibling */
|
|
if (ptr->sibling)
|
|
return (node_lookup(el, str, ptr->sibling,
|
|
cnt));
|
|
else
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* node_enum():
|
|
* Traverse the node printing the characters it is bound in buffer
|
|
*/
|
|
static int
|
|
node_enum(EditLine *el, keymacro_node_t *ptr, size_t cnt)
|
|
{
|
|
ssize_t used;
|
|
|
|
if (cnt >= KEY_BUFSIZ - 5) { /* buffer too small */
|
|
el->el_keymacro.buf[++cnt] = '"';
|
|
el->el_keymacro.buf[++cnt] = '\0';
|
|
(void) fprintf(el->el_errfile,
|
|
"Some extended keys too long for internal print buffer");
|
|
(void) fprintf(el->el_errfile, " \"%ls...\"\n",
|
|
el->el_keymacro.buf);
|
|
return 0;
|
|
}
|
|
if (ptr == NULL) {
|
|
#ifdef DEBUG_EDIT
|
|
(void) fprintf(el->el_errfile,
|
|
"node_enum: BUG!! Null ptr passed\n!");
|
|
#endif
|
|
return -1;
|
|
}
|
|
/* put this char at end of str */
|
|
used = ct_visual_char(el->el_keymacro.buf + cnt, KEY_BUFSIZ - cnt,
|
|
ptr->ch);
|
|
if (ptr->next == NULL) {
|
|
/* print this key and function */
|
|
el->el_keymacro.buf[cnt + (size_t)used ] = '"';
|
|
el->el_keymacro.buf[cnt + (size_t)used + 1] = '\0';
|
|
keymacro_kprint(el, el->el_keymacro.buf, &ptr->val, ptr->type);
|
|
} else
|
|
(void) node_enum(el, ptr->next, cnt + (size_t)used);
|
|
|
|
/* go to sibling if there is one */
|
|
if (ptr->sibling)
|
|
(void) node_enum(el, ptr->sibling, cnt);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* keymacro_kprint():
|
|
* Print the specified key and its associated
|
|
* function specified by val
|
|
*/
|
|
libedit_private void
|
|
keymacro_kprint(EditLine *el, const wchar_t *key, keymacro_value_t *val,
|
|
int ntype)
|
|
{
|
|
el_bindings_t *fp;
|
|
char unparsbuf[EL_BUFSIZ];
|
|
static const char fmt[] = "%-15s-> %s\n";
|
|
|
|
if (val != NULL)
|
|
switch (ntype) {
|
|
case XK_STR:
|
|
(void) keymacro__decode_str(val->str, unparsbuf,
|
|
sizeof(unparsbuf),
|
|
ntype == XK_STR ? "\"\"" : "[]");
|
|
(void) fprintf(el->el_outfile, fmt,
|
|
ct_encode_string(key, &el->el_scratch), unparsbuf);
|
|
break;
|
|
case XK_CMD:
|
|
for (fp = el->el_map.help; fp->name; fp++)
|
|
if (val->cmd == fp->func) {
|
|
wcstombs(unparsbuf, fp->name, sizeof(unparsbuf));
|
|
unparsbuf[sizeof(unparsbuf) -1] = '\0';
|
|
(void) fprintf(el->el_outfile, fmt,
|
|
ct_encode_string(key, &el->el_scratch), unparsbuf);
|
|
break;
|
|
}
|
|
#ifdef DEBUG_KEY
|
|
if (fp->name == NULL)
|
|
(void) fprintf(el->el_outfile,
|
|
"BUG! Command not found.\n");
|
|
#endif
|
|
|
|
break;
|
|
default:
|
|
EL_ABORT((el->el_errfile, "Bad XK_ type %d\n", ntype));
|
|
break;
|
|
}
|
|
else
|
|
(void) fprintf(el->el_outfile, fmt, ct_encode_string(key,
|
|
&el->el_scratch), "no input");
|
|
}
|
|
|
|
|
|
#define ADDC(c) \
|
|
if (b < eb) \
|
|
*b++ = c; \
|
|
else \
|
|
b++
|
|
/* keymacro__decode_str():
|
|
* Make a printable version of the ey
|
|
*/
|
|
libedit_private size_t
|
|
keymacro__decode_str(const wchar_t *str, char *buf, size_t len,
|
|
const char *sep)
|
|
{
|
|
char *b = buf, *eb = b + len;
|
|
const wchar_t *p;
|
|
|
|
b = buf;
|
|
if (sep[0] != '\0') {
|
|
ADDC(sep[0]);
|
|
}
|
|
if (*str == '\0') {
|
|
ADDC('^');
|
|
ADDC('@');
|
|
goto add_endsep;
|
|
}
|
|
for (p = str; *p != 0; p++) {
|
|
wchar_t dbuf[VISUAL_WIDTH_MAX];
|
|
wchar_t *p2 = dbuf;
|
|
ssize_t l = ct_visual_char(dbuf, VISUAL_WIDTH_MAX, *p);
|
|
while (l-- > 0) {
|
|
ssize_t n = ct_encode_char(b, (size_t)(eb - b), *p2++);
|
|
if (n == -1) /* ran out of space */
|
|
goto add_endsep;
|
|
else
|
|
b += n;
|
|
}
|
|
}
|
|
add_endsep:
|
|
if (sep[0] != '\0' && sep[1] != '\0') {
|
|
ADDC(sep[1]);
|
|
}
|
|
ADDC('\0');
|
|
if ((size_t)(b - buf) >= len)
|
|
buf[len - 1] = '\0';
|
|
return (size_t)(b - buf);
|
|
}
|