
Vim's 400 line function to wait for keyboard input - shubhamjain
http://geoff.greer.fm/vim/#realwaitforchar
======
joosters
But the code works. It supports lots of platforms through choice. Yes, you
could make the code prettier if you dropped some platforms. Yes, you could
refactor it to use some abstracting libraries that now exist. But _the code
works_. If you rewrote the code, the best result you could end up with is the
same functionality that still works. All other possible results are bad. There
is nothing to be gained.

~~~
ggreer
I made the page as a companion piece to a blog post[1]. The code works, but
it's quite buggy. Also, it is almost certainly broken on outdated OSes. It's
just that nobody uses the latest Vim on IRIX or VMS, so no bug reports get
filed.

A quick glance shows some obvious errors in RealWaitForChar(). For example:
with typical preprocessor defines, it uses gettimeofday() in a select() loop.
This will break if the system clock changes, either from user intervention or
ntpd. The correct solution is to use a monotonically-increasing timer, such as
Linux's clock_gettime() or OS X’s mach_absolute_time().

Vim's UI is powerful, but its codebase is a nightmare. My blog post explains
why in more detail.

1\. [http://geoff.greer.fm/2015/01/15/why-neovim-is-better-
than-v...](http://geoff.greer.fm/2015/01/15/why-neovim-is-better-than-vim/)

~~~
eridius
Your blog post recommends using NeoVim today. I've considered looking at it
several times, but the home page[1] still says

 _Is it ready to download and run now with all the features?_

 _No. Although some features are a work in progress, Neovim isn 't at a stable
point. Using Neovim should be done with caution as things may change._

This warning is why I haven't yet tried switching over. Are they just being
overly-cautious?

[1]: [http://neovim.io](http://neovim.io)

~~~
UberMouse
I use Neovim as my daily editor, it definitely has it's quirks over Vim
(sometimes it takes over 10 seconds to write to certain files, locking up
Neovim and there are some bugs with the fuzzy finder I use that cause file
corruption that aren't present in Vim) but on the whole it's pretty stable.
Haven't encountered any issues yet that are more than a minor annoyance.

~~~
simoncion
> ...there are some bugs with the fuzzy finder I use that cause file
> corruption...

Having your text editor corrupt the file it's working on sounds like the
absolute worst case scenario.

Is this fuzzy finder thing some third-party plugin? Do you have any idea why
it corrupts the files that you point it at?

~~~
UberMouse
It's a third party plugin, it's definitely a Neovim issue because the file
corruption doesn't happen with Vim. No idea why it happens. I'm fine living
with it because I just check the file back out if it corrupts it. It's only an
issue when opening a new file so it doesn't cause work to be lost.

A github issue about it
[https://github.com/junegunn/fzf/issues/206](https://github.com/junegunn/fzf/issues/206)

------
jcl
This reminds me of a talk at last year's CppCon about modernizing legacy code.
In it, they talked about a core output function of the MSVC standard library
that had accreted enough #ifdefs over the years that the function signature
alone spanned over 100 lines. It's a great example of how repeatedly making
the minimal necessary change can drive code into an unmaintainable mess.

When the team found that the function was impossible to modify to accommodate
C99 format string changes, they undertook a lengthy project to factor out the
#ifdef'd features using abstractions available in C++. Not only were they able
to turn the code into something much easier to modify, but they also fixed
multiple hidden performance bottlenecks, so the end result actually ran 5x
faster than the C version.

[https://youtu.be/LDxAgMe6D18?t=69](https://youtu.be/LDxAgMe6D18?t=69)

~~~
firebones
It's also a case where making that investment is worth it given the core
nature of the MSVC standard library which makes it integral to a universe of
other software. The global impact of a 5x improvement in something in every
app makes it a no-brainer. But there's a lot of debt out there that will be
replaced before the payoff ever comes.

Interesting Fermi problem for sadistic interviewers: what would be the global
energy saving per invocation of this vim function if a 5x performance
improvement were to be found?

------
kosma
There's a deeper underlying problem: the bit of code lacks any architecture.
Even though libuv and others didn't exist back then, that's no excuse to just
pull random descriptors from various parts of the program and stuff them in a
select, repeating variants of the same logic over and over. Even creating a
consistent notification/callback interface would make the code much more
readable, as the underlying pattern is always the same: watch a descriptors
and call a function when an event occurs. It's just lazy and ugly.

~~~
stuartaxelowen
Your solution sounds like Enterprise Java to me - instead of understanding one
long simple solution, you have to understand 3 shorter, less simple solutions.

------
breadbox
There is nothing wrong with supporting lots of platforms, but those #ifdefs
need to be encapsulated in wrappers functions (or macros). This is a classic
example of premature optimization, actually. You use one or two #ifdefs
directly because you hate to pay the cost of function overhead just to make
the code easier to read. (Even though there's practically no point in tiny
optimizations just before the code is going to wait for keyboard input.) A few
years go buy, a few more situations are done via #ifdef because at least that
way it's consistent. Eventually you have a nightmare function like this, where
reading it forces you to read every possible version of the function
simultaneously.

Encapsulate your #ifdefs people!

~~~
wyldfire
Or don't use them at all. For where the behavior must be different, create
different source files with the same function signature. When building, link
only with the implementation appropriate for the target being built.

------
AdmiralAsshat
To my knowledge, maintaining compatibility is one of the stated goals of Vim,
even at the expense of performance. NeoVim is supposed to strip out some of
these antiquated features.

~~~
austinl
Also, it just so happens that the people working on NeoVim are currently
porting all IO to libuv (the cross-platform library mentioned in the article).

[https://github.com/neovim/neovim#whats-being-worked-on-
now](https://github.com/neovim/neovim#whats-being-worked-on-now)

~~~
chronial
See here:
[https://github.com/neovim/neovim/commit/4528046](https://github.com/neovim/neovim/commit/4528046)

------
jandrese
This is a classic case of why legacy code is a nightmare to maintain. It's the
OpenSSL situation all over again, and one can definitely see the appeal of
going through there and ripping out all of the functionality that nobody uses
anymore just to make maintenance less of a nightmare. Luckily vim doesn't run
suid root on any sane system, so all of this legacy cruft is not as huge of a
threat surface on the machine.

~~~
kedean
It is still an issue, since vi/vim gets used for sudo purposes all the time.
Any time someone uses visudo to modify their sudoers file, for example, that's
giving root privileges to vim.

~~~
kccqzy
Visudo specifically avoids this problem by creating a copy of the sudoers
file, invoke the editor as a normal user, wait for it to exit, check syntax
and then rename the temporary file into the actual sudoers file.

------
Camillo
I'm a bit surprised that they have code like this:

    
    
            if (msec > 0 && (
        #  ifdef FEAT_XCLIPBOARD
                xterm_Shell != (Widget)0
        #   if defined(USE_XSMP) || defined(FEAT_MZSCHEME)
                ||
        #   endif
        #  endif
        #  ifdef USE_XSMP
                xsmp_icefd != -1
        #   ifdef FEAT_MZSCHEME
                ||
        #   endif
        #  endif
        #  ifdef FEAT_MZSCHEME
            (mzthreads_allowed() && p_mzq > 0)
        #  endif
                ))
    

When they could have written:

    
    
            if (msec > 0 && (
        #  ifdef FEAT_XCLIPBOARD
                xterm_Shell != (Widget)0 ||
        #  endif
        #  ifdef USE_XSMP
                xsmp_icefd != -1 ||
        #  endif
        #  ifdef FEAT_MZSCHEME
            (mzthreads_allowed() && p_mzq > 0) ||
        #  endif
                0))
    

Perhaps they were targeting compilers so primitive that they could not
optimize out a "|| 0".

(edited to hide my shame)

~~~
DSMan195276
A slight nit-pick, though I was going to say the exact same thing - `|| 0`. If
you do `|| 1` the compiler's going to optimize the entire 'or' expression out
;)

I was thinking that using `0` would cause a problem when you select none of
those compilation options, but then I realize the code won't even _compile_ if
you don't select at-least one (You get empty parenthesis fallowing the '&&' in
the 'if' statement), so it doesn't seem like a big deal.

~~~
Camillo
That's not a slight nit-pick, that's me being an idiot. I'm going to delete my
account and travel the world barefoot for two years as penance.

------
riquito
I've just realized that Vim never exited with an error in my whole life, while
pretty much all the other editors I've ever used did. While his code is old,
it's certainly resilient.

~~~
michaelmcmillan

        Open Vim. Type "1.02". Put the cursor on the 2. Hit C-x; get 1.01. Again; get 1.00. Again; get 1.01777777777777777777777.

\- Gary Bernhardt (@garybernhardt)

------
erikb
To answer most questions here: Yes, nowadays there are cross-platform
libraries you can use instead of implementing that yourself. And yes code that
has grown for centuries and contains unnecessary things or patterns that
aren't used nowadays can be refactored. Or at least that's how I'd interpret
the article.

~~~
ProAm
I really dislike the code review witch hunts you see on HN (or anywhere). On
one side of the coin you have startups that merely want to get something
thrown together with duct tape, working and ship so they can refactor later
and clean things up. On the other hand you have people writing blogs posts to
humble brag their code reading and blogging ability.

No one knows the circumstances that created this original code. The developer
may have been working on 10 projects and threw something together just so it'd
work.

If it ain't broke....

~~~
x0x0
A previous employer had a codebase that generated, on a full compilation, at
least 10k warnings. That same codebase powered $200mm/year in revenue.

~~~
omegaham
And if your developer time is better spent on producing more features than
cleaning up your previously made code, you'll end up with 10.5k warnings. I'm
sure that someone will freak out and cry, but as long as you can keep
extending it and working with it, why fix it?

Of course, technical debt builds up, and eventually you're badly locked in
until you refactor, so it's a balancing act.

~~~
nwmcsween
You're only looking at it from a sort of incompetent manager perspective.
Those 10k warnings will produce exploits, they also make reasoning about the
code much much harder costing time and money or even worse nobody understands
it.

------
saosebastiao
I would much prefer the coeffect system for managing different platforms and
capabilities:

[http://tomasp.net/blog/2014/why-coeffects-
matter/](http://tomasp.net/blog/2014/why-coeffects-matter/)

It would be so amazing to be able to take all of those ifdefs and turn them
into type variants and have the compiler enforce the safety and semantics of
the system. And it would be phenomenal for old code to break at compile time
when the environment changes, instead of waiting for a bug report to roll in.

So, paging T. Petricek...when are we gonna get it in F#? :) Even better if
someone can get it into Rust! That is the perfect type of feature for a
systems programming language.

------
mkagenius
Can't believe people are going mad over a code snippet when they don't know
under what coditions they were written and under what coditions the code base
is being maintained.

------
thebelal
For those interested in viewing this code in context is is at
([https://github.com/vim/vim/blob/db9bc0b7931d252cf578c1cd298a...](https://github.com/vim/vim/blob/db9bc0b7931d252cf578c1cd298ac905b904ebe7/src/os_unix.c#L5203-L5624))

Also note that RealWaitForChar has been completely removed from neovim since
April of 2014
([https://github.com/neovim/neovim/pull/474/files](https://github.com/neovim/neovim/pull/474/files))

------
gjkood
Does this have a noticeable performance impact for a typical vim end user,
using it in an interactive file editing workflow? On any modern hardware?

I suppose there may be some negative performance impact if we need to use it
for an automated/batch workflow (I can't think of many where something like
'sed' won't be better suited).

~~~
bpicolo
Doubtful, ifdefs are compile-time

~~~
gjkood
Agreed. I am in my 'Ready > Fire > Aim' mode today.

------
pkaye
The question is do you think it could have been done better given that they
support multiple OS/UI, etc...

~~~
geofft
You can refactor it into multiple functions (#ifdefs around a line containing
just "||"? seriously??), some of which are no-ops on some platforms /
configurations, and let the compiler deal with inlining the functions that
exist and optimizing out those that don't. (Not that you would have noticed
the overhead anyway, probably.)

~~~
x0x0
Probably with heavy code duplication though, as eg X clipboard management and
X session management appear to be cross-cutting concerns. I mean, if you bury
the ugly in just one function, I don't see how everyone is calling this horrid
engineering.

edit: I read this [1] and maybe the ugly isn't localized =P

edit2: since you're reading this, _thank you for ag_. I use it every day.

[1] [http://geoff.greer.fm/2015/01/15/why-neovim-is-better-
than-v...](http://geoff.greer.fm/2015/01/15/why-neovim-is-better-than-vim/)

~~~
geofft
(Just realized this might be confusing: I'm a different Geoff. Geoff Greer is
ggreer, but he's also in these comments.)

~~~
x0x0
wow, reading is hard. My apologies for getting you confused!

------
rootbear
I assume portability is why the function declaration is old K&R style and not
ANSI prototype style. I'm always surprised when I see K&R style in modern(ish)
code. I do miss the ability to declare multiple parameters of the same type
without repeating the type name, though. Oddly, several modern languages (like
D) seem to think that's a feature.

~~~
glass-
Check vim's style guide (:help style-example in vim), you'll find a note that
says:

> NOTE: Don't use ANSI style function declarations. A few people still have to
> use a compiler that doesn't support it.

~~~
rootbear
Amazing. I wonder what platforms those are.

------
Spakman
I suppose it's because I've spent quite a lot of time in the GNU Readline
source that this function doesn't seem _that_ bad. Not that I'm wanting to
endorse it as great code, of course.

I find it fascinating to look at how old but very well used software develops
(often, but not always) in ways that seem completely ghastly.

------
jhallenworld
JOE never tries to use poll or select (it uses multiple sub-processes writing
to the same pipe to implement input multiplexing). This archaic way works on
all version of UNIX, even very old ones which don't have select() or poll().
No #ifdefs needed :-)

Output processing in JOE is interesting: when writing to the terminal, JOE
periodically sleeps for a time based on the baud rate and amount of data. This
is to prevent too much output buffering so that at low baud rates you can
interrupt the screen refresh with type-ahead. If you don't do this at 1200
baud it's a big issue (you could wait for hours if you hit PageDown too many
times).

------
lnanek2
It's ugly, but vim is one of the very few programs I've always seen ready and
available on any machine I log into. Even the login shell used/available
changes, but vim is always there. So they are doing something right.

------
kyberias
I don't see any Amiga specific code here.

------
kelvin0
EMACs also does this with a VM made from a DSL written in LISP. Of course, I
never use either VIM or EMACS ... I am mostly in VS land 99% of my time. I
can't even try to imagine what THAT code most look like.

------
jerf
It would be interesting to run this through the preprocessor and see what it
comes out as on a real Linux system or something. I can't really tell through
all the #ifs, but it would certainly be much shorter.

------
dale-cooper
Another "interesting" project to look at is nethack.

------
mrbill
Speaking of legacy platforms, anyone know of a vi/tinyvi/etc work-alike for
CP/M?

I'm going to be doing some hacking on z80 assembly via Yaze, and it would be
great if I could have the editor I'm used to, natively on the platform (rather
than having to edit in OSX or whatever).

------
jheriko
there is no excuse for this. its trivially refactorable into something easier
to maintain with less ifdefs if you are willing to include/exclude files by
platform. (and if not, just wrap whole file contents in single ifdefs)

this is a monster that has grown over time without any proper love.

------
noobermin
To be very frank, I've seen much worse. "Functions" more than 400 lines long,
perhaps even 1000 or more, riddled with more macros like in this sample than
you can shake a stick at. I'd share a sample of this ghastly stuff, but it's
closed source, alas.

------
hackuser
I wonder how many examples there are of a more beloved UI (among coders) than
Vim's keyboard input. For myself, its exceptional keyboard responsiveness is a
essential part of what makes Vim feel so effortless, so hardwired to my brain.

I wish all code was equally buggy.

------
renox
> Vim tries to be compatible with every OS, including dead ones such as BeOS,
> VMS, and Amiga.

There are people working on BeOS revival: Haiku OS, they would (rightfully)
quite annoyed if vim dropped support for the OS they're trying to resurrect..

------
reilly3000
What are some examples of open-source project that are in daily use by
millions of folks that don't have some spaghetti code? I am sure there are
some out there, but I really can't think of any at the moment.

------
fibo
If I use vi, vim or neovim as a user I eill not look at the code first, I will
look at availability. If I am working on a RedHat server for a customer of
mine I will use vi cause it is already installed.

------
MBlume
I'd like to see what the code looks like after it's been through the
preprocessor in a modern Linux environment -- I'm guessing a hell of a lot
shorter?

------
andrewchambers
ifdefs inside functions create monstrosities, it is far better to have few
preprocessor conditionals, and instead have platform specific c files.

------
throwaway3648
Just getting window resizing across curses implementations took me 200 lines
of C

------
aceperry
First glance at the headline had me wondering, "Is this a good thing?"

------
vmorgulis
ncurse is similar with a lot of legacy code to support old terminals.

------
amadeus
Yet another Vim bashing post by the Floobits bros.

It's getting old.

------
daxfohl
So when will 16K be a standard resolution?

------
PSeitz
Do one thing and do it well

------
manis404
Really, why does Vim still exists in the first place?

~~~
dmerrick
Because it is one of the best text editors ever created.

~~~
pvdebbe
it's in the top two. ;)

------
cat_dev_null
If you are all such experts, why don't you "fix it" and then pull request it
in.

~~~
brandonwamboldt
Please, you can critique something even if you lack the skills to fix it, or
the desire to fix it, or the time to fix it.

Also, you can point out that something is broken even if you have no
suggestions on how to fix it.

~~~
knieveltech
You certainly can, and in polite society folks who frequently engage in this
sort of behavior are commonly referred to as "assholes".

------
rando3826
Emacs C code is far better! And it's only ~22% c code instead of vim's ~66%

reference: [https://www.openhub.net/p/vim](https://www.openhub.net/p/vim)
[https://www.openhub.net/p/emacs](https://www.openhub.net/p/emacs)

~~~
brudgers
Vim or Emacs, either is fine. So is Eclipse. Etc.

~~~
wtbob
> Vim or Emacs, either is fine.

True enough.

> So is Eclipse.

No, Eclipse must be killed with fire.

