aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorThijs Schreijer <thijs@thijsschreijer.nl>2024-06-20 22:43:06 +0200
committerGitHub <noreply@github.com>2024-06-20 22:43:06 +0200
commitc1a64c1b75f97cef97965b3bd9a941564a6270bd (patch)
treeb9a92dff6462abd5859c3c76f19748fad5d6c025 /src
parent47c24eed0191f8f72646be63dee94ac2b35eb062 (diff)
parentb87e6d6d762ee823e81dd7a8984f330eb4018fd8 (diff)
downloadluasystem-c1a64c1b75f97cef97965b3bd9a941564a6270bd.tar.gz
luasystem-c1a64c1b75f97cef97965b3bd9a941564a6270bd.tar.bz2
luasystem-c1a64c1b75f97cef97965b3bd9a941564a6270bd.zip
Merge pull request #21 from lunarmodules/terminal
Diffstat (limited to 'src')
-rw-r--r--src/bitflags.c270
-rw-r--r--src/bitflags.h30
-rw-r--r--src/compat.c16
-rw-r--r--src/compat.h12
-rw-r--r--src/core.c2
-rw-r--r--src/environment.c6
-rw-r--r--src/random.c7
-rw-r--r--src/term.c1133
-rw-r--r--src/time.c6
-rw-r--r--src/wcwidth.c285
-rw-r--r--src/wcwidth.h21
11 files changed, 1779 insertions, 9 deletions
diff --git a/src/bitflags.c b/src/bitflags.c
new file mode 100644
index 0000000..e397918
--- /dev/null
+++ b/src/bitflags.c
@@ -0,0 +1,270 @@
1/// Bitflags module.
2// The bitflag object makes it easy to manipulate flags in a bitmask.
3//
4// It has metamethods that do the hard work, adding flags sets them, substracting
5// unsets them. Comparing flags checks if all flags in the second set are also set
6// in the first set. The `has` method checks if all flags in the second set are
7// also set in the first set, but behaves slightly different.
8//
9// Indexing allows checking values or setting them by bit index (eg. 0-7 for flags
10// in the first byte).
11//
12// _NOTE_: unavailable flags (eg. Windows flags on a Posix system) should not be
13// omitted, but be assigned a value of 0. This is because the `has` method will
14// return `false` if the flags are checked and the value is 0.
15//
16// See `system.bitflag` (the constructor) for extensive examples on usage.
17// @classmod bitflags
18#include "bitflags.h"
19
20#define BITFLAGS_MT_NAME "LuaSystem.BitFlags"
21
22typedef struct {
23 LSBF_BITFLAG flags;
24} LS_BitFlags;
25
26/// Bit flags.
27// Bitflag objects can be used to easily manipulate and compare bit flags.
28// These are primarily for use with the terminal functions, but can be used
29// in other places as well.
30// @section bitflags
31
32
33// pushes a new LS_BitFlags object with the given value onto the stack
34void lsbf_pushbitflags(lua_State *L, LSBF_BITFLAG value) {
35 LS_BitFlags *obj = (LS_BitFlags *)lua_newuserdata(L, sizeof(LS_BitFlags));
36 if (!obj) luaL_error(L, "Memory allocation failed");
37 luaL_getmetatable(L, BITFLAGS_MT_NAME);
38 lua_setmetatable(L, -2);
39 obj->flags = value;
40}
41
42// gets the LS_BitFlags value at the given index. Returns a Lua error if it is not
43// a LS_BitFlags object.
44LSBF_BITFLAG lsbf_checkbitflags(lua_State *L, int index) {
45 LS_BitFlags *obj = (LS_BitFlags *)luaL_checkudata(L, index, BITFLAGS_MT_NAME);
46 return obj->flags;
47}
48
49// Validates that the given index is a table containing a field 'fieldname'
50// which is a bitflag object and returns its value.
51// If the index is not a table or the field is not a bitflag object, a Lua
52// error is raised. If the bitflag is not present, the default value is returned.
53// The stack remains unchanged.
54LSBF_BITFLAG lsbf_checkbitflagsfield(lua_State *L, int index, const char *fieldname, LSBF_BITFLAG default_value) {
55 luaL_checktype(L, index, LUA_TTABLE);
56 lua_getfield(L, index, fieldname);
57
58 // if null, return default value
59 if (lua_isnil(L, -1)) {
60 lua_pop(L, 1);
61 return default_value;
62 }
63
64 // check to bitflags
65 LS_BitFlags *obj = luaL_testudata(L, -1, BITFLAGS_MT_NAME);
66 if (obj == NULL) {
67 lua_pop(L, 1);
68 return luaL_error(L, "bad argument #%d, field '%s' must be a bitflag object", index, fieldname);
69 }
70 LSBF_BITFLAG value = obj->flags;
71 lua_pop(L, 1);
72 return value;
73}
74
75/***
76Creates a new bitflag object from the given value.
77@function system.bitflag
78@tparam[opt=0] number value the value to create the bitflag object from.
79@treturn bitflag bitflag object with the given values set.
80@usage
81local sys = require 'system'
82local flags = sys.bitflag(2) -- b0010
83
84-- get state of individual bits
85print(flags[0]) -- false
86print(flags[1]) -- true
87
88-- set individual bits
89flags[0] = true -- b0011
90print(flags:value()) -- 3
91print(flags) -- "bitflags: 3"
92
93-- adding flags (bitwise OR)
94local flags1 = sys.bitflag(1) -- b0001
95local flags2 = sys.bitflag(2) -- b0010
96local flags3 = flags1 + flags2 -- b0011
97
98-- substracting flags (bitwise AND NOT)
99print(flags3:value()) -- 3
100flag3 = flag3 - flag3 -- b0000
101print(flags3:value()) -- 0
102
103-- comparing flags
104local flags4 = sys.bitflag(7) -- b0111
105local flags5 = sys.bitflag(255) -- b11111111
106print(flags5 ~= flags4) -- true, not the same flags
107local flags6 = sys.bitflag(7) -- b0111
108print(flags6 == flags4) -- true, same flags
109
110-- comparison of subsets
111local flags7 = sys.bitflag(0) -- b0000
112local flags8 = sys.bitflag(3) -- b0011
113local flags9 = sys.bitflag(7) -- b0111
114print(flags9:has_all_of(flags8)) -- true, flags8 bits are all set in flags9
115print(flags8:has_any_of(flags9)) -- true, some of flags9 bits are set in flags8
116print(flags8:has_all_of(flags7)) -- false, flags7 (== 0) is not set in flags8
117*/
118static int lsbf_new(lua_State *L) {
119 LSBF_BITFLAG flags = 0;
120 if (lua_gettop(L) > 0) {
121 flags = luaL_checkinteger(L, 1);
122 }
123 lsbf_pushbitflags(L, flags);
124 return 1;
125}
126
127/***
128Retrieves the numeric value of the bitflag object.
129@function bitflag:value
130@treturn number the numeric value of the bitflags.
131@usage
132local sys = require 'system'
133local flags = sys.bitflag() -- b0000
134flags[0] = true -- b0001
135flags[2] = true -- b0101
136print(flags:value()) -- 5
137*/
138static int lsbf_value(lua_State *L) {
139 lua_pushinteger(L, lsbf_checkbitflags(L, 1));
140 return 1;
141}
142
143static int lsbf_tostring(lua_State *L) {
144 lua_pushfstring(L, "bitflags: %d", lsbf_checkbitflags(L, 1));
145 return 1;
146}
147
148static int lsbf_add(lua_State *L) {
149 lsbf_pushbitflags(L, lsbf_checkbitflags(L, 1) | lsbf_checkbitflags(L, 2));
150 return 1;
151}
152
153static int lsbf_sub(lua_State *L) {
154 lsbf_pushbitflags(L, lsbf_checkbitflags(L, 1) & ~lsbf_checkbitflags(L, 2));
155 return 1;
156}
157
158static int lsbf_eq(lua_State *L) {
159 lua_pushboolean(L, lsbf_checkbitflags(L, 1) == lsbf_checkbitflags(L, 2));
160 return 1;
161}
162
163/***
164Checks if all the flags in the given subset are set.
165If the flags to check has a value `0`, it will always return `false`. So if there are flags that are
166unsupported on a platform, they can be set to 0 and the `has_all_of` function will
167return `false` if the flags are checked.
168@function bitflag:has_all_of
169@tparam bitflag subset the flags to check for.
170@treturn boolean true if all the flags are set, false otherwise.
171@usage
172local sys = require 'system'
173local flags = sys.bitflag(12) -- b1100
174local myflags = sys.bitflag(15) -- b1111
175print(flags:has_all_of(myflags)) -- false, not all bits in myflags are set in flags
176print(myflags:has_all_of(flags)) -- true, all bits in flags are set in myflags
177*/
178static int lsbf_has_all_of(lua_State *L) {
179 LSBF_BITFLAG a = lsbf_checkbitflags(L, 1);
180 LSBF_BITFLAG b = lsbf_checkbitflags(L, 2);
181 // Check if all bits in b are also set in a, and b is not 0
182 lua_pushboolean(L, (a & b) == b && b != 0);
183 return 1;
184}
185
186/***
187Checks if any of the flags in the given subset are set.
188If the flags to check has a value `0`, it will always return `false`. So if there are flags that are
189unsupported on a platform, they can be set to 0 and the `has_any_of` function will
190return `false` if the flags are checked.
191@function bitflag:has_any_of
192@tparam bitflag subset the flags to check for.
193@treturn boolean true if any of the flags are set, false otherwise.
194@usage
195local sys = require 'system'
196local flags = sys.bitflag(12) -- b1100
197local myflags = sys.bitflag(7) -- b0111
198print(flags:has_any_of(myflags)) -- true, some bits in myflags are set in flags
199print(myflags:has_any_of(flags)) -- true, some bits in flags are set in myflags
200*/
201static int lsbf_has_any_of(lua_State *L) {
202 LSBF_BITFLAG a = lsbf_checkbitflags(L, 1);
203 LSBF_BITFLAG b = lsbf_checkbitflags(L, 2);
204 // Check if any bits in b are set in a
205 lua_pushboolean(L, (a & b) != 0);
206 return 1;
207}
208
209static int lsbf_index(lua_State *L) {
210 if (!lua_isnumber(L, 2)) {
211 // the parameter isn't a number, just lookup the key in the metatable
212 lua_getmetatable(L, 1);
213 lua_pushvalue(L, 2);
214 lua_gettable(L, -2);
215 return 1;
216 }
217
218 int index = luaL_checkinteger(L, 2);
219 if (index < 0 || index >= sizeof(LSBF_BITFLAG) * 8) {
220 return luaL_error(L, "index out of range");
221 }
222 lua_pushboolean(L, (lsbf_checkbitflags(L, 1) & (1 << index)) != 0);
223 return 1;
224}
225
226static int lsbf_newindex(lua_State *L) {
227 LS_BitFlags *obj = (LS_BitFlags *)luaL_checkudata(L, 1, BITFLAGS_MT_NAME);
228
229 if (!lua_isnumber(L, 2)) {
230 return luaL_error(L, "index must be a number");
231 }
232 int index = luaL_checkinteger(L, 2);
233 if (index < 0 || index >= sizeof(LSBF_BITFLAG) * 8) {
234 return luaL_error(L, "index out of range");
235 }
236
237 luaL_checkany(L, 3);
238 if (lua_toboolean(L, 3)) {
239 obj->flags |= (1 << index);
240 } else {
241 obj->flags &= ~(1 << index);
242 }
243 return 0;
244}
245
246static const struct luaL_Reg lsbf_funcs[] = {
247 {"bitflag", lsbf_new},
248 {NULL, NULL}
249};
250
251static const struct luaL_Reg lsbf_methods[] = {
252 {"value", lsbf_value},
253 {"has_all_of", lsbf_has_all_of},
254 {"has_any_of", lsbf_has_any_of},
255 {"__tostring", lsbf_tostring},
256 {"__add", lsbf_add},
257 {"__sub", lsbf_sub},
258 {"__eq", lsbf_eq},
259 {"__index", lsbf_index},
260 {"__newindex", lsbf_newindex},
261 {NULL, NULL}
262};
263
264void bitflags_open(lua_State *L) {
265 luaL_newmetatable(L, BITFLAGS_MT_NAME);
266 luaL_setfuncs(L, lsbf_methods, 0);
267 lua_pop(L, 1);
268
269 luaL_setfuncs(L, lsbf_funcs, 0);
270}
diff --git a/src/bitflags.h b/src/bitflags.h
new file mode 100644
index 0000000..f16b041
--- /dev/null
+++ b/src/bitflags.h
@@ -0,0 +1,30 @@
1#ifndef LSBITFLAGS_H
2#define LSBITFLAGS_H
3
4#include <lua.h>
5#include "compat.h"
6#include <lauxlib.h>
7#include <stdlib.h>
8
9// type used to store the bitflags
10#define LSBF_BITFLAG lua_Integer
11
12// Validates that the given index is a bitflag object and returns its value.
13// If the index is not a bitflag object, a Lua error is raised.
14// The value will be left on the stack.
15LSBF_BITFLAG lsbf_checkbitflags(lua_State *L, int index);
16
17
18// Validates that the given index is a table containing a field 'fieldname'
19// which is a bitflag object and returns its value.
20// If the index is not a table or the field is not a bitflag object, a Lua
21// error is raised. If the bitflag is not present, the default value is returned.
22// The stack remains unchanged.
23LSBF_BITFLAG lsbf_checkbitflagsfield(lua_State *L, int index, const char *fieldname, LSBF_BITFLAG default_value);
24
25
26// Pushes a new bitflag object with the given value onto the stack.
27// Might raise a Lua error if memory allocation fails.
28void lsbf_pushbitflags(lua_State *L, LSBF_BITFLAG value);
29
30#endif
diff --git a/src/compat.c b/src/compat.c
index 6f98854..2d2bec9 100644
--- a/src/compat.c
+++ b/src/compat.c
@@ -14,4 +14,20 @@ void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup) {
14 } 14 }
15 lua_pop(L, nup); /* remove upvalues */ 15 lua_pop(L, nup); /* remove upvalues */
16} 16}
17
18void *luaL_testudata(lua_State *L, int ud, const char *tname) {
19 void *p = lua_touserdata(L, ud);
20 if (p != NULL) { /* Check for userdata */
21 if (lua_getmetatable(L, ud)) { /* Does it have a metatable? */
22 lua_getfield(L, LUA_REGISTRYINDEX, tname); /* Get metatable we're looking for */
23 if (lua_rawequal(L, -1, -2)) { /* Compare metatables */
24 lua_pop(L, 2); /* Remove metatables from stack */
25 return p;
26 }
27 lua_pop(L, 2); /* Remove metatables from stack */
28 }
29 }
30 return NULL; /* Return NULL if check fails */
31}
32
17#endif 33#endif
diff --git a/src/compat.h b/src/compat.h
index 35f9ef2..2033aa3 100644
--- a/src/compat.h
+++ b/src/compat.h
@@ -6,6 +6,7 @@
6 6
7#if LUA_VERSION_NUM == 501 7#if LUA_VERSION_NUM == 501
8void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup); 8void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup);
9void *luaL_testudata(lua_State *L, int ud, const char *tname);
9#endif 10#endif
10 11
11 12
@@ -13,6 +14,17 @@ void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup);
13#include <sys/types.h> 14#include <sys/types.h>
14#endif 15#endif
15 16
17// Windows compatibility; define DWORD and TRUE/FALSE on non-Windows
18#ifndef _WIN32
19#ifndef DWORD
20#define DWORD unsigned long
21#endif
22#ifndef TRUE
23#define TRUE 1
24#define FALSE 0
25#endif
26#endif
27
16#ifdef _MSC_VER 28#ifdef _MSC_VER
17// MSVC Windows doesn't have ssize_t, so we define it here 29// MSVC Windows doesn't have ssize_t, so we define it here
18#if SIZE_MAX == UINT_MAX 30#if SIZE_MAX == UINT_MAX
diff --git a/src/core.c b/src/core.c
index 729023f..d233ecc 100644
--- a/src/core.c
+++ b/src/core.c
@@ -16,6 +16,7 @@ void time_open(lua_State *L);
16void environment_open(lua_State *L); 16void environment_open(lua_State *L);
17void random_open(lua_State *L); 17void random_open(lua_State *L);
18void term_open(lua_State *L); 18void term_open(lua_State *L);
19void bitflags_open(lua_State *L);
19 20
20/*------------------------------------------------------------------------- 21/*-------------------------------------------------------------------------
21 * Initializes all library modules. 22 * Initializes all library modules.
@@ -32,6 +33,7 @@ LUAEXPORT int luaopen_system_core(lua_State *L) {
32 lua_pushboolean(L, 0); 33 lua_pushboolean(L, 0);
33#endif 34#endif
34 lua_rawset(L, -3); 35 lua_rawset(L, -3);
36 bitflags_open(L); // must be first, used by others
35 time_open(L); 37 time_open(L);
36 random_open(L); 38 random_open(L);
37 term_open(L); 39 term_open(L);
diff --git a/src/environment.c b/src/environment.c
index 5f1c3da..ab5dd92 100644
--- a/src/environment.c
+++ b/src/environment.c
@@ -1,4 +1,8 @@
1/// @submodule system 1/// @module system
2
3/// Environment.
4// @section environment
5
2#include <lua.h> 6#include <lua.h>
3#include <lauxlib.h> 7#include <lauxlib.h>
4#include "compat.h" 8#include "compat.h"
diff --git a/src/random.c b/src/random.c
index 90fb3f2..e55461a 100644
--- a/src/random.c
+++ b/src/random.c
@@ -1,4 +1,9 @@
1/// @submodule system 1/// @module system
2
3/// Random.
4// @section random
5
6
2#include <lua.h> 7#include <lua.h>
3#include <lauxlib.h> 8#include <lauxlib.h>
4#include "compat.h" 9#include "compat.h"
diff --git a/src/term.c b/src/term.c
index 2adb1e9..d8cc38e 100644
--- a/src/term.c
+++ b/src/term.c
@@ -1,37 +1,1158 @@
1/// @submodule system 1/// @module system
2
3/// Terminal.
4// Unix: see https://blog.nelhage.com/2009/12/a-brief-introduction-to-termios-termios3-and-stty/
5//
6// Windows: see https://learn.microsoft.com/en-us/windows/console/console-reference
7// @section terminal
8
2#include <lua.h> 9#include <lua.h>
3#include <lauxlib.h> 10#include <lauxlib.h>
4#include <lualib.h> 11#include <lualib.h>
5#include "compat.h" 12#include "compat.h"
13#include "bitflags.h"
6 14
7#ifndef _MSC_VER 15#ifndef _MSC_VER
8# include <unistd.h> 16# include <unistd.h>
9#endif 17#endif
10 18
19#ifdef _WIN32
20# include <windows.h>
21# include <locale.h>
22#else
23# include <termios.h>
24# include <string.h>
25# include <errno.h>
26# include <fcntl.h>
27# include <sys/ioctl.h>
28# include <unistd.h>
29# include <wchar.h>
30# include <locale.h>
31#endif
32
33
34// Windows does not have a wcwidth function, so we use compatibilty code from
35// http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c by Markus Kuhn
36#include "wcwidth.h"
37
38
39#ifdef _WIN32
40// after an error is returned, GetLastError() result can be passed to this function to get a string
41// representation of the error on the stack.
42// result will be nil+error on the stack, always 2 results.
43static void termFormatError(lua_State *L, DWORD errorCode, const char* prefix) {
44//static void FormatErrorAndReturn(lua_State *L, DWORD errorCode, const char* prefix) {
45 LPSTR messageBuffer = NULL;
46 FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
47 NULL, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL);
48
49 lua_pushnil(L);
50 if (messageBuffer) {
51 if (prefix) {
52 lua_pushfstring(L, "%s: %s", prefix, messageBuffer);
53 } else {
54 lua_pushstring(L, messageBuffer);
55 }
56 LocalFree(messageBuffer);
57 } else {
58 lua_pushfstring(L, "%sError code %d", prefix ? prefix : "", errorCode);
59 }
60}
61#else
62static int pusherror(lua_State *L, const char *info)
63{
64 lua_pushnil(L);
65 if (info==NULL)
66 lua_pushstring(L, strerror(errno));
67 else
68 lua_pushfstring(L, "%s: %s", info, strerror(errno));
69 lua_pushinteger(L, errno);
70 return 3;
71}
72#endif
11 73
12/*** 74/***
13Checks if a file-handle is a TTY. 75Checks if a file-handle is a TTY.
14 76
15@function isatty 77@function isatty
16@tparam file file the file-handle to check 78@tparam file file the file-handle to check, one of `io.stdin`, `io.stdout`, `io.stderr`.
17@treturn boolean true if the file is a tty 79@treturn boolean true if the file is a tty
80@usage
81local system = require('system')
82if system.isatty(io.stdin) then
83 -- enable ANSI coloring etc on Windows, does nothing in Posix.
84 local flags = system.getconsoleflags(io.stdout)
85 system.setconsoleflags(io.stdout, flags + sys.COF_VIRTUAL_TERMINAL_PROCESSING)
86end
18*/ 87*/
19static int lua_isatty(lua_State* L) { 88static int lst_isatty(lua_State* L) {
20 FILE **fh = (FILE **) luaL_checkudata(L, 1, LUA_FILEHANDLE); 89 FILE **fh = (FILE **) luaL_checkudata(L, 1, LUA_FILEHANDLE);
21 lua_pushboolean(L, isatty(fileno(*fh))); 90 lua_pushboolean(L, isatty(fileno(*fh)));
22 return 1; 91 return 1;
23} 92}
24 93
25 94
95/*-------------------------------------------------------------------------
96 * Windows Get/SetConsoleMode functions
97 *-------------------------------------------------------------------------*/
26 98
27static luaL_Reg func[] = { 99typedef struct ls_RegConst {
28 { "isatty", lua_isatty }, 100 const char *name;
29 { NULL, NULL } 101 DWORD value;
102} ls_RegConst;
103
104// Define a macro to check if a constant is defined and set it to 0 if not.
105// This is needed because some flags are not defined on all platforms. So we
106// still export the constants, but they will be all 0, and hence not do anything.
107#ifdef _WIN32
108#define CHECK_WIN_FLAG_OR_ZERO(flag) flag
109#define CHECK_NIX_FLAG_OR_ZERO(flag) 0
110#else
111#define CHECK_WIN_FLAG_OR_ZERO(flag) 0
112#define CHECK_NIX_FLAG_OR_ZERO(flag) flag
113#endif
114
115// Export Windows constants to Lua
116static const struct ls_RegConst win_console_in_flags[] = {
117 // Console Input Flags
118 {"CIF_ECHO_INPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_ECHO_INPUT)},
119 {"CIF_INSERT_MODE", CHECK_WIN_FLAG_OR_ZERO(ENABLE_INSERT_MODE)},
120 {"CIF_LINE_INPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_LINE_INPUT)},
121 {"CIF_MOUSE_INPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_MOUSE_INPUT)},
122 {"CIF_PROCESSED_INPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_PROCESSED_INPUT)},
123 {"CIF_QUICK_EDIT_MODE", CHECK_WIN_FLAG_OR_ZERO(ENABLE_QUICK_EDIT_MODE)},
124 {"CIF_WINDOW_INPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_WINDOW_INPUT)},
125 {"CIF_VIRTUAL_TERMINAL_INPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_VIRTUAL_TERMINAL_INPUT)},
126 {"CIF_EXTENDED_FLAGS", CHECK_WIN_FLAG_OR_ZERO(ENABLE_EXTENDED_FLAGS)},
127 {"CIF_AUTO_POSITION", CHECK_WIN_FLAG_OR_ZERO(ENABLE_AUTO_POSITION)},
128 {NULL, 0}
129};
130
131static const struct ls_RegConst win_console_out_flags[] = {
132 // Console Output Flags
133 {"COF_PROCESSED_OUTPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_PROCESSED_OUTPUT)},
134 {"COF_WRAP_AT_EOL_OUTPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_WRAP_AT_EOL_OUTPUT)},
135 {"COF_VIRTUAL_TERMINAL_PROCESSING", CHECK_WIN_FLAG_OR_ZERO(ENABLE_VIRTUAL_TERMINAL_PROCESSING)},
136 {"COF_DISABLE_NEWLINE_AUTO_RETURN", CHECK_WIN_FLAG_OR_ZERO(DISABLE_NEWLINE_AUTO_RETURN)},
137 {"COF_ENABLE_LVB_GRID_WORLDWIDE", CHECK_WIN_FLAG_OR_ZERO(ENABLE_LVB_GRID_WORLDWIDE)},
138 {NULL, 0}
139};
140
141
142// Export Unix constants to Lua
143static const struct ls_RegConst nix_tcsetattr_actions[] = {
144 // The optional actions for tcsetattr
145 {"TCSANOW", CHECK_NIX_FLAG_OR_ZERO(TCSANOW)},
146 {"TCSADRAIN", CHECK_NIX_FLAG_OR_ZERO(TCSADRAIN)},
147 {"TCSAFLUSH", CHECK_NIX_FLAG_OR_ZERO(TCSAFLUSH)},
148 {NULL, 0}
149};
150
151static const struct ls_RegConst nix_console_i_flags[] = {
152 // Input flags (c_iflag)
153 {"I_IGNBRK", CHECK_NIX_FLAG_OR_ZERO(IGNBRK)},
154 {"I_BRKINT", CHECK_NIX_FLAG_OR_ZERO(BRKINT)},
155 {"I_IGNPAR", CHECK_NIX_FLAG_OR_ZERO(IGNPAR)},
156 {"I_PARMRK", CHECK_NIX_FLAG_OR_ZERO(PARMRK)},
157 {"I_INPCK", CHECK_NIX_FLAG_OR_ZERO(INPCK)},
158 {"I_ISTRIP", CHECK_NIX_FLAG_OR_ZERO(ISTRIP)},
159 {"I_INLCR", CHECK_NIX_FLAG_OR_ZERO(INLCR)},
160 {"I_IGNCR", CHECK_NIX_FLAG_OR_ZERO(IGNCR)},
161 {"I_ICRNL", CHECK_NIX_FLAG_OR_ZERO(ICRNL)},
162#ifndef __APPLE__
163 {"I_IUCLC", CHECK_NIX_FLAG_OR_ZERO(IUCLC)}, // Might not be available on all systems
164#else
165 {"I_IUCLC", 0},
166#endif
167 {"I_IXON", CHECK_NIX_FLAG_OR_ZERO(IXON)},
168 {"I_IXANY", CHECK_NIX_FLAG_OR_ZERO(IXANY)},
169 {"I_IXOFF", CHECK_NIX_FLAG_OR_ZERO(IXOFF)},
170 {"I_IMAXBEL", CHECK_NIX_FLAG_OR_ZERO(IMAXBEL)},
171 {NULL, 0}
30}; 172};
31 173
174static const struct ls_RegConst nix_console_o_flags[] = {
175 // Output flags (c_oflag)
176 {"O_OPOST", CHECK_NIX_FLAG_OR_ZERO(OPOST)},
177#ifndef __APPLE__
178 {"O_OLCUC", CHECK_NIX_FLAG_OR_ZERO(OLCUC)}, // Might not be available on all systems
179#else
180 {"O_OLCUC", 0},
181#endif
182 {"O_ONLCR", CHECK_NIX_FLAG_OR_ZERO(ONLCR)},
183 {"O_OCRNL", CHECK_NIX_FLAG_OR_ZERO(OCRNL)},
184 {"O_ONOCR", CHECK_NIX_FLAG_OR_ZERO(ONOCR)},
185 {"O_ONLRET", CHECK_NIX_FLAG_OR_ZERO(ONLRET)},
186 {"O_OFILL", CHECK_NIX_FLAG_OR_ZERO(OFILL)},
187 {"O_OFDEL", CHECK_NIX_FLAG_OR_ZERO(OFDEL)},
188 {"O_NLDLY", CHECK_NIX_FLAG_OR_ZERO(NLDLY)},
189 {"O_CRDLY", CHECK_NIX_FLAG_OR_ZERO(CRDLY)},
190 {"O_TABDLY", CHECK_NIX_FLAG_OR_ZERO(TABDLY)},
191 {"O_BSDLY", CHECK_NIX_FLAG_OR_ZERO(BSDLY)},
192 {"O_VTDLY", CHECK_NIX_FLAG_OR_ZERO(VTDLY)},
193 {"O_FFDLY", CHECK_NIX_FLAG_OR_ZERO(FFDLY)},
194 {NULL, 0}
195};
196
197static const struct ls_RegConst nix_console_l_flags[] = {
198 // Local flags (c_lflag)
199 {"L_ISIG", CHECK_NIX_FLAG_OR_ZERO(ISIG)},
200 {"L_ICANON", CHECK_NIX_FLAG_OR_ZERO(ICANON)},
201#ifndef __APPLE__
202 {"L_XCASE", CHECK_NIX_FLAG_OR_ZERO(XCASE)}, // Might not be available on all systems
203#else
204 {"L_XCASE", 0},
205#endif
206 {"L_ECHO", CHECK_NIX_FLAG_OR_ZERO(ECHO)},
207 {"L_ECHOE", CHECK_NIX_FLAG_OR_ZERO(ECHOE)},
208 {"L_ECHOK", CHECK_NIX_FLAG_OR_ZERO(ECHOK)},
209 {"L_ECHONL", CHECK_NIX_FLAG_OR_ZERO(ECHONL)},
210 {"L_NOFLSH", CHECK_NIX_FLAG_OR_ZERO(NOFLSH)},
211 {"L_TOSTOP", CHECK_NIX_FLAG_OR_ZERO(TOSTOP)},
212 {"L_ECHOCTL", CHECK_NIX_FLAG_OR_ZERO(ECHOCTL)}, // Might not be available on all systems
213 {"L_ECHOPRT", CHECK_NIX_FLAG_OR_ZERO(ECHOPRT)}, // Might not be available on all systems
214 {"L_ECHOKE", CHECK_NIX_FLAG_OR_ZERO(ECHOKE)}, // Might not be available on all systems
215 {"L_FLUSHO", CHECK_NIX_FLAG_OR_ZERO(FLUSHO)},
216 {"L_PENDIN", CHECK_NIX_FLAG_OR_ZERO(PENDIN)},
217 {"L_IEXTEN", CHECK_NIX_FLAG_OR_ZERO(IEXTEN)},
218 {NULL, 0}
219};
220
221static DWORD win_valid_in_flags = 0;
222static DWORD win_valid_out_flags = 0;
223static DWORD nix_valid_i_flags = 0;
224static DWORD nix_valid_o_flags = 0;
225static DWORD nix_valid_l_flags = 0;
226static void initialize_valid_flags()
227{
228 win_valid_in_flags = 0;
229 for (int i = 0; win_console_in_flags[i].name != NULL; i++)
230 {
231 win_valid_in_flags |= win_console_in_flags[i].value;
232 }
233 win_valid_out_flags = 0;
234 for (int i = 0; win_console_out_flags[i].name != NULL; i++)
235 {
236 win_valid_out_flags |= win_console_out_flags[i].value;
237 }
238 nix_valid_i_flags = 0;
239 for (int i = 0; nix_console_i_flags[i].name != NULL; i++)
240 {
241 nix_valid_i_flags |= nix_console_i_flags[i].value;
242 }
243 nix_valid_o_flags = 0;
244 for (int i = 0; nix_console_o_flags[i].name != NULL; i++)
245 {
246 nix_valid_o_flags |= nix_console_o_flags[i].value;
247 }
248 nix_valid_l_flags = 0;
249 for (int i = 0; nix_console_l_flags[i].name != NULL; i++)
250 {
251 nix_valid_l_flags |= nix_console_l_flags[i].value;
252 }
253}
254
255#ifdef _WIN32
256// first item on the stack should be io.stdin, io.stderr, or io.stdout, second item
257// should be the flags to validate.
258// If it returns NULL, then it leaves nil+err on the stack
259static HANDLE get_console_handle(lua_State *L, int flags_optional)
260{
261 if (lua_gettop(L) < 1) {
262 luaL_argerror(L, 1, "expected file handle");
263 }
264
265 HANDLE handle;
266 DWORD valid;
267 FILE *file = *(FILE **)luaL_checkudata(L, 1, LUA_FILEHANDLE);
268 if (file == stdin && file != NULL) {
269 handle = GetStdHandle(STD_INPUT_HANDLE);
270 valid = win_valid_in_flags;
271
272 } else if (file == stdout && file != NULL) {
273 handle = GetStdHandle(STD_OUTPUT_HANDLE);
274 valid = win_valid_out_flags;
275
276 } else if (file == stderr && file != NULL) {
277 handle = GetStdHandle(STD_ERROR_HANDLE);
278 valid = win_valid_out_flags;
279
280 } else {
281 luaL_argerror(L, 1, "invalid file handle"); // does not return
282 }
283
284 if (handle == INVALID_HANDLE_VALUE) {
285 termFormatError(L, GetLastError(), "failed to retrieve std handle");
286 lua_error(L); // does not return
287 }
288
289 if (handle == NULL) {
290 lua_pushnil(L);
291 lua_pushliteral(L, "failed to get console handle");
292 return NULL;
293 }
294
295 if (flags_optional && lua_gettop(L) < 2) {
296 return handle;
297 }
298
299 if (lua_gettop(L) < 2) {
300 luaL_argerror(L, 2, "expected flags");
301 }
302
303 LSBF_BITFLAG flags = lsbf_checkbitflags(L, 2);
304 if ((flags & ~valid) != 0) {
305 luaL_argerror(L, 2, "invalid flags");
306 }
307
308 return handle;
309}
310#else
311// first item on the stack should be io.stdin, io.stderr, or io.stdout. Throws a
312// Lua error if the file is not one of these.
313static int get_console_handle(lua_State *L)
314{
315 FILE **file = (FILE **)luaL_checkudata(L, 1, LUA_FILEHANDLE);
316 if (file == NULL || *file == NULL) {
317 return luaL_argerror(L, 1, "expected file handle"); // call doesn't return
318 }
319
320 // Check if the file is stdin, stdout, or stderr
321 if (*file == stdin || *file == stdout || *file == stderr) {
322 // Push the file descriptor onto the Lua stack
323 return fileno(*file);
324 }
325
326 return luaL_argerror(L, 1, "invalid file handle"); // does not return
327}
328#endif
329
330
331
332/***
333Sets the console flags (Windows).
334The `CIF_` and `COF_` constants are available on the module table. Where `CIF` are the
335input flags (for use with `io.stdin`) and `COF` are the output flags (for use with
336`io.stdout`/`io.stderr`).
337
338To see flag status and constant names check `listconsoleflags`.
339
340Note: not all combinations of flags are allowed, as some are mutually exclusive or mutually required.
341See [setconsolemode documentation](https://learn.microsoft.com/en-us/windows/console/setconsolemode)
342@function setconsoleflags
343@tparam file file file handle to operate on, one of `io.stdin`, `io.stdout`, `io.stderr`
344@tparam bitflags bitflags the flags to set/unset
345@treturn[1] boolean `true` on success
346@treturn[2] nil
347@treturn[2] string error message
348@usage
349local system = require('system')
350system.listconsoleflags(io.stdout) -- List all the available flags and their current status
351
352local flags = system.getconsoleflags(io.stdout)
353assert(system.setconsoleflags(io.stdout,
354 flags + system.COF_VIRTUAL_TERMINAL_PROCESSING)
355
356system.listconsoleflags(io.stdout) -- List again to check the differences
357*/
358static int lst_setconsoleflags(lua_State *L)
359{
360#ifdef _WIN32
361 HANDLE console_handle = get_console_handle(L, 0);
362 if (console_handle == NULL) {
363 return 2; // error message is already on the stack
364 }
365 LSBF_BITFLAG new_console_mode = lsbf_checkbitflags(L, 2);
366
367 if (!SetConsoleMode(console_handle, new_console_mode)) {
368 termFormatError(L, GetLastError(), "failed to set console mode");
369 return 2;
370 }
371
372#else
373 get_console_handle(L); // to validate args
374#endif
375
376 lua_pushboolean(L, 1);
377 return 1;
378}
379
380
381
382/***
383Gets console flags (Windows).
384The `CIF_` and `COF_` constants are available on the module table. Where `CIF` are the
385input flags (for use with `io.stdin`) and `COF` are the output flags (for use with
386`io.stdout`/`io.stderr`).
387
388_Note_: See [setconsolemode documentation](https://learn.microsoft.com/en-us/windows/console/setconsolemode)
389for more information on the flags.
390
391
392
393@function getconsoleflags
394@tparam file file file handle to operate on, one of `io.stdin`, `io.stdout`, `io.stderr`
395@treturn[1] bitflags the current console flags.
396@treturn[2] nil
397@treturn[2] string error message
398@usage
399local system = require('system')
400
401local flags = system.getconsoleflags(io.stdout)
402print("Current stdout flags:", tostring(flags))
403
404if flags:has_all_of(system.COF_VIRTUAL_TERMINAL_PROCESSING + system.COF_PROCESSED_OUTPUT) then
405 print("Both flags are set")
406else
407 print("At least one flag is not set")
408end
409*/
410static int lst_getconsoleflags(lua_State *L)
411{
412 DWORD console_mode = 0;
413
414#ifdef _WIN32
415 HANDLE console_handle = get_console_handle(L, 1);
416 if (console_handle == NULL) {
417 return 2; // error message is already on the stack
418 }
419
420 if (GetConsoleMode(console_handle, &console_mode) == 0)
421 {
422 lua_pushnil(L);
423 lua_pushliteral(L, "failed to get console mode");
424 return 2;
425 }
426#else
427 get_console_handle(L); // to validate args
428
429#endif
430 lsbf_pushbitflags(L, console_mode);
431 return 1;
432}
433
434
435
436/*-------------------------------------------------------------------------
437 * Unix tcgetattr/tcsetattr functions
438 *-------------------------------------------------------------------------*/
439// Code modified from the LuaPosix library by Gary V. Vaughan
440// see https://github.com/luaposix/luaposix
441
442/***
443Get termios state (Posix).
444The terminal attributes is a table with the following fields:
445
446- `iflag` input flags
447- `oflag` output flags
448- `lflag` local flags
449- `cflag` control flags
450- `ispeed` input speed
451- `ospeed` output speed
452- `cc` control characters
453
454@function tcgetattr
455@tparam file fd file handle to operate on, one of `io.stdin`, `io.stdout`, `io.stderr`
456@treturn[1] termios terminal attributes, if successful. On Windows the bitflags are all 0, and the `cc` table is empty.
457@treturn[2] nil
458@treturn[2] string error message
459@treturn[2] int errnum
460@return error message if failed
461@usage
462local system = require('system')
463
464local status = assert(tcgetattr(io.stdin))
465if status.iflag:has_all_of(system.I_IGNBRK) then
466 print("Ignoring break condition")
467end
468*/
469static int lst_tcgetattr(lua_State *L)
470{
471#ifndef _WIN32
472 int r, i;
473 struct termios t;
474 int fd = get_console_handle(L);
475
476 r = tcgetattr(fd, &t);
477 if (r == -1) return pusherror(L, NULL);
478
479 lua_newtable(L);
480 lsbf_pushbitflags(L, t.c_iflag);
481 lua_setfield(L, -2, "iflag");
482
483 lsbf_pushbitflags(L, t.c_oflag);
484 lua_setfield(L, -2, "oflag");
485
486 lsbf_pushbitflags(L, t.c_lflag);
487 lua_setfield(L, -2, "lflag");
488
489 lsbf_pushbitflags(L, t.c_cflag);
490 lua_setfield(L, -2, "cflag");
491
492 lua_pushinteger(L, cfgetispeed(&t));
493 lua_setfield(L, -2, "ispeed");
494
495 lua_pushinteger(L, cfgetospeed(&t));
496 lua_setfield(L, -2, "ospeed");
497
498 lua_newtable(L);
499 for (i=0; i<NCCS; i++)
500 {
501 lua_pushinteger(L, i);
502 lua_pushinteger(L, t.c_cc[i]);
503 lua_settable(L, -3);
504 }
505 lua_setfield(L, -2, "cc");
506
507#else
508 lua_settop(L, 1); // remove all but file handle
509 get_console_handle(L, 1); //check args
510
511 lua_newtable(L);
512 lsbf_pushbitflags(L, 0);
513 lua_setfield(L, -2, "iflag");
514 lsbf_pushbitflags(L, 0);
515 lua_setfield(L, -2, "oflag");
516 lsbf_pushbitflags(L, 0);
517 lua_setfield(L, -2, "lflag");
518 lsbf_pushbitflags(L, 0);
519 lua_setfield(L, -2, "cflag");
520 lua_pushinteger(L, 0);
521 lua_setfield(L, -2, "ispeed");
522 lua_pushinteger(L, 0);
523 lua_setfield(L, -2, "ospeed");
524 lua_newtable(L);
525 lua_setfield(L, -2, "cc");
526
527#endif
528 return 1;
529}
530
531
532
533/***
534Set termios state (Posix).
535This function will set the flags as given.
536
537The `I_`, `O_`, and `L_` constants are available on the module table. They are the respective
538flags for the `iflags`, `oflags`, and `lflags` bitmasks.
539
540To see flag status and constant names check `listtermflags`. For their meaning check
541[the manpage](https://www.man7.org/linux/man-pages/man3/termios.3.html).
542
543_Note_: only `iflag`, `oflag`, and `lflag` are supported at the moment. The other fields are ignored.
544@function tcsetattr
545@tparam file fd file handle to operate on, one of `io.stdin`, `io.stdout`, `io.stderr`
546@int actions one of `TCSANOW`, `TCSADRAIN`, `TCSAFLUSH`
547@tparam table termios a table with bitflag fields:
548@tparam[opt] bitflags termios.iflag if given will set the input flags
549@tparam[opt] bitflags termios.oflag if given will set the output flags
550@tparam[opt] bitflags termios.lflag if given will set the local flags
551@treturn[1] bool `true`, if successful. Always returns `true` on Windows.
552@return[2] nil
553@treturn[2] string error message
554@treturn[2] int errnum
555@usage
556local system = require('system')
557
558local status = assert(tcgetattr(io.stdin))
559if not status.lflag:has_all_of(system.L_ECHO) then
560 -- if echo is off, turn echoing newlines on
561 tcsetattr(io.stdin, system.TCSANOW, { lflag = status.lflag + system.L_ECHONL }))
562end
563*/
564static int lst_tcsetattr(lua_State *L)
565{
566#ifndef _WIN32
567 struct termios t;
568 int r, i;
569 int fd = get_console_handle(L); // first is the console handle
570 int act = luaL_checkinteger(L, 2); // second is the action to take
571
572 r = tcgetattr(fd, &t);
573 if (r == -1) return pusherror(L, NULL);
574
575 t.c_iflag = lsbf_checkbitflagsfield(L, 3, "iflag", t.c_iflag);
576 t.c_oflag = lsbf_checkbitflagsfield(L, 3, "oflag", t.c_oflag);
577 t.c_lflag = lsbf_checkbitflagsfield(L, 3, "lflag", t.c_lflag);
578
579 // Skipping the others for now
580
581 // lua_getfield(L, 3, "cflag"); t.c_cflag = optint(L, -1, 0); lua_pop(L, 1);
582 // lua_getfield(L, 3, "ispeed"); cfsetispeed( &t, optint(L, -1, B0) ); lua_pop(L, 1);
583 // lua_getfield(L, 3, "ospeed"); cfsetospeed( &t, optint(L, -1, B0) ); lua_pop(L, 1);
584
585 // lua_getfield(L, 3, "cc");
586 // for (i=0; i<NCCS; i++)
587 // {
588 // lua_pushinteger(L, i);
589 // lua_gettable(L, -2);
590 // t.c_cc[i] = optint(L, -1, 0);
591 // lua_pop(L, 1);
592 // }
593
594 r = tcsetattr(fd, act, &t);
595 if (r == -1) return pusherror(L, NULL);
596
597#else
598 // Windows does not have a tcsetattr function, but we check arguments anyway
599 luaL_checkinteger(L, 2);
600 lsbf_checkbitflagsfield(L, 3, "iflag", 0);
601 lsbf_checkbitflagsfield(L, 3, "oflag", 0);
602 lsbf_checkbitflagsfield(L, 3, "lflag", 0);
603 lua_settop(L, 1); // remove all but file handle
604 get_console_handle(L, 1);
605#endif
606
607 lua_pushboolean(L, 1);
608 return 1;
609}
610
611
612
613/***
614Enables or disables non-blocking mode for a file (Posix).
615@function setnonblock
616@tparam file fd file handle to operate on, one of `io.stdin`, `io.stdout`, `io.stderr`
617@tparam boolean make_non_block a truthy value will enable non-blocking mode, a falsy value will disable it.
618@treturn[1] bool `true`, if successful
619@treturn[2] nil
620@treturn[2] string error message
621@treturn[2] int errnum
622@see getnonblock
623@usage
624local sys = require('system')
625
626-- set io.stdin to non-blocking mode
627local old_setting = sys.getnonblock(io.stdin)
628sys.setnonblock(io.stdin, true)
629
630-- do stuff
631
632-- restore old setting
633sys.setnonblock(io.stdin, old_setting)
634*/
635static int lst_setnonblock(lua_State *L)
636{
637#ifndef _WIN32
638
639 int fd = get_console_handle(L);
640
641 int flags = fcntl(fd, F_GETFL, 0);
642 if (flags == -1) {
643 return pusherror(L, "Error getting handle flags: ");
644 }
645 if (lua_toboolean(L, 2)) {
646 // truthy: set non-blocking
647 flags |= O_NONBLOCK;
648 } else {
649 // falsy: set disable non-blocking
650 flags &= ~O_NONBLOCK;
651 }
652 if (fcntl(fd, F_SETFL, flags) == -1) {
653 return pusherror(L, "Error changing O_NONBLOCK: ");
654 }
655
656#else
657 if (lua_gettop(L) > 1) {
658 lua_settop(L, 1); // use one argument, because the second boolean will fail as get_console_flags expects bitflags
659 }
660 HANDLE console_handle = get_console_handle(L, 1);
661 if (console_handle == NULL) {
662 return 2; // error message is already on the stack
663 }
664
665#endif
666
667 lua_pushboolean(L, 1);
668 return 1;
669}
670
671
672
673/***
674Gets non-blocking mode status for a file (Posix).
675@function getnonblock
676@tparam file fd file handle to operate on, one of `io.stdin`, `io.stdout`, `io.stderr`
677@treturn[1] bool `true` if set to non-blocking, `false` if not. Always returns `false` on Windows.
678@treturn[2] nil
679@treturn[2] string error message
680@treturn[2] int errnum
681*/
682static int lst_getnonblock(lua_State *L)
683{
684#ifndef _WIN32
685
686 int fd = get_console_handle(L);
687
688 // Set O_NONBLOCK
689 int flags = fcntl(fd, F_GETFL, 0);
690 if (flags == -1) {
691 return pusherror(L, "Error getting handle flags: ");
692 }
693 if (flags & O_NONBLOCK) {
694 lua_pushboolean(L, 1);
695 } else {
696 lua_pushboolean(L, 0);
697 }
698
699#else
700 if (lua_gettop(L) > 1) {
701 lua_settop(L, 1); // use one argument, because the second boolean will fail as get_console_flags expects bitflags
702 }
703 HANDLE console_handle = get_console_handle(L, 1);
704 if (console_handle == NULL) {
705 return 2; // error message is already on the stack
706 }
707
708 lua_pushboolean(L, 0);
709
710#endif
711 return 1;
712}
713
714
715
716/*-------------------------------------------------------------------------
717 * Reading keyboard input
718 *-------------------------------------------------------------------------*/
719
720#ifdef _WIN32
721// Define a static buffer for UTF-8 characters
722static char utf8_buffer[4];
723static int utf8_buffer_len = 0;
724static int utf8_buffer_index = 0;
725#endif
726
727
728/***
729Reads a key from the console non-blocking. This function should not be called
730directly, but through the `system.readkey` or `system.readansi` functions. It
731will return the next byte from the input stream, or `nil` if no key was pressed.
732
733On Posix, `io.stdin` must be set to non-blocking mode using `setnonblock`
734and canonical mode must be turned off using `tcsetattr`,
735before calling this function. Otherwise it will block. No conversions are
736done on Posix, so the byte read is returned as-is.
737
738On Windows this reads a wide character and converts it to UTF-8. Multi-byte
739sequences will be buffered internally and returned one byte at a time.
740
741@function _readkey
742@treturn[1] integer the byte read from the input stream
743@treturn[2] nil if no key was pressed
744@treturn[3] nil on error
745@treturn[3] string error message
746@treturn[3] int errnum (on posix)
747*/
748static int lst_readkey(lua_State *L) {
749#ifdef _WIN32
750 if (utf8_buffer_len > 0) {
751 // Buffer not empty, return the next byte
752 lua_pushinteger(L, (unsigned char)utf8_buffer[utf8_buffer_index]);
753 utf8_buffer_index++;
754 utf8_buffer_len--;
755 // printf("returning from buffer: %d\n", luaL_checkinteger(L, -1));
756 if (utf8_buffer_len == 0) {
757 utf8_buffer_index = 0;
758 }
759 return 1;
760 }
761
762 if (!_kbhit()) {
763 return 0;
764 }
765
766 wchar_t wc = _getwch();
767 // printf("----\nread wchar_t: %x\n", wc);
768 if (wc == WEOF) {
769 lua_pushnil(L);
770 lua_pushliteral(L, "read error");
771 return 2;
772 }
773
774 if (sizeof(wchar_t) == 2) {
775 // printf("2-byte wchar_t\n");
776 // only 2 bytes wide, not 4
777 if (wc >= 0xD800 && wc <= 0xDBFF) {
778 // printf("2-byte wchar_t, received high, getting low...\n");
779
780 // we got a high surrogate, so we need to read the next one as the low surrogate
781 if (!_kbhit()) {
782 lua_pushnil(L);
783 lua_pushliteral(L, "incomplete surrogate pair");
784 return 2;
785 }
786
787 wchar_t wc2 = _getwch();
788 // printf("read wchar_t 2: %x\n", wc2);
789 if (wc2 == WEOF) {
790 lua_pushnil(L);
791 lua_pushliteral(L, "read error");
792 return 2;
793 }
794
795 if (wc2 < 0xDC00 || wc2 > 0xDFFF) {
796 lua_pushnil(L);
797 lua_pushliteral(L, "invalid surrogate pair");
798 return 2;
799 }
800 // printf("2-byte pair complete now\n");
801 wchar_t wch_pair[2] = { wc, wc2 };
802 utf8_buffer_len = WideCharToMultiByte(CP_UTF8, 0, wch_pair, 2, utf8_buffer, sizeof(utf8_buffer), NULL, NULL);
803
804 } else {
805 // printf("2-byte wchar_t, no surrogate pair\n");
806 // not a high surrogate, so we can handle just the 2 bytes directly
807 utf8_buffer_len = WideCharToMultiByte(CP_UTF8, 0, &wc, 1, utf8_buffer, sizeof(utf8_buffer), NULL, NULL);
808 }
809
810 } else {
811 // printf("4-byte wchar_t\n");
812 // 4 bytes wide, so handle as UTF-32 directly
813 utf8_buffer_len = WideCharToMultiByte(CP_UTF8, 0, &wc, 1, utf8_buffer, sizeof(utf8_buffer), NULL, NULL);
814 }
815 // printf("utf8_buffer_len: %d\n", utf8_buffer_len);
816 utf8_buffer_index = 0;
817 if (utf8_buffer_len <= 0) {
818 lua_pushnil(L);
819 lua_pushliteral(L, "UTF-8 conversion error");
820 return 2;
821 }
822
823 lua_pushinteger(L, (unsigned char)utf8_buffer[utf8_buffer_index]);
824 utf8_buffer_index++;
825 utf8_buffer_len--;
826 // printf("returning from buffer: %x\n", luaL_checkinteger(L, -1));
827 return 1;
828
829#else
830 // Posix implementation
831 char ch;
832 ssize_t bytes_read = read(STDIN_FILENO, &ch, 1);
833 if (bytes_read > 0) {
834 lua_pushinteger(L, (unsigned char)ch);
835 return 1;
836
837 } else if (bytes_read == 0) {
838 return 0; // End of file or stream closed
839
840 } else {
841 if (errno == EAGAIN || errno == EWOULDBLOCK) {
842 // Resource temporarily unavailable, no data available to read
843 return 0;
844 } else {
845 return pusherror(L, "read error");
846 }
847 }
848
849#endif
850}
851
852
853
854/*-------------------------------------------------------------------------
855 * Retrieve terminal size
856 *-------------------------------------------------------------------------*/
857
858
859/***
860Get the size of the terminal in rows and columns.
861@function termsize
862@treturn[1] int the number of rows
863@treturn[1] int the number of columns
864@treturn[2] nil
865@treturn[2] string error message
866*/
867static int lst_termsize(lua_State *L) {
868 int columns, rows;
869
870#ifdef _WIN32
871 CONSOLE_SCREEN_BUFFER_INFO csbi;
872 if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) {
873 termFormatError(L, GetLastError(), "Failed to get terminal size.");
874 return 2;
875 }
876 columns = csbi.srWindow.Right - csbi.srWindow.Left + 1;
877 rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
878
879#else
880 struct winsize ws;
881 if (ioctl(1, TIOCGWINSZ, &ws) == -1) {
882 return pusherror(L, "Failed to get terminal size.");
883 }
884 columns = ws.ws_col;
885 rows = ws.ws_row;
886
887#endif
888 lua_pushinteger(L, rows);
889 lua_pushinteger(L, columns);
890 return 2;
891}
892
893
894
895/*-------------------------------------------------------------------------
896 * utf8 conversion and support
897 *-------------------------------------------------------------------------*/
898
899// Function to convert a single UTF-8 character to a Unicode code point (uint32_t)
900// To prevent having to do codepage/locale changes, we use a custom implementation
901int utf8_to_wchar(const char *utf8, size_t len, mk_wchar_t *codepoint) {
902 if (len == 0) {
903 return -1; // No input provided
904 }
905
906 unsigned char c = (unsigned char)utf8[0];
907 if (c <= 0x7F) {
908 *codepoint = c;
909 return 1;
910 } else if ((c & 0xE0) == 0xC0) {
911 if (len < 2) return -1; // Not enough bytes
912 *codepoint = ((utf8[0] & 0x1F) << 6) | (utf8[1] & 0x3F);
913 return 2;
914 } else if ((c & 0xF0) == 0xE0) {
915 if (len < 3) return -1; // Not enough bytes
916 *codepoint = ((utf8[0] & 0x0F) << 12) | ((utf8[1] & 0x3F) << 6) | (utf8[2] & 0x3F);
917 return 3;
918 } else if ((c & 0xF8) == 0xF0) {
919 if (len < 4) return -1; // Not enough bytes
920 *codepoint = ((utf8[0] & 0x07) << 18) | ((utf8[1] & 0x3F) << 12) | ((utf8[2] & 0x3F) << 6) | (utf8[3] & 0x3F);
921 return 4;
922 } else {
923 // Invalid UTF-8 character
924 return -1;
925 }
926}
927
928
929/***
930Get the width of a utf8 character for terminal display.
931@function utf8cwidth
932@tparam string utf8_char the utf8 character to check, only the width of the first character will be returned
933@treturn[1] int the display width in columns of the first character in the string (0 for an empty string)
934@treturn[2] nil
935@treturn[2] string error message
936*/
937int lst_utf8cwidth(lua_State *L) {
938 const char *utf8_char;
939 size_t utf8_len;
940 utf8_char = luaL_checklstring(L, 1, &utf8_len);
941 int width = 0;
942
943 mk_wchar_t wc;
944
945 if (utf8_len == 0) {
946 lua_pushinteger(L, 0);
947 return 1;
948 }
949
950 // Convert the UTF-8 string to a wide character
951 int bytes_processed = utf8_to_wchar(utf8_char, utf8_len, &wc);
952 if (bytes_processed == -1) {
953 lua_pushnil(L);
954 lua_pushstring(L, "Invalid UTF-8 character");
955 return 2;
956 }
957
958 // Get the width of the wide character
959 width = mk_wcwidth(wc);
960 if (width == -1) {
961 lua_pushnil(L);
962 lua_pushstring(L, "Character width determination failed");
963 return 2;
964 }
965
966 lua_pushinteger(L, width);
967 return 1;
968}
969
970
971
972
973/***
974Get the width of a utf8 string for terminal display.
975@function utf8swidth
976@tparam string utf8_string the utf8 string to check
977@treturn[1] int the display width of the string in columns (0 for an empty string)
978@treturn[2] nil
979@treturn[2] string error message
980*/
981int lst_utf8swidth(lua_State *L) {
982 const char *utf8_str;
983 size_t utf8_len;
984 utf8_str = luaL_checklstring(L, 1, &utf8_len);
985 int total_width = 0;
986
987 if (utf8_len == 0) {
988 lua_pushinteger(L, 0);
989 return 1;
990 }
991
992 int bytes_processed = 0;
993 size_t i = 0;
994 mk_wchar_t wc;
995
996 while (i < utf8_len) {
997 bytes_processed = utf8_to_wchar(utf8_str + i, utf8_len - i, &wc);
998 if (bytes_processed == -1) {
999 lua_pushnil(L);
1000 lua_pushstring(L, "Invalid UTF-8 character");
1001 return 2;
1002 }
1003
1004 int width = mk_wcwidth(wc);
1005 if (width == -1) {
1006 lua_pushnil(L);
1007 lua_pushstring(L, "Character width determination failed");
1008 return 2;
1009 }
1010
1011 total_width += width;
1012 i += bytes_processed;
1013 }
1014
1015 lua_pushinteger(L, total_width);
1016 return 1;
1017}
1018
1019
1020
1021/*-------------------------------------------------------------------------
1022 * Windows codepage functions
1023 *-------------------------------------------------------------------------*/
1024
1025
1026/***
1027Gets the current console code page (Windows).
1028@function getconsolecp
1029@treturn[1] int the current code page (always 65001 on Posix systems)
1030*/
1031static int lst_getconsolecp(lua_State *L) {
1032 unsigned int cp = 65001;
1033#ifdef _WIN32
1034 cp = GetConsoleCP();
1035#endif
1036 lua_pushinteger(L, cp);
1037 return 1;
1038}
1039
1040
1041
1042/***
1043Sets the current console code page (Windows).
1044@function setconsolecp
1045@tparam int cp the code page to set, use `system.CODEPAGE_UTF8` (65001) for UTF-8
1046@treturn[1] bool `true` on success (always `true` on Posix systems)
1047*/
1048static int lst_setconsolecp(lua_State *L) {
1049 unsigned int cp = (unsigned int)luaL_checkinteger(L, 1);
1050 int success = TRUE;
1051#ifdef _WIN32
1052 SetConsoleCP(cp);
1053#endif
1054 lua_pushboolean(L, success);
1055 return 1;
1056}
1057
1058
1059
1060/***
1061Gets the current console output code page (Windows).
1062@function getconsoleoutputcp
1063@treturn[1] int the current code page (always 65001 on Posix systems)
1064*/
1065static int lst_getconsoleoutputcp(lua_State *L) {
1066 unsigned int cp = 65001;
1067#ifdef _WIN32
1068 cp = GetConsoleOutputCP();
1069#endif
1070 lua_pushinteger(L, cp);
1071 return 1;
1072}
1073
1074
1075
1076/***
1077Sets the current console output code page (Windows).
1078@function setconsoleoutputcp
1079@tparam int cp the code page to set, use `system.CODEPAGE_UTF8` (65001) for UTF-8
1080@treturn[1] bool `true` on success (always `true` on Posix systems)
1081*/
1082static int lst_setconsoleoutputcp(lua_State *L) {
1083 unsigned int cp = (unsigned int)luaL_checkinteger(L, 1);
1084 int success = TRUE;
1085#ifdef _WIN32
1086 SetConsoleOutputCP(cp);
1087#endif
1088 lua_pushboolean(L, success);
1089 return 1;
1090}
1091
1092
1093
32/*------------------------------------------------------------------------- 1094/*-------------------------------------------------------------------------
33 * Initializes module 1095 * Initializes module
34 *-------------------------------------------------------------------------*/ 1096 *-------------------------------------------------------------------------*/
1097
1098static luaL_Reg func[] = {
1099 { "isatty", lst_isatty },
1100 { "getconsoleflags", lst_getconsoleflags },
1101 { "setconsoleflags", lst_setconsoleflags },
1102 { "tcgetattr", lst_tcgetattr },
1103 { "tcsetattr", lst_tcsetattr },
1104 { "getnonblock", lst_getnonblock },
1105 { "setnonblock", lst_setnonblock },
1106 { "_readkey", lst_readkey },
1107 { "termsize", lst_termsize },
1108 { "utf8cwidth", lst_utf8cwidth },
1109 { "utf8swidth", lst_utf8swidth },
1110 { "getconsolecp", lst_getconsolecp },
1111 { "setconsolecp", lst_setconsolecp },
1112 { "getconsoleoutputcp", lst_getconsoleoutputcp },
1113 { "setconsoleoutputcp", lst_setconsoleoutputcp },
1114 { NULL, NULL }
1115};
1116
1117
1118
35void term_open(lua_State *L) { 1119void term_open(lua_State *L) {
1120 // set up constants and export the constants in module table
1121 initialize_valid_flags();
1122 // Windows flags
1123 for (int i = 0; win_console_in_flags[i].name != NULL; i++)
1124 {
1125 lsbf_pushbitflags(L, win_console_in_flags[i].value);
1126 lua_setfield(L, -2, win_console_in_flags[i].name);
1127 }
1128 for (int i = 0; win_console_out_flags[i].name != NULL; i++)
1129 {
1130 lsbf_pushbitflags(L, win_console_out_flags[i].value);
1131 lua_setfield(L, -2, win_console_out_flags[i].name);
1132 }
1133 // Unix flags
1134 for (int i = 0; nix_console_i_flags[i].name != NULL; i++)
1135 {
1136 lsbf_pushbitflags(L, nix_console_i_flags[i].value);
1137 lua_setfield(L, -2, nix_console_i_flags[i].name);
1138 }
1139 for (int i = 0; nix_console_o_flags[i].name != NULL; i++)
1140 {
1141 lsbf_pushbitflags(L, nix_console_o_flags[i].value);
1142 lua_setfield(L, -2, nix_console_o_flags[i].name);
1143 }
1144 for (int i = 0; nix_console_l_flags[i].name != NULL; i++)
1145 {
1146 lsbf_pushbitflags(L, nix_console_l_flags[i].value);
1147 lua_setfield(L, -2, nix_console_l_flags[i].name);
1148 }
1149 // Unix tcsetattr actions
1150 for (int i = 0; nix_tcsetattr_actions[i].name != NULL; i++)
1151 {
1152 lua_pushinteger(L, nix_tcsetattr_actions[i].value);
1153 lua_setfield(L, -2, nix_tcsetattr_actions[i].name);
1154 }
1155
1156 // export functions
36 luaL_setfuncs(L, func, 0); 1157 luaL_setfuncs(L, func, 0);
37} 1158}
diff --git a/src/time.c b/src/time.c
index 5f0ead0..05f4f1b 100644
--- a/src/time.c
+++ b/src/time.c
@@ -1,4 +1,8 @@
1/// @submodule system 1/// @module system
2
3/// Time.
4// @section time
5
2#include <lua.h> 6#include <lua.h>
3#include <lauxlib.h> 7#include <lauxlib.h>
4 8
diff --git a/src/wcwidth.c b/src/wcwidth.c
new file mode 100644
index 0000000..6032158
--- /dev/null
+++ b/src/wcwidth.c
@@ -0,0 +1,285 @@
1// This file was modified from the original versions, check "modified:" comments for details
2// Character range updates (both the table and the +1 check) were generated using ChatGPT.
3
4/*
5 * This is an implementation of wcwidth() and wcswidth() (defined in
6 * IEEE Std 1002.1-2001) for Unicode.
7 *
8 * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html
9 * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html
10 *
11 * In fixed-width output devices, Latin characters all occupy a single
12 * "cell" position of equal width, whereas ideographic CJK characters
13 * occupy two such cells. Interoperability between terminal-line
14 * applications and (teletype-style) character terminals using the
15 * UTF-8 encoding requires agreement on which character should advance
16 * the cursor by how many cell positions. No established formal
17 * standards exist at present on which Unicode character shall occupy
18 * how many cell positions on character terminals. These routines are
19 * a first attempt of defining such behavior based on simple rules
20 * applied to data provided by the Unicode Consortium.
21 *
22 * For some graphical characters, the Unicode standard explicitly
23 * defines a character-cell width via the definition of the East Asian
24 * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes.
25 * In all these cases, there is no ambiguity about which width a
26 * terminal shall use. For characters in the East Asian Ambiguous (A)
27 * class, the width choice depends purely on a preference of backward
28 * compatibility with either historic CJK or Western practice.
29 * Choosing single-width for these characters is easy to justify as
30 * the appropriate long-term solution, as the CJK practice of
31 * displaying these characters as double-width comes from historic
32 * implementation simplicity (8-bit encoded characters were displayed
33 * single-width and 16-bit ones double-width, even for Greek,
34 * Cyrillic, etc.) and not any typographic considerations.
35 *
36 * Much less clear is the choice of width for the Not East Asian
37 * (Neutral) class. Existing practice does not dictate a width for any
38 * of these characters. It would nevertheless make sense
39 * typographically to allocate two character cells to characters such
40 * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be
41 * represented adequately with a single-width glyph. The following
42 * routines at present merely assign a single-cell width to all
43 * neutral characters, in the interest of simplicity. This is not
44 * entirely satisfactory and should be reconsidered before
45 * establishing a formal standard in this area. At the moment, the
46 * decision which Not East Asian (Neutral) characters should be
47 * represented by double-width glyphs cannot yet be answered by
48 * applying a simple rule from the Unicode database content. Setting
49 * up a proper standard for the behavior of UTF-8 character terminals
50 * will require a careful analysis not only of each Unicode character,
51 * but also of each presentation form, something the author of these
52 * routines has avoided to do so far.
53 *
54 * http://www.unicode.org/unicode/reports/tr11/
55 *
56 * Markus Kuhn -- 2007-05-26 (Unicode 5.0)
57 *
58 * Permission to use, copy, modify, and distribute this software
59 * for any purpose and without fee is hereby granted. The author
60 * disclaims all warranties with regard to this software.
61 *
62 * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
63 */
64
65#include "wcwidth.h" // modified: used to define mk_wchar_t
66
67struct interval {
68 int first;
69 int last;
70};
71
72/* auxiliary function for binary search in interval table */
73static int bisearch(mk_wchar_t ucs, const struct interval *table, int max) { // modified: use mk_wchar_t
74 int min = 0;
75 int mid;
76
77 if (ucs < table[0].first || ucs > table[max].last)
78 return 0;
79 while (max >= min) {
80 mid = (min + max) / 2;
81 if (ucs > table[mid].last)
82 min = mid + 1;
83 else if (ucs < table[mid].first)
84 max = mid - 1;
85 else
86 return 1;
87 }
88
89 return 0;
90}
91
92
93/* The following two functions define the column width of an ISO 10646
94 * character as follows:
95 *
96 * - The null character (U+0000) has a column width of 0.
97 *
98 * - Other C0/C1 control characters and DEL will lead to a return
99 * value of -1.
100 *
101 * - Non-spacing and enclosing combining characters (general
102 * category code Mn or Me in the Unicode database) have a
103 * column width of 0.
104 *
105 * - SOFT HYPHEN (U+00AD) has a column width of 1.
106 *
107 * - Other format characters (general category code Cf in the Unicode
108 * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0.
109 *
110 * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF)
111 * have a column width of 0.
112 *
113 * - Spacing characters in the East Asian Wide (W) or East Asian
114 * Full-width (F) category as defined in Unicode Technical
115 * Report #11 have a column width of 2.
116 *
117 * - All remaining characters (including all printable
118 * ISO 8859-1 and WGL4 characters, Unicode control characters,
119 * etc.) have a column width of 1.
120 *
121 * This implementation assumes that mk_wchar_t characters are encoded
122 * in ISO 10646.
123 */
124
125int mk_wcwidth(mk_wchar_t ucs) // modified: use mk_wchar_t
126{
127 /* sorted list of non-overlapping intervals of non-spacing characters */
128 /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */
129 static const struct interval combining[] = { // modified: added new ranges to the list
130 { 0x0300, 0x036F }, { 0x0483, 0x0489 }, { 0x0591, 0x05BD },
131 { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, { 0x05C4, 0x05C5 },
132 { 0x05C7, 0x05C7 }, { 0x0600, 0x0605 }, { 0x0610, 0x061A },
133 { 0x061C, 0x061C }, { 0x064B, 0x065F }, { 0x0670, 0x0670 },
134 { 0x06D6, 0x06DC }, { 0x06DF, 0x06E4 }, { 0x06E7, 0x06E8 },
135 { 0x06EA, 0x06ED }, { 0x0711, 0x0711 }, { 0x0730, 0x074A },
136 { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x07FD, 0x07FD },
137 { 0x0816, 0x0819 }, { 0x081B, 0x0823 }, { 0x0825, 0x0827 },
138 { 0x0829, 0x082D }, { 0x0859, 0x085B }, { 0x08D3, 0x08E1 },
139 { 0x08E3, 0x0903 }, { 0x093A, 0x093C }, { 0x093E, 0x094F },
140 { 0x0951, 0x0957 }, { 0x0962, 0x0963 }, { 0x0981, 0x0983 },
141 { 0x09BC, 0x09BC }, { 0x09BE, 0x09C4 }, { 0x09C7, 0x09C8 },
142 { 0x09CB, 0x09CD }, { 0x09D7, 0x09D7 }, { 0x09E2, 0x09E3 },
143 { 0x09FE, 0x09FE }, { 0x0A01, 0x0A03 }, { 0x0A3C, 0x0A3C },
144 { 0x0A3E, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D },
145 { 0x0A51, 0x0A51 }, { 0x0A70, 0x0A71 }, { 0x0A75, 0x0A75 },
146 { 0x0A81, 0x0A83 }, { 0x0ABC, 0x0ABC }, { 0x0ABE, 0x0AC5 },
147 { 0x0AC7, 0x0AC9 }, { 0x0ACB, 0x0ACD }, { 0x0AE2, 0x0AE3 },
148 { 0x0AFA, 0x0AFF }, { 0x0B01, 0x0B03 }, { 0x0B3C, 0x0B3C },
149 { 0x0B3E, 0x0B44 }, { 0x0B47, 0x0B48 }, { 0x0B4B, 0x0B4D },
150 { 0x0B55, 0x0B57 }, { 0x0B62, 0x0B63 }, { 0x0B82, 0x0B82 },
151 { 0x0BBE, 0x0BC2 }, { 0x0BC6, 0x0BC8 }, { 0x0BCA, 0x0BCD },
152 { 0x0BD7, 0x0BD7 }, { 0x0C00, 0x0C04 }, { 0x0C3E, 0x0C44 },
153 { 0x0C46, 0x0C48 }, { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 },
154 { 0x0C62, 0x0C63 }, { 0x0C81, 0x0C83 }, { 0x0CBC, 0x0CBC },
155 { 0x0CBE, 0x0CC4 }, { 0x0CC6, 0x0CC8 }, { 0x0CCA, 0x0CCD },
156 { 0x0CD5, 0x0CD6 }, { 0x0CE2, 0x0CE3 }, { 0x0D00, 0x0D03 },
157 { 0x0D3B, 0x0D3C }, { 0x0D3E, 0x0D44 }, { 0x0D46, 0x0D48 },
158 { 0x0D4A, 0x0D4D }, { 0x0D57, 0x0D57 }, { 0x0D62, 0x0D63 },
159 { 0x0D82, 0x0D83 }, { 0x0DCF, 0x0DD4 }, { 0x0DD6, 0x0DD6 },
160 { 0x0DD8, 0x0DDF }, { 0x0DF2, 0x0DF3 }, { 0x0E31, 0x0E31 },
161 { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, { 0x0EB1, 0x0EB1 },
162 { 0x0EB4, 0x0EBC }, { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 },
163 { 0x0F35, 0x0F35 }, { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 },
164 { 0x0F71, 0x0F7E }, { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 },
165 { 0x0F8D, 0x0F97 }, { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 },
166 { 0x102D, 0x1030 }, { 0x1032, 0x1037 }, { 0x1039, 0x103A },
167 { 0x103D, 0x103E }, { 0x1058, 0x1059 }, { 0x105E, 0x1060 },
168 { 0x1071, 0x1074 }, { 0x1082, 0x1082 }, { 0x1085, 0x1086 },
169 { 0x108D, 0x108D }, { 0x109D, 0x109D }, { 0x135D, 0x135F },
170 { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 },
171 { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD },
172 { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD },
173 { 0x180B, 0x180E }, { 0x1885, 0x1886 }, { 0x18A9, 0x18A9 },
174 { 0x1920, 0x1922 }, { 0x1927, 0x1928 }, { 0x1932, 0x1932 },
175 { 0x1939, 0x193B }, { 0x1A17, 0x1A18 }, { 0x1A1B, 0x1A1B },
176 { 0x1A56, 0x1A56 }, { 0x1A58, 0x1A5E }, { 0x1A60, 0x1A60 },
177 { 0x1A62, 0x1A62 }, { 0x1A65, 0x1A6C }, { 0x1A73, 0x1A7C },
178 { 0x1A7F, 0x1A7F }, { 0x1AB0, 0x1ACE }, { 0x1B00, 0x1B03 },
179 { 0x1B34, 0x1B34 }, { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C },
180 { 0x1B42, 0x1B42 }, { 0x1B6B, 0x1B73 }, { 0x1B80, 0x1B82 },
181 { 0x1BA1, 0x1BA1 }, { 0x1BA6, 0x1BA7 }, { 0x1BAA, 0x1BAA },
182 { 0x1BAB, 0x1BAD }, { 0x1BE6, 0x1BE6 }, { 0x1BE8, 0x1BE9 },
183 { 0x1BED, 0x1BED }, { 0x1BEF, 0x1BF1 }, { 0x1C2C, 0x1C33 },
184 { 0x1C36, 0x1C37 }, { 0x1CD0, 0x1CD2 }, { 0x1CD4, 0x1CE8 },
185 { 0x1CED, 0x1CED }, { 0x1CF4, 0x1CF4 }, { 0x1CF8, 0x1CF9 },
186 { 0x1DC0, 0x1DF9 }, { 0x1DFB, 0x1DFF }, { 0x20D0, 0x20DC },
187 { 0x20E1, 0x20E1 }, { 0x20E5, 0x20F0 }, { 0x2CEF, 0x2CF1 },
188 { 0x2D7F, 0x2D7F }, { 0x2DE0, 0x2DFF }, { 0x302A, 0x302D },
189 { 0x3099, 0x309A }, { 0xA66F, 0xA672 }, { 0xA674, 0xA67D },
190 { 0xA69E, 0xA69F }, { 0xA6F0, 0xA6F1 }, { 0xA802, 0xA802 },
191 { 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, { 0xA825, 0xA826 },
192 { 0xA82C, 0xA82C }, { 0xA8C4, 0xA8C5 }, { 0xA8E0, 0xA8F1 },
193 { 0xA8FF, 0xA8FF }, { 0xA926, 0xA92D }, { 0xA947, 0xA951 },
194 { 0xA980, 0xA982 }, { 0xA9B3, 0xA9B3 }, { 0xA9B6, 0xA9B9 },
195 { 0xA9BC, 0xA9BD }, { 0xA9E5, 0xA9E5 }, { 0xAA29, 0xAA2E },
196 { 0xAA31, 0xAA32 }, { 0xAA35, 0xAA36 }, { 0xAA43, 0xAA43 },
197 { 0xAA4C, 0xAA4C }, { 0xAA7C, 0xAA7C }, { 0xAAB0, 0xAAB0 },
198 { 0xAAB2, 0xAAB4 }, { 0xAAB7, 0xAAB8 }, { 0xAABE, 0xAABF },
199 { 0xAAC1, 0xAAC1 }, { 0xAAEB, 0xAAEB }, { 0xAAEE, 0xAAEF },
200 { 0xAAF5, 0xAAF6 }, { 0xABE3, 0xABE4 }, { 0xABE6, 0xABE7 },
201 { 0xABE9, 0xABEA }, { 0xABEC, 0xABED }, { 0xFB1E, 0xFB1E },
202 { 0xFE00, 0xFE0F }, { 0xFE20, 0xFE2F }, { 0x101FD, 0x101FD },
203 { 0x102E0, 0x102E0 }, { 0x10376, 0x1037A }, { 0x10A01, 0x10A03 },
204 { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F }, { 0x10A38, 0x10A3A },
205 { 0x10A3F, 0x10A3F }, { 0x10AE5, 0x10AE6 }, { 0x10D24, 0x10D27 },
206 { 0x10EAB, 0x10EAC }, { 0x10F46, 0x10F50 }, { 0x10F82, 0x10F85 },
207 { 0x11000, 0x11002 }, { 0x11038, 0x11046 }, { 0x1107F, 0x11082 },
208 { 0x110B0, 0x110BA }, { 0x11100, 0x11102 }, { 0x11127, 0x11134 },
209 { 0x11145, 0x11146 }, { 0x11173, 0x11173 }, { 0x11180, 0x11182 },
210 { 0x111B3, 0x111C0 }, { 0x111C9, 0x111CC }, { 0x1122C, 0x11237 },
211 { 0x1123E, 0x1123E }, { 0x112DF, 0x112EA }, { 0x11300, 0x11303 },
212 { 0x1133B, 0x1133C }, { 0x1133E, 0x11344 }, { 0x11347, 0x11348 },
213 { 0x1134B, 0x1134D }, { 0x11357, 0x11357 }, { 0x11362, 0x11363 },
214 { 0x11435, 0x11446 }, { 0x1145E, 0x1145E }, { 0x114B0, 0x114C3 },
215 { 0x115AF, 0x115B5 }, { 0x115B8, 0x115C0 }, { 0x115DC, 0x115DD },
216 { 0x11630, 0x11640 }, { 0x116AB, 0x116B7 }, { 0x1171D, 0x1172B },
217 { 0x1182C, 0x1183A }, { 0x11930, 0x11935 }, { 0x11937, 0x11938 },
218 { 0x1193B, 0x1193E }, { 0x11940, 0x11940 }, { 0x11942, 0x11942 },
219 { 0x119D1, 0x119D7 }, { 0x119DA, 0x119E0 }, { 0x11A01, 0x11A0A },
220 { 0x11A33, 0x11A39 }, { 0x11A3B, 0x11A3E }, { 0x11A47, 0x11A47 },
221 { 0x11A51, 0x11A5B }, { 0x11A8A, 0x11A96 }, { 0x11A98, 0x11A99 },
222 { 0x11C30, 0x11C36 }, { 0x11C38, 0x11C3D }, { 0x11C3F, 0x11C3F },
223 { 0x11C92, 0x11CA7 }, { 0x11CAA, 0x11CB0 }, { 0x11CB2, 0x11CB3 },
224 { 0x11CB5, 0x11CB6 }, { 0x11D31, 0x11D36 }, { 0x11D3A, 0x11D3A },
225 { 0x11D3C, 0x11D3D }, { 0x11D3F, 0x11D45 }, { 0x11D47, 0x11D47 },
226 { 0x11D90, 0x11D91 }, { 0x11D95, 0x11D95 }, { 0x11D97, 0x11D97 },
227 { 0x11EF3, 0x11EF4 }, { 0x13430, 0x13438 }, { 0x16AF0, 0x16AF4 },
228 { 0x16B30, 0x16B36 }, { 0x16F4F, 0x16F4F }, { 0x16F8F, 0x16F92 },
229 { 0x1BC9D, 0x1BC9E }, { 0x1BCA0, 0x1BCA3 }, { 0x1D167, 0x1D169 },
230 { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD },
231 { 0x1D242, 0x1D244 }, { 0x1DA00, 0x1DA36 }, { 0x1DA3B, 0x1DA6C },
232 { 0x1DA75, 0x1DA75 }, { 0x1DA84, 0x1DA84 }, { 0x1DA9B, 0x1DA9F },
233 { 0x1DAA1, 0x1DAAF }, { 0x1E000, 0x1E006 }, { 0x1E008, 0x1E018 },
234 { 0x1E01B, 0x1E021 }, { 0x1E023, 0x1E024 }, { 0x1E026, 0x1E02A },
235 { 0x1E130, 0x1E136 }, { 0x1E2AE, 0x1E2AE }, { 0x1E2EC, 0x1E2EF },
236 { 0x1E4EC, 0x1E4EF }, { 0x1E8D0, 0x1E8D6 }, { 0x1E944, 0x1E94A },
237 { 0x1E947, 0x1E94A }, { 0xE0100, 0xE01EF }
238 };
239
240 /* test for 8-bit control characters */
241 if (ucs == 0)
242 return 0;
243 if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
244 return -1;
245
246 /* binary search in table of non-spacing characters */
247 if (bisearch(ucs, combining,
248 sizeof(combining) / sizeof(struct interval) - 1))
249 return 0;
250
251 /* if we arrive here, ucs is not a combining or C0/C1 control character */
252
253 return 1 +
254 (ucs >= 0x1100 &&
255 (ucs <= 0x115f || /* Hangul Jamo init. consonants */
256 ucs == 0x2329 || ucs == 0x232a ||
257 (ucs >= 0x2e80 && ucs <= 0xa4cf &&
258 ucs != 0x303f) || /* CJK ... Yi */
259 (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */
260 (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */
261 (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */
262 (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */
263 (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */
264 (ucs >= 0xffe0 && ucs <= 0xffe6) ||
265 (ucs >= 0x1f300 && ucs <= 0x1f64f) || /* modified: added Emoticons */
266 (ucs >= 0x1f680 && ucs <= 0x1f6ff) || /* modified: added Transport and Map Symbols */
267 (ucs >= 0x1f900 && ucs <= 0x1f9ff) || /* modified: added Supplemental Symbols and Pictographs */
268 (ucs >= 0x20000 && ucs <= 0x2fffd) ||
269 (ucs >= 0x30000 && ucs <= 0x3fffd)));
270}
271
272
273int mk_wcswidth(const mk_wchar_t *pwcs, size_t n) // modified: use mk_wchar_t
274{
275 int w, width = 0;
276
277 for (;*pwcs && n-- > 0; pwcs++)
278 if ((w = mk_wcwidth(*pwcs)) < 0)
279 return -1;
280 else
281 width += w;
282
283 return width;
284}
285
diff --git a/src/wcwidth.h b/src/wcwidth.h
new file mode 100644
index 0000000..f2fee11
--- /dev/null
+++ b/src/wcwidth.h
@@ -0,0 +1,21 @@
1// wcwidth.h
2
3// Windows does not have a wcwidth function, so we use compatibilty code from
4// http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c by Markus Kuhn
5
6#ifndef MK_WCWIDTH_H
7#define MK_WCWIDTH_H
8
9
10#ifdef _WIN32
11#include <stdint.h>
12typedef uint32_t mk_wchar_t; // Windows wchar_t can be 16-bit, we need 32-bit
13#else
14#include <wchar.h>
15typedef wchar_t mk_wchar_t; // Posix wchar_t is 32-bit so just use that
16#endif
17
18int mk_wcwidth(mk_wchar_t ucs);
19int mk_wcswidth(const mk_wchar_t *pwcs, size_t n);
20
21#endif // MK_WCWIDTH_H