├── .gitignore ├── CHANGELOG.md ├── README.md ├── RELEASE.md ├── doc ├── 00.index.md ├── 01.setup.md ├── 02.enteringRawMode.md ├── 03.rawInputAndOutput.md ├── 04.aTextViewer.md ├── 05.aTextEditor.md ├── 06.search.md ├── 07.syntaxHighlighting.md ├── 08.appendices.md └── html_in │ ├── fonts.css │ ├── fonts │ ├── fira-mono-v5-latin-500.eot │ ├── fira-mono-v5-latin-500.svg │ ├── fira-mono-v5-latin-500.ttf │ ├── fira-mono-v5-latin-500.woff │ ├── fira-mono-v5-latin-500.woff2 │ ├── fira-mono-v5-latin-regular.eot │ ├── fira-mono-v5-latin-regular.svg │ ├── fira-mono-v5-latin-regular.ttf │ ├── fira-mono-v5-latin-regular.woff │ ├── fira-mono-v5-latin-regular.woff2 │ ├── pt-serif-v8-latin-700.eot │ ├── pt-serif-v8-latin-700.svg │ ├── pt-serif-v8-latin-700.ttf │ ├── pt-serif-v8-latin-700.woff │ ├── pt-serif-v8-latin-700.woff2 │ ├── pt-serif-v8-latin-italic.eot │ ├── pt-serif-v8-latin-italic.svg │ ├── pt-serif-v8-latin-italic.ttf │ ├── pt-serif-v8-latin-italic.woff │ ├── pt-serif-v8-latin-italic.woff2 │ ├── pt-serif-v8-latin-regular.eot │ ├── pt-serif-v8-latin-regular.svg │ ├── pt-serif-v8-latin-regular.ttf │ ├── pt-serif-v8-latin-regular.woff │ ├── pt-serif-v8-latin-regular.woff2 │ ├── work-sans-v2-latin-700.eot │ ├── work-sans-v2-latin-700.svg │ ├── work-sans-v2-latin-700.ttf │ ├── work-sans-v2-latin-700.woff │ └── work-sans-v2-latin-700.woff2 │ ├── i │ ├── arrow.png │ ├── lego-step-one.png │ └── x.png │ ├── style.css │ ├── template.html │ └── template_index.html ├── leg.yml ├── repo-extra ├── LICENSE └── README.md ├── steps.diff └── steps.diff.LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | ftp.yml 2 | .cached-diffs 3 | steps/ 4 | repo/ 5 | doc/html_out/ 6 | doc/html_offline/ 7 | *.zip 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.0.0beta11 (October 3, 2017) 4 | 5 | * Fix number of bits in binary example. (Thanks @osiyuk) 6 | 7 | ## 1.0.0beta10 (July 12, 2017) 8 | 9 | * Make bit numbering clearer in the explanation of step 20. (Thanks @fyce) 10 | 11 | ## 1.0.0beta9 (May 22, 2017) 12 | 13 | * Make explanation of step 30 clearer by reordering paragraphs. (Thanks @mtn) 14 | * Update [How to contribute](http://viewsourcecode.org/snaptoken/kilo/08.appendices.html#how-to-contribute) 15 | section. (Thanks @mtn) 16 | 17 | ## 1.0.0beta8 (April 28, 2017) 18 | 19 | * `strchr()` was described incorrectly in 20 | [chapter 7](http://viewsourcecode.org/snaptoken/kilo/07.syntaxHighlighting.html#colorful-numbers). 21 | (Thanks Rudi) 22 | 23 | ## 1.0.0beta7 (April 20, 2017) 24 | 25 | * In `editorSelectSyntaxHighlight()`, change the logic for filename pattern 26 | matching, so that filenames like `kilo.c.c` will work. (Thanks Ivandro) 27 | 28 | ## 1.0.0beta6 (April 10, 2017) 29 | 30 | * Near the end of chapter 6, add `if (last_match == -1) direction = 1;` to 31 | `editorFindCallback()`, to fix a segfault. (Thanks @agacek) 32 | * In the [Scrolling with Page Up and Page Down](http://viewsourcecode.org/snaptoken/kilo/04.aTextViewer.html#scrolling-with-page-up-and-page-down) 33 | section in chapter 4, add `if (E.cy > E.numrows) E.cy = E.numrows;` to 34 | `editorProcessKeypress()`, so the cursor stays within the file. (Thanks 35 | @agacek) 36 | * At the beginning of chapter 3, remove the keypress printing code during the 37 | `refactor-input` step instead of the `ctrl-q` step, and make it clear that 38 | you're supposed to remove that code. (Thanks @wonthegame) 39 | 40 | ## 1.0.0beta5 (April 9, 2017) 41 | 42 | * Remove *all* newlines and carriage returns from the end of each line read by 43 | `editorOpen()`, by changing `if (linelen > 0 && ...` to 44 | `while (linelen > 0 && ...`. This allows text files with DOS line endings 45 | (`\r\n`) to be opened properly. 46 | 47 | ## 1.0.0beta4 (April 8, 2017) 48 | 49 | * In the [Multiple lines](http://viewsourcecode.org/snaptoken/kilo/04.aTextViewer.html#multiple-lines) 50 | section, make it clearer that the program doesn't compile for those steps. 51 | (Thanks @mapleray and @agentultra) 52 | 53 | ## 1.0.0beta3 (April 7, 2017) 54 | 55 | * In `editorPrompt()`, change `!iscntrl(c)` to `!iscntrl(c) && c < 128`, so 56 | that we don't try to append special keys like the arrow keys to the prompt 57 | input. (Thanks @fmdkdd) 58 | 59 | ## 1.0.0beta2 (April 6, 2017) 60 | 61 | * Replace all instances of `isprint()` with `!iscntrl()`, so that extended 62 | ASCII characters can be inserted and displayed in the editor. 63 | * Include font files in offline version of tutorial. 64 | 65 | ## 1.0.0beta1 (April 6, 2017) 66 | 67 | * Add changelog. 68 | * Fix landing page typo. (Thanks @mtr) 69 | 70 | ## 1.0.0beta0 (April 4, 2017) 71 | 72 | * First public release. 73 | 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Build Your Own Text Editor 2 | 3 | The tutorial is available here: http://viewsourcecode.org/snaptoken/kilo 4 | 5 | It is a static HTML site, and you can download your own local copy from here: 6 | https://github.com/snaptoken/kilo-tutorial/releases 7 | 8 | See the 9 | [appendices](http://viewsourcecode.org/snaptoken/kilo/08.appendices.html) for 10 | more information about the tutorial itself, including how to contribute. 11 | 12 | ## License 13 | 14 | The `kilo` source code contained in `steps.diff` was 15 | [originally written](https://github.com/antirez/kilo) by Salvatore Sanfilippo 16 | (aka [antirez](https://github.com/antirez)) and released under the BSD 2-clause 17 | license (see `steps.diff.LICENSE`). I've made a few minor changes to the 18 | original code. 19 | 20 | All other files in this repository are licensed under 21 | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). 22 | 23 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # How to release a new version (note to self) 2 | 3 | 1. Update version field in `leg.yml` 4 | 2. Update `CHANGELOG.md` 5 | 3. Run `leg doc -z` 6 | 4. Commit and push 7 | 5. Run `leg deploy` 8 | 6. Create new release on GitHub, uploading `doc/kilo-tutorial-{version}.zip` 9 | and copying notes from the `CHANGELOG.md` 10 | 11 | -------------------------------------------------------------------------------- /doc/00.index.md: -------------------------------------------------------------------------------- 1 | # Build Your Own Text Editor 2 | 3 | Welcome! This is an instruction booklet that shows you how to build a text 4 | editor in C. 5 | 6 | The text editor is [antirez's kilo](http://antirez.com/news/108), with some 7 | changes. It's about 1000 lines of C in a single file with no dependencies, and 8 | it implements all the basic features you expect in a minimal editor, as well as 9 | syntax highlighting and a search feature. 10 | 11 | This booklet walks you through building the editor in **184 steps**. Each step, 12 | you'll add, change, or remove a few lines of code. Most steps, you'll be able 13 | to **observe the changes** you made by compiling and running the program 14 | immediately afterwards. 15 | 16 | I explain each step along the way, sometimes in a lot of detail. Feel free to 17 | skim or skip the prose, as the main point of this is that **you are going to 18 | build a text editor from scratch**! Anything you learn along the way is bonus, 19 | and there's plenty to learn just from typing in the changes to the code and 20 | observing the results. 21 | 22 | See the [appendices](08.appendices.html) for more information on the tutorial 23 | itself (including what to do if you get stuck, and where to get help). 24 | 25 | If you're ready to begin, then go to [chapter 1](01.setup.html)! 26 | 27 | ## Table of Contents 28 | 29 | {{toc}} 30 | 31 | -------------------------------------------------------------------------------- /doc/01.setup.md: -------------------------------------------------------------------------------- 1 | # Setup 2 | 3 | ![Step 1 of a Lego instruction booklet: a single Lego piece](i/lego-step-one.png) 4 | 5 | Ahh, step 1. Don't you love a fresh start on a blank slate? And then selecting 6 | that singular brick onto which you will build your entire palatial estate? 7 | 8 | Unfortunately, when you're building a *computer program*, step 1 can get... 9 | complicated. And frustrating. You have to make sure your environment is set up 10 | for the programming language you're using, and you have to figure out how to 11 | compile and run your program in that environment. 12 | 13 | Fortunately, the program we are building doesn't depend on any external 14 | libraries, so you don't need anything beyond a C compiler and the standard 15 | library it comes with. (We will also be using the `make` program.) To check 16 | whether you have a C compiler installed, try running `cc --version` at the 17 | command line (`cc` stands for "C Compiler"). To check whether you have `make`, 18 | try running `make -v`. 19 | 20 | ## How to install a C compiler... 21 | 22 | ### ...in Windows 23 | 24 | You will **need to install some kind of Linux environment within Windows**. 25 | This is because our text editor interacts with the terminal at a low level 26 | using the `` header, which isn't available on Windows. I suggest 27 | using either 28 | [Bash on Windows](https://msdn.microsoft.com/en-us/commandline/wsl/about) or 29 | [Cygwin](https://www.cygwin.com/). 30 | 31 | **Bash on Windows**: Only works on 64-bit Windows 10. See the 32 | [installation guide](https://msdn.microsoft.com/en-us/commandline/wsl/install_guide). 33 | After installing it, run `bash` at the command line whenever you want to enter 34 | the Linux environment. Inside `bash`, run `sudo apt-get install gcc make` to 35 | install the GNU Compiler Collection and the `make` program. If `sudo` takes a 36 | really long time to do anything, you may have to 37 | [fix your /etc/hosts file](https://superuser.com/questions/1108197). 38 | 39 | **Cygwin**: Download the installer from 40 | [cygwin.com/install.html](https://cygwin.com/install.html). When the installer 41 | asks you to select packages to install, look in the `devel` category and select 42 | the `gcc-core` and `make` packages. To use Cygwin, you have to run the Cygwin 43 | terminal program. Unlike Bash on Windows, in Cygwin your home directory is 44 | separate from your Windows home directory. If you installed Cygwin to 45 | `C:\cygwin64`, then your home directory is at `C:\cygwin64\home\yourname`. So 46 | if you want to use a text editor outside of Cygwin to write your code, that's 47 | where you'll want to save to. 48 | 49 | ### ...in macOS 50 | 51 | When you try to run the `cc` command, a window should pop up asking if you want 52 | to install the command line developer tools. You can also run 53 | `xcode-select --install` to get this window to pop up. Then just click 54 | "Install" and it will install a C compiler and `make`, among other things. 55 | 56 | ### ...in Linux 57 | 58 | In Ubuntu, it's `sudo apt-get install gcc make`. Other distributions should 59 | have `gcc` and `make` packages available as well. 60 | 61 | ## The `main()` function 62 | 63 | Create a new file named `kilo.c` and give it a `main()` function. (`kilo` is 64 | the name of the text editor we are building.) 65 | 66 | {{main}} 67 | 68 | In C, you have to put all your executable code inside functions. The `main()` 69 | function in C is special. It is the default starting point when you run your 70 | program. When you `return` from the `main()` function, the program exits 71 | and passes the returned integer back to the operating system. A return value of 72 | `0` indicates success. 73 | 74 | C is a compiled language. That means we need to run our program through a C 75 | compiler to turn it into an executable file. We then run that executable like 76 | we would run any other program on the command line. 77 | 78 | To compile `kilo.c`, run `cc kilo.c -o kilo` in your shell. If no errors occur, 79 | this will produce an executable named `kilo`. `-o` stands for "output", and 80 | specifies that the output executable should be named `kilo`. 81 | 82 | To run `kilo`, type `./kilo` in your shell and press Enter. The 83 | program doesn't print any output, but you can check its exit status (the value 84 | `main()` returns) by running `echo $?`, which should print `0`. 85 | 86 | ## Compiling with `make` 87 | 88 | Typing `cc kilo.c -o kilo` every time you want to recompile gets tiring. The 89 | `make` program allows you to simply run `make` and it will compile your program 90 | for you. You just have to supply a `Makefile` to tell it how to compile your 91 | program. 92 | 93 | Create a new file literally named `Makefile` with the following contents. 94 | 95 | {{make}} 96 | 97 | The first line says that `kilo` is what we want to build, and that `kilo.c` is 98 | what's required to build it. The second line specifies the command to run in 99 | order to actually build `kilo` out of `kilo.c`. Make sure to indent the second 100 | line with an actual tab character, and not with spaces. You can indent C files 101 | however you want, but `Makefile`s must use tabs. 102 | 103 | We have added a few things to the compilation command: 104 | 105 | * `$(CC)` is a variable that `make` expands to `cc` by default. 106 | * `-Wall` stands for "**all** **W**arnings", and gets the compiler to warn you 107 | when it sees code in your program that might not technically be wrong, but is 108 | considered bad or questionable usage of the C language, like using variables 109 | before initializing them. 110 | * `-Wextra` and `-pedantic` turn on even more warnings. For each step in this 111 | tutorial, if your program compiles, it shouldn't produce any warnings except 112 | for "unused variable" warnings in some cases. If you get any other warnings, 113 | check to make sure your code exactly matches the code in that step. 114 | * `-std=c99` specifies the exact version of the C language **st**an**d**ard 115 | we're using, which is [C99](https://en.wikipedia.org/wiki/C99). C99 allows us 116 | to declare variables anywhere within a function, whereas 117 | [ANSI C](https://en.wikipedia.org/wiki/ANSI_C) requires all variables to be 118 | declared at the top of a function or block. 119 | 120 | Now that we have a `Makefile`, try running `make` to compile the program. 121 | 122 | It may output ``make: `kilo' is up to date.``. It can tell that the current 123 | version of `kilo.c` has already been compiled by looking at each file's 124 | last-modified timestamp. If `kilo` was last modified after `kilo.c` was last 125 | modified, then `make` assumes that `kilo.c` has already been compiled, and so 126 | it doesn't bother running the compilation command. If `kilo.c` was last 127 | modified after `kilo` was, then `make` recompiles `kilo.c`. This is more useful 128 | for large projects with many different components to compile, as most of the 129 | components shouldn't need to be recompiled over and over when you're only 130 | making changes to one component's source code. 131 | 132 | Try changing the return value in `kilo.c` to a number other than `0`. Then run 133 | `make`, and you should see it compile. Run `./kilo`, and try `echo $?` to see 134 | if you get the number you changed it to. Then change it back to `0`, recompile, 135 | and make sure it's back to returning `0`. 136 | 137 | After each step in this tutorial, you will want to recompile `kilo.c`, see if 138 | it finds any errors in your code, and then run `./kilo`. It is easy to forget 139 | to recompile, and just run `./kilo`, and wonder why your changes to `kilo.c` 140 | don't seem to have any effect. You must recompile in order for changes in 141 | `kilo.c` to be reflected in `kilo`. 142 | 143 | In the [next chapter](02.enteringRawMode.html), we'll work on getting the 144 | terminal into *raw mode*, and reading individual keypresses from the user. 145 | 146 | -------------------------------------------------------------------------------- /doc/02.enteringRawMode.md: -------------------------------------------------------------------------------- 1 | # Entering raw mode 2 | 3 | Let's try and read keypresses from the user. (The lines you need to add are 4 | highlighted and marked with arrows.) 5 | 6 | {{read}} 7 | 8 | `read()` and `STDIN_FILENO` come from ``. We are asking `read()` to 9 | read `1` byte from the standard input into the variable `c`, and to keep doing 10 | it until there are no more bytes to read. `read()` returns the number of bytes 11 | that it read, and will return `0` when it reaches the end of a file. 12 | 13 | When you run `./kilo`, your terminal gets hooked up to the standard input, and 14 | so your keyboard input gets read into the `c` variable. However, by default 15 | your terminal starts in **canonical mode**, also called **cooked mode**. In 16 | this mode, keyboard input is only sent to your program when the user presses 17 | Enter. This is useful for many programs: it lets the user type in a 18 | line of text, use Backspace to fix errors until they get their input 19 | exactly the way they want it, and finally press Enter to send it to 20 | the program. But it does not work well for programs with more complex user 21 | interfaces, like text editors. We want to process each keypress as it comes in, 22 | so we can respond to it immediately. 23 | 24 | What we want is **raw mode**. Unfortunately, there is no simple switch you can 25 | flip to set the terminal to raw mode. Raw mode is achieved by turning off a 26 | great many flags in the terminal, which we will do gradually over the course of 27 | this chapter. 28 | 29 | To exit the above program, press Ctrl-D to tell `read()` that it's 30 | reached the end of file. Or you can always press Ctrl-C to signal 31 | the process to terminate immediately. 32 | 33 | ## Press q to quit? 34 | 35 | To demonstrate how canonical mode works, we'll have the program exit when it 36 | reads a q keypress from the user. (Lines you need to change are 37 | highlighted and marked the same way as lines you need to add.) 38 | 39 | {{press-q}} 40 | 41 | To quit this program, you will have to type a line of text that includes a `q` 42 | in it, and then press enter. The program will quickly read the line of text one 43 | character at a time until it reads the `q`, at which point the `while` loop 44 | will stop and the program will exit. Any characters after the `q` will be left 45 | unread on the input queue, and you may see that input being fed into your shell 46 | after your program exits. 47 | 48 | ## Turn off echoing 49 | 50 | We can set a terminal's attributes by (1) using `tcgetattr()` to read the 51 | current attributes into a struct, (2) modifying the struct by hand, and 52 | (3) passing the modified struct to `tcsetattr()` to write the new terminal 53 | attributes back out. Let's try turning off the `ECHO` feature this way. 54 | 55 | {{echo}} 56 | 57 | `struct termios`, `tcgetattr()`, `tcsetattr()`, `ECHO`, and `TCSAFLUSH` all 58 | come from ``. 59 | 60 | The `ECHO` feature causes each key you type to be printed to the terminal, so 61 | you can see what you're typing. This is useful in canonical mode, but really 62 | gets in the way when we are trying to carefully render a user interface in raw 63 | mode. So we turn it off. This program does the same thing as the one in the 64 | previous step, it just doesn't print what you are typing. You may be familiar 65 | with this mode if you've ever had to type a password at the terminal, when 66 | using `sudo` for example. 67 | 68 | After the program quits, depending on your shell, you may find your terminal is 69 | still not echoing what you type. Don't worry, it will still listen to what you 70 | type. Just press Ctrl-C to start a fresh line of input to your 71 | shell, and type in `reset` and press Enter. This resets your 72 | terminal back to normal in most cases. Failing that, you can always restart 73 | your terminal emulator. We'll fix this whole problem in the next step. 74 | 75 | Terminal attributes can be read into a `termios` struct by `tcgetattr()`. After 76 | modifying them, you can then apply them to the terminal using `tcsetattr()`. 77 | The `TCSAFLUSH` argument specifies when to apply the change: in this case, it 78 | waits for all pending output to be written to the terminal, and also discards 79 | any input that hasn't been read. 80 | 81 | The `c_lflag` field is for "local flags". A comment in macOS's `` 82 | describes it as a "dumping ground for other state". So perhaps it should be 83 | thought of as "miscellaneous flags". The other flag fields are `c_iflag` (input 84 | flags), `c_oflag` (output flags), and `c_cflag` (control flags), all of which 85 | we will have to modify to enable raw mode. 86 | 87 | `ECHO` is a [bitflag](https://en.wikipedia.org/wiki/Bit_field), defined as 88 | `00000000000000000000000000001000` in binary. We use the bitwise-NOT operator 89 | (`~`) on this value to get `11111111111111111111111111110111`. We then 90 | bitwise-AND this value with the flags field, which forces the fourth bit in the 91 | flags field to become `0`, and causes every other bit to retain its current 92 | value. Flipping bits like this is common in C. 93 | 94 | ## Disable raw mode at exit 95 | 96 | Let's be nice to the user and restore their terminal's original attributes when 97 | our program exits. We'll save a copy of the `termios` struct in its original 98 | state, and use `tcsetattr()` to apply it to the terminal when the program 99 | exits. 100 | 101 | {{atexit}} 102 | 103 | `atexit()` comes from ``. We use it to register our 104 | `disableRawMode()` function to be called automatically when the program exits, 105 | whether it exits by returning from `main()`, or by calling the `exit()` 106 | function. This way we can ensure we'll leave the terminal attributes the way 107 | we found them when our program exits. 108 | 109 | We store the original terminal attributes in a global variable, `orig_termios`. 110 | We assign the `orig_termios` struct to the `raw` struct in order to make a copy 111 | of it before we start making our changes. 112 | 113 | You may notice that leftover input is no longer fed into your shell after the 114 | program quits. This is because of the `TCSAFLUSH` option being passed to 115 | `tcsetattr()` when the program exits. As described earlier, it discards any 116 | unread input before applying the changes to the terminal. (Note: This doesn't 117 | happen in Cygwin for some reason, but it won't matter once we are reading input 118 | one byte at a time.) 119 | 120 | ## Turn off canonical mode 121 | 122 | There is an `ICANON` flag that allows us to turn off canonical mode. This means 123 | we will finally be reading input byte-by-byte, instead of line-by-line. 124 | 125 | {{icanon}} 126 | 127 | `ICANON` comes from ``. Input flags (the ones in the `c_iflag` 128 | field) generally start with `I` like `ICANON` does. However, `ICANON` is not an 129 | input flag, it's a "local" flag in the `c_lflag` field. So that's confusing. 130 | 131 | Now the program will quit as soon as you press q. 132 | 133 | ## Display keypresses 134 | 135 | To get a better idea of how input in raw mode works, let's print out each byte 136 | that we `read()`. We'll print each character's numeric ASCII value, as well as 137 | the character it represents if it is a printable character. 138 | 139 | {{keypresses}} 140 | 141 | `iscntrl()` comes from ``, and `printf()` comes from ``. 142 | 143 | `iscntrl()` tests whether a character is a control character. Control 144 | characters are nonprintable characters that we don't want to print to the 145 | screen. ASCII codes 0–31 are all control characters, and 127 is also a 146 | control character. ASCII codes 32–126 are all printable. (Check out the 147 | [ASCII table](http://asciitable.com) to see all of the characters.) 148 | 149 | `printf()` can print multiple representations of a byte. `%d` tells it to 150 | format the byte as a decimal number (its ASCII code), and `%c` tells it to 151 | write out the byte directly, as a character. 152 | 153 | This is a very useful program. It shows us how various keypresses translate 154 | into the bytes we read. Most ordinary keys translate directly into the 155 | characters they represent. But try seeing what happens when you press the arrow 156 | keys, or Escape, or Page Up, or Page Down, or 157 | Home, or End, or Backspace, or 158 | Delete, or Enter. Try key combinations with 159 | Ctrl, like Ctrl-A, Ctrl-B, etc. 160 | 161 | You'll notice a few interesting things: 162 | 163 | * Arrow keys, Page Up, Page Down, Home, and 164 | End all input 3 or 4 bytes to the terminal: `27`, `'['`, and then 165 | one or two other characters. This is known as an *escape sequence*. All 166 | escape sequences start with a `27` byte. Pressing Escape sends a 167 | single `27` byte as input. 168 | * Backspace is byte `127`. Delete is a 4-byte escape 169 | sequence. 170 | * Enter is byte `10`, which is a newline character, also known as 171 | `'\n'`. 172 | * Ctrl-A is `1`, Ctrl-B is `2`, Ctrl-C is... 173 | oh, that terminates the program, right. But the Ctrl key 174 | combinations that do work seem to map the letters A–Z to the codes 175 | 1–26. 176 | 177 | By the way, if you happen to press Ctrl-S, you may find your program 178 | seems to be frozen. What you've done is you've asked your program to [stop 179 | sending you output](https://en.wikipedia.org/wiki/Software_flow_control). Press 180 | Ctrl-Q to tell it to resume sending you output. 181 | 182 | Also, if you press Ctrl-Z (or maybe Ctrl-Y), your program 183 | will be suspended to the background. Run the `fg` command to bring it back to 184 | the foreground. (It may quit immediately after you do that, as a result of 185 | `read()` returning `-1` to indicate that an error occurred. This happens on 186 | macOS, while Linux seems to be able to resume the `read()` call properly.) 187 | 188 | ## Turn off Ctrl-C and Ctrl-Z signals 189 | 190 | By default, Ctrl-C sends a `SIGINT` signal to the current process 191 | which causes it to terminate, and Ctrl-Z sends a `SIGTSTP` signal to 192 | the current process which causes it to suspend. Let's turn off the sending of 193 | both of these signals. 194 | 195 | {{isig}} 196 | 197 | `ISIG` comes from ``. Like `ICANON`, it starts with `I` but isn't an 198 | input flag. 199 | 200 | Now Ctrl-C can be read as a `3` byte and Ctrl-Z can be 201 | read as a `26` byte. 202 | 203 | This also disables Ctrl-Y on macOS, which is like Ctrl-Z 204 | except it waits for the program to read input before suspending it. 205 | 206 | ## Disable Ctrl-S and Ctrl-Q 207 | 208 | By default, Ctrl-S and Ctrl-Q are used for 209 | [software flow control](https://en.wikipedia.org/wiki/Software_flow_control). 210 | Ctrl-S stops data from being transmitted to the terminal until you 211 | press Ctrl-Q. This originates in the days when you might want to 212 | pause the transmission of data to let a device like a printer catch up. Let's 213 | just turn off that feature. 214 | 215 | {{ixon}} 216 | 217 | `IXON` comes from ``. The `I` stands for "input flag" (which it is, 218 | unlike the other `I` flags we've seen so far) and `XON` comes from the names of 219 | the two control characters that Ctrl-S and Ctrl-Q 220 | produce: `XOFF` to pause transmission and `XON` to resume transmission. 221 | 222 | Now Ctrl-S can be read as a `19` byte and Ctrl-Q can be 223 | read as a `17` byte. 224 | 225 | ## Disable Ctrl-V 226 | 227 | On some systems, when you type Ctrl-V, the terminal waits for you to 228 | type another character and then sends that character literally. For example, 229 | before we disabled Ctrl-C, you might've been able to type 230 | Ctrl-V and then Ctrl-C to input a `3` byte. We can turn 231 | off this feature using the `IEXTEN` flag. 232 | 233 | Turning off `IEXTEN` also fixes Ctrl-O in macOS, whose terminal 234 | driver is otherwise set to discard that control character. 235 | 236 | {{iexten}} 237 | 238 | `IEXTEN` comes from ``. It is another flag that starts with `I` but 239 | belongs in the `c_lflag` field. 240 | 241 | Ctrl-V can now be read as a `22` byte, and Ctrl-O as a 242 | `15` byte. 243 | 244 | ## Fix Ctrl-M 245 | 246 | If you run the program now and go through the whole alphabet while holding down 247 | Ctrl, you should see that we have every letter except M. 248 | Ctrl-M is weird: it's being read as `10`, when we expect it to be 249 | read as `13`, since it is the 13th letter of the alphabet, and 250 | Ctrl-J already produces a `10`. What else produces `10`? The 251 | Enter key does. 252 | 253 | It turns out that the terminal is helpfully translating any carriage returns 254 | (`13`, `'\r'`) inputted by the user into newlines (`10`, `'\n'`). Let's turn 255 | off this feature. 256 | 257 | {{icrnl}} 258 | 259 | `ICRNL` comes from ``. The `I` stands for "input flag", `CR` stands 260 | for "carriage return", and `NL` stands for "new line". 261 | 262 | Now Ctrl-M is read as a `13` (carriage return), and the 263 | Enter key is also read as a `13`. 264 | 265 | ## Turn off all output processing 266 | 267 | It turns out that the terminal does a similar translation on the output side. 268 | It translates each newline (`"\n"`) we print into a carriage return followed by 269 | a newline (`"\r\n"`). The terminal requires both of these characters in order 270 | to start a new line of text. The carriage return moves the cursor back to the 271 | beginning of the current line, and the newline moves the cursor down a line, 272 | scrolling the screen if necessary. (These two distinct operations originated in 273 | the days of typewriters and 274 | [teletypes](https://en.wikipedia.org/wiki/Teleprinter).) 275 | 276 | We will turn off all output processing features by turning off the `OPOST` 277 | flag. In practice, the `"\n"` to `"\r\n"` translation is likely the only output 278 | processing feature turned on by default. 279 | 280 | {{opost}} 281 | 282 | `OPOST` comes from ``. `O` means it's an output flag, and I assume 283 | `POST` stands for "post-processing of output". 284 | 285 | If you run the program now, you'll see that the newline characters we're 286 | printing are only moving the cursor down, and not to the left side of the 287 | screen. To fix that, let's add carriage returns to our `printf()` statements. 288 | 289 | {{carriage-returns}} 290 | 291 | From now on, we'll have to write out the full `"\r\n"` whenever we want 292 | to start a new line. 293 | 294 | ## Miscellaneous flags 295 | 296 | Let's turn off a few more flags. 297 | 298 | {{misc-flags}} 299 | 300 | `BRKINT`, `INPCK`, `ISTRIP`, and `CS8` all come from ``. 301 | 302 | This step probably won't have any observable effect for you, because these 303 | flags are either already turned off, or they don't really apply to modern 304 | terminal emulators. But at one time or another, switching them off was 305 | considered (by someone) to be part of enabling "raw mode", so we carry on the 306 | tradition (of whoever that someone was) in our program. 307 | 308 | As far as I can tell: 309 | 310 | * When `BRKINT` is turned on, a 311 | [break condition](https://www.cmrr.umn.edu/~strupp/serial.html#2_3_3) will 312 | cause a `SIGINT` signal to be sent to the program, like pressing `Ctrl-C`. 313 | * `INPCK` enables parity checking, which doesn't seem to apply to modern 314 | terminal emulators. 315 | * `ISTRIP` causes the 8th bit of each input byte to be stripped, meaning it 316 | will set it to `0`. This is probably already turned off. 317 | * `CS8` is not a flag, it is a bit mask with multiple bits, which we set using 318 | the bitwise-OR (`|`) operator unlike all the flags we are turning off. It 319 | sets the character size (CS) to 8 bits per byte. On my system, it's already 320 | set that way. 321 | 322 | ## A timeout for `read()` 323 | 324 | Currently, `read()` will wait indefinitely for input from the keyboard before 325 | it returns. What if we want to do something like animate something on the 326 | screen while waiting for user input? We can set a timeout, so that `read()` 327 | returns if it doesn't get any input for a certain amount of time. 328 | 329 | {{vmin-vtime}} 330 | 331 | `VMIN` and `VTIME` come from ``. They are indexes into the `c_cc` 332 | field, which stands for "control characters", an array of bytes that control 333 | various terminal settings. 334 | 335 | The `VMIN` value sets the minimum number of bytes of input needed before 336 | `read()` can return. We set it to `0` so that `read()` returns as soon as there 337 | is any input to be read. The `VTIME` value sets the maximum amount of time to 338 | wait before `read()` returns. It is in tenths of a second, so we set it to 1/10 339 | of a second, or 100 milliseconds. If `read()` times out, it will return `0`, 340 | which makes sense because its usual return value is the number of bytes read. 341 | 342 | When you run the program, you can see how often `read()` times out. If you 343 | don't supply any input, `read()` returns without setting the `c` variable, 344 | which retains its `0` value and so you see `0`s getting printed out. If you 345 | type really fast, you can see that `read()` returns right away after each 346 | keypress, so it's not like you can only read one keypress every tenth of a 347 | second. 348 | 349 | If you're using **Bash on Windows**, you may see that `read()` still blocks for 350 | input. It doesn't seem to care about the `VTIME` value. Fortunately, this won't 351 | make too big a difference in our text editor, as we'll be basically blocking 352 | for input anyways. 353 | 354 | ## Error handling 355 | 356 | `enableRawMode()` now gets us fully into raw mode. It's time to clean up the 357 | code by adding some error handling. 358 | 359 | First, we'll add a `die()` function that prints an error message and exits the 360 | program. 361 | 362 | {{die}} 363 | 364 | `perror()` comes from ``, and `exit()` comes from ``. 365 | 366 | Most C library functions that fail will set the global `errno` variable to 367 | indicate what the error was. `perror()` looks at the global `errno` variable 368 | and prints a descriptive error message for it. It also prints the string given 369 | to it before it prints the error message, which is meant to provide context 370 | about what part of your code caused the error. 371 | 372 | After printing out the error message, we exit the program with an exit status 373 | of `1`, which indicates failure (as would any non-zero value). 374 | 375 | Let's check each of our library calls for failure, and call `die()` when they 376 | fail. 377 | 378 | {{error-handling}} 379 | 380 | `errno` and `EAGAIN` come from ``. 381 | 382 | `tcsetattr()`, `tcgetattr()`, and `read()` all return `-1` on failure, and set 383 | the `errno` value to indicate the error. 384 | 385 | In Cygwin, when `read()` times out it returns `-1` with an `errno` of `EAGAIN`, 386 | instead of just returning `0` like it's supposed to. To make it work in Cygwin, 387 | we won't treat `EAGAIN` as an error. 388 | 389 | An easy way to make `tcgetattr()` fail is to give your program a text file or a 390 | pipe as the standard input instead of your terminal. To give it a file as 391 | standard input, run `./kilo Ctrl-Q to quit 4 | 5 | Last chapter we saw that the Ctrl key combined with the alphabetic 6 | keys seemed to map to bytes 1–26. We can use this to detect 7 | Ctrl key combinations and map them to different operations in our 8 | editor. We'll start by mapping Ctrl-Q to the quit operation. 9 | 10 | {{ctrl-q}} 11 | 12 | The `CTRL_KEY` macro bitwise-ANDs a character with the value `00011111`, in 13 | binary. (In C, you generally specify bitmasks using hexadecimal, since C 14 | doesn't have binary literals, and hexadecimal is more concise and readable once 15 | you get used to it.) In other words, it sets the upper 3 bits of the character 16 | to `0`. This mirrors what the Ctrl key does in the terminal: 17 | it strips bits 5 and 6 from whatever key you press in combination with 18 | Ctrl, and sends that. (By convention, bit numbering starts from 0.) 19 | The ASCII character set seems to be designed this way on purpose. 20 | (It is also similarly designed so that you can set and clear bit 5 to switch 21 | between lowercase and uppercase.) 22 | 23 | 24 | ## Refactor keyboard input 25 | 26 | Let's make a function for low-level keypress reading, and another function for 27 | mapping keypresses to editor operations. We'll also stop printing out 28 | keypresses at this point. 29 | 30 | {{refactor-input}} 31 | 32 | `editorReadKey()`'s job is to wait for one keypress, and return it. Later, 33 | we'll expand this function to handle escape sequences, which involves reading 34 | multiple bytes that represent a single keypress, as is the case with the arrow 35 | keys. 36 | 37 | `editorProcessKeypress()` waits for a keypress, and then handles it. Later, it 38 | will map various Ctrl key combinations and other special keys to 39 | different editor functions, and insert any alphanumeric and other printable 40 | keys' characters into the text that is being edited. 41 | 42 | Note that `editorReadKey()` belongs in the `/*** terminal ***/` section because 43 | it deals with low-level terminal input, whereas `editorProcessKeypress()` 44 | belongs in the new `/*** input ***/` section because it deals with mapping keys 45 | to editor functions at a much higher level. 46 | 47 | Now we have vastly simplified `main()`, and we will try to keep it that way. 48 | 49 | ## Clear the screen 50 | 51 | We're going to render the editor's user interface to the screen after each 52 | keypress. Let's start by just clearing the screen. 53 | 54 | {{clear-screen}} 55 | 56 | `write()` and `STDOUT_FILENO` come from ``. 57 | 58 | The `4` in our `write()` call means we are writing `4` bytes out to the 59 | terminal. The first byte is `\x1b`, which is the escape character, or `27` in 60 | decimal. (Try and remember `\x1b`, we will be using it a lot.) The other three 61 | bytes are `[2J`. 62 | 63 | We are writing an *escape sequence* to the terminal. Escape sequences always 64 | start with an escape character (`27`) followed by a `[` character. Escape 65 | sequences instruct the terminal to do various text formatting tasks, such as 66 | coloring text, moving the cursor around, and clearing parts of the screen. 67 | 68 | We are using the `J` command 69 | ([Erase In Display](http://vt100.net/docs/vt100-ug/chapter3.html#ED)) to clear 70 | the screen. Escape sequence commands take arguments, which come before the 71 | command. In this case the argument is `2`, which says to clear the entire 72 | screen. `[1J` would clear the screen up to where the cursor is, and 73 | `[0J` would clear the screen from the cursor up to the end of the screen. 74 | Also, `0` is the default argument for `J`, so just `[J` by itself would 75 | also clear the screen from the cursor to the end. 76 | 77 | For our text editor, we will be mostly using 78 | [VT100](https://en.wikipedia.org/wiki/VT100) escape sequences, which 79 | are supported very widely by modern terminal emulators. See the 80 | [VT100 User Guide](http://vt100.net/docs/vt100-ug/chapter3.html) for complete 81 | documentation of each escape sequence. 82 | 83 | If we wanted to support the maximum number of terminals out there, we could use 84 | the [ncurses](https://en.wikipedia.org/wiki/Ncurses) library, which uses the 85 | [terminfo](https://en.wikipedia.org/wiki/Terminfo) database to figure out the 86 | capabilities of a terminal and what escape sequences to use for that particular 87 | terminal. 88 | 89 | ## Reposition the cursor 90 | 91 | You may notice that the `[2J` command left the cursor at the bottom of the 92 | screen. Let's reposition it at the top-left corner so that we're ready to draw 93 | the editor interface from top to bottom. 94 | 95 | {{cursor-home}} 96 | 97 | This escape sequence is only `3` bytes long, and uses the `H` command 98 | ([Cursor Position](http://vt100.net/docs/vt100-ug/chapter3.html#CUP)) to 99 | position the cursor. The `H` command actually takes two arguments: the row 100 | number and the column number at which to position the cursor. So if you have an 101 | 80×24 size terminal and you want the cursor in the center of the screen, you could 102 | use the command `[12;40H`. (Multiple arguments are separated by a `;` 103 | character.) The default arguments for `H` both happen to be `1`, so we can 104 | leave both arguments out and it will position the cursor at the first row and 105 | first column, as if we had sent the `[1;1H` command. (Rows and columns are 106 | numbered starting at `1`, not `0`.) 107 | 108 | ## Clear the screen on exit 109 | 110 | Let's clear the screen and reposition the cursor when our program exits. If an 111 | error occurs in the middle of rendering the screen, we don't want a bunch of 112 | garbage left over on the screen, and we don't want the error to be printed 113 | wherever the cursor happens to be at that point. 114 | 115 | {{clean-exit}} 116 | 117 | We have two exit points we want to clear the screen at: `die()`, and when the 118 | user presses Ctrl-Q to quit. 119 | 120 | We could use `atexit()` to clear the screen when our program exits, but then 121 | the error message printed by `die()` would get erased right after printing it. 122 | 123 | ## Tildes 124 | 125 | It's time to start drawing. Let's draw a column of tildes (`~`) on the left 126 | hand side of the screen, like [vim](http://www.vim.org/) does. In our text 127 | editor, we'll draw a tilde at the beginning of any lines that come after the 128 | end of the file being edited. 129 | 130 | {{tildes}} 131 | 132 | `editorDrawRows()` will handle drawing each row of the buffer of text being 133 | edited. For now it draws a tilde in each row, which means that row is not part 134 | of the file and can't contain any text. 135 | 136 | We don't know the size of the terminal yet, so we don't know how many rows to 137 | draw. For now we just draw `24` rows. 138 | 139 | After we're done drawing, we do another `[H` escape sequence to reposition 140 | the cursor back up at the top-left corner. 141 | 142 | ## Global state 143 | 144 | Our next goal is to get the size of the terminal, so we know how many rows to 145 | draw in `editorDrawRows()`. But first, let's set up a global struct that will 146 | contain our editor state, which we'll use to store the width and height of the 147 | terminal. For now, let's just put our `orig_termios` global into the struct. 148 | 149 | {{global-state}} 150 | 151 | Our global variable containing our editor state is named `E`. We must replace 152 | all occurrences of `orig_termios` with `E.orig_termios`. 153 | 154 | ## Window size, the easy way 155 | 156 | On most systems, you should be able to get the size of the terminal by simply 157 | calling `ioctl()` with the `TIOCGWINSZ` request. (As far as I can tell, it 158 | stands for **T**erminal **IOC**tl (which itself stands for **I**nput/**O**utput 159 | **C**on**t**ro**l**) **G**et **WIN**dow **S**i**Z**e.) 160 | 161 | {{ioctl}} 162 | 163 | `ioctl()`, `TIOCGWINSZ`, and `struct winsize` come from ``. 164 | 165 | On success, `ioctl()` will place the number of columns wide and the number of 166 | rows high the terminal is into the given `winsize` struct. On failure, 167 | `ioctl()` returns `-1`. We also check to make sure the values it gave back 168 | weren't `0`, because apparently that's a possible erroneous outcome. If 169 | `ioctl()` failed in either way, we have `getWindowSize()` report failure by 170 | returning `-1`. If it succeeded, we pass the values back by setting the `int` 171 | references that were passed to the function. (This is a common approach to 172 | having functions return multiple values in C. It also allows you to use the 173 | return value to indicate success or failure.) 174 | 175 | Now let's add `screenrows` and `screencols` to our global editor state, and 176 | call `getWindowSize()` to fill in those values. 177 | 178 | {{init-editor}} 179 | 180 | `initEditor()`'s job will be to initialize all the fields in the `E` struct. 181 | 182 | Now we're ready to display the proper number of tildes on the screen: 183 | 184 | {{screenrows}} 185 | 186 | ## Window size, the hard way 187 | 188 | `ioctl()` isn't guaranteed to be able to request the window size on all 189 | systems, so we are going to provide a fallback method of getting the window 190 | size. 191 | 192 | The strategy is to position the cursor at the bottom-right of the screen, then 193 | use escape sequences that let us query the position of the cursor. That tells 194 | us how many rows and columns there must be on the screen. 195 | 196 | Let's start by moving the cursor to the bottom-right. 197 | 198 | {{bottom-right}} 199 | 200 | As you might have gathered from the code, there is no simple "move the cursor 201 | to the bottom-right corner" command. 202 | 203 | We are sending two escape sequences one after the other. The `C` command 204 | ([Cursor Forward](http://vt100.net/docs/vt100-ug/chapter3.html#CUF)) moves the 205 | cursor to the right, and the `B` command 206 | ([Cursor Down](http://vt100.net/docs/vt100-ug/chapter3.html#CUD)) moves the 207 | cursor down. The argument says how much to move it right or down by. We use a 208 | very large value, `999`, which should ensure that the cursor reaches the right 209 | and bottom edges of the screen. 210 | 211 | The `C` and `B` commands are specifically 212 | [documented](http://vt100.net/docs/vt100-ug/chapter3.html#CUD) to stop the 213 | cursor from going past the edge of the screen. The reason we don't use the 214 | `[999;999H` command is that the 215 | [documentation](http://vt100.net/docs/vt100-ug/chapter3.html#CUP) doesn't 216 | specify what happens when you try to move the cursor off-screen. 217 | 218 | Note that we are sticking a `1 ||` at the front of our `if` condition 219 | temporarily, so that we can test this fallback branch we are developing. 220 | 221 | Because we're always returning `-1` (meaning an error occurred) from 222 | `getWindowSize()` at this point, we make a call to `editorReadKey()` so we can 223 | observe the results of our escape sequences before the program calls `die()` 224 | and clears the screen. When you run the program, you should see the cursor is 225 | positioned at the bottom-right corner of the screen, and then when you press a 226 | key you'll see the error message printed by `die()` after it clears the screen. 227 | 228 | Next we need to get the cursor position. The `n` command 229 | ([Device Status Report](http://vt100.net/docs/vt100-ug/chapter3.html#DSR)) can 230 | be used to query the terminal for status information. We want to give it an 231 | argument of `6` to ask for the cursor position. Then we can read the reply from 232 | the standard input. Let's print out each character from the standard input to 233 | see what the reply looks like. 234 | 235 | {{cursor-query}} 236 | 237 | The reply is an escape sequence! It's an escape character (`27`), followed by a 238 | `[` character, and then the actual response: `24;80R`, or similar. (This 239 | escape sequence is documented as 240 | [Cursor Position Report](http://vt100.net/docs/vt100-ug/chapter3.html#CPR).) 241 | 242 | As before, we've inserted a temporary call to `editorReadKey()` to let us 243 | observe our debug output before the screen gets cleared on exit. 244 | 245 | (Note: If you're using **Bash on Windows**, `read()` doesn't time out so you'll 246 | be stuck in an infinite loop. You'll have to kill the process externally, or 247 | exit and reopen the command prompt window.) 248 | 249 | We're going to have to parse this response. But first, let's read it into a 250 | buffer. We'll keep reading characters until we get to the `R` character. 251 | 252 | {{response-buffer}} 253 | 254 | When we print out the buffer, we don't want to print the `'\x1b'` character, 255 | because the terminal would interpret it as an escape sequence and wouldn't 256 | display it. So we skip the first character in `buf` by passing `&buf[1]` to 257 | `printf()`. `printf()` expects strings to end with a `0` byte, so we make sure 258 | to assign `'\0'` to the final byte of `buf`. 259 | 260 | If you run the program, you'll see we have the response in `buf` in the form of 261 | `[24;80`. Let's parse the two numbers out of there using `sscanf()`: 262 | 263 | {{parse-response}} 264 | 265 | `sscanf()` comes from ``. 266 | 267 | First we make sure it responded with an escape sequence. Then we pass a pointer 268 | to the third character of `buf` to `sscanf()`, skipping the `'\x1b'` and `'['` 269 | characters. So we are passing a string of the form `24;80` to `sscanf()`. We 270 | are also passing it the string `%d;%d` which tells it to parse two integers 271 | separated by a `;`, and put the values into the `rows` and `cols` variables. 272 | 273 | Our fallback method for getting the window size is now complete. You should see 274 | that `editorDrawRows()` prints the correct number of tildes for the height of 275 | your terminal. 276 | 277 | Now that we know that works, let's remove the `1 ||` we put in the `if` 278 | condition temporarily. 279 | 280 | {{back-to-ioctl}} 281 | 282 | ## The last line 283 | 284 | Maybe you noticed the last line of the screen doesn't seem to have a tilde. 285 | That's because of a small bug in our code. When we print the final tilde, we 286 | then print a `"\r\n"` like on any other line, but this causes the terminal to 287 | scroll in order to make room for a new, blank line. Let's make the last line an 288 | exception when we print our `"\r\n"`'s. 289 | 290 | {{last-line}} 291 | 292 | ## Append buffer 293 | 294 | It's not a good idea to make a whole bunch of small `write()`'s every time we 295 | refresh the screen. It would be better to do one big `write()`, to make sure 296 | the whole screen updates at once. Otherwise there could be small 297 | unpredictable pauses between `write()`'s, which would cause an annoying flicker 298 | effect. 299 | 300 | We want to replace all our `write()` calls with code that appends the string to 301 | a buffer, and then `write()` this buffer out at the end. Unfortunately, C 302 | doesn't have dynamic strings, so we'll create our own dynamic string type that 303 | supports one operation: appending. 304 | 305 | Let's start by making a new `/*** append buffer ***/` section, and defining the 306 | `abuf` struct under it. 307 | 308 | {{abuf-struct}} 309 | 310 | An append buffer consists of a pointer to our buffer in memory, and a length. 311 | We define an `ABUF_INIT` constant which represents an empty buffer. This acts 312 | as a constructor for our `abuf` type. 313 | 314 | Next, let's define the `abAppend()` operation, as well as the `abFree()` 315 | destructor. 316 | 317 | {{abuf-append}} 318 | 319 | `realloc()` and `free()` come from ``. `memcpy()` comes from 320 | ``. 321 | 322 | To append a string `s` to an `abuf`, the first thing we do is make sure we 323 | allocate enough memory to hold the new string. We ask `realloc()` to give us a 324 | block of memory that is the size of the current string plus the size of the 325 | string we are appending. `realloc()` will either extend the size of the block 326 | of memory we already have allocated, or it will take care of `free()`ing the 327 | current block of memory and allocating a new block of memory somewhere else 328 | that is big enough for our new string. 329 | 330 | Then we use `memcpy()` to copy the string `s` after the end of the current data 331 | in the buffer, and we update the pointer and length of the `abuf` to the new 332 | values. 333 | 334 | `abFree()` is a destructor that deallocates the dynamic memory used by an 335 | `abuf`. 336 | 337 | Okay, our `abuf` type is ready to be put to use. 338 | 339 | {{use-abuf}} 340 | 341 | In `editorRefreshScreen()`, we first initialize a new `abuf` called `ab`, by 342 | assigning `ABUF_INIT` to it. We then replace each occurrence of 343 | `write(STDOUT_FILENO, ...)` with `abAppend(&ab, ...)`. We also pass `ab` into 344 | `editorDrawRows()`, so it too can use `abAppend()`. Lastly, we `write()` the 345 | buffer's contents out to standard output, and free the memory used by the 346 | `abuf`. 347 | 348 | ## Hide the cursor when repainting 349 | 350 | There is another possible source of the annoying flicker effect we will take 351 | care of now. It's possible that the cursor might be displayed in the middle of 352 | the screen somewhere for a split second while the terminal is drawing to the 353 | screen. To make sure that doesn't happen, let's hide the cursor before 354 | refreshing the screen, and show it again immediately after the refresh 355 | finishes. 356 | 357 | {{hide-cursor}} 358 | 359 | We use escape sequences to tell the terminal to hide and show the cursor. The 360 | `h` and `l` commands 361 | ([Set Mode](http://vt100.net/docs/vt100-ug/chapter3.html#SM), 362 | [Reset Mode](http://vt100.net/docs/vt100-ug/chapter3.html#RM)) are used to turn 363 | on and turn off various terminal features or 364 | ["modes"](http://vt100.net/docs/vt100-ug/chapter3.html#S3.3.4). The VT100 User 365 | Guide just linked to doesn't document argument `?25` which we use above. It 366 | appears the cursor hiding/showing feature appeared in 367 | [later VT models](http://vt100.net/docs/vt510-rm/DECTCEM.html). So some 368 | terminals might not support hiding/showing the cursor, but if they don't, then 369 | they will just ignore those escape sequences, which isn't a big deal in this 370 | case. 371 | 372 | ## Clear lines one at a time 373 | 374 | Instead of clearing the entire screen before each refresh, it seems more 375 | optimal to clear each line as we redraw them. Let's remove the `[2J` 376 | (clear entire screen) escape sequence, and instead put a `[K` sequence at 377 | the end of each line we draw. 378 | 379 | {{clear-line}} 380 | 381 | The `K` command 382 | ([Erase In Line](http://vt100.net/docs/vt100-ug/chapter3.html#EL)) erases part 383 | of the current line. Its argument is analogous to the `J` command's argument: 384 | `2` erases the whole line, `1` erases the part of the line to the left of the 385 | cursor, and `0` erases the part of the line to the right of the cursor. `0` is 386 | the default argument, and that's what we want, so we leave out the argument and 387 | just use `[K`. 388 | 389 | ## Welcome message 390 | 391 | Perhaps it's time to display a welcome message. Let's display the name of our 392 | editor and a version number a third of the way down the screen. 393 | 394 | {{welcome}} 395 | 396 | `snprintf()` comes from ``. 397 | 398 | We use the `welcome` buffer and `snprintf()` to interpolate our `KILO_VERSION` 399 | string into the welcome message. We also truncate the length of the string in 400 | case the terminal is too tiny to fit our welcome message. 401 | 402 | Now let's center it. 403 | 404 | {{center}} 405 | 406 | To center a string, you divide the screen width by `2`, and then subtract half 407 | of the string's length from that. In other words: 408 | `E.screencols/2 - welcomelen/2`, which simplifies to 409 | `(E.screencols - welcomelen) / 2`. That tells you how far from the left edge of 410 | the screen you should start printing the string. So we fill that space with 411 | space characters, except for the first character, which should be a tilde. 412 | 413 | ## Move the cursor 414 | 415 | Let's focus on input now. We want the user to be able to move the cursor 416 | around. The first step is to keep track of the cursor's `x` and `y` position in 417 | the global editor state. 418 | 419 | {{cx-cy}} 420 | 421 | `E.cx` is the horizontal coordinate of the cursor (the column) and `E.cy` is 422 | the vertical coordinate (the row). We initialize both of them to `0`, as we 423 | want the cursor to start at the top-left of the screen. (Since the C language 424 | uses indexes that start from `0`, we will use 0-indexed values wherever 425 | possible.) 426 | 427 | Now let's add code to `editorRefreshScreen()` to move the cursor to the 428 | position stored in `E.cx` and `E.cy`. 429 | 430 | {{set-cursor-position}} 431 | 432 | `strlen()` comes from ``. 433 | 434 | We changed the old `H` command into an `H` command with arguments, specifying 435 | the exact position we want the cursor to move to. (Make sure you deleted the 436 | old `H` command, as the above diff makes that easy to miss.) 437 | 438 | We add `1` to `E.cy` and `E.cx` to convert from 0-indexed values to the 439 | 1-indexed values that the terminal uses. 440 | 441 | At this point, you could try initializing `E.cx` to `10` or something, or 442 | insert `E.cx++` into the main loop, to confirm that the code works as intended 443 | so far. 444 | 445 | Next, we'll allow the user to move the cursor using the 446 | wasd keys. (If you're unfamiliar 447 | with using these keys as arrow keys: w is your up arrow, 448 | s is your down arrow, a is left, d is right.) 449 | 450 | {{move-cursor}} 451 | 452 | Now you should be able to move the cursor around with those keys. 453 | 454 | ## Arrow keys 455 | 456 | Now that we have a way of mapping keypresses to move the cursor, let's replace 457 | the wasd keys with the arrow keys. 458 | Last chapter we [saw](02.enteringRawMode.html#display-keypresses) that pressing 459 | an arrow key sends multiple bytes as input to our program. These bytes are in 460 | the form of an escape sequence that starts with `'\x1b'`, `'['`, followed by an 461 | `'A'`, `'B'`, `'C'`, or `'D'` depending on which of the four arrow keys was 462 | pressed. Let's modify `editorReadKey()` to read escape sequences of this form 463 | as a single keypress. 464 | 465 | {{detect-arrow-keys}} 466 | 467 | If we read an escape character, we immediately read two more bytes into the 468 | `seq` buffer. If either of these reads time out (after 0.1 seconds), then we 469 | assume the user just pressed the Escape key and return that. 470 | Otherwise we look to see if the escape sequence is an arrow key escape 471 | sequence. If it is, we just return the corresponding 472 | wasd character, for now. If it's 473 | not an escape sequence we recognize, we just return the escape character. 474 | 475 | We make the `seq` buffer 3 bytes long because we will be handling longer escape 476 | sequences in the future. 477 | 478 | We have basically aliased the arrow keys to the 479 | wasd keys. This gets the arrow keys 480 | working immediately, but leaves the 481 | wasd keys still mapped to the 482 | `editorMoveCursor()` function. What we want is for `editorReadKey()` to return 483 | special values for each arrow key that let us identify that a particular arrow 484 | key was pressed. 485 | 486 | Let's start by replacing each instance of the 487 | wasd characters with the constants 488 | `ARROW_UP`, `ARROW_LEFT`, `ARROW_DOWN`, and `ARROW_RIGHT`. 489 | 490 | {{arrow-keys-enum}} 491 | 492 | Now we just have to choose a representation for arrow keys that doesn't 493 | conflict with wasd, in the 494 | `editorKey` enum. We will give them a large integer value that is out of the 495 | range of a `char`, so that they don't conflict with any ordinary keypresses. We 496 | will also have to change all variables that store keypresses to be of type 497 | `int` instead of `char`. 498 | 499 | {{arrow-keys-int}} 500 | 501 | By setting the first constant in the enum to `1000`, the rest of the constants 502 | get incrementing values of `1001`, `1002`, `1003`, and so on. 503 | 504 | That concludes our arrow key handling code. At this point, it can be fun to try 505 | entering an escape sequence manually while the program runs. Try pressing the 506 | Escape key, the [ key, and Shift+C 507 | in sequence really fast, and you may 508 | see your keypresses being interpreted as the right arrow key being pressed. You 509 | have to be pretty fast to do it, so you may want to adjust the `VTIME` value 510 | in `enableRawMode()` temporarily, to make it easier. (It also helps to know 511 | that pressing Ctrl-[ is the same as pressing the Escape 512 | key, for the same reason that Ctrl-M is the same as pressing 513 | Enter: Ctrl clears the 6th and 7th bits of the character 514 | you type in combination with it.) 515 | 516 | ## Prevent moving the cursor off screen 517 | 518 | Currently, you can cause the `E.cx` and `E.cy` values to go into the negatives, 519 | or go past the right and bottom edges of the screen. Let's prevent that by 520 | doing some bounds checking in `editorMoveCursor()`. 521 | 522 | {{off-screen}} 523 | 524 | ## The Page Up and Page Down keys 525 | 526 | To complete our low-level terminal code, we need to detect a few more special 527 | keypresses that use escape sequences, like the arrow keys did. We'll start with 528 | the Page Up and Page Down keys. Page Up is 529 | sent as `[5~` and Page Down is sent as `[6~`. 530 | 531 | {{detect-page-up-down}} 532 | 533 | Now you see why we declared `seq` to be able to store 3 bytes. If the byte 534 | after `[` is a digit, we read another byte expecting it to be a `~`. Then we 535 | test the digit byte to see if it's a `5` or a `6`. 536 | 537 | Let's make Page Up and Page Down do something. For now, 538 | we'll have them move the cursor to the top of the screen or the bottom of the 539 | screen. 540 | 541 | {{page-up-down-simple}} 542 | 543 | We create a code block with that pair of braces so that we're allowed to 544 | declare the `times` variable. (You can't declare variables directly inside a 545 | `switch` statement.) We simulate the user pressing the or 546 | keys enough times to move to the top or bottom of the screen. 547 | Implementing Page Up and Page Down in this way will make 548 | it a lot easier for us later, when we implement scrolling. 549 | 550 | If you're on a laptop with an Fn key, you may be able to press 551 | Fn+ and Fn+ to simulate 552 | pressing the Page Up and Page Down keys. 553 | 554 | ## The Home and End keys 555 | 556 | Now let's implement the Home and End keys. Like the 557 | previous keys, these keys also send escape sequences. Unlike the previous keys, 558 | there are many different escape sequences that could be sent by these keys, 559 | depending on your OS, or your terminal emulator. The Home key could 560 | be sent as `[1~`, `[7~`, `[H`, or `OH`. Similarly, the 561 | End key could be sent as `[4~`, `[8~`, `[F`, or 562 | `OF`. Let's handle all of these cases. 563 | 564 | {{detect-home-end}} 565 | 566 | Now let's make Home and End do something. For now, we'll 567 | have them move the cursor to the left or right edges of the screen. 568 | 569 | {{home-end-simple}} 570 | 571 | If you're on a laptop with an Fn key, you may be able to press 572 | Fn+ and Fn+ to simulate 573 | pressing the Home and End keys. 574 | 575 | ## The Delete key 576 | 577 | Lastly, let's detect when the Delete key is pressed. It simply sends 578 | the escape sequence `[3~`, so it's easy to add to our switch statement. We 579 | won't make this key do anything for now. 580 | 581 | {{detect-delete-key}} 582 | 583 | If you're on a laptop with an Fn key, you may be able to press 584 | Fn+Backspace to simulate pressing the Delete 585 | key. 586 | 587 | In the [next chapter](04.aTextViewer.html), we will get our program to display 588 | text files, complete with vertical and horizontal scrolling and a status bar. 589 | 590 | -------------------------------------------------------------------------------- /doc/04.aTextViewer.md: -------------------------------------------------------------------------------- 1 | # A text viewer 2 | 3 | ## A line viewer 4 | 5 | Let's create a data type for storing a row of text in our editor. 6 | 7 | {{erow}} 8 | 9 | `erow` stands for "editor row", and stores a line of text as a pointer to the 10 | dynamically-allocated character data and a length. The `typedef` lets us refer 11 | to the type as `erow` instead of `struct erow`. 12 | 13 | We add an `erow` value to the editor global state, as well as a `numrows` 14 | variable. For now, the editor will only display a single line of text, and so 15 | `numrows` can be either `0` or `1`. We initialize it to `0` in `initEditor()`. 16 | 17 | Let's fill that `erow` with some text now. We won't worry about reading from a 18 | file just yet. Instead, we'll hardcode a "Hello, world" string into it. 19 | 20 | {{hello-world}} 21 | 22 | `malloc()` comes from ``. `ssize_t` comes from ``. 23 | 24 | `editorOpen()` will eventually be for opening and reading a file from disk, so 25 | we put it in a new `/*** file i/o ***/` section. To load our "Hello, world" 26 | message into the editor's `erow` struct, we set the `size` field to the length 27 | of our message, `malloc()` the necessary memory, and `memcpy()` the message to 28 | the `chars` field which points to the memory we allocated. Finally, we set the 29 | `E.numrows` variable to `1`, to indicate that the `erow` now contains a line 30 | that should be displayed. 31 | 32 | Let's display it then. 33 | 34 | {{draw-erow}} 35 | 36 | We wrap our previous row-drawing code in an `if` statement that checks whether 37 | we are currently drawing a row that is part of the text buffer, or a row that 38 | comes after the end of the text buffer. 39 | 40 | To draw a row that's part of the text buffer, we simply write out the `chars` 41 | field of the `erow`. But first, we take care to truncate the rendered line if 42 | it would go past the end of the screen. 43 | 44 | Next, let's allow the user to open an actual file. We'll read and display the 45 | first line of the file. 46 | 47 | {{open-file}} 48 | 49 | `FILE`, `fopen()`, and `getline()` come from ``. 50 | 51 | The core of `editorOpen()` is the same, we just get the `line` and `linelen` 52 | values from `getline()` now, instead of hardcoded values. 53 | 54 | `editorOpen()` now takes a filename and opens the file for reading using 55 | `fopen()`. We allow the user to choose a file to open by checking if they 56 | passed a filename as a command line argument. If they did, we call 57 | `editorOpen()` and pass it the filename. If they ran `./kilo` with no 58 | arguments, `editorOpen()` will not be called and they'll start with a blank 59 | file. 60 | 61 | `getline()` is useful for reading lines from a file when we don't know how much 62 | memory to allocate for each line. It takes care of memory management for you. 63 | First, we pass it a null `line` pointer and a `linecap` (line capacity) of `0`. 64 | That makes it allocate new memory for the next line it reads, and set `line` to 65 | point to the memory, and set `linecap` to let you know how much memory it 66 | allocated. Its return value is the length of the line it read, or `-1` if it's 67 | at the end of the file and there are no more lines to read. Later, when we have 68 | `editorOpen()` read multiple lines of a file, we will be able to feed the new 69 | `line` and `linecap` values back into `getline()` over and over, and it will 70 | try and reuse the memory that `line` points to as long as the `linecap` is big 71 | enough to fit the next line it reads. For now, we just copy the one line it 72 | reads into `E.row.chars`, and then `free()` the `line` that `getline()` 73 | allocated. 74 | 75 | We also strip off the newline or carriage return at the end of the line before 76 | copying it into our `erow`. We know each `erow` represents one line of text, so 77 | there's no use storing a newline character at the end of each one. 78 | 79 | If your compiler complains about `getline()`, you may need to define a [feature 80 | test macro](https://www.gnu.org/software/libc/manual/html_node/Feature-Test-Macros.html). 81 | Even if it compiles fine on your machine without them, let's add them to make 82 | our code more portable. 83 | 84 | {{feature-test-macros}} 85 | 86 | We add them above our includes, because the header files we're including use 87 | the macros to decide what features to expose. 88 | 89 | Now let's fix a quick bug. We want the welcome message to only display when the 90 | user starts the program with no arguments, and not when they open a file, as 91 | the welcome message could get in the way of displaying the file. 92 | 93 | {{hide-welcome}} 94 | 95 | There, now the welcome message only displays if the text buffer is completely 96 | empty. 97 | 98 | ## Multiple lines 99 | 100 | To store multiple lines, let's make `E.row` an array of `erow` structs. It will 101 | be a dynamically-allocated array, so we'll make it a pointer to `erow`, and 102 | initialize the pointer to `NULL`. (This will break a bunch of our code that 103 | doesn't expect `E.row` to be a pointer, so the program will fail to compile for 104 | the next few steps.) 105 | 106 | {{erow-array}} 107 | 108 | Next, let's move the code in `editorOpen()` that initializes `E.row` to a new 109 | function called `editorAppendRow()`. We'll also put it under a new section, 110 | `/*** row operations ***/`. 111 | 112 | {{append-row}} 113 | 114 | Notice that we renamed the `line` and `linelen` variables to `s` and `len`, 115 | which are now arguments to `editorAppendRow()`. 116 | 117 | We want `editorAppendRow()` to allocate space for a new `erow`, and then copy 118 | the given string to a new `erow` at the end of the `E.row` array. Let's do that 119 | now. 120 | 121 | {{fix-append-row}} 122 | 123 | We have to tell `realloc()` how many bytes we want to allocate, so we multiply 124 | the number of bytes each `erow` takes (`sizeof(erow)`) and multiply that by the 125 | number of rows we want. Then we set `at` to the index of the new row we want to 126 | initialize, and replace each occurrence of `E.row` with `E.row[at]`. Lastly, we 127 | change `E.numrows = 1` to `E.numrows++`. 128 | 129 | Next, let's update `editorDrawRows()` to use `E.row[y]` instead of `E.row`, 130 | when printing out the current line. 131 | 132 | {{draw-multiple-erows}} 133 | 134 | At this point the code should compile, but it still only reads a single line 135 | from the file. Let's add a `while` loop to `editorOpen()` to read an entire 136 | file into `E.row`. 137 | 138 | {{read-multiple-lines}} 139 | 140 | The `while` loop works because `getline()` returns `-1` when it gets to the end 141 | of the file and there are no more lines to read. 142 | 143 | Now you should see your screen fill up with lines of text when you run 144 | `./kilo kilo.c`, for example. 145 | 146 | ## Vertical scrolling 147 | 148 | Next we want to enable the user to scroll through the whole file, instead of 149 | just being able to see the top few lines of the file. Let's add a `rowoff` (row 150 | offset) variable to the global editor state, which will keep track of what row 151 | of the file the user is currently scrolled to. 152 | 153 | {{rowoff}} 154 | 155 | We initialize it to `0`, which means we'll be scrolled to the top of the file 156 | by default. 157 | 158 | Now let's have `editorDrawRows()` display the correct range of lines of the 159 | file according to the value of `rowoff`. 160 | 161 | {{filerow}} 162 | 163 | To get the row of the file that we want to display at each `y` position, we add 164 | `E.rowoff` to the `y` position. So we define a new variable `filerow` that 165 | contains that value, and use that as the index into `E.row`. 166 | 167 | Now where do we set the value of `E.rowoff`? Our strategy will be to check if 168 | the cursor has moved outside of the visible window, and if so, adjust 169 | `E.rowoff` so that the cursor is just inside the visible window. We'll put this 170 | logic in a function called `editorScroll()`, and call it right before we 171 | refresh the screen. 172 | 173 | {{editor-scroll}} 174 | 175 | The first `if` statement checks if the cursor is above the visible window, and 176 | if so, scrolls up to where the cursor is. The second `if` 177 | statement checks if the cursor is past the bottom of the visible window, and 178 | contains slightly more complicated arithmetic because `E.rowoff` refers to 179 | what's at the *top* of the screen, and we have to get `E.screenrows` involved 180 | to talk about what's at the *bottom* of the screen. 181 | 182 | Now let's allow the cursor to advance past the bottom of the screen (but not 183 | past the bottom of the file). 184 | 185 | {{enable-vertical-scroll}} 186 | 187 | You should be able to scroll through the entire file now, when you run `./kilo 188 | kilo.c`. (If the file contains tab characters, you'll see that the characters 189 | that the tabs take up aren't being erased properly when drawing to the screen. 190 | We'll fix this issue soon. In the meantime, you may want to test with a file 191 | that doesn't contain a lot of tabs.) 192 | 193 | If you try to scroll back up, you may notice the cursor isn't being 194 | positioned properly. That is because `E.cy` no longer refers to the position of 195 | the cursor on the screen. It refers to the position of the cursor within the 196 | text file. To position the cursor on the screen, we now have to subtract 197 | `E.rowoff` from the value of `E.cy`. 198 | 199 | {{fix-cursor-scrolling}} 200 | 201 | ## Horizontal scrolling 202 | 203 | Now let's work on horizontal scrolling. We'll implement it in just about the 204 | same way we implemented vertical scrolling. Start by adding a `coloff` (column 205 | offset) variable to the global editor state. 206 | 207 | {{coloff}} 208 | 209 | To display each row at the column offset, we'll use `E.coloff` as an index into 210 | the `chars` of each `erow` we display, and subtract the number of characters 211 | that are to the left of the offset from the length of the row. 212 | 213 | {{use-coloff}} 214 | 215 | Note that when subtracting `E.coloff` from the length, `len` can now be a 216 | negative number, meaning the user scrolled horizontally past the end of the 217 | line. In that case, we set `len` to `0` so that nothing is displayed on that 218 | line. 219 | 220 | Now let's update `editorScroll()` to handle horizontal scrolling. 221 | 222 | {{editor-scroll-horizontal}} 223 | 224 | As you can see, it is exactly parallel to the vertical scrolling code. We just 225 | replace `E.cy` with `E.cx`, `E.rowoff` with `E.coloff`, and `E.screenrows` with 226 | `E.screencols`. 227 | 228 | Now let's allow the user to scroll past the right edge of the screen. 229 | 230 | {{enable-horizontal-scroll}} 231 | 232 | You should be able to confirm that horizontal scrolling now works. 233 | 234 | Next, let's fix the cursor positioning, just like we did with vertical 235 | scrolling. 236 | 237 | {{fix-cursor-scrolling-horizontal}} 238 | 239 | ## Limit scrolling to the right 240 | 241 | Now both `E.cx` and `E.cy` refer to the cursor's position within the file, not 242 | its position on the screen. So our goal with the next few steps is to limit the 243 | values of `E.cx` and `E.cy` to only ever point to valid positions in the file. 244 | Otherwise, the user could move the cursor way off to the right of a line and 245 | start inserting text there, which wouldn't make much sense. (The only 246 | exceptions to this rule are that `E.cx` can point one character past the end of 247 | a line so that characters can be inserted at the end of the line, and `E.cy` 248 | can point one line past the end of the file so that new lines at the end of the 249 | file can be added easily.) 250 | 251 | Let's start by not allowing the user to scroll past the end of the current 252 | line. 253 | 254 | {{scroll-limits}} 255 | 256 | Since `E.cy` is allowed to be one past the last line of the file, we use the 257 | ternary operator to check if the cursor is on an actual line. If it is, then 258 | the `row` variable will point to the `erow` that the cursor is on, and we'll 259 | check whether `E.cx` is to the left of the end of that line before we allow the 260 | cursor to move to the right. 261 | 262 | ## Snap cursor to end of line 263 | 264 | The user is still able to move the cursor past the end of a line, however. They 265 | can do it by moving the cursor to the end of a long line, then moving it down 266 | to the next line, which is shorter. The `E.cx` value won't change, and the 267 | cursor will be off to the right of the end of the line it's now on. 268 | 269 | Let's add some code to `editorMoveCursor()` that corrects `E.cx` if it ends up 270 | past the end of the line it's on. 271 | 272 | {{snap-cursor}} 273 | 274 | We have to set `row` again, since `E.cy` could point to a different line than 275 | it did before. We then set `E.cx` to the end of that line if `E.cx` is to the 276 | right of the end of that line. Also note that we consider a `NULL` line to be 277 | of length `0`, which works for our purposes here. 278 | 279 | ## Moving left at the start of a line 280 | 281 | Let's allow the user to press at the beginning of the line to 282 | move to the end of the previous line. 283 | 284 | {{moving-left}} 285 | 286 | We make sure they aren't on the very first line before we move them up a line. 287 | 288 | ## Moving right at the end of a line 289 | 290 | Similarly, let's allow the user to press at the end of a line 291 | to go to the beginning of the next line. 292 | 293 | {{moving-right}} 294 | 295 | Here we have to make sure they're not at the end of the file before moving down 296 | a line. 297 | 298 | ## Rendering tabs 299 | 300 | If you try opening the `Makefile` using `./kilo Makefile`, you'll notice that 301 | the tab character on the second line of the Makefile takes up a width of 8 302 | columns or so. The length of a tab is up to the terminal being used and its 303 | settings. We want to *know* the length of each tab, and we also want control 304 | over how to render tabs, so we're going to add a second string to the `erow` 305 | struct called `render`, which will contain the actual characters to draw on the 306 | screen for that row of text. We'll only use `render` for tabs for now, but in 307 | the future it could be used to render nonprintable control characters as a `^` 308 | character followed by another character, such as `^A` for the Ctrl-A 309 | character (this is a common way to display control characters in the terminal). 310 | 311 | You may also notice that when the tab character in the `Makefile` is displayed 312 | by the terminal, it doesn't erase any characters on the screen within that tab. 313 | All a tab does is move the cursor forward to the next tab stop, similar to a 314 | carriage return or newline. This is another reason why we want to render tabs 315 | as multiple spaces, since spaces erase whatever character was there before. 316 | 317 | So, let's start by adding `render` and `rsize` (which contains the size of the 318 | contents of `render`) to the `erow` struct, and initializing them in 319 | `editorAppendRow()`, which is where new `erow`s get constructed and 320 | initialized. 321 | 322 | {{render}} 323 | 324 | Next, let's make an `editorUpdateRow()` function that uses the `chars` string 325 | of an `erow` to fill in the contents of the `render` string. We'll copy each 326 | character from `chars` to `render`. We won't worry about how to render tabs 327 | just yet. 328 | 329 | {{editor-update-row}} 330 | 331 | After the `for` loop, `idx` contains the number of characters we copied into 332 | `row->render`, so we assign it to `row->rsize`. 333 | 334 | Now let's replace `chars` and `size` with `render` and `rsize` in 335 | `editorDrawRows()`, when we display each `erow`. 336 | 337 | {{use-render}} 338 | 339 | Now the text viewer is displaying the characters in `render`. Let's add code to 340 | `editorUpdateRow()` that renders tabs as multiple space characters. 341 | 342 | {{tabs}} 343 | 344 | First, we have to loop through the `chars` of the row and count the tabs in 345 | order to know how much memory to allocate for `render`. The maximum number of 346 | characters needed for each tab is 8. `row->size` already counts 1 for each tab, 347 | so we multiply the number of tabs by 7 and add that to `row->size` to get the 348 | maximum amount of memory we'll need for the rendered row. 349 | 350 | After allocating the memory, we modify the `for` loop to check whether the 351 | current character is a tab. If it is, we append one space (because each tab 352 | must advance the cursor forward at least one column), and then append spaces 353 | until we get to a tab stop, which is a column that is divisible by 8. 354 | 355 | At this point, we should probably make the length of a tab stop a constant. 356 | 357 | {{tab-stop}} 358 | 359 | This makes the code clearer, and also makes the tab stop length configurable. 360 | 361 | ## Tabs and the cursor 362 | 363 | The cursor doesn't currently interact with tabs very well. When we position the 364 | cursor on the screen, we're still assuming each character takes up only one 365 | column on the screen. To fix this, let's introduce a new horizontal coordinate 366 | variable, `E.rx`. While `E.cx` is an index into the `chars` field of an `erow`, 367 | the `E.rx` variable will be an index into the `render` field. If there are no 368 | tabs on the current line, then `E.rx` will be the same as `E.cx`. If there are 369 | tabs, then `E.rx` will be greater than `E.cx` by however many extra spaces 370 | those tabs take up when rendered. 371 | 372 | Start by adding `rx` to the global state struct, and initializing it to `0`. 373 | 374 | {{rx}} 375 | 376 | We'll set the value of `E.rx` at the top of `editorScroll()`. For now we'll 377 | just set it to be the same as `E.cx`. Then we'll replace all instances of 378 | `E.cx` with `E.rx` in `editorScroll()`, because scrolling should take into 379 | account the characters that are actually rendered to the screen, and the 380 | rendered position of the cursor. 381 | 382 | {{rx-scroll}} 383 | 384 | Now change `E.cx` to `E.rx` in `editorRefreshScreen()` where we set the cursor 385 | position. 386 | 387 | {{use-rx}} 388 | 389 | All that's left to do is calculate the value of `E.rx` properly in 390 | `editorScroll()`. Let's create an `editorRowCxToRx()` function that converts a 391 | `chars` index into a `render` index. We'll need to loop through all the 392 | characters to the left of `cx`, and figure out how many spaces each tab takes 393 | up. 394 | 395 | {{cx-to-rx}} 396 | 397 | For each character, if it's a tab we use `rx % KILO_TAB_STOP` to find out how 398 | many columns we are to the right of the last tab stop, and then subtract that 399 | from `KILO_TAB_STOP - 1` to find out how many columns we are to the left of the 400 | next tab stop. We add that amount to `rx` to get just to the left of the next 401 | tab stop, and then the unconditional `rx++` statement gets us right on the next 402 | tab stop. Notice how this works even if we are currently on a tab stop. 403 | 404 | Let's call `editorRowCxToRx()` at the top of `editorScroll()` to finally set 405 | `E.rx` to its proper value. 406 | 407 | {{set-rx}} 408 | 409 | You should now be able to confirm that the cursor moves properly within lines 410 | that contain tabs. 411 | 412 | ## Scrolling with Page Up and Page Down 413 | 414 | Now that we have scrolling, let's make the Page Up and 415 | Page Down keys scroll up or down an entire page. 416 | 417 | {{page-up-down}} 418 | 419 | To scroll up or down a page, we position the cursor either at the top or bottom 420 | of the screen, and then simulate an entire screen's worth of 421 | or keypresses. Delegating to `editorMoveCursor()` takes care 422 | of all the bounds-checking and cursor-fixing that needs to be done when moving 423 | the cursor. 424 | 425 | ## Move to the end of the line with End 426 | 427 | Now let's have the End key move the cursor to the end of the current 428 | line. (The Home key already moves the cursor to the beginning of the 429 | line, since we made `E.cx` relative to the file instead of relative to the 430 | screen.) 431 | 432 | {{end-key}} 433 | 434 | The End key brings the cursor to the end of the current line. If 435 | there is no current line, then `E.cx` must be `0` and it should stay at `0`, so 436 | there's nothing to do. 437 | 438 | ## Status bar 439 | 440 | The last thing we'll add before finally getting to text editing is a status 441 | bar. This will show useful information such as the filename, how many lines are 442 | in the file, and what line you're currently on. Later we'll add a marker that 443 | tells you whether the file has been modified since it was last saved, and we'll 444 | also display the filetype when we implement syntax highlighting. 445 | 446 | First we'll simply make room for a one-line status bar at the bottom of the 447 | screen. 448 | 449 | {{status-bar-make-room}} 450 | 451 | We decrement `E.screenrows` so that `editorDrawRows()` doesn't try to draw a 452 | line of text at the bottom of the screen. We also have `editorDrawRows()` print 453 | a newline after the last row it draws, since the status bar is now the final 454 | line being drawn on the screen. 455 | 456 | Notice how with those two changes, our text viewer works just fine, including 457 | scrolling and cursor movement, and the last line where our status bar will be 458 | is left alone by the rest of the display code. 459 | 460 | To make the status bar stand out, we're going to display it with inverted 461 | colors: black text on a white background. The escape sequence `[7m` 462 | switches to inverted colors, and `[m` switches back to normal formatting. 463 | Let's draw a blank white status bar of inverted space characters. 464 | 465 | {{blank-status-bar}} 466 | 467 | The `m` command 468 | ([Select Graphic Rendition](http://vt100.net/docs/vt100-ug/chapter3.html#SGR)) 469 | causes the text printed after it to be printed with various possible attributes 470 | including bold (`1`), underscore (`4`), blink (`5`), and inverted colors (`7`). 471 | For example, you could specify all of these attributes using the command 472 | `[1;4;5;7m`. An argument of `0` clears all attributes, and is the default 473 | argument, so we use `[m` to go back to normal text formatting. 474 | 475 | Since we want to display the filename in the status bar, let's add a `filename` 476 | string to the global editor state, and save a copy of the filename there when a 477 | file is opened. 478 | 479 | {{filename}} 480 | 481 | `strdup()` comes from ``. It makes a copy of the given string, 482 | allocating the required memory and assuming you will `free()` that memory. 483 | 484 | We initialize `E.filename` to the `NULL` pointer, and it will stay `NULL` if a 485 | file isn't opened (which is what happens when the program is run without 486 | arguments). 487 | 488 | Now we're ready to display some information in the status bar. We'll display up 489 | to 20 characters of the filename, followed by the number of lines in the file. 490 | If there is no filename, we'll display `[No Name]` instead. 491 | 492 | {{status-bar-left}} 493 | 494 | We make sure to cut the status string short in case it doesn't fit inside the 495 | width of the window. Notice how we still use the code that draws spaces up to 496 | the end of the screen, so that the entire status bar has a white background. 497 | 498 | Now let's show the current line number, and align it to the right edge of the 499 | screen. 500 | 501 | {{status-bar-right}} 502 | 503 | The current line is stored in `E.cy`, which we add `1` to since `E.cy` is 504 | 0-indexed. After printing the first status string, we want to keep printing 505 | spaces until we get to the point where if we printed the second status string, 506 | it would end up against the right edge of the screen. That happens when 507 | `E.screencols - len` is equal to the length of the second status string. At 508 | that point we print the status string and break out of the loop, as the entire 509 | status bar has now been printed. 510 | 511 | ## Status message 512 | 513 | We're going to add one more line below our status bar. This will be for 514 | displaying messages to the user, and prompting the user for input when doing a 515 | search, for example. We'll store the current message in a string called 516 | `statusmsg`, which we'll put in the global editor state. We'll also store a 517 | timestamp for the message, so that we can erase it a few seconds after it's 518 | been displayed. 519 | 520 | {{status-message}} 521 | 522 | `time_t` comes from ``. 523 | 524 | We initialize `E.statusmsg` to an empty string, so no message will be displayed 525 | by default. `E.statusmsg_time` will contain the timestamp when we set a status 526 | message. 527 | 528 | Let's define an `editorSetStatusMessage()` function. This function will take a 529 | format string and a variable number of arguments, like the `printf()` family of 530 | functions. 531 | 532 | {{set-status-message}} 533 | 534 | `va_list`, `va_start()`, and `va_end()` come from ``. `vsnprintf()` 535 | comes from ``. `time()` comes from ``. 536 | 537 | In `main()`, we set the initial status message to a help message with the key 538 | bindings that our text editor uses (currently, just Ctrl-Q to quit). 539 | 540 | `vsnprintf()` helps us make our own `printf()`-style function. We store the 541 | resulting string in `E.statusmsg`, and set `E.statusmsg_time` to the current 542 | time, which can be gotten by passing `NULL` to `time()`. (It returns the number 543 | of seconds that have passed since 544 | [midnight, January 1, 1970](https://en.wikipedia.org/wiki/Unix_time) as an 545 | integer.) 546 | 547 | The `...` argument makes `editorSetStatusMessage()` a 548 | [variadic function](https://en.wikipedia.org/wiki/Variadic_function), meaning 549 | it can take any number of arguments. C's way of dealing with these arguments is 550 | by having you call `va_start()` and `va_end()` on a value of type `va_list`. 551 | The last argument before the `...` (in this case, `fmt`) must be passed to 552 | `va_start()`, so that the address of the next arguments is known. Then, between 553 | the `va_start()` and `va_end()` calls, you would call `va_arg()` and pass it 554 | the type of the next argument (which you usually get from the given format 555 | string) and it would return the value of that argument. In this case, we pass 556 | `fmt` and `ap` to `vsnprintf()` and it takes care of reading the format string 557 | and calling `va_arg()` to get each argument. 558 | 559 | Now that we have a status message to display, let's make room for a second line 560 | beneath our status bar where we'll display the message. 561 | 562 | {{message-bar-make-room}} 563 | 564 | We decrement `E.screenrows` again, and print a newline after the first status 565 | bar. We now have a blank final line once again. 566 | 567 | Let's draw the message bar in a new `editorDrawMessageBar()` function. 568 | 569 | {{draw-message-bar}} 570 | 571 | First we clear the message bar with the `[K` escape sequence. Then we make 572 | sure the message will fit the width of the screen, and then display the 573 | message, but only if the message is less than 5 seconds old. 574 | 575 | When you start up the program now, you should see the help message at the 576 | bottom. It will disappear *when you press a key* after 5 seconds. Remember, we 577 | only refresh the screen after each keypress. 578 | 579 | In the [next chapter](05.aTextEditor.html), we will turn our text viewer into a 580 | text editor, allowing the user to insert and delete characters and save their 581 | changes to disk. 582 | 583 | -------------------------------------------------------------------------------- /doc/05.aTextEditor.md: -------------------------------------------------------------------------------- 1 | # A text editor 2 | 3 | ## Insert ordinary characters 4 | 5 | Let's begin by writing a function that inserts a single character into an 6 | `erow`, at a given position. 7 | 8 | {{row-insert-char}} 9 | 10 | `memmove()` comes from ``. It is like `memcpy()`, but is safe to use 11 | when the source and destination arrays overlap. 12 | 13 | First we validate `at`, which is the index we want to insert the character 14 | into. Notice that `at` is allowed to go one character past the end of the 15 | string, in which case the character should be inserted at the end of the 16 | string. 17 | 18 | Then we allocate one more byte for the `chars` of the `erow` (we add `2` 19 | because we also have to make room for the null byte), and use `memmove()` to 20 | make room for the new character. We increment the `size` of the `chars` array, 21 | and then actually assign the character to its position in the array. Finally, 22 | we call `editorUpdateRow()` so that the `render` and `rsize` fields get updated 23 | with the new row content. 24 | 25 | Now we'll create a new section called `/*** editor operations ***/`. This 26 | section will contain functions that we'll call from `editorProcessKeypress()` 27 | when we're mapping keypresses to various text editing operations. We'll add a 28 | function to this section called `editorInsertChar()` which will take a 29 | character and use `editorRowInsertChar()` to insert that character into the 30 | position that the cursor is at. 31 | 32 | {{editor-insert-char}} 33 | 34 | If `E.cy == E.numrows`, then the cursor is on the tilde line after the end of 35 | the file, so we need to append a new row to the file before inserting a 36 | character there. After inserting a character, we move the cursor forward so 37 | that the next character the user inserts will go after the character just 38 | inserted. 39 | 40 | Notice that `editorInsertChar()` doesn't have to worry about the details of 41 | modifying an `erow`, and `editorRowInsertChar()` doesn't have to worry about 42 | where the cursor is. That is the difference between functions in the 43 | `/*** editor operations ***/` section and functions in the 44 | `/*** row operations ***/` section. 45 | 46 | Let's call `editorInsertChar()` in the `default:` case of the `switch` 47 | statement in `editorProcessKeypress()`. This will allow any keypress that isn't 48 | mapped to another editor function to be inserted directly into the text being 49 | edited. 50 | 51 | {{key-insert-char}} 52 | 53 | We've now officially upgraded our text viewer to a text editor. 54 | 55 | ## Prevent inserting special characters 56 | 57 | Currently, if you press keys like Backspace or Enter, 58 | those characters will be inserted directly into the text, which we certainly 59 | don't want. Let's handle a bunch of these special keys in 60 | `editorProcessKeypress()`, so that they don't fall through to the `default` 61 | case of calling `editorInsertChar()`. 62 | 63 | {{block-special-chars}} 64 | 65 | Backspace doesn't have a human-readable backslash-escape 66 | representation in C (like `\n`, `\r`, and so on), so we make it part of the 67 | `editorKey` enum and assign it its ASCII value of `127`. 68 | 69 | In `editorProcessKeypress()`, the first new key we add to the `switch` 70 | statement is `'\r'`, which is the Enter key. For now we want to 71 | ignore it, but obviously we'll be making it do something later, so we mark it 72 | with a `TODO` comment. 73 | 74 | We handle Backspace and Delete in a similar way, marking 75 | them with a `TODO`. We also handle the Ctrl-H key combination, which 76 | sends the control code `8`, which is originally what the Backspace 77 | character would send back in the day. If you look at the 78 | [ASCII table](http://www.asciitable.com/), you'll see that ASCII code `8` is 79 | named `BS` for "backspace", and ASCII code `127` is named `DEL` for "delete". 80 | But for whatever reason, in modern computers the Backspace key is 81 | mapped to `127` and the Delete key is mapped to the escape sequence 82 | `[3~`, as we saw at the end of 83 | [chapter 3](03.rawInputAndOutput.html#the-delete-key). 84 | 85 | Lastly, we handle Ctrl-L and Escape by not doing anything 86 | when those keys are pressed. Ctrl-L is traditionally used to refresh 87 | the screen in terminal programs. In our text editor, the screen refreshes after 88 | *any* keypress, so we don't have to do anything else to implement that feature. 89 | We ignore the Escape key because there are many key escape sequences 90 | that we aren't handling (such as the F1F12 keys), 91 | and the way we wrote `editorReadKey()`, pressing those keys will be equivalent 92 | to pressing the Escape key. We don't want the user to unwittingly 93 | insert the escape character `27` into their text, so we ignore those 94 | keypresses. 95 | 96 | ## Save to disk 97 | 98 | Now that we've finally made text editable, let's implement saving to disk. 99 | First we'll write a function that converts our array of `erow` structs into a 100 | single string that is ready to be written out to a file. 101 | 102 | {{rows-to-string}} 103 | 104 | First we add up the lengths of each row of text, adding `1` to each one for the 105 | newline character we'll add to the end of each line. We save the total length 106 | into `buflen`, to tell the caller how long the string is. 107 | 108 | Then, after allocating the required memory, we loop through the rows, and 109 | `memcpy()` the contents of each row to the end of the buffer, appending a 110 | newline character after each row. 111 | 112 | We return `buf`, expecting the caller to `free()` the memory. 113 | 114 | Now we'll implement the `editorSave()` function, which will actually write the 115 | string returned by `editorRowsToString()` to disk. 116 | 117 | {{save}} 118 | 119 | `open()`, `O_RDWR`, and `O_CREAT` come from ``. `ftruncate()` and 120 | `close()` come from ``. 121 | 122 | If it's a new file, then `E.filename` will be `NULL`, and we won't know where 123 | to save the file, so we just `return` without doing anything for now. Later, 124 | we'll figure out how to prompt the user for a filename. 125 | 126 | Otherwise, we call `editorRowsToString()`, and `write()` the string to the path 127 | in `E.filename`. We tell `open()` we want to create a new file if it 128 | doesn't already exist (`O_CREAT`), and we want to open it for reading and 129 | writing (`O_RDWR`). Because we used the `O_CREAT` flag, we have to pass an 130 | extra argument containing the mode (the permissions) the new file should have. 131 | `0644` is the standard permissions you usually want for text files. It gives 132 | the owner of the file permission to read and write the file, and everyone else 133 | only gets permission to read the file. 134 | 135 | `ftruncate()` sets the file's size to the specified length. If the file is 136 | larger than that, it will cut off any data at the end of the file to make it 137 | that length. If the file is shorter, it will add `0` bytes at the end to make 138 | it that length. 139 | 140 | The normal way to overwrite a file is to pass the `O_TRUNC` flag to `open()`, 141 | which truncates the file completely, making it an empty file, before writing 142 | the new data into it. By truncating the file ourselves to the same length as 143 | the data we are planning to write into it, we are making the whole overwriting 144 | operation a little bit safer in case the `ftruncate()` call succeeds but the 145 | `write()` call fails. In that case, the file would still contain most of the 146 | data it had before. But if the file was truncated completely by the `open()` 147 | call and then the `write()` failed, you'd end up with all of your data lost. 148 | 149 | More advanced editors will write to a new, temporary file, and then rename that 150 | file to the actual file the user wants to overwrite, and they'll carefully 151 | check for errors through the whole process. 152 | 153 | Anyways, all we have to do now is map a key to `editorSave()`, so let's do it! 154 | We'll use Ctrl-S. 155 | 156 | {{ctrl-s}} 157 | 158 | You should be able to open a file in the editor, insert some characters, press 159 | Ctrl-S, and reopen the file to confirm that the changes you made 160 | were saved. 161 | 162 | Let's add error handling to `editorSave()`. 163 | 164 | {{save-errors}} 165 | 166 | `open()` and `ftruncate()` both return `-1` on error. We expect `write()` to 167 | return the number of bytes we told it to write. Whether or not an error 168 | occurred, we ensure that the file is closed and the memory that `buf` points to 169 | is freed. 170 | 171 | Let's use `editorSetStatusMessage()` to notify the user whether the save 172 | succeeded or not. While we're at it, we'll add the Ctrl-S key 173 | binding to the help message that's set in `main()`. 174 | 175 | {{save-status-message}} 176 | 177 | `strerror()` comes from ``. 178 | 179 | `strerror()` is like `perror()` (which we use in `die()`), but it takes the 180 | `errno` value as an argument and returns the human-readable string for that 181 | error code, so that we can make the error a part of the status message we 182 | display to the user. 183 | 184 | The above code doesn't actually compile, because we are trying to call 185 | `editorSetStatusMessage()` before it is defined in the file. You can't do that 186 | in C, because C was made to be a language that can be compiled in a [single 187 | pass](https://en.wikipedia.org/wiki/One-pass_compiler), meaning it should be 188 | possible to compile each part of a program without knowing what comes later in 189 | the program. 190 | 191 | When we call a function in C, the compiler needs to know the arguments and 192 | return value of that function. We can tell the compiler this information about 193 | `editorSetStatusMessage()` by declaring a function prototype for it near the 194 | top of the file. This allows us to call the function before it is defined. 195 | We'll add a new `/*** prototypes ***/` section and put the declaration under 196 | it. 197 | 198 | {{prototypes}} 199 | 200 | ## Dirty flag 201 | 202 | We'd like to keep track of whether the text loaded in our editor differs from 203 | what's in the file. Then we can warn the user that they might lose unsaved 204 | changes when they try to quit. 205 | 206 | We call a text buffer "dirty" if it has been modified since opening or saving 207 | the file. Let's add a `dirty` variable to the global editor state, and 208 | initialize it to `0`. 209 | 210 | {{dirty}} 211 | 212 | Let's show the state of `E.dirty` in the status bar, by displaying `(modified)` 213 | after the filename if the file has been modified. 214 | 215 | {{show-dirty}} 216 | 217 | Now let's increment `E.dirty` in each row operation that makes a change to the 218 | text. 219 | 220 | {{increment-dirty}} 221 | 222 | We could have used `E.dirty = 1` instead of `E.dirty++`, but by incrementing it 223 | we can have a sense of "how dirty" the file is, which could be useful. (We'll 224 | just be treating `E.dirty` as a boolean value in this tutorial, so it doesn't 225 | really matter.) 226 | 227 | If you open a file at this point, you'll see that `(modified)` appears right 228 | away, before you make any changes. That's because `editorOpen()` calls 229 | `editorAppendRow()`, which increments `E.dirty`. To fix that, let's reset 230 | `E.dirty` to `0` at the end of `editorOpen()`, and also in `editorSave()`. 231 | 232 | {{reset-dirty}} 233 | 234 | Now you should see `(modified)` appear in the status bar when you first insert 235 | a character, and you should see it disappear when you save the file to disk. 236 | 237 | ## Quit confirmation 238 | 239 | Now we're ready to warn the user about unsaved changes when they try to quit. 240 | If `E.dirty` is set, we will display a warning in the status bar, and require 241 | the user to press Ctrl-Q three more times in order to quit without 242 | saving. 243 | 244 | {{quit-confirmation}} 245 | 246 | We use a static variable in `editorProcessKeypress()` to keep track of how many 247 | more times the user must press Ctrl-Q to quit. Each time they press 248 | Ctrl-Q with unsaved changes, we set the status message and decrement 249 | `quit_times`. When `quit_times` gets to `0`, we finally allow the program to 250 | exit. When they press any key other than Ctrl-Q, then `quit_times` 251 | gets reset back to `3` at the end of the `editorProcessKeypress()` function. 252 | 253 | ## Simple backspacing 254 | 255 | Let's implement backspacing next. First we'll create an `editorRowDelChar()` 256 | function, which deletes a character in an `erow`. 257 | 258 | {{row-del-char}} 259 | 260 | As you can see, it's very similar to `editorRowInsertChar()`, except we don't 261 | have any memory management to do. We just use `memmove()` to overwrite the 262 | deleted character with the characters that come after it (notice that the null 263 | byte at the end gets included in the move). Then we decrement the row's `size`, 264 | call `editorUpdateRow()`, and increment `E.dirty`. 265 | 266 | Now let's implement `editorDelChar()`, which uses `editorRowDelChar()` to 267 | delete the character that is to the left of the cursor. 268 | 269 | {{editor-del-char}} 270 | 271 | If the cursor's past the end of the file, then there is nothing to delete, and 272 | we `return` immediately. Otherwise, we get the `erow` the cursor is on, and if 273 | there is a character to the left of the cursor, we delete it and move the 274 | cursor one to the left. 275 | 276 | Let's map the Backspace, Ctrl-H, and Delete 277 | keys to `editorDelChar()`. 278 | 279 | {{key-del-char}} 280 | 281 | It so happens that in our editor, pressing the key and then 282 | Backspace is equivalent to what you would expect from pressing the 283 | Delete key in a text editor: it deletes the character to the right 284 | of the cursor. So that is how we implement the Delete key above. 285 | 286 | ## Backspacing at the start of a line 287 | 288 | Currently, `editorDelChar()` doesn't do anything when the cursor is at the 289 | beginning of a line. When the user backspaces at the beginning of a line, we 290 | want to append the contents of that line to the previous line, and then delete 291 | the current line. This effectively backspaces the implicit `\n` character in 292 | between the two lines to join them into one line. 293 | 294 | So we need two new row operations: one for appending a string to a row, and one 295 | for deleting a row. Let's start by implementing `editorDelRow()`, which will 296 | also require an `editorFreeRow()` function for freeing the memory owned by the 297 | `erow` we are deleting. 298 | 299 | {{del-row}} 300 | 301 | `editorDelRow()` looks a lot like `editorRowDelChar()`, because in both cases 302 | we are deleting a single element from an array of elements by its index. 303 | 304 | First we validate the `at` index. Then we free the memory owned by the row 305 | using `editorFreeRow()`. We then use `memmove()` to overwrite the deleted row 306 | struct with the rest of the rows that come after it, and decrement `numrows`. 307 | Finally, we increment `E.dirty`. 308 | 309 | Now let's implement `editorRowAppendString()`, which appends a string to the 310 | end of a row. 311 | 312 | {{row-append-string}} 313 | 314 | The row's new size is `row->size + len + 1` (including the null byte), so first 315 | we allocate that much memory for `row->chars`. Then we simply `memcpy()` the 316 | given string to the end of the contents of `row->chars`. We update `row->size`, 317 | call `editorUpdateRow()` as usual, and increment `E.dirty` as usual. 318 | 319 | Now we're ready to get `editorDelChar()` to handle the case where the cursor is 320 | at the beginning of a line. 321 | 322 | {{del-char-row}} 323 | 324 | If the cursor is at the beginning of the _first_ line, then there's nothing to 325 | do, so we `return` immediately. Otherwise, if we find that `E.cx == 0`, we call 326 | `editorRowAppendString()` and then `editorDelRow()` as we planned. `row` points 327 | to the row we are deleting, so we append `row->chars` to the previous row, and 328 | then delete the row that `E.cy` is on. We set `E.cx` to the end of the contents 329 | of the previous row before appending to that row. That way, the cursor will end 330 | up at the point where the two lines joined. 331 | 332 | Notice that pressing the Delete key at the end of a line works as 333 | the user would expect, joining the current line with the next line. This is 334 | because moving the cursor to the right at the end of a line moves it to the 335 | beginning of the next line. So making the Delete key an alias for 336 | the key followed by the Backspace key still works. 337 | 338 | ## The Enter key 339 | 340 | The last editor operation we have to implement is the Enter key. The 341 | Enter key allows the user to insert new lines into the text, or 342 | split a line into two lines. The first thing we need to do is rename the 343 | `editorAppendRow(...)` function to `editorInsertRow(int at, ...)`. It will now 344 | be able to insert a row at the index specified by the new `at` argument. 345 | 346 | {{append-to-insert}} 347 | 348 | Much like `editorRowInsertChar()`, we first validate `at`, then allocate memory 349 | for one more `erow`, and use `memmove()` to make room at the specified index 350 | for the new row. 351 | 352 | We also delete the old `int at = ...` line, since `at` is now being passed in 353 | as an argument. 354 | 355 | We now have to replace all calls to `editorAppendRow(...)` with calls to 356 | `editorInsertRow(E.numrows, ...)`. 357 | 358 | {{use-insert-row}} 359 | 360 | Now that we have `editorInsertRow()`, we're ready to implement 361 | `editorInsertNewline()`, which handles an Enter keypress. 362 | 363 | {{insert-newline}} 364 | 365 | If we're at the beginning of a line, all we have to do is insert a new blank 366 | row before the line we're on. 367 | 368 | Otherwise, we have to split the line we're on into two rows. First we call 369 | `editorInsertRow()` and pass it the characters on the current row that are to 370 | the right of the cursor. That creates a new row after the current one, with the 371 | correct contents. Then we reassign the `row` pointer, because 372 | `editorInsertRow()` calls `realloc()`, which might move memory around on us and 373 | invalidate the pointer (yikes). Then we truncate the current row's contents by 374 | setting its size to the position of the cursor, and we call 375 | `editorUpdateRow()` on the truncated row. (`editorInsertRow()` already calls 376 | `editorUpdateRow()` for the new row.) 377 | 378 | In both cases, we increment `E.cy`, and set `E.cx` to `0` to move the cursor to 379 | the beginning of the row. 380 | 381 | Finally, let's actually map the Enter key to the 382 | `editorInsertNewline()` operation. 383 | 384 | {{enter-key}} 385 | 386 | That concludes all of the text editing operations we are going to implement. If 387 | you wish, and if you are brave enough, you may now start using the editor to 388 | modify its own code for the rest of the tutorial. If you do, I suggest making 389 | regular backups of your work (using `git` or similar) in case you run into bugs 390 | in the editor. 391 | 392 | ## Save as... 393 | 394 | Currently, when the user runs `./kilo` with no arguments, they get a blank file 395 | to edit but have no way of saving. We need a way of prompting the user to input 396 | a filename when saving a new file. Let's make an `editorPrompt()` function that 397 | displays a prompt in the status bar, and lets the user input a line of text 398 | after the prompt. 399 | 400 | {{prompt}} 401 | 402 | The user's input is stored in `buf`, which is a dynamically allocated string 403 | that we initalize to the empty string. We then enter an infinite loop that 404 | repeatedly sets the status message, refreshes the screen, and waits for a 405 | keypress to handle. The `prompt` is expected to be a format string containing a 406 | `%s`, which is where the user's input will be displayed. 407 | 408 | When the user presses Enter, and their input is not empty, the 409 | status message is cleared and their input is returned. Otherwise, when they 410 | input a printable character, we append it to `buf`. If `buflen` has reached the 411 | maximum capacity we allocated (stored in `bufsize`), then we double `bufsize` 412 | and allocate that amount of memory before appending to `buf`. We also make sure 413 | that `buf` ends with a `\0` character, because both `editorSetStatusMessage()` 414 | and the caller of `editorPrompt()` will use it to know where the string ends. 415 | 416 | Notice that we have to make sure the input key isn't one of the special keys in 417 | the `editorKey` enum, which have high integer values. To do that, we test 418 | whether the input key is in the range of a `char` by making sure it is less 419 | than `128`. 420 | 421 | Now let's prompt the user for a filename in `editorSave()`, when `E.filename` 422 | is `NULL`. 423 | 424 | {{save-as}} 425 | 426 | Great, we now have basic "Save as..." functionality. Next, let's allow the user 427 | to press Escape to cancel the input prompt. 428 | 429 | {{prompt-escape}} 430 | 431 | When an input prompt is cancelled, we `free()` the `buf` ourselves and return 432 | `NULL`. So let's handle a return value of `NULL` in `editorSave()` by aborting 433 | the save operation and displaying a "Save aborted" message to the user. 434 | 435 | {{abort-save}} 436 | 437 | (Note: If you're using **Bash on Windows**, you will have to press 438 | Escape 3 times to get one Escape keypress to register in 439 | our program, because the `read()` calls in `editorReadKey()` that look for an 440 | escape sequence never time out.) 441 | 442 | Now let's allow the user to press Backspace (or Ctrl-H, 443 | or Delete) in the input prompt. 444 | 445 | {{prompt-backspace}} 446 | 447 | In the [next chapter](06.search.html), we'll make use of `editorPrompt()` to 448 | implement an incremental search feature in our editor. 449 | 450 | -------------------------------------------------------------------------------- /doc/06.search.md: -------------------------------------------------------------------------------- 1 | # Search 2 | 3 | Let's use `editorPrompt()` to implement a minimal search feature. When the user 4 | types a search query and presses Enter, we'll loop through all the 5 | rows of the file, and if a row contains their query string, we'll move the 6 | cursor to the match. 7 | 8 | {{basic-search}} 9 | 10 | `strstr()` comes from ``. 11 | 12 | If they pressed Escape to cancel the input prompt, then 13 | `editorPrompt()` returns `NULL` and we abort the search. 14 | 15 | Otherwise, we loop through all the rows of the file. We use `strstr()` to check 16 | if `query` is a substring of the current row. It returns `NULL` if there is no 17 | match, otherwise it returns a pointer to the matching substring. To convert 18 | that into an index that we can set `E.cx` to, we subtract the `row->render` 19 | pointer from the `match` pointer, since `match` is a pointer into the 20 | `row->render` string. Lastly, we set `E.rowoff` so that we are scrolled to the 21 | very bottom of the file, which will cause `editorScroll()` to scroll upwards at 22 | the next screen refresh so that the matching line will be at the very top of 23 | the screen. This way, the user doesn't have to look all over their screen to 24 | find where their cursor jumped to, and where the matching line is. 25 | 26 | There's one problem here. Did you notice what we just did wrong? We assigned a 27 | `render` index to `E.cx`, but `E.cx` is an index into `chars`. If there are 28 | tabs to the left of the match, the cursor is going to be in the wrong position. 29 | We need to convert the `render` index into a `chars` index before assigning it 30 | to `E.cx`. Let's create an `editorRowRxToCx()` function, which is the opposite 31 | of the `editorRowCxToRx()` function we wrote in 32 | [chapter 4](04.aTextViewer.html#tabs-and-the-cursor), but contains a lot of the 33 | same code. 34 | 35 | {{rx-to-cx}} 36 | 37 | To convert an `rx` into a `cx`, we do pretty much the same thing when 38 | converting the other way: loop through the `chars` string, calculating the 39 | current `rx` value (`cur_rx`) as we go. But instead of stopping when we hit a 40 | particular `cx` value and returning `cur_rx`, we want to stop when `cur_rx` 41 | hits the given `rx` value and return `cx`. 42 | 43 | The `return` statement at the very end is just in case the caller provided an 44 | `rx` that's out of range, which shouldn't happen. The `return` statement inside 45 | the `for` loop should handle all `rx` values that are valid indexes into 46 | `render`. 47 | 48 | Now let's call `editorRowRxToCx()` to convert the matched index to a `chars` 49 | index and assign that to `E.cx`. 50 | 51 | {{use-rx-to-cx}} 52 | 53 | Finally, let's map Ctrl-F to the `editorFind()` function, and add it 54 | to the help message we set in `main()`. 55 | 56 | {{ctrl-f}} 57 | 58 | ## Incremental search 59 | 60 | Now, let's make our search feature fancy. We want to support incremental 61 | search, meaning the file is searched after each keypress when the user is 62 | typing in their search query. 63 | 64 | To implement this, we're going to get `editorPrompt()` to take a callback 65 | function as an argument. We'll have it call this function after each keypress, 66 | passing the current search query inputted by the user and the last key they 67 | pressed. 68 | 69 | {{prompt-callback}} 70 | 71 | The `if` statements allow the caller to pass `NULL` for the callback, in case 72 | they don't want to use a callback. This is the case when we prompt the user 73 | for a filename, so let's pass `NULL` to `editorPrompt()` when we do that. We'll 74 | also pass `NULL` to `editorPrompt()` in `editorFind()` for now, to get the code 75 | to compile. 76 | 77 | {{null-callback}} 78 | 79 | Now let's move the actual searching code from `editorFind()` into a function 80 | called `editorFindCallback()`. Obviously this will be our callback function for 81 | `editorPrompt()`. 82 | 83 | {{incremental-search}} 84 | 85 | In the callback, we check if the user pressed Enter or 86 | Escape, in which case they are leaving search mode so we `return` 87 | immediately instead of doing another search. Otherwise, after any other 88 | keypress, we do another search for the current `query` string. 89 | 90 | That's all there is to it. We now have incremental search. 91 | 92 | ## Restore cursor position when cancelling search 93 | 94 | When the user presses Escape to cancel a search, we want the cursor 95 | to go back to where it was when they started the search. To do that, we'll have 96 | to save their cursor position and scroll position, and restore those values 97 | after the search is cancelled. 98 | 99 | {{restore-cursor}} 100 | 101 | If `query` is `NULL`, that means they pressed Escape, so in that 102 | case we restore the values we saved. 103 | 104 | ## Search forward and backward 105 | 106 | The last feature we'd like to add is to allow the user to advance to the next 107 | or previous match in the file using the arrow keys. The and 108 | keys will go to the previous match, and the 109 | and keys will go to the next match. 110 | 111 | We'll implement this feature using two static variables in our callback. 112 | `last_match` will contain the index of the row that the last match was on, or 113 | `-1` if there was no last match. And `direction` will store the direction of 114 | the search: `1` for searching forward, and `-1` for searching backward. 115 | 116 | {{callback-statics}} 117 | 118 | As you can see, we always reset `last_match` to `-1` unless an arrow key was 119 | pressed. So we'll only advance to the next or previous match when an arrow key 120 | is pressed. You can also see that we always set `direction` to `1` unless the 121 | or key was pressed. So we always search in 122 | the forward direction unless the user specifically asks to search backwards 123 | from the last match. 124 | 125 | If `key` is `'\r'` (Enter) or `'\x1b'` (Escape), that 126 | means we're about to leave search mode. So we reset `last_match` and 127 | `direction` to their initial values to get ready for the next search operation. 128 | 129 | Now that we have those variables all set up, let's put them to use. 130 | 131 | {{search-arrows}} 132 | 133 | `current` is the index of the current row we are searching. If there was a last 134 | match, it starts on the line after (or before, if we're searching backwards). 135 | If there wasn't a last match, it starts at the top of the file and searches in 136 | the forward direction to find the first match. 137 | 138 | The `if ... else if` causes `current` to go from the end of the file back to 139 | the beginning of the file, or vice versa, to allow a search to "wrap around" 140 | the end of a file and continue from the top (or bottom). 141 | 142 | When we find a match, we set `last_match` to `current`, so that if the user 143 | presses the arrow keys, we'll start the next search from that point. 144 | 145 | Finally, let's not forget to update the prompt text to let the user know they 146 | can use the arrow keys. 147 | 148 | {{search-arrows-help}} 149 | 150 | In the [next chapter](07.syntaxHighlighting.html), we'll implement syntax 151 | highlighting and filetype detection, to complete our text editor. 152 | 153 | -------------------------------------------------------------------------------- /doc/07.syntaxHighlighting.md: -------------------------------------------------------------------------------- 1 | # Syntax highlighting 2 | 3 | ## Colorful digits 4 | 5 | Let's start by just getting some color on the screen, as simply as possible. 6 | We'll attempt to highlight numbers by coloring each digit character 7 | red. 8 | 9 | {{syntax-digits}} 10 | 11 | We can no longer just feed the substring of `render` that we want to print 12 | right into `abAppend()`. We'll have to do it character-by-character from now 13 | on. So we loop through the characters and use `isdigit()` on each one to test 14 | if it is a digit character. If it is, we precede it with the `[31m` escape 15 | sequence and follow it by the `[39m` sequence. 16 | 17 | We previously used the `m` command 18 | ([Select Graphic Rendition](http://vt100.net/docs/vt100-ug/chapter3.html#SGR)) 19 | to draw the status bar using inverted colors. Now we are using it to set the 20 | text color. The 21 | [VT100 User Guide](http://vt100.net/docs/vt100-ug/chapter3.html) doesn't 22 | document color, so let's turn to the Wikipedia article on 23 | [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code). It 24 | includes a large table containing all the different argument codes you can use 25 | with the `m` command on various terminals. It also includes the ANSI color 26 | table with the 8 foreground/background colors available. 27 | 28 | The first table says we can set the text color using codes `30` to `37`, and 29 | reset it to the default color using `39`. The color table says `0` is black, 30 | `1` is red, and so on, up to `7` which is white. Putting these together, we can 31 | set the text color to red using `31` as an argument to the `m` command. After 32 | printing the digit, we use `39` as an argument to `m` to set the text color 33 | back to normal. 34 | 35 | ## Refactor syntax highlighting 36 | 37 | Now we know how to color text, but we're going to have to do a lot more work to 38 | actually highlight entire strings, keywords, comments, and so on. We can't just 39 | decide what color to use based on the class of each character, like we're doing 40 | with digits currently. What we want to do is figure out the highlighting for 41 | each row of text before we display it, and then rehighlight a line whenever it 42 | gets changed. To do that, we need to store the highlighting of each line in an 43 | array. Let's add an array to the `erow` struct named `hl`, which stands for 44 | "highlight". 45 | 46 | {{syntax-refactoring}} 47 | 48 | `hl` is an array of `unsigned char` values, meaning integers in the range of 49 | `0` to `255`. Each value in the array will correspond to a character in 50 | `render`, and will tell you whether that character is part of a string, or a 51 | comment, or a number, and so on. Let's create an `enum` containing the possible 52 | values that the `hl` array can contain. 53 | 54 | {{highlight-enum}} 55 | 56 | For now, we'll focus on highlighting numbers only. So we want every character 57 | that's part of a number to have a corresponding `HL_NUMBER` value in the `hl` 58 | array, and we want every other value in `hl` to be `HL_NORMAL`. 59 | 60 | Let's create a new `/*** syntax highlighting ***/` section, and create an 61 | `editorUpdateSyntax()` function in it. This function will go through the 62 | characters of an `erow` and highlight them by setting each value in the `hl` 63 | array. 64 | 65 | {{editor-update-syntax}} 66 | 67 | `memset()` comes from ``. 68 | 69 | First we `realloc()` the needed memory, since this might be a new row or the 70 | row might be bigger than the last time we highlighted it. Notice that the size 71 | of the `hl` array is the same as the `render` array, so we use `rsize` as the 72 | amount of memory to allocate for `hl`. 73 | 74 | Then we use `memset()` to set all characters to `HL_NORMAL` by default, before 75 | looping through the characters and setting the digits to `HL_NUMBER`. (Don't 76 | worry, we'll implement a better way of recognizing numbers soon enough, but 77 | right now we are focusing on refactoring.) 78 | 79 | Now let's actually call `editorUpdateSyntax()`. 80 | 81 | {{call-update-syntax}} 82 | 83 | `editorUpdateRow()` already has the job of updating the `render` array whenever 84 | the text of the row changes, so it makes sense that that's where we want to 85 | update the `hl` array. So after updating `render`, we call 86 | `editorUpdateSyntax()` at the end. 87 | 88 | Next, let's make an `editorSyntaxToColor()` function that maps values in `hl` 89 | to the actual ANSI color codes we want to draw them with. 90 | 91 | {{map-colors}} 92 | 93 | We return the ANSI code for "foreground red" for numbers, and "foreground 94 | white" for anything else that might slip through. (We'll be handling 95 | `HL_NORMAL` separately, so `editorSyntaxToColor()` doesn't need to handle it.) 96 | 97 | Now let's finally draw the highlighted text to the screen! 98 | 99 | {{use-hl}} 100 | 101 | First we get a pointer, `hl`, to the slice of the `hl` array that corresponds 102 | to the slice of `render` that we are printing. Then, for each character, if 103 | it's an `HL_NORMAL` character, we use `[39m` to make sure we're using the 104 | default text color before printing it. If it's not `HL_NORMAL`, we use 105 | `snprintf()` to write the escape sequence into a buffer which we pass to 106 | `abAppend()` before appending the actual character. Finally, after we're done 107 | looping through all the characters and displaying them, we print a final 108 | `[39m` escape sequence to make sure the text color is reset to default. 109 | 110 | This works, but do we really have to write out an escape sequence before every 111 | single character? In practice, most characters are going to be the same color 112 | as the previous character, so most of the escape sequences are redundant. Let's 113 | keep track of the current text color as we loop through the characters, and 114 | only print out an escape sequence when the color changes. 115 | 116 | {{current-color}} 117 | 118 | `current_color` is `-1` when we want the default text color, otherwise it is 119 | set to the value that `editorSyntaxToColor()` last returned. When the color 120 | changes, we print out the escape sequence for that color and set 121 | `current_color` to the new color. When we go from highlighted text back to 122 | `HL_NORMAL` text, we print out the `[39m` escape sequence and set 123 | `current_color` to `-1`. 124 | 125 | That concludes our refactoring of the syntax highlighting system. 126 | 127 | ## Colorful search results 128 | 129 | Before we start highlighting strings and keywords and all that, let's use our 130 | highlighting system to highlight search results. We'll start by adding 131 | `HL_MATCH` to the `editorHighlight` enum, and mapping it to the color blue 132 | (`34`) in `editorSyntaxToColor()`. 133 | 134 | {{hl-match}} 135 | 136 | Now all we have to do is `memset()` the matched substring to `HL_MATCH` in our 137 | search code. 138 | 139 | {{search-highlighting}} 140 | 141 | `match - row->render` is the index into `render` of the match, so we use that 142 | as our index into `hl`. 143 | 144 | ## Restore syntax highlighting after search 145 | 146 | Currently, search results stay highlighted in blue even after the user is done 147 | using the search feature. We want to restore `hl` to its previous value after 148 | each search. To do that, we'll save the original contents of `hl` in a static 149 | variable named `saved_hl` in `editorFindCallback()`, and restore `hl` to the 150 | contents of `saved_hl` at the top of the callback. 151 | 152 | {{restore-hl}} 153 | 154 | We use another static variable named `saved_hl_line` to know which line's `hl` 155 | needs to be restored. `saved_hl` is a dynamically allocated array which points 156 | to `NULL` when there is nothing to restore. If there is something to restore, 157 | we `memcpy()` it to the saved line's `hl` and then deallocate `saved_hl` and 158 | set it back to `NULL`. 159 | 160 | Notice that the `malloc()`'d memory is guaranteed to be `free()`'d, because 161 | when the user closes the search prompt by pressing Enter or 162 | Escape, `editorPrompt()` calls our callback, giving a chance for 163 | `hl` to be restored before `editorPrompt()` finally returns. Also notice that 164 | it's impossible for `saved_hl` to get `malloc()`'d before its old value gets 165 | `free()`'d, because we always `free()` it at the top of the function. And 166 | finally, it's impossible for the user to edit the file between saving and 167 | restoring the `hl`, so we can safely use `saved_hl_line` as an index into 168 | `E.row`. (It's important to think about these things.) 169 | 170 | ## Colorful numbers 171 | 172 | Alright, let's start working on highlighting numbers properly. First, we'll 173 | change our `for` loop in `editorUpdateSyntax()` to a `while` loop, to allow us 174 | to consume multiple characters each iteration. (We'll only consume one 175 | character at a time for numbers, but this will be useful for later.) 176 | 177 | {{syntax-while}} 178 | 179 | Now let's define an `is_separator()` function that takes a character and 180 | returns true if it's considered a separator character. 181 | 182 | {{is-separator}} 183 | 184 | `strchr()` comes from ``. It looks for the first occurrence of a 185 | character in a string, and returns a pointer to the matching character in the 186 | string. If the string doesn't contain the character, `strchr()` returns `NULL`. 187 | 188 | Right now, numbers are highlighted even if they're part of an identifier, such 189 | as the `32` in `int32_t`. To fix that, we'll require that numbers are preceded 190 | by a separator character, which includes whitespace or punctuation characters. 191 | We also include the null byte (`'\0'`), because then we can count the null byte 192 | at the end of each line as a separator, which will make some of our code 193 | simpler in the future. 194 | 195 | Let's add a `prev_sep` variable to `editorUpdateSyntax()` that keeps track of 196 | whether the previous character was a separator. Then let's use it to recognize 197 | and highlight numbers properly. 198 | 199 | {{prev-sep}} 200 | 201 | We initialize `prev_sep` to `1` (meaning true) because we consider the 202 | beginning of the line to be a separator. (Otherwise numbers at the very 203 | beginning of the line wouldn't be highlighted.) 204 | 205 | `prev_hl` is set to the highlight type of the previous character. To highlight 206 | a digit with `HL_NUMBER`, we now require the previous character to either be a 207 | separator, or to also be highlighted with `HL_NUMBER`. 208 | 209 | When we decide to highlight the current character a certain way (`HL_NUMBER` in 210 | this case), we increment `i` to "consume" that character, set `prev_sep` to `0` 211 | to indicate we are in the middle of highlighting something, and then `continue` 212 | the loop. We will use this pattern for each thing that we highlight. 213 | 214 | If we end up not highlighting the current character, then we'll end up at the 215 | bottom of the `while` loop, where we set `prev_sep` according to whether the 216 | current character is a separator, and we increment `i` to consume the 217 | character. The `memset()` we did at the top of the function means that an 218 | unhighlighted character will have a value of `HL_NORMAL` in `hl`. 219 | 220 | Now let's support highlighting numbers that contain decimal points. 221 | 222 | {{decimal-point}} 223 | 224 | A `.` character that comes after a character that we just highlighted as a 225 | number will now be considered part of the number. 226 | 227 | ## Detect filetype 228 | 229 | Before we go on to highlight other things, we're going to add filetype 230 | detection to our editor. This will allow us to have different rules for how to 231 | highlight different types of files. For example, text files shouldn't have any 232 | highlighting, and C files should highlight numbers, strings, C/C++-style 233 | comments, and many different keywords specific to C. 234 | 235 | Let's create an `editorSyntax` struct that will contain all the syntax 236 | highlighting information for a particular filetype. 237 | 238 | {{editor-syntax}} 239 | 240 | The `filetype` field is the name of the filetype that will be displayed to the 241 | user in the status bar. `filematch` is an array of strings, where each string 242 | contains a pattern to match a filename against. If the filename matches, then 243 | the file will be recognized as having that filetype. Finally, `flags` is a bit 244 | field that will contain flags for whether to highlight numbers and whether to 245 | highlight strings for that filetype. For now, we define just the 246 | `HL_HIGHLIGHT_NUMBERS` flag bit. 247 | 248 | Now let's make an array of built-in `editorSyntax` structs, and add one for the 249 | C language to it. 250 | 251 | {{hldb}} 252 | 253 | `HLDB` stands for "highlight database". Our `editorSyntax` struct for the C 254 | language contains the string `"c"` for the `filetype` field, the extensions 255 | `".c"`, `".h"`, and `".cpp"` for the `filematch` field (the array must be 256 | terminated with `NULL`), and the `HL_HIGHLIGHT_NUMBERS` flag turned on in the 257 | `flags` field. 258 | 259 | We then define an `HLDB_ENTRIES` constant to store the length of the `HLDB` 260 | array. 261 | 262 | Now let's add a pointer to the current `editorSyntax` struct in our global 263 | editor state, and initialize it to `NULL`. 264 | 265 | {{e-syntax}} 266 | 267 | When `E.syntax` is `NULL`, that means there is no filetype for the current 268 | file, and no syntax highlighting should be done. 269 | 270 | Let's show the current filetype in the status bar. If `E.syntax` is `NULL`, 271 | then we'll display `no ft` ("no filetype") instead. 272 | 273 | {{show-filetype}} 274 | 275 | Now let's change `editorUpdateSyntax()` to take the current `E.syntax` value 276 | into account. 277 | 278 | {{use-filetype}} 279 | 280 | If no filetype is set, we `return` immediately after `memset()`ting the entire 281 | line to `HL_NORMAL`. We also wrap the number-highlighting code in an `if` 282 | statement that checks to see if numbers should be highlighted for the current 283 | filetype. 284 | 285 | Now we'll create an `editorSelectSyntaxHighlight()` function that tries to 286 | match the current filename to one of the `filematch` fields in the `HLDB`. If 287 | one matches, it'll set `E.syntax` to that filetype. 288 | 289 | {{select-syntax}} 290 | 291 | `strrchr()` and `strcmp()` come from ``. `strrchr()` returns a 292 | pointer to the last occurrence of a character in a string, and `strcmp()` 293 | returns `0` if two given strings are equal. 294 | 295 | First we set `E.syntax` to `NULL`, so that if nothing matches or if there is no 296 | filename, then there is no filetype. 297 | 298 | Then we get a pointer to the extension part of the filename by using 299 | `strrchr()` to find the last occurrence of the `.` character. If there is no 300 | extension, then `ext` will be `NULL`. 301 | 302 | Finally, we loop through each `editorSyntax` struct in the `HLDB` array, and 303 | for each one of those, we loop through each pattern in its `filematch` array. 304 | If the pattern starts with a `.`, then it's a file extension pattern, and we 305 | use `strcmp()` to see if the filename ends with that extension. If it's not a 306 | file extension pattern, then we just check to see if the pattern exists 307 | anywhere in the filename, using `strstr()`. If the filename matched according 308 | to those rules, then we set `E.syntax` to the current `editorSyntax` struct, 309 | and `return`. 310 | 311 | We want to call `editorSelectSyntaxHighlight()` wherever `E.filename` changes. 312 | This is in `editorOpen()` and `editorSave()`. 313 | 314 | {{detect-filetype}} 315 | 316 | At this point, when you open a C file in the editor, you should see numbers 317 | getting highlighted, and you should see `c` in the status bar where we display 318 | the filetype. When you start up the editor with no arguments and save the file 319 | with a filename that ends in `.c`, you should see the filetype in the status 320 | bar change satisfyingly from `no ft` to `c`. However, any numbers you might 321 | have in the file will not be highlighted! Very unsatisfying! 322 | 323 | Let's rehighlight the entire file after setting `E.syntax` in 324 | `editorSelectSyntaxHighlight()`. 325 | 326 | {{rehighlight}} 327 | 328 | We simply loop through each row in the file, and call `editorUpdateSyntax()` on 329 | it. Now the highlighting immediately changes when the filetype changes. 330 | 331 | ## Colorful strings 332 | 333 | With all that out of the way, we can finally get to highlighting more things! 334 | Let's start with strings. 335 | 336 | {{hl-string}} 337 | 338 | We're coloring strings magenta (`35`). 339 | 340 | Now let's add an `HL_HIGHLIGHT_STRINGS` bit flag to the `flags` field of the 341 | `editorSyntax` struct, and turn on the flag when highlighting C files. 342 | 343 | {{string-flag}} 344 | 345 | Now for the actual highlighting code. We will use an `in_string` variable to 346 | keep track of whether we are currently inside a string. If we are, then we'll 347 | keep highlighting the current character as a string until we hit the closing 348 | quote. 349 | 350 | {{syntax-strings}} 351 | 352 | As you can see, we highlight both double-quoted strings and single-quoted 353 | strings (sorry Lispers/Rustaceans). We actually store either a double-quote 354 | (`"`) or a single-quote (`'`) character as the value of `in_string`, so that we 355 | know which one closes the string. 356 | 357 | So, going through the code from top to bottom: If `in_string` is set, then we 358 | know the current character can be highlighted with `HL_STRING`. Then we check 359 | if the current character is the closing quote (`c == in_string`), and if so, we 360 | reset `in_string` to `0`. Then, since we highlighted the current character, we 361 | have to consume it by incrementing `i` and `continue`ing out of the current 362 | loop iteration. We also set `prev_sep` to `1` so that if we're done 363 | highlighting the string, the closing quote is considered a separator. 364 | 365 | If we're not currently in a string, then we have to check if we're at the 366 | beginning of one by checking for a double- or single-quote. If we are, we store 367 | the quote in `in_string`, highlight it with `HL_STRING`, and consume it. 368 | 369 | We should probably take escaped quotes into account when highlighting strings. 370 | If the sequence `\'` or `\"` occurs in a string, then the escaped quote doesn't 371 | close the string in the vast majority of languages. 372 | 373 | {{string-escapes}} 374 | 375 | If we're in a string and the current character is a backslash (``\``), *and* 376 | there's at least one more character in that line that comes after the 377 | backslash, then we highlight the character that comes after the backslash with 378 | `HL_STRING` and consume it. We increment `i` by `2` to consume both characters 379 | at once. 380 | 381 | ## Colorful single-line comments 382 | 383 | Next let's highlight single-line comments. (We'll leave multi-line comments 384 | until the end, because they're complicated.) 385 | 386 | {{hl-comment}} 387 | 388 | Comments will be highlighted in cyan (`36`). 389 | 390 | We'll let each language specify its own single-line comment pattern, as they 391 | differ a lot between languages. Let's add a `singleline_comment_start` string 392 | to the `editorSyntax` struct, and set it to `"//"` for the C filetype. 393 | 394 | {{scs}} 395 | 396 | Okay, now for the highlighting code. 397 | 398 | {{syntax-comments}} 399 | 400 | `strncmp()` comes from ``. 401 | 402 | If you don't want single-line comment highlighting for a particular filetype, 403 | you should be able to set `singleline_comment_start` either to `NULL` or to the 404 | empty string (`""`). We make `scs` an alias for 405 | `E.syntax->singleline_comment_start` for easier typing (and readability, 406 | perhaps?). We then set `scs_len` to the length of the string, or `0` if the 407 | string is `NULL`. This lets us use `scs_len` as a boolean to know whether we 408 | should highlight single-line comments. 409 | 410 | So we wrap our comment highlighting code in an `if` statement that checks 411 | `scs_len` and also makes sure we're not in a string, since we're placing this 412 | code above the string highlighting code (order matters a lot in this 413 | function). 414 | 415 | If those checks passed, then we use `strncmp()` to check if this character is 416 | the start of a single-line comment. If so, then we simply `memset()` the whole 417 | rest of the line with `HL_COMMENT` and `break` out of the syntax highlighting 418 | loop. Just like that, we're done highlighting the line. 419 | 420 | ## Colorful keywords 421 | 422 | Now let's turn to highlighting keywords. We're going to allow languages to 423 | specify two types of keywords that will be highlighted in different colors. (In 424 | C, we'll highlight actual keywords in one color and common type names in the 425 | other color.) 426 | 427 | {{hl-keywords}} 428 | 429 | The two colors we'll use for keywords are yellow (`33`) and green (`32`). 430 | 431 | Let's add a `keywords` array to the `editorSyntax` struct. This will be a 432 | `NULL`-terminated array of strings, each string containing a keyword. To 433 | differentiate between the two types of keywords, we'll terminate the second 434 | type of keywords with a pipe (`|`) character (also known as a vertical bar). 435 | 436 | {{c-keywords}} 437 | 438 | As mentioned earlier, we'll highlight common C types as secondary keywords, so 439 | we end each one with a `|` character. 440 | 441 | Now let's highlight them. 442 | 443 | {{syntax-keywords}} 444 | 445 | First, at the top of the function we make `keywords` an alias for 446 | `E.syntax->keywords` since we'll be using it a lot, and in some pretty dense 447 | code. 448 | 449 | Keywords require a separator both before and after the keyword. Otherwise, the 450 | `void` in `avoid`, `voided`, or `avoidable` would be highlighted as a keyword, 451 | which is definitely a problem we want to, uh, circumnavigate. 452 | 453 | So we check `prev_sep` to make sure a separator came before the keyword, before 454 | looping through each possible keyword. For each keyword, we store the length in 455 | `klen` and whether it's a secondary keyword in `kw2`, in which case we 456 | decrement `klen` to account for the extraneous `|` character. 457 | 458 | We then use `strncmp()` to check if the keyword exists at our current position 459 | in the text, *and* we check to see if a separator character comes after the 460 | keyword. Since `\0` is considered a separator character, this works if the 461 | keyword is at the very end of the line. 462 | 463 | If all that passed, then we have a keyword to highlight. We use `memset()` to 464 | highlight the whole keyword at once, highlighting it with `HL_KEYWORD1` or 465 | `HL_KEYWORD2` depending on the value of `kw2`. We then consume the entire 466 | keyword by incrementing `i` by the length of the keyword. Then we `break` 467 | instead of `continue`ing, because we are in an inner loop, so we have to break 468 | out of that loop before `continue`ing the outer loop. That is why, after the 469 | `for` loop, we check if the loop was broken out of by seeing if it got to the 470 | terminating `NULL` value, and if it was broken out of, we `continue`. 471 | 472 | ## Nonprintable characters 473 | 474 | Before we tackle highlighting multi-line comments, let's take a quick break 475 | from `editorUpdateSyntax()`. 476 | 477 | We're going to display nonprintable characters in a more user-friendly way. 478 | Currently, nonprintable characters completely mess up the rendering that our 479 | editor does. Just try running `kilo` and passing itself in as an argument. That 480 | is, open the `kilo` executable file using `kilo`. And try moving the cursor 481 | around, and typing. It's not pretty. Every keypress causes the terminal to 482 | ding, because the audible bell character (`7`) is being printed out. Strings 483 | containing terminal escape sequences in our code are being printed out as 484 | actual escape sequences, because that's how they're stored in a raw executable. 485 | 486 | To prevent all that, we're going to translate nonprintable characters into 487 | printable ones. We'll render the alphabetic control characters 488 | (Ctrl-A = `1`, Ctrl-B = `2`, ..., Ctrl-Z = 489 | `26`) as the capital letters `A` through `Z`. We'll also render the `0` byte 490 | like a control character. Ctrl-@ = `0`, so we'll render it as an `@` 491 | sign. Finally, any other nonprintable characters we'll render as a question 492 | mark (`?`). And to differentiate these characters from their printable 493 | counterparts, we'll render them using inverted colors (black on white). 494 | 495 | {{nonprintables}} 496 | 497 | We use `iscntrl()` to check if the current character is a control character. If 498 | so, we translate it into a printable character by adding its value to `'@'` (in 499 | ASCII, the capital letters of the alphabet come after the `@` character), or 500 | using the `'?'` character if it's not in the alphabetic range. 501 | 502 | We then use the `[7m` escape sequence to switch to inverted colors before 503 | printing the translated symbol. We use `[m` to turn off inverted colors 504 | again. 505 | 506 | Unfortunately, `[m` turns off *all* text formatting, including colors. So 507 | let's print the escape sequence for the current color afterwards. 508 | 509 | {{nonprintables-fix-color}} 510 | 511 | You can test the coloring of nonprintables by pressing Ctrl-A, 512 | Ctrl-B, and so on to insert those control characters into strings or 513 | comments, and you should see that they get the same color as the surrounding 514 | characters, just inverted. 515 | 516 | ## Colorful multiline comments 517 | 518 | Okay, we have one last feature to implement: multi-line comment highlighting. 519 | Let's start by adding `HL_MLCOMMENT` to the `editorHighlight` enum. 520 | 521 | {{hl-multiline-comments}} 522 | 523 | We'll highlight multi-line comments to be the same color as single-line 524 | comments (cyan). 525 | 526 | Now we'll add two strings to `editorSyntax`: `multiline_comment_start` and 527 | `multiline_comment_end`. In C, these will be `"/*"` and `"*/"`. 528 | 529 | {{mcs-mce}} 530 | 531 | Now let's open `editorUpdateSyntax()` up once again. We'll add `mcs` and `mce` 532 | aliases that are analogous to the `scs` alias we already have for single-line 533 | comments. We'll also add `mcs_len` and `mce_len`. 534 | 535 | {{mcs-mce-len}} 536 | 537 | Now for the highlighting code. We won't worry about multiple lines just yet. 538 | 539 | {{syntax-mlcomment}} 540 | 541 | First we add an `in_comment` boolean variable to keep track of whether we're 542 | currently inside a multi-line comment (this variable isn't used for single-line 543 | comments). 544 | 545 | Moving down into the `while` loop, we require both `mcs` and `mce` to be 546 | non-`NULL` strings of length greater than `0` in order to turn on multi-line 547 | comment highlighting. We also check to make sure we're not in a string, because 548 | having `/*` inside a string doesn't start a comment in most languages. Okay, 549 | I'll say it: *all* languages. 550 | 551 | If we're currently in a multi-line comment, then we can safely highlight the 552 | current character with `HL_MLCOMMENT`. Then we check if we're at the end of a 553 | multi-line comment by using `strncmp()` with `mce`. If so, we use `memset()` to 554 | highlight the whole `mce` string with `HL_MLCOMMENT`, and then we consume it. 555 | If we're not at the end of the comment, we simply consume the current character 556 | which we already highlighted. 557 | 558 | If we're not currently in a multi-line comment, then we use `strncmp()` with 559 | `mcs` to check if we're at the beginning of a multi-line comment. If so, we use 560 | `memset()` to highlight the whole `mcs` string with `HL_MLCOMMENT`, set 561 | `in_comment` to true, and consume the whole `mcs` string. 562 | 563 | Now let's fix a bit of a complication that multi-line comments add: 564 | single-line comments should not be recognized inside multi-line comments. 565 | 566 | {{slcomment-within-mlcomment}} 567 | 568 | Okay, now let's work on highlighting multi-line comments that actually span 569 | over multiple lines. To do this, we need to know if the previous line is part 570 | of an unclosed multi-line comment. Let's add an `hl_open_comment` boolean 571 | variable to the `erow` struct. Let's also add an `idx` integer variable, so 572 | that each `erow` knows its own index within the file. That will allow each row 573 | to examine the previous row's `hl_open_comment` value. 574 | 575 | {{idx-and-hloc}} 576 | 577 | We initialize `idx` to the row's index in the file at the time it is inserted. 578 | Let's make sure to update the `idx` of each row whenever a row is inserted into 579 | or removed from the file. 580 | 581 | {{update-idx}} 582 | 583 | The `for` loops update the index of each row that was displaced by the insert 584 | or delete operation. 585 | 586 | Now, the final step. 587 | 588 | {{propagate-highlight}} 589 | 590 | Near the top of `editorUpdateSyntax()`, we initialize `in_comment` to true if 591 | the previous row has an unclosed multi-line comment. If that's the case, then 592 | the current row will start out being highlighted as a multi-line comment. 593 | 594 | At the bottom of `editorUpdateSyntax()`, we set the value of the current row's 595 | `hl_open_comment` to whatever state `in_comment` got left in after processing 596 | the entire row. That tells us whether the row ended as an unclosed multi-line 597 | comment or not. 598 | 599 | Then we have to consider updating the syntax of the next lines in the file. So 600 | far, we have only been updating the syntax of a line when the user changes that 601 | specific line. But with multi-line comments, a user could comment out an entire 602 | file just by changing one line. So it seems like we need to update the syntax 603 | of all the lines following the current line. However, we know the highlighting 604 | of the next line will not change if the value of this line's `hl_open_comment` 605 | did not change. So we check if it changed, and only call `editorUpdateSyntax()` 606 | on the next line if `hl_open_comment` changed (and if there is a next line in 607 | the file). Because `editorUpdateSyntax()` keeps calling itself with the next 608 | line, the change will continue to propagate to more and more lines until one of 609 | them is unchanged, at which point we know that all the lines after that one 610 | must be unchanged as well. 611 | 612 | ## You're done 613 | 614 | That's it! Our text editor is finished. In the 615 | [appendices](08.appendices.html), you'll find some ideas for features you might 616 | want to extend the editor with on your own. 617 | 618 | -------------------------------------------------------------------------------- /doc/08.appendices.md: -------------------------------------------------------------------------------- 1 | # Appendices 2 | 3 | ## How the diffs work 4 | 5 | Each step in this tutorial is presented as a diff. A diff shows you the changes 6 | you need to make to the previous step's code to get to the current step. Here's 7 | a sample diff, from step 7: 8 | 9 | {{icanon}} 10 | 11 | Each diff starts with a header that contains the filename of the file you need 12 | to edit ("kilo.c"), the step number ("Step 7"), and the step name ("icanon"). 13 | You can click the filename to see the full source code of the file for the 14 | current step on GitHub. You can also click the step name on the far right to 15 | browse all files for the current step on GitHub (which isn't particularly 16 | useful for this tutorial, since we're just working on a single source file). 17 | 18 | After the header, the contents of the file are shown. Lines that need to be 19 | added or changed are highlighted and marked with an arrow. Functions that don't 20 | contain any changed code are folded into a single line with their contents 21 | hidden. 22 | 23 | Lines that need to be removed are given a red background, a 24 | strike-through style, and are marked with an ✕. Removed lines are not 25 | shown when they are adjacent to an added or changed line, so you won't see them 26 | very often. 27 | 28 | The bottom of each diff shows you the compile status of that step. If it's 29 | green and says "compiles", then you can expect your code to compile after 30 | completing the step, and you can expect to be able to observe the change when 31 | you run the program. If there are no observable changes for that step, then the 32 | compile status will be blue and say, "compiles, but with no observable 33 | effects". On the rare occasion that the step doesn't compile, it will be red 34 | and say "doesn't compile". 35 | 36 | ## What to do if you are stuck 37 | 38 | Some of the code in this tutorial is very tricky to type in exactly, especially 39 | if you're not used to C. It's especially easy to make a mistake when you're 40 | making a change to a line, and you think you're done changing that line, but 41 | you missed one little change to another part of that same line. It's important 42 | to take your time, and compare the changed parts of the diff 43 | *character-by-character* with your code to make sure they're the same. 44 | 45 | If you suspect you made an error, but don't know where it is or how far back 46 | you might've made the error, you should get your computer to do a diff between 47 | your version of `kilo.c` and the tutorial's version of `kilo.c` for whatever 48 | step you're on. The 49 | [kilo-src](https://github.com/snaptoken/kilo-src) repository contains the 50 | `kilo.c` source code for every step in the tutorial. 51 | 52 | You will need `git` to do this. To install `git` (assuming you've completed 53 | [chapter 1](01.setup.html)): on **Ubuntu/Bash on Windows**, run 54 | `sudo apt-get install git`; on **Cygwin**, run the installer again and select 55 | the `git` package for installation; on **macOS**, `git` should've been 56 | installed when you installed command line tools. 57 | 58 | Once you have `git` installed, clone the 59 | [kilo-src](https://github.com/snaptoken/kilo-src) repository by running 60 | `git clone https://github.com/snaptoken/kilo-src`. `cd` into the repo using 61 | `cd kilo-src`. The repo has a 62 | [tag](https://git-scm.com/book/en/v2/Git-Basics-Tagging) for each step that 63 | points the step name to that step's commit in the repo. So to get the source 64 | code for the step named `icanon`, run `git checkout icanon`. The `kilo.c` file 65 | will now contain the code for that step. You can compare your `kilo.c` with 66 | this `kilo.c` by running something like 67 | `git diff --no-index -b ../path/to/your/kilo.c kilo.c`. This will show you the 68 | changes you would need to make to your `kilo.c` to get it to look like the one 69 | in the repo. The `-b` option ignores whitespace, so it won't matter if you use 70 | a different indent style than the one in the tutorial. 71 | 72 | ## Where to get help 73 | 74 | If you are having trouble, feel free to create an 75 | [issue](https://github.com/snaptoken/kilo-tutorial/issues) on the tutorial's 76 | [GitHub repo](https://github.com/snaptoken/kilo-tutorial), and ask a question. 77 | 78 | You can also [email me](mailto:jeremy.ruten@gmail.com) directly if you'd rather 79 | not use GitHub. 80 | 81 | ## Ideas for features to add on your own 82 | 83 | If you want to extend `kilo` on your own, I suggest trying to actually *use* 84 | `kilo` as your text editor for a while. You will very quickly become painfully 85 | aware of all sorts of features you're used to having in a text editor, but are 86 | missing in `kilo`. Those are the features you should try to add. And you should 87 | use `kilo` when you work on `kilo.c`. 88 | 89 | If you're still looking for ideas, here's a small list, roughly in order of 90 | increasing difficulty. 91 | 92 | * **More filetypes**: Add syntax highlighting rules for some of your favourite 93 | languages to the `HLDB` array. 94 | * **Line numbers**: Display the line number to the left of each line of the 95 | file. 96 | * **Soft indent**: If you like using spaces instead of tabs, make the 97 | Tab key insert spaces instead of `\t`. You may want 98 | Backspace to remove a Tab key's worth of spaces as 99 | well. 100 | * **Auto indent**: When starting a new line, indent it to the same level as the 101 | previous line. 102 | * **Hard-wrap lines**: Insert a newline in the text when the user is about 103 | to type past the end of the screen. Try not to insert the newline where it 104 | would split up a word. 105 | * **Soft-wrap lines**: When a line is longer than the screen width, use 106 | multiple lines on the screen to display it instead of horizontal scrolling. 107 | * **Use ncurses**: The [ncurses](https://en.wikipedia.org/wiki/Ncurses) 108 | library takes care of a lot of the low level terminal interaction for you, 109 | and makes your program more portable. 110 | * **Copy and paste**: Give the user a way to select text, and then copy the 111 | selected text when they press Ctrl-C, and let them paste the 112 | copied text when they press Ctrl-V. 113 | * **Config file**: Have `kilo` read a config file (maybe named `.kilorc`) to 114 | set options that are currently constants, like `KILO_TAB_STOP` and 115 | `KILO_QUIT_TIMES`. Try to make more things configurable. 116 | * **Modal editing**: If you like [vim](http://www.vim.org/), make `kilo` work 117 | more like vim by letting the user press i for "insert mode" and 118 | then press Escape to go back to "normal mode". Then start adding 119 | all your favourite vim commands, starting with the basic movement commands 120 | (hjkl). 121 | * **Multiple buffers**: Allow having multiple files open at once, and have some 122 | way of switching between them. 123 | 124 | ## More tutorials like this 125 | 126 | I am planning to make more tutorials like this one. They will all be available 127 | at [viewsourcecode.org/snaptoken](http://viewsourcecode.org/snaptoken). There 128 | is a link there that will let you sign up to receive an email whenever a new 129 | tutorial is available. There is also a list of similar tutorials by other 130 | people from around the web. 131 | 132 | The next tutorials will be a little different from this one. For example, one 133 | might be a [password manager](https://passwordstore.org) in 700 lines of shell 134 | script, and another might be a 135 | [web microframework](https://github.com/camping/camping) implemented as just a 136 | big rectangle of obfuscated Ruby. 137 | 138 | What the tutorials will have in common is the step-by-step build-it-yourself 139 | approach to reading and understanding the code of real open-source software 140 | projects. If there was a toy like Lego that involved putting *programs* 141 | together instead of physical structures, I think "snaptoken" would be a great 142 | name for it. That is the experience I'm trying to create with tutorials like 143 | this. 144 | 145 | ## How to contribute 146 | 147 | Contributions are welcome, whether it's changes to the text, the code, or the 148 | HTML/CSS. 149 | 150 | The text is in the `doc/` directory of the 151 | [kilo-tutorial](https://github.com/snaptoken/kilo-tutorial) repo. Each chapter 152 | is a markdown (`.md`) file. 153 | 154 | The HTML/CSS is in the `doc/html_in/` directory. 155 | 156 | The code is in `steps.diff`, which isn't human-editable. It is generated by a 157 | program called [leg](https://github.com/yjerem/leg). 158 | 159 | If you are making significant changes to the text, you probably want to 160 | generate the final static HTML files, to preview your changes. Here is how to 161 | generate the HTML output using the `leg` program: 162 | 163 | 1. You need to have 164 | [Ruby](https://www.ruby-lang.org/en/documentation/installation/) installed. 165 | 2. Install the `leg` binary by running `gem install snaptoken` (you may need to 166 | `sudo` this). 167 | 3. Inside the `kilo-tutorial` repo, run `leg doc` to generate the static HTML 168 | files in `doc/html_out/` and `doc/html_offline/`. 169 | 4. When running `leg doc`, each step's diff is cached in a hidden dotfile, so 170 | as long as you're only making changes to files in the `doc/` folder, you can 171 | run `leg doc -c` to use the cached diffs and regenerate the HTML output way 172 | faster. 173 | 174 | If you just have a small correction to make in the text, there is no need to go 175 | through all this. Just make the change in the chapter's markdown file and 176 | [submit a pull request](https://github.com/snaptoken/kilo-tutorial/pulls). 177 | 178 | ## Credits 179 | 180 | [antirez](http://invece.org/) is the author of 181 | [kilo](https://github.com/antirez/kilo). He wrote a 182 | [blog post](http://antirez.com/news/108) about it, in which he explains how he 183 | reused code from two of his other projects to quickly throw together `kilo` in 184 | just a few hours during a couple already busy weekends. It's not the sort of 185 | pristine code you usually see in programming tutorials, but I like it this way. 186 | I originally intended this tutorial to be an experimental form of documentation 187 | for his code, until I started making changes to the code all over the place to 188 | make for a better reading experience. 189 | 190 | I used many of the [patches](https://github.com/antirez/kilo/pulls) submitted 191 | to the `kilo` GitHub page to fix various bugs in `kilo`. The 192 | [openemacs](https://github.com/dvwallin/openemacs) project (a fork of 193 | `kilo`) was also helpful as a reference. 194 | 195 | I used [redcarpet](https://github.com/vmg/redcarpet) to render the Markdown 196 | source of this tutorial to HTML, and I used 197 | [rouge](https://github.com/jneen/rouge) for syntax highlighting. 198 | 199 | If you want to know more about me, see 200 | [viewsourcecode.org](http://viewsourcecode.org). 201 | 202 | ## License 203 | 204 | The `kilo` source code is released under the 205 | [BSD 2-Clause](https://github.com/snaptoken/kilo-tutorial/blob/master/steps.diff.LICENSE) 206 | license. 207 | 208 | The rest of the tutorial is licensed under 209 | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). 210 | 211 | -------------------------------------------------------------------------------- /doc/html_in/fonts.css: -------------------------------------------------------------------------------- 1 | /* fira-mono-regular - latin */ 2 | @font-face { 3 | font-family: 'Fira Mono'; 4 | font-style: normal; 5 | font-weight: 400; 6 | src: url('fonts/fira-mono-v5-latin-regular.eot'); /* IE9 Compat Modes */ 7 | src: url('fonts/fira-mono-v5-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 8 | url('fonts/fira-mono-v5-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */ 9 | url('fonts/fira-mono-v5-latin-regular.woff') format('woff'), /* Modern Browsers */ 10 | url('fonts/fira-mono-v5-latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */ 11 | url('fonts/fira-mono-v5-latin-regular.svg#FiraMono') format('svg'); /* Legacy iOS */ 12 | } 13 | 14 | /* fira-mono-500 - latin */ 15 | @font-face { 16 | font-family: 'Fira Mono'; 17 | font-style: normal; 18 | font-weight: 500; 19 | src: url('fonts/fira-mono-v5-latin-500.eot'); /* IE9 Compat Modes */ 20 | src: url('fonts/fira-mono-v5-latin-500.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 21 | url('fonts/fira-mono-v5-latin-500.woff2') format('woff2'), /* Super Modern Browsers */ 22 | url('fonts/fira-mono-v5-latin-500.woff') format('woff'), /* Modern Browsers */ 23 | url('fonts/fira-mono-v5-latin-500.ttf') format('truetype'), /* Safari, Android, iOS */ 24 | url('fonts/fira-mono-v5-latin-500.svg#FiraMono') format('svg'); /* Legacy iOS */ 25 | } 26 | 27 | /* pt-serif-regular - latin */ 28 | @font-face { 29 | font-family: 'PT Serif'; 30 | font-style: normal; 31 | font-weight: 400; 32 | src: url('fonts/pt-serif-v8-latin-regular.eot'); /* IE9 Compat Modes */ 33 | src: url('fonts/pt-serif-v8-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 34 | url('fonts/pt-serif-v8-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */ 35 | url('fonts/pt-serif-v8-latin-regular.woff') format('woff'), /* Modern Browsers */ 36 | url('fonts/pt-serif-v8-latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */ 37 | url('fonts/pt-serif-v8-latin-regular.svg#PTSerif') format('svg'); /* Legacy iOS */ 38 | } 39 | 40 | /* pt-serif-700 - latin */ 41 | @font-face { 42 | font-family: 'PT Serif'; 43 | font-style: normal; 44 | font-weight: 700; 45 | src: url('fonts/pt-serif-v8-latin-700.eot'); /* IE9 Compat Modes */ 46 | src: url('fonts/pt-serif-v8-latin-700.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 47 | url('fonts/pt-serif-v8-latin-700.woff2') format('woff2'), /* Super Modern Browsers */ 48 | url('fonts/pt-serif-v8-latin-700.woff') format('woff'), /* Modern Browsers */ 49 | url('fonts/pt-serif-v8-latin-700.ttf') format('truetype'), /* Safari, Android, iOS */ 50 | url('fonts/pt-serif-v8-latin-700.svg#PTSerif') format('svg'); /* Legacy iOS */ 51 | } 52 | 53 | /* pt-serif-italic - latin */ 54 | @font-face { 55 | font-family: 'PT Serif'; 56 | font-style: italic; 57 | font-weight: 400; 58 | src: url('fonts/pt-serif-v8-latin-italic.eot'); /* IE9 Compat Modes */ 59 | src: url('fonts/pt-serif-v8-latin-italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 60 | url('fonts/pt-serif-v8-latin-italic.woff2') format('woff2'), /* Super Modern Browsers */ 61 | url('fonts/pt-serif-v8-latin-italic.woff') format('woff'), /* Modern Browsers */ 62 | url('fonts/pt-serif-v8-latin-italic.ttf') format('truetype'), /* Safari, Android, iOS */ 63 | url('fonts/pt-serif-v8-latin-italic.svg#PTSerif') format('svg'); /* Legacy iOS */ 64 | } 65 | 66 | /* work-sans-700 - latin */ 67 | @font-face { 68 | font-family: 'Work Sans'; 69 | font-style: normal; 70 | font-weight: 700; 71 | src: url('fonts/work-sans-v2-latin-700.eot'); /* IE9 Compat Modes */ 72 | src: url('fonts/work-sans-v2-latin-700.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 73 | url('fonts/work-sans-v2-latin-700.woff2') format('woff2'), /* Super Modern Browsers */ 74 | url('fonts/work-sans-v2-latin-700.woff') format('woff'), /* Modern Browsers */ 75 | url('fonts/work-sans-v2-latin-700.ttf') format('truetype'), /* Safari, Android, iOS */ 76 | url('fonts/work-sans-v2-latin-700.svg#WorkSans') format('svg'); /* Legacy iOS */ 77 | } 78 | 79 | -------------------------------------------------------------------------------- /doc/html_in/fonts/fira-mono-v5-latin-500.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaptoken/kilo-tutorial/06eb5af3c811234fb2bf563c2d37de9d9254a9a9/doc/html_in/fonts/fira-mono-v5-latin-500.eot -------------------------------------------------------------------------------- /doc/html_in/fonts/fira-mono-v5-latin-500.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 18 | 21 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | 35 | 36 | 38 | 39 | 41 | 43 | 44 | 47 | 49 | 51 | 53 | 54 | 55 | 56 | 58 | 60 | 61 | 63 | 65 | 66 | 67 | 68 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 79 | 80 | 82 | 83 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 100 | 102 | 103 | 104 | 106 | 107 | 110 | 111 | 112 | 113 | 114 | 115 | 117 | 118 | 120 | 122 | 124 | 125 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 136 | 137 | 139 | 141 | 142 | 143 | 144 | 146 | 148 | 149 | 150 | 153 | 155 | 158 | 160 | 161 | 162 | 163 | 166 | 167 | 169 | 170 | 171 | 173 | 174 | 176 | 177 | 178 | 179 | 180 | 182 | 183 | 184 | 186 | 188 | 190 | 191 | 192 | 193 | 195 | 197 | 199 | 200 | 202 | 203 | 204 | 205 | 207 | 208 | 209 | 210 | 212 | 213 | 215 | 217 | 219 | 221 | 224 | 227 | 228 | 230 | 231 | 232 | 233 | 235 | 236 | 237 | 239 | 241 | 243 | 245 | 248 | 251 | 254 | 257 | 259 | 261 | 263 | 265 | 268 | 269 | 270 | 271 | 273 | 275 | 277 | 279 | 281 | 283 | 286 | 289 | 291 | 293 | 294 | 295 | 296 | 298 | 299 | 301 | 303 | 304 | 305 | 306 | 307 | 308 | 310 | 312 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | -------------------------------------------------------------------------------- /doc/html_in/fonts/fira-mono-v5-latin-500.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaptoken/kilo-tutorial/06eb5af3c811234fb2bf563c2d37de9d9254a9a9/doc/html_in/fonts/fira-mono-v5-latin-500.ttf -------------------------------------------------------------------------------- /doc/html_in/fonts/fira-mono-v5-latin-500.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaptoken/kilo-tutorial/06eb5af3c811234fb2bf563c2d37de9d9254a9a9/doc/html_in/fonts/fira-mono-v5-latin-500.woff -------------------------------------------------------------------------------- /doc/html_in/fonts/fira-mono-v5-latin-500.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaptoken/kilo-tutorial/06eb5af3c811234fb2bf563c2d37de9d9254a9a9/doc/html_in/fonts/fira-mono-v5-latin-500.woff2 -------------------------------------------------------------------------------- /doc/html_in/fonts/fira-mono-v5-latin-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaptoken/kilo-tutorial/06eb5af3c811234fb2bf563c2d37de9d9254a9a9/doc/html_in/fonts/fira-mono-v5-latin-regular.eot -------------------------------------------------------------------------------- /doc/html_in/fonts/fira-mono-v5-latin-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaptoken/kilo-tutorial/06eb5af3c811234fb2bf563c2d37de9d9254a9a9/doc/html_in/fonts/fira-mono-v5-latin-regular.ttf -------------------------------------------------------------------------------- /doc/html_in/fonts/fira-mono-v5-latin-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaptoken/kilo-tutorial/06eb5af3c811234fb2bf563c2d37de9d9254a9a9/doc/html_in/fonts/fira-mono-v5-latin-regular.woff -------------------------------------------------------------------------------- /doc/html_in/fonts/fira-mono-v5-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaptoken/kilo-tutorial/06eb5af3c811234fb2bf563c2d37de9d9254a9a9/doc/html_in/fonts/fira-mono-v5-latin-regular.woff2 -------------------------------------------------------------------------------- /doc/html_in/fonts/pt-serif-v8-latin-700.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaptoken/kilo-tutorial/06eb5af3c811234fb2bf563c2d37de9d9254a9a9/doc/html_in/fonts/pt-serif-v8-latin-700.eot -------------------------------------------------------------------------------- /doc/html_in/fonts/pt-serif-v8-latin-700.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaptoken/kilo-tutorial/06eb5af3c811234fb2bf563c2d37de9d9254a9a9/doc/html_in/fonts/pt-serif-v8-latin-700.ttf -------------------------------------------------------------------------------- /doc/html_in/fonts/pt-serif-v8-latin-700.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaptoken/kilo-tutorial/06eb5af3c811234fb2bf563c2d37de9d9254a9a9/doc/html_in/fonts/pt-serif-v8-latin-700.woff -------------------------------------------------------------------------------- /doc/html_in/fonts/pt-serif-v8-latin-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaptoken/kilo-tutorial/06eb5af3c811234fb2bf563c2d37de9d9254a9a9/doc/html_in/fonts/pt-serif-v8-latin-700.woff2 -------------------------------------------------------------------------------- /doc/html_in/fonts/pt-serif-v8-latin-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaptoken/kilo-tutorial/06eb5af3c811234fb2bf563c2d37de9d9254a9a9/doc/html_in/fonts/pt-serif-v8-latin-italic.eot -------------------------------------------------------------------------------- /doc/html_in/fonts/pt-serif-v8-latin-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaptoken/kilo-tutorial/06eb5af3c811234fb2bf563c2d37de9d9254a9a9/doc/html_in/fonts/pt-serif-v8-latin-italic.ttf -------------------------------------------------------------------------------- /doc/html_in/fonts/pt-serif-v8-latin-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaptoken/kilo-tutorial/06eb5af3c811234fb2bf563c2d37de9d9254a9a9/doc/html_in/fonts/pt-serif-v8-latin-italic.woff -------------------------------------------------------------------------------- /doc/html_in/fonts/pt-serif-v8-latin-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaptoken/kilo-tutorial/06eb5af3c811234fb2bf563c2d37de9d9254a9a9/doc/html_in/fonts/pt-serif-v8-latin-italic.woff2 -------------------------------------------------------------------------------- /doc/html_in/fonts/pt-serif-v8-latin-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaptoken/kilo-tutorial/06eb5af3c811234fb2bf563c2d37de9d9254a9a9/doc/html_in/fonts/pt-serif-v8-latin-regular.eot -------------------------------------------------------------------------------- /doc/html_in/fonts/pt-serif-v8-latin-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaptoken/kilo-tutorial/06eb5af3c811234fb2bf563c2d37de9d9254a9a9/doc/html_in/fonts/pt-serif-v8-latin-regular.ttf -------------------------------------------------------------------------------- /doc/html_in/fonts/pt-serif-v8-latin-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaptoken/kilo-tutorial/06eb5af3c811234fb2bf563c2d37de9d9254a9a9/doc/html_in/fonts/pt-serif-v8-latin-regular.woff -------------------------------------------------------------------------------- /doc/html_in/fonts/pt-serif-v8-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaptoken/kilo-tutorial/06eb5af3c811234fb2bf563c2d37de9d9254a9a9/doc/html_in/fonts/pt-serif-v8-latin-regular.woff2 -------------------------------------------------------------------------------- /doc/html_in/fonts/work-sans-v2-latin-700.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaptoken/kilo-tutorial/06eb5af3c811234fb2bf563c2d37de9d9254a9a9/doc/html_in/fonts/work-sans-v2-latin-700.eot -------------------------------------------------------------------------------- /doc/html_in/fonts/work-sans-v2-latin-700.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaptoken/kilo-tutorial/06eb5af3c811234fb2bf563c2d37de9d9254a9a9/doc/html_in/fonts/work-sans-v2-latin-700.ttf -------------------------------------------------------------------------------- /doc/html_in/fonts/work-sans-v2-latin-700.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaptoken/kilo-tutorial/06eb5af3c811234fb2bf563c2d37de9d9254a9a9/doc/html_in/fonts/work-sans-v2-latin-700.woff -------------------------------------------------------------------------------- /doc/html_in/fonts/work-sans-v2-latin-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaptoken/kilo-tutorial/06eb5af3c811234fb2bf563c2d37de9d9254a9a9/doc/html_in/fonts/work-sans-v2-latin-700.woff2 -------------------------------------------------------------------------------- /doc/html_in/i/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaptoken/kilo-tutorial/06eb5af3c811234fb2bf563c2d37de9d9254a9a9/doc/html_in/i/arrow.png -------------------------------------------------------------------------------- /doc/html_in/i/lego-step-one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaptoken/kilo-tutorial/06eb5af3c811234fb2bf563c2d37de9d9254a9a9/doc/html_in/i/lego-step-one.png -------------------------------------------------------------------------------- /doc/html_in/i/x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaptoken/kilo-tutorial/06eb5af3c811234fb2bf563c2d37de9d9254a9a9/doc/html_in/i/x.png -------------------------------------------------------------------------------- /doc/html_in/style.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=PT+Serif:400,400i,700|Work+Sans:700|Fira+Mono:400,500'); 2 | 3 | * { 4 | margin: 0; 5 | padding: 0; 6 | box-sizing: border-box; 7 | } 8 | 9 | body { 10 | font-family: 'PT Serif', Utopia, Georgia, Times, 'Apple Symbols', serif; 11 | line-height: 140%; 12 | color: #333; 13 | font-size: 18px; 14 | } 15 | 16 | #container { 17 | width: 700px; 18 | margin: 18px auto; 19 | } 20 | 21 | .bar { 22 | display: block; 23 | width: 100%; 24 | background-color: #ceb; 25 | box-shadow: 0px 0px 15px 1px #ddd; 26 | } 27 | 28 | .bar > nav { 29 | display: flex; 30 | justify-content: space-between; 31 | width: 700px; 32 | margin: 0 auto; 33 | } 34 | 35 | footer.bar > nav { 36 | justify-content: center; 37 | } 38 | 39 | .bar > nav > a { 40 | display: block; 41 | padding: 2px 0 4px 0; 42 | color: #152; 43 | } 44 | 45 | #version { 46 | text-align: right; 47 | font-size: 12px; 48 | font-family: 'Fira Mono', monospace; 49 | padding-right: 5px; 50 | } 51 | 52 | #version a { 53 | color: #333; 54 | } 55 | 56 | h1, h2, h3, h4, h5, h6 { 57 | font-family: 'Work Sans', Futura, Helvetica, Arial, sans-serif; 58 | color: #222; 59 | line-height: 100%; 60 | margin-top: 32px; 61 | } 62 | 63 | h2 a, h3 a, h4 a { 64 | color: inherit; 65 | text-decoration: none; 66 | } 67 | 68 | h2 a::before, h3 a::before, h4 a::before { 69 | content: '#'; 70 | color: #fff; 71 | font-weight: normal; 72 | transition: color 0.15s ease; 73 | display: block; 74 | float: left; 75 | width: 32px; 76 | margin-left: -32px; 77 | } 78 | 79 | h2 a:hover::before, h3 a:hover::before, h4 a:hover::before { 80 | color: #ccc; 81 | } 82 | 83 | h1 { 84 | margin-top: 0; 85 | font-size: 38px; 86 | border-bottom: 3px solid #e7c; 87 | display: inline-block; 88 | } 89 | 90 | h2 { 91 | font-size: 26px; 92 | } 93 | 94 | p { 95 | margin-top: 18px; 96 | } 97 | 98 | ul, ol { 99 | margin-top: 18px; 100 | margin-left: 36px; 101 | } 102 | 103 | hr { 104 | border: none; 105 | border-bottom: 1px solid #888; 106 | } 107 | 108 | a { 109 | color: #26d; 110 | } 111 | 112 | code { 113 | font-family: 'Fira Mono', monospace; 114 | font-size: inherit; 115 | white-space: nowrap; 116 | background-color: #eff4ea; 117 | padding: 1px 3px; 118 | } 119 | 120 | h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { 121 | font-weight: normal; 122 | } 123 | 124 | kbd { 125 | font-family: 'Fira Mono', monospace; 126 | border-radius: 3px; 127 | padding: 2px 3px; 128 | box-shadow: 1px 1px 1px #777; 129 | margin: 2px; 130 | font-size: 14px; 131 | background: #f7f7f7; 132 | font-weight: 500; 133 | color: #555; 134 | white-space: nowrap; 135 | } 136 | 137 | h1 kbd, h2 kbd, h3 kbd, h4 kbd, h5 kbd, h6 kbd { 138 | font-size: 80%; 139 | } 140 | 141 | .diff code { 142 | font-size: 14px; 143 | line-height: 20px; 144 | background-color: none; 145 | padding: 0; 146 | margin-bottom: 18px; 147 | white-space: inherit; 148 | } 149 | 150 | .diff pre { 151 | background-color: #fffcfa; 152 | padding: 5px 0; 153 | } 154 | 155 | .diff { 156 | border: 1px solid #ede7e3; 157 | border-radius: 3px; 158 | margin-top: 18px; 159 | } 160 | 161 | .diff .diff-header { 162 | display: flex; 163 | justify-content: space-between; 164 | padding: 0 5px; 165 | background-color: #ede7e3; 166 | font-size: 16px; 167 | color: #666; 168 | } 169 | 170 | .diff .step-number { 171 | font-weight: bold; 172 | } 173 | 174 | .diff .step-filename { 175 | font-weight: bold; 176 | } 177 | 178 | .diff .step-name { 179 | font-family: 'Fira Mono', monospace; 180 | font-size: 12px; 181 | } 182 | 183 | .diff .diff-header a { 184 | text-decoration: none; 185 | color: #666; 186 | } 187 | 188 | .diff .diff-header a:hover { 189 | text-decoration: underline; 190 | } 191 | 192 | .diff .step-filename a { 193 | text-decoration: underline; 194 | } 195 | 196 | .diff .diff-footer { 197 | background-color: #ede7e3; 198 | } 199 | 200 | .diff .diff-footer > div { 201 | font-size: 12px; 202 | line-height: 16px; 203 | height: 16px; 204 | padding-right: 5px; 205 | text-align: right; 206 | } 207 | 208 | .diff .diff-tag-c0 { 209 | color: #b33; 210 | } 211 | 212 | .diff .diff-tag-c1 { 213 | color: #33b; 214 | } 215 | 216 | .diff .diff-tag-c2 { 217 | color: #3b3; 218 | } 219 | 220 | .diff .diff-tag-c-unknown { 221 | color: #a62; 222 | } 223 | 224 | .diff .line { 225 | display: block; 226 | height: 20px; 227 | padding: 0 5px; 228 | position: relative; 229 | } 230 | 231 | .diff .line.folded { 232 | background-color: #eef; 233 | opacity: 0.5; 234 | } 235 | 236 | .diff ins.line { 237 | background-color: #ffd; 238 | text-decoration: none; 239 | } 240 | 241 | .diff ins.line::after { 242 | display: block; 243 | content: ''; 244 | width: 20px; 245 | height: 20px; 246 | background-image: url('i/arrow.png'); 247 | background-size: 20px 20px; 248 | position: absolute; 249 | right: -24px; 250 | top: 0; 251 | } 252 | 253 | .diff del.line { 254 | background-color: #fdd; 255 | text-decoration: line-through; 256 | } 257 | 258 | .diff del.line::after { 259 | display: block; 260 | content: ''; 261 | width: 20px; 262 | height: 20px; 263 | background-image: url('i/x.png'); 264 | background-size: 20px 20px; 265 | position: absolute; 266 | right: -24px; 267 | top: 0; 268 | } 269 | 270 | @media screen and (max-width: 700px) { 271 | #container { 272 | width: auto; 273 | margin: 18px 0; 274 | padding: 0 5px; 275 | } 276 | 277 | .bar > nav { 278 | width: auto; 279 | margin: 0; 280 | padding: 0 5px; 281 | } 282 | 283 | .highlight { 284 | overflow-x: scroll; 285 | } 286 | 287 | .diff .line { 288 | width: 700px; 289 | } 290 | 291 | .diff ins.line::after, .diff del.line::after { 292 | display: none; 293 | } 294 | } 295 | 296 | -------------------------------------------------------------------------------- /doc/html_in/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{title}} 7 | 8 | 9 | 10 |
11 | 16 |
17 |
18 | {{content}} 19 |
20 |
21 | {{version}} 22 | (changelog) 23 |
24 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /doc/html_in/template_index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{title}} 7 | 8 | 9 | 10 |
11 | 16 |
17 |
18 | {{content}} 19 |
20 |
21 | {{version}} 22 | (changelog) 23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /leg.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :name: kilo 3 | :version: "1.0.0beta11" 4 | :title: Build Your Own Text Editor 5 | :sync: repo 6 | :rouge_theme: github 7 | :bold_weight: 500 8 | :repo_author: 9 | :name: snaptoken 10 | :email: snaptoken@viewsourcecode.org 11 | :tags: 12 | :c0: "♏︎ doesn’t compile" 13 | :c1: "♎︎ compiles, but with no observable effects" 14 | :c2: "♐︎ compiles" 15 | :c-unknown: "♋︎ may or may not compile" 16 | ... 17 | -------------------------------------------------------------------------------- /repo-extra/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Salvatore Sanfilippo 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /repo-extra/README.md: -------------------------------------------------------------------------------- 1 | This repo contains the entire source code for each step of 2 | [kilo-tutorial](https://github.com/snaptoken/kilo-tutorial). Each step is 3 | represented by a commit. Each step name (found in the upper-right corner of 4 | each step diff in the [tutorial](http://viewsourcecode.org/snaptoken/kilo)) is 5 | a ref to that step's commit. 6 | 7 | If you want to compare your version of `kilo.c` with the version in this repo 8 | for a particular step, say `keypresses`, you could do it like this: 9 | 10 | $ git clone https://github.com/snaptoken/kilo-src 11 | $ cd kilo-src 12 | $ git checkout keypresses 13 | $ git diff --no-index -b ../path/to/your/kilo.c kilo.c 14 | 15 | `--no-index` lets you use `git diff` as an ordinary diff tool, and `-b` ignores 16 | differences in whitespace, which is important if you use a different indent 17 | style than the one in the tutorial. 18 | 19 | -------------------------------------------------------------------------------- /steps.diff.LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Salvatore Sanfilippo 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | --------------------------------------------------------------------------------