July 15th, 2015
(written by lawrence krubner, however indented passages are often quotes). You can contact lawrence at: email@example.com
When I worked at Category4, they had a homegrown HTML library. The method that generated HTML tables was over 1,100 lines of code (not a typo). So I’ve seen some fairly large functions. Dealing with them is why I now prefer 5 line functions written in a clean language like Clojure.
This function is over 400 lines and contains over 40 #ifdefs. Its job? To wait for keyboard input. Several factors caused this code to be so insane.
1.) Vim tries to be compatible with every OS, including dead ones such as BeOS, VMS, and Amiga.
2.) Features that drastically change behavior are enabled/disabled with preprocessor flags.
3.) Cross-platform libraries like libuv didn’t exist when Vim was created.
Complexity stemming from cross-platform support may be excusable, but even something as simple as reading keyboard input is a nightmare in Vim. Stepping through with a debugger will result in call stacks such as inchar() in getchar.c calling ui_inchar() in ui.c, which calls mch_inchar() in os_unix.c, which calls WaitForChar(), which calls RealWaitForChar(). This call stack can be completely different on different platforms. It also differs when running in command line versus GUI mode.
Figuring out Vim’s control flow is harrowing. Even when you hit paydirt in RealWaitForChar(), the code is extremely hard to follow. Here’s a snippet. You can view the whole function at my Vim Hall of WTF.
# if defined(HAVE_GETTIMEOFDAY) && defined(HAVE_SYS_TIME_H)
/* Remember at what time we started, so that we know how much longer we
* should wait after being interrupted. */
# define USE_START_TV
struct timeval start_tv;
if (msec > 0 && (
# ifdef FEAT_XCLIPBOARD
xterm_Shell != (Widget)0
# if defined(USE_XSMP) || defined(FEAT_MZSCHEME)
# ifdef USE_XSMP
xsmp_icefd != -1
# ifdef FEAT_MZSCHEME
# ifdef FEAT_MZSCHEME
(mzthreads_allowed() && p_mzq > 0)
That if statement’s conditions span 17 lines and 4 different #ifdefs. All to call gettimeofday(). Amusingly, even the body of that statement has a bug: times returned by gettimeofday() are not guaranteed to increase. User intervention or ntpd can cause the system clock to go back in time. The correct solution is to use a monotonically increasing time function, such Linux’s clock_gettime() or OS X’s mach_absolute_time().