Stevie's Blog

January 25, 2025

Creating an IDE for the C Programming Language

What Am I Even Trying To Do Here?


The latest project I am working on is Seaside, a modern and lightweight IDE for the C programming language built around the Clang/LLVM toolchain. The idea of creating an IDE for C might seem strange to some. Why do we need yet another development environment, aren’t there enough already? And why do I even care about C? In this article, I want to lay out my motivation and thought process behind starting this project.

Motivation

Every time I am starting a project in C, I am faced with the annoyances of having to make a lot of choices about my development environment upfront. Which compiler am I going to use? Which build tool? Which editor? How am I going to structure my project? Ideally, I would like to not have to think about this at all, and just whip out a properly integrated development environment that provides sensible defaults that let me just get started.

I’m generally a big fan of IDEs. Apart form C, the most programming I’ve done in my life so far has been in Java and C#, both of which feature comparatively great IDEs with a low barrier to entry and lots of features. For C, the major options in this regard are Visual Studio, CLion and Visual Studio Code. But here the struggle begins. When using Visual Studio, I get a full-fledged IDE with most of the features that I want, but I am practically required to use MSVC (or maybe clang-cl if I can manage to set it up). CLion seems like a good alternative and can use different compilers, but it is mostly centered around CMake as a build tool (with some bare bones support for classic Makefiles), and generally feels like a heavyweight monstrosity to use. Visual Studio Code lets me configure my build process as I wish, but it is also not as integrated as the other two, being a highly customizable code editor instead of an IDE specifically designed for one set of languages.

The choice of development environment also touches on my project structure. Which files am I going to combine into translation units? Do I want to use a unity build for simplicity? How does this affect compilation speed, how I write header files (or whether I write them at all), where I use the static keyword and how I name my functions? Does the IDE/editor and build tool I am using support my project structure, or do they expect a specific way of managing my code?1 The barrier to get started seems higher than with any other language I have ever used.

It doesn’t help that the performance of common IDEs and editors isn’t exactly stellar. It seems like pretty much every development environment available suffers from a combination of being slow, buggy and/or not being properly integrated. Do I really want to setup a project to work well in some IDE, when that environment is going to annoy the hell out of me due to its poor performance as the code base gets larger?2

Why are there so many unsatisfactory options here? Wouldn’t it be nice to have a fast, lightweight, reliable and properly integrated development environment for C that just let’s you focus on development?

Fragmentation

A one-size-fits-all solution is difficult, because the landscape of development environments for C is very fragmented. The primary reason for this is that there is no single generally agreed-upon way to program in C. There are multiple different compilers, multiple different build tools, multiple language standards, multiple compiler extensions to change the syntax and semantics of the language, as well as the notorious preprocessor that can turn your program into something that doesn’t even resemble the basic language syntax anymore. Looking at it this way, it is no wonder that the recommended way of developing a project in C is highly context-dependent. Consider these cases:

Obviously this list isn’t even exhaustive, these are only some of the contexts we might find ourselves in when developing in C. But it highlights the problem with building a generic integrated development environment for this programming language.

An important thing to notice is that these different contexts are oftentimes held in place by strong cultures. Linux kernel developers have a tendency to use classic Unix text editors and to stay on the command line. You’re going to be hard pressed to find someone using CLion in this environment. It makes sense to adapt to what others are doing and use the same tools, in order to save yourself from troubleshooting problems that arise from your weirdly specific setup. Additionally, as anyone with sufficient exposure to message boards will know, people working in different contexts (and even within one context) have strong opinions on different tools3 and rarely feel comfortable venturing outside of their preferred approaches.

Limitations

As we can see, the problem largely lies in the fact that there is no single context that one can target when building a development environment for C. Therefore, I see the following options for creating a better experience:

  1. Sidestep the problem and use a customizable editor that lets developers configure their environment specifically for their needs. This is the Visual Studio Code strategy. To some degree, it has had a lot of success in this space (to the extent that it has), precisely because it offers the ability to pick and choose plugins for your individual context. Of course, this strategy depends on people creating third-party plugins for the various compilers and build tools. But it also necessarily gives up a lot of potential integration, because the editor is fundamentally generic in nature and not designed to be used for your context from the ground up. The fact that VSCode is built upon web technologies doesn’t help with performance either, but that is of course not a requirement for building such a program.
  2. Focus on one single context, and create the best possible environment just for that. Obviously this is less generally applicable, and with the combinatorial explosion of components (compilers, build tools, project structures, macro conventions, etc.) the target audience might become very small. However, the environment can be tightly integrated with the specific context and made to be fast and simple, not least because it can make more assumptions about what the user wants to do. As a result, it can deliver the best user experience to a smaller number of people.
  3. Just do it, build the uber IDE that is intended to work in all the contexts. Be careful not to fall into the trap of becoming overly generic at the cost of integration (like a customizable editor), nor becoming slow and buggy due to the high complexity involved in supporting so many different use cases (a classic problem in software development). This is a huge undertaking, and it requires working closely with people in the various domains to learn what they need and how they work, as well as creating integrations with potentially proprietary tools. Even then, I suspect that there are some inherent tradeoffs one would have to make, which end up turning the result into a strictly worse outcome than multiple smaller, focused programs for different contexts.

Which is it going to be, then?

Focus

The context that I care most about myself is cross-platform application development, i.e. building graphical applications for desktop, mobile, web, set-top boxes and video game consoles. As such, what I want the most is an IDE that is tailored to this particular use case. What do I expect from that kind of an environment? And what would I need in order to get there?

For starters, the IDE would offer a fast, user-friendly visual debugger that syncs flawlessly with my source code. It would provide easily configurable build targets for various different platforms and OSes. It would let me compile to a statically linked, single file executable for desktop OSes. It would provide templates to make it easy to get started with a project built upon e.g. SDL or raylib, and to structure it around hot-reloading code at runtime with e.g. cr. It would make it trivial to link to additional third-party libraries in different ways (statically linked, dynamically linked, single header). It would offer sensible default build scripts for fast compilation speeds. It would have proper autocompletion and Intellisense-like features for my own code as well as the libraries I am using. And it would generally be fast, lightweight, reliable and get out of my way.

In principle, one might be able to get away with using Clang/LLVM as one single compiler toolchain for all these targets, with some caveats4. It comes with most of the basic tools that you would need in order to provide the most basic integrations, e.g. LLDB as a debugger, clangd as a language server, clang-tidy as a linter, memory and address sanitizers, and refactoring tools. For building, different vendors require different specific build steps, but one should be able to glue all of them together in one big, disgusting CMake build5. Of course, on proprietary platforms things are generally more challenging, especially when you have to deal with dev kits for consoles, NDAs around documentation and code signing processes. But we can already see that reducing the scope and applying a sharp focus to one use case makes the whole ordeal much more manageable.

As always, the devil is in the details, and I’m probably missing something. But it seems like a worthy thing to attempt. Oftentimes, it is better to think that something is quite feasible, even though it might ordinarily be considered impossible6.

Why Even C?

You might ask why I’m even focused on C as a language in the first place. In the aforementioned context of application development, C++ is much more common, and even then there are more modern languages that don’t share the same problems that we have laid out in the beginning. Having a generally agreed upon (or even officially provided) build process, an enforced project structure and a single reference compiler would make developing an IDE much easier. So why focus on C?

For one, we are in a weird middle phase of a low-level programming language revolution. The paradigm shift hasn’t quite taken place yet. Alternative languages are either still too early (e.g. Zig, Jai), or ended up not quite delivering for what we were looking for after all (e.g. Rust7), or simply didn’t get enough traction so far (e.g. Odin). As a result, there are not only lots of existing projects written in C and C++ that have to be maintained, but plenty of new ones that are started from scratch.

C is arguably the mother language of most low-level programming languages in use today. It has the only commonly supported ABI for foreign function interfaces, so if you want to build library code that is supposed to be called by as many languages as possible, C is still your best bet8 (unless you’re fine with putting your code behind a REST API). Of course, you could still use C++ in that case, and simply use extern "C" judiciously, but then you’re required to maintain the mismatch between C++ features on the inside and a pure C API on the outside.

I personally just like C more because of its simplicity. So far, I haven’t been able to befriend C++ due to the insane complexity the language has accrued over the years. It seems like there are a dozen different ways to do any one thing, and one more option to come around the corner with every new version of the standard. That being said, I’m not oblivious to the fact that C++ is very popular (or should I say, widespread) for cross-platform application development, and that it is the de facto standard in many areas. Yet, I have to keep the scope small at the beginning. So I’m going to focus on C, which I care about the most and use myself. At some point, should it get off the ground, I think it could be useful to extend the IDE to the broader “C family” of languages, i.e. C++, Objective C/C++ and perhaps also OpenCL/CUDA. Incidentally, these languages are all supported by Clang.


In summary, I am aware that this is a large project and that I haven’t fully conceptualized either the complexity of it, or the exact expectations and use cases that arise out of it. But I’m motivated to create something that I would enjoy using myself, and that can provide a better alternative for people like me. People who deeply care about the tools they are using and who are becoming increasingly disenchanted with the state of software development environments.


  1. This is admittedly a rather specific case, but in principle you could decide to not use header files at all when using a unity build to compile your project. But in that case it becomes impossible to use the clangd LSP implementation, because it is looking for header files in your project to provide suggestions.

  2. At this point, I am used to people being confused about my complaints. “That’s strange,” they say, “I never have any problems. It all just works fine. Are you sure the problem doesn’t lie on your side?” I’m not sure what to answer. My experience has been that the tools that I am using are getting slower, buggier and generally more unreliable with every update. I have experienced this on multiple different machines, making it rather unlikely that I have somehow botched my system in every case. I think I just have comparatively high standards for the software that I am using, and most other people don’t care as much about it. I would like to write about this in a future article, and hopefully provide some concrete evidence for my case.

  3. Is CMake perfect or hell?

  4. Apple and Sony maintain forks/patches for Clang targeted around their specific hardware, which might lead to incompatibilities with the upstream version, at least when using a newer version of the latter. Microsoft requires the use of clang-cl for compatibility with MSVC.

  5. CMake is Turing-complete and someone has written a ray tracer in it.

  6. I am reminded of the famous statement by Walter Bright, which I cannot find an exact quote for, that paraphrases as something like this:

    I once thought creating a new language was impossible, but writing a C++ compiler was feasible. In reality, the opposite turned out to be true.

    That didn’t stop him from creating the Digital Mars C++ compiler.

  7. This statement was intended to be somewhat provocative, so if you’re a Rust enthusiast who feels enraged as a result, then I did everything right! Joking aside, what I want to hint at is that not every project necessarily cares about memory safety, and not every developer appreciates the borrow checker and sophisticated ML-influenced type system for their use case.

  8. There is another hidden motivation for choosing to focus on C, which I want to write about in another article. In short, the idea of creating a library that’s useable from many different languages is very relevant to me for another project I have planned.