├── .github ├── FUNDING.yml ├── run-tests.sh └── workflows │ ├── pull_request.yml │ └── push.yml ├── .gitignore ├── LICENSE ├── Makefile ├── PRIMITIVES.md ├── README.md ├── kilua.lua ├── kilua.png ├── src ├── Makefile ├── buffer.cc ├── buffer.h ├── editor.cc ├── editor.h ├── intro.h ├── lua_buffers.cc ├── lua_core.cc ├── lua_files.cc ├── lua_movement.cc ├── lua_primitives.h ├── lua_screen.cc ├── lua_syntax.cc ├── main.cc ├── singleton.h └── util.h ├── syntax ├── README.md ├── cc.lua ├── email.lua ├── go.lua ├── html.lua ├── ini.lua ├── lisp.lua ├── lpeg_utils.lua ├── lua.lua ├── makefile.lua └── markdown.lua └── util └── xxd /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | github: skx 3 | custom: https://steve.fi/donate/ 4 | -------------------------------------------------------------------------------- /.github/run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Update 4 | apt-get update -qq 5 | 6 | # Install dependencies 7 | apt-get install -y make liblua5.2-dev libncursesw5-dev 8 | 9 | # Compile 10 | make 11 | -------------------------------------------------------------------------------- /.github/workflows/pull_request.yml: -------------------------------------------------------------------------------- 1 | on: pull_request 2 | name: Pull Request 3 | jobs: 4 | test: 5 | name: Test 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@master 9 | - name: Test 10 | uses: skx/github-action-tester@master 11 | -------------------------------------------------------------------------------- /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | name: Push Event 6 | jobs: 7 | test: 8 | name: Test 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@master 12 | - name: Test 13 | uses: skx/github-action-tester@master 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | kilua 2 | core 3 | src/config.h 4 | src/*.o 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Salvatore Sanfilippo 2 | 3 | Copyright (C) 2016 Steve Kemp https://steve.kemp.fi/ 4 | 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, 11 | this list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 21 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 24 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile for kilua. 3 | # 4 | # 5 | 6 | 7 | # 8 | # Default target 9 | # 10 | all: kilua 11 | 12 | # 13 | # Generate our embedded configuration-file. 14 | # 15 | src/config.h: util/xxd kilua.lua 16 | perl util/xxd kilua.lua > src/config.h 17 | 18 | 19 | # 20 | # Build the main binary. 21 | # 22 | kilua: src/config.h $(wildcard src/*.cc src/*.h) 23 | cd src && make 24 | 25 | 26 | # 27 | # Reformat our code 28 | # 29 | .PHONY: indent 30 | indent: 31 | cd src && make indent 32 | 33 | 34 | # 35 | # Cleanup 36 | # 37 | .PHONY: indent 38 | clean: 39 | cd src && make clean 40 | rm -f kilua src/config.h 41 | -------------------------------------------------------------------------------- /PRIMITIVES.md: -------------------------------------------------------------------------------- 1 | # Lua Primitives 2 | 3 | This document briefly describes each of the available Lua primitives 4 | which are implemented in the editor. 5 | 6 | Most of these primitives are demonstrated in the default [kilua.lua](https://github.com/skx/kilua/blob/master/kilua.lua) file. 7 | 8 | 9 | 10 | ## Buffer Primitives 11 | 12 | * `buffer` 13 | * Get or set the current buffer. 14 | * `buffer(1)` will select buffer 1. 15 | * `buffer("name")` will select the buffer with the given name, returning `-1` if it doesn't exist. 16 | * `buffer()` returns the index of the currently selected buffer. 17 | * `buffer_data()` 18 | * Get/Set buffer-specific data. 19 | * This is used for bookmarks, and per-buffer search-strings. 20 | * `buffers()` 21 | * Return a table of all known buffers. 22 | * The table will contain the integer-offset, along with the name. 23 | * `buffer_name()` 24 | * Get/Set the name of the current buffer. 25 | * `create_buffer()` 26 | * Create a buffer with the given name. 27 | * `kill_buffer()` 28 | * Delete the currently selected buffer. 29 | 30 | 31 | 32 | ## Core Primitives 33 | 34 | * `delete()` 35 | * Delete a single character, to the left of the point. 36 | * See `delete_forwards()` in the default configuration file for the reverse. 37 | * `dirty()` 38 | * Is the current buffer modified & unsaved? 39 | * `exit()` 40 | * Exit the editor, immediately. 41 | * `insert(string)` 42 | * Insert the given string into the current buffer. 43 | * `key()` 44 | * Read a single (wide) key from the user. 45 | * `mark()` 46 | * Get/Set the position of the mark. 47 | * The region between the mark and the cursor/point is the selection-region. 48 | * Set to -1,-1 to disable. 49 | * `menu()` 50 | * Given a table of strings allow the user to choose one of them, returning the index of the selected choice. 51 | * `point()` 52 | * Get/Set the position of the cursor/point. 53 | * `prompt( message )` 54 | * Prompt the user for a line of input, showing the specified message. 55 | * `open([filename])` 56 | * Open a file, and insert the text into the current buffer. 57 | * `save([filename])` 58 | * Save the current buffer. 59 | * If there is a filename given this will be used. 60 | * `search(regexp)` 61 | * Search forward for the given regular expression. 62 | * `selection()` 63 | * Return the text between the point and mark. 64 | * `status(msg)` 65 | * Set the contents of the status-bar. 66 | * `text()` 67 | * Retrieve the (ASCII) text in the buffer. 68 | 69 | 70 | ## File Primitives 71 | 72 | We only need two primitives so far for dealing with the filesystem: 73 | 74 | * `directory_entries(path)` 75 | * Return a table containing the names of all files beneath the given directory. 76 | * For example `directory_entries("/etc/")` 77 | * `exists(path)` 78 | * Return `true` if the given path exists, `false` otherwise. 79 | 80 | 81 | ## Movement Primitives 82 | 83 | * `move(direction)` 84 | * This function moves the cursor, if possible. 85 | * For example `move('left')`, `move("right")`, etc. 86 | * `eof()` 87 | * Move to the end of the current file/buffer. 88 | * `eol()` 89 | * Move to the end of the current line. 90 | * `sof()` 91 | * Move to the start of the current file/buffer. 92 | * `sol()` 93 | * Move to the start of the current line. 94 | 95 | See `page_up` and `page_down` in the default configuration file for examples 96 | of movement facilities built upon this one. 97 | 98 | 99 | ## Screen Primitives 100 | 101 | * `at(x,y)` 102 | * Return the (wide) character at the given position, if it exists. 103 | * `height()` 104 | * Return the height of the editor-area. This is the same as the screen height, minus two lines to account for the status-area. 105 | * `width()` 106 | * Return the width of the editor-area. 107 | 108 | 109 | 110 | ## Syntax Highlighting Primitives 111 | 112 | * `syntax()` 113 | * Get/Set the syntax-mode. 114 | * `update_colours()` 115 | * Update the syntax-highlighting results of the current buffer. 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![license](https://img.shields.io/github/license/skx/kilua.svg)]() 2 | 3 | 4 | # Kilua 5 | 6 | Kilua is an small, extensible, and Lua-powered text editor. 7 | 8 | [![screenshot](kilua.png)]() 9 | 10 | The project was originally based upon the minimal [kilo editor](https://github.com/antirez/kilo) originally written by @antirez, and [introduced here on his blog](http://antirez.com/news/108), but now shares no code with that project, just ancestry. 11 | 12 | kilua was written by [Steve Kemp](https://steve.kemp.fi/) and features many updates and additions compared to the original project: 13 | 14 | * Complete handling for UTF-8 and multi-byte text. 15 | * The ability to open/edit/view multiple files 16 | * This is done [via buffers](#buffers). 17 | * The addition of an embedded Lua instance. 18 | * You can define functions in your [init-files](#lua-support), and invoke them via `M-x function()`. 19 | * Regular expression support for searching. 20 | * The addition of [syntax-highlighting](#syntax-highlighting) via the `lua-lpeg` library. 21 | * **NOTE**: You should see the [installation](#installation) section for caveats here. 22 | * Syntax-highlighting is updated in the background, when the editor is idle, to avoid stalls and redraw delays. 23 | * Syntax-highlighting supports up to 256 colours, if your terminal supports them too. 24 | * The notion of [named marks](#bookmarks). 25 | * The [status bar](#status-bar) is configured via Lua. 26 | * Several bugfixes. 27 | 28 | Launching `kilua` works as you would expect: 29 | 30 | $ kilua [options] [file1] [file2] ... [fileN] 31 | 32 | Once launched the arrow keys will move you around, and the main keybindings 33 | to learn are: 34 | 35 | Ctrl-x Ctrl-o Open an existing file. 36 | Ctrl-x Ctrl-f Open an existing file. 37 | 38 | Ctrl-x Ctrl-s Save the current file. 39 | 40 | Ctrl-x Ctrl-c Quit. 41 | 42 | Ctrl-x c Create a new buffer 43 | Ctrl-x n Move to the next buffer. 44 | Ctrl-x p Move to the previous buffer. 45 | Ctrl-x b Select buffer from a list 46 | 47 | M-x Evaluate lua at the prompt. 48 | 49 | Ctrl-s Regular expression search. 50 | 51 | 52 | 53 | ## Command Line Options 54 | 55 | The following command-line options are recognized and understood: 56 | 57 | * `--config file` 58 | * Load the named (lua) configuration file, in addition to the defaults. 59 | * `--dump-config` 60 | * Display the (embedded) default configuration file. 61 | * `--eval` 62 | * Evaluate the given lua, post-load. 63 | * `--syntax-path` 64 | * Specify the location of syntax-highlighting functions. 65 | * `--version` 66 | * Report the version and exit. 67 | 68 | 69 | 70 | ## Installation 71 | 72 | Installation should be straight-forward, to build the code run: 73 | 74 | make 75 | 76 | Once built you can run the binary in a portable fashion, like so: 77 | 78 | ./kilua --syntax-path ./syntax [options] [file1] [file2] .. [fileN] 79 | 80 | The usage of `--syntax-path` is required to load the syntax files, but 81 | you can remove the option if you copy the contents of the `./syntax/` 82 | directory to either: 83 | 84 | * `/etc/kilua/syntax/` 85 | * `~/.kilua/syntax/` 86 | 87 | If you don't specify the location of the syntax-highlighting libraries, 88 | or you don't install them then you'll have zero syntax-highlighting support. 89 | 90 | This is a consequence of placing the syntax-highlighting code in external 91 | libraries: If you can't load those libraries then the functionality will 92 | not be available. 93 | 94 | 95 | 96 | ## Lua Support 97 | 98 | We build with Lua 5.2 by default, but if you edit `src/Makefile` you 99 | should also be able to build successfully with Lua 5.1. 100 | 101 | On startup the following configuration-files are read if present: 102 | 103 | * `~/.kilua/init.lua`. 104 | * `./.kilua/$hostname.lua`. 105 | * This is useful for those who store their dotfiles under revision control and share them across hosts. 106 | * You can use the `*Messages*` buffer to see which was found, if any. 107 | 108 | If neither file is read then the embedded copy of `kilua.lua`, which 109 | was generated at build-time, will be executed, which ensures that the 110 | minimum functionality is present. (i.e. If you load zero config 111 | files then there won't be any keybindings setup so you can neither 112 | navigate nor edit!) 113 | 114 | It is assumed you'll edit the [supplied startup](kilua.lua) file, to 115 | change the bindings to suit your needs, add functionality via 116 | the [supplied lua primitives](PRIMITIVES.md), and then copy into 117 | `~/.kilua/init.lua` (perhaps extending that with a per-host file too). 118 | 119 | Without any changes you'll get a functional editor which follows my 120 | particular preferences. 121 | 122 | > **Pull-requests** implementing useful functionality will be received with thanks, even if just to add syntax-highlighting for additional languages. 123 | 124 | 125 | 126 | ## Callbacks 127 | 128 | In the future more callbacks might be implemented, which are functions the 129 | C-core calls at various points. 130 | 131 | Right now the following callbacks exist and are invoked via the C-core: 132 | 133 | * `get_status_bar()` 134 | * This function is called to populate the status-bar in the footer. 135 | * `on_complete(str)` 136 | * This function is invoked to implement TAB-completion at the prompt. 137 | * `on_idle()` 138 | * Called roughly once a second, can be used to run background things. 139 | * If this function isn't defined it will not be invoked. 140 | * This is used to update syntax in the background. 141 | * `on_key(key)` 142 | * Called to process a single key input. 143 | * If this function isn't defined then input will not work, it is required. 144 | * `on_loaded(filename)` 145 | * Called when a file is loaded. 146 | * This sets up syntax highlighting in our default implementation for C and Lua files. 147 | * If this function is not defined then it will not be invoked. 148 | * `on_save(filename)` 149 | * Called __before__ a file is saved. 150 | * Can be used to strip trailing whitespace, etc. 151 | * If this function is not defined then it will not be invoked. 152 | * `on_saved(filename)` 153 | * Called __after__ a file is saved. 154 | * Can be used to make files executable, etc. 155 | * If this function is not defined then it will not be invoked. 156 | 157 | 158 | 159 | ## Buffers 160 | 161 | `kilua` allows multiple files to be opened, via the use of buffers. If `kilua` is launched without any filename parameters there will be two buffers: 162 | 163 | * `*Messages*` 164 | * This receives copies of the status-message. 165 | * An unnamed buffer for working with. 166 | * Enter your text here, then use `Ctrl-x Ctrl-s`, or `M-x save("name")`, to save it. 167 | 168 | Otherwise there will be one buffer for each file named upon the command-line, 169 | as well as the `*Messages*` buffer. (You can kill the `*Messages*` buffer 170 | if you wish, but it's a handy thing to have around.) 171 | 172 | The default key-bindings for working with buffers are: 173 | 174 | Action | Binding 175 | ---------------------------------- | -------------- 176 | Create a new buffer. | `Ctrl-x c` 177 | Kill the current buffer. | `Ctrl-x k` 178 | Kill the current buffer, forcibly. | `Ctrl-x K` 179 | Select the next buffer. | `Ctrl-x n` or `M-right` 180 | Select the previous buffer. | `Ctrl-x p` or `M-left` 181 | Choose a buffer, via menu. | `Ctrl-x b` or `Ctrl-x B` 182 | 183 | It's worth noting that you can easily create buffers dynamically, via lua, for 184 | example the following function can be called by `M-x uptime()`, and does 185 | what you expect: 186 | 187 | * Select the buffer with the name `*uptime*`. 188 | * If that buffer doesn't exist then create it. 189 | * Move to the end of the buffer. 190 | * Insert the output of running `/usr/bin/uptime` into the buffer. 191 | 192 | Uptime sample: 193 | 194 | -- Run `uptime`, and show the result in a dedicated buffer. 195 | function uptime() 196 | local result = buffer( "*uptime*" ) 197 | if ( result == -1 ) then create_buffer("*uptime*") end 198 | -- move to end of file. 199 | eof() 200 | insert(cmd_output("uptime")) 201 | end 202 | 203 | 204 | 205 | ## Bookmarks 206 | 207 | You can record your position (i.e. "mark") in a named key, and 208 | later jump to it, just like in `vi`. 209 | 210 | To record the current position use `M-m`, and press the key 211 | you wish to use. To return to it use `M-b XX` where XX was the 212 | key you chose. (Marks record the buffer, as well as the current cursor-position.) 213 | 214 | 215 | ## Status Bar 216 | 217 | The status-bar, shown as the penultimate line in the display, contains 218 | the name of the current file/buffer, as well as the cursor position, etc. 219 | 220 | The contents of the status-bar are generated via Lua, so it is simple 221 | to modify. The default display shows: 222 | 223 | "${buffer}/${buffers} - ${file} ${mode} ${modified} #BLANK# Col:${x} Row:${y} [${point}] ${time}" 224 | 225 | 226 | Values inside "`${...}`" are expanded via substitutions and the following 227 | are provided by default: 228 | 229 | Name | Meaning 230 | ---------------- | -------------- 231 | `${buffers}` | The count of open buffers. 232 | `${buffer}` | The number of the current buffer. 233 | `${date}` | The current date. 234 | `${file}` | The name of the file/buffer. 235 | `${mode}` | The syntax-highlighting mode in use, if any. 236 | `${modified}` | A string that reports whether the buffer is modified. 237 | `${point}` | The character under the point. 238 | `${time}` | The current time. 239 | `${words}` | The count of words in the buffer. 240 | `${x}` | The X-coordinate of the cursor. 241 | `${y}` | The Y-coordinate of the cursor. 242 | 243 | > **Pull-requests** adding more options here would be most welcome. 244 | 245 | 246 | ## Syntax Highlighting 247 | 248 | Syntax highlighting is handled via the `lua-lpeg` library, and so if 249 | that is not installed it will not be available. 250 | 251 | Each buffer has an associated syntax-highlighting mode, which is a string 252 | such as "c", "markdown", or "lua". The default configuration file sets 253 | the mode based upon the suffix of the file you're editing. 254 | 255 | If you wish to change the mode interactively to Lua, for example, then run: 256 | 257 | M-x syntax("lua") 258 | 259 | The implementation of syntax highlighting requires the loading of 260 | a library. For example the syntax highlighting of lua requires 261 | that the library `lua.lua` is loaded - The syntax modes are looked 262 | for in these locations: 263 | 264 | * `/etc/kilua/syntax` 265 | * Global syntax-modes. 266 | * `~/.kilua/syntax` 267 | * Per-user syntax-modes. 268 | * The path specified via the `--syntax-path` command-line option. 269 | 270 | The implementation is pretty simple: 271 | 272 | * A buffer consists of rows of text. 273 | * Each row contains both the character(s) in the row and the colour of each character. 274 | * The Lua function `update_colours` will allow the colour of each single character in the buffer to be set. 275 | 276 | To avoid delays when inserting text the rendering is updated in the background, 277 | via the `on_idle()` callback. This function does the obvious thing: 278 | 279 | * Retrieves the current contents of the buffer, via `text()`. 280 | * Invokes the LPEG parser on it. 281 | * This will generate a long string containing the colour of each byte of the text. 282 | * Set those colours, via `update_colours()`. 283 | 284 | As a concrete example, if the buffer contains the string "Steve Kemp" then 285 | the call to `update_colours` should contain: 286 | 287 | `RED RED RED RED RED WHITE GREEN GREEN GREEN GREEN` 288 | 289 | That would result in "Steve" being displayed in red, and "Kemp" in green. 290 | 291 | Currently we include syntax-highlighting for: 292 | 293 | * C 294 | * C++ 295 | * Go 296 | * HTML 297 | * Lua 298 | * Lisp 299 | * `Makefile`s. 300 | * Plain-text/markdown 301 | * This is a simple implementation which only highlights URLs and trailing whitespace. 302 | 303 | > **Pull-requests** adding more syntax modes would be most welcome. 304 | 305 | 306 | 307 | ## Discussion on Hacker News 308 | 309 | https://news.ycombinator.com/item?id=12137698 310 | 311 | 312 | 313 | ## The Future 314 | 315 | There are no obvious future plans, but [bug reports](https://github.com/skx/kilua/issues) may be made if you have a feature to suggest (or bug to report)! 316 | 317 | One thing that might be useful is a split-display, to view two files 318 | side by side, or one above the other. This is not yet planned, but 319 | I think it could be done reasonably cleanly. 320 | 321 | Steve 322 | \-- 323 | https://steve.kemp.fi/ 324 | 325 | -------------------------------------------------------------------------------- /kilua.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- 3 | -- This is the configuration file for kilua. 4 | -- 5 | -- When kilua starts it will look for two default configuration-files: 6 | -- 7 | -- ~/.kilua/init.lua 8 | -- ~/.kilua/$(hostname).lua 9 | -- 10 | -- If neither file is present then _this_ file will be loaded, as it is 11 | -- embedded into the generated binary, as part of the build-process. 12 | -- 13 | -- There are several functions that kilua invokes at various times, these 14 | -- callbacks are documented later in the file, but in brief they include: 15 | -- 16 | -- * get_status_bar() 17 | -- This function is called to populate the status-bar in the footer. 18 | -- 19 | -- * on_complete(str) 20 | -- This is invoked if the user presses `TAB` when prompted for input. 21 | -- 22 | -- * on_idle(key) 23 | -- Called when things are idle, to allow actions to be carried out. 24 | -- 25 | -- * on_loaded(filename) 26 | -- Called when a file is loaded, and enables syntax highlighting. 27 | -- 28 | -- * on_save(filename) 29 | -- Called __before__ a file is saved. 30 | -- 31 | -- * on_saved(filename) 32 | -- Called __after__ a file is saved. 33 | -- 34 | -- * on_key(key) 35 | -- Called when input is received. 36 | -- 37 | -- Otherwise the only magic here is the `keymap` table. Kilua will lookup 38 | -- every keypress in this table, and if there is a matching function defined 39 | -- it will be invoked, otherwise the literal character will be inserted. 40 | -- 41 | -- Steve 42 | -- 43 | -- https://steve.kemp.fi/ 44 | 45 | 46 | 47 | -- 48 | -- Keybinding Settings. 49 | -- 50 | ------------------------------------------------------------------------ 51 | --====================================================================== 52 | ------------------------------------------------------------------------ 53 | 54 | -- 55 | -- Table of bound keys 56 | -- 57 | local keymap = {} 58 | 59 | 60 | -- 61 | -- Key bindings. 62 | -- 63 | -- You'll see some bindings are of the form "function() ... end", this is 64 | -- required to cope with the fact that the function they invoke is not yet 65 | -- defined, as it comes later in this file. 66 | -- 67 | -- For example this would fail: 68 | -- 69 | -- keymap['^D'] = delete_forwards 70 | -- 71 | -- But by the time this file is _completely_ loaded the function is 72 | -- indeed defined which allows this to work: 73 | -- 74 | -- keymap['^D'] = function() delete_forwards() end 75 | -- 76 | -- 77 | keymap['ENTER'] = function() insert("\n") end 78 | keymap['KEY_BACKSPACE'] = delete 79 | keymap['KEY_DC'] = function() delete_forwards() end 80 | keymap['^H'] = delete 81 | keymap['^D'] = function() delete_forwards() end 82 | 83 | 84 | -- 85 | -- This is a complex binding, more than it needs to be, just to 86 | -- show how we can maintain state and bind a function. 87 | -- 88 | do 89 | -- 90 | -- This is the value that the user entered previously, and is 91 | -- the default if nothing is entered again. 92 | -- 93 | local search_term = '' 94 | 95 | keymap['^S'] = function() 96 | local term = prompt("(regexp) Search? " ) 97 | 98 | -- If nothing entered default to the previous value 99 | if term == nil or term == "" then 100 | term = search_term 101 | end 102 | 103 | if ( term ) then 104 | 105 | -- get the point 106 | local x,y = point(); 107 | 108 | -- if we matched 109 | if ( search( term ) ) then 110 | -- get the new point 111 | local x2,y2 = point() 112 | 113 | -- did they change? If not we move one to the right 114 | -- and try again 115 | if ( x == x2 and y == y2 ) then 116 | move("right") 117 | search(term) 118 | end 119 | end 120 | 121 | -- save the search term 122 | search_term = term 123 | end 124 | end 125 | end 126 | 127 | -- 128 | -- Goto line 129 | -- 130 | keymap['M-g' ] = function() goto_line() end 131 | 132 | -- 133 | -- Cut-line, and paste-line. 134 | -- 135 | keymap['^K'] = function() kill_line() end 136 | keymap['^Y'] = function() paste() end 137 | 138 | -- 139 | -- Esc-q quits immediately. 140 | -- 141 | keymap['M-q'] = exit 142 | 143 | -- 144 | -- Movement 145 | -- 146 | keymap['^A'] = sol -- start of line 147 | keymap['^E'] = eol -- end of line 148 | keymap['^B'] = function() move("left") end 149 | keymap['KEY_LEFT'] = function() move("left") end 150 | keymap['^F'] = function() move("right") end 151 | keymap['KEY_RIGHT'] = function() move("right") end 152 | keymap['^P'] = function() move("up") end 153 | keymap['KEY_UP'] = function() move("up") end 154 | keymap['KEY_DOWN'] = function() move("down") end 155 | keymap['^N'] = function() move("down") end 156 | keymap['KEY_HOME'] = sol -- start of line 157 | keymap['KEY_END'] = eol -- end of line 158 | keymap['M-KEY_HOME'] = sof -- start of file 159 | keymap['M-KEY_END'] = eof -- end of file 160 | keymap['KEY_PPAGE'] = function() page_up() end 161 | keymap['KEY_NPAGE'] = function() page_down() end 162 | 163 | 164 | -- 165 | -- M-x -> eval, just like emacs. 166 | -- 167 | keymap['M-x'] = function() eval_lua() end 168 | 169 | -- 170 | -- M-! ("Escape", then "!") will run a command and insert the output 171 | -- into the current buffer. 172 | -- 173 | keymap['M-!'] = function() 174 | local cmd = prompt( "execute:" ) 175 | if ( cmd ) then 176 | insert( cmd_output(cmd) ) 177 | end 178 | end 179 | 180 | 181 | -- 182 | -- M-m will record a bookmark. 183 | -- 184 | keymap['M-m'] = function() bookmark_save() end 185 | 186 | -- 187 | -- Prefix-map for jumping to recorded bookmark. 188 | -- 189 | keymap['M-b'] = {} 190 | 191 | 192 | -- 193 | -- Prefixed keybindings 194 | -- 195 | -- ^X ^S => Save 196 | -- ^X ^C => Exit 197 | -- ^X i => Insert file/command 198 | -- ^X ^X => Swap point and mark 199 | -- 200 | keymap['^X'] = {} 201 | keymap['^X']['^C'] = function() quit() end 202 | keymap['^X']['^S'] = save 203 | keymap['^X']['i'] = function() insert_contents() end 204 | 205 | -- ^X ^O or ^X ^F both open a file in new buffer 206 | -- ^X ^V open file in existing buffer 207 | keymap['^X']['^O'] = function() open_file(true) end 208 | keymap['^X']['^F'] = function() open_file(true) end 209 | keymap['^X']['^V'] = function() open_file(false) end 210 | 211 | -- 212 | -- Working with buffers. 213 | -- 214 | keymap['M-KEY_LEFT'] = function() prev_buffer() end 215 | keymap['M-KEY_RIGHT'] = function() next_buffer() end 216 | keymap['^X']['B'] = function() choose_buffer() end 217 | keymap['^X']['K'] = kill_buffer 218 | keymap['^X']['b'] = function() choose_buffer() end 219 | keymap['^X']['c'] = create_buffer 220 | keymap['^X']['k'] = function() confirm_kill_buffer() end 221 | keymap['^X']['n'] = function() next_buffer() end 222 | keymap['^X']['p'] = function() prev_buffer() end 223 | 224 | 225 | -- 226 | -- Working with the selection 227 | -- 228 | keymap['^ '] = function() toggle_mark() end 229 | keymap['M-w'] = function() copy_selection() end 230 | keymap['^W'] = function() cut_selection() end 231 | 232 | 233 | 234 | 235 | -- 236 | -- Callbacks and utilities they use. 237 | ----------------------------------------------------------------------------- 238 | 239 | 240 | 241 | -- 242 | -- This function is called when input is received. 243 | -- 244 | -- It will lookup the value of the keypress, and if it 245 | -- represents a binding it will execute that function. 246 | -- 247 | -- If no binding is found then the key is inserted into 248 | -- the editor, literally. 249 | -- 250 | -- There is some magic to handle multi-character keystrokes 251 | -- but it is pretty simple. 252 | -- 253 | do 254 | pending_char = nil 255 | pending_esc = false 256 | 257 | function on_key(k) 258 | 259 | -- 260 | -- Was this escape? 261 | -- 262 | -- Special case if it was ESC-ESC 263 | -- 264 | if ( k == "ESC" ) then 265 | if ( pending_esc ) then 266 | k = "M-ESC" 267 | pending_esc = false 268 | else 269 | pending_esc = true 270 | return 271 | end 272 | end 273 | 274 | -- 275 | -- Pending-escape? 276 | -- 277 | if ( pending_esc == true ) then 278 | k = "M-" .. k 279 | pending_esc = false 280 | end 281 | 282 | -- 283 | -- At this point the key didn't result in a function. 284 | -- 285 | -- Nor did it terminate a special action. 286 | -- 287 | -- So we'll now look for the expanded function. 288 | -- 289 | if ( pending_char ) then 290 | local val = keymap[pending_char][k] 291 | 292 | if ( type(val) == 'function' ) then 293 | -- 294 | -- We got a function to invoke! 295 | -- 296 | val() 297 | pending_char = nil 298 | return 299 | else 300 | -- 301 | -- Failure 302 | -- 303 | pending_char = nil 304 | return 305 | end 306 | end 307 | 308 | 309 | -- 310 | -- Lookup the key in our key-map. 311 | -- 312 | local result = keymap[k] 313 | 314 | -- 315 | -- Was there a result? 316 | -- 317 | if ( result ) then 318 | 319 | if ( type(result) == 'function' ) then 320 | result() 321 | pending_esc = false 322 | pending_char = nil 323 | return 324 | end 325 | if ( type(result) == 'table' ) then 326 | 327 | -- 328 | -- This is a pending multi-part key - record this part away. 329 | -- 330 | pending_esc = false 331 | pending_char = k 332 | return 333 | end 334 | end 335 | 336 | -- 337 | -- Otherwise just insert the character. 338 | -- 339 | insert(k) 340 | pending_char = nil 341 | end 342 | end 343 | 344 | 345 | -- 346 | -- This function is called when a file is loaded, and is used 347 | -- to setup the syntax highlighting. 348 | -- 349 | -- You can replace this function if you don't like the defaults 350 | -- or leave as-is if you do. 351 | -- 352 | function on_loaded( filename ) 353 | 354 | if ( not filename ) then return end 355 | 356 | -- Get the file-name + suffix. 357 | local file = filename:match("^.+/(.+)$") or filename 358 | local ext = file:match("^.+%.(.+)$") or file 359 | 360 | -- 361 | -- Association for suffix to mode. 362 | -- 363 | local x = {} 364 | x['c'] = "cc" 365 | x['cc'] = "cc" 366 | x['cpp'] = "cc" 367 | x['el'] = "lisp" 368 | x['go'] = "go" 369 | x['h'] = "cc" 370 | x['htm'] = "html" 371 | x['html'] = "html" 372 | x['email'] = "email" 373 | x['msg'] = "email" 374 | x['ini'] = "ini" 375 | x['lua'] = "lua" 376 | x['md'] = "markdown" 377 | x['txt'] = "markdown" 378 | x['Makefile'] = "makefile" 379 | 380 | -- 381 | -- Setup syntax, based upon suffix. 382 | -- 383 | if ( x[ext] ) then 384 | 385 | -- Load the syntax 386 | syntax( x[ext] ) 387 | status( "Selected syntax-mode " .. x[ext] .. " via suffix " .. ext ) 388 | 389 | -- Trigger immediate re-redender. 390 | on_idle() 391 | 392 | return 393 | end 394 | 395 | -- 396 | -- Setup syntax, based upon filename. 397 | -- 398 | -- This is used to allow us to handle files like `Makefile`, with no 399 | -- suffix, or otherwise with a fixed name. 400 | -- 401 | if ( x[file] ) then 402 | syntax( x[file] ) 403 | status( "Selected syntax-mode " .. x[ext] .. " via filename " .. file ) 404 | 405 | -- Trigger immediate re-redender. 406 | on_idle() 407 | 408 | return 409 | end 410 | end 411 | 412 | 413 | 414 | -- 415 | -- This function is called BEFORE a file is saved. 416 | -- 417 | -- You might strip trailing whitespace, indent, or similar. 418 | -- 419 | function on_save( filename ) 420 | status("About to save ..") 421 | end 422 | 423 | 424 | -- 425 | -- This function is called AFTER a file is saved. 426 | -- 427 | -- You might re-open the file and call `chmod 755 $file` if 428 | -- the file has a shebang-line, or perform similar magic 429 | -- here. 430 | -- 431 | function on_saved( filename ) 432 | 433 | -- 434 | -- Process only the first line. 435 | -- 436 | local n=0 437 | for l in io.lines(filename) do 438 | n=n+1 439 | if n==1 then 440 | if ( string.match(l, "^#!/" ) ) then 441 | -- shebang found 442 | status( "Shebang found in " .. filename .. " " .. l ) 443 | os.execute( "chmod 755 " .. filename ) 444 | end 445 | break 446 | end 447 | end 448 | end 449 | 450 | 451 | 452 | 453 | 454 | -- 455 | -- Core things 456 | -- 457 | ----------------------------------------------------------------------------- 458 | 459 | 460 | -- 461 | -- Prompt for a name and open it. 462 | -- 463 | function open_file(new_buff) 464 | 465 | -- 466 | -- If we're opening in a new buffer prompt for file 467 | -- then open it 468 | -- 469 | if new_buff then 470 | local name = prompt( "Open:" ) 471 | if ( name and name ~= "" ) then 472 | create_buffer() 473 | open(name) 474 | else 475 | status( "Cancelled" ) 476 | end 477 | return 478 | end 479 | 480 | -- 481 | -- Is there a currently dirty file? 482 | -- 483 | if ( dirty() ) then 484 | 485 | -- Buffer is dirty. 486 | status( "Buffer is dirty. Really replace? (y/n)?" ) 487 | 488 | local run = true 489 | 490 | while( run == true ) do 491 | k = key() 492 | if ( k == "y" or k == "Y" ) then 493 | run = false 494 | end 495 | if ( k == "n" or k == "N" ) then 496 | status("Cancelled") 497 | return 498 | end 499 | end 500 | end 501 | 502 | -- 503 | -- Either no dirty-buffer, or the user didn't care 504 | -- 505 | local name = prompt( "Replace with:" ) 506 | if ( name and name ~= "" ) then 507 | open(name) 508 | end 509 | end 510 | 511 | 512 | 513 | 514 | -- 515 | -- Utility functions. 516 | -- 517 | ----------------------------------------------------------------------------- 518 | 519 | -- 520 | -- Run a command and return the output, if any. 521 | -- 522 | function cmd_output(cmd) 523 | local handle = io.popen(cmd) 524 | local out = handle:read("*a") 525 | handle:close() 526 | return(out) 527 | end 528 | 529 | -- 530 | -- Delete forwards 531 | -- 532 | function delete_forwards() 533 | 534 | -- Get the current position. 535 | local x,y = point(); 536 | 537 | -- Move forwards one space. 538 | move("right") 539 | 540 | -- Get the position. 541 | local nx,ny = point() 542 | 543 | -- 544 | -- If the cursor position didn't change then we're done. 545 | -- 546 | -- Basically this means we tried to move, but couldn't, because 547 | -- we're at the end of the file. 548 | -- 549 | if ( nx == x and ny == y ) then 550 | return 551 | end 552 | 553 | -- 554 | -- Delete (backwards) a character. 555 | delete() 556 | end 557 | 558 | 559 | 560 | 561 | 562 | -- 563 | -- Handling for cutting the current line, and copying/cutting the 564 | -- current selection. 565 | -- 566 | ----------------------------------------------------------------------------- 567 | 568 | 569 | -- 570 | -- The paste-buffer 571 | -- 572 | paste_buffer = "" 573 | 574 | -- 575 | -- Kill the current line. 576 | -- 577 | function kill_line() 578 | 579 | -- Move to the end of the line, so we can see how long it is 580 | eol() 581 | local x,y = point() 582 | sol() 583 | 584 | paste_buffer = "" 585 | 586 | -- The count of characters we must remove 587 | local count = x 588 | 589 | -- While we've not deleted all the characters, do so. 590 | while( count > 0 ) do 591 | 592 | -- Store the character under the point in the paste-buffer 593 | paste_buffer = paste_buffer .. at() 594 | 595 | -- Then delete it. 596 | delete_forwards() 597 | 598 | -- Bump the count. 599 | count = count - 1 600 | end 601 | paste_buffer = paste_buffer .. "\n" 602 | end 603 | 604 | 605 | -- 606 | -- Paste the contents of our paste-buffer into the current buffer. 607 | -- 608 | -- This is currently set by `Ctrl-k` which kills the current line. 609 | -- 610 | function paste() 611 | insert( paste_buffer ) 612 | end 613 | 614 | 615 | -- 616 | -- Set the mark, or remove the mark. 617 | -- 618 | function toggle_mark() 619 | local mx, my = mark() 620 | if ( mx == -1 and my == -1 ) then 621 | -- set the mark 622 | local x,y = point() 623 | mark(x,y) 624 | status("Set mark to " .. x .. "," .. y ) 625 | else 626 | -- clear the mark 627 | mark(-1,-1) 628 | status("Cleared mark") 629 | end 630 | end 631 | 632 | 633 | -- 634 | -- Copy the selection into the paste-buffer. 635 | -- 636 | function copy_selection() 637 | local mx, my = mark() 638 | if ( mx == -1 and my == -1 ) then 639 | status("No selection!") 640 | return 641 | end 642 | 643 | paste_buffer = selection() 644 | mark(-1,-1) 645 | end 646 | 647 | 648 | -- 649 | -- Copy the selection into the paste-buffer, then delete it 650 | -- 651 | function cut_selection() 652 | local mx, my = mark() 653 | local px, py = point() 654 | if ( mx == -1 and my == -1 ) then 655 | status("No selection!") 656 | return 657 | end 658 | 659 | -- save the selected text. 660 | paste_buffer = selection() 661 | 662 | -- now we want to move the the "end" of the selection 663 | if ( py > my or (px > mx and py == my)) then 664 | -- OK 665 | else 666 | point( mx, my ) 667 | end 668 | move("right") 669 | 670 | -- delete 671 | len = #paste_buffer 672 | while( len > 0 ) do 673 | delete() 674 | len = len -1 675 | end 676 | 677 | -- remove the mark 678 | mark(-1,-1) 679 | end 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | -- 689 | -- Quit handling. 690 | ----------------------------------------------------------------------------- 691 | 692 | 693 | 694 | -- 695 | -- Kill the current buffer, prompting for confirmation if it is modified. 696 | -- 697 | function confirm_kill_buffer() 698 | if ( not dirty() ) then 699 | kill_buffer() 700 | return 701 | end 702 | 703 | -- Buffer is dirty. 704 | status( "Buffer is dirty. Really kill (y/n)?" ) 705 | 706 | while( true ) do 707 | k = key() 708 | if ( k == "y" or k == "Y" ) then 709 | kill_buffer() 710 | status("Killed") 711 | return 712 | end 713 | if ( k == "n" or k == "N" ) then 714 | status("Cancelled") 715 | return 716 | end 717 | end 718 | end 719 | 720 | 721 | 722 | -- 723 | -- Called on quit. 724 | -- 725 | -- * If the buffer is clean then exit immediately. 726 | -- * If the buffer is dirty require N Ctrl-q presses to exit. 727 | -- * Although this is odd to me, this is how kilua worked in pure C. 728 | -- 729 | function quit() 730 | -- 731 | -- Count of dirty-buffers. 732 | -- 733 | local dirty_count = 0 734 | 735 | -- 736 | -- The current-buffer 737 | -- 738 | local old_buffer = buffer() 739 | 740 | -- 741 | -- For each buffer 742 | -- 743 | for index,name in ipairs(buffers()) do 744 | -- 745 | -- Select the buffer 746 | -- 747 | buffer( index - 1 ) 748 | 749 | -- 750 | -- Is it dirty? 751 | -- 752 | if ( dirty() ) then 753 | dirty_count = dirty_count + 1 754 | end 755 | end 756 | 757 | -- 758 | -- Restore the old-current buffer 759 | -- 760 | buffer( old_buffer ) 761 | 762 | -- 763 | -- If there are dirty buffers .. 764 | -- 765 | if ( dirty_count > 0 ) then 766 | 767 | -- 768 | -- Show the user how many buffers are dirty. 769 | -- 770 | status( dirty_count .. "/" .. #buffers() .. " buffers are dirty. Really quit? (y/n)" ) 771 | 772 | -- 773 | -- Await confirmation 774 | -- 775 | k = key() 776 | if ( k == 'y' ) or ( k == 'Y' ) then 777 | exit() 778 | else 779 | status("Cancelled!") 780 | end 781 | else 782 | exit() 783 | end 784 | end 785 | 786 | 787 | -- 788 | -- Interactive evaluation 789 | ----------------------------------------------------------------------------- 790 | 791 | function eval_lua() 792 | local txt = prompt( "Eval:" ) 793 | if ( txt ) then 794 | local err, result = loadstring( txt )() 795 | if ( err ) then 796 | status( result ) 797 | end 798 | else 799 | status( "Evaluation cancelled!" ) 800 | end 801 | end 802 | 803 | 804 | -- 805 | -- Insert the contents of a file at the point. 806 | ----------------------------------------------------------------------------- 807 | 808 | function insert_contents() 809 | 810 | local file = prompt( "Insert? " ) 811 | 812 | if ( file == nil or file == "" ) then 813 | status( "Cancelled" ) 814 | return 815 | end 816 | 817 | -- Open the file, and insert the contents. 818 | for line in io.lines(file) do 819 | insert( line ) 820 | insert( "\n") 821 | end 822 | end 823 | 824 | 825 | 826 | 827 | 828 | -- 829 | -- Functions relating to buffers. 830 | -- 831 | ----------------------------------------------------------------------------- 832 | 833 | 834 | -- 835 | -- Prompt for a buffer, interactively 836 | -- 837 | function choose_buffer() 838 | 839 | -- 840 | -- Save the current buffer 841 | -- 842 | local cur = buffer() 843 | 844 | -- 845 | -- Build up a table of each buffer-name 846 | -- 847 | local m = {} 848 | 849 | -- 850 | -- For each buffer. 851 | -- 852 | for index,name in ipairs(buffers()) do 853 | 854 | -- Select it, so we can see if it is dirty 855 | buffer( index - 1 ) 856 | 857 | -- The name and mode 858 | local name = buffer_name() 859 | local mode = syntax() 860 | if ( mode and mode ~= "" ) then 861 | mode = "[" .. mode .. "]" 862 | end 863 | 864 | -- Add the result to our menu-choice-table. 865 | if ( dirty() ) then 866 | table.insert(m, index .. " " .. name .. " " .. " " .. mode ) 867 | else 868 | table.insert(m, index .. " " .. name .. " " .. mode ) 869 | end 870 | end 871 | 872 | -- 873 | -- Make the menu-choice 874 | -- 875 | local r = menu( m ) 876 | 877 | -- 878 | -- User cancelled. 879 | -- Restore the original buffer and return. 880 | -- 881 | if ( r == -1 ) then 882 | buffer( cur) 883 | return 884 | end 885 | 886 | -- 887 | -- Otherwise change to the buffer the user chose. 888 | -- 889 | buffer( r ) 890 | end 891 | 892 | 893 | -- 894 | -- Move to the next buffer; wrapping around. 895 | -- 896 | function next_buffer() 897 | local max = #buffers() 898 | local cur = buffer() 899 | 900 | if ( (cur +1) < max ) then 901 | buffer(cur + 1) 902 | else 903 | buffer(0) 904 | end 905 | end 906 | 907 | -- 908 | -- Move to the previous buffer; wrapping around. 909 | -- 910 | function prev_buffer() 911 | local cur = buffer() 912 | if ( cur > 0 ) then 913 | buffer( cur - 1) 914 | else 915 | local max = #buffers() 916 | buffer(max-1) 917 | end 918 | end 919 | 920 | 921 | 922 | 923 | -- 924 | -- Implementation of setting/jumping to (named) marks. 925 | ----------------------------------------------------------------------------- 926 | 927 | 928 | -- 929 | -- Load the bookmark stored in the given key. 930 | -- 931 | function bookmark_load(key) 932 | 933 | -- Save the current-buffer in case we can't find the bookmark 934 | local old_buffer = buffer() 935 | 936 | for index,name in ipairs(buffers()) do 937 | -- 938 | -- Select the buffer 939 | -- 940 | buffer( index - 1 ) 941 | 942 | -- 943 | -- Are there x/y coord set? 944 | -- 945 | local x = buffer_data( "bookmark_" .. k .. "_x" ) 946 | local y = buffer_data( "bookmark_" .. k .. "_y" ) 947 | 948 | if ( x ~= "" and y ~= "" ) then 949 | point(x,y) 950 | return 951 | end 952 | end 953 | 954 | -- 955 | -- Failed to find the bookmark 956 | -- 957 | status("Failed to find bookmark for '" .. key .. "'" ) 958 | 959 | -- 960 | -- Remove the binding 961 | -- 962 | keymap['M-b'][key] = nil 963 | 964 | -- 965 | -- Restore the old-current buffer 966 | -- 967 | buffer( old_buffer ) 968 | 969 | end 970 | 971 | 972 | -- 973 | -- Read a character, then use that to set a mark. 974 | -- 975 | -- Marks are bound to `M-m XX` where XX is the key 976 | -- the user entered. 977 | -- 978 | function bookmark_save() 979 | status( "Press key to use for bookmark") 980 | k = key() 981 | 982 | -- Get the point 983 | local x,y = point() 984 | 985 | -- store in the per-buffer data 986 | buffer_data( "bookmark_" .. k .. "_x", x ) 987 | buffer_data( "bookmark_" .. k .. "_y", y ) 988 | 989 | -- Allow it to be recalled 990 | keymap['M-b'][k] = function() bookmark_load(k) end 991 | status( "M-b-" .. k .. " will now take you to " .. x .. "," .. y .. " in this buffer") 992 | end 993 | 994 | 995 | 996 | 997 | 998 | 999 | 1000 | -- 1001 | -- Callback functions 1002 | -- 1003 | ----------------------------------------------------------------------------- 1004 | 1005 | 1006 | -- 1007 | -- `on_idle` is called every second, or so, and can run things in the 1008 | -- background. 1009 | -- 1010 | function on_idle() 1011 | 1012 | -- 1013 | -- Here we handle the syntax-highlighting, but if there 1014 | -- is no syntax set then we can return early to avoid the 1015 | -- overhead. 1016 | local s = syntax() 1017 | if ( s == nil or s == "" ) then 1018 | return 1019 | end 1020 | 1021 | -- 1022 | -- Get the text 1023 | -- 1024 | local text = text() 1025 | 1026 | -- 1027 | -- Transform that into a series of colours 1028 | -- 1029 | local colours = on_syntax_highlight( text ); 1030 | 1031 | -- 1032 | -- If it worked then set the colours. 1033 | -- 1034 | if ( colours ~= nil and colours ~= "" ) then 1035 | update_colours( colours ) 1036 | end 1037 | end 1038 | 1039 | 1040 | 1041 | -- 1042 | -- String interopolation function, taken from the Lua wiki: 1043 | -- 1044 | -- http://lua-users.org/wiki/StringInterpolation 1045 | -- 1046 | -- Usage: 1047 | -- 1048 | -- print( string.interp( "Hello ${name}", { name = "World" } ) 1049 | -- 1050 | function string.interp(s, tab) 1051 | return (s:gsub('($%b{})', function(w) return tab[w:sub(3, -2)] or w end)) 1052 | end 1053 | 1054 | 1055 | 1056 | 1057 | -- 1058 | -- If this function is defined it will be invoked to draw the status-bar. 1059 | -- 1060 | -- We use a format-string to determine what to draw, and ensure that we 1061 | -- pad/truncate to fit the width of the console 1062 | -- 1063 | function get_status_bar() 1064 | 1065 | -- 1066 | -- Format String of what we show. 1067 | -- 1068 | local fmt = "${buffer}/${buffers} - ${file} ${mode} ${modified} #BLANK# Col:${x} Row:${y} [${point}] ${time}" 1069 | 1070 | -- 1071 | -- Things we use. 1072 | -- 1073 | local x,y = point() 1074 | 1075 | -- 1076 | -- Table holding values we can interpolate. 1077 | -- 1078 | local t = {} 1079 | t['buffer'] = buffer() + 1 1080 | t['buffers'] = #buffers() 1081 | 1082 | -- 1083 | -- See https://www.lua.org/pil/22.1.html 1084 | -- 1085 | t['date'] = os.date("%A %d %B %Y") 1086 | t['time'] = os.date("%X") 1087 | 1088 | t['file'] = buffer_name() 1089 | 1090 | t['mode'] = "" 1091 | 1092 | local s = syntax() 1093 | if ( #s > 0 ) then 1094 | t['mode'] = s .. "-mode" 1095 | end 1096 | t['x'] = x 1097 | t['y'] = y + 1 1098 | t['point'] = at() 1099 | 1100 | if ( dirty() ) then 1101 | t['modified'] = "" 1102 | else 1103 | t['modified'] = "" 1104 | end 1105 | 1106 | -- 1107 | -- Width of console 1108 | -- 1109 | local w = width() 1110 | 1111 | -- 1112 | -- Interpolate our output 1113 | -- 1114 | local out = string.interp( fmt, t ) 1115 | 1116 | -- 1117 | -- If the format string includes `${point}` - which expands to the 1118 | -- character under the cursor - then we need to make sure that is 1119 | -- only a single character. 1120 | -- 1121 | -- If the character under the point is `€`, or other multi-byte 1122 | -- character, it will have a length of bigger than one byte 1123 | -- even though it is, by definition, a single character. 1124 | -- 1125 | local at_pos = string.find( fmt, "${point}" ) 1126 | local fix = 0 1127 | if ( at_pos ) then 1128 | fix = #at() -1 1129 | end 1130 | 1131 | -- 1132 | -- Too large? 1133 | -- 1134 | if ( #out > w ) then 1135 | -- Remove the marker. 1136 | out = out:gsub( "#BLANK#", "" ) 1137 | return( out:sub(0,w) ) 1138 | end 1139 | 1140 | -- 1141 | -- Too small? 1142 | -- 1143 | local pad = w - #out + fix + ( 7 ) -- 7 == length of '#BLANK#' 1144 | local spc = "" 1145 | while( pad > 0 ) do 1146 | spc = spc .. " " 1147 | pad = pad - 1 1148 | end 1149 | 1150 | -- 1151 | -- Replace the #BLANK# string with our new spc-string containing the 1152 | -- correct amount of padding. 1153 | -- 1154 | out = out:gsub( "#BLANK#", spc ) 1155 | return( out ) 1156 | end 1157 | 1158 | 1159 | -- 1160 | -- Callback function to perform TAB-completion at the prompt. 1161 | -- 1162 | -- This function should return the updated input to use at the prompt, 1163 | -- when the user presses TAB. 1164 | -- 1165 | -- i.e. Input "/etc/pas", the result should be "/etc/passwd". 1166 | -- 1167 | -- This only completes at the start of the string at the moment, but 1168 | -- extending it should be simple enough. 1169 | -- 1170 | function on_complete( str ) 1171 | 1172 | -- 1173 | -- Holder for things that we might be able to complete upon. 1174 | -- 1175 | tmp = {} 1176 | 1177 | -- 1178 | -- Add in all user-defined functions. 1179 | -- 1180 | for k,v in pairs(_G) do 1181 | tmp[k] = k .. "(" 1182 | end 1183 | 1184 | -- 1185 | -- If the token starts with "~" then replace that with 1186 | -- the users' home-directory 1187 | -- 1188 | if ( string.sub( str, 0, 1 ) == "~" ) then 1189 | str = string.sub( str, 2 ) 1190 | str = os.getenv("HOME") .. str 1191 | end 1192 | 1193 | -- 1194 | -- Is the user attempting to complete on a file-path? 1195 | -- 1196 | if ( string.match( str, "^/" ) ) then 1197 | 1198 | -- 1199 | -- Get the directory this is from. 1200 | -- 1201 | -- Default to / if we found no match. 1202 | -- 1203 | dir = string.match(str, "^(.*)/" ) 1204 | if ( dir == "" ) then dir = "/" end 1205 | 1206 | -- 1207 | -- If the directory exists then add all the entries to the completion-set. 1208 | -- 1209 | if ( exists( dir ) ) then 1210 | entries = directory_entries( dir ) 1211 | for i,v in ipairs(entries) do 1212 | 1213 | if ( exists( v .. "/." ) ) then 1214 | tmp[v .. "/" ] = v .. "/" 1215 | else 1216 | tmp[v] = v 1217 | end 1218 | end 1219 | end 1220 | end 1221 | 1222 | -- 1223 | -- We have a list of things in `tmp` which _might_ match 1224 | -- the user's input. 1225 | -- 1226 | -- Now build up a table of the things that actually _do_ match 1227 | -- what the user gave us. 1228 | -- 1229 | ret = { } 1230 | 1231 | -- 1232 | -- Do we have a match? 1233 | -- 1234 | for k,v in pairs(tmp) do 1235 | if ( string.match( v, "^" .. str ) ) then 1236 | table.insert(ret, v) 1237 | end 1238 | end 1239 | 1240 | -- 1241 | -- OK at this point we have a table `ret` with things that 1242 | -- match the users's input. 1243 | -- 1244 | 1245 | 1246 | -- 1247 | -- If there are zero entries then there is no completion. 1248 | -- 1249 | if ( #ret == 0 ) then 1250 | return( str ) 1251 | end 1252 | 1253 | -- 1254 | -- If there is just one entry - return it. 1255 | -- 1256 | if ( #ret == 1 ) then 1257 | return( ret[1] ) 1258 | end 1259 | 1260 | -- 1261 | -- Otherwise sort the choices and let the user choose. 1262 | -- 1263 | table.sort(ret) 1264 | 1265 | -- 1266 | -- So we have ambiguity. Resolve it 1267 | -- 1268 | local res = menu( ret ) 1269 | 1270 | -- 1271 | -- If the user cancelled that return the unmolested input 1272 | -- 1273 | if ( res == -1 ) then 1274 | return(str) 1275 | else 1276 | -- 1277 | -- Otherwise return what they chose. 1278 | -- 1279 | return( ret[res+1] ) 1280 | end 1281 | end 1282 | 1283 | 1284 | 1285 | -- 1286 | -- Functions relating to movement. 1287 | -- 1288 | ----------------------------------------------------------------------------- 1289 | 1290 | 1291 | -- 1292 | -- Move up a screenful of text. 1293 | -- 1294 | function page_up() 1295 | local h = height() - 1 1296 | while( h > 0 ) do 1297 | move( "up" ) 1298 | h = h - 1 1299 | end 1300 | end 1301 | 1302 | -- 1303 | -- Move down a screenful of text. 1304 | -- 1305 | -- Leave two lines of context. 1306 | -- 1307 | function page_down() 1308 | local h = height() - 1 1309 | while( h > 0 ) do 1310 | move( "down" ) 1311 | h = h - 1 1312 | end 1313 | end 1314 | 1315 | 1316 | -- 1317 | -- Move to the given line-number. 1318 | -- 1319 | -- NOTE: This is naive because it doesn't test that you entered a line-number 1320 | -- that doesn't exist. 1321 | -- 1322 | -- That said it does work, and in the worst case it can't do a bad thing. 1323 | -- 1324 | function goto_line(number) 1325 | if ( number == nil ) then 1326 | number = prompt( "Goto line?" ) 1327 | end 1328 | 1329 | if tonumber(number) == nil then 1330 | status( "You must enter a number!" ) 1331 | return 1332 | end 1333 | 1334 | number = tonumber(number) 1335 | 1336 | -- 1337 | -- Move to start of file 1338 | -- 1339 | sof(); 1340 | 1341 | 1342 | while( number >0 ) do 1343 | move( "down" ); 1344 | number = number - 1; 1345 | end 1346 | end 1347 | 1348 | 1349 | -- 1350 | -- Syntax Highlighting 1351 | -- 1352 | -- 1353 | ----------------------------------------------------------------------------- 1354 | 1355 | -- 1356 | -- Load syntax files from: 1357 | -- 1358 | -- /etc/kilua/syntax/ 1359 | -- ~/.kilua/syntax/ 1360 | -- 1361 | package.path = package.path .. ';/etc/kilua/syntax/?.lua' 1362 | package.path = package.path .. ';' .. os.getenv("HOME") .. '/.kilua/syntax/?.lua' 1363 | 1364 | 1365 | function load_syntax( lang ) 1366 | 1367 | -- Save the original path 1368 | orig = package.path 1369 | 1370 | -- Add the per-run one. 1371 | if ( syntax_path ) then 1372 | package.path = package.path .. ';' .. syntax_path .. "/?.lua" 1373 | end 1374 | 1375 | -- Load the library 1376 | local status, obj = pcall(require, lang) 1377 | 1378 | -- Restore the path 1379 | package.path = orig 1380 | 1381 | if ( status ) then 1382 | -- enable syntax highlighting. 1383 | return obj 1384 | else 1385 | -- disable syntax highlighting. 1386 | return nil 1387 | end 1388 | end 1389 | 1390 | 1391 | -- 1392 | -- Callback function that handles syntax highlighting. 1393 | -- 1394 | -- Given the text "Steve Kemp" this funciton should return 1395 | -- one character for each byte of that input. The character 1396 | -- will be added to '0' and used to colour the input. 1397 | -- 1398 | -- A defualt implementation would return 1399 | -- 1400 | -- WHITE 1401 | -- 1402 | -- For each character. 1403 | -- 1404 | function on_syntax_highlight( text ) 1405 | -- 1406 | -- Get the syntax mode 1407 | -- 1408 | local mode = syntax() 1409 | if ( mode == nil or mode == "" ) then 1410 | return "" 1411 | end 1412 | 1413 | -- 1414 | -- Load LPEG - if that fails then unset the syntax for this 1415 | -- buffer, which will ensure we're not called again in the future. 1416 | -- 1417 | lpeg = load_syntax( 'lpeg' ) 1418 | if ( not lpeg ) then 1419 | status("Lua LPEG library not available - syntax highlighting disabled") 1420 | syntax("") 1421 | return 1422 | end 1423 | 1424 | 1425 | 1426 | -- 1427 | -- Load the module, which will be cached the second time around. 1428 | -- 1429 | -- Again if this fails we'll disable syntax-highlighting for this 1430 | -- mode, by unsetting `syntax`. 1431 | -- 1432 | local obj = load_syntax( mode ) 1433 | if ( obj ) then 1434 | return(tostring(obj.parse(text))) 1435 | else 1436 | status("Failed to load syntax-module '" .. syntax() .. "' disabling highlighting.") 1437 | syntax("") 1438 | return("") 1439 | end 1440 | end 1441 | 1442 | 1443 | 1444 | -- 1445 | -- Show the version 1446 | -- 1447 | status("Kilua v" .. KILUA_VERSION) 1448 | 1449 | 1450 | 1451 | 1452 | 1453 | -- 1454 | ----------------------------------------------------------------------------- 1455 | -- 1456 | -- Functions below here are samples and test-code by Steve 1457 | -- 1458 | -- They might be inspirational, or they might kill your computer 1459 | -- 1460 | ----------------------------------------------------------------------------- 1461 | -- 1462 | 1463 | 1464 | -- 1465 | -- Call `make` - showing the output in our `*MAKE*` buffer. 1466 | -- 1467 | function make() 1468 | -- 1469 | -- Select the buffer. 1470 | -- 1471 | local result = buffer( "*Make*" ) 1472 | 1473 | -- 1474 | -- If selecting by name failed then the buffer can't exist. 1475 | -- 1476 | -- Create it. 1477 | -- 1478 | if ( result == -1 ) then 1479 | create_buffer( "*Make*" ) 1480 | end 1481 | 1482 | -- Ensure we append output 1483 | eof() 1484 | 1485 | -- Run the command. 1486 | insert(cmd_output( "make" ) ) 1487 | 1488 | -- completed 1489 | insert("completed\n") 1490 | end 1491 | 1492 | 1493 | 1494 | -- 1495 | -- Dump details about our buffers 1496 | -- 1497 | function bd() 1498 | -- 1499 | -- Create a buffer to show our output 1500 | -- 1501 | local result = buffer( "*Buffers*" ) 1502 | if ( result == -1 ) then 1503 | create_buffer( "*Buffers*" ) 1504 | end 1505 | 1506 | -- 1507 | -- Move to the end of the file. 1508 | -- 1509 | eof() 1510 | 1511 | -- 1512 | -- Get the buffers 1513 | -- 1514 | local b = buffers() 1515 | insert( "There are " .. #b .. " buffers" .. "\n" ) 1516 | 1517 | -- 1518 | -- Show them. 1519 | -- 1520 | for i,n in ipairs(b) do 1521 | insert( "Buffer " .. i .. " has name " .. n .. "\n" ) 1522 | end 1523 | 1524 | end 1525 | 1526 | 1527 | -- 1528 | -- Dump files beneath /etc 1529 | -- 1530 | function ls(path) 1531 | 1532 | -- 1533 | -- Create a buffer to show our output 1534 | -- 1535 | local result = buffer( "*ls*" ) 1536 | if ( result == -1 ) then 1537 | create_buffer( "*ls*" ) 1538 | end 1539 | 1540 | -- 1541 | -- Move to the end of the file. 1542 | -- 1543 | eof() 1544 | 1545 | -- 1546 | -- Get the path 1547 | -- 1548 | if ( path == nil ) or ( path == "" ) then 1549 | path = "/etc" 1550 | end 1551 | 1552 | -- 1553 | -- Get the directory entries 1554 | -- 1555 | local b = directory_entries( path ) 1556 | insert( "There are " .. #b .. " files" .. "\n" ) 1557 | 1558 | -- 1559 | -- Show them. 1560 | -- 1561 | for i,n in ipairs(b) do 1562 | insert( i .. " -> " .. n .. "\n" ) 1563 | end 1564 | 1565 | end 1566 | -------------------------------------------------------------------------------- /kilua.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skx/kilua/1a2309cf3b89d9ccbf65704b68ed4768c396b98c/kilua.png -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Compilation flags and libraries we use. 3 | # 4 | CPPFLAGS+=-fsanitize=address -fno-omit-frame-pointer -std=c++11 -ggdb -Wall -Werror -I/usr/include/ncursesw -I/usr/include/lua5.2 -DKILUA_VERSION="\"0.5\"" 5 | LDLIBS+=$(shell pkg-config --libs ncursesw) $(shell pkg-config --libs lua5.2) -fsanitize=address -fno-omit-frame-pointer -lstdc++ 6 | 7 | # 8 | # The linker & objects. 9 | # 10 | LINKER=$(CC) -o 11 | SOURCES := $(wildcard *.cc) 12 | OBJECTS := $(SOURCES:%.cc=%.o) 13 | 14 | 15 | 16 | # 17 | # The default target, our editor. 18 | # 19 | default: kilua 20 | 21 | 22 | # 23 | # Build the editor. 24 | # 25 | kilua: $(OBJECTS) 26 | $(LINKER) ../$@ $(LFLAGS) $(OBJECTS) $(LDLIBS) 27 | 28 | # 29 | # Cleanup 30 | # 31 | clean: 32 | rm -f editor *.orig core *.o 33 | 34 | 35 | # 36 | # Indent our .cc + .h files. 37 | # 38 | .PHONY: indent 39 | indent: 40 | astyle --style=allman -A1 --indent=spaces=4 --break-blocks --pad-oper --pad-header --unpad-paren --max-code-length=200 *.cc *.h 41 | -------------------------------------------------------------------------------- /src/buffer.cc: -------------------------------------------------------------------------------- 1 | /* buffer.cc - Our buffer/row implementation. 2 | * 3 | * ----------------------------------------------------------------------- 4 | * 5 | * Copyright (C) 2016 Steve Kemp https://steve.kemp.fi/ 6 | * 7 | * All rights reserved. 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions are 11 | * met: 12 | * 13 | * * Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * 16 | * * Redistributions in binary form must reproduce the above copyright 17 | * notice, this list of conditions and the following disclaimer in the 18 | * documentation and/or other materials provided with the distribution. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | 34 | #include 35 | #include "buffer.h" 36 | 37 | /** 38 | * Constructor. 39 | */ 40 | erow::erow() 41 | { 42 | chars = new std::vector; 43 | cols = new std::vector; 44 | } 45 | 46 | 47 | /** 48 | * Destructor. 49 | */ 50 | erow::~erow() 51 | { 52 | delete (chars); 53 | delete (cols); 54 | } 55 | 56 | /** 57 | * The text of the row, from the given character offset. 58 | */ 59 | std::wstring erow::text(int offset) 60 | { 61 | int max = chars->size(); 62 | 63 | std::wstring ret; 64 | 65 | while (offset < max) 66 | { 67 | ret += chars->at(offset); 68 | offset++; 69 | } 70 | 71 | return (ret); 72 | } 73 | 74 | /** 75 | * Constructor. 76 | */ 77 | Buffer::Buffer(const char *bname) 78 | { 79 | cx = 0; 80 | cy = 0; 81 | markx = -1; 82 | marky = -1; 83 | rowoff = 0; 84 | coloff = 0; 85 | m_name = strdup(bname); 86 | m_dirty = false; 87 | m_syntax = ""; 88 | 89 | /* 90 | * The buffer will have one (empty) row. 91 | */ 92 | erow *row = new erow(); 93 | rows.push_back(row); 94 | }; 95 | 96 | 97 | /** 98 | * Destructor 99 | */ 100 | Buffer::~Buffer() 101 | { 102 | /* 103 | * Remove the rows 104 | */ 105 | for (std::vector::iterator it = rows.begin(); it != rows.end(); ++it) 106 | { 107 | erow *row = (*it); 108 | delete (row); 109 | } 110 | 111 | rows.clear(); 112 | 113 | if (m_name) 114 | free(m_name); 115 | } 116 | 117 | 118 | /** 119 | * Remove all text from the buffer. 120 | */ 121 | void Buffer::empty_buffer() 122 | { 123 | for (std::vector::iterator it = rows.begin(); it != rows.end(); ++it) 124 | { 125 | erow *row = (*it); 126 | delete (row); 127 | } 128 | 129 | rows.clear(); 130 | cx = 0; 131 | cy = 0; 132 | markx = -1; 133 | marky = -1; 134 | rowoff = 0; 135 | coloff = 0; 136 | 137 | /* 138 | * The buffer will have one (empty) row. 139 | */ 140 | erow *row = new erow(); 141 | rows.push_back(row); 142 | } 143 | 144 | 145 | /** 146 | * Get the character offset of the given X,Y coordinate in our 147 | * buffer. 148 | */ 149 | int Buffer::pos2offset(int w_x, int w_y) 150 | { 151 | int nrows = rows.size(); 152 | int count = 0; 153 | 154 | for (int y = 0; y < nrows; y++) 155 | { 156 | erow *row = rows.at(y); 157 | int row_size = row->chars->size(); 158 | 159 | /* 160 | * NOTE: We add one character to the row 161 | * to cope with the trailing newline. 162 | */ 163 | for (int x = 0; x < row_size + 1; x++) 164 | { 165 | if (x == w_x && y == w_y) 166 | return count; 167 | 168 | count += 1; 169 | } 170 | } 171 | 172 | return -1; 173 | } 174 | 175 | /** 176 | * Is this buffer dirty? 177 | */ 178 | bool Buffer::dirty() 179 | { 180 | // buffers are never dirty, only files. 181 | if (m_name && (m_name[0] == '*')) 182 | return false; 183 | 184 | return (m_dirty); 185 | } 186 | 187 | 188 | /** 189 | * Mark the buffer as dirty. 190 | */ 191 | void Buffer::set_dirty(bool state) 192 | { 193 | m_dirty = state; 194 | } 195 | 196 | /** 197 | * Get the name of the buffer. 198 | */ 199 | const char *Buffer::get_name() 200 | { 201 | return (m_name); 202 | } 203 | 204 | 205 | /** 206 | * Set the name of the buffer. 207 | */ 208 | void Buffer::set_name(const char *name) 209 | { 210 | if (m_name) 211 | free(m_name); 212 | 213 | m_name = strdup(name); 214 | } 215 | 216 | 217 | /** 218 | * Get the buffer contents, as text. 219 | * 220 | * NOTE: NOT wide-text. 221 | */ 222 | std::string Buffer::text() 223 | { 224 | std::string text; 225 | 226 | int row_count = rows.size(); 227 | 228 | for (int y = 0; y < row_count; y++) 229 | { 230 | int chars = rows.at(y)->chars->size(); 231 | 232 | for (int x = 0; x < chars; x++) 233 | { 234 | /* 235 | * We append the character at the row,col position. 236 | * 237 | * NOTE This might be an N-byte character string, we 238 | * deliberately only use the first because LPEG doesn't 239 | * even handle UTF-8, so it doesn't matter. 240 | * 241 | * We could have said this instead: 242 | * 243 | * if (cur->rows.at(y)->chars->at(x)->size() > 1 ) 244 | * text += "?"; 245 | * else 246 | * text += cur->rows.at(y)->chars->at(x); 247 | * 248 | */ 249 | text += rows.at(y)->chars->at(x)[0]; 250 | } 251 | 252 | text += '\n'; 253 | } 254 | 255 | return (text); 256 | } 257 | 258 | 259 | /** 260 | * Update the colours of the current buffer, via the 261 | * result of the lua callback. 262 | */ 263 | void Buffer::update_syntax(const char *colours, size_t len) 264 | { 265 | /* 266 | * Free the current colour, if any. 267 | */ 268 | int row_count = rows.size(); 269 | 270 | for (int y = 0; y < row_count; y++) 271 | rows.at(y)->cols->clear(); 272 | 273 | /* 274 | * Now we'll update the colour of each character. 275 | */ 276 | int done = 0; 277 | 278 | for (int y = 0; y < row_count; y++) 279 | { 280 | /* 281 | * The current row. 282 | */ 283 | erow *crow = rows.at(y); 284 | 285 | /* 286 | * For each character in the row, set the colour 287 | * to be the return value. 288 | */ 289 | for (int x = 0; x < (int)crow->chars->size(); x++) 290 | { 291 | if (done < (int)len) 292 | crow->cols->push_back(colours[done]); 293 | else 294 | crow->cols->push_back(7) ; /* white */ 295 | 296 | done += 1; 297 | } 298 | 299 | /* 300 | * those damn newlines. 301 | */ 302 | done += 1; 303 | } 304 | 305 | } 306 | 307 | 308 | /** 309 | * Get per-buffer data. 310 | */ 311 | std::string Buffer::get_data(std::string key) 312 | { 313 | return (m_data[key]); 314 | } 315 | 316 | 317 | /** 318 | * Set per-buffer data. 319 | */ 320 | void Buffer::set_data(std::string key, std::string value) 321 | { 322 | m_data[key] = value; 323 | } 324 | -------------------------------------------------------------------------------- /src/buffer.h: -------------------------------------------------------------------------------- 1 | /* buffer.h - Our buffer/row definitions. 2 | * 3 | * ----------------------------------------------------------------------- 4 | * 5 | * Copyright (C) 2016 Steve Kemp https://steve.kemp.fi/ 6 | * 7 | * All rights reserved. 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions are 11 | * met: 12 | * 13 | * * Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * 16 | * * Redistributions in binary form must reproduce the above copyright 17 | * notice, this list of conditions and the following disclaimer in the 18 | * documentation and/or other materials provided with the distribution. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | 34 | #pragma once 35 | 36 | #include 37 | #include 38 | #include 39 | 40 | 41 | /** 42 | * This structure represents a single line of text. 43 | */ 44 | class erow 45 | { 46 | public: 47 | /** 48 | * Constructor. 49 | */ 50 | erow(); 51 | 52 | /** 53 | * Destructor. 54 | */ 55 | ~erow(); 56 | 57 | public: 58 | /** 59 | * The text of the row, from the given character offset. 60 | */ 61 | std::wstring text(int offset); 62 | 63 | /* 64 | * The character at each position in this row. 65 | */ 66 | std::vector *chars; 67 | 68 | /* 69 | * The colour to draw at each position. 70 | */ 71 | std::vector *cols; 72 | }; 73 | 74 | 75 | 76 | /** 77 | * This class represents a buffer. 78 | * 79 | * The editor allows multiple buffers to be retrieved. 80 | * 81 | * Each buffer has: 82 | * 83 | * A name. 84 | * An "is modified" flag 85 | * A number of rows of content. 86 | * 87 | */ 88 | class Buffer 89 | { 90 | public: 91 | /** 92 | * Constructor 93 | */ 94 | Buffer(const char *bname); 95 | 96 | /** 97 | * Destructor 98 | */ 99 | ~Buffer(); 100 | 101 | public: 102 | 103 | /** 104 | * Remove all text from the buffer. 105 | */ 106 | void empty_buffer(); 107 | 108 | /** 109 | * Get the character offset of the given X,Y coordinate in our buffer. 110 | */ 111 | int pos2offset(int w_x, int w_y); 112 | 113 | /** 114 | * Is this buffer dirty? 115 | */ 116 | bool dirty(); 117 | 118 | /** 119 | * Mark the buffer as dirty. 120 | */ 121 | void set_dirty(bool state); 122 | 123 | /** 124 | * Get the name of the buffer. 125 | */ 126 | const char *get_name(); 127 | 128 | /** 129 | * Set the name of the buffer. 130 | */ 131 | void set_name(const char *name); 132 | 133 | /** 134 | * Get the buffer contents, as text. 135 | * 136 | * NOTE: NOT wide-text. 137 | */ 138 | std::string text(); 139 | 140 | /** 141 | * Update the colours of the current buffer, via the 142 | * result of the lua callback. 143 | */ 144 | void update_syntax(const char *colours, size_t len); 145 | 146 | /** 147 | * Get per-buffer data. 148 | */ 149 | std::string get_data(std::string key); 150 | 151 | /** 152 | * Set per-buffer data. 153 | */ 154 | void set_data(std::string key, std::string value); 155 | 156 | public: 157 | 158 | /* Cursor x and y position in characters */ 159 | int cx, cy; 160 | 161 | /* Offset of row/col displayed. */ 162 | int rowoff, coloff; 163 | 164 | /* the actual rows */ 165 | std::vector rows; 166 | 167 | /* Syntax-mode which is in-use. */ 168 | std::string m_syntax; 169 | 170 | /* mark */ 171 | int markx; 172 | int marky; 173 | 174 | private: 175 | /* Is this buffer dirty? */ 176 | bool m_dirty; 177 | 178 | /* The name of this buffer */ 179 | char *m_name; 180 | 181 | /* per-buffer key/value data */ 182 | std::unordered_map m_data; 183 | 184 | }; 185 | -------------------------------------------------------------------------------- /src/editor.cc: -------------------------------------------------------------------------------- 1 | /* editor.cc - Implementation of our editor class. 2 | * 3 | * ----------------------------------------------------------------------- 4 | * 5 | * Copyright (C) 2016 Steve Kemp https://steve.kemp.fi/ 6 | * 7 | * All rights reserved. 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions are 11 | * met: 12 | * 13 | * * Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * 16 | * * Redistributions in binary form must reproduce the above copyright 17 | * notice, this list of conditions and the following disclaimer in the 18 | * documentation and/or other materials provided with the distribution. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | #include "editor.h" 44 | #include "intro.h" 45 | #include "util.h" 46 | 47 | 48 | bool one_key_pressed = false; 49 | std::vector intro; 50 | 51 | /** 52 | * Constructor. 53 | */ 54 | Editor::Editor() 55 | { 56 | /** 57 | * Create a new state and ensure that we have a buffer 58 | * which we can work with. 59 | */ 60 | m_state = new editorState(); 61 | 62 | /* 63 | * Create a new buffer for messages. 64 | */ 65 | Buffer *tmp = new Buffer("*Messages*"); 66 | m_state->buffers.push_back(tmp); 67 | m_state->current_buffer = 0; 68 | 69 | /* 70 | * Ensure that our status-message is empty. 71 | */ 72 | memset(m_state->statusmsg, '\0', sizeof(m_state->statusmsg)); 73 | 74 | 75 | /* 76 | * Setup lua. 77 | */ 78 | m_lua = luaL_newstate(); 79 | luaopen_base(m_lua); 80 | luaL_openlibs(m_lua); 81 | 82 | /* 83 | * Bind functions. 84 | */ 85 | lua_register(m_lua, "at", at_lua); 86 | lua_register(m_lua, "buffer", buffer_lua); 87 | lua_register(m_lua, "buffer_data", buffer_data_lua); 88 | lua_register(m_lua, "buffer_name", buffer_name_lua); 89 | lua_register(m_lua, "buffers", buffers_lua); 90 | lua_register(m_lua, "create_buffer", create_buffer_lua); 91 | lua_register(m_lua, "delete", delete_lua); 92 | lua_register(m_lua, "directory_entries", directory_entries_lua); 93 | lua_register(m_lua, "dirty", dirty_lua); 94 | lua_register(m_lua, "eof", eof_lua); 95 | lua_register(m_lua, "eol", eol_lua); 96 | lua_register(m_lua, "exists", exists_lua); 97 | lua_register(m_lua, "exit", exit_lua); 98 | lua_register(m_lua, "height", height_lua); 99 | lua_register(m_lua, "insert", insert_lua); 100 | lua_register(m_lua, "key", key_lua); 101 | lua_register(m_lua, "kill_buffer", kill_buffer_lua); 102 | lua_register(m_lua, "mark", mark_lua); 103 | lua_register(m_lua, "menu", menu_lua); 104 | lua_register(m_lua, "move", move_lua); 105 | lua_register(m_lua, "open", open_lua); 106 | lua_register(m_lua, "point", point_lua); 107 | lua_register(m_lua, "prompt", prompt_lua); 108 | lua_register(m_lua, "save", save_lua); 109 | lua_register(m_lua, "search", search_lua); 110 | lua_register(m_lua, "selection", selection_lua); 111 | lua_register(m_lua, "sof", sof_lua); 112 | lua_register(m_lua, "sol", sol_lua); 113 | lua_register(m_lua, "status", status_lua); 114 | lua_register(m_lua, "syntax", syntax_lua); 115 | lua_register(m_lua, "text", text_lua); 116 | lua_register(m_lua, "update_colours", update_colours_lua); 117 | lua_register(m_lua, "width", width_lua); 118 | 119 | /* 120 | * register our colours 121 | */ 122 | lua_pushinteger(m_lua, 1); 123 | lua_setglobal(m_lua, "RED"); 124 | 125 | lua_pushinteger(m_lua, 2); 126 | lua_setglobal(m_lua, "GREEN"); 127 | 128 | lua_pushinteger(m_lua, 3); 129 | lua_setglobal(m_lua, "YELLOW"); 130 | 131 | lua_pushinteger(m_lua, 4); 132 | lua_setglobal(m_lua, "BLUE"); 133 | 134 | lua_pushinteger(m_lua, 5); 135 | lua_setglobal(m_lua, "MEGENTA"); 136 | 137 | lua_pushinteger(m_lua, 6); 138 | lua_setglobal(m_lua, "CYAN"); 139 | 140 | lua_pushinteger(m_lua, 7); 141 | lua_setglobal(m_lua, "WHITE"); 142 | 143 | lua_pushinteger(m_lua, 8); 144 | lua_setglobal(m_lua, "REV_RED"); 145 | 146 | lua_pushinteger(m_lua, 9); 147 | lua_setglobal(m_lua, "REV_GREEN"); 148 | 149 | lua_pushinteger(m_lua, 10); 150 | lua_setglobal(m_lua, "REV_YELLOW"); 151 | 152 | lua_pushinteger(m_lua, 11); 153 | lua_setglobal(m_lua, "REV_BLUE"); 154 | 155 | lua_pushinteger(m_lua, 12); 156 | lua_setglobal(m_lua, "REV_MAGENTA"); 157 | 158 | lua_pushinteger(m_lua, 13); 159 | lua_setglobal(m_lua, "REV_CYAN"); 160 | 161 | lua_pushstring(m_lua, KILUA_VERSION); 162 | lua_setglobal(m_lua, "KILUA_VERSION"); 163 | 164 | } 165 | 166 | 167 | /** 168 | * Destructor. 169 | */ 170 | Editor::~Editor() 171 | { 172 | delete (m_state); 173 | } 174 | 175 | 176 | /** 177 | * Called by the main application at launch-time. 178 | */ 179 | void Editor::load_files(std::vector files) 180 | { 181 | 182 | /* 183 | * If we have no files .. create a scratch one. 184 | */ 185 | if (files.size() > 0) 186 | { 187 | for (std::vector::iterator it = files.begin(); it != files.end(); ++it) 188 | { 189 | Buffer *tmp = new Buffer((*it).c_str()); 190 | m_state->buffers.push_back(tmp); 191 | m_state->current_buffer = m_state->buffers.size() - 1; 192 | 193 | call_lua("open", "s>", (*it).c_str()); 194 | } 195 | } 196 | else 197 | { 198 | Buffer *tmp = new Buffer("default"); 199 | m_state->buffers.push_back(tmp); 200 | m_state->current_buffer = m_state->buffers.size() - 1; 201 | } 202 | } 203 | 204 | 205 | /** 206 | * Run the main loop - polling for input, reacting to the same, and 207 | * refreshing the screen. 208 | */ 209 | void Editor::main_loop() 210 | { 211 | 212 | while (1) 213 | { 214 | unsigned int ch; 215 | 216 | int res = get_wch(&ch); 217 | 218 | /* 219 | * Chances are this was a timeout. 220 | * 221 | * So invoke the handler and redraw. 222 | */ 223 | if (res == ERR) 224 | { 225 | call_lua("on_idle", ">"); 226 | draw_screen(); 227 | continue; 228 | } 229 | 230 | /* 231 | * We've had at least one keystroke - so the welcome 232 | * message can go away. 233 | */ 234 | one_key_pressed = true; 235 | 236 | /* 237 | * Expand the key. 238 | */ 239 | const char *name = lookup_key(ch); 240 | 241 | 242 | if (strcmp(name, "KEY_RESIZE") == 0) 243 | continue; 244 | 245 | /* 246 | * This is a bit horrid. 247 | * 248 | * The intention is to ensure that Lua can process special 249 | * key-strokes, and they are not inserted. 250 | */ 251 | if (name && strlen(name) > 1 && 252 | ( 253 | (strncmp(name, "KEY_", 4) == 0) || 254 | (name[0] == '^') || 255 | (strncmp(name, "ESC", 3) == 0) 256 | )) 257 | { 258 | call_lua("on_key", "s>", name); 259 | } 260 | else 261 | { 262 | /* 263 | * Convert the character to a string, then call Lua. 264 | */ 265 | char *ascii = Util::wchar2ascii(ch); 266 | call_lua("on_key", "s>", ascii); 267 | delete []ascii; 268 | } 269 | 270 | /* 271 | * Draw the screen. 272 | */ 273 | draw_screen(); 274 | } 275 | } 276 | 277 | 278 | /** 279 | * Return the current buffer. 280 | */ 281 | Buffer *Editor::current_buffer() 282 | { 283 | return (m_state->buffers.at(m_state->current_buffer)); 284 | } 285 | 286 | 287 | /** 288 | * Return the text in the status-bar. 289 | */ 290 | char * Editor::get_status() 291 | { 292 | return (m_state->statusmsg); 293 | } 294 | 295 | 296 | /** 297 | * Set the status-bar text. 298 | */ 299 | void Editor::set_status(int log, const char *fmt, ...) 300 | { 301 | va_list ap; 302 | va_start(ap, fmt); 303 | memset(m_state->statusmsg, '\0', sizeof(m_state->statusmsg)); 304 | vsnprintf(m_state->statusmsg, sizeof(m_state->statusmsg) - 1, fmt, ap); 305 | va_end(ap); 306 | 307 | /* 308 | * Append the message to the *Messages* buffer. 309 | */ 310 | if (log) 311 | { 312 | int old = m_state->current_buffer; 313 | 314 | int b = buffer_by_name("*Messages*"); 315 | 316 | if (b != -1) 317 | { 318 | m_state->current_buffer = b; 319 | 320 | wchar_t *wide = Util::ascii2wide(m_state->statusmsg); 321 | int size = wcslen(wide); 322 | 323 | for (int i = 0; i < size; i++) 324 | { 325 | insert(wide[i]); 326 | } 327 | 328 | delete []wide; 329 | 330 | insert('\n'); 331 | m_state->current_buffer = old; 332 | } 333 | } 334 | } 335 | 336 | 337 | /** 338 | * Draw the screen, as well as the status-bar and the message-area. 339 | */ 340 | void Editor::draw_screen() 341 | { 342 | /* 343 | * Clear the screen. 344 | */ 345 | clear(); 346 | 347 | /* 348 | * The current buffer, and row-count. 349 | */ 350 | Buffer *cur = m_state->buffers.at(m_state->current_buffer); 351 | int rows = cur->rows.size(); 352 | 353 | /* 354 | * Width of screen. 355 | */ 356 | int w = width(); 357 | 358 | /* 359 | * Count of characters which are before the screen position. 360 | * 361 | * We use this to show the marked region. 362 | */ 363 | int count = 0; 364 | 365 | for (int y = 0; y < cur->rowoff; y++) 366 | { 367 | erow *row = cur->rows.at(y); 368 | int row_size = row->chars->size(); 369 | count += row_size + 1; 370 | } 371 | 372 | /* 373 | * The position of the point and mark. 374 | */ 375 | int m_pos = cur->pos2offset(cur->markx, cur->marky); 376 | int c_pos = cur->pos2offset(cur->cx + cur->coloff, cur->cy + cur->rowoff); 377 | 378 | /* 379 | * The character offsets - the characters between these 380 | * two numbers should be in reverse. 381 | */ 382 | int sel_min = std::min(m_pos, c_pos); 383 | int sel_max = std::max(m_pos, c_pos); 384 | 385 | /* 386 | * If the mark is not set disable the whole damn thing. 387 | */ 388 | if ((cur->markx == -1) && (cur->marky == -1)) 389 | { 390 | sel_min = -1; 391 | sel_max = -1; 392 | } 393 | 394 | 395 | /* 396 | * For each row .. 397 | */ 398 | for (int y = 0; y < m_state->screenrows(); y++) 399 | { 400 | /* 401 | * If this row is past the end of our list - draw "~" and exit. 402 | */ 403 | if ((y + cur->rowoff) >= rows) 404 | { 405 | std::wstring x; 406 | x += '~'; 407 | 408 | /* Reset to white */ 409 | color_set(7, NULL); 410 | mvwaddwstr(stdscr, y, 0, x.c_str()); 411 | continue; 412 | } 413 | 414 | /* 415 | * The row of characters. 416 | */ 417 | erow *row = cur->rows.at(y + cur->rowoff); 418 | int row_max = row->chars->size(); 419 | 420 | /* 421 | * For each possible position in the row 422 | */ 423 | int x = 0; 424 | 425 | for (int c = 0; c < row_max; c++) 426 | { 427 | /* 428 | * If this character is visible .. 429 | */ 430 | if ((c >= cur->coloff) && (c < (cur->coloff + w))) 431 | { 432 | /* 433 | * Default colour - white. 434 | */ 435 | int col = 7; 436 | 437 | /* 438 | * Get & set the colour. 439 | */ 440 | if (c < (int)row->cols->size()) 441 | col = row->cols->at(c); 442 | 443 | color_set(col, NULL); 444 | 445 | /* 446 | * Is the current character between the point 447 | * and the mark? If so enable the reverse-drawing. 448 | */ 449 | if (count >= sel_min && count <= sel_max) 450 | attron(A_STANDOUT); 451 | 452 | /* 453 | * Draw the character. 454 | */ 455 | std::wstring t = row->chars->at(c); 456 | 457 | /* 458 | * Is it a TAB? Change to space, because otherwise 459 | * trailing whitespace screws up. 460 | */ 461 | if (t.at(0) == '\t') 462 | t = ' '; 463 | 464 | /* 465 | * Draw the character, and reset the colour to white. 466 | */ 467 | mvwaddwstr(stdscr, y, x, t.c_str()); 468 | color_set(7, NULL); /* white */ 469 | 470 | /* 471 | * Is the current character between the point 472 | * and the mark? If so disable the reverse-drawing. 473 | */ 474 | if (count >= sel_min && count <= sel_max) 475 | attroff(A_STANDOUT); 476 | 477 | count += 1; 478 | 479 | x += 1; 480 | } 481 | else 482 | { 483 | /* 484 | * none visible character in the row, we must 485 | * account for it still. 486 | */ 487 | count += 1; 488 | } 489 | } 490 | 491 | count += 1; /*newline*/ 492 | } 493 | 494 | if ((rows == 1) && (one_key_pressed == false)) 495 | { 496 | /* 497 | * Setup the introductionary message. 498 | */ 499 | init_intro(); 500 | 501 | int row = 0; 502 | 503 | for (auto it = intro.begin(); it != intro.end() ; ++it) 504 | { 505 | mvwaddstr(stdscr, row, 0, (*it).c_str()); 506 | row += 1; 507 | } 508 | } 509 | 510 | 511 | 512 | /* 513 | * Get the status-bar from Lua, if we can. 514 | */ 515 | char* result = NULL; 516 | call_lua("get_status_bar", ">s", &result); 517 | std::string status; 518 | 519 | if (result) 520 | status = result; 521 | else 522 | status = "Please define 'get_status_bar()'"; 523 | 524 | while ((int)status.length() < m_state->screencols()) 525 | { 526 | status += " "; 527 | } 528 | 529 | /* 530 | * Enable reverse. 531 | */ 532 | attron(A_STANDOUT); 533 | mvwaddstr(stdscr, m_state->screenrows(), 0, status.c_str()); 534 | attroff(A_STANDOUT); 535 | 536 | /* 537 | * Draw the message-area. 538 | */ 539 | std::string s = get_status(); 540 | 541 | if ((int)s.length() > m_state->screencols()) 542 | s = s.substr(s.length() - m_state->screencols() + 1); 543 | 544 | while ((int)s.length() < m_state->screencols()) 545 | { 546 | s += " "; 547 | } 548 | 549 | mvwaddstr(stdscr, m_state->screenrows() + 1, 0, s.c_str()); 550 | 551 | /* 552 | * The cursor can't be in the bottom two lines. 553 | */ 554 | if (cur->cy >= (m_state->screenrows())) 555 | cur->cy = (m_state->screenrows() - 1); 556 | 557 | /* 558 | * Show the cursor in the right location. 559 | */ 560 | ::move(cur->cy, cur->cx); 561 | 562 | 563 | refresh(); 564 | } 565 | 566 | /* 567 | * Magic. 568 | */ 569 | void Editor::insert(wchar_t c) 570 | { 571 | /* 572 | * Get the current buffer. 573 | */ 574 | Buffer *cur = m_state->buffers.at(m_state->current_buffer); 575 | 576 | /* 577 | * Current offset 578 | */ 579 | int row = cur->cy + cur->rowoff; 580 | int col = cur->cx + cur->coloff; 581 | 582 | /* 583 | * The current row. 584 | */ 585 | erow *cur_row = cur->rows.at(row); 586 | 587 | /* 588 | * If inserting a newline that's the same as inserting 589 | * a new row. 590 | */ 591 | if (c == '\n') 592 | { 593 | /* 594 | * OK the user pressed RETURN. 595 | * 596 | * We know this means we need to create a new-line, which we'll insert. 597 | */ 598 | erow *new_row = new erow(); 599 | 600 | /* 601 | * Take the characters in the current row after the point. 602 | * 603 | * We're going to remove them, and insert them at the start 604 | * of the new row. 605 | */ 606 | std::vector tmp; 607 | 608 | /* 609 | * Copy the characters after the point. 610 | */ 611 | for (std::vector::iterator it = cur_row->chars->begin() + col; it != cur_row->chars->end(); ++it) 612 | { 613 | tmp.push_back(*it); 614 | } 615 | 616 | 617 | /* 618 | * Add the characters onto the new row. 619 | */ 620 | for (std::vector::iterator it = tmp.begin(); it != tmp.end(); ++it) 621 | { 622 | new_row->chars->push_back(*it); 623 | } 624 | 625 | /* 626 | * Erase the characters from the current row. 627 | */ 628 | cur_row->chars->erase(cur_row->chars->begin() + col, cur_row->chars->end()); 629 | 630 | /* 631 | * Insert the new row. 632 | */ 633 | cur->rows.insert(cur->rows.begin() + row + 1, new_row); 634 | 635 | /* 636 | * Because we've added a new row we need to move down one 637 | * row, and to the start of the next line. 638 | */ 639 | move("down"); 640 | sol_lua(NULL); 641 | return; 642 | } 643 | 644 | /* 645 | * Trying to insert a character at an impossible position? 646 | */ 647 | if (row > (int) cur->rows.size()) 648 | return ; 649 | 650 | 651 | /* 652 | * We have a row. Insert the character. 653 | */ 654 | std::wstring x; 655 | x += c; 656 | 657 | /* 658 | * Insert the new character at the correct position. 659 | */ 660 | cur_row->chars->insert(cur_row->chars->begin() + col, x); 661 | 662 | /* 663 | * Move right - this handles scrolling correctly. 664 | */ 665 | move("right"); 666 | 667 | } 668 | 669 | 670 | /* 671 | * More magic. 672 | */ 673 | void Editor::delete_char() 674 | { 675 | /* 676 | * There are two cases - deletion from the middle/end 677 | * of a row, and deletion at the start of the row. 678 | * 679 | * The first case is simple, we just find the current 680 | * row and erase one "character" from it. 681 | * 682 | * The second case is harder, we need to remove the 683 | * current row, and append the characters after the mark 684 | * to it. 685 | * 686 | * punt for now 687 | */ 688 | 689 | /* 690 | * Get the current buffer. 691 | */ 692 | Buffer *cur = m_state->buffers.at(m_state->current_buffer); 693 | 694 | /* 695 | * Current offset 696 | */ 697 | int row = cur->cy + cur->rowoff; 698 | int col = cur->cx + cur->coloff; 699 | 700 | /* 701 | * Deleting from the top of the file is impossible. 702 | */ 703 | if ((col == 0) && (row == 0)) 704 | { 705 | return; 706 | } 707 | 708 | if (col == 0) 709 | { 710 | 711 | /* 712 | * The current row, and the previous row. 713 | */ 714 | erow *p_row = cur->rows.at(row - 1); 715 | int p_len = p_row->chars->size(); 716 | erow *c_row = cur->rows.at(row); 717 | 718 | /* 719 | * For each character in the current row, append to the previous. 720 | */ 721 | for (std::vector::iterator it = c_row->chars->begin() + col; it != c_row->chars->end(); ++it) 722 | { 723 | p_row->chars->push_back(*it); 724 | } 725 | 726 | /* 727 | * Now delete the current row. 728 | */ 729 | cur->rows.erase(cur->rows.begin() + row); 730 | delete c_row; 731 | 732 | /* 733 | * Finally we need to move the cursor to the correct location. 734 | * 735 | * The correct location is the end of the old line. 736 | */ 737 | move("up"); 738 | sol_lua(NULL); 739 | 740 | while (p_len) 741 | { 742 | move("right"); 743 | p_len--; 744 | } 745 | 746 | return; 747 | } 748 | 749 | /* 750 | * deleting from the middle of a row. 751 | */ 752 | erow *cur_row = cur->rows.at(row); 753 | cur_row->chars->erase(cur_row->chars->begin() + col - 1); 754 | move("left"); 755 | } 756 | 757 | 758 | /** 759 | * Convert the given key to a human-readable version of it. 760 | */ 761 | const char *Editor::lookup_key(unsigned int c) 762 | { 763 | if (c == 0) 764 | return ("^ "); 765 | 766 | if (c == '\n') 767 | return ("ENTER"); 768 | 769 | if (c == 27) 770 | return ("ESC"); 771 | 772 | if (c == '\t') 773 | return ("TAB"); 774 | 775 | if (c == ' ') 776 | return ("SPACE"); 777 | 778 | return (keyname(c)); 779 | } 780 | 781 | 782 | /** 783 | * Call a lua function, with variadic arguments. 784 | */ 785 | void Editor::call_lua(const char *func, const char *sig, ...) 786 | { 787 | va_list vl; 788 | int narg, nres; 789 | 790 | va_start(vl, sig); 791 | 792 | lua_getglobal(m_lua, func); 793 | 794 | if (lua_isnil(m_lua, -1)) 795 | return; 796 | 797 | /* push arguments */ 798 | narg = 0; 799 | 800 | while (*sig) 801 | { 802 | /* push arguments */ 803 | switch (*sig++) 804 | { 805 | 806 | case 'd': /* double argument */ 807 | lua_pushnumber(m_lua, va_arg(vl, double)); 808 | break; 809 | 810 | case 'i': /* int argument */ 811 | lua_pushnumber(m_lua, va_arg(vl, int)); 812 | break; 813 | 814 | case 's': /* string argument */ 815 | lua_pushstring(m_lua, va_arg(vl, char *)); 816 | break; 817 | 818 | case '>': 819 | goto endwhile; 820 | 821 | default: 822 | { 823 | fprintf(stderr, "invalid option (%c)", *(sig - 1)); 824 | return; 825 | } 826 | } 827 | 828 | narg++; 829 | } 830 | 831 | endwhile: 832 | 833 | 834 | /* do the call */ 835 | nres = strlen(sig); /* number of expected results */ 836 | 837 | if (lua_pcall(m_lua, narg, nres, 0) != 0) /* do the call */ 838 | { 839 | fprintf(stderr, "error running function `%s': %s", func, lua_tostring(m_lua, -1)); 840 | return; 841 | } 842 | 843 | /* retrieve results */ 844 | nres = -nres; /* stack index of first result */ 845 | 846 | while (*sig) 847 | { 848 | /* get results */ 849 | switch (*sig++) 850 | { 851 | 852 | case 'd': /* double result */ 853 | if (!lua_isnumber(m_lua, nres)) 854 | { 855 | 856 | fprintf(stderr, "wrong result type: expected number"); 857 | return; 858 | } 859 | 860 | *va_arg(vl, double *) = lua_tonumber(m_lua, nres); 861 | break; 862 | 863 | case 'i': /* int result */ 864 | if (!lua_isnumber(m_lua, nres)) 865 | { 866 | fprintf(stderr, "wrong result type: expected number"); 867 | return; 868 | } 869 | 870 | *va_arg(vl, int *) = (int)lua_tonumber(m_lua, nres); 871 | break; 872 | 873 | case 's': /* string result */ 874 | if (!lua_isstring(m_lua, nres)) 875 | { 876 | fprintf(stderr, "wrong result type: expected string"); 877 | return; 878 | } 879 | 880 | *va_arg(vl, const char **) = lua_tostring(m_lua, nres); 881 | break; 882 | 883 | default: 884 | { 885 | fprintf(stderr, "invalid result-type"); 886 | return; 887 | } 888 | } 889 | 890 | nres++; 891 | } 892 | 893 | va_end(vl); 894 | 895 | } 896 | 897 | 898 | /** 899 | * Move the cursor to the given position, if possible. 900 | */ 901 | void Editor::warp(int x, int y) 902 | { 903 | 904 | if (y < 0) 905 | y = 0; 906 | 907 | if (x < 0) 908 | x = 0; 909 | 910 | /* 911 | * Move to top-left 912 | */ 913 | Buffer *buffer = current_buffer(); 914 | buffer->cx = buffer->coloff = 0; 915 | buffer->cy = buffer->rowoff = 0; 916 | 917 | /* 918 | * Is that where we wanted to go? 919 | */ 920 | if (x == 0 && y == 0) 921 | return; 922 | 923 | /* 924 | * Move down first - that should always work. 925 | */ 926 | while (y > 0) 927 | { 928 | move("down"); 929 | y -= 1; 930 | } 931 | 932 | /* 933 | * Now move right. 934 | */ 935 | while (x > 0) 936 | { 937 | move("right"); 938 | x -= 1; 939 | } 940 | } 941 | 942 | 943 | /** 944 | * Move the cursor in the given direction. 945 | */ 946 | void Editor::move(const char *direction) 947 | { 948 | Editor *e = Editor::instance(); 949 | Buffer *buffer = e->current_buffer(); 950 | 951 | int max_row = buffer->rows.size(); 952 | 953 | if (strcmp(direction, "up") == 0) 954 | { 955 | if (buffer->cy > 0) 956 | buffer->cy -= 1; 957 | else if (buffer->rowoff > 0) 958 | buffer->rowoff -= 1; 959 | } 960 | else if (strcmp(direction, "down") == 0) 961 | { 962 | if (buffer->cy + buffer->rowoff < max_row - 1) 963 | { 964 | buffer->cy += 1; 965 | 966 | if (buffer->cy >= e->height()) 967 | { 968 | buffer->cy = e->height(); 969 | buffer->rowoff++; 970 | } 971 | } 972 | } 973 | else if (strcmp(direction, "left") == 0) 974 | { 975 | if (buffer->cx == 0) 976 | { 977 | if (buffer->coloff > 0) 978 | { 979 | buffer->coloff --; 980 | } 981 | else 982 | { 983 | int filerow = buffer->cy + buffer->rowoff; 984 | 985 | if (filerow > 0) 986 | { 987 | buffer->cy--; 988 | 989 | erow *row = buffer->rows.at(buffer->cy + buffer->rowoff); 990 | 991 | if ((int)row->chars->size() < e->width()) 992 | { 993 | buffer->coloff = 0; 994 | buffer->cx = row->chars->size() ; 995 | } 996 | else 997 | { 998 | buffer->cx = e->width() - 1; 999 | buffer->coloff = row->chars->size() - e->width() + 1; 1000 | } 1001 | } 1002 | } 1003 | } 1004 | else 1005 | { 1006 | buffer->cx -= 1; 1007 | } 1008 | 1009 | } 1010 | else if (strcmp(direction, "right") == 0) 1011 | { 1012 | 1013 | int x = buffer->cx + buffer->coloff; 1014 | int y = buffer->cy + buffer->rowoff; 1015 | 1016 | erow *row = buffer->rows.at(y); 1017 | 1018 | if (x < (int)row->chars->size()) 1019 | { 1020 | buffer->cx += 1; 1021 | 1022 | if (buffer->cx >= e->width()) 1023 | { 1024 | buffer->cx -= 1; 1025 | buffer->coloff += 1; 1026 | } 1027 | } 1028 | else 1029 | { 1030 | /* 1031 | * Ensure moving right on the last row doesn't work. 1032 | */ 1033 | if (y + 1 < max_row) 1034 | { 1035 | buffer->cx = 0; 1036 | buffer->coloff = 0; 1037 | buffer->cy += 1; 1038 | 1039 | if (buffer->cy >= e->width()) 1040 | { 1041 | buffer->cy -= 1; 1042 | buffer->rowoff += 1; 1043 | } 1044 | } 1045 | } 1046 | } 1047 | 1048 | while (buffer->cy + buffer->rowoff >= max_row) 1049 | { 1050 | if (buffer->rowoff) 1051 | buffer->rowoff--; 1052 | else 1053 | buffer->cy--; 1054 | } 1055 | 1056 | erow *row = buffer->rows.at(buffer->cy + buffer->rowoff); 1057 | 1058 | if ((int)row->chars->size() < (buffer->cx + buffer->coloff)) 1059 | { 1060 | eol_lua(NULL); 1061 | } 1062 | } 1063 | 1064 | 1065 | /* 1066 | * Create a new buffer, and make it active. 1067 | */ 1068 | void Editor::new_buffer(const char *name) 1069 | { 1070 | /* 1071 | * Is this buffer named? 1072 | */ 1073 | Buffer *tmp; 1074 | 1075 | if (name != NULL) 1076 | { 1077 | tmp = new Buffer(name); 1078 | } 1079 | else 1080 | { 1081 | std::string t; 1082 | t = "*Buffer-" + std::to_string(count_buffers() + 1) + "*"; 1083 | tmp = new Buffer(t.c_str()); 1084 | } 1085 | 1086 | m_state->buffers.push_back(tmp); 1087 | m_state->current_buffer = count_buffers() - 1; 1088 | } 1089 | 1090 | /* 1091 | * Jump to the given buffer. 1092 | */ 1093 | void Editor::set_current_buffer(int off) 1094 | { 1095 | int max = count_buffers(); 1096 | 1097 | if (off >= 0 && off < max) 1098 | { 1099 | m_state->current_buffer = off; 1100 | } 1101 | } 1102 | 1103 | /* 1104 | * Get the index of the current buffer. 1105 | */ 1106 | int Editor::get_current_buffer() 1107 | { 1108 | return (m_state->current_buffer); 1109 | } 1110 | 1111 | /* 1112 | * Count the buffers. 1113 | */ 1114 | int Editor::count_buffers() 1115 | { 1116 | return (m_state->buffers.size()); 1117 | } 1118 | 1119 | /** 1120 | * Lookup the index of the buffer with the specified name. 1121 | * 1122 | * Returns -1 on failure. 1123 | */ 1124 | int Editor::buffer_by_name(const char *name) 1125 | { 1126 | int offset = 0; 1127 | 1128 | for (std::vector::iterator it = m_state->buffers.begin(); 1129 | it != m_state->buffers.end(); ++it) 1130 | { 1131 | Buffer *tmp = (*it); 1132 | const char *tmp_name = tmp->get_name(); 1133 | 1134 | if (tmp_name && (strcmp(tmp_name, name) == 0)) 1135 | { 1136 | return (offset); 1137 | } 1138 | 1139 | offset += 1; 1140 | } 1141 | 1142 | return (-1); 1143 | } 1144 | 1145 | 1146 | /** 1147 | * Remove the current buffer. 1148 | * 1149 | * If that means there are no remaining buffers then terminate. 1150 | */ 1151 | void Editor::kill_current_buffer() 1152 | { 1153 | /* 1154 | * Kill the current buffer. 1155 | */ 1156 | auto it = m_state->buffers.begin() + m_state->current_buffer; 1157 | delete *it; 1158 | m_state->buffers.erase(it); 1159 | 1160 | if (count_buffers() < 1) 1161 | { 1162 | endwin(); 1163 | exit(1); 1164 | } 1165 | 1166 | m_state->current_buffer = count_buffers() - 1; 1167 | } 1168 | 1169 | 1170 | /** 1171 | * Return all the buffers. 1172 | */ 1173 | std::vector Editor::get_buffers() 1174 | { 1175 | return (m_state->buffers); 1176 | } 1177 | 1178 | /** 1179 | * Return the height of the edit-area - which is the height of the terminal 1180 | * minus two rows for the footer. 1181 | */ 1182 | int Editor::height() 1183 | { 1184 | return (m_state->screenrows()); 1185 | } 1186 | 1187 | /** 1188 | * Return the width of the edit-area / terminal 1189 | */ 1190 | int Editor::width() 1191 | { 1192 | return (m_state->screencols()); 1193 | } 1194 | 1195 | 1196 | /** 1197 | * Execute the contents of the given file, as lua. 1198 | */ 1199 | int Editor::load_lua(const char *filename) 1200 | { 1201 | if (access(filename, 0) == 0) 1202 | { 1203 | int erred = luaL_dofile(m_lua, filename); 1204 | 1205 | if (erred) 1206 | { 1207 | endwin(); 1208 | 1209 | if (lua_isstring(m_lua, -1)) 1210 | fprintf(stderr, "%s\n", lua_tostring(m_lua, -1)); 1211 | 1212 | fprintf(stderr, "Failed to load %s - aborting\n", filename); 1213 | exit(1); 1214 | } 1215 | 1216 | return 1; 1217 | } 1218 | else 1219 | { 1220 | set_status(1, "Not loading Lua file %s, it is not present", filename); 1221 | 1222 | } 1223 | 1224 | return 0; 1225 | } 1226 | 1227 | /** 1228 | * Eval a given string. 1229 | */ 1230 | int Editor::eval_lua(const char *text) 1231 | { 1232 | 1233 | int erred = luaL_dostring(m_lua, text); 1234 | 1235 | if (erred) 1236 | { 1237 | if (lua_isstring(m_lua, -1)) 1238 | set_status(1, "%s", lua_tostring(m_lua, -1)); 1239 | 1240 | return (0); 1241 | } 1242 | 1243 | return 1; 1244 | } 1245 | 1246 | 1247 | /** 1248 | * Update the syntax-path 1249 | */ 1250 | void Editor::set_syntax_path(const char *path) 1251 | { 1252 | lua_pushstring(m_lua, path); 1253 | lua_setglobal(m_lua, "syntax_path"); 1254 | } 1255 | 1256 | 1257 | /** 1258 | * Get the hostname we're running on. 1259 | */ 1260 | char *Editor::hostname() 1261 | { 1262 | const char *env = getenv("HOSTNAME"); 1263 | 1264 | if (env != NULL) 1265 | return (strdup(env)); 1266 | 1267 | /* 1268 | * Get the short version. 1269 | */ 1270 | char res[1024]; 1271 | res[sizeof(res) - 1] = '\0'; 1272 | gethostname(res, sizeof(res) - 1); 1273 | 1274 | /* 1275 | * Attempt to get the full vrsion. 1276 | */ 1277 | struct hostent *hstnm; 1278 | hstnm = gethostbyname(res); 1279 | 1280 | if (hstnm) 1281 | { 1282 | /* 1283 | * Success. 1284 | */ 1285 | return (strdup(hstnm->h_name)); 1286 | } 1287 | else 1288 | { 1289 | /* 1290 | * Failure: Return the short-version. 1291 | */ 1292 | return (strdup(res)); 1293 | } 1294 | } 1295 | 1296 | 1297 | /** 1298 | * Prompt the user to choose from a series of strings. 1299 | * 1300 | * Return the index of the selected choice, -1 if cancelled. 1301 | */ 1302 | int Editor::menu(std::vector choices) 1303 | { 1304 | int selected = 0; 1305 | int offset = 0; 1306 | 1307 | int w = width(); 1308 | int h = height(); 1309 | 1310 | /* 1311 | * Hide the cursor & clear the screen. 1312 | */ 1313 | curs_set(0); 1314 | ::clear(); 1315 | 1316 | while (true) 1317 | { 1318 | int max = choices.size(); 1319 | 1320 | for (int i = 0; i < h ; i++) 1321 | { 1322 | int row = offset + i; 1323 | 1324 | if (selected == i) 1325 | attron(A_STANDOUT); 1326 | 1327 | /* 1328 | * Get the choice. 1329 | */ 1330 | std::string tmp; 1331 | 1332 | if (row < max) 1333 | tmp = choices.at(row); 1334 | else 1335 | tmp = "~"; 1336 | 1337 | /* 1338 | * Pad, where appropriate. 1339 | */ 1340 | while ((int)tmp.size() < w) 1341 | tmp += " "; 1342 | 1343 | mvwaddstr(stdscr, i, 0, tmp.c_str()); 1344 | 1345 | if (selected == i) 1346 | attroff(A_STANDOUT); 1347 | } 1348 | 1349 | /* 1350 | * poll for input. 1351 | */ 1352 | unsigned int ch; 1353 | 1354 | int res = get_wch(&ch); 1355 | 1356 | if (res == ERR) 1357 | continue; 1358 | 1359 | if (ch == '\n') 1360 | { 1361 | curs_set(1); 1362 | return (offset + selected); 1363 | } 1364 | 1365 | if (ch == 27) 1366 | { 1367 | curs_set(1); 1368 | return (-1); 1369 | } 1370 | 1371 | if (ch == KEY_HOME) 1372 | { 1373 | // start 1374 | selected = 0; 1375 | offset = 0; 1376 | } 1377 | 1378 | if (ch == KEY_END) 1379 | { 1380 | // end 1381 | selected = 0; 1382 | offset = max - 1; 1383 | } 1384 | 1385 | if ((ch == KEY_UP) || (ch == KEY_PPAGE)) 1386 | { 1387 | int times = (ch == KEY_PPAGE) ? h - 2 : 1; 1388 | 1389 | for (int i = 0; i < times; i++) 1390 | { 1391 | if (selected > 0) 1392 | selected -= 1; 1393 | else if (offset > 0) 1394 | offset -= 1; 1395 | } 1396 | } 1397 | 1398 | /* 1399 | * TAB moves down, because this menu-code is used in 1400 | * TAB-completion and that saves a hand-movement. 1401 | */ 1402 | if ((ch == KEY_DOWN) || (ch == KEY_NPAGE) || (ch == '\t')) 1403 | { 1404 | int times = (ch == KEY_NPAGE) ? h - 2 : 1; 1405 | 1406 | for (int i = 0; i < times; i++) 1407 | { 1408 | if (selected + offset < max - 1) 1409 | selected += 1; 1410 | 1411 | if (selected >= h) 1412 | { 1413 | selected = h - 1; 1414 | 1415 | if (selected + offset < max - 1) 1416 | offset += 1; 1417 | } 1418 | } 1419 | } 1420 | 1421 | } 1422 | 1423 | /* 1424 | * Never reached. 1425 | */ 1426 | return 0; 1427 | 1428 | } 1429 | 1430 | /** 1431 | * Get the selected text. 1432 | */ 1433 | std::wstring Editor::get_selection() 1434 | { 1435 | std::wstring result; 1436 | 1437 | /* 1438 | * The current buffer, and row-count. 1439 | */ 1440 | Buffer *cur = m_state->buffers.at(m_state->current_buffer); 1441 | 1442 | /* 1443 | * If there is no mark - return early. 1444 | */ 1445 | if ((cur->markx == -1) && (cur->marky == -1)) 1446 | return result; 1447 | 1448 | /* 1449 | * Count the rows. 1450 | */ 1451 | int rows = cur->rows.size(); 1452 | 1453 | /* 1454 | * The position of the point and mark. 1455 | */ 1456 | int m_pos = cur->pos2offset(cur->markx, cur->marky); 1457 | int c_pos = cur->pos2offset(cur->cx + cur->coloff, cur->cy + cur->rowoff); 1458 | 1459 | /* 1460 | * The character offsets - the characters between these two offsets 1461 | * into the buffer are the selection. 1462 | */ 1463 | int sel_min = std::min(m_pos, c_pos); 1464 | int sel_max = std::max(m_pos, c_pos); 1465 | 1466 | /* 1467 | * Now build up the selection. 1468 | */ 1469 | int count = 0; 1470 | 1471 | for (int y = 0; y < rows; y++) 1472 | { 1473 | erow *row = cur->rows.at(y); 1474 | int row_size = row->chars->size(); 1475 | 1476 | /* 1477 | * NOTE: We add one character to the row 1478 | * to cope with the trailing newline. 1479 | */ 1480 | for (int x = 0; x < row_size + 1; x++) 1481 | { 1482 | if (count >= sel_min && count <= sel_max) 1483 | { 1484 | if (x < row_size) 1485 | result += row->chars->at(x); 1486 | else 1487 | result += '\n'; 1488 | } 1489 | 1490 | count += 1; 1491 | } 1492 | } 1493 | 1494 | return (result); 1495 | } 1496 | -------------------------------------------------------------------------------- /src/editor.h: -------------------------------------------------------------------------------- 1 | /* editor.h - Our editor class. 2 | * 3 | * ----------------------------------------------------------------------- 4 | * 5 | * Copyright (C) 2016 Steve Kemp https://steve.kemp.fi/ 6 | * 7 | * All rights reserved. 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions are 11 | * met: 12 | * 13 | * * Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * 16 | * * Redistributions in binary form must reproduce the above copyright 17 | * notice, this list of conditions and the following disclaimer in the 18 | * documentation and/or other materials provided with the distribution. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | #pragma once 34 | 35 | #include 36 | #include 37 | #include 38 | 39 | 40 | #include "buffer.h" 41 | #include "lua_primitives.h" 42 | #include "singleton.h" 43 | 44 | 45 | 46 | /** 47 | * This structure represents the global state of the editor. 48 | */ 49 | class editorState 50 | { 51 | 52 | public: 53 | /* 54 | * Number of rows that we can show. 55 | */ 56 | int screenrows() 57 | { 58 | struct winsize w; 59 | ioctl(0, TIOCGWINSZ, &w); 60 | return (w.ws_row - 2); 61 | }; 62 | 63 | /* 64 | * Number of cols that we can show 65 | */ 66 | int screencols() 67 | { 68 | struct winsize w; 69 | ioctl(0, TIOCGWINSZ, &w); 70 | return (w.ws_col); 71 | 72 | } 73 | 74 | /* 75 | * The status-message 76 | */ 77 | char statusmsg[255]; 78 | 79 | /* 80 | * The buffers we have. 81 | */ 82 | std::vector buffers; 83 | 84 | /* 85 | * The currently selected buffer. 86 | */ 87 | int current_buffer ; 88 | 89 | }; 90 | 91 | 92 | /** 93 | * The editor instance, which is a singleton. 94 | * 95 | * An editor instance contains a series of buffer-objects, as well 96 | * as a global Lua handle. 97 | * 98 | */ 99 | class Editor : public Singleton 100 | { 101 | public: 102 | /** 103 | * Constructor. 104 | */ 105 | Editor(); 106 | 107 | /** 108 | * Destructor. 109 | */ 110 | ~Editor(); 111 | 112 | public: 113 | /** 114 | * Called when the program is launched. 115 | * 116 | * If we've been given an array of files: 117 | * * For each file, create a buffer and open it. 118 | * Otherwise create a single buffer for working with. 119 | */ 120 | void load_files(std::vector files); 121 | 122 | /** 123 | * The main loop. 124 | * 125 | * Read keys constantly, pass them to Lua, then refresh the display. 126 | * 127 | */ 128 | void main_loop(); 129 | 130 | /** 131 | * Insert the given character into the current position in the 132 | * buffer. 133 | */ 134 | void insert(wchar_t c); 135 | 136 | /** 137 | * Delete one character, backwards, from the current position. 138 | */ 139 | void delete_char(); 140 | 141 | /** 142 | * Get the current buffer. 143 | * 144 | * Public so that the `move` primitive, etc, can access it. 145 | */ 146 | Buffer *current_buffer(); 147 | 148 | /** 149 | * Lookup the index of the buffer with the specified name. 150 | * 151 | * Returns -1 on failure. 152 | */ 153 | int buffer_by_name(const char *name); 154 | 155 | /** 156 | * Get the status-text. 157 | */ 158 | char * get_status(); 159 | 160 | /** 161 | * Update the status-text. 162 | */ 163 | void set_status(int log, const char *fmt, ...); 164 | 165 | 166 | /** 167 | * Update the syntax of the buffer. 168 | */ 169 | void update_syntax(); 170 | 171 | /** 172 | * Clear the screen and redraw it. 173 | */ 174 | void draw_screen(); 175 | 176 | /** 177 | * Get the height of our editing area. 178 | */ 179 | int height(); 180 | 181 | /** 182 | * Get the width of our editing area. 183 | */ 184 | int width(); 185 | 186 | /** 187 | * Call lua 188 | */ 189 | void call_lua(const char *func, const char *sig, ...); 190 | 191 | /** 192 | * Load a Lua file, if it exists, and execute it. 193 | */ 194 | int load_lua(const char *filename); 195 | 196 | /** 197 | * Eval a given string. 198 | */ 199 | int eval_lua(const char *text); 200 | 201 | /** 202 | * Update the syntax-path 203 | */ 204 | void set_syntax_path(const char *path); 205 | 206 | /** 207 | * Move the cursor to the given position, if possible. 208 | */ 209 | void warp(int x, int y); 210 | 211 | /** 212 | * Move the cursor in the given direction. 213 | */ 214 | void move(const char *direction); 215 | 216 | /** 217 | * Buffer handling. 218 | */ 219 | int get_current_buffer(); 220 | int count_buffers(); 221 | void set_current_buffer(int off); 222 | void new_buffer(const char *name = "unnamed"); 223 | void kill_current_buffer(); 224 | std::vector get_buffers(); 225 | 226 | /** 227 | * Get the hostname we're running on. 228 | */ 229 | char *hostname(); 230 | 231 | /** 232 | * Prompt the user to choose from a series of strings. 233 | * 234 | * Return the index of the selected choice, -1 if cancelled. 235 | */ 236 | int menu(std::vector choices); 237 | 238 | /** 239 | * Get the selected text. 240 | */ 241 | std::wstring get_selection(); 242 | 243 | private: 244 | 245 | /** 246 | * Lookup the long-name for the given key. 247 | */ 248 | const char *lookup_key(unsigned int c); 249 | 250 | /** 251 | * Our state. 252 | */ 253 | editorState *m_state; 254 | 255 | /** 256 | * Our lua object. 257 | */ 258 | lua_State * m_lua; 259 | }; 260 | -------------------------------------------------------------------------------- /src/intro.h: -------------------------------------------------------------------------------- 1 | /* intro.h - Holder for our welcome-message. 2 | * 3 | * ----------------------------------------------------------------------- 4 | * 5 | * Copyright (C) 2016 Steve Kemp https://steve.kemp.fi/ 6 | * 7 | * All rights reserved. 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions are 11 | * met: 12 | * 13 | * * Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * 16 | * * Redistributions in binary form must reproduce the above copyright 17 | * notice, this list of conditions and the following disclaimer in the 18 | * documentation and/or other materials provided with the distribution. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | 34 | #pragma once 35 | 36 | #include 37 | #include 38 | 39 | /* 40 | * This flag is set the first time a key is successfully pressed. 41 | * 42 | * It means that our buffer is no longer empty. 43 | */ 44 | extern bool one_key_pressed; 45 | 46 | 47 | /* 48 | * This array contains text that is drawn when: 49 | * 50 | * * The default-buffer is empty. 51 | * 52 | * * There has not been a single key-press. 53 | */ 54 | extern std::vector intro; 55 | 56 | 57 | /* 58 | * Populate the default message. 59 | * 60 | * This message cannot be larger than the display-size of the 61 | * editor - since there is no support for scrolling it. 62 | */ 63 | void init_intro() 64 | { 65 | if (intro.size() > 0) 66 | return; 67 | 68 | /* 69 | * Show the banner. 70 | */ 71 | intro.push_back("kilua v" KILUA_VERSION); 72 | intro.push_back("----------"); 73 | intro.push_back(" "); 74 | intro.push_back("Kilua was put together by Steve Kemp ,"); 75 | intro.push_back("based upon the prototype editor Antirez wrote and introduced"); 76 | intro.push_back("upon his blog ."); 77 | intro.push_back(" "); 78 | intro.push_back(" "); 79 | intro.push_back("Major keybindings"); 80 | intro.push_back("-----------------"); 81 | intro.push_back(" "); 82 | intro.push_back(" Exit | Ctrl-x Ctrl-c"); 83 | intro.push_back(" Create a new buffer | Ctrl-x c"); 84 | intro.push_back(" Switch buffers | Ctrl-x b"); 85 | intro.push_back(" Open a file | Ctrl-x Ctrl-f"); 86 | intro.push_back(" Save a file | Ctrl-x Ctrl-s"); 87 | intro.push_back(" Execute Lua code | M-x"); 88 | intro.push_back(" "); 89 | intro.push_back(" "); 90 | intro.push_back("For further details please see the project page on github:"); 91 | intro.push_back(" "); 92 | intro.push_back(" https://github.com/skx/kilua"); 93 | intro.push_back(" "); 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/lua_buffers.cc: -------------------------------------------------------------------------------- 1 | /* lua_buffers.cc - Implementation of our buffer-related lua primitives. 2 | * 3 | * ----------------------------------------------------------------------- 4 | * 5 | * Copyright (C) 2016 Steve Kemp https://steve.kemp.fi/ 6 | * 7 | * All rights reserved. 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions are 11 | * met: 12 | * 13 | * * Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * 16 | * * Redistributions in binary form must reproduce the above copyright 17 | * notice, this list of conditions and the following disclaimer in the 18 | * documentation and/or other materials provided with the distribution. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include "editor.h" 38 | #include "lua_primitives.h" 39 | 40 | 41 | /* 42 | * Get/Set per-buffer data. 43 | */ 44 | int buffer_data_lua(lua_State *L) 45 | { 46 | /* 47 | * Get the buffer. 48 | */ 49 | Editor *e = Editor::instance(); 50 | Buffer *buffer = e->current_buffer(); 51 | 52 | /* 53 | * One argument is a lookup. 54 | */ 55 | if (lua_gettop(L) == 1) 56 | { 57 | const char *key = lua_tostring(L, -1); 58 | lua_pushstring(L, buffer->get_data(key).c_str()); 59 | return 1; 60 | } 61 | 62 | /* 63 | * Two arguments means to set. 64 | */ 65 | if (lua_gettop(L) == 2) 66 | { 67 | const char *key = lua_tostring(L, -2); 68 | const char *val = lua_tostring(L, -1); 69 | buffer->set_data(key, val); 70 | return 0; 71 | } 72 | 73 | /* 74 | * Anything else is an error 75 | */ 76 | e->set_status(1, "Invalid argument count!"); 77 | return 0; 78 | 79 | 80 | } 81 | 82 | /* 83 | * Get/Set current buffer. 84 | */ 85 | int buffer_lua(lua_State *L) 86 | { 87 | Editor *e = Editor::instance(); 88 | 89 | /* 90 | * If called with a number, then select the given buffer. 91 | */ 92 | if (lua_isnumber(L, -1)) 93 | { 94 | int off = lua_tonumber(L, -1); 95 | e->set_current_buffer(off); 96 | } 97 | 98 | /* 99 | * If called with a string then select the buffer 100 | * by name, and return the offset. 101 | */ 102 | if (lua_isstring(L, -1)) 103 | { 104 | const char *name = lua_tostring(L, -1); 105 | int off = e->buffer_by_name(name); 106 | 107 | if (off != -1) 108 | e->set_current_buffer(off); 109 | 110 | lua_pushnumber(L, off); 111 | return 1; 112 | } 113 | 114 | 115 | /* 116 | * Return the number of the current buffer. 117 | */ 118 | int cur = e->get_current_buffer(); 119 | lua_pushnumber(L, cur); 120 | return 1; 121 | } 122 | 123 | 124 | /* 125 | * Get/Set the name of the buffer. 126 | */ 127 | int buffer_name_lua(lua_State *L) 128 | { 129 | Editor *e = Editor::instance(); 130 | Buffer *buffer = e->current_buffer(); 131 | 132 | const char *name = lua_tostring(L, -1); 133 | 134 | if (name) 135 | { 136 | buffer->set_name(name); 137 | } 138 | 139 | lua_pushstring(L, buffer->get_name()); 140 | return 1; 141 | } 142 | 143 | 144 | /* 145 | * Return a table of all known buffers. 146 | */ 147 | int buffers_lua(lua_State *L) 148 | { 149 | Editor *e = Editor::instance(); 150 | std::vectorbuffers = e->get_buffers(); 151 | 152 | lua_createtable(L, buffers.size(), 0); 153 | 154 | 155 | for (int i = 0; i < (int)buffers.size(); i++) 156 | { 157 | Buffer *b = buffers.at(i); 158 | lua_pushstring(L, b->get_name()); 159 | lua_rawseti(L, -2, i + 1); 160 | } 161 | 162 | return 1; 163 | } 164 | 165 | 166 | /* 167 | * Create a new buffer. 168 | */ 169 | int create_buffer_lua(lua_State *L) 170 | { 171 | /* 172 | * The name of the buffer. 173 | */ 174 | const char *name = lua_tostring(L, -1); 175 | 176 | Editor *e = Editor::instance(); 177 | e->new_buffer(name); 178 | return 0; 179 | } 180 | 181 | 182 | /* 183 | * Kill the current buffer. 184 | */ 185 | int kill_buffer_lua(lua_State *L) 186 | { 187 | (void)L; 188 | Editor *e = Editor::instance(); 189 | e->kill_current_buffer(); 190 | return 0; 191 | } 192 | -------------------------------------------------------------------------------- /src/lua_core.cc: -------------------------------------------------------------------------------- 1 | /* lua_core.cc - Implementation of our core lua primitives. 2 | * 3 | * ----------------------------------------------------------------------- 4 | * 5 | * Copyright (C) 2016 Steve Kemp https://steve.kemp.fi/ 6 | * 7 | * All rights reserved. 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions are 11 | * met: 12 | * 13 | * * Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * 16 | * * Redistributions in binary form must reproduce the above copyright 17 | * notice, this list of conditions and the following disclaimer in the 18 | * documentation and/or other materials provided with the distribution. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #include "editor.h" 40 | #include "lua_primitives.h" 41 | #include "util.h" 42 | 43 | 44 | 45 | /** 46 | * Delete a character. 47 | */ 48 | int delete_lua(lua_State *L) 49 | { 50 | (void)L; 51 | Editor *e = Editor::instance(); 52 | e->delete_char(); 53 | 54 | /* 55 | * The buffer is now dirty and needs to be re-rendered. 56 | */ 57 | Buffer *buffer = e->current_buffer(); 58 | buffer->set_dirty(true); 59 | 60 | return 0; 61 | } 62 | 63 | 64 | /** 65 | * Is the current buffer dirty? 66 | */ 67 | int dirty_lua(lua_State *L) 68 | { 69 | Editor *e = Editor::instance(); 70 | Buffer *buffer = e->current_buffer(); 71 | 72 | if (buffer->dirty()) 73 | lua_pushboolean(L, 1); 74 | else 75 | lua_pushboolean(L, 0); 76 | 77 | return 1; 78 | } 79 | 80 | 81 | /** 82 | * Exit the application. 83 | */ 84 | int exit_lua(lua_State *L) 85 | { 86 | (void)L; 87 | endwin(); 88 | exit(0); 89 | return 0; 90 | } 91 | 92 | 93 | /** 94 | * Insert a character-string. 95 | */ 96 | int insert_lua(lua_State *L) 97 | { 98 | Editor *e = Editor::instance(); 99 | const char *str = lua_tostring(L, -1); 100 | 101 | if (str == NULL) 102 | return 0; 103 | 104 | /* 105 | * Convert the input to wide characters. 106 | */ 107 | wchar_t *wide = Util::ascii2wide(str); 108 | int size = wcslen(wide); 109 | 110 | for (int i = 0; i < size ; i++) 111 | e->insert(wide[i]); 112 | 113 | delete []wide; 114 | 115 | /* 116 | * The buffer is now dirty and needs to be re-rendered. 117 | */ 118 | Buffer *buffer = e->current_buffer(); 119 | buffer->set_dirty(true); 120 | return 0; 121 | } 122 | 123 | 124 | /** 125 | * Read a single (wide) key. 126 | */ 127 | int key_lua(lua_State *L) 128 | { 129 | Editor *e = Editor::instance(); 130 | 131 | while (1) 132 | { 133 | e->draw_screen(); 134 | unsigned int ch; 135 | 136 | int res = get_wch(&ch); 137 | 138 | /* 139 | * Chances are this was a timeout. 140 | * 141 | * So invoke the handler and redraw. 142 | */ 143 | if (res == ERR) 144 | { 145 | } 146 | else 147 | { 148 | /* 149 | * Convert the character to a string. 150 | */ 151 | char *ascii = Util::wchar2ascii(ch); 152 | lua_pushstring(L, ascii); 153 | delete []ascii; 154 | return 1; 155 | } 156 | } 157 | 158 | } 159 | 160 | 161 | /* 162 | * Get/Set the mark. 163 | */ 164 | int mark_lua(lua_State *L) 165 | { 166 | Editor *e = Editor::instance(); 167 | Buffer *buffer = e->current_buffer(); 168 | 169 | if (lua_isnumber(L, -2) && lua_isnumber(L, -1)) 170 | { 171 | int y = lua_tonumber(L, -1); 172 | int x = lua_tonumber(L, -2); 173 | 174 | buffer->markx = x; 175 | buffer->marky = y; 176 | } 177 | 178 | lua_pushnumber(L, buffer->markx); 179 | lua_pushnumber(L, buffer->marky); 180 | return 2; 181 | } 182 | 183 | 184 | /** 185 | * Given a table of strings let the user select one, via a menu. 186 | */ 187 | int menu_lua(lua_State *L) 188 | { 189 | Editor *e = Editor::instance(); 190 | 191 | if (!lua_istable(L, 1)) 192 | { 193 | e->set_status(1, "Table expected!"); 194 | return 0; 195 | } 196 | 197 | /* 198 | * Build up the list of choices the user has submitted. 199 | */ 200 | std::vector < std::string > choices; 201 | 202 | lua_pushnil(L); 203 | 204 | while (lua_next(L, -2)) 205 | { 206 | const char *entry = lua_tostring(L, -1); 207 | choices.push_back(entry); 208 | lua_pop(L, 1); 209 | } 210 | 211 | /* 212 | * Now the user will choose an entry, interactively. 213 | */ 214 | int ret = e->menu(choices); 215 | lua_pushinteger(L, ret); 216 | return 1; 217 | } 218 | 219 | 220 | /* 221 | * Get/Set the point. 222 | */ 223 | int point_lua(lua_State *L) 224 | { 225 | Editor *e = Editor::instance(); 226 | Buffer *buffer = e->current_buffer(); 227 | 228 | if (lua_isnumber(L, -2) && lua_isnumber(L, -1)) 229 | { 230 | int y = lua_tonumber(L, -1); 231 | int x = lua_tonumber(L, -2); 232 | 233 | e->warp(x, y); 234 | } 235 | 236 | lua_pushnumber(L, buffer->cx + buffer->coloff); 237 | lua_pushnumber(L, buffer->cy + buffer->rowoff); 238 | return 2; 239 | } 240 | 241 | 242 | /* 243 | * Prompt for input in the status-area. 244 | */ 245 | int prompt_lua(lua_State *L) 246 | { 247 | /* 248 | * Get the prompt 249 | */ 250 | const char *prompt = lua_tostring(L, -1); 251 | 252 | /* 253 | * Get the editor. 254 | */ 255 | Editor *e = Editor::instance(); 256 | 257 | /* 258 | * Input buffer, and current offset. 259 | */ 260 | wchar_t input[200]; 261 | memset(input, '\0', sizeof(input)); 262 | int len = 0; 263 | 264 | /* 265 | * combined, what we show to the status. 266 | */ 267 | while (1) 268 | { 269 | /* 270 | * Build up the combined string of "prompt" + current input 271 | */ 272 | int c_len = wcslen(input) + strlen(prompt); 273 | char *c_txt = (char *)malloc(c_len + 1); 274 | 275 | sprintf(c_txt, "%s%ls", prompt, input); 276 | 277 | /* 278 | * Display the new prompt. 279 | */ 280 | e->set_status(0, c_txt); 281 | e->draw_screen(); 282 | free(c_txt); 283 | 284 | /* 285 | * cap at last-x if the input is too long. 286 | */ 287 | int y = len + strlen(prompt); 288 | 289 | if (y > e->width()) 290 | { 291 | y = e->width() - 1; 292 | } 293 | 294 | /* 295 | * Show the cursor at the right place. 296 | */ 297 | move(e->height() + 1, y); 298 | 299 | /* 300 | * poll for input. 301 | */ 302 | unsigned int ch; 303 | 304 | int res = get_wch(&ch); 305 | 306 | if (res == ERR) 307 | continue; 308 | 309 | if (ch == '\t') 310 | { 311 | /* 312 | * Pass the current text to the callback. 313 | */ 314 | char *current = Util::wide2ascii(input); 315 | 316 | /* 317 | * Call the handler. 318 | */ 319 | char *completed = NULL; 320 | 321 | e->call_lua("on_complete", "s>s", current, &completed); 322 | 323 | if (completed != NULL) 324 | { 325 | wchar_t *tmp = Util::ascii2wide(completed); 326 | wcscpy(input, tmp); 327 | len = wcslen(tmp); 328 | delete[]tmp; 329 | } 330 | 331 | delete []current; 332 | 333 | } 334 | else if (ch == '\n') 335 | { 336 | e->set_status(0, ""); 337 | 338 | char *out = Util::wide2ascii(input); 339 | lua_pushstring(L, out); 340 | delete []out; 341 | return 1; 342 | } 343 | else if (ch == KEY_BACKSPACE) 344 | { 345 | len --; 346 | 347 | if (len < 0) 348 | len = 0; 349 | 350 | input[len] = '\0'; 351 | 352 | } 353 | else if (ch == 27) 354 | { 355 | e->set_status(0, "Cancelled"); 356 | return 0; 357 | } 358 | else if (len < (int)sizeof(input)) 359 | { 360 | if (isprint(ch)) 361 | { 362 | input[len] = ch; 363 | len ++; 364 | } 365 | } 366 | } 367 | 368 | return 0; 369 | } 370 | 371 | 372 | /* 373 | * Open a file in Lua. 374 | */ 375 | int open_lua(lua_State *L) 376 | { 377 | Editor *e = Editor::instance(); 378 | Buffer *buffer = e->current_buffer(); 379 | buffer->empty_buffer(); 380 | 381 | /* 382 | * Name of the buffer. 383 | */ 384 | const char *path = buffer->get_name(); 385 | 386 | /* 387 | * Did we get a different name? 388 | */ 389 | const char *new_name = lua_tostring(L, -1); 390 | 391 | if (new_name != NULL) 392 | { 393 | buffer->set_name(new_name); 394 | path = new_name; 395 | } 396 | 397 | FILE *input; 398 | 399 | if ((input = fopen(path, "r")) != NULL) 400 | { 401 | wchar_t c; 402 | 403 | while (1) 404 | { 405 | c = fgetwc(input); 406 | 407 | if (c == (wchar_t)WEOF) 408 | break; 409 | 410 | e->insert(c); 411 | } 412 | 413 | fclose(input); 414 | } 415 | else 416 | { 417 | e->set_status(1, "Failed to open %s", path); 418 | } 419 | 420 | /* 421 | * Call the handler. 422 | */ 423 | e->call_lua("on_loaded", "s>", path); 424 | 425 | /* 426 | * Move to start of file. 427 | */ 428 | sof_lua(NULL); 429 | buffer->set_dirty(false); 430 | return (0); 431 | } 432 | 433 | 434 | /** 435 | * Save the current file. 436 | */ 437 | int save_lua(lua_State *L) 438 | { 439 | Editor *e = Editor::instance(); 440 | Buffer *buffer = e->current_buffer(); 441 | 442 | 443 | /* 444 | * Name of the buffer. 445 | */ 446 | const char *path = buffer->get_name(); 447 | 448 | /* 449 | * Did we get a different name? 450 | */ 451 | const char *new_name = lua_tostring(L, -1); 452 | 453 | if (new_name != NULL) 454 | { 455 | buffer->set_name(new_name); 456 | path = new_name; 457 | } 458 | 459 | 460 | /* 461 | * Call the pre-save handler. 462 | */ 463 | e->call_lua("on_save", ">"); 464 | 465 | 466 | FILE *handle; 467 | 468 | if ((handle = fopen(path, "w")) == NULL) 469 | { 470 | e->set_status(1, "Failed to open %s for writing", path); 471 | return 0; 472 | } 473 | 474 | /* 475 | * For each row. 476 | */ 477 | int rows = buffer->rows.size(); 478 | 479 | for (int y = 0; y < rows; y++) 480 | { 481 | /* 482 | * For each character 483 | */ 484 | int chars = buffer->rows.at(y)->chars->size(); 485 | 486 | for (int x = 0; x < chars; x++) 487 | { 488 | std::wstring chr = buffer->rows.at(y)->chars->at(x); 489 | fprintf(handle, "%ls", chr.c_str()); 490 | } 491 | 492 | fprintf(handle, "\n"); 493 | } 494 | 495 | fclose(handle); 496 | 497 | /* 498 | * Call the post-save handler. 499 | */ 500 | e->call_lua("on_saved", "s>", path); 501 | 502 | e->set_status(0, ""); 503 | 504 | buffer->set_dirty(false); 505 | return (0); 506 | } 507 | 508 | 509 | /* 510 | * Search (fowards) for a regexp 511 | */ 512 | int search_lua(lua_State *L) 513 | { 514 | Editor *e = Editor::instance(); 515 | Buffer *buffer = e->current_buffer(); 516 | 517 | /* 518 | * Get the search pattern. 519 | */ 520 | const char *pattern = lua_tostring(L, -1); 521 | 522 | if (pattern == NULL) 523 | { 524 | e->set_status(1, "There was no regular expression supplied!"); 525 | return 0; 526 | } 527 | 528 | /* 529 | * Compile the pattern as a regular expression. 530 | */ 531 | regex_t regex; 532 | 533 | if (regcomp(®ex, pattern, REG_EXTENDED | REG_ICASE) != 0) 534 | { 535 | e->set_status(1, "Failed to compile %s as a regular expression!", pattern); 536 | return 0; 537 | }; 538 | 539 | /* 540 | * Count the number of rows we have in the buffer. 541 | */ 542 | int rows = buffer->rows.size(); 543 | 544 | /* 545 | * The first line is special. 546 | */ 547 | bool first = true; 548 | 549 | /* 550 | * Searching fowwards .. 551 | */ 552 | int step = 1; 553 | 554 | /* 555 | * The starting offset into the buffer. 556 | */ 557 | int offset = buffer->cy + buffer->rowoff; 558 | 559 | /* 560 | * For each row in the buffer 561 | * 562 | * NOTE: We deliberately search for ONE TOO MANY rows here. 563 | * 564 | * THis means if the point is a "skx:[POINT]" a search for 565 | * "^skx" will match. 566 | * 567 | * Since we start searching the first line (the line with the point) 568 | * at the current cursor position we'd otherwise fail to find this 569 | * match. 570 | * 571 | */ 572 | for (int i = 0; i <= rows ; i ++) 573 | { 574 | /* 575 | * Ensure we wrap around the buffer, rather than 576 | * walking off the end of the list of rows. 577 | */ 578 | if (offset >= rows) 579 | offset = 0; 580 | 581 | if (offset < 0) 582 | offset = rows - 1; 583 | 584 | /* 585 | * Get the current row. 586 | */ 587 | erow *row = buffer->rows.at(offset); 588 | 589 | /* 590 | * Now we need to search for the given text 591 | * in the row. 592 | * 593 | * If we're in the first row we search from the 594 | * current X-position, otherwise we search from 595 | * the start of the line (ie. offset zero). 596 | */ 597 | int x = 0; 598 | 599 | if (first) 600 | x = buffer->cx + buffer->coloff; 601 | 602 | 603 | /* 604 | * Get the text in the row. 605 | */ 606 | std::wstring row_text = row->text(x); 607 | 608 | /* 609 | * Convert to a C-string 610 | */ 611 | char *tmp = Util::widestr2ascii(row_text); 612 | std::string text(tmp); 613 | delete[]tmp; 614 | 615 | 616 | /* 617 | * regexps match on txt; 618 | */ 619 | regmatch_t result[1]; 620 | int res = regexec(®ex, text.c_str(), 1, result, 0); 621 | 622 | /* 623 | * Did we match? 624 | */ 625 | if (res == 0) 626 | { 627 | /* 628 | * The offset of the match. 629 | */ 630 | int pos = result[0].rm_so; 631 | 632 | if (first) 633 | pos += (buffer->cx + buffer->coloff); 634 | 635 | /* 636 | * Move to the right row. 637 | */ 638 | buffer->cy = 0; 639 | buffer->rowoff = offset; 640 | 641 | /* move to start of line. */ 642 | sol_lua(L); 643 | 644 | /* move right enough times to move to the start of the match. */ 645 | while (pos > 0) 646 | { 647 | e->move("right"); 648 | pos--; 649 | } 650 | 651 | /* Avoid leaking our compiled regular expression object. */ 652 | regfree(®ex); 653 | 654 | /* true-result == matched */ 655 | lua_pushboolean(L, 1); 656 | return 1; 657 | } 658 | 659 | 660 | /* 661 | * Now we're searching the next line. 662 | */ 663 | offset += step; 664 | first = false; 665 | 666 | 667 | } 668 | 669 | e->set_status(1, "No match found!"); 670 | 671 | /* Avoid leaking our compiled regular expression object. */ 672 | regfree(®ex); 673 | 674 | /* false-result == matched */ 675 | lua_pushboolean(L, 0); 676 | return 1; 677 | } 678 | 679 | /* 680 | * Get the selection. 681 | */ 682 | int selection_lua(lua_State *L) 683 | { 684 | Editor *e = Editor::instance(); 685 | std::wstring sel = e->get_selection(); 686 | 687 | char *tmp = Util::widestr2ascii(sel); 688 | lua_pushstring(L, tmp); 689 | delete [] tmp; 690 | return (1); 691 | } 692 | 693 | /* 694 | * Update the status-text 695 | */ 696 | int status_lua(lua_State *L) 697 | { 698 | const char *x = lua_tostring(L, -1); 699 | Editor *e = Editor::instance(); 700 | e->set_status(1, x); 701 | return (0); 702 | } 703 | 704 | 705 | /* 706 | * Get the text of the buffer. 707 | */ 708 | int text_lua(lua_State *L) 709 | { 710 | Editor *e = Editor::instance(); 711 | Buffer *buffer = e->current_buffer(); 712 | 713 | lua_pushstring(L, buffer->text().c_str()); 714 | return 1; 715 | } 716 | -------------------------------------------------------------------------------- /src/lua_files.cc: -------------------------------------------------------------------------------- 1 | /* lua_files.cc - Implementation of our filesystem-related lua primitives. 2 | * 3 | * ----------------------------------------------------------------------- 4 | * 5 | * Copyright (C) 2016 Steve Kemp https://steve.kemp.fi/ 6 | * 7 | * All rights reserved. 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions are 11 | * met: 12 | * 13 | * * Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * 16 | * * Redistributions in binary form must reproduce the above copyright 17 | * notice, this list of conditions and the following disclaimer in the 18 | * documentation and/or other materials provided with the distribution. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #include "lua_primitives.h" 40 | 41 | 42 | /** 43 | * Get the files in the given directory. 44 | */ 45 | int directory_entries_lua(lua_State *L) 46 | { 47 | const char *str = lua_tostring(L, 1); 48 | std::vector < std::string > result; 49 | 50 | /* 51 | * Get the entries in the given path. 52 | */ 53 | DIR *dp = opendir(str); 54 | 55 | if (dp != NULL) 56 | { 57 | result.push_back(str); 58 | dirent *de; 59 | 60 | while ((de = readdir(dp)) != NULL) 61 | { 62 | std::string r = str; 63 | r += "/"; 64 | r += de->d_name; 65 | result.push_back(r); 66 | } 67 | } 68 | 69 | closedir(dp); 70 | 71 | /* 72 | * Sort them, to be nice. 73 | */ 74 | std::sort(result.begin(), result.end()); 75 | 76 | 77 | /* 78 | * If we got results then we're good, otherwise return nil 79 | */ 80 | if (result.empty()) 81 | { 82 | lua_pushnil(L); 83 | return 1; 84 | } 85 | 86 | 87 | /* 88 | * Now put those entries into a table. 89 | */ 90 | lua_newtable(L); 91 | int i = 1; 92 | 93 | for (auto it = result.begin(); it != result.end(); ++it) 94 | { 95 | std::string value = (*it); 96 | 97 | lua_pushinteger(L, i); 98 | lua_pushstring(L, value.c_str()); 99 | 100 | lua_settable(L, -3); 101 | 102 | i += 1; 103 | } 104 | 105 | return 1; 106 | } 107 | 108 | 109 | 110 | /** 111 | * Does the named file exist? 112 | */ 113 | int exists_lua(lua_State *L) 114 | { 115 | const char *str = lua_tostring(L, 1); 116 | 117 | if (str == NULL) 118 | { 119 | lua_pushnil(L); 120 | return 1; 121 | } 122 | 123 | struct stat sb; 124 | 125 | if ((stat(str, &sb) == 0)) 126 | lua_pushboolean(L, 1); 127 | else 128 | lua_pushboolean(L, 0); 129 | 130 | return 1; 131 | } 132 | 133 | -------------------------------------------------------------------------------- /src/lua_movement.cc: -------------------------------------------------------------------------------- 1 | /* lua_movement.cc - Implementation of movement-related lua primitives. 2 | * 3 | * ----------------------------------------------------------------------- 4 | * 5 | * Copyright (C) 2016 Steve Kemp https://steve.kemp.fi/ 6 | * 7 | * All rights reserved. 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions are 11 | * met: 12 | * 13 | * * Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * 16 | * * Redistributions in binary form must reproduce the above copyright 17 | * notice, this list of conditions and the following disclaimer in the 18 | * documentation and/or other materials provided with the distribution. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include "editor.h" 38 | #include "lua_primitives.h" 39 | 40 | 41 | 42 | /** 43 | * Move to the end of the file. 44 | */ 45 | int eof_lua(lua_State *L) 46 | { 47 | (void)L; 48 | Editor *e = Editor::instance(); 49 | Buffer *buffer = e->current_buffer(); 50 | buffer->cx = buffer->coloff = 0; 51 | buffer->cy = buffer->rowoff = 0; 52 | 53 | /* 54 | * Move down until we're at the end of file. 55 | */ 56 | int max_row = buffer->rows.size(); 57 | 58 | while (max_row) 59 | { 60 | e->move("down"); 61 | max_row--; 62 | } 63 | 64 | eol_lua(L); 65 | 66 | return 0; 67 | } 68 | 69 | 70 | /** 71 | * Move to the end of the line. 72 | */ 73 | int eol_lua(lua_State *L) 74 | { 75 | (void)L; 76 | Editor *e = Editor::instance(); 77 | Buffer *buffer = e->current_buffer(); 78 | 79 | /* 80 | * Length is enough to fit. 81 | */ 82 | erow *row = buffer->rows.at(buffer->cy + buffer->rowoff); 83 | 84 | if ((int)row->chars->size() < e->width()) 85 | { 86 | buffer->coloff = 0; 87 | buffer->cx = row->chars->size() ; 88 | } 89 | else 90 | { 91 | buffer->cx = e->width() - 1; 92 | buffer->coloff = row->chars->size() - e->width() + 1; 93 | } 94 | 95 | return 0; 96 | } 97 | 98 | 99 | /** 100 | * Move the cursor in a given direction. 101 | */ 102 | int move_lua(lua_State *L) 103 | { 104 | /* 105 | * Let us start out by working out where we are, the next row, 106 | * the previous row, etc. 107 | */ 108 | Editor *e = Editor::instance(); 109 | const char *x = lua_tostring(L, -1); 110 | 111 | if (x) 112 | e->move(x); 113 | 114 | return (0); 115 | } 116 | 117 | 118 | /** 119 | * Move to the start of the buffer. 120 | */ 121 | int sof_lua(lua_State *L) 122 | { 123 | (void)L; 124 | Editor *e = Editor::instance(); 125 | Buffer *buffer = e->current_buffer(); 126 | buffer->cx = buffer->coloff = 0; 127 | buffer->cy = buffer->rowoff = 0; 128 | return 0; 129 | } 130 | 131 | 132 | /** 133 | * Move to the start of the line. 134 | */ 135 | int sol_lua(lua_State *L) 136 | { 137 | (void)L; 138 | Editor *e = Editor::instance(); 139 | Buffer *buffer = e->current_buffer(); 140 | buffer->cx = 0; 141 | buffer->coloff = 0; 142 | return 0; 143 | } 144 | -------------------------------------------------------------------------------- /src/lua_primitives.h: -------------------------------------------------------------------------------- 1 | /* lua_primitives.h - Definitions of the Lua primitives. 2 | * 3 | * ----------------------------------------------------------------------- 4 | * 5 | * Copyright (C) 2016 Steve Kemp https://steve.kemp.fi/ 6 | * 7 | * All rights reserved. 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions are 11 | * met: 12 | * 13 | * * Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * 16 | * * Redistributions in binary form must reproduce the above copyright 17 | * notice, this list of conditions and the following disclaimer in the 18 | * documentation and/or other materials provided with the distribution. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | 34 | #pragma once 35 | 36 | extern "C" { 37 | #include 38 | #include 39 | #include 40 | } 41 | 42 | 43 | 44 | /* 45 | * Movement. 46 | */ 47 | extern int eof_lua(lua_State *L); 48 | extern int eol_lua(lua_State *L); 49 | extern int move_lua(lua_State *L); 50 | extern int sof_lua(lua_State *L); 51 | extern int sol_lua(lua_State *L); 52 | 53 | 54 | /* 55 | * Core 56 | */ 57 | extern int delete_lua(lua_State *L); 58 | extern int dirty_lua(lua_State *L); 59 | extern int exit_lua(lua_State *L); 60 | extern int insert_lua(lua_State *L); 61 | extern int key_lua(lua_State *L); 62 | extern int mark_lua(lua_State *L); 63 | extern int menu_lua(lua_State *L); 64 | extern int open_lua(lua_State *L); 65 | extern int point_lua(lua_State *L); 66 | extern int prompt_lua(lua_State *L); 67 | extern int save_lua(lua_State *L); 68 | extern int search_lua(lua_State *L); 69 | extern int selection_lua(lua_State *L); 70 | extern int status_lua(lua_State *L); 71 | extern int text_lua(lua_State *L); 72 | 73 | /* 74 | * Files. 75 | */ 76 | extern int directory_entries_lua(lua_State *L); 77 | extern int exists_lua(lua_State *L); 78 | 79 | /* 80 | * Screen. 81 | */ 82 | extern int at_lua(lua_State *L); 83 | extern int height_lua(lua_State *L); 84 | extern int width_lua(lua_State *L); 85 | 86 | /* 87 | * Syntax 88 | */ 89 | extern int syntax_lua(lua_State *L); 90 | extern int update_colours_lua(lua_State *L); 91 | 92 | /* 93 | * Buffers 94 | */ 95 | extern int buffer_data_lua(lua_State *L); 96 | extern int buffer_lua(lua_State *L); 97 | extern int buffer_name_lua(lua_State *L); 98 | extern int buffers_lua(lua_State *L); 99 | extern int create_buffer_lua(lua_State *L); 100 | extern int kill_buffer_lua(lua_State *L); 101 | -------------------------------------------------------------------------------- /src/lua_screen.cc: -------------------------------------------------------------------------------- 1 | /* lua_screen.cc - Implementation of our screen-related Lua primitives. 2 | * 3 | * ----------------------------------------------------------------------- 4 | * 5 | * Copyright (C) 2016 Steve Kemp https://steve.kemp.fi/ 6 | * 7 | * All rights reserved. 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions are 11 | * met: 12 | * 13 | * * Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * 16 | * * Redistributions in binary form must reproduce the above copyright 17 | * notice, this list of conditions and the following disclaimer in the 18 | * documentation and/or other materials provided with the distribution. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include "editor.h" 37 | #include "lua_primitives.h" 38 | 39 | 40 | 41 | /** 42 | * Get the character at the cursor position. 43 | */ 44 | int at_lua(lua_State *L) 45 | { 46 | /* 47 | * Get the buffer. 48 | */ 49 | Editor *e = Editor::instance(); 50 | Buffer *buffer = e->current_buffer(); 51 | 52 | /* 53 | * Get the cursor position. 54 | */ 55 | int x = buffer->cx + buffer->coloff; 56 | int y = buffer->cy + buffer->rowoff ; 57 | 58 | /* 59 | * Default return value. 60 | */ 61 | std::wstring res; 62 | 63 | /* 64 | * Get the row. 65 | */ 66 | erow *row = nullptr; 67 | 68 | if (y < (int)buffer->rows.size()) 69 | row = buffer->rows.at(y); 70 | 71 | if (row) 72 | { 73 | int len = row->chars->size(); 74 | 75 | if (x < len) 76 | { 77 | res = row->chars->at(x); 78 | } 79 | } 80 | 81 | char *str = new char[7]; 82 | sprintf(str, "%ls", res.c_str()); 83 | lua_pushstring(L, str); 84 | delete []str; 85 | return 1; 86 | } 87 | 88 | 89 | /** 90 | * Get the height of the drawing-area - minus the two line footer. 91 | */ 92 | int height_lua(lua_State *L) 93 | { 94 | Editor *e = Editor::instance(); 95 | lua_pushnumber(L, e->height()); 96 | return 1; 97 | } 98 | 99 | 100 | /** 101 | * Get the width of the screen. 102 | */ 103 | int width_lua(lua_State *L) 104 | { 105 | Editor *e = Editor::instance(); 106 | lua_pushnumber(L, e->width()); 107 | return 1; 108 | } 109 | -------------------------------------------------------------------------------- /src/lua_syntax.cc: -------------------------------------------------------------------------------- 1 | /* lua_syntax.cc - Implementation of our syntax-related lua primitives. 2 | * 3 | * ----------------------------------------------------------------------- 4 | * 5 | * Copyright (C) 2016 Steve Kemp https://steve.kemp.fi/ 6 | * 7 | * All rights reserved. 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions are 11 | * met: 12 | * 13 | * * Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * 16 | * * Redistributions in binary form must reproduce the above copyright 17 | * notice, this list of conditions and the following disclaimer in the 18 | * documentation and/or other materials provided with the distribution. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | 34 | #include 35 | #include 36 | #include 37 | #include "editor.h" 38 | #include "lua_primitives.h" 39 | 40 | 41 | 42 | /** 43 | * Get/Set the syntax mode. 44 | */ 45 | int syntax_lua(lua_State *L) 46 | { 47 | Editor *e = Editor::instance(); 48 | Buffer *buffer = e->current_buffer(); 49 | 50 | if (lua_isstring(L, -1)) 51 | { 52 | const char *mode = lua_tostring(L, -1); 53 | buffer->m_syntax = mode; 54 | } 55 | 56 | lua_pushstring(L, buffer->m_syntax.c_str()); 57 | return 1; 58 | } 59 | 60 | 61 | 62 | 63 | /** 64 | * Update the colours of each row. 65 | */ 66 | int update_colours_lua(lua_State *L) 67 | { 68 | Editor *e = Editor::instance(); 69 | Buffer *buffer = e->current_buffer(); 70 | 71 | /* 72 | * Our string might contain "\0" so we need 73 | * to get the string length explicitly. 74 | */ 75 | size_t size; 76 | const char *buff = lua_tolstring(L, -1, &size); 77 | 78 | /* 79 | * Update the syntax - again we pass the size 80 | * to cope with embedded NULL (i.e. colour 0). 81 | */ 82 | buffer->update_syntax(buff, size); 83 | return 0; 84 | } 85 | -------------------------------------------------------------------------------- /src/main.cc: -------------------------------------------------------------------------------- 1 | /* main.cc - Main application entry-point. 2 | * 3 | * ----------------------------------------------------------------------- 4 | * 5 | * Copyright (C) 2016 Steve Kemp https://steve.kemp.fi/ 6 | * 7 | * All rights reserved. 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions are 11 | * met: 12 | * 13 | * * Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * 16 | * * Redistributions in binary form must reproduce the above copyright 17 | * notice, this list of conditions and the following disclaimer in the 18 | * documentation and/or other materials provided with the distribution. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #include "config.h" 40 | #include "editor.h" 41 | 42 | 43 | 44 | /** 45 | * Setup the curses environment, along with the colours. 46 | */ 47 | void setup() 48 | { 49 | char e[] = "ESCDELAY=0"; 50 | putenv(e); 51 | setlocale(LC_ALL, ""); 52 | initscr(); 53 | start_color(); 54 | 55 | if (has_colors()) 56 | { 57 | init_pair(0, COLOR_WHITE, COLOR_BLACK); 58 | init_pair(1, COLOR_RED, COLOR_BLACK); 59 | init_pair(2, COLOR_GREEN, COLOR_BLACK); 60 | init_pair(3, COLOR_YELLOW, COLOR_BLACK); 61 | init_pair(4, COLOR_BLUE, COLOR_BLACK); 62 | init_pair(5, COLOR_MAGENTA, COLOR_BLACK); 63 | init_pair(6, COLOR_CYAN, COLOR_BLACK); 64 | init_pair(7, COLOR_WHITE, COLOR_BLACK); 65 | 66 | /* reverse */ 67 | init_pair(8, COLOR_WHITE, COLOR_RED); 68 | init_pair(9, COLOR_WHITE, COLOR_GREEN); 69 | init_pair(10, COLOR_WHITE, COLOR_YELLOW); 70 | init_pair(11, COLOR_WHITE, COLOR_BLUE); 71 | init_pair(12, COLOR_WHITE, COLOR_MAGENTA); 72 | init_pair(13, COLOR_WHITE, COLOR_CYAN); 73 | 74 | /* We'll avoid more colours in reverse here */ 75 | for (int i = 14; i <= 255; i++) 76 | init_pair(i, i, COLOR_BLACK); 77 | } 78 | else 79 | { 80 | endwin(); 81 | fprintf(stderr, "No colours!\n"); 82 | exit(1); 83 | } 84 | 85 | raw(); 86 | keypad(stdscr, TRUE); 87 | noecho(); 88 | timeout(750); 89 | } 90 | 91 | 92 | /** 93 | * Exit from curses 94 | */ 95 | void teardown() 96 | { 97 | endwin(); 98 | } 99 | 100 | 101 | /** 102 | * Setup the console, and start the editor. 103 | */ 104 | int main(int argc, char *argv[]) 105 | { 106 | 107 | /* 108 | * Setup curses. 109 | */ 110 | setup(); 111 | 112 | /* 113 | * Create a new editor. 114 | */ 115 | Editor *e = Editor::instance(); 116 | 117 | /* 118 | * Parse command-line options. 119 | */ 120 | while (1) 121 | { 122 | static struct option long_options[] = 123 | { 124 | {"config", required_argument, 0, 'c'}, 125 | {"dump-config", no_argument, 0, 'd'}, 126 | {"syntax-path", required_argument, 0, 's'}, 127 | {"version", no_argument, 0, 'v'}, 128 | {0, 0, 0, 0} 129 | }; 130 | 131 | /* getopt_long stores the option index here. */ 132 | int option_index = 0; 133 | 134 | char c = getopt_long(argc, argv, "c:s:vd", long_options, &option_index); 135 | 136 | /* Detect the end of the options. */ 137 | if (c == -1) 138 | break; 139 | 140 | switch (c) 141 | { 142 | case 'c': 143 | e->load_lua(optarg); 144 | break; 145 | 146 | case 'd': 147 | endwin(); 148 | printf("%s\n", kilua_lua); 149 | exit(0); 150 | break; 151 | 152 | case 's': 153 | e->set_syntax_path(optarg); 154 | break; 155 | 156 | case 'v': 157 | endwin(); 158 | fprintf(stderr, "kilua v£π\n"); 159 | 160 | exit(0); 161 | break; 162 | } 163 | } 164 | 165 | 166 | 167 | 168 | /* 169 | * Load our default configuration file ~/.kilua/init.lua 170 | */ 171 | int loaded = 0; 172 | 173 | char init_buf[1024] = {'\0'}; 174 | snprintf(init_buf, sizeof(init_buf) - 1, "%s%s", 175 | getenv("HOME"), "/.kilua/init.lua"); 176 | loaded += e->load_lua(init_buf); 177 | 178 | /* 179 | * Load our default configuration file ~/.kilua/$hostname.lua 180 | */ 181 | char *hostname = e->hostname(); 182 | snprintf(init_buf, sizeof(init_buf) - 1, "%s/.kilua/%s.lua", 183 | getenv("HOME"), hostname); 184 | loaded += e->load_lua(init_buf); 185 | free(hostname); 186 | 187 | /* 188 | * If we loaded nothing use the default. 189 | */ 190 | if (loaded == 0) 191 | e->eval_lua((const char *)kilua_lua); 192 | 193 | /* 194 | * Filenames we'll load in our editor session. 195 | */ 196 | std::vector files; 197 | 198 | /* 199 | * For each file on the cmmand line - load it. 200 | */ 201 | if (argc - optind) 202 | { 203 | for (int i = 0; i < (argc - optind); i++) 204 | files.push_back(argv[optind + i]); 205 | } 206 | 207 | /* 208 | * Load the files. 209 | */ 210 | e->load_files(files); 211 | 212 | /* 213 | * Initial render. 214 | */ 215 | e->draw_screen(); 216 | 217 | /* 218 | * Run main-loop - this never terminates. 219 | */ 220 | e->main_loop(); 221 | 222 | /* 223 | * But we pretend it does, and cleanup. 224 | */ 225 | teardown(); 226 | return 0; 227 | } 228 | -------------------------------------------------------------------------------- /src/singleton.h: -------------------------------------------------------------------------------- 1 | /* singleton.h - Implementation of singled design-pattern. 2 | * 3 | * ----------------------------------------------------------------------- 4 | * 5 | * Copyright (C) 2016 Steve Kemp https://steve.kemp.fi/ 6 | * 7 | * All rights reserved. 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions are 11 | * met: 12 | * 13 | * * Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * 16 | * * Redistributions in binary form must reproduce the above copyright 17 | * notice, this list of conditions and the following disclaimer in the 18 | * documentation and/or other materials provided with the distribution. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | 34 | 35 | #pragma once 36 | 37 | 38 | /** 39 | * A template base-class implementing the common Singleton design-pattern. 40 | */ 41 | template class Singleton 42 | { 43 | public: 44 | /** 45 | * Gain access to the singleton-instance. 46 | */ 47 | static T* instance() 48 | { 49 | if (!m_instance) 50 | m_instance = new T; 51 | 52 | return m_instance; 53 | }; 54 | 55 | /** 56 | * Destroy the given singleton-instance, if it has been created. 57 | */ 58 | static void destroy_instance() 59 | { 60 | if (m_instance) 61 | { 62 | delete m_instance; 63 | m_instance = NULL; 64 | } 65 | }; 66 | 67 | private: 68 | 69 | /** 70 | * The one instance of our object. 71 | */ 72 | static T* m_instance; 73 | }; 74 | 75 | template T* Singleton::m_instance = NULL; 76 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | /* util.h - character-set related utility-functions. 2 | * 3 | * ----------------------------------------------------------------------- 4 | * 5 | * Copyright (C) 2016 Steve Kemp https://steve.kemp.fi/ 6 | * 7 | * All rights reserved. 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions are 11 | * met: 12 | * 13 | * * Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * 16 | * * Redistributions in binary form must reproduce the above copyright 17 | * notice, this list of conditions and the following disclaimer in the 18 | * documentation and/or other materials provided with the distribution. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | #pragma once 34 | 35 | 36 | 37 | /** 38 | * A utility class for converting strings and characters. 39 | * 40 | * We assume that a single ASCII character can become a wide 41 | * character of no more than 6 bytes. 42 | */ 43 | class Util 44 | { 45 | public: 46 | static const int max_len = 6; 47 | 48 | 49 | /** 50 | * Convert the single wide-character specified to 51 | * an ASCII string. 52 | * 53 | * NOTE: The caller must `delete[]` the result. 54 | */ 55 | static char * wchar2ascii(wchar_t ch) 56 | { 57 | std::wstring tmp; 58 | tmp += ch; 59 | 60 | char *str = new char[Util::max_len]; 61 | sprintf(str, "%ls", tmp.c_str()); 62 | 63 | return (str); 64 | } 65 | 66 | 67 | /** 68 | * Convert a normal string to a wide string. 69 | * 70 | * NOTE: The caller must `delete[]` the result. 71 | */ 72 | static wchar_t * ascii2wide(const char *in) 73 | { 74 | size_t in_size = strlen(in) + 1; 75 | size_t out_size = in_size * Util::max_len; 76 | 77 | wchar_t* result = new wchar_t[out_size]; 78 | 79 | mbstowcs(result, in, out_size); 80 | return (result); 81 | }; 82 | 83 | 84 | /** 85 | * Convert the wide-character string specified to 86 | * an ASCII string. 87 | * 88 | * NOTE: The caller must `delete[]` the result. 89 | */ 90 | static char * wide2ascii(wchar_t *in) 91 | { 92 | std::wstring tmp; 93 | tmp += in; 94 | 95 | char *str = new char[(tmp.size() * Util::max_len) + 1]; 96 | sprintf(str, "%ls", in); 97 | 98 | return (str); 99 | }; 100 | 101 | static char * widestr2ascii(std::wstring in) 102 | { 103 | size_t len = in.size() * 5 + 1; 104 | char *str = new char[len + 1]; 105 | sprintf(str, "%ls", in.c_str()); 106 | return (str); 107 | }; 108 | 109 | }; 110 | -------------------------------------------------------------------------------- /syntax/README.md: -------------------------------------------------------------------------------- 1 | 2 | Syntax Highlighters 3 | ------------------- 4 | 5 | These use `lpeg`. 6 | 7 | A buffer has an associated syntax-mode, if a mode is set then 8 | the module `${mode}.lua` will be loaded, and the call-back function 9 | `on_syntax_highlight(text)` will be called. 10 | 11 | 12 | ## on_syntax_hightlight 13 | 14 | This function is expected to return a **string**, which will contain 15 | one character for each byte of the input. 16 | 17 | Given the input string "foo" the output string should encode the 18 | colour to use for the given offset. For example: 19 | 20 | on_syntax_highlight( "foo" ) -> "888" 21 | -> Draw each character in white. 22 | 23 | on_syntax_highlight( "stevekemp" ) -> "123456788" 24 | -> Draw each charcter in a different colour. 25 | 26 | 27 | **TODO**: 28 | 29 | * There are eight colours. 30 | * Write more documentation. 31 | -------------------------------------------------------------------------------- /syntax/cc.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Syntax Highlighting for C/C++ 3 | -- 4 | 5 | 6 | 7 | -- 8 | -- Require our helper-library 9 | -- 10 | local lpeg_utils = require( "lpeg_utils" ) 11 | 12 | 13 | -- 14 | -- This file is a Lua module. 15 | -- 16 | local mymodule = {} 17 | 18 | -- 19 | -- The result we return to the caller. 20 | -- 21 | local retval = "" 22 | 23 | -- 24 | -- Helper to add the colour. 25 | -- 26 | function add( colour, str ) 27 | length = string.len(str) 28 | while( length >0 ) do 29 | retval = retval .. string.char( colour ) 30 | length = length -1 31 | end 32 | end 33 | 34 | -- 35 | -- Shorten our references. 36 | -- 37 | local P = lpeg.P 38 | local R = lpeg.R 39 | local S = lpeg.S 40 | local C = lpeg.C 41 | 42 | 43 | 44 | -- 45 | -- Numbers 46 | -- 47 | local numbers = lpeg_utils.numbers() / function(...) add(YELLOW, ... ) end 48 | 49 | 50 | -- 51 | -- Character-strings 52 | -- 53 | local charlit = P'L'^-1 * P"'" * (P'\\' * P(1) + (1 - S"\\'"))^1 * P"'" 54 | local stringlit = P'L'^-1 * P'"' * (P'\\' * P(1) + (1 - S'\\"'))^0 * P'"' 55 | local strings = (charlit + stringlit) / function(...) add(BLUE, ... ) end 56 | 57 | 58 | -- 59 | -- Single and multi-line comments. 60 | -- 61 | local ccomment = P'/*' * (1 - P'*/')^0 * P'*/' 62 | local newcomment = P'//' * (1 - P'\n')^0 63 | local comment = (ccomment + newcomment) / function(...) add(RED, ... ) end 64 | 65 | -- 66 | -- Show trailing-whitespace with a `cyan` background. 67 | -- 68 | local trailing_space = S' \t'^1 * S'\n'/ function(...) add(REV_CYAN,... ) end 69 | 70 | -- 71 | -- Literals 72 | -- 73 | local literal = ( charlit + stringlit) / function(...) add(BLUE, ... ) end 74 | 75 | -- 76 | -- Keywords 77 | -- 78 | local keyword = lpeg_utils.tokens({ 79 | "auto", 80 | "bool", 81 | "break", 82 | "case", 83 | "char", 84 | "const", 85 | "continue", 86 | "default", 87 | "do", 88 | "double", 89 | "else", 90 | "enum", 91 | "extern", 92 | "float", 93 | "for", 94 | "goto", 95 | "if", 96 | "inline", 97 | "int", 98 | "long", 99 | "register", 100 | "restrict", 101 | "return", 102 | "short", 103 | "signed", 104 | "sizeof", 105 | "static", 106 | "struct", 107 | "switch", 108 | "typedef", 109 | "union", 110 | "unsigned", 111 | "void", 112 | "volatile", 113 | "while" 114 | } ) / function(...) add(CYAN, ... ) end 115 | 116 | -- 117 | -- Functions : 118 | -- TODO: Add more 119 | -- 120 | local functions = lpeg_utils.tokens({ 121 | "stderr", 122 | "stdout", 123 | "printf", 124 | "strlen", 125 | "wcslen", 126 | "malloc", 127 | "free", 128 | "delete", 129 | "new", 130 | "fprintf", 131 | "vsnprintf", 132 | "strcpy", 133 | "strncpy", 134 | "sprintf", 135 | "getenv", 136 | "ioctl" 137 | } ) / function(...) add(GREEN, ... ) end 138 | 139 | 140 | -- 141 | -- Match any single character 142 | -- 143 | local any = C(P(1) )/ function(...) add(WHITE,... ) end 144 | 145 | 146 | 147 | -- 148 | -- The complete set of tokens we understand 149 | -- 150 | local tokens = (comment + functions + keyword + numbers + strings + trailing_space + any)^0 151 | 152 | 153 | -- 154 | -- The function we export. 155 | -- 156 | function mymodule.parse(input) 157 | retval = "" 158 | lpeg.match(tokens, input) 159 | return( retval ) 160 | end 161 | 162 | -- 163 | -- Export ourself 164 | -- 165 | return mymodule 166 | -------------------------------------------------------------------------------- /syntax/email.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Trivial highlighting for emails 3 | -- 4 | -- This does not use LPEG, instead it parses line-by-line, as this 5 | -- is simpler. 6 | -- 7 | 8 | 9 | 10 | -- 11 | -- This file is a Lua module. 12 | -- 13 | local mymodule = {} 14 | 15 | 16 | -- 17 | -- Given a string split it into a table of lines, by the newline-character. 18 | -- 19 | function string.to_table(str) 20 | local t = {} 21 | local function helper(line) table.insert(t, line) return "" end 22 | helper((str:gsub("(.-)\r?\n", helper))) 23 | return t 24 | end 25 | 26 | 27 | -- 28 | -- Does the string start with the given character-string? 29 | -- 30 | function string.starts(String,Start) 31 | return string.sub(String,1,string.len(Start))==Start 32 | end 33 | 34 | 35 | 36 | -- 37 | -- The function we export. 38 | -- 39 | function mymodule.parse(input) 40 | local ret = "" 41 | 42 | -- 43 | -- Split the input by newlines. 44 | -- 45 | tmp = string.to_table( input ) 46 | 47 | -- 48 | -- We default to being in the message-header, not the 49 | -- message signature (which might not even be present). 50 | -- 51 | local header = true 52 | local sig = false 53 | 54 | -- 55 | -- Iterate over every line. 56 | -- 57 | for i,l in ipairs(tmp) do 58 | 59 | -- 60 | -- If we're in the header then we're blue 61 | -- 62 | if ( header ) then 63 | 64 | len = #l 65 | while( len > 0 ) do 66 | ret = ret .. BLUE 67 | len = len - 1 68 | end 69 | 70 | -- 71 | -- Is this the end of a header? 72 | -- 73 | if ( l == "" ) then 74 | header = false 75 | end 76 | else 77 | 78 | -- 79 | -- If we're in the signature we're yellow 80 | -- 81 | if ( sig ) then 82 | 83 | len = #l 84 | while( len > 0 ) do 85 | ret = ret .. YELLOW 86 | len = len - 1 87 | end 88 | else 89 | 90 | -- 91 | -- Otherwise we're in the body, and we default to 92 | -- white, unless we're seeing quoted-lines 93 | -- 94 | 95 | -- 96 | -- Default colour for body. 97 | -- 98 | local colour = WHITE 99 | 100 | -- 101 | -- Quotes 102 | -- 103 | if ( string.starts( l, ">" ) ) then colour = RED end 104 | if ( string.starts( l, "> " ) ) then colour = RED end 105 | if ( string.starts( l, "> > " ) ) then colour = GREEN end 106 | if ( string.starts( l, ">> " ) ) then colour = GREEN end 107 | 108 | -- 109 | -- Body 110 | -- 111 | len = #l 112 | while( len > 0 ) do 113 | ret = ret .. string.char(colour) 114 | len = len - 1 115 | end 116 | 117 | -- 118 | -- Are we at the signature? 119 | -- 120 | if ( l == "--" ) or ( l == "-- " ) then 121 | sig = true 122 | end 123 | end 124 | end 125 | 126 | -- 127 | -- Newline 128 | -- 129 | ret = ret .. WHITE 130 | end 131 | 132 | return(tostring(ret)) 133 | end 134 | 135 | -- 136 | -- Export ourself 137 | -- 138 | return mymodule 139 | -------------------------------------------------------------------------------- /syntax/go.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Syntax Highlighting for go 3 | -- 4 | 5 | 6 | 7 | -- 8 | -- Require our helper-library 9 | -- 10 | local lpeg_utils = require( "lpeg_utils" ) 11 | 12 | 13 | -- 14 | -- This file is a Lua module. 15 | -- 16 | local mymodule = {} 17 | 18 | -- 19 | -- The result we return to the caller. 20 | -- 21 | local retval = "" 22 | 23 | -- 24 | -- Helper to add the colour. 25 | -- 26 | function add( colour, str ) 27 | length = string.len(str) 28 | while( length >0 ) do 29 | retval = retval .. string.char( colour ) 30 | length = length -1 31 | end 32 | end 33 | 34 | -- 35 | -- Shorten our references. 36 | -- 37 | local P = lpeg.P 38 | local R = lpeg.R 39 | local S = lpeg.S 40 | local C = lpeg.C 41 | 42 | 43 | 44 | -- 45 | -- Numbers 46 | -- 47 | local numbers = lpeg_utils.numbers() / function(...) add(YELLOW, ... ) end 48 | 49 | 50 | -- 51 | -- Character-strings 52 | -- 53 | local charlit = P'L'^-1 * P"'" * (P'\\' * P(1) + (1 - S"\\'"))^1 * P"'" 54 | local stringlit = P'L'^-1 * P'"' * (P'\\' * P(1) + (1 - S'\\"'))^0 * P'"' 55 | local strings = (charlit + stringlit) / function(...) add(BLUE, ... ) end 56 | 57 | 58 | -- 59 | -- Single and multi-line comments. 60 | -- 61 | local ccomment = P'/*' * (1 - P'*/')^0 * P'*/' 62 | local newcomment = P'//' * (1 - P'\n')^0 63 | local comment = (ccomment + newcomment) / function(...) add(RED, ... ) end 64 | 65 | -- 66 | -- Show trailing-whitespace with a `cyan` background. 67 | -- 68 | local trailing_space = S' \t'^1 * S'\n'/ function(...) add(REV_CYAN,... ) end 69 | 70 | -- 71 | -- Literals 72 | -- 73 | local literal = ( charlit + stringlit) / function(...) add(BLUE, ... ) end 74 | 75 | -- 76 | -- Keywords 77 | -- 78 | local keyword = lpeg_utils.tokens({ 79 | 80 | -- real keywords 81 | 'break', 82 | 'case', 83 | 'chan', 84 | 'const', 85 | 'continue', 86 | 'default', 87 | 'defer', 88 | 'else', 89 | 'fallthrough', 90 | 'for', 91 | 'func', 92 | 'go', 93 | 'goto', 94 | 'if', 95 | 'import', 96 | 'interface', 97 | 'map', 98 | 'package', 99 | 'range', 100 | 'return', 101 | 'select', 102 | 'struct', 103 | 'switch', 104 | 'type', 105 | 'var', 106 | 107 | -- types 108 | 'bool', 109 | 'byte', 110 | 'complex64', 111 | 'complex128', 112 | 'error', 113 | 'float32', 114 | 'float64', 115 | 'int', 116 | 'int8', 117 | 'int16', 118 | 'int32', 119 | 'int64', 120 | 'rune', 121 | 'string', 122 | 'uint', 123 | 'uint8', 124 | 'uint16', 125 | 'uint32', 126 | 'uint64', 127 | 'uintptr', 128 | } ) / function(...) add(CYAN, ... ) end 129 | 130 | -- 131 | -- Functions : 132 | -- 133 | local functions = lpeg_utils.tokens({ 134 | 135 | -- misc 136 | 'true', 137 | 'false', 138 | 'iota', 139 | 'nil', 140 | 141 | -- real functions 142 | 'append', 143 | 'cap', 144 | 'close', 145 | 'complex', 146 | 'copy', 147 | 'delete', 148 | 'imag', 149 | 'len', 150 | 'make', 151 | 'new', 152 | 'panic', 153 | 'print', 154 | 'println', 155 | 'real', 156 | 'recover' 157 | } ) / function(...) add(GREEN, ... ) end 158 | 159 | 160 | -- 161 | -- Match any single character 162 | -- 163 | local any = C(P(1) )/ function(...) add(WHITE,... ) end 164 | 165 | 166 | 167 | -- 168 | -- The complete set of tokens we understand 169 | -- 170 | local tokens = (comment + functions + keyword + numbers + strings + trailing_space + any)^0 171 | 172 | 173 | -- 174 | -- The function we export. 175 | -- 176 | function mymodule.parse(input) 177 | retval = "" 178 | lpeg.match(tokens, input) 179 | return( retval ) 180 | end 181 | 182 | -- 183 | -- Export ourself 184 | -- 185 | return mymodule 186 | -------------------------------------------------------------------------------- /syntax/html.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Syntax Highlighting for HTML 3 | -- 4 | 5 | 6 | -- 7 | -- This file is a Lua module. 8 | -- 9 | local mymodule = {} 10 | 11 | -- 12 | -- The result we return to the caller. 13 | -- 14 | local retval = "" 15 | 16 | 17 | -- 18 | -- Helper to add the colouring. 19 | -- 20 | function add( colour, str ) 21 | length = string.len(str) 22 | while( length >0 ) do 23 | retval = retval .. string.char( colour ) 24 | length = length -1 25 | end 26 | end 27 | 28 | -- 29 | -- Shorten our references. 30 | -- 31 | local P = lpeg.P 32 | local R = lpeg.R 33 | local S = lpeg.S 34 | local C = lpeg.C 35 | 36 | 37 | -- 38 | -- Character-strings 39 | -- 40 | local string = P'"' * (P'\\' * P(1) + (1 - S'\\"'))^0 * P'"' 41 | local strings = (string) / function(...) add(CYAN, ... ) end 42 | 43 | -- 44 | -- Comments. 45 | -- 46 | local html_comment = P'')^0 * P'-->' 47 | local comments = (html_comment) / function(...) add(YELLOW, ... ) end 48 | 49 | -- 50 | -- Show trailing-whitespace with a `cyan` background. 51 | -- 52 | local trailing_space = S' \t'^1 * S'\n'/ function(...) add(REV_CYAN,... ) end 53 | 54 | -- 55 | -- Tags 56 | -- 57 | local tagged = P'<' * (1 - P'>')^0 * P'>' 58 | local tags = (tagged) / function(...) add(BLUE, ... ) end 59 | 60 | -- 61 | -- Match any single character 62 | -- 63 | local any = C(P(1) )/ function(...) add(WHITE,... ) end 64 | 65 | 66 | -- 67 | -- The complete set of tokens we understand 68 | -- 69 | local tokens = (strings + comments + trailing_space + tags + any)^0 70 | 71 | -- 72 | -- The function we export. 73 | -- 74 | function mymodule.parse(input) 75 | retval = "" 76 | lpeg.match(tokens, input) 77 | return(retval) 78 | end 79 | 80 | -- 81 | -- Export ourself 82 | -- 83 | return mymodule 84 | -------------------------------------------------------------------------------- /syntax/ini.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Syntax Highlighting for INI files. 3 | -- 4 | -- We only handle three cases 5 | -- 6 | -- 1. Section is `[blah]` and is red. 7 | -- 8 | -- 2. Key-name is "foo = " and is blue 9 | -- 10 | -- 3. All other characters (i.e. key value) are white. 11 | -- 12 | 13 | 14 | -- 15 | -- This file is a Lua module. 16 | -- 17 | local mymodule = {} 18 | 19 | -- 20 | -- The result we return to the caller. 21 | -- 22 | local retval = "" 23 | 24 | 25 | -- 26 | -- Helper to add the colouring. 27 | -- 28 | function add( colour, str ) 29 | length = string.len(str) 30 | while( length >0 ) do 31 | retval = retval .. string.char( colour ) 32 | length = length -1 33 | end 34 | end 35 | 36 | -- 37 | -- Shorten our references. 38 | -- 39 | local P = lpeg.P 40 | local R = lpeg.R 41 | local S = lpeg.S 42 | local C = lpeg.C 43 | 44 | 45 | -- 46 | -- Sections. 47 | -- 48 | local sect = P'[' * (1 - P']')^0 * P']' 49 | local section = (sect) / function(...) add(RED, ... ) end 50 | 51 | -- 52 | -- Key = 53 | -- 54 | local key_name = R"09" + R("az", "AZ") + S(' \t') 55 | local key_rule = key_name^1 * P'='^1 56 | local key_match = (key_rule) / function(...) add(BLUE, ... ) end 57 | 58 | -- 59 | -- Show trailing-whitespace with a `cyan` background. 60 | -- 61 | local trailing_space = S' \t'^1 * S'\n'/ function(...) add(REV_CYAN,... ) end 62 | 63 | -- 64 | -- Match any single character: Which should be the value of the key. 65 | -- 66 | local any = C(P(1) )/ function(...) add(WHITE,... ) end 67 | 68 | 69 | -- 70 | -- The complete set of tokens we understand 71 | -- 72 | local tokens = (section + key_match + trailing_space + any)^0 73 | 74 | -- 75 | -- The function we export. 76 | -- 77 | function mymodule.parse(input) 78 | retval = "" 79 | lpeg.match(tokens, input) 80 | return(retval) 81 | end 82 | 83 | -- 84 | -- Export ourself 85 | -- 86 | return mymodule 87 | -------------------------------------------------------------------------------- /syntax/lisp.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- This is a simple "syntax highlighter" - it just highlights 3 | -- parenthesis. 4 | -- 5 | -- It is designed to demonstrate how you don't need to use LPEG 6 | -- if you don't want 7 | -- 8 | 9 | 10 | -- 11 | -- This file is a Lua module. 12 | -- 13 | local mymodule = {} 14 | 15 | 16 | -- 17 | -- The function we export. 18 | -- 19 | function mymodule.parse(input) 20 | local ret = "" 21 | 22 | for letter in input:gmatch(".") do 23 | if ( letter == '(' or letter == ')' ) then 24 | ret = ret .. string.char( CYAN ) 25 | else 26 | ret = ret .. string.char( WHITE ) 27 | end 28 | end 29 | return(tostring(ret)) 30 | end 31 | 32 | -- 33 | -- Export ourself 34 | -- 35 | return mymodule 36 | -------------------------------------------------------------------------------- /syntax/lpeg_utils.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- This is a helper-library for LPEG-based parsing. 3 | -- 4 | 5 | 6 | -- 7 | -- This file is a Lua module. 8 | -- 9 | local mymodule = {} 10 | 11 | 12 | 13 | -- 14 | -- Allow matching keywords 15 | -- 16 | function mymodule.tokens(words) 17 | -- return value. 18 | local tmp = nil 19 | 20 | -- keywords are only a-z 21 | local idchars = lpeg.R("az") 22 | 23 | -- 24 | -- Make a pattern that will match the given keyword 25 | -- 26 | -- This is designed to match only on boundaries, but I suspect 27 | -- it is a little broken. 28 | -- 29 | -- Reference 30 | -- http://stackoverflow.com/questions/38690698/using-lpeg-to-only-capture-on-word-boundaries/38767940#38767940 31 | -- 32 | local function make_token(t) 33 | local nosuffix = ( lpeg.P(t) * -idchars ) 34 | local noprefix = ( lpeg.B( 1 -idchars ) * ( lpeg.P(t) * -idchars ) ) 35 | return( lpeg.C( noprefix * nosuffix^0 ) ) 36 | end 37 | 38 | -- 39 | -- For each kewyword .. append. 40 | -- 41 | for i,o in ipairs(words) do 42 | if ( tmp ) then 43 | tmp = tmp + make_token(o) 44 | else 45 | tmp = make_token(o) 46 | end 47 | end 48 | return( tmp ) 49 | end 50 | 51 | 52 | -- 53 | -- Helper function which returns something that matches "all the numbers" 54 | -- 55 | function mymodule.numbers() 56 | local P = lpeg.P 57 | local R = lpeg.R 58 | local S = lpeg.S 59 | local C = lpeg.C 60 | 61 | local digit = R'09' 62 | local letter = R('az', 'AZ') + P'_' 63 | local alphanum = letter + digit 64 | local hex = R('af', 'AF', '09') 65 | local exp = S'eE' * S'+-'^-1 * digit^1 66 | local fs = S'fFlL' 67 | local is = S'uUlL'^0 68 | 69 | local hexnum = P'0' * S'xX' * hex^1 * is^-1 70 | local octnum = P'0' * digit^1 * is^-1 71 | local decnum = digit^1 * is^-1 72 | local floatnum = digit^1 * exp * fs^-1 + 73 | digit^0 * P'.' * digit^1 * exp^-1 * fs^-1 + 74 | digit^1 * P'.' * digit^0 * exp^-1 * fs^-1 75 | 76 | return( hexnum + octnum + floatnum + decnum) 77 | end 78 | 79 | -- 80 | -- Export ourself 81 | -- 82 | return mymodule 83 | -------------------------------------------------------------------------------- /syntax/lua.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Syntax Highlighting for Lua 3 | -- 4 | 5 | 6 | -- 7 | -- Require our helper-library 8 | -- 9 | local lpeg_utils = require( "lpeg_utils" ) 10 | 11 | 12 | -- 13 | -- This file is a Lua module. 14 | -- 15 | local mymodule = {} 16 | 17 | -- 18 | -- The result we return to the caller. 19 | -- 20 | local retval = "" 21 | 22 | 23 | -- 24 | -- Helper to add the colouring. 25 | -- 26 | function add( colour, str ) 27 | length = string.len(str) 28 | while( length >0 ) do 29 | retval = retval .. string.char( colour ) 30 | length = length -1 31 | end 32 | end 33 | 34 | -- 35 | -- Shorten our references. 36 | -- 37 | local P = lpeg.P 38 | local R = lpeg.R 39 | local S = lpeg.S 40 | local C = lpeg.C 41 | 42 | 43 | -- 44 | -- Numbers 45 | -- 46 | local numbers = lpeg_utils.numbers() / function(...) add(YELLOW, ... ) end 47 | 48 | -- 49 | -- Character-strings 50 | -- 51 | local charlit = P'L'^-1 * P"'" * (P'\\' * P(1) + (1 - S"\\'"))^1 * P"'" 52 | local stringlit = P'L'^-1 * P'"' * (P'\\' * P(1) + (1 - S'\\"'))^0 * P'"' 53 | local strings = (charlit + stringlit) / function(...) add(BLUE, ... ) end 54 | 55 | -- 56 | -- Single & multi-line comments. 57 | -- 58 | local ccomment = P'--[[' * (1 - P'--]]')^0 * P'--]]' 59 | local newcomment = P'--' * (1 - P'\n')^0 60 | local comment = (ccomment + newcomment) / function(...) add(RED, ... ) end 61 | 62 | -- 63 | -- Show trailing-whitespace with a `cyan` background. 64 | -- 65 | local trailing_space = S' \t'^1 * S'\n'/ function(...) add(REV_CYAN,... ) end 66 | 67 | -- 68 | -- Keywords 69 | -- 70 | local keyword = lpeg_utils.tokens({ 71 | "and", 72 | "break", 73 | "do", 74 | "else", 75 | "elsif", 76 | "end", 77 | "false", 78 | "for", 79 | "function", 80 | "goto", 81 | "if", 82 | "in", 83 | "local", 84 | "nil", 85 | "not", 86 | "or", 87 | "repeat", 88 | "return", 89 | "then", 90 | "true", 91 | "until", 92 | "while" 93 | }) / function(...) add(CYAN, ... ) end 94 | 95 | 96 | -- 97 | -- Functions from the standard-library 98 | -- 99 | local func = { 100 | "assert", 101 | "ipairs", 102 | "load", 103 | "pairs", 104 | "print", 105 | "require", 106 | "tonumber", 107 | "tostring", 108 | "type" 109 | } 110 | 111 | -- 112 | -- Add the functions from the standard-packages to our function-table 113 | -- 114 | local packages = { 115 | "io", 116 | "math", 117 | "os", 118 | "string", 119 | "table" 120 | } 121 | for i,n in pairs(packages) do 122 | for k,v in pairs(_G[n]) do 123 | table.insert( func, n .. "." .. k) 124 | end 125 | end 126 | 127 | -- 128 | -- Now highlight the functions 129 | -- 130 | local functions = lpeg_utils.tokens(func) / function(...) add(GREEN, ... ) end 131 | 132 | 133 | -- 134 | -- Match any single character 135 | -- 136 | local any = C(P(1) )/ function(...) add(WHITE,... ) end 137 | 138 | 139 | -- 140 | -- The complete set of tokens we understand 141 | -- 142 | local tokens = (comment + keyword + functions + strings + numbers + trailing_space + any)^0 143 | 144 | -- 145 | -- The function we export. 146 | -- 147 | function mymodule.parse(input) 148 | retval = "" 149 | lpeg.match(tokens, input) 150 | return(retval) 151 | end 152 | 153 | -- 154 | -- Export ourself 155 | -- 156 | return mymodule 157 | -------------------------------------------------------------------------------- /syntax/makefile.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Trivial highlighting for Makefiles. 3 | -- 4 | 5 | 6 | 7 | -- 8 | -- This file is a Lua module. 9 | -- 10 | local mymodule = {} 11 | 12 | 13 | -- 14 | -- Given a string split it into a table of lines, by the newline-character. 15 | -- 16 | function string.to_table(str) 17 | local t = {} 18 | local function helper(line) table.insert(t, line) return "" end 19 | helper((str:gsub("(.-)\r?\n", helper))) 20 | return t 21 | end 22 | 23 | 24 | -- 25 | -- Does the string start with the given character-string? 26 | -- 27 | function string.starts(String,Start) 28 | return string.sub(String,1,string.len(Start))==Start 29 | end 30 | 31 | 32 | 33 | -- 34 | -- The function we export. 35 | -- 36 | function mymodule.parse(input) 37 | local ret = "" 38 | 39 | -- 40 | -- Split the input by newlines. 41 | -- 42 | tmp = string.to_table( input ) 43 | 44 | -- 45 | -- Iterate over every line. 46 | -- 47 | for i,l in ipairs(tmp) do 48 | 49 | -- 50 | -- Default colour 51 | -- 52 | local colour = WHITE 53 | 54 | -- Starts with a comment? 55 | if (string.starts( l, "#" ) ) then 56 | colour = RED 57 | else 58 | -- Starts with a TAB? 59 | if (string.starts( l, '\t' ) ) then 60 | colour = BLUE 61 | else 62 | -- Is a rule ? 63 | if ( string.find(l, ":" ) ) then 64 | colour = YELLOW 65 | end 66 | end 67 | end 68 | 69 | -- 70 | -- For each character in the line, set the colour. 71 | -- 72 | len = #l 73 | while( len > 0 ) do 74 | ret = ret .. string.char( colour ) 75 | len = len - 1 76 | end 77 | 78 | -- 79 | -- Newline 80 | -- 81 | ret = ret .. WHITE 82 | end 83 | 84 | return(tostring(ret)) 85 | end 86 | 87 | -- 88 | -- Export ourself 89 | -- 90 | return mymodule 91 | -------------------------------------------------------------------------------- /syntax/markdown.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Syntax Highlighting for markdown / text. 3 | -- 4 | -- This mode only does two things: 5 | -- 6 | -- * Highlights URLS. 7 | -- 8 | -- * Highlights trailing whitespace. 9 | -- 10 | -- Steve 11 | -- 12 | -- 13 | 14 | -- 15 | -- This file is a Lua module. 16 | -- 17 | local mymodule = {} 18 | 19 | -- 20 | -- The string we return. 21 | -- 22 | local retval = "" 23 | 24 | -- 25 | -- Helper to add the colour. 26 | -- 27 | function add( colour, str ) 28 | length = string.len(str) 29 | while( length >0 ) do 30 | retval = retval .. string.char( colour ) 31 | length = length -1 32 | end 33 | end 34 | 35 | local P = lpeg.P 36 | local R = lpeg.R 37 | local S = lpeg.S 38 | local C = lpeg.C 39 | 40 | 41 | -- Terminating characters for an URL. 42 | local term = S']> \n' 43 | 44 | -- The two types of links we show. 45 | local http = P('http://') * (1 -term)^0/ function(...) add(RED,...)end 46 | local https = P('https://') * (1 -term)^0/ function(...) add(BLUE,...)end 47 | 48 | -- Show trailing-whitespace with a `cyan` background. 49 | local trailing_space = S' \t'^1 * S'\n'/ function(...) add(REV_CYAN,... ) end 50 | 51 | -- Any character - allows continuation. 52 | local any = C(P(1) )/ function(...) add(WHITE,... ) end 53 | 54 | -- We support links and "any"thing else. 55 | local tokens = (http + https + trailing_space + any )^0 56 | 57 | -- 58 | -- The function we export. 59 | -- 60 | function mymodule.parse(input) 61 | retval = "" 62 | lpeg.match(tokens, input) 63 | return( retval ) 64 | end 65 | 66 | -- 67 | -- Export ourself 68 | -- 69 | return mymodule 70 | -------------------------------------------------------------------------------- /util/xxd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | # 3 | # xxdi.pl - perl implementation of 'xxd -i' mode 4 | # 5 | # Copyright 2013 Greg Kroah-Hartman 6 | # Copyright 2013 Linux Foundation 7 | # 8 | # Released under the GPLv2. 9 | # 10 | # Implements the "basic" functionality of 'xxd -i' in perl to keep build 11 | # systems from having to build/install/rely on vim-core, which not all 12 | # distros want to do. But everyone has perl, so use it instead. 13 | 14 | use strict; 15 | use warnings; 16 | 17 | my $indata = slurp( @ARGV ? $ARGV[0] : \*STDIN ); 18 | my $len_data = length($indata); 19 | my $num_digits_per_line = 12; 20 | my $var_name; 21 | my $outdata; 22 | 23 | # Use the variable name of the file we read from, converting '/' and '. 24 | # to '_', or, if this is stdin, just use "stdin" as the name. 25 | if (@ARGV) 26 | { 27 | $var_name = $ARGV[0]; 28 | $var_name =~ s/\//_/g; 29 | $var_name =~ s/\./_/g; 30 | } 31 | else 32 | { 33 | $var_name = "stdin"; 34 | } 35 | 36 | $outdata .= "unsigned char $var_name\[] = {"; 37 | 38 | # trailing ',' is acceptable, so instead of duplicating the logic for 39 | # just the last character, live with the extra ','. 40 | for ( my $key = 0 ; $key < $len_data ; $key++ ) 41 | { 42 | if ( $key % $num_digits_per_line == 0 ) 43 | { 44 | $outdata .= "\n\t"; 45 | } 46 | $outdata .= sprintf( "0x%.2x, ", ord( substr( $indata, $key, 1 ) ) ); 47 | } 48 | 49 | $outdata .= sprintf( "0x00, "); 50 | $outdata .= "\n};\nunsigned int $var_name\_len = $len_data;\n"; 51 | 52 | binmode STDOUT; 53 | print { *STDOUT } $outdata; 54 | 55 | 56 | sub slurp 57 | { 58 | my ($file) = (@_); 59 | 60 | my $txt = ""; 61 | 62 | open( my $handle, "<", $file ) or 63 | die "Failed to open $file - $!"; 64 | 65 | while ( my $line = <$handle> ) 66 | { 67 | $txt .= $line; 68 | } 69 | close($handle); 70 | return ($txt); 71 | } 72 | --------------------------------------------------------------------------------