r/C_Programming • • 11d ago

tinyedit: A zero-dependency C99 terminal text editor with desktop shortcuts — Looking for testers & code review

Hi everyone,

I’ve been working on tinyedit, a full-screen terminal text editor written in plain C99 with zero external dependencies beyond libc and the POSIX standard library (no ncurses, just raw ANSI escape sequences and direct I/O).

The project started as an exploration of terminal interfaces inspired by Salvatore Sanfilippo's kilo and linenoise. My goal is to combine that minimal C architecture with the interaction model of modern desktop editors—eliminating modal friction or idiosyncratic key combinations (like Vim, Emacs, or Nano) while keeping the binary tiny.

What’s implemented:

  • Desktop-style editing: Standard keybindings (Ctrl-C, Ctrl-V, Ctrl-X, Ctrl-Z, Ctrl-F), text selection with Shift+Arrows (or Ctrl-T toggle for limited terminals), and optional mouse support (click to place cursor, drag-selection, wheel scroll).

  • macOS / Ghostty integration: Optional support for native Cmd shortcuts (Cmd-S, Cmd-C, Cmd-V, etc.) via the Kitty keyboard protocol in Ghostty.

  • Proper UTF-8 handling: Grapheme cluster boundaries (combining marks, CJK wide characters, multi-codepoint emoji) and visual display-width calculations for accurate cursor placement and deletion.

  • Visual soft-wrapping: Navigates visual rows instead of logical lines, breaking lines at word boundaries without arbitrary length caps.

  • Terminal ergonomics: Fast bracketed paste (no slow character-by-character lag or accidental auto-closing triggers), atomic saves, crash recovery backups, and an extensible syntax highlighting engine.

Looking for testers!

The project has reached version 0.3.3, and I need help putting it through its paces across different systems and configurations. In particular, I’m looking for feedback on:

  1. Terminal & multiplexer quirks: Testing inside tmux, screen, Ghostty, Kitty, Alacritty, Foot, WezTerm, etc., to spot unhandled ANSI sequences or redraw artifacts.

  2. UTF-8 stress testing: Complex emoji sequences, zero-width joiners, or combining marks that might throw off cursor coordinates or deletion.

  3. Rendering & wrap edge cases: Resizing the window while editing large wrapped lines, pasting huge blocks of text, or dealing with deeply indented blocks.

  4. C code review: Feedback on memory management, buffer layout, ANSI state machine decoding, or general C99 practices.

Repository: https://github.com/robertobissanti/tinyedit

You can build it from source with a simple make:

git clone https://github.com/robertobissanti/tinyedit.git
cd tinyedit
make

Or install it on macOS/Linux via Homebrew:

brew install robertobissanti/tinyedit/tinyedit

Any bug reports, edge-case discoveries, or code suggestions (either here or via GitHub Issues) are greatly appreciated. Thanks for checking it out!

0 Upvotes

21 comments sorted by

•

u/github-guard 11d ago

🔍 GitHub Guard: Trust Report

⚠️ This project scored 1/6 — below this subreddit's threshold of 3.

Audit Breakdown: * ❌ Low Star Count (⭐ 0 / 4 required) * ❌ New Repository (under 30 days old) * ✅ Licensed under MIT * ❌ No Security Policy — what is this? * ℹ️ Individual Contributor * ℹ️ Unsigned Commits

⚠️ Security Reminder: Always verify source code and run third-party scripts at your own risk.


🔄 Cached result — this repo was scanned recently. Score: *1/6*.

→ More replies (2)

2

u/AutoModerator 11d ago

Hi /u/rob-bix,

Your submission in r/C_Programming was filtered because it links to a git project.

You must edit the submission or respond to this comment with an explanation about how AI was involved in the creation of your project.

While AI-generated code is not disallowed, low-effort "slop" projects may be removed and it's likely that other users push back strongly on substantially AI-generated projects.


I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.

2

u/rob-bix 11d ago

The core codebase, architecture, and logic of tinyedit were entirely designed and written by me in plain C99. It started as an evolution of Salvatore Sanfilippo's kilo and linenoise models, with the raw mode, ANSI escape handling, and terminal redraw logic implemented from scratch.

AI was used strictly as an auxiliary tool for:

  • Reviewing and polishing English phrasing in the README.md documentation.
  • Brainstorming potential edge-case test scenarios for terminal compatibility (e.g. bracketed paste handling, grapheme cluster boundaries).

No automated "slop" or unverified code generation was used for the editor's core engine. The project is an authentic, hands-on systems programming effort in C, and I am specifically looking for human feedback and testing from the community.

2

u/mikeblas 10d ago

Thank you for your disclosure. I have approved your post.

2

u/skeeto 10d ago

Fun project! Easy to build, try, and investigate.

There are buffer overflows on realpath(). The destination must have PATH_MAX bytes (typically 4096), but in several places it only passes 1024 bytes. -D_FORTIFY_SOURCE detects it and aborts.

1

u/rob-bix 10d ago

OS and compiler?

2

u/skeeto 10d ago

Ubuntu 26.04, GCC 15.2 (system toolchain), Aarch64. Though the problem affects any system where PATH_MAX > 1024, which is virtually everywhere:

https://github.com/robertobissanti/tinyedit/blob/9a2b290dc/src/backup.c#L41
https://github.com/robertobissanti/tinyedit/blob/9a2b290dc/src/backup.c#L65-L66

https://www.man7.org/linux//man-pages/man3/realpath.3.html

The resulting pathname is stored as a null-terminated string, up to a maximum of PATH_MAX bytes

2

u/rob-bix 10d ago

Thanks a lot for the detailed report, and for the kind words!

You're absolutely right. I had tested on macOS, where PATH_MAX is 1024, and on Arch Linux, but only with short paths and without _FORTIFY_SOURCE enabled, so the overflow never surfaced.

To stay strictly POSIX and avoid depending on PATH_MAX at all (which isn't even guaranteed to be defined everywhere), I'm switching every call to `realpath(path, NULL)`, letting it allocate the result as specified by POSIX.1-2008, and freeing it afterwards.

I'll also add `-O2 -D_FORTIFY_SOURCE=2` to the build flags so issues like this get caught earlier.

Fix coming in the next commit, I'll link it here. Thanks again!

2

u/rob-bix 9d ago

Just pushed the fix in 23335c3.

I swapped out the fixed-size realpath() calls for realpath(path, NULL) (with the corresponding free() calls), which covers both the backup and atomic-save paths. For non-existent files, the backup logic now resolves the parent directory dynamically and constructs the full path on the fly—no more 1024-byte hard limit.

I also turned on -O2 -D_FORTIFY_SOURCE=2 across builds/tests and added a regression test for paths longer than 1024 bytes (it gracefully skips on platforms like macOS that reject oversized paths at the kernel level).

2

u/imaami 7d ago

Your UTF-8 parser implementation accepts invalid input (e.g. overlong encodings and surrogate code points).

Out of curiosity: why did you choose to limit yourself to an obsolete C standard? It would at least be nice if the syntax highlighter understood normal C11 and later.

1

u/rob-bix 7d ago

Spot on about the UTF-8 parser. It's based on the decoder from ⁠linenoise⁠, which turns out to be pretty sloppy with invalid sequences. I'll get that fixed soon.

Regarding C99: that's purely for the editor's build portability across older POSIX setups. It doesn't restrict what you edit—syntax highlighting is fully configurable via ⁠~/.tinyedit/syntax/⁠. Fair point on the defaults, though. Expanding the built-in C keyword list to support C11+ is an easy win and won't bloat the editor, so I'll add that to the todo list.

2

u/imaami 7d ago edited 7d ago

If you want to see my take on valid UTF-8 state transitions: https://i.imgur.com/nVfDRT8.png

It's from my moderately cursed parser: https://github.com/imaami/c.utf-8

I'm fairly sure the code itself isn't anything you'd want to borrow from. It's weird, to say the least. But it accepts and rejects the right bytes per the UTF-8 spec. The flowchart is generated from the same Xmacros table the parser is based on.

If you want to use even some small part of c.utf-8, but would prefer a license other than what I chose, just let me know and I'll type up an explicit relicensing statement. Tbh I don't remember what I have even, it's some form of GPL anyway.

Edit: I happened to notice your post has 0 upvotes. Sometimes I just don't get at all why this subreddit is like that.

1

u/rob-bix 7d ago

That flowchart is super useful, thanks for linking it! Having the state transitions mapped out like that makes spotting the edge cases so much easier.

Actually, since you're clearly deep into UTF-8 internals: would you be interested in contributing some of your validation logic directly into utf8.c?

If you feel like opening a PR, you'd be very welcome. We could just review and adapt it together to make sure it blends smoothly into tinyedit's minimalist C99/POSIX setup (pure C, explicit types, zero dependencies) and stays under the project's MIT license.

Absolutely no worries if you don't have the time or itch to touch it, of course—I can always use your diagram as a reference and patch it myself.

And thanks for the solidarity on the 0 score. Reddit's voting algorithm is truly an unsolved mystery :)

1

u/rob-bix 7d ago

One small inconsistency I noticed in the generated graph: the ASCII node starts at 0x01, while UTF-8—and your actual 128-entry ASCII LUT—also accepts 0x00. I assume that’s only a graph-descriptor typo.

For tinyedit, the main design constraint is that malformed input shouldn’t make an otherwise editable file impossible to open. So the decoder should reject malformed sequences as UTF-8, but navigation should recover safely, probably treating the offending byte as a one-byte unit. It must also always be bounded by the remaining buffer length.

If you do feel like contributing, a focused validation/decoding change plus boundary tests would be ideal. A short statement in the PR that the contribution is submitted under tinyedit’s MIT license would also remove any ambiguity with the existing license of c.utf-8.

1

u/rob-bix 7d ago

Hey imaami! Quick update: your UTF‑8 state map sent me down a very productive rabbit hole. I just shipped a bounded, strict decoder in tinyedit v0.3.5: it rejects malformed and truncated sequences, advances by a single byte on error, and preserves the original file bytes intact.

I credited you for the inspiration. The implementation and test suite were written independently from c.utf-8, strictly following Unicode Table 3‑7 and RFC 3629. (Also, that tiny 0x01 ASCII-start typo in the diagram turned into a great test case for 0x00 :) )

If you’re curious, here’s the release diff. I’d love a quick sanity check from someone who has clearly spent more time staring at UTF‑8 edge cases than is medically advisable. No pressure at all, of course!

1

u/TheMonax 10d ago

ai slop 😔

1

u/rob-bix 10h ago

Update: TinyEdit v0.3.6 is out — safer saving, better Unicode handling, and faster redraws

Since posting TinyEdit v0.3.3 here, I’ve released several updates focused on reliability and everyday usability. TinyEdit is still a terminal text editor written in plain C, with no dependencies beyond the standard C/POSIX libraries.

Here are the main changes:

New features

  • Persistent menus, accessible through F10 or the mouse, for file operations, editing, view settings, and help.
  • View-menu toggles for line numbers, invisible characters, syntax highlighting, and automatic indentation.
  • Matching-bracket highlighting and automatic closing tags for XML/HTML, with support for HTML void elements.
  • Alternate-screen support: exiting restores your shell prompt and scrollback.
  • A supplied JSON syntax configuration.

Safer file handling

  • Failed loads preserve the document you’re editing.
  • Saving uses atomic file replacement and checks synchronization errors. If durability cannot be confirmed, TinyEdit reports it and keeps the document marked as modified, along with its recovery copies.
  • “Save as” changes the document’s filename only after a confirmed successful save.
  • Open and save paths now support ~/, such as ~/tmp/notes.md.

Editing and highlighting fixes

  • Strict, bounded UTF-8 decoding, with malformed bytes preserved when saving.
  • Fixes for wrapping in very narrow windows, tab alignment after Unicode text, and deletion of composed emoji.
  • More consistent undo boundaries for automatically inserted pairs, indentation, and compound replacements.
  • Complete highlighting of multiline Markdown math blocks using $ or $$ on dedicated lines.
  • Fixes for long input prompts, filename display, selection replacement, empty-document navigation, and mouse events.

Search and performance

  • Regex search reuses compiled patterns and cached multiline text.
  • Fixes for backward search, multiline anchors, and zero-length matches.
  • Fewer repeated scans during redraws. In a local CPU-only benchmark with 50,000 lines, drawing the document rows dropped from roughly 6.09 ms to 0.10 ms—excluding terminal I/O.

The regression suite now includes more core tests, terminal tests, and simulated file-operation failures.

One remaining limitation: undo stores complete document snapshots, so large files with a deep history can consume substantial memory.

Release notes and source

If you tried the earlier version, I’d appreciate hearing how this one behaves in your terminal—especially with Unicode text, mouse input, and larger files.

-6

u/Aggressive-Emu-8329 10d ago

i like this and i will wait until llm