From d940df2869d012849c9332be2c31a938eabc3d11 Mon Sep 17 00:00:00 2001 From: sfja Date: Fri, 4 Jul 2025 02:48:44 +0200 Subject: [PATCH] init --- .clang-format | 19 +++++ .gitignore | 2 + Makefile | 27 +++++++ app.c | 122 ++++++++++++++++++++++++++++++++ app.h | 29 ++++++++ app_ressources.h | 28 ++++++++ buffer.c | 69 ++++++++++++++++++ buffer.h | 18 +++++ compile_flags.txt | 10 +++ editor.c | 170 ++++++++++++++++++++++++++++++++++++++++++++ editor.h | 26 +++++++ init.patch | 175 ++++++++++++++++++++++++++++++++++++++++++++++ main.c | 122 ++++++++++++++++++++++++++++++++ 13 files changed, 817 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 app.c create mode 100644 app.h create mode 100644 app_ressources.h create mode 100644 buffer.c create mode 100644 buffer.h create mode 100644 compile_flags.txt create mode 100644 editor.c create mode 100644 editor.h create mode 100644 init.patch create mode 100644 main.c diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..d04c6be --- /dev/null +++ b/.clang-format @@ -0,0 +1,19 @@ +Language: Cpp +BasedOnStyle: WebKit +IndentWidth: 4 +ColumnLimit: 80 +IndentCaseLabels: true +InsertNewlineAtEOF: true +AllowShortFunctionsOnASingleLine: None + +AlignAfterOpenBracket: AlwaysBreak + +BinPackArguments: false +AllowAllArgumentsOnNextLine: true + +# BinPackParameters: OnePerLine # llvm >= 20 +BinPackParameters: true +AllowAllParametersOfDeclarationOnNextLine: true +PenaltyReturnTypeOnItsOwnLine: 1000 # abitrary, seems to work + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..55725dd --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +fed + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..fe2e849 --- /dev/null +++ b/Makefile @@ -0,0 +1,27 @@ + +CFLAGS = -std=c23 +CFLAGS += -fsanitize=address,undefined -g +CFLAGS += -Wall -Wextra -Wpedantic -Wconversion -pedantic -pedantic-errors + +LFLAGS = -lm + +CFLAGS += $(shell pkg-config sdl2 SDL2_ttf --cflags) +LFLAGS += $(shell pkg-config sdl2 SDL2_ttf --libs) + +CFILES = \ + main.c \ + app.c \ + buffer.c \ + editor.c \ + +HFILES = $(shell find . -name "*.h") + +TARGET=fed + +$(TARGET): $(CFILES) $(HFILES) + gcc -o $@ $(CFILES) $(CFLAGS) $(LFLAGS) + +clean: + rm -rf $(TARGET) + + diff --git a/app.c b/app.c new file mode 100644 index 0000000..1a9c52c --- /dev/null +++ b/app.c @@ -0,0 +1,122 @@ +#include "app.h" +#include "editor.h" +#include +#include +#include + +static void ptr_vec_push( + void*** vec, size_t* capacity, size_t* size, void* element) +{ + if (*size + 1 > *capacity) { + *capacity *= 2; + *vec = realloc(*vec, *capacity * sizeof(void*)); + } + (*vec)[*size] = element; + *size += 1; +} + +void app_init(App* app) +{ + const size_t buffers_initial_capacity = 16; + + *app = (App) { + .buffers = malloc(buffers_initial_capacity), + .buffers_capacity = buffers_initial_capacity, + .buffers_size = 0, + .res = (AppRessources) { + .font = nullptr, + .char_size = {}, + }, + .editor = {}, + }; + editor_init(&app->editor, 1000, 1000, &app->res); +} + +void app_deinit(App* app) +{ + if (app->res.font) + TTF_CloseFont(app->res.font); + for (size_t i = 0; i < app->buffers_size; ++i) { + buffer_deinit(app->buffers[i]); + free(app->buffers[i]); + } + free(app->buffers); +} + +int app_ressources_load(App* app) +{ + app->res.font + = TTF_OpenFont("/usr/share/fonts/TTF/FiraCodeNerdFont-Medium.ttf", 16); + if (!app->res.font) { + fprintf(stderr, "error: cannot open font: %s\n", SDL_GetError()); + return -1; + } + app->res.char_size = text_size("A", app->res.font); + app->res.theme = defaultTheme; + + return 0; +} + +void app_ressources_unload(App* app) +{ + if (app->res.font) { + TTF_CloseFont(app->res.font); + app->res.font = nullptr; + } +} + +void app_new_file(App* app) +{ + Buffer* buffer = malloc(sizeof(Buffer)); + buffer_init(buffer); + + ptr_vec_push( + (void***)&app->buffers, + &app->buffers_capacity, + &app->buffers_size, + buffer); +} + +void app_open_file(App* app, const char* path) +{ + Buffer* buffer = malloc(sizeof(Buffer)); + buffer_from_file(buffer, path); + + ptr_vec_push( + (void***)&app->buffers, + &app->buffers_capacity, + &app->buffers_size, + buffer); +} + +void app_prepare(App* app) +{ + if (app->buffers_size == 0) { + app_new_file(app); + } + editor_open(&app->editor, app->buffers[app->buffers_size - 1]); +} + +void app_handle_keydown(App* app, int keysum) +{ + + switch (keysum) { + case SDLK_UP: + editor_cursor_up(&app->editor); + break; + case SDLK_DOWN: + editor_cursor_down(&app->editor); + break; + case SDLK_LEFT: + editor_cursor_left(&app->editor); + break; + case SDLK_RIGHT: + editor_cursor_right(&app->editor); + break; + } +} + +void app_render(App* app, void* renderer) +{ + editor_render(&app->editor, renderer, 0, 0); +} diff --git a/app.h b/app.h new file mode 100644 index 0000000..2d487ec --- /dev/null +++ b/app.h @@ -0,0 +1,29 @@ +#ifndef APP_H +#define APP_H + +#include "app_ressources.h" +#include "buffer.h" +#include "editor.h" +#include + +typedef struct { + Buffer** buffers; + size_t buffers_capacity; + size_t buffers_size; + AppRessources res; + Editor editor; +} App; + +void app_init(App* app); +void app_deinit(App* app); +int app_ressources_load(App* app); +void app_ressources_unload(App* app); +void app_new_file(App* app); +void app_open_file(App* app, const char* path); +void app_prepare(App* app); +void app_handle_keydown(App* app, int keysum); +void app_render(App* app, void* renderer); + +extern const Theme defaultTheme; + +#endif diff --git a/app_ressources.h b/app_ressources.h new file mode 100644 index 0000000..61934b4 --- /dev/null +++ b/app_ressources.h @@ -0,0 +1,28 @@ +#ifndef APP_RESSOURCES_H +#define APP_RESSOURCES_H + +#include + +typedef struct { + int width, height; +} TextSize; + +TextSize text_size(const char* text, void* font); + +typedef struct { + uint8_t r, g, b, a; +} Color; + +typedef struct { + Color foreground; + Color background; + Color linenumber; +} Theme; + +typedef struct { + void* font; + TextSize char_size; + Theme theme; +} AppRessources; + +#endif diff --git a/buffer.c b/buffer.c new file mode 100644 index 0000000..7025233 --- /dev/null +++ b/buffer.c @@ -0,0 +1,69 @@ +#include "buffer.h" +#include +#include +#include +#include + +static int file_read( + char** text, size_t* text_capacity, size_t* text_length, const char* path) +{ + FILE* file = fopen(path, "r"); + if (!file) { + fprintf(stderr, "error: could not open file '%s'\n", path); + return -1; + } + + fseek(file, 0, SEEK_END); + size_t file_size = (size_t)ftell(file); + rewind(file); + + size_t initial_capacity = 8; + while (initial_capacity < file_size + 1) + initial_capacity *= 2; + + *text = calloc(initial_capacity, sizeof(char)); + *text_capacity = initial_capacity; + *text_length = file_size; + + size_t bytes_read = fread(*text, sizeof(char), file_size, file); + if (bytes_read != file_size) { + fprintf(stderr, "error: could not read file '%s'\n", path); + return -1; + } + + return 0; +} + +void buffer_init(Buffer* buffer) +{ + const size_t text_initial_capacity = 8; + + *buffer = (Buffer) { + .path = nullptr, + .text = calloc(1, text_initial_capacity), + .text_capacity = text_initial_capacity, + .text_length = 0, + .changed = false, + }; +} + +int buffer_from_file(Buffer* buffer, const char* path) +{ + *buffer = (Buffer) { + .path = strdup(path), + .text = nullptr, + .text_capacity = 0, + .text_length = 0, + .changed = false, + }; + int status = file_read( + &buffer->text, &buffer->text_capacity, &buffer->text_length, path); + return status; +} + +void buffer_deinit(Buffer* buffer) +{ + if (buffer->path) + free(buffer->path); + free(buffer->text); +} diff --git a/buffer.h b/buffer.h new file mode 100644 index 0000000..a075e34 --- /dev/null +++ b/buffer.h @@ -0,0 +1,18 @@ +#ifndef BUFFER_H +#define BUFFER_H + +#include + +typedef struct { + char* path; + char* text; + size_t text_capacity; + size_t text_length; + bool changed; +} Buffer; + +void buffer_init(Buffer* buffer); +int buffer_from_file(Buffer* buffer, const char* path); +void buffer_deinit(Buffer* buffer); + +#endif diff --git a/compile_flags.txt b/compile_flags.txt new file mode 100644 index 0000000..7672925 --- /dev/null +++ b/compile_flags.txt @@ -0,0 +1,10 @@ +-xc +-std=c23 +-Wall +-Wextra +-Wpedantic +-Wconversion +-pedantic +-pedantic-errors +-Wno-unused-variable + diff --git a/editor.c b/editor.c new file mode 100644 index 0000000..36f4e6f --- /dev/null +++ b/editor.c @@ -0,0 +1,170 @@ +#include "editor.h" +#include +#include + +void render_text( + SDL_Renderer* renderer, const char* text, TTF_Font* font, int x, int y, + SDL_Color fg, SDL_Color bg); + +void editor_init( + Editor* editor, int width, int height, AppRessources* ressources) +{ + *editor = (Editor) { + .buffer = nullptr, + width, + height, + .cursor_idx = 0, + .cursor_line = 1, + .cursor_col = 1, + ressources, + }; +} + +void editor_open(Editor* editor, Buffer* buffer) +{ + editor->buffer = buffer; + editor->cursor_idx = 0; + editor->cursor_line = 1; + editor->cursor_col = 1; +} + +static int cols_at_idx(Editor* editor, size_t idx) +{ + Buffer* buffer = editor->buffer; + + int line_start = (int)idx; + while (line_start > 0 && buffer->text[line_start] != '\n') { + line_start -= 1; + } + int line_end = (int)idx + 1; + while (line_end < (int)buffer->text_length + && buffer->text[line_end] != '\n') { + line_end += 1; + } + printf("idx = %ld, cols = %d\n", idx, line_end - line_start); + return line_end - line_start; +} + +static void cursor_increment(Editor* editor) +{ + if (editor->cursor_idx >= editor->buffer->text_length) + return; + if (editor->buffer->text[editor->cursor_idx] == '\n') { + editor->cursor_line += 1; + editor->cursor_col = 1; + } else { + editor->cursor_col += 1; + } + editor->cursor_idx += 1; +} + +static void cursor_decrement(Editor* editor) +{ + if (editor->cursor_idx <= 0) + return; + editor->cursor_idx -= 1; + if (editor->buffer->text[editor->cursor_idx] == '\n') { + editor->cursor_line -= 1; + editor->cursor_col = cols_at_idx(editor, editor->cursor_idx); + } else { + editor->cursor_col -= 1; + } +} + +void editor_cursor_up(Editor* editor) +{ + if (editor->cursor_line == 1) + return; + int originalLine = editor->cursor_line; + int originalCol = editor->cursor_col; + while (editor->cursor_line >= originalLine + || editor->cursor_col > originalCol) { + cursor_decrement(editor); + } +} + +void editor_cursor_down(Editor* editor) +{ + int originalLine = editor->cursor_line; + int originalCol = editor->cursor_col; + while (editor->cursor_line <= originalLine + || editor->cursor_col > originalCol) { + cursor_increment(editor); + } +} + +void editor_cursor_left(Editor* editor) +{ +} + +void editor_cursor_right(Editor* editor) +{ +} + +void editor_render(Editor* editor, void* renderer, int x, int y) +{ + Theme* theme = &editor->ressources->theme; + SDL_Color fg = *(SDL_Color*)&theme->foreground; + SDL_Color bg = *(SDL_Color*)&theme->background; + + SDL_SetRenderDrawColor(renderer, bg.r, bg.g, bg.b, bg.a); + SDL_RenderFillRect( + renderer, &(SDL_Rect) { 0, 0, editor->width, editor->height }); + + TextSize char_size = editor->ressources->char_size; + + Buffer* buffer = editor->buffer; + + size_t i = 0; + int line = 1; + int offset_y = 0; + + while (i < buffer->text_length) { + size_t line_start = i; + while (i < buffer->text_length && buffer->text[i] != '\n') { + i += 1; + } + + char line_number[16] = ""; + snprintf(line_number, 15, "%4d", line); + + TextSize ln_size = text_size(line_number, editor->ressources->font); + render_text( + renderer, + line_number, + editor->ressources->font, + x, + y + offset_y, + *(SDL_Color*)&theme->linenumber, + bg); + + char* line_text = strndup(&buffer->text[line_start], i - line_start); + TextSize line_size = text_size(line_text, editor->ressources->font); + render_text( + renderer, + line_text, + editor->ressources->font, + x + ln_size.width + 8, + y + offset_y, + fg, + bg); + free(line_text); + + offset_y += line_size.height; + + if (i < buffer->text_length && buffer->text[i] == '\n') { + i += 1; + } + line += 1; + } + + SDL_SetRenderDrawColor(renderer, fg.r, fg.g, fg.b, fg.a); + SDL_RenderFillRect( + renderer, + &(SDL_Rect) { + x + char_size.width * (editor->cursor_col - 1 + 4) + 8 - 1, + y + char_size.height * (editor->cursor_line - 1), + 2, + char_size.height, + }); +} diff --git a/editor.h b/editor.h new file mode 100644 index 0000000..430d851 --- /dev/null +++ b/editor.h @@ -0,0 +1,26 @@ +#ifndef EDITOR_H +#define EDITOR_H + +#include "app_ressources.h" +#include "buffer.h" + +typedef struct { + Buffer* buffer; + int width; + int height; + size_t cursor_idx; + int cursor_line; + int cursor_col; + AppRessources* ressources; +} Editor; + +void editor_init( + Editor* editor, int width, int height, AppRessources* ressources); +void editor_open(Editor* editor, Buffer* buffer); +void editor_cursor_up(Editor* editor); +void editor_cursor_down(Editor* editor); +void editor_cursor_left(Editor* editor); +void editor_cursor_right(Editor* editor); +void editor_render(Editor* editor, void* renderer, int x, int y); + +#endif diff --git a/init.patch b/init.patch new file mode 100644 index 0000000..97afb9e --- /dev/null +++ b/init.patch @@ -0,0 +1,175 @@ +diff --git a/.clang-format b/.clang-format +new file mode 100644 +index 0000000..a56cbd0 +--- /dev/null ++++ b/.clang-format +@@ -0,0 +1,14 @@ ++Language: Cpp ++BasedOnStyle: WebKit ++IndentWidth: 4 ++ColumnLimit: 80 ++IndentCaseLabels: true ++InsertNewlineAtEOF: true ++AllowShortFunctionsOnASingleLine: None ++ ++BinPackArguments: false ++AllowAllArgumentsOnNextLine: true ++ ++BinPackParameters: false ++AllowAllParametersOfDeclarationOnNextLine: true ++ +diff --git a/.gitignore b/.gitignore +new file mode 100644 +index 0000000..1fd9dda +--- /dev/null ++++ b/.gitignore +@@ -0,0 +1,2 @@ ++./game ++ +diff --git a/Makefile b/Makefile +new file mode 100644 +index 0000000..3fef9c2 +--- /dev/null ++++ b/Makefile +@@ -0,0 +1,17 @@ ++ ++CFLAGS = -std=c23 ++CFLAGS += -fsanitize=address,undefined -g ++CFLAGS += -Wall -Wextra -Wpedantic -Wconversion -pedantic -pedantic-errors ++ ++LFLAGS = ++ ++CFLAGS += $(shell pkg-config sdl2 --cflags) ++LFLAGS += $(shell pkg-config sdl2 --libs) ++ ++game: main.c ++ gcc -o $@ $^ $(CFLAGS) $(LFLAGS) ++ ++clean: ++ rm -rf game ++ ++ +diff --git a/compile_flags.txt b/compile_flags.txt +new file mode 100644 +index 0000000..7672925 +--- /dev/null ++++ b/compile_flags.txt +@@ -0,0 +1,10 @@ ++-xc ++-std=c23 ++-Wall ++-Wextra ++-Wpedantic ++-Wconversion ++-pedantic ++-pedantic-errors ++-Wno-unused-variable ++ +diff --git a/main.c b/main.c +new file mode 100644 +index 0000000..879ae5d +--- /dev/null ++++ b/main.c +@@ -0,0 +1,102 @@ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++uint32_t be_to_le_u32(uint32_t be_value); ++uint32_t rgba(uint32_t color); ++uint32_t rgb(uint32_t color); ++ ++int main(void) ++{ ++ ++ SDL_Init(SDL_INIT_VIDEO); ++ ++ SDL_Window* window; ++ SDL_Renderer* renderer; ++ ++ SDL_CreateWindowAndRenderer(512, 512, 0, &window, &renderer); ++ // SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); ++ ++ SDL_Texture* buffer = SDL_CreateTexture(renderer, ++ SDL_PIXELFORMAT_RGBA32, ++ SDL_TEXTUREACCESS_STREAMING, ++ 512, ++ 512); ++ ++ SDL_SetTextureBlendMode(buffer, SDL_BLENDMODE_BLEND); ++ ++ while (true) { ++ ++ SDL_Event event; ++ while (SDL_PollEvent(&event)) { ++ switch (event.type) { ++ case SDL_QUIT: ++ goto exit_game; ++ case SDL_KEYDOWN: { ++ switch (event.key.keysym.sym) { ++ case SDLK_ESCAPE: ++ goto exit_game; ++ } ++ } ++ } ++ } ++ ++ SDL_RenderClear(renderer); ++ ++ uint32_t* pixels; ++ int row_size; ++ SDL_LockTexture( ++ buffer, &(SDL_Rect) { 0, 0, 512, 512 }, (void**)&pixels, &row_size); ++ row_size /= sizeof(uint32_t); ++ ++ for (int y = 0; y < 100; ++y) { ++ for (int x = 0; x < 100; ++x) { ++ pixels[(y + 100) * row_size + x + 100] = rgb(0xff0000); ++ } ++ } ++ ++ SDL_UnlockTexture(buffer); ++ ++ SDL_RenderCopy(renderer, buffer, nullptr, nullptr); ++ ++ SDL_RenderPresent(renderer); ++ ++ SDL_Delay(16); ++ } ++ ++exit_game:; ++ printf("exitting...\n"); ++ ++ SDL_DestroyRenderer(renderer); ++ SDL_DestroyWindow(window); ++ SDL_Quit(); ++} ++ ++uint32_t be_to_le_u32(uint32_t be_value) ++{ ++ uint32_t le_value = 0; ++ le_value |= (be_value & 0xff) << 24; ++ le_value |= (be_value >> 8 & 0xff) << 16; ++ le_value |= (be_value >> 16 & 0xff) << 8; ++ le_value |= be_value >> 24 & 0xff; ++ return le_value; ++} ++ ++uint32_t rgba(uint32_t color) ++{ ++ return be_to_le_u32(color); ++} ++ ++uint32_t rgb(uint32_t color) ++{ ++ return rgba(color << 8 | 0xff); ++} diff --git a/main.c b/main.c new file mode 100644 index 0000000..78de710 --- /dev/null +++ b/main.c @@ -0,0 +1,122 @@ +#include "app.h" +#include "app_ressources.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const Theme defaultTheme = { + .foreground = { 0xfb, 0xf1, 0xc7, 0xff }, + .background = { 0x28, 0x28, 0x28, 0xff }, + .linenumber = { 0x7c, 0x6f, 0x64, 0xff }, +}; + +TextSize text_size(const char* text, void* font) +{ + int w, h; + TTF_SizeText(font, text, &w, &h); + return (TextSize) { w, h }; +} + +void render_text( + SDL_Renderer* renderer, const char* text, TTF_Font* font, int x, int y, + SDL_Color fg, SDL_Color bg) +{ + + SDL_Surface* surface = TTF_RenderUTF8_Shaded(font, text, fg, bg); + + SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface); + + TextSize size = text_size(text, font); + SDL_RenderCopy( + renderer, + texture, + nullptr, + &(SDL_Rect) { x, y, size.width, size.height }); + + SDL_DestroyTexture(texture); + SDL_FreeSurface(surface); +} + +int main(int argc, char** argv) +{ + App app_instance; + App* app = &app_instance; + app_init(app); + + for (int i = 1; i < argc; ++i) { + app_open_file(app, argv[i]); + } + app_prepare(app); + + SDL_Init(SDL_INIT_VIDEO); + TTF_Init(); + + app_ressources_load(app); + + SDL_Window* window = SDL_CreateWindow( + "fed", + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + 1000, + 1000, + SDL_WINDOW_RESIZABLE); + + SDL_Renderer* renderer = SDL_CreateRenderer(window, 0, 0); + // SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_ADD); + + uint64_t before = SDL_GetTicks64(); + + while (true) { + uint64_t now = SDL_GetTicks64(); + uint64_t delta = now - before; + (void)delta; + + SDL_Event event; + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_QUIT: + goto exit_game; + case SDL_KEYDOWN: { + switch (event.key.keysym.sym) { + case SDLK_ESCAPE: + goto exit_game; + } + } + default: + app_handle_keydown(app, event.key.keysym.sym); + } + } + + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0); + SDL_RenderClear(renderer); + + app_render(app, renderer); + + SDL_RenderPresent(renderer); + SDL_Delay(16); + + before = now; + } + +exit_game:; + printf("exitting...\n"); + + app_ressources_unload(app); + + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + + TTF_Quit(); + SDL_Quit(); + + app_deinit(app); +}