Thursday, 13 August 2020
I recently got into Go (the game, not the language), and due to the circumstances as of writing, I mostly get to play by my own vs a Bot.
The weird thing about most Go-related software is that it’s either dead, barely or even non-functional or aggressively skeuomorphic. Because I had had some extra time on my hands, I decided to write my own client, using C and XCB1, that was about as complicated as it has to be, for a non-professional player like me2.
In this text, I’d like to write about the tooling I used, particularly revolving around GNU Emacs. The reason for this is that it’s somewhat hard to come by good, well-informed tips that still work. I hope to explain the technical background, my choices and possible alternatives one might consider.
It will be necessary to start by pointing out that I started my project from scratch, so I got to choose everything – build system, file layout, libraries, etc.
In my case, I ended up using a single portable Makefile, a flat file
hierarchy and the XCB
library. Locating the library and it’s header files was done using pkg-config
.
All in all, it’s nothing spectacular. It provides a stable foundation for writing a clean program.
By default, Emacs comes with a major mode for C. It does syntax highlighting, indentation and a few basic operations on semantic units. But what I don’t want a basic environment, I want a comfortable environment.
If you ask most people online what they would recommend, you’d hear a lot of people say “lsp-mode”. Now I happen to think that lsp-mode is a bad emacs citizen, and would rather avoid it. Eglot is somewhat better (I’d use it if I had to), but in all in, I’d rather avoid LSP3.
So what am I looking for? My modest list consists of:
It turns out that Emacs provides all of these things, without having to install anything! Sadly it’s not always the best you could get, so I’ll also mention what alternatives you could consider.
This is probably the easiest to configure, but most people don’t know how. Usually people jump to Flycheck, but that requires additional configuration and is an external package anyway.
Instead, I suggest considering Flymake, the built in syntax checker. I suspect one of the reasons it’s mentioned less often, is that it’s not quite clear how the minor mode should be used: Just enabling it, provides no feedback, and if did, it’s not clear how to find out what’s wrong.
There are two steps to configuring Flymake:
For the first part, you just have to add flymake-mode
to
whatever major-mode hook you’re using, in our case
c-mode-hook
, and when that’s done, optionally bind
flymake-goto-next-error
and
flymake-goto-prev-error
. I recommend user-keys
M-n and M-p, if you’re not using them for anything
else.
The backend is a bit trickier, or at least less intuitive. But all we
have to do, is all a rule to our Makefile: check-syntax
.
Here’s what I used:
check-syntax: # flymake support
$(CC) -fsyntax-only $(CFLAGS) $(CHK_SOURCES)
.PHONY: check-syntax
How this works: By default, a backend tries to invoke
make
with the check-syntax
target, and it sets
the CHK_SOURCES
macro to whatever file is being inspected.
By adding this rule, Flymake will parse the diagnostics (note that
nothing is compiled because of the -fsyntax-only
), and
display them in the current buffer. The CFLAGS
are
necessary, so that Flymake warns you of the same issue while writing as
while compiling.
The last question is how to find out what the issue is. A squiggly
it’s always enough to help. And placing the point on the highlighted
area doesn’t do much either… You might have noticed that
flymake-goto-{next,prev}-error
displays the message in the
mini buffer, but there’s no command in the Flymake package the just
display the issue you’re currently looking at without having to do an
unnecessary M-p M-n
… The solution:
display-local-help
, bound to C-h .
by default.
Flymake integrates into the existing help system, and only displays it’s
information when queried – you don’t want to spam the echo area after
all.
That’s that. If you add another dependency, and update your Makefile, Flymake will immediately make use of it. In that case,
The classical method of finding where a symbol is defined, is by
using tags files.
Various programs can generate the TAGS
file: By default,
etags should come with every GNU Emacs installation, but Exubrant Ctags or Universal Ctags might work better.
Xref, the cross-referencing subsystem (hence the name), has a TAGS backend built in by default. This supports querying the definition at point (M-.), finding miscellaneous symbols (C-u M-.), listing all symbols that match a pattern (C-M-.) and even finding where a symbols is used (M-?).
Some people say it’s inconvenient to maintain a TAGS
file, which I understand. What helped me was adding a TAGS
rule to the Makefile:
TAGS: $(SRC)
ctags -Re $(SRC)
that I invoke every time I re-compile the program (more on that
later). In this example, the macro SRC
would list all
source files of the project. A non-portable GNU makefile, might generate
this using
SRC != find . -name '*.c' -or -name '*.h'
Another idea would be to add a function to
after-save-hook
that updates the TAGS file for a single
file.
But TAGS
have their limitations, for example when
projects are too large and loading the TAGS file becomes too
inconvenient. In that case, some people like to use Dumb Jump (MELPA).
It’s idea is that it uses grep
-like tools (git-grep, the
silver searcher, ripgrep, …) to find patterns that might be the
definitions of whatever symbol one is looking for. That way, you get to
trade in maintaining a TAGS
file, for searching the
code-base every time you need a definition.
Dumb Jump has also recently been extended, by yours truly, to implement part of the Xref interface, that improves the UI and makes it easier to switch from one backend to another.
Some might be surprised to find out, that after setting up a TAGS file, Emacs can automatically use that file for auto completion. Sadly, it’s not too clever, and just suggests every tags it knows of, every time. Depending on your standards, this might not be enough.
For that reason, I wrote clang-capf
,
that implements the bridge between Clang‘s completion mechanism, and
Emacs’ completion-at-point
subsystem. After it has been set-up, M-<Tab> provides a
list of context-sensitive completions4.
Sadly, the results could be even more context sensitive, but clang
doesn’t seem to support this by default. For example, if a function
requires a FILE
pointer as an argument, clang won’t just
return visible file pointers as suggestions, but usually everything it
knows – which is usually more than a TAGS
file knows.
When a project has more than one file, it would be nice to easily
move between them. Depending on your Emacs usage, a simple C-x
C-f and C-x b might suffice, but for a while now, Emacs
also bundles “project.el
”, a framework to operate on the
current project. It’s commands will be bound under the
C-x p
prefix by default, but you can already use it via ELPA if you’re
using Emacs 26.3 or newer.
I use project-find-file
indirectly via a little helper
function bound to C-x C-r5:
defun find-file-recursive ()
("Open a file in current subtree."
"P")
(interactive if (project-current)
(
(project-find-file)let ((files (process-lines "find" "."))
("Find file: " files nil t)))))) (find-file (completing-read
Again, using Ivy, I get a list of files in the project, even sub-directories, and can easily switch between them.
Another useful command, is project-compile
that start
compile
in the project’s root directory. I invoke this
once, give it the command
make all TAGS
and then re-run the command later on using recompile
,
bound to a key such as F2. Warnings and erros can be navigated using
next-error
(M-g M-n) and
previous-error
(M-g M-p).
And when done, project-kill-buffers
keeps the buffer
list clean.
That’s it. A comfortable development environment, at least if you ask me. A lot of these things are built in, and feel native in Emacs. This feel ultimately beats anything that wants to smuggle in the Workflow of some other modern editors such as IntelliJ, Atom or VSCode. I’m often sad to see how many people train Emacs into behave like these, when you arguable should not want this – for your own and your editors sake.
One thing I didn’t go into was debugging, but if you know GDB, running
M-x gdb
should give you everything you want. Another thing
thing I’m still looking out for is a good way to use eldoc, to get the
signatures of functions and types of variables. It might be worth
looking into clang for that.
Before anyone cries out that C is dangerous and must not be used, I would like to defend myself, by pointing out, that:
I’m familiar with sites like online-go.com, but I wanted an offline tool.
In case anyone is interested, my preliminary status can be found here, but that’s not what this text is about.↩︎
It might get better, but currently LSP is dragging a lot of tools down. I’ve seen Go and Haskell (GHC) abandon their existing systems for auto-completion, for self-declared “alpha” software, that has mainly been tested against VSCode. In it’s implementation, it should also be clear that LSP is biased towards web-based editors, such as (unsurprisingly) VSCode, as it’s based on a HTTP-like protocol, with no ability to negotiate the form the data is transmitted with, besides JSON.↩︎
By default, the results are diplayed in their own buffer, which is somewhat visually unpleasing. I personally use Ivy to get a prettier list.↩︎
By default,
find-file-read-only
is bound to this key, but I have never
used it.↩︎