< up >
2023-01-02

femto - project showcase

Femto is a minimal CLI text editor made in C with zero dependencies inspired by Antirez’s kilo editor. This retrospective contains all noteworthy progress made within this project starting from the idea until the ‘end’.

I’ve created this post about a year ago, but never published it due to missing content/relevant information. Coming back a year later I see this from a different perspective. This is a good rough overview before diving into the C source. The testing framework cilicon might be a good inspiration for unit-testing with C, too.

General information

Features:

Bugs:

Personal project outcome:

Motivation

Internal structure

Femto consists of seven components:

Session

The session type contains the state of the running application:

typedef struct {
    /* Name of the read file */
    char *filename;

    /* Dimensions of the terminal */
    TerminalSize terminal_size;

    /* Position on the screen */
    TerminalPosition cursor_position;

    /* Offset between file content and screen */
    TerminalPosition offset;

    /* Array of lines which hold the content */
    Line *lines;
    size_t line_count;

    /* Total length of all line lengths combined */
    size_t content_length;

    /* Dirty flag */
    bool dirty;
} Session;

Any action a user makes affects this state.

Screen

Buffer

A simple data+length structure with functions to write more convenient to the data:

typedef struct
{
    char *data;
    unsigned length;
}Buffer;

This struture is currently only used by the screen module as framebuffer.

Terminal

It’s purpose overlaps with the screen module. Both are somehow manipulating the terminal’s behaviour.

FileIO

FileIO uses the filename given in the session object and stores the file’s content into it.

UI

Handle dialogs including getting the users input, generating the right status bar and trigger a refresh. It comes with two dialogs:

Internally it has a generic dialog function which could be used to add more.

Helper

The helper has two major responisibilities:

The logger has a printf alike formatter function which automatically includes the module name and line number where the logger gets called. This is accomplished by hiding the real formatter function behind a preprocessor directive which adds the module name and line number in place.

cilicon - unit test library

Cilicon is a syntactic sugar library which ‘frames’ a unit test using concepts like ‘it’, ‘suite’ and ‘expect_…’ inspired by ruby’s rspec.

Each C module has it’s own test suite represented in an extra test file. For example the module buffer has it’s own test_suite_buffer with the file buffer_test.c:

void test_create_buffer()
{
    TEST_IT_NAME("creates a buffer");

    Buffer *buf = fe_create_buffer();
    if( ! expect_not_null( buf )) return;

    fe_free_buffer(buf);
    
    TEST_OK
}

void test_append_to_buffer()
{
    TEST_IT_NAME("appends to a buffer");
    const char *test_string = "const char *test_string";
    Buffer *buf = fe_create_buffer();
    
    if( ! expect_not_null( buf )) return;

    fe_append_to_buffer(buf, test_string, strlen(test_string));
    
    if( ! expect_b_eq( (void*) test_string, buf->data, strlen( test_string ), buf->length  )) goto cleanup;

    TEST_OK;

cleanup:
    fe_free_buffer(buf);
}

void test_suite_buffer()
{
    TEST_SUITE_NAME("Buffer");

    test_create_buffer();
    test_append_to_buffer();
}

Which outputs the following on success:

---- Buffer ----
it creates a buffer                      OK
it appends to a buffer                   OK

ALL PASSED

And on failure:

---- Buffer ----
it creates a buffer                      OK
it appends to a buffer                   FAIL
Expected equal buffers, got unequal.

1 of 2 expects failed.
FATALITY

Thoughts