jked

📅 2024-12-12

In Star Wars, the light saber is a weapon that Jedi Knights personally construct for themselves, presumably so that the saber is uniquely adapted to their abilities, characteristics, and the nature of their connection to the Force.

Not as clumsy or random as a blaster; an elegant weapon for a more civilized age.

I find it pleasing/amusing to make an analogy between Jedi Knights and computer programmers. Obviously, the light saber is as crucial a tool for the Jedi as the IDE is to the programmer. General-purpose IDEs built for everyone are massive, lumbering hulks that drain one's battery and hog up CPU time. Can you imagine a Jedi using a run-of-the-mill mass-produced light saber, clumsily attempting to deflect blaster shots? I do appreciate that modern IDEs are comfortable and immensely powerful, with all the autocompletion, code sensing, live analysis, and error checking. The advantage of all that is a lower cognitive load, enabling one to engage their mind to the coding task more effectively. This comfort comes at a cost, though. For example, look at Visual Studio Code that is built on Electron (i.e., a browser engine) or even my perennial favorite, Qt Creator. The former is platform-independent and infinitely flexible, customizable to one's needs. In many ways, it is the modern equivalent to Emacs, powered by TypeScript instead of Lisp. But I'm fundamentally allergic to the idea of doing my coding inside something based on web technologies. There are layers upon layers of software that does not need to exist to perform the task at hand. In comparison, Qt Creator has been relatively lightweight, being mostly built on native code and Qt frameworks. However, in recent years, it has incorporated clangd/LSP background processes, slowly but surely accumulating features and complexity. I reached some sort of a breaking point recently when I noticed that while I had my project open in Qt Creator, the clangd background processes would routinely hang at 100% indefinitely, apparently getting stuck at parsing some part of the sources. It got me wondering that perhaps I need to start looking for a new IDE.

What other IDE would I prefer to use, though? Ideally, I would want one constructed from the kyber crystal of my choosing, not one of the myriad editors out there that I may or may not be able to fit into my usage patterns and needs: platform-independence and focus on programming tasks, particularly fluent code navigation, being absolutely critical features. I had already decided to stay away from the "classics": Emacs is too archaic and ergonomically hostile, while Vim requires a 1970s-era UX mental model. There are a lot of other fine text editors out there, but mostly they are specific to a particular platform. For example, I've always enjoyed TextMate on macOS, although it lacks many programming-specific niceties. Alas, having a nice editor on one platform does not help when working on other platforms.

23 years ago, I wrote a simple text editor out of curiosity. It was a single C source file containing 1800 lines and ran on Windows in console mode inside a command prompt. The feature set basically consisted of loading and saving a file, moving the cursor, copy/paste, and finding a string. No bells or whistles, no frills. Well, maybe one frill: it had a multi-column file selection dialog for navigating directories and opening files. As an editor, though, it was basically useless for anything more serious than viewing files. It did that poorly as well because it — and I — didn't know much about text encodings.

That initial version of the editor was soon forgotten. But then in 2015 (14 years later!) I committed the old sources to a Git repository and started refactoring it towards something more useful. I can't recall my motivation for this. The repository history records changes such as moving the UI to ncurses, splitting the source to multiple files, porting everything to UNIX (Linux), switching to C++ strings, making the screen resizable, improving the file selection dialog, supporting both LF and CRLF line endings, using regular expressions when searching text, and basic awareness of the user's locale for converting text properly. All this was done in December 2015, and by January 2016 I had a somewhat more usable version of the editor, now called "jked", running on both Windows, macOS and Linux.

This version 2.0 of the editor was 3600 lines of C/C++ in 26 files. It was still pretty much useless. Nothing more than a toy, really. I remember using it now and then for basic text editing, but it certainly was insufficient to unseat GNU Nano, which has been my primary terminal text editor for years. I recall thinking I could use jked on Windows at least, where console text editors were not so great, but I don't really spend much time in text mode on Windows.

Fast forward to 2024. It was roughly three months ago from today when that background clangd process started hanging in Qt Creator. While I was considering alternatives, a thought occurred: would it be feasible for me to make an editor that is good enough to be a basic IDE? If that was possible, I could make it work exactly like I wanted; it could be tuned precisely for my programming needs. I found the notion of constructing my own light saber an immensely appealing proposition.

My focus on the_Foundation and Lagrange in recent years has given me confidence that I can have a level of self-sufficiency in my programming, where I don't have to lean heavily on existing software frameworks beyond the language's standard library. Porting Lagrange to the terminal with Curses also taught/reminded me a whole lot about how versatile the terminal can be as an environment. Programming is 99.9% text anyway, so being limited to a monospace presentation and character-based output isn't really much of an issue.

So in October 2024, nine years after jked 2.0, I opened the old source code and took a critical look. Decades-old code is always a bit of a time capsule. It was evident my programming sensibilities and skills had evolved significantly since 2015. At least the code was still small enough to be somewhat coherent. It was nominally C++ but really just C without any idiomatic C++ features to speak of. But the bones of the program seemed sound enough.

Thinking to myself, "am I seriously doing this?", I started implementing the really basic missing features: command line argument handling, jumping to a line by number, and undo. There were a couple of critical non-trivial features to be done first: word wrapping, having multiple files open at the same time, autosaving, and restoring the session after a crash. Fortunately, my initial choices in the program design were not in conflict with any of these core essentials, so after a few weeks I had on my hands a little editor that could begin challenging Nano as my everyday terminal $EDITOR.

However, the goal was to create a programming IDE, not just a text editor. In practice, I was still writing all the code in Visual Studio Code. Surely, for this to be a credible endeavor, the IDE must to be developed primarily with itself.

A massive concern that I had at this point was the whole LSP subsystem. My objective was to create an editor that is not constantly running background tasks and essentially recompiling everything after each keypress to check the syntax and update the code model. Yet, the editor must have sufficient knowledge of code symbols and structure to make quick navigation and autocompletion possible. There are indexing utilities like `ctags` for solving this, but I wanted something that is both language-agnostic and not dependent on external processes. Fortunately, my experience using TextMate had pointed to a possible solution: custom language grammars defined by regular expressions. This turned out to work rather well, although the implementation isn't nearly as sophisticated as TextMate's. I could now define basic grammars for syntax highlighting and symbol detection for C, C++, Objective-C, Python, shell scripts, XML, Markdown, TaskPaper, and of course, Gemtext. This encompasses most of what I need on a daily basis.

With the symbol index in place, I added a way to jump to any indexed symbol based on a fuzzy word search. This is my preferred way of navigating sources: just type a few distinctive letters and you land directly where you wanted to go. The symbol index can also provide a table of contents for the current file, which is great for navigating long Markdown or Gemtext documents by heading, for instance. Once I loaded up Lagrange in the editor, it was quite a delight being able to access all the .gmi headings (Help, version history, etc.) and source symbols through the same interface.

Over the following weeks, I worked through the list of smaller features that I need in day-to-day programming: the concept of a "project" enclosing a directory tree with local settings, folding segments of the document based on indentation (for example, hiding the body of a function that is not relevant at the moment), switching between header and source counterparts, jumping to the definition of the symbol at the cursor, regex-based search in all project subdirectories, commenting marked lines, sorting lines, cursor position history for returning to a previous edit location, persistent clipboard history, and splitting the view for showing a function declaration or type definition for reference (since there is no actual code model, it's good to view these on screen). When it comes to the split view feature, I was fortunate that the initial ncurses port was already WINDOW-based so it did not assume full screen output. Adding more windows was therefore not too difficult.

Finally, to make this an actual Integrated Development Environment, there also needed to be a way to run build tools and other external utilities. I added a mechanism for running a child process and piping its output to a readonly buffer inside the editor. This was then used for Build and Run shell commands (customizable per project), looking up Unix manual pages, and also running Clang-Format to clean up the formatting of a source file.

One cool part of this light saber is that it is not locked to a particular color: each buffer gets its own interface hue, Lagrange-style.

jked 3.0 is now fully functional. It comprises 52 files and about 15K source lines; roughly a 4x increase from the previous version. I've been using it for developing jked itself for a few weeks, and the recent Lagrange fixes were also made with it. It also seems to be decent for editing Bubble sources, although with dynamic languages like Python it is very helpful for the IDE to be proactively checking everything on the fly. I could incorporate a linter that is run instead of Clang-Format; currently I'm just using `python3 -m py_compile` to check for syntax errors as the Build command.

To be fair, one IDE cannot realistically address all needs and purposes while also remaining nimble and small. For example, debugging and profiling are best done with the platform vendor's tools, like Xcode and Instruments on macOS/iOS and Visual Studio on Windows. These are infrequent tasks, though.

While writing this post I noticed a couple of minor bugs. Here is perhaps the biggest pitfall: when you're trying to accomplish a task, having to fix bugs in the editor is a big distraction. But there is only a finite number of bugs in the code (right??), so after the most common ones are squashed, it should all be smooth sailing in an editor designed for my needs and purposes, an editor that does not accumulate excess features and bloat like generative assistants or background code modeling, thrives in the stable console/terminal environment, can be run remotely over SSH, and can be expanded with language grammars and symbol detection for per-project needs. I'm quite excited about this.

I am also pleased to peek in the activity monitor and see that the editor is using precisely 0% CPU time while idling.

🏷 Programming, jked

CC-BY-SA 4.0

The original Gemtext version of this page can be accessed with a Gemini client: gemini://skyjake.fi/gemlog/2024-12_jked.gmi