Factor buffer out of ui

In preparation for doing line wrapping outside of ncurses.
master
C. McEnroe 2020-09-01 20:35:17 -04:00
parent a98fd70aac
commit ded2b6afb6
5 changed files with 150 additions and 56 deletions

View File

@ -7,6 +7,7 @@ LDLIBS = -lncursesw -ltls
-include config.mk -include config.mk
OBJS += buffer.o
OBJS += chat.o OBJS += chat.o
OBJS += command.o OBJS += command.o
OBJS += complete.o OBJS += complete.o

View File

@ -1,4 +1,4 @@
.Dd August 4, 2020 .Dd September 1, 2020
.Dt README 7 .Dt README 7
.Os "Causal Agency" .Os "Causal Agency"
.\" To view this file, run: man ./README.7 .\" To view this file, run: man ./README.7
@ -154,6 +154,8 @@ curses interface
IRC message handling IRC message handling
.It Pa command.c .It Pa command.c
input command handling input command handling
.It Pa buffer.c
line wrapping
.It Pa edit.c .It Pa edit.c
line editing line editing
.It Pa complete.c .It Pa complete.c

103
buffer.c 100644
View File

@ -0,0 +1,103 @@
/* Copyright (C) 2020 C. McEnroe <june@causal.agency>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Additional permission under GNU GPL version 3 section 7:
*
* If you modify this Program, or any covered work, by linking or
* combining it with OpenSSL (or a modified version of that library),
* containing parts covered by the terms of the OpenSSL License and the
* original SSLeay license, the licensors of this Program grant you
* additional permission to convey the resulting work. Corresponding
* Source for a non-source form of such a combination shall include the
* source code for the parts of OpenSSL used as well as that of the
* covered work.
*/
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <sysexits.h>
#include <time.h>
#include "chat.h"
struct Lines {
size_t len;
struct Line lines[BufferCap];
};
_Static_assert(!(BufferCap & (BufferCap - 1)), "BufferCap is power of two");
struct Buffer {
struct Lines soft;
struct Lines hard;
};
struct Buffer *bufferAlloc(void) {
struct Buffer *buffer = calloc(1, sizeof(*buffer));
if (!buffer) err(EX_OSERR, "calloc");
return buffer;
}
void bufferFree(struct Buffer *buffer) {
for (size_t i = 0; i < BufferCap; ++i) {
free(buffer->soft.lines[i].str);
free(buffer->hard.lines[i].str);
}
free(buffer);
}
static const struct Line *linesLine(const struct Lines *lines, size_t i) {
const struct Line *line = &lines->lines[(lines->len + i) % BufferCap];
return (line->str ? line : NULL);
}
const struct Line *bufferSoft(const struct Buffer *buffer, size_t i) {
return linesLine(&buffer->soft, i);
}
const struct Line *bufferHard(const struct Buffer *buffer, size_t i) {
return linesLine(&buffer->hard, i);
}
static void flow(struct Lines *hard, int cols, const struct Line *soft) {
(void)hard;
(void)cols;
(void)soft;
}
void bufferPush(
struct Buffer *buffer, int cols,
enum Heat heat, time_t time, const char *str
) {
struct Line *soft = &buffer->soft.lines[buffer->soft.len++ % BufferCap];
free(soft->str);
soft->heat = heat;
soft->time = time;
soft->str = strdup(str);
if (!soft->str) err(EX_OSERR, "strdup");
flow(&buffer->hard, cols, soft);
}
void bufferReflow(struct Buffer *buffer, int cols) {
buffer->hard.len = 0;
for (size_t i = 0; i < BufferCap; ++i) {
free(buffer->hard.lines[i].str);
buffer->hard.lines[i].str = NULL;
}
for (size_t i = 0; i < BufferCap; ++i) {
const struct Line *soft = bufferSoft(buffer, i);
if (soft) flow(&buffer->hard, cols, soft);
}
}

17
chat.h
View File

@ -276,6 +276,23 @@ void uiFormat(
void uiLoad(const char *name); void uiLoad(const char *name);
int uiSave(const char *name); int uiSave(const char *name);
enum { BufferCap = 1024 };
struct Buffer;
struct Line {
enum Heat heat;
time_t time;
char *str;
};
struct Buffer *bufferAlloc(void);
void bufferFree(struct Buffer *buffer);
const struct Line *bufferSoft(const struct Buffer *buffer, size_t i);
const struct Line *bufferHard(const struct Buffer *buffer, size_t i);
void bufferPush(
struct Buffer *buffer, int cols,
enum Heat heat, time_t time, const char *str
);
void bufferReflow(struct Buffer *buffer, int cols);
enum Edit { enum Edit {
EditHead, EditHead,
EditTail, EditTail,

81
ui.c
View File

@ -74,34 +74,6 @@ static WINDOW *status;
static WINDOW *marker; static WINDOW *marker;
static WINDOW *input; static WINDOW *input;
struct Line {
enum Heat heat;
time_t time;
char *str;
};
enum { BufferCap = 1024 };
struct Buffer {
size_t len;
struct Line lines[BufferCap];
};
_Static_assert(!(BufferCap & (BufferCap - 1)), "BufferCap is power of two");
static void bufferPush(
struct Buffer *buffer, enum Heat heat, time_t time, const char *str
) {
struct Line *line = &buffer->lines[buffer->len++ % BufferCap];
free(line->str);
line->heat = heat;
line->time = time;
line->str = strdup(str);
if (!line->str) err(EX_OSERR, "strdup");
}
static struct Line bufferLine(const struct Buffer *buffer, size_t i) {
return buffer->lines[(buffer->len + i) % BufferCap];
}
struct Window { struct Window {
uint id; uint id;
WINDOW *pad; WINDOW *pad;
@ -113,7 +85,7 @@ struct Window {
uint unreadHard; uint unreadHard;
uint unreadSoft; uint unreadSoft;
uint unreadWarm; uint unreadWarm;
struct Buffer buffer; struct Buffer *buffer;
}; };
static struct { static struct {
@ -170,14 +142,13 @@ static uint windowFor(uint id) {
wmove(window->pad, WindowLines - 1, 0); wmove(window->pad, WindowLines - 1, 0);
window->mark = true; window->mark = true;
window->ignore = true; window->ignore = true;
window->buffer = bufferAlloc();
return windowPush(window); return windowPush(window);
} }
static void windowFree(struct Window *window) { static void windowFree(struct Window *window) {
for (size_t i = 0; i < BufferCap; ++i) { bufferFree(window->buffer);
free(window->buffer.lines[i].str);
}
delwin(window->pad); delwin(window->pad);
free(window); free(window);
} }
@ -625,7 +596,7 @@ static void notify(uint id, const char *str) {
void uiWrite(uint id, enum Heat heat, const time_t *src, const char *str) { void uiWrite(uint id, enum Heat heat, const time_t *src, const char *str) {
struct Window *window = windows.ptrs[windowFor(id)]; struct Window *window = windows.ptrs[windowFor(id)];
time_t ts = (src ? *src : time(NULL)); time_t ts = (src ? *src : time(NULL));
bufferPush(&window->buffer, heat, ts, str); bufferPush(window->buffer, COLS, heat, ts, str);
if (heat < Cold && window->ignore) return; if (heat < Cold && window->ignore) return;
int lines = 0; int lines = 0;
@ -668,15 +639,15 @@ static void reflow(struct Window *window) {
int flowed = 0; int flowed = 0;
window->unreadSoft = 0; window->unreadSoft = 0;
for (size_t i = 0; i < BufferCap; ++i) { for (size_t i = 0; i < BufferCap; ++i) {
struct Line line = bufferLine(&window->buffer, i); const struct Line *line = bufferSoft(window->buffer, i);
if (!line.str) continue; if (!line) continue;
if (line.heat < Cold && window->ignore) continue; if (line->heat < Cold && window->ignore) continue;
int lines = 0; int lines = 0;
if (i == (size_t)(BufferCap - window->unreadHard)) { if (i == (size_t)(BufferCap - window->unreadHard)) {
waddch(window->pad, '\n'); waddch(window->pad, '\n');
lines++; lines++;
} }
lines += wordWrap(window->pad, line.str); lines += wordWrap(window->pad, line->str);
if (i >= (size_t)(BufferCap - window->unreadHard)) { if (i >= (size_t)(BufferCap - window->unreadHard)) {
window->unreadSoft += lines; window->unreadSoft += lines;
} }
@ -695,7 +666,7 @@ static void resize(void) {
getmaxyx(windows.ptrs[0]->pad, height, width); getmaxyx(windows.ptrs[0]->pad, height, width);
if (width == COLS) return; if (width == COLS) return;
for (uint num = 0; num < windows.len; ++num) { for (uint num = 0; num < windows.len; ++num) {
wresize(windows.ptrs[num]->pad, BufferCap, COLS); wresize(windows.ptrs[num]->pad, WindowLines, COLS);
reflow(windows.ptrs[num]); reflow(windows.ptrs[num]);
} }
(void)height; (void)height;
@ -707,10 +678,10 @@ static void bufferList(const struct Buffer *buffer) {
waiting = true; waiting = true;
for (size_t i = 0; i < BufferCap; ++i) { for (size_t i = 0; i < BufferCap; ++i) {
struct Line line = bufferLine(buffer, i); const struct Line *line = bufferSoft(buffer, i);
if (!line.str) continue; if (!line) continue;
struct tm *tm = localtime(&line.time); struct tm *tm = localtime(&line->time);
if (!tm) err(EX_OSERR, "localtime"); if (!tm) err(EX_OSERR, "localtime");
char buf[sizeof("00:00:00")]; char buf[sizeof("00:00:00")];
@ -720,20 +691,20 @@ static void bufferList(const struct Buffer *buffer) {
bool align = false; bool align = false;
struct Style style = StyleDefault; struct Style style = StyleDefault;
while (*line.str) { for (const char *str = line->str; *str;) {
if (*line.str == '\t') { if (*str == '\t') {
printf("%c", (align ? '\t' : ' ')); printf("%c", (align ? '\t' : ' '));
align = true; align = true;
line.str++; str++;
} }
size_t len = styleParse(&style, (const char **)&line.str); size_t len = styleParse(&style, &str);
size_t tab = strcspn(line.str, "\t"); size_t tab = strcspn(str, "\t");
if (tab < len) len = tab; if (tab < len) len = tab;
vid_attr(styleAttr(style), stylePair(style), NULL); vid_attr(styleAttr(style), stylePair(style), NULL);
printf("%.*s", (int)len, line.str); printf("%.*s", (int)len, str);
line.str += len; str += len;
} }
printf("\n"); printf("\n");
} }
@ -926,7 +897,7 @@ static void keyCode(int code) {
break; case KeyMetaB: edit(id, EditPrevWord, 0); break; case KeyMetaB: edit(id, EditPrevWord, 0);
break; case KeyMetaD: edit(id, EditDeleteNextWord, 0); break; case KeyMetaD: edit(id, EditDeleteNextWord, 0);
break; case KeyMetaF: edit(id, EditNextWord, 0); break; case KeyMetaF: edit(id, EditNextWord, 0);
break; case KeyMetaL: bufferList(&window->buffer); break; case KeyMetaL: bufferList(window->buffer);
break; case KeyMetaM: waddch(window->pad, '\n'); break; case KeyMetaM: waddch(window->pad, '\n');
break; case KeyMetaQ: edit(id, EditCollapse, 0); break; case KeyMetaQ: edit(id, EditCollapse, 0);
break; case KeyMetaU: windowScrollUnread(window); break; case KeyMetaU: windowScrollUnread(window);
@ -1060,11 +1031,11 @@ int uiSave(const char *name) {
if (writeTime(file, window->unreadHard)) return -1; if (writeTime(file, window->unreadHard)) return -1;
if (writeTime(file, window->unreadWarm)) return -1; if (writeTime(file, window->unreadWarm)) return -1;
for (size_t i = 0; i < BufferCap; ++i) { for (size_t i = 0; i < BufferCap; ++i) {
struct Line line = bufferLine(&window->buffer, i); const struct Line *line = bufferSoft(window->buffer, i);
if (!line.str) continue; if (!line) continue;
if (writeTime(file, line.time)) return -1; if (writeTime(file, line->time)) return -1;
if (writeTime(file, line.heat)) return -1; if (writeTime(file, line->heat)) return -1;
if (writeString(file, line.str)) return -1; if (writeString(file, line->str)) return -1;
} }
if (writeTime(file, 0)) return -1; if (writeTime(file, 0)) return -1;
} }
@ -1122,7 +1093,7 @@ void uiLoad(const char *name) {
if (!time) break; if (!time) break;
enum Heat heat = (version > 2 ? readTime(file) : Cold); enum Heat heat = (version > 2 ? readTime(file) : Cold);
readString(file, &buf, &cap); readString(file, &buf, &cap);
bufferPush(&window->buffer, heat, time, buf); bufferPush(window->buffer, COLS, heat, time, buf);
} }
reflow(window); reflow(window);
} }