aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRaymaekers Luca <raymaekers.luca@gmail.com>2024-11-18 00:13:44 +0100
committerRaymaekers Luca <raymaekers.luca@gmail.com>2024-11-18 20:50:27 +0100
commit0d635bc20467b3d789091f14affe8c499c74d2ea (patch)
tree74e06b8130d50c46b633ae70af22364646a20692
parent0ce18f9b70d907c8c50d45c4b5b279b8bd9275f1 (diff)
Added markdown support for messages
-rw-r--r--README.md1
-rw-r--r--chatty.c380
-rw-r--r--server.c8
-rw-r--r--ui.c291
4 files changed, 461 insertions, 219 deletions
diff --git a/README.md b/README.md
index a9c5dc4..1541f41 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,7 @@ The idea is the following:
- [ ] bug: wrapping does not work and displays nothing if there is no screen space
- [ ] bug: reconnect does not work when server does not know id
- [ ] markup for messages
+- [ ] convert tabs to spaces
## server
- [ ] check that fds arena does not overflow
diff --git a/chatty.c b/chatty.c
index a1747a0..c827448 100644
--- a/chatty.c
+++ b/chatty.c
@@ -3,6 +3,7 @@
#include "chatty.h"
#include "protocol.h"
+#include "ui.c"
#include <arpa/inet.h>
#include <assert.h>
@@ -29,11 +30,11 @@ enum { FDS_BI = 0, // for one-way communication with the server (eg. TextMessage
FDS_MAX };
typedef struct {
- u8 author[AUTHOR_LEN];
- ID id;
+ u8 Author[AUTHOR_LEN];
+ ID ID;
} User;
#define USER_FMT "[%s](%lu)"
-#define USER_ARG(client) client.author, client.id
+#define USER_ARG(client) client.Author, client.ID
// User used by chatty
global_variable User user = {0};
@@ -42,10 +43,10 @@ global_variable struct sockaddr_in address;
// fill str array with char
void
-fillstr(u32* str, u32 ch, u32 len)
+fillstr(u32* Str, u32 ch, u32 Len)
{
- for (u32 i = 0; i < len; i++)
- str[i] = ch;
+ for (u32 i = 0; i < Len; i++)
+ Str[i] = ch;
}
// Centered popup displaying message in the appropriate cololrs
@@ -61,15 +62,15 @@ popup(u32 fg, u32 bg, char* text)
// Returns user if the id was the user's ID
// Returns 0 if nothing was found
User*
-getUserByID(Arena* clientsArena, ID id)
+get_user_by_id(Arena* clientsArena, ID id)
{
// User is not in the clientsArena
- if (id == user.id) return &user;
+ if (id == user.ID) return &user;
User* clients = clientsArena->addr;
for (u64 i = 0; i < (clientsArena->pos / sizeof(*clients)); i++)
{
- if (clients[i].id == id)
+ if (clients[i].ID == id)
return clients + i;
}
return 0;
@@ -78,11 +79,11 @@ getUserByID(Arena* clientsArena, ID id)
// Request information of client from fd byd id and add it to clientsArena
// Returns pointer to added client
User*
-addUserInfo(Arena* clientsArena, s32 fd, u64 id)
+add_user_info(Arena* clientsArena, s32 fd, u64 id)
{
// Request information about ID
HeaderMessage header = HEADER_INIT(HEADER_TYPE_ID);
- header.id = user.id;
+ header.id = user.ID;
IDMessage message = {id};
s32 nsend = sendAnyMessage(fd, header, &message);
assert(nsend != -1);
@@ -93,8 +94,8 @@ addUserInfo(Arena* clientsArena, s32 fd, u64 id)
// Add the information
User* client = ArenaPush(clientsArena, sizeof(*client));
- memcpy(client->author, introduction_message.author, AUTHOR_LEN);
- client->id = id;
+ memcpy(client->Author, introduction_message.author, AUTHOR_LEN);
+ client->ID = id;
loggingf("Got " USER_FMT "\n", USER_ARG((*client)));
return client;
@@ -102,7 +103,7 @@ addUserInfo(Arena* clientsArena, s32 fd, u64 id)
// Tries to connect to address and populates resulting file descriptors in ConnectionResult.
s32
-getConnection(struct sockaddr_in* address)
+get_connection(struct sockaddr_in* address)
{
s32 fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1) return -1;
@@ -120,10 +121,10 @@ u32
authenticate(User* user, s32 fd)
{
/* Scenario 1: Already have an ID */
- if (user->id)
+ if (user->ID)
{
HeaderMessage header = HEADER_INIT(HEADER_TYPE_ID);
- IDMessage message = {user->id};
+ IDMessage message = {user->ID};
s32 nsend = sendAnyMessage(fd, header, &message);
assert(nsend != -1);
@@ -144,14 +145,14 @@ authenticate(User* user, s32 fd)
{
HeaderMessage header = HEADER_INIT(HEADER_TYPE_INTRODUCTION);
IntroductionMessage message;
- memcpy(message.author, user->author, AUTHOR_LEN);
+ memcpy(message.author, user->Author, AUTHOR_LEN);
s32 nsend = sendAnyMessage(fd, header, &message);
assert(nsend != -1);
IDMessage id_message;
s32 nrecv = recvAnyMessageType(fd, &header, &id_message, HEADER_TYPE_ID);
assert(nrecv != -1);
- user->id = id_message.id;
+ user->ID = id_message.id;
return 1;
}
}
@@ -164,7 +165,7 @@ authenticate(User* user, s32 fd)
// Returns 0.
#define Miliseconds(s) (s*1000*1000)
void*
-threadReconnect(void* fds_ptr)
+thread_reconnect(void* fds_ptr)
{
s32 unifd, bifd;
struct pollfd* fds = fds_ptr;
@@ -175,13 +176,13 @@ threadReconnect(void* fds_ptr)
// timeout
nanosleep(&t, &t);
- bifd = getConnection(&address);
+ bifd = get_connection(&address);
if (bifd == -1)
{
loggingf("errno: %d\n", errno);
continue;
}
- unifd = getConnection(&address);
+ unifd = get_connection(&address);
if (unifd == -1)
{
loggingf("errno: %d\n", errno);
@@ -212,115 +213,19 @@ threadReconnect(void* fds_ptr)
return 0;
}
-// Print `text` wrapped to limit_x. It will print no more than limit_y lines. x, y, fg and
-// bg will be passed to the tb_printf() function calls.
-// pfx is a string that will be printed first and will not be wrapped on characters like msg->text,
-// this is useful when for example: printing messages and wanting to have consistent
-// timestamp+author name.
-// Returns the number of lines printed.
-// TODO: (bug) text after pfx is wrapped one too soon
-// TODO: text == 0 to know how many lines *would* be printed
-// - no this should be a separate function
-// TODO: check if text[i] goes out of bounds
-u32
-tb_printf_wrap(u32 x, u32 y, u32 fg, u32 bg, u32* text, s32 text_len, u32 fg_pfx, u32 bg_pfx, u8* pfx, s32 limit_x, u32 limit_y)
-{
- assert(limit_x > 0);
-
- // lines y, incremented after each wrap
- s32 ly = y;
- // character the text is split on
- u32 t = 0;
- // index used for searching in string
- s32 i = limit_x;
- // previous i for windowing through the text
- s32 offset = 0;
- // used when retrying to get a longer limit
- u32 failed = 0;
-
- // NOTE: We can assume that we need to wrap, therefore print a newline after the prefix string
- if (pfx != 0)
- {
- tb_printf(x, ly, fg_pfx, bg_pfx, "%s", pfx);
-
- // If the text fits on one line print the text and return
- // Otherwise print the text on the next line
- s32 pfx_len = strlen((char*)pfx);
- if (limit_x > pfx_len + text_len)
- {
- tb_printf(x + pfx_len, y, fg, bg, "%ls", text);
- return 1;
- }
- else
- {
- ly++;
- }
- }
-
- /// Algorithm
- // 1. Start at limit
- // 2. Look backwards for whitespace
- // 3. Whitespace found?
- // n) failed++
- // i = limit + limit*failed
- // step 2.
- // y) step 4.
- // 4. failed = 0
- // 5. terminate text at i found
- // 6. print text
- // 7. restore text[i]
- // 8. step 2. until i >= text_len
- // 9. print remaining part of the string
-
- while (i < text_len && ly - y < limit_y)
- {
- // search backwards for whitespace
- while (i > offset && text[i] != L' ')
- i--;
-
- // retry with bigger limit
- if (i == offset)
- {
- offset = i;
- failed++;
- i += limit_x + failed * limit_x;
- continue;
- }
- else
- {
- failed = 0;
- }
-
- t = text[i];
- text[i] = 0;
- tb_printf(x, ly, fg, bg, "%ls", text + offset);
- text[i] = t;
-
- i++; // after the space
- ly++;
-
- offset = i;
- i += limit_x;
- }
- if ((u32)ly <= limit_y)
- {
- tb_printf(x, ly, fg, bg, "%ls", text + offset);
- ly++;
- }
-
- return ly - y;
-}
-
// home screen, the first screen the user sees
// it displays a prompt with the user input of input_len wide characters
// and the received messages from msgsArena
void
-screen_home(Arena* msgsArena, u32 nmessages, Arena* clientsArena, struct pollfd* fds, u32 input[], u32 input_len)
+screen_home(Arena* ScratchArena,
+ Arena* MessagesArena, u32 MessagesNum,
+ Arena* ClientsArena, struct pollfd* fds,
+ u32 Input[], u32 InputLen)
{
// config options
const s32 box_max_len = 80;
const s32 box_x = 0, box_y = global.height - 3, box_pad_x = 1, box_mar_x = 1, box_bwith = 1, box_height = 3;
- const u32 prompt_x = box_x + box_pad_x + box_mar_x + box_bwith + input_len;
+ const u32 prompt_x = box_x + box_pad_x + box_mar_x + box_bwith + InputLen;
// the minimum height required is the hight for the box prompt
// the minimum width required is that one character should fit in the box prompt
@@ -342,37 +247,40 @@ screen_home(Arena* msgsArena, u32 nmessages, Arena* clientsArena, struct pollfd*
// Looks like this:
// 03:24:29 [1234567890ab] hello homes how are
// you doing?
- // 03:24:33 [TlasT] I am fine
+ // 03:24:33 [TlasT] │ I am fine
+ // 03:24:33 [Fin] │ I am too
{
- u32 free_y = global.height - box_height;
- if (free_y <= 0)
+ u32 VerticalBarOffset = TIMESTAMP_LEN + AUTHOR_LEN + 2;
+
+ u32 FreeHeight = global.height - box_height;
+ if (FreeHeight <= 0)
goto draw_prompt;
- u8* addr = msgsArena->addr;
- assert(addr != 0);
- // on what line to print the current message, used for scrolling
- u32 msg_y = 0;
+ // Used to go to the next message in MessagesArena by incrementing with the messages' size.
+ u8* MessageAddress = MessagesArena->addr;
+ assert(MessageAddress != 0);
- u32 offs = (nmessages > free_y) ? nmessages - free_y : 0;
- // skip offs ccount messages
- for (u32 i = 0; i < offs; i++)
+ // Skip messages if there is not enough space to display them all
+ u32 MessagesOffset = (MessagesNum > FreeHeight) ? MessagesNum - FreeHeight : 0;
+ for (u32 MessageIndex = 0; MessageIndex < MessagesOffset; MessageIndex++)
{
- HeaderMessage* header = (HeaderMessage*)addr;
- addr += sizeof(*header);
+ HeaderMessage* header = (HeaderMessage*)MessageAddress;
+ MessageAddress += sizeof(*header);
+
switch (header->type)
{
case HEADER_TYPE_TEXT:
{
- TextMessage* message = (TextMessage*)addr;
- addr += TEXTMESSAGE_SIZE;
- addr += message->len * sizeof(*message->text);
+ TextMessage* message = (TextMessage*)MessageAddress;
+ MessageAddress += TEXTMESSAGE_SIZE;
+ MessageAddress += message->len * sizeof(*message->text);
break;
}
case HEADER_TYPE_PRESENCE:
- addr += sizeof(PresenceMessage);
+ MessageAddress += sizeof(PresenceMessage);
break;
case HEADER_TYPE_HISTORY:
- addr += sizeof(HistoryMessage);
+ MessageAddress += sizeof(HistoryMessage);
break;
default:
// unhandled message type
@@ -380,37 +288,35 @@ screen_home(Arena* msgsArena, u32 nmessages, Arena* clientsArena, struct pollfd*
}
}
- // In each case statement advance the addr pointer by the size of the message
- for (u32 i = offs; i < nmessages && msg_y < free_y; i++)
+ u32 MessageY = 0;
+
+ for (u32 i = MessagesOffset;
+ i < MessagesNum;
+ i++)
{
- HeaderMessage* header = (HeaderMessage*)addr;
- addr += sizeof(*header);
+ if (MessageY >= FreeHeight) break;
- // Get User for message
- User* client;
- switch (header->type)
+ HeaderMessage* header = (HeaderMessage*)MessageAddress;
+ MessageAddress += sizeof(*header);
+
+ User* client = get_user_by_id(ClientsArena, header->id);
+ if (!client)
{
- case HEADER_TYPE_TEXT:
- case HEADER_TYPE_PRESENCE:
- client = getUserByID(clientsArena, header->id);
- if (!client)
- {
- loggingf("User not known, requesting from server\n");
- client = addUserInfo(clientsArena, fds[FDS_BI].fd, header->id);
- }
- assert(client);
- break;
+ loggingf("User not known, requesting from server\n");
+ client = add_user_info(ClientsArena, fds[FDS_BI].fd, header->id);
}
+ assert(client);
switch (header->type)
{
case HEADER_TYPE_TEXT:
{
- TextMessage* message = (TextMessage*)addr;
+ TextMessage* message = (TextMessage*)MessageAddress;
+
// Color own messages
u32 fg = 0;
- if (user.id == header->id)
+ if (user.ID == header->id)
{
fg = TB_CYAN;
}
@@ -420,35 +326,73 @@ screen_home(Arena* msgsArena, u32 nmessages, Arena* clientsArena, struct pollfd*
}
// prefix is of format "HH:MM:SS [<author>] ", create it
- u8 pfx[AUTHOR_LEN - 1 + TIMESTAMP_LEN - 1 + 4 + 1] = {0};
u8 timestamp[TIMESTAMP_LEN];
formatTimestamp(timestamp, message->timestamp);
- sprintf((char*)pfx, "%s [%s] ", timestamp, client->author);
- msg_y += tb_printf_wrap(0, msg_y, TB_WHITE, 0, (u32*)&message->text, message->len, fg, 0, pfx, global.width, free_y - msg_y);
+ tb_printf(0, MessageY, TB_WHITE, 0, "%s", timestamp);
+ tb_printf(TIMESTAMP_LEN, MessageY, fg, 0, "[%s]", client->Author);
+
+ // Only display when there is enough space
+ if (global.width > VerticalBarOffset + 2)
+ {
+ raw_result RawText = markdown_to_raw(ScratchArena, (u32*)&message->text, message->len);
+ markdown_formatoptions MDFormat = preprocess_markdown(ScratchArena,
+ (u32*)&message->text,
+ message->len);
+
+ u32 timesWrapped = tb_print_wrapped_with_markdown(VerticalBarOffset + 2, MessageY, fg, 0,
+ RawText.Text, RawText.Len,
+ global.width, global.height, MDFormat);
+
+ // Free the memory
+ ScratchArena->pos = 0;
+
+ MessageY += timesWrapped;
+ }
+ else
+ {
+ // We still displayed the timestamp so we need to increment the Y.
+ MessageY++;
+ }
u32 message_size = TEXTMESSAGE_SIZE + message->len * sizeof(*message->text);
- addr += message_size;
+ MessageAddress += message_size;
} break;
case HEADER_TYPE_PRESENCE:
{
- PresenceMessage* message = (PresenceMessage*)addr;
- tb_printf(0, msg_y, 0, 0, " [%s] *%s*", client->author, presenceTypeString(message->type));
- msg_y++;
- addr += sizeof(*message);
+ PresenceMessage* message = (PresenceMessage*)MessageAddress;
+ tb_printf(TIMESTAMP_LEN, MessageY, TB_MAGENTA, 0, "[%s]", client->Author);
+
+ // Wrap Text in '*'
+ u8 *Text = presenceTypeString(message->type);
+ u32 Len = 0;
+ while(Text[Len]) Len++;
+ u32 FormattedText[Len+2];
+ FormattedText[0] = '*';
+ FormattedText[Len+1] = '*';
+ for (u32 i = 1; i < Len + 1; i++) FormattedText[i] = Text[i-1];
+
+ tb_print_markdown(VerticalBarOffset + 2, MessageY, 0, 0, FormattedText, Len + 2);
+
+ MessageY++;
+ MessageAddress += sizeof(*message);
} break;
case HEADER_TYPE_HISTORY:
{
- HistoryMessage* message = (HistoryMessage*)addr;
- addr += sizeof(*message);
+ HistoryMessage* message = (HistoryMessage*)MessageAddress;
+ MessageAddress += sizeof(*message);
// TODO: implement
} break;
default:
- tb_printf(0, msg_y, 0, 0, "%s", headerTypeString(header->type));
- msg_y++;
+ tb_printf(0, MessageY, 0, 0, "%s", headerTypeString(header->type));
+ MessageY++;
break;
}
}
+
+ // Print vertical bar
+ for (u32 Y = 0; Y < FreeHeight; Y++)
+ tb_print(VerticalBarOffset, Y, 0, 0, "│");
draw_prompt:
// Draw prompt box which is a box made out of
@@ -500,16 +444,16 @@ screen_home(Arena* msgsArena, u32 nmessages, Arena* clientsArena, struct pollfd*
if (freesp <= 0)
return;
- if (input_len > freesp)
+ if (InputLen > freesp)
{
- u32* text_offs = input + (input_len - freesp);
+ u32* text_offs = Input + (InputLen - freesp);
tb_printf(box_x + box_mar_x + box_pad_x + box_bwith, box_y + 1, 0, 0, "%ls", text_offs);
global.cursor_x = box_x + box_pad_x + box_mar_x + box_bwith + freesp;
}
else
{
global.cursor_x = prompt_x;
- tb_printf(box_x + box_mar_x + box_pad_x + box_bwith, box_y + 1, 0, 0, "%ls", input);
+ tb_printf(box_x + box_mar_x + box_pad_x + box_bwith, box_y + 1, 0, 0, "%ls", Input);
}
}
@@ -532,21 +476,23 @@ main(int argc, char** argv)
u32 arg_len = strlen(argv[1]);
assert(arg_len <= AUTHOR_LEN - 1);
- memcpy(user.author, argv[1], arg_len);
- user.author[arg_len] = '\0';
+ memcpy(user.Author, argv[1], arg_len);
+ user.Author[arg_len] = '\0';
s32 err = 0; // error code for functions
- u32 nmessages = 0; // Number of messages in msgsArena
+ u32 MessagesNum = 0; // Number of messages in msgsArena
s32 nrecv = 0; // number of bytes received
- u32 input[INPUT_LIMIT] = {0}; // input buffer
- u32 ninput = 0; // number of characters in input
+ u32 Input[INPUT_LIMIT] = {0}; // input buffer
+ u32 InputLen = 0; // number of characters in input
- Arena msgsArena;
- Arena clientsArena;
- ArenaAlloc(&msgsArena, Megabytes(64)); // Messages received & sent
- ArenaAlloc(&clientsArena, Megabytes(1)); // Arena for storing clients
+ Arena ScratchArena;
+ Arena MessagesArena;
+ Arena ClientsArena;
+ ArenaAlloc(&MessagesArena, Megabytes(64)); // Messages received & sent
+ ArenaAlloc(&ClientsArena, Megabytes(1)); // Arena for storing clients
+ ArenaAlloc(&ScratchArena, Megabytes(1)); // Arena for storing clients
struct tb_event ev; // event fork keypress & resize
u8 quit = 0; // boolean to indicate if we want to quit the main loop
@@ -585,13 +531,13 @@ main(int argc, char** argv)
/* Authentication */
{
s32 unifd, bifd;
- bifd = getConnection(&address);
+ bifd = get_connection(&address);
if (bifd == -1)
{
loggingf("errno: %d\n", errno);
return 1;
}
- unifd = getConnection(&address);
+ unifd = get_connection(&address);
if (unifd == -1)
{
loggingf("errno: %d\n", errno);
@@ -617,7 +563,7 @@ main(int argc, char** argv)
write(idfile, &user.id, sizeof(user.id));
#endif
- loggingf("Got ID: %lu\n", user.id);
+ loggingf("Got ID: %lu\n", user.ID);
// for wide character printing
assert(setlocale(LC_ALL, "") != 0);
@@ -626,7 +572,7 @@ main(int argc, char** argv)
tb_init();
tb_get_fds(&fds[FDS_TTY].fd, &fds[FDS_RESIZE].fd);
- screen_home(&msgsArena, nmessages, &clientsArena, fds, input, ninput);
+ screen_home(&ScratchArena, &MessagesArena, MessagesNum, &ClientsArena, fds, Input, InputLen);
tb_present();
// main loop
@@ -653,7 +599,7 @@ main(int argc, char** argv)
assert(err == 0);
fds[FDS_UNI].fd = -1; // ignore
// start trying to reconnect in a thread
- err = pthread_create(&thr_rec, 0, &threadReconnect, (void*)fds);
+ err = pthread_create(&thr_rec, 0, &thread_reconnect, (void*)fds);
assert(err == 0);
}
else
@@ -664,22 +610,22 @@ main(int argc, char** argv)
continue;
}
- void* addr = ArenaPush(&msgsArena, sizeof(header));
+ void* addr = ArenaPush(&MessagesArena, sizeof(header));
memcpy(addr, &header, sizeof(header));
// Messages handled from server
switch (header.type)
{
case HEADER_TYPE_TEXT:
- recvTextMessage(&msgsArena, fds[FDS_UNI].fd);
- nmessages++;
+ recvTextMessage(&MessagesArena, fds[FDS_UNI].fd);
+ MessagesNum++;
break;
case HEADER_TYPE_PRESENCE:;
- PresenceMessage* message = ArenaPush(&msgsArena, sizeof(*message));
+ PresenceMessage* message = ArenaPush(&MessagesArena, sizeof(*message));
nrecv = recv(fds[FDS_UNI].fd, message, sizeof(*message), 0);
assert(nrecv != -1);
assert(nrecv == sizeof(*message));
- nmessages++;
+ MessagesNum++;
break;
default:
loggingf("Got unhandled message: %s\n", headerTypeString(header.type));
@@ -697,24 +643,24 @@ main(int argc, char** argv)
{
case TB_KEY_CTRL_W:
// delete consecutive whitespace
- while (ninput)
+ while (InputLen)
{
- if (input[ninput - 1] == L' ')
+ if (Input[InputLen - 1] == L' ')
{
- input[ninput - 1] = 0;
- ninput--;
+ Input[InputLen - 1] = 0;
+ InputLen--;
continue;
}
break;
}
// delete until whitespace
- while (ninput)
+ while (InputLen)
{
- if (input[ninput - 1] == L' ')
+ if (Input[InputLen - 1] == L' ')
break;
// erase
- input[ninput - 1] = 0;
- ninput--;
+ Input[InputLen - 1] = 0;
+ InputLen--;
}
break;
case TB_KEY_CTRL_Z:
@@ -730,7 +676,7 @@ main(int argc, char** argv)
quit = 1;
break;
case TB_KEY_CTRL_M: // send message
- if (ninput == 0)
+ if (InputLen == 0)
// do not send empty message
break;
if (fds[FDS_UNI].fd == -1)
@@ -738,43 +684,43 @@ main(int argc, char** argv)
break;
// null terminate
- input[ninput] = 0;
- ninput++;
+ Input[InputLen] = 0;
+ InputLen++;
// Save header
- HeaderMessage* header = ArenaPush(&msgsArena, sizeof(*header));
+ HeaderMessage* header = ArenaPush(&MessagesArena, sizeof(*header));
header->version = PROTOCOL_VERSION;
header->type = HEADER_TYPE_TEXT;
- header->id = user.id;
+ header->id = user.ID;
// Save message
- TextMessage* sendmsg = ArenaPush(&msgsArena, TEXTMESSAGE_SIZE);
+ TextMessage* sendmsg = ArenaPush(&MessagesArena, TEXTMESSAGE_SIZE);
sendmsg->timestamp = time(0);
- sendmsg->len = ninput;
+ sendmsg->len = InputLen;
- u32 text_size = ninput * sizeof(*input);
- ArenaPush(&msgsArena, text_size);
- memcpy(&sendmsg->text, input, text_size);
+ u32 text_size = InputLen * sizeof(*Input);
+ ArenaPush(&MessagesArena, text_size);
+ memcpy(&sendmsg->text, Input, text_size);
sendAnyMessage(fds[FDS_UNI].fd, *header, sendmsg);
- nmessages++;
+ MessagesNum++;
// also clear input
case TB_KEY_CTRL_U: // clear input
- bzero(input, ninput * sizeof(*input));
- ninput = 0;
+ bzero(Input, InputLen * sizeof(*Input));
+ InputLen = 0;
break;
default:
if (ev.ch == 0)
break;
// TODO: show error
- if (ninput == INPUT_LIMIT - 1) // last byte reserved for \0
+ if (InputLen == INPUT_LIMIT - 1) // last byte reserved for \0
break;
// append key to input buffer
- input[ninput] = ev.ch;
- ninput++;
+ Input[InputLen] = ev.ch;
+ InputLen++;
}
if (quit)
break;
@@ -787,7 +733,7 @@ main(int argc, char** argv)
tb_poll_event(&ev);
}
- screen_home(&msgsArena, nmessages, &clientsArena, fds, input, ninput);
+ screen_home(&ScratchArena, &MessagesArena, MessagesNum, &ClientsArena, fds, Input, InputLen);
tb_present();
}
diff --git a/server.c b/server.c
index 4bc1ea8..bb94b36 100644
--- a/server.c
+++ b/server.c
@@ -2,6 +2,7 @@
#include "protocol.h"
#include <assert.h>
+#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <poll.h>
@@ -15,7 +16,7 @@
// timeout on polling
#define TIMEOUT 60 * 1000
// max pending connections
-#define MAX_CONNECTIONS 16
+#define MAX_CONNECTIONS 1600
// Get number of connections from arena position
// NOTE: this is somewhat wrong, because of when disconnections happen
#define FDS_SIZE (fdsArena.pos / sizeof(struct pollfd))
@@ -442,7 +443,10 @@ main(int argc, char** argv)
// We received a message, try to parse the header
HeaderMessage header;
s32 nrecv = recv(fds[conn].fd, &header, sizeof(header), 0);
- assert(nrecv != -1);
+ if(nrecv == -1)
+ {
+ loggingf("Received error from fd: %d, errno: %d\n", fds[conn].fd, errno);
+ };
Client* client;
if (nrecv != sizeof(header))
diff --git a/ui.c b/ui.c
new file mode 100644
index 0000000..9148810
--- /dev/null
+++ b/ui.c
@@ -0,0 +1,291 @@
+#define DEBUG
+
+// Format option at a position in raw text, used when iterating to know when to toggle a color
+// option.
+typedef struct {
+ u32 Position;
+ u32 Color;
+} format_option;
+
+// Array of format options and length of said array
+typedef struct {
+ format_option* Options;
+ u32 Len;
+} markdown_formatoptions;
+
+typedef struct {
+ u32* Text;
+ u32 Len;
+} raw_result;
+
+// Return True if ch is whitespace
+Bool
+is_whitespace(u32 ch)
+{
+ if (ch == L' ')
+ return True;
+ return False;
+}
+
+// Return True if ch is a supported markdown markup character
+// TODO: tilde
+Bool
+is_markdown(u32 ch)
+{
+ if (ch == L'_' ||
+ ch == L'*')
+ return True;
+ return False;
+}
+
+// Print `Text`, `Len` characters long with markdown
+// NOTE: This function has no wrapping support
+void
+tb_print_markdown(u32 X, u32 Y, u32 fg, u32 bg, u32* Text, u32 Len)
+{
+ for (u32 ch = 0; ch < Len; ch++)
+ {
+ if (Text[ch] == L'_')
+ {
+ if (ch < Len - 1 && Text[ch + 1] == L'_')
+ {
+ fg ^= TB_UNDERLINE;
+ ch++;
+ }
+ else
+ {
+ fg ^= TB_ITALIC;
+ }
+ }
+ else if (Text[ch] == L'*')
+ {
+ if (ch < Len - 1 && Text[ch + 1] == L'*')
+ {
+ fg ^= TB_BOLD;
+ ch++;
+ }
+ else
+ {
+ fg ^= TB_ITALIC;
+ }
+ }
+ else
+ {
+ tb_printf(X, Y, fg, bg, "%lc", Text[ch]);
+#ifdef DEBUG
+ tb_present();
+#endif
+ X++;
+ }
+ }
+}
+
+// Print `Text`, `Len` characters long as a string wrapped at `XLimit` width and `YLimit` height.
+void
+tb_print_wrapped(u32 X, u32 Y, u32 XLimit, u32 YLimit, u32* Text, u32 Len)
+{
+ // Iterator in text
+ assert(XLimit > 0);
+ assert(YLimit > 0);
+ u32 i = XLimit;
+
+ u32 PrevI = 0;
+
+ // For printing
+ u32 t = 0;
+
+ while(i < Len)
+ {
+ // Search backwards for whitespace
+ while (!is_whitespace(Text[i]))
+ {
+ i--;
+
+ // Failed to find whitespace, break on limit at character
+ if (i == PrevI)
+ {
+ i += XLimit;
+ break;
+ }
+ }
+
+ t = Text[i];
+ Text[i] = 0;
+ tb_printf(X, Y++, 0, 0, "%ls", Text + PrevI);
+#ifdef DEBUG
+ tb_present();
+#endif
+
+ Text[i] = t;
+
+ if (is_whitespace(Text[i])) i++;
+
+ PrevI = i;
+ i += XLimit;
+
+ if (Y >= YLimit - 1)
+ {
+ break;
+ }
+ }
+ tb_printf(X, Y++, 0, 0, "%ls", Text + PrevI);
+}
+
+// Print raw string with markdown format options in `MDFormat`, wrapped at
+// `XLimit` and `YLimit`. The string is offset by `XOffset` and `YOffset`.
+// `fg` and `bg` are passed to `tb_printf`.
+// `Len` is the length of the string not including a null terminator
+// The wrapping algorithm searches for a whitespace backwards and if none are found it wraps at
+// `XLimit`.
+// This function first builds an array of positions where to wrap and then prints `Text` by
+// character using the array in `MDFormat.Options` and `WrapPositions` to know when to act.
+// Returns how many times wrapped
+u32
+tb_print_wrapped_with_markdown(u32 XOffset, u32 YOffset, u32 fg, u32 bg,
+ u32* Text, u32 Len,
+ u32 XLimit, u32 YLimit,
+ markdown_formatoptions MDFormat)
+{
+ XLimit -= XOffset;
+ YLimit -= YOffset;
+ assert(YLimit > 0);
+ assert(XLimit > 0);
+
+ u32 TextIndex = XLimit;
+ u32 PrevTextIndex = 0;
+
+ u32 WrapPositions[Len/XLimit + 1];
+ u32 WrapPositionsLen = 0;
+
+ // Get wrap positions
+ while (TextIndex < Len)
+ {
+ while (!is_whitespace(Text[TextIndex]))
+ {
+ TextIndex--;
+
+ if (TextIndex == PrevTextIndex)
+ {
+ TextIndex += XLimit;
+ break;
+ }
+ }
+
+ WrapPositions[WrapPositionsLen] = TextIndex;
+ WrapPositionsLen++;
+
+ PrevTextIndex = TextIndex;
+ TextIndex += XLimit;
+ }
+
+ u32 MDFormatOptionsIndex = 0;
+ u32 WrapPositionsIndex = 0;
+ u32 X = XOffset, Y = YOffset;
+
+ for (u32 TextIndex = 0; TextIndex < Len; TextIndex++)
+ {
+ if (MDFormat.Len &&
+ TextIndex == MDFormat.Options[MDFormatOptionsIndex].Position)
+ {
+ fg ^= MDFormat.Options[MDFormatOptionsIndex].Color;
+ MDFormatOptionsIndex++;
+ }
+ if (WrapPositionsLen &&
+ TextIndex == WrapPositions[WrapPositionsIndex])
+ {
+ Y++;
+ if (Y == YLimit) return WrapPositionsIndex + 1;
+ WrapPositionsIndex++;
+ X = XOffset;
+ if (is_whitespace(Text[TextIndex])) continue;
+ }
+ tb_printf(X++, Y, fg, bg, "%lc", Text[TextIndex]);
+ }
+ assert(WrapPositionsIndex == WrapPositionsLen);
+ assert(MDFormat.Len == MDFormatOptionsIndex);
+
+ return WrapPositionsLen + 1;
+}
+
+// Return string without markdown markup characters using `is_markdown()`
+// ScratchArena is used to allocate space for the raw text
+// Len should be characters + null terminator
+// Copies the null terminator as well
+raw_result
+markdown_to_raw(Arena* ScratchArena, u32* Text, u32 Len)
+{
+ raw_result Result = {0};
+ Result.Text = ScratchArena->addr;
+
+ for (u32 i = 0; i < Len; i++)
+ {
+ if (!is_markdown(Text[i]))
+ {
+ u32* ch = ArenaPush(ScratchArena, sizeof(*ch));
+ *ch = Text[i];
+ Result.Len++;
+ }
+ }
+
+ return Result;
+}
+
+// Get a string with markdown in it and fill array in makrdown_formtoptions with position and colors
+// Use Scratcharena to make allocations on that buffer, The Maximimum space needed is Len, eg. when
+// the string is only markup characters.
+markdown_formatoptions
+preprocess_markdown(Arena* ScratchArena, u32* Text, u32 Len)
+{
+ markdown_formatoptions Result = {0};
+ Result.Options = (format_option*)((u8*)ScratchArena->addr + ScratchArena->pos);
+
+ format_option* FormatOpt;
+
+ // raw char iterator
+ u32 rawch = 0;
+
+ for (u32 i = 0; i < Len; i++)
+ {
+ switch (Text[i])
+ {
+ case L'_':
+ {
+ FormatOpt = ArenaPush(ScratchArena, sizeof(*FormatOpt));
+ Result.Len++;
+
+ FormatOpt->Position = rawch;
+ if (i < Len - 1 && Text[i + 1] == '_')
+ {
+ FormatOpt->Color = TB_UNDERLINE;
+ i++;
+ }
+ else
+ {
+ FormatOpt->Color = TB_ITALIC;
+ }
+ } break;
+ case L'*':
+ {
+ FormatOpt = ArenaPush(ScratchArena, sizeof(*FormatOpt));
+ Result.Len++;
+
+ FormatOpt->Position = rawch;
+ if (i < Len - 1 && Text[i + 1] == '*')
+ {
+ FormatOpt->Color = TB_BOLD;
+ i++;
+ }
+ else
+ {
+ FormatOpt->Color = TB_ITALIC;
+ }
+ } break;
+ default:
+ {
+ rawch++;
+ } break;
+ }
+ }
+
+ return Result;
+}