This commit is contained in:
2026-03-05 23:46:48 +01:00
commit 64ee43df24
8 changed files with 578 additions and 0 deletions

4
.editorconfig Normal file
View File

@@ -0,0 +1,4 @@
[*]
root = true
indent_style = space
indent_size = 4

7
.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
.DS_Store
.idea
*.log
tmp/
build/
.cache/

55
CMakeLists.txt Normal file
View File

@@ -0,0 +1,55 @@
cmake_minimum_required(VERSION 3.23)
project(editor)
include(FetchContent)
include(FindPkgConfig)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
FetchContent_Declare(
raylib
GIT_REPOSITORY https://github.com/raysan5/raylib.git
GIT_TAG 5.5
)
FetchContent_Declare(
sds
GIT_REPOSITORY https://github.com/antirez/sds.git
GIT_TAG 2.0.0
)
FetchContent_MakeAvailable(raylib)
FetchContent_MakeAvailable(sds)
if(NOT TARGET sds)
add_library(sds STATIC "${sds_SOURCE_DIR}/sds.c")
target_include_directories(sds PUBLIC "${sds_SOURCE_DIR}")
endif()
find_package(Freetype REQUIRED)
pkg_check_modules(HARFBUZZ REQUIRED harfbuzz)
add_executable(editor)
target_compile_features(editor PRIVATE c_std_11)
set_property(TARGET editor PROPERTY C_EXTENSIONS OFF)
target_sources(editor
PRIVATE
main.c
editor.c
)
target_compile_options(editor PRIVATE -Wall -Werror)
target_compile_options(editor PRIVATE ${HARFBUZZ_CFLAGS_OTHER})
target_include_directories(editor PRIVATE ${HARFBUZZ_INCLUDE_DIRS})
target_link_libraries(editor
PRIVATE
raylib
sds
Freetype::Freetype
${HARFBUZZ_LIBRARIES}
)

7
TODO.org Normal file
View File

@@ -0,0 +1,7 @@
#+title: Todo
* TODOs
** TODO render cursor
** TODO render better font (SDF?)
** TODO find functional language to embed
*** [?] do I need to write my own VM?

5
editor.c Normal file
View File

@@ -0,0 +1,5 @@
#include "editor.h"
EditorState init_editor_state(sds init_text) {
return (EditorState){.cursor_pos = 4, .text = init_text};
}

17
editor.h Normal file
View File

@@ -0,0 +1,17 @@
#ifndef EDITOR_H_
#define EDITOR_H_
#include "sds.h"
#include <stdint.h>
typedef struct {
uint32_t cursor_pos;
sds text;
} EditorState;
/*
* initializes the editor state
*/
EditorState init_editor_state(sds init_text);
#endif // EDITOR_H_

32
main.c Normal file
View File

@@ -0,0 +1,32 @@
#include "editor.h"
#include "raylib.h"
#include <stdint.h>
#include <stdio.h>
EditorState editor_state;
Font editor_font;
void ed_load_font() {}
int main() {
sds init_text = sdsnew("Das ist nur ein Test String🙂!");
editor_state = init_editor_state(init_text);
printf("%s\n", editor_state.text);
InitWindow(800, 600, "editor");
while (!WindowShouldClose()) {
BeginDrawing();
ClearBackground(BLACK);
DrawText(editor_state.text, 8, 8, 18, RAYWHITE);
DrawRectangle(8 * editor_state.cursor_pos + 8, 8, 2, 18, GREEN);
EndDrawing();
}
CloseWindow();
return 0;
}

451
vendor/arena.h vendored Normal file
View File

@@ -0,0 +1,451 @@
// Copyright 2022 Alexey Kutepov <reximkut@gmail.com>
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#ifndef ARENA_H_
#define ARENA_H_
#include <stddef.h>
#include <stdint.h>
#ifndef ARENA_NOSTDIO
#include <stdarg.h>
#include <stdio.h>
#endif // ARENA_NOSTDIO
#ifndef ARENA_ASSERT
#include <assert.h>
#define ARENA_ASSERT assert
#endif
#define ARENA_BACKEND_LIBC_MALLOC 0
#define ARENA_BACKEND_LINUX_MMAP 1
#define ARENA_BACKEND_WIN32_VIRTUALALLOC 2
#define ARENA_BACKEND_WASM_HEAPBASE 3
#ifndef ARENA_BACKEND
#define ARENA_BACKEND ARENA_BACKEND_LIBC_MALLOC
#endif // ARENA_BACKEND
typedef struct Region Region;
struct Region {
Region *next;
size_t count;
size_t capacity;
uintptr_t data[];
};
typedef struct {
Region *begin, *end;
} Arena;
typedef struct {
Region *region;
size_t count;
} Arena_Mark;
#ifndef ARENA_REGION_DEFAULT_CAPACITY
#define ARENA_REGION_DEFAULT_CAPACITY (8*1024)
#endif // ARENA_REGION_DEFAULT_CAPACITY
Region *new_region(size_t capacity);
void free_region(Region *r);
void *arena_alloc(Arena *a, size_t size_bytes);
void *arena_realloc(Arena *a, void *oldptr, size_t oldsz, size_t newsz);
char *arena_strdup(Arena *a, const char *cstr);
void *arena_memdup(Arena *a, void *data, size_t size);
void *arena_memcpy(void *dest, const void *src, size_t n);
#ifndef ARENA_NOSTDIO
char *arena_sprintf(Arena *a, const char *format, ...);
char *arena_vsprintf(Arena *a, const char *format, va_list args);
#endif // ARENA_NOSTDIO
Arena_Mark arena_snapshot(Arena *a);
void arena_reset(Arena *a);
void arena_rewind(Arena *a, Arena_Mark m);
void arena_free(Arena *a);
void arena_trim(Arena *a);
#ifndef ARENA_DA_INIT_CAP
#define ARENA_DA_INIT_CAP 256
#endif // ARENA_DA_INIT_CAP
#ifdef __cplusplus
#define cast_ptr(ptr) (decltype(ptr))
#else
#define cast_ptr(...)
#endif
#define arena_da_append(a, da, item) \
do { \
if ((da)->count >= (da)->capacity) { \
size_t new_capacity = (da)->capacity == 0 ? ARENA_DA_INIT_CAP : (da)->capacity*2; \
(da)->items = cast_ptr((da)->items)arena_realloc( \
(a), (da)->items, \
(da)->capacity*sizeof(*(da)->items), \
new_capacity*sizeof(*(da)->items)); \
(da)->capacity = new_capacity; \
} \
\
(da)->items[(da)->count++] = (item); \
} while (0)
// Append several items to a dynamic array
#define arena_da_append_many(a, da, new_items, new_items_count) \
do { \
if ((da)->count + (new_items_count) > (da)->capacity) { \
size_t new_capacity = (da)->capacity; \
if (new_capacity == 0) new_capacity = ARENA_DA_INIT_CAP; \
while ((da)->count + (new_items_count) > new_capacity) new_capacity *= 2; \
(da)->items = cast_ptr((da)->items)arena_realloc( \
(a), (da)->items, \
(da)->capacity*sizeof(*(da)->items), \
new_capacity*sizeof(*(da)->items)); \
(da)->capacity = new_capacity; \
} \
arena_memcpy((da)->items + (da)->count, (new_items), (new_items_count)*sizeof(*(da)->items)); \
(da)->count += (new_items_count); \
} while (0)
// Append a sized buffer to a string builder
#define arena_sb_append_buf arena_da_append_many
// Append a NULL-terminated string to a string builder
#define arena_sb_append_cstr(a, sb, cstr) \
do { \
const char *s = (cstr); \
size_t n = arena_strlen(s); \
arena_da_append_many(a, sb, s, n); \
} while (0)
// Append a single NULL character at the end of a string builder. So then you can
// use it a NULL-terminated C string
#define arena_sb_append_null(a, sb) arena_da_append(a, sb, 0)
#endif // ARENA_H_
#ifdef ARENA_IMPLEMENTATION
#if ARENA_BACKEND == ARENA_BACKEND_LIBC_MALLOC
#include <stdlib.h>
// TODO: instead of accepting specific capacity new_region() should accept the size of the object we want to fit into the region
// It should be up to new_region() to decide the actual capacity to allocate
Region *new_region(size_t capacity)
{
size_t size_bytes = sizeof(Region) + sizeof(uintptr_t)*capacity;
// TODO: it would be nice if we could guarantee that the regions are allocated by ARENA_BACKEND_LIBC_MALLOC are page aligned
Region *r = (Region*)malloc(size_bytes);
ARENA_ASSERT(r); // TODO: since ARENA_ASSERT is disableable go through all the places where we use it to check for failed memory allocation and return with NULL there.
r->next = NULL;
r->count = 0;
r->capacity = capacity;
return r;
}
void free_region(Region *r)
{
free(r);
}
#elif ARENA_BACKEND == ARENA_BACKEND_LINUX_MMAP
#include <unistd.h>
#include <sys/mman.h>
Region *new_region(size_t capacity)
{
size_t size_bytes = sizeof(Region) + sizeof(uintptr_t) * capacity;
Region *r = mmap(NULL, size_bytes, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
ARENA_ASSERT(r != MAP_FAILED);
r->next = NULL;
r->count = 0;
r->capacity = capacity;
return r;
}
void free_region(Region *r)
{
size_t size_bytes = sizeof(Region) + sizeof(uintptr_t) * r->capacity;
int ret = munmap(r, size_bytes);
ARENA_ASSERT(ret == 0);
}
#elif ARENA_BACKEND == ARENA_BACKEND_WIN32_VIRTUALALLOC
#if !defined(_WIN32)
# error "Current platform is not Windows"
#endif
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#define INV_HANDLE(x) (((x) == NULL) || ((x) == INVALID_HANDLE_VALUE))
Region *new_region(size_t capacity)
{
SIZE_T size_bytes = sizeof(Region) + sizeof(uintptr_t) * capacity;
Region *r = VirtualAllocEx(
GetCurrentProcess(), /* Allocate in current process address space */
NULL, /* Unknown position */
size_bytes, /* Bytes to allocate */
MEM_COMMIT | MEM_RESERVE, /* Reserve and commit allocated page */
PAGE_READWRITE /* Permissions ( Read/Write )*/
);
if (INV_HANDLE(r))
ARENA_ASSERT(0 && "VirtualAllocEx() failed.");
r->next = NULL;
r->count = 0;
r->capacity = capacity;
return r;
}
void free_region(Region *r)
{
if (INV_HANDLE(r))
return;
BOOL free_result = VirtualFreeEx(
GetCurrentProcess(), /* Deallocate from current process address space */
(LPVOID)r, /* Address to deallocate */
0, /* Bytes to deallocate ( Unknown, deallocate entire page ) */
MEM_RELEASE /* Release the page ( And implicitly decommit it ) */
);
if (FALSE == free_result)
ARENA_ASSERT(0 && "VirtualFreeEx() failed.");
}
#elif ARENA_BACKEND == ARENA_BACKEND_WASM_HEAPBASE
// Stolen from https://surma.dev/things/c-to-webassembly/
extern unsigned char __heap_base;
// Since ARENA_BACKEND_WASM_HEAPBASE entirely hijacks __heap_base it is expected that no other means of memory
// allocation are used except the arenas.
unsigned char* bump_pointer = &__heap_base;
// TODO: provide a way to deallocate all the arenas at once by setting bump_pointer back to &__heap_base?
// __builtin_wasm_memory_size and __builtin_wasm_memory_grow are defined in units of page sizes
#define ARENA_WASM_PAGE_SIZE (64*1024)
Region *new_region(size_t capacity)
{
size_t size_bytes = sizeof(Region) + sizeof(uintptr_t)*capacity;
Region *r = (void*)bump_pointer;
// grow memory brk() style
size_t current_memory_size = ARENA_WASM_PAGE_SIZE * __builtin_wasm_memory_size(0);
size_t desired_memory_size = (size_t) bump_pointer + size_bytes;
if (desired_memory_size > current_memory_size) {
size_t delta_bytes = desired_memory_size - current_memory_size;
size_t delta_pages = (delta_bytes + (ARENA_WASM_PAGE_SIZE - 1))/ARENA_WASM_PAGE_SIZE;
if (__builtin_wasm_memory_grow(0, delta_pages) < 0) {
ARENA_ASSERT(0 && "memory.grow failed");
return NULL;
}
}
bump_pointer += size_bytes;
r->next = NULL;
r->count = 0;
r->capacity = capacity;
return r;
}
void free_region(Region *r)
{
// Since ARENA_BACKEND_WASM_HEAPBASE uses a primitive bump allocator to
// allocate the regions, free_region() does nothing. It is generally
// not recommended to free arenas anyway since it is better to keep
// reusing already allocated memory with arena_reset().
(void) r;
}
#else
# error "Unknown Arena backend"
#endif
// TODO: add debug statistic collection mode for arena
// Should collect things like:
// - How many times new_region was called
// - How many times existing region was skipped
// - How many times allocation exceeded ARENA_REGION_DEFAULT_CAPACITY
void *arena_alloc(Arena *a, size_t size_bytes)
{
size_t size = (size_bytes + sizeof(uintptr_t) - 1)/sizeof(uintptr_t);
if (a->end == NULL) {
ARENA_ASSERT(a->begin == NULL);
size_t capacity = ARENA_REGION_DEFAULT_CAPACITY;
if (capacity < size) capacity = size;
a->end = new_region(capacity);
a->begin = a->end;
}
while (a->end->count + size > a->end->capacity && a->end->next != NULL) {
a->end = a->end->next;
}
if (a->end->count + size > a->end->capacity) {
ARENA_ASSERT(a->end->next == NULL);
size_t capacity = ARENA_REGION_DEFAULT_CAPACITY;
if (capacity < size) capacity = size;
a->end->next = new_region(capacity);
a->end = a->end->next;
}
void *result = &a->end->data[a->end->count];
a->end->count += size;
return result;
}
void *arena_realloc(Arena *a, void *oldptr, size_t oldsz, size_t newsz)
{
if (newsz <= oldsz) return oldptr;
void *newptr = arena_alloc(a, newsz);
char *newptr_char = (char*)newptr;
char *oldptr_char = (char*)oldptr;
for (size_t i = 0; i < oldsz; ++i) {
newptr_char[i] = oldptr_char[i];
}
return newptr;
}
size_t arena_strlen(const char *s)
{
size_t n = 0;
while (*s++) n++;
return n;
}
void *arena_memcpy(void *dest, const void *src, size_t n)
{
char *d = dest;
const char *s = src;
for (; n; n--) *d++ = *s++;
return dest;
}
char *arena_strdup(Arena *a, const char *cstr)
{
size_t n = arena_strlen(cstr);
char *dup = (char*)arena_alloc(a, n + 1);
arena_memcpy(dup, cstr, n);
dup[n] = '\0';
return dup;
}
void *arena_memdup(Arena *a, void *data, size_t size)
{
return arena_memcpy(arena_alloc(a, size), data, size);
}
#ifndef ARENA_NOSTDIO
char *arena_vsprintf(Arena *a, const char *format, va_list args)
{
va_list args_copy;
va_copy(args_copy, args);
int n = vsnprintf(NULL, 0, format, args_copy);
va_end(args_copy);
ARENA_ASSERT(n >= 0);
char *result = (char*)arena_alloc(a, n + 1);
vsnprintf(result, n + 1, format, args);
return result;
}
char *arena_sprintf(Arena *a, const char *format, ...)
{
va_list args;
va_start(args, format);
char *result = arena_vsprintf(a, format, args);
va_end(args);
return result;
}
#endif // ARENA_NOSTDIO
Arena_Mark arena_snapshot(Arena *a)
{
Arena_Mark m;
if(a->end == NULL){ //snapshot of uninitialized arena
ARENA_ASSERT(a->begin == NULL);
m.region = a->end;
m.count = 0;
}else{
m.region = a->end;
m.count = a->end->count;
}
return m;
}
void arena_reset(Arena *a)
{
for (Region *r = a->begin; r != NULL; r = r->next) {
r->count = 0;
}
a->end = a->begin;
}
void arena_rewind(Arena *a, Arena_Mark m)
{
if(m.region == NULL){ //snapshot of uninitialized arena
arena_reset(a); //leave allocation
return;
}
m.region->count = m.count;
for (Region *r = m.region->next; r != NULL; r = r->next) {
r->count = 0;
}
a->end = m.region;
}
void arena_free(Arena *a)
{
Region *r = a->begin;
while (r) {
Region *r0 = r;
r = r->next;
free_region(r0);
}
a->begin = NULL;
a->end = NULL;
}
void arena_trim(Arena *a){
Region *r = a->end->next;
while (r) {
Region *r0 = r;
r = r->next;
free_region(r0);
}
a->end->next = NULL;
}
#endif // ARENA_IMPLEMENTATION