Clinkの日本語パス問題を修正(1)

などで紹介されているWindowsコマンドプロンプトbash風の行編集機能他を追加するClinkですが、カレントディレクトリのパスに日本語が含まれていると

  • 行の折り返し位置がおかしい
  • Ctrl-Uによる行削除の範囲がおかしい

という日本人にとっては結構痛い不具合があるので、ちょっとソースを追っかけてみました。

[readline\readline\display.c]

int
rl_expand_prompt (prompt)
     char *prompt;
{
...
  p = strrchr (prompt, '\n');
  if (!p)
    {
      /* The prompt is only one logical line, though it might wrap. */
      local_prompt = expand_prompt (prompt, &prompt_visible_length,
					    &prompt_last_invisible,
					    &prompt_invis_chars_first_line,
					    &prompt_physical_chars);
      local_prompt_prefix = (char *)0;
      local_prompt_len = local_prompt ? strlen (local_prompt) : 0;
      return (prompt_visible_length);
    }
  else
    {
      /* The prompt spans multiple lines. */
      t = ++p;
      local_prompt = expand_prompt (p, &prompt_visible_length,
				       &prompt_last_invisible,
				       &prompt_invis_chars_first_line,
				       &prompt_physical_chars);
      c = *t; *t = '\0';
...

プロンプトの文字列長にも複数の概念があって、その中でprompt_physical_charsが名前からしてプロンプトの物理文字数を保持しているようです。

static char *
expand_prompt (pmt, lp, lip, niflp, vlp)
     char *pmt;
     int *lp, *lip, *niflp, *vlp;
{
...
#if defined (HANDLE_MULTIBYTE)
	  if (MB_CUR_MAX > 1 && rl_byte_oriented == 0)
	    {
	      pind = p - pmt;
	      ind = _rl_find_next_mbchar (pmt, pind, 1, MB_FIND_NONZERO);
	      l = ind - pind;
	      while (l--)
	        *r++ = *p++;
	      if (!ignoring)
		{
		  /* rl ends up being assigned to prompt_visible_length,
		     which is the number of characters in the buffer that
		     contribute to characters on the screen, which might
		     not be the same as the number of physical characters
		     on the screen in the presence of multibyte characters */
		  rl += ind - pind;
		  physchars += _rl_col_width (pmt, pind, ind, 0);
		}
	      else
		ninvis += ind - pind;
	      p--;			/* compensate for later increment */
	    }
	  else
#endif
	    {
	      *r++ = *p;
	      if (!ignoring)
		{
		  rl++;			/* visible length byte counter */
		  physchars++;
		}
	      else
		ninvis++;		/* invisible chars byte counter */
	    }
...

HANDLE_MULTIBYTEというシンボルの存在と実装コードから、マルチバイト文字≒日本語等の存在もちゃんと考慮されているようです。

#if defined (HANDLE_MULTIBYTE)
/* Calculate the number of screen columns occupied by STR from START to END.
   In the case of multibyte characters with stateful encoding, we have to
   scan from the beginning of the string to take the state into account. */
static int
_rl_col_width (str, start, end, flags)
     const char *str;
     int start, end, flags;
{
...
  while (point < end)
    {
      tmp = mbrtowc (&wc, str + point, max, &ps);
      if (MB_INVALIDCH ((size_t)tmp))
	{
	  /* In this case, the bytes are invalid or too short to compose a
	     multibyte character, so we assume that the first byte represents
	     a single character. */
	  point++;
	  max--;

	  /* and assume that the byte occupies a single column. */
	  width++;

	  /* Clear the state of the byte sequence, because in this case the
	     effect of mbstate is undefined. */
	  memset (&ps, 0, sizeof (mbstate_t));
	}
      else if (MB_NULLWCH (tmp))
	break;			/* Found '\0' */
      else
	{
	  point += tmp;
	  max -= tmp;
	  tmp = wcwidth(wc);
	  width += (tmp >= 0) ? tmp : 1;
	}
    }
...

マルチバイト文字なら、ちゃんと文字幅を計算して加算しているように見えますが、

[readline\compat\config.h]

#define wcwidth(x)          (1)

(;゚д゚)ポカーン

…というわけで、これを真面目に実装すればたぶん何とかなるのではないかというアプローチで、次回は修正編です。