neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

terminfo.c (19000B)


      1 // Built-in fallback terminfo entries.
      2 
      3 #include <stdbool.h>
      4 #include <string.h>
      5 
      6 #ifdef HAVE_UNIBILIUM
      7 # include <unibilium.h>
      8 #endif
      9 
     10 #include "klib/kvec.h"
     11 #include "nvim/api/private/defs.h"
     12 #include "nvim/api/private/helpers.h"
     13 #include "nvim/ascii_defs.h"
     14 #include "nvim/charset.h"
     15 #include "nvim/memory.h"
     16 #include "nvim/tui/terminfo.h"
     17 #include "nvim/tui/terminfo_builtin.h"
     18 
     19 #ifdef __FreeBSD__
     20 # include "nvim/os/os.h"
     21 #endif
     22 
     23 typedef struct {
     24  long nums[20];
     25  char *strings[20];
     26  size_t offset;
     27 } TPSTACK;
     28 
     29 #include "tui/terminfo.c.generated.h"
     30 
     31 bool terminfo_is_term_family(const char *term, const char *family)
     32 {
     33  if (!term) {
     34    return false;
     35  }
     36  size_t tlen = strlen(term);
     37  size_t flen = strlen(family);
     38  return tlen >= flen
     39         && 0 == memcmp(term, family, flen)
     40         // Per commentary in terminfo, minus is the only valid suffix separator.
     41         // The screen terminfo may have a terminal name like screen.xterm. By making
     42         // the dot(.) a valid separator, such terminal names will also be the
     43         // terminal family of the screen.
     44         && (NUL == term[flen] || '-' == term[flen] || '.' == term[flen]);
     45 }
     46 
     47 bool terminfo_is_bsd_console(const char *term)
     48 {
     49 #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) \
     50  || defined(__DragonFly__)
     51  if (strequal(term, "vt220")         // OpenBSD
     52      || strequal(term, "vt100")) {   // NetBSD
     53    return true;
     54  }
     55 # if defined(__FreeBSD__)
     56  // FreeBSD console sets TERM=xterm, but it does not support xterm features
     57  // like cursor-shaping. Assume that TERM=xterm is degraded. #8644
     58  return strequal(term, "xterm") && os_env_exists("XTERM_VERSION", true);
     59 # endif
     60 #endif
     61  return false;
     62 }
     63 
     64 /// Loads a built-in terminfo db when we (unibilium) failed to load a terminfo
     65 /// record from the environment (termcap systems, unrecognized $TERM, …).
     66 /// We do not attempt to detect xterm pretenders here.
     67 ///
     68 /// @param term $TERM value
     69 /// @param[out,static] termname decided builtin 'term' name
     70 /// @return [allocated] terminfo structure
     71 const TerminfoEntry *terminfo_from_builtin(const char *term, char **termname)
     72 {
     73  if (strequal(term, "ghostty") || strequal(term, "xterm-ghostty")) {
     74    *termname = "ghostty";
     75    return &ghostty_terminfo;
     76  } else if (terminfo_is_term_family(term, "xterm")) {
     77    *termname = "xterm";
     78    return &xterm_256colour_terminfo;
     79  } else if (terminfo_is_term_family(term, "screen")) {
     80    *termname = "screen";
     81    return &screen_256colour_terminfo;
     82  } else if (terminfo_is_term_family(term, "tmux")) {
     83    *termname = "tmux";
     84    return &tmux_256colour_terminfo;
     85  } else if (terminfo_is_term_family(term, "rxvt")) {
     86    *termname = "rxvt";
     87    return &rxvt_256colour_terminfo;
     88  } else if (terminfo_is_term_family(term, "putty")) {
     89    *termname = "putty";
     90    return &putty_256colour_terminfo;
     91  } else if (terminfo_is_term_family(term, "linux")) {
     92    *termname = "linux";
     93    return &linux_16colour_terminfo;
     94  } else if (terminfo_is_term_family(term, "interix")) {
     95    *termname = "interix";
     96    return &interix_8colour_terminfo;
     97  } else if (terminfo_is_term_family(term, "iterm")
     98             || terminfo_is_term_family(term, "iterm2")
     99             || terminfo_is_term_family(term, "iTerm.app")
    100             || terminfo_is_term_family(term, "iTerm2.app")) {
    101    *termname = "iterm";
    102    return &iterm_256colour_terminfo;
    103  } else if (terminfo_is_term_family(term, "st")) {
    104    *termname = "st";
    105    return &st_256colour_terminfo;
    106  } else if (terminfo_is_term_family(term, "gnome")
    107             || terminfo_is_term_family(term, "vte")) {
    108    *termname = "vte";
    109    return &vte_256colour_terminfo;
    110  } else if (terminfo_is_term_family(term, "cygwin")) {
    111    *termname = "cygwin";
    112    return &cygwin_terminfo;
    113  } else if (terminfo_is_term_family(term, "win32con")) {
    114    *termname = "win32con";
    115    return &win32con_terminfo;
    116  } else if (terminfo_is_term_family(term, "conemu")) {
    117    *termname = "conemu";
    118    return &conemu_terminfo;
    119  } else if (terminfo_is_term_family(term, "vtpcon")) {
    120    *termname = "vtpcon";
    121    return &vtpcon_terminfo;
    122  } else {
    123    *termname = "ansi";
    124    return &ansi_terminfo;
    125  }
    126 }
    127 
    128 bool terminfo_from_database(TerminfoEntry *ti, char *termname, Arena *arena)
    129 {
    130 #ifdef HAVE_UNIBILIUM
    131  unibi_term *ut = unibi_from_term(termname);
    132  if (!ut) {
    133    return false;
    134  }
    135 
    136  ti->bce = unibi_get_bool(ut, unibi_back_color_erase);
    137  ti->max_colors = unibi_get_num(ut, unibi_max_colors);
    138  ti->lines = unibi_get_num(ut, unibi_lines);
    139  ti->columns = unibi_get_num(ut, unibi_columns);
    140 
    141  // Check for Tc or RGB
    142  ti->has_Tc_or_RGB = false;
    143  ti->Su = false;
    144  for (size_t i = 0; i < unibi_count_ext_bool(ut); i++) {
    145    const char *n = unibi_get_ext_bool_name(ut, i);
    146    if (n && (!strcmp(n, "Tc") || !strcmp(n, "RGB"))) {
    147      ti->has_Tc_or_RGB = true;
    148    } else if (n && !strcmp(n, "Su")) {
    149      ti->Su = true;
    150    }
    151  }
    152 
    153  static const enum unibi_string uni_ids[] = {
    154 # define X(name) unibi_##name,
    155    XLIST_TERMINFO_BUILTIN
    156 # undef X
    157  };
    158 
    159  for (size_t i = 0; i < ARRAY_SIZE(uni_ids); i++) {
    160    const char *val = unibi_get_str(ut, uni_ids[i]);
    161    ti->defs[i] = val ? arena_strdup(arena, val) : NULL;
    162  }
    163 
    164  static const char *uni_ext[] = {
    165 # define X(informal_name, terminfo_name) #terminfo_name,
    166    XLIST_TERMINFO_EXT
    167 # undef X
    168  };
    169 
    170  size_t max = unibi_count_ext_str(ut);
    171  for (size_t i = 0; i < ARRAY_SIZE(uni_ext); i++) {
    172    const char *name = uni_ext[i];
    173    for (size_t val = 0; val < max; val++) {
    174      const char *n = unibi_get_ext_str_name(ut, val);
    175      if (n && strequal(n, name)) {
    176        const char *data = unibi_get_ext_str(ut, val);
    177        ti->defs[kTermExtOffset + i] = data ? arena_strdup(arena, data) : NULL;
    178        break;
    179      }
    180    }
    181  }
    182 
    183 # define X(name) { unibi_key_##name, unibi_string_begin_ },
    184 # define Y(name) { unibi_key_##name, unibi_key_s##name },
    185  static const enum unibi_string uni_keys[][2] = {
    186    XYLIST_TERMINFO_KEYS
    187  };
    188 # undef X
    189 # undef Y
    190 
    191  for (size_t i = 0; i < ARRAY_SIZE(uni_keys); i++) {
    192    const char *val = unibi_get_str(ut, uni_keys[i][0]);
    193    if (val) {
    194      ti->keys[i][0] = arena_strdup(arena, val);
    195      if (uni_keys[i][1] != unibi_string_begin_) {
    196        const char *sval = unibi_get_str(ut, uni_keys[i][1]);
    197        ti->keys[i][1] = sval ? arena_strdup(arena, sval) : NULL;
    198      }
    199    }
    200  }
    201 
    202  static const enum unibi_string uni_fkeys[] = {
    203 # define X(name) unibi_key_##name,
    204    XLIST_TERMINFO_FKEYS
    205 # undef X
    206  };
    207 
    208  for (size_t i = 0; i < ARRAY_SIZE(uni_fkeys); i++) {
    209    const char *val = unibi_get_str(ut, uni_fkeys[i]);
    210    ti->f_keys[i] = val ? arena_strdup(arena, val) : NULL;
    211  }
    212 
    213  unibi_destroy(ut);
    214  return true;
    215 #else
    216  return false;
    217 #endif
    218 }
    219 
    220 static const char *fmt(bool val)
    221 {
    222  return val ? "true" : "false";
    223 }
    224 
    225 /// Dumps termcap info to the messages area.
    226 /// Serves a similar purpose as Vim `:set termcap` (removed in Nvim).
    227 ///
    228 /// @return allocated string
    229 String terminfo_info_msg(const TerminfoEntry *ti, const char *termname, bool from_db)
    230 {
    231  StringBuilder data = KV_INITIAL_VALUE;
    232 
    233  kv_printf(data, "&term: %s\n", termname);
    234  if (from_db) {
    235    kv_printf(data, "using terminfo database\n");
    236  } else {
    237    kv_printf(data, "using builtin terminfo\n");
    238  }
    239  kv_printf(data, "\n");
    240 
    241  kv_printf(data, "Boolean capabilities:\n");
    242  kv_printf(data, "  back_color_erase: %s\n", fmt(ti->bce));
    243  kv_printf(data, "  truecolor ('Tc' or 'RGB'): %s\n", fmt(ti->has_Tc_or_RGB));
    244  kv_printf(data, "  extended underline ('Su'): %s\n", fmt(ti->Su));
    245  kv_printf(data, "\n");
    246 
    247  kv_printf(data, "Numeric capabilities: (-1 for unknown)\n");
    248  kv_printf(data, "  lines: %d\n", ti->lines);
    249  kv_printf(data, "  columns: %d\n", ti->columns);
    250  kv_printf(data, "  max_colors: %d\n", ti->columns);
    251  kv_printf(data, "\n");
    252 
    253  kv_printf(data, "String capabilities:\n");
    254 
    255  static const char *string_names[] = {
    256 #define X(name) #name,
    257    XLIST_TERMINFO_BUILTIN
    258 #undef X
    259 #define X(internal_name, terminfo_name) (#internal_name " (" #terminfo_name ")"),
    260    XLIST_TERMINFO_EXT
    261 #undef X
    262  };
    263 
    264  for (size_t i = 0; i < ARRAY_SIZE(string_names); i++) {
    265    const char *s = ti->defs[i];
    266    if (s) {
    267      kv_printf(data, "  %-31s = ", string_names[i]);
    268      // Most of these strings will contain escape sequences.
    269      kv_transstr(&data, s, false);
    270      kv_push(data, '\n');
    271    }
    272  }
    273 
    274  static const char *key_names[] = {
    275 #define X(name) #name,
    276 #define Y(name) #name,
    277    XYLIST_TERMINFO_KEYS
    278 #undef X
    279 #undef Y
    280  };
    281 
    282  for (size_t i = 0 + 1; i < ARRAY_SIZE(key_names); i++) {
    283    const char *s = ti->keys[i][0];
    284    if (s) {
    285      kv_printf(data, "  key_%-27s = ", key_names[i]);
    286      kv_transstr(&data, s, false);
    287      const char *ss = ti->keys[i][1];
    288      if (ss) {
    289        kv_printf(data, ", key_s%s = ", key_names[i]);
    290        kv_transstr(&data, ss, false);
    291      }
    292      kv_push(data, '\n');
    293    }
    294  }
    295 
    296  static const char *fkey_names[] = {
    297 #define X(name) #name,
    298    XLIST_TERMINFO_FKEYS
    299 #undef X
    300  };
    301 
    302  for (size_t i = 0 + 1; i < ARRAY_SIZE(fkey_names); i++) {
    303    const char *s = ti->f_keys[i];
    304    if (s) {
    305      kv_printf(data, "  key_%-27s = ", fkey_names[i]);
    306      kv_transstr(&data, s, false);
    307      kv_push(data, '\n');
    308    }
    309  }
    310 
    311  kv_push(data, NUL);
    312  return cbuf_as_string(data.items, data.size - 1);
    313 }
    314 
    315 // The implementation of terminfo_fmt() is based on NetBSD libterminfo,
    316 // with full license reproduced below
    317 
    318 // Copyright (c) 2009, 2011, 2013 The NetBSD Foundation, Inc.
    319 //
    320 // This code is derived from software contributed to The NetBSD Foundation
    321 // by Roy Marples.
    322 //
    323 // Redistribution and use in source and binary forms, with or without
    324 // modification, are permitted provided that the following conditions
    325 // are met:
    326 // 1. Redistributions of source code must retain the above copyright
    327 //    notice, this list of conditions and the following disclaimer.
    328 // 2. Redistributions in binary form must reproduce the above copyright
    329 //    notice, this list of conditions and the following disclaimer in the
    330 //    documentation and/or other materials provided with the distribution.
    331 //
    332 // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
    333 // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
    334 // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
    335 // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
    336 // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
    337 // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    338 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    339 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    340 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
    341 // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    342 
    343 // nvim modifications:
    344 // - use typesafe param args instead of va_args and piss
    345 // - caller provides the output buffer
    346 // - static variables are not preserved
    347 
    348 static int push(long num, char *string, TPSTACK *stack)
    349 {
    350  if (stack->offset >= ARRAY_SIZE(stack->nums)) {
    351    return -1;
    352  }
    353  stack->nums[stack->offset] = num;
    354  stack->strings[stack->offset] = string;
    355  stack->offset++;
    356  return 0;
    357 }
    358 
    359 static int pop(long *num, char **string, TPSTACK *stack)
    360 {
    361  if (stack->offset == 0) {
    362    if (num) {
    363      *num = 0;
    364    }
    365    if (string) {
    366      *string = NULL;
    367    }
    368    return -1;
    369  }
    370  stack->offset--;
    371  if (num) {
    372    *num = stack->nums[stack->offset];
    373  }
    374  if (string) {
    375    *string = stack->strings[stack->offset];
    376  }
    377  return 0;
    378 }
    379 
    380 static bool ochar(char **buf, const char *buf_end, int c)
    381 {
    382  if (c == 0) {
    383    c = 0200;
    384  }
    385  // Check we have space and a terminator
    386  if (buf_end - *buf < 2) {
    387    return 0;
    388  }
    389  *(*buf)++ = (char)c;
    390  return 1;
    391 }
    392 
    393 static bool onum(char **buf, const char *buf_end, const char *fmt, int num, size_t len)
    394 {
    395  const size_t LONG_STR_MAX = 21;
    396  len = MAX(len, LONG_STR_MAX);
    397 
    398  if (buf_end - *buf < (ssize_t)(len + 2)) {
    399    return 0;
    400  }
    401  int l = snprintf(*buf, len + 2, fmt, num);
    402  if (l == -1) {
    403    return 0;
    404  }
    405  *buf += l;
    406  return true;
    407 }
    408 
    409 /// @return number of chars printed or 0 for any error
    410 size_t terminfo_fmt(char *buf_start, char *buf_end, const char *str, TPVAR params[9])
    411 {
    412  char c, fmt[64], *fp, *ostr;
    413  long val, val2;
    414  long dnums[26];  // dynamic variables a-z, not preserved
    415  long snums[26];  // static variables a-z, not preserved EITHER HAHA
    416  memset(dnums, 0, sizeof snums);
    417  memset(snums, 0, sizeof snums);
    418 
    419  char *buf = buf_start;
    420 
    421  size_t l, width, precision, olen;
    422  TPSTACK stack;
    423  unsigned done, dot, minus;
    424 
    425  memset(&stack, 0, sizeof(stack));
    426  while ((c = *str++) != '\0') {
    427    if (c != '%' || (c = *str++) == '%') {
    428      if (c == '\0') {
    429        break;
    430      }
    431      if (!ochar(&buf, buf_end, c)) {
    432        return false;
    433      }
    434      continue;
    435    }
    436 
    437    // Handle formatting.
    438    fp = fmt;
    439    *fp++ = '%';
    440    done = dot = minus = 0;
    441    width = precision = 0;
    442    val = 0;
    443    while (done == 0 && (size_t)(fp - fmt) < sizeof(fmt)) {
    444      switch (c) {
    445      case 'c':
    446      case 's':
    447        *fp++ = c;
    448        done = 1;
    449        break;
    450      case 'd':
    451      case 'o':
    452      case 'x':
    453      case 'X':
    454        *fp++ = 'l';
    455        *fp++ = c;
    456        done = 1;
    457        break;
    458      case '#':
    459      case ' ':
    460        *fp++ = c;
    461        break;
    462      case '.':
    463        *fp++ = c;
    464        if (dot == 0) {
    465          dot = 1;
    466          width = (size_t)val;
    467        } else {
    468          done = 2;
    469        }
    470        val = 0;
    471        break;
    472      case ':':
    473        minus = 1;
    474        break;
    475      case '-':
    476        if (minus) {
    477          *fp++ = c;
    478        } else {
    479          done = 1;
    480        }
    481        break;
    482      default:
    483        if (isdigit((unsigned char)c)) {
    484          val = (val * 10) + (c - '0');
    485          if (val > 10000) {
    486            done = 2;
    487          } else {
    488            *fp++ = c;
    489          }
    490        } else {
    491          done = 1;
    492        }
    493      }
    494      if (done == 0) {
    495        c = *str++;
    496      }
    497    }
    498    if (done == 2) {
    499      // Found an error in the format
    500      fp = fmt + 1;
    501      *fp = *str;
    502      olen = 0;
    503    } else {
    504      if (dot == 0) {
    505        width = (size_t)val;
    506      } else {
    507        precision = (size_t)val;
    508      }
    509      olen = MAX(width, precision);
    510    }
    511    *fp++ = '\0';
    512 
    513    // Handle commands
    514    switch (c) {
    515    case 'c':
    516      pop(&val, NULL, &stack);
    517      if (!ochar(&buf, buf_end, (unsigned char)val)) {
    518        return false;
    519      }
    520      break;
    521    case 's':
    522      pop(NULL, &ostr, &stack);
    523      if (ostr != NULL) {
    524        int r;
    525 
    526        l = strlen(ostr);
    527        if (l < olen) {
    528          l = olen;
    529        }
    530        if ((size_t)(buf_end - buf) < (l + 1)) {
    531          return false;
    532        }
    533        r = snprintf(buf, l + 1,
    534                     fmt, ostr);
    535        if (r != -1) {
    536          buf += (size_t)r;
    537        }
    538      }
    539      break;
    540    case 'l':
    541      pop(NULL, &ostr, &stack);
    542      if (ostr == NULL) {
    543        l = 0;
    544      } else {
    545        l = strlen(ostr);
    546      }
    547      push((long)l, NULL, &stack);
    548      break;
    549    case 'd':
    550    case 'o':
    551    case 'x':
    552    case 'X':
    553      pop(&val, NULL, &stack);
    554      if (onum(&buf, buf_end, fmt, (int)val, olen) == 0) {
    555        return 0;
    556      }
    557      break;
    558    case 'p':
    559      if (*str < '1' || *str > '9') {
    560        break;
    561      }
    562      l = (size_t)(*str++ - '1');
    563      if (push(params[l].num, params[l].string, &stack)) {
    564        return 0;
    565      }
    566      break;
    567    case 'P':
    568      pop(&val, NULL, &stack);
    569      if (*str >= 'a' && *str <= 'z') {
    570        dnums[*str - 'a'] = val;
    571      } else if (*str >= 'A' && *str <= 'Z') {
    572        snums[*str - 'A'] = val;
    573      }
    574      break;
    575    case 'g':
    576      if (*str >= 'a' && *str <= 'z') {
    577        if (push(dnums[*str - 'a'], NULL, &stack)) {
    578          return 0;
    579        }
    580      } else if (*str >= 'A' && *str <= 'Z') {
    581        if (push(snums[*str - 'A'], NULL, &stack)) {
    582          return 0;
    583        }
    584      }
    585      break;
    586    case 'i':
    587      params[0].num++;
    588      params[1].num++;
    589      break;
    590    case '\'':
    591      if (push((long)(unsigned char)(*str++), NULL, &stack)) {
    592        return 0;
    593      }
    594      while (*str != '\0' && *str != '\'') {
    595        str++;
    596      }
    597      if (*str == '\'') {
    598        str++;
    599      }
    600      break;
    601    case '{':
    602      val = 0;
    603      for (; isdigit((unsigned char)(*str)); str++) {
    604        val = (val * 10) + (*str - '0');
    605      }
    606      if (push(val, NULL, &stack)) {
    607        return 0;
    608      }
    609      while (*str != '\0' && *str != '}') {
    610        str++;
    611      }
    612      if (*str == '}') {
    613        str++;
    614      }
    615      break;
    616    case '+':
    617    case '-':
    618    case '*':
    619    case '/':
    620    case 'm':
    621    case 'A':
    622    case 'O':
    623    case '&':
    624    case '|':
    625    case '^':
    626    case '=':
    627    case '<':
    628    case '>':
    629      pop(&val, NULL, &stack);
    630      pop(&val2, NULL, &stack);
    631      switch (c) {
    632      case '+':
    633        val = val + val2;
    634        break;
    635      case '-':
    636        val = val2 - val;
    637        break;
    638      case '*':
    639        val = val * val2;
    640        break;
    641      case '/':
    642        val = val ? val2 / val : 0;
    643        break;
    644      case 'm':
    645        val = val ? val2 % val : 0;
    646        break;
    647      case 'A':
    648        val = val && val2;
    649        break;
    650      case 'O':
    651        val = val || val2;
    652        break;
    653      case '&':
    654        val = val & val2;
    655        break;
    656      case '|':
    657        val = val | val2;
    658        break;
    659      case '^':
    660        val = val ^ val2;
    661        break;
    662      case '=':
    663        val = val == val2;
    664        break;
    665      case '<':
    666        val = val2 < val;
    667        break;
    668      case '>':
    669        val = val2 > val;
    670        break;
    671      }
    672      if (push(val, NULL, &stack)) {
    673        return 0;
    674      }
    675      break;
    676    case '!':
    677    case '~':
    678      pop(&val, NULL, &stack);
    679      switch (c) {
    680      case '!':
    681        val = !val;
    682        break;
    683      case '~':
    684        val = ~val;
    685        break;
    686      }
    687      if (push(val, NULL, &stack)) {
    688        return 0;
    689      }
    690      break;
    691    case '?':  // if
    692      break;
    693    case 't':  // then
    694      pop(&val, NULL, &stack);
    695      if (val == 0) {
    696        l = 0;
    697        for (; *str != '\0'; str++) {
    698          if (*str != '%') {
    699            continue;
    700          }
    701          str++;
    702          if (*str == '?') {
    703            l++;
    704          } else if (*str == ';') {
    705            if (l > 0) {
    706              l--;
    707            } else {
    708              str++;
    709              break;
    710            }
    711          } else if (*str == 'e' && l == 0) {
    712            str++;
    713            break;
    714          }
    715        }
    716      }
    717      break;
    718    case 'e':  // else
    719      l = 0;
    720      for (; *str != '\0'; str++) {
    721        if (*str != '%') {
    722          continue;
    723        }
    724        str++;
    725        if (*str == '?') {
    726          l++;
    727        } else if (*str == ';') {
    728          if (l > 0) {
    729            l--;
    730          } else {
    731            str++;
    732            break;
    733          }
    734        }
    735      }
    736      break;
    737    case ';':  // fi
    738      break;
    739    }
    740  }
    741  return (size_t)(buf - buf_start);
    742 }