This is the source code for e, a text editor. It's implemented using C++ and JavaScript (via Google's V8).
A key feature is that all editor functionality is written in JavaScript which can be extended and customized by the user, without the need to recompile the binary. There is zero editor logic hard coded in C++. There is zero editor logic hard coded in JavaScript. Everything is completely extensible.
It doesn't work very well. There are a lot of rough edges. In theory you can use it for editing, and it's possible to save buffers. But it's missing a lot of features that other editors have.
Currently work is being done to make vi mode more functional. Implementing most of the classic BSD vi functionality (i.e. without vim extensions) is definitely within sight at this point, and would make the editor a lot more usable.
The project is not yet self-hosting (I'm editing the code in Emacs).
Love it or hate it, one of the things that makes Emacs a really, really great editor is that it's not written in C or C++, it's written in elisp. Only the lowest-level I/O bits are written in C, and exported to elisp code.
This makes Emacs really extensible. The author of e normally uses Emacs, and runs it using viper-mode, a minor mode that makes Emacs behave like vi. That's pretty crazy -- using Emacs, you can pretend like you're using vi, but also tap into this huge millions-of-lines-of-code Emacs elisp ecosystem. The fact that you can completely customize Emacs down to the lowest level of keyboard handling to tack on a brand new editing system -- and have it still be fast -- shows the power of this approach.
That said, if you've written much elisp you know it's not a very good language. It's very antiquated, and very complicated (and I'm not just saying that because it's a Lisp -- it's antiquated and complicated compared to pretty much every other Lisp implementation in wide use today). Elisp also has the problem that it doesn't have a state-of-the-art bytecode interpreter. This can make elisp rather slow, particularly when loading new files and while garbage collections are running.
Editors like vim feel fast and have great editing capabilities, but are hard to customize. If you've ever tried to really dive into vimscript or its Python bindings you'll recognize that the scripting features feel tacked on and clunky.
JavaScript solves all (or nearly all of these problems). It's really easy (and fun!) to use. Tons of people already know how to read, write, and debug JavaScript. And there are a number of great open source implementations. This project uses Google's V8 implementation of JavaScript.
The goal of e is to keep the amount of C/C++ code to a minimum, and try to provide a super-fast ultra-customizable text editor using JavaScript. Out of the box it will probably look and feel like a lot like vim.
I think Node.js is a great project. It has I/O capabilities, and there's at least one curses binding floating around, so there's no reason you couldn't write the same thing using node.
The main answer is I wanted a project to expand my C++ skills, hence this project. That said, I think there are some other advantages to a separate V8 embedding for an editor:
- After compiling, you get a real bona-fide binary; you can copy it around on different machines if they have the right shared libraries built.
- It's easy to statically link things, creating a stand-alone binary; this makes it even easier to copy around and install on different machines.
- The I/O model is a lot simpler (mostly due to doing synchronous I/O).
- You can still do asynchronous I/O if you want; as a matter of fact, behind the
scenes e is using boost::asio to process keypresses asynchronously (to allow
various timers to run in the background). Look at the implementation of
CursesWindow::Loop
if you want to learn more about this.
Here's a screenshot. It's not very exciting.
Ctrl-C will always exit the editor.
You can save a file with Ctrl-S. There's currently no way to open a new buffer.
The best way to do this is to add JavaScript code to ~/.e.js
. The way the
editor starts up is that by default it will do the following (in the order
listed):
- load
js/core.js
for an unoptimized build, or the equivalent "bundled" JavaScript in an optimized build (see the "Bundled Javascript" section below for details). - load any scripts passed on the command line with
-s
or--script
- load
~/.e.js
Because you can use the require()
method to include other scripts, you can
structure your own customizations however you like (e.g. by splitting them out
into multiple files).
In general most of the core event loop uses the same event listener pattern as browsers, which makes it easy to extend/customize behavior in a non-intrusive way. The various events and listeners that you can use are not well documented, however.
When running make
, documentation will be generated at docs/jsdoc.html
. This
will happen every time the e
binary is successfully compiled.
C++ code all lives in the src
directory. The C++ code all passes the checks by
third_party/cpplint.py
(which you should install as a git hook if you're
hacking on the editor).
JavaScript code (well, ECMAScript really) lives in the js/
directory.
Third party code, regardless of language or origin, is in the third_party/
directory. This also includes some third party "data", such as the terminfo
capabilities file.
Read on to get the appropriate instructions for your operating system.
The following are dependencies that must be satisified to build the editor:
- boost, including the boost::asio and boost::program_options components
- ncurses (you probably already have this; other curses implementations might work too)
- V8 (built as a shared library)
The following dependencies are optional:
- libunwind
- liblzma (only needed if you want to build the
optimized
opt
binary) - ncursesw (the unicode version of curses)
- tcmalloc
Many of these "optional" dependencies are controlled by USE_
defines in
e.gyp
; if you're having problems with an optional dependency, you may find it
useful to take a look at e.gyp
and tweak it according to your needs.
For a normal editor build, there will be no "bundled" JavaScript. Instead what
happens is the editor will look for a file named js/core.js
and load that as
the first script.
For an optimized editor build (i.e. the opt
binary), the core JavaScript code
will be bundled into the binary. This is done by the script
scripts/gen_bundled_core.py
. This script will attempt to recursively expand
any requires()
statements seen in the file js/core.js
, until it has a single
string representing all of the JavaScript code that might be needed while
running the editor. This JavaScript is then "pre-compiled" by
scripts/precompile.cc
which uses the public V8 pre-compilation methods, and
additionally compressed using LZMA.
The pre-compilation data and the LZMA compressed core scripts are written out to
src/bundled_core.h
and src/bundled_core.cc
and end up included in the final
executable. When the executable starts up, it will decompress the LZMA data and
load the core script.
If you're running an optimized build, and don't want to run the editor with the bundled JavaScript, invoke it as
e --debug <args>
This will implicitly skip loading any bundled javascript, and force js/core.js
to be the first JavaScript file loaded, just like in the unoptimized build. The
--debug
flag is actually a shortcut for, and identical to, running the editor
as
e --skip-core -s js/core.js
In the future the meaning of --debug
may expand to give more debugging
information, but that's unlikely to happen until the editor is out of alpha
stage.
You'll need GYP installed. You ought to then be
able to simply run make
to build the project. Internally, this will create a
directory called build
with a generated Makefile
from GYP, and then invoking
make
in the future will run any necessary build scripts and then do a
recursive make using that file. (Note: the file generated by GYP is
build/Makefile
, the root-level Makefile
is different.)
To have GYP build your auto-generated Makefile
, just run ./configure
. This
script will correctly invoke GYP for the project, and report errors if it fails
to run GYP.
If you have any build problems while running make
, you might want to try
invoking make
as
make V=1
which sets verbose build output. This will help you hunt down the exact compiler/linker errors.
Building on Ubuntu, both 11.10 (Oneiric) and 12.04 (Precise), is supported. For both releases you need to install the following packages:
sudo apt-get install binutils-gold build-essential git gyp libboost-dev \
libboost-program-options-dev libboost-system-dev liblzma-dev \
libncurses5-dev libncursesw5-dev libtcmalloc-minimal0 libunwind7-dev \
libv8-dev pkg-config python-lzma python-jinja2
Then you will need to set up a tcmalloc.so
symlink:
sudo ln -s /usr/lib/libtcmalloc_minimal.so.0 /usr/lib/libtcmalloc.so
After that you should be good to go.
Pretty much the same as Ubuntu, with slightly different packages (not listed
here; inquire if you need assistance). You will also need to manually create a
tcmalloc.so
symlink, as with Ubuntu. Unfortunately, Ubuntu and Fedora version
the tcmalloc .so files differently, so there's not a straightforward way to
handle this setup automatically.
This should be pretty similar to building on Linux, except that before it will
work someone needs to modify e.gyp
. It's probably just a matter of instructing
GYP to correctly link things.
This is unlikely to work. There are a fair number of Unix-isms in the code (although nothing too nasty, they should be fixable by using various Boost interfaces).
Probably the more difficult thing about this would be getting a working curses implementation. There seem to be a couple out there but who knows how well they work.