GitHub

@@ -10,6 +10,136 @@

1010

#include <string.h>

1111

#include <ctype.h>

121213+

#ifdef MRB_UTF8_STRING

14+

/*

15+

* UTF-8 helper functions

16+

* These are only compiled when MRB_UTF8_STRING is defined

17+

*/

18+19+

/* Check if byte is a UTF-8 lead byte (not a continuation byte) */

20+

static mrb_bool

21+

utf8_islead(unsigned char c)

22+

{

23+

return (c & 0xC0) != 0x80;

24+

}

25+26+

/* UTF-8 character length table indexed by (first_byte >> 3) */

27+

static const char utf8_len_table[] = {

28+

1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x00-0x7F: ASCII */

29+

0, 0, 0, 0, 0, 0, 0, 0, /* 0x80-0xBF: continuation (invalid start) */

30+

2, 2, 2, 2, /* 0xC0-0xDF: 2-byte sequences */

31+

3, 3, /* 0xE0-0xEF: 3-byte sequences */

32+

4, /* 0xF0-0xF7: 4-byte sequences */

33+

0 /* 0xF8-0xFF: invalid */

34+

};

35+36+

/*

37+

* Get byte length of UTF-8 character at position

38+

* Returns 1 for invalid sequences (safe fallback)

39+

*/

40+

static size_t

41+

utf8_char_len(const char *p, const char *end)

42+

{

43+

size_t len;

44+

if (p >= end) return 0;

45+46+

len = (size_t)utf8_len_table[(unsigned char)p[0] >> 3];

47+

if (len == 0 || len > (size_t)(end - p)) return 1;

48+49+

/* Validate continuation bytes */

50+

switch (len) {

51+

case 4:

52+

if (!utf8_islead((unsigned char)p[3])) break; /* continuation expected */

53+

return 1;

54+

case 3:

55+

if (!utf8_islead((unsigned char)p[2])) break;

56+

return 1;

57+

case 2:

58+

if (!utf8_islead((unsigned char)p[1])) break;

59+

return 1;

60+

}

61+

return len;

62+

}

63+64+

/*

65+

* Find start of previous UTF-8 character

66+

* Returns byte offset from start of string to the previous character

67+

* If at position 0, returns 0

68+

*/

69+

static size_t

70+

utf8_prev_char_start(const char *str, size_t pos)

71+

{

72+

size_t i;

73+

if (pos == 0) return 0;

74+75+

/* Scan back to find a lead byte (max 4 bytes back) */

76+

for (i = 1; i <= 4 && i <= pos; i++) {

77+

if (utf8_islead((unsigned char)str[pos - i])) {

78+

return pos - i;

79+

}

80+

}

81+

/* No lead byte found, assume single byte */

82+

return pos - 1;

83+

}

84+85+

/*

86+

* Calculate display width for a UTF-8 character

87+

* Returns 2 for CJK/wide characters, 1 for others

88+

*

89+

* This is a simplified version - proper implementation would use wcwidth()

90+

* We detect East Asian Wide characters by their code point ranges:

91+

* - CJK Unified Ideographs: U+4E00-U+9FFF (3-byte UTF-8: E4-E9)

92+

* - Hiragana/Katakana: U+3040-U+30FF (3-byte UTF-8: E3 81-83)

93+

* - Full-width forms: U+FF00-U+FFEF (3-byte UTF-8: EF BC-BF)

94+

*/

95+

static int

96+

utf8_char_width(const char *p, const char *end)

97+

{

98+

unsigned char c = (unsigned char)p[0];

99+100+

if (c < 0x80) return 1; /* ASCII */

101+

if (c < 0xE0) return 1; /* 2-byte (Latin extended, etc.) */

102+103+

/* 3-byte sequences - check for wide characters */

104+

if (c >= 0xE3 && c <= 0xE9 && (end - p) >= 3) {

105+

/* CJK and Japanese ranges are typically double-width */

106+

return 2;

107+

}

108+

if (c == 0xEF && (end - p) >= 3) {

109+

unsigned char c2 = (unsigned char)p[1];

110+

if (c2 >= 0xBC && c2 <= 0xBF) {

111+

/* Full-width ASCII and symbols */

112+

return 2;

113+

}

114+

}

115+116+

/* 4-byte sequences (emoji, etc.) - typically double-width */

117+

if (c >= 0xF0) return 2;

118+119+

return 1;

120+

}

121+122+

/*

123+

* Calculate display column from byte position

124+

* Sums up the display width of all characters before the byte position

125+

*/

126+

static size_t

127+

utf8_display_col(const char *str, size_t byte_pos)

128+

{

129+

size_t col = 0;

130+

const char *p = str;

131+

const char *end = str + byte_pos;

132+133+

while (p < end) {

134+

size_t char_len = utf8_char_len(p, str + byte_pos + 4); /* +4 for safety */

135+

if (char_len == 0) break;

136+

col += (size_t)utf8_char_width(p, end);

137+

p += char_len;

138+

}

139+

return col;

140+

}

141+

#endif /* MRB_UTF8_STRING */

142+13143

/*

14144

* Helper: Initialize a single line

15145

*/

@@ -76,6 +206,7 @@ line_insert_at(mirb_line *line, size_t pos, char c)

76206

/*

77207

* Helper: Delete character at position in line

78208

*/

209+

#ifndef MRB_UTF8_STRING

79210

static mrb_bool

80211

line_delete_at(mirb_line *line, size_t pos)

81212

{

@@ -85,6 +216,23 @@ line_delete_at(mirb_line *line, size_t pos)

85216

line->len--;

86217

return TRUE;

87218

}

219+

#endif

220+221+

#ifdef MRB_UTF8_STRING

222+

/*

223+

* Helper: Delete N bytes at position in line (for UTF-8 multibyte chars)

224+

*/

225+

static mrb_bool

226+

line_delete_bytes_at(mirb_line *line, size_t pos, size_t count)

227+

{

228+

if (pos >= line->len || count == 0) return FALSE;

229+

if (pos + count > line->len) count = line->len - pos;

230+231+

memmove(line->data + pos, line->data + pos + count, line->len - pos - count + 1);

232+

line->len -= count;

233+

return TRUE;

234+

}

235+

#endif

8823689237

/*

90238

* Helper: Set line content

@@ -336,11 +484,22 @@ mirb_buffer_delete_back(mirb_buffer *buf)

336484

if (buf->cursor_col > 0) {

337485

/* Delete within line */

338486

mirb_line *line = &buf->lines[buf->cursor_line];

487+

#ifdef MRB_UTF8_STRING

488+

/* Find start of previous UTF-8 character and delete entire character */

489+

size_t prev_pos = utf8_prev_char_start(line->data, buf->cursor_col);

490+

size_t char_len = buf->cursor_col - prev_pos;

491+

if (line_delete_bytes_at(line, prev_pos, char_len)) {

492+

buf->cursor_col = prev_pos;

493+

buf->modified = TRUE;

494+

return TRUE;

495+

}

496+

#else

339497

if (line_delete_at(line, buf->cursor_col - 1)) {

340498

buf->cursor_col--;

341499

buf->modified = TRUE;

342500

return TRUE;

343501

}

502+

#endif

344503

}

345504

else if (buf->cursor_line > 0) {

346505

/* Join with previous line */

@@ -378,10 +537,20 @@ mirb_buffer_delete_forward(mirb_buffer *buf)

378537379538

if (buf->cursor_col < line->len) {

380539

/* Delete within line */

540+

#ifdef MRB_UTF8_STRING

541+

/* Delete entire UTF-8 character at cursor */

542+

size_t char_len = utf8_char_len(line->data + buf->cursor_col,

543+

line->data + line->len);

544+

if (line_delete_bytes_at(line, buf->cursor_col, char_len)) {

545+

buf->modified = TRUE;

546+

return TRUE;

547+

}

548+

#else

381549

if (line_delete_at(line, buf->cursor_col)) {

382550

buf->modified = TRUE;

383551

return TRUE;

384552

}

553+

#endif

385554

}

386555

else if (buf->cursor_line < buf->line_count - 1) {

387556

/* Join with next line */

@@ -497,7 +666,13 @@ mrb_bool

497666

mirb_buffer_cursor_left(mirb_buffer *buf)

498667

{

499668

if (buf->cursor_col > 0) {

669+

#ifdef MRB_UTF8_STRING

670+

/* Move back to start of previous UTF-8 character */

671+

mirb_line *line = &buf->lines[buf->cursor_line];

672+

buf->cursor_col = utf8_prev_char_start(line->data, buf->cursor_col);

673+

#else

500674

buf->cursor_col--;

675+

#endif

501676

return TRUE;

502677

}

503678

else if (buf->cursor_line > 0) {

@@ -517,7 +692,14 @@ mirb_buffer_cursor_right(mirb_buffer *buf)

517692

mirb_line *line = &buf->lines[buf->cursor_line];

518693519694

if (buf->cursor_col < line->len) {

695+

#ifdef MRB_UTF8_STRING

696+

/* Skip entire UTF-8 character */

697+

size_t char_len = utf8_char_len(line->data + buf->cursor_col,

698+

line->data + line->len);

699+

buf->cursor_col += char_len;

700+

#else

520701

buf->cursor_col++;

702+

#endif

521703

return TRUE;

522704

}

523705

else if (buf->cursor_line < buf->line_count - 1) {

@@ -835,3 +1017,19 @@ mirb_buffer_line_len(mirb_buffer *buf, size_t index)

8351017

if (index >= buf->line_count) return 0;

8361018

return buf->lines[index].len;

8371019

}

1020+1021+

/*

1022+

* Get cursor display column (visual column for terminal positioning)

1023+

* When MRB_UTF8_STRING is defined, calculates display width considering

1024+

* multibyte characters. Otherwise, returns the byte position directly.

1025+

*/

1026+

size_t

1027+

mirb_buffer_cursor_display_col(mirb_buffer *buf)

1028+

{

1029+

#ifdef MRB_UTF8_STRING

1030+

mirb_line *line = &buf->lines[buf->cursor_line];

1031+

return utf8_display_col(line->data, buf->cursor_col);

1032+

#else

1033+

return buf->cursor_col;

1034+

#endif

1035+

}

Read the original on github.com ↗