From 2712a1bf3e5cca546d987b78392cc856986d8996 Mon Sep 17 00:00:00 2001 From: Ole Morud Date: Sun, 25 Dec 2022 16:06:34 +0100 Subject: [PATCH] Add spreadsheet program --- .clang-format | 110 +++++++++++++++++++ .gitignore | 3 + ISSUES | 33 ++++++ Makefile | 32 ++++++ README.md | 20 ++++ src/book.c | 55 ++++++++++ src/book.h | 20 ++++ src/cell.c | 52 +++++++++ src/cell.h | 38 +++++++ src/celltree.c | 65 ++++++++++++ src/celltree.h | 21 ++++ src/common.h | 7 ++ src/config.h | 30 ++++++ src/draw.c | 247 +++++++++++++++++++++++++++++++++++++++++++ src/draw.h | 21 ++++ src/function_parse.c | 147 +++++++++++++++++++++++++ src/function_parse.h | 7 ++ src/interface.c | 166 +++++++++++++++++++++++++++++ src/interface.h | 69 ++++++++++++ src/keymap.c | 81 ++++++++++++++ src/keymap.h | 10 ++ src/main.c | 33 ++++++ src/sheet.c | 27 +++++ src/sheet.h | 20 ++++ 24 files changed, 1314 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 ISSUES create mode 100644 Makefile create mode 100644 README.md create mode 100644 src/book.c create mode 100644 src/book.h create mode 100644 src/cell.c create mode 100644 src/cell.h create mode 100644 src/celltree.c create mode 100644 src/celltree.h create mode 100644 src/common.h create mode 100644 src/config.h create mode 100644 src/draw.c create mode 100644 src/draw.h create mode 100644 src/function_parse.c create mode 100644 src/function_parse.h create mode 100644 src/interface.c create mode 100644 src/interface.h create mode 100644 src/keymap.c create mode 100644 src/keymap.h create mode 100644 src/main.c create mode 100644 src/sheet.c create mode 100644 src/sheet.h diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..126d419 --- /dev/null +++ b/.clang-format @@ -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 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4f19896 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +bin +obj +.vscode \ No newline at end of file diff --git a/ISSUES b/ISSUES new file mode 100644 index 0000000..5f75d1e --- /dev/null +++ b/ISSUES @@ -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 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..93ddeb0 --- /dev/null +++ b/Makefile @@ -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) \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c678aa4 --- /dev/null +++ b/README.md @@ -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 +``` diff --git a/src/book.c b/src/book.c new file mode 100644 index 0000000..5dd0551 --- /dev/null +++ b/src/book.c @@ -0,0 +1,55 @@ +#include "book.h" +#include "config.h" +#include +#include +#include + +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; +} diff --git a/src/book.h b/src/book.h new file mode 100644 index 0000000..9c6af8d --- /dev/null +++ b/src/book.h @@ -0,0 +1,20 @@ +#ifndef BOOK_H +#define BOOK_H + +#include "sheet.h" +#include +#include + +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 diff --git a/src/cell.c b/src/cell.c new file mode 100644 index 0000000..12ec5b9 --- /dev/null +++ b/src/cell.c @@ -0,0 +1,52 @@ +#include "cell.h" +#include +#include +#include + +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; + } +} diff --git a/src/cell.h b/src/cell.h new file mode 100644 index 0000000..eef842f --- /dev/null +++ b/src/cell.h @@ -0,0 +1,38 @@ +#ifndef CELL_H +#define CELL_H + +#include +#include +#include +#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 diff --git a/src/celltree.c b/src/celltree.c new file mode 100644 index 0000000..4a21225 --- /dev/null +++ b/src/celltree.c @@ -0,0 +1,65 @@ +#include "celltree.h" +#include +#include +#include + +/* 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); + } +} diff --git a/src/celltree.h b/src/celltree.h new file mode 100644 index 0000000..633ac99 --- /dev/null +++ b/src/celltree.h @@ -0,0 +1,21 @@ +#ifndef CELLTREE_H +#define CELLTREE_H + +#include +#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 \ No newline at end of file diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..78befa1 --- /dev/null +++ b/src/common.h @@ -0,0 +1,7 @@ + +#ifndef COMMON_H +#define COMMON_H + +enum modes {Edit, Command, G}; + +#endif \ No newline at end of file diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..7174bdf --- /dev/null +++ b/src/config.h @@ -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 \ No newline at end of file diff --git a/src/draw.c b/src/draw.c new file mode 100644 index 0000000..e36ea02 --- /dev/null +++ b/src/draw.c @@ -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 functions +#include // handle sigterm +#include // 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)); +} + diff --git a/src/draw.h b/src/draw.h new file mode 100644 index 0000000..6306e6e --- /dev/null +++ b/src/draw.h @@ -0,0 +1,21 @@ + +#ifndef DRAW_H +#define DRAW_H + +#include "book.h" +#include +#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 diff --git a/src/function_parse.c b/src/function_parse.c new file mode 100644 index 0000000..8b1aa24 --- /dev/null +++ b/src/function_parse.c @@ -0,0 +1,147 @@ + +#include +#include +#include +#include +#include +#include + +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; +} + + diff --git a/src/function_parse.h b/src/function_parse.h new file mode 100644 index 0000000..d1f9988 --- /dev/null +++ b/src/function_parse.h @@ -0,0 +1,7 @@ + +#ifndef FUNCTION_PARSE_H +#define FUNCTION_PARSE_H + + + +#endif \ No newline at end of file diff --git a/src/interface.c b/src/interface.c new file mode 100644 index 0000000..8e0f95f --- /dev/null +++ b/src/interface.c @@ -0,0 +1,166 @@ + +#include "interface.h" +#include "cell.h" +#include +#include +#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); +} diff --git a/src/interface.h b/src/interface.h new file mode 100644 index 0000000..174f1b0 --- /dev/null +++ b/src/interface.h @@ -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 diff --git a/src/keymap.c b/src/keymap.c new file mode 100644 index 0000000..5bb78b4 --- /dev/null +++ b/src/keymap.c @@ -0,0 +1,81 @@ + +#include "keymap.h" +#include "draw.h" +#include "interface.h" +#include + +#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)(); +} diff --git a/src/keymap.h b/src/keymap.h new file mode 100644 index 0000000..e45f52e --- /dev/null +++ b/src/keymap.h @@ -0,0 +1,10 @@ + +#ifndef KEYMAP_H +#define KEYMAP_H + +#include "common.h" +#include + +void keymap_parse_key(size_t c, enum modes mode); + +#endif diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..db72f60 --- /dev/null +++ b/src/main.c @@ -0,0 +1,33 @@ +#include "book.h" +#include "draw.h" +#include "config.h" +#include "interface.h" +#include "sheet.h" +#include +#include +#include + + +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; +} diff --git a/src/sheet.c b/src/sheet.c new file mode 100644 index 0000000..5bfc5d8 --- /dev/null +++ b/src/sheet.c @@ -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); +} diff --git a/src/sheet.h b/src/sheet.h new file mode 100644 index 0000000..2bd9f5e --- /dev/null +++ b/src/sheet.h @@ -0,0 +1,20 @@ +#ifndef SHEET_H +#define SHEET_H + +#include +#include +#include +#include +#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 \ No newline at end of file