Add spreadsheet program

This commit is contained in:
Ole Morud
2022-12-25 16:06:34 +01:00
committed by Ole Morud
commit 2712a1bf3e
24 changed files with 1314 additions and 0 deletions

110
.clang-format Normal file
View File

@@ -0,0 +1,110 @@
# modified .clang-format from
# https://github.com/torvalds/linux/blob/master/.clang-format
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Left
AlignOperands: true
AlignTrailingComments: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: true
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: false
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: true
AfterNamespace: true
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Custom
BreakBeforeInheritanceComma: false
BreakBeforeTernaryOperators: false
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeComma
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: false
ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: false
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: false
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '.*'
Priority: 1
IncludeIsMainRegex: '(Test)?$'
IndentCaseLabels: false
IndentGotoLabels: false
IndentPPDirectives: None
IndentWidth: 4
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 4
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
# Taken from git's rules
PenaltyBreakAssignment: 10
PenaltyBreakBeforeFirstCallParameter: 30
PenaltyBreakComment: 10
PenaltyBreakFirstLessLess: 0
PenaltyBreakString: 10
PenaltyExcessCharacter: 100
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right
ReflowComments: false
SortIncludes: false
SortUsingDeclarations: false
SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatementsExceptForEachMacros
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInContainerLiterals: false
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
TabWidth: 4

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
bin
obj
.vscode

33
ISSUES Normal file
View File

@@ -0,0 +1,33 @@
Missing features
----------------
- [x] 1. Implement working cell edit function
- [x] 2. Add type detection in cells
- [x] int64, long double, text
- [ ] date, functions
- [ ] 3. Add text parsing in cells
- [x] parse types
- [ ] parse functions
- [ ] 4. Add file saving/loading
- [ ] 5. Implement scrolling
Medium priority
---------------
- Date/time support in cells
- Extend type detection
Low priority
------------
[1.]
- Avoid realloc on each append/pop when editing text in interface.c/editor_backspace()
[misc.]
- Decide and implement data structure for insertion/search of cells (currently BST, maybe red-black-tree?)
- Use BST magic to find relevant subtree to print given a scroll_x and scroll_y
- Remove cells from BST when they are empty

32
Makefile Normal file
View File

@@ -0,0 +1,32 @@
CC := gcc
CFLAGS := -O0 -g -Wall -Wextra -fanalyzer \
-fsanitize=address -fno-omit-frame-pointer
LDFLAGS := -g -fsanitize=address
LDLIBS := -lncurses
SRC_DIR := src
OBJ_DIR := obj
BIN_DIR := bin
EXE := $(BIN_DIR)/run
SRC := $(wildcard $(SRC_DIR)/*.c)
OBJ := $(SRC:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)
.PHONY: all clean
all: $(EXE)
$(EXE): $(OBJ) | $(BIN_DIR)
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR)
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
$(BIN_DIR) $(OBJ_DIR):
mkdir -p $@
clean:
@$(RM) -rv $(BIN_DIR) $(OBJ_DIR)
-include $(OBJ:.o=.d)

20
README.md Normal file
View File

@@ -0,0 +1,20 @@
# Terminal spreadsheet
![spreadsheet2](https://user-images.githubusercontent.com/82065181/209571188-7c1e90ca-d7f4-4de7-adc4-64000e6bf0f2.png)
This is a WIP spreadsheet application for the terminal using ncurses.
See [ISSUES](ISSUES) to get an idea of what needs to be implemented.
## Download and build
```sh
git clone https://github.com/olemorud/terminal-spreadsheet.git
cd terminal-spreadsheet
make
```
## Run
```sh
cd bin
./run
```

55
src/book.c Normal file
View File

@@ -0,0 +1,55 @@
#include "book.h"
#include "config.h"
#include <err.h>
#include <stdlib.h>
#include <string.h>
void book_init(struct book *b, char *title)
{
b->n_sheets = 0;
b->sheets = malloc(0);
if (!book_add_sheet(b))
exit(1);
(b->sheets)[1] = NULL;
if (title != NULL)
b->title = strndup(title, MAX_STR);
else
b->title = strdup("");
}
void book_delete(struct book *b)
{
if (b == NULL) {
warn("attempted to delete NULL book");
return;
}
size_t i = 0;
while ((b->sheets)[i] != NULL) {
sheet_delete((b->sheets)[i]);
i++;
}
}
bool book_add_sheet(struct book *b)
{
struct sheet *s;
b->n_sheets++;
void* tmp = realloc(b->sheets, sizeof(struct sheet*) * (b->n_sheets + 1));
if (tmp == NULL)
return false;
b->sheets = tmp;
b->sheets[b->n_sheets] = NULL;
s = malloc(sizeof(struct sheet));
sheet_init(s, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_SHEET_TITLE);
b->sheets[b->n_sheets-1] = s;
return true;
}

20
src/book.h Normal file
View File

@@ -0,0 +1,20 @@
#ifndef BOOK_H
#define BOOK_H
#include "sheet.h"
#include <stdbool.h>
#include <stddef.h>
struct book {
struct sheet **sheets;
size_t n_sheets;
char *title;
};
void book_init(struct book *b, char *title);
void book_delete(struct book *b);
bool book_add_sheet(struct book *b);
#endif

52
src/cell.c Normal file
View File

@@ -0,0 +1,52 @@
#include "cell.h"
#include <stdlib.h>
#include <err.h>
#include <string.h>
void cell_parse_function(struct cell *cell);
void cell_init(struct cell *c, size_t x, size_t y, enum cell_type type)
{
c->referenced_by = NULL;
c->x_pos = x;
c->y_pos = y;
c->type = type;
c->text = calloc(1,1);
c->value.integer = 0;
}
void cell_delete(struct cell *c)
{
if (c == NULL) {
warn("attempted to delete NULL cell");
return;
}
free(c->text);
free(c);
}
void cell_set_type(struct cell *cell)
{
if (cell->text[0] == '=') {
//cell_parse_function(cell);
return;
}
char *endptr;
int i = strtoll(cell->text, &endptr, 10);
if (*endptr == '\0') {
cell->type = Integer;
cell->value.integer = i;
return;
}
int d = strtold(cell->text, &endptr);
if (*endptr == '\0') {
cell->type = Floating;
cell->value.floating = d;
return;
}
}

38
src/cell.h Normal file
View File

@@ -0,0 +1,38 @@
#ifndef CELL_H
#define CELL_H
#include <stddef.h>
#include <stdint.h>
#include <time.h>
#include "common.h"
enum cell_type {
Text,
Integer,
Floating,
Date
};
struct cell_list {
struct cell *cell;
struct cell_list *next;
};
struct cell {
struct cell_list *referenced_by;
size_t x_pos;
size_t y_pos;
char *text;
enum cell_type type;
union {
int64_t integer;
double floating;
time_t date;
} value;
};
void cell_init(struct cell *c, size_t x_pos, size_t y_pos, enum cell_type type);
void cell_delete(struct cell *c);
void cell_set_type(struct cell *cell);
#endif

65
src/celltree.c Normal file
View File

@@ -0,0 +1,65 @@
#include "celltree.h"
#include <stdlib.h>
#include <errno.h>
#include <err.h>
/* returns true if (ax,ay) `greater than` (bx,by) */
static bool greater_than(int x_a, int y_a, int x_b, int y_b)
{
if (x_a == x_b)
return y_a > y_b;
return x_a > x_b;
}
void celltree_delete(struct celltree *root)
{
if (root == NULL) {
return;
}
cell_delete(root->cell);
celltree_delete(root->right);
celltree_delete(root->left);
free(root);
}
struct cell* celltree_search(struct celltree *root, size_t x, size_t y)
{
if (root == NULL) {
return NULL;
}
struct cell *c = root->cell;
if (c->x_pos == x && c->y_pos == y) {
return root->cell;
}
if (greater_than(x, y, c->x_pos, c->y_pos)) {
return celltree_search(root->right, x, y);
} else {
return celltree_search(root->left, x, y);
}
}
void celltree_insert(struct celltree **root, struct cell *c)
{
if (*root == NULL) {
*root = malloc(sizeof **root);
if (*root == NULL)
exit(errno);
(*root)->cell = c;
(*root)->right = NULL;
(*root)->left = NULL;
return;
}
if (greater_than(c->x_pos, c->y_pos, (*root)->cell->x_pos,
(*root)->cell->y_pos)) {
celltree_insert(&(*root)->right, c);
} else {
celltree_insert(&(*root)->left, c);
}
}

21
src/celltree.h Normal file
View File

@@ -0,0 +1,21 @@
#ifndef CELLTREE_H
#define CELLTREE_H
#include <stdbool.h>
#include "cell.h"
/* TODO: implement red-black tree */
struct celltree {
struct cell *cell;
struct celltree *right;
struct celltree *left;
bool isRed;
};
void celltree_delete(struct celltree *root);
struct cell *celltree_search(struct celltree *root, size_t x, size_t y);
void celltree_insert(struct celltree **root, struct cell *c);
#endif

7
src/common.h Normal file
View File

@@ -0,0 +1,7 @@
#ifndef COMMON_H
#define COMMON_H
enum modes {Edit, Command, G};
#endif

30
src/config.h Normal file
View File

@@ -0,0 +1,30 @@
#ifndef CONFIG_H
#define CONFIG_H
// strings
#define DEFAULT_BOOK_TITLE "Untitled"
#define DEFAULT_SHEET_TITLE "Sheet "
#define DEFAULT_HEIGHT 40
#define DEFAULT_WIDTH 80
#define MAX_STR 64
// dimensions
#define CELL_SIZE 12
#define TAB_SIZE 24
#define Y_AXIS_WIDTH 4
#define N_CELLS_WIDTH 5
#define N_CELLS_HEIGHT 5
// colors
#define COLOR_TITLE 20
#define COLOR_BACKGROUND 21
#define COLOR_LIGHTER_GRAY 22
#define COLOR_LIGHT_GRAY 23
#define COLOR_GRAY 24
#define COLOR_HIGHLIGHTED 25
#define COLOR_EDITMODE 26
#endif

247
src/draw.c Normal file
View File

@@ -0,0 +1,247 @@
#include "draw.h"
#include "cell.h"
#include "celltree.h"
#include "config.h" // colors, sizes ...
#include "sheet.h" // struct sheet
#include <ncurses.h> // ncurses functions
#include <signal.h> // handle sigterm
#include <string.h> // strlen
#define _STR(x) #x
#define STR(x) _STR(x)
static void cleanup()
{
clear();
echo();
curs_set(1);
refresh();
endwin();
}
static int x_pos(int x)
{
return x * CELL_SIZE + Y_AXIS_WIDTH;
}
static int y_pos(int y)
{
return y + 2;
}
static int get_cell_color(int const row, int const col)
{
return (row + col) % 2 ? COLOR_LIGHTER_GRAY : COLOR_LIGHT_GRAY;
}
void draw_init(struct book *book)
{
initscr();
atexit(cleanup);
signal(SIGTERM, exit);
noecho();
cbreak();
keypad(stdscr, true);
set_escdelay(0);
curs_set(0);
start_color();
init_color(COLOR_LIGHTER_GRAY, 900, 900, 900);
init_pair(COLOR_LIGHTER_GRAY, COLOR_BLACK, COLOR_LIGHTER_GRAY);
init_color(COLOR_LIGHT_GRAY, 800, 800, 800);
init_pair(COLOR_LIGHT_GRAY, COLOR_BLACK, COLOR_LIGHT_GRAY);
init_color(COLOR_GRAY, 650, 650, 650);
init_pair(COLOR_GRAY, COLOR_BLACK, COLOR_GRAY);
init_color(COLOR_HIGHLIGHTED, 800, 600, 600);
init_pair(COLOR_HIGHLIGHTED, COLOR_BLACK, COLOR_HIGHLIGHTED);
init_color(COLOR_EDITMODE, 600, 800, 600);
init_pair(COLOR_EDITMODE, COLOR_BLACK, COLOR_EDITMODE);
init_pair(COLOR_TITLE, COLOR_BLACK, COLOR_GREEN);
init_pair(COLOR_BACKGROUND, COLOR_MAGENTA, COLOR_GREEN);
bkgdset(COLOR_PAIR(COLOR_BACKGROUND));
draw_book(book, 0);
draw_highlight(0, 0);
draw_right_status("Normal");
refresh();
}
void draw_cell(struct cell const * const cell)
{
int x = x_pos(cell->x_pos);
int y = y_pos(cell->y_pos);
int color = get_cell_color(y, x);
move(y, x);
attron(COLOR_PAIR(color));
switch(cell->type){
case Integer:
printw("%" STR(CELL_SIZE) "li", cell->value.integer);
break;
case Floating:
printw("%" STR(CELL_SIZE) "lf", cell->value.floating);
break;
default:
printw("%-" STR(CELL_SIZE) "s", cell->text);
break;
}
attroff(COLOR_PAIR(color));
}
static void draw_celltree(struct celltree const *const root)
{
if (root == NULL) {
return;
}
draw_cell(root->cell);
draw_celltree(root->right);
draw_celltree(root->left);
}
static void draw_row_num(int n)
{
attron(COLOR_PAIR(COLOR_GRAY));
printw("%" STR(Y_AXIS_WIDTH) "d", n);
attroff(COLOR_PAIR(COLOR_GRAY));
}
static void draw_row_header(int row)
{
// TODO: make it dynamic
char buf[4] = " ";
int i = sizeof buf - 2;
row++;
while (row && i >= 0) {
buf[i] = (row % 26) + 'A' - 1;
row /= 26;
i--;
}
attron(COLOR_PAIR(COLOR_GRAY));
printw("%" STR(CELL_SIZE) "s", buf);
attroff(COLOR_PAIR(COLOR_GRAY));
}
static void draw_sheet(struct sheet *s)
{
addch('\n');
int width = getmaxx(stdscr) - Y_AXIS_WIDTH;
int n_cells_wide = width / CELL_SIZE;
int n_cells_tall = getmaxy(stdscr) - 3;
// print row headers
chgat(Y_AXIS_WIDTH, 0, COLOR_GRAY, NULL);
for (int i = 0; i < n_cells_wide; i++) {
draw_row_header(i);
}
// clear sheet before drawing on top
for (int row = 0; row < n_cells_tall; row++) {
int color;
addch('\n');
draw_row_num(row);
for (int col = 0; col < n_cells_wide; col++) {
color = get_cell_color(row, col);
attron(COLOR_PAIR(color));
mvprintw(y_pos(row),
x_pos(col),
"%" STR(CELL_SIZE) "s", " ");
}
attroff(COLOR_PAIR(color));
}
// draw on top of empty sheet
draw_celltree(s->root_cell);
}
static void draw_input_bar()
{
int width, height;
getmaxyx(stdscr, height, width);
move(height - 1, 0);
attron(COLOR_PAIR(COLOR_TITLE));
for (int i = 0; i < width; i++) {
addch(' ');
}
}
void draw_book(struct book *book, size_t tab)
{
move(0, 0);
attron(COLOR_PAIR(COLOR_TITLE));
printw(" %s ", book->title);
attroff(COLOR_PAIR(COLOR_TITLE));
for (size_t i = 0; i < book->n_sheets; i++) {
int attr = tab == i ? COLOR_GRAY : COLOR_LIGHT_GRAY;
attron(COLOR_PAIR(attr));
printw("%10s ", book->sheets[i]->title);
attroff(COLOR_PAIR(attr));
}
draw_sheet(book->sheets[tab]);
draw_input_bar();
}
/* highlights relative to screen */
void draw_highlight(int const x, int const y)
{
static int prev_x;
static int prev_y;
int color;
mvchgat(y_pos(y), x_pos(x), CELL_SIZE, 0, COLOR_HIGHLIGHTED,
NULL);
color = get_cell_color(prev_y, prev_x);
mvchgat(y_pos(prev_y), x_pos(prev_x), CELL_SIZE, 0, color, NULL);
prev_x = x;
prev_y = y;
}
void draw_left_status(char const *const s)
{
int y = getmaxy(stdscr);
attron(COLOR_PAIR(COLOR_TITLE));
mvprintw(y - 1, 1, "%s", s);
attron(COLOR_PAIR(COLOR_TITLE));
}
void draw_right_status(char const *const s)
{
int x, y;
getmaxyx(stdscr, y, x);
attron(COLOR_PAIR(COLOR_TITLE));
mvprintw(y - 1, x - 1 - strlen(s), "%s", s);
attron(COLOR_PAIR(COLOR_TITLE));
}

21
src/draw.h Normal file
View File

@@ -0,0 +1,21 @@
#ifndef DRAW_H
#define DRAW_H
#include "book.h"
#include <stddef.h>
#include "cell.h"
void draw_highlight(int const x, int const y);
void draw_init(struct book *book);
void draw_book(struct book *b, size_t tab);
void draw_cell(struct cell const * const cell);
void draw_left_status(const char *const s);
void draw_right_status(char const *const s);
#endif

147
src/function_parse.c Normal file
View File

@@ -0,0 +1,147 @@
#include <ctype.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <errno.h>
static char *operators[] = {
"+",
"-",
"*",
"/",
">",
"<",
NULL,
};
static char *separators[] = {
",",
"(",
")",
NULL,
};
enum type {
error,
operator,
separator,
identifier,
number,
};
struct token {
char const * value;
size_t value_len;
enum type type;
};
struct token_list {
struct token *tokens;
size_t len;
char *error_msg;
};
// returns length of string in haystack matching needle, or 0 if it
// doesn't exist
static int contains(char * const *haystack, char* needle)
{
for(char *s = *haystack; s; s++){
if (strcmp(s, needle) == 0) {
return strlen(s);
}
}
return false;
}
static struct token eat(char **str, int (*f)(int), enum type type)
{
struct token tok;
tok.type = type;
tok.value = *str;
tok.value_len = 0;
while (f(**str)) {
*str += 1;
tok.value_len += 1;
}
return tok;
}
static struct token tokenize_identifier(char** str)
{
return eat(str, isalnum, identifier);
}
static struct token tokenize_number(char **str)
{
return eat(str, isdigit, identifier);
}
// Returns a token array from function
static struct token_list tokenize(char *str)
{
struct token_list *tok_list = malloc(sizeof *tok_list);
if (tok_list == NULL) {
exit(errno);
}
const size_t max_tok = 16; // TODO: dynamically grow array
tok_list->tokens = malloc(sizeof *(tok_list->tokens) * max_tok);
if (tok_list->tokens == NULL)
exit(errno);
tok_list->len = 0;
// identifiers start with letters but can contain numbers
while (*str && tok_list->len < max_tok) {
struct token t;
size_t n;
if (isalpha(*str)) {
t = tokenize_identifier(&str);
}
else if (!isalnum(*str) ) {
t = tokenize_number(&str);
}
else if ((n = contains(operators, str))) {
t.type = operator;
t.value = str;
t.value_len = n;
}
else if ((n = contains(separators, str))) {
t.type = separator;
t.value = str;
t.value_len = n;
}
else {
t.type = error;
t.value = str;
t.value_len = strlen(str);
}
tok_list->tokens[tok_list->len] = t;
tok_list->len += 1;
}
return *tok_list;
}
// credits: Daniel J. Bernstein
// https://web.archive.org/web/20220328102559/http://www.cse.yorku.ca/~oz/hash.html
static unsigned long hash(unsigned char *str)
{
unsigned long hash = 5381;
int c = 0;
for(size_t i=0; str[i] != '\0'; i++)
hash = hash * 33 + c;
return hash;
}

7
src/function_parse.h Normal file
View File

@@ -0,0 +1,7 @@
#ifndef FUNCTION_PARSE_H
#define FUNCTION_PARSE_H
#endif

166
src/interface.c Normal file
View File

@@ -0,0 +1,166 @@
#include "interface.h"
#include "cell.h"
#include <curses.h>
#include <stdlib.h>
#include "celltree.h"
#include "keymap.h"
#include "draw.h"
int g_display_sel_x = 0;
int g_display_sel_y = 0;
int g_tab = 0;
//int g_right_scrolled = 0;
//int g_down_scrolled = 0;
struct book *g_book = NULL;
struct cell *g_cur = NULL;
enum modes mode = Command;
void interface_attach(struct book *b)
{
g_book = b;
}
void interface_move_right()
{
g_display_sel_x++;
draw_highlight(g_display_sel_x, g_display_sel_y);
}
void interface_move_left()
{
if (g_display_sel_x == 0)
return;
g_display_sel_x--;
draw_highlight(g_display_sel_x, g_display_sel_y);
}
void interface_move_up()
{
if (g_display_sel_y == 0)
return;
g_display_sel_y--;
draw_highlight(g_display_sel_x, g_display_sel_y);
}
void interface_move_down()
{
g_display_sel_y++;
draw_highlight(g_display_sel_x, g_display_sel_y);
}
void interface_next_tab()
{
g_tab++;
g_tab %= g_book->n_sheets;
draw_book(g_book, g_tab);
}
void interface_prev_tab()
{
g_tab--;
if (g_tab < 0) {
g_tab = g_book->n_sheets - 1;
}
draw_book(g_book, g_tab);
}
void interface_editor_backspace()
{
size_t textlen = strlen(g_cur->text);
if (textlen == 0)
return;
int x, y;
addch(' ');
getyx(stdscr, y, x);
move(y, x - 1);
g_cur->text = realloc(g_cur->text, textlen);
g_cur->text[textlen - 1] = '\0';
refresh();
}
void interface_editor_append(const char c)
{
int x, y;
size_t textlen = strlen(g_cur->text);
g_cur->text = realloc(g_cur->text, textlen + 2);
g_cur->text[textlen] = c;
g_cur->text[textlen + 1] = '\0';
getyx(stdscr, y, x);
draw_cell(g_cur);
move(y, x);
refresh();
}
static void start_edit_cell()
{
// struct cell* cur
g_cur = celltree_search(g_book->sheets[g_tab]->root_cell, g_display_sel_x,
g_display_sel_y);
if (g_cur == NULL) {
g_cur = malloc(sizeof(struct cell));
cell_init(g_cur, g_display_sel_x, g_display_sel_y, Text);
celltree_insert(&g_book->sheets[g_tab]->root_cell, g_cur);
}
draw_left_status(g_cur->text);
}
void interface_mode_normal()
{
draw_right_status("Normal");
mode = Command;
curs_set(0);
}
void interface_mode_g()
{
draw_right_status("g");
mode = G;
}
void interface_mode_edit()
{
mode = Edit;
draw_right_status("Insert");
curs_set(1);
echo();
start_edit_cell();
}
void interface_commit_cell_change()
{
cell_set_type(g_cur);
draw_cell(g_cur);
}
void interface_handle_resize()
{
draw_book(g_book, g_tab);
refresh();
}
void interface_interact()
{
refresh();
int x, y;
getmaxyx(stdscr, y, x);
move(y - 1, x - 1);
int c = getch();
keymap_parse_key(c, mode);
}

69
src/interface.h Normal file
View File

@@ -0,0 +1,69 @@
#ifndef INTERFACE_H
#define INTERFACE_H
#include "book.h"
/* */
void interface_commit_cell_change();
/* terminal resize handler */
void interface_handle_resize();
/* attach book to interface */
void interface_attach(struct book *b);
/* move one cell right */
void interface_move_right();
/* move one cell left */
void interface_move_left();
/* move one cell up */
void interface_move_up();
/* move one cell down */
void interface_move_down();
/* move to prev tab */
void interface_prev_tab();
/* move to next tab */
void interface_next_tab();
/* writes a status message in the bottom
* right corner
**/
void interface_write_right_status(char const *const s);
/* event loop */
void interface_interact();
/*
* Insert Mode commands
* --------------------
*/
/* Add char to input */
void interface_editor_append(const char c);
/* Remove last char */
void interface_editor_backspace();
/*
* Mode switching
* --------------
*/
/* switch to g mode TODO: find better way */
void interface_mode_g();
/* switch to insert mode */
void interface_mode_edit();
/* switch to normal mode */
void interface_mode_normal();
#endif

81
src/keymap.c Normal file
View File

@@ -0,0 +1,81 @@
#include "keymap.h"
#include "draw.h"
#include "interface.h"
#include <curses.h>
#define KEY_ESC 27
static void * const keymap_always[1024] = {
[KEY_RESIZE] = interface_handle_resize,
[27] /*ESC*/ = interface_mode_normal,
};
static void * const keymap_normal[1024] = {
['i'] = interface_mode_edit,
['a'] = interface_mode_edit,
['g'] = interface_mode_g,
[KEY_LEFT] = interface_move_left,
['h'] = interface_move_left,
[KEY_DOWN] = interface_move_down,
['j'] = interface_move_down,
[KEY_UP] = interface_move_up,
['k'] = interface_move_up,
[KEY_RIGHT] = interface_move_right,
['l'] = interface_move_right,
};
static void * const keymap_edit[1024] = {
[KEY_ESC] = interface_mode_normal,
[KEY_BACKSPACE] = interface_editor_backspace,
};
static void * const keymap_g[1024] = {
['t'] = interface_next_tab,
['T'] = interface_prev_tab,
};
static void undefined()
{
int height = getmaxy(stdscr);
move(height - 1, 0);
printw("key not defined");
}
void keymap_parse_key(size_t c, enum modes mode)
{
void (*cmd)(void) = NULL;
switch (mode) {
case Command:
cmd = keymap_normal[c];
break;
case G:
cmd = keymap_g[c];
break;
case Edit:
cmd = keymap_edit[c];
if (cmd == NULL){
interface_editor_append(c);
return;
}
if (c == KEY_ESC) {
interface_commit_cell_change();
}
break;
}
if(mode != Edit) {
mode = Command;
}
if (cmd == NULL) {
cmd = keymap_always[c];
if (cmd == NULL) {
cmd = undefined;
}
}
(*cmd)();
}

10
src/keymap.h Normal file
View File

@@ -0,0 +1,10 @@
#ifndef KEYMAP_H
#define KEYMAP_H
#include "common.h"
#include <stddef.h>
void keymap_parse_key(size_t c, enum modes mode);
#endif

33
src/main.c Normal file
View File

@@ -0,0 +1,33 @@
#include "book.h"
#include "draw.h"
#include "config.h"
#include "interface.h"
#include "sheet.h"
#include <curses.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
(void)argv;
(void)argc;
struct book b;
book_init(&b, DEFAULT_BOOK_TITLE);
book_add_sheet(&b);
book_add_sheet(&b);
book_add_sheet(&b);
draw_init(&b);
interface_attach(&b);
while (1) {
interface_interact();
}
return 0;
}

27
src/sheet.c Normal file
View File

@@ -0,0 +1,27 @@
#include "sheet.h"
#include "celltree.h"
void sheet_init(struct sheet *s, size_t width, size_t height, char *title)
{
s->width = width;
s->height = height;
s->root_cell = NULL;
if (title) {
s->title = strndup(title, MAX_STR);
} else {
s->title = strdup("");
}
}
void sheet_delete(struct sheet *s)
{
if (s == NULL) {
warn("attempted to delete NULL sheet");
return;
}
celltree_delete(s->root_cell);
free(s->title);
free(s);
}

20
src/sheet.h Normal file
View File

@@ -0,0 +1,20 @@
#ifndef SHEET_H
#define SHEET_H
#include <stddef.h>
#include <string.h>
#include <stdlib.h>
#include <err.h>
#include "config.h"
struct sheet {
struct celltree *root_cell;
size_t width;
size_t height;
char* title;
};
void sheet_init(struct sheet *s, size_t width, size_t height, char* title);
void sheet_delete(struct sheet *s);
#endif