The idea
I saw a video on YouTube about a file manager in the terminal that has you typing paths by default, instead of a mode for searching files. I thought the idea was interesting and it sounded like a nice workflow. I also thought that I needed a project though, so I decided to make it myself.
I decided to build it in C using ncurses, because a lot of older software is made with that stack, so it’s probably pretty based. I went into this with no experience with ncurses and some expercience in C, but none in any larger projects.
Implementation
After checking command line arguments and setting up the terminal, the program enters into a run function, which keeps track of the current directory and what’s typed with a dynamic string struct I made. It then loads the current directory’s contents so they can be used, and draws everything to the terminal.
After that, it goes into a loop that gets the input and, using a switch case, checks it against all of the keybinds. The default case just appends the character to what’s typed. If it matches one of the keybinds, however, it handles them however they need to be handled. That may include changing the current directory, or reloading the directory contents if needed. After that, it redraws stuff to the terminal.
When you press a keybind to close it, the run function returns a struct that contains a path and a bool, which determines if the path is opened in the editor or moved into. The path string is escaped and the program prints it to stdout with either cd or $EDITOR. If you use eval $(donkey) your shell will evaluate these.
switch (c) {
case ctrl('c'):
return (RunOutPut){ .path = NULL };
case 27: // escape
null_terminate(&dir);
return (RunOutPut){ .cd = true, .path = dir.buf };
case ctrl('w'):
...
default:
...
}
Takeaways
This is bigger than anything else I have written in C, so my main takeaway is actually just about C itself, and that’s that errors suck in C. Every time you call some function like fopen, you need to check the man page for if it can error, and if it can, what it will return on an error. Then, you need to check if the value returned is that, and do something different if it is. If you forget to do this, nothing catches it until you’re using the tool and it crashes. In rust, for example, you know from a return value that it can error, and handling the error is often very easy, like just putting ? to bubble the error. In the future, I may look into languages like Zig or Odin to have somethng like C with solutions to some of the annoyances that come up working in C.
This is rust code to open a file for reading and bubble the error if it doesn’t work:
let file = File::open("example.txt")?;
And this is some C code to do the same thing; note that it doesn’t tell you if you forget to check the return value:
FILE *file = fopen("example.txt", "r");
if (file == NULL) {
return -1;
}
My other big takeaway is that ncurses is great! I often don’t like libraries and things that force me to work in a certain way. Ncurses isn’t one of those, so I really liked working with it. It really just did whatever I needed it to do.
See for yourself
If you want to read the source code or use the tool for yourself, it’s up on GitHub.
